Index: binaries/data/mods/public/gui/gamesetup/gamesetup.js
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup.js
+++ binaries/data/mods/public/gui/gamesetup/gamesetup.js
@@ -518,6 +518,7 @@
"RevealMap": "revealMap",
"ExploreMap": "exploreMap",
"DisableTreasures": "disableTreasures",
+ "EnableSpies": "enableSpies",
"LockTeams": "lockTeams",
"LastManStanding" : "lastManStanding",
"CheatsEnabled": "enableCheats"
@@ -1421,6 +1422,7 @@
setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled);
setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures);
+ setGUIBoolean("enableSpies", "enableSpiesText", !!mapSettings.EnableSpies);
setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap);
setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap);
setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams);
@@ -1448,7 +1450,7 @@
for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap",
"startingResources", "ceasefire", "revealMap",
- "exploreMap", "disableTreasures", "lockTeams", "lastManStanding"])
+ "exploreMap", "disableTreasures", "enableSpies", "lockTeams", "lastManStanding"])
hideControl(ctrl, ctrl + "Text", notScenario);
Engine.GetGUIObjectByName("civResetButton").hidden = !notScenario;
Index: binaries/data/mods/public/gui/gamesetup/gamesetup.xml
===================================================================
--- binaries/data/mods/public/gui/gamesetup/gamesetup.xml
+++ binaries/data/mods/public/gui/gamesetup/gamesetup.xml
@@ -407,7 +407,17 @@
-
-
+
Last Man Standing:
@@ -427,7 +437,7 @@
-
+
Cheats:
@@ -437,7 +447,7 @@
-
+
Rated Game:
@@ -452,7 +462,7 @@
name="hideMoreOptions"
type="button"
style="StoneButton"
- size="50%-70 428 50%+70 456"
+ size="50%-70 458 50%+70 486"
tooltip_style="onscreenToolTip"
hotkey="cancel"
>
Index: binaries/data/mods/public/gui/session/diplomacy_window.xml
===================================================================
--- binaries/data/mods/public/gui/session/diplomacy_window.xml
+++ binaries/data/mods/public/gui/session/diplomacy_window.xml
@@ -1,7 +1,7 @@
E
Enemy
-
+
Tribute
@@ -55,7 +55,7 @@
-
+
@@ -63,9 +63,13 @@
-
+
+
+
+
+
Index: binaries/data/mods/public/gui/session/menu.js
===================================================================
--- binaries/data/mods/public/gui/session/menu.js
+++ binaries/data/mods/public/gui/session/menu.js
@@ -314,7 +314,16 @@
g_IsDiplomacyOpen = true;
+ updateDiplomacyPanel();
+}
+
+function updateDiplomacyPanel()
+{
+ if (g_ViewedPlayer < 1 || !g_IsDiplomacyOpen)
+ return;
+
let isCeasefireActive = GetSimState().ceasefireActive;
+ let hasSharedLos = GetSimState().players[g_ViewedPlayer].hasSharedLos;
// Get offset for one line
let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
@@ -331,6 +340,7 @@
diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked);
diplomacyFormatTributeButtons(i, myself || playerInactive);
diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]);
+ diplomacyFormatSpyRequestButton(i, myself || playerInactive || g_Players[i].isMutualAlly[g_ViewedPlayer] && hasSharedLos);
}
Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
}
@@ -454,6 +464,59 @@
}; })(i);
}
+function diplomacyFormatSpyRequestButton(i, hidden)
+{
+ let button = Engine.GetGUIObjectByName("diplomacySpyRequest["+(i-1)+"]");
+ let template = GetTemplateData("special/spy");
+ button.hidden = hidden || !template || GetSimState().players[g_ViewedPlayer].disabledTemplates["special/spy"];
+ if (button.hidden)
+ return;
+
+ button.enabled = controlsPlayer(g_ViewedPlayer);
+ let modifier = "";
+ let tooltips = [translate("Bribe a random unit from this player and share its vision during 10 s.")];
+ if (!button.enabled)
+ modifier = "color:0 0 0 127:grayscale:";
+ else
+ {
+ if (template.requiredTechnology)
+ {
+ let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
+ "tech": template.requiredTechnology,
+ "player": g_ViewedPlayer
+ });
+ if (!technologyEnabled)
+ {
+ modifier = "color:0 0 0 127:grayscale:"
+ button.enabled = false;
+ tooltips.push(getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[g_ViewedPlayer].civ));
+ }
+ }
+
+ if (template.cost)
+ {
+ let neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
+ "cost": template.cost,
+ "player": g_ViewedPlayer
+ });
+ if (neededResources)
+ {
+ if (button.enabled)
+ modifier = resourcesToAlphaMask(neededResources) +":";
+ button.enabled = false;
+ tooltips.push(getNeededResourcesTooltip(neededResources));
+ }
+ }
+ }
+ let icon = Engine.GetGUIObjectByName("diplomacySpyRequestImage["+(i-1)+"]");
+ icon.sprite = modifier + "stretched:session/icons/economics.png";
+ button.tooltip = tooltips.filter(tip => tip).join("\n");
+ button.onpress = (function(i) { return function() {
+ Engine.PostNetworkCommand({ "type": "spy-request", "source": g_ViewedPlayer, "target": i });
+ closeDiplomacy();
+ }; })(i);
+}
+
function closeDiplomacy()
{
g_IsDiplomacyOpen = false;
Index: binaries/data/mods/public/gui/session/messages.js
===================================================================
--- binaries/data/mods/public/gui/session/messages.js
+++ binaries/data/mods/public/gui/session/messages.js
@@ -517,7 +517,7 @@
g_Players = getPlayerData(g_Players);
if (g_IsDiplomacyOpen)
- openDiplomacy();
+ updateDiplomacyPanel();
}
/**
Index: binaries/data/mods/public/gui/session/session.js
===================================================================
--- binaries/data/mods/public/gui/session/session.js
+++ binaries/data/mods/public/gui/session/session.js
@@ -786,6 +786,9 @@
if (battleState)
global.music.setState(global.music.states[battleState]);
}
+
+ if (g_IsDiplomacyOpen)
+ updateDiplomacyPanel();
}
function onReplayFinished()
Index: binaries/data/mods/public/simulation/components/VisionSharing.js
===================================================================
--- binaries/data/mods/public/simulation/components/VisionSharing.js
+++ binaries/data/mods/public/simulation/components/VisionSharing.js
@@ -1,12 +1,21 @@
function VisionSharing() {}
VisionSharing.prototype.Schema =
- "";
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
VisionSharing.prototype.Init = function()
{
this.activated = false;
- this.shared = new Set();
+ this.shared = undefined;
+ this.spyId = 0;
+ this.spies = undefined;
};
/**
@@ -20,7 +29,7 @@
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() <= 0)
return;
- this.shared.add(cmpOwnership.GetOwner());
+ this.shared = new Set([cmpOwnership.GetOwner()]);
Engine.PostMessage(this.entity, MT_VisionSharingChanged,
{ "entity": this.entity, "player": cmpOwnership.GetOwner(), "add": true });
this.activated = true;
@@ -56,6 +65,12 @@
}
}
}
+
+ // vision sharing due to spies
+ if (this.spies)
+ for (let spy of this.spies.values())
+ if (spy > 0 && spy != owner)
+ shared.add(spy);
}
if (!this.activated)
@@ -73,6 +88,11 @@
this.shared = shared;
};
+VisionSharing.prototype.IsBribable = function()
+{
+ return this.template.Bribable == "true";
+};
+
VisionSharing.prototype.OnDiplomacyChanged = function(msg)
{
this.CheckVisionSharings();
@@ -89,4 +109,57 @@
this.CheckVisionSharings();
};
+VisionSharing.prototype.AddSpy = function(player, timeLength)
+{
+ if (!this.IsBribable())
+ return;
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() == player || player <= 0)
+ return;
+
+ let cmpTechnologyManager = QueryPlayerIDInterface(player, IID_TechnologyManager);
+ if (!cmpTechnologyManager || !cmpTechnologyManager.CanProduce("special/spy"))
+ return;
+
+ let template = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).GetTemplate("special/spy");
+ let costs = {};
+ for (let res in template.Cost.Resources)
+ costs[res] = Math.floor(ApplyValueModificationsToTemplate("Cost/Resources/"+res, +template.Cost.Resources[res], player, template));
+ let cmpPlayer = QueryPlayerIDInterface(player);
+ if (!cmpPlayer || !cmpPlayer.TrySubtractResources(costs))
+ return;
+
+ // If no duration given, take it from the spy template and scale it with the ent vision
+ // When no duration argument nor in spy template, it is a permanent spy
+ let duration = timeLength;
+ if (!duration && template.VisionSharing && template.VisionSharing.Duration)
+ {
+ duration = ApplyValueModificationsToTemplate("VisionSharing/Duration", +template.VisionSharing.Duration, player, template);
+ let cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
+ if (cmpVision)
+ duration *= 60 / Math.max(30, cmpVision.GetRange());
+ }
+
+ if (!this.spies)
+ this.spies = new Map();
+
+ this.spies.set(++this.spyId, player);
+ if (duration)
+ {
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ cmpTimer.SetTimeout(this.entity, IID_VisionSharing, "RemoveSpy", duration*1000, { "id": this.spyId });
+ }
+ this.Activate();
+ this.CheckVisionSharings();
+
+ return this.spyId;
+};
+
+VisionSharing.prototype.RemoveSpy = function(data)
+{
+ this.spies.delete(data.id);
+ this.CheckVisionSharings();
+};
+
Engine.RegisterComponentType(IID_VisionSharing, "VisionSharing", VisionSharing);
Index: binaries/data/mods/public/simulation/data/technologies/unlock_spies.json
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/data/technologies/unlock_spies.json
@@ -0,0 +1,10 @@
+{
+ "genericName": "Espionage",
+ "description": "Merchants first goal was trading, but they also gathered information on the countries they crossed.",
+ "cost": { "food": 800, "wood": 800, "stone": 500, "metal": 500 },
+ "requirements": { "tech": "phase_city" },
+ "icon": "spy_trader.png",
+ "researchTime": 80,
+ "tooltip": "Allows to bribe other player units and share their vision.",
+ "soundComplete": "interface/alarm/alarm_upgradearmory.xml"
+}
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -748,6 +748,20 @@
cmpAIInterface.PushEvent("AttackRequest", cmd);
},
+ "spy-request": function(player, cmd, data)
+ {
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let ents = cmpRangeManager.GetEntitiesByPlayer(cmd.target).filter(ent => {
+ let cmpVisionSharing = Engine.QueryInterface(ent, IID_VisionSharing);
+ return cmpVisionSharing && cmpVisionSharing.IsBribable();
+ });
+ let ent = pickRandom(ents);
+ if (ent)
+ Engine.QueryInterface(ent, IID_VisionSharing).AddSpy(cmd.source);
+ else
+ notifySpyFailure(player);
+ },
+
"dialog-answer": function(player, cmd, data)
{
// Currently nothing. Triggers can read it anyway, and send this
@@ -808,6 +822,20 @@
}
/**
+ * Sends a GUI notification when no units to bribe
+ */
+function notifySpyFailure(player)
+{
+ var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ cmpGUIInterface.PushNotification({
+ "type": "text",
+ "players": [player],
+ "message": "There are no bribable units",
+ "translateMessage": true
+ });
+}
+
+/**
* Get some information about the formations used by entities.
* The entities must have a UnitAI component.
*/
Index: binaries/data/mods/public/simulation/helpers/Player.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Player.js
+++ binaries/data/mods/public/simulation/helpers/Player.js
@@ -121,6 +121,12 @@
if (disabledTemplates.length)
cmpPlayer.SetDisabledTemplates(disabledTemplates);
+ if (!settings.EnableSpies)
+ {
+ cmpPlayer.AddDisabledTechnology("unlock_spies");
+ cmpPlayer.AddDisabledTemplate("special/spy");
+ }
+
// If diplomacy explicitly defined, use that; otherwise use teams
if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined)
cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy"));
Index: binaries/data/mods/public/simulation/templates/special/spy.xml
===================================================================
--- /dev/null
+++ binaries/data/mods/public/simulation/templates/special/spy.xml
@@ -0,0 +1,24 @@
+
+
+
+ 0
+ 0
+ 0
+
+ 0
+ 0
+ 0
+ 2000
+
+
+
+ gaia
+ Spy
+ Spy
+ unlock_spies
+
+
+ false
+ 15
+
+
Index: binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure.xml
+++ binaries/data/mods/public/simulation/templates/template_structure.xml
@@ -140,7 +140,9 @@
40
-
+
+ false
+
false
Index: binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
+++ binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml
@@ -48,6 +48,7 @@
trade_gain_01
trade_gain_02
trade_commercial_treaty
+ unlock_spies
units/{civ}_support_trader
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml
+++ binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -124,6 +124,9 @@
12
+
+ false
+
true
false
Index: binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml
@@ -63,4 +63,7 @@
50
+
+ true
+
Index: binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml
+++ binaries/data/mods/public/simulation/templates/template_unit_support_trader.xml
@@ -50,4 +50,7 @@
60
+
+ true
+