-
Notifications
You must be signed in to change notification settings - Fork 84
[ZH] Simulate Replays with CSV files #925
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8005a7b
c2f8d14
942e5e1
120fa4b
a77e2b3
f0057b7
3241aef
9fa390b
7900ab1
3499ccd
818161f
19b8a42
4e562e2
60789e6
3852327
8faca18
6b23bf8
c0671bd
dffca9e
6b3348f
a2e7c5a
efd7c14
4673944
9b7eadc
a82de61
4835c7f
7efcf7c
7352e5b
2f01c18
08fad6f
3873f69
eac7e88
c6ede9d
8c6bfef
95e6769
9820db2
1582909
dbeb149
b924e23
8875ac2
e493557
4f59916
1fc78f3
03bf7b3
7f44033
0b15614
417cc01
208365a
cc1c163
889fd8e
a969df5
cd802df
f640caa
12e00f7
e8b6be5
fe00285
1219542
4eb8988
03c530b
928c624
a872b93
c937813
2319046
8208f9d
a3c07e9
1b715d2
115b153
c2d58ac
283e5c6
8c9612b
6a362bd
1558a3a
568832b
d90e11a
ed64972
d660a93
020ee42
b7d559d
702dfa0
1e70e82
6ddfdf7
30fc12f
b24eca8
d3febea
b3243c8
c353b94
0a63c92
5fced5b
2d2576f
89431bf
87462b7
dbf9d82
a1266af
6d5b60c
754b054
a404c92
7951bc4
104a25b
a3a21b9
cb021ff
a4bf57f
a0ecf5a
ee4a554
f80d432
6b72afd
7dbe6f7
ede82f8
118ef4d
e2947b4
05ca54e
df74c7f
1a7d27a
06cea29
6315f27
a417c7a
0f9a0cd
aaaff0c
c6e803b
b2ab15e
939cda6
97d90b7
609d08b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/* | ||
** Command & Conquer Generals Zero Hour(tm) | ||
** Copyright 2025 TheSuperHackers | ||
** | ||
** This program is free software: you can redistribute it and/or modify | ||
** it under the terms of the GNU General Public License as published by | ||
** the Free Software Foundation, either version 3 of the License, or | ||
** (at your option) any later version. | ||
** | ||
** This program is distributed in the hope that it will be useful, | ||
** but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
** GNU General Public License for more details. | ||
** | ||
** You should have received a copy of the GNU General Public License | ||
** along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#pragma once | ||
|
||
|
||
bool WriteOutReplayList(AsciiString relativeFolder); | ||
bool ReadReplayListFromCsv(AsciiString filename, std::vector<AsciiString>* replayList); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. camelCase function name |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
#include "Common/Recorder.h" | ||
#include "Common/version.h" | ||
#include "GameClient/ClientInstance.h" | ||
#include "Common/ReplayListCsv.h" | ||
#include "GameClient/TerrainVisual.h" // for TERRAIN_LOD_MIN definition | ||
#include "GameClient/GameText.h" | ||
#include "GameNetwork/NetworkDefs.h" | ||
|
@@ -442,6 +443,40 @@ Int parseReplay(char *args[], int num) | |
return 1; | ||
} | ||
|
||
Int parseSimReplayList(char *args[], int num) | ||
{ | ||
if (num > 1) | ||
{ | ||
AsciiString filename = args[1]; | ||
bool success = ReadReplayListFromCsv(filename, &TheWritableGlobalData->m_simulateReplays); | ||
if (!success) | ||
{ | ||
printf("Cannot open csv file: \"%s\"\n", filename.str()); | ||
exit(1); | ||
} | ||
TheWritableGlobalData->m_playIntro = FALSE; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The code starting from here is identical to the one in the function above. This can be refactored to reuse under a common name, for example "prepareClientForReplaySimulation()". |
||
TheWritableGlobalData->m_afterIntro = TRUE; | ||
TheWritableGlobalData->m_playSizzle = FALSE; | ||
TheWritableGlobalData->m_shellMapOn = FALSE; | ||
|
||
// Make replay playback possible while other clients (possible retail) are running | ||
rts::ClientInstance::setMultiInstance(TRUE); | ||
rts::ClientInstance::skipPrimaryInstance(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is odd that this preparation for the client is done here in the command line parsing. So for example parsing the repeated -replay command line argument means this setup is being called many times, depending on how often it is used in the command line. I think this can be safely moved at the end of if (!TheGlobalData->m_replay.empty() || ...)
prepareClientForReplaySimulation() There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's just setting some global variables, no need to make a big thing out of that. |
||
return 2; | ||
} | ||
return 1; | ||
} | ||
|
||
Int parseWriteReplayList(char *args[], int num) | ||
{ | ||
if (num > 1) | ||
{ | ||
TheWritableGlobalData->m_writeReplayList = args[1]; | ||
TheWritableGlobalData->m_headless = TRUE; | ||
} | ||
return 1; | ||
} | ||
|
||
Int parseJobs(char *args[], int num) | ||
{ | ||
if (num > 1) | ||
|
@@ -1162,6 +1197,18 @@ static CommandLineParam paramsForStartup[] = | |
// (If you have 4 cores, call it with -jobs 4) | ||
// If you do not call this, all replays will be simulated in sequence in the same process. | ||
{ "-jobs", parseJobs }, | ||
|
||
// TheSuperHackers @feature helmutbuhler 28/04/2025 | ||
// Pass in a csv file to play back multiple replays. The file must be in the replay folder. | ||
{ "-replayList", parseSimReplayList }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parseReplayList |
||
|
||
// TheSuperHackers @feature helmutbuhler 28/04/2025 | ||
// Write out information about all replays in a folder to a csv file. | ||
// Call it with -writeReplayList . for all replays in the replay folder. | ||
// Call it with -writeReplayList folder for all replays in the folder subfolder. | ||
// The result will be saved in replay_list.csv in that folder. | ||
// todo: this is a bit unintuitive. Maybe use ReplaySimulation::resolveFilenameWildcards for this? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the plan here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought maybe we combine -writeReplayList with -replay, so you can call it like: |
||
{ "-writeReplayList", parseWriteReplayList }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect this lives in the client so that maybe there can also be a debug button in the UI to generate this replay list? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nah, I was just too lazy to make it a separate tool. But we can also add a button for it. |
||
}; | ||
|
||
// These Params are parsed during Engine Init before INI data is loaded | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
|
||
#include "Common/GameEngine.h" | ||
#include "Common/ReplaySimulation.h" | ||
#include "Common/ReplayListCsv.h" | ||
|
||
|
||
/** | ||
|
@@ -46,6 +47,11 @@ Int GameMain() | |
{ | ||
exitcode = ReplaySimulation::simulateReplays(TheGlobalData->m_simulateReplays, TheGlobalData->m_simulateReplayJobs); | ||
} | ||
else if (!TheGlobalData->m_writeReplayList.isEmpty()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does this live here, inside GameMain, requiring whole GameEngine initialization? Shouldn't this be somewhere else, close to parsing the Command Line? I suspect There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I initially tried to init those things separately, but they kept piling up. You also need TheLocalFilesystem and I think some more. I think it's just simpler to just delay the writing like this. |
||
{ | ||
bool success = WriteOutReplayList(TheGlobalData->m_writeReplayList); | ||
exitcode = success ? 0 : 1; | ||
} | ||
else | ||
{ | ||
// run it | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TheSuperHackers epilogue can be omitted for simplicity. It is already marked as such at the Command Line argument entry.