Page MenuHomeWildfire Games

Gamesetup map specific options - Landscape, Daytime
ClosedPublic

Authored by elexis on Sun, Jan 12, 9:57 PM.

Details

Summary

This patch adds two more long awaited gamesetup options:

  • The ability to chose between the 12 langscapes of the Unknown map. This concludes the deduplication of Unknown, Unknown Land and Unknown.
  • The ability to chose between the Day and Night variant of Danubius. The feature is reused to chose between Day and Dawn on Polar Sea.

The patch adds new map previews for every of these options.

The patch removes the biome hardcoding from the getMapPreview code that comes from rP21523, refs #4962.
To achieve that, it reintroduces the g_GameAttributes.settings.Preview field removed in rP23374/D2483 (as it had no more use case then).

The patch was meant to be specific to these maps only to showcase gamesettings that only work for one map.
However these two settings are likely enough to be reused by other maps, so the first Map specific option will probably be in "Fert": D1634.

Test Plan

Notice the strings are correctly extracted in messages.json. "Make sure that it works". Probably find something to complain about the provisional strings.

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

elexis created this revision.Sun, Jan 12, 9:57 PM

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

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/1018/display/redirect

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

Linter detected issues:
Executing section Source...
Executing section JS...
|    | [NORMAL] ESLintBear (semi):
|    | Missing semicolon.
|----|    | /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/Panels/MapPreview.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/Panels/MapPreview.js
|  18|  18| 		if (!g_GameAttributes.settings.Preview || g_GameAttributes.settings.Preview != preview)
|  19|  19| 		{
|  20|  20| 			g_GameAttributes.settings.Preview = preview;
|  21|    |-			this.gameSettingsControl.updateGameAttributes
|    |  21|+			this.gameSettingsControl.updateGameAttributes;
|  22|  22| 		}
|  23|  23| 	}
|  24|  24| 

binaries/data/mods/public/gui/gamesetup/Panels/MapPreview.js
|  21| »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] ESLintBear (no-unused-expressions):
|    | Expected an assignment or function call and instead saw an expression.

binaries/data/mods/public/gui/gamesetup/Panels/MapPreview.js
|  21| »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] JSHintBear:
|    | Expected an assignment or function call and instead saw an expression.

binaries/data/mods/public/gui/gamesetup/Panels/MapPreview.js
|  21| »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] JSHintBear:
|    | Missing semicolon.
|    | [NORMAL] ESLintBear (semi):
|    | Missing semicolon.
|----|    | /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/GameSettings/GameSettingControlManager.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/GameSettings/GameSettingControlManager.js
|  59|  59| 			if (this.gameSettingControls[name] == gameSettingControl)
|  60|  60| 				return name;
|  61|  61| 
|  62|    |-		throw new Error("getGameSettingControlName not find gameSettingControl")
|    |  62|+		throw new Error("getGameSettingControlName not find gameSettingControl");
|  63|  63| 	}
|  64|  64| 
|  65|  65| 	isGameSettingControlHidden(gameSettingControl)

