Add spying to the game

Summary:
With c2d0327af9 we can now add spying into the game: you have first to
research a tech (available in phase 3, espionage), and then you can
bribe a random unit (defined as Bribable in its template) from a chosen
player and share its vision during 15s.
There is also an option in the gamesetup to disable this feature,
analoguous to the treasure disabling option.
In the current version, only traders (land and naval ones) are
bribables.
Reviewed By: Itms, elexis
Differential Revision: https://code.wildfiregames.com/D117
This was SVN commit r19247.
This commit is contained in:
mimo 2017-02-27 18:17:40 +00:00
parent f3e4e619bc
commit d9d1f1bbeb
16 changed files with 258 additions and 27 deletions

View File

@ -538,6 +538,7 @@ function initRadioButtons()
"RevealMap": "revealMap", "RevealMap": "revealMap",
"ExploreMap": "exploreMap", "ExploreMap": "exploreMap",
"DisableTreasures": "disableTreasures", "DisableTreasures": "disableTreasures",
"DisableSpies": "disableSpies",
"LockTeams": "lockTeams", "LockTeams": "lockTeams",
"LastManStanding" : "lastManStanding", "LastManStanding" : "lastManStanding",
"CheatsEnabled": "enableCheats" "CheatsEnabled": "enableCheats"
@ -1442,6 +1443,7 @@ function updateGUIObjects()
setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled); setGUIBoolean("enableCheats", "enableCheatsText", !!mapSettings.CheatsEnabled);
setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures); setGUIBoolean("disableTreasures", "disableTreasuresText", !!mapSettings.DisableTreasures);
setGUIBoolean("disableSpies", "disableSpiesText", !!mapSettings.DisableSpies);
setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap); setGUIBoolean("exploreMap", "exploreMapText", !!mapSettings.ExploreMap);
setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap); setGUIBoolean("revealMap", "revealMapText", !!mapSettings.RevealMap);
setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams); setGUIBoolean("lockTeams", "lockTeamsText", !!mapSettings.LockTeams);
@ -1469,7 +1471,7 @@ function updateGUIObjects()
for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap", for (let ctrl of ["victoryCondition", "wonderDuration", "populationCap",
"startingResources", "ceasefire", "revealMap", "startingResources", "ceasefire", "revealMap",
"exploreMap", "disableTreasures", "lockTeams", "lastManStanding"]) "exploreMap", "disableTreasures", "disableSpies", "lockTeams", "lastManStanding"])
hideControl(ctrl, ctrl + "Text", notScenario); hideControl(ctrl, ctrl + "Text", notScenario);
Engine.GetGUIObjectByName("civResetButton").hidden = !notScenario; Engine.GetGUIObjectByName("civResetButton").hidden = !notScenario;

View File

