设为首页收藏本站

塞爱维(CIV)文明联盟

 找回密码
 注册
查看: 37538|回复: 21

[原创] [吃DLL长姿势系列 第一弹]抛砖引玉 - 文明5间谍系统数值解析

[复制链接]
发表于 2012-11-8 12:24:37 | 显示全部楼层 |阅读模式
DLL开源了,一直对新版的间谍和宗教系统很有兴趣,于是翻了一下源代码,大致理解了整个间谍系统。给大家作为参考。
所有的代码几乎都在CvEspionageClass.cpp下面。常数在CvGlobal.cpp下面(部分引用XML),关于操纵选举的部分在CvMinorCivAI.cpp下面。
注意本楼并没有解读间谍AI,等待对AI有研究的诸位来完成。
也给各位对修改这个系统有兴趣的各位MODDER作为参考。
由于本人一向玩的英文版,有的翻译和cc翻译可能出现偏差,见谅。


目录:

2楼 太长不看党的福音——所有结论一览
(吐槽:虽然还是很长)
3楼 常数部分:城邦选举回合数,间谍旅行、建立监视、复活所需时间,以及和谍报系统有关的建筑
4楼 城市间谍:了解阴谋
5楼 城市间谍:窃取科技 - 窃取速度和窃取目标
6楼 城市间谍:窃取科技 - 可能结果,兼论警察国家这个政策到底是干嘛的
7楼 城邦间谍:操纵选举
8楼 城邦间谍:发动政变 – 成功概率
9楼 城邦间谍:发动政变 – 可能结果

[ 本帖最后由 object022 于 2012-11-8 19:01 编辑 ]
 楼主| 发表于 2012-11-8 12:24:50 | 显示全部楼层
这里是所有结论的总揽,对阅读C++代码没有任何兴趣的人,就不用看这楼之后的东西了。
[杂项]
间谍旅行的时间=1Turn 建立监视=3Turn 复活=5Turn
潜伏对方城市后获得周围1圈视野
[文明阴谋]
潜伏于敌方城市的间谍将每5回合汇报一次情报。
获取AI阴谋的策略:对所有AI有阴谋的文明,随机选择一个进行汇报
阴谋分为两类:一类是对某个文明的城市进行一次突袭,如果你用的是1级间谍,是不能知道目标文明和城市的。
另一类是这个AI正在对某个文明实行欺骗(准备背刺或者准备突袭,但表面上是友好或者中立)。似乎是新版加入的内容。
还能获得的情报是该AI正在建造军队(海军/陆军),以及该城市正在建造的奇观。
[窃取科技:速度]
速度=XYZ,X为目标城市的科研产出,Y是目标城市和文明的间谍防御修正(毫无防御为100,治安署和警察局各-25,国家情报局-25,GFW在本城-100在本文明-25,警察国家-25),Z为本间谍等级的速度修正(1级为1,2级为1.25,3级为1.5),且最低为1。可以发现反间谍不影响这个进度。
需要的进度=125X,X为你能偷取的科技中的最高烧瓶数。
所需回合数是需要进度/速度的上取整。
[窃取科技:结果]
目标城市无反间谍的时候:没有警察国家政策时 不被发现/被发现但不被识别/被识别 的概率是33.3%/33.3%/33.3%,有警察国家后变成 26.7%/26.7%/46.7%
目标城市有反间谍的时候(以下概率值为被发现但不被识别/被识别/被杀死的概率)
1级反间谍的概率:没有警察国家时33.3%/33.3%/33.3%,有警察国家时26.7%/26.7%/46.7%。
2级反间谍的概率:没有警察国家时23.3%/33.3%/43.3%,有警察国家时16.7%/26.7%/56.7%。
3级反间谍的概率:没有警察国家时13.3%/33.3%/53.3%,有警察国家时6.7%/26.7%/66.7%。
可以发现这个概率和自己的间谍是几级是没有关系的。
[操纵选举]
间谍在城邦中操纵选举时可以获得投向自己文明的票,1级间谍1回合1票,2级1回合4票,3级1回合9票。如果城邦选举时间谍不在场,之前积累的投票全部失效。
城邦选举时,如果有至少一个文明的投票依然有效,以投票作为权值随机选择一个文明作为获胜者(比如A有9票,B有1票,那么A有90%的几率获胜)
获胜文明获得20影响力,其他文明失去5点影响力,扣减到0时停止。
[发动政变:成功率]
当且仅当当前城邦有一个盟友且盟友不是你的时候,才可以发动政变。
政变的成功率和以下因素有关:你和当前盟友的影响力之差,你的间谍等级,当前盟友是否在城邦放置间谍以及其间谍等级。
每一点影响力之差减少的成功概率如下(记为X)
当当前盟友没有安排间谍时,我方间谍为1/2/3级时,X=1.5/0.75/0.375
当前盟友在城邦里安排了一个1级间谍时,我方间谍为1/2/3级时,X=3/1.5/0/75
当前盟友在城邦里安排了一个2级间谍时,我方间谍为1/2/3级时,X=4.5/3/2.25
当前盟友在城邦里安排了一个3级间谍时,我方间谍为1/2/3级时,X=5.25/3.75/3【由于一个设计BUG,后面三行的X在当前版本会全部减半。太疏忽了,太疏忽了…】
最终概率为100-TX,T为两者影响力之差,小于0时强制取0,大于85时强制取85。
[发动政变:结果]
政变成功:
你的影响力和当前盟友影响力互换,你成为了新的盟友。
除你以外,所有人的影响力-20。
政变失败:
你的影响力变为-10,其他人不变。
你的间谍死亡。

