Skip to content

Conversation

SadPencil
Copy link
Member

@SadPencil SadPencil commented Mar 10, 2024

This PR includes and therefore closes #367 and closes #370, avoiding maintaining too many PRs

This PR works with CnCNet/yrpp-spawner#19

the campaign tag selector

This PR is backward compatible as by default the option is off. Modders can enable the CampaignTagSelectorEnabled option to use this feature.

This feature enables modders to make a "choose your side" interface before the campaign selector. Choosing among "Act 1/Act 2/..." or any other tags is also possible.

The screenshot below shows the feature. (I am not an expert in beautifying user interfaces so it is only a working example. Fully configurable through ini files.)

img1
img2
img3

The modders define tags for missions in battle.ini and customize the selector window in CampaignTagSelector.ini using the new INItializableWindow ini format.

I use TSC v6 client as an example. The corresponding files are attached here.

TSC.v6.example.zip

the custom mission support

Apart from the campaign tag selector feature brought by #370, this PR also include a custom mission support for YR and Ares/Phobos.

Users can drop third-party fan-made singleplayer missions in Maps\CustomMissions folder, like this:

CustomMissions
├── vnsepa.csf # optional
├── vnsepa.map
├── vnsepa.pal # optional
└── vnsepa.shp # optional

This installs a custom mission named vnsepa.

vnsepa.map file is basically a .map file containing campaigns, with the following new-added sectiones:

[CNCNET:MISSION:BATTLE.INI]
CD=2
BuildOffAlly=yes
Scenario=vnsepa.map ; this line will be ignored by the client
Description=vnsepa
Summary=MAP:DESCREDDAWN
Side=0
SideName=Allies
Act=1
LongDescription=Operation: The Separation - Location: Kon Tum, Central Vietnam blah blah

[CNCNET:MISSION:MISSION.INI]
Briefing=Brief:AREDDAWN
ShowBriefing=true
UIName=MAP:TITREDDAWN
LSLoadMessage=LoadMsg:AREDDAWN
LSLoadBriefing=LoadBrief:AREDDAWN
LS640BriefLocX=20
LS640BriefLocY=20
LS800BriefLocX=20
LS800BriefLocY=20
LS640BkgdName=custommission.shp ; the filename is fixed
LS800BkgdName=custommission.shp ; the filename is fixed
LS800BkgdPal=custommission.pal ; the filename is fixed
LoadScreenText.Color=LightGrey

; Map created with FinalAlert 2(tm) Mission Editor
; Get it at http://www.westwood.com
; note that all comments were truncated

The two sections define mission info for the client as well as YR spawner.

To show custom missions, modders need to define a button ButtonTag_CUSTOM in file CampaignTagSelector.ini.

Copy link

github-actions bot commented Mar 10, 2024

Nightly build for this pull request:

  • artifacts.zip
    This comment is automatic and is meant to allow guests to get latest automatic builds without registering. It is updated on every successful build.

@SadPencil SadPencil force-pushed the feature-custom-mission branch 5 times, most recently from 6414310 to 787614a Compare March 10, 2024 12:12
@SadPencil SadPencil force-pushed the feature-custom-mission branch from 787614a to 1485f2a Compare September 22, 2024 16:26
@SadPencil SadPencil marked this pull request as draft November 14, 2024 01:53
@SadPencil SadPencil force-pushed the feature-custom-mission branch from ba2aa28 to 35a20f2 Compare May 27, 2025 10:05
@SadPencil SadPencil marked this pull request as ready for review May 27, 2025 10:05
@Starkku
Copy link
Contributor

Starkku commented May 28, 2025

I took a liberty of adding some improvements to this PR.

  • CampaignTagSelector <-> CampaignSelector no longer displays fade effect between them.
  • CampaignSelector is now INItializableWindow. The default config has been updated accordingly.
  • If CampaignTagSelector is enabled in ClientDefinitions.ini, CampaignSelector has a button available to return to it.
  • Map file section names CNCNET:MISSION:BATTLE.INI and CNCNET:MISSION:MISSION.INI have been changed to ClientMissionConfig and GameMissionConfig respectively.
  • The logic for reading the GameMissionConfig section written under scenario name in spawn.ini has been decoupled from custom mission logic for potential future proofing.
  • Client forces scenario names in spawn.ini to all-caps because game makes assumptions about them being so.
  • Added an option (ReturnToMainMenuOnMissionLaunch) to control whether or not client returns to main menu when launching a mission. Defaults to true.
  • Generalized the user setting control logic in CampaignSelector and added new INI key for them (ResetToDefaultOnGameExit) that if enabled resets the control and its associated setting to default value every time game process exits.

The spawner PR which is required for the custom mission logic to work correctly in YR has been updated accordingly as well, as has been Phobos' develop branch to improve compatibility with the show briefing feature.

There is currently no work done on any sort of campaign progression tracker or global variable toggles. They might be better saved for another PR anyway.

@SadPencil
Copy link
Member Author

图片

