Page MenuHomeWildfire Games

Implement Single-Player (SP) campaign interface
Needs ReviewPublic

Authored by wraitii on Dec 26 2016, 3:32 PM.

Details

Reviewers
leper
elexis
Group Reviewers
Restricted Owners Package(Owns No Changed Paths)
Trac Tickets
#4387
Summary

Campaign implementation for Single Player.

In the interest of modulability, provide a basic campaign interface and a common core.

Files in gui/campaigns directly are the common modules (unconvinced about the value of a "common" folder here).

Files in gui/simple_campaign are the files for the "campaign menu" screen, similar to AoE 2's maps where you clicked on a specific scenario. This one is intended to be replaced/extended by basically all campaigns and is rather ugly and limited atm.

Files in gui/simple_setup are the files for the "start a new campaign" screen, to select which campaign you want to start. This one may or may not get extended by mods, but for "pure" campaigns I don't think it'd be necessary.

Open questions:

  • I suppose campaign maps should go into their own folder, should that folder be inside the public/campaigns/ folder?
  • Where do we store campaign images?
Test Plan

Test the interface I implemented, review the code, review the architecture of Gui files. Decide on initial campaign tutorials?
Please also highlight any potential MP pitfall that we may have missed, so far I believe only the gamesetup would need an update.

Diff Detail

Repository
rP 0 A.D. Public Repository
Branch
D11_campaigns
Lint
Lint OK
Unit
No Unit Test Coverage
Build Status
Buildable 6729
Build 11066: Vulcan BuildJenkins
Build 11065: arc lint + arc unit

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
wraitii added inline comments.Dec 5 2017, 5:27 PM
binaries/data/mods/public/campaigns/tutorial.json
17

Will check, don't remember.

binaries/data/mods/public/gui/campaign/campaign.js
50

TBH I find the imperative way easier to read, but it's probably through habit.

binaries/data/mods/public/gui/campaign/load.js
107

Pretty sure it bugged out at some point and thus I added this, but it can probably be removed, yes.

binaries/data/mods/public/maps/tutorials/Introductory_Tutorial.js
322 ↗(On Diff #4508)

Honestly have no idea, but it no longer works for sure. It's definitely a separate thing though.

elexis added a comment.Dec 5 2017, 5:38 PM
In D11#44238, @wraitii wrote:

Thanks for the comments, I'll probably make an improvement pass this WE.

The missing translation

I'm not familiar with the messages.json file, what's it used for?

Well first of all Internationalization_and_Localization or the brief summary in #4891.
Every developer must know how this file works, otherwise strings in new or moved files just won't be translated.
Should be a copy & paste job to add a path entry in messages.js by copying from something similar.

savegame restriction

Not entirely sure what you're referring to. I believe this supports loading and saving campaign games that work as campaign games (they might also appear in the save game list though atm, don't remember.)

The DelteFile proposed here can delete for instance the user.cfg. That should become impossible IMO. We should have DeleteSavegame and DeleteSavegameCampaign or something like that that.
JSInterface_VFS.cpp could have a DeleteFile function which fails if #define PathRestriction_ not met. And the path restriction would be campaign/ or whatever in this diff and savegames/ in SavedGame.cpp`.
This way we had only one function instead of two.

(Comments above still valid)

Did a quick test of the UI, seems okayish, but it's seems more maintainable in the long term to reuse the savegame dialog instead of having this a split screen with the level selection.
If the finicky things are fixed to, we might indeed enter the room where using the c-word might not receive inflammatory responses.
Or rather I could do some commits in github rather than posting comments here. (As all the copypasta was removed, it would be possible for me to do so without having to reverse engineer what was meant)

Main Menu:

  • "Load Game" in the main menu should become "Load Match" to be less ambiguous.
  • "New Campaign" should probably come before "Continue Campaign"

Savegames:

  • The savegame screen ingame should distinguish campaign savegames from skirmish savegames. Could be done later, but would be important.
  • I don't know if "Load Match" loading a savegame done while doing a campaign is and should be possible. If it were, it should ensure that the entire campaign state is loaded I presume.
  • Apparently bug: After having loaded a campaign state, I see "no saved games found", but I did save a match that was started from a campaign. It works when clicking on "continue campaign".
  • Not sure if a match-savegame selection screen should be combined with the level-of-campaign selection screen. Probably better to reuse the load dialog.

UI theme:
Last time it was discussed, the modern button theme is supposed to be considered deprecated and red buttons should be prefered, Unimportant and can be done afterwards.

binaries/data/mods/public/gui/campaign/campaign.js
3

Just var g_Foo; unless we need to distinguish between null and undefined.

binaries/data/mods/public/gui/campaign/campaign_io.js
8

"loadAvailableCampaigns"? (A campaign template sounds like a template for a campaign, i.e. it wouldn't be a campaign)

binaries/data/mods/public/gui/campaign/load.js
45

It should be like the replay menu - incompatible items should be gray while keeping the name

47

sprintf+translate

binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.js
33

= Object.keys(g_CampaignsAvailable).map(directory =>g_CampaignsAvailable[key].Name )

37

= Object.keys(g_CampaignsAvailable)

g_CampaignsAvailable returning an array instead of object would remove the Object.keys in both places

51

Should use lowerCamelCase or UpperCamelCase consistently, the same case that the majority of the GUI pages use

binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.xml
49

Campaign

binaries/data/mods/public/gui/gamesetup/gamesetup.js
238

I'd really love campaign maps that are random map scripts (like danubius), so that one can create a campaign that is different each time.
I guess it doesn't have to be done now, but it could become g_Autostart = { "mapType": scenario, "mapName": "file" }

binaries/data/mods/public/gui/pregame/mainmenu.js
47

button1.enabled = loadAvailableCampaignTemplates()).length;
button2.enabled = canLoadCurrentCampaignSave();

Ideally the function cannot return true if there aren't campaigns, otherwise we can still add button2.enabled = button1.enabled && canLoad.

binaries/data/mods/public/maps/tutorials/Introductory_Tutorial.js
322 ↗(On Diff #4508)

mimo had committed this fix

source/ps/scripting/JSInterface_VFS.cpp
279
# can be extended later for skirmish savegames
#define PathRestriction_Deletion {L"campaignsaves/"}

bool JSI_VFS::DeleteFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath)
	if (!PathRestrictionMet(pCxPrivate, PathRestriction_Deletion, filePath))
		return false;

Can you update the branch? I'll add it as a remote and probably send pull requests rather than requests for changes, since the patch looks ok since the last updae.

