Index: ps/trunk/binaries/data/mods/_test.sim/simulation/components/test-serialize.js
===================================================================
--- ps/trunk/binaries/data/mods/_test.sim/simulation/components/test-serialize.js (revision 24461)
+++ ps/trunk/binaries/data/mods/_test.sim/simulation/components/test-serialize.js (revision 24462)
@@ -1,90 +1,94 @@
function TestScript1_values() {}
TestScript1_values.prototype.Init = function() {
this.x = +this.template.x;
this.str = "this is a string";
- this.things = { a: 1, b: "2", c: [3, "4", [5, []]] };
+ this.things = { "a": 1, "b": "2", "c": [3, "4", [5, []]] };
};
TestScript1_values.prototype.GetX = function() {
// print(uneval(this));
return this.x;
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_values", TestScript1_values);
// -------- //
function TestScript1_entity() {}
TestScript1_entity.prototype.GetX = function() {
// Test that .entity is readonly
try {
delete this.entity;
Engine.TS_FAIL("Missed exception");
- } catch (e) { }
+ } catch (e) { /* OK */ }
try {
this.entity = -1;
Engine.TS_FAIL("Missed exception");
- } catch (e) { }
+ } catch (e) { /* OK */ }
// and return the value
return this.entity;
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_entity", TestScript1_entity);
// -------- //
function TestScript1_nontree() {}
TestScript1_nontree.prototype.Init = function() {
var n = [1];
- this.x = [n, n, null, { y: n }];
+ this.x = [n, n, null, { "y": n }];
this.x[2] = this.x;
};
TestScript1_nontree.prototype.GetX = function() {
// print(uneval(this)+"\n");
this.x[0][0] += 1;
return this.x[0][0] + this.x[1][0] + this.x[2][0][0] + this.x[3].y[0];
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_nontree", TestScript1_nontree);
// -------- //
function TestScript1_custom() {}
TestScript1_custom.prototype.Init = function() {
this.y = 2;
};
TestScript1_custom.prototype.Serialize = function() {
- return {c:1};
+ return { "c": 1 };
+};
+
+TestScript1_custom.prototype.Deserialize = function(data) {
+ this.c = data.c;
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_custom", TestScript1_custom);
// -------- //
function TestScript1_getter() {}
TestScript1_getter.prototype.Init = function() {
this.x = 100;
- this.__defineGetter__('x', function () { print("FAIL\n"); die(); return 200; });
+ this.__defineGetter__('x', function() { print("FAIL\n"); die(); return 200; });
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_getter", TestScript1_getter);
// -------- //
function TestScript1_consts() {}
TestScript1_consts.prototype.Schema = "";
TestScript1_consts.prototype.GetX = function() {
return (+this.entity) + (+this.template.x);
};
Engine.RegisterComponentType(IID_Test1, "TestScript1_consts", TestScript1_consts);
Index: ps/trunk/binaries/data/mods/public/globalscripts/tests/test_vector.js
===================================================================
--- ps/trunk/binaries/data/mods/public/globalscripts/tests/test_vector.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/globalscripts/tests/test_vector.js (revision 24462)
@@ -0,0 +1,9 @@
+function test_serialization()
+{
+ let test_val = new Vector2D(1, 2);
+ let rt = Engine.SerializationRoundTrip(test_val);
+ TS_ASSERT_EQUALS(test_val.constructor, rt.constructor);
+ TS_ASSERT_EQUALS(rt.add(test_val).x, 2);
+}
+
+test_serialization();
Property changes on: ps/trunk/binaries/data/mods/public/globalscripts/tests/test_vector.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/maps/random/danubius_triggers.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/danubius_triggers.js (revision 24461)
+++ ps/trunk/binaries/data/mods/public/maps/random/danubius_triggers.js (revision 24462)
@@ -1,641 +1,640 @@
// Ships respawn every few minutes, attack the closest warships, then patrol the sea.
// To prevent unlimited spawning of ships, no more than the amount of ships intended at a given time are spawned.
// Ships are filled or refilled with new units.
// The number of ships, number of units per ship, as well as ratio of siege engines, champion and heroes
// increases with time, while keeping an individual and randomized composition for each ship.
// Each hero exists at most once per map.
// Every few minutes, equal amount of ships unload units at the sides of the river unless
// one side of the river was wiped from players.
// Siege engines attack defensive structures, units attack units then patrol that side of the river.
const showDebugLog = false;
const danubiusAttackerTemplates = deepfreeze({
"ships": TriggerHelper.GetTemplateNamesByClasses("Warship", "gaul", undefined, undefined, true),
"siege": TriggerHelper.GetTemplateNamesByClasses("Siege", "gaul", undefined, undefined, true),
"females": TriggerHelper.GetTemplateNamesByClasses("FemaleCitizen", "gaul", undefined, undefined, true),
"healers": TriggerHelper.GetTemplateNamesByClasses("Healer", "gaul", undefined, undefined, true),
"champions": TriggerHelper.GetTemplateNamesByClasses("Champion", "gaul", undefined, undefined, true),
"champion_infantry": TriggerHelper.GetTemplateNamesByClasses("Champion+Infantry", "gaul", undefined, undefined, true),
"citizen_soldiers": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier", "gaul", undefined, "Basic", true),
"heroes": [
// Excludes the Vercingetorix variant
"units/gaul/hero_viridomarus",
"units/gaul/hero_vercingetorix",
"units/gaul/hero_brennus"
]
});
var ccDefenders = [
{ "count": 8, "templates": danubiusAttackerTemplates.citizen_soldiers },
{ "count": 13, "templates": danubiusAttackerTemplates.champions },
{ "count": 4, "templates": danubiusAttackerTemplates.healers },
{ "count": 5, "templates": danubiusAttackerTemplates.females },
{ "count": 10, "templates": ["gaia/fauna_sheep"] }
];
var gallicBuildingGarrison = [
{
"buildingClasses": ["House"],
"unitTemplates": danubiusAttackerTemplates.females.concat(danubiusAttackerTemplates.healers)
},
{
"buildingClasses": ["CivCentre", "Temple"],
"unitTemplates": danubiusAttackerTemplates.champions,
},
{
"buildingClasses": ["Tower", "Outpost"],
"unitTemplates": danubiusAttackerTemplates.champion_infantry
}
];
/**
* Notice if gaia becomes too strong, players will just turtle and try to outlast the players on the other side.
* However we want interaction and fights between the teams.
* This can be accomplished by not wiping out players buildings entirely.
*/
/**
* Time in minutes between two consecutive waves spawned from the gaia civic centers, if they still exist.
*/
var ccAttackerInterval = t => randFloat(6, 8);
/**
* Number of attackers spawned at a civic center at t minutes ingame time.
*/
var ccAttackerCount = t => Math.min(20, Math.max(0, Math.round(t * 1.5)));
/**
* Time between two consecutive waves.
*/
var shipRespawnTime = () => randFloat(8, 10);
/**
* Limit of ships on the map when spawning them.
* Have at least two ships, so that both sides will be visited.
*/
var shipCount = (t, numPlayers) => Math.max(2, Math.round(Math.min(1.5, t / 10) * numPlayers));
/**
* Order all ships to ungarrison at the shoreline.
*/
var shipUngarrisonInterval = () => randFloat(5, 7);
/**
* Time between refillings of all ships with new soldiers.
*/
var shipFillInterval = () => randFloat(4, 5);
/**
* Total count of gaia attackers per shipload.
*/
var attackersPerShip = t => Math.min(30, Math.round(t * 2));
/**
* Likelihood of adding a non-existing hero at t minutes.
*/
var heroProbability = t => Math.max(0, Math.min(1, (t - 25) / 60));
/**
* Percent of healers to add per shipload after potentially adding a hero and siege engines.
*/
var healerRatio = t => randFloat(0, 0.1);
/**
* Number of siege engines to add per shipload.
*/
var siegeCount = t => 1 + Math.min(2, Math.floor(t / 30));
/**
* Percent of champions to be added after spawning heroes, healers and siege engines.
* Rest will be citizen soldiers.
*/
var championRatio = t => Math.min(1, Math.max(0, (t - 25) / 75));
/**
* Number of trigger points to patrol when not having enemies to attack.
*/
var patrolCount = 5;
/**
* Which units ships should focus when attacking and patrolling.
*/
var shipTargetClass = "Warship";
/**
* Which entities siege engines should focus when attacking and patrolling.
*/
var siegeTargetClass = "Defensive";
/**
* Which entities units should focus when attacking and patrolling.
*/
var unitTargetClass = "Unit+!Ship";
/**
* Ungarrison ships when being in this range of the target.
*/
var shipUngarrisonDistance = 50;
/**
* Currently formations are not working properly and enemies in vision range are often ignored.
* So only have a small chance of using formations.
*/
var formationProbability = 0.2;
var unitFormations = [
"special/formations/box",
"special/formations/battle_line",
"special/formations/line_closed",
"special/formations/column_closed"
];
/**
* Chance for the units at the meeting place to participate in the ritual.
*/
var ritualProbability = 0.75;
/**
* Units celebrating at the meeting place will perform one of these animations
* if idle and switch back when becoming idle again.
*/
var ritualAnimations = {
"female": ["attack_slaughter"],
"male": ["attack_capture", "promotion", "attack_slaughter"],
"healer": ["attack_capture", "promotion", "heal"]
};
var triggerPointShipSpawn = "A";
var triggerPointShipPatrol = "B";
var triggerPointUngarrisonLeft = "C";
var triggerPointUngarrisonRight = "D";
var triggerPointLandPatrolLeft = "E";
var triggerPointLandPatrolRight = "F";
var triggerPointCCAttackerPatrolLeft = "G";
var triggerPointCCAttackerPatrolRight = "H";
var triggerPointRiverDirection = "I";
/**
* Which playerID to use for the opposing gallic reinforcements.
*/
var gaulPlayer = 0;
Trigger.prototype.debugLog = function(txt)
{
if (showDebugLog)
print("DEBUG [" + Math.round(TriggerHelper.GetMinutes()) + "] " + txt + "\n");
};
Trigger.prototype.GarrisonAllGallicBuildings = function()
{
this.debugLog("Garrisoning all gallic buildings");
for (let buildingGarrison of gallicBuildingGarrison)
for (let buildingClass of buildingGarrison.buildingClasses)
{
let unitCounts = TriggerHelper.SpawnAndGarrisonAtClasses(gaulPlayer, buildingClass, buildingGarrison.unitTemplates, 1);
this.debugLog("Garrisoning at " + buildingClass + ": " + uneval(unitCounts));
}
};
/**
* Spawn units of the template at each gaia Civic Center and set them to defensive.
*/
Trigger.prototype.SpawnInitialCCDefenders = function()
{
this.debugLog("To defend CCs, spawning " + uneval(ccDefenders));
for (let ent of this.civicCenters)
for (let ccDefender of ccDefenders)
for (let spawnedEnt of TriggerHelper.SpawnUnits(ent, pickRandom(ccDefender.templates), ccDefender.count, gaulPlayer))
TriggerHelper.SetUnitStance(spawnedEnt, "defensive");
};
Trigger.prototype.SpawnCCAttackers = function()
{
let time = TriggerHelper.GetMinutes();
let [spawnLeft, spawnRight] = this.GetActiveRiversides();
for (let gaiaCC of this.civicCenters)
{
if (!TriggerHelper.IsInWorld(gaiaCC))
continue;
let isLeft = this.IsLeftRiverside(gaiaCC);
if (isLeft && !spawnLeft || !isLeft && !spawnRight)
continue;
let templateCounts = TriggerHelper.BalancedTemplateComposition(this.GetAttackerComposition(time, false), ccAttackerCount(time));
this.debugLog("Spawning civic center attackers at " + gaiaCC + ": " + uneval(templateCounts));
let ccAttackers = [];
for (let templateName in templateCounts)
{
let ents = TriggerHelper.SpawnUnits(gaiaCC, templateName, templateCounts[templateName], gaulPlayer);
if (danubiusAttackerTemplates.heroes.indexOf(templateName) != -1 && ents[0])
this.heroes.add(ents[0]);
ccAttackers = ccAttackers.concat(ents);
}
let patrolPointRef = isLeft ?
triggerPointCCAttackerPatrolLeft :
triggerPointCCAttackerPatrolRight;
this.AttackAndPatrol(ccAttackers, unitTargetClass, patrolPointRef, "CCAttackers", false);
}
if (this.civicCenters.size)
this.DoAfterDelay(ccAttackerInterval() * 60 * 1000, "SpawnCCAttackers", {});
};
/**
* Remember most Humans present at the beginning of the match (before spawning any unit) and
* make them defensive.
*/
Trigger.prototype.StartCelticRitual = function()
{
for (let ent of TriggerHelper.GetPlayerEntitiesByClass(gaulPlayer, "Human"))
{
if (randBool(ritualProbability))
this.ritualEnts.add(ent);
TriggerHelper.SetUnitStance(ent, "defensive");
}
this.DoRepeatedly(5 * 1000, "UpdateCelticRitual", {});
};
/**
* Play one of the given animations for most participants if and only if they are idle.
*/
Trigger.prototype.UpdateCelticRitual = function()
{
for (let ent of this.ritualEnts)
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (!cmpUnitAI || cmpUnitAI.GetCurrentState() != "INDIVIDUAL.IDLE")
continue;
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
if (!cmpIdentity)
continue;
let animations = ritualAnimations[
cmpIdentity.HasClass("Healer") ? "healer" :
cmpIdentity.HasClass("Female") ? "female" : "male"];
let cmpVisual = Engine.QueryInterface(ent, IID_Visual);
if (!cmpVisual)
continue;
if (animations.indexOf(cmpVisual.GetAnimationName()) == -1)
cmpVisual.SelectAnimation(pickRandom(animations), false, 1, "");
}
};
/**
* Spawn ships with a unique attacker composition each until
* the number of ships is reached that is supposed to exist at the given time.
*/
Trigger.prototype.SpawnShips = function()
{
let time = TriggerHelper.GetMinutes();
let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetActivePlayers().length;
let shipSpawnCount = shipCount(time, numPlayers) - this.ships.size;
this.debugLog("Spawning " + shipSpawnCount + " ships");
while (this.ships.size < shipSpawnCount)
this.ships.add(
TriggerHelper.SpawnUnits(
pickRandom(this.GetTriggerPoints(triggerPointShipSpawn)),
pickRandom(danubiusAttackerTemplates.ships),
1,
gaulPlayer)[0]);
for (let ship of this.ships)
this.AttackAndPatrol([ship], shipTargetClass, triggerPointShipPatrol, "Ship", true);
this.DoAfterDelay(shipRespawnTime(time) * 60 * 1000, "SpawnShips", {});
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.fillShipsTimer);
this.FillShips();
};
Trigger.prototype.GetAttackerComposition = function(time, siegeEngines)
{
let champRatio = championRatio(time);
return [
{
"templates": danubiusAttackerTemplates.heroes,
"count": randBool(heroProbability(time)) ? 1 : 0,
"unique_entities": Array.from(this.heroes)
},
{
"templates": danubiusAttackerTemplates.siege,
"count": siegeEngines ? siegeCount(time) : 0
},
{
"templates": danubiusAttackerTemplates.healers,
"frequency": healerRatio(time)
},
{
"templates": danubiusAttackerTemplates.champions,
"frequency": champRatio
},
{
"templates": danubiusAttackerTemplates.citizen_soldiers,
"frequency": 1 - champRatio
}
];
};
Trigger.prototype.FillShips = function()
{
let time = TriggerHelper.GetMinutes();
for (let ship of this.ships)
{
let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
continue;
let templateCounts = TriggerHelper.BalancedTemplateComposition(
this.GetAttackerComposition(time, true),
Math.max(0, attackersPerShip(time) - cmpGarrisonHolder.GetEntities().length));
this.debugLog("Filling ship " + ship + " with " + uneval(templateCounts));
for (let templateName in templateCounts)
{
let ents = TriggerHelper.SpawnGarrisonedUnits(ship, templateName, templateCounts[templateName], gaulPlayer);
if (danubiusAttackerTemplates.heroes.indexOf(templateName) != -1 && ents[0])
this.heroes.add(ents[0]);
}
}
this.fillShipsTimer = this.DoAfterDelay(shipFillInterval() * 60 * 1000, "FillShips", {});
};
/**
* Attack the closest enemy target around, then patrol the map.
*/
Trigger.prototype.AttackAndPatrol = function(entities, targetClass, triggerPointRef, debugName, attack)
{
if (!entities.length)
return;
let healers = TriggerHelper.MatchEntitiesByClass(entities, "Healer").filter(TriggerHelper.IsInWorld);
if (healers.length)
{
let healerTargets = TriggerHelper.MatchEntitiesByClass(entities, "Hero Champion");
if (!healerTargets.length)
healerTargets = TriggerHelper.MatchEntitiesByClass(entities, "Soldier");
ProcessCommand(gaulPlayer, {
"type": "guard",
"entities": healers,
"target": pickRandom(healerTargets),
"queued": false
});
}
let attackers = TriggerHelper.MatchEntitiesByClass(entities, "!Healer").filter(TriggerHelper.IsInWorld);
if (!attackers.length)
return;
let isLeft = this.IsLeftRiverside(attackers[0]);
let targets = TriggerHelper.MatchEntitiesByClass(TriggerHelper.GetAllPlayersEntities(), targetClass);
let closestTarget;
let minDistance = Infinity;
for (let target of targets)
{
if (!TriggerHelper.IsInWorld(target) || this.IsLeftRiverside(target) != isLeft)
continue;
let targetDistance = PositionHelper.DistanceBetweenEntities(attackers[0], target);
if (targetDistance < minDistance)
{
closestTarget = target;
minDistance = targetDistance;
}
}
this.debugLog(debugName + " " + uneval(attackers) + " attack " + uneval(closestTarget));
if (attack && closestTarget)
ProcessCommand(gaulPlayer, {
"type": "attack",
"entities": attackers,
"target": closestTarget,
"queued": true,
"allowCapture": false
});
let patrolTargets = shuffleArray(this.GetTriggerPoints(triggerPointRef)).slice(0, patrolCount);
this.debugLog(debugName + " " + uneval(attackers) + " patrol to " + uneval(patrolTargets));
for (let patrolTarget of patrolTargets)
{
let targetPos = TriggerHelper.GetEntityPosition2D(patrolTarget);
ProcessCommand(gaulPlayer, {
"type": "patrol",
"entities": attackers,
"x": targetPos.x,
"z": targetPos.y,
"targetClasses": {
"attack": targetClass
},
"queued": true,
"allowCapture": false
});
}
};
/**
* To avoid unloading unlimited amounts of units on empty riversides,
* only add attackers to riversides where player buildings exist that are
* actually targeted.
*/
Trigger.prototype.GetActiveRiversides = function()
{
let left = false;
let right = false;
for (let ent of TriggerHelper.GetAllPlayersEntitiesByClass(siegeTargetClass))
{
if (this.IsLeftRiverside(ent))
left = true;
else
right = true;
if (left && right)
break;
}
return [left, right];
};
Trigger.prototype.IsLeftRiverside = function(ent)
{
return Vector2D.sub(TriggerHelper.GetEntityPosition2D(ent), this.mapCenter).cross(this.riverDirection) < 0;
};
/**
* Order all ships to abort naval warfare and move to the shoreline all few minutes.
*/
Trigger.prototype.UngarrisonShipsOrder = function()
{
let [ungarrisonLeft, ungarrisonRight] = this.GetActiveRiversides();
if (!ungarrisonLeft && !ungarrisonRight)
return;
// Determine which ships should ungarrison on which side of the river
let ships = Array.from(this.ships);
let shipsLeft = [];
let shipsRight = [];
if (ungarrisonLeft && ungarrisonRight)
{
shipsLeft = shuffleArray(ships).slice(0, Math.round(ships.length / 2));
shipsRight = ships.filter(ship => shipsLeft.indexOf(ship) == -1);
}
else if (ungarrisonLeft)
shipsLeft = ships;
else if (ungarrisonRight)
shipsRight = ships;
// Determine which ships should ungarrison and patrol at which trigger point names
let sides = [];
if (shipsLeft.length)
sides.push({
"ships": shipsLeft,
"ungarrisonPointRef": triggerPointUngarrisonLeft,
"landPointRef": triggerPointLandPatrolLeft
});
if (shipsRight.length)
sides.push({
"ships": shipsRight,
"ungarrisonPointRef": triggerPointUngarrisonRight,
"landPointRef": triggerPointLandPatrolRight
});
// Order those ships to move to a randomly chosen trigger point on the determined
// side of the river. Remember that chosen ungarrison point and the name of the
// trigger points where the ungarrisoned units should patrol afterwards.
for (let side of sides)
for (let ship of side.ships)
{
let ungarrisonPoint = pickRandom(this.GetTriggerPoints(side.ungarrisonPointRef));
let ungarrisonPos = TriggerHelper.GetEntityPosition2D(ungarrisonPoint);
this.debugLog("Ship " + ship + " will ungarrison at " + side.ungarrisonPointRef +
" (" + ungarrisonPos.x + "," + ungarrisonPos.y + ")");
Engine.QueryInterface(ship, IID_UnitAI).Walk(ungarrisonPos.x, ungarrisonPos.y, false);
this.shipTarget[ship] = { "landPointRef": side.landPointRef, "ungarrisonPoint": ungarrisonPoint };
}
this.DoAfterDelay(shipUngarrisonInterval() * 60 * 1000, "UngarrisonShipsOrder", {});
};
/**
* Check frequently whether the ships are close enough to unload at the shoreline.
*/
Trigger.prototype.CheckShipRange = function()
{
for (let ship of this.ships)
{
if (!this.shipTarget[ship] || PositionHelper.DistanceBetweenEntities(ship, this.shipTarget[ship].ungarrisonPoint) > shipUngarrisonDistance)
continue;
let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder);
if (!cmpGarrisonHolder)
continue;
let humans = TriggerHelper.MatchEntitiesByClass(cmpGarrisonHolder.GetEntities(), "Human");
let siegeEngines = TriggerHelper.MatchEntitiesByClass(cmpGarrisonHolder.GetEntities(), "Siege");
this.debugLog("Ungarrisoning ship " + ship + " at " + uneval(this.shipTarget[ship]));
cmpGarrisonHolder.UnloadAll();
this.AttackAndPatrol([ship], shipTargetClass, triggerPointShipPatrol, "Ships", true);
if (randBool(formationProbability))
TriggerHelper.SetUnitFormation(gaulPlayer, humans, pickRandom(unitFormations));
this.AttackAndPatrol(siegeEngines, siegeTargetClass, this.shipTarget[ship].landPointRef, "Siege Engines", true);
// Order soldiers at last, so the follow-player observer feature focuses the soldiers
this.AttackAndPatrol(humans, unitTargetClass, this.shipTarget[ship].landPointRef, "Units", true);
delete this.shipTarget[ship];
}
};
Trigger.prototype.DanubiusOwnershipChange = function(data)
{
if (data.from != 0)
return;
if (this.heroes.delete(data.entity))
this.debugLog("Hero " + data.entity + " died");
if (this.ships.delete(data.entity))
this.debugLog("Ship " + data.entity + " sunk");
if (this.civicCenters.delete(data.entity))
this.debugLog("Gaia civic center " + data.entity + " destroyed or captured");
this.ritualEnts.delete(data.entity);
};
Trigger.prototype.InitDanubius = function()
{
// Set a custom animation of idle ritual units frequently
this.ritualEnts = new Set();
// To prevent spawning more than the limits, track IDs of current entities
this.ships = new Set();
this.heroes = new Set();
// Remember gaia CCs to spawn attackers from
this.civicCenters = new Set(TriggerHelper.GetPlayerEntitiesByClass(gaulPlayer, "CivCentre"));
// Depends on this.heroes
Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).RegisterTrigger("OnOwnershipChanged", "DanubiusOwnershipChange", { "enabled": true });
// Maps from gaia ship entity ID to ungarrison trigger point entity ID and land patrol triggerpoint name
this.shipTarget = {};
this.fillShipsTimer = undefined;
// Be able to distinguish between the left and right riverside
- // TODO: The Vector2D types don't survive deserialization, so use an object with x and y properties only!
let mapSize = TriggerHelper.GetMapSizeTerrain();
- this.mapCenter = clone(new Vector2D(mapSize / 2, mapSize / 2));
+ this.mapCenter = new Vector2D(mapSize / 2, mapSize / 2);
- this.riverDirection = clone(Vector2D.sub(
+ this.riverDirection = Vector2D.sub(
TriggerHelper.GetEntityPosition2D(this.GetTriggerPoints(triggerPointRiverDirection)[0]),
- this.mapCenter));
+ this.mapCenter);
this.StartCelticRitual();
this.GarrisonAllGallicBuildings();
this.SpawnInitialCCDefenders();
this.SpawnCCAttackers();
this.SpawnShips();
this.DoAfterDelay(shipUngarrisonInterval() * 60 * 1000, "UngarrisonShipsOrder", {});
this.DoRepeatedly(5 * 1000, "CheckShipRange", {});
};
{
Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).RegisterTrigger("OnInitGame", "InitDanubius", { "enabled": true });
}
Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 24461)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 24462)
@@ -1,970 +1,976 @@
function Player() {}
Player.prototype.Schema =
"" +
"" +
"" +
Resources.BuildSchema("positiveDecimal") +
"" +
"" +
Resources.BuildSchema("positiveDecimal") +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
/**
* Don't serialize diplomacyColor or displayDiplomacyColor since they're modified by the GUI.
*/
Player.prototype.Serialize = function()
{
let state = {};
for (let key in this)
if (this.hasOwnProperty(key))
state[key] = this[key];
state.diplomacyColor = undefined;
state.displayDiplomacyColor = false;
return state;
};
+Player.prototype.Deserialize = function(state)
+{
+ for (let prop in state)
+ this[prop] = state[prop];
+};
+
/**
* Which units will be shown with special icons at the top.
*/
var panelEntityClasses = "Hero Relic";
Player.prototype.Init = function()
{
this.playerID = undefined;
this.name = undefined; // Define defaults elsewhere (supporting other languages).
this.civ = undefined;
this.color = undefined;
this.diplomacyColor = undefined;
this.displayDiplomacyColor = false;
this.popUsed = 0; // Population of units owned or trained by this player.
this.popBonuses = 0; // Sum of population bonuses of player's entities.
this.maxPop = 300; // Maximum population.
this.trainingBlocked = false; // Indicates whether any training queue is currently blocked.
this.resourceCount = {};
this.resourceGatherers = {};
this.tradingGoods = []; // Goods for next trade-route and its probabilities * 100.
this.team = -1; // Team number of the player, players on the same team will always have ally diplomatic status. Also this is useful for team emblems, scoring, etc.
this.teamsLocked = false;
this.state = "active"; // Game state. One of "active", "defeated", "won".
this.diplomacy = []; // Array of diplomatic stances for this player with respect to other players (including gaia and self).
this.sharedDropsites = false;
this.formations = [];
this.startCam = undefined;
this.controlAllUnits = false;
this.isAI = false;
this.cheatsEnabled = false;
this.panelEntities = [];
this.resourceNames = {};
this.disabledTemplates = {};
this.disabledTechnologies = {};
this.startingTechnologies = [];
this.spyCostMultiplier = +this.template.SpyCostMultiplier;
this.barterEntities = [];
this.barterMultiplier = {
"buy": clone(this.template.BarterMultiplier.Buy),
"sell": clone(this.template.BarterMultiplier.Sell)
};
// Initial resources.
let resCodes = Resources.GetCodes();
for (let res of resCodes)
{
this.resourceCount[res] = 300;
this.resourceNames[res] = Resources.GetResource(res).name;
this.resourceGatherers[res] = 0;
}
// Trading goods probability in steps of 5.
let resTradeCodes = Resources.GetTradableCodes();
let quotient = Math.floor(20 / resTradeCodes.length);
let remainder = 20 % resTradeCodes.length;
for (let i in resTradeCodes)
this.tradingGoods.push({
"goods": resTradeCodes[i],
"proba": 5 * (quotient + (+i < remainder ? 1 : 0))
});
};
Player.prototype.SetPlayerID = function(id)
{
this.playerID = id;
};
Player.prototype.GetPlayerID = function()
{
return this.playerID;
};
Player.prototype.SetName = function(name)
{
this.name = name;
};
Player.prototype.GetName = function()
{
return this.name;
};
Player.prototype.SetCiv = function(civcode)
{
let oldCiv = this.civ;
this.civ = civcode;
// Normally, the civ is only set once. But in Atlas, map designers can change civs at any time.
if (oldCiv && this.playerID && oldCiv != civcode)
Engine.BroadcastMessage(MT_CivChanged, {
"player": this.playerID,
"from": oldCiv,
"to": civcode
});
};
Player.prototype.GetCiv = function()
{
return this.civ;
};
Player.prototype.SetColor = function(r, g, b)
{
let colorInitialized = !!this.color;
this.color = { "r": r / 255, "g": g / 255, "b": b / 255, "a": 1 };
// Used in Atlas.
if (colorInitialized)
Engine.BroadcastMessage(MT_PlayerColorChanged, {
"player": this.playerID
});
};
Player.prototype.SetDiplomacyColor = function(color)
{
this.diplomacyColor = { "r": color.r / 255, "g": color.g / 255, "b": color.b / 255, "a": 1 };
};
Player.prototype.SetDisplayDiplomacyColor = function(displayDiplomacyColor)
{
this.displayDiplomacyColor = displayDiplomacyColor;
};
Player.prototype.GetColor = function()
{
return this.color;
};
Player.prototype.GetDisplayedColor = function()
{
return this.displayDiplomacyColor ? this.diplomacyColor : this.color;
};
// Try reserving num population slots. Returns 0 on success or number of missing slots otherwise.
Player.prototype.TryReservePopulationSlots = function(num)
{
if (num != 0 && num > (this.GetPopulationLimit() - this.popUsed))
return num - (this.GetPopulationLimit() - this.popUsed);
this.popUsed += num;
return 0;
};
Player.prototype.UnReservePopulationSlots = function(num)
{
this.popUsed -= num;
};
Player.prototype.GetPopulationCount = function()
{
return this.popUsed;
};
Player.prototype.AddPopulation = function(num)
{
this.popUsed += num;
};
Player.prototype.SetPopulationBonuses = function(num)
{
this.popBonuses = num;
};
Player.prototype.AddPopulationBonuses = function(num)
{
this.popBonuses += num;
};
Player.prototype.GetPopulationLimit = function()
{
return Math.min(this.GetMaxPopulation(), this.popBonuses);
};
Player.prototype.SetMaxPopulation = function(max)
{
this.maxPop = max;
};
Player.prototype.GetMaxPopulation = function()
{
return Math.round(ApplyValueModificationsToEntity("Player/MaxPopulation", this.maxPop, this.entity));
};
Player.prototype.CanBarter = function()
{
return this.barterEntities.length > 0;
};
Player.prototype.GetBarterMultiplier = function()
{
return this.barterMultiplier;
};
Player.prototype.GetSpyCostMultiplier = function()
{
return this.spyCostMultiplier;
};
Player.prototype.GetPanelEntities = function()
{
return this.panelEntities;
};
Player.prototype.IsTrainingBlocked = function()
{
return this.trainingBlocked;
};
Player.prototype.BlockTraining = function()
{
this.trainingBlocked = true;
};
Player.prototype.UnBlockTraining = function()
{
this.trainingBlocked = false;
};
Player.prototype.SetResourceCounts = function(resources)
{
for (let res in resources)
this.resourceCount[res] = resources[res];
};
Player.prototype.GetResourceCounts = function()
{
return this.resourceCount;
};
Player.prototype.GetResourceGatherers = function()
{
return this.resourceGatherers;
};
/**
* @param {string} type - The generic type of resource to add the gatherer for.
*/
Player.prototype.AddResourceGatherer = function(type)
{
++this.resourceGatherers[type];
};
/**
* @param {string} type - The generic type of resource to remove the gatherer from.
*/
Player.prototype.RemoveResourceGatherer = function(type)
{
--this.resourceGatherers[type];
};
/**
* Add resource of specified type to player.
* @param {string} type - Generic type of resource.
* @param {number} amount - Amount of resource, which should be added.
*/
Player.prototype.AddResource = function(type, amount)
{
this.resourceCount[type] += +amount;
};
/**
* Add resources to player.
*/
Player.prototype.AddResources = function(amounts)
{
for (let type in amounts)
this.resourceCount[type] += +amounts[type];
};
Player.prototype.GetNeededResources = function(amounts)
{
// Check if we can afford it all.
let amountsNeeded = {};
for (let type in amounts)
if (this.resourceCount[type] != undefined && amounts[type] > this.resourceCount[type])
amountsNeeded[type] = amounts[type] - Math.floor(this.resourceCount[type]);
if (Object.keys(amountsNeeded).length == 0)
return undefined;
return amountsNeeded;
};
Player.prototype.SubtractResourcesOrNotify = function(amounts)
{
let amountsNeeded = this.GetNeededResources(amounts);
// If we don't have enough resources, send a notification to the player.
if (amountsNeeded)
{
let parameters = {};
let i = 0;
for (let type in amountsNeeded)
{
++i;
parameters["resourceType" + i] = this.resourceNames[type];
parameters["resourceAmount" + i] = amountsNeeded[type];
}
let msg = "";
// When marking strings for translations, you need to include the actual string,
// not some way to derive the string.
if (i < 1)
warn("Amounts needed but no amounts given?");
else if (i == 1)
msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s");
else if (i == 2)
msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s");
else if (i == 3)
msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s");
else if (i == 4)
msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s, %(resourceAmount4)s %(resourceType4)s");
else
warn("Localisation: Strings are not localised for more than 4 resources");
// Send as time-notification.
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [this.playerID],
"message": msg,
"parameters": parameters,
"translateMessage": true,
"translateParameters": {
"resourceType1": "withinSentence",
"resourceType2": "withinSentence",
"resourceType3": "withinSentence",
"resourceType4": "withinSentence"
}
});
return false;
}
for (let type in amounts)
this.resourceCount[type] -= amounts[type];
return true;
};
Player.prototype.TrySubtractResources = function(amounts)
{
if (!this.SubtractResourcesOrNotify(amounts))
return false;
let cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpStatisticsTracker)
for (let type in amounts)
cmpStatisticsTracker.IncreaseResourceUsedCounter(type, amounts[type]);
return true;
};
Player.prototype.GetNextTradingGoods = function()
{
let value = randFloat(0, 100);
let last = this.tradingGoods.length - 1;
let sumProba = 0;
for (let i = 0; i < last; ++i)
{
sumProba += this.tradingGoods[i].proba;
if (value < sumProba)
return this.tradingGoods[i].goods;
}
return this.tradingGoods[last].goods;
};
Player.prototype.GetTradingGoods = function()
{
let tradingGoods = {};
for (let resource of this.tradingGoods)
tradingGoods[resource.goods] = resource.proba;
return tradingGoods;
};
Player.prototype.SetTradingGoods = function(tradingGoods)
{
let resTradeCodes = Resources.GetTradableCodes();
let sumProba = 0;
for (let resource in tradingGoods)
{
if (resTradeCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0)
{
error("Invalid trading goods: " + uneval(tradingGoods));
return;
}
sumProba += tradingGoods[resource];
}
if (sumProba != 100)
{
error("Invalid trading goods probability: " + uneval(sumProba));
return;
}
this.tradingGoods = [];
for (let resource in tradingGoods)
this.tradingGoods.push({
"goods": resource,
"proba": tradingGoods[resource]
});
};
Player.prototype.GetState = function()
{
return this.state;
};
/**
* @param {string} newState - Either "defeated" or "won".
* @param {string|undefined} message - A string to be shown in chat, for example
* markForTranslation("%(player)s has been defeated (failed objective).").
* If it is undefined, the caller MUST send that GUI notification manually.
*/
Player.prototype.SetState = function(newState, message)
{
if (this.state != "active")
return;
if (newState != "won" && newState != "defeated")
{
warn("Can't change playerstate to " + this.state);
return;
}
if (!this.playerID)
{
warn("Gaia can't change state.");
return;
}
this.state = newState;
let won = newState == "won";
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (won)
cmpRangeManager.SetLosRevealAll(this.playerID, true);
else
{
// Reassign all player's entities to Gaia.
let entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID);
// The ownership change is done in two steps so that entities don't hit idle
// (and thus possibly look for "enemies" to attack) before nearby allies get
// converted to Gaia as well.
for (let entity of entities)
{
let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
cmpOwnership.SetOwnerQuiet(0);
}
// With the real ownership change complete, send OwnershipChanged messages.
for (let entity of entities)
Engine.PostMessage(entity, MT_OwnershipChanged, {
"entity": entity,
"from": this.playerID,
"to": 0
});
}
Engine.PostMessage(this.entity, won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID });
if (message)
{
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"type": won ? "won" : "defeat",
"players": [this.playerID],
"allies": [this.playerID],
"message": message
});
}
};
Player.prototype.GetTeam = function()
{
return this.team;
};
Player.prototype.SetTeam = function(team)
{
if (this.teamsLocked)
return;
this.team = team;
// Set all team members as allies.
if (this.team != -1)
{
let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
for (let i = 0; i < numPlayers; ++i)
{
let cmpPlayer = QueryPlayerIDInterface(i);
if (this.team != cmpPlayer.GetTeam())
continue;
this.SetAlly(i);
cmpPlayer.SetAlly(this.playerID);
}
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {
"player": this.playerID,
"otherPlayer": null
});
};
Player.prototype.SetLockTeams = function(value)
{
this.teamsLocked = value;
};
Player.prototype.GetLockTeams = function()
{
return this.teamsLocked;
};
Player.prototype.GetDiplomacy = function()
{
return this.diplomacy.slice();
};
Player.prototype.SetDiplomacy = function(dipl)
{
this.diplomacy = dipl.slice();
Engine.BroadcastMessage(MT_DiplomacyChanged, {
"player": this.playerID,
"otherPlayer": null
});
};
Player.prototype.SetDiplomacyIndex = function(idx, value)
{
let cmpPlayer = QueryPlayerIDInterface(idx);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
this.diplomacy[idx] = value;
Engine.BroadcastMessage(MT_DiplomacyChanged, {
"player": this.playerID,
"otherPlayer": cmpPlayer.GetPlayerID()
});
// Mutual worsening of relations.
if (cmpPlayer.diplomacy[this.playerID] > value)
cmpPlayer.SetDiplomacyIndex(this.playerID, value);
};
Player.prototype.UpdateSharedLos = function()
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
if (!cmpRangeManager || !cmpTechnologyManager)
return;
if (!cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech))
{
cmpRangeManager.SetSharedLos(this.playerID, [this.playerID]);
return;
}
cmpRangeManager.SetSharedLos(this.playerID, this.GetMutualAllies());
};
Player.prototype.GetFormations = function()
{
return this.formations;
};
Player.prototype.SetFormations = function(formations)
{
this.formations = formations;
};
Player.prototype.GetStartingCameraPos = function()
{
return this.startCam.position;
};
Player.prototype.GetStartingCameraRot = function()
{
return this.startCam.rotation;
};
Player.prototype.SetStartingCamera = function(pos, rot)
{
this.startCam = { "position": pos, "rotation": rot };
};
Player.prototype.HasStartingCamera = function()
{
return this.startCam !== undefined;
};
Player.prototype.HasSharedLos = function()
{
let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
return cmpTechnologyManager && cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech);
};
Player.prototype.HasSharedDropsites = function()
{
return this.sharedDropsites;
};
Player.prototype.SetControlAllUnits = function(c)
{
this.controlAllUnits = c;
};
Player.prototype.CanControlAllUnits = function()
{
return this.controlAllUnits;
};
Player.prototype.SetAI = function(flag)
{
this.isAI = flag;
};
Player.prototype.IsAI = function()
{
return this.isAI;
};
Player.prototype.GetPlayersByDiplomacy = function(func)
{
let players = [];
for (let i = 0; i < this.diplomacy.length; ++i)
if (this[func](i))
players.push(i);
return players;
};
Player.prototype.SetAlly = function(id)
{
this.SetDiplomacyIndex(id, 1);
};
/**
* Check if given player is our ally.
*/
Player.prototype.IsAlly = function(id)
{
return this.diplomacy[id] > 0;
};
Player.prototype.GetAllies = function()
{
return this.GetPlayersByDiplomacy("IsAlly");
};
/**
* Check if given player is our ally excluding ourself
*/
Player.prototype.IsExclusiveAlly = function(id)
{
return this.playerID != id && this.IsAlly(id);
};
/**
* Check if given player is our ally, and we are its ally
*/
Player.prototype.IsMutualAlly = function(id)
{
let cmpPlayer = QueryPlayerIDInterface(id);
return this.IsAlly(id) && cmpPlayer && cmpPlayer.IsAlly(this.playerID);
};
Player.prototype.GetMutualAllies = function()
{
return this.GetPlayersByDiplomacy("IsMutualAlly");
};
/**
* Check if given player is our ally, and we are its ally, excluding ourself
*/
Player.prototype.IsExclusiveMutualAlly = function(id)
{
return this.playerID != id && this.IsMutualAlly(id);
};
Player.prototype.SetEnemy = function(id)
{
this.SetDiplomacyIndex(id, -1);
};
/**
* Check if given player is our enemy
*/
Player.prototype.IsEnemy = function(id)
{
return this.diplomacy[id] < 0;
};
Player.prototype.GetEnemies = function()
{
return this.GetPlayersByDiplomacy("IsEnemy");
};
Player.prototype.SetNeutral = function(id)
{
this.SetDiplomacyIndex(id, 0);
};
/**
* Check if given player is neutral
*/
Player.prototype.IsNeutral = function(id)
{
return this.diplomacy[id] == 0;
};
/**
* Do some map dependant initializations
*/
Player.prototype.OnGlobalInitGame = function(msg)
{
let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
if (cmpTechnologyManager)
for (let tech of this.startingTechnologies)
cmpTechnologyManager.ResearchTechnology(tech);
// Replace the "{civ}" code with this civ ID.
let disabledTemplates = this.disabledTemplates;
this.disabledTemplates = {};
for (let template in disabledTemplates)
if (disabledTemplates[template])
this.disabledTemplates[template.replace(/\{civ\}/g, this.civ)] = true;
};
/**
* Keep track of population effects of all entities that
* become owned or unowned by this player.
*/
Player.prototype.OnGlobalOwnershipChanged = function(msg)
{
if (msg.from != this.playerID && msg.to != this.playerID)
return;
let cmpCost = Engine.QueryInterface(msg.entity, IID_Cost);
if (msg.from == this.playerID)
{
if (cmpCost)
this.popUsed -= cmpCost.GetPopCost();
let panelIndex = this.panelEntities.indexOf(msg.entity);
if (panelIndex >= 0)
this.panelEntities.splice(panelIndex, 1);
let barterIndex = this.barterEntities.indexOf(msg.entity);
if (barterIndex >= 0)
this.barterEntities.splice(barterIndex, 1);
}
if (msg.to == this.playerID)
{
if (cmpCost)
this.popUsed += cmpCost.GetPopCost();
let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (!cmpIdentity)
return;
if (MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses))
this.panelEntities.push(msg.entity);
if (cmpIdentity.HasClass("Barter") && !Engine.QueryInterface(msg.entity, IID_Foundation))
this.barterEntities.push(msg.entity);
}
};
Player.prototype.OnResearchFinished = function(msg)
{
if (msg.tech == this.template.SharedLosTech)
this.UpdateSharedLos();
else if (msg.tech == this.template.SharedDropsitesTech)
this.sharedDropsites = true;
};
Player.prototype.OnDiplomacyChanged = function()
{
this.UpdateSharedLos();
};
Player.prototype.OnValueModification = function(msg)
{
if (msg.component != "Player")
return;
if (msg.valueNames.indexOf("Player/SpyCostMultiplier") != -1)
this.spyCostMultiplier = ApplyValueModificationsToEntity("Player/SpyCostMultiplier", +this.template.SpyCostMultiplier, this.entity);
if (msg.valueNames.some(mod => mod.startsWith("Player/BarterMultiplier/")))
for (let res in this.template.BarterMultiplier.Buy)
{
this.barterMultiplier.buy[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Buy/"+res, +this.template.BarterMultiplier.Buy[res], this.entity);
this.barterMultiplier.sell[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Sell/"+res, +this.template.BarterMultiplier.Sell[res], this.entity);
}
};
Player.prototype.SetCheatsEnabled = function(flag)
{
this.cheatsEnabled = flag;
};
Player.prototype.GetCheatsEnabled = function()
{
return this.cheatsEnabled;
};
Player.prototype.TributeResource = function(player, amounts)
{
let cmpPlayer = QueryPlayerIDInterface(player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
let resTribCodes = Resources.GetTributableCodes();
for (let resCode in amounts)
if (resTribCodes.indexOf(resCode) == -1 ||
!Number.isInteger(amounts[resCode]) ||
amounts[resCode] < 0)
{
warn("Invalid tribute amounts: " + uneval(resCode) + ": " + uneval(amounts));
return;
}
if (!this.SubtractResourcesOrNotify(amounts))
return;
cmpPlayer.AddResources(amounts);
let total = Object.keys(amounts).reduce((sum, type) => sum + amounts[type], 0);
let cmpOurStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpOurStatisticsTracker)
cmpOurStatisticsTracker.IncreaseTributesSentCounter(total);
let cmpTheirStatisticsTracker = QueryPlayerIDInterface(player, IID_StatisticsTracker);
if (cmpTheirStatisticsTracker)
cmpTheirStatisticsTracker.IncreaseTributesReceivedCounter(total);
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (cmpGUIInterface)
cmpGUIInterface.PushNotification({
"type": "tribute",
"players": [player],
"donator": this.playerID,
"amounts": amounts
});
Engine.BroadcastMessage(MT_TributeExchanged, {
"to": player,
"from": this.playerID,
"amounts": amounts
});
};
Player.prototype.AddDisabledTemplate = function(template)
{
this.disabledTemplates[template] = true;
Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
};
Player.prototype.RemoveDisabledTemplate = function(template)
{
this.disabledTemplates[template] = false;
Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
};
Player.prototype.SetDisabledTemplates = function(templates)
{
this.disabledTemplates = {};
for (let template of templates)
this.disabledTemplates[template] = true;
Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
};
Player.prototype.GetDisabledTemplates = function()
{
return this.disabledTemplates;
};
Player.prototype.AddDisabledTechnology = function(tech)
{
this.disabledTechnologies[tech] = true;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
};
Player.prototype.RemoveDisabledTechnology = function(tech)
{
this.disabledTechnologies[tech] = false;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
};
Player.prototype.SetDisabledTechnologies = function(techs)
{
this.disabledTechnologies = {};
for (let tech of techs)
this.disabledTechnologies[tech] = true;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
};
Player.prototype.GetDisabledTechnologies = function()
{
return this.disabledTechnologies;
};
Player.prototype.AddStartingTechnology = function(tech)
{
if (this.startingTechnologies.indexOf(tech) == -1)
this.startingTechnologies.push(tech);
};
Player.prototype.SetStartingTechnologies = function(techs)
{
this.startingTechnologies = techs;
};
Player.prototype.OnGlobalPlayerDefeated = function(msg)
{
let cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
if (!cmpSound)
return;
let soundGroup = cmpSound.GetSoundGroup(this.playerID === msg.playerId ? "defeated" : this.IsAlly(msg.playerId) ? "defeated_ally" : this.state === "won" ? "won" : "defeated_enemy");
if (soundGroup)
Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager).PlaySoundGroupForPlayer(soundGroup, this.playerID);
};
Engine.RegisterComponentType(IID_Player, "Player", Player);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 24461)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Setup.js (revision 24462)
@@ -1,72 +1,72 @@
/**
* Used to initialize non-player settings relevant to the map, like
* default stance and victory conditions. DO NOT load players here
*/
function LoadMapSettings(settings)
{
if (!settings)
settings = {};
if (settings.DefaultStance)
for (let ent of Engine.GetEntitiesWithInterface(IID_UnitAI))
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.SwitchToStance(settings.DefaultStance);
}
if (settings.RevealMap)
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetLosRevealAll(-1, true);
}
if (settings.DisableTreasures)
for (let ent of Engine.GetEntitiesWithInterface(IID_ResourceSupply))
{
let cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply.GetType().generic == "treasure")
Engine.DestroyEntity(ent);
}
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager)
cmpRangeManager.SetLosCircular(!!settings.CircularMap);
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
if (cmpObstructionManager)
cmpObstructionManager.SetPassabilityCircular(!!settings.CircularMap);
if (settings.TriggerDifficulty !== undefined)
Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).SetDifficulty(settings.TriggerDifficulty);
else if (settings.SupportedTriggerDifficulties) // used by Atlas and autostart games
{
let difficulties = Engine.ReadJSONFile("simulation/data/settings/trigger_difficulties.json").Data;
let defaultDiff = difficulties.find(d => d.Name == settings.SupportedTriggerDifficulties.Default).Difficulty;
Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).SetDifficulty(defaultDiff);
}
let cmpEndGameManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_EndGameManager);
- let gameSettings = { "victoryConditions": settings.VictoryConditions };
+ let gameSettings = { "victoryConditions": clone(settings.VictoryConditions) };
if (gameSettings.victoryConditions.indexOf("capture_the_relic") != -1)
{
gameSettings.relicCount = settings.RelicCount;
gameSettings.relicDuration = settings.RelicDuration * 60 * 1000;
}
if (gameSettings.victoryConditions.indexOf("wonder") != -1)
gameSettings.wonderDuration = settings.WonderDuration * 60 * 1000;
if (gameSettings.victoryConditions.indexOf("regicide") != -1)
gameSettings.regicideGarrison = settings.RegicideGarrison;
cmpEndGameManager.SetGameSettings(gameSettings);
cmpEndGameManager.SetAlliedVictory(settings.LockTeams || !settings.LastManStanding);
if (settings.LockTeams && settings.LastManStanding)
warn("Last man standing is only available in games with unlocked teams!");
let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);
if (settings.Ceasefire)
cmpCeasefireManager.StartCeasefire(settings.Ceasefire * 60 * 1000);
}
Engine.RegisterGlobal("LoadMapSettings", LoadMapSettings);
Index: ps/trunk/source/scriptinterface/ScriptInterface.cpp
===================================================================
--- ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 24461)
+++ ps/trunk/source/scriptinterface/ScriptInterface.cpp (revision 24462)
@@ -1,1051 +1,1056 @@
/* Copyright (C) 2020 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 "ScriptContext.h"
#include "ScriptExtraHeaders.h"
#include "ScriptInterface.h"
#include "ScriptStats.h"
#include "lib/debug.h"
#include "lib/utf8.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/utf16string.h"
#include
#include