Skip to content

Commit 3bfa487

Browse files
authored
Improved starting experience for multifaction (#508)
* [Multifaction] Improved start for new factions General Updates: - Enhanced multifaction starting experience for Core, Biotech, and Ideology to mirror single-player starts. - Scenario selection is now available before creating a new faction. - Correct starting gear, possessions, research, and initial dialogue are now properly generated. Bug Fixes: - Lost tribe factions can now be joined from the faction creation sidebar. - Multifaction start: Map fog correctly disappears after creating a new faction map. - Multifaction start: All scattered resources/items are now automatically forbidden after map generation (workaround in place; root cause still under investigation). - Biotech: ExoMech Remains generate now on the map. * [Multifaction, Async] Set start map time to zero Before: New multi-faction async maps are generated with the latest map time (maptick) of all maps. Issues: Potentially starting in winter, the storyteller might send incidents immediately, etc. Now: When creating a faction, a checkbox can be selected to start the map from zero ticks. Now: On Faction creation a checkbox can be checked to start the map from zero ticks * [Multifaction] Improved usability of the factionsidebar - Improved usability of factionsidebar => Updated layout of the faction creator and chooser => Added Tooltips to explain new settings and why they are sometimes disabled - Refactored FactionSidebar.cs a bit * Bugfix: Temp faction wasn't deleted correctly #508 I couldn’t find any way to generate the correct apparel for a "Lost Tribe" scenario start without creating a temporary faction. However, this temporary faction wasn’t deleted properly and some lingering references caused warnings during saving and loading. * Explain program state change #508 * Created issue for forbidden workaround #508 see Issue #535 * Refactor: GameStartAbsTick init handling #508 Moved the initialization of gameStartAbsTickMap into its property getter to ensure compatibility with old save files. This approach is more robust, as it handles edge cases where the value might be zero. Additionally, it avoids accessing TickManager during object construction. * Restrict scenario selection only for Anomaly mod #508 Updated the scenario selection logic to restrict it only when the Anomaly mod is active, instead of both Royalty and Anomaly. Adjusted the tooltip message accordingly. * Changes suggested by Sokyran #508 Discord messages Sokyran: I think that Find.CurrentMap == null check could be skipped here if you change WorldRendererUtility.WorldRenderedNow to WorldRendererUtility.WorldSelected. At least the null CurrentMap check wasn't needed before. https://github.com/Tick-git/Multiplayer/blob/03f6d2b7e1c704563810abe74d6df538738c7926/Source/Client/AsyncTime/AsyncTimePatches.cs#L149-L150 Is 2 constructors needed here? The second one already includes the default value of 0, so in that they already match what they do. https://github.com/Tick-git/Multiplayer/blob/03f6d2b7e1c704563810abe74d6df538738c7926/Source/Client/AsyncTime/AsyncTimeComp.cs#L101-L111 --------- Co-authored-by: Florian-Lipke <62646477+Florian-Lipke@users.noreply.github.com>
1 parent f1a12cb commit 3bfa487

File tree

11 files changed

+651
-226
lines changed

11 files changed

+651
-226
lines changed

Source/Client/AsyncTime/AsyncTimeComp.cs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,22 @@ public void SetDesiredTimeSpeed(TimeSpeed speed)
6565

6666
public int TickableId => map.uniqueID;
6767

68+
public int GameStartAbsTick
69+
{
70+
get
71+
{
72+
if (gameStartAbsTickMap == 0)
73+
{
74+
gameStartAbsTickMap = Find.TickManager?.gameStartAbsTick ?? 0;
75+
}
76+
77+
return gameStartAbsTickMap;
78+
}
79+
}
80+
6881
public Map map;
6982
public int mapTicks;
83+
private int gameStartAbsTickMap;
7084
private TimeSpeed timeSpeedInt;
7185
public bool forcedNormalSpeed;
7286
public int eventCount;
@@ -84,9 +98,10 @@ public void SetDesiredTimeSpeed(TimeSpeed speed)
8498

8599
public Queue<ScheduledCommand> cmds = new();
86100

87-
public AsyncTimeComp(Map map)
101+
public AsyncTimeComp(Map map, int gameStartAbsTick = 0)
88102
{
89103
this.map = map;
104+
this.gameStartAbsTickMap = gameStartAbsTick;
90105
}
91106

92107
public void Tick()
@@ -198,6 +213,8 @@ public void ExposeData()
198213
Scribe_Values.Look(ref mapTicks, "mapTicks");
199214
Scribe_Values.Look(ref timeSpeedInt, "timeSpeed");
200215

216+
Scribe_Values.Look(ref gameStartAbsTickMap, "gameStartAbsTickMap");
217+
201218
Scribe_Deep.Look(ref storyteller, "storyteller");
202219

203220
Scribe_Deep.Look(ref storyWatcher, "storyWatcher");
@@ -441,5 +458,4 @@ public enum DesignatorMode : byte
441458
MultiCell,
442459
Thing
443460
}
444-
445461
}

