1
- using ClientCore ;
2
- using Microsoft . Xna . Framework ;
3
- using System ;
1
+ using System ;
4
2
using System . Collections . Generic ;
5
- using DTAClient . Domain ;
6
3
using System . IO ;
4
+ using System . Linq ;
5
+ using ClientCore ;
7
6
using ClientGUI ;
8
- using Rampastring . XNAUI . XNAControls ;
9
- using Rampastring . XNAUI ;
10
- using Rampastring . Tools ;
11
7
using ClientUpdater ;
12
8
using ClientCore . Extensions ;
9
+ using DTAClient . Domain ;
10
+ using Microsoft . Xna . Framework ;
11
+ using Rampastring . Tools ;
12
+ using Rampastring . XNAUI ;
13
+ using Rampastring . XNAUI . XNAControls ;
14
+ using System . Diagnostics ;
15
+ using System . Globalization ;
13
16
14
17
namespace DTAClient . DXGUI . Generic
15
18
{
@@ -34,7 +37,7 @@ public CampaignSelector(WindowManager windowManager, DiscordHandler discordHandl
34
37
35
38
private DiscordHandler discordHandler ;
36
39
37
- private List < Mission > Missions = new List < Mission > ( ) ;
40
+ private List < Mission > lbCampaignListMissions = new List < Mission > ( ) ;
38
41
private XNAListBox lbCampaignList ;
39
42
private XNAClientButton btnLaunch ;
40
43
private XNATextBlock tbMissionDescription ;
@@ -57,6 +60,30 @@ public CampaignSelector(WindowManager windowManager, DiscordHandler discordHandl
57
60
58
61
private Mission missionToLaunch ;
59
62
63
+ private List < Mission > _allMissions = [ ] ;
64
+ public IReadOnlyCollection < Mission > AllMissions { get => _allMissions ; }
65
+
66
+ private Dictionary < int , Mission > _uniqueIDToMissions = new ( ) ;
67
+ public IReadOnlyDictionary < int , Mission > UniqueIDToMissions => _uniqueIDToMissions ;
68
+
69
+ private void AddMission ( Mission mission )
70
+ {
71
+ // no matter whether the key is duplicated, the mission is always added to AllMissions
72
+ _allMissions . Add ( mission ) ;
73
+
74
+ // but only the first mission is recorded in UniqueIDToMissions
75
+ if ( _uniqueIDToMissions . ContainsKey ( mission . MissionID ) )
76
+ {
77
+ Logger . Log ( $ "CampaignSelector: duplicated mission. CodeName: { mission . CodeName } . ID: { mission . MissionID } . Description: { mission . UntranslatedGUIName } .") ;
78
+ if ( ! string . IsNullOrEmpty ( mission . Scenario ) )
79
+ mission . Enabled = false ;
80
+ }
81
+ else
82
+ {
83
+ _uniqueIDToMissions . Add ( mission . MissionID , mission ) ;
84
+ }
85
+ }
86
+
60
87
public override void Initialize ( )
61
88
{
62
89
BackgroundTexture = AssetLoader . LoadTexture ( "missionselectorbg.png" ) ;
@@ -199,7 +226,7 @@ private void LbCampaignList_SelectedIndexChanged(object sender, EventArgs e)
199
226
return ;
200
227
}
201
228
202
- Mission mission = Missions [ lbCampaignList . SelectedIndex ] ;
229
+ Mission mission = lbCampaignListMissions [ lbCampaignList . SelectedIndex ] ;
203
230
204
231
if ( string . IsNullOrEmpty ( mission . Scenario ) )
205
232
{
@@ -221,14 +248,14 @@ private void LbCampaignList_SelectedIndexChanged(object sender, EventArgs e)
221
248
222
249
private void BtnCancel_LeftClick ( object sender , EventArgs e )
223
250
{
224
- Enabled = false ;
251
+ Disable ( ) ;
225
252
}
226
253
227
254
private void BtnLaunch_LeftClick ( object sender , EventArgs e )
228
255
{
229
256
int selectedMissionId = lbCampaignList . SelectedIndex ;
230
257
231
- Mission mission = Missions [ selectedMissionId ] ;
258
+ Mission mission = lbCampaignListMissions [ selectedMissionId ] ;
232
259
233
260
if ( ! ClientConfiguration . Instance . ModMode &&
234
261
( ! Updater . IsFileNonexistantOrOriginal ( mission . Scenario ) || AreFilesModified ( ) ) )
@@ -267,45 +294,70 @@ private void CheaterWindow_YesClicked(object sender, EventArgs e)
267
294
/// </summary>
268
295
private void LaunchMission ( Mission mission )
269
296
{
297
+ CustomMissionHelper . DeleteSupplementalMissionFiles ( ) ;
298
+ CustomMissionHelper . CopySupplementalMissionFiles ( mission ) ;
299
+
300
+ string scenario = mission . Scenario ;
301
+
270
302
bool copyMapsToSpawnmapINI = ClientConfiguration . Instance . CopyMissionsToSpawnmapINI ;
271
303
272
304
Logger . Log ( "About to write spawn.ini." ) ;
273
- using ( var spawnStreamWriter = new StreamWriter ( SafePath . CombineFilePath ( ProgramConstants . GamePath , "spawn.ini" ) ) )
305
+ IniFile spawnIni = new ( )
274
306
{
275
- spawnStreamWriter . WriteLine ( "; Generated by DTA Client" ) ;
276
- spawnStreamWriter . WriteLine ( "[Settings]" ) ;
277
- if ( copyMapsToSpawnmapINI )
278
- spawnStreamWriter . WriteLine ( "Scenario=spawnmap.ini" ) ;
279
- else
280
- spawnStreamWriter . WriteLine ( "Scenario=" + mission . Scenario ) ;
307
+ Comment = "Generated by CnCNet Client"
308
+ } ;
309
+ IniSection spawnIniSettings = new ( "Settings" ) ;
310
+
311
+ if ( copyMapsToSpawnmapINI )
312
+ spawnIniSettings . AddKey ( "Scenario" , "spawnmap.ini" ) ;
313
+ else
314
+ spawnIniSettings . AddKey ( "Scenario" , scenario ) ;
281
315
282
316
// No one wants to play missions on Fastest, so we'll change it to Faster
283
317
if ( UserINISettings . Instance . GameSpeed == 0 )
284
318
UserINISettings . Instance . GameSpeed . Value = 1 ;
285
319
286
- spawnStreamWriter . WriteLine ( "CampaignID=" + mission . Index ) ;
287
- spawnStreamWriter . WriteLine ( "GameSpeed=" + UserINISettings . Instance . GameSpeed ) ;
320
+ spawnIniSettings . AddKey ( "GameSpeed" , UserINISettings . Instance . GameSpeed . ToString ( ) ) ;
288
321
#if YR || ARES
289
- spawnStreamWriter . WriteLine ( "Ra2Mode=" + ! mission . RequiredAddon ) ;
322
+ spawnIniSettings . AddKey ( "Ra2Mode" , ( ! mission . RequiredAddon ) . ToString ( CultureInfo . InvariantCulture ) ) ;
290
323
#else
291
- spawnStreamWriter . WriteLine ( "Firestorm=" + mission . RequiredAddon ) ;
324
+ spawnIniSettings . AddKey ( "Firestorm" , mission . RequiredAddon . ToString ( CultureInfo . InvariantCulture ) ) ;
292
325
#endif
293
- spawnStreamWriter . WriteLine ( "CustomLoadScreen=" + LoadingScreenController . GetLoadScreenName ( mission . Side . ToString ( ) ) ) ;
294
- spawnStreamWriter . WriteLine ( "IsSinglePlayer=Yes" ) ;
295
- spawnStreamWriter . WriteLine ( "SidebarHack=" + ClientConfiguration . Instance . SidebarHack ) ;
296
- spawnStreamWriter . WriteLine ( "Side=" + mission . Side ) ;
297
- spawnStreamWriter . WriteLine ( "BuildOffAlly=" + mission . BuildOffAlly ) ;
326
+
327
+ spawnIniSettings . AddKey ( "CustomLoadScreen" , LoadingScreenController . GetLoadScreenName ( mission . Side . ToString ( ) ) ) ;
328
+
329
+ spawnIniSettings . AddKey ( "IsSinglePlayer" , "Yes" ) ;
330
+ spawnIniSettings . AddKey ( "SidebarHack" , ClientConfiguration . Instance . SidebarHack . ToString ( CultureInfo . InvariantCulture ) ) ;
331
+ spawnIniSettings . AddKey ( "Side" , mission . Side . ToString ( CultureInfo . InvariantCulture ) ) ;
332
+ spawnIniSettings . AddKey ( "BuildOffAlly" , mission . BuildOffAlly . ToString ( CultureInfo . InvariantCulture ) ) ;
298
333
299
334
UserINISettings . Instance . Difficulty . Value = trbDifficultySelector . Value ;
300
335
301
- spawnStreamWriter . WriteLine ( "DifficultyModeHuman=" + ( mission . PlayerAlwaysOnNormalDifficulty ? "1" : trbDifficultySelector . Value . ToString ( ) ) ) ;
302
- spawnStreamWriter . WriteLine ( "DifficultyModeComputer=" + GetComputerDifficulty ( ) ) ;
336
+ spawnIniSettings . AddKey ( "DifficultyModeHuman" , mission . PlayerAlwaysOnNormalDifficulty ? "1" : trbDifficultySelector . Value . ToString ( CultureInfo . InvariantCulture ) ) ;
337
+ spawnIniSettings . AddKey ( "DifficultyModeComputer" , GetComputerDifficulty ( ) . ToString ( CultureInfo . InvariantCulture ) ) ;
338
+
339
+ if ( mission . IsCustomMission )
340
+ {
341
+ spawnIniSettings . AddKey ( "CustomMissionID" , mission . MissionID . ToString ( CultureInfo . InvariantCulture ) ) ;
342
+ }
343
+
344
+ spawnIni . AddSection ( spawnIniSettings ) ;
345
+
346
+ if ( mission . IsCustomMission && mission . CustomMission_MissionMdIniSection is not null )
347
+ {
348
+ // copy an IniSection
349
+ IniSection spawnIniMissionIniSection = new ( scenario ) ;
350
+ foreach ( var kvp in mission . CustomMission_MissionMdIniSection . Keys )
351
+ {
352
+ spawnIniMissionIniSection . AddKey ( kvp . Key , kvp . Value ) ;
353
+ }
303
354
304
- spawnStreamWriter . WriteLine ( ) ;
305
- spawnStreamWriter . WriteLine ( ) ;
306
- spawnStreamWriter . WriteLine ( ) ;
355
+ // append the new IniSection
356
+ spawnIni . AddSection ( spawnIniMissionIniSection ) ;
307
357
}
308
358
359
+ spawnIni . WriteIniFile ( SafePath . CombineFilePath ( ProgramConstants . GamePath , "spawn.ini" ) ) ;
360
+
309
361
var difficultyIni = new IniFile ( SafePath . CombineFilePath ( ProgramConstants . GamePath , DifficultyIniPaths [ trbDifficultySelector . Value ] ) ) ;
310
362
string difficultyName = DifficultyNames [ trbDifficultySelector . Value ] ;
311
363
@@ -319,7 +371,7 @@ private void LaunchMission(Mission mission)
319
371
UserINISettings . Instance . Difficulty . Value = trbDifficultySelector . Value ;
320
372
UserINISettings . Instance . SaveSettings ( ) ;
321
373
322
- ( ( MainMenuDarkeningPanel ) Parent ) . Hide ( ) ;
374
+ Disable ( ) ;
323
375
324
376
discordHandler . UpdatePresence ( mission . UntranslatedGUIName , difficultyName , mission . IconPath , true ) ;
325
377
GameProcessLogic . GameProcessExited += GameProcessExited_Callback ;
@@ -338,6 +390,9 @@ private void GameProcessExited_Callback()
338
390
protected virtual void GameProcessExited ( )
339
391
{
340
392
GameProcessLogic . GameProcessExited -= GameProcessExited_Callback ;
393
+
394
+ CustomMissionHelper . DeleteSupplementalMissionFiles ( ) ;
395
+
341
396
// Logger.Log("GameProcessExited: Updating Discord Presence.");
342
397
discordHandler . UpdatePresence ( ) ;
343
398
}
@@ -346,8 +401,36 @@ private void ReadMissionList()
346
401
{
347
402
ParseBattleIni ( "INI/Battle.ini" ) ;
348
403
349
- if ( Missions . Count == 0 )
404
+ if ( AllMissions . Count == 0 )
350
405
ParseBattleIni ( "INI/" + ClientConfiguration . Instance . BattleFSFileName ) ;
406
+
407
+ LoadCustomMissions ( ) ;
408
+
409
+ LoadMissionsWithFilter ( null ) ;
410
+ }
411
+
412
+ private void LoadCustomMissions ( )
413
+ {
414
+ string customMissionsDirectory = SafePath . CombineDirectoryPath ( ProgramConstants . GamePath , ClientConfiguration . Instance . CustomMissionPath ) ;
415
+ if ( ! Directory . Exists ( customMissionsDirectory ) )
416
+ return ;
417
+
418
+ string [ ] mapFiles = Directory . GetFiles ( customMissionsDirectory , "*.map" ) ;
419
+ foreach ( string mapFilePath in mapFiles )
420
+ {
421
+ var mapFile = new IniFile ( mapFilePath ) ;
422
+
423
+ IniSection missionSection = mapFile . GetSection ( "CNCNET:MISSION:BATTLE.INI" ) ;
424
+ if ( missionSection is null )
425
+ continue ;
426
+
427
+ IniSection ? missionMdIniSection = mapFile . GetSection ( "CNCNET:MISSION:MISSION.INI" ) ;
428
+
429
+ string filename = new FileInfo ( mapFilePath ) . Name ;
430
+ string scenario = SafePath . CombineFilePath ( ClientConfiguration . Instance . CustomMissionPath , filename ) ;
431
+ Mission mission = Mission . NewCustomMission ( missionSection , missionCodeName : filename . ToUpperInvariant ( ) , scenario , missionMdIniSection ) ;
432
+ AddMission ( mission ) ;
433
+ }
351
434
}
352
435
353
436
/// <summary>
@@ -366,7 +449,7 @@ private bool ParseBattleIni(string path)
366
449
return false ;
367
450
}
368
451
369
- if ( Missions . Count > 0 )
452
+ if ( lbCampaignListMissions . Count > 0 )
370
453
{
371
454
throw new InvalidOperationException ( "Loading multiple Battle*.ini files is not supported anymore." ) ;
372
455
}
@@ -386,10 +469,40 @@ private bool ParseBattleIni(string path)
386
469
if ( ! battleIni . SectionExists ( battleSection ) )
387
470
continue ;
388
471
389
- var mission = new Mission ( battleIni , battleSection , i ) ;
472
+ var mission = new Mission ( battleIni . GetSection ( battleSection ) , missionCodeName : battleEntry ) ;
473
+ AddMission ( mission ) ;
474
+ }
475
+
476
+ Logger . Log ( "Finished parsing " + path + "." ) ;
477
+ return true ;
478
+ }
479
+
480
+ /// <summary>
481
+ /// Load or re-load missons with selected tags.
482
+ /// </summary>
483
+ /// <param name="selectedTags">Missions with at lease one of which tags to be shown. As an exception, null means show all missions.</param>
484
+ public void LoadMissionsWithFilter ( ISet < string > selectedTags = null )
485
+ {
486
+ lbCampaignListMissions . Clear ( ) ;
390
487
391
- Missions . Add ( mission ) ;
488
+ lbCampaignList . IsChangingSize = true ;
392
489
490
+ lbCampaignList . Clear ( ) ;
491
+ lbCampaignList . SelectedIndex = - 1 ;
492
+
493
+ // The following two lines are handled by LbCampaignList_SelectedIndexChanged
494
+ // tbMissionDescription.Text = string.Empty;
495
+ // btnLaunch.AllowClick = false;
496
+
497
+ // Select missions with the filter
498
+ IEnumerable < Mission > missions = AllMissions ;
499
+ if ( selectedTags != null )
500
+ missions = missions . Where ( mission => mission . Tags . Intersect ( selectedTags ) . Any ( ) ) . ToList ( ) ;
501
+ lbCampaignListMissions = missions . ToList ( ) ;
502
+
503
+ // Update lbCampaignList with selected missions
504
+ foreach ( Mission mission in lbCampaignListMissions )
505
+ {
393
506
var item = new XNAListBoxItem ( ) ;
394
507
item . Text = mission . GUIName ;
395
508
if ( ! mission . Enabled )
@@ -414,10 +527,10 @@ private bool ParseBattleIni(string path)
414
527
lbCampaignList . AddItem ( item ) ;
415
528
}
416
529
417
- Logger . Log ( "Finished parsing " + path + "." ) ;
418
- return true ;
419
- }
530
+ lbCampaignList . IsChangingSize = false ;
420
531
532
+ lbCampaignList . TopIndex = 0 ;
533
+ }
421
534
public override void Draw ( GameTime gameTime )
422
535
{
423
536
base . Draw ( gameTime ) ;
0 commit comments