Conversation
The Generals target does not compile. Each of these is a partially applied upstream change that left Generals inconsistent with itself, while GeneralsMD received the complete change. - Common/GameCommon.h includes "WWCommon.h" unqualified, which cannot resolve because only the WWVegas roots are on the include path. Qualifies it as "WWLib/WWCommon.h", matching what Core/GameEngine/Include/Common/GameCommon.h already does. - Common/GameEngine.h is missing the public access specifier, so the constructor, destructor and virtual overrides are all private and GameMain() cannot call init() or execute(). - GameLogic/GameLogic.h declares the game mode enum anonymously while the same header declares toString(GameMode) and getGameMode() returns GameMode, and m_gameMode is declared Int while GameLogic.cpp assigns it to a GameMode. Names the enum and corrects the member type to match GeneralsMD. Together these clear 452 of the 687 errors the target produced. The remainder are separate gaps and are not addressed here.
…rter StatsExporter.cpp is the only translation unit that includes both the engine headers and <zlib.h>. BaseTypeCore.h typedefs Byte as char while zlib's zconf.h typedefs it as unsigned char, so the file fails to compile whenever it is rebuilt. CompressionManager.cpp includes the same header without trouble because it never pulls in the engine types. zconf.h skips its own Byte typedef when __MACTYPES__ is defined, which is its documented hook for hosts that already provide the type. Only the gz* file API is used here and none of it mentions Byte or Bytef. Note that Bytef resolves to the engine's Byte as a result, so the byte oriented zlib calls must not be used in this file. Use core_compression for those. The comment at the include says so.
A defeated player in a LAN or online match is left sitting in a passive spectating state until they quit by hand. This adds an opt-in countdown that returns their client to the score screen on its own. Disabled by default. The player chooses a duration in the options menu: Off, 30 seconds, 1, 2, 3 or 5 minutes. The behaviour is purely local to the defeated player's own client. It changes no simulation state and is observable only by the player it happens to, so it carries no desync surface, needs no agreement between peers, and only automates the quit that player can already perform by hand. It is therefore driven by a local preference rather than the game options wire format, which leaves GameInfoToAsciiString, the replay header and the skirmish save version untouched. Countdown behaviour: - Runs in LAN and online games only. Skirmish is excluded because a defeated skirmish player restarts rather than sitting out a match. Campaign and replay playback are excluded by the same predicate. - Never arms while an ally is still alive. VictoryConditions marks a defeated player victorious if a surviving ally goes on to win, so leaving early would discard a win the player is still entitled to. Re-tested every frame rather than once at defeat, because with three or more alliances the local alliance can be wiped out while the match carries on between the others. - Never arms for observers. - Cancels once a single alliance remains, so it cannot race the normal end of match path into the score screen. - Frame based, so it freezes while the game is paused and follows the game speed setting rather than the wall clock. - Calls exitGame() rather than quit(), which would either open the quit menu or self destruct the player in a multiplayer game. - Latches after firing, because exitGame() only posts a deferred MSG_CLEAR_GAME_DATA and update() keeps running for several frames afterwards. The preference is resolved once during map load rather than at defeat time so the file read never lands mid match. Values are clamped on read, so a hand edited Options.ini cannot produce a pathological timer. The options menu control is null guarded on every use and the save path only writes when the control is present, so a client running against unmodified window assets degrades to the feature being off instead of crashing or silently resetting an existing preference. A duration in Options.ini that is not one of the offered ones is shown as an extra entry rather than being rewritten on save. Requires new window and string assets, see the pull request description.
Adds a host enforced auto-leave duration to the online lobby, set with
/autoleave <seconds> in the staging room. The host value wins when non zero.
Zero means the host does not enforce one and each player's own preference
applies, which is the default for a new lobby.
The value travels in the backend lobby record alongside the other host set
rules, not in GameInfoToAsciiString, so there is no wire format change. This
matters: ParseAsciiStringToGameInfo rejects the whole options string on an
unrecognised key, so adding a tag there would make a patched host's lobby
unjoinable and invisible to unpatched clients, and would change the replay
header. None of that applies here.
Client plumbing mirrors MaximumCameraHeight, which is the closest existing
precedent for a host set numeric lobby rule:
- ELobbyUpdateField::LOBBY_AUTO_LEAVE = 20
- NGMP_OnlineServices_LobbyInterface::UpdateCurrentLobby_AutoLeave()
- LobbyEntry::auto_leave_seconds, read at both lobby parse sites
- NGMPGame applies it to the GameInfo the gameplay code reads
- GameInfo carries it, deliberately outside the ascii options string and the
slot list xfer
Note that this client's copy of ELobbyUpdateField had drifted behind the
service, stopping at JOINABILITY = 18 while the service also defines
HOST_ACTION_BULK_SLOT_UPDATE = 19. Claiming 19 here would have collided with
that handler, so auto-leave takes 20. The comment at the enum records this.
The lobby parse sites use value("AutoLeaveSeconds", 0) rather than get_to(),
which throws on a missing key. This is deliberate and load bearing: it lets the
client ship before the service does, and a client talking to a service that does
not yet return the field simply sees zero and leaves the feature off. Do not
replace it with get_to() for consistency with the neighbouring lines.
Input is a chat command rather than a staging room control because the online
options row has roughly 50px of free width against the 200px a labelled combo
box needs, so a control would require shrinking the chat box. The fork already
uses this pattern for host settings, see /maxcameraheight and /friendsonly.
This is inert until the Generals Online service stores and echoes the field.
The contract is in the pull request description.
Note that auto-leave is advisory rather than enforceable, since the behaviour is
client side. A modified client can ignore it.
JawadYzbk
force-pushed
the
feature/auto-kick-defeat
branch
from
September 16, 2026 12:39
8b84c75 to
e0b4354
Compare
Author
|
after investigating, |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds an opt-in countdown that returns a defeated player's client to the score screen on its own, instead of leaving them sitting in a passive spectating state until they quit by hand.
Disabled by default. LAN and online games only.
Important
The host-enforced half is inert until the Generals Online service stores and echoes one new lobby field. The contract is in §6 below. The per-player half works today and needs nothing server side.
1. What it does
A player picks a duration in the options menu — Off, 30 seconds, 1, 2, 3 or 5 minutes. When they are defeated, their client counts down, announces it on screen, and then returns itself to the score screen.
An online lobby host can additionally enforce a duration for everyone in the lobby with
/autoleave <seconds>. The host value wins when non-zero; zero means the host does not enforce one and each player's own setting applies.Auto-leave is advisory, not enforceable — the behaviour is client side, so a modified client can ignore it. It is a convenience, not an anti-abuse measure.
2. Why a local preference rather than a game option
The effect — "my client returns to the score screen" — is observable only by the player it happens to. It changes no simulation state, so there is no desync surface and nothing for peers to agree on, and it only automates a quit that player can already perform by hand.
Routing it through
GameInfo+GameInfoToAsciiStringwould have cost a wire format break, a replay compatibility break, and aSkirmishGameInfo::xferversion bump, for no benefit. Concretely,ParseAsciiStringToGameInforejects the entire options string on an unrecognised key:So a new tag there would make a patched host's lobby unjoinable and invisible to unpatched clients, and would change the replay header. None of that applies to this change.
GameInfoToAsciiString, the replay header and the skirmish save version are all untouched.The host-enforced value travels in the backend lobby record alongside the other host-set rules, which is why it needs service work rather than a protocol change.
3. Countdown behaviour and guards
Implemented in
VictoryConditions, which already owns defeat state. Each guard exists for a specific reason:isInMultiplayerGame()is exactlyGAME_LAN || GAME_INTERNET, which also excludes campaign and replay playback.markAllianceVictorious()marks a defeated player victorious if a surviving ally wins. Leaving early would discard a win the player is still entitled to. Re-tested every frame, not once at defeat: with three or more alliances the local alliance can be wiped out while the match carries on between the others.cachePlayerPtrs()setsm_localPlayerDefeatedfor observers, so testingm_isObserveris load bearing.exitGame()races the normal end-of-match path into the score screen.TheGameInfopoints at the recorded game during playback, so a replay of a match the watched player lost would otherwise exit itself partway through.exitGame()only posts a deferredMSG_CLEAR_GAME_DATA;update()keeps running for several frames, and without the latch the countdown re-arms and re-announces on the way out.Other details:
exitGame(), notquit()—quit()would either open the quit menu or self destruct the player in a multiplayer game.Options.inicannot produce a pathological timer.4. Degrading against unmodified assets
The options menu control is null guarded on every use, and the save path only writes when the control is present — so a client running a patched exe against stock window assets degrades to the feature being off rather than crashing, and does not silently reset an existing preference to Off.
This is not hypothetical: existing code in the same area is not uniformly null safe (
LanGameOptionsMenu.cppdereferencescomboBoxStartingCashunguarded), and exe and assets do get out of sync in the field.A duration in
Options.inithat is not one of the offered ones is shown as an extra combo entry rather than being rewritten on save, so a hand-edited value round trips.5. Assets required
The
.wndand.csffiles are game data, not source, so they are not in this repository and not in this diff. Both are overridable with loose files —FileSystem::openFiletriesTheLocalFileSystembeforeTheArchiveFileSystem.Window layout —
Window\Menus\OptionsMenu.wnd(note:Window\, notData\Window\; layouts resolve asWindow\%srelative to the game root). Two controls appended as the last children ofScrollParent, in the free slot on theRetaliationrow:SCREENRECT(at800x600)OptionsMenu.wnd:StaticTextAutoLeaveOnDefeatSTATICTEXT387 326→507 350OptionsMenu.wnd:ComboBoxAutoLeaveOnDefeatCOMBOBOX511 326→631 350Cloned from
DetailLabelandComboBoxDetailin the same file so the styling matches, withMAXDISPLAY: 6so all six durations show without scrolling. Purely additive: 292 lines added, 0 removed, 0 changed.Strings — 10 labels appended to
generals.csf. Appending to the binary table preserves every existing string byte for byte and keeps per-language support; a plain-textData\Generals.strwould replace the whole table and pin the game to one language, sinceg_strFilehas no language slot whileg_csfFiledoes.GUI:AutoLeaveOnDefeatAuto-Leave:TOOLTIP:AutoLeaveOnDefeatLAN and online games only. After you are defeated, …GUI:AutoLeaveOnDefeatOffOffGUI:AutoLeaveOnDefeat3030 SecondsGUI:AutoLeaveOnDefeat601 MinuteGUI:AutoLeaveOnDefeat1202 MinutesGUI:AutoLeaveOnDefeat1803 MinutesGUI:AutoLeaveOnDefeat3005 MinutesGUI:AutoLeaveOnDefeatCustom%d SecondsGUI:AutoLeaveOnDefeatCountdownReturning to the score screen in %d secondsLabel order does not matter — the engine
qsorts the lookup table on load andbsearches it.Where these belong
Game data is not carried in this repository — it lives in the complementary assets project,
TheSuperHackers/GeneralsGamePatch, which already tracks 160.wndfiles underPatch104pZH/GameFilesEdited/Window/, includingWindow/Menus/OptionsMenu.wnd. So the layout change is a companion PR there, not part of this diff.I checked for a conflict: that repo's
OptionsMenu.wndis byte-identical to the stock archive copy apart from line endings (it is stored LF, the archive is CRLF), so there are no existing edits for these two controls to clash with, and the additions apply cleanly on top of their version.I have tooling for all of this — a BIG lister/extractor, an idempotent
.wndpatcher that works from anchors rather than line numbers, and a CSF reader/writer with a byte-identical round-trip gate. Happy to contribute it wherever it is wanted; it is kept out of this PR to keep the diff focused.6. Backend work required (Generals Online service)
Add one lobby field. Implement it as a structural copy of the existing
MaximumCameraHeightfield (update id17) — same permission check, validation, storage and broadcast path. The relevant service code isGenOnlineService/Controllers/Lobby/LobbyController.cs(theELobbyUpdateFielddispatch) andGenOnlineService/LobbyManager.cs(storage and broadcast).AutoLeaveSeconds(PascalCase, likeMaximumCameraHeight)auto_leave_seconds(snake_case, likemax_cam_height)uint16_t0(off)0–3600inclusiveThe casing asymmetry is deliberate and matches every other lobby field.
Update endpoint —
POST {API}/Lobby/{lobbyID}, claimingfield = 20(LOBBY_AUTO_LEAVE).Warning
The client's copy of
ELobbyUpdateFieldhad drifted behind the service: it stops atJOINABILITY = 18, whileServices/GenOnlineService/Controllers/Lobby/LobbyController.csalso definesHOST_ACTION_BULK_SLOT_UPDATE = 19. Claiming19would have collided with that handler. Auto-leave therefore takes20, and the comment at the client enum records why. Worth a second look from someone who knows whether the client is missing anything else from that contract.9and10remain reserved asUNUSED/UNUSED_2and must not be reused.{ "field": 20, "auto_leave_seconds": 60 }The service must reject non-host callers, reject values outside
0..3600(reject rather than clamp, so a modified or stale client gets an error instead of silently getting a different rule), store the value, and broadcast the updated lobby.Create endpoint — the
PUTbody now carries"auto_leave_seconds": 0. Accept and store; treat a missing key as0. New lobbies default to off.Read path — include
AutoLeaveSecondsanywhereMaximumCameraHeightalready appears: both the lobby list/browse response and the single-lobby detail response. Return0, nevernull.Deployment ordering — either half can ship first. The client reads the field with
value("AutoLeaveSeconds", 0)rather than a throwingget_to(), so a client talking to a service that does not yet return the field simply sees0and leaves the feature off. That accessor choice is load bearing; please do not "fix" it to match the neighbouring lines.Because
GeneralsOnlineDevelopmentTeam/Servicesis in this org, this can be a companion PR rather than an external dependency.7. Commits
build: Fix Generals game engine compile errorsbuild: Fix Byte redefinition between zlib and the engine in StatsExporterfeat: Add optional auto-leave on defeat countdownfeat: Let the online lobby host enforce the auto-leave duration/autoleave, host overrideThe two
build:commits are separable and can be split into their own PR if preferred. They are included because the targets do not otherwise compile:WWCommon.hinclude, a missingpublic:inGameEngine.h, and an anonymous game mode enum in a header that declarestoString(GameMode). These clear 452 of 687 errors; the remaining 235 are separate gaps and are not addressed here (chieflyUDPTransport.hliving only under GeneralsMD, and the body/damage module hierarchy missing virtuals its own subclasses declareoverrideon).Bytetypedef collision between the engine and vcpkg's zlib.8. Testing
Verified:
Options.iniand reloads..wndand.csfoverrides load correctly over the archives.Not yet verified:
/autoleaveparses and host-gates, but cannot round trip until the service supports field20.TheInGameUI->messagewith a%dargument is not used elsewhere in this codebase (existing messages use%ls). If the seconds render incorrectly the fix is to pre-format the value into the string.9. Generals parity
Zero Hour only, per
CONTRIBUTING.md. The Generals replica is not included because the Generals target does not currently compile beyond the fixes in this PR, and the feature depends on NGMP, which is Zero Hour only. The local-preference half would port cleanly once that target builds.