接下来的几楼分别从各个角度详细解释了这些东西,如果你只是想要结论的,就不用往下看了。
另:密集恐惧症患者止步。

[ 本帖最后由 object022 于 2012-11-8 18:45 编辑 ]

评分

1

查看全部评分

回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:25:06 | 显示全部楼层
先来看几个常数,这些定义在CvGlobal下,部分引用了XML。感谢贴吧的 @旋流之舞踏 提供新版XML数据。
  1. const int iSpyTurnsToTravel = 1;
  2. const int iSpyTurnsToEstablishSurveillance = 3;
  3. const int iSpyTurnsToRevive = 5;
  4. const int iIntrigueTurnsValid = 5;
  5. 引用XML:
  6. m_iESPIONAGE_GATHERING_INTEL_COST_PERCENT = GC.getDefineINT("ESPIONAGE_GATHERING_INTEL_COST_PERCENT") = 125
  7. m_iESPIONAGE_GATHERING_INTEL_RATE_BY_SPY_RANK_PERCENT = GC.getDefineINT("ESPIONAGE_GATHERING_INTEL_RATE_BY_SPY_RANK_PERCENT") = 25
  8. m_iESPIONAGE_GATHERING_INTEL_RATE_BASE_PERCENT = GC.getDefineINT("ESPIONAGE_GATHERING_INTEL_RATE_BASE_PERCENT") = 100
  9. m_iESPIONAGE_TURNS_BETWEEN_CITY_STATE_ELECTIONS = GC.getDefineINT("ESPIONAGE_TURNS_BETWEEN_CITY_STATE_ELECTIONS") = 15
  10. m_iESPIONAGE_INFLUENCE_GAINED_FOR_RIGGED_ELECTION = GC.getDefineINT("ESPIONAGE_INFLUENCE_GAINED_FOR_RIGGED_ELECTION") = 20
  11. m_iESPIONAGE_INFLUENCE_LOST_FOR_RIGGED_ELECTION = GC.getDefineINT("ESPIONAGE_INFLUENCE_LOST_FOR_RIGGED_ELECTION") = 5
  12. m_iESPIONAGE_SURVEILLANCE_SIGHT_RANGE = GC.getDefineINT("ESPIONAGE_SURVEILLANCE_SIGHT_RANGE") = 1
  13. m_iESPIONAGE_COUP_OTHER_PLAYERS_INFLUENCE_DROP = GC.getDefineINT("ESPIONAGE_COUP_OTHER_PLAYERS_INFLUENCE_DROP") = 20
复制代码

上面4个直接读名字就知道意思了。这里定义了间谍到达目的地的回合数(1回合)、建立监视的回合数(3回合)、复活的回合数(5回合)以及探取阴谋的回合间隔(5回合)。
而下面的常数定义和间谍的各个功能有关,以后再说。唯一一个可以先点到的是SIGHT_RANGE,这个指定了建立监视之后城市周边可见的距离。如果你想要启蒙时代开全图的效果,可以把这个值改到1000之类……
另外来看一下影响谍报系统的建筑和政策:
建筑:
治安署、警察局:-25% 偷取速度。
GFW:所在城市-100% 偷取速度,其他城市-25%。
国家情报局:所有城市 -15% 偷取速度。
政策:
独裁-警察国家:所有城市 -25% 偷取速度,间谍杀死敌人概率+25%。
字面上都很好理解。后面我们会看到,警察国家的+25%抓获概率计算方式其实很有趣。