binaries/data/mods/public/gui/gamesetup/GameSettings/GameSettingControlManager.js
|  62| »   »   throw·new·Error("getGameSettingControlName·not·find·gameSettingControl")
|    | [NORMAL] JSHintBear:
|    | Missing semicolon.
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 765| 765| 			createAreas(
| 766| 766| 				new ChainPlacer(2, Math.floor(scaleByMapSize(4, 6)), 15, Infinity),
| 767| 767| 				[
| 768|    |-						new SmoothElevationPainter(ELEVATION_SET, i ? heightLand : waterHeight, 4),
|    | 768|+					new SmoothElevationPainter(ELEVATION_SET, i ? heightLand : waterHeight, 4),
| 769| 769| 						i ? new TileClassPainter(clLand) : new TileClassUnPainter(clLand)
| 770| 770| 				],
| 771| 771| 				[
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 766| 766| 				new ChainPlacer(2, Math.floor(scaleByMapSize(4, 6)), 15, Infinity),
| 767| 767| 				[
| 768| 768| 						new SmoothElevationPainter(ELEVATION_SET, i ? heightLand : waterHeight, 4),
| 769|    |-						i ? new TileClassPainter(clLand) : new TileClassUnPainter(clLand)
|    | 769|+					i ? new TileClassPainter(clLand) : new TileClassUnPainter(clLand)
| 770| 770| 				],
| 771| 771| 				[
| 772| 772| 						avoidClasses(clPlayer, 20, clPeninsulaSteam, 20),
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 769| 769| 						i ? new TileClassPainter(clLand) : new TileClassUnPainter(clLand)
| 770| 770| 				],
| 771| 771| 				[
| 772|    |-						avoidClasses(clPlayer, 20, clPeninsulaSteam, 20),
|    | 772|+					avoidClasses(clPlayer, 20, clPeninsulaSteam, 20),
| 773| 773| 						borderClasses(borderClass, shoreDist, shoreDist)
| 774| 774| 				],
| 775| 775| 				scaleByMapSize(7, 130) * 2,
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 5 tabs but found 6.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 770| 770| 				],
| 771| 771| 				[
| 772| 772| 						avoidClasses(clPlayer, 20, clPeninsulaSteam, 20),
| 773|    |-						borderClasses(borderClass, shoreDist, shoreDist)
|    | 773|+					borderClasses(borderClass, shoreDist, shoreDist)
| 774| 774| 				],
| 775| 775| 				scaleByMapSize(7, 130) * 2,
| 776| 776| 				150);
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 3 tabs but found 4.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 897| 897| 	g_Map.log("Creating grass patches");
| 898| 898| 	for (let size of [scaleByMapSize(2, 32), scaleByMapSize(3, 48), scaleByMapSize(5, 80)])
| 899| 899| 		createAreas(
| 900|    |-				new ClumpPlacer(size, 0.3, 0.06, 0.5),
|    | 900|+			new ClumpPlacer(size, 0.3, 0.06, 0.5),
| 901| 901| 			new TerrainPainter(tTier4Terrain),
| 902| 902| 			[avoidClasses(clForest, 0, clHill, 2, clDirt, 5, clPlayer, 7), stayClasses(clLand, 4)],
| 903| 903| 			patchCount);
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/unknown.js
| 917| 917| 		new SimpleGroup([new SimpleObject(oStoneSmall, 2, 5, 1, 3)], true, clRock),
| 918| 918| 		0,
| 919| 919| 		[avoidClasses(clForest, 1, clPlayer, 10, clRock, 10, clHill, 2), stayClasses(clLand, 3)],
| 920|    |-		randIntInclusive(scaleByMapSize(2, 9),scaleByMapSize(9, 40)),
|    | 920|+		randIntInclusive(scaleByMapSize(2, 9), scaleByMapSize(9, 40)),
| 921| 921| 		100);
| 922| 922| 
| 923| 923| 	g_Map.log("Creating metal mines");

binaries/data/mods/public/maps/random/unknown.js
| 887| »   for·(let·size·of·[scaleByMapSize(3,·48),·scaleByMapSize(5,·84),·scaleByMapSize(8,·128)])
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'size' is already declared in the upper scope.

binaries/data/mods/public/maps/random/unknown.js
| 898| »   for·(let·size·of·[scaleByMapSize(2,·32),·scaleByMapSize(3,·48),·scaleByMapSize(5,·80)])
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'size' is already declared in the upper scope.
|    | [NORMAL] ESLintBear (semi):
|    | Missing semicolon.
|----|    | /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/GameSettings/Single/Daytime.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/gui/gamesetup/GameSettings/Single/Daytime.js
|  52|  52| 			if (!g_GameAttributes.settings.Preview || g_GameAttributes.settings.Preview != preview)
|  53|  53| 			{
|  54|  54| 				g_GameAttributes.settings.Preview = preview;
|  55|    |-				this.gameSettingsControl.updateGameAttributes
|    |  55|+				this.gameSettingsControl.updateGameAttributes;
|  56|  56| 			}
|  57|  57| 		}
|  58|  58| 		else if (g_GameAttributes.settings.Daytime !== undefined)

binaries/data/mods/public/gui/gamesetup/GameSettings/Single/Daytime.js
|  55| »   »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] ESLintBear (no-unused-expressions):
|    | Expected an assignment or function call and instead saw an expression.

binaries/data/mods/public/gui/gamesetup/GameSettings/Single/Daytime.js
|  55| »   »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] JSHintBear:
|    | Expected an assignment or function call and instead saw an expression.