@Starkku Do you have a migration guide on CampaignSelector? Making it an INItializableWindow brings an incompatible change which modders may feel hard to follow. Consider that CampaignSelector is hardly modified, perhaps we can revert this change for a better compatibility, and delay the INItializableWindow migration to when the global variable things are migrated?

@nikolyn10
Copy link

For the purposes of legacy support and ease of use, it may be worth making the CampaignSelector able to create a default set of controls if it doesn't find the ones expected.

@SadPencil
Copy link
Member Author

For the purposes of legacy support and ease of use, it may be worth making the CampaignSelector able to create a default set of controls if it doesn't find the ones expected.

good thought

@Starkku is that possible?

@Starkku
Copy link
Contributor

Starkku commented Jun 2, 2025

Yes and no. Default settings are doable but partly defeat the purpose and other INItializableWindows do not do it. It could however also interact weirdly with any INI-provided customizations that are not using the INItializableWindow systems.

If it really is that big of an issue that example configs (both the TS client one already included if need be, one in Mod Base) do not solve then can consider it I guess. I would however probably just write a migration guide alongside other docs and provide example configs.

@Metadorius
Copy link
Member

Yes and no. Default settings are doable but partly defeat the purpose and other INItializableWindows do not do it. It could however also interact weirdly with any INI-provided customizations that are not using the INItializableWindow systems.

If it really is that big of an issue that example configs (both the TS client one already included if need be, one in Mod Base) do not solve then can consider it I guess. I would however probably just write a migration guide alongside other docs and provide example configs.

Back when INItializableWindow was provided for game lobbies it was proving to be somewhat problematic for non-IT people to migrate to if they had their own customisations applied.

Maybe something like an upgrade script could be provided? Or a legacy compatibility mode that is default off, or something like that.

@Starkku
Copy link
Contributor

Starkku commented Jun 2, 2025

Not enthusiastic towards either of those. Upgrade script would take way too much time to work out and take a lot of edge-cases into accord (especially since it would have to somehow know the built-in defaults in client for the legacy windows) and compatibility mode would just be extra complexity on client side. For me right now it is either full migration with maybe docs/example configs like it already has or I'll just revert the migration, I have no interest in taking any other approach at this point in time. If someone else wants to work on either of those be my guest, although I would advise against the legacy compatibility mode.

- btnReturn is now an optional control
- Fixed user setting controls not being handled correctly
- Added a client side-solution for save/load missing ingame strings for now
@SadPencil
Copy link
Member Author

SadPencil commented Jun 2, 2025

@Starkku I suggest revert the initializablewindow migration in CampaignSelector. We can perform this migration when global variables are introduced, in another PR

@SadPencil
Copy link
Member Author

SadPencil commented Jun 2, 2025

If the migration is not introduced now, this will be just an optional feature that people don't have to do anything to upgrade to this version. Otherwise we'll have to release 2.13.

Since 2.12.4 2.12.5 2.12.8 was the only "safe" version or a non-buggy version since 2.12.0 (the buggy here refers to some new bugs introduced in 2.12.0, not other undiscovered bugs or known bugs) so I want incoming 2.12.x versions a little bit longer to make people feel safe about upgrading the client

(safe: no breaking changes and no bugs on existing features that exist for a long time)

@Starkku
Copy link
Contributor

Starkku commented Jun 3, 2025

Reverted INItializableWindow change for CampaignSelector. Return to campaign tag selector button now requires explicit enabling via Enabled=true and Visible=true in INI even if campaign tag selector is enabled.

private CampaignTagSelector campaignTagSelector;

private List<Mission> Missions = new List<Mission>();
private List<Mission> lbCampaignListMissions = new List<Mission>();
Copy link
Member

Choose a reason for hiding this comment

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

this is not listbox, hence the lb prefix is incorrect

Copy link
Member Author

Choose a reason for hiding this comment

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

82ac513 Rename lbCampaignListMissions to selectedMissions

Comment on lines 38 to 44
// copy the CSF file if exists
foreach ((string ext, string filename) in new (string, string)[]
{
("csf", ClientConfiguration.Instance.CustomMissionCsfName),
("pal", ClientConfiguration.Instance.CustomMissionPalName),
("shp", ClientConfiguration.Instance.CustomMissionShpName),
})
Copy link
Member

Choose a reason for hiding this comment

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

how about making this customisable and not just csf, pal, shp? just need to ensure it doesn't override any client files accidentally

not sure if better done in this PR or left for another one

Copy link
Member Author

Choose a reason for hiding this comment

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

34a5675 Allow customized custom mission supplemental files

Comment on lines 47 to 48
if (SafePath.GetFile(SafePath.CombineFilePath(ProgramConstants.GamePath, sourceFileName)).Exists)
File.Copy(SafePath.CombineFilePath(ProgramConstants.GamePath, sourceFileName), SafePath.CombineFilePath(ProgramConstants.GamePath, filename));
Copy link
Member

Choose a reason for hiding this comment

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

