Index: ps/trunk/binaries/data/mods/public/globalscripts/utility.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/utility.js (revision 18543)
+++ ps/trunk/binaries/data/mods/public/globalscripts/utility.js (revision 18544)
@@ -1,18 +1,36 @@
/**
* returns a clone of a simple object or array
* Only valid JSON objects are accepted
* So no recursion, and only plain objects or arrays
*/
function clone(o)
{
let r;
if (o instanceof Array)
r = [];
else if (o instanceof Object)
r = {};
else // native data type
return o;
for (let key in o)
r[key] = clone(o[key]);
return r;
}
+
+/**
+ * "Inside-out" implementation of Fisher-Yates shuffle
+ */
+function shuffleArray(source)
+{
+ if (!source.length)
+ return [];
+
+ let result = [source[0]];
+ for (let i = 1; i < source.length; ++i)
+ {
+ let j = Math.floor(Math.random() * i);
+ result[i] = result[j];
+ result[j] = source[i];
+ }
+ return result;
+}
Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 18543)
+++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/library.js (revision 18544)
@@ -1,558 +1,540 @@
const PI = Math.PI;
const TWO_PI = 2 * Math.PI;
const TERRAIN_SEPARATOR = "|";
const SEA_LEVEL = 20.0;
const CELL_SIZE = 4;
const HEIGHT_UNITS_PER_METRE = 92;
const MIN_MAP_SIZE = 128;
const MAX_MAP_SIZE = 512;
const MAP_BORDER_WIDTH = 3;
const FALLBACK_CIV = "athen";
/**
* Constants needed for heightmap_manipulation.js
*/
-const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE // Engine limit, Roughly 700 meters
+const MAX_HEIGHT_RANGE = 0xFFFF / HEIGHT_UNITS_PER_METRE; // Engine limit, Roughly 700 meters
const MIN_HEIGHT = - SEA_LEVEL;
const MAX_HEIGHT = MAX_HEIGHT_RANGE - SEA_LEVEL;
// Default angle for buildings
const BUILDING_ORIENTATION = - PI / 4;
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)
{
return min + (max - min) * (g_Map.size - MIN_MAP_SIZE) / (MAX_MAP_SIZE - MIN_MAP_SIZE);
}
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;
}
/**
- * "Inside-out" implementation of Fisher-Yates shuffle
- */
-function shuffleArray(source)
-{
- if (!source.length)
- return [];
-
- let result = [source[0]];
- for (let i = 1; i < source.length; ++i)
- {
- let j = randInt(0, i);
- result[i] = result[j];
- result[j] = source[i];
- }
- return result;
-}
-
-/**
* Retries the given function with those arguments as often as specified.
*/
function retryPlacing(placeFunc, placeArgs, retryFactor, amount, getResult)
{
let maxFail = amount * retryFactor;
let results = [];
let good = 0;
let bad = 0;
while (good < amount && bad <= maxFail)
{
let result = placeFunc(placeArgs);
if (result !== undefined)
{
++good;
if (getResult)
results.push(result);
}
else
++bad;
}
return getResult ? results : good;
}
/**
* Helper function for randomly placing areas and groups on the map.
*/
function randomizePlacerCoordinates(placer, halfMapSize)
{
if (!!g_MapSettings.CircularMap)
{
// Polar coordinates
let r = halfMapSize * Math.sqrt(randFloat()); // uniform distribution
let theta = randFloat(0, 2 * PI);
placer.x = Math.floor(r * Math.cos(theta)) + halfMapSize;
placer.z = Math.floor(r * Math.sin(theta)) + halfMapSize;
}
else
{
// Rectangular coordinates
placer.x = randInt(g_Map.size);
placer.z = randInt(g_Map.size);
}
}
/**
* Helper function for randomly placing areas and groups in the given areas.
*/
function randomizePlacerCoordinatesFromAreas(placer, areas)
{
let i = randInt(areas.length);
let pt = areas[i].points[randInt(areas[i].points.length)];
placer.x = pt.x;
placer.z = pt.z;
}
/**
* 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 (args) {
randomizePlacerCoordinates(args.placer, args.halfMapSize);
return g_Map.createArea(args.placer, args.painter, args.constraint);
};
let args = {
"placer": centeredPlacer,
"painter": painter,
"constraint": constraint,
"halfMapSize": g_Map.size / 2
};
return retryPlacing(placeFunc, args, retryFactor, amount, true);
}
/**
* 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)
{
if (!areas.length)
return [];
let placeFunc = function (args) {
randomizePlacerCoordinatesFromAreas(args.placer, args.areas);
return g_Map.createArea(args.placer, args.painter, args.constraint);
};
let args = {
"placer": centeredPlacer,
"painter": painter,
"constraint": constraint,
"areas": areas,
"halfMapSize": g_Map.size / 2
};
return retryPlacing(placeFunc, args, retryFactor, amount, true);
}
/**
* Attempts to place the given number of groups in random places of the map.
* Returns the number of actually placed groups.
*/
function createObjectGroups(placer, player, constraint, amount, retryFactor = 10)
{
let placeFunc = function (args) {
randomizePlacerCoordinates(args.placer, args.halfMapSize);
return createObjectGroup(args.placer, args.player, args.constraint);
};
let args = {
"placer": placer,
"player": player,
"constraint": constraint,
"halfMapSize": g_Map.size / 2 - 3
};
return retryPlacing(placeFunc, args, retryFactor, amount, false);
}
/**
* Attempts to place the given number of groups in random places of the given areas.
* Returns the number of actually placed groups.
*/
function createObjectGroupsByAreas(placer, player, constraint, amount, retryFactor, areas)
{
if (!areas.length)
return 0;
let placeFunc = function (args) {
randomizePlacerCoordinatesFromAreas(args.placer, args.areas);
return createObjectGroup(args.placer, args.player, args.constraint);
};
let args = {
"placer": placer,
"player": player,
"constraint": constraint,
"areas": areas
};
return retryPlacing(placeFunc, args, retryFactor, amount, false);
}
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("createSimpleTerrain expects string as input, received "+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, MAP_BORDER_WIDTH))
g_Map.addObject(new Entity(type, player, x, z, angle));
}
function placeTerrain(x, z, terrain)
{
// convert terrain param into terrain object
g_Map.placeTerrain(x, z, createTerrain(terrain));
}
function isCircularMap()
{
return !!g_MapSettings.CircularMap;
}
function getMapBaseHeight()
{
return g_MapSettings.BaseHeight || 0;
}
function createTileClass()
{
return g_Map.createTileClass();
}
function getTileClass(id)
{
if (!g_Map.validClass(id))
return undefined;
return g_Map.tileClasses[id];
}
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 getMapArea()
{
return g_Map.size * g_Map.size;
}
function getNumPlayers()
{
return g_MapSettings.PlayerData.length - 1;
}
function getCivCode(player)
{
if (g_MapSettings.PlayerData[player+1].Civ)
return g_MapSettings.PlayerData[player+1].Civ;
warn("undefined civ specified for player " + (player + 1) + ", falling back to '" + FALLBACK_CIV + "'");
return FALLBACK_CIV;
}
function areAllies(player1, player2)
{
if (g_MapSettings.PlayerData[player1+1].Team === undefined ||
g_MapSettings.PlayerData[player2+1].Team === undefined ||
g_MapSettings.PlayerData[player2+1].Team == -1 ||
g_MapSettings.PlayerData[player1+1].Team == -1)
return false;
return g_MapSettings.PlayerData[player1+1].Team === g_MapSettings.PlayerData[player2+1].Team;
}
function getPlayerTeam(player)
{
if (g_MapSettings.PlayerData[player+1].Team === undefined)
return -1;
return g_MapSettings.PlayerData[player+1].Team;
}
/**
* Sorts an array of player IDs by team index. Players without teams come first.
* Randomize order for players of the same team.
*/
function sortPlayers(playerIndices)
{
return shuffleArray(playerIndices).sort((p1, p2) => getPlayerTeam(p1 - 1) - getPlayerTeam(p2 - 1));
}
function primeSortPlayers(playerIndices)
{
if (!playerIndices.length)
return [];
let prime = [];
for (let i = 0; i < Math.ceil(playerIndices.length / 2); ++i)
{
prime.push(playerIndices[i]);
prime.push(playerIndices[playerIndices.length - 1 - i]);
}
return prime;
}
function getStartingEntities(player)
{
let civ = getCivCode(player);
if (!g_CivData[civ] || !g_CivData[civ].StartEntities || !g_CivData[civ].StartEntities.length)
{
warn("Invalid or unimplemented civ '"+civ+"' specified, falling back to '" + FALLBACK_CIV + "'");
civ = FALLBACK_CIV;
}
return g_CivData[civ].StartEntities;
}
function getHeight(x, z)
{
return g_Map.getHeight(x, z);
}
function setHeight(x, z, height)
{
g_Map.setHeight(x, z, 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;
}
/**
* Returns the distance between 2 points
*/
function getDistance(x1, z1, x2, z2)
{
return Math.pow(Math.pow(x1 - x2, 2) + Math.pow(z1 - z2, 2), 1/2);
}
/**
* Returns the angle of the vector between point 1 and point 2.
* The angle is counterclockwise from the positive x axis.
*/
function getAngle(x1, z1, x2, z2)
{
return Math.atan2(z2 - z1, x2 - x1);
}
/**
* Returns the gradient of the line between point 1 and 2 in the form dz/dx
*/
function getGradient(x1, z1, x2, z2)
{
if (x1 == x2 && z1 == z2)
return 0;
return (z1-z2)/(x1-x2);
}
function getTerrainTexture(x, y)
{
return g_Map.getTexture(x, y);
}
Index: ps/trunk/binaries/data/mods/public/maps/scripts/Regicide.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scripts/Regicide.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/maps/scripts/Regicide.js (revision 18544)
@@ -0,0 +1,104 @@
+Trigger.prototype.CheckRegicideDefeat = function(data)
+{
+ if (data.entity == this.regicideHeroes[data.from])
+ TriggerHelper.DefeatPlayer(data.from);
+};
+
+Trigger.prototype.InitRegicideGame = function(msg)
+{
+ let playersCivs = [];
+ for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID)
+ playersCivs[playerID] = QueryPlayerIDInterface(playerID).GetCiv();
+
+ // Get all hero templates of these civs
+ let heroTemplates = {};
+ let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
+ for (let templateName of cmpTemplateManager.FindAllTemplates(false))
+ {
+ if (templateName.substring(0,6) != "units/")
+ continue;
+
+ let identity = cmpTemplateManager.GetTemplate(templateName).Identity;
+ let classes = GetIdentityClasses(identity);
+
+ if (classes.indexOf("Hero") == -1 ||
+ playersCivs.every(civ => civ != identity.Civ))
+ continue;
+
+ if (!heroTemplates[identity.Civ])
+ heroTemplates[identity.Civ] = [];
+
+ if (heroTemplates[identity.Civ].indexOf(templateName) == -1)
+ heroTemplates[identity.Civ].push({
+ "templateName": templateName,
+ "classes": classes
+ });
+ }
+
+ // Sort available spawn points by preference
+ let spawnPreference = ["Ship", "Structure", "CivilCentre"];
+ let getSpawnPreference = entity => {
+ let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
+ let classes = cmpIdentity.GetClassesList();
+ return spawnPreference.findIndex(className => classes.indexOf(className) != -1);
+ };
+
+ // Attempt to spawn one hero per player
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID)
+ {
+ let spawnPoints = cmpRangeManager.GetEntitiesByPlayer(playerID).sort((entity1, entity2) =>
+ getSpawnPreference(entity2) - getSpawnPreference(entity1));
+
+ this.regicideHeroes[playerID] = this.SpawnRegicideHero(playerID, heroTemplates[playersCivs[playerID]], spawnPoints);
+ }
+};
+
+/**
+ * Spawn a random hero at one of the given locations (which are checked in order).
+ * Garrison it if the location is a ship.
+ *
+ * @param spawnPoints - entity IDs at which to spawn
+ */
+Trigger.prototype.SpawnRegicideHero = function(playerID, heroTemplates, spawnPoints)
+{
+ for (let heroTemplate of shuffleArray(heroTemplates))
+ for (let spawnPoint of spawnPoints)
+ {
+ let cmpPosition = Engine.QueryInterface(spawnPoint, IID_Position);
+ if (!cmpPosition || !cmpPosition.IsInWorld())
+ continue;
+
+ // Consider nomad maps where units start on a ship
+ let isShip = TriggerHelper.EntityHasClass(spawnPoint, "Ship");
+ if (isShip)
+ {
+ let cmpGarrisonHolder = Engine.QueryInterface(spawnPoint, IID_GarrisonHolder);
+ if (cmpGarrisonHolder.IsFull() ||
+ !MatchesClassList(heroTemplate.classes, cmpGarrisonHolder.GetAllowedClasses()))
+ continue;
+ }
+
+ let hero = TriggerHelper.SpawnUnits(spawnPoint, heroTemplate.templateName, 1, playerID);
+ if (!hero.length)
+ continue;
+
+ hero = hero[0];
+
+ if (isShip)
+ {
+ let cmpUnitAI = Engine.QueryInterface(hero, IID_UnitAI);
+ cmpUnitAI.Garrison(spawnPoint);
+ }
+
+ return hero;
+ }
+
+ error("Couldn't spawn hero for player " + playerID);
+ return undefined;
+};
+
+let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
+cmpTrigger.regicideHeroes = [];
+cmpTrigger.DoAfterDelay(0, "InitRegicideGame", {});
+cmpTrigger.RegisterTrigger("OnOwnershipChanged", "CheckRegicideDefeat", { "enabled": true });
Property changes on: ps/trunk/binaries/data/mods/public/maps/scripts/Regicide.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js (revision 18543)
+++ ps/trunk/binaries/data/mods/public/maps/scripts/TriggerHelper.js (revision 18544)
@@ -1,160 +1,160 @@
// Contains standardized functions suitable for using in trigger scripts.
// Do not use them in any other simulation script.
var TriggerHelper = {};
TriggerHelper.GetPlayerIDFromEntity = function(ent)
{
let cmpPlayer = Engine.QueryInterface(ent, IID_Player);
if (cmpPlayer)
return cmpPlayer.GetPlayerID();
return -1;
};
TriggerHelper.GetOwner = function(ent)
{
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
return cmpOwnership.GetOwner();
return -1;
};
/**
- * Can be used to "force" a building to spawn a group of entities.
- * Only works for buildings that can already train units.
+ * Can be used to "force" a building/unit to spawn a group of entities.
+ *
* @param source Entity id of the point where they will be spawned from
* @param template Name of the template
* @param count Number of units to spawn
* @param owner Player id of the owner of the new units. By default, the owner
* of the source entity.
*/
TriggerHelper.SpawnUnits = function(source, template, count, owner)
{
let entities = [];
let cmpFootprint = Engine.QueryInterface(source, IID_Footprint);
let cmpPosition = Engine.QueryInterface(source, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
{
error("tried to create entity from a source without position");
return entities;
}
if (owner == null)
owner = TriggerHelper.GetOwner(source);
for (let i = 0; i < count; ++i)
{
let ent = Engine.AddEntity(template);
let cmpEntPosition = Engine.QueryInterface(ent, IID_Position);
if (!cmpEntPosition)
{
error("tried to create entity without position");
continue;
}
let cmpEntOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpEntOwnership)
cmpEntOwnership.SetOwner(owner);
entities.push(ent);
let pos;
if (cmpFootprint)
pos = cmpFootprint.PickSpawnPoint(ent);
// TODO this can happen if the player build on the place
// where our trigger point is
// We should probably warn the trigger maker in some way,
// but not interrupt the game for the player
if (!pos || pos.y < 0)
pos = cmpPosition.GetPosition();
cmpEntPosition.JumpTo(pos.x, pos.z);
}
return entities;
};
/**
* Spawn units from all trigger points with this reference
* If player is defined, only spaw units from the trigger points
* that belong to that player
* @param ref Trigger point reference name to spawn units from
* @param template Template name
* @param count Number of spawned entities per Trigger point
* @param owner Owner of the spawned units. Default: the owner of the origins
* @return A list of new entities per origin like
* {originId1: [entId1, entId2], originId2: [entId3, entId4], ...}
*/
TriggerHelper.SpawnUnitsFromTriggerPoints = function(ref, template, count, owner = null)
{
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
let triggerPoints = cmpTrigger.GetTriggerPoints(ref);
let entities = {};
for (let point of triggerPoints)
entities[point] = TriggerHelper.SpawnUnits(point, template, count, owner);
return entities;
};
/**
* Returns the resource type that can be gathered from an entity
*/
TriggerHelper.GetResourceType = function(entity)
{
let cmpResourceSupply = Engine.QueryInterface(entity, IID_ResourceSupply);
if (!cmpResourceSupply)
return undefined;
return cmpResourceSupply.GetType();
};
/**
* The given player will win the game.
* If it's not a last man standing game, then allies will win too.
*/
TriggerHelper.SetPlayerWon = function(playerID)
{
let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
cmpEndGameManager.MarkPlayerAsWon(playerID);
};
/**
* Defeats a player
*/
TriggerHelper.DefeatPlayer = function(playerID)
{
let cmpPlayer = QueryPlayerIDInterface(playerID);
if (cmpPlayer)
cmpPlayer.SetState("defeated");
};
/**
* Returns the number of current players
*/
TriggerHelper.GetNumberOfPlayers = function()
{
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
return cmpPlayerManager.GetNumPlayers();
};
/**
* A function to determine if an entity has a specific class
* @param entity ID of the entity that we want to check for classes
* @param classname The name of the class we are checking if the entity has
*/
TriggerHelper.EntityHasClass = function(entity, classname)
{
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
if (!cmpIdentity)
return false;
let classes = cmpIdentity.GetClassesList();
return classes && classes.indexOf(classname) != -1;
};
Engine.RegisterGlobal("TriggerHelper", TriggerHelper);
Index: ps/trunk/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json (revision 18544)
@@ -0,0 +1,15 @@
+{
+ "TranslatedKeys": ["Title", "Description"],
+ "Data":
+ {
+ "Title": "Regicide",
+ "Description": "Defeat opponents by killing their hero",
+ "Scripts":
+ [
+ "scripts/TriggerHelper.js",
+ "scripts/ConquestCommon.js",
+ "scripts/Conquest.js",
+ "scripts/Regicide.js"
+ ]
+ }
+}
Property changes on: ps/trunk/binaries/data/mods/public/simulation/data/settings/victory_conditions/regicide.json
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp
===================================================================
--- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 18543)
+++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp (revision 18544)
@@ -1,576 +1,577 @@
-/* Copyright (C) 2015 Wildfire Games.
+/* Copyright (C) 2016 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 "Map.h"
#include "AtlasObject/AtlasObject.h"
#include "GameInterface/Messages.h"
#include "ScenarioEditor/ScenarioEditor.h"
#include "ScenarioEditor/Tools/Common/Tools.h"
#include "wx/busyinfo.h"
#include "wx/filename.h"
enum
{
ID_MapName,
ID_MapDescription,
ID_MapReveal,
ID_MapType,
ID_MapPreview,
ID_MapTeams,
ID_MapKW_Demo,
ID_MapKW_Naval,
ID_RandomScript,
ID_RandomSize,
ID_RandomSeed,
ID_RandomReseed,
ID_RandomGenerate,
ID_SimPlay,
ID_SimFast,
ID_SimSlow,
ID_SimPause,
ID_SimReset,
ID_OpenPlayerPanel
};
enum
{
SimInactive,
SimPlaying,
SimPlayingFast,
SimPlayingSlow,
SimPaused
};
bool IsPlaying(int s) { return (s == SimPlaying || s == SimPlayingFast || s == SimPlayingSlow); }
// TODO: Some of these helper things should be moved out of this file
// and into shared locations
// Helper function for adding tooltips
static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
{
window->SetToolTip(tip);
return window;
}
// Helper class for storing AtObjs
class AtObjClientData : public wxClientData
{
public:
AtObjClientData(const AtObj& obj) : obj(obj) {}
virtual ~AtObjClientData() {}
AtObj GetValue() { return obj; }
private:
AtObj obj;
};
class MapSettingsControl : public wxPanel
{
public:
MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor);
void CreateWidgets();
void ReadFromEngine();
void SetMapSettings(const AtObj& obj);
AtObj UpdateSettingsObject();
private:
void SendToEngine();
void OnEdit(wxCommandEvent& WXUNUSED(evt))
{
SendToEngine();
}
std::set m_MapSettingsKeywords;
std::vector m_PlayerCivChoices;
Observable& m_MapSettings;
DECLARE_EVENT_TABLE();
};
BEGIN_EVENT_TABLE(MapSettingsControl, wxPanel)
EVT_TEXT(ID_MapName, MapSettingsControl::OnEdit)
EVT_TEXT(ID_MapDescription, MapSettingsControl::OnEdit)
EVT_TEXT(ID_MapPreview, MapSettingsControl::OnEdit)
EVT_CHECKBOX(wxID_ANY, MapSettingsControl::OnEdit)
EVT_CHOICE(wxID_ANY, MapSettingsControl::OnEdit)
END_EVENT_TABLE();
MapSettingsControl::MapSettingsControl(wxWindow* parent, ScenarioEditor& scenarioEditor)
: wxPanel(parent, wxID_ANY), m_MapSettings(scenarioEditor.GetMapSettings())
{
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Map settings"));
SetSizer(sizer);
}
void MapSettingsControl::CreateWidgets()
{
wxSizer* sizer = GetSizer();
/////////////////////////////////////////////////////////////////////////
// Map settings
wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL);
nameSizer->Add(new wxStaticText(this, wxID_ANY, _("Name")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
nameSizer->Add(8, 0);
nameSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapName),
_("Displayed name of the map")), wxSizerFlags().Proportion(1));
sizer->Add(nameSizer, wxSizerFlags().Expand());
sizer->Add(0, 2);
sizer->Add(new wxStaticText(this, wxID_ANY, _("Description")));
sizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapDescription, wxEmptyString, wxDefaultPosition, wxSize(-1, 100), wxTE_MULTILINE),
_("Short description used on the map selection screen")), wxSizerFlags().Expand());
sizer->AddSpacer(5);
// TODO: replace by filenames in binaries/data/mods/public/simulation/data/settings/victory_conditions/
wxArrayString gameTypes;
gameTypes.Add(_T("conquest"));
gameTypes.Add(_T("conquest_structures"));
gameTypes.Add(_T("conquest_units"));
gameTypes.Add(_T("wonder"));
gameTypes.Add(_T("endless"));
+ gameTypes.Add(_T("regicide"));
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5);
gridSizer->AddGrowableCol(1);
// TODO: have preview selector tool?
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Preview")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
gridSizer->Add(Tooltipped(new wxTextCtrl(this, ID_MapPreview, wxEmptyString),
_("Texture used for map preview")), wxSizerFlags().Expand());
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Reveal map")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
gridSizer->Add(Tooltipped(new wxCheckBox(this, ID_MapReveal, wxEmptyString),
_("If checked, players won't need to explore")));
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Game type")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
gridSizer->Add(Tooltipped(new wxChoice(this, ID_MapType, wxDefaultPosition, wxDefaultSize, gameTypes),
_("Select the game type (or victory condition)")), wxSizerFlags().Expand());
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Lock teams")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
gridSizer->Add(Tooltipped(new wxCheckBox(this, ID_MapTeams, wxEmptyString),
_("If checked, teams will be locked")));
sizer->Add(gridSizer, wxSizerFlags().Expand());
sizer->AddSpacer(5);
wxStaticBoxSizer* keywordsSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Keywords"));
wxFlexGridSizer* kwGridSizer = new wxFlexGridSizer(4, 5, 5);
kwGridSizer->Add(new wxStaticText(this, wxID_ANY, _("Demo")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
kwGridSizer->Add(Tooltipped(new wxCheckBox(this, ID_MapKW_Demo, wxEmptyString),
_("If checked, map will only be visible using filters in game setup")));
kwGridSizer->Add(new wxStaticText(this, wxID_ANY, _("Naval")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
kwGridSizer->Add(Tooltipped(new wxCheckBox(this, ID_MapKW_Naval, wxEmptyString),
_("If checked, map will only be visible using filters in game setup")));
keywordsSizer->Add(kwGridSizer);
sizer->Add(keywordsSizer, wxSizerFlags().Expand());
}
void MapSettingsControl::ReadFromEngine()
{
AtlasMessage::qGetMapSettings qry;
qry.Post();
if (!(*qry.settings).empty())
{
// Prevent error if there's no map settings to parse
m_MapSettings = AtlasObject::LoadFromJSON(*qry.settings);
}
// map name
wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Name"]));
// map description
wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Description"]));
// map preview
wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->ChangeValue(wxString(m_MapSettings["Preview"]));
// reveal map
wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->SetValue(wxString(m_MapSettings["RevealMap"]) == L"true");
// game type / victory conditions
if (m_MapSettings["GameType"].defined())
wxDynamicCast(FindWindow(ID_MapType), wxChoice)->SetStringSelection(wxString(m_MapSettings["GameType"]));
else
wxDynamicCast(FindWindow(ID_MapType), wxChoice)->SetSelection(0);
// lock teams
wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->SetValue(wxString(m_MapSettings["LockTeams"]) == L"true");
// keywords
{
m_MapSettingsKeywords.clear();
for (AtIter keyword = m_MapSettings["Keywords"]["item"]; keyword.defined(); ++keyword)
m_MapSettingsKeywords.insert(std::wstring(keyword));
wxDynamicCast(FindWindow(ID_MapKW_Demo), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"demo") != 0);
wxDynamicCast(FindWindow(ID_MapKW_Naval), wxCheckBox)->SetValue(m_MapSettingsKeywords.count(L"naval") != 0);
}
}
void MapSettingsControl::SetMapSettings(const AtObj& obj)
{
m_MapSettings = obj;
m_MapSettings.NotifyObservers();
SendToEngine();
}
AtObj MapSettingsControl::UpdateSettingsObject()
{
// map name
m_MapSettings.set("Name", wxDynamicCast(FindWindow(ID_MapName), wxTextCtrl)->GetValue());
// map description
m_MapSettings.set("Description", wxDynamicCast(FindWindow(ID_MapDescription), wxTextCtrl)->GetValue());
// map preview
m_MapSettings.set("Preview", wxDynamicCast(FindWindow(ID_MapPreview), wxTextCtrl)->GetValue());
// reveal map
m_MapSettings.setBool("RevealMap", wxDynamicCast(FindWindow(ID_MapReveal), wxCheckBox)->GetValue());
// game type / victory conditions
m_MapSettings.set("GameType", wxDynamicCast(FindWindow(ID_MapType), wxChoice)->GetStringSelection());
// keywords
{
if (wxDynamicCast(FindWindow(ID_MapKW_Demo), wxCheckBox)->GetValue())
m_MapSettingsKeywords.insert(L"demo");
else
m_MapSettingsKeywords.erase(L"demo");
if (wxDynamicCast(FindWindow(ID_MapKW_Naval), wxCheckBox)->GetValue())
m_MapSettingsKeywords.insert(L"naval");
else
m_MapSettingsKeywords.erase(L"naval");
AtObj keywords;
keywords.set("@array", L"");
for (std::set::iterator it = m_MapSettingsKeywords.begin(); it != m_MapSettingsKeywords.end(); ++it)
keywords.add("item", it->c_str());
m_MapSettings.set("Keywords", keywords);
}
// teams locked
m_MapSettings.setBool("LockTeams", wxDynamicCast(FindWindow(ID_MapTeams), wxCheckBox)->GetValue());
return m_MapSettings;
}
void MapSettingsControl::SendToEngine()
{
UpdateSettingsObject();
std::string json = AtlasObject::SaveToJSON(m_MapSettings);
// TODO: would be nice if we supported undo for settings changes
POST_COMMAND(SetMapSettings, (json));
}
MapSidebar::MapSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), m_SimState(SimInactive)
{
m_MapSettingsCtrl = new MapSettingsControl(this, m_ScenarioEditor);
m_MainSizer->Add(m_MapSettingsCtrl, wxSizerFlags().Expand());
{
/////////////////////////////////////////////////////////////////////////
// Random map settings
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Random map"));
sizer->Add(new wxChoice(this, ID_RandomScript), wxSizerFlags().Expand());
sizer->AddSpacer(5);
sizer->Add(new wxButton(this, ID_OpenPlayerPanel, _T("Change players")), wxSizerFlags().Expand());
sizer->AddSpacer(5);
wxFlexGridSizer* gridSizer = new wxFlexGridSizer(2, 5, 5);
gridSizer->AddGrowableCol(1);
wxChoice* sizeChoice = new wxChoice(this, ID_RandomSize);
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Map size")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
gridSizer->Add(sizeChoice, wxSizerFlags().Expand());
gridSizer->Add(new wxStaticText(this, wxID_ANY, _("Random seed")), wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT));
wxBoxSizer* seedSizer = new wxBoxSizer(wxHORIZONTAL);
seedSizer->Add(Tooltipped(new wxTextCtrl(this, ID_RandomSeed, _T("0"), wxDefaultPosition, wxDefaultSize, 0, wxTextValidator(wxFILTER_NUMERIC)),
_("Seed value for random map")), wxSizerFlags(1).Expand());
seedSizer->Add(Tooltipped(new wxButton(this, ID_RandomReseed, _("R"), wxDefaultPosition, wxSize(24, -1)),
_("New random seed")));
gridSizer->Add(seedSizer, wxSizerFlags().Expand());
sizer->Add(gridSizer, wxSizerFlags().Expand());
sizer->AddSpacer(5);
sizer->Add(Tooltipped(new wxButton(this, ID_RandomGenerate, _("Generate map")),
_("Run selected random map script")), wxSizerFlags().Expand());
m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
}
{
/////////////////////////////////////////////////////////////////////////
// Simulation buttons
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Simulation test"));
wxGridSizer* gridSizer = new wxGridSizer(5);
gridSizer->Add(Tooltipped(new wxButton(this, ID_SimPlay, _("Play")),
_("Run the simulation at normal speed")), wxSizerFlags().Expand());
gridSizer->Add(Tooltipped(new wxButton(this, ID_SimFast, _("Fast")),
_("Run the simulation at 8x speed")), wxSizerFlags().Expand());
gridSizer->Add(Tooltipped(new wxButton(this, ID_SimSlow, _("Slow")),
_("Run the simulation at 1/8x speed")), wxSizerFlags().Expand());
gridSizer->Add(Tooltipped(new wxButton(this, ID_SimPause, _("Pause")),
_("Pause the simulation")), wxSizerFlags().Expand());
gridSizer->Add(Tooltipped(new wxButton(this, ID_SimReset, _("Reset")),
_("Reset the editor to initial state")), wxSizerFlags().Expand());
sizer->Add(gridSizer, wxSizerFlags().Expand());
UpdateSimButtons();
m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10));
}
}
void MapSidebar::OnCollapse(wxCollapsiblePaneEvent& WXUNUSED(evt))
{
Freeze();
// Toggling the collapsing doesn't seem to update the sidebar layout
// automatically, so do it explicitly here
Layout();
Refresh(); // fixes repaint glitch on Windows
Thaw();
}
void MapSidebar::OnFirstDisplay()
{
// We do this here becase messages are used which requires simulation to be init'd
m_MapSettingsCtrl->CreateWidgets();
m_MapSettingsCtrl->ReadFromEngine();
// Load the map sizes list
AtlasMessage::qGetMapSizes qrySizes;
qrySizes.Post();
AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes);
wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice);
for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s)
{
long tiles = 0;
wxString(s["Tiles"]).ToLong(&tiles);
sizeChoice->Append(wxString(s["Name"]), (void*)(intptr_t)tiles);
}
sizeChoice->SetSelection(0);
// Load the RMS script list
AtlasMessage::qGetRMSData qry;
qry.Post();
std::vector scripts = *qry.data;
wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice);
scriptChoice->Clear();
for (size_t i = 0; i < scripts.size(); ++i)
{
AtObj data = AtlasObject::LoadFromJSON(scripts[i]);
wxString name(data["settings"]["Name"]);
scriptChoice->Append(name, new AtObjClientData(*data["settings"]));
}
scriptChoice->SetSelection(0);
Layout();
}
void MapSidebar::OnMapReload()
{
m_MapSettingsCtrl->ReadFromEngine();
// Reset sim test buttons
POST_MESSAGE(SimPlay, (0.f, false));
POST_MESSAGE(SimStopMusic, ());
POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml"));
m_SimState = SimInactive;
UpdateSimButtons();
}
void MapSidebar::UpdateSimButtons()
{
wxButton* button;
button = wxDynamicCast(FindWindow(ID_SimPlay), wxButton);
wxCHECK(button, );
button->Enable(m_SimState != SimPlaying);
button = wxDynamicCast(FindWindow(ID_SimFast), wxButton);
wxCHECK(button, );
button->Enable(m_SimState != SimPlayingFast);
button = wxDynamicCast(FindWindow(ID_SimSlow), wxButton);
wxCHECK(button, );
button->Enable(m_SimState != SimPlayingSlow);
button = wxDynamicCast(FindWindow(ID_SimPause), wxButton);
wxCHECK(button, );
button->Enable(IsPlaying(m_SimState));
button = wxDynamicCast(FindWindow(ID_SimReset), wxButton);
wxCHECK(button, );
button->Enable(m_SimState != SimInactive);
}
void MapSidebar::OnSimPlay(wxCommandEvent& event)
{
float speed = 1.f;
int newState = SimPlaying;
if (event.GetId() == ID_SimFast)
{
speed = 8.f;
newState = SimPlayingFast;
}
else if (event.GetId() == ID_SimSlow)
{
speed = 0.125f;
newState = SimPlayingSlow;
}
if (m_SimState == SimInactive)
{
// Force update of player settings
POST_MESSAGE(LoadPlayerSettings, (false));
POST_MESSAGE(SimStateSave, (L"default"));
POST_MESSAGE(GuiSwitchPage, (L"page_session.xml"));
POST_MESSAGE(SimPlay, (speed, true));
m_SimState = newState;
}
else // paused or already playing at a different speed
{
POST_MESSAGE(SimPlay, (speed, true));
m_SimState = newState;
}
UpdateSimButtons();
}
void MapSidebar::OnSimPause(wxCommandEvent& WXUNUSED(event))
{
if (IsPlaying(m_SimState))
{
POST_MESSAGE(SimPlay, (0.f, true));
m_SimState = SimPaused;
}
UpdateSimButtons();
}
void MapSidebar::OnSimReset(wxCommandEvent& WXUNUSED(event))
{
if (IsPlaying(m_SimState))
{
POST_MESSAGE(SimPlay, (0.f, true));
POST_MESSAGE(SimStateRestore, (L"default"));
POST_MESSAGE(SimStopMusic, ());
POST_MESSAGE(SimPlay, (0.f, false));
POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml"));
m_SimState = SimInactive;
}
else if (m_SimState == SimPaused)
{
POST_MESSAGE(SimPlay, (0.f, true));
POST_MESSAGE(SimStateRestore, (L"default"));
POST_MESSAGE(SimStopMusic, ());
POST_MESSAGE(SimPlay, (0.f, false));
POST_MESSAGE(GuiSwitchPage, (L"page_atlas.xml"));
m_SimState = SimInactive;
}
UpdateSimButtons();
}
void MapSidebar::OnRandomReseed(wxCommandEvent& WXUNUSED(evt))
{
// Pick a shortish randomish value
wxString seed;
seed << (int)floor((rand() / (float)RAND_MAX) * 10000.f);
wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->SetValue(seed);
}
void MapSidebar::OnRandomGenerate(wxCommandEvent& WXUNUSED(evt))
{
if (m_ScenarioEditor.DiscardChangesDialog())
return;
wxChoice* scriptChoice = wxDynamicCast(FindWindow(ID_RandomScript), wxChoice);
if (scriptChoice->GetSelection() < 0)
return;
// TODO: this settings thing seems a bit of a mess,
// since it's mixing data from three different sources
AtObj settings = m_MapSettingsCtrl->UpdateSettingsObject();
AtObj scriptSettings = dynamic_cast(scriptChoice->GetClientObject(scriptChoice->GetSelection()))->GetValue();
settings.addOverlay(scriptSettings);
wxChoice* sizeChoice = wxDynamicCast(FindWindow(ID_RandomSize), wxChoice);
wxString size;
size << (intptr_t)sizeChoice->GetClientData(sizeChoice->GetSelection());
settings.setInt("Size", wxAtoi(size));
settings.setInt("Seed", wxAtoi(wxDynamicCast(FindWindow(ID_RandomSeed), wxTextCtrl)->GetValue()));
std::string json = AtlasObject::SaveToJSON(settings);
wxBusyInfo busy(_("Generating map"));
wxBusyCursor busyc;
wxString scriptName(settings["Script"]);
// Copy the old map settings, so we don't lose them if the map generation fails
AtObj oldSettings = settings;
AtlasMessage::qGenerateMap qry((std::wstring)scriptName.wc_str(), json);
qry.Post();
if (qry.status < 0)
{
// Display error message and revert to old map settings
wxLogError(_("Random map script '%ls' failed"), scriptName.wc_str());
m_MapSettingsCtrl->SetMapSettings(oldSettings);
}
m_ScenarioEditor.NotifyOnMapReload();
}
void MapSidebar::OnOpenPlayerPanel(wxCommandEvent& WXUNUSED(evt))
{
m_ScenarioEditor.SelectPage(_T("PlayerSidebar"));
}
BEGIN_EVENT_TABLE(MapSidebar, Sidebar)
EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, MapSidebar::OnCollapse)
EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay)
EVT_BUTTON(ID_SimFast, MapSidebar::OnSimPlay)
EVT_BUTTON(ID_SimSlow, MapSidebar::OnSimPlay)
EVT_BUTTON(ID_SimPause, MapSidebar::OnSimPause)
EVT_BUTTON(ID_SimReset, MapSidebar::OnSimReset)
EVT_BUTTON(ID_RandomReseed, MapSidebar::OnRandomReseed)
EVT_BUTTON(ID_RandomGenerate, MapSidebar::OnRandomGenerate)
EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel)
END_EVENT_TABLE();