@ -407,7 +407,17 @@
</object> </object>
</object> </object>
<object name="optionLockTeams" size="14 308 94% 336"> <object name="optionDisableSpies" size="14 308 94% 336">
<object size="0 0 40% 28" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Disable Spies:</translatableAttribute>
</object>
<object name="disableSpiesText" size="40% 0 100% 28" type="text" style="ModernLeftLabelText"/>
<object name="disableSpies" size="40%+10 5 40%+30 100%-5" type="checkbox" style="ModernTickBox" hidden="true" tooltip_style="onscreenToolTip">
<translatableAttribute id="tooltip">Disable spies during the game.</translatableAttribute>
</object>
</object>
<object name="optionLockTeams" size="14 338 94% 366">
<object size="0 0 40% 28" type="text" style="ModernRightLabelText"> <object size="0 0 40% 28" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Teams Locked:</translatableAttribute> <translatableAttribute id="caption">Teams Locked:</translatableAttribute>
</object> </object>
@ -417,7 +427,7 @@
</object> </object>
</object> </object>
<object name="optionLastManStanding" size="14 338 94% 366"> <object name="optionLastManStanding" size="14 368 94% 396">
<object size="0 0 40% 28" type="text" style="ModernRightLabelText"> <object size="0 0 40% 28" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Last Man Standing:</translatableAttribute> <translatableAttribute id="caption">Last Man Standing:</translatableAttribute>
</object> </object>
@ -427,7 +437,7 @@
</object> </object>
</object> </object>
<object name="optionCheats" size="14 368 94% 396" hidden="true"> <object name="optionCheats" size="14 398 94% 426" hidden="true">
<object size="0 0 40% 28" type="text" style="ModernRightLabelText"> <object size="0 0 40% 28" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Cheats:</translatableAttribute> <translatableAttribute id="caption">Cheats:</translatableAttribute>
</object> </object>
@ -437,7 +447,7 @@
</object> </object>
</object> </object>
<object name="optionRating" size="14 398 94% 426" hidden="true"> <object name="optionRating" size="14 428 94% 456" hidden="true">
<object size="0 0 40% 28" hidden="false" type="text" style="ModernRightLabelText"> <object size="0 0 40% 28" hidden="false" type="text" style="ModernRightLabelText">
<translatableAttribute id="caption">Rated Game:</translatableAttribute> <translatableAttribute id="caption">Rated Game:</translatableAttribute>
</object> </object>
@ -452,7 +462,7 @@
name="hideMoreOptions" name="hideMoreOptions"
type="button" type="button"
style="StoneButton" style="StoneButton"
size="50%-70 428 50%+70 456" size="50%-70 458 50%+70 486"
tooltip_style="onscreenToolTip" tooltip_style="onscreenToolTip"
hotkey="cancel" hotkey="cancel"
> >

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<object name="diplomacyDialogPanel" <object name="diplomacyDialogPanel"
size="50%-260 50%-200 50%+260 50%+150" size="50%-280 50%-200 50%+280 50%+150"
type="image" type="image"
hidden="true" hidden="true"
sprite="ModernDialog" sprite="ModernDialog"
@ -35,7 +35,7 @@
<translatableAttribute id="caption">E</translatableAttribute> <translatableAttribute id="caption">E</translatableAttribute>
<translatableAttribute id="tooltip">Enemy</translatableAttribute> <translatableAttribute id="tooltip">Enemy</translatableAttribute>
</object> </object>
<object name="diplomacyHeaderTribute" size="430 0 100%-30 100%" type="text" style="DiplomacyText" text_align="center"> <object name="diplomacyHeaderTribute" size="430 0 100%-70 100%" type="text" style="DiplomacyText" text_align="center">
<translatableAttribute id="caption">Tribute</translatableAttribute> <translatableAttribute id="caption">Tribute</translatableAttribute>
</object> </object>
</object> </object>
@ -55,7 +55,7 @@
<object name="diplomacyPlayerEnemy[n]" size="400 0 420 100%" type="button" style="StoneButton" hidden="true"/> <object name="diplomacyPlayerEnemy[n]" size="400 0 420 100%" type="button" style="StoneButton" hidden="true"/>
<!-- Tribute --> <!-- Tribute -->
<object size="430 0 100%-40 100%"> <object size="430 0 100%-80 100%">
<repeat count="8" var="r"> <repeat count="8" var="r">
<object name="diplomacyPlayer[n]_tribute[r]" size="0 0 20 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true"> <object name="diplomacyPlayer[n]_tribute[r]" size="0 0 20 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyPlayer[n]_tribute[r]_image" type="image" size="0 0 100% 100%" ghost="true"/> <object name="diplomacyPlayer[n]_tribute[r]_image" type="image" size="0 0 100% 100%" ghost="true"/>
@ -63,9 +63,13 @@
</repeat> </repeat>
</object> </object>
<object name="diplomacyAttackRequest[n]" size="100%-20 0 100% 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true"> <object name="diplomacyAttackRequest[n]" size="100%-58 0 100%-38 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacyAttackRequestImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/attack-request.png" ghost="true"/> <object name="diplomacyAttackRequestImage[n]" type="image" size="0 0 100% 100%" sprite="stretched:session/icons/attack-request.png" ghost="true"/>
</object> </object>
<object name="diplomacySpyRequest[n]" size="100%-30 0 100%-10 100%" type="button" style="iconButton" tooltip_style="sessionToolTipBold" hidden="true">
<object name="diplomacySpyRequestImage[n]" type="image" size="2 2 100%-2 100%-2" ghost="true"/>
</object>
</object> </object>
</repeat> </repeat>
</object> </object>

