Index: ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js (nonexistent)
@@ -1,34 +0,0 @@
-/**
- * Loads history and some gameplay data for civs.
- *
- * @param selectableOnly {boolean} - Only load those which can be selected
- * in the gamesetup. Scenario maps might set non-selectable civs.
- * @param gaia {boolean} - Whether to include gaia as a mock civ.
- */
-function loadCivData(selectableOnly, gaia)
-{
- let civData = {};
- let civFiles = Engine.BuildDirEntList("simulation/data/civs/", "*.json", false);
-
- for (let filename of civFiles)
- {
- let data = Engine.ReadJSONFile(filename);
- if (!data)
- continue;
-
- translateObjectKeys(data, ["Name", "Description", "History", "Special"]);
- if (!selectableOnly || data.SelectableInGameSetup)
- civData[data.Code] = data;
-
- // Sanity check
- for (let prop of ["Code", "Culture", "Name", "Emblem", "History", "Music", "Factions", "CivBonuses",
- "TeamBonuses", "Structures", "StartEntities", "Formations", "AINames", "SelectableInGameSetup"])
- if (data[prop] == undefined)
- error(filename + " doesn't contain " + prop);
- }
-
- if (gaia)
- civData.gaia = { "Code": "gaia", "Name": translate("Gaia") };
-
- return deepfreeze(civData);
-}
Property changes on: ps/trunk/binaries/data/mods/public/gui/common/functions_civinfo.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/globalscripts/Templates.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 20527)
+++ ps/trunk/binaries/data/mods/public/globalscripts/Templates.js (revision 20528)
@@ -1,488 +1,518 @@
/**
+ * Loads history and gameplay data of all civs.
+ * Can be used from GUI and rmgen (because the simulation functions differ currently).
+ *
+ * @param selectableOnly {boolean} - Only load civs that can be selected
+ * in the gamesetup. Scenario maps might set non-selectable civs.
+ */
+function loadCivFiles(selectableOnly)
+{
+ let propertyNames = [
+ "Code", "Culture", "Name", "Emblem", "History", "Music", "Factions", "CivBonuses", "TeamBonuses",
+ "Structures", "StartEntities", "Formations", "AINames", "SkirmishReplacements", "SelectableInGameSetup"];
+
+ let civData = {};
+
+ for (let filename of Engine.BuildDirEntList("simulation/data/civs/", "*.json", false))
+ {
+ let data = Engine.ReadJSONFile(filename);
+
+ for (let prop of propertyNames)
+ if (data[prop] === undefined)
+ throw new Error(filename + " doesn't contain " + prop);
+
+ if (!selectableOnly || data.SelectableInGameSetup)
+ civData[data.Code] = data;
+ }
+
+ return civData;
+}
+
+/**
* Gets an array of all classes for this identity template
*/
function GetIdentityClasses(template)
{
var classList = [];
if (template.Classes && template.Classes._string)
classList = classList.concat(template.Classes._string.split(/\s+/));
if (template.VisibleClasses && template.VisibleClasses._string)
classList = classList.concat(template.VisibleClasses._string.split(/\s+/));
if (template.Rank)
classList = classList.concat(template.Rank);
return classList;
}
/**
* Gets an array with all classes for this identity template
* that should be shown in the GUI
*/
function GetVisibleIdentityClasses(template)
{
if (template.VisibleClasses && template.VisibleClasses._string)
return template.VisibleClasses._string.split(/\s+/);
return [];
}
/**
* Check if a given list of classes matches another list of classes.
* Useful f.e. for checking identity classes.
*
* @param classes - List of the classes to check against.
* @param match - Either a string in the form
* "Class1 Class2+Class3"
* where spaces are handled as OR and '+'-signs as AND,
* and ! is handled as NOT, thus Class1+!Class2 = Class1 AND NOT Class2.
* Or a list in the form
* [["Class1"], ["Class2", "Class3"]]
* where the outer list is combined as OR, and the inner lists are AND-ed.
* Or a hybrid format containing a list of strings, where the list is
* combined as OR, and the strings are split by space and '+' and AND-ed.
*
* @return undefined if there are no classes or no match object
* true if the the logical combination in the match object matches the classes
* false otherwise.
*/
function MatchesClassList(classes, match)
{
if (!match || !classes)
return undefined;
// Transform the string to an array
if (typeof match == "string")
match = match.split(/\s+/);
for (let sublist of match)
{
// If the elements are still strings, split them by space or by '+'
if (typeof sublist == "string")
sublist = sublist.split(/[+\s]+/);
if (sublist.every(c => (c[0] == "!" && classes.indexOf(c.substr(1)) == -1)
|| (c[0] != "!" && classes.indexOf(c) != -1)))
return true;
}
return false;
}
/**
* Gets the value originating at the value_path as-is, with no modifiers applied.
*
* @param {object} template - A valid template as returned from a template loader.
* @param {string} value_path - Route to value within the xml template structure.
* @return {number}
*/
function GetBaseTemplateDataValue(template, value_path)
{
let current_value = template;
for (let property of value_path.split("/"))
current_value = current_value[property] || 0;
return +current_value;
}
/**
* Gets the value originating at the value_path with the modifiers dictated by the mod_key applied.
*
* @param {object} template - A valid template as returned from a template loader.
* @param {string} value_path - Route to value within the xml template structure.
* @param {string} mod_key - Tech modification key, if different from value_path.
* @param {number} player - Optional player id.
* @param {object} modifiers - Value modifiers from auto-researched techs, unit upgrades,
* etc. Optional as only used if no player id provided.
* @return {number} Modifier altered value.
*/
function GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers={})
{
let current_value = GetBaseTemplateDataValue(template, value_path);
mod_key = mod_key || value_path;
if (player)
current_value = ApplyValueModificationsToTemplate(mod_key, current_value, player, template);
else if (modifiers)
current_value = GetTechModifiedProperty(modifiers, GetIdentityClasses(template.Identity), mod_key, current_value);
// Using .toFixed() to get around spidermonkey's treatment of numbers (3 * 1.1 = 3.3000000000000003 for instance).
return +current_value.toFixed(8);
}
/**
* Get information about a template with or without technology modifications.
*
* NOTICE: The data returned here should have the same structure as
* the object returned by GetEntityState and GetExtendedEntityState!
*
* @param {object} template - A valid template as returned by the template loader.
* @param {number} player - An optional player id to get the technology modifications
* of properties.
* @param {object} auraTemplates - In the form of { key: { "auraName": "", "auraDescription": "" } }.
* @param {object} resources - An instance of the Resources prototype.
* @param {object} damageTypes - An instance of the DamageTypes prototype.
* @param {object} modifiers - Modifications from auto-researched techs, unit upgrades
* etc. Optional as only used if there's no player
* id provided.
*/
function GetTemplateDataHelper(template, player, auraTemplates, resources, damageTypes, modifiers={})
{
// Return data either from template (in tech tree) or sim state (ingame).
// @param {string} value_path - Route to the value within the template.
// @param {string} mod_key - Modification key, if not the same as the value_path.
let getEntityValue = function(value_path, mod_key) {
return GetModifiedTemplateDataValue(template, value_path, mod_key, player, modifiers);
};
let ret = {};
if (template.Armour)
{
ret.armour = {};
for (let damageType of damageTypes.GetTypes())
ret.armour[damageType] = getEntityValue("Armour/" + damageType);
}
if (template.Attack)
{
ret.attack = {};
for (let type in template.Attack)
{
let getAttackStat = function(stat) {
return getEntityValue("Attack/" + type + "/" + stat);
};
if (type == "Capture")
ret.attack.Capture = {
"value": getAttackStat("Value")
};
else
{
ret.attack[type] = {
"minRange": getAttackStat("MinRange"),
"maxRange": getAttackStat("MaxRange"),
"elevationBonus": getAttackStat("ElevationBonus")
};
for (let damageType of damageTypes.GetTypes())
ret.attack[type][damageType] = getAttackStat(damageType);
ret.attack[type].elevationAdaptedRange = Math.sqrt(ret.attack[type].maxRange *
(2 * ret.attack[type].elevationBonus + ret.attack[type].maxRange));
}
ret.attack[type].repeatTime = getAttackStat("RepeatTime");
if (template.Attack[type].Splash)
{
ret.attack[type].splash = {
// true if undefined
"friendlyFire": template.Attack[type].Splash.FriendlyFire != "false",
"shape": template.Attack[type].Splash.Shape
};
for (let damageType of damageTypes.GetTypes())
ret.attack[type].splash[damageType] = getAttackStat("Splash/" + damageType);
}
}
}
if (template.DeathDamage)
{
ret.deathDamage = {
"friendlyFire": template.DeathDamage.FriendlyFire != "false"
};
for (let damageType of damageTypes.GetTypes())
ret.deathDamage[damageType] = getEntityValue("DeathDamage/" + damageType);
}
if (template.Auras)
{
ret.auras = {};
for (let auraID of template.Auras._string.split(/\s+/))
{
let aura = auraTemplates[auraID];
ret.auras[auraID] = {
"name": aura.auraName,
"description": aura.auraDescription || null,
"radius": aura.radius || null
};
}
}
if (template.BuildingAI)
ret.buildingAI = {
"defaultArrowCount": Math.round(getEntityValue("BuildingAI/DefaultArrowCount")),
"garrisonArrowMultiplier": getEntityValue("BuildingAI/GarrisonArrowMultiplier"),
"maxArrowCount": Math.round(getEntityValue("BuildingAI/MaxArrowCount"))
};
if (template.BuildRestrictions)
{
// required properties
ret.buildRestrictions = {
"placementType": template.BuildRestrictions.PlacementType,
"territory": template.BuildRestrictions.Territory,
"category": template.BuildRestrictions.Category,
};
// optional properties
if (template.BuildRestrictions.Distance)
{
ret.buildRestrictions.distance = {
"fromClass": template.BuildRestrictions.Distance.FromClass,
};
if (template.BuildRestrictions.Distance.MinDistance)
ret.buildRestrictions.distance.min = getEntityValue("BuildRestrctions/Distance/MinDistance");
if (template.BuildRestrictions.Distance.MaxDistance)
ret.buildRestrictions.distance.max = getEntityValue("BuildRestrctions/Distance/MaxDistance");
}
}
if (template.TrainingRestrictions)
ret.trainingRestrictions = {
"category": template.TrainingRestrictions.Category,
};
if (template.Cost)
{
ret.cost = {};
for (let resCode in template.Cost.Resources)
ret.cost[resCode] = getEntityValue("Cost/Resources/" + resCode);
if (template.Cost.Population)
ret.cost.population = getEntityValue("Cost/Population");
if (template.Cost.PopulationBonus)
ret.cost.populationBonus = getEntityValue("Cost/PopulationBonus");
if (template.Cost.BuildTime)
ret.cost.time = getEntityValue("Cost/BuildTime");
}
if (template.Footprint)
{
ret.footprint = { "height": template.Footprint.Height };
if (template.Footprint.Square)
ret.footprint.square = {
"width": +template.Footprint.Square["@width"],
"depth": +template.Footprint.Square["@depth"]
};
else if (template.Footprint.Circle)
ret.footprint.circle = { "radius": +template.Footprint.Circle["@radius"] };
else
warn("GetTemplateDataHelper(): Unrecognized Footprint type");
}
if (template.GarrisonHolder)
{
ret.garrisonHolder = {
"buffHeal": getEntityValue("GarrisonHolder/BuffHeal")
};
if (template.GarrisonHolder.Max)
ret.garrisonHolder.capacity = getEntityValue("GarrisonHolder/Max");
}
if (template.Heal)
ret.heal = {
"hp": getEntityValue("Heal/HP"),
"range": getEntityValue("Heal/Range"),
"rate": getEntityValue("Heal/Rate")
};
if (template.ResourceGatherer)
{
ret.resourceGatherRates = {};
let baseSpeed = getEntityValue("ResourceGatherer/BaseSpeed");
for (let type in template.ResourceGatherer.Rates)
ret.resourceGatherRates[type] = getEntityValue("ResourceGatherer/Rates/"+ type) * baseSpeed;
}
if (template.ResourceTrickle)
{
ret.resourceTrickle = {
"interval": +template.ResourceTrickle.Interval,
"rates": {}
};
for (let type in template.ResourceTrickle.Rates)
ret.resourceTrickle.rates[type] = getEntityValue("ResourceTrickle/Rates/" + type);
}
if (template.Loot)
{
ret.loot = {};
for (let type in template.Loot)
ret.loot[type] = getEntityValue("Loot/"+ type);
}
if (template.Obstruction)
{
ret.obstruction = {
"active": ("" + template.Obstruction.Active == "true"),
"blockMovement": ("" + template.Obstruction.BlockMovement == "true"),
"blockPathfinding": ("" + template.Obstruction.BlockPathfinding == "true"),
"blockFoundation": ("" + template.Obstruction.BlockFoundation == "true"),
"blockConstruction": ("" + template.Obstruction.BlockConstruction == "true"),
"disableBlockMovement": ("" + template.Obstruction.DisableBlockMovement == "true"),
"disableBlockPathfinding": ("" + template.Obstruction.DisableBlockPathfinding == "true"),
"shape": {}
};
if (template.Obstruction.Static)
{
ret.obstruction.shape.type = "static";
ret.obstruction.shape.width = +template.Obstruction.Static["@width"];
ret.obstruction.shape.depth = +template.Obstruction.Static["@depth"];
}
else if (template.Obstruction.Unit)
{
ret.obstruction.shape.type = "unit";
ret.obstruction.shape.radius = +template.Obstruction.Unit["@radius"];
}
else
ret.obstruction.shape.type = "cluster";
}
if (template.Pack)
ret.pack = {
"state": template.Pack.State,
"time": getEntityValue("Pack/Time"),
};
if (template.Health)
ret.health = Math.round(getEntityValue("Health/Max"));
if (template.Identity)
{
ret.selectionGroupName = template.Identity.SelectionGroupName;
ret.name = {
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
"generic": template.Identity.GenericName
};
ret.icon = template.Identity.Icon;
ret.tooltip = template.Identity.Tooltip;
ret.requiredTechnology = template.Identity.RequiredTechnology;
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
}
if (template.UnitMotion)
{
ret.speed = {
"walk": getEntityValue("UnitMotion/WalkSpeed"),
};
if (template.UnitMotion.Run)
ret.speed.run = getEntityValue("UnitMotion/Run/Speed");
}
if (template.Upgrade)
{
ret.upgrades = [];
for (let upgradeName in template.Upgrade)
{
let upgrade = template.Upgrade[upgradeName];
let cost = {};
if (upgrade.Cost)
for (let res in upgrade.Cost)
cost[res] = getEntityValue("Upgrade/" + upgradeName + "/Cost/" + res, "Upgrade/Cost/" + res);
if (upgrade.Time)
cost.time = getEntityValue("Upgrade/" + upgradeName + "/Time", "Upgrade/Time");
ret.upgrades.push({
"entity": upgrade.Entity,
"tooltip": upgrade.Tooltip,
"cost": cost,
"icon": upgrade.Icon || undefined,
"requiredTechnology": upgrade.RequiredTechnology || undefined
});
}
}
if (template.ProductionQueue)
{
ret.techCostMultiplier = {};
for (let res in template.ProductionQueue.TechCostMultiplier)
ret.techCostMultiplier[res] = getEntityValue("ProductionQueue/TechCostMultiplier/" + res);
}
if (template.Trader)
ret.trader = {
"GainMultiplier": getEntityValue("Trader/GainMultiplier")
};
if (template.WallSet)
ret.wallSet = {
"templates": {
"tower": template.WallSet.Templates.Tower,
"gate": template.WallSet.Templates.Gate,
"long": template.WallSet.Templates.WallLong,
"medium": template.WallSet.Templates.WallMedium,
"short": template.WallSet.Templates.WallShort,
},
"maxTowerOverlap": +template.WallSet.MaxTowerOverlap,
"minTowerOverlap": +template.WallSet.MinTowerOverlap,
};
if (template.WallPiece)
ret.wallPiece = { "length": +template.WallPiece.Length };
return ret;
}
/**
* Get basic information about a technology template.
* @param {object} template - A valid template as obtained by loading the tech JSON file.
* @param {string} civ - Civilization for which the tech requirements should be calculated.
*/
function GetTechnologyBasicDataHelper(template, civ)
{
return {
"name": {
"generic": template.genericName
},
"icon": template.icon ? "technologies/" + template.icon : undefined,
"description": template.description,
"reqs": DeriveTechnologyRequirements(template, civ),
"modifications": template.modifications,
"affects": template.affects
};
}
/**
* Get information about a technology template.
* @param {object} template - A valid template as obtained by loading the tech JSON file.
* @param {string} civ - Civilization for which the specific name and tech requirements should be returned.
*/
function GetTechnologyDataHelper(template, civ, resources)
{
let ret = GetTechnologyBasicDataHelper(template, civ);
if (template.specificName)
ret.name.specific = template.specificName[civ] || template.specificName.generic;
ret.cost = { "time": template.researchTime ? +template.researchTime : 0 };
for (let type of resources.GetCodes())
ret.cost[type] = +(template.cost && template.cost[type] || 0);
ret.tooltip = template.tooltip;
ret.requirementsTooltip = template.requirementsTooltip || "";
return ret;
}
function calculateCarriedResources(carriedResources, tradingGoods)
{
var resources = {};
if (carriedResources)
for (let resource of carriedResources)
resources[resource.type] = (resources[resource.type] || 0) + resource.amount;
if (tradingGoods && tradingGoods.amount)
resources[tradingGoods.type] =
(resources[tradingGoods.type] || 0) +
(tradingGoods.amount.traderGain || 0) +
(tradingGoods.amount.market1Gain || 0) +
(tradingGoods.amount.market2Gain || 0);
return resources;
}
Index: ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/civinfo/civinfo.xml (revision 20528)
@@ -1,129 +1,128 @@
-
Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 20528)
@@ -1,245 +1,260 @@
/**
* Used for acoustic GUI notifications.
* Define the soundfile paths and specific time thresholds (avoid spam).
* And store the timestamp of last interaction for each notification.
*/
var g_SoundNotifications = {
"nick": { "soundfile": "audio/interface/ui/chat_alert.ogg", "threshold": 3000 }
};
// Get list of XML files in pathname with recursion, excepting those starting with _
function getXMLFileList(pathname)
{
var files = Engine.BuildDirEntList(pathname, "*.xml", true);
var result = [];
// Get only subpath from filename and discard extension
for (var i = 0; i < files.length; ++i)
{
var file = files[i];
file = file.substring(pathname.length, file.length - 4);
// Split path into directories so we can check for beginning _ character
var tokens = file.split("/");
if (tokens[tokens.length - 1][0] != "_")
result.push(file);
}
return result;
}
function getJSONFileList(pathname)
{
// Remove the path and extension from each name, since we just want the filename
return Engine.BuildDirEntList(pathname, "*.json", false).map(
filename => filename.substring(pathname.length, filename.length - 5));
}
+/**
+ * Returns translated history and gameplay data of all civs, optionally including a mock gaia civ.
+ */
+function loadCivData(selectableOnly, gaia)
+{
+ let civData = loadCivFiles(selectableOnly);
+
+ translateObjectKeys(civData, ["Name", "Description", "History", "Special"]);
+
+ if (gaia)
+ civData.gaia = { "Code": "gaia", "Name": translate("Gaia") };
+
+ return deepfreeze(civData);
+}
+
// A sorting function for arrays of objects with 'name' properties, ignoring case
function sortNameIgnoreCase(x, y)
{
let lowerX = x.name.toLowerCase();
let lowerY = y.name.toLowerCase();
if (lowerX < lowerY)
return -1;
if (lowerX > lowerY)
return 1;
return 0;
}
/**
* Escape tag start and escape characters, so users cannot use special formatting.
* Also limit string length to 256 characters (not counting escape characters).
*/
function escapeText(text, limitLength = true)
{
if (!text)
return text;
if (limitLength)
text = text.substr(0, 255);
return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\[");
}
function unescapeText(text)
{
if (!text)
return text;
return text.replace(/\\\\/g, "\\").replace(/\\\[/g, "\[");
}
/**
* Merge players by team to remove duplicate Team entries, thus reducing the packet size of the lobby report.
*/
function playerDataToStringifiedTeamList(playerData)
{
let teamList = {};
for (let pData of playerData)
{
let team = pData.Team === undefined ? -1 : pData.Team;
if (!teamList[team])
teamList[team] = [];
teamList[team].push(pData);
delete teamList[team].Team;
}
return escapeText(JSON.stringify(teamList), false);
}
function stringifiedTeamListToPlayerData(stringifiedTeamList)
{
let teamList = JSON.parse(unescapeText(stringifiedTeamList));
let playerData = [];
for (let team in teamList)
for (let pData of teamList[team])
{
pData.Team = team;
playerData.push(pData);
}
return playerData;
}
function translateMapTitle(mapTitle)
{
return mapTitle == "random" ? translateWithContext("map selection", "Random") : translate(mapTitle);
}
/**
* Convert time in milliseconds to [hh:]mm:ss string representation.
* @param time Time period in milliseconds (integer)
* @return String representing time period
*/
function timeToString(time)
{
return Engine.FormatMillisecondsIntoDateStringGMT(time, time < 1000 * 60 * 60 ?
translate("mm:ss") : translate("HH:mm:ss"));
}
function removeDupes(array)
{
// loop backwards to make splice operations cheaper
let i = array.length;
while (i--)
if (array.indexOf(array[i]) != i)
array.splice(i, 1);
}
function singleplayerName()
{
return Engine.ConfigDB_GetValue("user", "playername.singleplayer") || Engine.GetSystemUsername();
}
function multiplayerName()
{
return Engine.ConfigDB_GetValue("user", "playername.multiplayer") || Engine.GetSystemUsername();
}
function tryAutoComplete(text, autoCompleteList)
{
if (!text.length)
return text;
var wordSplit = text.split(/\s/g);
if (!wordSplit.length)
return text;
var lastWord = wordSplit.pop();
if (!lastWord.length)
return text;
for (var word of autoCompleteList)
{
if (word.toLowerCase().indexOf(lastWord.toLowerCase()) != 0)
continue;
text = wordSplit.join(" ");
if (text.length > 0)
text += " ";
text += word;
break;
}
return text;
}
function autoCompleteNick(guiObject, playernames)
{
let text = guiObject.caption;
if (!text.length)
return;
let bufferPosition = guiObject.buffer_position;
let textTillBufferPosition = text.substring(0, bufferPosition);
let newText = tryAutoComplete(textTillBufferPosition, playernames);
guiObject.caption = newText + text.substring(bufferPosition);
guiObject.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length);
}
function clearChatMessages()
{
g_ChatMessages.length = 0;
Engine.GetGUIObjectByName("chatText").caption = "";
try {
for (let timer of g_ChatTimers)
clearTimeout(timer);
g_ChatTimers.length = 0;
} catch (e) {
}
}
/**
* Manage acoustic GUI notifications.
*
* @param {string} type - Notification type.
*/
function soundNotification(type)
{
if (Engine.ConfigDB_GetValue("user", "sound.notify." + type) != "true")
return;
let notificationType = g_SoundNotifications[type];
let timeNow = Date.now();
if (!notificationType.lastInteractionTime || timeNow > notificationType.lastInteractionTime + notificationType.threshold)
Engine.PlayUISound(notificationType.soundfile, false);
notificationType.lastInteractionTime = timeNow;
}
/**
* Horizontally spaces objects within a parent
*
* @param margin The gap, in px, between the objects
*/
function horizontallySpaceObjects(parentName, margin = 0)
{
let objects = Engine.GetGUIObjectByName(parentName).children;
for (let i = 0; i < objects.length; ++i)
{
let size = objects[i].size;
let width = size.right - size.left;
size.left = i * (width + margin) + margin;
size.right = (i + 1) * (width + margin);
objects[i].size = size;
}
}
/**
* Hide all children after a certain index
*/
function hideRemaining(parentName, start = 0)
{
let objects = Engine.GetGUIObjectByName(parentName).children;
for (let i = start; i < objects.length; ++i)
objects[i].hidden = true;
}
Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.xml (revision 20528)
@@ -1,331 +1,330 @@
-
Match SetupLoadingLoading map data. Please wait...
onTick();
Player NameColorPlayer PlacementCivilizationView civilization infoReset any civilizations that have been selected to the default (random)resetCivilizations();TeamReset all teams to the default.resetTeams();Pick a color.Select player.Configure AI settings.Select player's civilization.Select player's team.Show this message in the futuresaveSPTipsSetting();submitChatInput();autoCompleteNick(this, g_Autocomplete);SendsubmitChatInput();Cheats enabled.
if (g_IsController)
launchGame();
else
toggleReady();
BackcancelSetup();More OptionsSee more game optionsshowMoreOptions(true);More OptionsOKClose more game options windowshowMoreOptions(false);
Index: ps/trunk/binaries/data/mods/public/gui/reference/structree/structree.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/reference/structree/structree.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/reference/structree/structree.xml (revision 20528)
@@ -1,127 +1,126 @@
-
Structure TreeCivilization:selectCiv(this.list_data[this.selected]);Trainer UnitsCloseclosePage();
Index: ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/replaymenu/replay_menu.xml (revision 20528)
@@ -1,270 +1,269 @@
-
Replay GamesdisplayReplayList();displayReplayList();
autoCompleteNick(this, g_Playernames);
displayReplayList();displayReplayList();displayReplayList();displayReplayList();displayReplayDetails();displayReplayList();startReplay();Date / TimePlayersMap NameSizePopulationDurationdisplayReplayList();Filter compatible replaysdisplayReplayList();displayReplayList();displayReplayList();Map Type:Map Size:Victory:displayReplayDetails();SpoilerMain MenuEngine.SwitchGuiPage("page_pregame.xml");DeletedeleteReplayButtonPressed();Reload CacheRebuild the replay cache from scratch. Potentially slow!reloadCache();SummaryshowReplaySummary();Start ReplaystartReplay();
Index: ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/savedgames/load.xml (revision 20528)
@@ -1,122 +1,121 @@
-
Load GameselectionChanged();init();loadGame();Date / TimeMap TypeMap NameDescriptionNo saved games found.CancelEngine.PopGuiPage();DeletedeleteGame();LoadloadGame();init();Filter compatible saved gamesPlayers:Played time:Map Type:Map Size:Victory:
Index: ps/trunk/binaries/data/mods/public/gui/session/session.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/session/session.xml (revision 20528)
@@ -1,169 +1,168 @@
-
onTick();
onWindowResized();
onSimulationUpdate();
onReplayFinished();
onReplayOutOfSync();
Engine.ConfigDB_CreateValue("user", "gui.session.timeelapsedcounter", String(Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") != "true"));
Engine.ConfigDB_CreateValue("user", "gui.session.ceasefirecounter", String(Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") != "true"));
ExitleaveGame();Game PausedClick to Resume GametogglePause();
Index: ps/trunk/binaries/data/mods/public/gui/summary/summary.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (revision 20527)
+++ ps/trunk/binaries/data/mods/public/gui/summary/summary.xml (revision 20528)
@@ -1,211 +1,210 @@
-
SummaryselectPanel(this);ScoreselectPanel(this);BuildingsselectPanel(this);UnitsselectPanel(this);ResourcesselectPanel(this);MarketselectPanel(this);MiscellaneousselectPanel(this);ChartsPlayer nameCategory:CategoryValue:ValueType:TypeReplayconfirmStartReplay();ContinuecontinueButton();
Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20527)
+++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 20528)
@@ -1,523 +1,525 @@
const PI = Math.PI;
const TWO_PI = 2 * Math.PI;
const TERRAIN_SEPARATOR = "|";
const SEA_LEVEL = 20.0;
const HEIGHT_UNITS_PER_METRE = 92;
const MAP_BORDER_WIDTH = 3;
/**
* Constants needed for heightmap_manipulation.js
*/
const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters
const MIN_HEIGHT = - SEA_LEVEL;
/**
* Length of one tile of the terrain grid in metres.
* Useful to transform footprint sizes of templates to the coordinate system used by getMapSize.
*/
const TERRAIN_TILE_SIZE = Engine.GetTerrainTileSize();
const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL;
// Default angle for buildings
const BUILDING_ORIENTATION = - PI / 4;
+const g_CivData = deepfreeze(loadCivFiles(false));
+
function fractionToTiles(f)
{
return g_Map.size * f;
}
function tilesToFraction(t)
{
return t / g_Map.size;
}
function fractionToSize(f)
{
return getMapArea() * f;
}
function sizeToFraction(s)
{
return s / getMapArea();
}
function scaleByMapSize(min, max, minMapSize = 128, maxMapSize = 512)
{
return min + (max - min) * (g_Map.size - minMapSize) / (maxMapSize - minMapSize);
}
function cos(x)
{
return Math.cos(x);
}
function sin(x)
{
return Math.sin(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(a, b)
{
return a > b ? a : b;
}
function min(a, b)
{
return a < b ? a : b;
}
/**
* Retries the given function with those arguments as often as specified.
*/
function retryPlacing(placeFunc, retryFactor, amount, getResult, behaveDeprecated = false)
{
let maxFail = amount * retryFactor;
let results = [];
let good = 0;
let bad = 0;
while (good < amount && bad <= maxFail)
{
let result = placeFunc();
if (result !== undefined || behaveDeprecated)
{
++good;
if (getResult)
results.push(result);
}
else
++bad;
}
return getResult ? results : good;
}
/**
* Sets the x and z property of the given object (typically a Placer or Group) to a random point on the map.
* @param passableOnly - Should be true for entity placement and false for terrain or elevation operations.
*/
function randomizeCoordinates(obj, passableOnly)
{
let border = passableOnly ? MAP_BORDER_WIDTH : 0;
if (g_MapSettings.CircularMap)
{
// Polar coordinates
// Uniformly distributed on the disk
let halfMapSize = g_Map.size / 2 - border;
let r = halfMapSize * Math.sqrt(randFloat(0, 1));
let theta = randFloat(0, 2 * Math.PI);
obj.x = Math.floor(r * Math.cos(theta)) + halfMapSize;
obj.z = Math.floor(r * Math.sin(theta)) + halfMapSize;
}
else
{
// Rectangular coordinates
obj.x = randIntExclusive(border, g_Map.size - border);
obj.z = randIntExclusive(border, g_Map.size - border);
}
}
/**
* Sets the x and z property of the given JS object (typically a Placer or Group) to a random point of the area.
*/
function randomizeCoordinatesFromAreas(obj, areas)
{
let pt = pickRandom(pickRandom(areas).points);
obj.x = pt.x;
obj.z = pt.z;
}
// TODO this is a hack to simulate the old behaviour of those functions
// until all old maps are changed to use the correct version of these functions
function createObjectGroupsDeprecated(group, player, constraint, amount, retryFactor = 10)
{
return createObjectGroups(group, player, constraint, amount, retryFactor, true);
}
function createObjectGroupsByAreasDeprecated(group, player, constraint, amount, retryFactor, areas)
{
return createObjectGroupsByAreas(group, player, constraint, amount, retryFactor, areas, true);
}
/**
* Attempts to place the given number of areas in random places of the map.
* Returns actually placed areas.
*/
function createAreas(centeredPlacer, painter, constraint, amount, retryFactor = 10)
{
let placeFunc = function() {
randomizeCoordinates(centeredPlacer, false);
return createArea(centeredPlacer, painter, constraint);
};
return retryPlacing(placeFunc, retryFactor, amount, true, false);
}
/**
* Attempts to place the given number of areas in random places of the given areas.
* Returns actually placed areas.
*/
function createAreasInAreas(centeredPlacer, painter, constraint, amount, retryFactor, areas)
{
let placeFunc = function() {
randomizeCoordinatesFromAreas(centeredPlacer, areas);
return createArea(centeredPlacer, painter, constraint);
};
return retryPlacing(placeFunc, retryFactor, amount, true, false);
}
/**
* Attempts to place the given number of groups in random places of the map.
* Returns the number of actually placed groups.
*/
function createObjectGroups(group, player, constraint, amount, retryFactor = 10, behaveDeprecated = false)
{
let placeFunc = function() {
randomizeCoordinates(group, true);
return createObjectGroup(group, player, constraint);
};
return retryPlacing(placeFunc, retryFactor, amount, false, behaveDeprecated);
}
/**
* Attempts to place the given number of groups in random places of the given areas.
* Returns the number of actually placed groups.
*/
function createObjectGroupsByAreas(group, player, constraint, amount, retryFactor, areas, behaveDeprecated = false)
{
let placeFunc = function() {
randomizeCoordinatesFromAreas(group, areas);
return createObjectGroup(group, player, constraint);
};
return retryPlacing(placeFunc, retryFactor, amount, false, behaveDeprecated);
}
function createTerrain(terrain)
{
if (!(terrain instanceof Array))
return createSimpleTerrain(terrain);
return new RandomTerrain(terrain.map(t => createTerrain(t)));
}
function createSimpleTerrain(terrain)
{
if (typeof(terrain) != "string")
throw new Error("createSimpleTerrain expects string as input, received " + uneval(terrain));
// Split string by pipe | character, this allows specifying terrain + tree type in single string
let params = terrain.split(TERRAIN_SEPARATOR, 2);
if (params.length != 2)
return new SimpleTerrain(terrain);
return new SimpleTerrain(params[0], params[1]);
}
function placeObject(x, z, type, player, angle)
{
if (g_Map.validT(x, z))
g_Map.addObject(new Entity(type, player, x, z, angle));
}
function placeTerrain(x, z, terrainNames)
{
createTerrain(terrainNames).place(x, z);
}
function initTerrain(terrainNames)
{
let terrain = createTerrain(terrainNames);
for (let x = 0; x < getMapSize(); ++x)
for (let z = 0; z < getMapSize(); ++z)
terrain.place(x, z);
}
function isCircularMap()
{
return !!g_MapSettings.CircularMap;
}
function getMapBaseHeight()
{
return g_MapSettings.BaseHeight;
}
function createTileClass()
{
return g_Map.createTileClass();
}
function getTileClass(id)
{
if (!g_Map.validClass(id))
return undefined;
return g_Map.tileClasses[id];
}
/**
* Constructs a new Area shaped by the Placer meeting the Constraint and calls the Painters there.
* Supports both Centered and Non-Centered Placers.
*/
function createArea(placer, painter, constraint)
{
if (!constraint)
constraint = new NullConstraint();
else if (constraint instanceof Array)
constraint = new AndConstraint(constraint);
let points = placer.place(constraint);
if (!points)
return undefined;
let area = g_Map.createArea(points);
if (painter instanceof Array)
painter = new MultiPainter(painter);
painter.paint(area);
return area;
}
/**
* @param mode is one of the HeightPlacer constants determining whether to exclude the min/max elevation.
*/
function paintTerrainBasedOnHeight(minHeight, maxHeight, mode, terrain)
{
createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TerrainPainter(terrain));
}
function paintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
{
createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TileClassPainter(getTileClass(tileClass)));
}
function unPaintTileClassBasedOnHeight(minHeight, maxHeight, mode, tileClass)
{
createArea(
new HeightPlacer(mode, minHeight, maxHeight),
new TileClassUnPainter(getTileClass(tileClass)));
}
/**
* Places the Entities of the given Group if they meet the Constraint
* and sets the given player as the owner.
*/
function createObjectGroup(group, player, constraint)
{
if (!constraint)
constraint = new NullConstraint();
else if (constraint instanceof Array)
constraint = new AndConstraint(constraint);
return group.place(player, constraint);
}
function getMapSize()
{
return g_Map.size;
}
function getMapArea()
{
return Math.square(g_Map.size);
}
function getMapCenter()
{
return deepfreeze(new Vector2D(g_Map.size / 2, g_Map.size / 2));
}
function getNumPlayers()
{
return g_MapSettings.PlayerData.length - 1;
}
function getCivCode(playerID)
{
return g_MapSettings.PlayerData[playerID].Civ;
}
function areAllies(playerID1, playerID2)
{
return (
g_MapSettings.PlayerData[playerID1].Team !== undefined &&
g_MapSettings.PlayerData[playerID2].Team !== undefined &&
g_MapSettings.PlayerData[playerID1].Team != -1 &&
g_MapSettings.PlayerData[playerID2].Team != -1 &&
g_MapSettings.PlayerData[playerID1].Team === g_MapSettings.PlayerData[playerID2].Team);
}
function getPlayerTeam(playerID)
{
if (g_MapSettings.PlayerData[playerID].Team === undefined)
return -1;
return g_MapSettings.PlayerData[playerID].Team;
}
function getHeight(x, z)
{
return g_Map.getHeight(x, z);
}
function setHeight(x, z, height)
{
g_Map.setHeight(x, z, height);
}
function initHeight(height)
{
g_Map.initHeight(height);
}
/**
* Utility functions for classes
*/
/**
* Add point to given class by id
*/
function addToClass(x, z, id)
{
let tileClass = getTileClass(id);
if (tileClass !== null)
tileClass.add(x, z);
}
/**
* Remove point from the given class by id
*/
function removeFromClass(x, z, id)
{
let tileClass = getTileClass(id);
if (tileClass !== null)
tileClass.remove(x, z);
}
/**
* Create a painter for the given class
*/
function paintClass(id)
{
return new TileClassPainter(getTileClass(id));
}
/**
* Create a painter for the given class
*/
function unPaintClass(id)
{
return new TileClassUnPainter(getTileClass(id));
}
/**
* Create an avoid constraint for the given classes by the given distances
*/
function avoidClasses(/*class1, dist1, class2, dist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/2; ++i)
ar.push(new AvoidTileClassConstraint(arguments[2*i], arguments[2*i+1]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Create a stay constraint for the given classes by the given distances
*/
function stayClasses(/*class1, dist1, class2, dist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/2; ++i)
ar.push(new StayInTileClassConstraint(arguments[2*i], arguments[2*i+1]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Create a border constraint for the given classes by the given distances
*/
function borderClasses(/*class1, idist1, odist1, class2, idist2, odist2, etc*/)
{
let ar = [];
for (let i = 0; i < arguments.length/3; ++i)
ar.push(new BorderTileClassConstraint(arguments[3*i], arguments[3*i+1], arguments[3*i+2]));
// Return single constraint
if (ar.length == 1)
return ar[0];
return new AndConstraint(ar);
}
/**
* Checks if the given tile is in class "id"
*/
function checkIfInClass(x, z, id)
{
let tileClass = getTileClass(id);
if (tileClass === null)
return 0;
let members = tileClass.countMembersInRadius(x, z, 1);
if (members === null)
return 0;
return members;
}
function getTerrainTexture(x, y)
{
return g_Map.getTexture(x, y);
}
Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/mapgen.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/rmgen/mapgen.js (revision 20527)
+++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/mapgen.js (revision 20528)
@@ -1,52 +1,39 @@
var TILE_CENTERED_HEIGHT_MAP = false;
var WATER_LEVEL_CHANGED = false;
var g_Map;
/**
* Camera location in case there are no player entities.
*/
var g_Camera = {
"Position": { "x": 256, "y": 150, "z": 256 },
"Rotation": 0,
"Declination": 0.523599
};
-var g_CivData = {};
-
function InitMap()
{
- // Get civ data as array of JSON strings
- var data = Engine.GetCivData();
- if (!data || !data.length)
- throw new Error("InitMapGen: error reading civ data");
-
- for (var i = 0; i < data.length; ++i)
- {
- var civData = JSON.parse(data[i]);
- g_CivData[civData.Code] = civData;
- }
-
log("Creating new map...");
g_Map = new Map(g_MapSettings.Size, g_MapSettings.BaseHeight);
initTerrain(g_MapSettings.BaseTerrain);
}
function ExportMap()
{
log("Saving map...");
if (!WATER_LEVEL_CHANGED)
g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1;
Engine.ExportMap({
"entities": g_Map.exportEntityList(),
"height": g_Map.exportHeightData(),
"seaLevel": SEA_LEVEL,
"size": g_Map.size,
"textureNames": g_Map.IDToName,
"tileData": g_Map.exportTerrainTextures(),
"Camera": g_Camera,
"Environment": g_Environment
});
}
Index: ps/trunk/source/graphics/MapGenerator.cpp
===================================================================
--- ps/trunk/source/graphics/MapGenerator.cpp (revision 20527)
+++ ps/trunk/source/graphics/MapGenerator.cpp (revision 20528)
@@ -1,330 +1,296 @@
/* Copyright (C) 2017 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 "graphics/Terrain.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Profile.h"
#include "ps/scripting/JSInterface_VFS.h"
// TODO: what's a good default? perhaps based on map size
#define RMS_RUNTIME_SIZE 96 * 1024 * 1024
CMapGeneratorWorker::CMapGeneratorWorker()
{
// If something happens before we initialize, that's a failure
m_Progress = -1;
}
CMapGeneratorWorker::~CMapGeneratorWorker()
{
// Wait for thread to end
pthread_join(m_WorkerThread, NULL);
}
void CMapGeneratorWorker::Initialize(const VfsPath& scriptFile, const std::string& settings)
{
CScopeLock lock(m_WorkerMutex);
// Set progress to positive value
m_Progress = 1;
m_ScriptPath = scriptFile;
m_Settings = settings;
// Launch the worker thread
int ret = pthread_create(&m_WorkerThread, NULL, &RunThread, this);
ENSURE(ret == 0);
}
void* CMapGeneratorWorker::RunThread(void *data)
{
debug_SetThreadName("MapGenerator");
g_Profiler2.RegisterCurrentThread("MapGenerator");
CMapGeneratorWorker* self = static_cast(data);
self->m_ScriptInterface = new ScriptInterface("Engine", "MapGenerator", ScriptInterface::CreateRuntime(g_ScriptRuntime, RMS_RUNTIME_SIZE));
// Run map generation scripts
if (!self->Run() || self->m_Progress > 0)
{
// Don't leave progress in an unknown state, if generator failed, set it to -1
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = -1;
}
// At this point the random map scripts are done running, so the thread has no further purpose
// and can die. The data will be stored in m_MapData already if successful, or m_Progress
// will contain an error value on failure.
return NULL;
}
bool CMapGeneratorWorker::Run()
{
// We must destroy the ScriptInterface in the same thread because the JSAPI requires that!
// Also we must not be in a request when calling the ScriptInterface destructor, so the autoFree object
// must be instantiated before the request (destructors are called in reverse order of instantiation)
struct AutoFree {
AutoFree(ScriptInterface* p) : m_p(p) {}
~AutoFree() { SAFE_DELETE(m_p); }
ScriptInterface* m_p;
} autoFree(m_ScriptInterface);
JSContext* cx = m_ScriptInterface->GetContext();
JSAutoRequest rq(cx);
m_ScriptInterface->SetCallbackData(static_cast (this));
// Replace RNG with a seeded deterministic function
m_ScriptInterface->ReplaceNondeterministicRNG(m_MapGenRNG);
m_ScriptInterface->LoadGlobalScripts();
// Functions for RMS
JSI_VFS::RegisterReadOnlyScriptFunctions(*m_ScriptInterface);
m_ScriptInterface->RegisterFunction("LoadLibrary");
m_ScriptInterface->RegisterFunction("ExportMap");
m_ScriptInterface->RegisterFunction("SetProgress");
m_ScriptInterface->RegisterFunction("MaybeGC");
- m_ScriptInterface->RegisterFunction, CMapGeneratorWorker::GetCivData>("GetCivData");
m_ScriptInterface->RegisterFunction("GetTemplate");
m_ScriptInterface->RegisterFunction("TemplateExists");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindTemplates>("FindTemplates");
m_ScriptInterface->RegisterFunction, std::string, bool, CMapGeneratorWorker::FindActorTemplates>("FindActorTemplates");
m_ScriptInterface->RegisterFunction("GetTerrainTileSize");
// Parse settings
JS::RootedValue settingsVal(cx);
if (!m_ScriptInterface->ParseJSON(m_Settings, &settingsVal) && settingsVal.isUndefined())
{
LOGERROR("CMapGeneratorWorker::Run: Failed to parse settings");
return false;
}
// Prevent unintentional modifications to the settings object by random map scripts
if (!m_ScriptInterface->FreezeObject(settingsVal, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to deepfreeze settings");
return false;
}
// Init RNG seed
u32 seed = 0;
if (!m_ScriptInterface->HasProperty(settingsVal, "Seed") ||
!m_ScriptInterface->GetProperty(settingsVal, "Seed", seed))
LOGWARNING("CMapGeneratorWorker::Run: No seed value specified - using 0");
m_MapGenRNG.seed(seed);
// Copy settings to global variable
JS::RootedValue global(cx, m_ScriptInterface->GetGlobalObject());
if (!m_ScriptInterface->SetProperty(global, "g_MapSettings", settingsVal, true, true))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to define g_MapSettings");
return false;
}
// Load RMS
LOGMESSAGE("Loading RMS '%s'", m_ScriptPath.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(m_ScriptPath))
{
LOGERROR("CMapGeneratorWorker::Run: Failed to load RMS '%s'", m_ScriptPath.string8());
return false;
}
return true;
}
int CMapGeneratorWorker::GetProgress()
{
CScopeLock lock(m_WorkerMutex);
return m_Progress;
}
shared_ptr CMapGeneratorWorker::GetResults()
{
CScopeLock lock(m_WorkerMutex);
return m_MapData;
}
bool CMapGeneratorWorker::LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->LoadScripts(name);
}
void CMapGeneratorWorker::ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
// Copy results
CScopeLock lock(self->m_WorkerMutex);
self->m_MapData = self->m_ScriptInterface->WriteStructuredClone(data);
self->m_Progress = 0;
}
void CMapGeneratorWorker::SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
// Copy data
CScopeLock lock(self->m_WorkerMutex);
self->m_Progress = progress;
}
void CMapGeneratorWorker::MaybeGC(ScriptInterface::CxPrivate* pCxPrivate)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
self->m_ScriptInterface->MaybeGC();
}
-std::vector CMapGeneratorWorker::GetCivData(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
-{
- VfsPath path(L"simulation/data/civs/");
- VfsPaths pathnames;
-
- std::vector data;
-
- // Load all JSON files in civs directory
- Status ret = vfs::GetPathnames(g_VFS, path, L"*.json", pathnames);
- if (ret == INFO::OK)
- {
- for (const VfsPath& p : pathnames)
- {
- // Load JSON file
- CVFSFile file;
- PSRETURN ret = file.Load(g_VFS, p);
- if (ret != PSRETURN_OK)
- LOGERROR("CMapGeneratorWorker::GetCivData: Failed to load file '%s': %s", p.string8(), GetErrorString(ret));
- else
- data.push_back(file.DecodeUTF8()); // assume it's UTF-8
- }
- }
- else
- {
- // Some error reading directory
- wchar_t error[200];
- LOGERROR("CMapGeneratorWorker::GetCivData: Error reading directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
- }
-
- return data;
-
-}
-
CParamNode CMapGeneratorWorker::GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
const CParamNode& templateRoot = self->m_TemplateLoader.GetTemplateFileData(templateName).GetChild("Entity");
if (!templateRoot.IsOk())
LOGERROR("Invalid template found for '%s'", templateName.c_str());
return templateRoot;
}
bool CMapGeneratorWorker::TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.TemplateExists(templateName);
}
std::vector CMapGeneratorWorker::FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, SIMULATION_TEMPLATES);
}
std::vector CMapGeneratorWorker::FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories)
{
CMapGeneratorWorker* self = static_cast(pCxPrivate->pCBData);
return self->m_TemplateLoader.FindTemplates(path, includeSubdirectories, ACTOR_TEMPLATES);
}
int CMapGeneratorWorker::GetTerrainTileSize(ScriptInterface::CxPrivate* UNUSED(pCxPrivate))
{
return TERRAIN_TILE_SIZE;
}
bool CMapGeneratorWorker::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
Status ret = vfs::GetPathnames(g_VFS, path, L"*.js", pathnames);
if (ret == INFO::OK)
{
for (const VfsPath& p : pathnames)
{
LOGMESSAGE("Loading map generator script '%s'", p.string8());
if (!m_ScriptInterface->LoadGlobalScriptFile(p))
{
LOGERROR("CMapGeneratorWorker::LoadScripts: Failed to load script '%s'", p.string8());
return false;
}
}
}
else
{
// Some error reading directory
wchar_t error[200];
LOGERROR("CMapGeneratorWorker::LoadScripts: Error reading scripts in directory '%s': %s", path.string8(), utf8_from_wstring(StatusDescription(ret, error, ARRAY_SIZE(error))));
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////
CMapGenerator::CMapGenerator() : m_Worker(new CMapGeneratorWorker())
{
}
CMapGenerator::~CMapGenerator()
{
delete m_Worker;
}
void CMapGenerator::GenerateMap(const VfsPath& scriptFile, const std::string& settings)
{
m_Worker->Initialize(scriptFile, settings);
}
int CMapGenerator::GetProgress()
{
return m_Worker->GetProgress();
}
shared_ptr CMapGenerator::GetResults()
{
return m_Worker->GetResults();
}
Index: ps/trunk/source/graphics/MapGenerator.h
===================================================================
--- ps/trunk/source/graphics/MapGenerator.h (revision 20527)
+++ ps/trunk/source/graphics/MapGenerator.h (revision 20528)
@@ -1,155 +1,152 @@
/* Copyright (C) 2017 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/FileIo.h"
#include "ps/ThreadUtil.h"
#include "ps/TemplateLoader.h"
#include "scriptinterface/ScriptInterface.h"
#include
#include
class CMapGeneratorWorker;
/**
* Random map generator interface. Initialized by CMapReader and then checked
* periodically during loading, until it's finished (progress value is 0).
*
* The actual work is performed by CMapGeneratorWorker in a separate thread.
*/
class CMapGenerator
{
NONCOPYABLE(CMapGenerator);
public:
CMapGenerator();
~CMapGenerator();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void GenerateMap(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
CMapGeneratorWorker* m_Worker;
};
/**
* Random map generator worker thread.
* (This is run in a thread so that the GUI remains responsive while loading)
*
* Thread-safety:
* - Initialize and constructor/destructor must be called from the main thread.
* - ScriptInterface created and destroyed by thread
* - StructuredClone used to return JS map data - JS:Values can't be used across threads/runtimes.
*/
class CMapGeneratorWorker
{
public:
CMapGeneratorWorker();
~CMapGeneratorWorker();
/**
* Start the map generator thread
*
* @param scriptFile The VFS path for the script, e.g. "maps/random/latium.js"
* @param settings JSON string containing settings for the map generator
*/
void Initialize(const VfsPath& scriptFile, const std::string& settings);
/**
* Get status of the map generator thread
*
* @return Progress percentage 1-100 if active, 0 when finished, or -1 on error
*/
int GetProgress();
/**
* Get random map data, according to this format:
* http://trac.wildfiregames.com/wiki/Random_Map_Generator_Internals#Dataformat
*
* @return StructuredClone containing map data
*/
shared_ptr GetResults();
private:
// Mapgen
/**
* Load all scripts of the given library
*
* @param libraryName String specifying name of the library (subfolder of ../maps/random/)
* @return true if all scripts ran successfully, false if there's an error
*/
bool LoadScripts(const std::wstring& libraryName);
// callbacks for script functions
static bool LoadLibrary(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name);
static void ExportMap(ScriptInterface::CxPrivate* pCxPrivate, JS::HandleValue data);
static void SetProgress(ScriptInterface::CxPrivate* pCxPrivate, int progress);
static void MaybeGC(ScriptInterface::CxPrivate* pCxPrivate);
- static bool FileExists(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath);
- static JS::Value ReadJSONFile(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& filePath);
- static std::vector GetCivData(ScriptInterface::CxPrivate* pCxPrivate);
static CParamNode GetTemplate(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName);
static bool TemplateExists(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName);
static std::vector FindTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories);
static std::vector FindActorTemplates(ScriptInterface::CxPrivate* pCxPrivate, const std::string& path, bool includeSubdirectories);
static int GetTerrainTileSize(ScriptInterface::CxPrivate* pCxPrivate);
std::set m_LoadedLibraries;
shared_ptr m_MapData;
boost::rand48 m_MapGenRNG;
int m_Progress;
ScriptInterface* m_ScriptInterface;
VfsPath m_ScriptPath;
std::string m_Settings;
CTemplateLoader m_TemplateLoader;
// Thread
static void* RunThread(void* data);
bool Run();
pthread_t m_WorkerThread;
CMutex m_WorkerMutex;
};
#endif //INCLUDED_MAPGENERATOR