[ 本帖最后由 object022 于 2012-11-8 12:30 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:25:20 | 显示全部楼层
一个间谍在其他文明的城市建立监视后,可以发现这个文明AI的计划。
这个部分的代码在void CvPlayerEspionage::UncoverIntrigue(uint uiSpyIndex)函数下,因为篇幅过长,就不把所有代码贴出来了。有兴趣的可以自己去翻。我就贴它的几个步骤:
  1.         // make a list of the active civs
  2.         std::vector<int> aiMajorCivIndex;
  3.         for(int i = 0; i < MAX_MAJOR_CIVS; i++)
  4.         {
  5.                 if(GET_PLAYER((PlayerTypes)i).isAlive())
  6.                 {
  7.                         aiMajorCivIndex.push_back(i);
  8.                 }
  9.         }

  10.         // randomize that list
  11.         for(uint ui = 0; ui < aiMajorCivIndex.size(); ui++)
  12.         {
  13.                 int iTempValue;
  14.                 uint uiTargetSlot = GC.getGame().getJonRandNum(aiMajorCivIndex.size(), "Randomizing aiMajorCivIndex list within UncoverIntrigue");
  15.                 iTempValue = aiMajorCivIndex[ui];
  16.                 aiMajorCivIndex[ui] = aiMajorCivIndex[uiTargetSlot];
  17.                 aiMajorCivIndex[uiTargetSlot] = iTempValue;
  18.         }
复制代码
这里可以看到先建立了一个所有文明的列表,然后对它进行了一次洗牌(也就是随机排序)。
接下来是对某个文明发动突袭的情报,这里的策略是按照之前洗牌的列表进行查找,找到第一个该AI有计划攻击的文明就跳出,也就是说只会返回一条情报,并且目标文明是随机的。
  1.                 if(m_aSpyList[uiSpyIndex].m_eRank >= SPY_RANK_AGENT)
  2.                 {
  3.                         CvPlot* pPlot = pSneakAttackOperation->GetTargetPlot();
  4.                         if(pPlot)
  5.                         {
  6.                                 pTargetCity = pPlot->getPlotCity();
  7.                         }
  8.                 }
复制代码
  1.                 if (m_aSpyList[uiSpyIndex].m_eRank == SPY_RANK_RECRUIT)
  2.                 {
  3.                         eRevealedTargetPlayer = (PlayerTypes)MAX_MAJOR_CIVS; // hack to indicate that we shouldn't know the target due to our low spy rank
  4.                 }
  5.                 else
  6.                 {
  7.                         if(GET_TEAM(m_pPlayer->getTeam()).isHasMet(GET_PLAYER(eTargetPlayer).getTeam()))
  8.                         {
  9.                                 eRevealedTargetPlayer = eTargetPlayer;
  10.                         }
  11.                 }
复制代码
这两段代码指明了一个很多人习惯忽略的事情:1级的间谍是不能获取发动突袭的目标的。
  1.                 if(eHonestApproach == MAJOR_CIV_APPROACH_DECEPTIVE || eHonestApproach == MAJOR_CIV_APPROACH_WAR)
  2.                 {
  3.                         // if the surface approach hides this
  4.                         if(eSurfaceApproach == MAJOR_CIV_APPROACH_FRIENDLY || eSurfaceApproach == MAJOR_CIV_APPROACH_NEUTRAL)
  5.                         {
  6.                                 if(GET_TEAM(GET_PLAYER(eCityOwner).getTeam()).isAtWar(GET_PLAYER(eOtherOtherPlayer).getTeam()))
  7.                                 {
  8.                                         // If the teams are already at war, this isn't notable
  9.                                         continue;
  10.                                 }

  11.                                 if(GET_TEAM(m_pPlayer->getTeam()).isHasMet(GET_PLAYER(eOtherOtherPlayer).getTeam()))
  12.                                 {
  13.                                         AddIntrigueMessage(m_pPlayer->GetID(), eCityOwner, eOtherOtherPlayer, NO_BUILDING, NO_PROJECT, INTRIGUE_TYPE_DECEPTION, uiSpyIndex, pCity, true);
  14.                                 }
  15.                                 else
  16.                                 {
  17.                                         AddIntrigueMessage(m_pPlayer->GetID(), eCityOwner, NO_PLAYER, NO_BUILDING, NO_PROJECT, INTRIGUE_TYPE_DECEPTION, uiSpyIndex, pCity, true);
  18.                                 }
  19.                                 break; // we reported intrigue, now bail out
  20.                         }
  21.                 }
  22.        
复制代码
另外一种情报,似乎是新版才加入的:指明某个AI正在欺骗另一个AI,表面上友好或中立,却在暗地里准备发动攻击。
接下来还有建立军队和建造奇观的提示。这些比较简单,不提。

[ 本帖最后由 object022 于 2012-11-8 12:30 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:25:33 | 显示全部楼层
接下来说一个大家都很关心的内容:窃取科技。
这里有两个核心的函数:
int CvPlayerEspionage::CalcPerTurn(int iSpyState, CvCity* pCity, int iSpyIndex)
int CvPlayerEspionage::CalcRequired(int iSpyState, CvCity* pCity, int iSpyIndex)
当iSpyState == SPY_STATE_GATHERING_INTEL 时,前者指明每回合间谍偷取的速度,后者指定偷取一个科技需要的总值。
需要的回合数就等于后者/前者,注意是上取整。当然这里指的是在间谍面板中显示的值,实际的工作机制和伟人槽之类的类似,目标城市建立新的反间谍建筑或者间谍离开都会重新计算PerTurn的值,但是之前的积累不变。
现在我们来看这两个东西的计算方式:
  1.         case SPY_STATE_GATHERING_INTEL:
  2.         {
  3.                 if(pCity)
  4.                 {
  5.                         PlayerTypes eCityOwner = pCity->getOwner();
  6.                         int iBaseYieldRate = pCity->getYieldRateTimes100(YIELD_SCIENCE);
  7.                         iBaseYieldRate *= GC.getESPIONAGE_GATHERING_INTEL_RATE_BASE_PERCENT();
  8.                         iBaseYieldRate *= GC.getGame().getGameSpeedInfo().getSpyRatePercent();
  9.                         iBaseYieldRate /= 10000;
  10.                         int iFinalModifier = (iBaseYieldRate * (100 + pCity->GetEspionageModifier() + GET_PLAYER(eCityOwner).GetEspionageModifier() + GET_PLAYER(eCityOwner).GetPlayerPolicies()->GetNumericModifier(POLICYMOD_STEAL_TECH_SLOWER_MODIFIER))) / 100;

  11.                         int iResult = max(iFinalModifier, 1);
  12.                         if(iSpyIndex >= 0)
  13.                         {
  14.                                 iResult *= 100 + (GC.getESPIONAGE_GATHERING_INTEL_RATE_BY_SPY_RANK_PERCENT() * m_aSpyList[iSpyIndex].m_eRank);
  15.                                 iResult /= 100;
  16.                         }

  17.                         return iResult;
  18.                 }
复制代码
第6-9行指定了基础速度=(X*100)*100*Y/10000,X是目标城科研产出
100就是ESPIONAGE_GATHERING_INTEL_RATE_BASE_PERCENT,在3楼可以找到
这里Y就是getSpyRatePercent(),我查了一下xml,在快速下是50,其他速度下是100
因此可以得到基础速度(不考虑目标城市的防御建筑和本间谍等级)=100X,在快速下50X
接下来的iFinalModifier指定了目标城市的间谍防御,按百分比减成。可能的减成包括:治安署和警察局各-25%,本文明的国家情报局-15%,GFW在本城-100%在本文明-25%,警察国家(政策)-25%。
因此可以把现在的窃取速度定为X*Y,X是本城产出,Y是目标城市和文明的间谍防御修正指数(如果毫无防御为100,治安署和警察局各-25,国家情报局-25,GFW在本城-100在本文明-25,警察国家-25)
之后一行是为了防止速度<0的情况(比如建造了GFW),在这时候强制将速度设定为1(这时候也不用玩了吧)。
最后是和间谍等级有关的修正。M_eRank是一个枚举类型,它的定义是:
Enum(SPY_RANK_RECRUIT,SPY_RANK_AGENT,SPY_RANK_SPECIAL_AGENT,MAX_SPY_RANK)
看起来第一个的编号是1,实际上由于C++所有数组都是从0开始编号的,第一级对应的编号其实是0,第二级是1,第三级是2,而最后一项编号为3,正好用于指定这个枚举类型包含了几个种类。后面还会用到这些。
在3楼可以看到XML中定义ESPIONAGE_GATHERING_INTEL_RATE_BY_SPY_RANK_PERCENT()=25,也就是说二级的间谍偷取速度+25%,三级+50%。
因此可以得到最终公式:速度=XYZ,X为目标城市的科研产出,Y是目标城市和文明的间谍防御修正(毫无防御为100,治安署和警察局各-25,国家情报局-25,GFW在本城-100在本文明-25,警察国家-25),Z为本间谍等级的速度修正(1级为1,2级为1.25,3级为1.5)

接下来看所需的进度。
  1.                         uint uiMaxTechCostAdjusted = m_aiMaxTechCost[ePlayer];                        
  2.                         uiMaxTechCostAdjusted *= GC.getESPIONAGE_GATHERING_INTEL_COST_PERCENT();
  3.                         uiMaxTechCostAdjusted /= 100;
  4.                         int iMaxTechCostAdjusted = uiMaxTechCostAdjusted;
复制代码
第一行是基准。后面的m_aiMaxTechCost在void CvPlayerEspionage::BuildStealableTechList(PlayerTypes ePlayer)中进行赋值,如下:
  1.                 int iTechCost = m_pPlayer->GetPlayerTechs()->GetResearchCost(eTech) * 100;
  2.                 if(iTechCost > iMaxTechCost)
  3.                 {
  4.                         iMaxTechCost = iTechCost;
  5.                 }
复制代码
结合上下文,这个值指的是你能偷取的烧瓶最高的科技*100。
而需要的总和比较好理解了。第二行的ESPIONAGE_GATHERING_INTEL_COST_PERCENT定义为125。
因此公式就是125X,X为你能偷取的科技中的最高烧瓶数。
另外,如果你打算做一个MOD,想调节这个进度的话,只要定义ESPIONAGE_GATHERING_INTEL_COST_PERCENT就行了。比如让所有人几乎都科研平行就可以改成1,让这个东西废掉就可以改成10000之类的数值……当然更多的还是为了游戏平衡的微调。

[ 本帖最后由 object022 于 2012-11-8 12:33 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:25:49 | 显示全部楼层
偷到了科技,很高兴……别急。还有事情。
文明百科告诉你,偷科技不是没有风险的。
在目标城市里放置了反间谍的时候,有三种可能情况:被发现但没有被鉴定出来自文明,被发现并被认出所在文明,被发现并被杀死
而没有放置反间谍的时候,也有三种可能情况,前两种和上面一样,第三种改为不被发现
这楼着重讨论这些情况的概率。
这里必须重新提及一个政策:独裁-警察国家。法庭+3本地快乐,敌方间谍窃取速度-25%,反间谍抓获敌方间谍概率+25%。实际上,似乎不是这么简单……。
我们先来看简单点的情况,目标城市里没有反间谍的时候。
  1.                                 iSpyResult = GC.getGame().getJonRandNum(300, "Random roll for the result of a spying mission without a counterspy in the city");
  2.                                 iSpyResult *= (100 + GET_PLAYER(pCity->getOwner()).GetPlayerPolicies()->GetNumericModifier(POLICYMOD_CATCH_SPIES_MODIFIER));
  3.                                 iSpyResult /= 100;
  4.                                 if(iSpyResult < 100)
  5.                                 {
  6.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_UNDETECTED);
  7.                                 }
  8.                                 else if(iSpyResult < 200)
  9.                                 {
  10.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_DETECTED);
  11.                                 }
  12.                                 else
  13.                                 {
  14.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_IDENTIFIED);
  15.                                 }
复制代码
第一行得到了一个0到299(包括)之间的随机数。因为这个是完全随机的,在接下来计算概率的时候,我会采用枚举所有可能的300个数并统计的方法,因此(a,b,c)表示统计结束后,有a个数是第一个结果,后面两个结果分别有b和c个数,并且a+b+c=300。
第二行和第三行将随机数乘上了一个政策修正值,实际上只有警察国家这个政策会改动这个修正值。
之后的IF语句指明当最后的随机数<100时,结果是不被发现,100-199时是被发现不被识别,大于等于200时是被识别。
先看没有警察国家的情况,很容易得到不被发现/被发现不被识别/被识别的随机数分别有(100,100,100)个,也就是33%/33%/33%,都是三分之一。
目标文明获得警察国家政策之后,通过程序计算可以得出,三种情况的随机数有(80,80,140)个,也就是26.7%/26.7%/46.7%。
  1.                                 iSpyResult = GC.getGame().getJonRandNum(300, "Random roll for the result of a spy mission with a counterspy in the city");
  2.                                 int iCounterspyIndex = GET_PLAYER(eCityOwner).GetEspionage()->GetSpyIndexInCity(pCity);
  3.                                 iSpyResult += GET_PLAYER(eCityOwner).GetEspionage()->m_aSpyList[iCounterspyIndex].m_eRank * 30;
  4.                                 iSpyResult *= (100 + GET_PLAYER(pCity->getOwner()).GetPlayerPolicies()->GetNumericModifier(POLICYMOD_CATCH_SPIES_MODIFIER));
  5.                                 iSpyResult /= 100;
  6.                                 if(iSpyResult < 100)
  7.                                 {
  8.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_DETECTED);
  9.                                 }
  10.                                 else if(iSpyResult < 200)
  11.                                 {
  12.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_IDENTIFIED);
  13.                                 }
  14.                                 else
  15.                                 {
  16.                                         pCityEspionage->SetSpyResult(ePlayer, SPY_RESULT_KILLED);
复制代码
而目标城市拥有反间谍的情况会麻烦一点,因为要考虑反间谍的等级。
和上面类似,第一行得到了一个0到299的随机数。第二到三行则把这个随机数加上了间谍等级*30(注意我们之前提到过,1级间谍的等级实际上是写为0的,2级为1,3级为2)
也就是说,1级反间谍的随机数不变,2级+30,3级+60
之后照例是警察国家的加成。
1级反间谍的概率和上面那个是一样的。没有警察国家时的随机数个数是(100,100,100),有警察国家时是(80,80,140)。注意这时候的情况已经是被发现不被识别/被识别/被杀死了。
2级反间谍需要在基础随机数上+30,计算后的概率是没有警察国家时是(70,100,130)也就是23.3%/33.3%/43.3%,有警察国家时是(50/80/170)也就是16.7%/26.7%/56.7%。
3级反间谍需要在基础随机数上+60,计算后的概率是没有警察国家时是(40,100,160)也就是13.3%/33.3%/53.3%,有警察国家时是(20/80/200)也就是6.7%/26.7%/66.7%。
这样也可以看出,警察国家的加成并不是单纯的发现概率+25%,它只是在表示结果的随机数上乘上了一个1.25,用程序很好描述,用自然语言反倒麻烦。

[ 本帖最后由 object022 于 2012-11-8 12:34 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:26:06 | 显示全部楼层
接下来看一下间谍在城邦中的作用。城邦中的间谍可以做两种事情:操纵选举和发动政变。这一楼先来看前者。
  1.         case SPY_STATE_RIG_ELECTION:
  2.         {
  3.                 int iResult = 1;
  4.                 if(iSpyIndex >= 0)
  5.                 {
  6.                         iResult = (m_aSpyList[iSpyIndex].m_eRank + 1) * (m_aSpyList[iSpyIndex].m_eRank + 1);
  7.                 }
  8.                 return iResult;
  9.         }
复制代码
这一段代码在CalcPerTurn中,它指明了间谍操纵城邦选举的“速度”(实际上并不是真的速度,因为城邦选举的速度是固定的,15回合一次。这个值由ESPIONAGE_TURNS_BETWEEN_CITY_STATE_ELECTIONS决定)。
我们将这个速度解释为投票。
可以看出投票只和自己间谍的等级有关系,1级间谍是1,2级间谍是4,3级间谍是⑨。
城邦选举的部分在CvMinorCivAI的void CvMinorCivAI::DoElection()下。
第一步是枚举所有间谍,注意下面代码:
  1.                         // if the spy assigned here is not rigging the election yet, continue
  2.                         if(pPlayerEspionage->m_aSpyList[iSpyID].m_eSpyState != SPY_STATE_RIG_ELECTION)
  3.                         {
  4.                                 continue;
  5.                         }
复制代码
也就是说,如果选举的时候间谍不在场,是不可能获得胜利的。
而选取胜利者的核心代码如下:
  1.                 RandomNumberDelegate fcn;
  2.                 fcn = MakeDelegate(&GC.getGame(), &CvGame::getJonRandNum);
  3.                 PlayerTypes eElectionWinner = wvVotes.ChooseByWeight(&fcn, "Choosing CS election winner by weight");
复制代码
看到那个ChooseByWeight就能够猜到结论了:按照前15回合积累的投票权重随机选择获胜者。也就是说如果一个3级的间谍在城邦里蹲上了15回合,就会获得105的投票,这是最大可能了。之后投票清空。
选举结束之后的事情也很简单。
  1.                                 ChangeFriendshipWithMajor(ePlayer, GC.getESPIONAGE_INFLUENCE_GAINED_FOR_RIGGED_ELECTION(), false);
复制代码
  1.                                 if (GetEffectiveFriendshipWithMajorTimes100(ePlayer) > 0)
  2.                                 {
  3.                                         int iDiminishAmount = min(GC.getESPIONAGE_INFLUENCE_LOST_FOR_RIGGED_ELECTION() * 100, GetEffectiveFriendshipWithMajorTimes100(ePlayer));
  4.                                         ChangeFriendshipWithMajorTimes100(ePlayer, -iDiminishAmount, false);
  5.                                 }
复制代码
获胜者获得20(由ESPIONAGE_INFLUENCE_GAINED_FOR_RIGGED_ELECTION决定)影响力,其他人失去5(由ESPIONAGE_INFLUENCE_LOST_FOR_RIGGED_ELECTION决定)影响力,并且不能为负。
后面那段代码看起来很奇怪,这是因为游戏实际存储影响力精确到小数点后2位,而全部*100是为了保证仅仅扣到0。下面还有类似的地方会用到这个。

[ 本帖最后由 object022 于 2012-11-8 12:36 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:26:20 | 显示全部楼层
城邦间谍的另外一个功能,是策动政变。
首先来看策动政变的条件,关键代码:
  1.         CvMinorCivAI* pMinorCivAI = GET_PLAYER(eCityOwner).GetMinorCivAI();
  2.         PlayerTypes eMinorCivAlly = pMinorCivAI->GetAlly();

  3.         if(eMinorCivAlly != NO_PLAYER && eMinorCivAlly != m_pPlayer->GetID())
  4.         {
  5.                 return true;
  6.         }
复制代码
也就是说,当且仅当当前城邦有盟友且盟友不是你的时候,你才可以发动政变。
主要的实现在int CvPlayerEspionage::GetCoupChanceOfSuccess(uint uiSpyIndex)和bool CvPlayerEspionage::AttemptCoup(uint uiSpyIndex)里,前者返回政变成功率,后者执行一次政变并返回成功与否。
先来看前者,核心代码非常长,我把它分成了几个段:
  1.         PlayerTypes eAllyPlayer = pMinorCivAI->GetAlly();
  2.         int iAllySpyRank = 0;
  3.         bool bNoAllySpy = true;
  4.         if(pCityEspionage->m_aiSpyAssignment[eAllyPlayer] != -1)
  5.         {
  6.                 int iAllySpyIndex = pCityEspionage->m_aiSpyAssignment[eAllyPlayer];
  7.                 iAllySpyRank = GET_PLAYER(eAllyPlayer).GetEspionage()->m_aSpyList[iAllySpyIndex].m_eRank;
  8.                 bNoAllySpy = true;
  9.         }

  10.         int iAllyInfluence = pMinorCivAI->GetEffectiveFriendshipWithMajorTimes100(eAllyPlayer);
  11.         int iMyInfluence = pMinorCivAI->GetEffectiveFriendshipWithMajorTimes100(m_pPlayer->GetID());
  12.         int iDeltaInfluence = iAllyInfluence - iMyInfluence;

  13.         //float fNobodyBonus = 0.5;
  14.         //float fMultiplyConstant = 3.0f;
  15.         //float fSpyLevelDeltaZero = 0.0f;
  16.         //float fSpyLevelDeltaOne = 1.5f;
  17.         //float fSpyLevelDeltaTwo = 2.25;
  18.         float fNobodyBonus = GC.getESPIONAGE_COUP_NOBODY_BONUS();
  19.         float fMultiplyConstant = GC.getESPIONAGE_COUP_MULTIPLY_CONSTANT();
  20.         float fSpyLevelDeltaZero = GC.getESPIONAGE_COUP_SPY_LEVEL_DELTA_ZERO();
  21.         float fSpyLevelDeltaOne = GC.getESPIONAGE_COUP_SPY_LEVEL_DELTA_ONE();
  22.         float fSpyLevelDeltaTwo = GC.getESPIONAGE_COUP_SPY_LEVEL_DELTA_TWO();
复制代码
上面这一段首先判断当前城邦的盟友是否在那个城邦里放置了间谍。
之后定义了iDeltaInfluence,这个值表示100乘上你的影响力和当前盟友影响力之差。
最后声明了几个常量,后面计算中有用。根据贴吧的 @笨笨异形 的数据,这些值和上面注释掉的相等。
另外我要吐槽一下这里似乎出现了一个设计失误。根据变量名意思,第八行的bNoAllySpy明显应该赋值为False……
之后程序定义了两个值:fMySpyValue和fAllySpyValue(这段代码没什么意义,不放了,可以自己找)。当我方/盟友方放置的间谍为1/2/3级时,这个值分别是0/1.5/2.25。
  1.         float fSpyMultipier = fAllySpyValue - fMySpyValue + fMultiplyConstant;
  2.         if (bNoAllySpy)
  3.         {
  4.                 fSpyMultipier *= fNobodyBonus;
  5.         }

  6.         int iResultPercentage = 100 - (int)((iDeltaInfluence * fSpyMultipier) / 100);

  7.         if(iResultPercentage > 85)
  8.         {
  9.                 iResultPercentage = 85;
  10.         }
  11.         else if(iResultPercentage < 0)
  12.         {
  13.                 iResultPercentage = 0;
  14.         }
复制代码
这里是计算概率的核心部分。
政变乘数=(对方间谍的Value-我方间谍的Value+固定值)*无人加成,固定值为3,当对方没有放间谍时Value=0并且获得0.5的无人加成
如果懒得翻上面的值表,直接给出这个值(称为X)的结论:
当当前盟友没有安排间谍时,我方间谍为1/2/3级时,X=1.5/0.75/0.375
当前盟友在城邦里安排了一个1级间谍时,我方间谍为1/2/3级时,X=3/1.5/0/75
当前盟友在城邦里安排了一个2级间谍时,我方间谍为1/2/3级时,X=4.5/3/2.25
当前盟友在城邦里安排了一个3级间谍时,我方间谍为1/2/3级时,X=5.25/3.75/3
之后的成功概率=100-(T*X/100),其中T是之前的iDeltaInfluence,也就是你和当前盟友的关系值之差*100,正好后面除掉了,所以可以理解为每1点关系值之差失败几率增加X%
最后的8行用于调整概率,>85%调为85,<0%调为0。

[ 本帖最后由 object022 于 2012-11-8 15:35 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:26:36 | 显示全部楼层
现在我们来看后者:政变之后发生的事情。
  1.         int iRandRoll = GC.getGame().getJonRandNum(100, "Roll for the result of an attempted coup");
  2.         if(iRandRoll <= GetCoupChanceOfSuccess(uiSpyIndex))
复制代码
第一行产生一个0-99的随机数,表示政变结果。第二行是一个条件句,如果成功了,执行if之后的内容,否则执行else之后的内容。
  1.                 int iInfluenceTemp = aiNewInfluenceValueTimes100[ePreviousAlly];
  2.                 aiNewInfluenceValueTimes100[ePreviousAlly] = aiNewInfluenceValueTimes100[m_pPlayer->GetID()];
  3.                 aiNewInfluenceValueTimes100[m_pPlayer->GetID()] = iInfluenceTemp;
  4.                 // reduce the influence of all the other players
  5.                 for(uint ui = 0; ui < MAX_MAJOR_CIVS; ui++)
  6.                 {
  7.                         if(ui == m_pPlayer->GetID())
  8.                         {
  9.                                 continue;
  10.                         }
  11.                         // only drop the influence if they have positive influence
  12.                         if(aiNewInfluenceValueTimes100[ui] > 0)
  13.                         {
  14.                                 int iNewInfluence = aiNewInfluenceValueTimes100[ui] - (GC.getESPIONAGE_COUP_OTHER_PLAYERS_INFLUENCE_DROP() * 100);
  15.                                 iNewInfluence = max(iNewInfluence, 0);
  16.                                 aiNewInfluenceValueTimes100[ui] = iNewInfluence;
  17.                         }
  18.                 }
  19.                 bAttemptSuccess = true;
复制代码
这段是政变成功之后的步骤。可以看到做了如下两件事:
第一,把你的影响力和当前盟友的影响力互换。
第二,除你以外所有人的影响力扣减20(由ESPIONAGE_COUP_OTHER_PLAYERS_INFLUENCE_DROP决定)。
  1.                 // reduce influence of player
  2.                 // right now move the influence into a negative space
  3.                 aiNewInfluenceValueTimes100[m_pPlayer->GetID()] = (-10 * 100);
  4.                 bAttemptSuccess = false;

  5.                 // kill the spy
  6.                 ExtractSpyFromCity(uiSpyIndex); // move the dead body out so that someone else can move in
  7.                 m_aSpyList[uiSpyIndex].m_eSpyState = SPY_STATE_DEAD; // have to official kill him after the extraction
复制代码
政变失败之后,也只做了两件事:
第一,把你的影响力设定为-10。
第二,间谍死亡。

[ 本帖最后由 object022 于 2012-11-8 12:38 编辑 ]
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:26:51 | 显示全部楼层
大体的分析就这样子了。
总体来说这个设计还是不错的,虽然我更怀念4代那个无所不能的间谍……间谍经济啊……
给我的总体感觉是在操纵城邦时,高级间谍的优势似乎比较明显。
另外还有一点就是警察国家这个政策其实很强力,如果放在Order下不知道会怎么样。

剩下的就是欢迎讨论了~

[ 本帖最后由 object022 于 2012-11-8 12:43 编辑 ]
回复 支持 反对

使用道具 举报

发表于 2012-11-8 12:29:26 | 显示全部楼层
第一個0.0
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 12:37:20 | 显示全部楼层

回复 11# 的帖子

我还没复制粘贴完你就来了
回复 支持 反对

使用道具 举报

发表于 2012-11-8 12:39:56 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2012-11-8 12:41:53 | 显示全部楼层
又一个技术贴啊
回复 支持 反对

使用道具 举报

发表于 2012-11-8 14:07:44 | 显示全部楼层
这个给力啊,前排支持~~~
回复 支持 反对

使用道具 举报

发表于 2012-11-8 17:35:33 | 显示全部楼层

回复 8# 的帖子

政变这个地方有两个问题,首先那个bNobodyBonus的bug会导致永远有这个加成
然后那些常量的值和上面注掉的好像不一样,至少在初始化的时候bNobodyBonus和fMultiplyConstant都是初始化为1.0的
然后在XML里面又没找到这些

其实我是为了找神级AI政变有没有加成去看这些code的,结论是似乎没有加成。AI会根据政变成功率判断是否要进行政变,尝试的概率和政变成功概率相同。但如果连续两次计算政变是否成功的随机数之间是独立的的话应该会看到大把AI政变失败的情况,但事实上感觉AI政变玩家同盟的城邦好像成功率还是异常的高。也许还有什么地方的code没看全吧
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 18:44:45 | 显示全部楼层

回复 16# 的帖子

对……我认为这个是设计失误……
但是我认为会通过Patch改正,所以就没有体现这个bug……


AI的部分我没有去仔细翻。很可能AI会先砸钱,让政变成功率比较高,然后再发动政变?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-8 18:47:34 | 显示全部楼层

回复 16# 的帖子

还有常量的值是没有错的,新版的XML里面的确有这些变量,贴吧的一位吧友提供了数据。那些只是初始化值,只是初始化而已。
回复 支持 反对

使用道具 举报

发表于 2012-11-8 22:40:22 | 显示全部楼层
技术顶贴
回复 支持 反对

使用道具 举报

 楼主| 发表于 2012-11-9 13:05:44 | 显示全部楼层
这贴居然没人看
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|塞爱维(CIV)文明联盟    

GMT+8, 2024-4-29 15:42

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表