Index: ps/trunk/binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js (revision 23943)
+++ ps/trunk/binaries/data/mods/public/gui/session/chat/ChatMessageFormatSimulation.js (revision 23944)
@@ -1,157 +1,157 @@
/**
* These classes construct a chat message from simulation events initiated from the GuiInterface PushNotification method.
*/
class ChatMessageFormatSimulation
{
}
ChatMessageFormatSimulation.attack = class
{
parse(msg)
{
if (msg.player != g_ViewedPlayer)
return "";
let message = msg.targetIsDomesticAnimal ?
translate("Your livestock has been attacked by %(attacker)s!") :
translate("You have been attacked by %(attacker)s!");
return sprintf(message, {
"attacker": colorizePlayernameByID(msg.attacker)
});
}
};
ChatMessageFormatSimulation.barter = class
{
parse(msg)
{
if (!g_IsObserver || Engine.ConfigDB_GetValue("user", "gui.session.notifications.barter") != "true")
return "";
- let amountsSold = {};
- amountsSold[msg.resourceSold] = msg.amountsSold;
+ let amountGiven = {};
+ amountGiven[msg.resourceGiven] = msg.amountGiven;
- let amountsBought = {};
- amountsBought[msg.resourceBought] = msg.amountsBought;
+ let amountGained = {};
+ amountGained[msg.resourceGained] = msg.amountGained;
- return sprintf(translate("%(player)s bartered %(amountsBought)s for %(amountsSold)s."), {
+ return sprintf(translate("%(player)s bartered %(amountGiven)s for %(amountGained)s."), {
"player": colorizePlayernameByID(msg.player),
- "amountsBought": getLocalizedResourceAmounts(amountsBought),
- "amountsSold": getLocalizedResourceAmounts(amountsSold)
+ "amountGiven": getLocalizedResourceAmounts(amountGiven),
+ "amountGained": getLocalizedResourceAmounts(amountGained)
});
}
};
ChatMessageFormatSimulation.diplomacy = class
{
parse(msg)
{
let messageType;
if (g_IsObserver)
messageType = "observer";
else if (Engine.GetPlayerID() == msg.sourcePlayer)
messageType = "active";
else if (Engine.GetPlayerID() == msg.targetPlayer)
messageType = "passive";
else
return "";
return sprintf(translate(this.strings[messageType][msg.status]), {
"player": colorizePlayernameByID(messageType == "active" ? msg.targetPlayer : msg.sourcePlayer),
"player2": colorizePlayernameByID(messageType == "active" ? msg.sourcePlayer : msg.targetPlayer)
});
}
};
ChatMessageFormatSimulation.diplomacy.prototype.strings = {
"active": {
"ally": markForTranslation("You are now allied with %(player)s."),
"enemy": markForTranslation("You are now at war with %(player)s."),
"neutral": markForTranslation("You are now neutral with %(player)s.")
},
"passive": {
"ally": markForTranslation("%(player)s is now allied with you."),
"enemy": markForTranslation("%(player)s is now at war with you."),
"neutral": markForTranslation("%(player)s is now neutral with you.")
},
"observer": {
"ally": markForTranslation("%(player)s is now allied with %(player2)s."),
"enemy": markForTranslation("%(player)s is now at war with %(player2)s."),
"neutral": markForTranslation("%(player)s is now neutral with %(player2)s.")
}
};
ChatMessageFormatSimulation.phase = class
{
parse(msg)
{
let notifyPhase = Engine.ConfigDB_GetValue("user", "gui.session.notifications.phase");
if (notifyPhase == "none" || msg.player != g_ViewedPlayer && !g_IsObserver && !g_Players[msg.player].isMutualAlly[g_ViewedPlayer])
return "";
let message = "";
if (notifyPhase == "all")
{
if (msg.phaseState == "started")
message = translate("%(player)s is advancing to the %(phaseName)s.");
else if (msg.phaseState == "aborted")
message = translate("The %(phaseName)s of %(player)s has been aborted.");
}
if (msg.phaseState == "completed")
message = translate("%(player)s has reached the %(phaseName)s.");
return sprintf(message, {
"player": colorizePlayernameByID(msg.player),
"phaseName": getEntityNames(GetTechnologyData(msg.phaseName, g_Players[msg.player].civ))
});
}
};
ChatMessageFormatSimulation.playerstate = class
{
parse(msg)
{
if (!msg.message.pluralMessage)
return sprintf(translate(msg.message), {
"player": colorizePlayernameByID(msg.players[0])
});
let mPlayers = msg.players.map(playerID => colorizePlayernameByID(playerID));
let lastPlayer = mPlayers.pop();
return sprintf(translatePlural(msg.message.message, msg.message.pluralMessage, msg.message.pluralCount), {
// Translation: This comma is used for separating first to penultimate elements in an enumeration.
"players": mPlayers.join(translate(", ")),
"lastPlayer": lastPlayer
});
}
};
/**
* Optionally show all tributes sent in observer mode and tributes sent between allied players.
* Otherwise, only show tributes sent directly to us, and tributes that we send.
*/
ChatMessageFormatSimulation.tribute = class
{
parse(msg)
{
let message = "";
if (msg.targetPlayer == Engine.GetPlayerID())
message = translate("%(player)s has sent you %(amounts)s.");
else if (msg.sourcePlayer == Engine.GetPlayerID())
message = translate("You have sent %(player2)s %(amounts)s.");
else if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.tribute") == "true" &&
(g_IsObserver || g_GameAttributes.settings.LockTeams &&
g_Players[msg.sourcePlayer].isMutualAlly[Engine.GetPlayerID()] &&
g_Players[msg.targetPlayer].isMutualAlly[Engine.GetPlayerID()]))
message = translate("%(player)s has sent %(player2)s %(amounts)s.");
return sprintf(message, {
"player": colorizePlayernameByID(msg.sourcePlayer),
"player2": colorizePlayernameByID(msg.targetPlayer),
"amounts": getLocalizedResourceAmounts(msg.amounts)
});
}
};
Index: ps/trunk/binaries/data/mods/public/gui/session/messages.js
===================================================================
--- ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23943)
+++ ps/trunk/binaries/data/mods/public/gui/session/messages.js (revision 23944)
@@ -1,556 +1,556 @@
/**
* All tutorial messages received so far.
*/
var g_TutorialMessages = [];
/**
* GUI tags applied to the most recent tutorial message.
*/
var g_TutorialNewMessageTags = { "color": "yellow" };
/**
* These handlers are called everytime a client joins or disconnects.
*/
var g_PlayerAssignmentsChangeHandlers = new Set();
/**
* These handlers are called when the ceasefire time has run out.
*/
var g_CeasefireEndedHandlers = new Set();
/**
* These handlers are fired when the match is networked and
* the current client established the connection, authenticated,
* finished the loading screen, starts or finished synchronizing after a rejoin.
* The messages are constructed in NetClient.cpp.
*/
var g_NetworkStatusChangeHandlers = new Set();
/**
* These handlers are triggered whenever a client finishes the loading screen.
*/
var g_ClientsLoadingHandlers = new Set();
/**
* These handlers are fired if the server informed the players that the networked game is out of sync.
*/
var g_NetworkOutOfSyncHandlers = new Set();
/**
* Handle all netmessage types that can occur.
*/
var g_NetMessageTypes = {
"netstatus": msg => {
handleNetStatusMessage(msg);
},
"netwarn": msg => {
addNetworkWarning(msg);
},
"out-of-sync": msg => {
for (let handler of g_NetworkOutOfSyncHandlers)
handler(msg);
},
"players": msg => {
handlePlayerAssignmentsMessage(msg);
},
"paused": msg => {
g_PauseControl.setClientPauseState(msg.guid, msg.pause);
},
"clients-loading": msg => {
for (let handler of g_ClientsLoadingHandlers)
handler(msg.guids);
},
"rejoined": msg => {
addChatMessage({
"type": "rejoined",
"guid": msg.guid
});
},
"kicked": msg => {
addChatMessage({
"type": "kicked",
"username": msg.username,
"banned": msg.banned
});
},
"chat": msg => {
addChatMessage({
"type": "message",
"guid": msg.guid,
"text": msg.text
});
},
"gamesetup": msg => {}, // Needed for autostart
"start": msg => {}
};
var g_PlayerStateMessages = {
"won": translate("You have won!"),
"defeated": translate("You have been defeated!")
};
/**
* Defines how the GUI reacts to notifications that are sent by the simulation.
* Don't open new pages (message boxes) here! Otherwise further notifications
* handled in the same turn can't access the GUI objects anymore.
*/
var g_NotificationsTypes =
{
"aichat": function(notification, player)
{
let message = {
"type": "message",
"text": notification.message,
"guid": findGuidForPlayerID(player) || -1,
"player": player,
"translate": true
};
if (notification.translateParameters)
{
message.translateParameters = notification.translateParameters;
message.parameters = notification.parameters;
colorizePlayernameParameters(notification.parameters);
}
addChatMessage(message);
},
"defeat": function(notification, player)
{
playersFinished(notification.allies, notification.message, false);
},
"won": function(notification, player)
{
playersFinished(notification.allies, notification.message, true);
},
"diplomacy": function(notification, player)
{
updatePlayerData();
g_DiplomacyColors.onDiplomacyChange();
addChatMessage({
"type": "diplomacy",
"sourcePlayer": player,
"targetPlayer": notification.targetPlayer,
"status": notification.status
});
},
"ceasefire-ended": function(notification, player)
{
updatePlayerData();
for (let handler of g_CeasefireEndedHandlers)
handler();
},
"tutorial": function(notification, player)
{
updateTutorial(notification);
},
"tribute": function(notification, player)
{
addChatMessage({
"type": "tribute",
"sourcePlayer": notification.donator,
"targetPlayer": player,
"amounts": notification.amounts
});
},
"barter": function(notification, player)
{
addChatMessage({
"type": "barter",
"player": player,
- "amountsSold": notification.amountsSold,
- "amountsBought": notification.amountsBought,
- "resourceSold": notification.resourceSold,
- "resourceBought": notification.resourceBought
+ "amountGiven": notification.amountGiven,
+ "amountGained": notification.amountGained,
+ "resourceGiven": notification.resourceGiven,
+ "resourceGained": notification.resourceGained
});
},
"spy-response": function(notification, player)
{
g_DiplomacyDialog.onSpyResponse(notification, player);
if (notification.entity && g_ViewedPlayer == player)
{
g_DiplomacyDialog.close();
setCameraFollow(notification.entity);
}
},
"attack": function(notification, player)
{
if (player != g_ViewedPlayer)
return;
// Focus camera on attacks
if (g_FollowPlayer)
{
setCameraFollow(notification.target);
g_Selection.reset();
if (notification.target)
g_Selection.addList([notification.target]);
}
if (Engine.ConfigDB_GetValue("user", "gui.session.notifications.attack") !== "true")
return;
addChatMessage({
"type": "attack",
"player": player,
"attacker": notification.attacker,
"targetIsDomesticAnimal": notification.targetIsDomesticAnimal
});
},
"phase": function(notification, player)
{
addChatMessage({
"type": "phase",
"player": player,
"phaseName": notification.phaseName,
"phaseState": notification.phaseState
});
},
"dialog": function(notification, player)
{
if (player == Engine.GetPlayerID())
openDialog(notification.dialogName, notification.data, player);
},
"playercommand": function(notification, player)
{
// For observers, focus the camera on units commanded by the selected player
if (!g_FollowPlayer || player != g_ViewedPlayer)
return;
let cmd = notification.cmd;
// Ignore rallypoint commands of trained animals
let entState = cmd.entities && cmd.entities[0] && GetEntityState(cmd.entities[0]);
if (g_ViewedPlayer != 0 &&
entState && entState.identity && entState.identity.classes &&
entState.identity.classes.indexOf("Animal") != -1)
return;
// Focus the structure to build.
if (cmd.type == "repair")
{
let targetState = GetEntityState(cmd.target);
if (targetState)
Engine.CameraMoveTo(targetState.position.x, targetState.position.z);
}
else if (cmd.type == "delete-entities" && notification.position)
Engine.CameraMoveTo(notification.position.x, notification.position.y);
// Focus commanded entities, but don't lose previous focus when training units
else if (cmd.type != "train" && cmd.type != "research" && entState)
setCameraFollow(cmd.entities[0]);
if (["walk", "attack-walk", "patrol"].indexOf(cmd.type) != -1)
DrawTargetMarker(cmd);
// Select units affected by that command
let selection = [];
if (cmd.entities)
selection = cmd.entities;
if (cmd.target)
selection.push(cmd.target);
// Allow gaia in selection when gathering
g_Selection.reset();
g_Selection.addList(selection, false, cmd.type == "gather");
},
"play-tracks": function(notification, player)
{
if (notification.lock)
{
global.music.storeTracks(notification.tracks.map(track => ({ "Type": "custom", "File": track })));
global.music.setState(global.music.states.CUSTOM);
}
global.music.setLocked(notification.lock);
}
};
function registerPlayerAssignmentsChangeHandler(handler)
{
g_PlayerAssignmentsChangeHandlers.add(handler);
}
function registerCeasefireEndedHandler(handler)
{
g_CeasefireEndedHandlers.add(handler);
}
function registerNetworkOutOfSyncHandler(handler)
{
g_NetworkOutOfSyncHandlers.add(handler);
}
function registerNetworkStatusChangeHandler(handler)
{
g_NetworkStatusChangeHandlers.add(handler);
}
function registerClientsLoadingHandler(handler)
{
g_ClientsLoadingHandlers.add(handler);
}
function findGuidForPlayerID(playerID)
{
return Object.keys(g_PlayerAssignments).find(guid => g_PlayerAssignments[guid].player == playerID);
}
/**
* Processes all pending notifications sent from the GUIInterface simulation component.
*/
function handleNotifications()
{
for (let notification of Engine.GuiInterfaceCall("GetNotifications"))
{
if (!notification.players || !notification.type || !g_NotificationsTypes[notification.type])
{
error("Invalid GUI notification: " + uneval(notification));
continue;
}
for (let player of notification.players)
g_NotificationsTypes[notification.type](notification, player);
}
}
function toggleTutorial()
{
let tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel");
tutorialPanel.hidden = !tutorialPanel.hidden || !Engine.GetGUIObjectByName("tutorialText").caption;
}
/**
* Updates the tutorial panel when a new goal.
*/
function updateTutorial(notification)
{
// Show the tutorial panel if not yet done
Engine.GetGUIObjectByName("tutorialPanel").hidden = false;
if (notification.warning)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = coloredText(translate(notification.warning), "orange");
return;
}
let notificationText =
notification.instructions.reduce((instructions, item) =>
instructions + (typeof item == "string" ? translate(item) : colorizeHotkey(translate(item.text), item.hotkey)),
"");
Engine.GetGUIObjectByName("tutorialText").caption = g_TutorialMessages.concat(setStringTags(notificationText, g_TutorialNewMessageTags)).join("\n");
g_TutorialMessages.push(notificationText);
if (notification.readyButton)
{
Engine.GetGUIObjectByName("tutorialReady").hidden = false;
if (notification.leave)
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click to quit this tutorial.");
Engine.GetGUIObjectByName("tutorialReady").caption = translate("Quit");
Engine.GetGUIObjectByName("tutorialReady").onPress = endGame;
}
else
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Click when ready.");
}
else
{
Engine.GetGUIObjectByName("tutorialWarning").caption = translate("Follow the instructions.");
Engine.GetGUIObjectByName("tutorialReady").hidden = true;
}
}
/**
* Process every CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer.
* Saves the received object to mainlog.html.
*/
function handleNetMessages()
{
while (true)
{
let msg = Engine.PollNetworkClient();
if (!msg)
return;
log("Net message: " + uneval(msg));
if (g_NetMessageTypes[msg.type])
g_NetMessageTypes[msg.type](msg);
else
error("Unrecognised net message type '" + msg.type + "'");
}
}
function handleNetStatusMessage(message)
{
if (g_Disconnected)
return;
g_IsNetworkedActive = message.status == "active";
if (message.status == "disconnected")
{
g_Disconnected = true;
updateCinemaPath();
closeOpenDialogs();
}
for (let handler of g_NetworkStatusChangeHandlers)
handler(message);
}
function handlePlayerAssignmentsMessage(message)
{
for (let guid in g_PlayerAssignments)
if (!message.newAssignments[guid])
onClientLeave(guid);
let joins = Object.keys(message.newAssignments).filter(guid => !g_PlayerAssignments[guid]);
g_PlayerAssignments = message.newAssignments;
joins.forEach(guid => {
onClientJoin(guid);
});
for (let handler of g_PlayerAssignmentsChangeHandlers)
handler();
// TODO: use subscription instead
updateGUIObjects();
}
function onClientJoin(guid)
{
let playerID = g_PlayerAssignments[guid].player;
if (g_Players[playerID])
{
g_Players[playerID].guid = guid;
g_Players[playerID].name = g_PlayerAssignments[guid].name;
g_Players[playerID].offline = false;
}
addChatMessage({
"type": "connect",
"guid": guid
});
}
function onClientLeave(guid)
{
g_PauseControl.setClientPauseState(guid, false);
for (let id in g_Players)
if (g_Players[id].guid == guid)
g_Players[id].offline = true;
addChatMessage({
"type": "disconnect",
"guid": guid
});
}
function addChatMessage(msg)
{
g_Chat.ChatMessageHandler.handleMessage(msg);
}
function clearChatMessages()
{
g_Chat.ChatOverlay.clearChatMessages();
}
/**
* This function is used for AIs, whose names don't exist in g_PlayerAssignments.
*/
function colorizePlayernameByID(playerID)
{
let username = g_Players[playerID] && escapeText(g_Players[playerID].name);
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameByGUID(guid)
{
let username = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].name : "";
let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1;
return colorizePlayernameHelper(username, playerID);
}
function colorizePlayernameHelper(username, playerID)
{
let playerColor = playerID > -1 ? g_DiplomacyColors.getPlayerColor(playerID) : "white";
return coloredText(username || translate("Unknown Player"), playerColor);
}
/**
* Insert the colorized playername to chat messages sent by the AI and time notifications.
*/
function colorizePlayernameParameters(parameters)
{
for (let param in parameters)
if (param.startsWith("_player_"))
parameters[param] = colorizePlayernameByID(parameters[param]);
}
/**
* Custom dialog response handling, usable by trigger maps.
*/
function sendDialogAnswer(guiObject, dialogName)
{
Engine.GetGUIObjectByName(dialogName + "-dialog").hidden = true;
Engine.PostNetworkCommand({
"type": "dialog-answer",
"dialog": dialogName,
"answer": guiObject.name.split("-").pop(),
});
resumeGame();
}
/**
* Custom dialog opening, usable by trigger maps.
*/
function openDialog(dialogName, data, player)
{
let dialog = Engine.GetGUIObjectByName(dialogName + "-dialog");
if (!dialog)
{
warn("messages.js: Unknow dialog with name " + dialogName);
return;
}
dialog.hidden = false;
for (let objName in data)
{
let obj = Engine.GetGUIObjectByName(dialogName + "-dialog-" + objName);
if (!obj)
{
warn("messages.js: Key '" + objName + "' not found in '" + dialogName + "' dialog.");
continue;
}
for (let key in data[objName])
{
let n = data[objName][key];
if (typeof n == "object" && n.message)
{
let message = n.message;
if (n.translateMessage)
message = translate(message);
let parameters = n.parameters || {};
if (n.translateParameters)
translateObjectKeys(parameters, n.translateParameters);
obj[key] = sprintf(message, parameters);
}
else
obj[key] = n;
}
}
g_PauseControl.implicitPause();
}
Index: ps/trunk/binaries/data/mods/public/simulation/components/Barter.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Barter.js (revision 23943)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Barter.js (revision 23944)
@@ -1,158 +1,158 @@
function Barter() {}
Barter.prototype.Schema =
"";
/**
* The "true price" is a base price of 100 units of resource (for the case of some resources being of more worth than others).
* With current bartering system only relative values makes sense so if for example stone is two times more expensive than wood,
* there will 2:1 exchange rate.
*
* Constant part of price percentage difference between true price and buy/sell price.
* Buy price equal to true price plus constant difference.
* Sell price equal to true price minus constant difference.
*/
Barter.prototype.CONSTANT_DIFFERENCE = 10;
/**
* Additional difference of prices in percents, added after each deal to specified resource price.
*/
Barter.prototype.DIFFERENCE_PER_DEAL = 2;
/**
* Price difference percentage which restored each restore timer tick
*/
Barter.prototype.DIFFERENCE_RESTORE = 0.5;
/**
* Interval of timer which slowly restore prices after deals
*/
Barter.prototype.RESTORE_TIMER_INTERVAL = 5000;
Barter.prototype.Init = function()
{
this.priceDifferences = {};
for (let resource of Resources.GetBarterableCodes())
this.priceDifferences[resource] = 0;
this.restoreTimer = undefined;
};
Barter.prototype.GetPrices = function(playerID)
{
let prices = { "buy": {}, "sell": {} };
let multiplier = QueryPlayerIDInterface(playerID).GetBarterMultiplier();
for (let resource of Resources.GetBarterableCodes())
{
let truePrice = Resources.GetResource(resource).truePrice;
prices.buy[resource] = truePrice * (100 + this.CONSTANT_DIFFERENCE + this.priceDifferences[resource]) * multiplier.buy[resource] / 100;
prices.sell[resource] = truePrice * (100 - this.CONSTANT_DIFFERENCE + this.priceDifferences[resource]) * multiplier.sell[resource] / 100;
}
return prices;
};
Barter.prototype.PlayerHasMarket = function(playerID)
{
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var entities = cmpRangeManager.GetEntitiesByPlayer(playerID);
for (var entity of entities)
{
var cmpFoundation = Engine.QueryInterface(entity, IID_Foundation);
var cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("Barter"))
return true;
}
return false;
};
Barter.prototype.ExchangeResources = function(playerID, resourceToSell, resourceToBuy, amount)
{
if (amount <= 0)
{
warn("ExchangeResources: incorrect amount: " + uneval(amount));
return;
}
let availResources = Resources.GetBarterableCodes();
if (availResources.indexOf(resourceToSell) == -1)
{
warn("ExchangeResources: incorrect resource to sell: " + uneval(resourceToSell));
return;
}
if (availResources.indexOf(resourceToBuy) == -1)
{
warn("ExchangeResources: incorrect resource to buy: " + uneval(resourceToBuy));
return;
}
// This can occur when the player issues the order just before the market is destroyed or captured
if (!this.PlayerHasMarket(playerID))
return;
if (amount != 100 && amount != 500)
return;
var cmpPlayer = QueryPlayerIDInterface(playerID);
var prices = this.GetPrices(playerID);
var amountsToSubtract = {};
amountsToSubtract[resourceToSell] = amount;
if (cmpPlayer.TrySubtractResources(amountsToSubtract))
{
var amountToAdd = Math.round(prices["sell"][resourceToSell] / prices["buy"][resourceToBuy] * amount);
cmpPlayer.AddResource(resourceToBuy, amountToAdd);
- // Display chat message to observers
+ // Display chat message to observers.
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
if (cmpGUIInterface)
cmpGUIInterface.PushNotification({
"type": "barter",
"players": [playerID],
- "amountsSold": amount,
- "amountsBought": amountToAdd,
- "resourceSold": resourceToSell,
- "resourceBought": resourceToBuy
+ "amountGiven": amount,
+ "amountGained": amountToAdd,
+ "resourceGiven": resourceToSell,
+ "resourceGained": resourceToBuy
});
var cmpStatisticsTracker = QueryPlayerIDInterface(playerID, IID_StatisticsTracker);
if (cmpStatisticsTracker)
{
cmpStatisticsTracker.IncreaseResourcesSoldCounter(resourceToSell, amount);
cmpStatisticsTracker.IncreaseResourcesBoughtCounter(resourceToBuy, amountToAdd);
}
let difference = this.DIFFERENCE_PER_DEAL * amount / 100;
// Increase price difference for both exchange resources.
// Overall price difference (dynamic +/- constant) can't exceed +-99%.
this.priceDifferences[resourceToSell] -= difference;
this.priceDifferences[resourceToSell] = Math.min(99 - this.CONSTANT_DIFFERENCE, Math.max(this.CONSTANT_DIFFERENCE - 99, this.priceDifferences[resourceToSell]));
this.priceDifferences[resourceToBuy] += difference;
this.priceDifferences[resourceToBuy] = Math.min(99 - this.CONSTANT_DIFFERENCE, Math.max(this.CONSTANT_DIFFERENCE - 99, this.priceDifferences[resourceToBuy]));
}
if (this.restoreTimer === undefined)
{
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.restoreTimer = cmpTimer.SetInterval(this.entity, IID_Barter, "ProgressTimeout", this.RESTORE_TIMER_INTERVAL, this.RESTORE_TIMER_INTERVAL, {});
}
};
Barter.prototype.ProgressTimeout = function(data)
{
let needRestore = false;
for (let resource of Resources.GetBarterableCodes())
{
// Calculate value to restore, it should be limited to [-DIFFERENCE_RESTORE; DIFFERENCE_RESTORE] interval
let differenceRestore = Math.min(this.DIFFERENCE_RESTORE, Math.max(-this.DIFFERENCE_RESTORE, this.priceDifferences[resource]));
differenceRestore = -differenceRestore;
this.priceDifferences[resource] += differenceRestore;
// If price difference still exists then set flag to run timer again
if (this.priceDifferences[resource] != 0)
needRestore = true;
}
if (!needRestore)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.restoreTimer);
this.restoreTimer = undefined;
}
};
Engine.RegisterSystemComponentType(IID_Barter, "Barter", Barter);