Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ebfb7d0
Implement a spawn.ini reader
ZivDero Aug 28, 2026
4feddad
Merge branch 'main' into cncnet
ZivDero Aug 29, 2026
5eec22b
Client launch (#75)
ZivDero Aug 29, 2026
d9e326d
Merge branch 'main' into cncnet
ZivDero Aug 29, 2026
bc471c8
Pin what the client launch path promises
ZivDero Aug 29, 2026
e3226f0
Hold the campaign difficulty to the settings the game has
ZivDero Aug 29, 2026
28cb381
Carry the campaign handicap pair on the session
ZivDero Aug 29, 2026
dc8c57e
Launch a campaign mission from a client's file
ZivDero Aug 29, 2026
8961ed2
Merge branch 'main' into cncnet
ZivDero Aug 30, 2026
91ac18b
Merge branch 'main' into cncnet
ZivDero Aug 30, 2026
d226c5d
Resume a saved game from a spawn request
ZivDero Aug 30, 2026
7960033
Do not repeat a score the game does not have
ZivDero Aug 30, 2026
be31071
Merge branch 'main' into cncnet
ZivDero Aug 30, 2026
4bcb208
Assemble a game against other machines up to its sockets
ZivDero Aug 30, 2026
fdcdb39
Wire a spawned game against other machines to its network
ZivDero Aug 30, 2026
45605e0
Resume a saved game against other machines from a launch file
ZivDero Aug 30, 2026
b246350
Trim the client launch comments to what the code cannot show
ZivDero Aug 30, 2026
f04bac9
Condense the client launch change records
ZivDero Aug 30, 2026
74bd1d0
Credit the spawners the launch file vocabulary comes from
ZivDero Aug 30, 2026
b665f76
Shorten the client launch change records
ZivDero Aug 30, 2026
75a5d9d
Merge branch 'main' into cncnet
ZivDero Aug 30, 2026
e877e07
Stop reading the launch keys the game will never take
ZivDero Aug 30, 2026
63bef66
Simplify switch
ZivDero Aug 30, 2026
eca0437
Merge remote-tracking branch 'origin/main' into cncnet
ZivDero Aug 31, 2026
d08d35f
CI fix
ZivDero Aug 31, 2026
d5636f4
Judge every launch value the game cannot survive
ZivDero Aug 31, 2026
f87f6b5
Turn the AI difficulty the right way round
ZivDero Aug 31, 2026
499918f
Carry the match options through a network resume
ZivDero Aug 31, 2026
95ab7af
Commit the session specials a spawned match plays under
ZivDero Aug 31, 2026
c7c3cc8
Bind the file's game speed on every launch kind
ZivDero Aug 31, 2026
b75c301
Clamp the loading screen to the sides that have art
ZivDero Aug 31, 2026
2cbe6e9
Reserve chosen start positions before anybody picks
ZivDero Aug 31, 2026
c78cbcb
Say the easiest opponent plays at the hardest setting
ZivDero Aug 31, 2026
a00ce53
Keep the map generator's settings reachable
ZivDero Aug 31, 2026
eb58ba6
Take the Save button off the dialog that cannot load
ZivDero Aug 31, 2026
f2b7273
Place the map settings helper above its own documentation
ZivDero Aug 31, 2026
2ff1ceb
Trim the loading screen comment
ZivDero Aug 31, 2026
2bd3a0d
Leave a node's address as its constructor made it
ZivDero Aug 31, 2026
8ecbaeb
Judge a seat by the name length the game already names
ZivDero Aug 31, 2026
ef6d2d7
Move define above unrelated comment
ZivDero Aug 31, 2026
4aeb13a
Leave the house sort as the unique colors it assumes
ZivDero Aug 31, 2026
74a0ac8
Say only what the alliance mask names
ZivDero Aug 31, 2026
d2f9396
Merge branch 'main' into cncnet
ZivDero Aug 31, 2026
56c6297
Tighten prose
ZivDero Sep 1, 2026
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
21 changes: 21 additions & 0 deletions code/gamedirs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ static char const * const DefaultSearchFolders = "INI,MIX,Maps";

static char const * const ConfigName = "OPENTS.INI";

/*
* The folder saved games are kept in, under whichever directory the player's own files go.
*/
static char const * const SavedGamesFolder = "Saved Games";

/*
* The folders the configuration itself is looked for in, relative to the data directory.
*/
Expand Down Expand Up @@ -314,6 +319,22 @@ std::string User_File_Write_Name(char const * filename)
}


/// <summary>
/// Names a saved game inside the folder they are kept in. The folder is not one of the
/// searched ones and is created here, so a launcher can browse it before the first save is
/// written.
/// </summary>
/// <returns>The name to open, delete or scan for.</returns>
std::string Saved_Game_Name(char const * filename)
{
std::string const folder = UserDirectory + SavedGamesFolder;

CreateDirectory(folder.c_str(), NULL);

return(folder + '\\' + filename);
}


