Skip to content
Open
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: 1 addition & 1 deletion Source/Client/Factions/FactionRepeater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static bool Template<T>(IDictionary<int, T> factionIdToData, Action<T> da
{
if (Multiplayer.Client == null || ignore) return true;

var spectatorId = Multiplayer.WorldComp.spectatorFaction.loadID;
var spectatorId = Multiplayer.WorldComp.spectatorFaction?.loadID;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can null spectatorFaction ever happen? Seems to be forcefully installed even when there is none during MultiplayerWorldComp.ExposeData, it's not tested for null anywhere else either.

Copy link
Copy Markdown
Contributor Author

@Sakura-TA Sakura-TA Apr 19, 2026

Choose a reason for hiding this comment

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

Met one time with VAE...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If I remember correctly, read it at MapComponent.FinalizeInit can cause issue, I was using new API patching this
https://github.com/Vanilla-Expanded/VanillaAspirationsExpanded/blob/abb0c64efc7861cc04a0f1909eeaa5a4f589aed0/1.5/Source/VAspirE/World%20and%20Map%20Components/WorldComponent_PawnList.cs#L22

Likely when MapComponent.FinalizeInit and WorldComponent.FinalizeInit is calling?

ignore = true;
foreach (var (id, data) in factionIdToData)
{
Expand Down
22 changes: 22 additions & 0 deletions Source/Client/MultiplayerAPIBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Multiplayer.API;
using Multiplayer.Client;
using Verse;
using Multiplayer.Client.Factions;
using Multiplayer.Client.Patches;
using RimWorld;

Expand Down Expand Up @@ -199,5 +200,26 @@ public void SetThingFilterContext(ThingFilterContext context)
{
ThingFilterMarkers.DrawnThingFilter = context;
}

public bool IsMultifaction => Client.Multiplayer.GameComp.multifaction;

public Faction SpectatorFaction => Client.Multiplayer.WorldComp.spectatorFaction;

public void PushFaction(Map map, Faction faction) => FactionExtensions.PushFaction(map, faction);

public void PopFaction(Map map = null) => FactionExtensions.PopFaction(map);

public void RepeatForWorldFactions(Action action)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Wouldn't it be better to share a readonly faction list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You mean like we implement a IFacionData to expose factionDatas and ask modders to iter through it themselves?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm struggling with this idea exposing factionData, cuz push/popfaction is serving situations that we set faction data of distinct faction and RepeatFaction serves the iterator through all player-factions so I think all the usages are covered?
Still not a big problem if modders using it wisely though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I get it. It works but damn it looks ugly haha. Users need to know exactly what's being injected into the context. Otherwise, if something isn't implemented, they'll be scratching their heads wondering why it fails with no hint that its not being injected. Making it explicit would help a lot.

MP.RepeatForWorldFactions(() =>
{
    int id = Faction.OfPlayer.loadID;
    var researchManager = Find.ResearchManager;

    // Need to iterate again to get specific map faction
    MP.RepeatForMapFactions(map, () => {
        // Very ugly
        if (id == Faction.OfPlayer.loadId)
        {
            var designationManager = mapFaction.designationManager;

            // do stuff with map
        }
    });
    
    // do stuff with world
});

My suggestion somewhat:

foreach(var worldFaction in MP.Factions)
{
    int id = worldFaction.factionId;
    var researchManager = worldFaction.researchManager;

    var mapFaction = worldFaction.Map(map);
    var designationManager = mapFaction.designationManager;

    // do stuff with world or map
}

FactionWorldData and FactionMapData would need to be exposed as interfaces and those types need to implement them.

{
bool ignore = false;
FactionRepeater.Template(Client.Multiplayer.game?.worldComp.factionData, _ => action(), null, ref ignore);
}

public void RepeatForMapFactions(Map map, Action action)
{
if (map == null) throw new ArgumentNullException(nameof(map));
bool ignore = false;
FactionRepeater.Template(map.MpComp()?.factionData, _ => action(), map, ref ignore);
}
}
}
Loading