Skip to content

fix(beta): restore custom contact and avatar behavior - #147

Merged
ifBars merged 1 commit into
betafrom
agent/beta-contact-avatar-fixes
Jul 27, 2026
Merged

fix(beta): restore custom contact and avatar behavior#147
ifBars merged 1 commit into
betafrom
agent/beta-contact-avatar-fixes

Conversation

@ifBars

@ifBars ifBars commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

  • Include custom supplier NPCs in the Contacts app relationship display.
  • Preserve avatar-equippable presentation for non-viewmodel equippables with a shared runtime prefab cache.

Validation

  • dotnet build S1API.sln -c MonoMelon --no-restore -p:AutomateLocalDeployment=false
  • dotnet test S1API.Tests/S1API.Tests.csproj -c MonoMelon --no-restore --no-build --filter FullyQualifiedName~ContactsAppRoleTests
  • dotnet build S1API.sln -c Il2CppMelon --no-restore -p:AutomateLocalDeployment=false
  • dotnet test S1API.Tests/S1API.Tests.csproj -c Il2CppMelon --no-restore --no-build --filter FullyQualifiedName~ContactsAppRoleTests

Compatibility

No public or protected signatures, durable IDs, save formats, or network payload shapes changed. Existing viewmodel avatar behavior is preserved; non-viewmodel equippables now receive the configured avatar presentation.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6dc2a35f-4c16-4cc0-b799-1c150d12d463

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 27, 2026

Copy link
Copy Markdown

Greptile Summary

This PR restores two behaviors that were dropped on the beta branch: custom supplier NPCs now appear in the Contacts app relationship graph, and non-viewmodel equippables built with EquippableBuilder now trigger the correct third-person avatar animation via a new Harmony postfix.

  • ContactsApp suppliers: AddRelationCircles previously filtered only customer and dealer custom NPCs; the new IsContactRole helper adds the supplier check, backed by a test that covers all three role types.
  • Non-viewmodel avatar dispatch: EquippableAvatarPatches patches Equippable.Equip/Unequip to call SendEquippable_Networked for any non-viewmodel equippable that carries an AvatarEquippable child component; viewmodels are explicitly excluded and continue to use the native lifecycle.
  • Shared prefab lifecycle: RuntimePrefabCache replaces the previous DontDestroyOnLoad + SetActive(false) pattern, keeping activeSelf true on prefab templates while an inactive parent root prevents their MonoBehaviour ticks.

Confidence Score: 4/5

Safe to merge; all changes are additive and the existing viewmodel path is unchanged.

The contacts patch and avatar dispatch logic are straightforward and well-scoped. The one area worth a second look is the callback-component swap in EquippableBuilder.Build(): a stale null-assign to callbackComponent.AvatarEquippable remains from the old implementation before ApplyAvatarPresentation overwrites it, which is harmless today but could confuse a future reader editing that path. The test also leaves private type registrations in the static NPC sets with no teardown, which could surface as unexpected noise if the test suite grows.

Files Needing Attention: S1API/Items/EquippableBuilder.cs (stale assignment in callback-component swap path) and S1API.Tests/Entities/ContactsAppRoleTests.cs (no teardown for static NPC type registrations)

Important Files Changed

Filename Overview
S1API/Internal/Patches/ContactsAppPatches.cs Adds IsContactRole helper and expands AddRelationCircles to include supplier NPCs alongside customers and dealers
S1API.Tests/Entities/ContactsAppRoleTests.cs New unit test for IsContactRole; registers test types into static NPC sets without any teardown, leaving those types registered for the remainder of the test session
S1API/Internal/Patches/EquippableAvatarPatches.cs New Harmony patch that drives SendEquippable_Networked for non-viewmodel equippables with a configured AvatarEquippable child component
S1API/Internal/Utils/RuntimePrefabCache.cs New utility that parents prefab templates under a DontDestroyOnLoad inactive root, keeping activeSelf true while preventing component ticks
S1API/Items/EquippableBuilder.cs Extracts ApplyAvatarPresentation into a shared method applied to all equippable types; replaces manual DontDestroyOnLoad+SetActive(false) with RuntimePrefabCache.Store; leaves one stale null-assign in the callback-component swap path

Sequence Diagram

sequenceDiagram
    participant GM as Game (Equippable.Equip)
    participant HAP as EquippableAvatarPatches
    participant TG as TryGetNonViewmodelAvatar
    participant PL as Player.Local

    GM->>HAP: Equip_Postfix(__instance)
    HAP->>TG: TryGetNonViewmodelAvatar(equippable)
    alt is Equippable_Viewmodel
        TG-->>HAP: false (skip — native viewmodel lifecycle handles avatar)
    else has AvatarEquippable child with non-empty AssetPath
        TG-->>HAP: true, avatar
        HAP->>PL: SendEquippable_Networked(avatar.AssetPath)
    end

    GM->>HAP: Unequip_Prefix(__instance)
    HAP->>TG: TryGetNonViewmodelAvatar(equippable)
    alt is Equippable_Viewmodel or no avatar child
        TG-->>HAP: false (skip)
    else has AvatarEquippable child
        TG-->>HAP: true
        HAP->>PL: SendEquippable_Networked(string.Empty)
    end
Loading

