Notes on Diplomacy (Preliminary Version)
Much of the following is cut and pasted from the 'AI Customization via SLIC' document and other documentation that can be found on the Apolyton Modification site or in some cases in the aidata folder.
by Peter Triggs
But, to start with, consider the following (Modification:Data File Definitions [diplomacy.txt]):
This file defines the Diplomatic States. The active Diplomatic State is selected by logic from within the game. Future plans are to expose the state selection logic to SLIC so state changes can be scripted in SLIC scripts. This seems to contradict much of what is said in the AI Customization document, but is too cryptic to make a lot of sense (at least to me). It might mean that although it's possible for us to define new diplomatic states (like Dale did in Diplomod) it's damn near impossible for us to get the game to make them 'active', i.e., to use them. On the other hand it may be possible to work around this problem by generating events (although there can be a fairly heavy CPU cost in doing this).
Contents:
0) Proposals and Diplomatic States: What a diplomatic state does. The 'inherit' flag. Proposals:How prioritized. Re-prioritizing and adding new diplomatic states. The link between diplomatic and strategic states. The order of diplomatic events.
1) Sending a Proposal: To the Human. To An AI Civ. More on Motivating the AI to send a proposal.
2) Receiving a Proposal
3) Responding to Counter-proposals and Rejections:
4) Responding to Threats:
Appendix 1: Some SLIC functions.
Appendix 2: The New Proposal List.
0) Proposals and Diplomatic States:
A diplomatic state defines "how each AI player reacts diplomatically to each other player in the game." Originally, there were only three diplomatic states: DIPLOMACY_DEFAULT, DIPLOMACY_PROVOKE_WAR, and DIPLOMACY_MAKE_FRIEND. Dale added three more for Diplomod: DIPLOMACY_AI_AI_NORMAL, DIPLOMACY_AI_AI_SUCKUP, and DIPLOMACY_AI_AI_WARMONGER.
DIPLOMACY_DEFAULT is, self-evidently, the default state. The other two original states, PROVOKE_WAR, and MAKE_FRIEND, are loaded in some hard coded circumstances that we don't know about. (It probably depends at least in part on the AI civs leader's personality attributes: trustworthiness, conquest, and alignment.)
These two states are much smaller than DIPLOMACY_DEFAULT, because they 'inherit' it:
a diplomatic state might define other states that it inherits from in order reduce the redundancy in diplomatic state records. If the Inherit field is set, then the referenced diplomacy record is loaded first and the current record is merged in overwriting any variables that it redefines.
For example, in DIPLOMACY_PROVOKE_WAR the ProposalElement PROPOSAL_REQUEST_WITHDRAW_TROOPS is redefined as ProposalElement { Proposal PROPOSAL_REQUEST_WITHDRAW_TROOPS SendPriority 250 AcceptPriority 0 RejectPriority 150 SenderRegardResult -50 ReceiverRegardResult 200 ViolationRegardCost -250 ViolationTrustCost -20 }
This overwrites the corresponding definition in DIPLOMACY_DEFAULT (see below), while proposal elements like OFFER_GIVE_ADVANCE, and REQUEST_GIVE_ADVANCE, which are not redefined in PROVOKE_WAR retain their DEFAULT values.
Every diplomatic state contains a whole bunch of parameters concerning 'regard events' and regard/trust penalties/bonuses, but what follows just deals with sending and receiving proposals.
Although the analogy isn't perfect, proposals are to Diplomatic states as goals are to Strategic states: just as goals define what an AI civ does to an opponant (how it "tasks it's units"), so proposals define what it says to him. Moreover, just as Goals are prioritized in each of the various Strategies, so each New Proposal is given a sequence of base priorities in each of the various Diplomatic 'strategies' or states in diplomacy.txt: ProposalElement Proposal - Reference of a proposal from DiplomacyProposal.txt SendPriority - Base priority to send this proposal AcceptPriority - Base priority to accept this proposal RejectPriority - Base priority to reject this proposal SenderRegardResult - Regard penalty/bonus for the sender of this proposal ReceiverRegardResult - Regard penalty/bonus for the receiver of this proposal ViolationRegardCost - Regard penalty for violating this proposal ViolationTrustCost - Trust penalty for violating this proposal
For example (DIPLOMACY_DEFAULT), ProposalElement { Proposal PROPOSAL_REQUEST_WITHDRAW_TROOPS SendPriority 100 AcceptPriority 110 RejectPriority 100 SenderRegardResult -50 ReceiverRegardResult 200 ViolationRegardCost -250 ViolationTrustCost -20 }
Compare this with the same proposal element when it occurs in PROVOKE_WAR (above). When an AI civ is in a PROVOKE_WAR state it has a much higher priority to request that troops be withdrawn, nil priority to accept such a request, and a slightly higher priority to reject such a request.
Here's the explanation of how diplomatic states are used:
Unlike strategic states, each player maintains multiple diplomatic states that define their relationship with each foreigner. Also unlike strategic states, diplomatic states are taken all or nothing. After all diplomatic states have been considered in a turn, only the one with the highest priority is used.
The base priorities of the various diplomatic states are again hard-coded but we can re-prioritize them by calling the ConsiderDiplomaticState function within the NextDiplomaticState event: HandleEvent (NextDiplomaticState) ‘Example_NextDiplomaticState’ pre { //stuff // ConsiderDiplomaticState(player[0],player[1], priority, DiplomacyDB(DIPLOMACY_<state name>),-1,-1,-1); // player[0] is the player being whose turn it is // player[1] is the foreigner }
For example, HandleEvent (NextDiplomaticState) ‘Example_Load_Provoke_War’ pre { if( !IsHumanPlayer(player[0]) && GetEffectiveRegard(player[0],player[1])< 200){ ConsiderDiplomaticState(player[0],player[1], 2000, DiplomacyDB(DIPLOMACY_PROVOKE_WAR)); } }
will put an AI player[0] into a PROVOKE_WAR diplomatic state with any player[1] whenever his effective regard for player[1] is less than 200 (i.e., he doesn't really like player[1]).
We can also use this event/function combination to load any new diplomatic state that we may care to define. In Diplomod, Dale uses it to load his DIPLOMACY_AI_AI_NORMAL, DIPLOMACY_AI_AI_SUCKUP, and DIPLOMACY_AI_AI_WARMONGER states on a random basis, but my inclination is to try to find some more game dependent situations that warrent new diplomatic states. For example, we could define a new state DIPLOMACY_DEVELOPING_NATION where the priorities for requesting advances are very high and then HandleEvent (NextDiplomaticState) ‘Example_Load_Developing_Nation’ pre { if(!IsHumanPlayer(player[0]) && KnowledgeRank(player[0])<KnowledgeRank(player[1])){ ConsiderDiplomaticState(player[0],player[1], 2000, DiplomacyDB(DIPLOMACY_DEVELOPING_NATION)); Event:ReactionMotivation(player[0],player[1]); Event:DesireMotivation(player[0],player[1]); } }
which would load this state whenever AI player[0] is behind any player[1] in science. The two following motivation events should then force the AI player[0] to use these priorities to send proposals to any other player it has contact with. (Restrict to AI's?) (To be really safe, maybe we should load a new strategy that prioritizes science.)
(comment on initializing)
Finally, diplomatic states are linked to strategic states via the motivations priorities in the various strategies, e.g., (STRATEGY_SCIENTIST_DEFAULT): // // DIPLOMACY // // priority for fear motivations FearInvasion 1000 FearCityDefense 1100 FearPiracy 900 FearScienceRank 1000 FearMilitaryRank 800 FearTradeRank 800 FearPollution 900 // priority for desire motivations DesireAttack 800 DesireGold 900 DesireScience 900 DesireMakeFriend 1000 DesireEnlistFriend 1100
It's these motivations that ultimately trigger the sending of new proposals (from Interactions.txt): How does diplomacy work? AI Diplomacy sequence: Step 1) Create ranked list of motivations (fears and desires). [from Strategies] Step 2) Select top motivation. [Event:FearMotivation, DesireMotivation, ReactionMotivation] Step 3) Select highest priority New Proposal triggered by top motivation. [GetNewProposalPriority function] Step 4) Send highest priority New Proposal triggered by motivation, if any. [ConsiderNewProposal function]
Diplomatic events: There are a number of Diplomatic events. They occur roughly in the following order, but the occurence of some depends on the occurence of previous ones (see below):
(Note on methodology: As well as relying on the documentation, I added handlers like HandleEvent(DesireMotivation)'TestDesireMotivation' pre { message(1,'TestDesireMotivationM'); } messagebox 'TestDesireMotivationM' { Text (ID_M_TestDesireMotivation) ; //M_TestDesireMotivation "Desire {player[0].civ_name_plural} to {player[1].civ_name_plural} value[0]={value[0]}" } HandleEvent(NewProposalReady)'TestNewProposalReady' pre { message(1,'TestNewProposalReadyM'); } messagebox 'TestNewProposalReadyM' { Text (ID_M_TestNewProposalReady) ; //M_TestNewProposalReady "M_TestNewProposalReady" }
and then tried to deduce the order of events from the order of the resulting messages.) BeginTurnAgreements(int_t<player>) Begin player diplomatic agreements phase InitDiplomaticState(int_t<player>, int_t<player>) Initialize Diplomatic state NextDiplomaticState(int_t<player>, int_t<player>) Determine next diplomatic state ComputeMotivations(int_t<player>) Determine fears and desires StartNegotiations(int_t<player>) After computing motivations, select new proposals FearMotivation(int_t<player>, int_t<player>, int_t) Find new proposal for fear motivation
The final int can take values like 3,8,9,10,11. DesireMotivation(int_t<player>, int_t<player>, int_t) Find new proposal for desire motivation
Here the final int can take values like 13,18,20,21.
Both of these last events can occur more than once per turn. In both cases the number of times the event occurs can be raised by using the ConsiderMotivation(int<player>,int,int) function, e.g.: HandleEvent(ComputeMotivations)'TestComputeMotivations' pre { stuff ConsiderMotivation(player[0],5000,8) }
the '8' being the value that comes up in the later FearMotivation event. These numbers must refer to the motivation priorities in the player's current strategy, but I haven't figured out how. ReactionMotivation(int_t<player>, int_t<player>) Find new proposals motivated as reactions to game events NewProposalReady(int_t<player>, int_t<player>) Notify world that a new proposal is ready NewProposal(int_t<player>, int_t<player>) A new proposal has been made. NewNegotiationEvent(int_t<player>, int_t<player>) A new negotiation history event has been added ResponseReady(int_t<player>, int_t<player>) Notify world that a diplomatic response is ready Counter(int_t<player>, int_t<player>) A proposal has been countered. Reject(int_t<player>, int_t<player>) A proposal has been rejected Accept(int_t<player>, int_t<player>) A proposal has been accepted. Threaten(int_t<player>, int_t<player>) A player has been threatened ContinueDiplomacy(int_t<player>, int_t<player>) Decide if sender should make a new proposal. ---------------- ToggleInitiative(int_t<player>, int_t<player>) Initiative passes to party currently without initiative.
1) Sending a Proposal:
To The Human: Although there's nothing in what follows to indicate that the recipient of the proposal must be the human player, in practice that's the way it is. There was even a supposed example (see below), but I couldn't get it to work. Also see below for workarounds.
At the beginning of every player turn the ReactionMotivation and possibly one or both of the FearMotivation or DesireMotivation events will be triggered. To add a rule which can trigger the sending of a new proposal you should write a SLIC function to handle one of these events. For simplicity it is best to write your handles to be triggered by the ReactionMotivation event. For all of these events, player[0] is the player whose turn it is, and player[1] is the foreigner that would receive the new proposal.
The recommended procedure is:
You should access the current diplomacy state for the player using the GetNewProposalPriority function and also call any other functions needed to determine whether or not the player should send a particular new proposal. If it is determined that the proposal should be sent, then call the function ConsiderNewProposal. If multiple calls to ConsiderNewProposal are made, then the proposal with the highest priority will be sent.
So an easy way to force the AI to send a particular new proposal (to the Human) is just to use the ConsiderNewProposal function. It's syntax is: VOID ConsiderNewProposal(int<player0>, int<player1>, int1, int2, param, int3, int4, int5) e.g., ConsiderNewProposal(player[0].owner, // sender player[1].owner, // receiver 1000, // priority 4, // ProposalDB(index), 4 is REQUEST_WITHDRAW_TROOPS ??int // an int parameter that seems to depend on the new proposal type ID_string, // e.g., ID_EXPLAIN_WANT_GOLD_ADVANCE ID_string, // e.g., ID_ADVICE_WANT_GOLD_ADVANCE ID_string, // e.g., ID_NEWS_WANT_GOLD_ADVANCE) );
These string ID's were found in junkstring.txt. I've tried to test the function with string ID's but haven't seen them appear anywhere. My guess is that these function parameters were never fully implemented. Here's an example of it's use: HandleEvent(ReactionMotivation) 'NewDipProposal' pre { ... stuff... ConsiderNewProposal(player[0].owner, player[1].owner, 2000, 21, 0, ID_TEST,ID_TEST,ID_TEST); }
The priority 2000 will invariably force (AI-)player[0] to send new proposal #21 to player[1] (who will inevitably be the human player).
The problem is with that strange parameter: Some proposals involve cities, advances, or gold. This parameter is what determines the specific city, advance, or amount of gold. But I can't figure out how it works, and without it can't generate those proposals.
According to Azmel2 (Richard Myers):
You can also redefine specific diplomatic responses and new proposal triggers. The commented out handler in diplomacy.slc titled 'WantGoldAdvance_NewProposalEvent' is an example of how to make the AI consider sending a new proposal with high (1000) priority requesting a desired gold advance. // DESIRES // ask for the next advance on our list of gold enhancing advances // if the target player has it, and we are on good terms with them. int_t dipAdvance; HandleEvent(DesireMotivation) 'WantGoldAdvance_NewProposalEvent' pre { // get the next unknown gold advance on our advance list that // has already been discovered by the target player. // dipAdvance = GetDesiredAdvance(player[0].owner, // player[1].owner, // GOLD_LIST); // dipAdvance = 5; //ConsiderNewProposal(player[0].owner, // sender // player[1].owner, // receiver // 1000, // priority // PROPOSAL_REQUEST_GIVE_ADVANCE, // param1 // dipAdvance, // param2 // ID_EXPLAIN_WANT_GOLD_ADVANCE, // ID_ADVICE_WANT_GOLD_ADVANCE, // ID_NEWS_WANT_GOLD_ADVANCE); }
But I couldn't get this to work: the "5" (or dipAdvance) produces a "Wrong type of argument" error.
The syntax for the GetNewProposalPriority function is: INT GetNewProposalPriority(player[0],player[1],int)
where the last int is a ProposalDB index. It returns the priority for this proposal as determined by the current diplomactic state that player[0] takes toward player[1].
To An AI Civ: It's the motivation settings in the strategies in combination with the priorities for the proposal in diplomacy.txt that should determine whether or not a New Proposal is sent. But this happens in the three motivation events: FearMotivation, DesireMotivation, and ReactionMotivation. Each of these has a player[0] (the sender) and a player[1] (the receiver) parameter and no matter what I tried I couldn't get the events occur with anything other than the human as player[1].
So, as they say, here's a workaround. Just prior to these motivation events the NextDiplomaticState event occurs, again with player[0] and player[1] parameters, and here the game does take player[1] in a loop through all the other players that player[0] has contact with. There's two tricks involved: first, you work in this event rather than the motivation events we're supposed to be working in, and second, immediately after telling the sender to consider a new proposal you generate a NewProposal event. Here's an example test handler: HandleEvent(NextDiplomaticState)'Test_AI_AI_Diplomacy' pre { if (!IsHumanPlayer(player[0]) && !IsHumanPlayer(player[1])) { if (TurnsAtWar(player[0],player[1])>25 ) {// 0 for test // offer cease-fire LogRegardEvent(player[0],player[1], 500, 0, ID_NONE, 0); ConsiderNewProposal(player[0],player[1], 2000,32,0,ID_NONE,ID_NONE,ID_NONE); Event:NewProposal(player[0],player[1]); } if (HasAgreement(player[0], player[1],32) && !HasAgreement(player[0], player[1],33) ) { //if they have a cease-fire but not a peace treaty, offer one LogRegardEvent(player[0],player[1], 500, 0, ID_NONE, 0); ConsiderNewProposal(player[0], player[1], 2000, 33, 0,ID_NONE,ID_NONE,ID_NONE); Event:NewProposal(player[0], player[1]); } if(AtWarWith(player[0],1) && AtWarWith(player[1],1)){// 1 being the Human //if both sender and recipient are at war with the Human if (!HasAgreement(player[0], player[1],33) ) {//are these mutual? //if they don't have a peace treaty, offer one ConsiderNewProposal(player[0], player[1], 2000, 33, 0,ID_NONE,ID_NONE,ID_NONE); Event:NewProposal(player[0], player[1]); } if(HasAgreement(player[0], player[1],33) && !HasAgreement(player[0], player[1],38)){ //if they have a peace treaty but not an alliance, offer one ConsiderNewProposal(player[0], player[1], 2000, 38, 0,ID_NONE,ID_NONE,ID_NONE); Event:NewProposal(player[0], player[1]); } } } }
More on Motivating the AI to send a proposal: You can generate the motivation events within NextDiplomaticState, but it's very CPU intensive new strategies new diplomatic states use ConsiderMotivation
2) Receiving a Proposal:
From here on down we're talking about 'Response Logic', how does an AI civ respond to a new proposal that's been sent to them (by either a human or another AI player).
The instructions are:
If an AI has received a new proposal, then the NewProposal event will be triggered. You should write functions to handle this event and respond to it. The parameter player[0] will be the player that sent the proposal and player[1] will be the player that received it. You should call the functions GetLastNewProposalType, GetLastNewProposalArg and GetLastNewProposalTone to figure out how to respond; other functions might also be useful when deciding how to respond. You should use the function ConsiderResponse if you wish to accept or counter the new proposal. By default the new proposal will be rejected. If multiple calls to ConsiderResponse are made then the response with the highest priority will be sent.
Here's the syntax for the above functions: INT GetLastNewProposalType(sender, receiver, first/second_type);
where first/second_type is 0 to get the type of the 'initial' proposal or 1 to get the type of the 'in addition' proposal if it has one. This seems only to apply if the Human sent it; I have yet to see how to get the AI to initiate such a two part proposal. INT GetLastNewProposalArg(sender, receiver,int,param/int);
Don't know what the ints are for. But I always get either 0 or -1. This should return a value related to the above strange parameter. GetLastNewProposalArg(sender, receiver,0,tmpCity); // 0= first_type ?
when it's offer city is syntactically correct but always returns tmpCity=0, the invalid one. INT GetLastNewProposalTone( sender, receiver);
Returns 0= Magnanimous/Kind, 1=Friendly/Equal, 2=Obsequious/Meek, 3=Haughty/Indignant, 4=Hostile/Angry. VOID ConsiderResponse(sender, receiver, priority,response_type , [counter/threat type], [param1], [tone]); where: enum RESPONSE_TYPE { RESPONSE_INVALID, // 0 RESPONSE_REJECT, // 1 RESPONSE_ACCEPT, // 2 RESPONSE_COUNTER, // 3 RESPONSE_THREATEN, // 4 RESPONSE_MAX // 5 (just marks end of list, I think) If RESPONSE_TYPE=3 then counter/threat type should be the proposalDB index of the proposal you want to counter with. If RESPONSE_TYPE=4 then enum THREAT_TYPE { THREAT_NONE, //0 THREAT_ATTACK_CITY, //1 THREAT_PIRATE, //2 THREAT_SPECIAL_ATTACK_CITY, //3 THREAT_DESTROY_CITY, //4 THREAT_END_AGREEMENT, //5 THREAT_TRADE_EMBARGO, //6 THREAT_DECLARE_WAR, //7 THREAT_MAX //8
(I'm guessing at the threat types, haven't actually tried them.)
param1 depends on the counter/threat type and may be, e.g., a city_t variable. In this case the problem is to fill it in. tone is an int: 0= Magnanimous/Kind 1=Friendly/Equal 2=Obsequious/Meek 3=Haughty/Indignant 4=Hostile/Angry.
This is just like the problematic cases above, but here there was an example: // Example proposal response: // when a foreigner asks for gold and AI can take a city from the foreigner, ask for the city //HandleEvent(NewProposal) 'Example_ProposalResponseEvent' pre { . . . // INT FindCityToExtortFrom(player, foreigner, max_value, &cityvar) // city_t city; // FindCityToExtortFrom(player[1], player[0], total_sender_result, city); // // // PROPOSAL_REQUEST_GIVE_GOLD == 20 // // PROPOSAL_OFFER_GIVE_CITY == 1 // if (CityIsValid(city) && first_type == 20 && total_receiver_result < 0 && total_sender_result > 0) // { // // VOID ConsiderResponse(sender, receiver, priority, response_type, [counter/threat type], [param1], [tone]) // // PROPOSAL_OFFER_GIVE_CITY = 1 and DIPLOMATIC_TONE_ANGRY = 4 // // RESPONSE_COUNTER == 3 // ConsiderResponse(player[0], player[1], 100, 3, 1, city, 4); // } // else // { // // VOID ConsiderResponse(sender, receiver, priority, response_type, [counter/threat type], [param1], [tone]) // // RESPONSE_REJECT == 1 // // RESPONSE_ACCEPT == 2 // ConsiderResponse(player[0], player[1], 10, 1); // }
One of the 'other functions' mentioned is: INT GetNewProposalResult(sender,receiver,player,type) // type: 0 = science, 1 = gold, 2 = production, 3 = regard
where, for example, we can have stuff like: int_t sender_science_result; int_t receiver_science_result; sender_science_result = GetNewProposalResult(player[0], player[1], player[0], 0); receiver_science_result = GetNewProposalResult(player[0], player[1], player[1], 0); int_t sender_gold_result; int_t receiver_gold_result; sender_gold_result = GetNewProposalResult(player[0], player[1], player[0], 1); receiver_gold_result = GetNewProposalResult(player[0], player[1], player[1], 1); int_t sender_production_result; int_t receiver_production_result; sender_production_result = GetNewProposalResult(player[0], player[1], player[0], 2); receiver_production_result = GetNewProposalResult(player[0], player[1], player[1], 2);
and then use these results to determine the AI's response as in the above example. So, someone (either Human or AI) has sent a new proposal and it's been received by the AI player[1]. Here's an example of a handler where player[1] responds to a proposal asking him to withdraw his troops by countering with a proposal that the sender, player[0], should stop piracy: HandleEvent(NewProposal) 'ResponseToWithdrawCounterWithStopPiracy' pre {int_t first_type; //initial proposal int_t second_type; // 'in exchange' proposal first_type = GetLastNewProposalType(player[0], player[1], 0); second_type = GetLastNewProposalType(player[0], player[1], 1); if ( first_type==4 || second_type == 4 ) { ConsiderResponse(player[0], player[1], 1000, 3, 6); } }
Here's one where player[1] responds to a proposal (#22) when it is additionally accompanied with an offer of gold: HandleEvent(NewProposal)'RespondToNewProposals' pre {int_t first_type; //initial proposal int_t second_type; // 'in exchange' proposal int_t sender_gold_result; first_type = GetLastNewProposalType(player[0], player[1], 0); second_type = GetLastNewProposalType(player[0], player[1], 1); receiver_gold_result = GetNewProposalResult(player[0], player[1], player[1], 1); if (first_type==22 && second_type == 19) {//request pollution agreement && offer gold if (receiver_gold_result >100) { ConsiderResponse(player[0], player[1], 2000,2); //accept } else{ ConsiderResponse(player[0], player[1], 2000,1); //reject } } }
3) Responding to Counter-proposals and Rejections.
If an AI has received a response to their new proposal other than accept, then the Counter event will be triggered if the receiver of the proposal countered or a Reject event if they rejected it outright. The parameter player[0] will be the player that sent the original new proposal and player[1] will be the player that was the original receiver. You should call the functions GetLastResponseType, GetLastCounterResponseType, GetLastCounterResponseArg before determining how to respond to a counter proposal or rejection. You can then call the ConsiderResponse function (with the receiver and sender id’s swapped if accepting or rejecting) to respond. You can also call ConsiderResponse to threaten.
The syntax involved here is: INT GetLastResponseType(player[0], player[1], first/secondtype); Returns(?):RESPONSE_TYPE { RESPONSE_INVALID, // 0 RESPONSE_REJECT, // 1 RESPONSE_ACCEPT, // 2 RESPONSE_COUNTER, // 3 RESPONSE_THREATEN, // 4 INT GetLastCounterResponseType(player[0], player[1], first/secondtype);
INT GetLastCounterResponseArg(player[0], player[1],int,int); Same problem as GetLastNewProposalArg INT GetCounterProposalResult(sender,receiver,player,type); // like GetNewProposalResult // type: 0 = science, 1 = gold, 2 = production, 3 = regard
4) Responding to Threats.
If an AI has received a threat from a proposal sender, then the Threaten event will be triggered. The parameter player[0] will be the player that send the original new proposal and player[1] will be the player that was the original receiver. You can call the GetLastNewProposal* and GetLastResponse* functions to determine how to respond to a threat. You can then call the ConsiderResponse function to accept or reject the original proposal after a threat. INT GetLastNewProposal* INT GetLastResponse*
Appendix 1: Some SLIC functions.
A number of these were gleaned from Diplomod; thanks, Dale.
GetPublicRegard( ??? )
INT GetEffectiveRegard(int<player>, int<player>) [regard adjusted for being at war]
INT GetTrust(int<player>, int<player>)
Azmel2 [Richard Myers]:
Both attitude [regard] and trust are on a scale from 0 to 1000. The break down is as follows:
0 - 100, hot war (Goals with TargetOwner: HotEnemy) 101-300, cold war (Goals with TargetOwner: ColdEnemy) 301-700, neutral (Goals with TargetOwner: Neutral) 701-900, friend 900-1000, allied
The first three also say which goals will be pursued against foreign nations that you have a particular attitude toward. For example, you'll only do HotEnemy goals against foreigners you have a regard of 100 or less with. Going to war will also induce an automatic hotwar regard. Trust is on the same scale. An AI will not sign certain treaties or agreements unless trust or regard is above a certain point. That can be redefined using your own diplomatic response logic.
VOID RecomputeRegard(int<player>)
VOID SetTrust(int<player>, int<player>, int)
Sets first player's trust towards second player to 0<= int <=1000. (?)
VOID LogRegardEvent(int<player>, int<player>, regardDelta, regardEventType, ID_string, Delta_length)
regardDelta is the amount to increase the first player's regard towards the second by.
The Regard Event Types are ScenarioEvent, MilitaryPowerEvent, MilitarySafetyEvent, DiplomacyEvent, GoldEvent, KnowledgeEvent, & ProductionEvent. So "0" is probably the null event type.
ID_string is a string used in the Diplomacy intelligence screen.
Delta_length is, I think, the number of turns this should last for.
Example: // Make the Allies like each other LogRegardEvent(2, 3, 1000, 0, ID_WW_BLANK, turnMax);
where turnMax is the number of turns in the WW2 scenario.
INT GetPersonalityType(int<player>)
Returns the personalityDB index of the player's leader
Dale discovered an optional second int argument: GetPersonalityType(int<player>,[int]), but it's use is completely mystifying, so far as I know.
INT GetDesireWarWith(int<player>, int<player>)
Returns 1 or 0. [May be used to load ProvokeWar Dip State, also in response logic.]
INT TurnsAtWar(int<player>, int<player>)
Returns -1 if they've never been at war and otherwise what it says (0 means they have been but are not now at war).
INT TurnsSinceLastWar(int<player>, int<player>)
Returns -1 if they've never been at war and otherwise what it says (0 means they are now at war).
INT AtWarWith(int<player>, int<player>)
Returns 1 or 0.
INT EffectiveWarWith(int<player>, int<player>)
Returns 1 if first player is effectively at war with second player. (regard<100 but yet to attack/declare war?)
INT AtWarCount(int<player>)
Returns the number of civs that player is at war with.
INT EffectiveAtWarCount(int<player>)
Returns the number of civs that player is effectively at war with. (regard<100 but yet to attack/declare war?)
INT IsHostile(int<player>, int<player>)
[?,only seen it return 0]
INT GetAtRiskCitiesValue(int<player>, int<player>)
Returns the number of first player's cities that second player could attack on the next turn ?.
INT GetTotalValue(int<player>)
Returns the total value (whatever that is) of the player's cities. Used in Response Logic ( 2 REQUEST_GIVE_CITY)
INT GetMostAtRiskCity(player, foreigner, &cityvar)
Example: When asked for a ceasefire, an AI civ counters by asking for a city. HandleEvent(NewProposal) 'Example_ProposalResponseEvent' pre { int_t first_type; city_t tmpcity; first_type = GetLastNewProposalType(player[0], player[1], 0); GetMostAtRiskCity(player[1], player[0], tmpcity); if (CityIsValid(tmpcity) && first_type == 32) { ConsiderResponse(player[0], player[1], 1000, 3, 1, tmpcity, 4); } else{ ConsiderResponse(player[0], player[1], 10, 1); } }
INT FindCityToExtortFrom(player, foreigner, max_value, &cityvar)
Should be like GetMostAtRiskCity but no positive results yet
INT GetRelativeStrength(int<player>, int<player>)
Returns 0- very weak, 1- weak, 2- average, ...
INT GetLastHotwarAttack(int<player>, int<player>)
Returns the number of turns since second player last Hotwar attacked (goals with Hotenemy target?) first player (9999 if never)
INT GetLastColdwarAttack(int<player>, int<player>)
Returns the number of turns since second player last Coldwar attacked (goals with Coldenemy target?) first player (9999 if never)
INT HasAgreement(int<player>, int<player>, int)
Returns 0 or 1. The final int is a proposalDB index
VOID CancelAgreement(int<player>, int<player>, int)
The final int is a proposalDB index
INT GetAgreementDuration(int<player>, int<player>, int)
The final int is a proposalDB index. Returns the number of turns the first player has had this agreement with the second.
INT CanFormAlliance(int<player>,int<player)
[???]
INT GetDesiredAdvanceFrom(int<player>, int<player>, int, int)
INT GetStopResearchingAdvance(player[0], player[1])
INT GetNextAdvance(player[0])
Returns the advanceDB index of whatever player[0] is researching
Appendix 2: The New Proposal List.
Remarks on Response Logic are gathered together from Interactions.txt. These look to me to be someone's "work in progress" notes and the resulting comments may not all be true. The motivation comments (e.g., 8 REQUEST_BREAK_AGREEMENT, (MOTIVATION_DESIRE_ENLIST_FRIEND)) must refer to the conditions which initiate these proposals. 0 NONE, icon ="updi01.tga", the default one 1 OFFER_GIVE_CITY, icon ="updi01.tga", the default one 2 REQUEST_GIVE_CITY, icon ="updi01.tga", the default one 'LandForPeace' triggered when:
1) Declared war exists between sender and receiver, 2) receiver trusts sender, 3) more than 50% of receiver's city value is at risk from sender 4) the total value of receivers at risk cities exceeds the value of the requested city, 5) neither sender nor receiver has overwhelming military superiority. then, COUNTER_PROPOSAL with request TREATY_CEASEFIRE. REQUEST_GIVE_CITY + threat THREAT_ATTACK_CITY: Reject if receivers cities at risk of conquest by the sender exceeds 75% of the total city value in his nation. (Slic function) 'Reject' triggered when: 1) No declared war between sender and receiver. 2) Sender does not have overwhelming (>30%) military superiority over receiver. then, sender responds with RESPONSE_REJECT. 'CaveInTo' triggered when: 1) TBD. (always) then, sender responds with RESPONSE_ACCEPT. 3 OFFER_WITHDRAW_TROOPS, icon ="updi01.tga", the default one 4 REQUEST_WITHDRAW_TROOPS, (REACTION) Image "updi47.tga"
'MutualWithdraw' triggered when: 1) Do not want war and 2) Sender has troops in our territory; then, COUNTER_PROPOSAL with request for OFFER_WITHDRAW_TROOPS. 'PayTribute' triggered when: 1) Do not want war and 2) Sender has no troops in our territory 3) We are stronger; then, COUNTER_PROPOSAL with request for OFFER_GIVE_GOLD. 5 OFFER_STOP_PIRACY, 6 REQUEST_STOP_PIRACY, (FEAR_PIRACY)Image "updi46.tga" 'StopPiracy' triggered when: 1) receiver does not lose any gold from piracy from sender. 2) sender loses gold from piracy by receiver. then, COUNTER_PROPOSAL with request of OFFER_GIVE_GOLD: (3 * gold pirated by receiver each turn from sender). 'StopPiracy' triggered when: 1) receiver loses more gold from senders piracy than receiver loses from senders piracy. then, COUNTER_PROPOSAL with request of OFFER_STOP_PIRACY 7 OFFER_BREAK_AGREEMENT, 8 REQUEST_BREAK_AGREEMENT, (MOTIVATION_DESIRE_ENLIST_FRIEND) 9 OFFER_STOP_RESEARCH, 10 REQUEST_STOP_RESEARCH, Image "updi45.tga" Only valid if we are researching an advance from our stop researching advance list. Receivers with the scientist personality will always REJECT this. If sender is not equal to or stronger than receiver militarily, REJECT. Otherwise, if they are currently researching the same advance, COUNTER for them to top researching the same advance. If they are much stronger, and already have the advance they are asking us to stop researching, then ask for a PEACE_TREATY. 11 OFFER_REDUCE_NUCLEAR_WEAPONS, 12 REQUEST_REDUCE_NUCLEAR_WEAPONS, 13 OFFER_REDUCE_BIO_WEAPONS, 14 REQUEST_REDUCE_BIO_WEAPONS, 15 OFFER_REDUCE_NANO_WEAPONS, 16 REQUEST_REDUCE_NANO_WEAPONS, all these= Image "updi01.tga" REQUEST_REDUCE_{NUCLEAR, BIO, NANO}_WEAPONS: Triggered for coldwar enemies with nuclear, bio or nano weapons. Looks at ratio of his weapons to our cities and our weapons to his cities. Tries to get enemy to reduce to the point that our losses would be half of his and so that our losses will never be more than 50% of our cities. 17 OFFER_GIVE_ADVANCE, 18 REQUEST_GIVE_ADVANCE, (DESIRE_SCIENCE) 'PayForAdvance' triggered when: 1) sender has not offered to pay (80% of advance cost) gold 2) sender can pay (80% of advance cost) gold then, COUNTER_PROPOSAL with request of OFFER_GIVE_GOLD of (80% of advance cost) gold. 'AdvanceExchange' triggered when: 1) sender has an advance that receiver wants. 2) advance that receiver wants costs less than 120% of advance sender requested. then, COUNTER_PROPOSAL with request of OFFER_GIVE_ADVANCE of advance receiver wants. 19 OFFER_GIVE_GOLD, 20 REQUEST_GIVE_GOLD, 'AdvanceForGold' triggered when: 1) sender has an advance that receiver wants. 2) advance that receiver wants costs less than 120% of gold requested. then, COUNTER_PROPOSAL with request of OFFER_GIVE_ADVANCE of advance receiver wants. 21 OFFER_REDUCE_POLLUTION, 22 REQUEST_REDUCE_POLLUTION, Image "updi44.tga" If receiver produces less than 50% of the pollution of sender, then REJECT. If receiver fears pollution disaster, then COUNTER propose as follows: Ecotopians request sender to reduce pollution to to 5% below their pollution level, diplomats reject if there is no peace treaty or counters with a 10% reduction, and all others counter with a 5% reduction. 23 OFFER_MAP, 24 REQUEST_MAP, (DESIRE_MAKE_FRIEND) Always ACCEPT for allies and REJECT for anyone we are at war with or have coldwar or less regard for. If we have gotten their map within the last 5 turns, REJECT. In all other cases, counter with a request for their map. 25 OFFER_HONOR_MILITARY_AGREEMENT, 26 REQUEST_HONOR_MILITARY_AGREEMENT, If receiver at war with foreigner, has alliance with sender or has lawful personality then ACCEPT. If sender much stronger than foreigner or receiver, then ACCEPT. If foreigner much stronger than sender, then REJECT. Otherwise, REJECT if regard for sender not friendly or higher or ACCEPT if regard for foreigner not friendly or higher. 27 OFFER_HONOR_POLLUTION_AGREEMENT, Image "updi08.tga" 28 REQUEST_HONOR_POLLUTION_AGREEMENT, Image "updi08.tga" If request to honor pollution agreement requests significantly larger reduction than original pact, then REJECT. If sender has not met pollution goal or half way to goal within 20 turns (10 turns for Ecotopian receivers), then counter with request that sender honor their pollution agreement. If receiver met pollution goal or half way to goal within 20 turns, then ACCEPT 29 OFFER_END_EMBARGO, 30 REQUEST_END_EMBARGO, (DESIRE_GOLD, REACTION) Image "updi48.tga" If embargo has not continued for more than 5 turns, then REJECT. If receiver does not have friendly regard for sender and sender gets 50% or more gold from total trade than receiver, then COUNTER asking for 2x the difference in gold from trade. If sender has embargo against receiver, then COUNTER propose that they end their embargo, otherwise, REJECT. Otherwise, ACCEPT request. 31 TREATY_DECLARE_WAR, Image "updi02.tga" 1) War is declared if you attack someone or they attack you. 2) If you form an alliance with someone, you go to war with anyone they are at war with. 3) If AI not currently at war with anyone, has hot war level regard towards you, has not been at war with you in the last 5 turns (or never), is not allied with you and shares a continent with you, then it declares war. 32 TREATY_CEASEFIRE, (REQUEST_TREATY_CEASEFIRE:??) Image "updi03.tga" (FEAR_INVASION, FEAR_CITY_DEFENSE, FEAR_MILITARY_RANK) Accept when 1) war has been declared between sender and receiver, 2) sender trusts receiver 3) sender does not want to be at war with receiver. [GetDesireWarWith(i,j)?] 33 TREATY_PEACE, (REQUEST_TREATY_PEACE ??) (DESIRE_MAKE_FRIEND) Image "updi04.tga" Accept when 1) no war has occurred between sender and receiver for 10 turns, 2) sender trusts receiver, 3) receiver has no troops on our territory 4) no coldwar attacks have been made by receiver for 5 rounds 5) sender does not want to be at war with receiver and 6) we have average regard towards them. 34 TREATY_TRADE_PACT, (DESIRE_MAKE_FRIEND, DESIRE_GOLD)Image "updi05.tga" ACCEPT when 1) no war has occurred between sender and receiver for 10 turns, 2) receiver trusts sender, 3) sender has no troops on receivers territory, 4) sender is not pirating receivers trade routes, 5) more than 10% of the value of receivers trade is with the sender. 6) value of senders trade with receiver does not exceed twice receivers trade with sender. 35 TREATY_RESEARCH_PACT, (DESIRE_MAKE_FRIEND, FEAR_SCIENCE_RANK) Image "updi06.tga" ACCEPT when 1) no war has occurred between sender and receiver for 10 turns, 2) receiver trusts sender, 3) sender has no troops on our territory, 4) receiver generates less science than sender per turn, or 5) receiver does not generate more than 20% more science than sender. 36 TREATY_MILITARY_PACT, Image "updi07.tga" (DESIRE_MAKE_FRIEND, DESIRE_ENLIST_FRIEND, FEAR_MILITARY_RANK) ACCEPT when 1) no war has occurred between sender and receiver for 10 turns, 2) receiver trusts sender, 3) sender has no troops on receivers territory, 3) we are at war and peace with the same nations. 37 TREATY_POLLUTION_PACT, Image "updi08.tga" ACCEPT when 1) no war has occurred between sender and receiver for 10 turns, 2) receiver trusts sender, 3) sender has no troops on our territory, 3) for Ecotopians, any proposal as long as sender is producing no more than 5% of the pollution as receiver. For diplomats, as long as sender is producing no more than 20% of the pollution as receiver, and the proposal is not for more than 20% reduction, and for everyone else, only if no more than 10% reduction. 38 TREATY_ALLIANCE. (DESIRE_MAKE_FRIEND, FEAR_MILITARY_RANK) Image "updi09.tga" Accept when 1) no war has occurred between sender and receiver for 25 turns, 2) sender trusts receiver a lot, 3) receiver has no troops on our territory 4) no coldwar attacks have been made by receiver for 15 rounds 5) we are at war and peace with the same nations and 6) have very high regard for receiver (ie. lots of gold and advances gifts.
|