static void Scan_Folder(char const * prefix, char const * pattern, std::vector<std::string> & names)
{
std::string const search = std::string(prefix) + pattern;
Expand Down
6 changes: 6 additions & 0 deletions code/gamedirs.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,11 @@ char const * Game_Directory_Error(void);
*/
std::string User_File_Write_Name(char const * filename);

/*
* Where the player's saved games are. They are never searched for: the folder is named
* outright wherever a saved game is opened, listed or removed.
*/
std::string Saved_Game_Name(char const * filename);

std::vector<std::string> Parse_Search_Folders(char const * list);
std::vector<std::string> Search_Files(char const * pattern);
2 changes: 2 additions & 0 deletions code/house.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ HouseClass::HouseClass(HouseTypeClass const * type) :
BuildingsLost(0),
WhoLastHurtMe(HOUSE_NONE),
Center(0,0,0),
SpawnWaypoint(-1),
Radius(0),
LATime(0),
LAEnemy(HOUSE_NONE),
Expand Down Expand Up @@ -6491,6 +6492,7 @@ void HouseClass::Serialize(SaveStreamClass & stream)
stream.Serialize(EnemyAirForcePrediction);
stream.Serialize(EnemyInfantryForcePrediction);
stream.Serialize(PowerSurplus);
stream.Serialize(SpawnWaypoint);
}


Expand Down
3 changes: 1 addition & 2 deletions code/house.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,6 @@ class ObjectTypeClass;
class SaveStreamClass;
template<class T> class DynamicVectorClass;

#define HOUSE_NAME_MAX 20


/****************************************************************************
** Certain aspects of the house "country" are initially set by the scenario
Expand Down Expand Up @@ -565,6 +563,7 @@ class HouseClass : public AbstractClass
** the base.
*/
Coord Center; // Center of the base.
int SpawnWaypoint; // starting waypoint this house was placed at; -1 = never placed
int Radius; // Average building distance from center (leptons).
struct {
int AirDefense;
Expand Down
2 changes: 2 additions & 0 deletions code/house.hh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#pragma once


#define HOUSE_NAME_MAX 20

/**********************************************************************
** The houses that can be played are listed here. Each has their own
** personality and strengths.
Expand Down
108 changes: 67 additions & 41 deletions code/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
#include "scheme.h"
#include "script.h"
#include "session.h"
#include "spawner.h"
#include "side.h"
#include "skirmish.h"
#include "smudtype.h"
Expand Down Expand Up @@ -413,18 +414,20 @@ int Init_Game(int , char * [])
/*
** Play the startup animation.
*/
if (Special.IsFromInstall == true) {
DebugString("Playing first time intro sequence.\n");
Play_Movie("EVA.VQA", THEME_NONE, false);
}
if (!Spawner_Is_Requested()) {
if (Special.IsFromInstall == true) {
DebugString("Playing first time intro sequence.\n");
Play_Movie("EVA.VQA", THEME_NONE, false);
}

DebugString("Playing startup movies.\n");
Play_Movie("WWLOGO.VQA", THEME_NONE);
if (!Get_New_Menu()->MixFile) {
if (CCFileClass("FS_TITLE.VQA").Is_Available() == true) {
Play_Movie("FS_TITLE.VQA", THEME_NONE, false);
} else {
Play_Movie("STARTUP.VQA", THEME_NONE, false);
DebugString("Playing startup movies.\n");
Play_Movie("WWLOGO.VQA", THEME_NONE);
if (!Get_New_Menu()->MixFile) {
if (CCFileClass("FS_TITLE.VQA").Is_Available() == true) {
Play_Movie("FS_TITLE.VQA", THEME_NONE, false);
} else {
Play_Movie("STARTUP.VQA", THEME_NONE, false);
}
}
}

Expand Down Expand Up @@ -648,6 +651,21 @@ void Init_Campaigns(void)
}


/// <summary>
/// Reads the countries and the sides they belong to from the rules, so a house's side is
/// known before anything asks for it.
/// </summary>
void Prepare_Side_Roster(void)
{
Rule->Do_HouseTypes(*RuleINI);
Rule->Do_Sides(*RuleINI);

for (int index = 0; index < HouseTypes.Count(); index++) {
HouseTypes[index]->Read_INI(*RuleINI);
}
}


/// <summary>
/// Can this campaign be played with the addons that are enabled?
/// A base game campaign is offered only when no addon is running, and an addon's own
Expand Down Expand Up @@ -1072,6 +1090,18 @@ bool Select_Game(bool )
}
}

