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
@@ -538,6 +538,7 @@
"RevealMap": "revealMap",
"ExploreMap": "exploreMap",
"DisableTreasures": "disableTreasures",
+ "DisableSpies": "disableSpies",
"LockTeams": "lockTeams",
"LastManStanding" : "lastManStanding",
"CheatsEnabled": "enableCheats"
@@ -1442,6 +1443,7 @@
setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled);
setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures);
+ setGUIBoolean("disableSpies", "disableSpiesText", !!mapSettings.DisableSpies);
setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap);
setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap);
setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams);
@@ -1469,7 +1471,7 @@
for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap",
"startingResources", "ceasefire", "revealMap",
- "exploreMap", "disableTreasures", "lockTeams", "lastManStanding"])
+ "exploreMap", "disableTreasures", "disableSpies", "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(true);
+}
+
+function updateDiplomacyPanel(opening = false)
+{
+ 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;
@@ -329,8 +338,11 @@
diplomacySetupTexts(i, rowsize);
diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked);
- diplomacyFormatTributeButtons(i, myself || playerInactive);
+ // Tribute buttons do not need to be updated onTick, and should not because of massTributing
+ if (opening)
+ 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;
}
@@ -449,8 +461,61 @@
button.enabled = controlsPlayer(g_ViewedPlayer);
button.tooltip = translate("Request your allies to attack this enemy");
- button.onpress = (function(i) { return function() {
- Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "target": i });
+ button.onPress = (function(i) { return function() {
+ Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "player": i });
+ }; })(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 a limited period.")];
+ 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, "player": i });
+ closeDiplomacy();
}; })(i);
}
@@ -540,7 +605,7 @@
let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
- buttonResource.onpress = (function(resource){
+ buttonResource.onPress = (function(resource){
return function() {
if (Engine.HotkeyIsPressed("session.fulltradeswap"))
{
@@ -555,7 +620,7 @@
})(resCode);
buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
- buttonUp.onpress = (function(resource){
+ buttonUp.onPress = (function(resource){
return function() {
proba[resource] += Math.min(STEP, proba[selec]);
proba[selec] -= Math.min(STEP, proba[selec]);
@@ -565,7 +630,7 @@
})(resCode);
buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
- buttonDn.onpress = (function(resource){
+ buttonDn.onPress = (function(resource){
return function() {
proba[selec] += Math.min(STEP, proba[resource]);
proba[resource] -= Math.min(STEP, proba[resource]);
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
@@ -515,9 +515,6 @@
function updateDiplomacy()
{
updatePlayerData();
-
- if (g_IsDiplomacyOpen)
- openDiplomacy();
}
/**
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
@@ -847,6 +847,8 @@
if (battleState)
global.music.setState(global.music.states[battleState]);
}
+
+ 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,9 +88,9 @@
this.shared = shared;
};
-VisionSharing.prototype.OnDiplomacyChanged = function(msg)
+VisionSharing.prototype.IsBribable = function()
{
- this.CheckVisionSharings();
+ return this.template.Bribable == "true";
};
VisionSharing.prototype.OnGarrisonedUnitsChanged = function(msg)
@@ -89,4 +104,69 @@
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();
+};
+
+/**
+ * Returns true if this entity share its vision with player
+ */
+VisionSharing.prototype.ShareVisionWith = function(player)
+{
+ if (this.activated)
+ return this.shared.has(player);
+
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ return cmpOwnership && cmpOwnership.GetOwner() == player;
+};
+
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 about the countries they crossed.",
+ "cost": { "food": 500, "wood": 500, "stone": 300, "metal": 300 },
+ "requirements": { "tech": "phase_city" },
+ "icon": "spy_trader.png",
+ "researchTime": 80,
+ "tooltip": "Allows to bribe other players' units to 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,25 @@
cmpAIInterface.PushEvent("AttackRequest", cmd);
},
+ "spy-request": function(player, cmd, data)
+ {
+ let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let ents = cmpRangeManager.GetEntitiesByPlayer(cmd.player).filter(ent => {
+ let cmpVisionSharing = Engine.QueryInterface(ent, IID_VisionSharing);
+ return cmpVisionSharing && cmpVisionSharing.IsBribable() && !cmpVisionSharing.ShareVisionWith(player);
+ });
+ let ent = pickRandom(ents);
+ if (ent)
+ Engine.QueryInterface(ent, IID_VisionSharing).AddSpy(cmd.source);
+ else
+ Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
+ "type": "text",
+ "players": [player],
+ "message": markForTranslation("There are no bribable units"),
+ "translateMessage": true
+ });
+ },
+
"dialog-answer": function(player, cmd, data)
{
// Currently nothing. Triggers can read it anyway, and send this
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.DisableSpies)
+ {
+ cmpPlayer.AddDisabledTechnology("unlock_spies");
+ cmpPlayer.AddDisabledTemplate("special/spy");
+ }
+
// If diplomacy explicitly defined, use that; otherwise use teams
if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined)
cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy"));
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
+ 900
+
+
+
+ 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
@@ -26,7 +26,7 @@
Merchantman
Trade between docks. Garrison a Trader aboard for additional profit (+20% for each garrisoned). Gather profitable aquatic treasures.
- Trader
+ Trader Bribable
phase_town
@@ -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
@@ -12,7 +12,7 @@
-ConquestCritical
- Trader
+ Trader Bribable
Trader
Trade was a very important part of ancient civilization - effective trading and control of trade routes equaled wealth. Trade took place by many forms from foot to caravans to merchant ships. One of the most notorious examples of the power of trade was the Silk Road.
Trade resources between your own markets and those of your allies.
@@ -50,4 +50,7 @@
60
+
+ true
+