diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.js b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
index 1f622830d4..0a55bf8d33 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -1,3 +1,14 @@
+////////////////////////////////////////////////////////////////////////////////////////////////
+// Constants
+// TODO: Move some of these into common location (other scripts may need)
+const MAP_SIZES_TEXT = ["Small", "Medium", "Large", "Huge"];
+const MAP_SIZES_DATA = [16, 20, 24, 32];
+
+// Max number of players for any map
+const MAX_PLAYERS = 8;
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
// Is this is a networked game, or offline
var g_IsNetworked;
@@ -11,6 +22,7 @@ var g_IsInGuiUpdate;
var g_PlayerAssignments = {};
// Default game setup attributes
+var g_DefaultPlayerData = [];
var g_GameAttributes = {
mapType: "",
map: "",
@@ -27,12 +39,6 @@ var g_GameAttributes = {
}
}
-// Max number of players for any map
-var g_MaxPlayers = 8;
-
-// Number of players for currently selected map
-var g_NumPlayers = 0;
-
var g_AIs = [];
var g_ChatMessages = [];
@@ -43,12 +49,13 @@ var g_CivData = {};
var g_MapFilters = [];
-
// To prevent the display locking up while we load the map metadata,
// we'll start with a 'loading' message and switch to the main screen in the
// tick handler
var g_LoadingState = 0; // 0 = not started, 1 = loading, 2 = loaded
+////////////////////////////////////////////////////////////////////////////////////////////////
+
function init(attribs)
{
switch (attribs.type)
@@ -75,26 +82,26 @@ function initMain()
{
// Load AI list
g_AIs = Engine.GetAIs();
-
+
// Sort AIs by displayed name
g_AIs.sort(function (a, b) {
return a.data.name < b.data.name ? -1 : b.data.name < a.data.name ? +1 : 0;
});
-
+
// Get default player data - remove gaia
- var pDefs = initPlayerDefaults();
- pDefs.shift();
+ g_DefaultPlayerData = initPlayerDefaults();
+ g_DefaultPlayerData.shift();
// Build player settings using defaults
- g_GameAttributes.settings.PlayerData = pDefs;
+ g_GameAttributes.settings.PlayerData = g_DefaultPlayerData;
// Init civs
initCivNameList();
// Init map types
var mapTypes = getGUIObjectByName("mapTypeSelection");
- mapTypes.list = ["Scenario"]; // TODO: May offer saved game type for multiplayer games?
- mapTypes.list_data = ["scenario"];
+ mapTypes.list = ["Scenario","Random"];
+ mapTypes.list_data = ["scenario","random"];
// Setup map filters - will appear in order they are added
addFilter("Default", function(settings) { return settings && !keywordTestOR(settings.Keywords, ["demo", "hidden"]); });
@@ -106,10 +113,14 @@ function initMain()
var mapFilters = getGUIObjectByName("mapFilterSelection");
mapFilters.list = getFilters();
g_GameAttributes.mapFilter = "Default";
-
+
// Setup controls for host only
if (g_IsController)
{
+ // Generate seed for random maps
+ // TODO: There's probably a better way to do this
+ g_GameAttributes.settings.Seed = Math.floor(Math.random() * 65536);
+
// Set a default map
// TODO: This should be remembered from the last session
if (!g_IsNetworked)
@@ -122,46 +133,46 @@ function initMain()
initMapNameList();
- var numPlayers = getGUIObjectByName("numPlayersSelection");
+ var numPlayersSelect = getGUIObjectByName("numPlayersSelection");
var players = [];
- for (var i = 1; i <= g_MaxPlayers; ++i)
+ for (var i = 1; i <= MAX_PLAYERS; ++i)
players.push(i);
- numPlayers.list = players;
- numPlayers.list_data = players;
- numPlayers.selected = g_MaxPlayers - 1;
+ numPlayersSelect.list = players;
+ numPlayersSelect.list_data = players;
+ numPlayersSelect.selected = MAX_PLAYERS - 1;
var victoryConditions = getGUIObjectByName("victoryCondition");
victoryConditions.list = ["Conquest", "None"];
victoryConditions.list_data = ["conquest", "endless"];
victoryConditions.onSelectionChange = function()
- {
+ { // Update attributes so other players can see change
if (this.selected != -1)
g_GameAttributes.settings.GameType = this.list_data[this.selected];
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
victoryConditions.selected = -1;
var mapSize = getGUIObjectByName("mapSize");
- mapSize.list = ["Small", "Medium", "Large", "Huge"];
- mapSize.list_data = [16, 32, 48, 64];
+ mapSize.list = MAP_SIZES_TEXT;
+ mapSize.list_data = MAP_SIZES_DATA;
mapSize.onSelectionChange = function()
- {
+ { // Update attributes so other players can see change
if (this.selected != -1)
- g_GameAttributes.settings.Size = parseInt(this.list_data[this.selected]);
+ g_GameAttributes.settings.Size = this.list_data[this.selected];
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
- mapSize.selected = -1;
+ mapSize.selected = 0;
getGUIObjectByName("revealMap").onPress = function()
{ // Update attributes so other players can see change
g_GameAttributes.settings.RevealMap = this.checked;
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
getGUIObjectByName("lockTeams").onPress = function()
@@ -169,7 +180,7 @@ function initMain()
g_GameAttributes.settings.LockTeams = this.checked;
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
}
else
@@ -182,8 +193,12 @@ function initMain()
// Disable player and game options controls
// TODO: Shouldn't players be able to choose their own assignment?
- for (var i = 0; i < g_MaxPlayers; ++i)
+ for (var i = 0; i < MAX_PLAYERS; ++i)
+ {
getGUIObjectByName("playerAssignment["+i+"]").enabled = false;
+ getGUIObjectByName("playerCiv["+i+"]").hidden = true;
+ getGUIObjectByName("playerTeam["+i+"]").hidden = true;
+ }
getGUIObjectByName("numPlayersBox").hidden = true;
@@ -200,7 +215,7 @@ function initMain()
// Settings for all possible player slots
var boxSpacing = 32;
- for (var i = 0; i < g_MaxPlayers; ++i)
+ for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Space player boxes
var box = getGUIObjectByName("playerBox["+i+"]");
@@ -223,7 +238,7 @@ function initMain()
g_GameAttributes.settings.PlayerData[playerSlot].Team = this.selected - 1;
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
// Set events
@@ -234,10 +249,10 @@ function initMain()
g_GameAttributes.settings.PlayerData[playerSlot].Civ = this.list_data[this.selected];
if (!g_IsInGuiUpdate)
- onGameAttributesChange();
+ updateGameAttributes();
};
}
-
+
if (g_IsNetworked)
{
// For multiplayer, focus the chat input box by default
@@ -251,37 +266,6 @@ function initMain()
}
}
-function cancelSetup()
-{
- Engine.DisconnectNetworkGame();
-}
-
-function onTick()
-{
- // First tick happens before first render, so don't load yet
- if (g_LoadingState == 0)
- {
- g_LoadingState++;
- }
- else if (g_LoadingState == 1)
- {
- getGUIObjectByName("loadingWindow").hidden = true;
- getGUIObjectByName("setupWindow").hidden = false;
- initMain();
- g_LoadingState++;
- }
- else if (g_LoadingState == 2)
- {
- while (true)
- {
- var message = Engine.PollNetworkClient();
- if (!message)
- break;
- handleNetMessage(message);
- }
- }
-}
-
function handleNetMessage(message)
{
log("Net message: "+uneval(message));
@@ -296,20 +280,20 @@ function handleNetMessage(message)
Engine.PopGuiPage();
reportDisconnect(message.reason);
break;
-
+
default:
error("Unrecognised netstatus type "+message.status);
break;
}
break;
-
+
case "gamesetup":
if (message.data) // (the host gets undefined data on first connect, so skip that)
g_GameAttributes = message.data;
onGameAttributesChange();
break;
-
+
case "players":
// Find and report all joinings/leavings
for (var host in message.hosts)
@@ -322,7 +306,7 @@ function handleNetMessage(message)
g_PlayerAssignments = message.hosts;
updatePlayerList();
break;
-
+
case "start":
Engine.SwitchGuiPage("page_loading.xml", {
"attribs": g_GameAttributes,
@@ -330,7 +314,7 @@ function handleNetMessage(message)
"playerAssignments": g_PlayerAssignments
});
break;
-
+
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
@@ -345,12 +329,12 @@ function getMapDisplayName(map)
{
var mapData = loadMapData(map);
- if(!mapData || !mapData.settings || !mapData.settings.Name)
+ if (!mapData || !mapData.settings || !mapData.settings.Name)
{ // Give some msg that map format is unsupported
log("Map data missing in scenario '"+map+"' - likely unsupported format");
return map;
}
-
+
return mapData.settings.Name;
}
@@ -374,15 +358,15 @@ function initCivNameList()
g_CivData = loadCivData();
var civList = [ { "name": civ.Name, "code": civ.Code } for each (civ in g_CivData) ];
-
+
// Alphabetically sort the list, ignoring case
civList.sort(sortNameIgnoreCase);
-
+
var civListNames = [ civ.name for each (civ in civList) ];
var civListCodes = [ civ.code for each (civ in civList) ];
// Update the dropdowns
- for (var i = 0; i < g_MaxPlayers; ++i)
+ for (var i = 0; i < MAX_PLAYERS; ++i)
{
var civ = getGUIObjectByName("playerCiv["+i+"]");
civ.list = civListNames;
@@ -391,7 +375,6 @@ function initCivNameList()
}
}
-
// Initialise the list control containing all the available maps
function initMapNameList()
{
@@ -405,7 +388,7 @@ function initMapNameList()
case "scenario":
mapFiles = getXMLFileList(g_GameAttributes.mapPath);
break;
-
+
case "random":
mapFiles = getJSONFileList(g_GameAttributes.mapPath);
break;
@@ -414,7 +397,7 @@ function initMapNameList()
error("initMapNameList: Unexpected map type '"+g_GameAttributes.mapType+"'");
return;
}
-
+
// Apply map filter, if any defined
var mapList = [];
for (var i = 0; i < mapFiles.length; ++i)
@@ -428,16 +411,16 @@ function initMapNameList()
// Alphabetically sort the list, ignoring case
mapList.sort(sortNameIgnoreCase);
-
+
var mapListNames = [ map.name for each (map in mapList) ];
var mapListFiles = [ map.file for each (map in mapList) ];
-
+
// Select the default map
var selected = mapListFiles.indexOf(g_GameAttributes.map);
// Default to the first element if list is not empty and we can't find the one we searched for
if (selected == -1 && mapList.length)
selected = 0;
-
+
// Update the list control
mapSelectionBox.list = mapListNames;
mapSelectionBox.list_data = mapListFiles;
@@ -470,13 +453,47 @@ function loadMapData(name)
return g_MapData[name];
}
+////////////////////////////////////////////////////////////////////////////////////////////////
+// GUI event handlers
+
+function cancelSetup()
+{
+ Engine.DisconnectNetworkGame();
+}
+
+function onTick()
+{
+ // First tick happens before first render, so don't load yet
+ if (g_LoadingState == 0)
+ {
+ g_LoadingState++;
+ }
+ else if (g_LoadingState == 1)
+ {
+ getGUIObjectByName("loadingWindow").hidden = true;
+ getGUIObjectByName("setupWindow").hidden = false;
+ initMain();
+ g_LoadingState++;
+ }
+ else if (g_LoadingState == 2)
+ {
+ while (true)
+ {
+ var message = Engine.PollNetworkClient();
+ if (!message)
+ break;
+ handleNetMessage(message);
+ }
+ }
+}
+
// Called when user selects number of players
function selectNumPlayers(num)
{
// Avoid recursion
if (g_IsInGuiUpdate)
return;
-
+
// Network clients can't change number of players
if (g_IsNetworked && !g_IsController)
return;
@@ -485,12 +502,19 @@ function selectNumPlayers(num)
if (g_GameAttributes.mapType != "random")
return;
- g_NumPlayers = num;
+ // Update player data
+ var pData = g_GameAttributes.settings.PlayerData;
+ if (pData && num < pData.length)
+ { // Remove extra player data
+ g_GameAttributes.settings.PlayerData = pData.slice(0, num);
+ }
+ else
+ { // Add player data from defaults
+ for (var i = pData.length; i < num; ++i)
+ g_GameAttributes.settings.PlayerData.push(g_DefaultPlayerData[i]);
+ }
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- else
- onGameAttributesChange();
+ updateGameAttributes();
}
// Called when the user selects a map type from the list
@@ -527,10 +551,7 @@ function selectMapType(type)
initMapNameList();
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- else
- onGameAttributesChange();
+ updateGameAttributes();
}
function selectMapFilter(filterName)
@@ -547,10 +568,7 @@ function selectMapFilter(filterName)
initMapNameList();
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- else
- onGameAttributesChange();
+ updateGameAttributes();
}
// Called when the user selects a map from the list
@@ -573,61 +591,98 @@ function selectMap(name)
var mapData = loadMapData(name);
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
- // Load map type specific settings (so we can overwrite them in the GUI)
+ // Copy any new settings
switch (g_GameAttributes.mapType)
{
case "scenario":
- g_NumPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_MaxPlayers);
+ g_GameAttributes.settings.PlayerData = (mapSettings.PlayerData ? mapSettings.PlayerData : g_DefaultPlayerData);
break;
case "random":
- // Copy any new settings
- if (mapSettings.PlayerData)
- g_NumPlayers = mapSettings.PlayerData.length;
-
+ g_GameAttributes.script = mapSettings.Script;
g_GameAttributes.settings.Size = getSetting(mapSettings, g_GameAttributes.settings, "Size");
g_GameAttributes.settings.BaseTerrain = getSetting(mapSettings, g_GameAttributes.settings, "BaseTerrain");
g_GameAttributes.settings.BaseHeight = getSetting(mapSettings, g_GameAttributes.settings, "BaseHeight");
- g_GameAttributes.settings.RevealMap = getSetting(mapSettings, g_GameAttributes.settings, "RevealMap");
- g_GameAttributes.settings.GameType = getSetting(mapSettings, g_GameAttributes.settings, "GameType");
break;
default:
error("selectMap: Unexpected map type '"+g_GameAttributes.mapType+"'");
return;
}
-
+ g_GameAttributes.settings.Description = getSetting(mapSettings, g_GameAttributes.settings, "Description");
+ g_GameAttributes.settings.RevealMap = getSetting(mapSettings, g_GameAttributes.settings, "RevealMap");
+ g_GameAttributes.settings.LockTeams = getSetting(mapSettings, g_GameAttributes.settings, "LockTeams");
+ g_GameAttributes.settings.GameType = getSetting(mapSettings, g_GameAttributes.settings, "GameType");
+
// Reset player assignments on map change
if (!g_IsNetworked)
{ // Slot 1
g_PlayerAssignments = { "local": { "name": "You", "player": 1, "civ": "", "team": -1} };
- updatePlayerList();
}
else
{
+ var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_GameAttributes.settings.PlayerData.length);
+
for (var guid in g_PlayerAssignments)
{ // Unassign extra players
var player = g_PlayerAssignments[guid].player;
- if (player <= g_MaxPlayers && player > g_NumPlayers)
+ if (player <= MAX_PLAYERS && player > numPlayers)
Engine.AssignNetworkPlayer(player, "");
}
}
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- else
- onGameAttributesChange();
+ updateGameAttributes();
}
+function launchGame()
+{
+ if (g_IsNetworked && !g_IsController)
+ {
+ error("Only host can start game");
+ return;
+ }
+
+ if (g_IsNetworked)
+ {
+ Engine.SetNetworkGameAttributes(g_GameAttributes);
+ Engine.StartNetworkGame();
+ }
+ else
+ {
+ // Find the player ID which the user has been assigned to
+ var numPlayers = g_GameAttributes.settings.PlayerData.length;
+ var playerID = -1;
+ for (var i = 0; i < numPlayers; ++i)
+ {
+ var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
+ if (assignBox.list_data[assignBox.selected] == "local")
+ playerID = i+1;
+ }
+ // Remove extra player data
+ g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, numPlayers);
+
+ Engine.StartGame(g_GameAttributes, playerID);
+ Engine.SwitchGuiPage("page_loading.xml", {
+ "attribs": g_GameAttributes,
+ "isNetworked" : g_IsNetworked,
+ "playerAssignments": g_PlayerAssignments
+ });
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////
+
function onGameAttributesChange()
{
g_IsInGuiUpdate = true;
+ // Don't set any attributes here, just show the changes in GUI
+
var mapName = g_GameAttributes.map || "";
- var mapData = loadMapData(mapName);
- var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
-
+ var mapSettings = g_GameAttributes.settings;
+ var numPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : MAX_PLAYERS);
+
// Update some controls for clients
if (!g_IsController)
{
@@ -641,8 +696,6 @@ function onGameAttributesChange()
mapSelectionBox.selected = mapSelectionBox.list_data.indexOf(mapName);
initMapNameList();
-
- g_NumPlayers = (mapSettings.PlayerData ? mapSettings.PlayerData.length : g_MaxPlayers);
}
// Controls common to all map types
@@ -655,19 +708,16 @@ function onGameAttributesChange()
var lockTeamsText = getGUIObjectByName("lockTeamsText");
var mapSizeText = getGUIObjectByName("mapSizeText");
var numPlayersBox = getGUIObjectByName("numPlayersBox");
-
+
// Handle map type specific logic
switch (g_GameAttributes.mapType)
{
case "random":
-
- g_GameAttributes.script = mapSettings.Script;
- var sizeIdx = mapSize.list_data.indexOf(g_GameAttributes.settings.Size.toString());
-
- // Show options for host/controller
+ var sizeIdx = MAP_SIZES_DATA.indexOf(mapSettings.Size);
+
if (g_IsController)
- {
- getGUIObjectByName("numPlayersSelection").selected = g_NumPlayers - 1;
+ { //Host
+ getGUIObjectByName("numPlayersSelection").selected = numPlayers - 1;
numPlayersBox.hidden = false;
mapSize.hidden = false;
revealMap.hidden = false;
@@ -675,25 +725,23 @@ function onGameAttributesChange()
lockTeams.hidden = false;
mapSizeText.caption = "Map size:";
- mapSize.selected = sizeIdx;
revealMapText.caption = "Reveal map:";
- revealMap.checked = (g_GameAttributes.settings.RevealMap ? true : false);
+ revealMap.checked = (mapSettings.RevealMap ? true : false);
victoryConditionText.caption = "Victory condition:";
- victoryCondition.selected = victoryCondition.list_data.indexOf(g_GameAttributes.settings.GameType);
+ victoryCondition.selected = victoryCondition.list_data.indexOf(mapSettings.GameType);
lockTeamsText.caption = "Teams locked:";
- lockTeams.checked = (g_GameAttributes.settings.LockTeams === undefined || g_GameAttributes.settings.LockTeams ? true : false);
+ lockTeams.checked = (mapSettings.LockTeams === undefined || mapSettings.LockTeams ? true : false);
}
else
- {
- mapSizeText.caption = "Map size: " + (mapSize.list[sizeIdx] !== undefined ? mapSize.list[sizeIdx] : "Default");
- revealMapText.caption = "Reveal map: " + (g_GameAttributes.settings.RevealMap ? "Yes" : "No");
- victoryConditionText.caption = "Victory condition: " + (g_GameAttributes.settings.GameType && g_GameAttributes.settings.GameType == "endless" ? "None" : "Conquest");
- lockTeamsText.caption = "Teams locked: " + (g_GameAttributes.settings.LockTeams === undefined || g_GameAttributes.settings.LockTeams ? "Yes" : "No");
+ { // Client
+ mapSizeText.caption = "Map size: " + (sizeIdx != -1 ? MAP_SIZES_TEXT[sizeIdx] : "Other (" + mapSettings.Size + ")");
+ revealMapText.caption = "Reveal map: " + (mapSettings.RevealMap ? "Yes" : "No");
+ victoryConditionText.caption = "Victory condition: " + (mapSettings.GameType && mapSettings.GameType == "endless" ? "None" : "Conquest");
+ lockTeamsText.caption = "Teams locked: " + (mapSettings.LockTeams === undefined || mapSettings.LockTeams ? "Yes" : "No");
}
break;
- case "saved":
case "scenario":
// For scenario just reflect settings for the current map
numPlayersBox.hidden = true;
@@ -716,21 +764,20 @@ function onGameAttributesChange()
// Display map name
getGUIObjectByName("mapInfoName").caption = getMapDisplayName(mapName);
-
+
// Load the description from the map file, if there is one
var description = mapSettings.Description || "Sorry, no description available.";
-
+
// Describe the number of players
- var playerString = g_NumPlayers + " " + (g_NumPlayers == 1 ? "player" : "players") + ". ";
+ var playerString = numPlayers + " " + (numPlayers == 1 ? "player" : "players") + ". ";
-
- for (var i = 0; i < g_MaxPlayers; ++i)
+ for (var i = 0; i < MAX_PLAYERS; ++i)
{
// Show only needed player slots
- getGUIObjectByName("playerBox["+i+"]").hidden = (i >= g_NumPlayers);
+ getGUIObjectByName("playerBox["+i+"]").hidden = (i >= numPlayers);
// Show player data or defaults as necessary
- if (i < g_NumPlayers)
+ if (i < numPlayers)
{
var pName = getGUIObjectByName("playerName["+i+"]");
var pCiv = getGUIObjectByName("playerCiv["+i+"]");
@@ -741,7 +788,7 @@ function onGameAttributesChange()
// Player data / defaults
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[i] : {};
- var pDefs = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[i] : {};
+ var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[i] : {};
// Common to all game types
var color = iColorToString(getSetting(pData, pDefs, "Colour"));
@@ -750,16 +797,18 @@ function onGameAttributesChange()
var team = getSetting(pData, pDefs, "Team");
var civ = getSetting(pData, pDefs, "Civ");
-
- if (g_GameAttributes.mapType == "scenario")
- {
- // TODO: If scenario settings can be changed, handle that (using dropdowns rather than textboxes)
- pCivText.caption = g_CivData[civ].Name;
- pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
+
+ // For clients or scenarios, hide some player dropdowns
+ if (!g_IsController || g_GameAttributes.mapType == "scenario")
+ {
pCivText.hidden = false;
pCiv.hidden = true;
pTeamText.hidden = false;
pTeam.hidden = true;
+
+ // Set text values
+ pCivText.caption = g_CivData[civ].Name;
+ pTeamText.caption = (team !== undefined && team >= 0) ? team+1 : "-";
}
else if (g_GameAttributes.mapType == "random")
{
@@ -783,41 +832,12 @@ function onGameAttributesChange()
updatePlayerList();
}
-function launchGame()
+function updateGameAttributes()
{
- if (g_IsNetworked && !g_IsController)
- {
- error("Only host can start game");
- return;
- }
-
- // TODO: Generate seed here for random maps
-
if (g_IsNetworked)
- {
Engine.SetNetworkGameAttributes(g_GameAttributes);
- Engine.StartNetworkGame();
- }
- else
- {
- // Find the player ID which the user has been assigned to
- var playerID = -1;
- for (var i = 0; i < g_NumPlayers; ++i)
- {
- var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
- if (assignBox.list_data[assignBox.selected] == "local")
- playerID = i+1;
- }
- // Remove extra player data
- g_GameAttributes.settings.PlayerData = g_GameAttributes.settings.PlayerData.slice(0, g_NumPlayers);
-
- Engine.StartGame(g_GameAttributes, playerID);
- Engine.SwitchGuiPage("page_loading.xml", {
- "attribs": g_GameAttributes,
- "isNetworked" : g_IsNetworked,
- "playerAssignments": g_PlayerAssignments
- });
- }
+ else
+ onGameAttributesChange();
}
function updatePlayerList()
@@ -842,135 +862,139 @@ function updatePlayerList()
}
for each (var ai in g_AIs)
- {
+ { // Give AI a different color so it stands out
aiAssignments[ai.id] = hostNameList.length;
- hostNameList.push("[color=\"90 90 90 255\"]AI: " + ai.data.name);
+ hostNameList.push("[color=\"30 90 30 255\"]AI: " + ai.data.name);
hostGuidList.push("ai:" + ai.id);
}
noAssignment = hostNameList.length;
hostNameList.push("[color=\"90 90 90 255\"]Unassigned");
hostGuidList.push("");
-
- for (var i = 0; i < g_MaxPlayers; ++i)
+
+ for (var i = 0; i < MAX_PLAYERS; ++i)
{
let playerSlot = i;
let playerID = i+1; // we don't show Gaia, so first slot is ID 1
-
+
var selection = assignments[playerID];
-
+
var configButton = getGUIObjectByName("playerConfig["+i+"]");
configButton.hidden = true;
-
- // If no human is assigned, look for an AI instead
- if (selection === undefined)
+
+ // Look for valid player slots
+ if (playerSlot < g_GameAttributes.settings.PlayerData.length)
{
- var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
- if (aiId)
- selection = aiAssignments[aiId];
- else
- selection = noAssignment;
-
- // Since no human is assigned, show the AI config button
- if (g_IsController)
+ // If no human is assigned, look for an AI instead
+ if (selection === undefined)
{
- configButton.hidden = false;
- configButton.onpress = function()
+ var aiId = g_GameAttributes.settings.PlayerData[playerSlot].AI;
+ if (aiId)
+ selection = aiAssignments[aiId];
+ else
+ selection = noAssignment;
+
+ // Since no human is assigned, show the AI config button
+ if (g_IsController)
{
- Engine.PushGuiPage("page_aiconfig.xml", {
- ais: g_AIs,
- id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
- callback: function(ai) {
- g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id;
+ configButton.hidden = false;
+ configButton.onpress = function()
+ {
+ Engine.PushGuiPage("page_aiconfig.xml", {
+ ais: g_AIs,
+ id: g_GameAttributes.settings.PlayerData[playerSlot].AI,
+ callback: function(ai) {
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = ai.id;
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- else
- updatePlayerList();
+ if (g_IsNetworked)
+ Engine.SetNetworkGameAttributes(g_GameAttributes);
+ else
+ updatePlayerList();
+ }
+ });
+ };
+ }
+ }
+ else
+ {
+ // There was a human, so make sure we don't have any AI left
+ // over in their slot, if we're in charge of the attributes
+ if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
+ {
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
+ if (g_IsNetworked)
+ Engine.SetNetworkGameAttributes(g_GameAttributes);
+ }
+ }
+
+ var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
+ assignBox.list = hostNameList;
+ assignBox.list_data = hostGuidList;
+ if (assignBox.selected != selection)
+ assignBox.selected = selection;
+
+ if (g_IsNetworked && g_IsController)
+ {
+ assignBox.onselectionchange = function ()
+ {
+ if (!g_IsInGuiUpdate)
+ {
+ var guid = hostGuidList[this.selected];
+ if (guid == "")
+ {
+ // Unassign any host from this player slot
+ Engine.AssignNetworkPlayer(playerID, "");
+ // Remove AI from this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
}
- });
+ else if (guid.substr(0, 3) == "ai:")
+ {
+ // Unassign any host from this player slot
+ Engine.AssignNetworkPlayer(playerID, "");
+ // Set the AI for this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
+ }
+ else
+ {
+ // Reassign the host to this player slot
+ Engine.AssignNetworkPlayer(playerID, guid);
+ // Remove AI from this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
+ }
+ Engine.SetNetworkGameAttributes(g_GameAttributes);
+ }
};
}
- }
- else
- {
- // There was a human, so make sure we don't have any AI left
- // over in their slot, if we're in charge of the attributes
- if (g_IsController && g_GameAttributes.settings.PlayerData[playerSlot].AI != "")
+ else if (!g_IsNetworked)
{
- g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
- if (g_IsNetworked)
- Engine.SetNetworkGameAttributes(g_GameAttributes);
+ assignBox.onselectionchange = function ()
+ {
+ if (!g_IsInGuiUpdate)
+ {
+ var guid = hostGuidList[this.selected];
+ if (guid == "")
+ {
+ // Remove AI from this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
+ }
+ else if (guid.substr(0, 3) == "ai:")
+ {
+ // Set the AI for this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
+ }
+ else
+ {
+ // Update the selected host's player ID
+ g_PlayerAssignments[guid].player = playerID;
+ // Remove AI from this player slot
+ g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
+ }
+
+ updatePlayerList();
+ }
+ };
}
}
-
- var assignBox = getGUIObjectByName("playerAssignment["+i+"]");
- assignBox.list = hostNameList;
- assignBox.list_data = hostGuidList;
- if (assignBox.selected != selection)
- assignBox.selected = selection;
-
- if (g_IsNetworked && g_IsController)
- {
- assignBox.onselectionchange = function ()
- {
- if (!g_IsInGuiUpdate)
- {
- var guid = hostGuidList[this.selected];
- if (guid == "")
- {
- // Unassign any host from this player slot
- Engine.AssignNetworkPlayer(playerID, "");
- // Remove AI from this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
- }
- else if (guid.substr(0, 3) == "ai:")
- {
- // Unassign any host from this player slot
- Engine.AssignNetworkPlayer(playerID, "");
- // Set the AI for this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
- }
- else
- {
- // Reassign the host to this player slot
- Engine.AssignNetworkPlayer(playerID, guid);
- // Remove AI from this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
- }
- Engine.SetNetworkGameAttributes(g_GameAttributes);
- }
- };
- }
- else if (!g_IsNetworked)
- {
- assignBox.onselectionchange = function ()
- {
- if (!g_IsInGuiUpdate)
- {
- var guid = hostGuidList[this.selected];
- if (guid == "")
- {
- // Remove AI from this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
- }
- else if (guid.substr(0, 3) == "ai:")
- {
- // Set the AI for this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = guid.substr(3);
- }
- else
- {
- // Update the selected host's player ID
- g_PlayerAssignments[guid].player = playerID;
- // Remove AI from this player slot
- g_GameAttributes.settings.PlayerData[playerSlot].AI = "";
- }
-
- updatePlayerList();
- }
- };
- }
}
g_IsInGuiUpdate = false;
@@ -1002,7 +1026,7 @@ function addChatMessage(msg)
var mapData = loadMapData(mapName);
var mapSettings = (mapData && mapData.settings ? mapData.settings : {});
var pData = mapSettings.PlayerData ? mapSettings.PlayerData[player] : {};
- var pDefs = g_GameAttributes.settings.PlayerData ? g_GameAttributes.settings.PlayerData[player] : {};
+ var pDefs = g_DefaultPlayerData ? g_DefaultPlayerData[player] : {};
color = iColorToString(getSetting(pData, pDefs, "Colour"));
}
@@ -1032,7 +1056,7 @@ function addChatMessage(msg)
getGUIObjectByName("chatText").caption = g_ChatMessages.join("\n");
}
-
+////////////////////////////////////////////////////////////////////////////////////////////////
// Basic map filters API
// Add a new map list filter
diff --git a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
index 0bb256961b..9548ca34a5 100644
--- a/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ b/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -134,15 +134,15 @@
diff --git a/binaries/data/mods/public/maps/random/andalucia.js b/binaries/data/mods/public/maps/random/andalucia.js
deleted file mode 100644
index 9882668657..0000000000
--- a/binaries/data/mods/public/maps/random/andalucia.js
+++ /dev/null
@@ -1,4 +0,0 @@
-const SIZE = 64;
-
-init(SIZE, "grass dirt 50", 0);
-
diff --git a/binaries/data/mods/public/maps/random/blank.js b/binaries/data/mods/public/maps/random/blank.js
new file mode 100644
index 0000000000..5caf9e789f
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/blank.js
@@ -0,0 +1,8 @@
+// Blank map, possibly useful for Atlas or as fallback
+
+RMS.LoadLibrary("rmgen");
+
+InitMap();
+
+// Export map data
+ExportMap();
diff --git a/binaries/data/mods/public/maps/random/blank256x256.js b/binaries/data/mods/public/maps/random/blank256x256.js
deleted file mode 100644
index 6e0733af6e..0000000000
--- a/binaries/data/mods/public/maps/random/blank256x256.js
+++ /dev/null
@@ -1 +0,0 @@
-init(256, "grass_mediterranean_green_50", 0);
diff --git a/binaries/data/mods/public/maps/random/blank400x400.js b/binaries/data/mods/public/maps/random/blank400x400.js
deleted file mode 100644
index ac6c1e5070..0000000000
--- a/binaries/data/mods/public/maps/random/blank400x400.js
+++ /dev/null
@@ -1 +0,0 @@
-init(400, "grass_mediterranean_green_50", 0);
diff --git a/binaries/data/mods/public/maps/random/blank512x512.js b/binaries/data/mods/public/maps/random/blank512x512.js
deleted file mode 100644
index 50458d8728..0000000000
--- a/binaries/data/mods/public/maps/random/blank512x512.js
+++ /dev/null
@@ -1 +0,0 @@
-init(512, "grass_mediterranean_green_50", 0);
diff --git a/binaries/data/mods/public/maps/random/cantabrian_highlands.js b/binaries/data/mods/public/maps/random/cantabrian_highlands.js
index 406731d06b..b78fcf2a15 100644
--- a/binaries/data/mods/public/maps/random/cantabrian_highlands.js
+++ b/binaries/data/mods/public/maps/random/cantabrian_highlands.js
@@ -1,336 +1,376 @@
-// constants
-
-const SIZE = 208;
-const NUM_PLAYERS = 4;
-
-
-const tGrass = ["grass_temperate_pasture_a", "grass_temperate_pasture_b"];
-const tGrassForest = "grass_temperate_a";
-const tCliff = ["cliff_temperate_granite", "cliff_temperate_brown"];
-const tForest = "forestfloor_temperate_a";
-const tGrassDirt75 = "grass_temperate_dirt_2";
-const tGrassDirt50 = "grass_temperate_dirt_b";
-const tGrassDirt25 = "grass_temperate_dry_dirt";
-const tDirt = "dirt_temperate_b_dry";
-const tGrassPatch = "grass_temperate_field_wild";
-const tShore = "shoreline_temperate_rocks";
-const tShoreBlend = "shoreline_temperate_rocks_dirt";
-const tWater = "sand_temperate_vegetation";
-
-const oTree = "flora_tree_oak";
-const oTreeLarge = "flora_tree_oak";
-const oBerryBush = "flora_bush_berry";
-const oSheep = "fauna_sheep";
-const oDeer = "fauna_deer";
-const oMine = "geology_stone_temperate";
-const oGrass = "props/flora/grass_temp_field.xml";
-const oGrassShort = "props/flora/grass_field_lush_short.xml";
-const oReeds = "props/flora/grass_temp_field_dry.xml";
-const oRockLarge = "geology/stone_granite_large.xml";
-const oRockMedium = "flora_bush_temperate";
-const oBushMedium = "props/flora/bush_tempe_me.xml";
-const oBushSmall = "props/flora/bush_tempe_sm.xml";
-
-// initialize map
-
-println("Initializing map...");
-init(SIZE, tGrass, 3);
-
-// create tile classes
-
-clPlayer = createTileClass();
-clHill = createTileClass();
-clForest = createTileClass();
-clWater = createTileClass();
-clSettlement = createTileClass();
-clDirt = createTileClass();
-clRock = createTileClass();
-clFood = createTileClass();
-clBaseResource = createTileClass();
-
-// place players
-
-playerX = new Array(NUM_PLAYERS+1);
-playerY = new Array(NUM_PLAYERS+1);
-playerAngle = new Array(NUM_PLAYERS+1);
-
-startAngle = randFloat() * 2 * PI;
-for(i=1; i<=NUM_PLAYERS; i++) {
- playerAngle[i] = startAngle + i*2*PI/NUM_PLAYERS;
- playerX[i] = 0.5 + 0.35*cos(playerAngle[i]);
- playerY[i] = 0.5 + 0.35*sin(playerAngle[i]);
-}
-
-for(i=1; i<=NUM_PLAYERS; i++) {
- println("Creating base for player " + i + "...");
-
- // some constants
- radius = 17;
- cliffRadius = 2;
- elevation = 20;
-
- // get the x and y in tiles
- fx = fractionToTiles(playerX[i]);
- fy = fractionToTiles(playerY[i]);
- ix = round(fx);
- iy = round(fy);
-
- // calculate size based on the radius
- size = PI * radius * radius;
-
- // create the hill
- placer = new ClumpPlacer(size, 0.95, 0.6, 0, ix, iy);
- terrainPainter = new LayeredPainter(
- [cliffRadius], // widths
- [tCliff, tGrass] // terrains
- );
- elevationPainter = new SmoothElevationPainter(
- ELEVATION_SET, // type
- elevation, // elevation
- cliffRadius // blend radius
- );
- createArea(placer, [terrainPainter, elevationPainter, paintClass(clPlayer)], null);
-
- // create the ramp
- rampAngle = playerAngle[i] + PI + (2*randFloat()-1)*PI/8;
- rampDist = radius - 1;
- rampX = round(fx + rampDist * cos(rampAngle));
- rampY = round(fy + rampDist * sin(rampAngle));
- placer = new ClumpPlacer(100, 0.9, 0.5, 0, rampX, rampY);
- painter = new SmoothElevationPainter(ELEVATION_SET, elevation-6, 5);
- createArea(placer, painter, null);
- placer = new ClumpPlacer(75, 0.9, 0.5, 0, rampX, rampY);
- painter = new TerrainPainter(tGrass);
- createArea(placer, painter, null);
-
- // create the central dirt patch
- placer = new ClumpPlacer(PI*3.5*3.5, 0.3, 0.1, 0, ix, iy);
- painter = new LayeredPainter(
- [1,1,1], // widths
- [tGrassDirt75, tGrassDirt50, tGrassDirt25, tDirt] // terrains
- );
- createArea(placer, painter, null);
-
- // create the TC and the "villies"
- group = new SimpleGroup(
- [ // elements (type, count, distance)
- new SimpleObject("hele_civil_centre", 1,1, 0,0),
- new SimpleObject("hele_infantry_javelinist_b", 3,3, 5,5)
- ],
- true, null, ix, iy
- );
- createObjectGroup(group, i);
-
- // Create the Settlement under the TC
- group = new SimpleGroup(
- [new SimpleObject("special_settlement", 1,1, 0,0)],
- true, null, ix, iy
- );
- createObjectGroup(group, 0);
-
- // create berry bushes
- bbAngle = randFloat()*2*PI;
- bbDist = 9;
- bbX = round(fx + bbDist * cos(bbAngle));
- bbY = round(fy + bbDist * sin(bbAngle));
- group = new SimpleGroup(
- [new SimpleObject(oBerryBush, 5,5, 0,2)],
- true, clBaseResource, bbX, bbY
- );
- createObjectGroup(group, 0);
-
- // create mines
- mAngle = bbAngle;
- while(abs(mAngle - bbAngle) < PI/3) {
- mAngle = randFloat()*2*PI;
- }
- mDist = 9;
- mX = round(fx + mDist * cos(mAngle));
- mY = round(fy + mDist * sin(mAngle));
- group = new SimpleGroup(
- [new SimpleObject(oMine, 4,4, 0,2)],
- true, clBaseResource, mX, mY
- );
- createObjectGroup(group, 0);
-
- // create starting straggler trees
- group = new SimpleGroup(
- [new SimpleObject(oTree, 3,3, 8,12)],
- true, clBaseResource, ix, iy
- );
- createObjectGroup(group, 0, avoidClasses(clBaseResource,2));
-
- // create grass tufts
- for(j=0; j<10; j++) {
- gAngle = randFloat()*2*PI;
- gDist = 6 + randInt(9);
- gX = round(fx + gDist * cos(gAngle));
- gY = round(fy + gDist * sin(gAngle));
- group = new SimpleGroup([new SimpleObject(oGrassShort, 3,6, 0,1, -PI/8,PI/8)],
- false, clBaseResource, gX, gY);
- createObjectGroup(group, 0);
- }
-}
-
-// create lakes
-println("Creating lakes...");
-placer = new ClumpPlacer(140, 0.8, 0.1, 0);
-terrainPainter = new LayeredPainter(
- [1,1], // widths
- [tShoreBlend, tShore, tWater] // terrains
-);
-elevationPainter = new SmoothElevationPainter(ELEVATION_SET, -7, 3);
-createAreas(placer, [terrainPainter, elevationPainter, paintClass(clWater)],
- avoidClasses(clPlayer, 2, clWater, 20),
- round(1.3 * NUM_PLAYERS)
-);
-
-// create bumps
-println("Creating bumps...");
-placer = new ClumpPlacer(10, 0.3, 0.06, 0);
-painter = new SmoothElevationPainter(ELEVATION_MODIFY, 2, 2);
-createAreas(placer, painter,
- avoidClasses(clWater, 2, clPlayer, 0),
- SIZE*SIZE/100
-);
-
-// create hills
-println("Creating hills...");
-placer = new ClumpPlacer(30, 0.2, 0.1, 0);
-terrainPainter = new LayeredPainter(
- [3], // widths
- [tCliff, [tGrass,tGrass,tGrassDirt75]] // terrains
-);
-elevationPainter = new SmoothElevationPainter(ELEVATION_SET, 12, 2);
-createAreas(placer, [terrainPainter, elevationPainter, paintClass(clHill)],
- avoidClasses(clPlayer, 2, clWater, 5, clHill, 15),
- 2 * NUM_PLAYERS
-);
-
-// create forests
-println("Creating forests...");
-placer = new ClumpPlacer(32, 0.1, 0.1, 0);
-painter = new LayeredPainter([2], [[tGrassForest, tGrass, tForest],
- [tGrassForest, tForest]]);
-createAreas(placer, [painter, paintClass(clForest)],
- avoidClasses(clPlayer, 1, clWater, 3, clForest, 10, clHill, 0),
- 6 * NUM_PLAYERS
-);
-
-// create dirt patches
-println("Creating dirt patches...");
-var sizes = [8,14,20];
-for(i=0; i y ? x : y;
-}
-
-function min(x, y) {
- return x < y ? x : y;
-}
-
-// Paint elevation
-
-println("Painting elevation...");
-
-noise0 = new Noise2D(4 * SIZE/128);
-noise1 = new Noise2D(8 * SIZE/128);
-noise2 = new Noise2D(15 * SIZE/128);
-
-noise2a = new Noise2D(20 * SIZE/128);
-noise2b = new Noise2D(35 * SIZE/128);
-
-noise3 = new Noise2D(4 * SIZE/128);
-noise4 = new Noise2D(6 * SIZE/128);
-noise5 = new Noise2D(11 * SIZE/128);
-
-for(ix=0; ix<=SIZE; ix++) {
- for(iy=0; iy<=SIZE; iy++) {
- x = ix / (SIZE + 1.0);
- y = iy / (SIZE + 1.0);
- pn = playerNearness(x, y);
-
- h = 0;
- distToWater = 0;
-
- h = 32 * (x-.5);
-
-
- // add the rough shape of the water
- if(x < WATER_WIDTH) {
- h = max(-16.0, -28.0*(WATER_WIDTH-x)/WATER_WIDTH);
- }
- else if(x > 1.0-WATER_WIDTH) {
- h = max(-16.0, -28.0*(x-(1.0-WATER_WIDTH))/WATER_WIDTH);
- }
- else {
- distToWater = (0.5 - WATER_WIDTH - Math.abs(x-0.5));
- u = 1 - Math.abs(x-0.5) / (0.5-WATER_WIDTH);
- h = 12*u;
- }
-
- // add some base noise
- baseNoise = 16*noise0.eval(x,y) + 8*noise1.eval(x,y) + 4*noise2.eval(x,y) - (16+8+4)/2;
- if( baseNoise < 0 ) {
- baseNoise *= pn;
- baseNoise *= max(0.1, distToWater / (0.5-WATER_WIDTH));
- }
- oldH = h;
- h += baseNoise;
-
- // add some higher-frequency noise on land
- if( oldH > 0 )
- {
- h += (0.4*noise2a.eval(x,y) + 0.2*noise2b.eval(x,y)) * min(oldH/10.0, 1.0);
- }
-
- // create cliff noise
- if( h > -10 )
- {
- cliffNoise = (1*noise3.eval(x,y) + 0.5*noise4.eval(x,y)) / 1.5;
- if(h < 1) {
- u = 1 - .3*((h-1)/-10);
- cliffNoise *= u;
- }
- cliffNoise += .05 * distToWater / (0.5 - WATER_WIDTH);
- if(cliffNoise > .6) {
- u = 0.8 * (cliffNoise-.6);
- cliffNoise += u * noise5.eval(x,y);
- cliffNoise /= (1+u);
- }
- cliffNoise -= 0.59;
- cliffNoise *= pn;
- if(cliffNoise > 0) {
- h += 19 * min(cliffNoise, 0.045) / 0.045;
- }
- }
-
- // set the height
- setHeight(ix, iy, h);
- }
-}
-
-// Paint base terrain
-
-println("Painting terrain...");
-
-noise6 = new Noise2D(10 * SIZE/128);
-noise7 = new Noise2D(20 * SIZE/128);
-
-noise8 = new Noise2D(13 * SIZE/128);
-noise9 = new Noise2D(26 * SIZE/128);
-
-noise10 = new Noise2D(50 * SIZE/128);
-
-for(ix=0; ix 15) {
- for(nx=max(ix-1, 0); nx<=min(ix+2, SIZE); nx++) {
- for(ny=max(iy-1, 0); ny<=min(iy+2, SIZE); ny++) {
- minAdjHeight = min(minAdjHeight, getHeight(nx, ny));
- }
- }
- }
-
- // choose a terrain based on elevation
- t = tGrass;
-
- // water
- if(maxH < -12) {
- t = tOceanDepths;
- }
- else if(maxH < -8.8) {
- t = tOceanRockDeep;
- }
- else if(maxH < -4.7) {
- t = tOceanCoral;
- }
- else if(maxH < -2.8) {
- t = tOceanRockShallow;
- }
- else if(maxH < .9 && minH < .35) {
- t = tBeachWet;
- }
- else if(maxH < 1.5 && minH < .9) {
- t = tBeachDry;
- }
- else if(maxH < 2.3 && minH < 1.3) {
- t = tBeachGrass;
- }
-
- if(minH < 0) {
- addToClass(ix, iy, clWater);
- }
-
- // cliffs
- if(maxH - minH > 2.9 && minH > -7) {
- t = tCliff;
- addToClass(ix, iy, clCliff);
- }
- else if((maxH - minH > 2.5 && minH > -5) || (maxH-minAdjHeight > 2.9 && minH > 0) ) {
- if(minH < -1) t = tCliff;
- else if(minH < .5) t = tBeachCliff;
- else t = [tDirtCliff, tGrassCliff, tGrassCliff, tGrassRock, tCliff];
- addToClass(ix, iy, clCliff);
- }
-
- // forests
- if(maxH - minH < 1 && minH > 1) {
- forestNoise = (noise6.eval(x,y) + 0.5*noise7.eval(x,y)) / 1.5 * pn;
- forestNoise -= 0.59;
- if(forestNoise > 0) {
- if(minH > 5) {
- typeNoise = noise10.eval(x,y);
- if(typeNoise < .43 && forestNoise < .05) t = tPoplarForest;
- else if(typeNoise < .63) t = tMainForest;
- else t = tPineForest;
- addToClass(ix, iy, clForest);
- }
- else if(minH < 3) {
- t = tPalmForest;
- addToClass(ix, iy, clForest);
- }
- }
- }
-
- // grass variations
- if(t==tGrass)
- {
- grassNoise = (noise8.eval(x,y) + .6*noise9.eval(x,y)) / 1.6;
- if(grassNoise < .3) {
- t = (maxH - minH > 1.2) ? tDirtCliff : tDirt;
- }
- else if(grassNoise < .34) {
- t = (maxH - minH > 1.2) ? tGrassCliff : tGrassDry;
- if(maxH - minH < .5 && randFloat() < .03) {
- placeObject(oGrassDry, 0, ix+randFloat(), iy+randFloat(), randFloat()*2*Math.PI);
- }
- }
- else if(grassNoise > .61) {
- t = (maxH - minH > 1.2) ? tGrassRock : tGrassShrubs;
- }
- else {
- if(maxH - minH < .5 && randFloat() < .05) {
- placeObject(oGrass, 0, ix+randFloat(), iy+randFloat(), randFloat()*2*Math.PI);
- }
- }
- }
-
- placeTerrain(ix, iy, t);
- }
-}
-
-println("Placing object groups...");
-
-// create settlements
-group = new SimpleGroup([new SimpleObject("special_settlement", 1,1, 0,0)], true, clSettlement);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 5, clForest, 4, clPlayer, 25, clCliff, 4, clSettlement, 35),
- 2 * NUM_PLAYERS, 50
-);
-
-// create straggler trees
-trees = [oCarob, oBeech, oLombardyPoplar, oLombardyPoplar, oPine]
-for(t in trees) {
- group = new SimpleGroup([new SimpleObject(trees[t], 1,1, 0,1)], true, clForest);
- createObjectGroups(group, 0,
- avoidClasses(clWater, 5, clCliff, 0, clForest, 1, clSettlement, 4, clPlayer, 15),
- SIZE*SIZE/7000, 50
- );
-}
-
-// create cypresses
-group = new SimpleGroup([
- new SimpleObject("flora/trees/cypress2.xml", 1,3, 0,3),
- new SimpleObject("flora/trees/cypress1.xml", 0,2, 0,2)]);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 4, clCliff, 2, clForest, 1, clSettlement, 4, clPlayer, 15),
- SIZE*SIZE/3500, 50
-);
-
-// create bushes
-group = new SimpleGroup([
- new SimpleObject(oBushSmall, 0,2, 0,2),
- new SimpleObject(oBushSmallDry, 0,2, 0,2),
- new SimpleObject(oBushMed, 0,1, 0,2),
- new SimpleObject(oBushMedDry, 0,1, 0,2)]);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 4, clCliff, 2),
- SIZE*SIZE/1800, 50
-);
-
-// create rocks
-group = new SimpleGroup([
- new SimpleObject(oRockSmall, 0,3, 0,2),
- new SimpleObject(oRockMed, 0,2, 0,2),
- new SimpleObject(oRockLarge, 0,1, 0,2)]);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 0, clCliff, 0),
- SIZE*SIZE/1800, 50
-);
-
-// create stone
-group = new SimpleGroup([new SimpleObject(oStone, 2,3, 0,2)], true, clStone);
-createObjectGroups(group, 0,
- [avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clStone, 15, clSettlement, 4),
- new BorderTileClassConstraint(clCliff, 0, 5)],
- 3 * NUM_PLAYERS, 100
-);
-
-// create metal
-group = new SimpleGroup([new SimpleObject(oMetal, 2,3, 0,2)], true, clMetal);
-createObjectGroups(group, 0,
- [avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clMetal, 15, clStone, 5, clSettlement, 4),
- new BorderTileClassConstraint(clCliff, 0, 5)],
- 3 * NUM_PLAYERS, 100
-);
-
-// create sheep
-group = new SimpleGroup([new SimpleObject(oSheep, 2,4, 0,2)], true, clFood);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 5, clForest, 1, clCliff, 1, clPlayer, 20,
- clMetal, 2, clStone, 2, clFood, 8, clSettlement, 4),
- 3 * NUM_PLAYERS, 100
-);
-
-// create berry bushes
-group = new SimpleGroup([new SimpleObject(oBerryBush, 5,7, 0,3)], true, clFood);
-createObjectGroups(group, 0,
- avoidClasses(clWater, 5, clForest, 1, clCliff, 1, clPlayer, 20,
- clMetal, 2, clStone, 2, clFood, 8, clSettlement, 4),
- 1.5 * NUM_PLAYERS, 100
-);
+RMS.LoadLibrary("rmgen");
+
+const WATER_WIDTH = 0.2;
+
+// terrain textures
+const tOceanDepths = "medit_sea_depths";
+const tOceanRockDeep = "medit_sea_coral_deep";
+const tOceanRockShallow = "medit_rocks_wet";
+const tOceanCoral = "medit_sea_coral_plants";
+const tBeachWet = "medit_sand_wet";
+const tBeachDry = "medit_sand";
+const tBeachGrass = "beach_medit_grass_50";
+const tBeachCliff = "cliff_medit_beach";
+const tGrassDry = ["medit_grass_field_brown", "medit_grass_field_dry", "medit_grass_field_b"];
+const tGrass = ["medit_grass_field", "medit_grass_field_a", "medit_grass_flowers"];
+const tGrassLush = ["grass_temperate_dry_tufts", "medit_grass_flowers"];
+const tGrassShrubs = ["medit_grass_shrubs", "medit_grass_flowers"];
+const tGrassRock = ["medit_rocks_grass"];
+const tDirt = "medit_dirt";
+const tDirtGrass = "medit_dirt_b";
+const tDirtCliff = "medit_cliff_italia";
+const tGrassCliff = "medit_cliff_italia_grass";
+const tCliff = ["medit_cliff_italia", "medit_cliff_italia", "medit_cliff_italia_grass"];
+const tForestFloor = "forestfloor_medit_dirt";
+
+// gaia entities
+const oBeech = "gaia/flora_tree_euro_beech";
+const oBerryBush = "gaia/flora_bush_berry";
+const oCarob = "gaia/flora_tree_carob";
+const oCypress1 = "gaia/flora_tree_cypress";
+const oCypress2 = "gaia/flora_tree_cypress";
+const oLombardyPoplar = "gaia/flora_tree_poplar_lombardy";
+const oOak = "gaia/flora_tree_oak";
+const oPalm = "gaia/flora_tree_medit_fan_palm";
+const oPine = "gaia/flora_tree_aleppo_pine";
+const oPoplar = "gaia/flora_tree_poplar";
+const oSheep = "gaia/fauna_sheep";
+const oStone = "gaia/geology_stone_greek";
+const oMetal = "gaia/geology_metal_greek";
+
+// decorative props
+const aBushLargeDry = "actor|props/flora/bush_medit_la_dry.xml";
+const aBushLarge = "actor|props/flora/bush_medit_la.xml";
+const aBushMedDry = "actor|props/flora/bush_medit_me_dry.xml";
+const aBushMed = "actor|props/flora/bush_medit_me.xml";
+const aBushSmall = "actor|props/flora/bush_medit_sm.xml";
+const aBushSmallDry = "actor|props/flora/bush_medit_sm_dry.xml";
+const aGrass = "actor|props/flora/grass_medit_field.xml";
+const aGrassDry = "actor|props/flora/grass_soft_dry_small.xml";
+const aRockLarge = "actor|geology/stone_granite_greek_large.xml";
+const aRockMed = "actor|geology/stone_granite_greek_med.xml";
+const aRockSmall = "actor|geology/stone_granite_greek_small.xml";
+const aWaterLog = "actor|props/flora/water_log.xml";
+
+// terrain + entity (for painting)
+var pPalmForest = tForestFloor+TERRAIN_SEPARATOR+oPalm;
+var pPineForest = tForestFloor+TERRAIN_SEPARATOR+oPine;
+var pCarobForest = tForestFloor+TERRAIN_SEPARATOR+oCarob;
+var pBeechForest = tForestFloor+TERRAIN_SEPARATOR+oBeech;
+var pPoplarForest = tForestFloor+TERRAIN_SEPARATOR+oLombardyPoplar;
+var tPalmForest = [pPalmForest, tGrass];
+var tPineForest = [pPineForest, tGrass];
+var tMainForest = [pCarobForest, pBeechForest, tGrass, tGrass];
+var tPoplarForest = [pPoplarForest, tGrass];
+
+// initialize map
+
+log("Initializing map...");
+
+InitMap();
+
+var numPlayers = getNumPlayers();
+var mapSize = getMapSize();
+
+// Create classes
+
+var clWater = createTileClass();
+var clCliff = createTileClass();
+var clForest = createTileClass();
+var clMetal = createTileClass();
+var clStone = createTileClass();
+var clFood = createTileClass();
+var clPlayer = createTileClass();
+var clBaseResource = createTileClass();
+
+// Place players
+
+log("Placing players...");
+
+var playerX = new Array(numPlayers+1);
+var playerY = new Array(numPlayers+1);
+
+var numLeftPlayers = floor(numPlayers/2);
+for (var i=1; i <= numLeftPlayers; i++)
+{
+ playerX[i] = 0.28 + (2*randFloat()-1)*0.01;
+ playerY[i] = (0.5+i-1)/numLeftPlayers + (2*randFloat()-1)*0.01;
+}
+for (var i=numLeftPlayers+1; i <= numPlayers; i++)
+{
+ playerX[i] = 0.72 + (2*randFloat()-1)*0.01;
+ playerY[i] = (0.5+i-numLeftPlayers-1)/numLeftPlayers + (2*randFloat()-1)*0.01;
+}
+
+for (var i=1; i <= numPlayers; i++)
+{
+ log("Creating base for player " + i + "...");
+
+ // get fractional locations in tiles
+ var ix = round(fractionToTiles(playerX[i]));
+ var iy = round(fractionToTiles(playerY[i]));
+ addToClass(ix, iy, clPlayer);
+
+ // create TC and starting units
+ // TODO: Get civ specific starting units
+ var civ = getCivCode(i - 1);
+ placeObject("structures/"+civ + "_civil_centre", i, ix, iy, PI*3/4);
+ var group = new SimpleGroup(
+ [new SimpleObject("units/"+civ+"_support_female_citizen", 3,3, 5,5)],
+ true, null, ix, iy
+ );
+ createObjectGroup(group, i);
+
+ // create starting berry bushes
+ var bbAngle = randFloat()*2*PI;
+ var bbDist = 9;
+ var bbX = round(ix + bbDist * cos(bbAngle));
+ var bbY = round(iy + bbDist * sin(bbAngle));
+ group = new SimpleGroup(
+ [new SimpleObject(oBerryBush, 5,5, 0,2)],
+ true, clBaseResource, bbX, bbY
+ );
+ createObjectGroup(group, 0);
+
+ // create starting mines
+ var mAngle = bbAngle;
+ while(abs(mAngle - bbAngle) < PI/3) {
+ mAngle = randFloat()*2*PI;
+ }
+ var mDist = 9;
+ var mX = round(ix + mDist * cos(mAngle));
+ var mY = round(iy + mDist * sin(mAngle));
+ group = new SimpleGroup(
+ [new SimpleObject(oStone, 2,2, 0,3),
+ new SimpleObject(oMetal, 2,2, 0,3)],
+ true, clBaseResource, mX, mY
+ );
+ createObjectGroup(group, 0);
+
+ // create starting straggler trees
+ group = new SimpleGroup(
+ [new SimpleObject(oPalm, 3,3, 7,10)],
+ true, clBaseResource, ix, iy
+ );
+ createObjectGroup(group, 0, avoidClasses(clBaseResource,2));
+}
+
+function distanceToPlayers(x, y)
+{
+ var r = 10000;
+ for (var i=1; i <= numPlayers; i++)
+ {
+ var dx = x - playerX[i];
+ var dy = y - playerY[i];
+ r = min(r, dx*dx + dy*dy);
+ }
+ return sqrt(r);
+}
+
+function playerNearness(x, y)
+{
+ var d = fractionToTiles(distanceToPlayers(x,y));
+
+ if (d < 13)
+ return 0;
+ else if (d < 19)
+ return (d-13)/(19-13);
+ else
+ return 1;
+}
+
+// Paint elevation
+
+log("Painting elevation...");
+
+var noise0 = new Noise2D(4 * mapSize/128);
+var noise1 = new Noise2D(8 * mapSize/128);
+var noise2 = new Noise2D(15 * mapSize/128);
+
+var noise2a = new Noise2D(20 * mapSize/128);
+var noise2b = new Noise2D(35 * mapSize/128);
+
+var noise3 = new Noise2D(4 * mapSize/128);
+var noise4 = new Noise2D(6 * mapSize/128);
+var noise5 = new Noise2D(11 * mapSize/128);
+
+for (var ix=0; ix<=mapSize; ix++)
+{
+ for (var iy=0; iy<=mapSize; iy++)
+ {
+ var x = ix / (mapSize + 1.0);
+ var y = iy / (mapSize + 1.0);
+ var pn = playerNearness(x, y);
+
+ var h = 0;
+ var distToWater = 0;
+
+ h = 32 * (x - 0.5);
+
+ // add the rough shape of the water
+ if (x < WATER_WIDTH)
+ {
+ h = max(-16.0, -28.0*(WATER_WIDTH-x)/WATER_WIDTH);
+ }
+ else if (x > 1.0-WATER_WIDTH)
+ {
+ h = max(-16.0, -28.0*(x-(1.0-WATER_WIDTH))/WATER_WIDTH);
+ }
+ else
+ {
+ distToWater = (0.5 - WATER_WIDTH - abs(x-0.5));
+ var u = 1 - abs(x-0.5) / (0.5-WATER_WIDTH);
+ h = 12*u;
+ }
+
+ // add some base noise
+ var baseNoise = 16*noise0.get(x,y) + 8*noise1.get(x,y) + 4*noise2.get(x,y) - (16+8+4)/2;
+ if ( baseNoise < 0 )
+ {
+ baseNoise *= pn;
+ baseNoise *= max(0.1, distToWater / (0.5-WATER_WIDTH));
+ }
+ var oldH = h;
+ h += baseNoise;
+
+ // add some higher-frequency noise on land
+ if ( oldH > 0 )
+ {
+ h += (0.4*noise2a.get(x,y) + 0.2*noise2b.get(x,y)) * min(oldH/10.0, 1.0);
+ }
+
+ // create cliff noise
+ if ( h > -10 )
+ {
+ var cliffNoise = (noise3.get(x,y) + 0.5*noise4.get(x,y)) / 1.5;
+ if (h < 1)
+ {
+ var u = 1 - 0.3*((h-1)/-10);
+ cliffNoise *= u;
+ }
+ cliffNoise += 0.05 * distToWater / (0.5 - WATER_WIDTH);
+ if (cliffNoise > 0.6)
+ {
+ var u = 0.8 * (cliffNoise - 0.6);
+ cliffNoise += u * noise5.get(x,y);
+ cliffNoise /= (1 + u);
+ }
+ cliffNoise -= 0.59;
+ cliffNoise *= pn;
+ if (cliffNoise > 0)
+ {
+ h += 19 * min(cliffNoise, 0.045) / 0.045;
+ }
+ }
+
+ // set the height
+ setHeight(ix, iy, h);
+ }
+}
+
+// Paint base terrain
+
+log("Painting terrain...");
+
+var noise6 = new Noise2D(10 * mapSize/128);
+var noise7 = new Noise2D(20 * mapSize/128);
+
+var noise8 = new Noise2D(13 * mapSize/128);
+var noise9 = new Noise2D(26 * mapSize/128);
+
+var noise10 = new Noise2D(50 * mapSize/128);
+
+for (var ix=0; ix 15)
+ {
+ var maxNx = min(ix+2, mapSize);
+ var maxNy = min(iy+2, mapSize);
+ for (var nx=max(ix-1, 0); nx <= maxNx; nx++)
+ {
+ for (var ny=max(iy-1, 0); ny <= maxNy; ny++)
+ {
+ minAdjHeight = min(minAdjHeight, getHeight(nx, ny));
+ }
+ }
+ }
+
+ // choose a terrain based on elevation
+ var t = tGrass;
+
+ // water
+ if (maxH < -12)
+ {
+ t = tOceanDepths;
+ }
+ else if (maxH < -8.8)
+ {
+ t = tOceanRockDeep;
+ }
+ else if (maxH < -4.7)
+ {
+ t = tOceanCoral;
+ }
+ else if (maxH < -2.8)
+ {
+ t = tOceanRockShallow;
+ }
+ else if (maxH < 0.9 && minH < 0.35)
+ {
+ t = tBeachWet;
+ }
+ else if (maxH < 1.5 && minH < 0.9)
+ {
+ t = tBeachDry;
+ }
+ else if (maxH < 2.3 && minH < 1.3)
+ {
+ t = tBeachGrass;
+ }
+
+ if (minH < 0)
+ {
+ addToClass(ix, iy, clWater);
+ }
+
+ // cliffs
+ if (maxH - minH > 2.9 && minH > -7)
+ {
+ t = tCliff;
+ addToClass(ix, iy, clCliff);
+ }
+ else if ((maxH - minH > 2.5 && minH > -5) || (maxH-minAdjHeight > 2.9 && minH > 0) )
+ {
+ if (minH < -1)
+ t = tCliff;
+ else if (minH < 0.5)
+ t = tBeachCliff;
+ else
+ t = [tDirtCliff, tGrassCliff, tGrassCliff, tGrassRock, tCliff];
+
+ addToClass(ix, iy, clCliff);
+ }
+
+ // forests
+ if (maxH - minH < 1 && minH > 1)
+ {
+ var forestNoise = (noise6.get(x,y) + 0.5*noise7.get(x,y)) / 1.5 * pn;
+ forestNoise -= 0.59;
+
+ if (forestNoise > 0)
+ {
+ if (minH > 5)
+ {
+ var typeNoise = noise10.get(x,y);
+
+ if (typeNoise < 0.43 && forestNoise < 0.05)
+ t = tPoplarForest;
+ else if (typeNoise < 0.63)
+ t = tMainForest;
+ else
+ t = tPineForest;
+
+ addToClass(ix, iy, clForest);
+ }
+ else if (minH < 3)
+ {
+ t = tPalmForest;
+ addToClass(ix, iy, clForest);
+ }
+ }
+ }
+
+ // grass variations
+ if (t == tGrass)
+ {
+ var grassNoise = (noise8.get(x,y) + 0.6*noise9.get(x,y)) / 1.6;
+ if (grassNoise < 0.3)
+ {
+ t = (maxH - minH > 1.2) ? tDirtCliff : tDirt;
+ }
+ else if (grassNoise < 0.34)
+ {
+ t = (maxH - minH > 1.2) ? tGrassCliff : tGrassDry;
+ if (maxH - minH < 0.5 && randFloat() < 0.03)
+ {
+ placeObject(aGrassDry, 0, ix+randFloat(), iy+randFloat(), randFloat()*2*PI);
+ }
+ }
+ else if (grassNoise > 0.61)
+ {
+ t = ((maxH - minH) > 1.2 ? tGrassRock : tGrassShrubs);
+ }
+ else
+ {
+ if ((maxH - minH) < 0.5 && randFloat() < 0.05)
+ {
+ placeObject(aGrass, 0, ix+randFloat(), iy+randFloat(), randFloat()*2*PI);
+ }
+ }
+ }
+
+ placeTerrain(ix, iy, t);
+ }
+}
+
+log("Placing straggler trees...");
+// create straggler trees
+var trees = [oCarob, oBeech, oLombardyPoplar, oLombardyPoplar, oPine];
+for (var t in trees)
+{
+ group = new SimpleGroup([new SimpleObject(trees[t], 1,1, 0,1)], true, clForest);
+ createObjectGroups(group, 0,
+ avoidClasses(clWater, 5, clCliff, 0, clForest, 1, clPlayer, 15),
+ mapSize*mapSize/7000, 50
+ );
+}
+
+log("Placing cypress trees...");
+// create cypresses
+group = new SimpleGroup(
+ [new SimpleObject(oCypress2, 1,3, 0,3),
+ new SimpleObject(oCypress1, 0,2, 0,2)]
+);
+createObjectGroups(group, 0,
+ avoidClasses(clWater, 4, clCliff, 2, clForest, 1, clPlayer, 15),
+ mapSize*mapSize/3500, 50
+);
+
+log("Placing bushes...");
+// create bushes
+group = new SimpleGroup(
+ [new SimpleObject(aBushSmall, 0,2, 0,2), new SimpleObject(aBushSmallDry, 0,2, 0,2),
+ new SimpleObject(aBushMed, 0,1, 0,2), new SimpleObject(aBushMedDry, 0,1, 0,2)]
+);
+createObjectGroups(group, 0,
+ avoidClasses(clWater, 4, clCliff, 2),
+ mapSize*mapSize/1800, 50
+);
+
+log("Placing rocks...");
+// create rocks
+group = new SimpleGroup(
+ [new SimpleObject(aRockSmall, 0,3, 0,2), new SimpleObject(aRockMed, 0,2, 0,2),
+ new SimpleObject(aRockLarge, 0,1, 0,2)]
+);
+createObjectGroups(group, 0,
+ avoidClasses(clWater, 0, clCliff, 0),
+ mapSize*mapSize/1800, 50
+);
+
+log("Placing stone mines...");
+// create stone
+group = new SimpleGroup([new SimpleObject(oStone, 2,3, 0,2)], true, clStone);
+createObjectGroups(group, 0,
+ [avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clStone, 15),
+ new BorderTileClassConstraint(clCliff, 0, 5)],
+ 3 * numPlayers, 100
+);
+
+log("Placing metal mines...");
+// create metal
+group = new SimpleGroup([new SimpleObject(oMetal, 2,3, 0,2)], true, clMetal);
+createObjectGroups(group, 0,
+ [avoidClasses(clWater, 0, clForest, 0, clPlayer, 20, clMetal, 15, clStone, 5),
+ new BorderTileClassConstraint(clCliff, 0, 5)],
+ 3 * numPlayers, 100
+);
+
+log("Placing sheep...");
+// create sheep
+group = new SimpleGroup([new SimpleObject(oSheep, 2,4, 0,2)], true, clFood);
+createObjectGroups(group, 0,
+ avoidClasses(clWater, 5, clForest, 1, clCliff, 1, clPlayer, 20, clMetal, 2, clStone, 2, clFood, 8),
+ 3 * numPlayers, 100
+);
+
+log("Placing berry bushes...");
+// create berry bushes
+group = new SimpleGroup([new SimpleObject(oBerryBush, 5,7, 0,3)], true, clFood);
+createObjectGroups(group, 0,
+ avoidClasses(clWater, 5, clForest, 1, clCliff, 1, clPlayer, 20, clMetal, 2, clStone, 2, clFood, 8),
+ 1.5 * numPlayers, 100
+);
+
+// Export map data
+ExportMap();
diff --git a/binaries/data/mods/public/maps/random/latium.json b/binaries/data/mods/public/maps/random/latium.json
new file mode 100644
index 0000000000..404cee1d37
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/latium.json
@@ -0,0 +1,10 @@
+{
+ "settings" : {
+ "Name" : "Latium",
+ "Script" : "latium.js",
+ "Description" : "The Italian peninsula",
+ "BaseTerrain" : ["medit_grass_field", "medit_grass_field_a", "medit_grass_flowers"],
+ "BaseHeight" : 0,
+ "XXXXXX" : "Optionally define other things here, like we would for a scenario"
+ }
+}
\ No newline at end of file
diff --git a/binaries/data/mods/public/maps/random/neareastern_badlands.js b/binaries/data/mods/public/maps/random/neareastern_badlands.js
index 87b9e46011..32ced1c06e 100644
--- a/binaries/data/mods/public/maps/random/neareastern_badlands.js
+++ b/binaries/data/mods/public/maps/random/neareastern_badlands.js
@@ -1,280 +1,277 @@
-// constants
-
-const SIZE = 208;
-const NUM_PLAYERS = 4;
-
-const tSand = "desert_rough";
-const tDunes = "desert_wave";
-const tFineSand = "desert_sahara";
-const tCliff = "cliff_desert";
-const tForest = "grass_sand_75|flora/trees/palm_b.xml";
-const tGrassSand75 = "grass_sand_75";
-const tGrassSand50 = "grass_sand_50";
-const tGrassSand25 = "grass_sand_25_2";
-const tDirt = "dirt_hard";
-const tDirtCracks = "dirt_cracks";
-const tShore = "sand";
-const tWater = "water_2";
-const tWaterDeep = "water_3";
-
-const oTree = "flora/trees/palm_b.xml";
-const oBerryBush = "flora_bush_berry";
-const oBush = "props/flora/bush_dry_a.xml";
-const oSheep = "fauna_sheep";
-const oDeer = "fauna_deer";
-const oMine = "geology_stone_light";
-const oDecorativeRock = "geology/gray1.xml";
-
-// some utility functions to save typing
-
-function paintClass(cl) {
- return new TileClassPainter(cl);
-}
-
-function avoidClasses(/*class1, dist1, class2, dist2, etc*/) {
- var ar = new Array(arguments.length/2);
- for(var i=0; i 0
+ && this.tileClass.countNonMembersInRadius(x, y, this.distanceInside) > 0);
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/entity.js b/binaries/data/mods/public/maps/random/rmgen/entity.js
new file mode 100644
index 0000000000..7728ecff7f
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/entity.js
@@ -0,0 +1,22 @@
+function Entity(name, player, x, y, angle)
+{
+ // Get unique ID
+ this.id = g_Map.getEntityID();
+ this.name = name;
+
+ // Convert from tile coords to map coords
+ this.x = x;
+ this.y = y;
+
+ if (player !== undefined)
+ {
+ this.player = player;
+ this.isActor = false;
+ }
+ else
+ { // Actors have no player ID
+ this.isActor = true;
+ }
+
+ this.orientation = (angle !== undefined ? angle : 0);
+}
diff --git a/binaries/data/mods/public/maps/random/rmgen/library.js b/binaries/data/mods/public/maps/random/rmgen/library.js
new file mode 100644
index 0000000000..34043f9ef0
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/library.js
@@ -0,0 +1,319 @@
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Constant definitions
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+const PI = Math.PI;
+
+const SEA_LEVEL = 20.0;
+
+const TERRAIN_SEPARATOR = "|";
+
+const TILES_PER_PATCH = 16;
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Utility functions
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+function fractionToTiles(f) {
+ return getMapSize() * f;
+}
+
+function tilesToFraction(t) {
+ return t / getMapSize();
+}
+
+function fractionToSize(f) {
+ return getMapSizeSqr() * f;
+}
+
+function sizeToFraction(s) {
+ return s / getMapSizeSqr();
+}
+
+function cos(x) {
+ return Math.cos(x);
+}
+
+function sin(x) {
+ return Math.sin(x);
+}
+
+function tan(x) {
+ return Math.tan(x);
+}
+
+function abs(x) {
+ return Math.abs(x);
+}
+
+function round(x) {
+ return Math.round(x);
+}
+
+function lerp(a, b, t) {
+ return a + (b-a) * t;
+}
+
+function sqrt(x) {
+ return Math.sqrt(x);
+}
+
+function ceil(x) {
+ return Math.ceil(x);
+}
+
+function floor(x) {
+ return Math.floor(x);
+}
+
+function max(x, y) {
+ return x > y ? x : y;
+}
+
+function min(x, y) {
+ return x < y ? x : y;
+}
+
+function println(x) {
+ print(x);
+ print("\n");
+}
+
+function argsToArray(x)
+{
+ var numArgs = x.length;
+ if (numArgs != 1)
+ {
+ var ret = new Array(numArgs);
+ for (var i=0; i < numArgs; i++)
+ {
+ ret[i] = x[i];
+ }
+ return ret;
+ }
+ else
+ {
+ return x[0];
+ }
+}
+
+function chooseRand()
+{
+ if (arguments.length==0)
+ {
+ error("chooseRand: requires at least 1 argument");
+ }
+ var ar = argsToArray(arguments);
+ return ar[randInt(ar.length)];
+}
+
+function createAreas(centeredPlacer, painter, constraint, num, retryFactor)
+{
+ if (retryFactor === undefined)
+ retryFactor = 10;
+
+ var maxFail = num * retryFactor;
+ var good = 0;
+ var bad = 0;
+ var ret = [];
+ while(good < num && bad <= maxFail)
+ {
+ centeredPlacer.x = randInt(getMapSize());
+ centeredPlacer.y = randInt(getMapSize());
+ var r = g_Map.createArea(centeredPlacer, painter, constraint);
+ if (r !== undefined)
+ {
+ good++;
+ ret.push(r);
+ }
+ else
+ {
+ bad++;
+ }
+ }
+
+ return ret;
+}
+
+function createObjectGroups(placer, player, constraint, num, retryFactor)
+{
+ if (retryFactor === undefined)
+ retryFactor = 10;
+
+ var maxFail = num * retryFactor;
+ var good = 0;
+ var bad = 0;
+ while(good < num && bad <= maxFail)
+ {
+ placer.x = randInt(getMapSize());
+ placer.y = randInt(getMapSize());
+ var r = createObjectGroup(placer, player, constraint);
+
+ if (r !== undefined)
+ {
+ good++;
+ }
+ else
+ {
+ bad++;
+ }
+ }
+ return good;
+}
+
+function createTerrain(terrain)
+{
+ if (terrain instanceof Array)
+ {
+ var terrainList = [];
+
+ for (var i = 0; i < terrain.length; ++i)
+ terrainList.push(createTerrain(terrain[i]));
+
+ return new RandomTerrain(terrainList);
+ }
+ else
+ {
+ return createSimpleTerrain(terrain);
+ }
+}
+
+function createSimpleTerrain(terrain)
+{
+ if (typeof(terrain) == "string")
+ { // Split string by pipe | character, this allows specifying terrain + tree type in single string
+ var params = terrain.split(TERRAIN_SEPARATOR, 2);
+
+ if (params.length != 2)
+ {
+ return new SimpleTerrain(terrain);
+ }
+ else
+ {
+ return new SimpleTerrain(params[0], params[1]);
+ }
+ }
+ else
+ {
+ error("createSimpleTerrain expects string as input, received "+terrain);
+ return undefined;
+ }
+}
+
+function placeObject(type, player, x, y, angle)
+{
+ g_Map.addObjects(new Entity(type, player, x, y, angle));
+}
+
+function placeTerrain(x, y, terrain)
+{
+ // convert terrain param into terrain object
+ g_Map.placeTerrain(x, y, createTerrain(terrain));
+
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Access global map variable
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+function createTileClass()
+{
+ return g_Map.createTileClass();
+}
+
+function getTileClass(id)
+{
+ // Check for valid class id
+ if (id < 1 || id > g_Map.tileClasses.length)
+ {
+ //error("Invalid tile class id: "+id);
+ return null;
+ }
+
+ return g_Map.tileClasses[id - 1];
+}
+
+function createArea(placer, painter, constraint)
+{
+ return g_Map.createArea(placer, painter, constraint);
+}
+
+function createObjectGroup(placer, player, constraint)
+{
+ return g_Map.createObjectGroup(placer, player, constraint);
+}
+
+function getMapSize()
+{
+ return g_Map.size;
+}
+
+function getMapSizeSqr()
+{
+ return g_Map.size*g_Map.size;
+}
+
+function getNumPlayers()
+{
+ return g_MapSettings.PlayerData.length;
+}
+
+function getCivCode(player)
+{
+ return g_MapSettings.PlayerData[player].Civ;
+}
+
+function getHeight(x, y)
+{
+ g_Map.getHeight(x, y);
+}
+
+function setHeight(x, y, height)
+{
+ g_Map.setHeight(x, y, height);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Utility functions for classes
+/////////////////////////////////////////////////////////////////////////////////////////////
+
+
+// Add point to given class by id
+function addToClass(x, y, id)
+{
+ var tileClass = getTileClass(id);
+
+ if (tileClass !== null)
+ tileClass.add(x, y);
+}
+
+// Create a painter for the given class
+function paintClass(id)
+{
+ return new TileClassPainter(getTileClass(id));
+}
+
+// Create an avoid constraint for the given classes by the given distances
+function avoidClasses(/*class1, dist1, class2, dist2, etc*/)
+{
+ var ar = new Array(arguments.length/2);
+ for (var i=0; i < arguments.length/2; i++)
+ {
+ ar[i] = new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]);
+ }
+ // Return single constraint
+ return new AndConstraint(ar);
+}
+
+// Create a stay constraint for the given classes by the given distances
+function stayClasses(/*class1, dist1, class2, dist2, etc*/)
+{
+ var ar = new Array(arguments.length/2);
+ for (var i=0; i < arguments.length/2; i++)
+ {
+ ar[i] = new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1]);
+ }
+ // Return single constraint
+ return new AndConstraint(ar);
+}
diff --git a/binaries/data/mods/public/maps/random/rmgen/map.js b/binaries/data/mods/public/maps/random/rmgen/map.js
new file mode 100644
index 0000000000..5b4ab757fd
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/map.js
@@ -0,0 +1,325 @@
+//////////////////////////////////////////////////////////////////////
+// Map
+//////////////////////////////////////////////////////////////////////
+
+function Map(size, baseHeight)
+{
+ // Size must be 0 to 1024, divisible by 16
+ this.size = size;
+
+ // Create 2D arrays for texture, object, and area maps
+ this.texture = new Array(size);
+ this.terrainObjects = new Array(size);
+ this.area = new Array(size);
+
+ for (var i = 0; i < size; i++)
+ {
+ this.texture[i] = new Uint16Array(size); // uint16
+ this.terrainObjects[i] = new Array(size); // entity
+ this.area[i] = new Array(size); // area
+
+ for (var j = 0; j < size; j++)
+ {
+ this.area[i][j] = {}; // undefined would cause a warning in strict mode
+ this.terrainObjects[i][j] = [];
+ }
+ }
+
+ var mapSize = size+1;
+
+ // Create 2D array for heightmap
+ this.height = new Array(mapSize);
+ for (var i=0; i < mapSize; i++)
+ {
+ this.height[i] = new Array(mapSize);
+ for (var j=0; j < mapSize; j++)
+ { // Initialize height map to baseHeight
+ this.height[i][j] = baseHeight;
+ }
+ }
+
+ // Create name <-> id maps for textures
+ this.nameToID = {};
+ this.IDToName = []; //string
+
+ // Other arrays
+ this.objects = []; //object
+ this.areas = []; //area
+ this.tileClasses = []; //int
+
+ // Starting entity ID
+ this.entityCount = 150;
+}
+
+Map.prototype.initTerrain = function(baseTerrain)
+{
+ // Initialize base terrain
+ var size = this.size;
+ for (var i=0; i < size; i++)
+ {
+ for (var j=0; j < size; j++)
+ {
+ baseTerrain.place(i, j);
+ }
+ }
+};
+
+// Return ID of texture (by name)
+Map.prototype.getID = function(texture)
+{
+ if (texture in (this.nameToID))
+ {
+ return this.nameToID[texture];
+ }
+
+ // Add new texture
+ var id = this.IDToName.length;
+ this.nameToID[texture] = id;
+ this.IDToName[id] = texture;
+
+ return id;
+};
+
+// Return next free entity ID
+Map.prototype.getEntityID = function()
+{
+ return this.entityCount++;
+}
+
+// Check bounds
+Map.prototype.validT = function(x, y)
+{
+ return x >= 0 && y >= 0 && x < this.size && y < this.size;
+};
+
+// Check bounds on height map (size + 1 by size + 1)
+Map.prototype.validH = function(x, y)
+{
+ return x >= 0 && y >= 0 && x <= this.size && y <= this.size;
+};
+
+// Check bounds on tile class
+Map.prototype.validClass = function(c)
+{
+ return c >= 0 && c < this.tileClasses.length;
+};
+
+Map.prototype.getTexture = function(x, y)
+{
+ if (!this.validT(x, y))
+ error("getTexture: invalid tile position ("+x+", "+y+")");
+
+ return this.IDToName[this.texture[x][y]];
+};
+
+Map.prototype.setTexture = function(x, y, texture)
+{
+ if (!this.validT(x, y))
+ error("setTexture: invalid tile position ("+x+", "+y+")");
+
+ this.texture[x][y] = this.getID(texture);
+};
+
+Map.prototype.getHeight = function(x, y)
+{
+ if (!this.validH(x, y))
+ error("getHeight: invalid vertex position ("+x+", "+y+")");
+
+ return this.height[x][y];
+};
+
+Map.prototype.setHeight = function(x, y, height)
+{
+ if (!this.validH(x, y))
+ error("setHeight: invalid vertex position ("+x+", "+y+")");
+
+ this.height[x][y] = height;
+};
+
+Map.prototype.getTerrainObjects = function(x, y)
+{
+ if (!this.validT(x, y))
+ error("getTerrainObjects: invalid tile position ("+x+", "+y+")");
+
+ return this.terrainObjects[x][y];
+};
+
+Map.prototype.setTerrainObjects = function(x, y, objects)
+{
+ if (!this.validT(x, y))
+ error("setTerrainObjects: invalid tile position ("+x+", "+y+")");
+
+ this.terrainObjects[x][y] = objects;
+};
+
+Map.prototype.placeTerrain = function(x, y, terrain)
+{
+ terrain.place(x, y);
+};
+
+Map.prototype.addObjects = function(obj)
+{
+ this.objects = this.objects.concat(obj);
+};
+
+Map.prototype.createArea = function(placer, painter, constraint)
+{
+ // Check for multiple painters
+ if (painter instanceof Array)
+ {
+ var painterArray = painter;
+ painter = new MultiPainter(painterArray);
+ }
+
+ // Check for null constraint
+ if (constraint === undefined || constraint === null)
+ {
+ constraint = new NullConstraint();
+ }
+ else if (constraint instanceof Array)
+ { // Check for multiple constraints
+ var constraintArray = constraint;
+ constraint = new AndConstraint(constraintArray);
+ }
+
+ var points = placer.place(constraint);
+ if (!points)
+ return undefined;
+
+ var a = new Area(points);
+ for (var i=0; i < points.length; i++)
+ {
+ this.area[points[i].x][points[i].y] = a;
+ }
+
+ painter.paint(a);
+ this.areas.push(a);
+
+ return a;
+};
+
+Map.prototype.createObjectGroup = function(placer, player, constraint)
+{
+ // Check for null constraint
+ if (constraint === undefined || constraint === null)
+ {
+ constraint = new NullConstraint();
+ }
+ else if (constraint instanceof Array)
+ { // Check for multiple constraints
+ var constraintArray = constraint;
+ constraint = new AndConstraint(constraintArray);
+ }
+
+ return placer.place(player, constraint);
+};
+
+Map.prototype.createTileClass = function()
+{
+ this.tileClasses.push(new TileClass(this.size));
+
+ return this.tileClasses.length;
+};
+
+// Get height taking into account terrain curvature
+Map.prototype.getExactHeight = function(x, y)
+{
+ var xi = min(floor(x), this.size);
+ var yi = min(floor(y), this.size);
+ var xf = x - xi;
+ var yf = y - yi;
+
+ var h00 = this.height[xi][yi];
+ var h01 = this.height[xi][yi+1];
+ var h10 = this.height[xi+1][yi];
+ var h11 = this.height[xi+1][yi+1];
+
+ return ( 1 - yf ) * ( ( 1 - xf ) * h00 + xf * h10 ) + yf * ( ( 1 - xf ) * h01 + xf * h11 ) ;
+};
+
+Map.prototype.getMapData = function()
+{
+ var data = {};
+
+ // Build entity array
+ var entities = [];
+
+ // Terrain objects first (trees)
+ var size = this.size;
+ for (var x=0; x < size; ++x)
+ {
+ for (var y=0; y < size; ++y)
+ {
+ if (this.terrainObjects[x][y].length)
+ entities = entities.concat(this.terrainObjects[x][y]);
+ }
+ }
+
+ // Now other entities
+ entities = entities.concat(this.objects);
+
+ // Convert from tiles to map coordinates
+ for (var n in entities)
+ {
+ var e = entities[n];
+ e.x *= 4;
+ e.y *= 4;
+
+ entities[n] = e;
+ }
+ data["entities"] = entities;
+
+ // Terrain
+ data["size"] = this.size;
+
+ // Convert 2D heightmap array to flat array
+ // Flat because it's easier to handle by the engine
+ var mapSize = size+1;
+ var height16 = new Array(mapSize*mapSize); // uint16
+ for (var x=0; x < mapSize; x++)
+ {
+ for (var y=0; y < mapSize; y++)
+ {
+ var intHeight = ((this.height[x][y] + SEA_LEVEL) * 256.0 / 0.35)|0; // floor
+
+ if (intHeight > 65000)
+ intHeight = 65000;
+ else if (intHeight < 0)
+ intHeight = 0;
+
+ height16[y*mapSize + x] = intHeight;
+ }
+ }
+ data["height"] = height16;
+ data["seaLevel"] = SEA_LEVEL;
+
+ // Get array of textures used in this map
+ var textureNames = [];
+ for (var name in this.nameToID)
+ textureNames.push(name);
+
+ data["textureNames"] = textureNames;
+ data["numTextures"] = textureNames.length;
+
+ // Convert 2D tile data to flat array, reodering into patches as expected by MapReader
+ var tiles = new Array(size*size);
+ var patches = size/16;
+ for (var x=0; x < size; x++)
+ {
+ var patchX = floor(x/16);
+ var offX = x%16;
+ for (var y=0; y < size; y++)
+ {
+ var patchY = floor(y/16);
+ var offY = y%16;
+ tiles[(patchY*patches + patchX)*256 + (offY*16 + offX)] =
+ { "texIdx1" : this.texture[x][y],
+ "texIdx2" : 0xFFFF,
+ "priority" : 0
+ };
+ }
+ }
+ data["tileData"] = tiles;
+
+ return data;
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/mapgen.js b/binaries/data/mods/public/maps/random/rmgen/mapgen.js
new file mode 100644
index 0000000000..39b76524e4
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/mapgen.js
@@ -0,0 +1,67 @@
+var g_Map;
+
+var g_Environment = {
+ SkySet: "cirrus",
+ SunColour: {r: 1.47461, g: 1.47461, b: 1.47461},
+ SunElevation: 0.951868,
+ SunRotation: -0.532844,
+ TerrainAmbientColour: {r: 0.337255, g: 0.403922, b: 0.466667},
+ UnitsAmbientColour: {r: 0.501961, g: 0.501961, b: 0.501961},
+ Water: {
+ WaterBody: {
+ Type: "default",
+ Colour: {r: 0.294118, g: 0.34902, b: 0.694118},
+ Height: 17.6262,
+ Shininess: 150,
+ Waviness: 8,
+ Murkiness: 0.458008,
+ Tint: {r: 0.447059, g: 0.411765, b: 0.321569},
+ ReflectionTint: {r: 0.619608, g: 0.584314, b: 0.47451},
+ ReflectionTintStrength: 0.298828
+ }
+ }
+};
+
+var g_Camera = {
+ Position: {x: 100, y: 150, z: -100},
+ Rotation: 0,
+ Declination: 0.523599
+};
+
+/////////////////////////////////////////////////////////////////////////////////////
+
+function InitMap()
+{
+ if (g_MapSettings === undefined || g_MapSettings == {})
+ { // If settings missing, warn and use some defaults
+ warn("InitMapGen: settings missing");
+ g_MapSettings = {
+ Size : 13,
+ BaseTerrain: "grass1_spring",
+ BaseHeight: 0,
+ PlayerData : [ {}, {} ]
+ };
+ }
+
+ // Create new map
+ log("Creating new map...");
+ var terrain = createTerrain(g_MapSettings.BaseTerrain);
+
+ g_Map = new Map(g_MapSettings.Size * TILES_PER_PATCH, g_MapSettings.BaseHeight);
+ g_Map.initTerrain(terrain);
+}
+
+function ExportMap()
+{ // Wrapper for engine function
+ log("Saving map...");
+
+ // Get necessary data from map
+ var data = g_Map.getMapData();
+
+ // Add environment and camera settings
+ g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1;
+ data.Environment = g_Environment;
+ data.Camera = g_Camera;
+
+ RMS.ExportMap(data);
+}
diff --git a/binaries/data/mods/public/maps/random/rmgen/noise.js b/binaries/data/mods/public/maps/random/rmgen/noise.js
new file mode 100644
index 0000000000..b27bc3a3be
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/noise.js
@@ -0,0 +1,141 @@
+// Utility function used in both noises as an ease curve
+function easeCurve(t)
+{
+ return t*t*t*(t*(t*6-15)+10);
+}
+
+// Find mod of number but only positive values
+function modPos(num, m)
+{
+ var p = num % m;
+ if (p < 0)
+ p += m;
+
+ return p;
+}
+
+/////////////////////////////////////////////////////////////////////
+// Noise2D
+/////////////////////////////////////////////////////////////////////
+
+function Noise2D(freq)
+{
+ freq = floor(freq);
+ this.freq = freq;
+ this.grads = new Array(freq);
+
+ for (var i=0; i < freq; ++i)
+ {
+ this.grads[i] = new Array(freq);
+ for (var j=0; j < freq; ++j)
+ {
+ var a = randFloat() * 2 * PI;
+
+ this.grads[i][j] = new Vector2D(cos(a), sin(a));
+ }
+ }
+}
+
+Noise2D.prototype.get = function(x, y)
+{
+ x *= this.freq;
+ y *= this.freq;
+
+ var ix = modPos(floor(x), this.freq);
+ var iy = modPos(floor(y), this.freq);
+
+ var fx = x - ix;
+ var fy = y - iy;
+
+ var ix1 = (ix+1) % this.freq;
+ var iy1 = (iy+1) % this.freq;
+
+ var s = this.grads[ix][iy].dot(new Vector2D(fx, fy));
+ var t = this.grads[ix1][iy].dot(new Vector2D(fx-1, fy));
+ var u = this.grads[ix][iy1].dot(new Vector2D(fx, fy-1));
+ var v = this.grads[ix1][iy1].dot(new Vector2D(fx-1, fy-1));
+
+ var ex = easeCurve(fx);
+ var ey = easeCurve(fy);
+ var a = s + ex*(t-s);
+ var b = u + ex*(v-u);
+ return (a + ey*(b-a)) * 0.5 + 0.5;
+};
+
+/////////////////////////////////////////////////////////////////////
+// Noise3D
+/////////////////////////////////////////////////////////////////////
+
+function Noise3D(freq, vfreq)
+{
+ freq = floor(freq);
+ vfreq = floor(vfreq);
+ this.freq = freq;
+ this.vfreq = vfreq;
+ this.grads = new Array(freq);
+
+ for (var i=0; i < freq; ++i)
+ {
+ this.grads[i] = new Array(freq);
+ for (var j=0; j < freq; ++j)
+ {
+ this.grads[i][j] = new Array(vfreq);
+ for(var k=0; k < vfreq; ++k)
+ {
+ var v = new Vector3D();
+ do
+ {
+ v.set(2*randFloat()-1, 2*randFloat()-1, 2*randFloat()-1);
+ }
+ while(v.lengthSquared() > 1 || v.lengthSquared() < 0.1);
+
+ v.normalize();
+
+ this.grads[i][j][k] = v;
+ }
+ }
+ }
+}
+
+Noise3D.prototype.get = function(x, y, z)
+{
+ x *= this.freq;
+ y *= this.freq;
+ z *= this.vfreq;
+
+ var ix =modPos(floor(x), this.freq);
+ var iy = modPos(floor(y), this.freq);
+ var iz = modPos(floor(z), this.vfreq);
+
+ var fx = x - ix;
+ var fy = y - iy;
+ var fz = z - iz;
+
+ var ix1 = (ix+1) % this.freq;
+ var iy1 = (iy+1) % this.freq;
+ var iz1 = (iz+1) % this.vfreq;
+
+ var s0 = this.grads[ix][iy][iz].dot(new Vector3D(fx, fy, fz));
+ var t0 = this.grads[ix1][iy][iz].dot(new Vector3D(fx-1, fy, fz));
+ var u0 = this.grads[ix][iy1][iz].dot(new Vector3D(fx, fy-1, fz));
+ var v0 = this.grads[ix1][iy1][iz].dot(new Vector3D(fx-1, fy-1, fz));
+
+ var s1 = this.grads[ix][iy][iz1].dot(new Vector3D(fx, fy, fz-1));
+ var t1 = this.grads[ix1][iy][iz1].dot(new Vector3D(fx-1, fy, fz-1));
+ var u1 = this.grads[ix][iy1][iz1].dot(new Vector3D(fx, fy-1, fz-1));
+ var v1 = this.grads[ix1][iy1][iz1].dot(new Vector3D(fx-1, fy-1, fz-1));
+
+ var ex = easeCurve(fx);
+ var ey = easeCurve(fy);
+ var ez = easeCurve(fz);
+
+ var a0 = s0 + ex*(t0-s0);
+ var b0 = u0 + ex*(v0-u0);
+ var c0 = a0 + ey*(b0-a0);
+
+ var a1 = s1 + ex*(t1-s1);
+ var b1 = u1 + ex*(v1-u1);
+ var c1 = a1 + ey*(b1-a1);
+
+ return (c0 + ez*(c1-c0)) * 0.5 + 0.5;
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/painter.js b/binaries/data/mods/public/maps/random/rmgen/painter.js
new file mode 100644
index 0000000000..f63f99877c
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/painter.js
@@ -0,0 +1,379 @@
+const ELEVATION_SET = 0;
+const ELEVATION_MODIFY = 1;
+
+/////////////////////////////////////////////////////////////////////////////
+// ElevationPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function ElevationPainter(elevation)
+{
+ this.elevation = elevation;
+ this.DX = [0, 1, 1, 0];
+ this.DY = [0, 0, 1, 1];
+}
+
+ElevationPainter.prototype.paint = function(area)
+{
+ var length = area.points.length;
+ var elevation = this.elevation;
+
+ for (var i=0; i < length; i++)
+ {
+ var pt = area.points[i];
+
+ for (var j=0; j < 4; j++)
+ {
+ g_Map.height[pt.x+this.DX[j]][pt.y+this.DY[j]] = elevation;
+ }
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// LayeredPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function LayeredPainter(terrainArray, widths)
+{
+ if (!(terrainArray instanceof Array))
+ error("terrains must be an array!");
+
+ this.terrains = [];
+ for (var i = 0; i < terrainArray.length; ++i)
+ this.terrains.push(createTerrain(terrainArray[i]));
+
+ this.widths = widths;
+}
+
+LayeredPainter.prototype.paint = function(area)
+{
+ var size = getMapSize();
+ var saw = new Array(size);
+ var dist = new Array(size);
+
+ // init typed arrays
+ for (var i = 0; i < size; ++i)
+ {
+ saw[i] = new Uint8Array(size); // bool / uint8
+ dist[i] = new Uint16Array(size); // uint16
+ }
+
+ // Point queue (implemented with array)
+ var pointQ = [];
+
+ // push edge points
+ var pts = area.points;
+ var length = pts.length;
+
+ for (var i=0; i < length; i++)
+ {
+ var x = pts[i].x;
+ var y = pts[i].y;
+
+ for (var dx=-1; dx <= 1; dx++)
+ {
+ var nx = x+dx;
+ for (var dy=-1; dy <= 1; dy++)
+ {
+ var ny = y+dy;
+
+ if (g_Map.validT(nx, ny) && g_Map.area[nx][ny] != area && !saw[nx][ny])
+ {
+ saw[nx][ny] = 1;
+ dist[nx][ny] = 0;
+ pointQ.push(new Point(nx, ny));
+ }
+ }
+ }
+ }
+
+ // do BFS inwards to find distances to edge
+ while (pointQ.length)
+ {
+ var pt = pointQ.shift(); // Pop queue
+ var px = pt.x;
+ var py = pt.y;
+ var d = dist[px][py];
+
+ // paint if in area
+ if (g_Map.area[px][py] == area)
+ {
+ var w=0;
+ var i=0;
+
+ for (; i < this.widths.length; i++)
+ {
+ w += this.widths[i];
+ if (w >= d)
+ {
+ break;
+ }
+ }
+ this.terrains[i].place(px, py);
+ }
+
+ // enqueue neighbours
+ for (var dx=-1; dx<=1; dx++)
+ {
+ var nx = px+dx;
+ for (var dy=-1; dy<=1; dy++)
+ {
+ var ny = py+dy;
+
+ if (g_Map.validT(nx, ny) && g_Map.area[nx][ny] == area && !saw[nx][ny])
+ {
+ saw[nx][ny] = 1;
+ dist[nx][ny] = d+1;
+ pointQ.push(new Point(nx, ny));
+ }
+ }
+ }
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// MultiPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function MultiPainter(painters)
+{
+ this.painters = painters;
+}
+
+MultiPainter.prototype.paint = function(area)
+{
+ for (var i=0; i < this.painters.length; i++)
+ {
+ this.painters[i].paint(area);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// SmoothElevationPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function SmoothElevationPainter(type, elevation, blendRadius)
+{
+ this.type = type;
+ this.elevation = elevation;
+ this.blendRadius = blendRadius;
+
+ if (type != ELEVATION_SET && type != ELEVATION_MODIFY)
+ error("SmoothElevationPainter: invalid type '"+type+"'");
+}
+
+SmoothElevationPainter.prototype.checkInArea = function(area, x, y)
+{
+ if (g_Map.validT(x, y))
+ {
+ return (g_Map.area[x][y] == area);
+ }
+ else
+ {
+ return false;
+ }
+};
+
+SmoothElevationPainter.prototype.paint = function(area)
+{
+ var pointQ = [];
+ var pts = area.points;
+ var heightPts = [];
+
+ var mapSize = getMapSize()+1;
+
+ var saw = new Array(mapSize);
+ var dist = new Array(mapSize);
+ var gotHeightPt = new Array(mapSize);
+ var newHeight = new Array(mapSize);
+
+ // init typed arrays
+ for (var i = 0; i < mapSize; ++i)
+ {
+ saw[i] = new Uint8Array(mapSize); // bool / uint8
+ dist[i] = new Uint16Array(mapSize); // uint16
+ gotHeightPt[i] = new Uint8Array(mapSize); // bool / uint8
+ newHeight[i] = new Float32Array(mapSize); // float32
+ }
+
+ var length = pts.length;
+
+ // get a list of all points
+ for (var i=0; i < length; i++)
+ {
+ var x = pts[i].x;
+ var y = pts[i].y;
+
+ for (var dx=-1; dx <= 2; dx++)
+ {
+ var nx = x+dx;
+ for (var dy=-1; dy <= 2; dy++)
+ {
+ var ny = y+dy;
+
+ if (g_Map.validH(nx, ny) && !gotHeightPt[nx][ny])
+ {
+ gotHeightPt[nx][ny] = 1;
+ heightPts.push(new Point(nx, ny));
+ newHeight[nx][ny] = g_Map.height[nx][ny];
+ }
+ }
+ }
+ }
+
+ // push edge points
+ for (var i=0; i < length; i++)
+ {
+ var x = pts[i].x, y = pts[i].y;
+ for (var dx=-1; dx <= 2; dx++)
+ {
+ var nx = x+dx;
+ for (var dy=-1; dy <= 2; dy++)
+ {
+ var ny = y+dy;
+
+ if (g_Map.validH(nx, ny)
+ && !this.checkInArea(area, nx, ny)
+ && !this.checkInArea(area, nx-1, ny)
+ && !this.checkInArea(area, nx, ny-1)
+ && !this.checkInArea(area, nx-1, ny-1)
+ && !saw[nx][ny])
+ {
+ saw[nx][ny]= 1;
+ dist[nx][ny] = 0;
+ pointQ.push(new Point(nx, ny));
+ }
+ }
+ }
+ }
+
+ // do BFS inwards to find distances to edge
+ while(pointQ.length)
+ {
+ var pt = pointQ.shift();
+ var px = pt.x;
+ var py = pt.y;
+ var d = dist[px][py];
+
+ // paint if in area
+ if (g_Map.validH(px, py)
+ && (this.checkInArea(area, px, py) || this.checkInArea(area, px-1, py)
+ || this.checkInArea(area, px, py-1) || this.checkInArea(area, px-1, py-1)))
+ {
+ if (d <= this.blendRadius)
+ {
+ var a = (d-1) / this.blendRadius;
+ if (this.type == ELEVATION_SET)
+ {
+ newHeight[px][py] = a*this.elevation + (1-a)*g_Map.height[px][py];
+ }
+ else
+ { // type == MODIFY
+ newHeight[px][py] += a*this.elevation;
+ }
+ }
+ else
+ { // also happens when blendRadius == 0
+ if (this.type == ELEVATION_SET)
+ {
+ newHeight[px][py] = this.elevation;
+ }
+ else
+ { // type == MODIFY
+ newHeight[px][py] += this.elevation;
+ }
+ }
+ }
+
+ // enqueue neighbours
+ for (var dx=-1; dx <= 1; dx++)
+ {
+ var nx = px+dx;
+ for (var dy=-1; dy <= 1; dy++)
+ {
+ var ny = py+dy;
+
+ if (g_Map.validH(nx, ny)
+ && (this.checkInArea(area, nx, ny) || this.checkInArea(area, nx-1, ny)
+ || this.checkInArea(area, nx, ny-1) || this.checkInArea(area, nx-1, ny-1))
+ && !saw[nx][ny])
+ {
+ saw[nx][ny] = 1;
+ dist[nx][ny] = d+1;
+ pointQ.push(new Point(nx, ny));
+ }
+ }
+ }
+ }
+
+ length = heightPts.length;
+
+ // smooth everything out
+ for (var i = 0; i < length; ++i)
+ {
+ var pt = heightPts[i];
+ var px = pt.x;
+ var py = pt.y;
+
+ if ((this.checkInArea(area, px, py) || this.checkInArea(area, px-1, py)
+ || this.checkInArea(area, px, py-1) || this.checkInArea(area, px-1, py-1)))
+ {
+ var sum = 8 * newHeight[px][py];
+ var count = 8;
+
+ for (var dx=-1; dx <= 1; dx++)
+ {
+ var nx = px+dx;
+ for (var dy=-1; dy <= 1; dy++)
+ {
+ var ny = py+dy;
+
+ if (g_Map.validH(nx, ny))
+ {
+ sum += newHeight[nx][ny];
+ count++;
+ }
+ }
+ }
+
+ g_Map.height[px][py] = sum/count;
+ }
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// TerrainPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function TerrainPainter(terrain)
+{
+ this.terrain = createTerrain(terrain);
+}
+
+TerrainPainter.prototype.paint = function(area)
+{
+ var length = area.points.length;
+ for (var i=0; i < length; i++)
+ {
+ var pt = area.points[i];
+ this.terrain.place(pt.x, pt.y);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// TileClassPainter
+/////////////////////////////////////////////////////////////////////////////
+
+function TileClassPainter(tileClass)
+{
+ this.tileClass = tileClass;
+}
+
+TileClassPainter.prototype.paint = function(area)
+{
+ var length = area.points.length;
+ for (var i=0; i < length; i++)
+ {
+ var pt = area.points[i];
+ this.tileClass.add(pt.x, pt.y);
+ }
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/placer.js b/binaries/data/mods/public/maps/random/rmgen/placer.js
new file mode 100644
index 0000000000..dd13ce71c6
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/placer.js
@@ -0,0 +1,291 @@
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ClumpPlacer
+/////////////////////////////////////////////////////////////////////////////////////////
+
+function ClumpPlacer(size, coherence, smoothness, failFraction, x, y)
+{
+ this.size = size;
+ this.coherence = coherence;
+ this.smoothness = smoothness;
+ this.failFraction = (failFraction !== undefined ? failFraction : 0);
+ this.x = (x !== undefined ? x : -1);
+ this.y = (y !== undefined ? y : -1);
+}
+
+ClumpPlacer.prototype.place = function(constraint)
+{
+ if (!g_Map.validT(this.x, this.y) || !constraint.allows(this.x, this.y))
+ {
+ return false;
+ }
+
+ var retVec = [];
+
+ var size = getMapSize();
+ var gotRet = new Array(size);
+ for (var i = 0; i < size; ++i)
+ {
+ gotRet[i] = new Uint8Array(size); // bool / uint8
+ }
+
+ var radius = sqrt(this.size / PI);
+ var perim = 4 * radius * 2 * PI;
+ var intPerim = ceil(perim);
+
+ var ctrlPts = 1 + Math.floor(1.0/Math.max(this.smoothness,1.0/intPerim));
+
+ if (ctrlPts > radius * 2 * PI)
+ ctrlPts = Math.floor(radius * 2 * PI) + 1;
+
+ var noise = new Float32Array(intPerim); //float32
+ var ctrlCoords = new Float32Array(ctrlPts+1); //float32
+ var ctrlVals = new Float32Array(ctrlPts+1); //float32
+
+ for (var i=0; i < ctrlPts; i++)
+ {
+ ctrlCoords[i] = i * perim / ctrlPts;
+ ctrlVals[i] = 2.0*randFloat();
+ }
+
+ var c = 0;
+ var looped = 0;
+ for (var i=0; i < intPerim; i++)
+ {
+ if (ctrlCoords[(c+1) % ctrlPts] < i && !looped)
+ {
+ c = (c+1) % ctrlPts;
+ if (c == ctrlPts-1)
+ looped = 1;
+ }
+
+ var t = (i - ctrlCoords[c]) / ((looped ? perim : ctrlCoords[(c+1)%ctrlPts]) - ctrlCoords[c]);
+ var v0 = ctrlVals[(c+ctrlPts-1)%ctrlPts];
+ var v1 = ctrlVals[c];
+ var v2 = ctrlVals[(c+1)%ctrlPts];
+ var v3 = ctrlVals[(c+2)%ctrlPts];
+ var P = (v3 - v2) - (v0 - v1);
+ var Q = (v0 - v1) - P;
+ var R = v2 - v0;
+ var S = v1;
+
+ noise[i] = P*t*t*t + Q*t*t + R*t + S;
+ if (noise[i] > 4294967296)
+ warn("noise["+i+"] is beyond max_uint32 bounds");
+ }
+
+ var failed = 0;
+ for (var p=0; p < intPerim; p++)
+ {
+ var th = 2 * PI * p / perim;
+ var r = radius * (1 + (1-this.coherence)*noise[p]);
+ var s = sin(th);
+ var c = cos(th);
+ var xx=this.x;
+ var yy=this.y;
+
+ for (var k=0; k < ceil(r); k++)
+ {
+ var i = Math.floor(xx);
+ var j = Math.floor(yy);
+ if (g_Map.validT(i, j) && constraint.allows(i, j))
+ {
+ if (!gotRet[i][j])
+ { // Only include each point once
+ gotRet[i][j] = 1;
+ retVec.push(new Point(i, j));
+ }
+ }
+ else
+ {
+ failed++;
+ }
+ xx += s;
+ yy += c;
+ }
+ }
+
+ return ((failed > this.size*this.failFraction) ? undefined : retVec);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// RectPlacer
+/////////////////////////////////////////////////////////////////////////////////////////
+
+function RectPlacer(x1, y1, x2, y2)
+{
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+
+ if (x1 > x2 || y1 > y2)
+ error("RectPlacer: incorrect bounds on rect");
+}
+
+RectPlacer.prototype.place = function(constraint)
+{
+ var ret = [];
+
+ var x2 = this.x2;
+ var y2 = this.y2;
+
+ for (var x=this.x1; x < x2; x++)
+ {
+ for (var y=this.y1; y < y2; y++)
+ {
+ if (g_Map.validT(x, y) && constraint.allows(x, y))
+ {
+ ret.push(new Point(x, y));
+ }
+ else
+ {
+ return undefined;
+ }
+ }
+ }
+
+ return ret;
+
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ObjectGroupPlacer
+/////////////////////////////////////////////////////////////////////////////////////////
+
+function ObjectGroupPlacer() {}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SimpleGroup
+/////////////////////////////////////////////////////////////////////////////////////////
+
+function SimpleObject(type, minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle)
+{
+ this.type = type;
+ this.minCount = minCount;
+ this.maxCount = maxCount;
+ this.minDistance = minDistance;
+ this.maxDistance = maxDistance;
+ this.minAngle = (minAngle !== undefined ? minAngle : 0);
+ this.maxAngle = (maxAngle !== undefined ? maxAngle : 2*PI);
+
+ if (minCount > maxCount)
+ error("SimpleObject: minCount must be less than or equal to maxCount");
+
+ if (minDistance > maxDistance)
+ error("SimpleObject: minDistance must be less than or equal to maxDistance");
+
+ if (minAngle > maxAngle)
+ error("SimpleObject: minAngle must be less than or equal to maxAngle");
+}
+
+SimpleObject.prototype.place = function(cx, cy, player, avoidSelf, constraint)
+{
+ var failCount = 0;
+ var count = randInt(this.minCount, this.maxCount);
+ var resultObjs = [];
+
+ for (var i=0; i < count; i++)
+ {
+ while(true)
+ {
+ var distance = randFloat(this.minDistance, this.maxDistance);
+ var direction = randFloat(0, 2*PI);
+
+ var x = cx + 0.5 + distance*cos(direction);
+ var y = cy + 0.5 + distance*sin(direction);
+ var fail = false; // reset place failure flag
+
+ if (x < 0 || y < 0 || x > g_Map.size || y > g_Map.size)
+ {
+ fail = true;
+ }
+ else
+ {
+ if (avoidSelf)
+ {
+ var length = resultObjs.length;
+ for (var i = 0; (i < length) && !fail; i++)
+ {
+ var dx = x - resultObjs[i].x;
+ var dy = y - resultObjs[i].y;
+
+ if ((dx*dx + dy*dy) < 1)
+ {
+ fail = true;
+ }
+ }
+ }
+
+ if (!fail)
+ {
+ if (!constraint.allows(Math.floor(x), Math.floor(y)))
+ {
+ fail = true;
+ }
+ else
+ { // if we got here, we're good
+ var angle = randFloat(this.minAngle, this.maxAngle);
+ resultObjs.push(new Entity(this.type, player, x, y, angle));
+ break;
+ }
+ }
+ }
+
+ if (fail)
+ {
+ failCount++;
+ if (failCount > 20) // TODO: Make this adjustable
+ {
+ return undefined;
+ }
+ }
+ }
+ }
+
+ return resultObjs;
+};
+
+function SimpleGroup(elements, avoidSelf, tileClass, x, y)
+{
+ this.elements = elements;
+ this.tileClass = (tileClass !== undefined ? getTileClass(tileClass) : null);
+ this.avoidSelf = (avoidSelf !== undefined ? avoidSelf : false);
+ this.x = (x !== undefined ? x : -1);
+ this.y = (y !== undefined ? y : -1);
+}
+
+SimpleGroup.prototype.place = function(player, constraint)
+{
+ var resultObjs = [];
+
+ // Try placement of objects
+ var length = this.elements.length;
+ for (var i=0; i < length; i++)
+ {
+ var objs = this.elements[i].place(this.x, this.y, player, this.avoidSelf, constraint);
+ if (objs === undefined)
+ { // Failure
+ return false;
+ }
+ else
+ {
+ resultObjs = resultObjs.concat(objs);
+ }
+ }
+
+ // Add placed objects to map
+ length = resultObjs.length;
+ for (var i=0; i < length; i++)
+ {
+ g_Map.addObjects(resultObjs[i]);
+
+ if (this.tileClass !== null)
+ { // Round object position to integer
+ this.tileClass.add(Math.floor(resultObjs[i].x), Math.floor(resultObjs[i].y));
+ }
+ }
+
+ return true;
+};
+
diff --git a/binaries/data/mods/public/maps/random/rmgen/point.js b/binaries/data/mods/public/maps/random/rmgen/point.js
new file mode 100644
index 0000000000..4d150f069a
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/point.js
@@ -0,0 +1,5 @@
+function Point(x, y)
+{
+ this.x = (x !== undefined ? x : 0);
+ this.y = (y !== undefined ? y : 0);
+}
diff --git a/binaries/data/mods/public/maps/random/rmgen/random.js b/binaries/data/mods/public/maps/random/rmgen/random.js
new file mode 100644
index 0000000000..e0a0907927
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/random.js
@@ -0,0 +1,53 @@
+
+/*
+ * Return a random floating point number using Math.random library
+ *
+ * If no parameter given, the returned float is in the interval [0, 1)
+ * If two parameters are given, they are minval and maxval, and the returned float is in the interval [minval, maxval)
+ */
+function randFloat()
+{
+ if (arguments.length == 0)
+ {
+ return Math.random();
+ }
+ else if (arguments.length == 2)
+ {
+ var minVal = arguments[0];
+ var maxVal = arguments[1];
+
+ return minVal + randFloat() * (maxVal - minVal);
+ }
+ else
+ {
+ error("randFloat() received invalid number of arguments: "+arguments.length);
+ return undefined;
+ }
+}
+
+/*
+ * Return a random integer using Math.random library
+ *
+ * If one parameter given, it's maxval, and the returned integer is in the interval [0, maxval)
+ * If two parameters are given, they are minval and maxval, and the returned integer is in the interval [minval, maxval]
+ */
+function randInt()
+{
+ if (arguments.length == 1)
+ {
+ var maxVal = arguments[0];
+ return Math.floor(Math.random() * maxVal);
+ }
+ else if (arguments.length == 2)
+ {
+ var minVal = arguments[0];
+ var maxVal = arguments[1];
+
+ return minVal + randInt(maxVal - minVal + 1);
+ }
+ else
+ {
+ error("randInt() received invalid number of arguments: "+arguments.length);
+ return undefined;
+ }
+}
diff --git a/binaries/data/mods/public/maps/random/rmgen/terrain.js b/binaries/data/mods/public/maps/random/rmgen/terrain.js
new file mode 100644
index 0000000000..1747e4ca28
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/terrain.js
@@ -0,0 +1,57 @@
+//////////////////////////////////////////////////////////////////////
+// Terrain
+//////////////////////////////////////////////////////////////////////
+
+function Terrain() {}
+
+Terrain.prototype.place = function(x, y)
+{
+ // Clear old array
+ g_Map.terrainObjects[x][y] = [];
+
+ this.placeNew(x, y);
+};
+
+Terrain.prototype.placeNew = function() {};
+
+//////////////////////////////////////////////////////////////////////
+// SimpleTerrain
+//////////////////////////////////////////////////////////////////////
+
+function SimpleTerrain(texture, treeType)
+{
+ if (texture === undefined)
+ error("SimpleTerrain: texture not defined");
+
+ this.texture = texture;
+ this.treeType = treeType;
+}
+
+SimpleTerrain.prototype = new Terrain();
+SimpleTerrain.prototype.constructor = SimpleTerrain;
+SimpleTerrain.prototype.placeNew = function(x, y)
+{
+ if (this.treeType !== undefined)
+ g_Map.terrainObjects[x][y].push(new Entity(this.treeType, 0, x+0.5, y+0.5, randFloat()*PI));
+
+ g_Map.texture[x][y] = g_Map.getID(this.texture);
+};
+
+//////////////////////////////////////////////////////////////////////
+// RandomTerrain
+//////////////////////////////////////////////////////////////////////
+
+function RandomTerrain(terrains)
+{
+ if (!(terrains instanceof Array) || !terrains.length)
+ error("Invalid terrains array");
+
+ this.terrains = terrains;
+}
+
+RandomTerrain.prototype = new Terrain();
+RandomTerrain.prototype.constructor = RandomTerrain;
+RandomTerrain.prototype.placeNew = function(x, y)
+{
+ this.terrains[randInt(this.terrains.length)].placeNew(x, y);
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/tileclass.js b/binaries/data/mods/public/maps/random/rmgen/tileclass.js
new file mode 100644
index 0000000000..bcf9471f2c
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/tileclass.js
@@ -0,0 +1,141 @@
+//////////////////////////////////////////////////////////////////////
+// RangeOp
+//////////////////////////////////////////////////////////////////////
+
+function RangeOp(size)
+{
+ // Get smallest power of 2 which is greater than or equal to size
+ this.nn = 1;
+ while (this.nn < size) {
+ this.nn *= 2;
+ }
+
+ this.vals = new Int16Array(2*this.nn); // int16
+}
+
+RangeOp.prototype.set = function(pos, amt)
+{
+ this.add(pos, amt - this.vals[this.nn + pos]);
+};
+
+RangeOp.prototype.add = function(pos, amt)
+{
+ for(var s = this.nn; s >= 1; s /= 2)
+ {
+ this.vals[s + pos] += amt;
+ pos = Math.floor(pos/2);
+ }
+};
+
+RangeOp.prototype.get = function(start, end)
+{
+ var ret = 0;
+ var i;
+ var nn = this.nn;
+
+ // Count from start to end by powers of 2
+ for (i = 1; start+i <= end; i *= 2)
+ {
+ if (start & i)
+ { // For each bit in start
+ ret += this.vals[nn/i + Math.floor(start/i)];
+ start += i;
+ }
+ }
+
+ //
+ while(i >= 1)
+ {
+ if(start+i <= end)
+ {
+ ret += this.vals[nn/i + Math.floor(start/i)];
+ start += i;
+ }
+ i /= 2;
+ }
+
+ return ret;
+};
+
+
+//////////////////////////////////////////////////////////////////////
+// TileClass
+//////////////////////////////////////////////////////////////////////
+
+function TileClass(size)
+{
+ this.size = size;
+ this.inclusionCount = new Array(size);
+ this.rangeCount = new Array(size);
+
+ for (var i=0; i < size; ++i)
+ {
+ this.inclusionCount[i] = new Int16Array(size); //int16
+ this.rangeCount[i] = new RangeOp(size);
+ }
+}
+
+TileClass.prototype.add = function(x, y)
+{
+ if (!this.inclusionCount[x][y])
+ {
+ this.rangeCount[y].add(x, 1);
+ }
+
+ this.inclusionCount[x][y]++;
+};
+
+TileClass.prototype.remove = function(x, y)
+{
+ this.inclusionCount[x][y]--;
+ if(!this.inclusionCount[x][y])
+ {
+ this.rangeCount[y].add(x, -1);
+ }
+};
+
+TileClass.prototype.countInRadius = function(cx, cy, radius, returnMembers)
+{
+ var members = 0;
+ var nonMembers = 0;
+ var size = this.size;
+
+ var ymax = cy+radius;
+
+ for (var y = cy-radius; y <= ymax; y++)
+ {
+ var iy = Math.floor(y);
+ if(iy >= 0 && iy < size)
+ {
+ var dy = y - cy;
+ var dx = Math.sqrt(radius*radius - dy*dy);
+
+ var lowerX = Math.floor(cx - dx);
+ var upperX = Math.floor(cx + dx);
+
+ var minX = (lowerX > 0 ? lowerX : 0);
+ var maxX = (upperX < size ? upperX+1 : size);
+
+ var total = maxX - minX;
+ var mem = this.rangeCount[iy].get(minX, maxX);
+
+ members += mem;
+ nonMembers += total - mem;
+ }
+ }
+
+ if (returnMembers)
+ return members;
+ else
+ return nonMembers;
+};
+
+TileClass.prototype.countMembersInRadius = function(cx, cy, radius)
+{
+ return this.countInRadius(cx, cy, radius, true);
+};
+
+TileClass.prototype.countNonMembersInRadius = function(cx, cy, radius)
+{
+ return this.countInRadius(cx, cy, radius, false);
+};
diff --git a/binaries/data/mods/public/maps/random/rmgen/vector.js b/binaries/data/mods/public/maps/random/rmgen/vector.js
new file mode 100644
index 0000000000..618b58a384
--- /dev/null
+++ b/binaries/data/mods/public/maps/random/rmgen/vector.js
@@ -0,0 +1,135 @@
+/////////////////////////////////////////////////////////////////////
+// Vector2D
+/////////////////////////////////////////////////////////////////////
+
+// TODO: Type errors if v not instanceof Vector classes
+// TODO: Possible implement in C++
+
+function Vector2D(x, y)
+{
+ if (arguments.length == 2)
+ {
+ this.set(x, y);
+ }
+ else
+ {
+ this.set(0, 0);
+ }
+}
+
+Vector2D.prototype.set = function(x, y)
+{
+ this.x = x;
+ this.y = y;
+};
+
+Vector2D.prototype.add = function(v)
+{
+ return new Vector2D(this.x + v.x, this.y + v.y);
+};
+
+Vector2D.prototype.sub = function(v)
+{
+ return new Vector2D(this.x - v.x, this.y - v.y);
+};
+
+Vector2D.prototype.mult = function(f)
+{
+ return new Vector2D(this.x * f, this.y * f);
+};
+
+Vector2D.prototype.div = function(f)
+{
+ return new Vector2D(this.x / f, this.y / f);
+};
+
+Vector2D.prototype.dot = function(v)
+{
+ return this.x * v.x + this.y * v.y;
+};
+
+Vector2D.prototype.lengthSquared = function()
+{
+ return this.dot(this);
+};
+
+Vector2D.prototype.length = function()
+{
+ return sqrt(this.lengthSquared());
+};
+
+Vector2D.prototype.normalize = function()
+{
+ var mag = this.length();
+
+ this.x /= mag;
+ this.y /= mag;
+};
+
+/////////////////////////////////////////////////////////////////////
+// Vector3D
+/////////////////////////////////////////////////////////////////////
+
+function Vector3D(x, y, z)
+{
+ if (arguments.length == 3)
+ {
+ this.set(x, y, z);
+ }
+ else
+ {
+ this.set(0, 0, 0);
+ }
+}
+
+Vector3D.prototype.set = function(x, y, z)
+{
+ this.x = x;
+ this.y = y;
+ this.z = z;
+};
+
+Vector3D.prototype.add = function(v)
+{
+ return new Vector3D(this.x + v.x, this.y + v.y, this.z + v.z);
+};
+
+Vector3D.prototype.sub = function(v)
+{
+ return new Vector3D(this.x - v.x, this.y - v.y, this.z - v.z);
+};
+
+Vector3D.prototype.mult = function(f)
+{
+ return new Vector3D(this.x * f, this.y * f, this.z * f);
+};
+
+Vector3D.prototype.div = function(f)
+{
+ return new Vector3D(this.x / f, this.y / f, this.z / f);
+};
+
+Vector3D.prototype.dot = function(v)
+{
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+};
+
+Vector3D.prototype.lengthSquared = function()
+{
+ return this.dot(this);
+};
+
+Vector3D.prototype.length = function()
+{
+ return sqrt(this.lengthSquared());
+};
+
+Vector3D.prototype.normalize = function()
+{
+ var mag = this.length();
+
+ this.x /= mag;
+ this.y /= mag;
+ this.z /= mag;
+};
+
diff --git a/binaries/data/mods/public/maps/random/test.js b/binaries/data/mods/public/maps/random/test.js
deleted file mode 100644
index 7e66cbda3d..0000000000
--- a/binaries/data/mods/public/maps/random/test.js
+++ /dev/null
@@ -1,137 +0,0 @@
-const SIZE = 160;
-
-const sand = "beach_medit_dry";
-const grass1 = "grass_temperate_a";
-const grass2 = "grass_mediterranean_green_flowers";
-const forestFloor = "forrestfloor";
-const dirt1 = "grass_sand_75";
-const dirt2 = "grass_sand_50";
-const dirt3 = "dirt_brown_e";
-const cliffBase = "cliff base a";
-const cliffBeach = "beech_cliff_a_75";
-const cliff = "cliff_face3";
-
-const oTree = "flora_tree_oak";
-const oGrass = "props/flora/grass_soft_small.xml"
-const oMine = "geology_stone_light";
-
-// Initialize
-
-init(SIZE, grass1, 0);
-
-// Create classes
-
-clImpassable = createTileClass();
-clRock = createTileClass();
-
-// Paint elevation
-
-noise0 = new Noise2D(4 * SIZE/128.0);
-noise1 = new Noise2D(8 * SIZE/128.0);
-noise2 = new Noise2D(11 * SIZE/128.0);
-noise3 = new Noise2D(30 * SIZE/128.0);
-noise4 = new Noise2D(60 * SIZE/128.0);
-
-for(ix=0; ix 3.2) {
- setTexture(ix, iy, cliff);
- addToClass(ix, iy, clImpassable);
- }
- else if(maxH - minH > 2.7) {
- setTexture(ix, iy, cliffBase);
- addToClass(ix, iy, clImpassable);
- }
- else if(minH <= 0) {
- setTexture(ix, iy, sand);
- addToClass(ix, iy, clImpassable);
- }
- else {
- setTexture(ix, iy, grass1);
- }
- }
-}
-
-// Paint forest and dirt
-
-forestNoise1 = new Noise2D(20 * SIZE/128.0);
-forestNoise2 = new Noise2D(40 * SIZE/128.0);
-dirtNoise = new Noise2D(80 * SIZE/128.0);
-
-for(ix=0; ix 0) {
- fn = (forestNoise1.eval(x,y) + .5*forestNoise1.eval(x,y)) / 1.5;
-
- if(minH > .5 && fn < .38 && dirtNoise.eval(x,y) > .55) {
- if(dirtNoise.eval(x,y) > .72) {
- setTexture(ix, iy, dirt2);
- }
- else {
- setTexture(ix, iy, dirt1);
- }
- }
-
- if(fn > .6 && randFloat() < (.3 + .7 * Math.min(fn-.6, .1) / .1) ) {
- placeObject(oTree, 0, ix+.4+.2*randFloat(), iy+.4+.2*randFloat(), randFloat()*2*Math.PI);
- addToClass(ix, iy, clImpassable);
- if(randFloat() < .7) {
- setTexture(ix, iy, forestFloor);
- }
- }
- }
- }
-}
-
-println("Creating mines...");
-group = new SimpleGroup([new SimpleObject(oMine, 3,4, 0,2)], true, clRock);
-createObjectGroups(group, 0,
- new AvoidTileClassConstraint(clImpassable, 2, clRock, 13),
- 12, 100
-);
-
diff --git a/binaries/data/mods/public/maps/random/test2.js b/binaries/data/mods/public/maps/random/test2.js
deleted file mode 100644
index 042e77128d..0000000000
--- a/binaries/data/mods/public/maps/random/test2.js
+++ /dev/null
@@ -1,5 +0,0 @@
-//TODO: Move to some library file?
-
-
-initFromScenario("mediterannean", LOAD_ALL);
-
diff --git a/binaries/data/mods/public/maps/rmlibrary.js b/binaries/data/mods/public/maps/rmlibrary.js
deleted file mode 100644
index 06f75c9a81..0000000000
--- a/binaries/data/mods/public/maps/rmlibrary.js
+++ /dev/null
@@ -1,272 +0,0 @@
-// Object type constants
-
-const
- TYPE_RECT_PLACER = 1,
- TYPE_TERRAIN_PAINTER = 2,
- TYPE_NULL_CONSTRAINT = 3,
- TYPE_LAYERED_PAINTER = 4,
- TYPE_AVOID_AREA_CONSTRAINT = 5,
- TYPE_CLUMP_PLACER = 6,
- TYPE_AVOID_TEXTURE_CONSTRAINT = 7,
- TYPE_ELEVATION_PAINTER = 8,
- TYPE_SMOOTH_ELEVATION_PAINTER = 9,
- TYPE_SIMPLE_GROUP = 10,
- TYPE_AVOID_TILE_CLASS_CONSTRAINT = 11,
- TYPE_TILE_CLASS_PAINTER = 12,
- TYPE_STAY_IN_TILE_CLASS_CONSTRAINT = 13,
- TYPE_BORDER_TILE_CLASS_CONSTRAINT = 14;
-
-// SmoothElevationPainter constants
-
-const ELEVATION_SET = 0;
-const ELEVATION_MODIFY = 1;
-
-// PI
-
-const PI = Math.PI;
-
-// initFromScenario constants
-
-const LOAD_NOTHING = 0;
-const LOAD_TERRAIN = 1;
-const LOAD_INTERACTIVES = 2;
-const LOAD_NON_INTERACTIVES = 4;
-const LOAD_ALL = LOAD_TERRAIN | LOAD_INTERACTIVES | LOAD_NON_INTERACTIVES;
-
-// Utility functions
-
-function fractionToTiles(f) {
- return getMapSize() * f;
-}
-
-function tilesToFraction(t) {
- return t / getMapSize();
-}
-
-function fractionToSize(f) {
- return getMapSize() * getMapSize() * f;
-}
-
-function sizeToFraction(s) {
- return s / getMapSize() / getMapSize();
-}
-
-function cos(x) {
- return Math.cos(x);
-}
-
-function sin(x) {
- return Math.sin(x);
-}
-
-function tan(x) {
- return Math.tan(x);
-}
-
-function abs(x) {
- return Math.abs(x);
-}
-
-function round(x) {
- return Math.round(x);
-}
-
-function lerp(a, b, t) {
- return a + (b-a) * t;
-}
-
-function println(x) {
- print(x);
- print("\n");
-}
-
-function argsToArray(x) {
- if(x.length!=1) {
- var ret = new Array();
- for(var i=0; i.
+ */
+
+#ifndef INCLUDED_ENTITY
+#define INCLUDED_ENTITY
+
+// Struct for parsing random map data
+struct Entity
+{
+ std::wstring templateName;
+ u16 entityID;
+ u16 playerID;
+ float positionX;
+ float positionZ;
+ float orientationY;
+ bool isActor;
+};
+
+
+#endif
diff --git a/source/graphics/MapGenerator.cpp b/source/graphics/MapGenerator.cpp
new file mode 100644
index 0000000000..84331af206
--- /dev/null
+++ b/source/graphics/MapGenerator.cpp
@@ -0,0 +1,135 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "MapGenerator.h"
+
+#include "lib/timer.h"
+#include "ps/CLogger.h"
+#include
+
+// TODO: what's a good default? perhaps based on map size
+#define RMS_RUNTIME_SIZE 96 * 1024 * 1024
+
+boost::rand48 g_MapGenRNG;
+
+
+CMapGenerator::CMapGenerator() : m_ScriptInterface("RMS", "MapGenerator", ScriptInterface::CreateRuntime(RMS_RUNTIME_SIZE))
+{
+ m_ScriptInterface.SetCallbackData(static_cast (this));
+
+ // Replace RNG with a seeded deterministic function
+ m_ScriptInterface.ReplaceNondeterministicFunctions(g_MapGenRNG);
+
+ // functions for RMS
+ m_ScriptInterface.RegisterFunction("LoadLibrary");
+ m_ScriptInterface.RegisterFunction("ExportMap");
+}
+
+CMapGenerator::~CMapGenerator()
+{
+ // Clean up rooted objects before destroying their script context
+ m_MapData = CScriptValRooted();
+}
+
+bool CMapGenerator::GenerateMap(const VfsPath& scriptFile, const CScriptValRooted& settings)
+{
+ TIMER(L"GenerateMap");
+
+ // Init RNG seed
+ uint32 seed;
+ if (!m_ScriptInterface.GetProperty(settings.get(), "Seed", seed))
+ { // No seed specfified
+ LOGWARNING(L"GenerateMap: No seed value specified - using 0");
+ seed = 0;
+ }
+
+ g_MapGenRNG.seed(seed);
+
+ // Copy settings to script context
+ if (!m_ScriptInterface.SetProperty(m_ScriptInterface.GetGlobalObject(), "g_MapSettings", settings))
+ return false;
+
+ // Load RMS
+ LOGMESSAGE(L"Loading RMS '%ls'", scriptFile.c_str());
+ if (!m_ScriptInterface.LoadGlobalScriptFile(scriptFile))
+ {
+ LOGERROR(L"Failed to load RMS '%ls'", scriptFile.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+ScriptInterface& CMapGenerator::GetScriptInterface()
+{
+ return m_ScriptInterface;
+}
+
+CScriptValRooted& CMapGenerator::GetMapData()
+{
+ return m_MapData;
+}
+
+bool CMapGenerator::LoadLibrary(void* cbdata, std::wstring name)
+{
+ CMapGenerator* self = static_cast (cbdata);
+
+ return self->LoadScripts(name);
+}
+
+void CMapGenerator::ExportMap(void* cbdata, CScriptValRooted data)
+{
+ CMapGenerator* self = static_cast (cbdata);
+
+ // Copy results
+ self->m_MapData = data;
+}
+
+bool CMapGenerator::LoadScripts(const std::wstring& libraryName)
+{
+ // Ignore libraries that are already loaded
+ if (m_LoadedLibraries.find(libraryName) != m_LoadedLibraries.end())
+ return true;
+
+ // Mark this as loaded, to prevent it recursively loading itself
+ m_LoadedLibraries.insert(libraryName);
+
+ VfsPath path = L"maps/random/" + libraryName + L"/";
+ VfsPaths pathnames;
+
+ // Load all scripts in mapgen directory
+ if (fs_util::GetPathnames(g_VFS, path, L"*.js", pathnames) < 0)
+ {
+ LOGERROR(L"Error reading scripts in directory '%ls'", path.c_str());
+ return false;
+ }
+
+ for (VfsPaths::iterator it = pathnames.begin(); it != pathnames.end(); ++it)
+ {
+ LOGMESSAGE(L"Loading map generator script '%ls'", it->c_str());
+
+ if (!m_ScriptInterface.LoadGlobalScriptFile(*it))
+ {
+ LOGERROR(L"Failed to load script '%ls'", it->c_str());
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/source/graphics/MapGenerator.h b/source/graphics/MapGenerator.h
new file mode 100644
index 0000000000..87773cb4be
--- /dev/null
+++ b/source/graphics/MapGenerator.h
@@ -0,0 +1,58 @@
+/* Copyright (C) 2011 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_MAPGENERATOR
+#define INCLUDED_MAPGENERATOR
+
+#include "ps/CStr.h"
+#include "ps/FileIo.h"
+#include "scriptinterface/ScriptInterface.h"
+
+
+class CMapGenerator
+{
+
+public:
+ // constructor
+ CMapGenerator();
+
+ // destructor
+ ~CMapGenerator();
+
+ // return success of map generation
+ bool GenerateMap(const VfsPath& scriptFile, const CScriptValRooted& settings);
+
+ // accessors
+ ScriptInterface& GetScriptInterface();
+
+ CScriptValRooted& GetMapData();
+
+ // callbacks for script functions
+ static bool LoadLibrary(void* cbdata, std::wstring name);
+
+ static void ExportMap(void* cbdata, CScriptValRooted data);
+
+private:
+
+ bool LoadScripts(const std::wstring& libraryName);
+
+ ScriptInterface m_ScriptInterface;
+ CScriptValRooted m_MapData;
+ std::set m_LoadedLibraries;
+};
+
+#endif //INCLUDED_MAPGENERATOR
diff --git a/source/graphics/MapIO.h b/source/graphics/MapIO.h
index 46326a2400..c1857c0a23 100644
--- a/source/graphics/MapIO.h
+++ b/source/graphics/MapIO.h
@@ -24,7 +24,7 @@ public:
// current file version given to saved maps
enum { FILE_VERSION = 5 };
// supported file read version - file with version less than this will be reject
- enum { FILE_READ_VERSION = 1 };
+ enum { FILE_READ_VERSION = 5 };
#pragma pack(push, 1)
// description of a tile for I/O purposes
diff --git a/source/graphics/MapReader.cpp b/source/graphics/MapReader.cpp
index 324e6e60ca..24776a93a6 100644
--- a/source/graphics/MapReader.cpp
+++ b/source/graphics/MapReader.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -21,7 +21,9 @@
#include "graphics/Camera.h"
#include "graphics/CinemaTrack.h"
+#include "graphics/Entity.h"
#include "graphics/GameView.h"
+#include "graphics/MapGenerator.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
@@ -109,10 +111,66 @@ void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
if (!only_xml)
RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
- if (file_format_version >= 3) {
- // read the corresponding XML file
- RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 5800);
- }
+ // read the corresponding XML file
+ RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 5800);
+
+ // apply data to the world
+ RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
+
+ // load map settings script (must be done after reading map)
+ RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
+
+ RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5);
+}
+
+
+// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
+void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted& settings, CTerrain *pTerrain_,
+ WaterManager* pWaterMan_, SkyManager* pSkyMan_,
+ CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_,
+ CSimulation2 *pSimulation2_, int playerID_)
+{
+ // latch parameters (held until DelayedLoadFinished)
+ m_ScriptFile = scriptFile;
+ m_ScriptSettings = settings;
+ pTerrain = pTerrain_;
+ pLightEnv = pLightEnv_;
+ pGameView = pGameView_;
+ pWaterMan = pWaterMan_;
+ pSkyMan = pSkyMan_;
+ pCinema = pCinema_;
+ pTrigMan = pTrigMan_;
+ pSimulation2 = pSimulation2_;
+ m_PlayerID = playerID_;
+
+ m_CameraStartupTarget = INVALID_ENTITY;
+
+ // delete all existing entities
+ if (pSimulation2)
+ pSimulation2->ResetState();
+
+ only_xml = false;
+
+ // copy random map settings (before entity creation)
+ RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
+
+ // load player settings script (must be done before reading map)
+ RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
+
+ // load map generator with random map script
+ RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 2000);
+
+ // parse RMS results into terrain structure
+ RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
+
+ // parse RMS results into environment settings
+ RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5);
+
+ // parse RMS results into camera settings
+ RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5);
+
+ // parse RMS results into entities
+ RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
// apply data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
@@ -220,12 +278,9 @@ int CMapReader::ApplyData()
}
}
- if (file_format_version >= 4)
- {
- // copy over the lighting parameters
- if (pLightEnv)
- *pLightEnv = m_LightEnv;
- }
+ // copy over the lighting parameters
+ if (pLightEnv)
+ *pLightEnv = m_LightEnv;
if (pGameView)
{
@@ -351,7 +406,6 @@ private:
void ReadCamera(XMBElement parent);
void ReadCinema(XMBElement parent);
void ReadTriggers(XMBElement parent);
-// void ReadTriggerGroup(XMBElement parent, MapTriggerGroup& group);
int ReadEntities(XMBElement parent, double end_time);
int ReadOldEntities(XMBElement parent, double end_time);
int ReadNonEntities(XMBElement parent, double end_time);
@@ -783,174 +837,7 @@ void CXMLReader::ReadCinema(XMBElement parent)
void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
{
-// MapTriggerGroup rootGroup( L"Triggers", L"" );
-// if (m_MapReader.pTrigMan)
-// m_MapReader.pTrigMan->DestroyEngineTriggers();
-// ReadTriggerGroup(parent, rootGroup);
}
-/*
-void CXMLReader::ReadTriggerGroup(XMBElement parent, MapTriggerGroup& group)
-{
- #define EL(x) int el_##x = xmb_file.GetElementID(#x)
- #define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
-
- EL(group);
- EL(trigger);
- EL(active);
- EL(delay);
- EL(maxruncount);
- EL(conditions);
- EL(logicblock);
- EL(logicblockend);
- EL(condition);
- EL(parameter);
- EL(linklogic);
- EL(effects);
- EL(effect);
- EL(function);
- EL(display);
-
- AT(name);
- AT(function);
- AT(display);
- AT(not);
-
- #undef EL
- #undef AT
-
- CStrW name = parent.GetAttributes().GetNamedItem(at_name), parentName = group.parentName;
- if ( group.name == L"Triggers" )
- name = group.name;
-
- MapTriggerGroup mapGroup(name, parentName);
-
- XERO_ITER_EL(parent, groupChild)
- {
- int elementName = groupChild.GetNodeName();
- if ( elementName == el_group )
- ReadTriggerGroup(groupChild, mapGroup);
-
- else if ( elementName == el_trigger )
- {
- MapTrigger mapTrigger;
- mapTrigger.name = CStrW( groupChild.GetAttributes().GetNamedItem(at_name) );
-
- //Read everything in this trigger
- XERO_ITER_EL(groupChild, triggerChild)
- {
- elementName = triggerChild.GetNodeName();
- if ( elementName == el_active )
- {
- if ( CStr("false") == CStr( triggerChild.GetText() ) )
- mapTrigger.active = false;
- else
- mapTrigger.active = true;
- }
-
- else if ( elementName == el_maxruncount )
- mapTrigger.maxRunCount = CStr( triggerChild.GetText() ).ToInt();
- else if ( elementName == el_delay )
- mapTrigger.timeValue = CStr( triggerChild.GetText() ).ToFloat();
-
- else if ( elementName == el_conditions )
- {
- //Read in all conditions for this trigger
- XERO_ITER_EL(triggerChild, condition)
- {
- elementName = condition.GetNodeName();
- if ( elementName == el_condition )
- {
- MapTriggerCondition mapCondition;
- mapCondition.name = condition.GetAttributes().GetNamedItem(at_name);
- mapCondition.functionName = condition.GetAttributes().GetNamedItem(at_function);
- mapCondition.displayName = condition.GetAttributes().GetNamedItem(at_display);
-
- CStr notAtt(condition.GetAttributes().GetNamedItem(at_not));
- if ( notAtt == CStr("true") )
- mapCondition.negated = true;
-
- //Read in each condition child
- XERO_ITER_EL(condition, conditionChild)
- {
- elementName = conditionChild.GetNodeName();
-
- if ( elementName == el_function )
- mapCondition.functionName = CStrW(conditionChild.GetText());
- else if ( elementName == el_display )
- mapCondition.displayName = CStrW(conditionChild.GetText());
- else if ( elementName == el_parameter )
- mapCondition.parameters.push_back( conditionChild.GetText() );
- else if ( elementName == el_linklogic )
- {
- CStr logic = conditionChild.GetText();
- if ( logic == CStr("AND") )
- mapCondition.linkLogic = 1;
- else
- mapCondition.linkLogic = 2;
- }
- }
- mapTrigger.conditions.push_back(mapCondition);
- } //Read all conditions
-
- else if ( elementName == el_logicblock)
- {
- if ( CStr(condition.GetAttributes().GetNamedItem(at_not)) == CStr("true") )
- mapTrigger.AddLogicBlock(true);
- else
- mapTrigger.AddLogicBlock(false);
- }
- else if ( elementName == el_logicblockend)
- mapTrigger.AddLogicBlockEnd();
-
- } //Read all conditions
- }
-
- else if ( elementName == el_effects )
- {
- //Read all effects
- XERO_ITER_EL(triggerChild, effect)
- {
- if ( effect.GetNodeName() != el_effect )
- {
- debug_warn(L"Invalid effect tag in trigger XML file");
- return;
- }
- MapTriggerEffect mapEffect;
- mapEffect.name = effect.GetAttributes().GetNamedItem(at_name);
-
- //Read parameters
- XERO_ITER_EL(effect, effectChild)
- {
- elementName = effectChild.GetNodeName();
- if ( elementName == el_function )
- mapEffect.functionName = effectChild.GetText();
- else if ( elementName == el_display )
- mapEffect.displayName = effectChild.GetText();
- else if ( elementName == el_parameter )
- mapEffect.parameters.push_back( effectChild.GetText() );
- else
- {
- debug_warn(L"Invalid parameter tag in trigger XML file");
- return;
- }
- }
- mapTrigger.effects.push_back(mapEffect);
- }
- }
- else
- debug_warn(L"Invalid trigger node child in trigger XML file");
-
- } //Read trigger children
- m_MapReader.pTrigMan->AddTrigger(mapGroup, mapTrigger);
- }
- else
- debug_warn(L"Invalid group node child in XML file");
- } //Read group children
-
- if (m_MapReader.pTrigMan)
- m_MapReader.pTrigMan->AddGroup(mapGroup);
-}
-*/
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
@@ -1046,172 +933,6 @@ int CXMLReader::ReadEntities(XMBElement parent, double end_time)
return 0;
}
-int CXMLReader::ReadOldEntities(XMBElement parent, double end_time)
-{
- XMBElementList entities = parent.GetChildNodes();
-
- while (entity_idx < entities.Count)
- {
- // all new state at this scope and below doesn't need to be
- // wrapped, since we only yield after a complete iteration.
-
- XMBElement entity = entities.Item(entity_idx++);
- debug_assert(entity.GetNodeName() == el_entity);
-
- XMBAttributeList attrs = entity.GetAttributes();
-
- CStrW TemplateName;
- int PlayerID = 0;
- CFixedVector3D Position;
- fixed Orientation;
-
- XERO_ITER_EL(entity, setting)
- {
- int element_name = setting.GetNodeName();
-
- //
- if (element_name == el_template)
- {
- TemplateName = setting.GetText().FromUTF8();
- }
- //
- else if (element_name == el_player)
- {
- PlayerID = setting.GetText().ToInt();
- }
- //
- else if (element_name == el_position)
- {
- XMBAttributeList attrs = setting.GetAttributes();
- Position = CFixedVector3D(
- fixed::FromString(attrs.GetNamedItem(at_x)),
- fixed::FromString(attrs.GetNamedItem(at_y)),
- fixed::FromString(attrs.GetNamedItem(at_z)));
- }
- //
- else if (element_name == el_orientation)
- {
- XMBAttributeList attrs = setting.GetAttributes();
- Orientation = fixed::FromString(attrs.GetNamedItem(at_angle));
- }
- else
- debug_warn(L"Invalid map XML data");
- }
-
- // The old version uses a flat entity naming system, so we need
- // to translate it into the hierarchical filename
- if (TemplateName.Find(L"flora") == 0 || TemplateName.Find(L"fauna") == 0 || TemplateName.Find(L"geology") == 0 || TemplateName.Find(L"special") == 0)
- TemplateName = L"gaia/" + TemplateName;
- else if (TemplateName.Find(L"cart") == 0 || TemplateName.Find(L"celt") == 0 || TemplateName.Find(L"hele") == 0 ||
- TemplateName.Find(L"iber") == 0 || TemplateName.Find(L"pers") == 0 || TemplateName.Find(L"rome") == 0)
- {
- if (TemplateName.Find(L"cavalry") == 5 || TemplateName.Find(L"hero") == 5 || TemplateName.Find(L"infantry") == 5 ||
- TemplateName.Find(L"mechanical") == 5 || TemplateName.Find(L"ship") == 5 || TemplateName.Find(L"super") == 5 || TemplateName.Find(L"support") == 5)
- TemplateName = L"units/" + TemplateName;
- else
- TemplateName = L"structures/" + TemplateName;
- }
- else if (TemplateName.Find(L"skeleton") == 0)
- TemplateName = L"units/" + TemplateName;
- else if (TemplateName.Find(L"camp") == 0 || TemplateName.Find(L"fence") == 0 || TemplateName.Find(L"temp") == 0)
- TemplateName = L"other/" + TemplateName;
-
- entity_id_t ent = m_MapReader.pSimulation2->AddEntity(TemplateName);
- if (ent != INVALID_ENTITY)
- {
- CmpPtr cmpPosition(*m_MapReader.pSimulation2, ent);
- if (!cmpPosition.null())
- {
- cmpPosition->JumpTo(Position.X, Position.Z);
- cmpPosition->SetYRotation(Orientation);
- }
-
- CmpPtr cmpOwner(*m_MapReader.pSimulation2, ent);
- if (!cmpOwner.null())
- cmpOwner->SetOwner(PlayerID);
-
- if (m_MapReader.m_CameraStartupTarget == INVALID_ENTITY && !cmpPosition.null())
- {
- // Special-case civil centre files to initialise the camera.
- if (PlayerID == m_MapReader.m_PlayerID && boost::algorithm::ends_with(TemplateName, L"civil_centre"))
- {
- m_MapReader.m_CameraStartupTarget = ent;
- }
- }
- }
-
- completed_jobs++;
- LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
- }
-
- return 0;
-}
-
-
-int CXMLReader::ReadNonEntities(XMBElement parent, double end_time)
-{
- XMBElementList nonentities = parent.GetChildNodes();
- while (nonentity_idx < nonentities.Count)
- {
- // all new state at this scope and below doesn't need to be
- // wrapped, since we only yield after a complete iteration.
-
- XMBElement nonentity = nonentities.Item(nonentity_idx++);
- debug_assert(nonentity.GetNodeName() == el_nonentity);
-
- CStrW ActorName;
- CFixedVector3D Position;
- fixed Orientation;
-
- XERO_ITER_EL(nonentity, setting)
- {
- int element_name = setting.GetNodeName();
-
- //
- if (element_name == el_actor)
- {
- ActorName = setting.GetText().FromUTF8();
- }
- //
- else if (element_name == el_position)
- {
- XMBAttributeList attrs = setting.GetAttributes();
- Position = CFixedVector3D(
- fixed::FromString(attrs.GetNamedItem(at_x)),
- fixed::FromString(attrs.GetNamedItem(at_y)),
- fixed::FromString(attrs.GetNamedItem(at_z)));
- }
- //
- else if (element_name == el_orientation)
- {
- XMBAttributeList attrs = setting.GetAttributes();
- Orientation = fixed::FromString(attrs.GetNamedItem(at_angle));
- }
- else
- debug_warn(L"Invalid map XML data");
- }
-
- std::set selections; // TODO: read from file
-
- entity_id_t ent = m_MapReader.pSimulation2->AddEntity(L"actor|" + ActorName);
- if (ent != INVALID_ENTITY)
- {
- CmpPtr cmpPos(*m_MapReader.pSimulation2, ent);
- if (!cmpPos.null())
- {
- cmpPos->JumpTo(Position.X, Position.Z);
- cmpPos->SetYRotation(Orientation);
- }
- }
-
- completed_jobs++;
- LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
- }
-
- return 0;
-}
-
-
int CXMLReader::ProgressiveRead()
{
// yield after this time is reached. balances increased progress bar
@@ -1240,18 +961,6 @@ int CXMLReader::ProgressiveRead()
{
//Already loaded - this is to prevent an assertion
}
- else if (m_MapReader.file_format_version <= 4 && name == "Entities")
- {
- ret = ReadOldEntities(node, end_time);
- if (ret != 0) // error or timed out
- return ret;
- }
- else if (m_MapReader.file_format_version <= 4 && name == "Nonentities")
- {
- ret = ReadNonEntities(node, end_time);
- if (ret != 0) // error or timed out
- return ret;
- }
else if (name == "Entities")
{
ret = ReadEntities(node, end_time);
@@ -1338,3 +1047,264 @@ int CMapReader::DelayLoadFinished()
return 0;
}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+int CMapReader::LoadRMSettings()
+{
+ // copy random map settings over to sim
+ pSimulation2->SetMapSettings(m_ScriptSettings);
+
+ return 0;
+}
+
+int CMapReader::GenerateMap()
+{
+ CMapGenerator mapGen;
+
+ VfsPath scriptPath;
+
+ if (m_ScriptFile.length())
+ scriptPath = L"maps/random/"+m_ScriptFile;
+
+ // Copy map settings from simulator to mapgen context
+ CScriptValRooted scriptSettings(mapGen.GetScriptInterface().GetContext(), mapGen.GetScriptInterface().CloneValueFromOtherContext(pSimulation2->GetScriptInterface(), m_ScriptSettings.get()));
+
+ // Try to generate map
+ if (!mapGen.GenerateMap(scriptPath, scriptSettings))
+ { // RMS failed
+ // TODO: Need to do something safe here, like cancel loading and return to main menu
+ LOGERROR(L"Map generation failed: RMS returned undefined");
+ return -1;
+ }
+
+ if (mapGen.GetMapData().undefined())
+ {
+ LOGERROR(L"undefined map data");
+ }
+
+ // Copy data from mapgen to simulator context
+ m_MapData = CScriptValRooted(pSimulation2->GetScriptInterface().GetContext(), pSimulation2->GetScriptInterface().CloneValueFromOtherContext(mapGen.GetScriptInterface(), mapGen.GetMapData().get()));
+
+ return 0;
+};
+
+
+int CMapReader::ParseTerrain()
+{
+ // parse terrain from map data
+
+#define GET_TERRAIN_PROPERTY(prop, out)\
+ if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData.get(), #prop, out))\
+ LOGERROR(L"CMapReader::ParseTerrain() failed to get '%hs' property", #prop);\
+
+ size_t size;
+ GET_TERRAIN_PROPERTY(size, size)
+
+ m_PatchesPerSide = size / PATCH_SIZE;
+
+ // flat heightmap of u16 data
+ GET_TERRAIN_PROPERTY(height, m_Heightmap)
+
+ // load textures
+ GET_TERRAIN_PROPERTY(numTextures, num_terrain_tex)
+
+ std::vector textureNames;
+ GET_TERRAIN_PROPERTY(textureNames, textureNames)
+
+ while (cur_terrain_tex < num_terrain_tex)
+ {
+ debug_assert(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
+ CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
+ m_TerrainTextures.push_back(texentry);
+
+ cur_terrain_tex++;
+ }
+
+ // build tile data
+ m_Tiles.resize(SQR(size));
+
+ std::vector tileData;
+ GET_TERRAIN_PROPERTY(tileData, tileData)
+
+ for (size_t i = 0; i < tileData.size(); ++i)
+ {
+ m_Tiles[i] = tileData[i];
+ }
+
+ // reset generator state
+ cur_terrain_tex = 0;
+
+#undef GET_TERRAIN_PROPERTY
+
+ return 0;
+}
+
+int CMapReader::ParseEntities()
+{
+ // parse entities from map data
+ std::vector entities;
+
+ if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData.get(), "entities", entities))
+ LOGWARNING(L"CMapReader::ParseEntities() failed to get 'entities' property");
+
+ size_t entity_idx = 0;
+ size_t num_entities = entities.size();
+
+ Entity currEnt;
+
+ while (entity_idx < num_entities)
+ {
+ // Get current entity struct
+ currEnt = entities[entity_idx];
+
+ entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
+ // Check that entity was added
+ if (ent == INVALID_ENTITY)
+ {
+ LOGERROR(L"Failed to load entity template '%ls'", currEnt.templateName.c_str());
+ }
+ else
+ {
+ CmpPtr cmpPosition(*pSimulation2, ent);
+ if (!cmpPosition.null())
+ {
+ cmpPosition->JumpTo(entity_pos_t::FromFloat(currEnt.positionX), entity_pos_t::FromFloat(currEnt.positionZ));
+ cmpPosition->SetYRotation(entity_angle_t::FromFloat(currEnt.orientationY));
+ // TODO: other parts of the position
+ }
+
+ CmpPtr cmpOwner(*pSimulation2, ent);
+ if (!cmpOwner.null())
+ cmpOwner->SetOwner(currEnt.playerID);
+
+ if (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre"))
+ {
+ // HACK: we special-case civil centre files to initialise the camera.
+ // This ought to be based on a more generic mechanism for indicating
+ // per-player camera start locations.
+ if (m_CameraStartupTarget == INVALID_ENTITY && currEnt.playerID == m_PlayerID && !cmpPosition.null())
+ m_CameraStartupTarget = ent;
+
+ }
+ }
+
+ entity_idx++;
+ }
+
+ return 0;
+}
+
+int CMapReader::ParseEnvironment()
+{
+ // parse environment settings from map data
+
+#define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
+ if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
+ LOGWARNING(L"CMapReader::ParseEnvironment() failed to get '%hs' property", #prop);\
+
+ CScriptValRooted envObj;
+ GET_ENVIRONMENT_PROPERTY(m_MapData.get(), Environment, envObj)
+
+ if (envObj.undefined())
+ {
+ LOGWARNING(L"CMapReader::ParseEnvironment(): Environment settings not found");
+ return 0;
+ }
+
+ std::wstring skySet;
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), SkySet, skySet)
+ pSkyMan->SetSkySet(skySet);
+
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), SunColour, m_LightEnv.m_SunColor)
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), SunElevation, m_LightEnv.m_Elevation)
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), SunRotation, m_LightEnv.m_Rotation)
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), TerrainAmbientColour, m_LightEnv.m_TerrainAmbientColor)
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), UnitsAmbientColour, m_LightEnv.m_UnitsAmbientColor)
+
+ // Water properties
+ CScriptValRooted waterObj;
+ GET_ENVIRONMENT_PROPERTY(envObj.get(), Water, waterObj)
+
+ CScriptValRooted waterBodyObj;
+ GET_ENVIRONMENT_PROPERTY(waterObj.get(), WaterBody, waterBodyObj)
+
+ // Water level - necessary
+ float waterHeight;
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Height, waterHeight)
+
+ CmpPtr cmpWaterMan(*pSimulation2, SYSTEM_ENTITY);
+ debug_assert(!cmpWaterMan.null());
+ cmpWaterMan->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
+
+ // If we have graphics, get rest of settings
+ if (pWaterMan)
+ {
+ std::wstring waterType;
+ // TODO: Water type not implemented
+ //GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Type, waterType)
+
+ RGBColor waterColour;
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Colour, waterColour)
+ pWaterMan->m_WaterColor = CColor(waterColour.X, waterColour.Y, waterColour.Z, 1.0f);
+
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Shininess, pWaterMan->m_Shininess)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Waviness, pWaterMan->m_Waviness)
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Murkiness, pWaterMan->m_Murkiness)
+
+ RGBColor waterTint;
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Tint, waterTint)
+ pWaterMan->m_WaterTint = CColor(waterTint.X, waterTint.Y, waterTint.Z, 1.0f);
+
+ RGBColor reflectTint;
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), ReflectionTint, reflectTint)
+ pWaterMan->m_ReflectionTint = CColor(reflectTint.X, reflectTint.Y, reflectTint.Z, 1.0f);
+
+ GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), ReflectionTintStrength, pWaterMan->m_ReflectionTintStrength)
+ }
+
+ m_LightEnv.CalculateSunDirection();
+
+#undef GET_ENVIRONMENT_PROPERTY
+
+ return 0;
+}
+
+int CMapReader::ParseCamera()
+{
+ // parse camera settings from map data
+ // defaults if we don't find camera
+ float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
+ CVector3D translation = CVector3D(100, 150, -100);
+
+#define GET_CAMERA_PROPERTY(val, prop, out)\
+ if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
+ LOGWARNING(L"CMapReader::ParseCamera() failed to get '%hs' property", #prop);\
+
+ CScriptValRooted cameraObj;
+ GET_CAMERA_PROPERTY(m_MapData.get(), Camera, cameraObj)
+
+ if (!cameraObj.undefined())
+ { // If camera property exists, read values
+ CFixedVector3D pos;
+ GET_CAMERA_PROPERTY(cameraObj.get(), Position, pos)
+ translation = pos;
+
+ GET_CAMERA_PROPERTY(cameraObj.get(), Rotation, rotation)
+ GET_CAMERA_PROPERTY(cameraObj.get(), Declination, declination)
+ }
+#undef GET_CAMERA_PROPERTY
+
+ if (pGameView)
+ {
+ pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
+ pGameView->GetCamera()->m_Orientation.RotateY(rotation);
+ pGameView->GetCamera()->m_Orientation.Translate(translation);
+ pGameView->GetCamera()->UpdateFrustum();
+ }
+
+ return 0;
+}
diff --git a/source/graphics/MapReader.h b/source/graphics/MapReader.h
index df81c203b7..e53472a177 100644
--- a/source/graphics/MapReader.h
+++ b/source/graphics/MapReader.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -81,6 +81,25 @@ private:
// clean up everything used during delayed load
int DelayLoadFinished();
+
+ // Copy random map settings over to sim
+ int LoadRMSettings();
+
+ // Generate random map
+ int GenerateMap();
+
+ // Parse script data into terrain
+ int ParseTerrain();
+
+ // Parse script data into entities
+ int ParseEntities();
+
+ // Parse script data into environment
+ int ParseEnvironment();
+
+ // Parse script data into camera
+ int ParseCamera();
+
// size of map
ssize_t m_PatchesPerSide;
@@ -95,6 +114,11 @@ private:
// startup script
CStrW m_Script;
+ // random map data
+ CStrW m_ScriptFile;
+ CScriptValRooted m_ScriptSettings;
+ CScriptValRooted m_MapData;
+
// state latched by LoadMap and held until DelayedLoadFinished
CFileUnpacker unpacker;
CTerrain* pTerrain;
diff --git a/source/ps/Game.cpp b/source/ps/Game.cpp
index b430ade5e2..769522ca83 100644
--- a/source/ps/Game.cpp
+++ b/source/ps/Game.cpp
@@ -142,7 +142,14 @@ void CGame::RegisterInit(const CScriptValRooted& attribs)
}
else if (mapType == "random")
{
- // TODO: Coming in another patch
+ // Load random map attributes
+ std::wstring scriptFile;
+ CScriptValRooted settings;
+
+ m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "script", scriptFile);
+ m_Simulation2->GetScriptInterface().GetProperty(attribs.get(), "settings", settings);
+
+ m_World->RegisterInitRMS(scriptFile, settings, m_PlayerID);
}
LDR_EndRegistering();
diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp
index 3581263c37..937b7e2ca6 100644
--- a/source/ps/GameSetup/GameSetup.cpp
+++ b/source/ps/GameSetup/GameSetup.cpp
@@ -957,28 +957,62 @@ static bool Autostart(const CmdLineArgs& args)
* -autostart=mapname -- single-player
* -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players
* -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1
+ * -autostart=scriptname -autostart-random
*/
- CStr autostartMap = args.Get("autostart");
- if (autostartMap.empty())
+ CStr autoStartName = args.Get("autostart");
+ if (autoStartName.empty())
return false;
g_Game = new CGame();
ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface();
+
CScriptValRooted attrs;
scriptInterface.Eval("({})", attrs);
- scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"));
- scriptInterface.SetProperty(attrs.get(), "map", std::string(autostartMap));
+ CScriptVal settings;
+ scriptInterface.Eval("({})", settings);
+ CScriptVal playerData;
+ scriptInterface.Eval("([])", playerData);
- // Set attrs.settings = { PlayerData: [ { AI: ... }, ... ] }:
+ // Set different attributes for random or scenario game
+ if (args.Has("autostart-random"))
+ {
+ scriptInterface.SetProperty(attrs.get(), "script", std::string(autoStartName), false); // RMS name
+ scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"), false);
+
+ // For random map, there are special settings
+ // TODO: Get these from command line - using defaults for now
+ scriptInterface.SetProperty(settings.get(), "Size", 16); // Random map size (in patches)
+ scriptInterface.SetProperty(settings.get(), "Seed", 0); // Random seed
+ scriptInterface.SetProperty(settings.get(), "BaseTerrain", std::string("grass1_spring")); // Base terrain texture
+ scriptInterface.SetProperty(settings.get(), "BaseHeight", 0); // Base terrain height
+
+ // Define players
+ // TODO: Get these from command line - using defaults for now
+ size_t numPlayers = 2;
+ for (size_t i = 0; i < numPlayers; ++i)
+ {
+ CScriptVal player;
+ scriptInterface.Eval("({})", player);
+
+ scriptInterface.SetProperty(player.get(), "Civ", std::string("hele"));
+ scriptInterface.SetPropertyInt(playerData.get(), i, player);
+ }
+ }
+ else
+ {
+ scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName), false);
+ scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false);
+ }
+
+ // Set player data for AIs
+ // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }:
/*
* Handle command-line options for AI:
* -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2
*/
- CScriptVal playerData;
- scriptInterface.Eval("([])", playerData);
if (args.Has("autostart-ai"))
{
std::vector aiArgs = args.GetMultiple("autostart-ai");
@@ -995,13 +1029,12 @@ static bool Autostart(const CmdLineArgs& args)
}
}
- CScriptVal settings;
- scriptInterface.Eval("({})", settings);
+ // Add player data to map settings
scriptInterface.SetProperty(settings.get(), "PlayerData", playerData);
+
+ // Add map settings to game attributes
scriptInterface.SetProperty(attrs.get(), "settings", settings);
-
-
CScriptVal mpInitData;
g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData);
g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs",
diff --git a/source/ps/World.cpp b/source/ps/World.cpp
index c589047d44..96e1f34794 100644
--- a/source/ps/World.cpp
+++ b/source/ps/World.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -94,6 +94,32 @@ void CWorld::RegisterInit(const CStrW& mapFile, int playerID)
}
}
+void CWorld::RegisterInitRMS(const CStrW& scriptFile, const CScriptValRooted& settings, int playerID)
+{
+ // If scriptFile is empty, a blank map will be generated using settings (no RMS run)
+ CMapReader* reader = 0;
+
+ try
+ {
+ reader = new CMapReader;
+ CTriggerManager* pTriggerManager = NULL;
+ reader->LoadRandomMap(scriptFile, settings, m_Terrain,
+ CRenderer::IsInitialised() ? g_Renderer.GetWaterManager() : NULL,
+ CRenderer::IsInitialised() ? g_Renderer.GetSkyManager() : NULL,
+ &g_LightEnv, m_pGame->GetView(),
+ m_pGame->GetView() ? m_pGame->GetView()->GetCinema() : NULL,
+ pTriggerManager, m_pGame->GetSimulation2(), playerID);
+ // fails immediately, or registers for delay loading
+ }
+ catch (PSERROR_File& err)
+ {
+ delete reader;
+ LOGERROR(L"Failed to generate random map %ls: %hs", scriptFile.c_str(), err.what());
+ throw PSERROR_Game_World_MapLoadFailed();
+ }
+}
+
+
/**
* Destructor.
*
diff --git a/source/ps/World.h b/source/ps/World.h
index 19a2b5ee33..cfc129c20f 100644
--- a/source/ps/World.h
+++ b/source/ps/World.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2009 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -25,6 +25,7 @@
#define INCLUDED_WORLD
#include "ps/Errors.h"
+#include "scriptinterface/ScriptInterface.h"
#ifndef ERROR_GROUP_GAME_DEFINED
#define ERROR_GROUP_GAME_DEFINED
@@ -74,6 +75,11 @@ public:
*/
void RegisterInit(const CStrW& mapFile, int playerID);
+ /*
+ Initialize the World - generate and load the random map
+ */
+ void RegisterInitRMS(const CStrW& scriptFile, const CScriptValRooted& settings, int playerID);
+
/**
* Get the pointer to the terrain object.
*
diff --git a/source/scriptinterface/ScriptConversions.cpp b/source/scriptinterface/ScriptConversions.cpp
index 1660bc5b4d..4840319d3e 100644
--- a/source/scriptinterface/ScriptConversions.cpp
+++ b/source/scriptinterface/ScriptConversions.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2011 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,6 +19,9 @@
#include "ScriptInterface.h"
+#include "graphics/Color.h"
+#include "graphics/Entity.h"
+#include "graphics/MapIO.h"
#include "ps/utf16string.h"
#include "ps/CLogger.h"
#include "ps/CStr.h"
@@ -84,6 +87,16 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, u32& out
return true;
}
+template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, u16& out)
+{
+ uint16 ret;
+ WARN_IF_NOT(JSVAL_IS_NUMBER(v), v);
+ if (!JS_ValueToUint16(cx, v, &ret))
+ return false;
+ out = ret;
+ return true;
+}
+
// NOTE: we can't define a jsval specialisation, because that conflicts with integer types
template<> bool ScriptInterface::FromJSVal(JSContext* UNUSED(cx), jsval v, CScriptVal& out)
{
@@ -122,6 +135,69 @@ template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v,
return true;
}
+template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, Entity& out)
+{
+ JSObject* obj;
+ if (!JS_ValueToObject(cx, v, &obj) || obj == NULL)
+ FAIL("Argument must be an object");
+
+ jsval name, id, player, x, z, orient, actor;
+
+ if(!JS_GetProperty(cx, obj, "isActor", &actor) || !FromJSVal(cx, actor, out.isActor))
+ FAIL("Failed to read Entity.isActor property");
+
+ if (!out.isActor)
+ if(!JS_GetProperty(cx, obj, "player", &player) || !FromJSVal(cx, player, out.playerID))
+ FAIL("Failed to read Entity.player property");
+
+ if (!JS_GetProperty(cx, obj, "name", &name) || !FromJSVal(cx, name, out.templateName))
+ FAIL("Failed to read Entity.name property");
+ if (!JS_GetProperty(cx, obj, "id", &id) || !FromJSVal(cx, id, out.entityID))
+ FAIL("Failed to read Entity.id property");
+ if (!JS_GetProperty(cx, obj, "x", &x) || !FromJSVal(cx, x, out.positionX))
+ FAIL("Failed to read Entity.x property");
+ if (!JS_GetProperty(cx, obj, "y", &z) || !FromJSVal(cx, z, out.positionZ))
+ FAIL("Failed to read Entity.y property");
+ if (!JS_GetProperty(cx, obj, "orientation", &orient) || !FromJSVal(cx, orient, out.orientationY))
+ FAIL("Failed to read Entity.orientation property");
+
+ return true;
+}
+
+template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, RGBColor& out)
+{
+ JSObject* obj;
+ if (!JS_ValueToObject(cx, v, &obj) || obj == NULL)
+ FAIL("Argument must be an object");
+
+ jsval r, g, b;
+ if (!JS_GetProperty(cx, obj, "r", &r) || !FromJSVal(cx, r, out.X))
+ FAIL("Failed to read RGBColor.r property");
+ if (!JS_GetProperty(cx, obj, "g", &g) || !FromJSVal(cx, g, out.Y))
+ FAIL("Failed to read RGBColor.g property");
+ if (!JS_GetProperty(cx, obj, "b", &b) || !FromJSVal(cx, b, out.Z))
+ FAIL("Failed to read RGBColor.b property");
+
+ return true;
+}
+
+template<> bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, CMapIO::STileDesc& out)
+{
+ JSObject* obj;
+ if (!JS_ValueToObject(cx, v, &obj) || obj == NULL)
+ FAIL("Argument must be an object");
+
+ jsval texIdx1, texIdx2, priority;
+ if (!JS_GetProperty(cx, obj, "texIdx1", &texIdx1) || !FromJSVal(cx, texIdx1, out.m_Tex1Index))
+ FAIL("Failed to read CMapIO::STileDesc.m_Tex1Index property");
+ if (!JS_GetProperty(cx, obj, "texIdx2", &texIdx2) || !FromJSVal(cx, texIdx2, out.m_Tex2Index))
+ FAIL("Failed to read CMapIO::STileDesc.m_Tex2Index property");
+ if (!JS_GetProperty(cx, obj, "priority", &priority) || !FromJSVal(cx, priority, out.m_Priority))
+ FAIL("Failed to read CMapIO::STileDesc.m_Priority property");
+
+ return true;
+}
+
////////////////////////////////////////////////////////////////
// Primitive types:
@@ -267,12 +343,24 @@ template static bool FromJSVal_vector(JSContext* cx, jsval v, std::v
VECTOR(int)
VECTOR(u32)
+VECTOR(u16)
VECTOR(std::string)
VECTOR(std::wstring)
VECTOR(CScriptValRooted)
+
class IComponent;
template<> jsval ScriptInterface::ToJSVal >(JSContext* cx, const std::vector& val)
{
return ToJSVal_vector(cx, val);
}
+
+template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out)
+{
+ return FromJSVal_vector(cx, v, out);
+}
+
+template<> bool ScriptInterface::FromJSVal >(JSContext* cx, jsval v, std::vector& out)
+{
+ return FromJSVal_vector(cx, v, out);
+}
diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
index cc5459c0a0..745863b63e 100644
--- a/source/scriptinterface/ScriptInterface.cpp
+++ b/source/scriptinterface/ScriptInterface.cpp
@@ -43,8 +43,7 @@
#include "valgrind.h"
-const int RUNTIME_SIZE = 16 * 1024 * 1024; // TODO: how much memory is needed?
-const int STACK_CHUNK_SIZE = 8192;
+#define STACK_CHUNK_SIZE 8192
#if ENABLE_SCRIPT_PROFILING
# define signbit std::signbit
@@ -57,10 +56,10 @@ const int STACK_CHUNK_SIZE = 8192;
class ScriptRuntime
{
public:
- ScriptRuntime() :
+ ScriptRuntime(int runtimeSize) :
m_rooter(NULL)
{
- m_rt = JS_NewRuntime(RUNTIME_SIZE);
+ m_rt = JS_NewRuntime(runtimeSize);
debug_assert(m_rt); // TODO: error handling
#if ENABLE_SCRIPT_PROFILING
@@ -211,9 +210,9 @@ private:
}
};
-shared_ptr ScriptInterface::CreateRuntime()
+shared_ptr ScriptInterface::CreateRuntime(int runtimeSize)
{
- return shared_ptr(new ScriptRuntime);
+ return shared_ptr(new ScriptRuntime(runtimeSize));
}
////////////////////////////////////////////////////////////////
@@ -438,8 +437,8 @@ ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh
#endif
// Some other JIT flags to experiment with:
-// options |= JSOPTION_JIT;
-// options |= JSOPTION_PROFILING;
+ options |= JSOPTION_JIT;
+ options |= JSOPTION_PROFILING;
JS_SetOptions(m_cx, options);
diff --git a/source/scriptinterface/ScriptInterface.h b/source/scriptinterface/ScriptInterface.h
index 222fe639e3..0d44d19fc9 100644
--- a/source/scriptinterface/ScriptInterface.h
+++ b/source/scriptinterface/ScriptInterface.h
@@ -40,6 +40,10 @@ namespace boost { class rand48; }
// but as large as necessary for all wrapped functions)
#define SCRIPT_INTERFACE_MAX_ARGS 6
+// TODO: what's a good default?
+#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024
+
+
#ifdef NDEBUG
#define ENABLE_SCRIPT_PROFILING 0
#else
@@ -67,8 +71,9 @@ public:
* ScriptInterfaces contexts. Values created in one context may be used
* in any other context from the same runtime (but not any other runtime).
* Each runtime should only ever be used on a single thread.
+ * @param runtimeSize Maximum size in bytes of the new runtime
*/
- static shared_ptr CreateRuntime();
+ static shared_ptr CreateRuntime(int runtimeSize = DEFAULT_RUNTIME_SIZE);
/**
* Constructor.