/*
* A launch replaces the menu once. An ended match or a refusal answers false, so the
* process exits and the client sees it go.
*/
if (Spawner_Is_Requested()) {
if (!Spawner_Prepare(gameloaded)) {
return(false);
}
process = false;
Theme.Stop(true);
}

while (process) {

/*
Expand Down Expand Up @@ -1118,33 +1148,6 @@ bool Select_Game(bool )
break;
}

switch (Options.Difficulty) {
case 0:
Scen->CDifficulty = DIFF_HARD;
Scen->Difficulty = DIFF_EASY;
break;

case 1:
Scen->CDifficulty = DIFF_HARD;
Scen->Difficulty = DIFF_NORMAL;
break;

case 2:
Scen->CDifficulty = DIFF_NORMAL;
Scen->Difficulty = DIFF_NORMAL;
break;

case 3:
Scen->CDifficulty = DIFF_EASY;
Scen->Difficulty = DIFF_NORMAL;
break;

case 4:
Scen->CDifficulty = DIFF_EASY;
Scen->Difficulty = DIFF_HARD;
break;
}

Theme.Stop(true);

int timeout = (TickCount + 5 * TIMER_SECOND);
Expand Down Expand Up @@ -1387,8 +1390,14 @@ bool Select_Game(bool )
Session.PlayerIsGDI = stricmp(HouseTypes[Session.Players[0]->Player.House]->Name(), "GDI") == 0;
}

if (Session.Type != GAME_NORMAL || Debug_ForceScenario || Session.Play) {
if (!Start_Scenario(Scen->ScenarioName, true, CAMPAIGN_NONE)) {
// The menu sets the difficulty pair on every path but a client launch, which chose it.
if (!Spawner_Is_Active()) {
Session.CampaignDifficulty = (DiffType)Options.Difficulty;
Session.CampaignCDifficulty = (DiffType)(DIFF_COUNT - 1 - Options.Difficulty);
}

if (Session.Type != GAME_NORMAL || Debug_ForceScenario || Session.Play || Spawner_Is_Active()) {
if (!Start_Scenario(Scen->ScenarioName, true, Spawner_Is_Active() ? Scen->Campaign : CAMPAIGN_NONE)) {
if (Debug_Map) {
return(false);
} else {
Expand All @@ -1405,6 +1414,13 @@ bool Select_Game(bool )
}
}

// The mission read clears these, so a launch file's carried-over flags are set after it.
if (Spawner_Is_Active() && Session.Type == GAME_NORMAL) {
for (int index = 0; index < ARRAY_SIZE(Environment.Globals); index++) {
Scen->Set_Global_To(index, Environment.Globals[index]);
}
}

/*
** Save initialization values if we're recording this game.
*/
Expand Down Expand Up @@ -1656,6 +1672,12 @@ bool Parse_Command_Line(int argc, char * argv[])
continue;
}

// A client asking the game to launch what SPAWN.INI describes.
if (stricmp(string, "-SPAWN") == 0) {
Spawner_Request();
continue;
}

if (memcmp(string, "-TIME=", 6) == 0) {
sscanf(&string[6], "%d", &TournamentTime);
}
Expand Down Expand Up @@ -1831,7 +1853,11 @@ void Init_Random(void)
** a recording; the random number generator is initialized by loading
** the game.
*/
if (Session.LoadGame || Session.Play) {
if (Session.LoadGame) {
return;
}

if (Session.Play) {
Scen->RandomNumber = Seed;
NonCriticalRandomNumber = Seed;
DebugString("Seed is %08x\n", Seed);
Expand Down
2 changes: 2 additions & 0 deletions code/init.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ void Title_Screen_Restore(bool force=false);

void Init_Campaigns(void);

void Prepare_Side_Roster(void);

void Delete_All_Objects(void);

void Init_Theater(TheaterType theater);
Expand Down
8 changes: 5 additions & 3 deletions code/language/language.rc
Original file line number Diff line number Diff line change
Expand Up @@ -1426,11 +1426,13 @@ STYLE WS_CHILD
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "Resume Mission",IDC_RESUME_MISSION,"Button",
BS_OWNERDRAW,120,48,99,14
BS_OWNERDRAW,120,59,99,14
CONTROL "Game Controls",IDC_GAME_CONTROLS,"Button",BS_OWNERDRAW,
120,12,99,14
120,8,99,14
CONTROL "Save Game",IDC_SAVE_GAME,"Button",BS_OWNERDRAW,
120,25,99,14
CONTROL "Abort Mission",IDC_ABORT_MISSION,"Button",BS_OWNERDRAW,
120,30,99,14
120,42,99,14
CONTROL "Slider1",IDC_GAME_SPEED_SLIDER,"msctls_trackbar32",
TBS_BOTH | TBS_NOTICKS,95,115,148,13
LTEXT "Game Speed",-1,39,115,58,13,SS_CENTERIMAGE | NOT
Expand Down
Loading