The gamespeed choices 5x to 20x are only available in replay mode, but it should also be possible to watch AIs play as quickly as the CPU allows.
Notice the check for g_IsReplay is removed, which has the nice side-effect of preventing fast-forward if someone decided to change the perspective to an AI or unassigned playerslot.
Details
- Reviewers
Grugnas - Commits
- rP20577: Allow fast-forwarding of matches with only AI players.
- Trac Tickets
- #4078
Convince yourself that you can fast forward in replays and fast forward in the gamesetup if no human is assigned.
Diff Detail
- Repository
- rP 0 A.D. Public Repository
- Lint
Automatic diff as part of commit; lint not applicable. - Unit
Automatic diff as part of commit; unit tests not applicable.
Event Timeline
binaries/data/mods/public/gui/common/functions_utility.js | ||
---|---|---|
262 ↗ | (On Diff #2387) | I'm thinking about common/gamesetup_util.js, which could also be used by campaigns f.e.. |
binaries/data/mods/public/gui/gamesetup/gamesetup.js | ||
2013 ↗ | (On Diff #2387) | We could probably move updatePlayerAssignmentChoices and updateGameSpeedChoices to new properties of g_Dropdowns and g_PlayerDropdowns, but for the latter it had the disadvantage of being called once for each playerslot needlessly (might be tolerable, or maybe we could add an early return) and the disadvantage to clog that array. |
Executing section Default... Executing section Source... Executing section JS... | | [NORMAL] ESLintBear (no-else-return): | | Unnecessary 'else' after 'return'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | 43| 43| return -1; | 44| 44| else if (lowerX > lowerY) | 45| 45| return 1; | 46| |- else | 47| |- return 0; | | 46|+ return 0; | 48| 47| } | 49| 48| | 50| 49| /** | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before '||'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | 263| 263| { | 264| 264| let noPlayers = g_GameAttributes.settings.PlayerData.every((pData, idx) => | 265| 265| !pData || | 266| |- pData.AI != "" || | | 266|+ pData.AI != "" || | 267| 267| Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != idx + 1)); | 268| 268| | 269| 269| return prepareForDropdown(g_Settings.GameSpeeds.filter(speed => !speed.FastForward || noPlayers)); | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before '1'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | 264| 264| let noPlayers = g_GameAttributes.settings.PlayerData.every((pData, idx) => | 265| 265| !pData || | 266| 266| pData.AI != "" || | 267| |- Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != idx + 1)); | | 267|+ Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != idx + 1)); | 268| 268| | 269| 269| return prepareForDropdown(g_Settings.GameSpeeds.filter(speed => !speed.FastForward || noPlayers)); | 270| 270| } binaries/data/mods/public/gui/common/functions_utility.js | 212| » }·catch·(e)·{ | | [NORMAL] ESLintBear (no-empty): | | Empty block statement. binaries/data/mods/public/gui/common/functions_utility.js | 176| » » if·(word.toLowerCase().indexOf(lastWord.toLowerCase())·!=·0) | | [NORMAL] JSHintBear: | | Use '!==' to compare with '0'. binaries/data/mods/public/gui/common/functions_utility.js | 266| » » pData.AI·!=·""··|| | | [NORMAL] JSHintBear: | | Use '!==' to compare with ''. | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before '+'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/session/session.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/session/session.js |1605|1605| |1606|1606| playerStatistics.economyScore += total + ","; |1607|1607| playerStatistics.militaryScore += Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + |1608| |- player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; | |1608|+ player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; |1609|1609| playerStatistics.totalScore += (total + Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + |1610|1610| player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10)) + ","; |1611|1611| binaries/data/mods/public/gui/session/session.js | 405| » » colorizeHotkey("%(hotkey)s"·+·"·",·"selection.idleworker")·+ | | [NORMAL] ESLintBear (no-useless-concat): | | Unexpected string concatenation of literals. binaries/data/mods/public/gui/session/session.js | 425| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·panelEnt.slot·!==·undefined·&&·panelEnt.slot·==·slot); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 436| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·panelEnt.slot·!==·undefined·&&·panelEnt.slot·==·slot); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 965| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·ent·==·panelEnt.ent); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 994| » let·getPanelEntNameTooltip·=·panelEntState·=>·"[font=\"sans-bold-16\"]"·+·template.name.specific·+·"[/font]"; | | [NORMAL] ESLintBear (no-shadow): | | 'panelEntState' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1070| » » button.onpress·=·(function(i)·{·return·function()·{·performGroup((Engine.HotkeyIsPressed("selection.add")·?·"add"·:·"select"),·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1071| » » button.ondoublepress·=·(function(i)·{·return·function()·{·performGroup("snap",·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1072| » » button.onpressright·=·(function(i)·{·return·function()·{·performGroup("breakUp",·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 837| » » i·==·0·|| | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js | 839| » » g_GameAttributes.settings.PlayerData[i].AI·!=·""); | | [NORMAL] JSHintBear: | | Use '!==' to compare with ''. binaries/data/mods/public/gui/session/session.js | 941| » if·(direction·==·0) | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js | 965| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·ent·==·panelEnt.ent); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js | 989| » g_PanelEntities·=·g_PanelEntities.sort((panelEntA,·panelEntB)·=>·panelEntIndex(panelEntA.ent)·-·panelEntIndex(panelEntB.ent)) | | [NORMAL] JSHintBear: | | Missing semicolon. binaries/data/mods/public/gui/session/session.js |1069| » » button.hidden·=·g_Groups.groups[i].getTotalCount()·==·0; | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js |1070| » » button.onpress·=·(function(i)·{·return·function()·{·performGroup((Engine.HotkeyIsPressed("selection.add")·?·"add"·:·"select"),·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1071| » » button.ondoublepress·=·(function(i)·{·return·function()·{·performGroup("snap",·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1072| » » button.onpressright·=·(function(i)·{·return·function()·{·performGroup("breakUp",·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1077| » » » let·icon·=·GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre,·cur)·=>·{ | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1131| » » if·(player·!=·0·&& | | [NORMAL] JSHintBear: | | Use '!==' to compare with '0'. binaries/data/mods/public/gui/session/session.js |1227| » »
http://jw:8080/job/phabricator_lint/109/ for more details.
Executing section Default... Executing section Source... Executing section JS... | | [NORMAL] ESLintBear (no-else-return): | | Unnecessary 'else' after 'return'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/common/functions_utility.js | 43| 43| return -1; | 44| 44| else if (lowerX > lowerY) | 45| 45| return 1; | 46| |- else | 47| |- return 0; | | 46|+ return 0; | 48| 47| } | 49| 48| | 50| 49| /** binaries/data/mods/public/gui/common/functions_utility.js | 212| » }·catch·(e)·{ | | [NORMAL] ESLintBear (no-empty): | | Empty block statement. binaries/data/mods/public/gui/common/functions_utility.js | 176| » » if·(word.toLowerCase().indexOf(lastWord.toLowerCase())·!=·0) | | [NORMAL] JSHintBear: | | Use '!==' to compare with '0'. binaries/data/mods/public/gui/common/functions_utility.js | 266| » » pData.AI·!=·""·|| | | [NORMAL] JSHintBear: | | Use '!==' to compare with ''. | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before '+'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/session/session.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/session/session.js |1605|1605| |1606|1606| playerStatistics.economyScore += total + ","; |1607|1607| playerStatistics.militaryScore += Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + |1608| |- player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; | |1608|+ player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; |1609|1609| playerStatistics.totalScore += (total + Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + |1610|1610| player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10)) + ","; |1611|1611| binaries/data/mods/public/gui/session/session.js | 405| » » colorizeHotkey("%(hotkey)s"·+·"·",·"selection.idleworker")·+ | | [NORMAL] ESLintBear (no-useless-concat): | | Unexpected string concatenation of literals. binaries/data/mods/public/gui/session/session.js | 425| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·panelEnt.slot·!==·undefined·&&·panelEnt.slot·==·slot); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 436| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·panelEnt.slot·!==·undefined·&&·panelEnt.slot·==·slot); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 965| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·ent·==·panelEnt.ent); | | [NORMAL] ESLintBear (no-shadow): | | 'panelEnt' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 994| » let·getPanelEntNameTooltip·=·panelEntState·=>·"[font=\"sans-bold-16\"]"·+·template.name.specific·+·"[/font]"; | | [NORMAL] ESLintBear (no-shadow): | | 'panelEntState' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1070| » » button.onpress·=·(function(i)·{·return·function()·{·performGroup((Engine.HotkeyIsPressed("selection.add")·?·"add"·:·"select"),·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1071| » » button.ondoublepress·=·(function(i)·{·return·function()·{·performGroup("snap",·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js |1072| » » button.onpressright·=·(function(i)·{·return·function()·{·performGroup("breakUp",·i);·};·})(i); | | [NORMAL] ESLintBear (no-shadow): | | 'i' is already declared in the upper scope. binaries/data/mods/public/gui/session/session.js | 837| » » i·==·0·|| | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js | 839| » » g_GameAttributes.settings.PlayerData[i].AI·!=·""); | | [NORMAL] JSHintBear: | | Use '!==' to compare with ''. binaries/data/mods/public/gui/session/session.js | 941| » if·(direction·==·0) | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js | 965| » » let·panelEnt·=·g_PanelEntities.find(panelEnt·=>·ent·==·panelEnt.ent); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js | 989| » g_PanelEntities·=·g_PanelEntities.sort((panelEntA,·panelEntB)·=>·panelEntIndex(panelEntA.ent)·-·panelEntIndex(panelEntB.ent)) | | [NORMAL] JSHintBear: | | Missing semicolon. binaries/data/mods/public/gui/session/session.js |1069| » » button.hidden·=·g_Groups.groups[i].getTotalCount()·==·0; | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. binaries/data/mods/public/gui/session/session.js |1070| » » button.onpress·=·(function(i)·{·return·function()·{·performGroup((Engine.HotkeyIsPressed("selection.add")·?·"add"·:·"select"),·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1071| » » button.ondoublepress·=·(function(i)·{·return·function()·{·performGroup("snap",·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1072| » » button.onpressright·=·(function(i)·{·return·function()·{·performGroup("breakUp",·i);·};·})(i); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1077| » » » let·icon·=·GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre,·cur)·=>·{ | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1131| » » if·(player·!=·0·&& | | [NORMAL] JSHintBear: | | Use '!==' to compare with '0'. binaries/data/mods/public/gui/session/session.js |1227| » » button.onpress·=·(function(e)·{·return·function()·{·selectAndMoveTo(e);·};·})(researchStarted[tech].researcher); | | [NORMAL] JSHintBear: | | Don't make functions within a loop. binaries/data/mods/public/gui/session/session.js |1415| » » if·(+playerID·==·0) | | [NORMAL] JSHintBear: | | Use '===' to compare with '0'. | | [NORMAL] ESLintBear (no-undef-init): | | It's not necessary to initialize 'g_GameStanzaTimer' to undefined. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | 313| 313| /** | 314| 314| * Index of the GUI timer. | 315| 315| */ | 316| |-var g_GameStanzaTimer = undefined; | | 316|+var g_GameStanzaTimer; | 317| 317| | 318| 318| /** | 319| 319| * Only send a lobby update if something actually changed. | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before 'name'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | 899| 899| name = | 900| 900| '[color="' + | 901| 901| g_ReadyData[assignedGUID ? g_PlayerAssignments[assignedGUID].status : 2].color + | 902| |- '"]' + name + '[/color]'; | | 902|+ '"]' + name + '[/color]'; | 903| 903| | 904| 904| return name; | 905| 905| }, | | [NORMAL] ESLintBear (no-multi-spaces): | | Multiple spaces found before 'attribs'. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | 937| 937| | 938| 938| g_IsNetworked = attribs.type != "offline"; | 939| 939| g_IsController = attribs.type != "client"; | 940| |- g_IsTutorial = attribs.tutorial && attribs.tutorial == true; | | 940|+ g_IsTutorial = attribs.tutorial && attribs.tutorial == true; | 941| 941| g_ServerName = attribs.serverName; | 942| 942| g_ServerPort = attribs.serverPort; | 943| 943| g_StunEndpoint = attribs.stunEndpoint; | | [NORMAL] ESLintBear (no-undef-init): | | It's not necessary to initialize 'yPos' to undefined. |----| | /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js | |++++| /mnt/data/jenkins-phabricator/workspace/phabricator_lint/binaries/data/mods/public/gui/gamesetup/gamesetup.js |1138|1138| |1139|1139| function verticallyDistributeGUIObjects(parent, objectHeight, ignore) |1140|1140| { |1141| |- let yPos = undefined; | |1141|+ let yPos; |1142|1142| |1143|1143| let parentObject = Engine.GetGUIObjectByName(parent); |1144|1144| for (let child of parentObject.children) binaries/data/mods/public/gui/gamesetup/gamesetup.js | 689| » » » let·pData·=·playerData.find(pData·=>·sameColor(g_PlayerColorPickerList[selectedIdx],·pData.Color)); | | [NORMAL] ESLintBear (no-shadow): | | 'pData' is already declared in the upper scope. binaries/data/mods/public/gui/gamesetup/gamesetup.js |1573| » while·(g_IsNetworked) | | [NORMAL] ESLintBear (no-unmodified-loop-con
http://jw:8080/job/phabricator_lint/110/ for more details.
Build is green
Updating workspaces. Build (release)... Build (debug)... Running release tests... Running cxxtest tests (306 tests)..................................................................................................................................................................................................................................................................................................................OK! Running debug tests... Running cxxtest tests (306 tests)..................................................................................................................................................................................................................................................................................................................OK! Checking XML files...
http://jw:8080/job/phabricator/1450/ for more details.
Build is green
Updating workspaces. Build (release)... Build (debug)... Running release tests... Running cxxtest tests (306 tests)..................................................................................................................................................................................................................................................................................................................OK! Running debug tests... Running cxxtest tests (306 tests)..................................................................................................................................................................................................................................................................................................................OK! Checking XML files...
http://jw:8080/job/phabricator/1451/ for more details.
Grugnas found out that the session code doesn't work as advertized.
binaries/data/mods/public/gui/gamesetup/gamesetup.js | ||
---|---|---|
5 ↗ | (On Diff #2388) | const should be grouped |
- Only update in non-networked sessions, since gamespeed changes are not broadcasted
- Developer overlay player switching support
- Update on defeat/win
- Select default speed when fast-forward became disabled in the gamesetup using supplementDefaults
- Increase dropdown size in the gamsetup
- A rename in gamesetup
- Clean and simplify common function
Successful build - Chance fights ever on the side of the prudent.
Updating workspaces... Build (release)... Build (debug)... Running release tests... Running cxxtest tests (308 tests)....................................................................................................................................................................................................................................................................................................................OK! Running debug tests... Running cxxtest tests (308 tests)....................................................................................................................................................................................................................................................................................................................OK! Checking XML files...
@mimo I suspect you might want to use this feature and I suspect others don't trust me that I can find oversights on my own
Thanks for the test and the conversation Grugnas!
binaries/data/mods/public/gui/gamesetup/gamesetup.js | ||
---|---|---|
1532 ↗ | (On Diff #4437) | Going to allow players to watch bots in fast forward speed, even if they can't stop it in running games. |
binaries/data/mods/public/gui/session/menu.js | ||
995 ↗ | (On Diff #4437) | In networked games we can't change the gamespeed to begin with, so it doesn't matter if we show the fast forwarding options too, so removing the condition. |
binaries/data/mods/public/gui/session/session.js | ||
494 ↗ | (On Diff #4437) | (relevant in case of switching the perspective to observer using the developer overlay) |
551 ↗ | (On Diff #4437) | (relevant in case the singleplayer has finished) |