View File

@ -314,7 +314,16 @@ function openDiplomacy()
g_IsDiplomacyOpen = true; g_IsDiplomacyOpen = true;
updateDiplomacyPanel(true);
}
function updateDiplomacyPanel(opening = false)
{
if (g_ViewedPlayer < 1 || !g_IsDiplomacyOpen)
return;
let isCeasefireActive = GetSimState().ceasefireActive; let isCeasefireActive = GetSimState().ceasefireActive;
let hasSharedLos = GetSimState().players[g_ViewedPlayer].hasSharedLos;
// Get offset for one line // Get offset for one line
let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size;
@ -329,8 +338,11 @@ function openDiplomacy()
diplomacySetupTexts(i, rowsize); diplomacySetupTexts(i, rowsize);
diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked); 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]); 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; Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false;
} }
@ -449,8 +461,61 @@ function diplomacyFormatAttackRequestButton(i, hidden)
button.enabled = controlsPlayer(g_ViewedPlayer); button.enabled = controlsPlayer(g_ViewedPlayer);
button.tooltip = translate("Request your allies to attack this enemy"); button.tooltip = translate("Request your allies to attack this enemy");
button.onpress = (function(i) { return function() { button.onPress = (function(i) { return function() {
Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "target": i }); 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); }; })(i);
} }
@ -540,7 +605,7 @@ function openTrade()
let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]"); let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton["+i+"]");
buttonResource.enabled = controlsPlayer(g_ViewedPlayer); buttonResource.enabled = controlsPlayer(g_ViewedPlayer);
buttonResource.onpress = (function(resource){ buttonResource.onPress = (function(resource){
return function() { return function() {
if (Engine.HotkeyIsPressed("session.fulltradeswap")) if (Engine.HotkeyIsPressed("session.fulltradeswap"))
{ {
@ -555,7 +620,7 @@ function openTrade()
})(resCode); })(resCode);
buttonUp.enabled = controlsPlayer(g_ViewedPlayer); buttonUp.enabled = controlsPlayer(g_ViewedPlayer);
buttonUp.onpress = (function(resource){ buttonUp.onPress = (function(resource){
return function() { return function() {
proba[resource] += Math.min(STEP, proba[selec]); proba[resource] += Math.min(STEP, proba[selec]);
proba[selec] -= Math.min(STEP, proba[selec]); proba[selec] -= Math.min(STEP, proba[selec]);
@ -565,7 +630,7 @@ function openTrade()
})(resCode); })(resCode);
buttonDn.enabled = controlsPlayer(g_ViewedPlayer); buttonDn.enabled = controlsPlayer(g_ViewedPlayer);
buttonDn.onpress = (function(resource){ buttonDn.onPress = (function(resource){
return function() { return function() {
proba[selec] += Math.min(STEP, proba[resource]); proba[selec] += Math.min(STEP, proba[resource]);
proba[resource] -= Math.min(STEP, proba[resource]); proba[resource] -= Math.min(STEP, proba[resource]);

View File

@ -515,9 +515,6 @@ function handleNotifications()
function updateDiplomacy() function updateDiplomacy()
{ {
updatePlayerData(); updatePlayerData();
if (g_IsDiplomacyOpen)
openDiplomacy();
} }
/** /**

View File

@ -847,6 +847,8 @@ function updateGUIObjects()
if (battleState) if (battleState)
global.music.setState(global.music.states[battleState]); global.music.setState(global.music.states[battleState]);
} }
updateDiplomacyPanel();
} }
function onReplayFinished() function onReplayFinished()

View File

@ -1,12 +1,21 @@
function VisionSharing() {} function VisionSharing() {}
VisionSharing.prototype.Schema = VisionSharing.prototype.Schema =
"<empty/>"; "<element name='Bribable'>" +
"<data type='boolean'/>" +
"</element>" +
"<optional>" +
"<element name='Duration' a:help='Duration (in second) of the vision sharing for spies'>" +
"<ref name='positiveDecimal'/>" +
"</element>" +
"</optional>";
VisionSharing.prototype.Init = function() VisionSharing.prototype.Init = function()
{ {
this.activated = false; this.activated = false;
this.shared = new Set(); this.shared = undefined;
this.spyId = 0;
this.spies = undefined;
}; };
/** /**
@ -20,7 +29,7 @@ VisionSharing.prototype.Activate = function()
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership || cmpOwnership.GetOwner() <= 0) if (!cmpOwnership || cmpOwnership.GetOwner() <= 0)
return; return;
this.shared.add(cmpOwnership.GetOwner()); this.shared = new Set([cmpOwnership.GetOwner()]);
Engine.PostMessage(this.entity, MT_VisionSharingChanged, Engine.PostMessage(this.entity, MT_VisionSharingChanged,
{ "entity": this.entity, "player": cmpOwnership.GetOwner(), "add": true }); { "entity": this.entity, "player": cmpOwnership.GetOwner(), "add": true });
this.activated = true; this.activated = true;
@ -56,6 +65,12 @@ VisionSharing.prototype.CheckVisionSharings = function()
} }
} }
} }
// 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) if (!this.activated)
@ -73,9 +88,9 @@ VisionSharing.prototype.CheckVisionSharings = function()
this.shared = shared; this.shared = shared;
}; };
VisionSharing.prototype.OnDiplomacyChanged = function(msg) VisionSharing.prototype.IsBribable = function()
{ {
this.CheckVisionSharings(); return this.template.Bribable == "true";
}; };
VisionSharing.prototype.OnGarrisonedUnitsChanged = function(msg) VisionSharing.prototype.OnGarrisonedUnitsChanged = function(msg)
@ -89,4 +104,69 @@ VisionSharing.prototype.OnOwnershipChanged = function(msg)
this.CheckVisionSharings(); 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); Engine.RegisterComponentType(IID_VisionSharing, "VisionSharing", VisionSharing);

View File

@ -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"
}

View File

@ -748,6 +748,25 @@ var g_Commands = {
cmpAIInterface.PushEvent("AttackRequest", cmd); 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) "dialog-answer": function(player, cmd, data)
{ {
// Currently nothing. Triggers can read it anyway, and send this // Currently nothing. Triggers can read it anyway, and send this

View File

@ -121,6 +121,12 @@ function LoadPlayerSettings(settings, newPlayers)
if (disabledTemplates.length) if (disabledTemplates.length)
cmpPlayer.SetDisabledTemplates(disabledTemplates); cmpPlayer.SetDisabledTemplates(disabledTemplates);
if (settings.DisableSpies)
{
cmpPlayer.AddDisabledTechnology("unlock_spies");
cmpPlayer.AddDisabledTemplate("special/spy");
}
// If diplomacy explicitly defined, use that; otherwise use teams // If diplomacy explicitly defined, use that; otherwise use teams
if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined) if (getSetting(playerData, playerDefaults, i, "Diplomacy") !== undefined)
cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy")); cmpPlayer.SetDiplomacy(getSetting(playerData, playerDefaults, i, "Diplomacy"));

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<Entity>
<Cost>
<Population>0</Population>
<PopulationBonus>0</PopulationBonus>
<BuildTime>0</BuildTime>
<Resources>
<food>0</food>
<wood>0</wood>
<stone>0</stone>
<metal>900</metal>
</Resources>
</Cost>
<Identity>
<Civ>gaia</Civ>
<Classes datatype="tokens">Spy</Classes>
<GenericName>Spy</GenericName>
<RequiredTechnology>unlock_spies</RequiredTechnology>
</Identity>
<VisionSharing>
<Bribable>false</Bribable>
<Duration>15</Duration>
</VisionSharing>
</Entity>

View File

@ -140,7 +140,9 @@
<Vision> <Vision>
<Range>40</Range> <Range>40</Range>
</Vision> </Vision>
<VisionSharing/> <VisionSharing>
<Bribable>false</Bribable>
</VisionSharing>
<VisualActor> <VisualActor>
<ConstructionPreview/> <ConstructionPreview/>
<SilhouetteDisplay>false</SilhouetteDisplay> <SilhouetteDisplay>false</SilhouetteDisplay>

View File

@ -48,6 +48,7 @@
trade_gain_01 trade_gain_01
trade_gain_02 trade_gain_02
trade_commercial_treaty trade_commercial_treaty
unlock_spies
</Technologies> </Technologies>
<Entities datatype="tokens"> <Entities datatype="tokens">
units/{civ}_support_trader units/{civ}_support_trader

View File

@ -124,6 +124,9 @@
<Vision> <Vision>
<Range>12</Range> <Range>12</Range>
</Vision> </Vision>
<VisionSharing>
<Bribable>false</Bribable>
</VisionSharing>
<VisualActor> <VisualActor>
<SilhouetteDisplay>true</SilhouetteDisplay> <SilhouetteDisplay>true</SilhouetteDisplay>
<SilhouetteOccluder>false</SilhouetteOccluder> <SilhouetteOccluder>false</SilhouetteOccluder>

View File

@ -26,7 +26,7 @@
<Identity> <Identity>
<GenericName>Merchantman</GenericName> <GenericName>Merchantman</GenericName>
<Tooltip>Trade between docks. Garrison a Trader aboard for additional profit (+20% for each garrisoned). Gather profitable aquatic treasures.</Tooltip> <Tooltip>Trade between docks. Garrison a Trader aboard for additional profit (+20% for each garrisoned). Gather profitable aquatic treasures.</Tooltip>
<VisibleClasses datatype="tokens">Trader</VisibleClasses> <VisibleClasses datatype="tokens">Trader Bribable</VisibleClasses>
<RequiredTechnology>phase_town</RequiredTechnology> <RequiredTechnology>phase_town</RequiredTechnology>
<Formations disable=""/> <Formations disable=""/>
</Identity> </Identity>
@ -63,4 +63,7 @@
<Vision> <Vision>
<Range>50</Range> <Range>50</Range>
</Vision> </Vision>
<VisionSharing>
<Bribable>true</Bribable>
</VisionSharing>
</Entity> </Entity>

View File

@ -12,7 +12,7 @@
</Health> </Health>
<Identity> <Identity>
<Classes datatype="tokens">-ConquestCritical</Classes> <Classes datatype="tokens">-ConquestCritical</Classes>
<VisibleClasses datatype="tokens">Trader</VisibleClasses> <VisibleClasses datatype="tokens">Trader Bribable</VisibleClasses>
<GenericName>Trader</GenericName> <GenericName>Trader</GenericName>
<History>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.</History> <History>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.</History>
<Tooltip>Trade resources between your own markets and those of your allies.</Tooltip> <Tooltip>Trade resources between your own markets and those of your allies.</Tooltip>
@ -50,4 +50,7 @@
<Vision> <Vision>
<Range>60</Range> <Range>60</Range>
</Vision> </Vision>
<VisionSharing>
<Bribable>true</Bribable>
</VisionSharing>
</Entity> </Entity>