MaxWasUnavailable/LobbyCompatibility

Refactor LoadLobbyListAndFilterTranspiler

Closed this issue · 0 comments

Right now, the LoadLobbyListAndFilterTranspiler patch looks like this:

// Does the following:
// - Adds SteamLobbyManager.levelListContainer to the stack
// - Replaces gameObject.GetComponentInChildren<LobbySlot>() with a call to InitializeLobbySlot
// - Calls InitializeLobbySlot(gameObject, levelListContainer), which returns the same component after injecting modded lobby data
// TODO: Directly inject the lobby into InitializeLobbySlot instead of grabbing it in Start() in ModdedLobbySlot
return new CodeMatcher(instructions)
    .SearchForward(instruction => instruction.Calls(lobbySlotMethod))
    .ThrowIfInvalid("Could not find LobbySlot method")
    .RemoveInstructions(1)
    .Insert(
        new CodeInstruction(OpCodes.Ldloc_1),
        new CodeInstruction(OpCodes.Ldfld, levelListContainerField),
        new CodeInstruction(
            OpCodes.Call,
            AccessTools.Method(typeof(LoadLobbyListAndFilterTranspiler), nameof(InitializeLobbySlot))))
    .InstructionEnumeration();

// ...

// Inject custom LobbySlot component for modded lobby data
private static LobbySlot InitializeLobbySlot(GameObject gameObject, Transform levelListContainer)
{
    var lobbySlot = gameObject.GetComponentInChildren<LobbySlot>();
    var moddedLobbySlot = lobbySlot.gameObject.AddComponent<ModdedLobbySlot>();

    // Set container parent for hover tooltip position math
    moddedLobbySlot.SetParentContainer(levelListContainer.parent);

    return lobbySlot;
}

In InitializeLobbySlot, a new component named ModdedLobbySlot is added, which uses the following to get a reference to the LobbySlot component:

        // Runs after LobbySlot data set
        private void Start()
        {
            // Not 100% ideal, but I don't want to mess with IL/stack weirdness too much right now
            _lobbySlot = GetComponent<LobbySlot>();

Waiting until the first frame isn't 100% ideal, as it has to wait to setup the "modded" lobby button, and things could theoretically go wrong. I haven't seen it in practice, but the LobbySlot component could not exist, or it could not be properly initialized by this point.

Ideally, the patch would be moved from replacing gameObject.GetComponentInChildren<LobbySlot>();, to executing right after the LobbySlot is initialized (around lobbyName = null).

This way, we could load up the stack with the following info, and create a ModdedLobbySlot instantly.

InitializeLobbySlot(GameObject gameObject, Transform levelListContainer, LobbySlot lobbySlot)
// gameObject to add the component onto
// levelListContainer (ScrollView.Viewport.Content) for later tooltip hover math - stored as SteamLobbyManager.levelListContainer
// LobbySlot to get the lobby info

This probably isn't urgent unless we notice things breaking, but it would improve:

  • Compatibility (not sure if anything else replaces gameObject.GetComponentInChildren<LobbySlot>();, but still)
  • Performance (removing a bunch of technically unnecessary GetComponent calls would be nice)
  • Reliability (LobbySlot will 100% always exist if we're calling the method)