Comments Outside Diff (1)

  1. S1API/Items/EquippableBuilder.cs, line 188-189 (link)

    P2 Stale null-assign before ApplyAvatarPresentation

    At this point in the callback-component swap, viewmodelEquippable.AvatarEquippable (saved into avatarEquippable two lines earlier) is always nullApplyAvatarPresentation() hasn't run yet and the freshly-created viewmodel has no avatar component attached. The assignment callbackComponent.AvatarEquippable = avatarEquippable therefore unconditionally writes null, which ApplyAvatarPresentation() (called at line 210) immediately overwrites with the correctly constructed component. The line is a no-op leftover from the previous implementation and can be removed.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: S1API/Items/EquippableBuilder.cs
    Line: 188-189
    
    Comment:
    **Stale null-assign before ApplyAvatarPresentation**
    
    At this point in the callback-component swap, `viewmodelEquippable.AvatarEquippable` (saved into `avatarEquippable` two lines earlier) is always `null``ApplyAvatarPresentation()` hasn't run yet and the freshly-created viewmodel has no avatar component attached. The assignment `callbackComponent.AvatarEquippable = avatarEquippable` therefore unconditionally writes `null`, which `ApplyAvatarPresentation()` (called at line 210) immediately overwrites with the correctly constructed component. The line is a no-op leftover from the previous implementation and can be removed.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex Fix in Claude Code Fix in Cursor

Fix All in Codex Fix All in Claude Code Fix All in Cursor

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
S1API.Tests/Entities/ContactsAppRoleTests.cs:8-18
**Missing teardown for static NPC type registrations**

`NPC.RegisterCustomerType`, `RegisterDealerType`, and `RegisterSupplierType` write into static `HashSet` collections on the `NPC` class. The test registers `CustomerContact`, `DealerContact`, and `SupplierContact` but never removes them. Any other test that runs after this one in the same session and inspects NPC type counts or enumerates registered types will see these test-only private classes in the sets. Adding an `IDisposable` teardown (or `[Fact]`-level cleanup via `IAsyncLifetime`) that calls the corresponding deregister/clear helpers — or a dedicated test-scoped setup that uses a fresh static-reset path if one exists — would keep the test hermetic.

### Issue 2 of 2
S1API/Items/EquippableBuilder.cs:188-189
**Stale null-assign before ApplyAvatarPresentation**

At this point in the callback-component swap, `viewmodelEquippable.AvatarEquippable` (saved into `avatarEquippable` two lines earlier) is always `null``ApplyAvatarPresentation()` hasn't run yet and the freshly-created viewmodel has no avatar component attached. The assignment `callbackComponent.AvatarEquippable = avatarEquippable` therefore unconditionally writes `null`, which `ApplyAvatarPresentation()` (called at line 210) immediately overwrites with the correctly constructed component. The line is a no-op leftover from the previous implementation and can be removed.

Reviews (1): Last reviewed commit: "fix(beta): restore custom contact and av..." | Re-trigger Greptile

@ifBars
ifBars merged commit 16e3861 into beta Jul 27, 2026
5 checks passed
@ifBars
ifBars deleted the agent/beta-contact-avatar-fixes branch July 27, 2026 06:42
Comment on lines +8 to +18
[Fact]
public void IsContactRole_IncludesCustomersDealersAndSuppliers()
{
NPC.RegisterCustomerType(typeof(CustomerContact));
NPC.RegisterDealerType(typeof(DealerContact));
NPC.RegisterSupplierType(typeof(SupplierContact));

Assert.True(ContactsAppPatches.IsContactRole(typeof(CustomerContact)));
Assert.True(ContactsAppPatches.IsContactRole(typeof(DealerContact)));
Assert.True(ContactsAppPatches.IsContactRole(typeof(SupplierContact)));
Assert.False(ContactsAppPatches.IsContactRole(typeof(NonContact)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Missing teardown for static NPC type registrations

NPC.RegisterCustomerType, RegisterDealerType, and RegisterSupplierType write into static HashSet collections on the NPC class. The test registers CustomerContact, DealerContact, and SupplierContact but never removes them. Any other test that runs after this one in the same session and inspects NPC type counts or enumerates registered types will see these test-only private classes in the sets. Adding an IDisposable teardown (or [Fact]-level cleanup via IAsyncLifetime) that calls the corresponding deregister/clear helpers — or a dedicated test-scoped setup that uses a fresh static-reset path if one exists — would keep the test hermetic.

Prompt To Fix With AI
This is a comment left during a code review.
Path: S1API.Tests/Entities/ContactsAppRoleTests.cs
Line: 8-18

Comment:
**Missing teardown for static NPC type registrations**

`NPC.RegisterCustomerType`, `RegisterDealerType`, and `RegisterSupplierType` write into static `HashSet` collections on the `NPC` class. The test registers `CustomerContact`, `DealerContact`, and `SupplierContact` but never removes them. Any other test that runs after this one in the same session and inspects NPC type counts or enumerates registered types will see these test-only private classes in the sets. Adding an `IDisposable` teardown (or `[Fact]`-level cleanup via `IAsyncLifetime`) that calls the corresponding deregister/clear helpers — or a dedicated test-scoped setup that uses a fresh static-reset path if one exists — would keep the test hermetic.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Codex Fix in Claude Code Fix in Cursor

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