binaries/data/mods/public/gui/gamesetup/GameSettings/Single/Daytime.js
|  55| »   »   »   »   this.gameSettingsControl.updateGameAttributes
|    | [NORMAL] JSHintBear:
|    | Missing semicolon.
|    | [NORMAL] ESLintBear (no-multi-spaces):
|    | Multiple spaces found before '"temple"'.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
| 237| 237| 	"Geto-Dacian Tribal Confederation",
| 238| 238| 	new Array(2).fill([
| 239| 239| 		"gate", "pillar", "hut", "long", "long",
| 240|    |-		"cornerIn", "defense_tower", "long",  "temple", "long",
|    | 240|+		"cornerIn", "defense_tower", "long", "temple", "long",
| 241| 241| 		"pillar", "house", "long", "short", "pillar", "gate", "pillar", "longhouse", "long", "long",
| 242| 242| 		"cornerIn", "defense_tower", "long", "tavern", "long", "pillar"
| 243| 243| 	]).reduce((result, items) => result.concat(items), []));
|    | [NORMAL] ESLintBear (yoda):
|    | Expected literal to be on the right side of <.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
| 382| 382| 	"waterFunc": (position, height, riverFraction) => {
| 383| 383| 		let origPos = position.clone().rotateAround(-startAngle, mapCenter);
| 384| 384| 		// Distinguish left and right shoreline
| 385|    |-		if (0 < height && height < 1 &&
|    | 385|+		if (height > 0 && height < 1 &&
| 386| 386| 			origPos.y > ShorelineDistance && origPos.y < mapSize - ShorelineDistance)
| 387| 387| 			clShore[origPos.x < mapCenter.x ? 0 : 1].add(position);
| 388| 388| 	},
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
| 446| 446| g_Map.log("Creating grass patches");
| 447| 447| createLayeredPatches(
| 448| 448| 	[scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)],
| 449|    |-	[[tGrass, tGrass2],[tGrass2, tGrass3], [tGrass3, tGrass]],
|    | 449|+	[[tGrass, tGrass2], [tGrass2, tGrass3], [tGrass3, tGrass]],
| 450| 450| 	[1, 1],
| 451| 451| 	avoidClasses(clForest, 0, clPlayer, 10, clWater, 2, clDirt, 2, clHill, 1, clGauls, 5, clPath, 1),
| 452| 452| 	scaleByMapSize(15, 45),
|    | [NORMAL] ESLintBear (comma-spacing):
|    | There should be no space before ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
| 568| 568| 		],
| 569| 569| 		i == 0 ?
| 570| 570| 			avoidClasses(clWater, 4, clForest, 1, clPlayer, 16, clRock, 4, clMetal, 4, clHill, 4, clGauls, 5, clPath, 1) :
| 571|    |-			[stayClasses(clIsland, 4) , avoidClasses(clForest, 1, clRock, 4, clMetal, 4)]);
|    | 571|+			[stayClasses(clIsland, 4), avoidClasses(clForest, 1, clRock, 4, clMetal, 4)]);
| 572| 572| Engine.SetProgress(75);
| 573| 573| 
| 574| 574| g_Map.log("Creating fish");

binaries/data/mods/public/maps/random/danubius.js
| 308| »   »   »   »   for·(let·i·=·0;·i·<·positions.length;·++i)
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'i' is already declared in the upper scope.

