1
0
forked from mirrors/0ad
Files
0ad/binaries/data/mods/public/simulation/components/Player.js
T
elexis cf04bad4bc Ingame summary screen and allied resource / population tooltips for active players. Patch by Imarok, also reviewed by fatherbushido and discussed with mimo, fixes #3841.
Shows (only) the stats of the current player in the summary screen while
playing.
Also shows those of mutual allies too if the shared vision tech is
researched.
Display the resources and population counts of allies in the tooltips of
the top panel.
Use a sprintf for the pop / limit ratio and remove hardcoded resource
strings in the XML, refs #3934.

This was SVN commit r18533.
2016-07-20 01:22:07 +00:00

857 lines
21 KiB
JavaScript

function Player() {}
Player.prototype.Schema =
"<element name='SharedLosTech' a:help='Allies will share los when this technology is researched. Leave empty to never share LOS.'>" +
"<text/>" +
"</element>" +
"<element name='SharedDropsitesTech' a:help='Allies will share dropsites when this technology is researched. Leave empty to never share dropsites.'>" +
"<text/>" +
"</element>";
Player.prototype.Init = function()
{
this.playerID = undefined;
this.name = undefined; // define defaults elsewhere (supporting other languages)
this.civ = undefined;
this.color = { "r": 0.0, "g": 0.0, "b": 0.0, "a": 1.0 };
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 = {
"food": 300,
"wood": 300,
"metal": 300,
"stone": 300
};
// goods for next trade-route and its proba in % (the sum of probas must be 100)
this.tradingGoods = [
{ "goods": "wood", "proba": 30 },
{ "goods": "stone", "proba": 35 },
{ "goods": "metal", "proba": 35 },
];
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.gatherRateMultiplier = 1;
this.tradeRateMultiplier = 1;
this.cheatsEnabled = false;
this.cheatTimeMultiplier = 1;
this.heroes = [];
this.resourceNames = {
"food": markForTranslation("Food"),
"wood": markForTranslation("Wood"),
"metal": markForTranslation("Metal"),
"stone": markForTranslation("Stone"),
};
this.disabledTemplates = {};
this.disabledTechnologies = {};
this.startingTechnologies = [];
};
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)
{
this.color = { "r": r/255.0, "g": g/255.0, "b": b/255.0, "a": 1.0 };
};
Player.prototype.GetColor = function()
{
return 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(ApplyValueModificationsToPlayer("Player/MaxPopulation", this.maxPop, this.entity, this.playerID));
};
Player.prototype.SetGatherRateMultiplier = function(value)
{
this.gatherRateMultiplier = value;
};
Player.prototype.GetGatherRateMultiplier = function()
{
return this.gatherRateMultiplier;
};
Player.prototype.SetTradeRateMultiplier = function(value)
{
this.tradeRateMultiplier = value;
};
Player.prototype.GetTradeRateMultiplier = function()
{
return this.tradeRateMultiplier;
};
Player.prototype.GetHeroes = function()
{
return this.heroes;
};
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)
{
if (resources.food !== undefined)
this.resourceCount.food = resources.food;
if (resources.wood !== undefined)
this.resourceCount.wood = resources.wood;
if (resources.stone !== undefined)
this.resourceCount.stone = resources.stone;
if (resources.metal !== undefined)
this.resourceCount.metal = resources.metal;
};
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;
}
// Subtract the resources
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 = 100*Math.random();
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)
{
var sumProba = 0;
for (var resource in tradingGoods)
sumProba += tradingGoods[resource];
if (sumProba != 100) // consistency check
{
error("Player.js SetTradingGoods: " + uneval(tradingGoods));
tradingGoods = { "food": 20, "wood":20, "stone":30, "metal":30 };
}
this.tradingGoods = [];
for (var resource in tradingGoods)
this.tradingGoods.push( {"goods": resource, "proba": tradingGoods[resource]} );
};
Player.prototype.GetState = function()
{
return this.state;
};
Player.prototype.SetState = function(newState, resign)
{
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";
// Reveal map to all or only that player
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
cmpRangeManager.SetLosRevealAll(won ? -1 : this.playerID, true);
if (!won)
{
// 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.BroadcastMessage(won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID });
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (won)
cmpGUIInterface.PushNotification({
"type": "won",
"players": [this.playerID]
});
else
cmpGUIInterface.PushNotification({
"type": "defeat",
"players": [this.playerID],
"resign": resign
});
};
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
let cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (cmpPlayerManager && this.team != -1)
for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++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;
};
Player.prototype.SetDiplomacy = function(dipl)
{
this.diplomacy = dipl;
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 cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
if (!cmpRangeManager || !cmpPlayerManager || !cmpTechnologyManager)
return;
let sharedLos = [];
if (!cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech))
{
cmpRangeManager.SetSharedLos(this.playerID, [this.playerID]);
return;
}
for (var i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
if (this.IsMutualAlly(i))
sharedLos.push(i);
cmpRangeManager.SetSharedLos(this.playerID, sharedLos);
};
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.SetAlly = function(id)
{
this.SetDiplomacyIndex(id, 1);
};
/**
* Check if given player is our ally
*/
Player.prototype.IsAlly = function(id)
{
return this.diplomacy[id] > 0;
};
/**
* 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);
};
/**
* 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);
};
/**
* Get all enemies of a given player.
*/
Player.prototype.GetEnemies = function()
{
var enemies = [];
for (var i = 0; i < this.diplomacy.length; ++i)
if (this.diplomacy[i] < 0)
enemies.push(i);
return enemies;
};
/**
* Check if given player is our enemy
*/
Player.prototype.IsEnemy = function(id)
{
return this.diplomacy[id] < 0;
};
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 && cmpIdentity.HasClass("Hero"))
{
//Remove from Heroes list
var index = this.heroes.indexOf(msg.entity);
if (index >= 0)
this.heroes.splice(index, 1);
}
}
if (msg.to == this.playerID)
{
if (cmpCost)
this.popUsed += cmpCost.GetPopCost();
if (cmpIdentity && cmpIdentity.HasClass("Hero"))
this.heroes.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.SetCheatsEnabled = function(flag)
{
this.cheatsEnabled = flag;
};
Player.prototype.GetCheatsEnabled = function()
{
return this.cheatsEnabled;
};
Player.prototype.SetCheatTimeMultiplier = function(time)
{
this.cheatTimeMultiplier = time;
};
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;
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);