Source/Client/AsyncTime/SetMapTime.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,14 @@ public struct TimeSnapshot
180180
public int ticks;
181181
public TimeSpeed speed;
182182
public TimeSlower slower;
183+
public int gameStartAbsTick;
183184

184185
public void Set()
185186
{
186187
Find.TickManager.ticksGameInt = ticks;
187188
Find.TickManager.slower = slower;
188189
Find.TickManager.curTimeSpeed = speed;
190+
Find.TickManager.gameStartAbsTick = gameStartAbsTick;
189191
}
190192

191193
public static TimeSnapshot Current()
@@ -194,7 +196,8 @@ public static TimeSnapshot Current()
194196
{
195197
ticks = Find.TickManager.ticksGameInt,
196198
speed = Find.TickManager.curTimeSpeed,
197-
slower = Find.TickManager.slower
199+
slower = Find.TickManager.slower,
200+
gameStartAbsTick = Find.TickManager.gameStartAbsTick
198201
};
199202
}
200203

@@ -210,6 +213,7 @@ public static TimeSnapshot Current()
210213
tickManager.ticksGameInt = mapComp.mapTicks;
211214
tickManager.slower = mapComp.slower;
212215
tickManager.CurTimeSpeed = mapComp.DesiredTimeSpeed;
216+
tickManager.gameStartAbsTick = mapComp.GameStartAbsTick;
213217

214218
return prev;
215219
}

Source/Client/Factions/FactionCreator.cs