we have linking now which would make sense if some additional stuff like custom assets is supplied, I think could use them here?

Copy link
Member Author

Choose a reason for hiding this comment

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

b051a1a Create hard links for supplemental mission files

@SadPencil
Copy link
Member Author

Error: D:\a\xna-cncnet-client\xna-cncnet-client\DXMainClient\Domain\CustomMissionHelper.cs(32,50): error CS0121: The call is ambiguous between the following methods or properties: 'string.Split(char[]?, StringSplitOptions)' and 'string.Split(string?, StringSplitOptions)' [D:\a\xna-cncnet-client\xna-cncnet-client\DXMainClient\DXMainClient.csproj::TargetFramework=net8.0]

didn't encounter this error on my machine. will workaround it anyway

@SadPencil SadPencil requested a review from Metadorius August 16, 2025 10:09
@Starkku
Copy link
Contributor

Starkku commented Aug 18, 2025

Looks good to me now. The supplementary file logic currently makes assumptions that there are no multiple files included with same file extension (it cannot handle this case gracefully) but I am unsure if this is required, only scenario I can think of is separate LS for the different resolutions, this is not required in YR but might in TS, uncertain how to solve that.

I took a liberty of adjusting the defaults for loading screen files to use the ones from the supplementary file definitions if undefined in mission config, as there is practically no reason for it to be customized in mission config most of the time when it should match whatever is in supplementary file definitions to work unless there is a loading screen with that name that is included in game/mod files (which will still work if you write that in the mission config).

@SadPencil
Copy link
Member Author

@Metadorius Can this PR be merged?

```ini
[Settings]
CustomMissionPath=Maps/CustomMissions ; path to the folder containing fan-made maps
CustomMissionSupplementDefinition=csf|stringtable99.csf|pal|custommission.pal|shp|custommission.shp ; supplement files that are supposed to be copied to the game folder when a custom mission is played
Copy link
Member

Choose a reason for hiding this comment

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

What is the reason to introduce new syntax like this? Can't you do the same with , as a separator or simple as separate keys?

Copy link
Member Author

Choose a reason for hiding this comment

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

This seems to be the only place where the list is processed as pairs. Therefore I want to use a different separator to notify modders. And | representing pairs is also used in Win32 OpenFileDialog

Copy link
Contributor

@Starkku Starkku Aug 19, 2025

Choose a reason for hiding this comment

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

The more confusing part is that the pairs are also separated from other pairs by |

The entire system also hinges on not defining more than one pair with same file extension. It cannot currently handle that.

Copy link
Member Author

@SadPencil SadPencil Aug 19, 2025

Choose a reason for hiding this comment

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

The more confusing part is that the pairs are also separated from other pairs by |

The "pairs are also separated from other pairs" matches the same convention in https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.filedialog.filter
e.g.,

dlg.Filter = "Word Documents|*.doc|Excel Worksheets|*.xls|PowerPoint Presentations|*.ppt" +
             "|Office Files|*.doc;*.xls;*.ppt" +
             "|All Files|*.*";

The entire system also hinges on not defining more than one pair with same file extension. It cannot currently handle that.

This can be ignored for now, left as another PR. The TS resolution things require a special handling so we can leave it as a future work.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think non-IT modders ever encountered that, so that diminishes the example. Why not define those all as separate keys?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think non-IT modders ever encountered that, so that diminishes the example. Why not define those all as separate keys?

Yeah, right. Regex is friendly to non-IT modders but "|" symbol is not.

Whatever. So what's the prefered way to express the setting? csf,stringtable99.csf,pal,custommission.pal,shp,custommission.shp or something?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, right. Regex is friendly to non-IT modders but "|" symbol is not.

I never claimed it is, regex is hostile to anyone, even IT people lol. In that case it provided flexbility though, in this case it doesn't provide flexibility.

I thought of something like

CustomMissionFile0Extension=csf
CustomMissionFile0CopyAs=stringtable99.csf
CustomMissionFile1Extension=...

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, right. Regex is friendly to non-IT modders but "|" symbol is not.

I never claimed it is, regex is hostile to anyone, even IT people lol. In that case it provided flexbility though, in this case it doesn't provide flexibility.

I thought of something like

CustomMissionFile0Extension=csf
CustomMissionFile0CopyAs=stringtable99.csf
CustomMissionFile1Extension=...

I would prefer the following, still making the setting in oneline

csf:stringtable99.csf, pal:custommission.pal

: or =

Copy link
Member

Choose a reason for hiding this comment

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

That is somewhat better because it's possible to add new parameters without breaking backwards compatibility, but I am still not a fan of cramming everything in one line, it's not like we're constrained on storage space.

@Metadorius
Copy link
Member

@Metadorius Can this PR be merged?

I've stolen some of the changes into my branch for campaign game options, I propose we merge that first and then rebase this one (if even needed), since my changes are a subset of those and doing things backwards would most likely guarantee a conflict.

@SadPencil SadPencil added this to the 2.13.0 milestone Aug 19, 2025
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.

4 participants