Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 15613)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 15614)
@@ -1,692 +1,698 @@
// Chat data
const CHAT_TIMEOUT = 30000;
const MAX_NUM_CHAT_LINES = 20;
var chatMessages = [];
var chatTimers = [];
// Notification Data
const NOTIFICATION_TIMEOUT = 10000;
const MAX_NUM_NOTIFICATION_LINES = 3;
var notifications = [];
var notificationsTimers = [];
var cheats = getCheatsData();
function getCheatsData()
{
var cheats = {};
var cheatFileList = getJSONFileList("simulation/data/cheats/");
for each (var fileName in cheatFileList)
{
var currentCheat = parseJSONData("simulation/data/cheats/"+fileName+".json");
if (Object.keys(cheats).indexOf(currentCheat.Name) !== -1)
warn("Cheat name '" + currentCheat.Name + "' is already present");
else
cheats[currentCheat.Name] = currentCheat.Data;
}
return cheats;
}
var g_NotificationsTypes =
{
"chat": function(notification, player)
{
var message = {
"type": "message",
"text": notification.message
}
var guid = findGuidForPlayerID(g_PlayerAssignments, player);
if (guid == undefined)
{
message["guid"] = -1;
message["player"] = player;
} else {
message["guid"] = guid;
}
addChatMessage(message);
},
"aichat": function(notification, player)
{
var message = {
"type": "message",
"text": notification.message
}
if (notification.type == "aichat")
message["translate"] = true;
var guid = findGuidForPlayerID(g_PlayerAssignments, player);
if (guid == undefined)
{
message["guid"] = -1;
message["player"] = player;
} else {
message["guid"] = guid;
}
addChatMessage(message);
},
"defeat": function(notification, player)
{
addChatMessage({
"type": "defeat",
"guid": findGuidForPlayerID(g_PlayerAssignments, player),
"player": player
});
// If the diplomacy panel is open refresh it.
if (isDiplomacyOpen)
openDiplomacy();
},
"diplomacy": function(notification, player)
{
addChatMessage({
"type": "diplomacy",
"player": player,
"player1": notification.player1,
"status": notification.status
});
// If the diplomacy panel is open refresh it.
if (isDiplomacyOpen)
openDiplomacy();
},
"quit": function(notification, player)
{
exit(); // TODO this doesn't work anymore
},
"tribute": function(notification, player)
{
addChatMessage({
"type": "tribute",
"player": player,
"player1": notification.donator,
"amounts": notification.amounts
});
},
"attack": function(notification, player)
{
if (player != Engine.GetPlayerID())
return;
if (Engine.ConfigDB_GetValue("user", "gui.session.attacknotificationmessage") !== "true")
return;
addChatMessage({
"type": "attack",
"player": player,
"attacker": notification.attacker
});
},
"dialog": function(notification, player)
{
if (player == Engine.GetPlayerID())
openDialog(notification.dialogName, notification.data, player);
},
+ "resetselectionpannel": function(notification, player)
+ {
+ if (player != Engine.GetPlayerID())
+ return;
+ g_Selection.rebuildSelection([]);
+ }
};
// Notifications
function handleNotifications()
{
var notification = Engine.GuiInterfaceCall("GetNextNotification");
if (!notification)
return;
if (!notification.type)
{
error("notification without type found.\n"+uneval(notification))
return;
}
if (!notification.players)
{
error("notification without players found.\n"+uneval(notification))
return;
}
var action = g_NotificationsTypes[notification.type];
if (!action)
{
error("unknown notification type '" + notification.type + "' found.");
return;
}
for (var player of notification.players)
action(notification, player);
}
function updateTimeNotifications()
{
var notifications = Engine.GuiInterfaceCall("GetTimeNotifications");
var notificationText = "";
var playerID = Engine.GetPlayerID();
for (var n of notifications)
{
if (!n.players)
{
warn("notification has unknown player list. Text:\n"+n.message);
continue;
}
if (n.players.indexOf(playerID) == -1)
continue;
var message = n.message;
if (n.translateMessage)
message = translate(message);
var parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
parameters.time = timeToString(n.time);
notificationText += sprintf(message, parameters) + "\n";
}
Engine.GetGUIObjectByName("notificationText").caption = notificationText;
}
// Returns [username, playercolor] for the given player
function getUsernameAndColor(player)
{
// This case is hit for AIs, whose names don't exist in playerAssignments.
var color = g_Players[player].color;
return [
escapeText(g_Players[player].name),
color.r + " " + color.g + " " + color.b,
];
}
// Messages
function handleNetMessage(message)
{
log(sprintf(translate("Net message: %(message)s"), { message: uneval(message) }));
switch (message.type)
{
case "netstatus":
// If we lost connection, further netstatus messages are useless
if (g_Disconnected)
return;
var obj = Engine.GetGUIObjectByName("netStatus");
switch (message.status)
{
case "waiting_for_players":
obj.caption = translate("Waiting for other players to connect...");
obj.hidden = false;
break;
case "join_syncing":
obj.caption = translate("Synchronising gameplay with other players...");
obj.hidden = false;
break;
case "active":
obj.caption = "";
obj.hidden = true;
break;
case "connected":
obj.caption = translate("Connected to the server.");
obj.hidden = false;
break;
case "authenticated":
obj.caption = translate("Connection to the server has been authenticated.");
obj.hidden = false;
break;
case "disconnected":
g_Disconnected = true;
obj.caption = translate("Connection to the server has been lost.") + "\n\n" + translate("The game has ended.");
obj.hidden = false;
break;
default:
error("Unrecognised netstatus type '" + message.status + "'");
break;
}
break;
case "players":
// Find and report all leavings
for (var host in g_PlayerAssignments)
{
if (! message.hosts[host])
{
// Tell the user about the disconnection
addChatMessage({ "type": "disconnect", "guid": host });
// Update the cached player data, so we can display the disconnection status
updatePlayerDataRemove(g_Players, host);
}
}
// Find and report all joinings
for (var host in message.hosts)
{
if (! g_PlayerAssignments[host])
{
// Update the cached player data, so we can display the correct name
updatePlayerDataAdd(g_Players, host, message.hosts[host]);
// Tell the user about the connection
addChatMessage({ "type": "connect", "guid": host }, message.hosts);
}
}
g_PlayerAssignments = message.hosts;
if (g_IsController)
{
var players = [ assignment.name for each (assignment in g_PlayerAssignments) ]
Engine.SendChangeStateGame(Object.keys(g_PlayerAssignments).length, players.join(", "));
}
break;
case "chat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text });
break;
case "aichat":
addChatMessage({ "type": "message", "guid": message.guid, "text": message.text, "translate": true });
break;
// To prevent errors, ignore these message types that occur during autostart
case "gamesetup":
case "start":
break;
default:
error("Unrecognised net message type '" + message.type + "'");
}
}
function submitChatDirectly(text)
{
if (text.length)
{
if (g_IsNetworked)
Engine.SendNetworkChat(text);
else
addChatMessage({ "type": "message", "guid": "local", "text": text });
}
}
function submitChatInput()
{
var input = Engine.GetGUIObjectByName("chatInput");
var text = input.caption;
var isCheat = false;
if (text.length)
{
if (!g_IsObserver && g_Players[Engine.GetPlayerID()].cheatsEnabled)
{
for each (var cheat in Object.keys(cheats))
{
// Line must start with the cheat.
if (text.indexOf(cheat) !== 0)
continue;
// test for additional parameter which is the rest of the string after the cheat
var parameter = "";
if (cheats[cheat].DefaultParameter !== undefined)
{
var par = text.substr(cheat.length);
par = par.replace(/^\W+/, '').replace(/\W+$/, ''); // remove whitespaces at start and end
// check, if the isNumeric flag is set
if (cheats[cheat].isNumeric)
{
// Match the first word in the substring.
var match = par.match(/\S+/);
if (match && match[0])
par = Math.floor(match[0]);
// check, if valid number could be parsed
if (par <= 0 || isNaN(par))
par = "";
}
// replace default parameter, if not empty or number
if (par.length > 0 || parseFloat(par) === par)
parameter = par;
else
parameter = cheats[cheat].DefaultParameter;
}
Engine.PostNetworkCommand({
"type": "cheat",
"action": cheats[cheat].Action,
"parameter": parameter,
"text": cheats[cheat].Type,
"selected": g_Selection.toList(),
"templates": cheats[cheat].Templates,
"player": Engine.GetPlayerID()});
isCheat = true;
break;
}
}
if (!isCheat)
{
if (Engine.GetGUIObjectByName("toggleTeamChat").checked)
text = "/team " + text;
if (g_IsNetworked)
Engine.SendNetworkChat(text);
else
addChatMessage({ "type": "message", "guid": "local", "text": text });
}
input.caption = ""; // Clear chat input
}
input.blur(); // Remove focus
toggleChatWindow();
}
function addChatMessage(msg, playerAssignments)
{
// Default to global assignments, but allow overriding for when reporting
// new players joining
if (!playerAssignments)
playerAssignments = g_PlayerAssignments;
var playerColor, username;
// No context by default. May be set by parseChatCommands().
msg.context = "";
if ("guid" in msg && playerAssignments[msg.guid])
{
var n = playerAssignments[msg.guid].player;
// Observers have an ID of -1 which is not a valid index.
if (n < 0)
n = 0;
playerColor = g_Players[n].color.r + " " + g_Players[n].color.g + " " + g_Players[n].color.b;
username = escapeText(playerAssignments[msg.guid].name);
// Parse in-line commands in regular messages.
if (msg.type == "message")
parseChatCommands(msg, playerAssignments);
}
else if (msg.type == "defeat" && msg.player)
{
[username, playerColor] = getUsernameAndColor(msg.player);
}
else if (msg.type == "message")
{
[username, playerColor] = getUsernameAndColor(msg.player);
parseChatCommands(msg, playerAssignments);
}
else
{
playerColor = "255 255 255";
username = translate("Unknown player");
}
var formatted;
switch (msg.type)
{
case "connect":
formatted = sprintf(translate("%(player)s has joined the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "disconnect":
formatted = sprintf(translate("%(player)s has left the game."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "defeat":
// In singleplayer, the local player is "You". "You has" is incorrect.
if (!g_IsNetworked && msg.player == Engine.GetPlayerID())
formatted = translate("You have been defeated.");
else
formatted = sprintf(translate("%(player)s has been defeated."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "diplomacy":
var status = (msg.status == "ally" ? "allied" : (msg.status == "enemy" ? "at war" : "neutral"));
if (msg.player == Engine.GetPlayerID())
{
[username, playerColor] = getUsernameAndColor(msg.player1);
if (msg.status == "ally")
formatted = sprintf(translate("You are now allied with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
else if (msg.status == "enemy")
formatted = sprintf(translate("You are now at war with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
else // (msg.status == "neutral")
formatted = sprintf(translate("You are now neutral with %(player)s."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
}
else if (msg.player1 == Engine.GetPlayerID())
{
[username, playerColor] = getUsernameAndColor(msg.player);
if (msg.status == "ally")
formatted = sprintf(translate("%(player)s is now allied with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
else if (msg.status == "enemy")
formatted = sprintf(translate("%(player)s is now at war with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
else // (msg.status == "neutral")
formatted = sprintf(translate("%(player)s is now neutral with you."), { player: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
}
else // No need for other players to know of this.
return;
break;
case "tribute":
if (msg.player != Engine.GetPlayerID())
return;
[username, playerColor] = getUsernameAndColor(msg.player1);
// Format the amounts to proper English: 200 food, 100 wood, and 300 metal; 100 food; 400 wood and 200 stone
var amounts = Object.keys(msg.amounts)
.filter(function (type) { return msg.amounts[type] > 0; })
.map(function (type) { return msg.amounts[type] + " " + type; });
if (amounts.length > 1)
{
var lastAmount = amounts.pop();
amounts = sprintf(translate("%(previousAmounts)s and %(lastAmount)s"), {
previousAmounts: amounts.join(translate(", ")),
lastAmount: lastAmount
});
}
formatted = sprintf(translate("%(player)s has sent you %(amounts)s."), {
player: "[color=\"" + playerColor + "\"]" + username + "[/color]",
amounts: amounts
});
break;
case "attack":
if (msg.player != Engine.GetPlayerID())
return;
[username, playerColor] = getUsernameAndColor(msg.attacker);
formatted = sprintf(translate("You have been attacked by %(attacker)s!"), { attacker: "[color=\"" + playerColor + "\"]" + username + "[/color]" });
break;
case "message":
// May have been hidden by the 'team' command.
if (msg.hide)
return;
var message;
if ("translate" in msg && msg.translate)
message = translate(msg.text); // No need to escape, not a use message.
else
message = escapeText(msg.text)
if (msg.action)
{
if (msg.context !== "")
{
formatted = sprintf(translate("(%(context)s) * %(user)s %(message)s"), {
context: msg.context,
user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
message: message
});
}
else
{
formatted = sprintf(translate("* %(user)s %(message)s"), {
user: "[color=\"" + playerColor + "\"]" + username + "[/color]",
message: message
});
}
}
else
{
var userTag = sprintf(translate("<%(user)s>"), { user: username })
var formattedUserTag = sprintf(translate("<%(user)s>"), { user: "[color=\"" + playerColor + "\"]" + username + "[/color]" })
if (msg.context !== "")
{
formatted = sprintf(translate("(%(context)s) %(userTag)s %(message)s"), {
context: msg.context,
userTag: formattedUserTag,
message: message
});
}
else
{
formatted = sprintf(translate("%(userTag)s %(message)s"), { userTag: formattedUserTag, message: message});
}
}
break;
default:
error(sprintf("Invalid chat message '%(message)s'", { message: uneval(msg) }));
return;
}
chatMessages.push(formatted);
chatTimers.push(setTimeout(removeOldChatMessages, CHAT_TIMEOUT));
if (chatMessages.length > MAX_NUM_CHAT_LINES)
removeOldChatMessages();
else
Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n");
}
function removeOldChatMessages()
{
clearTimeout(chatTimers[0]); // The timer only needs to be cleared when new messages bump old messages off
chatTimers.shift();
chatMessages.shift();
Engine.GetGUIObjectByName("chatText").caption = chatMessages.join("\n");
}
// Parses chat messages for commands.
function parseChatCommands(msg, playerAssignments)
{
// Only interested in messages that start with '/'.
if (!msg.text || msg.text[0] != '/')
return;
var sender;
if (playerAssignments[msg.guid])
sender = playerAssignments[msg.guid].player;
else
sender = msg.player;
var recurse = false;
var split = msg.text.split(/\s/);
// Parse commands embedded in the message.
switch (split[0])
{
case "/all":
// Resets values that 'team' or 'enemy' may have set.
msg.context = "";
msg.hide = false;
recurse = true;
break;
case "/team":
// Check if we are in a team.
if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1)
{
if (g_Players[Engine.GetPlayerID()].team != g_Players[sender].team)
msg.hide = true;
else
msg.context = translate("Team");
}
else
msg.hide = true;
recurse = true;
break;
case "/enemy":
// Check if we are in a team.
if (g_Players[Engine.GetPlayerID()] && g_Players[Engine.GetPlayerID()].team != -1)
{
if (g_Players[Engine.GetPlayerID()].team == g_Players[sender].team && sender != Engine.GetPlayerID())
msg.hide = true;
else
msg.context = translate("Enemy");
}
recurse = true;
break;
case "/me":
msg.action = true;
break;
case "/msg":
var trimmed = msg.text.substr(split[0].length + 1);
var matched = "";
// Reject names which don't match or are a superset of the intended name.
for each (var player in playerAssignments)
if (trimmed.indexOf(player.name + " ") == 0 && player.name.length > matched.length)
matched = player.name;
// If the local player's name was the longest one matched, show the message.
var playerName = g_Players[Engine.GetPlayerID()].name;
if (matched.length && (matched == playerName || sender == Engine.GetPlayerID()))
{
msg.context = translate("Private");
msg.text = trimmed.substr(matched.length + 1);
msg.hide = false; // Might override team message hiding.
return;
}
else
msg.hide = true;
break;
default:
return;
}
msg.text = msg.text.substr(split[0].length + 1);
// Hide the message if parsing commands left it empty.
if (!msg.text.length)
msg.hide = true;
// Attempt to parse more commands if the current command allows it.
if (recurse)
parseChatCommands(msg, playerAssignments);
}
function sendDialogAnswer(guiObject, dialogName)
{
Engine.GetGUIObjectByName(dialogName+"-dialog").hidden = true;
Engine.PostNetworkCommand({
"type": "dialog-answer",
"dialog": dialogName,
"answer": guiObject.name.split("-").pop(),
});
resumeGame();
}
function openDialog(dialogName, data, player)
{
var dialog = Engine.GetGUIObjectByName(dialogName+"-dialog");
if (!dialog)
{
warn("messages.js: Unknow dialog with name "+dialogName);
return;
}
dialog.hidden = false;
for (var objName in data)
{
var obj = Engine.GetGUIObjectByName(dialogName + "-dialog-" + objName);
if (!obj)
{
warn("messages.js: Key '" + objName + "' not found in '" + dialogName + "' dialog.");
continue;
}
for (var key in data[objName])
{
var n = data[objName][key];
if (typeof n == "object" && n.message)
{
var message = n.message;
if (n.translateMessage)
message = translate(message);
var parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
obj[key] = sprintf(message, parameters);
}
else
obj[key] = n;
}
}
pauseGame();
}
Index: ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.json
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.json (revision 15613)
+++ ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest.json (revision 15614)
@@ -1,16 +1,15 @@
{
"settings" : {
"Name" : "Survival of the Fittest",
"Script" : "survivalofthefittest.js",
"Description" : "IMPORTANT NOTE: AI PLAYERS WONT WORK WITH THIS MAP\n\nProtect your base against endless waves of enemies. Use your woman citizen to gather resources. The last player remaining will be the winner.",
"BaseTerrain" : ["medit_sea_depths"],
"BaseHeight" : 30,
"CircularMap" : true,
- "Keywords": ["demo"],
"TriggerScripts": [
"scripts/TriggerHelper.js",
"random/survivalofthefittest_triggers.js"
],
"XXXXXX" : "Optionally define other things here, like we would for a scenario"
}
}
Index: ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js (revision 15613)
+++ ps/trunk/binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js (revision 15614)
@@ -1,159 +1,171 @@
var treasures = ["gaia/special_treasure_food_barrel",
"gaia/special_treasure_food_bin",
"gaia/special_treasure_food_crate",
"gaia/special_treasure_food_jars",
"gaia/special_treasure_metal",
"gaia/special_treasure_stone",
"gaia/special_treasure_wood",
"gaia/special_treasure_wood",
"gaia/special_treasure_wood"];
var attackerEntityTemplates = ["units/athen_champion_infantry",
"units/athen_champion_marine",
"units/athen_champion_ranged",
"units/brit_champion_cavalry",
"units/brit_champion_infantry",
"units/cart_champion_cavalry",
"units/cart_champion_elephant",
"units/cart_champion_infantry",
"units/cart_champion_pikeman",
"units/gaul_champion_cavalry",
"units/gaul_champion_fanatic",
"units/gaul_champion_infantry",
"units/iber_champion_cavalry",
"units/iber_champion_infantry",
"units/mace_champion_cavalry",
"units/mace_champion_infantry_a",
"units/mace_champion_infantry_e",
"units/maur_champion_chariot",
"units/maur_champion_elephant",
"units/maur_champion_infantry",
"units/maur_champion_maiden",
"units/maur_champion_maiden_archer",
"units/pers_champion_cavalry",
"units/pers_champion_infantry",
"units/ptol_champion_cavalry",
"units/ptol_champion_elephant",
"units/rome_champion_cavalry",
"units/rome_champion_infantry",
"units/sele_champion_cavalry",
"units/sele_champion_chariot",
"units/sele_champion_elephant",
"units/sele_champion_infantry_pikeman",
"units/sele_champion_infantry_swordsman",
"units/spart_champion_infantry_pike",
"units/spart_champion_infantry_spear",
"units/spart_champion_infantry_sword"];
Trigger.prototype.StartAnEnemyWave = function()
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
var attackerEntity = attackerEntityTemplates[Math.floor(Math.random() * attackerEntityTemplates.length)];
var attackerCount = Math.round(cmpTimer.GetTime() / 180000); // A soldier for each 3 minutes of the game. Should be waves of 20 soldiers after an hour
// spawn attackers
var attackers = TriggerHelper.SpawnUnitsFromTriggerPoints("A", attackerEntity, attackerCount, 0);
for (var origin in attackers)
{
var cmpPlayer = QueryOwnerInterface(+origin, IID_Player);
if (cmpPlayer.GetState() != "active")
continue;
var cmpPosition = Engine.QueryInterface(this.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position);
// this shouldn't happen if the player is still active
if (!cmpPosition || !cmpPosition.IsInWorld)
continue;
// store the x and z coordinates in the command
var cmd = cmpPosition.GetPosition();
cmd.type = "attack-walk";
cmd.entities = attackers[origin];
cmd.queued = true;
// send the attack-walk command
ProcessCommand(0, cmd);
}
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [1,2,3,4,5,6,7,8],
"message": markForTranslation("An enemy wave is attacking!"),
"translateMessage": true
});
cmpTrigger.DoAfterDelay(180000, "StartAnEnemyWave", {}); // The next wave will come in 3 minutes
}
Trigger.prototype.InitGame = function()
{
var numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
// Find all of the civic centers
for (var i = 1; i < numberOfPlayers; ++i)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var playerEntities = cmpRangeManager.GetEntitiesByPlayer(i); // Get all of each player's entities
for each (var entity in playerEntities)
{
if (TriggerHelper.EntityHasClass(entity, "CivilCentre"))
{
cmpTrigger.playerCivicCenter[i] = entity;
}
}
}
+ // Fix alliances
+ for (var i = 1; i < numberOfPlayers; ++i)
+ {
+ var cmpPlayer = TriggerHelper.GetPlayerComponent(i);
+ for (var j = 1; j < numberOfPlayers; ++j)
+ if (i != j)
+ cmpPlayer.SetAlly(j);
+ cmpPlayer.SetLockTeams(true);
+ }
+
// make gaia black
TriggerHelper.GetPlayerComponent(0).SetColour(0, 0, 0);
// Place the treasures
this.PlaceTreasures();
}
Trigger.prototype.PlaceTreasures = function()
{
var triggerPoints = cmpTrigger.GetTriggerPoints("B");
for (var point of triggerPoints)
{
var template = treasures[Math.floor(Math.random() * treasures.length)]
TriggerHelper.SpawnUnits(point, template, 1, 0);
}
cmpTrigger.DoAfterDelay(4*60*1000, "PlaceTreasures", {}); //Place more treasures after 4 minutes
}
Trigger.prototype.InitializeEnemyWaves = function()
{
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [1,2,3,4,5,6,7,8],
"message": markForTranslation("The first wave will start in 15 minutes!"),
"translateMessage": true
});
cmpTrigger.DoAfterDelay(15*60*1000, "StartAnEnemyWave", {});
}
Trigger.prototype.DefeatPlayerOnceCCIsDestroyed = function(data)
{
// Defeat a player that has lost his civic center
if (data.entity == cmpTrigger.playerCivicCenter[data.from] && data.to == -1)
+ {
TriggerHelper.DefeatPlayer(data.from);
- // Check if only one player remains. He will be the winner.
- var lastPlayerStanding = 0;
- var numPlayersStanding = 0;
- var numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
- for (var i = 1; i < numberOfPlayers; ++i)
- {
- if (TriggerHelper.GetPlayerComponent(i).GetState() == "active")
+ // Check if only one player remains. He will be the winner.
+ var lastPlayerStanding = 0;
+ var numPlayersStanding = 0;
+ var numberOfPlayers = TriggerHelper.GetNumberOfPlayers();
+ for (var i = 1; i < numberOfPlayers; ++i)
{
- lastPlayerStanding = i;
- ++numPlayersStanding;
+ if (TriggerHelper.GetPlayerComponent(i).GetState() == "active")
+ {
+ lastPlayerStanding = i;
+ ++numPlayersStanding;
+ }
}
+ if (numPlayersStanding == 1)
+ TriggerHelper.SetPlayerWon(lastPlayerStanding);
}
- if (numPlayersStanding == 1)
- TriggerHelper.SetPlayerWon(lastPlayerStanding);
}
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.playerCivicCenter = {};
cmpTrigger.DoAfterDelay(0, "InitGame", {});
cmpTrigger.DoAfterDelay(1000, "InitializeEnemyWaves", {});
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "DefeatPlayerOnceCCIsDestroyed", {"enabled": true});
Index: ps/trunk/binaries/data/mods/public/simulation/components/Builder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Builder.js (revision 15613)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Builder.js (revision 15614)
@@ -1,78 +1,86 @@
function Builder() {}
Builder.prototype.Schema =
"Allows the unit to construct and repair buildings." +
"" +
"1.0" +
"" +
"\n structures/{civ}_barracks\n structures/{civ}_civil_centre\n structures/celt_sb1\n " +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"";
Builder.prototype.Init = function()
{
};
Builder.prototype.Serialize = null; // we have no dynamic state to save
Builder.prototype.GetEntitiesList = function()
{
var entities = [];
var string = this.template.Entities._string;
if (string)
{
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());
entities = string.split(/\s+/);
+
+ // Remove disabled entities
+ var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player)
+ var disabledEntities = cmpPlayer.GetDisabledTemplates();
+
+ for (var i = entities.length - 1; i >= 0; --i)
+ if (disabledEntities[entities[i]])
+ entities.splice(i, 1);
}
return entities;
};
Builder.prototype.GetRange = function()
{
var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
var max = 2;
if (cmpObstruction)
max += cmpObstruction.GetUnitRadius();
return { "max": max, "min": 0 };
};
/**
* Build/repair the target entity. This should only be called after a successful range check.
* It should be called at a rate of once per second.
* Returns obj with obj.finished==true if this is a repair and it's fully repaired.
*/
Builder.prototype.PerformBuilding = function(target)
{
var rate = ApplyValueModificationsToEntity("Builder/Rate", +this.template.Rate, this.entity);
// If it's a foundation, then build it
var cmpFoundation = Engine.QueryInterface(target, IID_Foundation);
if (cmpFoundation)
{
cmpFoundation.Build(this.entity, rate);
return;
}
// Otherwise try to repair it
var cmpHealth = Engine.QueryInterface(target, IID_Health);
if (cmpHealth)
{
cmpHealth.Repair(this.entity, rate);
return;
}
};
Engine.RegisterComponentType(IID_Builder, "Builder", Builder);
Index: ps/trunk/binaries/data/mods/public/simulation/components/Player.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 15613)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Player.js (revision 15614)
@@ -1,702 +1,732 @@
function Player() {}
Player.prototype.Schema =
"";
Player.prototype.Init = function()
{
this.playerID = undefined;
this.name = undefined; // define defaults elsewhere (supporting other languages)
this.civ = undefined;
this.colour = { "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
};
this.tradingGoods = [ // goods for next trade-route and its proba in % (the sum of probas must be 100)
{ "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.conquestCriticalEntitiesCount = 0; // number of owned units with ConquestCritical class
this.formations = [];
this.startCam = undefined;
this.controlAllUnits = false;
this.isAI = false;
this.gatherRateMultiplier = 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 = {};
};
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)
{
this.civ = civcode;
};
Player.prototype.GetCiv = function()
{
return this.civ;
};
Player.prototype.SetColour = function(r, g, b)
{
this.colour = { "r": r/255.0, "g": g/255.0, "b": b/255.0, "a": 1.0 };
};
Player.prototype.GetColour = function()
{
return this.colour;
};
// 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.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));
};
Player.prototype.SetGatherRateMultiplier = function(value)
{
this.gatherRateMultiplier = value;
};
Player.prototype.GetGatherRateMultiplier = function()
{
return this.gatherRateMultiplier;
};
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");
var notification = {
"players": [this.playerID],
"message": msg,
"parameters": parameters,
"translateMessage": true,
"translateParameters": {
"resourceType1": "withinSentence",
"resourceType2": "withinSentence",
"resourceType3": "withinSentence",
"resourceType4": "withinSentence",
},
};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
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 each (var resource in 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)
{
this.state = newState;
};
Player.prototype.GetConquestCriticalEntitiesCount = function()
{
return this.conquestCriticalEntitiesCount;
};
Player.prototype.GetTeam = function()
{
return this.team;
};
Player.prototype.SetTeam = function(team)
{
if (!this.teamsLocked)
{
this.team = team;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (cmpPlayerManager && this.team != -1)
{
// Set all team members as allies
for (var i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
{
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(i), IID_Player);
if (this.team == cmpPlayer.GetTeam())
{
this.SetAlly(i);
cmpPlayer.SetAlly(this.playerID);
}
}
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
};
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)
{
// Should we check for teamsLocked here?
this.diplomacy = dipl;
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
};
Player.prototype.SetDiplomacyIndex = function(idx, value)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(idx), IID_Player);
if (!cmpPlayer)
return;
if (this.state != "active" || cmpPlayer.state != "active")
return;
// You can have alliances with other players,
if (this.teamsLocked)
{
// but can't stab your team members in the back
if (this.team == -1 || this.team != cmpPlayer.GetTeam())
{
// Break alliance or declare war
if (Math.min(this.diplomacy[idx],cmpPlayer.diplomacy[this.playerID]) > value)
{
this.diplomacy[idx] = value;
cmpPlayer.SetDiplomacyIndex(this.playerID, value);
}
else
{
this.diplomacy[idx] = value;
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
}
else
{
// Break alliance or declare war (worsening of relations is mutual)
if (Math.min(this.diplomacy[idx],cmpPlayer.diplomacy[this.playerID]) > value)
{
// This is duplicated because otherwise we get too much recursion
this.diplomacy[idx] = value;
cmpPlayer.SetDiplomacyIndex(this.playerID, value);
}
else
{
this.diplomacy[idx] = value;
}
Engine.BroadcastMessage(MT_DiplomacyChanged, {"player": this.playerID});
}
};
Player.prototype.UpdateSharedLos = function()
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (!cmpRangeManager)
return;
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var sharedLos = [];
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.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, and we are its ally
*/
Player.prototype.IsMutualAlly = function(id)
{
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return false;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(id), IID_Player);
return this.IsAlly(id) && cmpPlayer && cmpPlayer.IsAlly(this.playerID);
};
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;
};
/**
* 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);
var cmpFoundation = Engine.QueryInterface(msg.entity, IID_Foundation);
if (msg.from == this.playerID)
{
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("ConquestCritical"))
this.conquestCriticalEntitiesCount--;
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 (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("ConquestCritical"))
this.conquestCriticalEntitiesCount++;
if (cmpCost)
this.popUsed += cmpCost.GetPopCost();
if (cmpIdentity && cmpIdentity.HasClass("Hero"))
this.heroes.push(msg.entity);
}
};
Player.prototype.OnPlayerDefeated = function(msg)
{
this.state = "defeated";
// TODO: Tribute all resources to this player's active allies (if any)
// Reassign all player's entities to Gaia
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var 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 each (var entity in entities)
{
var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
cmpOwnership.SetOwnerQuiet(0);
}
// With the real ownership change complete, send OwnershipChanged messages.
for each (var entity in entities)
Engine.PostMessage(entity, MT_OwnershipChanged, { "entity": entity,
"from": this.playerID, "to": 0 });
// Reveal the map for this player.
cmpRangeManager.SetLosRevealAll(this.playerID, true);
// Send a chat message notifying of the player's defeat.
var notification = {"type": "defeat", "players": [this.playerID]};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification(notification);
};
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 cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
if (!cmpPlayerManager)
return;
var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(player), IID_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(function (sum, type){ return 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 notification = {"type": "tribute", "players": [player], "donator": this.playerID, "amounts": amounts};
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (cmpGUIInterface)
cmpGUIInterface.PushNotification(notification);
};
+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 = templates;
+ Engine.BroadcastMessage(MT_DisabledTemplatesChanged, {});
+ var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGuiInterface.PushNotification({"type": "resetselectionpannel", "players": [this.GetPlayerID()]});
+};
+
+Player.prototype.GetDisabledTemplates = function(templates)
+{
+ return this.disabledTemplates;
+};
+
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 15613)
+++ ps/trunk/binaries/data/mods/public/simulation/components/ProductionQueue.js (revision 15614)
@@ -1,788 +1,803 @@
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/{civ}_support_trader\n units/celt_infantry_spearman_b\n " +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"";
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;
this.alertRaiser = undefined;
};
ProductionQueue.prototype.PutUnderAlert = function(raiser)
{
this.alertRaiser = raiser;
};
ProductionQueue.prototype.ResetAlert = function()
{
this.alertRaiser = undefined;
};
/*
* 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;
var string = this.template.Entities._string;
if (!string)
return;
// Replace the "{civ}" codes with this entity's civ ID
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
if (cmpIdentity)
string = string.replace(/\{civ\}/g, cmpIdentity.GetCiv());
var entitiesList = string.split(/\s+/);
-
+
+ // Remove disabled entities
+ var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player)
+ var disabledEntities = cmpPlayer.GetDisabledTemplates();
+
+ for (var i = entitiesList.length - 1; i >= 0; --i)
+ if (disabledEntities[entitiesList[i]])
+ entitiesList.splice(i, 1);
+
// check if some templates need to show their advanced or elite version
var upgradeTemplate = function(templateName)
{
var template = cmpTemplateManager.GetTemplate(templateName);
while (template && template.Promotion !== undefined)
{
var requiredXp = ApplyValueModificationsToTemplate("Promotion/RequiredXp", +template.Promotion.RequiredXp, playerID, template);
if (requiredXp > 0)
break;
templateName = template.Promotion.Entity;
template = cmpTemplateManager.GetTemplate(templateName);
}
return templateName;
};
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
- var playerID = QueryOwnerInterface(this.entity, IID_Player).GetPlayerID();
+ var playerID = cmpPlayer.GetPlayerID();
for each (var templateName in entitiesList)
this.entitiesList.push(upgradeTemplate(templateName));
for each (var item in 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 techs = string.split(/\s+/);
var techList = [];
var superseded = {}; // Stores the tech which supersedes the key
// 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];
var template = cmpTechnologyManager.GetTechnologyTemplate(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;
}
var template = cmpTechnologyManager.GetTechnologyTemplate(tech);
if (template.top)
ret[i] = {"pair": true, "top": template.top, "bottom": template.bottom};
else
ret[i] = tech;
}
return ret;
};
ProductionQueue.prototype.IsTechnologyResearchedOrInProgress = function(tech)
{
if (!tech)
return false;
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
var template = cmpTechnologyManager.GetTechnologyTemplate(tech);
if (template.top)
{
return (cmpTechnologyManager.IsTechnologyResearched(template.top) || cmpTechnologyManager.IsInProgress(template.top)
|| cmpTechnologyManager.IsTechnologyResearched(template.bottom) || cmpTechnologyManager.IsInProgress(template.bottom))
}
else
{
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)
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (this.queue.length < MAX_QUEUE_SIZE)
{
if (type == "unit")
{
// Find the template data so we can determine the build costs
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.GetTemplate(templateName);
if (!template)
return;
if (template.Promotion)
{
var requiredXp = ApplyValueModificationsToTemplate("Promotion/RequiredXp", +template.Promotion.RequiredXp, cmpPlayer.GetPlayerID(), template);
if (requiredXp == 0)
{
this.AddBatch(template.Promotion.Entity, type, count, metadata);
return;
}
}
// Apply a time discount to larger batches.
var 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
var costs = {};
var totalCosts = {};
var buildTime = ApplyValueModificationsToTemplate("Cost/BuildTime", +template.Cost.BuildTime, cmpPlayer.GetPlayerID(), template);
var time = timeMult * buildTime;
for (var r in template.Cost.Resources)
{
costs[r] = ApplyValueModificationsToTemplate("Cost/Resources/"+r, +template.Cost.Resources[r], cmpPlayer.GetPlayerID(), template);
totalCosts[r] = Math.floor(count * costs[r]);
}
var population = +template.Cost.Population;
// 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)
{
var unitCategory = template.TrainingRestrictions.Category;
var 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": population,
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
// Call the related trigger event
var 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")
{
// Load the technology template
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
var template = cmpTechTempMan.GetTemplate(templateName);
if (!template)
return;
var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
var time = template.researchTime * cmpPlayer.GetCheatTimeMultiplier();
var cost = {};
for each (var r in ["food", "wood", "stone", "metal"])
cost[r] = Math.floor(template.cost[r]);
// 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.
var cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
cmpTechnologyManager.QueuedResearch(templateName, this.entity);
if (this.queue.length == 0)
cmpTechnologyManager.StartedResearch(templateName);
this.queue.push({
"id": this.nextID++,
"player": cmpPlayer.GetPlayerID(),
"count": 1,
"technologyTemplate": templateName,
"resources": deepcopy(template.cost), // need to copy to avoid serialization problems
"productionStarted": false,
"timeTotal": time*1000,
"timeRemaining": time*1000,
});
// Call the related trigger event
var 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)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_ProductionQueue, "ProgressTimeout", g_ProgressInterval, {});
}
}
else
{
var notification = {"players": [cmpPlayer.GetPlayerID()], "message": markForTranslation("The production queue is full."), "translateMessage": true };
var 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 (var i = 0; i < this.entityCache.length; ++i)
{
Engine.DestroyEntity(this.entityCache[i]);
}
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, IID_Player);
// Update entity count in the EntityLimits component
if (item.unitTemplate)
{
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempMan.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 each (var r in ["food", "wood", "stone", "metal"])
{
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);
}
// 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 each (var item in 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, IID_Player);
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.GetCheatTimeMultiplier();
};
ProductionQueue.prototype.OnOwnershipChanged = function(msg)
{
if (msg.from != -1)
{
// Unset flag that previous owner's training may be blocked
var cmpPlayer = QueryPlayerIDInterface(msg.from, IID_Player);
if (cmpPlayer && this.queue.length > 0)
cmpPlayer.UnBlockTraining();
}
if (msg.to != -1)
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.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)
{
var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint);
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
var createdEnts = [];
var spawnedEnts = [];
if (this.entityCache.length == 0)
{
// We need entities to test spawning, but we don't want to waste resources,
// so only create them once and use as needed
for (var i = 0; i < count; ++i)
{
var ent = Engine.AddEntity(templateName);
this.entityCache.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 to an 'alive' one
var cmpTrainingRestrictions = Engine.QueryInterface(ent, IID_TrainingRestrictions);
if (cmpTrainingRestrictions)
{
var unitCategory = cmpTrainingRestrictions.GetCategory();
var cmpPlayerEntityLimits = QueryOwnerInterface(this.entity, IID_EntityLimits);
cmpPlayerEntityLimits.ChangeCount(unitCategory,-1);
}
}
}
var cmpAutoGarrison = undefined;
if (cmpRallyPoint)
{
var data = cmpRallyPoint.GetData()[0];
if (data && data.target && data.target == this.entity && data.command == "garrison")
cmpAutoGarrison = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
}
for (var i = 0; i < count; ++i)
{
var ent = this.entityCache[0];
var cmpNewOwnership = Engine.QueryInterface(ent, IID_Ownership);
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
if (cmpAutoGarrison && cmpAutoGarrison.PerformGarrison(ent))
{
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
cmpUnitAI.Autogarrison(this.entity);
}
else
{
var pos = cmpFootprint.PickSpawnPoint(ent);
if (pos.y < 0)
{
// Fail: there wasn't any space to spawn the unit
break;
}
else
{
// Successfully spawned
var cmpNewPosition = Engine.QueryInterface(ent, IID_Position);
cmpNewPosition.JumpTo(pos.x, pos.z);
// TODO: what direction should they face in?
spawnedEnts.push(ent);
}
}
var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
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)
{
var rallyPos = cmpRallyPoint.GetPositions()[0];
if (rallyPos)
{
var commands = GetRallyPointCommands(cmpRallyPoint, spawnedEnts);
for each(var com in commands)
ProcessCommand(cmpOwnership.GetOwner(), com);
}
}
}
if (createdEnts.length > 0)
{
Engine.PostMessage(this.entity, MT_TrainingFinished, {
"entities": createdEnts,
"owner": cmpOwnership.GetOwner(),
"metadata": metadata,
});
if(this.alertRaiser && spawnedEnts.length > 0)
{
var cmpAlertRaiser = Engine.QueryInterface(this.alertRaiser, IID_AlertRaiser);
if(cmpAlertRaiser)
cmpAlertRaiser.UpdateUnits(spawnedEnts);
}
}
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, IID_Player);
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)
{
// 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);
}
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, IID_Player);
var notification = {"players": [cmpPlayer.GetPlayerID()], "message": "Can't find free space to spawn trained units" };
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);
var template = cmpTechnologyManager.GetTechnologyTemplate(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);