wraitii added a comment.EditedJan 21 2018, 5:31 PM

@elexis up to date and rebased on https://github.com/wraitii/0ad/tree/D11_campaigns

Feel free to commandeer the revision and push your changes on phabricator too.

(the first two commits are custom commits though, not related to the campaign patch.)

elexis updated the Trac tickets for this revision.Mar 6 2018, 12:54 PM
wraitii updated this revision to Diff 7277.Jan 5 2019, 7:16 PM
wraitii marked 37 inline comments as done.

Rebase, address the bulk of elexis' comments above. Github branch up-to-date https://github.com/wraitii/0ad/tree/D11_campaigns .

Vulcan added a comment.Jan 5 2019, 7:23 PM

Successful build - Chance fights ever on the side of the prudent.

Linter detected issues:
Executing section Source...

source/ps/scripting/JSInterface_VFS.h
|   1| /*·Copyright·(C)·2018·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2019" year instead of "2018"

source/ps/scripting/JSInterface_VFS.cpp
|   1| /*·Copyright·(C)·2018·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2019" year instead of "2018"

source/ps/GameSetup/GameSetup.cpp
|   1| /*·Copyright·(C)·2018·Wildfire·Games.
|    | [NORMAL] LicenseYearBear:
|    | License should have "2019" year instead of "2018"
Executing section JS...
|    | [NORMAL] ESLintBear (no-trailing-spaces):
|    | Trailing spaces not allowed.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|  45|  45| 		warn("Campaign template " + name + " is not a valid campaign template.");
|  46|  46| 		return false;
|  47|  47| 	}
|  48|    |-	
|    |  48|+
|  49|  49| 	g_CampaignTemplate = data;
|  50|  50| 
|  51|  51| 	return true;
|    | [NORMAL] ESLintBear (semi):
|    | Missing semicolon.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|  86|  86| 	let campaignData = loadCampaignSave(g_CampaignSaveFilename);
|  87|  87| 	if (!campaignData)
|  88|  88| 	{
|  89|    |-		warn("Campaign failed to load properly. Quitting campaign mode.")
|    |  89|+		warn("Campaign failed to load properly. Quitting campaign mode.");
|  90|  90| 		return false;
|  91|  91| 	}
|  92|  92| 
|    | [NORMAL] ESLintBear (key-spacing):
|    | Extra space after key 'ID'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|  97|  97| 		return false;
|  98|  98| 
|  99|  99| 	Engine.SwitchGuiPage("campaign/" + g_CampaignTemplate.Interface + "/page_menu.xml", {
| 100|    |-		"ID" : g_CampaignID,
|    | 100|+		"ID": g_CampaignID,
| 101| 101| 		"template" : g_CampaignTemplate,
| 102| 102| 		"save": g_CampaignSaveFilename,
| 103| 103| 		"data" : g_CampaignState
|    | [NORMAL] ESLintBear (key-spacing):
|    | Extra space after key 'template'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|  98|  98| 
|  99|  99| 	Engine.SwitchGuiPage("campaign/" + g_CampaignTemplate.Interface + "/page_menu.xml", {
| 100| 100| 		"ID" : g_CampaignID,
| 101|    |-		"template" : g_CampaignTemplate,
|    | 101|+		"template": g_CampaignTemplate,
| 102| 102| 		"save": g_CampaignSaveFilename,
| 103| 103| 		"data" : g_CampaignState
| 104| 104| 	});
|    | [NORMAL] ESLintBear (key-spacing):
|    | Extra space after key 'data'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
| 100| 100| 		"ID" : g_CampaignID,
| 101| 101| 		"template" : g_CampaignTemplate,
| 102| 102| 		"save": g_CampaignSaveFilename,
| 103|    |-		"data" : g_CampaignState
|    | 103|+		"data": g_CampaignState
| 104| 104| 	});
| 105| 105| 
| 106| 106| 	return true;
|    | [NORMAL] ESLintBear (semi-spacing):
|    | Missing whitespace after semicolon.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
| 121| 121| 
| 122| 122| function loadCampaignSave(filename)
| 123| 123| {
| 124|    |-	return Engine.ReadJSONFile("campaignsaves/" + filename + ".0adcampaign");;
|    | 124|+	return Engine.ReadJSONFile("campaignsaves/" + filename + ".0adcampaign"); ;
| 125| 125| }
|    | [NORMAL] ESLintBear (no-extra-semi):
|    | Unnecessary semicolon.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/campaign_io.js
| 121| 121| 
| 122| 122| function loadCampaignSave(filename)
| 123| 123| {
| 124|    |-	return Engine.ReadJSONFile("campaignsaves/" + filename + ".0adcampaign");;
|    | 124|+	return Engine.ReadJSONFile("campaignsaves/" + filename + ".0adcampaign");
| 125| 125| }

binaries/data/mods/public/gui/campaign/campaign_io.js
| 124| »   return·Engine.ReadJSONFile("campaignsaves/"·+·filename·+·".0adcampaign");;
|    | [NORMAL] ESLintBear (no-unreachable):
|    | Unreachable code.

binaries/data/mods/public/gui/campaign/campaign_io.js
|  89| »   »   warn("Campaign·failed·to·load·properly.·Quitting·campaign·mode.")
|    | [NORMAL] JSHintBear:
|    | Missing semicolon.

binaries/data/mods/public/gui/campaign/campaign_io.js
| 124| »   return·Engine.ReadJSONFile("campaignsaves/"·+·filename·+·".0adcampaign");;
|    | [NORMAL] JSHintBear:
|    | Unreachable ';' after 'return'.

binaries/data/mods/public/gui/campaign/campaign_io.js
| 124| »   return·Engine.ReadJSONFile("campaignsaves/"·+·filename·+·".0adcampaign");;
|    | [NORMAL] JSHintBear:
|    | Unnecessary semicolon.
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  62|  62| var g_RomanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"];
|  63|  63| 
|  64|  64| var g_PlayerTeamList = prepareForDropdown([{
|  65|    |-		"label": translateWithContext("team", "None"),
|    |  65|+	"label": translateWithContext("team", "None"),
|  66|  66| 		"id": -1
|  67|  67| 	}].concat(
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  63|  63| 
|  64|  64| var g_PlayerTeamList = prepareForDropdown([{
|  65|  65| 		"label": translateWithContext("team", "None"),
|  66|    |-		"id": -1
|    |  66|+	"id": -1
|  67|  67| 	}].concat(
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|  69| 			"label": i + 1,
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 0 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  64|  64| var g_PlayerTeamList = prepareForDropdown([{
|  65|  65| 		"label": translateWithContext("team", "None"),
|  66|  66| 		"id": -1
|  67|    |-	}].concat(
|    |  67|+}].concat(
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|  69| 			"label": i + 1,
|  70|  70| 			"id": i
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  65|  65| 		"label": translateWithContext("team", "None"),
|  66|  66| 		"id": -1
|  67|  67| 	}].concat(
|  68|    |-		Array(g_MaxTeams).fill(0).map((v, i) => ({
|    |  68|+	Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|  69| 			"label": i + 1,
|  70|  70| 			"id": i
|  71|  71| 		}))
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  66|  66| 		"id": -1
|  67|  67| 	}].concat(
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|    |-			"label": i + 1,
|    |  69|+		"label": i + 1,
|  70|  70| 			"id": i
|  71|  71| 		}))
|  72|  72| 	)
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  67|  67| 	}].concat(
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|  69| 			"label": i + 1,
|  70|    |-			"id": i
|    |  70|+		"id": i
|  71|  71| 		}))
|  72|  72| 	)
|  73|  73| );
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  68|  68| 		Array(g_MaxTeams).fill(0).map((v, i) => ({
|  69|  69| 			"label": i + 1,
|  70|  70| 			"id": i
|  71|    |-		}))
|    |  71|+	}))
|  72|  72| 	)
|  73|  73| );
|  74|  74| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 0 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  69|  69| 			"label": i + 1,
|  70|  70| 			"id": i
|  71|  71| 		}))
|  72|    |-	)
|    |  72|+)
|  73|  73| );
|  74|  74| 
|  75|  75| /**
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  78|  78| var g_RelicCountList = Object.keys(g_CivData).map((civ, i) => i + 1);
|  79|  79| 
|  80|  80| var g_PlayerCivList = g_CivData && prepareForDropdown([{
|  81|    |-		"name": translateWithContext("civilization", "Random"),
|    |  81|+	"name": translateWithContext("civilization", "Random"),
|  82|  82| 		"tooltip": translate("Picks one civilization at random when the game starts."),
|  83|  83| 		"color": g_ColorRandom,
|  84|  84| 		"code": "random"
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  79|  79| 
|  80|  80| var g_PlayerCivList = g_CivData && prepareForDropdown([{
|  81|  81| 		"name": translateWithContext("civilization", "Random"),
|  82|    |-		"tooltip": translate("Picks one civilization at random when the game starts."),
|    |  82|+	"tooltip": translate("Picks one civilization at random when the game starts."),
|  83|  83| 		"color": g_ColorRandom,
|  84|  84| 		"code": "random"
|  85|  85| 	}].concat(
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  80|  80| var g_PlayerCivList = g_CivData && prepareForDropdown([{
|  81|  81| 		"name": translateWithContext("civilization", "Random"),
|  82|  82| 		"tooltip": translate("Picks one civilization at random when the game starts."),
|  83|    |-		"color": g_ColorRandom,
|    |  83|+	"color": g_ColorRandom,
|  84|  84| 		"code": "random"
|  85|  85| 	}].concat(
|  86|  86| 		Object.keys(g_CivData).filter(
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  81|  81| 		"name": translateWithContext("civilization", "Random"),
|  82|  82| 		"tooltip": translate("Picks one civilization at random when the game starts."),
|  83|  83| 		"color": g_ColorRandom,
|  84|    |-		"code": "random"
|    |  84|+	"code": "random"
|  85|  85| 	}].concat(
|  86|  86| 		Object.keys(g_CivData).filter(
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 0 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  82|  82| 		"tooltip": translate("Picks one civilization at random when the game starts."),
|  83|  83| 		"color": g_ColorRandom,
|  84|  84| 		"code": "random"
|  85|    |-	}].concat(
|    |  85|+}].concat(
|  86|  86| 		Object.keys(g_CivData).filter(
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|  88|  88| 		).map(civ => ({
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  83|  83| 		"color": g_ColorRandom,
|  84|  84| 		"code": "random"
|  85|  85| 	}].concat(
|  86|    |-		Object.keys(g_CivData).filter(
|    |  86|+	Object.keys(g_CivData).filter(
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|  88|  88| 		).map(civ => ({
|  89|  89| 			"name": g_CivData[civ].Name,
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  84|  84| 		"code": "random"
|  85|  85| 	}].concat(
|  86|  86| 		Object.keys(g_CivData).filter(
|  87|    |-			civ => g_CivData[civ].SelectableInGameSetup
|    |  87|+		civ => g_CivData[civ].SelectableInGameSetup
|  88|  88| 		).map(civ => ({
|  89|  89| 			"name": g_CivData[civ].Name,
|  90|  90| 			"tooltip": g_CivData[civ].History,
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  85|  85| 	}].concat(
|  86|  86| 		Object.keys(g_CivData).filter(
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|  88|    |-		).map(civ => ({
|    |  88|+	).map(civ => ({
|  89|  89| 			"name": g_CivData[civ].Name,
|  90|  90| 			"tooltip": g_CivData[civ].History,
|  91|  91| 			"color": g_ColorRegular,
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  86|  86| 		Object.keys(g_CivData).filter(
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|  88|  88| 		).map(civ => ({
|  89|    |-			"name": g_CivData[civ].Name,
|    |  89|+		"name": g_CivData[civ].Name,
|  90|  90| 			"tooltip": g_CivData[civ].History,
|  91|  91| 			"color": g_ColorRegular,
|  92|  92| 			"code": civ
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  87|  87| 			civ => g_CivData[civ].SelectableInGameSetup
|  88|  88| 		).map(civ => ({
|  89|  89| 			"name": g_CivData[civ].Name,
|  90|    |-			"tooltip": g_CivData[civ].History,
|    |  90|+		"tooltip": g_CivData[civ].History,
|  91|  91| 			"color": g_ColorRegular,
|  92|  92| 			"code": civ
|  93|  93| 		})).sort(sortNameIgnoreCase)
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  88|  88| 		).map(civ => ({
|  89|  89| 			"name": g_CivData[civ].Name,
|  90|  90| 			"tooltip": g_CivData[civ].History,
|  91|    |-			"color": g_ColorRegular,
|    |  91|+		"color": g_ColorRegular,
|  92|  92| 			"code": civ
|  93|  93| 		})).sort(sortNameIgnoreCase)
|  94|  94| 	)
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  89|  89| 			"name": g_CivData[civ].Name,
|  90|  90| 			"tooltip": g_CivData[civ].History,
|  91|  91| 			"color": g_ColorRegular,
|  92|    |-			"code": civ
|    |  92|+		"code": civ
|  93|  93| 		})).sort(sortNameIgnoreCase)
|  94|  94| 	)
|  95|  95| );
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  90|  90| 			"tooltip": g_CivData[civ].History,
|  91|  91| 			"color": g_ColorRegular,
|  92|  92| 			"code": civ
|  93|    |-		})).sort(sortNameIgnoreCase)
|    |  93|+	})).sort(sortNameIgnoreCase)
|  94|  94| 	)
|  95|  95| );
|  96|  96| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 0 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|  91|  91| 			"color": g_ColorRegular,
|  92|  92| 			"code": civ
|  93|  93| 		})).sort(sortNameIgnoreCase)
|  94|    |-	)
|    |  94|+)
|  95|  95| );
|  96|  96| 
|  97|  97| /**
|    | [NORMAL] ESLintBear (no-trailing-spaces):
|    | Trailing spaces not allowed.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|1240|1240| 			offset = -Math.min(slideSpeed * dt, maxOffset);
|1241|1241| 	}
|1242|1242| 
|1243|    |-	updateSettingsPanelPosition(offset);	
|    |1243|+	updateSettingsPanelPosition(offset);
|1244|1244| }
|1245|1245| 
|1246|1246| /**
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/gamesetup/gamesetup.js
|1771|1771| 	let biomeList;
|1772|1772| 
|1773|1773| 	if (g_GameAttributes.mapType == "random" && g_GameAttributes.settings.SupportedBiomes)
|1774|    |-	{
|    |1774|+	
|1775|1775| 		if (typeof g_GameAttributes.settings.SupportedBiomes == "string")
|1776|1776| 			biomeList = g_Settings.Biomes.filter(biome => biome.Id.startsWith(g_GameAttributes.settings.SupportedBiomes));
|1777|1777| 		else
|1778|1778| 			biomeList = g_Settings.Biomes.filter(
|1779|1779| 				biome => g_GameAttributes.settings.SupportedBiomes.indexOf(biome.Id) != -1);
|1780|    |-	}
|    |1780|+	
|1781|1781| 
|1782|1782| 	g_BiomeList = biomeList && prepareForDropdown(
|1783|1783| 		[{

binaries/data/mods/public/gui/gamesetup/gamesetup.js
|2015| »   while·(g_IsNetworked)
|    | [NORMAL] ESLintBear (no-unmodified-loop-condition):
|    | 'g_IsNetworked' is not modified in this loop.
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 3 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 491| 491| 	Engine.GetGUIObjectByName("summaryText").caption =
| 492| 492| 		g_GameData.gui.isInGame ?
| 493| 493| 			translate("Current Scores") :
| 494|    |-		g_GameData.gui.isReplay ?
|    | 494|+			g_GameData.gui.isReplay ?
| 495| 495| 			translate("Scores at the end of the game.") :
| 496| 496| 		g_GameData.gui.disconnected ?
| 497| 497| 			translate("You have been disconnected.") :
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 4 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 492| 492| 		g_GameData.gui.isInGame ?
| 493| 493| 			translate("Current Scores") :
| 494| 494| 		g_GameData.gui.isReplay ?
| 495|    |-			translate("Scores at the end of the game.") :
|    | 495|+				translate("Scores at the end of the game.") :
| 496| 496| 		g_GameData.gui.disconnected ?
| 497| 497| 			translate("You have been disconnected.") :
| 498| 498| 		!assignedState ?
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 4 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 493| 493| 			translate("Current Scores") :
| 494| 494| 		g_GameData.gui.isReplay ?
| 495| 495| 			translate("Scores at the end of the game.") :
| 496|    |-		g_GameData.gui.disconnected ?
|    | 496|+				g_GameData.gui.disconnected ?
| 497| 497| 			translate("You have been disconnected.") :
| 498| 498| 		!assignedState ?
| 499| 499| 			translate("You have left the game.") :
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 494| 494| 		g_GameData.gui.isReplay ?
| 495| 495| 			translate("Scores at the end of the game.") :
| 496| 496| 		g_GameData.gui.disconnected ?
| 497|    |-			translate("You have been disconnected.") :
|    | 497|+					translate("You have been disconnected.") :
| 498| 498| 		!assignedState ?
| 499| 499| 			translate("You have left the game.") :
| 500| 500| 		assignedState.state == "won" ?
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 495| 495| 			translate("Scores at the end of the game.") :
| 496| 496| 		g_GameData.gui.disconnected ?
| 497| 497| 			translate("You have been disconnected.") :
| 498|    |-		!assignedState ?
|    | 498|+					!assignedState ?
| 499| 499| 			translate("You have left the game.") :
| 500| 500| 		assignedState.state == "won" ?
| 501| 501| 			translate("You have won the battle!") :
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 6 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 496| 496| 		g_GameData.gui.disconnected ?
| 497| 497| 			translate("You have been disconnected.") :
| 498| 498| 		!assignedState ?
| 499|    |-			translate("You have left the game.") :
|    | 499|+						translate("You have left the game.") :
| 500| 500| 		assignedState.state == "won" ?
| 501| 501| 			translate("You have won the battle!") :
| 502| 502| 		assignedState.state == "defeated" ?
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 6 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 497| 497| 			translate("You have been disconnected.") :
| 498| 498| 		!assignedState ?
| 499| 499| 			translate("You have left the game.") :
| 500|    |-		assignedState.state == "won" ?
|    | 500|+						assignedState.state == "won" ?
| 501| 501| 			translate("You have won the battle!") :
| 502| 502| 		assignedState.state == "defeated" ?
| 503| 503| 			translate("You have been defeated…") :
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 7 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 498| 498| 		!assignedState ?
| 499| 499| 			translate("You have left the game.") :
| 500| 500| 		assignedState.state == "won" ?
| 501|    |-			translate("You have won the battle!") :
|    | 501|+							translate("You have won the battle!") :
| 502| 502| 		assignedState.state == "defeated" ?
| 503| 503| 			translate("You have been defeated…") :
| 504| 504| 			translate("You have abandoned the game.");
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 7 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 499| 499| 			translate("You have left the game.") :
| 500| 500| 		assignedState.state == "won" ?
| 501| 501| 			translate("You have won the battle!") :
| 502|    |-		assignedState.state == "defeated" ?
|    | 502|+							assignedState.state == "defeated" ?
| 503| 503| 			translate("You have been defeated…") :
| 504| 504| 			translate("You have abandoned the game.");
| 505| 505| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 8 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 500| 500| 		assignedState.state == "won" ?
| 501| 501| 			translate("You have won the battle!") :
| 502| 502| 		assignedState.state == "defeated" ?
| 503|    |-			translate("You have been defeated…") :
|    | 503|+								translate("You have been defeated…") :
| 504| 504| 			translate("You have abandoned the game.");
| 505| 505| 
| 506| 506| 	Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 8 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 501| 501| 			translate("You have won the battle!") :
| 502| 502| 		assignedState.state == "defeated" ?
| 503| 503| 			translate("You have been defeated…") :
| 504|    |-			translate("You have abandoned the game.");
|    | 504|+								translate("You have abandoned the game.");
| 505| 505| 
| 506| 506| 	Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
| 507| 507| 		translate("Game time elapsed: %(time)s"), {
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/summary/summary.js
| 506| 506| 	Engine.GetGUIObjectByName("timeElapsed").caption = sprintf(
| 507| 507| 		translate("Game time elapsed: %(time)s"), {
| 508| 508| 			"time": timeToString(g_GameData.sim.timeElapsed)
| 509|    |-	});
|    | 509|+		});
| 510| 510| 
| 511| 511| 	let mapType = g_Settings.MapTypes.find(type => type.Name == g_GameData.sim.mapSettings.mapType);
| 512| 512| 	let mapSize = g_Settings.MapSizes.find(size => size.Tiles == g_GameData.sim.mapSettings.Size || 0);
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js
| 145| 145| 	let dateString = sprintf(translate("\\[%(date)s]"), { "date": dateTimeString });
| 146| 146| 
| 147| 147| 	if (engineInfo)
| 148|    |-	{
|    | 148|+	
| 149| 149| 		if (!isCompatibleSavegame(metadata, engineInfo) || !hasSameEngineVersion(metadata, engineInfo))
| 150| 150| 			dateString = "[color=\"red\"]" + dateString + "[/color]";
| 151| 151| 		else if (!hasSameMods(metadata, engineInfo))
| 152| 152| 			dateString = "[color=\"orange\"]" + dateString + "[/color]";
| 153|    |-	}
|    | 153|+	
| 154| 154| 
| 155| 155| 	return sprintf(
| 156| 156| 		metadata.description ?
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 6 tabs but found 5.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
| 397| 397| 				// Players see colors depending on diplomacy
| 398| 398| 				g_DisplayedPlayerColors[i] =
| 399| 399| 					g_ViewedPlayer == i ? getDiplomacyColor("self") :
| 400|    |-					g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") :
|    | 400|+						g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") :
| 401| 401| 					g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") :
| 402| 402| 					getDiplomacyColor("enemy");
| 403| 403| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 7 tabs but found 5.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
| 398| 398| 				g_DisplayedPlayerColors[i] =
| 399| 399| 					g_ViewedPlayer == i ? getDiplomacyColor("self") :
| 400| 400| 					g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") :
| 401|    |-					g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") :
|    | 401|+							g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") :
| 402| 402| 					getDiplomacyColor("enemy");
| 403| 403| 
| 404| 404| 		g_DisplayedPlayerColors[0] = g_Players[0].color;
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 8 tabs but found 5.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
| 399| 399| 					g_ViewedPlayer == i ? getDiplomacyColor("self") :
| 400| 400| 					g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") :
| 401| 401| 					g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") :
| 402|    |-					getDiplomacyColor("enemy");
|    | 402|+								getDiplomacyColor("enemy");
| 403| 403| 
| 404| 404| 		g_DisplayedPlayerColors[0] = g_Players[0].color;
| 405| 405| 	}
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 4 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
| 655| 655| 					"civ": setStringTags(g_CivData[g_Players[g_ViewedPlayer].civ].Name, { "font": "sans-bold-stroke-14" }),
| 656| 656| 					"hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"),
| 657| 657| 					"hotkey_structree": colorizeHotkey("%(hotkey)s", "structree")
| 658|    |-			});
|    | 658|+				});
| 659| 659| 	}
| 660| 660| 
| 661| 661| 	// Following gaia can be interesting on scripted maps
|    | [NORMAL] ESLintBear (key-spacing):
|    | Extra space after key 'campaignData'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
| 762| 762| 			"replayDirectory": !g_HasRejoined && replayDirectory,
| 763| 763| 			"replaySelectionData": g_ReplaySelectionData
| 764| 764| 		},
| 765|    |-		"campaignData" : campaignData
|    | 765|+		"campaignData": campaignData
| 766| 766| 	});
| 767| 767| }
| 768| 768| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 3 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|1248|1248| 
|1249|1249| 	let orderHotkeyTooltip = Object.keys(viewablePlayerStates).length <= 1 ? "" :
|1250|1250| 		"\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), {
|1251|    |-		"hotkey": setStringTags("\\[Click]", g_HotkeyTags),
|    |1251|+			"hotkey": setStringTags("\\[Click]", g_HotkeyTags),
|1252|1252| 		"order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending")
|1253|1253| 	});
|1254|1254| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 3 tabs but found 2.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|1249|1249| 	let orderHotkeyTooltip = Object.keys(viewablePlayerStates).length <= 1 ? "" :
|1250|1250| 		"\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), {
|1251|1251| 		"hotkey": setStringTags("\\[Click]", g_HotkeyTags),
|1252|    |-		"order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending")
|    |1252|+			"order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending")
|1253|1253| 	});
|1254|1254| 
|1255|1255| 	let resCodes = g_ResourceData.GetCodes();
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 1.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|1250|1250| 		"\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), {
|1251|1251| 		"hotkey": setStringTags("\\[Click]", g_HotkeyTags),
|1252|1252| 		"order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending")
|1253|    |-	});
|    |1253|+		});
|1254|1254| 
|1255|1255| 	let resCodes = g_ResourceData.GetCodes();
|1256|1256| 	for (let r = 0; r < resCodes.length; ++r)
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 1 tab but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/gui/session/session.js
|1749|1749| 	for (let rct of resourcesCounterTypes)
|1750|1750| 		for (let rt of resourcesTypes)
|1751|1751| 			reportObject[rt + rct.substr(9)] = playerStatistics[rct][rt];
|1752|    |-			// eg. rt = food rct.substr = Gathered rct = resourcesGathered
|    |1752|+	// eg. rt = food rct.substr = Gathered rct = resourcesGathered
|1753|1753| 
|1754|1754| 	reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood;
|1755|1755| 	for (let type of unitsClasses)

binaries/data/mods/public/gui/session/session.js
|1076| »   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
|1151| »   »   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
|1152| »   »   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
|1153| »   »   button.onpressright·=·(function(i)·{·return·function()·{·performGroup("breakUp",·i);·};·})(i);
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'i' is already declared in the upper scope.
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|  66|  66| 		let phase = "";
|  67|  67| 		let cmpTechnologyManager = QueryPlayerIDInterface(i, IID_TechnologyManager);
|  68|  68| 		if (cmpTechnologyManager)
|  69|    |-		{
|    |  69|+		
|  70|  70| 			if (cmpTechnologyManager.IsTechnologyResearched("phase_city"))
|  71|  71| 				phase = "city";
|  72|  72| 			else if (cmpTechnologyManager.IsTechnologyResearched("phase_town"))
|  73|  73| 				phase = "town";
|  74|  74| 			else if (cmpTechnologyManager.IsTechnologyResearched("phase_village"))
|  75|  75| 				phase = "village";
|  76|    |-		}
|    |  76|+		
|  77|  77| 
|  78|  78| 		// store player ally/neutral/enemy data as arrays
|  79|  79| 		let allies = [];
|    | [NORMAL] ESLintBear (semi):
|    | Missing semicolon.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
| 196| 196| 	return {
| 197| 197| 		"status": QueryPlayerIDInterface(player, IID_Player).GetState()
| 198| 198| 	};
| 199|    |-}
|    | 199|+};
| 200| 200| 
| 201| 201| GuiInterface.prototype.GetRenamedEntities = function(player)
| 202| 202| {
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
| 417| 417| 			ret.attack[type].elevationBonus = range.elevationBonus;
| 418| 418| 
| 419| 419| 			if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
| 420|    |-			{
|    | 420|+			
| 421| 421| 				// For units, take the range in front of it, no spread. So angle = 0
| 422| 422| 				ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 0);
| 423|    |-			}
|    | 423|+			
| 424| 424| 			else if(cmpPosition && cmpPosition.IsInWorld())
| 425| 425| 			{
| 426| 426| 				// For buildings, take the average elevation around it. So angle = 2*pi
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
| 422| 422| 				ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 0);
| 423| 423| 			}
| 424| 424| 			else if(cmpPosition && cmpPosition.IsInWorld())
| 425|    |-			{
|    | 425|+			
| 426| 426| 				// For buildings, take the average elevation around it. So angle = 2*pi
| 427| 427| 				ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 2*Math.PI);
| 428|    |-			}
|    | 428|+			
| 429| 429| 			else
| 430| 430| 			{
| 431| 431| 				// not in world, set a default?
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'else'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
| 427| 427| 				ret.attack[type].elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(cmpPosition.GetPosition(), cmpPosition.GetRotation(), range.max, range.elevationBonus, 2*Math.PI);
| 428| 428| 			}
| 429| 429| 			else
| 430|    |-			{
|    | 430|+			
| 431| 431| 				// not in world, set a default?
| 432| 432| 				ret.attack[type].elevationAdaptedRange = ret.attack.maxRange;
| 433|    |-			}
|    | 433|+			
| 434| 434| 		}
| 435| 435| 	}
| 436| 436| 
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 2 tabs but found 3.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
| 798| 798| 		updateEntityColor(data.showAllStatusBars && (i == player || player == -1) ?
| 799| 799| 			[IID_Minimap, IID_RangeOverlayRenderer, IID_RallyPointRenderer, IID_StatusBars] :
| 800| 800| 			[IID_Minimap, IID_RangeOverlayRenderer, IID_RallyPointRenderer],
| 801|    |-			cmpRangeManager.GetEntitiesByPlayer(i));
|    | 801|+		cmpRangeManager.GetEntitiesByPlayer(i));
| 802| 802| 	}
| 803| 803| 	updateEntityColor([IID_Selectable, IID_StatusBars], data.selected);
| 804| 804| 	Engine.QueryInterface(SYSTEM_ENTITY, IID_TerritoryManager).UpdateColors();
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'else'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1317|1317| 		}
|1318|1318| 	}
|1319|1319| 	else
|1320|    |-	{
|    |1320|+	
|1321|1321| 		// Didn't snap to an existing entity, add the starting tower manually. To prevent odd-looking rotation jumps
|1322|1322| 		// when shift-clicking to build a wall, reuse the placement angle that was last seen on a validly positioned
|1323|1323| 		// wall piece.
|1338|1338| 			"pos": start.pos,
|1339|1339| 			"angle": previewEntities.length > 0 ? previewEntities[0].angle : this.placementWallLastAngle
|1340|1340| 		});
|1341|    |-	}
|    |1341|+	
|1342|1342| 
|1343|1343| 	if (end.pos)
|1344|1344| 	{
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1341|1341| 	}
|1342|1342| 
|1343|1343| 	if (end.pos)
|1344|    |-	{
|    |1344|+	
|1345|1345| 		// Analogous to the starting side case above
|1346|1346| 		if (end.snappedEnt && end.snappedEnt != INVALID_ENTITY)
|1347|1347| 		{
|1379|1379| 				"pos": end.pos,
|1380|1380| 				"angle": previewEntities.length > 0 ? previewEntities[previewEntities.length-1].angle : this.placementWallLastAngle
|1381|1381| 			});
|1382|    |-	}
|    |1382|+	
|1383|1383| 
|1384|1384| 	let cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
|1385|1385| 	if (!cmpTerrain)
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1557|1557| 
|1558|1558| 		let cmpVisual = Engine.QueryInterface(ent, IID_Visual);
|1559|1559| 		if (cmpVisual)
|1560|    |-		{
|    |1560|+		
|1561|1561| 			if (!allPiecesValid || !canAfford)
|1562|1562| 				cmpVisual.SetShadingColor(1.4, 0.4, 0.4, 1);
|1563|1563| 			else
|1564|1564| 				cmpVisual.SetShadingColor(1, 1, 1, 1);
|1565|    |-		}
|    |1565|+		
|1566|1566| 
|1567|1567| 		++entPool.numUsed;
|1568|1568| 	}
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1631|1631| 			{
|1632|1632| 				minDist2 = dist2;
|1633|1633| 				minDistEntitySnapData = {
|1634|    |-						"x": pos.x,
|    |1634|+					"x": pos.x,
|1635|1635| 						"z": pos.z,
|1636|1636| 						"angle": cmpPosition.GetRotation().y,
|1637|1637| 						"ent": ent
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1632|1632| 				minDist2 = dist2;
|1633|1633| 				minDistEntitySnapData = {
|1634|1634| 						"x": pos.x,
|1635|    |-						"z": pos.z,
|    |1635|+					"z": pos.z,
|1636|1636| 						"angle": cmpPosition.GetRotation().y,
|1637|1637| 						"ent": ent
|1638|1638| 				};
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1633|1633| 				minDistEntitySnapData = {
|1634|1634| 						"x": pos.x,
|1635|1635| 						"z": pos.z,
|1636|    |-						"angle": cmpPosition.GetRotation().y,
|    |1636|+					"angle": cmpPosition.GetRotation().y,
|1637|1637| 						"ent": ent
|1638|1638| 				};
|1639|1639| 			}
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1634|1634| 						"x": pos.x,
|1635|1635| 						"z": pos.z,
|1636|1636| 						"angle": cmpPosition.GetRotation().y,
|1637|    |-						"ent": ent
|    |1637|+					"ent": ent
|1638|1638| 				};
|1639|1639| 			}
|1640|1640| 		}
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1779|1779| 			result.gain = cmpEntityTrader.GetGoods().amount;
|1780|1780| 	}
|1781|1781| 	else if (data.target === secondMarket)
|1782|    |-	{
|    |1782|+	
|1783|1783| 		result = {
|1784|1784| 			"type": "is second",
|1785|1785| 			"gain": cmpEntityTrader.GetGoods().amount,
|1786|1786| 		};
|1787|    |-	}
|    |1787|+	
|1788|1788| 	else if (!firstMarket)
|1789|1789| 	{
|1790|1790| 		result = { "type": "set first" };
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1786|1786| 		};
|1787|1787| 	}
|1788|1788| 	else if (!firstMarket)
|1789|    |-	{
|    |1789|+	
|1790|1790| 		result = { "type": "set first" };
|1791|    |-	}
|    |1791|+	
|1792|1792| 	else if (!secondMarket)
|1793|1793| 	{
|1794|1794| 		result = {
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'if' condition.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1790|1790| 		result = { "type": "set first" };
|1791|1791| 	}
|1792|1792| 	else if (!secondMarket)
|1793|    |-	{
|    |1793|+	
|1794|1794| 		result = {
|1795|1795| 			"type": "set second",
|1796|1796| 			"gain": cmpEntityTrader.CalculateGain(firstMarket, data.target),
|1797|1797| 		};
|1798|    |-	}
|    |1798|+	
|1799|1799| 	else
|1800|1800| 	{
|1801|1801| 		// Else both markets are not null and target is different from them
|    | [NORMAL] ESLintBear (curly):
|    | Unnecessary { after 'else'.
|----|    | /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|    |++++| /mnt/data/jenkins-phabricator/workspace/differential/binaries/data/mods/public/simulation/components/GuiInterface.js
|1797|1797| 		};
|1798|1798| 	}
|1799|1799| 	else
|1800|    |-	{
|    |1800|+	
|1801|1801| 		// Else both markets are not null and target is different from them
|1802|1802| 		result = { "type": "set first" };
|1803|    |-	}
|    |1803|+	
|1804|1804| 	return result;
|1805|1805| };
|1806|1806| 

binaries/data/mods/public/simulation/components/GuiInterface.js
| 199| }
|    | [NORMAL] JSHintBear:
|    | Missing semicolon.
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/differential/926/

Angen awarded a token.Jan 5 2019, 7:42 PM
elexis added a comment.Jan 6 2019, 3:53 PM

Can't predict all requirements for a campaign before thinking about implementing a campaign.
Don't know how many barries this will put to the "Implement MP campaigns", probably not so many.
GUI Code will be nicer with 0 JS code in XML files and everything being OOP, but doing it consistent with the rest of the current GUI code is ok.
Didn't test the patch, otherwise I could have given some feedback to the UI design.
Abandoned by campaign plan, otherwise I could have given some requirements analysis and feature design feedback.
Good that you updated the github branch.

The directory and filenames could be a bit more specific, they should inform the reader what exactly they contain (campaign, newcampaign_modal.js campaign_io, ...).
So for example "campaign_selection" informs the reader which aspect of campaigns are implemented in that directory. This also helps developers not mixing unrelated things into the same folder. But I didn't read enough to give more detailed specifics for pathnames.

binaries/data/mods/public/campaigns/example.json
1

This sounds like a candidate for the hypothetic pyrogenesis-examples mod #5366. Otherwise I'd recommend not to commit this as it will be translated. Otherwise, campaign authors can also take an example at the tutorial map.

3

"This shows map authors how to create campaign" or similar

13

Something nonempty

binaries/data/mods/public/campaigns/tutorial.json
9

Wondering if it's good that one can load arbitrary maptypes here and not only campaign maps.
Makes me also wonder whether we want players to be able to select campaign maps in the regular gamesetup menu in SP and MP, and
whether there are other hard distinctions between campaign and the other maptypes.

20

If Levels is an array, the order is implied. Not so important however. I still have a campaign screen in my mind that is a graphical map, not a list table, in which case the order would be ignored.

binaries/data/mods/public/gui/campaign/campaign_io.js
11

listFiles

66

||

117

That (and possibly related variables) are not a filenames

124

;;

binaries/data/mods/public/gui/campaign/load.js
23

Looks like it could become one line using Math.min, Math.max (here and in the place it was copied from)

31

Perhaps it's easier to just use the actual filenames as keys (if list and list_data are not easily avoidable by JS)

37

crime against transifex

55

When you’re riding a dead horse, the best strategy is to get off

binaries/data/mods/public/gui/campaign/load.xml
9

same

binaries/data/mods/public/gui/campaign/newcampaign_modal.js
32

===, !== only if the type is relevant (for consistency)

54

assigning object properties on a separate line each arranges it more cleanly

binaries/data/mods/public/gui/campaign/newcampaign_modal.xml
10

directory includes used nowadays to some displeasure

binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js
1

Good that campaigns can come with custom GUi pages.
Maybe simple -> default.
Interface -> GUIPage?

21

setMapPreviewImage

61

setStringTags(caption, { "color": "gray" })

95

Engine.GetGUIObjectByName("startButton").enabled = meetsRequirements(level);

111

same as above

114

let gameSelection = Engine.GetGUIObjectByName("gameSelection"); (makes it easier to keep track of GUIObjects if the variable names correlate with the GUIObject names globally)

136

Would have proposed Array function find, but why does it loop at all?

144

generateSavegameDateString to avoid duplication that is patched by some temple diff

152

Should be red as well. (Also in the future we should not just state theyre incompatible but provide an auto-launch of the needed mods if they exist)

164

generateSavegameLabel. (Reveals a bit how the patch was written)

175

Application exit is in the main menu, not in every menu

188

type was removed recently.

Going through the gamesetup is healthy as it avoids 10 gamesetup rewrites.

binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.xml
7

Guess we can move that function to common/ in case this is committed.

binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.js
1

/**

11

-\n

27

camp =>

29

classic, due to the COList : CList inheritance. Notice COList doesnt even use list and list_data anywhere (only possibly JS using these CList properties. Would require lots of wasted braincells to fix)

50

still

binaries/data/mods/public/gui/campaign/simple_setup/campaignsetup.xml
8

directory includes. (And I even against the complaints say it helps mods, not break them as they don't have to copy the XML file to add an include.)

binaries/data/mods/public/gui/pregame/mainmenu.xml
6

same

181

type went

210

type went

224

same

binaries/data/mods/public/gui/session/session.js
742

This assignment looks troublesome. The init attributes should never be changed. I guess one receives a copy instead of a deeply frozen reference.
Better store data that changes during the sesion in a separate variable than mixing it with unchangeable init attributes.

Engine.GetInitAttributes().campaignData duplo

One could also examine whether passing Engine.GetInitAttributes() entirely to the summary screen would be good or bad.
Taking a quick look at the code, it already seems to be the case in getReplayMetadata. So it sounds like refactoring to untangle.

765

So that's not simulation data?

binaries/data/mods/public/gui/session/session.xml
6

-\n

8

This include shouldn't be here. gui/campaign/ should contain everything and only that which page_campaign.xml uses.

I don't know which functions you use here, but possibly gui/common/campaign.js if its only one or two functions.

binaries/data/mods/public/gui/summary/summary.xml
10

same (directory includes and these includes not shoulding having beeing here).
Wait, this should display a different GUI page if a condition is met? Then the page shouldn't be opened to begin with

binaries/data/mods/public/simulation/components/GuiInterface.js
197

That's already contained in the GetSimulationState

source/ps/scripting/JSInterface_VFS.cpp
1

9 and so on

219

If I recall correctly, we were already discussing the possibility of abuse of this function, a possible restriction to the two directories where we allow deletion (so that a malicious mod can only delete all savegames and campaign saves).
The delete-savegame function can be superseded by this one.

273

(OT: Because we already own FromJSVal<Path>, all JSInterface files should be registered with VfsPath directly instead of receiving a string and converting that (possibly incorrectly).)

288

??

Answers to some comments.

binaries/data/mods/public/campaigns/tutorial.json
9

One argument for "arbitrary maps in campaign" is making a Rise of Nations like campaign, where the 'campaign map' is a thing and the actual games are regular maps.

20

This is tied to the interface one chooses above, in this case simple_campaign. Another interface might do something completely different.

binaries/data/mods/public/gui/campaign/load.js
45

Not done yet.

55

I think this is supposed to update the GUI elements but I believe I've not implemented that yet.

binaries/data/mods/public/gui/campaign/simple_campaign/mainmenu.js
1

I'm not sure we want the list interface to be the default one. I had an AOE-2 like campaign map in mind for the default.

binaries/data/mods/public/gui/session/session.js
765

I believe (been a while) this data can come from the campaign metadata itself, so keeping it separate makes sense imo. For example a campaign might keep track of how well you are doing, but that's not really 'Sim Data' to me.

binaries/data/mods/public/gui/session/session.xml
8

I'll check if it's needed at all. Don't remember why I added it a solid 2 years ago.

9

These are needed to pass the end game data.

binaries/data/mods/public/gui/summary/summary.xml
10

Not sure what you mean here?
This is so Summary.xml brings you back to the campaign interface. I guess I should just PopPage ?

binaries/data/mods/public/maps/tutorials/Introductory_Tutorial.js
322 ↗(On Diff #4508)

Is this still broken?

source/ps/scripting/JSInterface_VFS.cpp
219

We did. Haven't implemented said restriction when rebasing since it's more work and honestly this patch is already almost too big to grok.

288

Same, not sure when that got here.

As a reminder of what we're trying to achieve here: the goal (to me) is not to provide something beautiful, or even something complete, but rather a necessary, minimal, and well-architectured stepping stone for other code. As the patch stands, it is usable for a very basic campaign, though it's missing plenty of things one would want, but I think that's fine, we can add those on top of this core stuff, so long as the general direction of the core stuff isn't stupid.

elexis added inline comments.Jan 6 2019, 11:03 PM
binaries/data/mods/public/campaigns/example.json
3

Did someone say messages.json? (Also some exotic transifex file if new directory IIRC)

binaries/data/mods/public/campaigns/tutorial.json
9

I'm sure there are important arguments for both sides. But that can be decided later as it doesn't invalidate any of the existing code I suppose.
But it should be decided before people make campaigns, as the maptype decision can invalidate campaign implementation.

The tutorial maptype already exists, so I guess it should be figured out by wise men whether that type should be migrated, nuked or kept separate.

binaries/data/mods/public/gui/pregame/mainmenu.js
57

(I didn't leave one of these "I have a patch since 2 years that rewrites this entire file" comments yet)

binaries/data/mods/public/gui/session/session.js
765

That data comes from Engine.GetInitAttributes() but that should only contain the gamesettings which determine the simulation.
That's in particular relevant because GUI data is non-deterministic and per client, but simulation data is. So if you have someone spectating the campaign playyer or watching a replay, the GUI data must be irrelevant, nonpresent.
Savegames also have a sim/gui data bipartition IIRC.

binaries/data/mods/public/gui/session/session.xml
9

The io remark was about the name. I/O typically describes hardware.

binaries/data/mods/public/gui/summary/summary.xml
10

gui/common/ or gui/campaign/common/.
(Heads up: any/common/ (by definition) should not contain code that is only valid on one page)

source/ps/scripting/JSInterface_VFS.cpp
219

Apparently JS is agnostic of the .0adsave suffix, so SavedGames::DeleteSavedGame can't be replaced without hardcoding the suffic in JS.

One could also examine the position that everything that this function could delete could already be deleted otherwise (config system) or be a valid user-triggered deletion (screenshots? but that would require displaying them in 0ad. So perhaps make it safe until then. Should be two lines, one of them defining a path, the other calling PathRestrictionMet)