Index: ps/trunk/binaries/data/mods/public/simulation/components/Cost.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Cost.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Cost.js (revision 22964)
@@ -1,119 +1,117 @@
function Cost() {}
Cost.prototype.Schema =
"Specifies the construction/training costs of this entity." +
"" +
"1" +
"15" +
"20.0" +
"" +
"50" +
"0" +
"0" +
"25" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
Resources.BuildSchema("nonNegativeInteger") +
"";
Cost.prototype.Init = function()
{
this.populationCost = +this.template.Population;
this.populationBonus = +this.template.PopulationBonus;
};
Cost.prototype.GetPopCost = function()
{
return this.populationCost;
};
Cost.prototype.GetPopBonus = function()
{
return this.populationBonus;
};
Cost.prototype.GetBuildTime = function()
{
- var cmpPlayer = QueryOwnerInterface(this.entity);
- var buildTime = (+this.template.BuildTime) * cmpPlayer.GetTimeMultiplier();
- return ApplyValueModificationsToEntity("Cost/BuildTime", buildTime, this.entity);
+ return ApplyValueModificationsToEntity("Cost/BuildTime", +this.template.BuildTime, this.entity);
};
Cost.prototype.GetResourceCosts = function(owner)
{
if (!owner)
{
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
error("GetResourceCosts called without valid ownership");
else
owner = cmpOwnership.GetOwner();
}
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let entityTemplateName = cmpTemplateManager.GetCurrentTemplateName(this.entity);
let entityTemplate = cmpTemplateManager.GetTemplate(entityTemplateName);
let costs = {};
for (let res in this.template.Resources)
costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +this.template.Resources[res], owner, entityTemplate);
return costs;
};
Cost.prototype.OnOwnershipChanged = function(msg)
{
if (msg.from != INVALID_PLAYER)
{
let cmpPlayer = QueryPlayerIDInterface(msg.from);
if (cmpPlayer)
cmpPlayer.AddPopulationBonuses(-this.GetPopBonus());
}
if (msg.to != INVALID_PLAYER)
{
let cmpPlayer = QueryPlayerIDInterface(msg.to);
if (cmpPlayer)
cmpPlayer.AddPopulationBonuses(this.GetPopBonus());
}
};
Cost.prototype.OnValueModification = function(msg)
{
if (msg.component != "Cost")
return;
// foundations shouldn't give a pop bonus and a pop cost
var cmpFoundation = Engine.QueryInterface(this.entity, IID_Foundation);
if (cmpFoundation)
return;
// update the population costs
var newPopCost = Math.round(ApplyValueModificationsToEntity("Cost/Population", +this.template.Population, this.entity));
var popCostDifference = newPopCost - this.populationCost;
this.populationCost = newPopCost;
// update the population bonuses
var newPopBonus = Math.round(ApplyValueModificationsToEntity("Cost/PopulationBonus", +this.template.PopulationBonus, this.entity));
var popDifference = newPopBonus - this.populationBonus;
this.populationBonus = newPopBonus;
var cmpPlayer = QueryOwnerInterface(this.entity);
if (!cmpPlayer)
return;
if (popCostDifference)
cmpPlayer.AddPopulation(popCostDifference);
if (popDifference)
cmpPlayer.AddPopulationBonuses(popDifference);
};
Engine.RegisterComponentType(IID_Cost, "Cost", Cost);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Pack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Pack.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Pack.js (revision 22964)
@@ -1,141 +1,139 @@
function Pack() {}
const PACKING_INTERVAL = 250;
Pack.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"packed" +
"unpacked" +
"" +
"";
Pack.prototype.Init = function()
{
this.packed = this.template.State == "packed";
this.packing = false;
this.elapsedTime = 0;
this.timer = undefined;
};
Pack.prototype.OnDestroy = function()
{
this.CancelTimer();
};
Pack.prototype.CancelTimer = function()
{
if (this.timer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
}
};
Pack.prototype.IsPacked = function()
{
return this.packed;
};
Pack.prototype.IsPacking = function()
{
return this.packing;
};
Pack.prototype.Pack = function()
{
if (this.IsPacked() || this.IsPacking())
return;
this.packing = true;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, { "packing": true });
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SelectAnimation("packing", true, 1.0, "packing");
};
Pack.prototype.Unpack = function()
{
if (!this.IsPacked() || this.IsPacking())
return;
this.packing = true;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, { "packing": false });
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SelectAnimation("unpacking", true, 1.0);
};
Pack.prototype.CancelPack = function()
{
if (!this.IsPacking())
return;
this.CancelTimer();
this.packing = false;
this.SetElapsedTime(0);
// Clear animation
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
cmpVisual.SelectAnimation("idle", false, 1.0);
};
Pack.prototype.GetPackTime = function()
{
- let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
-
- return ApplyValueModificationsToEntity("Pack/Time", +this.template.Time, this.entity) * cmpPlayer.GetTimeMultiplier();
+ return ApplyValueModificationsToEntity("Pack/Time", +this.template.Time, this.entity);
};
Pack.prototype.GetElapsedTime = function()
{
return this.elapsedTime;
};
Pack.prototype.GetProgress = function()
{
return Math.min(this.elapsedTime / this.GetPackTime(), 1);
};
Pack.prototype.SetElapsedTime = function(time)
{
this.elapsedTime = time;
Engine.PostMessage(this.entity, MT_PackProgressUpdate, { "progress": this.elapsedTime });
};
Pack.prototype.PackProgress = function(data, lateness)
{
if (this.elapsedTime < this.GetPackTime())
{
this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
return;
}
this.CancelTimer();
this.packed = !this.packed;
this.packing = false;
Engine.PostMessage(this.entity, MT_PackFinished, { "packed": this.packed });
let newEntity = ChangeEntityTemplate(this.entity, this.template.Entity);
if (newEntity)
PlaySound(this.packed ? "packed" : "unpacked", newEntity);
};
Engine.RegisterComponentType(IID_Pack, "Pack", Pack);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 22964)
@@ -1,983 +1,937 @@
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;
};
/**
* 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.tradingGoods = []; // goods for next trade-route and its proba in % (the sum of probas must be 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.timeMultiplier = 1;
- this.gatherRateMultiplier = 1;
- this.tradeRateMultiplier = 1;
this.cheatsEnabled = false;
- this.cheatTimeMultiplier = 1;
this.panelEntities = [];
this.resourceNames = {};
this.disabledTemplates = {};
this.disabledTechnologies = {};
this.startingTechnologies = [];
this.spyCostMultiplier = +this.template.SpyCostMultiplier;
this.barterMultiplier = {
"buy": clone(this.template.BarterMultiplier.Buy),
"sell": clone(this.template.BarterMultiplier.Sell)
};
// Initial resources and trading goods probability in steps of 5
let resCodes = Resources.GetCodes();
let quotient = Math.floor(20 / resCodes.length);
let remainder = 20 % resCodes.length;
for (let i in resCodes)
{
let res = resCodes[i];
this.resourceCount[res] = 300;
this.resourceNames[res] = Resources.GetResource(res).name;
this.tradingGoods.push({
"goods": res,
"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)
{
var oldCiv = this.civ;
this.civ = civcode;
// Normally, the civ is only set once
// But in Atlas, the map designers can change civs at any time
var playerID = this.GetPlayerID();
if (oldCiv && playerID && oldCiv != civcode)
Engine.BroadcastMessage(MT_CivChanged, {
"player": playerID,
"from": oldCiv,
"to": civcode
});
};
Player.prototype.GetCiv = function()
{
return this.civ;
};
Player.prototype.SetColor = function(r, g, b)
{
var 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.GetPopulationCount()))
return num - (this.GetPopulationLimit() - this.GetPopulationCount());
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.GetBarterMultiplier = function()
{
return this.barterMultiplier;
};
-Player.prototype.GetGatherRateMultiplier = function()
-{
- return this.gatherRateMultiplier / this.cheatTimeMultiplier;
-};
-
-Player.prototype.GetTimeMultiplier = function()
-{
- return this.timeMultiplier * this.cheatTimeMultiplier;
-};
-
-Player.prototype.GetTradeRateMultiplier = function()
-{
- return this.tradeRateMultiplier;
-};
-
Player.prototype.GetSpyCostMultiplier = function()
{
return this.spyCostMultiplier;
};
/**
* Setters currently used by the AI to set the difficulty level
*/
-Player.prototype.SetGatherRateMultiplier = function(value)
-{
- this.gatherRateMultiplier = value;
- Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "gather" });
-};
-
-Player.prototype.SetTimeMultiplier = function(value)
-{
- this.timeMultiplier = value;
- Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "time" });
-};
-
-Player.prototype.SetTradeRateMultiplier = function(value)
-{
- this.tradeRateMultiplier = value;
-};
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;
};
/**
* Add resource of specified type to player
* @param type Generic type of resource (string)
* @param amount Amount of resource, which should be added (integer)
*/
Player.prototype.AddResource = function(type, amount)
{
this.resourceCount[type] += +amount;
};
/**
* Add resources to player
*/
Player.prototype.AddResources = function(amounts)
{
for (var type in amounts)
this.resourceCount[type] += +amounts[type];
};
Player.prototype.GetNeededResources = function(amounts)
{
// Check if we can afford it all
var amountsNeeded = {};
for (var 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)
{
var amountsNeeded = this.GetNeededResources(amounts);
// If we don't have enough resources, send a notification to the player
if (amountsNeeded)
{
var parameters = {};
var i = 0;
for (var type in amountsNeeded)
{
++i;
parameters["resourceType"+i] = this.resourceNames[type];
parameters["resourceAmount"+i] = amountsNeeded[type];
}
var 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 (var type in amounts)
this.resourceCount[type] -= amounts[type];
return true;
};
Player.prototype.TrySubtractResources = function(amounts)
{
if (!this.SubtractResourcesOrNotify(amounts))
return false;
var cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpStatisticsTracker)
for (var type in amounts)
cmpStatisticsTracker.IncreaseResourceUsedCounter(type, amounts[type]);
return true;
};
Player.prototype.GetNextTradingGoods = function()
{
var value = randFloat(0, 100);
var last = this.tradingGoods.length - 1;
var sumProba = 0;
for (var 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()
{
var tradingGoods = {};
for (let resource of this.tradingGoods)
tradingGoods[resource.goods] = resource.proba;
return tradingGoods;
};
Player.prototype.SetTradingGoods = function(tradingGoods)
{
let resCodes = Resources.GetCodes();
let sumProba = 0;
for (let resource in tradingGoods)
{
if (resCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0)
{
error("Invalid trading goods: " + uneval(tradingGoods));
return;
}
sumProba += tradingGoods[resource];
}
if (sumProba != 100)
{
error("Invalid trading goods: " + uneval(tradingGoods));
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;
}
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)
{
var players = [];
for (var 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)
{
var 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;
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
var cmpCost = Engine.QueryInterface(msg.entity, IID_Cost);
if (msg.from == this.playerID)
{
if (cmpCost)
this.popUsed -= cmpCost.GetPopCost();
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses))
{
let index = this.panelEntities.indexOf(msg.entity);
if (index >= 0)
this.panelEntities.splice(index, 1);
}
}
if (msg.to == this.playerID)
{
if (cmpCost)
this.popUsed += cmpCost.GetPopCost();
if (cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses))
this.panelEntities.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.SetCheatTimeMultiplier = function(time)
-{
- this.cheatTimeMultiplier = time;
- Engine.BroadcastMessage(MT_MultiplierChanged, { "player": this.playerID, "type": "cheat" });
-};
-
-Player.prototype.GetCheatTimeMultiplier = function()
-{
- return this.cheatTimeMultiplier;
-};
-
Player.prototype.TributeResource = function(player, amounts)
{
var cmpPlayer = QueryPlayerIDInterface(player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
for (let resCode in amounts)
if (Resources.GetCodes().indexOf(resCode) == -1 ||
!Number.isInteger(amounts[resCode]) ||
amounts[resCode] < 0)
{
warn("Invalid tribute amounts: " + uneval(amounts));
return;
}
if (!this.SubtractResourcesOrNotify(amounts))
return;
cmpPlayer.AddResources(amounts);
var total = Object.keys(amounts).reduce((sum, type) => sum + amounts[type], 0);
var cmpOurStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
if (cmpOurStatisticsTracker)
cmpOurStatisticsTracker.IncreaseTributesSentCounter(total);
var cmpTheirStatisticsTracker = QueryPlayerIDInterface(player, IID_StatisticsTracker);
if (cmpTheirStatisticsTracker)
cmpTheirStatisticsTracker.IncreaseTributesReceivedCounter(total);
var 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, {});
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
"type": "resetselectionpannel",
"players": [this.GetPlayerID()]
});
};
Player.prototype.RemoveDisabledTemplate = function(template)
{
this.disabledTemplates[template] = false;
Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {});
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
"type": "resetselectionpannel",
"players": [this.GetPlayerID()]
});
};
Player.prototype.SetDisabledTemplates = function(templates)
{
this.disabledTemplates = {};
for (let template of templates)
this.disabledTemplates[template] = true;
Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {});
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGuiInterface.PushNotification({
"type": "resetselectionpannel",
"players": [this.GetPlayerID()]
});
};
Player.prototype.GetDisabledTemplates = function()
{
return this.disabledTemplates;
};
Player.prototype.AddDisabledTechnology = function(tech)
{
this.disabledTechnologies[tech] = true;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {});
};
Player.prototype.RemoveDisabledTechnology = function(tech)
{
this.disabledTechnologies[tech] = false;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {});
};
Player.prototype.SetDisabledTechnologies = function(techs)
{
this.disabledTechnologies = {};
for (let tech of techs)
this.disabledTechnologies[tech] = true;
Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, {});
};
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;
};
Engine.RegisterComponentType(IID_Player, "Player", Player);
Index: ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js (revision 22964)
@@ -1,833 +1,829 @@
var g_ProgressInterval = 1000;
const MAX_QUEUE_SIZE = 16;
function ProductionQueue() {}
ProductionQueue.prototype.Schema =
"Allows the building to train new units and research technologies" +
"" +
"0.7" +
"" +
"\n units/{civ}_support_female_citizen\n units/{native}_support_trader\n units/athen_infantry_spearman_b\n " +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
"";
ProductionQueue.prototype.Init = function()
{
this.nextID = 1;
this.queue = [];
// Queue items are:
// {
// "id": 1,
// "player": 1, // who paid for this batch; we need this to cope with refunds cleanly
// "unitTemplate": "units/example",
// "count": 10,
// "neededSlots": 3, // number of population slots missing for production to begin
// "resources": { "wood": 100, ... }, // resources per unit, multiply by count to get total
// "population": 1, // population per unit, multiply by count to get total
// "productionStarted": false, // true iff we have reserved population
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }
//
// {
// "id": 1,
// "player": 1, // who paid for this research; we need this to cope with refunds cleanly
// "technologyTemplate": "example_tech",
// "resources": { "wood": 100, ... }, // resources needed for research
// "productionStarted": false, // true iff production has started
// "timeTotal": 15000, // msecs
// "timeRemaining": 10000, // msecs
// }
this.timer = undefined; // g_ProgressInterval msec timer, active while the queue is non-empty
this.paused = false;
this.entityCache = [];
this.spawnNotified = false;
};
/*
* Returns list of entities that can be trained by this building.
*/
ProductionQueue.prototype.GetEntitiesList = function()
{
return this.entitiesList;
};
ProductionQueue.prototype.CalculateEntitiesList = function()
{
this.entitiesList = [];
if (!this.template.Entities)
return;
let string = this.template.Entities._string;
if (!string)
return;
// Replace the "{civ}" and "{native}" codes with the owner's civ ID and entity's civ ID.
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let cmpPlayer = QueryOwnerInterface(this.entity);
if (!cmpPlayer)
return;
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
string = string.replace(/\{native\}/g, cmpIdentity.GetCiv());
let entitiesList = string.replace(/\{civ\}/g, cmpPlayer.GetCiv()).split(/\s+/);
// filter out disabled and invalid entities
let disabledEntities = cmpPlayer.GetDisabledTemplates();
entitiesList = entitiesList.filter(ent => !disabledEntities[ent] && cmpTemplateManager.TemplateExists(ent));
// check if some templates need to show their advanced or elite version
let upgradeTemplate = function(templateName)
{
let template = cmpTemplateManager.GetTemplate(templateName);
while (template && template.Promotion !== undefined)
{
let requiredXp = ApplyValueModificationsToTemplate("Promotion/RequiredXp", +template.Promotion.RequiredXp, cmpPlayer.GetPlayerID(), template);
if (requiredXp > 0)
break;
templateName = template.Promotion.Entity;
template = cmpTemplateManager.GetTemplate(templateName);
}
return templateName;
};
for (let templateName of entitiesList)
this.entitiesList.push(upgradeTemplate(templateName));
for (let item of this.queue)
if (item.unitTemplate)
item.unitTemplate = upgradeTemplate(item.unitTemplate);
};
/*
* Returns list of technologies that can be researched by this building.
*/
ProductionQueue.prototype.GetTechnologiesList = function()
{
if (!this.template.Technologies)
return [];
var string = this.template.Technologies._string;
if (!string)
return [];
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
if (!cmpTechnologyManager)
return [];
var cmpPlayer = QueryOwnerInterface(this.entity);
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (!cmpPlayer || !cmpIdentity)
return [];
var techs = string.split(/\s+/);
// Replace the civ specific technologies
for (let i = 0; i < techs.length; ++i)
{
let tech = techs[i];
if (tech.indexOf("{civ}") == -1)
continue;
let civTech = tech.replace("{civ}", cmpPlayer.GetCiv());
techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic");
}
// Remove any technologies that can't be researched by this civ
techs = techs.filter(tech =>
cmpTechnologyManager.CheckTechnologyRequirements(
DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), cmpPlayer.GetCiv()),
true));
var techList = [];
var superseded = {}; // Stores the tech which supersedes the key
var disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
// Add any top level technologies to an array which corresponds to the displayed icons
// Also store what a technology is superceded by in the superceded object {"tech1":"techWhichSupercedesTech1", ...}
for (var i in techs)
{
var tech = techs[i];
if (disabledTechnologies && disabledTechnologies[tech])
continue;
let template = TechnologyTemplates.Get(tech);
if (!template.supersedes || techs.indexOf(template.supersedes) === -1)
techList.push(tech);
else
superseded[template.supersedes] = tech;
}
// Now make researched/in progress techs invisible
for (var i in techList)
{
var tech = techList[i];
while (this.IsTechnologyResearchedOrInProgress(tech))
tech = superseded[tech];
techList[i] = tech;
}
var ret = [];
// This inserts the techs into the correct positions to line up the technology pairs
for (var i = 0; i < techList.length; i++)
{
var tech = techList[i];
if (!tech)
{
ret[i] = undefined;
continue;
}
let template = TechnologyTemplates.Get(tech);
if (template.top)
ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
else
ret[i] = tech;
}
return ret;
};
ProductionQueue.prototype.GetTechCostMultiplier = function()
{
let techCostMultiplier = {};
for (let res in this.template.TechCostMultiplier)
techCostMultiplier[res] = ApplyValueModificationsToEntity("ProductionQueue/TechCostMultiplier/"+res, +this.template.TechCostMultiplier[res], this.entity);
return techCostMultiplier;
};
ProductionQueue.prototype.IsTechnologyResearchedOrInProgress = function(tech)
{
if (!tech)
return false;
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
let template = TechnologyTemplates.Get(tech);
if (template.top)
return cmpTechnologyManager.IsTechnologyResearched(template.top) || cmpTechnologyManager.IsInProgress(template.top) ||
cmpTechnologyManager.IsTechnologyResearched(template.bottom) || cmpTechnologyManager.IsInProgress(template.bottom);
return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech);
};
/*
* Adds a new batch of identical units to train or a technology to research to the production queue.
*/
ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadata)
{
// TODO: there should probably be a limit on the number of queued batches
// TODO: there should be a way for the GUI to determine whether it's going
// to be possible to add a batch (based on resource costs and length limits)
let cmpPlayer = QueryOwnerInterface(this.entity);
if (this.queue.length < MAX_QUEUE_SIZE)
{
if (type == "unit")
{
if (!Number.isInteger(count) || count <= 0)
{
error("Invalid batch count " + count);
return;
}
// Find the template data so we can determine the build costs
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let template = cmpTemplateManager.GetTemplate(templateName);
if (!template)
return;
if (template.Promotion && ApplyValueModificationsToTemplate("Promotion/RequiredXp", +template.Promotion.RequiredXp, cmpPlayer.GetPlayerID(), template) == 0)
{
this.AddBatch(template.Promotion.Entity, type, count, metadata);
return;
}
// Apply a time discount to larger batches.
let timeMult = this.GetBatchTime(count);
// We need the costs after tech modifications
// Obviously we don't have the entities yet, so we must use template data
let costs = {};
let totalCosts = {};
let buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, cmpPlayer.GetPlayerID(), template);
let time = timeMult * buildTime;
for (let res in template.Cost.Resources)
{
costs[res] = ApplyValueModificationsToTemplate("Cost/Resources/"+res, +template.Cost.Resources[res], cmpPlayer.GetPlayerID(), template);
totalCosts[res] = Math.floor(count * costs[res]);
}
// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(totalCosts))
return;
// Update entity count in the EntityLimits component
if (template.TrainingRestrictions)
{
let unitCategory = template.TrainingRestrictions.Category;
let cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.ChangeCount(unitCategory, count);
}
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"unitTemplate": templateName,
"count": count,
"metadata": metadata,
"resources": costs,
"population": ApplyValueModificationsToTemplate("Cost/Population", +template.Cost.Population, cmpPlayer.GetPlayerID(), template),
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
// Call the related trigger event
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.CallEvent("TrainingQueued", { "playerid": cmpPlayer.GetPlayerID(), "unitTemplate": templateName, "count": count, "metadata": metadata, "trainerEntity": this.entity });
}
else if (type == "technology")
{
if (!TechnologyTemplates.Has(templateName))
return;
if (!this.GetTechnologiesList().some(tech =>
tech &&
(tech == templateName ||
tech.pair &&
(tech.top == templateName || tech.bottom == templateName))))
{
error("This entity cannot research " + templateName);
return;
}
let template = TechnologyTemplates.Get(templateName);
let techCostMultiplier = this.GetTechCostMultiplier();
- let time = techCostMultiplier.time * template.researchTime * cmpPlayer.GetTimeMultiplier();
+ let time = techCostMultiplier.time * template.researchTime;
let cost = {};
for (let res in template.cost)
cost[res] = Math.floor((techCostMultiplier[res] || 1) * template.cost[res]);
// TrySubtractResources should report error to player (they ran out of resources)
if (!cmpPlayer.TrySubtractResources(cost))
return;
// Tell the technology manager that we have started researching this so that people can't research the same
// thing twice.
let cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.QueuedResearch(templateName, this.entity);
if (this.queue.length == 0)
cmpTechnologyManager.StartedResearch(templateName, false);
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"count": 1,
"technologyTemplate": templateName,
"resources": cost,
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
// Call the related trigger event
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.CallEvent("ResearchQueued", { "playerid": cmpPlayer.GetPlayerID(), "technologyTemplate": templateName, "researcherEntity": this.entity });
}
else
{
warn("Tried to add invalid item of type \"" + type + "\" and template \"" + templateName + "\" to a production queue");
return;
}
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
// If this is the first item in the queue, start the timer
if (!this.timer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, {});
}
}
else
{
let notification = { "players": [cmpPlayer.GetPlayerID()], "message": markForTranslation("The production queue is full."), "translateMessage": true };
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
}
};
/*
* Removes an existing batch of units from the production queue.
* Refunds resource costs and population reservations.
*/
ProductionQueue.prototype.RemoveBatch = function(id)
{
// Destroy any cached entities (those which didn't spawn for some reason)
for (let ent of this.entityCache)
Engine.DestroyEntity(ent);
this.entityCache = [];
for (var i = 0; i < this.queue.length; ++i)
{
var item = this.queue[i];
if (item.id != id)
continue;
// Now we've found the item to remove
var cmpPlayer = QueryPlayerIDInterface(item.player);
// Update entity count in the EntityLimits component
if (item.unitTemplate)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetTemplate(item.unitTemplate);
if (template.TrainingRestrictions)
{
var unitCategory = template.TrainingRestrictions.Category;
var cmpPlayerEntityLimits = QueryPlayerIDInterface(item.player, IID_EntityLimits);
cmpPlayerEntityLimits.ChangeCount(unitCategory, -item.count);
}
}
// Refund the resource cost for this batch
var totalCosts = {};
var cmpStatisticsTracker = QueryPlayerIDInterface(item.player, IID_StatisticsTracker);
for (let r in item.resources)
{
totalCosts[r] = Math.floor(item.count * item.resources[r]);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceUsedCounter(r, -totalCosts[r]);
}
cmpPlayer.AddResources(totalCosts);
// Remove reserved population slots if necessary
if (item.productionStarted && item.unitTemplate)
cmpPlayer.UnReservePopulationSlots(item.population * item.count);
// Mark the research as stopped if we cancel it
if (item.technologyTemplate)
{
// item.player is used as this.entity's owner may be invalid (deletion, etc.)
var cmpTechnologyManager = QueryPlayerIDInterface(item.player, IID_TechnologyManager);
cmpTechnologyManager.StoppedResearch(item.technologyTemplate, true);
}
// Remove from the queue
// (We don't need to remove the timer - it'll expire if it discovers the queue is empty)
this.queue.splice(i, 1);
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
return;
}
};
/*
* Returns basic data from all batches in the production queue.
*/
ProductionQueue.prototype.GetQueue = function()
{
var out = [];
for (var item of this.queue)
{
out.push({
"id": item.id,
"unitTemplate": item.unitTemplate,
"technologyTemplate": item.technologyTemplate,
"count": item.count,
"neededSlots": item.neededSlots,
"progress": 1 - (item.timeRemaining / (item.timeTotal || 1)),
"timeRemaining": item.timeRemaining,
"metadata": item.metadata,
});
}
return out;
};
/*
* Removes all existing batches from the queue.
*/
ProductionQueue.prototype.ResetQueue = function()
{
// Empty the production queue and refund all the resource costs
// to the player. (This is to avoid players having to micromanage their
// buildings' queues when they're about to be destroyed or captured.)
while (this.queue.length)
this.RemoveBatch(this.queue[0].id);
};
/*
* Returns batch build time.
*/
ProductionQueue.prototype.GetBatchTime = function(batchSize)
{
- var cmpPlayer = QueryOwnerInterface(this.entity);
-
- var batchTimeModifier = ApplyValueModificationsToEntity("ProductionQueue/BatchTimeModifier", +this.template.BatchTimeModifier, this.entity);
-
// TODO: work out what equation we should use here.
- return Math.pow(batchSize, batchTimeModifier) * cmpPlayer.GetTimeMultiplier();
+ return Math.pow(batchSize, ApplyValueModificationsToEntity("ProductionQueue/BatchTimeModifier", +this.template.BatchTimeModifier, this.entity));
};
ProductionQueue.prototype.OnOwnershipChanged = function(msg)
{
if (msg.from != INVALID_PLAYER)
{
// Unset flag that previous owner's training may be blocked
var cmpPlayer = QueryPlayerIDInterface(msg.from);
if (cmpPlayer && this.queue.length > 0)
cmpPlayer.UnBlockTraining();
}
if (msg.to != INVALID_PLAYER)
this.CalculateEntitiesList();
// Reset the production queue whenever the owner changes.
// (This should prevent players getting surprised when they capture
// an enemy building, and then loads of the enemy's civ's soldiers get
// created from it. Also it means we don't have to worry about
// updating the reserved pop slots.)
this.ResetQueue();
};
ProductionQueue.prototype.OnCivChanged = function()
{
this.CalculateEntitiesList();
};
ProductionQueue.prototype.OnDestroy = function()
{
// Reset the queue to refund any resources
this.ResetQueue();
if (this.timer)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
}
};
/*
* This function creates the entities and places them in world if possible
* and returns the number of successfully created entities.
* (some of these entities may be garrisoned directly if autogarrison, the others are spawned).
*/
ProductionQueue.prototype.SpawnUnits = function(templateName, count, metadata)
{
let cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
let createdEnts = [];
let spawnedEnts = [];
// We need entities to test spawning, but we don't want to waste resources,
// so only create them once and use as needed
if (this.entityCache.length == 0)
for (let i = 0; i < count; ++i)
this.entityCache.push(Engine.AddEntity(templateName));
let cmpAutoGarrison;
if (cmpRallyPoint)
{
let data = cmpRallyPoint.GetData()[0];
if (data && data.target && data.target == this.entity && data.command == "garrison")
cmpAutoGarrison = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
}
for (let i = 0; i < count; ++i)
{
let ent = this.entityCache[0];
let cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership);
let garrisoned = false;
if (cmpAutoGarrison)
{
// Temporary owner affectation needed for GarrisonHolder checks
cmpNewOwnership.SetOwnerQuiet(cmpOwnership.GetOwner());
garrisoned = cmpAutoGarrison.PerformGarrison(ent);
cmpNewOwnership.SetOwnerQuiet(INVALID_PLAYER);
}
if (garrisoned)
{
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.Autogarrison(this.entity);
}
else
{
let pos = cmpFootprint.PickSpawnPoint(ent);
if (pos.y < 0)
break;
let cmpNewPosition = Engine.QueryInterface(ent, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (cmpPosition)
cmpNewPosition.SetYRotation(cmpPosition.GetPosition().horizAngleTo(pos));
spawnedEnts.push(ent);
}
// Decrement entity count in the EntityLimits component
// since it will be increased by EntityLimits.OnGlobalOwnershipChanged function,
// i.e. we replace a 'trained' entity by 'alive' one
// Must be done after spawn check so EntityLimits decrements only if unit spawns
let cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
if (cmpTrainingRestrictions)
{
let unitCategory = cmpTrainingRestrictions.GetCategory();
let cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.ChangeCount(unitCategory, -1);
}
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
let cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpPlayerStatisticsTracker)
cmpPlayerStatisticsTracker.IncreaseTrainedUnitsCounter(ent);
// Play a sound, but only for the first in the batch (to avoid nasty phasing effects)
if (createdEnts.length == 0)
PlaySound("trained", ent);
this.entityCache.shift();
createdEnts.push(ent);
}
if (spawnedEnts.length > 0 && !cmpAutoGarrison)
{
// If a rally point is set, walk towards it (in formation) using a suitable command based on where the
// rally point is placed.
if (cmpRallyPoint)
{
let rallyPos = cmpRallyPoint.GetPositions()[0];
if (rallyPos)
{
let commands = GetRallyPointCommands(cmpRallyPoint, spawnedEnts);
for (let com of commands)
ProcessCommand(cmpOwnership.GetOwner(), com);
}
}
}
if (createdEnts.length > 0)
Engine.PostMessage(this.entity, MT_TrainingFinished, {
"entities": createdEnts,
"owner": cmpOwnership.GetOwner(),
"metadata": metadata,
});
return createdEnts.length;
};
/*
* Increments progress on the first batch in the production queue, and blocks the
* queue if population limit is reached or some units failed to spawn.
*/
ProductionQueue.prototype.ProgressTimeout = function(data)
{
// Check if the production is paused (eg the entity is garrisoned)
if (this.paused)
return;
// Allocate the 1000msecs to as many queue items as it takes
// until we've used up all the time (so that we work accurately
// with items that take fractions of a second)
var time = g_ProgressInterval;
var cmpPlayer = QueryOwnerInterface(this.entity);
while (time > 0 && this.queue.length)
{
var item = this.queue[0];
if (!item.productionStarted)
{
// If the item is a unit then do population checks
if (item.unitTemplate)
{
// If something change population cost
var template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate(item.unitTemplate);
item.population = ApplyValueModificationsToTemplate("Cost/Population", +template.Cost.Population, item.player, template);
// Batch's training hasn't started yet.
// Try to reserve the necessary population slots
item.neededSlots = cmpPlayer.TryReservePopulationSlots(item.population * item.count);
if (item.neededSlots)
{
// Not enough slots available - don't train this batch now
// (we'll try again on the next timeout)
// Set flag that training is blocked
cmpPlayer.BlockTraining();
break;
}
// Unset flag that training is blocked
cmpPlayer.UnBlockTraining();
}
if (item.technologyTemplate)
{
// Mark the research as started.
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.StartedResearch(item.technologyTemplate, true);
}
item.productionStarted = true;
if (item.unitTemplate)
Engine.PostMessage(this.entity, MT_TrainingStarted, { "entity": this.entity });
}
// If we won't finish the batch now, just update its timer
if (item.timeRemaining > time)
{
item.timeRemaining -= time;
// send a message for the AIs.
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
break;
}
if (item.unitTemplate)
{
var numSpawned = this.SpawnUnits(item.unitTemplate, item.count, item.metadata);
if (numSpawned == item.count)
{
// All entities spawned, this batch finished
cmpPlayer.UnReservePopulationSlots(item.population * numSpawned);
time -= item.timeRemaining;
this.queue.shift();
// Unset flag that training is blocked
cmpPlayer.UnBlockTraining();
this.spawnNotified = false;
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
else
{
if (numSpawned > 0)
{
// Only partially finished
cmpPlayer.UnReservePopulationSlots(item.population * numSpawned);
item.count -= numSpawned;
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
// Some entities failed to spawn
// Set flag that training is blocked
cmpPlayer.BlockTraining();
if (!this.spawnNotified)
{
var cmpPlayer = QueryOwnerInterface(this.entity);
var notification = { "players": [cmpPlayer.GetPlayerID()], "message": markForTranslation("Can't find free space to spawn trained units"), "translateMessage": true };
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
this.spawnNotified = true;
}
break;
}
}
else if (item.technologyTemplate)
{
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.ResearchTechnology(item.technologyTemplate);
let template = TechnologyTemplates.Get(item.technologyTemplate);
if (template && template.soundComplete)
{
var cmpSoundManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager);
if (cmpSoundManager)
cmpSoundManager.PlaySoundGroup(template.soundComplete, this.entity);
}
time -= item.timeRemaining;
this.queue.shift();
Engine.PostMessage(this.entity, MT_ProductionQueueChanged, { });
}
}
// If the queue's empty, delete the timer, else repeat it
if (this.queue.length == 0)
{
this.timer = undefined;
// Unset flag that training is blocked
// (This might happen when the player unqueues all batches)
cmpPlayer.UnBlockTraining();
}
else
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, data);
}
};
ProductionQueue.prototype.PauseProduction = function()
{
this.timer = undefined;
this.paused = true;
};
ProductionQueue.prototype.UnpauseProduction = function()
{
this.paused = false;
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, {});
};
ProductionQueue.prototype.OnValueModification = function(msg)
{
// if the promotion requirements of units is changed,
// update the entities list so that automatically promoted units are shown
// appropriately in the list
if (msg.component == "Promotion")
this.CalculateEntitiesList();
};
ProductionQueue.prototype.OnDisabledTemplatesChanged = function(msg)
{
// if the disabled templates of the player is changed,
// update the entities list so that this is reflected there
this.CalculateEntitiesList();
};
Engine.RegisterComponentType(IID_ProductionQueue, "ProductionQueue", ProductionQueue);
Index: ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js (revision 22964)
@@ -1,352 +1,350 @@
function ResourceGatherer() {}
ResourceGatherer.prototype.Schema =
"Lets the unit gather resources from entities that have the ResourceSupply component." +
"" +
"2.0" +
"1.0" +
"" +
"1" +
"3" +
"3" +
"2" +
"" +
"" +
"10" +
"10" +
"10" +
"10" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
Resources.BuildSchema("positiveDecimal", ["treasure"], true) +
"" +
"" +
Resources.BuildSchema("positiveDecimal") +
"";
ResourceGatherer.prototype.Init = function()
{
this.carrying = {}; // { generic type: integer amount currently carried }
// (Note that this component supports carrying multiple types of resources,
// each with an independent capacity, but the rest of the game currently
// ensures and assumes we'll only be carrying one type at once)
// The last exact type gathered, so we can render appropriate props
this.lastCarriedType = undefined; // { generic, specific }
};
/**
* Returns data about what resources the unit is currently carrying,
* in the form [ {"type":"wood", "amount":7, "max":10} ]
*/
ResourceGatherer.prototype.GetCarryingStatus = function()
{
let ret = [];
for (let type in this.carrying)
{
ret.push({
"type": type,
"amount": this.carrying[type],
"max": +this.GetCapacity(type)
});
}
return ret;
};
/**
* Used to instantly give resources to unit
* @param resources The same structure as returned form GetCarryingStatus
*/
ResourceGatherer.prototype.GiveResources = function(resources)
{
for (let resource of resources)
this.carrying[resource.type] = +resource.amount;
Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
};
/**
* Returns the generic type of one particular resource this unit is
* currently carrying, or undefined if none.
*/
ResourceGatherer.prototype.GetMainCarryingType = function()
{
// Return the first key, if any
for (let type in this.carrying)
return type;
return undefined;
};
/**
* Returns the exact resource type we last picked up, as long as
* we're still carrying something similar enough, in the form
* { generic, specific }
*/
ResourceGatherer.prototype.GetLastCarriedType = function()
{
if (this.lastCarriedType && this.lastCarriedType.generic in this.carrying)
return this.lastCarriedType;
return undefined;
};
// Since this code is very performancecritical and applying technologies quite slow, cache it.
ResourceGatherer.prototype.RecalculateGatherRatesAndCapacities = function()
{
- let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
- let multiplier = cmpPlayer ? cmpPlayer.GetGatherRateMultiplier() : 1;
- this.baseSpeed = multiplier * ApplyValueModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity);
+ this.baseSpeed = ApplyValueModificationsToEntity("ResourceGatherer/BaseSpeed", +this.template.BaseSpeed, this.entity);
this.rates = {};
for (let r in this.template.Rates)
{
let type = r.split(".");
if (type[0] != "treasure" && type.length > 1 && !Resources.GetResource(type[0]).subtypes[type[1]])
{
error("Resource subtype not found: " + type[0] + "." + type[1]);
continue;
}
let rate = ApplyValueModificationsToEntity("ResourceGatherer/Rates/" + r, +this.template.Rates[r], this.entity);
this.rates[r] = rate * this.baseSpeed;
}
this.capacities = {};
for (let r in this.template.Capacities)
this.capacities[r] = ApplyValueModificationsToEntity("ResourceGatherer/Capacities/" + r, +this.template.Capacities[r], this.entity);
};
ResourceGatherer.prototype.GetGatherRates = function()
{
return this.rates;
};
ResourceGatherer.prototype.GetGatherRate = function(resourceType)
{
if (!this.template.Rates[resourceType])
return 0;
return this.rates[resourceType];
};
ResourceGatherer.prototype.GetCapacity = function(resourceType)
{
if(!this.template.Capacities[resourceType])
return 0;
return this.capacities[resourceType];
};
ResourceGatherer.prototype.GetRange = function()
{
return { "max": +this.template.MaxDistance, "min": 0 };
// maybe this should depend on the unit or target or something?
};
/**
* Try to gather treasure
* @return 'true' if treasure is successfully gathered, otherwise 'false'
*/
ResourceGatherer.prototype.TryInstantGather = function(target)
{
let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
let type = cmpResourceSupply.GetType();
if (type.generic != "treasure")
return false;
let status = cmpResourceSupply.TakeResources(cmpResourceSupply.GetCurrentAmount());
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (cmpPlayer)
cmpPlayer.AddResource(type.specific, status.amount);
let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseTreasuresCollectedCounter();
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
if (cmpTrigger && cmpPlayer)
cmpTrigger.CallEvent("TreasureCollected", { "player": cmpPlayer.GetPlayerID(), "type": type.specific, "amount": status.amount });
return true;
};
/**
* Gather from the target entity. This should only be called after a successful range check,
* and if the target has a compatible ResourceSupply.
* Call interval will be determined by gather rate, so always gather 1 amount when called.
*/
ResourceGatherer.prototype.PerformGather = function(target)
{
if (!this.GetTargetGatherRate(target))
return { "exhausted": true };
let gatherAmount = 1;
let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
let type = cmpResourceSupply.GetType();
// Initialise the carried count if necessary
if (!this.carrying[type.generic])
this.carrying[type.generic] = 0;
// Find the maximum so we won't exceed our capacity
let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic];
let status = cmpResourceSupply.TakeResources(Math.min(gatherAmount, maxGathered));
this.carrying[type.generic] += status.amount;
this.lastCarriedType = type;
// Update stats of how much the player collected.
// (We have to do it here rather than at the dropsite, because we
// need to know what subtype it was)
let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific);
Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
return {
"amount": status.amount,
"exhausted": status.exhausted,
"filled": this.carrying[type.generic] >= this.GetCapacity(type.generic)
};
};
/**
* Compute the amount of resources collected per second from the target.
* Returns 0 if resources cannot be collected (e.g. the target doesn't
* exist, or is the wrong type).
*/
ResourceGatherer.prototype.GetTargetGatherRate = function(target)
{
let cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply);
if (!cmpResourceSupply)
return 0;
let type = cmpResourceSupply.GetType();
let rate = 0;
if (type.specific)
rate = this.GetGatherRate(type.generic+"."+type.specific);
if (rate == 0 && type.generic)
rate = this.GetGatherRate(type.generic);
if ("Mirages" in cmpResourceSupply)
return rate;
// Apply diminishing returns with more gatherers, for e.g. infinite farms. For most resources this has no effect
// (GetDiminishingReturns will return null). We can assume that for resources that are miraged this is the case
// (else just add the diminishing returns data to the mirage data and remove the early return above)
let diminishingReturns = cmpResourceSupply.GetDiminishingReturns();
if (diminishingReturns)
rate *= diminishingReturns;
return rate;
};
/**
* Returns whether this unit can carry more of the given type of resource.
* (This ignores whether the unit is actually able to gather that
* resource type or not.)
*/
ResourceGatherer.prototype.CanCarryMore = function(type)
{
let amount = this.carrying[type] || 0;
return amount < this.GetCapacity(type);
};
ResourceGatherer.prototype.IsCarrying = function(type)
{
let amount = this.carrying[type] || 0;
return amount > 0;
};
/**
* Returns whether this unit is carrying any resources of a type that is
* not the requested type. (This is to support cases where the unit is
* only meant to be able to carry one type at once.)
*/
ResourceGatherer.prototype.IsCarryingAnythingExcept = function(exceptedType)
{
for (let type in this.carrying)
if (type != exceptedType)
return true;
return false;
};
/**
* Transfer our carried resources to our owner immediately.
* Only resources of the given types will be transferred.
* (This should typically be called after reaching a dropsite).
*/
ResourceGatherer.prototype.CommitResources = function(types)
{
let cmpPlayer = QueryOwnerInterface(this.entity);
if (cmpPlayer)
for (let type of types)
if (type in this.carrying)
{
cmpPlayer.AddResource(type, this.carrying[type]);
delete this.carrying[type];
}
Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
};
/**
* Drop all currently-carried resources.
* (Currently they just vanish after being dropped - we don't bother depositing
* them onto the ground.)
*/
ResourceGatherer.prototype.DropResources = function()
{
this.carrying = {};
Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
};
// Since we cache gather rates, we need to make sure we update them when tech changes.
// and when our owner change because owners can had different techs.
ResourceGatherer.prototype.OnValueModification = function(msg)
{
if (msg.component != "ResourceGatherer")
return;
this.RecalculateGatherRatesAndCapacities();
};
ResourceGatherer.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to == INVALID_PLAYER)
return;
this.RecalculateGatherRatesAndCapacities();
};
ResourceGatherer.prototype.OnGlobalInitGame = function(msg)
{
this.RecalculateGatherRatesAndCapacities();
};
ResourceGatherer.prototype.OnMultiplierChanged = function(msg)
{
if (msg.player == QueryOwnerInterface(this.entity, IID_Player).GetPlayerID())
this.RecalculateGatherRatesAndCapacities();
};
Engine.RegisterComponentType(IID_ResourceGatherer, "ResourceGatherer", ResourceGatherer);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Upgrade.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Upgrade.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Upgrade.js (revision 22964)
@@ -1,320 +1,318 @@
function Upgrade() {}
const UPGRADING_PROGRESS_INTERVAL = 250;
Upgrade.prototype.Schema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
Resources.BuildSchema("nonNegativeInteger") +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Upgrade.prototype.Init = function()
{
this.upgrading = false;
this.completed = false;
this.elapsedTime = 0;
this.timer = undefined;
this.expendedResources = {};
this.upgradeTemplates = {};
for (let choice in this.template)
{
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
let name = this.template[choice].Entity;
if (cmpIdentity)
name = name.replace(/\{civ\}/g, cmpIdentity.GetCiv());
if (this.upgradeTemplates.name)
warn("Upgrade Component: entity " + this.entity + " has two upgrades to the same entity, only the last will be used.");
this.upgradeTemplates[name] = choice;
}
};
// This will also deal with the "OnDestroy" case.
Upgrade.prototype.OnOwnershipChanged = function(msg)
{
if (!this.completed)
this.CancelUpgrade(msg.from);
if (msg.to != INVALID_PLAYER)
this.owner = msg.to;
};
Upgrade.prototype.ChangeUpgradedEntityCount = function(amount)
{
if (!this.IsUpgrading())
return;
let cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let template = cmpTempMan.GetTemplate(this.upgrading);
let categoryTo;
if (template.TrainingRestrictions)
categoryTo = template.TrainingRestrictions.Category;
else if (template.BuildRestrictions)
categoryTo = template.BuildRestrictions.Category;
if (!categoryTo)
return;
let categoryFrom;
let cmpTrainingRestrictions = Engine.QueryInterface(this.entity, IID_TrainingRestrictions);
let cmpBuildRestrictions = Engine.QueryInterface(this.entity, IID_BuildRestrictions);
if (cmpTrainingRestrictions)
categoryFrom = cmpTrainingRestrictions.GetCategory();
else if (cmpBuildRestrictions)
categoryFrom = cmpBuildRestrictions.GetCategory();
if (categoryTo == categoryFrom)
return;
let cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
cmpEntityLimits.ChangeCount(categoryTo, amount);
};
Upgrade.prototype.CanUpgradeTo = function(template)
{
return this.upgradeTemplates[template] !== undefined;
};
Upgrade.prototype.GetUpgrades = function()
{
let ret = [];
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
for (let option in this.template)
{
let choice = this.template[option];
let templateName = cmpIdentity ? choice.Entity.replace(/\{civ\}/g, cmpIdentity.GetCiv()) : choice.Entity;
let cost = {};
if (choice.Cost)
cost = this.GetResourceCosts(templateName);
if (choice.Time)
cost.time = this.GetUpgradeTime(templateName);
let hasCost = choice.Cost || choice.Time;
ret.push({
"entity": templateName,
"icon": choice.Icon || undefined,
"cost": hasCost ? cost : undefined,
"tooltip": choice.Tooltip || undefined,
"requiredTechnology": this.GetRequiredTechnology(option),
});
}
return ret;
};
Upgrade.prototype.CancelTimer = function()
{
if (!this.timer)
return;
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
this.timer = undefined;
};
Upgrade.prototype.IsUpgrading = function()
{
return !!this.upgrading;
};
Upgrade.prototype.GetUpgradingTo = function()
{
return this.upgrading;
};
Upgrade.prototype.WillCheckPlacementRestrictions = function(template)
{
if (!this.upgradeTemplates[template])
return undefined;
// is undefined by default so use X in Y
return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
};
Upgrade.prototype.GetRequiredTechnology = function(templateArg)
{
let choice = this.upgradeTemplates[templateArg] || templateArg;
if (this.template[choice].RequiredTechnology)
return this.template[choice].RequiredTechnology;
if (!("RequiredTechnology" in this.template[choice]))
return undefined;
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
let entType = this.template[choice].Entity;
if (cmpIdentity)
entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
let template = cmpTemplateManager.GetTemplate(entType);
return template.Identity.RequiredTechnology || undefined;
};
Upgrade.prototype.GetResourceCosts = function(template)
{
if (!this.upgradeTemplates[template])
return undefined;
if (this.IsUpgrading() && template == this.GetUpgradingTo())
return clone(this.expendedResources);
let choice = this.upgradeTemplates[template];
if (!this.template[choice].Cost)
return {};
let costs = {};
for (let r in this.template[choice].Cost)
costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, +this.template[choice].Cost[r], this.entity);
return costs;
};
Upgrade.prototype.Upgrade = function(template)
{
if (this.IsUpgrading() || !this.upgradeTemplates[template])
return false;
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
this.expendedResources = this.GetResourceCosts(template);
if (!cmpPlayer.TrySubtractResources(this.expendedResources))
{
this.expendedResources = {};
return false;
}
this.upgrading = template;
// Prevent cheating
this.ChangeUpgradedEntityCount(1);
if (this.GetUpgradeTime(template) !== 0)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
}
else
this.UpgradeProgress();
return true;
};
Upgrade.prototype.CancelUpgrade = function(owner)
{
if (!this.IsUpgrading())
return;
let cmpPlayer = QueryPlayerIDInterface(owner, IID_Player);
if (cmpPlayer)
cmpPlayer.AddResources(this.expendedResources);
this.expendedResources = {};
this.ChangeUpgradedEntityCount(-1);
this.upgrading = false;
this.CancelTimer();
this.SetElapsedTime(0);
};
Upgrade.prototype.GetUpgradeTime = function(templateArg)
{
let template = this.upgrading || templateArg;
let choice = this.upgradeTemplates[template];
if (!choice)
return undefined;
if (!this.template[choice].Time)
return 0;
- let cmpPlayer = QueryPlayerIDInterface(this.owner, IID_Player);
- return ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) *
- cmpPlayer.GetTimeMultiplier();
+ return ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity);
};
Upgrade.prototype.GetElapsedTime = function()
{
return this.elapsedTime;
};
Upgrade.prototype.GetProgress = function()
{
if (!this.IsUpgrading())
return undefined;
return this.GetUpgradeTime() == 0 ? 1 : Math.min(this.elapsedTime / 1000.0 / this.GetUpgradeTime(), 1.0);
};
Upgrade.prototype.SetElapsedTime = function(time)
{
this.elapsedTime = time;
};
Upgrade.prototype.UpgradeProgress = function(data, lateness)
{
if (this.elapsedTime/1000.0 < this.GetUpgradeTime())
{
this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
return;
}
this.CancelTimer();
this.completed = true;
this.ChangeUpgradedEntityCount(-1);
this.expendedResources = {};
let newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
if (newEntity)
PlaySound("upgraded", newEntity);
};
Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Pack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Pack.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Pack.js (revision 22964)
@@ -1,159 +1,155 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("Transform.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Guard.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Pack.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/ResourceGatherer.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("Pack.js");
Engine.RegisterGlobal("MT_EntityRenamed", "entityRenamed");
const ent = 170;
const newEnt = 171;
const PACKING_INTERVAL = 250;
let timerActivated = false;
AddMock(ent, IID_Visual, {
"SelectAnimation": (name, once, speed) => name
});
AddMock(ent, IID_Ownership, {
"GetOwner": () => 1
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => 11
});
-AddMock(11, IID_Player, {
- "GetTimeMultiplier": () => 1
-});
-
AddMock(ent, IID_Sound, {
"PlaySoundGroup": name => {}
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
"CancelTimer": id => { timerActivated = false; return; },
"SetInterval": (ent, iid, funcname, time, repeattime, data) => { timerActivated = true; return 7; }
});
Engine.AddEntity = function(template) {
TS_ASSERT_EQUALS(template, "finalTemplate");
return true;
};
// Test Packing
let template = {
"Entity": "finalTemplate",
"Time": "2000",
"State": "unpacked"
};
let cmpPack = ConstructComponent(ent, "Pack", template);
// Check internals
TS_ASSERT(!cmpPack.packed);
TS_ASSERT(!cmpPack.packing);
TS_ASSERT_EQUALS(cmpPack.elapsedTime, 0);
TS_ASSERT_EQUALS(cmpPack.timer, undefined);
TS_ASSERT(!cmpPack.IsPacked());
TS_ASSERT(!cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetProgress(), 0);
// Pack
cmpPack.Pack();
TS_ASSERT_EQUALS(cmpPack.timer, 7);
TS_ASSERT(cmpPack.IsPacking());
// Listen to destroy message
cmpPack.OnDestroy();
TS_ASSERT(!cmpPack.timer);
TS_ASSERT(!timerActivated);
// Test UnPacking
template = {
"Entity": "finalTemplate",
"Time": "2000",
"State": "packed"
};
cmpPack = ConstructComponent(ent, "Pack", template);
// Check internals
TS_ASSERT(cmpPack.packed);
TS_ASSERT(!cmpPack.packing);
TS_ASSERT_EQUALS(cmpPack.elapsedTime, 0);
TS_ASSERT_EQUALS(cmpPack.timer, undefined);
TS_ASSERT(cmpPack.IsPacked());
TS_ASSERT(!cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetProgress(), 0);
// Unpack
cmpPack.Unpack();
TS_ASSERT(cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.timer, 7);
// Unpack progress
cmpPack.elapsedTime = 400;
cmpPack.PackProgress({}, 100);
TS_ASSERT(cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetElapsedTime(), 400 + 100 + PACKING_INTERVAL);
TS_ASSERT_EQUALS(cmpPack.GetProgress(), (400 + 100 + PACKING_INTERVAL) / 2000);
// Try to Pack or Unpack while packing, nothing happen
cmpPack.elapsedTime = 400;
cmpPack.Unpack();
TS_ASSERT(cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetElapsedTime(), 400);
TS_ASSERT_EQUALS(cmpPack.timer, 7);
TS_ASSERT(timerActivated);
cmpPack.Pack();
TS_ASSERT(cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetElapsedTime(), 400);
TS_ASSERT_EQUALS(cmpPack.timer, 7);
TS_ASSERT(timerActivated);
// Cancel
cmpPack.CancelPack();
TS_ASSERT(!cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetElapsedTime(), 0);
TS_ASSERT_EQUALS(cmpPack.GetProgress(), 0);
TS_ASSERT_EQUALS(cmpPack.timer, undefined);
TS_ASSERT(!timerActivated);
// Progress until completing
cmpPack.Unpack();
cmpPack.elapsedTime = 1800;
cmpPack.PackProgress({}, 100);
TS_ASSERT(cmpPack.IsPacking());
TS_ASSERT_EQUALS(cmpPack.GetElapsedTime(), 1800 + 100 + PACKING_INTERVAL);
// Cap progress at 100%
TS_ASSERT_EQUALS(cmpPack.GetProgress(), 1);
TS_ASSERT_EQUALS(cmpPack.timer, 7);
TS_ASSERT(timerActivated);
// Unpack completing
cmpPack.Unpack();
cmpPack.elapsedTime = 2100;
cmpPack.PackProgress({}, 100);
TS_ASSERT(!cmpPack.IsPacking());
TS_ASSERT(!cmpPack.timer);
TS_ASSERT(!timerActivated);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UpgradeModification.js (revision 22964)
@@ -1,162 +1,161 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Resources = {
"BuildSchema": type => {
let schema = "";
for (let res of ["food", "metal", "stone", "wood"])
schema +=
"" +
"" +
"" +
"" +
"";
return "" + schema + "";
}
};
Engine.LoadComponentScript("interfaces/ModifiersManager.js"); // Provides `IID_ModifiersManager`, used below.
Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below.
// What we're testing:
Engine.LoadComponentScript("interfaces/Upgrade.js");
Engine.LoadComponentScript("Upgrade.js");
// Input (bare minimum needed for tests):
let techs = {
"alter_tower_upgrade_cost": {
"modifications": [
{ "value": "Upgrade/Cost/stone", "add": 60.0 },
{ "value": "Upgrade/Cost/wood", "multiply": 0.5 },
{ "value": "Upgrade/Time", "replace": 90 }
],
"affects": ["Tower"]
}
};
let template = {
"Identity": {
"Classes": { '@datatype': "tokens", "_string": "Tower" },
"VisibleClasses": { '@datatype': "tokens", "_string": "" }
},
"Upgrade": {
"Tower": {
"Cost": { "stone": "100", "wood": "50" },
"Entity": "structures/{civ}_defense_tower",
"Time": "100"
}
}
};
let civCode = "pony";
let playerID = 1;
// Usually, the tech modifications would be worked out by the TechnologyManager
// with assistance from globalscripts. This test is not about testing the
// TechnologyManager, so the modifications (both with and without the technology
// researched) are worked out before hand and placed here.
let isResearched = false;
let templateTechModifications = {
"without": {},
"with": {
"Upgrade/Cost/stone": [{ "affects": [["Tower"]], "add": 60 }],
"Upgrade/Cost/wood": [{ "affects": [["Tower"]], "multiply": 0.5 }],
"Upgrade/Time": [{ "affects": [["Tower"]], "replace": 90 }]
}
};
let entityTechModifications = {
"without": {
'Upgrade/Cost/stone': { "20": { "origValue": 100, "newValue": 100 } },
'Upgrade/Cost/wood': { "20": { "origValue": 50, "newValue": 50 } },
'Upgrade/Time': { "20": { "origValue": 100, "newValue": 100 } }
},
"with": {
'Upgrade/Cost/stone': { "20": { "origValue": 100, "newValue": 160 } },
'Upgrade/Cost/wood': { "20": { "origValue": 50, "newValue": 25 } },
'Upgrade/Time': { "20": { "origValue": 100, "newValue": 90 } }
}
};
/**
* Initialise various bits.
*/
// System Entities:
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": pID => 10 // Called in helpers/player.js::QueryPlayerIDInterface(), as part of Tests T2 and T5.
});
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
"GetTemplate": () => template // Called in components/Upgrade.js::ChangeUpgradedEntityCount().
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
"SetInterval": () => 1, // Called in components/Upgrade.js::Upgrade().
"CancelTimer": () => {} // Called in components/Upgrade.js::CancelUpgrade().
});
AddMock(SYSTEM_ENTITY, IID_ModifiersManager, {
"ApplyTemplateModifiers": (valueName, curValue, template, player) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToTemplate()
// as part of Tests T2 and T5 below.
let mods = isResearched ? templateTechModifications.with : templateTechModifications.without;
if (mods[valueName])
return GetTechModifiedProperty(mods[valueName], GetIdentityClasses(template.Identity), curValue);
return curValue;
},
"ApplyModifiers": (valueName, curValue, ent) => {
// Called in helpers/ValueModification.js::ApplyValueModificationsToEntity()
// as part of Tests T3, T6 and T7 below.
let mods = isResearched ? entityTechModifications.with : entityTechModifications.without;
return mods[valueName][ent].newValue;
}
});
// Init Player:
AddMock(10, IID_Player, {
"AddResources": () => {}, // Called in components/Upgrade.js::CancelUpgrade().
"GetPlayerID": () => playerID, // Called in helpers/Player.js::QueryOwnerInterface() (and several times below).
- "GetTimeMultiplier": () => 1.0, // Called in components/Upgrade.js::GetUpgradeTime().
"TrySubtractResources": () => true // Called in components/Upgrade.js::Upgrade().
});
// Create an entity with an Upgrade component:
AddMock(20, IID_Ownership, {
"GetOwner": () => playerID // Called in helpers/Player.js::QueryOwnerInterface().
});
AddMock(20, IID_Identity, {
"GetCiv": () => civCode // Called in components/Upgrade.js::init().
});
let cmpUpgrade = ConstructComponent(20, "Upgrade", template.Upgrade);
cmpUpgrade.owner = playerID;
/**
* Now to start the test proper
* To start with, no techs are researched...
*/
// T1: Check the cost of the upgrade without a player value being passed (as it would be in the structree).
let parsed_template = GetTemplateDataHelper(template, null, {});
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T2: Check the value, with a player ID (as it would be in-session).
parsed_template = GetTemplateDataHelper(template, playerID, {});
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T3: Check that the value is correct within the Update Component.
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 100, "wood": 50, "time": 100 });
/**
* Tell the Upgrade component to start the Upgrade,
* then mark the technology that alters the upgrade cost as researched.
*/
cmpUpgrade.Upgrade("structures/"+civCode+"_defense_tower");
isResearched = true;
// T4: Check that the player-less value hasn't increased...
parsed_template = GetTemplateDataHelper(template, null, {});
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 100, "wood": 50, "time": 100 });
// T5: ...but the player-backed value has.
parsed_template = GetTemplateDataHelper(template, playerID, {});
TS_ASSERT_UNEVAL_EQUALS(parsed_template.upgrades[0].cost, { "stone": 160, "wood": 25, "time": 90 });
// T6: The upgrade component should still be using the old resource cost (but new time cost) for the upgrade in progress...
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 100, "wood": 50, "time": 90 });
// T7: ...but with the upgrade cancelled, it now uses the modified value.
cmpUpgrade.CancelUpgrade(playerID);
TS_ASSERT_UNEVAL_EQUALS(cmpUpgrade.GetUpgrades()[0].cost, { "stone": 160, "wood": 25, "time": 90 });
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Cheat.js (revision 22964)
@@ -1,170 +1,177 @@
function Cheat(input)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager || input.player < 0)
return;
var playerEnt = cmpPlayerManager.GetPlayerByID(input.player);
if (playerEnt == INVALID_ENTITY)
return;
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
if (!cmpPlayer)
return;
var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (!cmpPlayer.GetCheatsEnabled())
return;
switch(input.action)
{
case "addresource":
cmpPlayer.AddResource(input.text, input.parameter);
return;
case "revealmap":
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetLosRevealAll(-1, true);
return;
case "maxpopulation":
cmpPlayer.SetPopulationBonuses(500);
return;
case "changemaxpopulation":
cmpPlayer.SetMaxPopulation(500);
return;
case "convertunit":
for (let ent of input.selected)
{
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (cmpOwnership)
cmpOwnership.SetOwner(cmpPlayer.GetPlayerID());
}
return;
case "killunits":
for (let ent of input.selected)
{
let cmpHealth = Engine.QueryInterface(ent, IID_Health);
if (cmpHealth)
cmpHealth.Kill();
else
Engine.DestroyEntity(ent);
}
return;
case "defeatplayer":
cmpPlayer = QueryPlayerIDInterface(input.parameter);
if (cmpPlayer)
cmpPlayer.SetState("defeated", markForTranslation("%(player)s has been defeated (cheat)."));
return;
case "createunits":
var cmpProductionQueue = input.selected.length && Engine.QueryInterface(input.selected[0], IID_ProductionQueue);
if (!cmpProductionQueue)
{
cmpGuiInterface.PushNotification({
"type": "text",
"players": [input.player],
"message": markForTranslation("You need to select a building that trains units."),
"translateMessage": true
});
return;
}
for (let i = 0; i < Math.min(input.parameter, cmpPlayer.GetMaxPopulation() - cmpPlayer.GetPopulationCount()); ++i)
cmpProductionQueue.SpawnUnits(input.templates[i % input.templates.length], 1, null);
return;
case "fastactions":
- cmpPlayer.SetCheatTimeMultiplier((cmpPlayer.GetCheatTimeMultiplier() == 1) ? 0.01 : 1);
- return;
- case "changespeed":
- cmpPlayer.SetCheatTimeMultiplier(input.parameter);
+ let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
+ if (cmpModifiersManager.HasAnyModifier("cheat/fastactions", playerEnt))
+ cmpModifiersManager.RemoveAllModifiers("cheat/fastactions", playerEnt);
+ else
+ cmpModifiersManager.AddModifiers("cheat/fastactions", {
+ "Cost/BuildTime": { "affects": [["Structure"], ["Unit"]], "multiply": 0.01 },
+ "ResourceGatherer/BaseSpeed": { "affects": [["Structure"], ["Unit"]], "multiply": 1000 },
+ "Pack/Time": { "affects": [["Structure"], ["Unit"]], "multiply": 0.01 },
+ "Upgrade/Time": { "affects": [["Structure"], ["Unit"]], "multiply": 0.01 },
+ "ProductionQueue/TechCostMultiplier/time": { "affects": [["Structure"], ["Unit"]], "multiply": 0.01 }
+ }, playerEnt);
return;
case "changephase":
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
if (!cmpTechnologyManager)
return;
// store the phase we want in the next input parameter
var parameter;
if (!cmpTechnologyManager.IsTechnologyResearched("phase_town"))
parameter = "phase_town";
else if (!cmpTechnologyManager.IsTechnologyResearched("phase_city"))
parameter = "phase_city";
else
return;
if (TechnologyTemplates.Has(parameter + "_" + cmpPlayer.civ))
parameter += "_" + cmpPlayer.civ;
else
parameter += "_generic";
Cheat({ "player": input.player, "action": "researchTechnology", "parameter": parameter, "selected": input.selected });
return;
case "researchTechnology":
if (!input.parameter.length)
return;
var techname = input.parameter;
var cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
if (!cmpTechnologyManager)
return;
// check, if building is selected
if (input.selected[0])
{
var cmpProductionQueue = Engine.QueryInterface(input.selected[0], IID_ProductionQueue);
if (cmpProductionQueue)
{
// try to spilt the input
var tmp = input.parameter.split(/\s+/);
var number = +tmp[0];
var pair = tmp.length > 1 && (tmp[1] == "top" || tmp[1] == "bottom") ? tmp[1] : "top"; // use top as default value
// check, if valid number was parsed.
if (number || number === 0)
{
// get name of tech
var techs = cmpProductionQueue.GetTechnologiesList();
if (number > 0 && number <= techs.length)
{
var tech = techs[number-1];
if (!tech)
return;
// get name of tech
if (tech.pair)
techname = tech[pair];
else
techname = tech;
}
else
return;
}
}
}
if (TechnologyTemplates.Has(techname) &&
!cmpTechnologyManager.IsTechnologyResearched(techname))
cmpTechnologyManager.ResearchTechnology(techname);
return;
case "metaCheat":
for (let resource of Resources.GetCodes())
Cheat({ "player": input.player, "action": "addresource", "text": resource, "parameter": input.parameter });
Cheat({ "player": input.player, "action": "maxpopulation" });
Cheat({ "player": input.player, "action": "changemaxpopulation" });
Cheat({ "player": input.player, "action": "fastactions" });
for (let i=0; i<2; ++i)
Cheat({ "player": input.player, "action": "changephase", "selected": input.selected });
return;
case "playRetro":
let play = input.parameter.toLowerCase() != "off";
cmpGuiInterface.PushNotification({
"type": "play-tracks",
"tracks": play && input.parameter.split(" "),
"lock": play,
"players": [input.player]
});
return;
default:
warn("Cheat '" + input.action + "' is not implemented");
return;
}
}
Engine.RegisterGlobal("Cheat", Cheat);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/InitGame.js (revision 22964)
@@ -1,84 +1,87 @@
/**
* Called when the map has been loaded, but before the simulation has started.
* Only called when a new game is started, not when loading a saved game.
*/
function PreInitGame()
{
// We need to replace skirmish "default" entities with real ones.
// This needs to happen before AI initialization (in InitGame).
// And we need to flush destroyed entities otherwise the AI gets the wrong game state in
// the beginning and a bunch of "destroy" messages on turn 0, which just shouldn't happen.
Engine.BroadcastMessage(MT_SkirmishReplace, {});
Engine.FlushDestroyedEntities();
let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
for (let i = 1; i < numPlayers; ++i) // ignore gaia
{
let cmpTechnologyManager = QueryPlayerIDInterface(i, IID_TechnologyManager);
if (cmpTechnologyManager)
cmpTechnologyManager.UpdateAutoResearch();
}
// Explore the map inside the players' territory borders
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.ExploreTerritories();
}
function InitGame(settings)
{
// No settings when loading a map in Atlas, so do nothing
if (!settings)
{
// Map dependent initialisations of components (i.e. garrisoned units)
Engine.BroadcastMessage(MT_InitGame, {});
return;
}
if (settings.ExploreMap)
{
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
for (let i = 1; i < settings.PlayerData.length; ++i)
cmpRangeManager.ExploreAllTiles(i);
}
// Sandbox, Very Easy, Easy, Medium, Hard, Very Hard
// rate apply on resource stockpiling as gathering and trading
// time apply on building, upgrading, packing, training and technologies
let rate = [ 0.42, 0.56, 0.75, 1.00, 1.25, 1.56 ];
let time = [ 1.40, 1.25, 1.10, 1.00, 1.00, 1.00 ];
+ let cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
let cmpAIManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AIManager);
for (let i = 0; i < settings.PlayerData.length; ++i)
{
let cmpPlayer = QueryPlayerIDInterface(i);
cmpPlayer.SetCheatsEnabled(!!settings.CheatsEnabled);
if (settings.PlayerData[i] && settings.PlayerData[i].AI && settings.PlayerData[i].AI != "")
{
let AIDiff = +settings.PlayerData[i].AIDiff;
cmpAIManager.AddPlayer(settings.PlayerData[i].AI, i, AIDiff, settings.PlayerData[i].AIBehavior || "random");
cmpPlayer.SetAI(true);
AIDiff = Math.min(AIDiff, rate.length - 1);
- cmpPlayer.SetGatherRateMultiplier(rate[AIDiff]);
- cmpPlayer.SetTradeRateMultiplier(rate[AIDiff]);
- cmpPlayer.SetTimeMultiplier(time[AIDiff]);
+ cmpModifiersManager.AddModifiers("AI Bonus", {
+ "ResourceGatherer/BaseSpeed": { "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] },
+ "Trader/GainMultiplier": { "affects": ["Unit", "Structure"], "multiply": rate[AIDiff] },
+ "Cost/BuildTime": { "affects": ["Unit", "Structure"], "multiply": time[AIDiff] },
+ }, cmpPlayer.entity);
}
if (settings.PopulationCap)
cmpPlayer.SetMaxPopulation(settings.PopulationCap);
if (settings.mapType !== "scenario" && settings.StartingResources)
{
let resourceCounts = cmpPlayer.GetResourceCounts();
let newResourceCounts = {};
for (let resouces in resourceCounts)
newResourceCounts[resouces] = settings.StartingResources;
cmpPlayer.SetResourceCounts(newResourceCounts);
}
}
// Map or player data (handicap...) dependent initialisations of components (i.e. garrisoned units)
Engine.BroadcastMessage(MT_InitGame, {});
cmpAIManager.TryLoadSharedComponent();
cmpAIManager.RunGamestateInit();
}
Engine.RegisterGlobal("PreInitGame", PreInitGame);
Engine.RegisterGlobal("InitGame", InitGame);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/Player.js (revision 22964)
@@ -1,366 +1,361 @@
/**
* Used to create player entities prior to reading the rest of a map,
* all other initialization must be done after loading map (terrain/entities).
* DO NOT use other components here, as they may fail unpredictably.
* settings is the object containing settings for this map.
* newPlayers if true will remove old player entities or add new ones until
* the new number of player entities is obtained
* (used when loading a map or when Atlas changes the number of players).
*/
function LoadPlayerSettings(settings, newPlayers)
{
var playerDefaults = Engine.ReadJSONFile("simulation/data/settings/player_defaults.json").PlayerData;
// Default settings
if (!settings)
settings = {};
// Add gaia to simplify iteration
// (if gaia is not already the first civ such as when called from Atlas' ActorViewer)
if (settings.PlayerData && settings.PlayerData[0] &&
(!settings.PlayerData[0].Civ || settings.PlayerData[0].Civ != "gaia"))
settings.PlayerData.unshift(null);
var playerData = settings.PlayerData;
// Disable the AIIinterface when no AI players are present
if (playerData && !playerData.some(v => v && !!v.AI))
Engine.QueryInterface(SYSTEM_ENTITY, IID_AIInterface).Disable();
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var numPlayers = cmpPlayerManager.GetNumPlayers();
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
// Remove existing players or add new ones
if (newPlayers)
{
var settingsNumPlayers = 9; // default 8 players + gaia
if (playerData)
settingsNumPlayers = playerData.length; // includes gaia (see above)
else
warn("Player.js: Setup has no player data - using defaults");
while (settingsNumPlayers > numPlayers)
{
// Add player entity to engine
var entID = Engine.AddEntity(GetPlayerTemplateName(getSetting(playerData, playerDefaults, numPlayers, "Civ")));
var cmpPlayer = Engine.QueryInterface(entID, IID_Player);
if (!cmpPlayer)
throw new Error("Player.js: Error creating player entity " + numPlayers);
cmpPlayerManager.AddPlayer(entID);
++numPlayers;
}
while (settingsNumPlayers < numPlayers)
{
cmpPlayerManager.RemoveLastPlayer();
--numPlayers;
}
}
// Even when no new player, we must check the template compatibility as player template may be civ dependent
for (var i = 0; i < numPlayers; ++i)
{
var template = GetPlayerTemplateName(getSetting(playerData, playerDefaults, i, "Civ"));
var entID = cmpPlayerManager.GetPlayerByID(i);
if (cmpTemplateManager.GetCurrentTemplateName(entID) === template)
continue;
// We need to recreate this player to have the right template
entID = Engine.AddEntity(template);
cmpPlayerManager.ReplacePlayer(i, entID);
}
// Initialize the player data
for (var i = 0; i < numPlayers; ++i)
{
let cmpPlayer = QueryPlayerIDInterface(i);
cmpPlayer.SetName(getSetting(playerData, playerDefaults, i, "Name"));
cmpPlayer.SetCiv(getSetting(playerData, playerDefaults, i, "Civ"));
var color = getSetting(playerData, playerDefaults, i, "Color");
cmpPlayer.SetColor(color.r, color.g, color.b);
// Special case for gaia
if (i == 0)
{
// Gaia should be its own ally.
cmpPlayer.SetAlly(0);
// Gaia is everyone's enemy
for (var j = 1; j < numPlayers; ++j)
cmpPlayer.SetEnemy(j);
continue;
}
- // Note: this is not yet implemented but I leave it commented to highlight it's easy
- // If anyone ever adds handicap.
- //if (getSetting(playerData, playerDefaults, i, "GatherRateMultiplier") !== undefined)
- // cmpPlayer.SetGatherRateMultiplier(getSetting(playerData, playerDefaults, i, "GatherRateMultiplier"));
-
if (getSetting(playerData, playerDefaults, i, "PopulationLimit") !== undefined)
cmpPlayer.SetMaxPopulation(getSetting(playerData, playerDefaults, i, "PopulationLimit"));
if (getSetting(playerData, playerDefaults, i, "Resources") !== undefined)
cmpPlayer.SetResourceCounts(getSetting(playerData, playerDefaults, i, "Resources"));
if (getSetting(playerData, playerDefaults, i, "StartingTechnologies") !== undefined)
cmpPlayer.SetStartingTechnologies(getSetting(playerData, playerDefaults, i, "StartingTechnologies"));
if (getSetting(playerData, playerDefaults, i, "DisabledTechnologies") !== undefined)
cmpPlayer.SetDisabledTechnologies(getSetting(playerData, playerDefaults, i, "DisabledTechnologies"));
let disabledTemplates = [];
if (settings.DisabledTemplates !== undefined)
disabledTemplates = settings.DisabledTemplates;
if (getSetting(playerData, playerDefaults, i, "DisabledTemplates") !== undefined)
disabledTemplates = disabledTemplates.concat(getSetting(playerData, playerDefaults, i, "DisabledTemplates"));
if (disabledTemplates.length)
cmpPlayer.SetDisabledTemplates(disabledTemplates);
if (settings.DisableSpies)
{
cmpPlayer.AddDisabledTechnology("unlock_spies");
cmpPlayer.AddDisabledTemplate("special/spy");
}
// If diplomacy explicitly defined, use that; otherwise use teams
if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined)
cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy"));
else
{
// Init diplomacy
var myTeam = getSetting(playerData, playerDefaults, i, "Team");
// Set all but self as enemies as SetTeam takes care of allies
for (var j = 0; j < numPlayers; ++j)
{
if (i == j)
cmpPlayer.SetAlly(j);
else
cmpPlayer.SetEnemy(j);
}
cmpPlayer.SetTeam(myTeam === undefined ? -1 : myTeam);
}
cmpPlayer.SetFormations(
getSetting(playerData, playerDefaults, i, "Formations") ||
Engine.ReadJSONFile("simulation/data/civs/" + cmpPlayer.GetCiv() + ".json").Formations);
var startCam = getSetting(playerData, playerDefaults, i, "StartingCamera");
if (startCam !== undefined)
cmpPlayer.SetStartingCamera(startCam.Position, startCam.Rotation);
}
// NOTE: We need to do the team locking here, as otherwise
// SetTeam can't ally the players.
if (settings.LockTeams)
for (let i = 0; i < numPlayers; ++i)
QueryPlayerIDInterface(i).SetLockTeams(true);
}
// Get a setting if it exists or return default
function getSetting(settings, defaults, idx, property)
{
if (settings && settings[idx] && (property in settings[idx]))
return settings[idx][property];
// Use defaults
if (defaults && defaults[idx] && (property in defaults[idx]))
return defaults[idx][property];
return undefined;
}
function GetPlayerTemplateName(civ)
{
let path = "special/player/player";
if (Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).TemplateExists(path + "_" + civ))
return path + "_" + civ;
return path;
}
/**
* @param id An entity's ID
* @returns The entity ID of the owner player (not his player ID) or ent if ent is a player entity.
*/
function QueryOwnerEntityID(ent)
{
let cmpPlayer = Engine.QueryInterface(ent, IID_Player);
if (cmpPlayer)
return ent;
let cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership)
return null;
let owner = cmpOwnership.GetOwner();
if (owner == INVALID_PLAYER)
return null;
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return null;
return cmpPlayerManager.GetPlayerByID(owner);
}
/**
* Similar to Engine.QueryInterface but applies to the player entity
* that owns the given entity.
* iid is typically IID_Player.
*/
function QueryOwnerInterface(ent, iid = IID_Player)
{
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
if (!cmpOwnership)
return null;
var owner = cmpOwnership.GetOwner();
if (owner == INVALID_PLAYER)
return null;
return QueryPlayerIDInterface(owner, iid);
}
/**
* Similar to Engine.QueryInterface but applies to the player entity
* with the given ID number.
* iid is typically IID_Player.
*/
function QueryPlayerIDInterface(id, iid = IID_Player)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
var playerEnt = cmpPlayerManager.GetPlayerByID(id);
if (!playerEnt)
return null;
return Engine.QueryInterface(playerEnt, iid);
}
/**
* Similar to Engine.QueryInterface but first checks if the entity
* mirages the interface.
*/
function QueryMiragedInterface(ent, iid)
{
var cmp = Engine.QueryInterface(ent, IID_Mirage);
if (cmp && !cmp.Mirages(iid))
return null;
else if (!cmp)
cmp = Engine.QueryInterface(ent, iid);
return cmp;
}
/**
* Similar to Engine.QueryInterface, but checks for all interfaces
* implementing a builder list (currently Foundation and Repairable)
* TODO Foundation and Repairable could both implement a BuilderList component
*/
function QueryBuilderListInterface(ent)
{
return Engine.QueryInterface(ent, IID_Foundation) || Engine.QueryInterface(ent, IID_Repairable);
}
/**
* Returns true if the entity 'target' is owned by an ally of
* the owner of 'entity'.
*/
function IsOwnedByAllyOfEntity(entity, target)
{
return IsOwnedByEntityHelper(entity, target, "IsAlly");
}
function IsOwnedByMutualAllyOfEntity(entity, target)
{
return IsOwnedByEntityHelper(entity, target, "IsMutualAlly");
}
function IsOwnedByEntityHelper(entity, target, check)
{
// Figure out which player controls us
let owner = 0;
let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
if (cmpOwnership)
owner = cmpOwnership.GetOwner();
// Figure out which player controls the target entity
let targetOwner = 0;
let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
if (cmpOwnershipTarget)
targetOwner = cmpOwnershipTarget.GetOwner();
let cmpPlayer = QueryPlayerIDInterface(owner);
return cmpPlayer && cmpPlayer[check](targetOwner);
}
/**
* Returns true if the entity 'target' is owned by player
*/
function IsOwnedByPlayer(player, target)
{
var cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
return cmpOwnershipTarget && player == cmpOwnershipTarget.GetOwner();
}
function IsOwnedByGaia(target)
{
return IsOwnedByPlayer(0, target);
}
/**
* Returns true if the entity 'target' is owned by an ally of player
*/
function IsOwnedByAllyOfPlayer(player, target)
{
return IsOwnedByHelper(player, target, "IsAlly");
}
function IsOwnedByMutualAllyOfPlayer(player, target)
{
return IsOwnedByHelper(player, target, "IsMutualAlly");
}
function IsOwnedByNeutralOfPlayer(player,target)
{
return IsOwnedByHelper(player, target, "IsNeutral");
}
function IsOwnedByEnemyOfPlayer(player, target)
{
return IsOwnedByHelper(player, target, "IsEnemy");
}
function IsOwnedByHelper(player, target, check)
{
let targetOwner = 0;
let cmpOwnershipTarget = Engine.QueryInterface(target, IID_Ownership);
if (cmpOwnershipTarget)
targetOwner = cmpOwnershipTarget.GetOwner();
let cmpPlayer = QueryPlayerIDInterface(player);
return cmpPlayer && cmpPlayer[check](targetOwner);
}
Engine.RegisterGlobal("LoadPlayerSettings", LoadPlayerSettings);
Engine.RegisterGlobal("QueryOwnerEntityID", QueryOwnerEntityID);
Engine.RegisterGlobal("QueryOwnerInterface", QueryOwnerInterface);
Engine.RegisterGlobal("QueryPlayerIDInterface", QueryPlayerIDInterface);
Engine.RegisterGlobal("QueryMiragedInterface", QueryMiragedInterface);
Engine.RegisterGlobal("QueryBuilderListInterface", QueryBuilderListInterface);
Engine.RegisterGlobal("IsOwnedByAllyOfEntity", IsOwnedByAllyOfEntity);
Engine.RegisterGlobal("IsOwnedByMutualAllyOfEntity", IsOwnedByMutualAllyOfEntity);
Engine.RegisterGlobal("IsOwnedByPlayer", IsOwnedByPlayer);
Engine.RegisterGlobal("IsOwnedByGaia", IsOwnedByGaia);
Engine.RegisterGlobal("IsOwnedByAllyOfPlayer", IsOwnedByAllyOfPlayer);
Engine.RegisterGlobal("IsOwnedByMutualAllyOfPlayer", IsOwnedByMutualAllyOfPlayer);
Engine.RegisterGlobal("IsOwnedByNeutralOfPlayer", IsOwnedByNeutralOfPlayer);
Engine.RegisterGlobal("IsOwnedByEnemyOfPlayer", IsOwnedByEnemyOfPlayer);
Index: ps/trunk/binaries/data/mods/public/simulation/helpers/TraderGain.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/helpers/TraderGain.js (revision 22963)
+++ ps/trunk/binaries/data/mods/public/simulation/helpers/TraderGain.js (revision 22964)
@@ -1,70 +1,66 @@
function CalculateTraderGain(firstMarket, secondMarket, traderTemplate, trader)
{
let cmpMarket1 = QueryMiragedInterface(firstMarket, IID_Market);
let cmpMarket2 = QueryMiragedInterface(secondMarket, IID_Market);
if (!cmpMarket1 || !cmpMarket2)
return null;
let cmpMarket1Player = QueryOwnerInterface(firstMarket);
let cmpMarket2Player = QueryOwnerInterface(secondMarket);
if (!cmpMarket1Player || !cmpMarket2Player)
return null;
let cmpFirstMarketPosition = Engine.QueryInterface(firstMarket, IID_Position);
let cmpSecondMarketPosition = Engine.QueryInterface(secondMarket, IID_Position);
if (!cmpFirstMarketPosition || !cmpFirstMarketPosition.IsInWorld() ||
!cmpSecondMarketPosition || !cmpSecondMarketPosition.IsInWorld())
return null;
let firstMarketPosition = cmpFirstMarketPosition.GetPosition2D();
let secondMarketPosition = cmpSecondMarketPosition.GetPosition2D();
let mapSize = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain).GetMapSize();
let gainMultiplier = TradeGainNormalization(mapSize);
if (trader)
{
let cmpTrader = Engine.QueryInterface(trader, IID_Trader);
if (!cmpTrader)
return null;
gainMultiplier *= cmpTrader.GetTraderGainMultiplier();
}
- else //called from the gui, modifications already applied
+ else // Called from the gui, modifications already applied.
{
if (!traderTemplate || !traderTemplate.GainMultiplier)
return null;
gainMultiplier *= traderTemplate.GainMultiplier;
}
let gain = {};
// Calculate ordinary Euclidean distance between markets.
// We don't use pathfinder, because ordinary distance looks more fair.
let distanceSq = firstMarketPosition.distanceToSquared(secondMarketPosition);
// We calculate gain as square of distance to encourage trading between remote markets
// and gainMultiplier corresponds to the gain for a 100m distance
gain.traderGain = gainMultiplier * TradeGain(distanceSq, mapSize);
gain.market1Owner = cmpMarket1Player.GetPlayerID();
gain.market2Owner = cmpMarket2Player.GetPlayerID();
// If trader undefined, the trader owner is supposed to be the same as the first market
let cmpPlayer = trader ? QueryOwnerInterface(trader) : cmpMarket1Player;
if (!cmpPlayer)
return null;
gain.traderOwner = cmpPlayer.GetPlayerID();
- // Add potential player trade multipliers
- let playerBonus = cmpPlayer.GetTradeRateMultiplier();
+
// If markets belong to different players, add gain from international trading
if (gain.market1Owner != gain.market2Owner)
{
- let market1PlayerBonus = cmpMarket1Player.GetTradeRateMultiplier();
- let market2PlayerBonus = cmpMarket2Player.GetTradeRateMultiplier();
let internationalBonus1 = cmpMarket1.GetInternationalBonus();
let internationalBonus2 = cmpMarket2.GetInternationalBonus();
- gain.market1Gain = Math.round(gain.traderGain * internationalBonus1 * market1PlayerBonus);
- gain.market2Gain = Math.round(gain.traderGain * internationalBonus2 * market2PlayerBonus);
+ gain.market1Gain = Math.round(gain.traderGain * internationalBonus1);
+ gain.market2Gain = Math.round(gain.traderGain * internationalBonus2);
}
- gain.traderGain = Math.round(gain.traderGain * playerBonus);
return gain;
}
Engine.RegisterGlobal("CalculateTraderGain", CalculateTraderGain);