Lines changed: 135 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using JetBrains.Annotations;
45
using Multiplayer.API;
@@ -14,6 +15,7 @@ public static class FactionCreator
1415
{
1516
private static Dictionary<int, List<Pawn>> pawnStore = new();
1617
public static bool generatingMap;
18+
public const string preventSpecialCalculationPathInGenTicksTicksAbs = "dummy";
1719

1820
public static void ClearData()
1921
{
@@ -27,34 +29,30 @@ public static void SendPawn(int playerId, Pawn p)
2729
}
2830

2931
[SyncMethod]
30-
public static void CreateFaction(
31-
int playerId, string factionName, int tile,
32-
[CanBeNull] ScenarioDef scenarioDef, ChooseIdeoInfo chooseIdeoInfo,
33-
bool generateMap
34-
)
32+
public static void CreateFaction(int playerId, FactionCreationData creationData)
3533
{
3634
var self = TickPatch.currentExecutingCmdIssuedBySelf;
3735

3836
LongEventHandler.QueueLongEvent(() =>
3937
{
40-
PrepareGameInitData(playerId);
38+
var scenario = creationData.scenarioDef?.scenario ?? Current.Game.Scenario;
39+
Map newMap = null;
40+
41+
PrepareGameInitData(playerId, scenario, self, creationData.startingPossessions);
4142

42-
var scenario = scenarioDef?.scenario ?? Current.Game.Scenario;
4343
var newFaction = NewFactionWithIdeo(
44-
factionName,
44+
creationData.factionName,
4545
scenario.playerFaction.factionDef,
46-
chooseIdeoInfo
46+
creationData.chooseIdeoInfo
4747
);
4848

49-
Map newMap = null;
50-
51-
if (generateMap)
49+
if (creationData.generateMap)
5250
using (MpScope.PushFaction(newFaction))
5351
{
5452
foreach (var pawn in StartingPawnUtility.StartingAndOptionalPawns)
5553
pawn.ideo.SetIdeo(newFaction.ideos.PrimaryIdeo);
5654

57-
newMap = GenerateNewMap(tile, scenario);
55+
newMap = GenerateNewMap(creationData.startingTile, scenario, creationData.setupNextMapFromTickZero);
5856
}
5957

6058
foreach (Map map in Find.Maps)
@@ -70,6 +68,10 @@ bool generateMap
7068

7169
Multiplayer.game.ChangeRealPlayerFaction(newFaction);
7270

71+
CameraJumper.TryJump(MapGenerator.playerStartSpotInt, newMap);
72+
73+
PostGameStart(scenario);
74+
7375
// todo setting faction of self
7476
Multiplayer.Client.Send(
7577
Packets.Client_SetFaction,
@@ -80,66 +82,141 @@ bool generateMap
8082
}, "GeneratingMap", doAsynchronously: true, GameAndMapInitExceptionHandlers.ErrorWhileGeneratingMap);
8183
}
8284

83-
private static Map GenerateNewMap(int tile, Scenario scenario)
85+
private static void PostGameStart(Scenario scenario)
86+
{
87+
/**
88+
ScenPart_StartingResearch.cs
89+
ScenPart_AutoActivateMonolith.cs
90+
ScenPart_CreateIncident.cs
91+
ScenPart_GameStartDialog.cs
92+
ScenPart_PlayerFaction.cs
93+
ScenPart_Rule.cs
94+
95+
Would like to call PostGameStart on all implementations (scenario.PostGameStart) -
96+
but dont know if it breaks with dlcs other than biotech - especially while only called
97+
on self
98+
**/
99+
100+
HashSet<Type> types = new HashSet<Type>
101+
{
102+
typeof(ScenPart_PlayerFaction),
103+
typeof(ScenPart_GameStartDialog),
104+
typeof(ScenPart_StartingResearch),
105+
};
106+
107+
foreach (ScenPart part in scenario.AllParts)
108+
{
109+
if (types.Contains(part.GetType()))
110+
{
111+
part.PostGameStart();
112+
}
113+
}
114+
}
115+
116+
private static Map GenerateNewMap(int tile, Scenario scenario, bool setupNextMapFromTickZero)
84117
{
85118
// This has to be null, otherwise, during map generation, Faction.OfPlayer returns it which breaks FactionContext
86119
Find.GameInitData.playerFaction = null;
87120
Find.GameInitData.PrepForMapGen();
88121

89-
var mapParent = (Settlement)WorldObjectMaker.MakeWorldObject(WorldObjectDefOf.Settlement);
90-
mapParent.Tile = tile;
91-
mapParent.SetFaction(Faction.OfPlayer);
92-
Find.WorldObjects.Add(mapParent);
122+
// ScenPart_PlayerFaction --> PreMapGenerate
123+
124+
var settlement = (Settlement)WorldObjectMaker.MakeWorldObject(WorldObjectDefOf.Settlement);
125+
settlement.Tile = tile;
126+
settlement.SetFaction(Faction.OfPlayer);
127+
Find.WorldObjects.Add(settlement);
128+
129+
// ^^^^ Duplicate Code here ^^^^
93130

94131
var prevScenario = Find.Scenario;
132+
var prevStartingTile = Find.GameInfo.startingTile;
133+
95134
Current.Game.Scenario = scenario;
135+
Find.GameInfo.startingTile = tile;
136+
Find.GameInitData.startingTile = tile;
137+
138+
MapSetup.SetupNextMapFromTickZero = setupNextMapFromTickZero;
139+
96140
generatingMap = true;
97141

98142
try
99143
{
100-
return GetOrGenerateMapUtility.GetOrGenerateMap(
144+
Map map = GetOrGenerateMapUtility.GetOrGenerateMap(
101145
tile,
102146
new IntVec3(250, 1, 250),
103147
null
104148
);
149+
150+
SetAllThingWithCompsOnMapForbidden(map);
151+
152+
return map;
105153
}
106154
finally
107155
{
108156
generatingMap = false;
109157
Current.Game.Scenario = prevScenario;
158+
Find.GameInfo.startingTile = prevStartingTile;
159+
Find.GameInitData.startingTile = prevStartingTile;
160+
}
161+
}
162+
163+
// TODO: Remove this Workaround (see Issue #535)
164+
private static void SetAllThingWithCompsOnMapForbidden(Map map)
165+
{
166+
foreach (Thing thing in map.listerThings.AllThings)
167+
{
168+
if (thing is ThingWithComps)
169+
{
170+
thing.SetForbidden(true, false);
171+
}
110172
}
111173
}
112174

113175
private static void InitNewGame()
114176
{
115177
PawnUtility.GiveAllStartingPlayerPawnsThought(ThoughtDefOf.NewColonyOptimism);
178+
116179
ResearchUtility.ApplyPlayerStartingResearch();
117180
// Initialize anomaly. Since the Anomaly comp is currently shared by all players,
118181
// we need to ensure that any new factions have access to anomaly research if
119182
// anomaly content was started.
120183
Find.ResearchManager.Notify_MonolithLevelChanged(Find.Anomaly.Level);
121184
}
122185

123-
private static void PrepareGameInitData(int sessionId)
186+
private static void PrepareGameInitData(int sessionId, Scenario scenario, bool self, List<ThingDefCount> startingPossessions)
124187
{
125-
Current.Game.InitData = new GameInitData
188+
if (!self)
126189
{
127-
startingPawnCount = 3,
128-
gameToLoad = "dummy" // Prevent special calculation path in GenTicks.TicksAbs
129-
};
190+
Current.Game.InitData = new GameInitData()
191+
{
192+
startingPawnCount = GetStartingPawnsCountForScenario(scenario),
193+
gameToLoad = preventSpecialCalculationPathInGenTicksTicksAbs
194+
};
195+
}
130196

131197
if (pawnStore.TryGetValue(sessionId, out var pawns))
132198
{
133-
Current.Game.InitData.startingAndOptionalPawns = pawns;
134-
Current.Game.InitData.startingPossessions = new Dictionary<Pawn, List<ThingDefCount>>();
135-
foreach (var p in pawns)
136-
Current.Game.InitData.startingPossessions[p] = new List<ThingDefCount>();
199+
Pawn firstPawn = pawns.First();
200+
201+
GameInitData gameInitData = Current.Game.InitData;
202+
gameInitData.startingAndOptionalPawns = pawns;
203+
gameInitData.startingPossessions = new Dictionary<Pawn, List<ThingDefCount>>();
204+
205+
foreach (var pawn in pawns)
206+
{
207+
gameInitData.startingPossessions[pawn] = new List<ThingDefCount>();
208+
}
209+
210+
foreach (var possesion in startingPossessions)
211+
{
212+
gameInitData.startingPossessions[firstPawn].Add(possesion);
213+
}
137214

138215
pawnStore.Remove(sessionId);
139216
}
140217
}
141218

142-
private static Faction NewFactionWithIdeo(string name, FactionDef def, ChooseIdeoInfo chooseIdeoInfo)
219+
private static Faction NewFactionWithIdeo(string name, FactionDef def, IdeologyData chooseIdeoInfo)
143220
{
144221
var faction = new Faction
145222
{
@@ -148,6 +225,7 @@ private static Faction NewFactionWithIdeo(string name, FactionDef def, ChooseIde
148225
Name = name,
149226
hidden = true
150227
};
228+
151229
faction.ideos = new FactionIdeosTracker(faction);
152230

153231
if (!ModsConfig.IdeologyActive || Find.IdeoManager.classicMode || chooseIdeoInfo.SelectedIdeo == null)
@@ -181,7 +259,7 @@ private static Faction NewFactionWithIdeo(string name, FactionDef def, ChooseIde
181259
return faction;
182260
}
183261

184-
private static Ideo GenerateIdeo(ChooseIdeoInfo chooseIdeoInfo)
262+
private static Ideo GenerateIdeo(IdeologyData chooseIdeoInfo)
185263
{
186264
List<MemeDef> list = chooseIdeoInfo.SelectedIdeo.memes.ToList();
187265

@@ -195,4 +273,30 @@ private static Ideo GenerateIdeo(ChooseIdeoInfo chooseIdeoInfo)
195273

196274
return ideo;
197275
}
276+
277+
public static int GetStartingPawnsCountForScenario(Scenario scenario)
278+
{
279+
foreach (ScenPart part in scenario.AllParts)
280+
{
281+
if (part is ScenPart_ConfigPage_ConfigureStartingPawnsBase startingPawnsConfig)
282+
{
283+
return startingPawnsConfig.TotalPawnCount;
284+
}
285+
}
286+
287+
MpLog.Error("No starting pawns config found to access startingPawnCount");
288+
289+
return 0;
290+
}
198291
}
292+
public record FactionCreationData : ISyncSimple
293+
{
294+
public string factionName;
295+
public int startingTile;
296+
[CanBeNull] public ScenarioDef scenarioDef;
297+
public IdeologyData chooseIdeoInfo;
298+
public bool generateMap;
299+
public List<ThingDefCount> startingPossessions;
300+
public bool setupNextMapFromTickZero;
301+
}
302+

0 commit comments

Comments
 (0)