binaries/data/mods/public/maps/random/danubius.js
| 328| »   »   for·(let·i·=·0;·i·<·gallicCCTreasureCount;·++i)
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'i' is already declared in the upper scope.
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 134| 134| g_Map.log("Creating dirt patches");
| 135| 135| createLayeredPatches(
| 136| 136| 	[scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)],
| 137|    |-	[[tDirt,tHalfSnow], [tHalfSnow,tSnowLimited]],
|    | 137|+	[[tDirt, tHalfSnow], [tHalfSnow,tSnowLimited]],
| 138| 138| 	[2],
| 139| 139| 	avoidClasses(clWater, 3, clDirt, 5, clPlayer, 12),
| 140| 140| 	scaleByMapSize(15, 45),
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 134| 134| g_Map.log("Creating dirt patches");
| 135| 135| createLayeredPatches(
| 136| 136| 	[scaleByMapSize(3, 6), scaleByMapSize(5, 10), scaleByMapSize(8, 21)],
| 137|    |-	[[tDirt,tHalfSnow], [tHalfSnow,tSnowLimited]],
|    | 137|+	[[tDirt,tHalfSnow], [tHalfSnow, tSnowLimited]],
| 138| 138| 	[2],
| 139| 139| 	avoidClasses(clWater, 3, clDirt, 5, clPlayer, 12),
| 140| 140| 	scaleByMapSize(15, 45),
|    | [NORMAL] ESLintBear (indent):
|    | Expected indentation of 0 tabs but found 1.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 150| 150| Engine.SetProgress(70);
| 151| 151| 
| 152| 152| g_Map.log("Creating stone mines");
| 153|    |-	createMines(
|    | 153|+createMines(
| 154| 154| 	[
| 155| 155| 		[new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
| 156| 156| 		[new SimpleObject(oStoneSmall, 2,5, 1,3)]
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 153| 153| 	createMines(
| 154| 154| 	[
| 155| 155| 		[new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
| 156|    |-		[new SimpleObject(oStoneSmall, 2,5, 1,3)]
|    | 156|+		[new SimpleObject(oStoneSmall, 2, 5, 1,3)]
| 157| 157| 	],
| 158| 158| 	avoidClasses(clWater, 3, clPlayer, 20, clRock, 18, clHill, 2),
| 159| 159| 	clRock);
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 153| 153| 	createMines(
| 154| 154| 	[
| 155| 155| 		[new SimpleObject(oStoneSmall, 0, 2, 0, 4, 0, 2 * Math.PI, 1), new SimpleObject(oStoneLarge, 1, 1, 0, 4, 0, 2 * Math.PI, 4)],
| 156|    |-		[new SimpleObject(oStoneSmall, 2,5, 1,3)]
|    | 156|+		[new SimpleObject(oStoneSmall, 2,5, 1, 3)]
| 157| 157| 	],
| 158| 158| 	avoidClasses(clWater, 3, clPlayer, 20, clRock, 18, clHill, 2),
| 159| 159| 	clRock);
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 161| 161| g_Map.log("Creating metal mines");
| 162| 162| createMines(
| 163| 163| 	[
| 164|    |-		[new SimpleObject(oMetalLarge, 1,1, 0,4)]
|    | 164|+		[new SimpleObject(oMetalLarge, 1, 1, 0,4)]
| 165| 165| 	],
| 166| 166| 	avoidClasses(clWater, 3, clPlayer, 20, clMetal, 18, clRock, 5, clHill, 2),
| 167| 167| 	clMetal);
|    | [NORMAL] ESLintBear (comma-spacing):
|    | A space is required after ','.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/polar_sea.js
| 161| 161| g_Map.log("Creating metal mines");
| 162| 162| createMines(
| 163| 163| 	[
| 164|    |-		[new SimpleObject(oMetalLarge, 1,1, 0,4)]
|    | 164|+		[new SimpleObject(oMetalLarge, 1,1, 0, 4)]
| 165| 165| 	],
| 166| 166| 	avoidClasses(clWater, 3, clPlayer, 20, clMetal, 18, clRock, 5, clHill, 2),
| 167| 167| 	clMetal);
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/1536/display/redirect

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

Link to build: https://jenkins.wildfiregames.com/job/macos-differential/114/display/redirect

Stan added a subscriber: Stan.Mon, Jan 13, 12:05 PM

The combination of those three files (Don't forget to backup) trigger an infinite loop for me.

In D2564#107218, @Stan wrote:

The combination of those three files (Don't forget to backup) trigger an infinite loop for me.

That comes from rP23374, fixed in rP23391, triggered when selecting Sandbox AI difficulty by default.

Probably find something to complain about the provisional strings.

I copied some strings from existing maps (Isthmus, Mainland, Continent) and improved some others. I suppose they're not lyrical masterpieces but better than many existing lousy map descriptions ("A typical map without any water." and "The unknown." are existing ones for instance).

A possible future improvement is to display these two options in gamedescription.js.

I've removed the GameSettingControl.js diff for now since its not needed for the patch (as the g_GameSettingsLayout is not modified externally here)

elexis updated this revision to Diff 11010.Tue, Jan 14, 7:51 AM

See above.

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

Link to build: https://jenkins.wildfiregames.com/job/vs2015-differential/1034/display/redirect

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

Link to build: https://jenkins.wildfiregames.com/job/macos-differential/130/display/redirect

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

Linter detected issues:
Executing section Source...
Executing section JS...

binaries/data/mods/public/maps/random/unknown.js
| 898| »   for·(let·size·of·[scaleByMapSize(2,·32),·scaleByMapSize(3,·48),·scaleByMapSize(5,·80)])
|    | [NORMAL] ESLintBear (no-shadow):
|    | 'size' is already declared in the upper scope.
|    | [NORMAL] ESLintBear (yoda):
|    | Expected literal to be on the right side of <.
|----|    | /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
|    |++++| /zpool0/trunk/binaries/data/mods/public/maps/random/danubius.js
| 382| 382| 	"waterFunc": (position, height, riverFraction) => {
| 383| 383| 		let origPos = position.clone().rotateAround(-startAngle, mapCenter);
| 384| 384| 		// Distinguish left and right shoreline
| 385|    |-		if (0 < height && height < 1 &&
|    | 385|+		if (height > 0 && height < 1 &&
| 386| 386| 			origPos.y > ShorelineDistance && origPos.y < mapSize - ShorelineDistance)
| 387| 387| 			clShore[origPos.x < mapCenter.x ? 0 : 1].add(position);
| 388| 388| 	},
Executing section cli...

Link to build: https://jenkins.wildfiregames.com/job/docker-differential/1552/display/redirect

This revision was not accepted when it landed; it landed in state Needs Review.Tue, Jan 14, 8:02 AM
This revision was automatically updated to reflect the committed changes.
Owners added subscribers: Restricted Owners Package, Restricted Owners Package, Restricted Owners Package.Tue, Jan 14, 8:02 AM