Diplomacy and Trade Logging

EmperorFool

Deity
Joined
Mar 2, 2007
Messages
9,637
Location
Mountain View, California
I have finally demystified CyDiplomacy and friends and am able to intercept all diplomatic actions (demands, requests for help, trades, gifts, and requests to trade embargo, go to war, sign peace, etc) as well as trades.

This will have several good ramifications.

  • Logging of all trades to Autolog
  • No longer will tech trades look like original research, though I need to figure out how to carry the trade forward so the techAcquired event will ignore it.
  • Logging of demands/help/gifts and whether or not they were accepted
    "Ruff gives in to Monty's demand for Rifling."
    "EmperorFool agrees to come to Mansa Musa's aid against Isabella."
    "Alerum joins Victoria's trade embargo against Asoka."
  • Mods that want to have events based on all of these diplomatic happenings can use the event manager just as they always have (I'm creating a slew of events for this purpose).
I wanted to throw this out there in case anyone has any feature suggestions based on this work. Sometimes knowing of a new possibility can prime the creative juices. :D

Note that the only time BUG gets signaled about these events is when the human player is involved. Inter-AI diplomacy isn't detectable in this fashion.
 
How? How? I've been looking at the event code for ages trying to figure out how to do this. No doubt your superior mystic skills have enabled you to create new events (btw: must look at how you do that!).

Anyway - very well done.
 
To see how I've created new events, take a look at BugEventManager.py. To see a simple case of using a new event, see CvOptionsScreenCallbackInterface.py.

BugEventManager

Code:
def addEvent(self, eventType):
	"""Creates a new event type without any handlers.
	
	Prints a warning if eventType is already defined.
	"""
	if self.hasEvent(eventType):
		BugUtil.debug("WARN: event '%s' already defined" % eventType)
	else:
		BugUtil.debug("BUG: adding event '%s'" % eventType)
		self.EventHandlerMap[eventType] = []

CvOptionsScreenCallbackInterface

Code:
def handleLanguagesDropdownBoxInput ( argsList ):
	"Handles Languages Dropdown Box input"
	iValue, szName = argsList
	
	CyGame().setCurrentLanguage(iValue)
	
	popup = PyPopup.PyPopup()
	popup.setHeaderString("")
	popup.setBodyString(localText.getText("TXT_KEY_FEAT_ACCOMPLISHED_OK", ()))
	popup.launch()
	
# BUG - BugEventManager - start
	[B]CvEventInterface.getEventManager().fireEvent("LanguageChanged", iValue)[/B]
# BUG - BugEventManager - end
	
	return 1

Handling New Events

Actually, this works just like all the builtin events -- via XML. Either use an <events> function/class tag to call Python code that calls addEventHandler(type, function), or use one <event> tag per event handler.

<events> is great for using with mods that were built to work with CvCustomEventManager. This requires no Python changes in the mod's files. The <events> tag specifies the module name (e.g. UnitNameEventManager), omitting the class/function name makes it assume a class with the same name, and that class's __init__ function takes care of the rest, just as it did with CvCEM.

The <event> tag is nice for features created for BUG that don't have a lot of new events. Actually, it would be just fine for ones with a lot of new events, too. This removes the need for an artificial class/function to interact with BugEventManager.

ColorUtil handles the "LanguageChanged" event by rebuilding its list of colors so that users will see translated names in the dropdown lists on the options screen. Since it already had a function to do this -- createColors() -- all that was needed was the XML:

Code:
<event type="LanguageChanged" module="ColorUtil" function="createColors"/>

I'll be committing the diplomacy stuff shortly so Dresden can use it for the INFO screen, but it works exactly as the existing examples. The only trick was figuring out where to intercept Civ to call fireEvent() and extracting the proposed trade/gift/demand data from CyDiplomacy. That latter part took far more work.
 
I've committed the first cut of Diplomacy Events. Many of the core events are hooked up, but it will take quite a bit of grunt work to hook them up to Autolog.

Does anyone want to take this on? It's not difficult -- just need to create messages and put them into XML. The Python code will be very simple and can be copied-n-pasted from other events with minor tweaking.

Note: If you get a diplomacy window with the leaderhead but missing text and options, please post the contents of PythonErr.log so I can fix the problem. It's tough because you can't force the AI to come to you with diplomatic requests. :(
 
I'd have another suggestion for the logger. Ruff has included BattleStats in the logger, which is great for SG reports. However you have to hit Alt+B at the end of your set, and I often forget that. Would it be possible to write the BattleStats into the log automatically if the players saves the game manually? I don't know if there is an event like on.gamesave or such or if it is different from the autosave civ does, but it would be a neat addition for the forgetable persons like me... :D

Like check if logging is enabled => check if the game was saved manually => if so, write BattleStats to the log
 
·Imhotep·;7237199 said:
I'd have another suggestion for the logger. Ruff has included BattleStats in the logger, which is great for SG reports. However you have to hit Alt+B at the end of your set, and I often forget that. Would it be possible to write the BattleStats into the log automatically if the players saves the game manually? I don't know if there is an event like on.gamesave or such or if it is different from the autosave civ does, but it would be a neat addition for the forgetable persons like me... :D

Like check if logging is enabled => check if the game was saved manually => if so, write BattleStats to the log
I remember you asking about this before. There is an onSave but I would bet that it is also triggered by the auto-save feature. We could add a prompt to dump battle stats or EF might suggest one of his new fangled events. What about 'onExit'? Do you usually save an SG and then exit civ when you have finished your set?
 
AFAIK, Ruff is correct that the onPreSave/onSave events -- which is where we could dump the stats -- are both fired when doing an autosave. Perhaps there's an argument saying it's auto or not. I'll have to do some investigation.

This goes back to my suggestion awhile back of having an SG counter that you set when you start your turnset. It would hook into the Reminders but have more features. Dumping battle stats could be one such additional feature.

IIRC, the idea was panned as being unnecessary or perhaps extraneous. Does this start to chip away at that argument? Are there other benefits to having a turn counter?

  • Popup to set # of turns, nick and start counter.
  • Show # of turns remaining in NJAGC or somewhere more prominent.
  • Dump battle stats automatically.
  • Popup and flashing warning text nag when it hits zero.
  • Store history of nicks and # of turns and dates.
 
AFAIK, Ruff is correct that the onPreSave/onSave events -- which is where we could dump the stats -- are both fired when doing an autosave. Perhaps there's an argument saying it's auto or not. I'll have to do some investigation.

This goes back to my suggestion awhile back of having an SG counter that you set when you start your turnset. It would hook into the Reminders but have more features. Dumping battle stats could be one such additional feature.

IIRC, the idea was panned as being unnecessary or perhaps extraneous. Does this start to chip away at that argument? Are there other benefits to having a turn counter?

  • Popup to set # of turns, nick and start counter.
  • Show # of turns remaining in NJAGC or somewhere more prominent.
  • Dump battle stats automatically.
  • Popup and flashing warning text nag when it hits zero.
  • Store history of nicks and # of turns and dates.

Now that would be all I'd look for :goodjob: . Actually I set a reminder called "stop playing" for every of my SG turnsets, you know, sometimes one gets carried away... :D
 
I've committed the first cut of Diplomacy Events. Many of the core events are hooked up, but it will take quite a bit of grunt work to hook them up to Autolog.

Does anyone want to take this on? It's not difficult -- just need to create messages and put them into XML. The Python code will be very simple and can be copied-n-pasted from other events with minor tweaking.
Does this still need to be done before feature freeze? If so and you can whip up an example, I'll do the grunt work.
 
Does this still need to be done before feature freeze? If so and you can whip up an example, I'll do the grunt work.

That would be awesome as I'm totally swamped right now. I started a few too many things and am trying to tie up the loose ends for the release.

As I said, most of the diplomacy events have been created, but many are incomplete.

For example, the events for religion and civic pressure, war demand, and trade embargo are all complete. They each define three events: the request, accept and reject. In all cases, all of the needed parameters are passed to the event.

DiplomacyUtil has functions to listen for only the religion and civic events as examples, so you can copy those to autologeventmanager and fill them out. For this you won't need to modify DiplomacyUtil.

You'll have to play around with the test games to see how some of the other events pass their parameters. All offer-related events -- offer city, offer deal, offer peace, offer help (tech/money), offer vassal -- are accepted/rejected by the same events: DealAccepted and DealRejected.

As for how to implement the Autolog side, just take a look at any of the existing events for how to write to the log. If you have any questions, ask away. I didn't write the code, and in fact Cammagno did the work to put all the log messages into XML, so he might be able to speak more to how it works, but I think it's pretty straight-forward.
 
OK, I'll get started. What sort of options should we go with? The current autolog options look like this:
1223075085.jpg

One way to go would be add another column for Trade/Demands with options for:
  • Trade Offer - All the Deal* events and responses
  • Gifts - City/Help/Peace/VassalOffered
  • Help/Tribute - Demand and response
  • Religion/Civic - Demand and response
  • Join War - Demand and response
  • Join Embargo - Demand and response
I figured limit it to 6 since that's about how much room there is per column. An alternative is to combine Gifts with Trade Offers since they both have the same response mechanism and separate Religion & Civics.

Another way to go is to have just 2 options: a catchall single option for AI-initiated deals (all *Demanded/*Offered) and a second option for all user responses (*Accepted/*Rejected)
 
That seems like a good start. Another option is to roll some of them into the existing options, e.g. Religions and War, but I do like them being separate. I'm afraid they won't fit in other languages, but it's better to do what makes sense and deal with rearranging for languages rather than designing for it, I think.

I don't use Autolog, so perhaps some SGers could chime in on what they'd like.
 
I'm running into the strangest problem in testing. Suddenly, for reasons I can't fathom, gc.getInfoTypeForString("TXT_KEY_CIVIC_THEOCRACY") is failing (i.e. returning -1) when I *know* it worked in the past since the test save was one of mine.

This is screwing up the processing of civic demands since that's how we get the ID of the civic to pass to the event. I can probably work around it by stripping any "TXT_KEY_" from the string since gc.getInfoTypeForString("CIVIC_THEOCRACY") still returns the expected 22, but it's just really odd and was wondering if anyone had a clue what was going on.
 
TXT_KEY_CIVIC_THEOCRACY should not return 22 because it is simply a <TEXT> key -- not an Info object. Here's the sample event in DiplomacyUtil:

Code:
def onCivicDemanded(argsList):
	ePlayer, eTargetPlayer, eCivic = argsList
	BugUtil.debug("DiplomacyUtil - %s asks %s to switch to %s",
			PlayerUtil.getPlayer(ePlayer).getName(), 
			PlayerUtil.getPlayer(eTargetPlayer).getName(), 
			gc.getCivicInfo(eCivic).getDescription())

and IIRC, this works fine. eCivic should be 22 in the case you mention. Is this not working? Where are you getting the text key from?

Oh, are you talking about extractAndLookupInfoType()? Hmm, I thought that had worked before as well. I think I originally used only the method of obtaining the AI's favorite civic directly and switched it to use extractAndLookupInfoType() once I saw the parameter being passed. I probably didn't test it (my bad), but I don't remember.

I do remember making all three events use the parameter and then discovering that the parameter isn't passed for the accept/reject events. I would just switch the demand event use the same method of obtaining the civic as the other two events. Problem solved, and safer than munging the text key.
 
Yeah, the problem was coming from extractAndLookupInfoType(). I'll switch it to use the same method the responses use then.

Please ignore anything below this line. Testing Autolog output here and I might accidentally hit post instead of preview ;)

========================================================================================
 
Oh, and another thing. The religion responses can't use the same function as the demand because the parameters are different (FromPlayer and ToPlayer switch). Would you prefer I broke them into two separate functions (like extractReligionOnDemand() and extractReligionOnResponse()) or use lamdas like the civic processors do?
 
Yes, please use the lambdas like the civic functions. I suppose you can ditch extractReligion() then. Sorry for the sloppy code -- I intended to work through all this stuff when I did the Autolog hookup. Tag, you're it! :p
 
Is it just me or does CyDiplomacy.getPlayerTradeOffer() only ever return anything if the argument is 0? For example if civA trades cows,wheat to civB for coal,oil it only ever seems to report cows for coal. Here's an example, I tried to catch USER_DIPLOCOMMENT_ACCEPT with the standard DealAccepted event and made the following trade:
1223095188.jpg


And then stuck the following debug print into DiplomacyUtil.getProposedTradeData() along with forcing it to do all 50 iterations:
Code:
def getProposedTradeData(getFunc, addFunc):
	for index in range(MAX_TRADE_DATA):
		data = getFunc(index)
		if data:
			addFunc(data)
			foo = "[ItemType=%d, bHidden=%s, bOffering=%s, iData=%d]" % (data.ItemType, data.bHidden, data.bOffering, data.iData)
			BugUtil.debug("DiplomacyUtil.getProposedTradeData index=%d, data=%s" %(index, foo))
		#else:
			#break
	else:
		BugUtil.warn("DiplomacyUtil.getProposedTradeData - reached %d items, ignoring rest",
				MAX_TRADE_DATA)
The results:
Code:
00:38:59 DEBUG: DiplomacyUtil - USER_DIPLOCOMMENT_ACCEPT [-1] from 0 to 3 with (-1, -1)
00:38:59 DEBUG: DiplomacyUtil.getProposedTradeData index=0, data=[ItemType=10, bHidden=False, bOffering=True, iData=1]
00:38:59 WARN : DiplomacyUtil.getProposedTradeData - reached 50 items, ignoring rest
00:38:59 DEBUG: DiplomacyUtil.getProposedTradeData index=0, data=[ItemType=10, bHidden=False, bOffering=True, iData=4]
00:38:59 WARN : DiplomacyUtil.getProposedTradeData - reached 50 items, ignoring rest
00:38:59 DEBUG: DiplomacyUtil.getProposedTrade - <trade 0 [Coal] for 3 [Iron]>
00:38:59 DEBUG: DiplomacyUtil::onDealAccepted [0, 3, <trade 0 [Coal] for 3 [Iron]>]

This isn't a big deal for demands since I doubt the AI ever demands more than 1 thing at a time but for user-initiated trade deals it's a big pain. And of course since the trade screen isn't part of the SDK it's hard to track down. Unless I'm missing something obvious, I guess we have to ignore non-demand trade deals for now.
 
I could have sworn I saw this working just fine with multi-item deals. :confused: And you're right, this isn't part of the SDK so there's no way to see what it's doing nor fix it if it's really broken. :(
 
Back
Top Bottom