Python Script for Elections

Headlock

Nomnomnomnom
Joined
Sep 1, 2007
Messages
133
Location
Over there!
Hi All,

Ive been building up a library of random events based on Fierabras' and Solver's excellent guides.

Im working on making regularly recurring elections, using the Random Events system.

I nearly have it nailed except for one crucial aspect:

1) I have a trigger that says theres an election due
(this uses Fierabras' turn script from the Zeus Spawn event)
2) This has 2 events- liberal/conservative options with varied effects.
3) You choose one,
4) 4 turns later the event you chose expires
5) the next turn, the Election trigger fires again

There will be further refinements of this process, once i get stage 4 to work.

Basically, Im trying to build a python script that:
1 - checks if an election can be held,
2 - expires the event that is chosen, 4 turns AFTER that Election IS held.

Im very experienced with the XML side of this- you can safely assume I have tried everything possible there (ie additonalevents & eventtimes), yet it still is not working properly.

Thence, the need for the revised python script :/
This is the python code I have so far:

CvRandomEventInterface.py
Spoiler :
Code:
######## GENERAL_ELECTION ###########

[COLOR="Blue"]def canTriggerGeneralElection(argsList):[/COLOR]
kTriggeredData = argsList[0]
turn = kTriggeredData.iTurn
player = gc.getPlayer(kTriggeredData.ePlayer)

if (turn % 5 == 0):
return true
return false

if (player.getCivilizationType() == gc.getInfoTypeForString("CIVILIZATION_AMERICA")):
return true
return false

[COLOR="Blue"]def expireGeneralElection1(argsList):[/COLOR]
iEvent = argsList[0]
kTriggeredData = argsList[1]
turn = kTriggeredData.iTurn

if gc.getGame().getGameTurn() >= kTriggeredData.iTurn + 4
return true

return false

[COLOR="Blue"]def expireGeneralElection2(argsList):[/COLOR]
iEvent = argsList[0]
kTriggeredData = argsList[1]
turn = kTriggeredData.iTurn

if gc.getGame().getGameTurn() >= kTriggeredData.iTurn + 4
return true

return false


EventTriggerInfos
Spoiler :
Code:
<EventTriggerInfo>
			<Type>EVENTTRIGGER_GENERAL_ELECTION</Type>
			<WorldNewsTexts>
				<Text>TXT_KEY_EVENTTRIGGER_GENERAL_ELECTION</Text>
			</WorldNewsTexts>
			<TriggerTexts>
				<TriggerText>
					<Text>TXT_KEY_EVENT_GENERAL_ELECTION</Text>
					<Era>NONE</Era>
				</TriggerText>
			</TriggerTexts>
			<bSinglePlayer>0</bSinglePlayer>
			<iPercentGamesActive>100</iPercentGamesActive>
			<iWeight>-1</iWeight>
			<bProbabilityUnitMultiply>0</bProbabilityUnitMultiply>
			<bProbabilityBuildingMultiply>0</bProbabilityBuildingMultiply>
			<Civic>CIVIC_UNIVERSAL_SUFFRAGE</Civic>
			<iMinTreasury>0</iMinTreasury>
			<iMinPopulation>0</iMinPopulation>
			<iMaxPopulation>0</iMaxPopulation>
			<iMinMapLandmass>0</iMinMapLandmass>
			<iMinOurLandmass>0</iMinOurLandmass>
			<iMaxOurLandmass>-1</iMaxOurLandmass>
			<MinDifficulty>NONE</MinDifficulty>
			<iAngry>0</iAngry>
			<iUnhealthy>0</iUnhealthy>
			<UnitsRequired/>
			<iNumUnits>0</iNumUnits>
			<iNumUnitsGlobal>0</iNumUnitsGlobal>
			<iUnitDamagedWeight>0</iUnitDamagedWeight>
			<iUnitDistanceWeight>0</iUnitDistanceWeight>
			<iUnitExperienceWeight>0</iUnitExperienceWeight>
			<bUnitsOnPlot>0</bUnitsOnPlot>
			<BuildingsRequired/>
			<iNumBuildings>0</iNumBuildings>
			<iNumBuildingsGlobal>0</iNumBuildingsGlobal>
			<iNumPlotsRequired>0</iNumPlotsRequired>
			<bOwnPlot>0</bOwnPlot>
			<iPlotType>-1</iPlotType>
			<FeaturesRequired/>
			<TerrainsRequired/>
			<ImprovementsRequired/>
			<BonusesRequired/>
			<RoutesRequired/>
			<ReligionsRequired/>
			<iNumReligions>0</iNumReligions>
			<CorporationsRequired/>
			<iNumCorporations>0</iNumCorporations>
			<bPickReligion>0</bPickReligion>
			<bStateReligion>0</bStateReligion>
			<bHolyCity>0</bHolyCity>
			<bPickCorporation>0</bPickCorporation>
			<bHeadquarters>0</bHeadquarters>
			<Events>
				<Event>EVENT_GENERAL_ELECTION_1</Event>
				<Event>EVENT_GENERAL_ELECTION_2</Event>
			</Events>
			<PrereqEvents/>
			<bPrereqEventPlot>0</bPrereqEventPlot>
			<OrPreReqs/>
			<AndPreReqs>
				<PrereqTech>TECH_CONSTITUTION</PrereqTech>
			</AndPreReqs>
			<ObsoleteTechs/>
			<bRecurring>1</bRecurring>
			<bTeam>0</bTeam>
			<bGlobal>0</bGlobal>
			<bPickPlayer>0</bPickPlayer>
			<bOtherPlayerWar>0</bOtherPlayerWar>
			<bOtherPlayerHasReligion>0</bOtherPlayerHasReligion>
			<bOtherPlayerHasOtherReligion>0</bOtherPlayerHasOtherReligion>
			<bOtherPlayerAI>0</bOtherPlayerAI>
			<iOtherPlayerShareBorders>0</iOtherPlayerShareBorders>
			<OtherPlayerHasTech>NONE</OtherPlayerHasTech>
			<bPickCity>0</bPickCity>
			<bPickOtherPlayerCity>0</bPickOtherPlayerCity>
			<bShowPlot>0</bShowPlot>
			<iCityFoodWeight>0</iCityFoodWeight>
			<PythonCanDo>canTriggerGeneralElection</PythonCanDo>
			<PythonCanDoCity/>
			<PythonCanDoUnit/>
			<PythonCallback/>
		</EventTriggerInfo>

EventInfos
Spoiler :
Code:
		<EventInfo>
			<Type>EVENT_GENERAL_ELECTION_1</Type>
			<Description>TXT_KEY_EVENT_GENERAL_ELECTION_1</Description>
			<LocalInfoText/>
			<WorldNewsTexts>
				<Text>TXT_KEY_EVENT_NEWS_GENERAL_ELECTION_1</Text>
			</WorldNewsTexts>
			<OtherPlayerPopup/>
			<QuestFailText/>
			<bQuest>0</bQuest>
			<bGlobal>0</bGlobal>
			<bTeam>0</bTeam>
			<bPickCity>0</bPickCity>
			<bPickOtherPlayerCity>0</bPickOtherPlayerCity>
			<bDeclareWar>0</bDeclareWar>
			<iGold>0</iGold>
			<bGoldToPlayer>0</bGoldToPlayer>
			<iRandomGold>0</iRandomGold>
			<iCulture>0</iCulture>
			<iEspionagePoints>0</iEspionagePoints>
			<bGoldenAge>0</bGoldenAge>
			<iFreeUnitSupport>0</iFreeUnitSupport>
			<iInflationMod>0</iInflationMod>
			<iSpaceProductionMod>0</iSpaceProductionMod>
			<Tech>NONE</Tech>
			<TechFlavors/>
			<iTechPercent>0</iTechPercent>
			<iTechCostPercent>0</iTechCostPercent>
			<iTechMinTurnsLeft>0</iTechMinTurnsLeft>
			<PrereqTech>NONE</PrereqTech>
			<UnitClass>NONE</UnitClass>
			<iNumFreeUnits>0</iNumFreeUnits>
			<bDisbandUnit>0</bDisbandUnit>
			<iUnitExperience>0</iUnitExperience>
			<iUnitImmobileTurns>0</iUnitImmobileTurns>
			<UnitPromotion/>
			<UnitName/>
			<UnitCombatPromotions/>
			<UnitClassPromotions/>
			<BuildingClass>NONE</BuildingClass>
			<iBuildingChange>0</iBuildingChange>
			<BuildingExtraYields/>
			<BuildingExtraCommerces> 
				<BuildingExtraCommerce> 
					<BuildingClass>BUILDINGCLASS_LIBRARY</BuildingClass> 
					<CommerceType>COMMERCE_RESEARCH</CommerceType> 
					<iExtraCommerce>5</iExtraCommerce> 
				</BuildingExtraCommerce>
				<BuildingExtraCommerce> 
					<BuildingClass>BUILDINGCLASS_THEATRE</BuildingClass> 
					<CommerceType>COMMERCE_CULTURE</CommerceType> 
					<iExtraCommerce>3</iExtraCommerce> 
				</BuildingExtraCommerce> 
			</BuildingExtraCommerces>
			<BuildingExtraHappies/>
			<BuildingExtraHealths> 
				<BuildingExtraHealth> 
					<BuildingClass>BUILDINGCLASS_GROCER</BuildingClass> 
					<iHealth>1</iHealth> 
				</BuildingExtraHealth> 
			</BuildingExtraHealths>
			<iHappy>0</iHappy>
			<iHealth>0</iHealth>
			<iHurryAnger>0</iHurryAnger>
			<iHappyTurns>5</iHappyTurns>
			<iRevoltTurns>0</iRevoltTurns>
			<iMinPillage>0</iMinPillage>
			<iMaxPillage>0</iMaxPillage>
			<iFood>0</iFood>
			<iFoodPercent>0</iFoodPercent>
			<FreeSpecialistCounts/>
			<FeatureType>NONE</FeatureType>
			<iFeatureChange>0</iFeatureChange>
			<ImprovementType>NONE</ImprovementType>
			<iImprovementChange>0</iImprovementChange>
			<BonusType>NONE</BonusType>
			<iBonusChange>0</iBonusChange>
			<RouteType>NONE</RouteType>
			<iRouteChange>0</iRouteChange>
			<BonusRevealed>NONE</BonusRevealed>
			<BonusGift>NONE</BonusGift>
			<PlotExtraYields/>
			<iConvertOwnCities>0</iConvertOwnCities>
			<iConvertOtherCities>0</iConvertOtherCities>
			<iMaxNumReligions>-1</iMaxNumReligions>
			<iOurAttitudeModifier>0</iOurAttitudeModifier>
			<iAttitudeModifier>0</iAttitudeModifier>
			<iTheirEnemyAttitudeModifier>0</iTheirEnemyAttitudeModifier>
			<iPopulationChange>0</iPopulationChange>
			<AdditionalEvents/>
			<EventTimes/>
			<ClearEvents/>
			<PythonCallback/>
			<PythonExpireCheck>expireGeneralElection2</PythonExpireCheck>
			<PythonCanDo/>
			<PythonHelp/>
			<Button>,Art/Interface/Buttons/Process/Blank.dds,Art/Interface/Buttons/Beyond_the_Sword_Atlas.dds,8,5</Button>
			<iAIValue>1000</iAIValue>
		</EventInfo>
		<EventInfo>
			<Type>EVENT_GENERAL_ELECTION_2</Type>
			<Description>TXT_KEY_EVENT_GENERAL_ELECTION_2</Description>
			<LocalInfoText/>
			<WorldNewsTexts>
				<Text>TXT_KEY_EVENT_NEWS_GENERAL_ELECTION_2</Text>
			</WorldNewsTexts>
			<OtherPlayerPopup/>
			<QuestFailText/>
			<bQuest>0</bQuest>
			<bGlobal>0</bGlobal>
			<bTeam>0</bTeam>
			<bPickCity>0</bPickCity>
			<bPickOtherPlayerCity>0</bPickOtherPlayerCity>
			<bDeclareWar>0</bDeclareWar>
			<iGold>0</iGold>
			<bGoldToPlayer>0</bGoldToPlayer>
			<iRandomGold>0</iRandomGold>
			<iCulture>0</iCulture>
			<iEspionagePoints>0</iEspionagePoints>
			<bGoldenAge>0</bGoldenAge>
			<iFreeUnitSupport>0</iFreeUnitSupport>
			<iInflationMod>0</iInflationMod>
			<iSpaceProductionMod>0</iSpaceProductionMod>
			<Tech>NONE</Tech>
			<TechFlavors/>
			<iTechPercent>0</iTechPercent>
			<iTechCostPercent>0</iTechCostPercent>
			<iTechMinTurnsLeft>0</iTechMinTurnsLeft>
			<PrereqTech>NONE</PrereqTech>
			<UnitClass>NONE</UnitClass>
			<iNumFreeUnits>0</iNumFreeUnits>
			<bDisbandUnit>0</bDisbandUnit>
			<iUnitExperience>0</iUnitExperience>
			<iUnitImmobileTurns>0</iUnitImmobileTurns>
			<UnitPromotion/>
			<UnitName/>
			<UnitCombatPromotions/>
			<UnitClassPromotions/>
			<BuildingClass>NONE</BuildingClass>
			<iBuildingChange>0</iBuildingChange>
			<BuildingExtraYields>
				<BuildingExtraYield>
					<BuildingClass>BUILDINGCLASS_FACTORY</BuildingClass>
					<YieldType>YIELD_PRODUCTION</YieldType>
					<iExtraYield>5</iExtraYield>
				</BuildingExtraYield>
			</BuildingExtraYields>
			<BuildingExtraCommerces> 
				<BuildingExtraCommerce> 
					<BuildingClass>BUILDINGCLASS_BANK</BuildingClass> 
					<CommerceType>COMMERCE_GOLD</CommerceType> 
					<iExtraCommerce>5</iExtraCommerce> 
				</BuildingExtraCommerce>
				<BuildingExtraCommerce> 
					<BuildingClass>BUILDINGCLASS_THEATRE</BuildingClass> 
					<CommerceType>COMMERCE_CULTURE</CommerceType> 
					<iExtraCommerce>-2</iExtraCommerce> 
				</BuildingExtraCommerce> 
			</BuildingExtraCommerces>
			<BuildingExtraHappies/>
			<BuildingExtraHealths/>
			<iHappy>0</iHappy>
			<iHealth>-1</iHealth>
			<iHurryAnger>0</iHurryAnger>
			<iHappyTurns>0</iHappyTurns>
			<iRevoltTurns>0</iRevoltTurns>
			<iMinPillage>0</iMinPillage>
			<iMaxPillage>0</iMaxPillage>
			<iFood>0</iFood>
			<iFoodPercent>0</iFoodPercent>
			<FreeSpecialistCounts/>
			<FeatureType>NONE</FeatureType>
			<iFeatureChange>0</iFeatureChange>
			<ImprovementType>NONE</ImprovementType>
			<iImprovementChange>0</iImprovementChange>
			<BonusType>NONE</BonusType>
			<iBonusChange>0</iBonusChange>
			<RouteType>NONE</RouteType>
			<iRouteChange>0</iRouteChange>
			<BonusRevealed>NONE</BonusRevealed>
			<BonusGift>NONE</BonusGift>
			<PlotExtraYields/>
			<iConvertOwnCities>0</iConvertOwnCities>
			<iConvertOtherCities>0</iConvertOtherCities>
			<iMaxNumReligions>-1</iMaxNumReligions>
			<iOurAttitudeModifier>0</iOurAttitudeModifier>
			<iAttitudeModifier>0</iAttitudeModifier>
			<iTheirEnemyAttitudeModifier>0</iTheirEnemyAttitudeModifier>
			<iPopulationChange>0</iPopulationChange>
			<AdditionalEvents/>
			<EventTimes/>
			<ClearEvents/>
			<PythonCallback/>
			<PythonExpireCheck>expireGeneralElection2</PythonExpireCheck>
			<PythonCanDo/>
			<PythonHelp/>
			<Button>,Art/Interface/Buttons/Process/Blank.dds,Art/Interface/Buttons/Beyond_the_Sword_Atlas.dds,8,5</Button>
			<iAIValue>1000</iAIValue>
		</EventInfo>

The errors I am getting so far:
1- No code errors in python when loading Civ4 Bts
2- No code errors in XML when laoding a game in Civ4 BtS
3- The bloody elections happen ever turn! despite Fierabras' code!
4- The expire code does not seem to function- libraries keep adding on science, factorys add phammers, etc, Every turn.

Bascially, the elections are happening too often, and the events chosen are Not expiring.

I have very very little experience or ability with python. Im reading and trying to learn it now, but im pretty much still right at the beginning. Most of my python scripting consists of seeing what works, copy/pasting and altering values to suit my events. But this has no precedent, that I can find....

Any help would be greatly appreciated!
HDK:(
 
Your posted Python code is missing all the indentation, but I've rewritten part of it to add print statements so you can see in PythonDbg.log what turns things are happening. Use this in place of the code you posted:

Code:
######## GENERAL_ELECTION ###########

import CvUtil

def canTriggerGeneralElection(argsList):
	kTriggeredData = argsList[0]
	turn = kTriggeredData.iTurn
	player = gc.getPlayer(kTriggeredData.ePlayer)
	
	CvUtil.PyPrint("trigger turn = %d" % turn)
	if (turn % 5 == 0):
		CvUtil.PyPrint("election!")
		return true
	return false
	
	# This is ignored because of "return false" above
	if (player.getCivilizationType() == gc.getInfoTypeForString("CIVILIZATION_AMERICA")):
		return true
	return false

def expireGeneralElection1(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	turn = kTriggeredData.iTurn
	
	CvUtil.PyPrint("triggered turn = %d, current turn = %d" % (turn, gc.getGame().getGameTurn()))
	if gc.getGame().getGameTurn() >= turn + 4
		CvUtil.PyPrint("expired")
		return true
	
	return false

def expireGeneralElection2(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	turn = kTriggeredData.iTurn
	
	CvUtil.PyPrint("triggered turn = %d, current turn = %d" % (turn, gc.getGame().getGameTurn()))
	if gc.getGame().getGameTurn() >= turn + 4
		CvUtil.PyPrint("expired")
		return true
	
	return false

You should expect something like this:

Code:
trigger turn = 10
election!
triggered turn = 10, current turn = 10   [I]-- not sure about this one[/I]
trigger turn = 11
triggered turn = 10, current turn = 11
trigger turn = 12
triggered turn = 10, current turn = 12
trigger turn = 13
triggered turn = 10, current turn = 13
trigger turn = 14
triggered turn = 10, current turn = 14
trigger turn = 15
election!
triggered turn = 10, current turn = 15
expired

I don't know how the expiration functions are called as I haven't looked at the event code at all yet. Is expiration part of the built-in event code?
 
Hi Emperor Fool!

Thank you very much fro the feedback, apologies for the long delay in replying.

Problems though:
1) Your code here does not work :/
- The event trigger fires EVERY turn, with the python code you provided above.
- However, If i change it back to the original code I had, using Fierebras' Zeus code, then it fires Once, waits # of turns and then fires again- ie, as it should.

I do not understand why your code does not work, but I had to strip it right down to "if (turn % 5 == 0) part, and also remove any Util/logging references.

Again, I do not understand why that was neccessary,as I know what you are doin- trying to log what the event is actually upto each turn. Sorry, but no dice...

2) I think my expirecheck is the wrong way to go- i need the python to actually UNDO the choice made at election time, thus giving a clean slate for the next election.

3) A develoment of this event would be to have the effects proposed at Election time to be a random amount, with a certain range of areas(health/culture/productionetc etc). This would prevent the boredom of seeing the exact same event every # of turns...

Thank you for the proposed code, though :)
HDK
 
You said above that your code fires the election every turn, but now it doesn't? What did you change?

The logging is what might help you figure out what's wrong, so I suggest getting it to work. What errors are you getting in PythonErr.log or on-screen when you leave those lines in?
 
You said above that your code fires the election every turn, but now it doesn't? What did you change?

- The exact code that you posted still resulted in election being fired every turn.
- So, I reverted back to ONLY Fireabras' code - ie. no check if player is American, no expire checks. This gave me an election every # of turns.
-The pythonerr.log is attached below, within the .rar file. This also contains the xml files, and both your python file and the python file with just fierabras code, that works.

The logging is what might help you figure out what's wrong, so I suggest getting it to work. What errors are you getting in PythonErr.log or on-screen when you leave those lines in?

- I never get any python errors onscreen with this event, when i use JUST fierebras' code; ie the Pythonerr.log is clear.
- WIth your suggested code i only get text in the pythonnerr.log; there are no python errors ingame.

***************
- As mentioned in my previous post I think the expirechecks are a (neccesary) red-herring, and are not enoughfor this event to work. It would be better to run this all through python, "under the radar" so to speak.
- So, the event would fire &
=Elections are called.
=Player picks an event
=Python eventually notes that 5 turns have passed since the event being chosen and an expire check runs (which ONLY clears the record of events which have run).
=Python again notes that 5 turns have passed since the event being chosen, ALSO undoes the EFFECTS of the event, WITHOUT notifiying the Player.
=On the 6th turn the event fires again.

..ad infinitum.

the critical parts are:
1) expiring the event 5 turns after the event fires.
2) THEN undo-ing the effects of the event chosen.

So, i reckon I can get the expirecheck to work. Its the UNDO-ing part i really need help with :/

Thank you very much for your feedback, it is vey helpfull:)
Best regards,
HDK
 

Attachments

  • Files.rar
    200.8 KB · Views: 59
Here's the error from my code:

Code:
  File "CvRandomEventInterface", line 4149
    if gc.getGame().getGameTurn() >= turn + 4
                                             ^
SyntaxError: invalid syntax

Note that the caret is pointing to just after the 4 at the end of the line. If tests must end with a colon :)) which I probably forgot to add.

Here's my updated code with the PyPrint statements change to "print" as you said these caused errors as well:

Code:
######## GENERAL_ELECTION ###########

def canTriggerGeneralElection(argsList):
	kTriggeredData = argsList[0]
	turn = kTriggeredData.iTurn
	player = gc.getPlayer(kTriggeredData.ePlayer)
	
	print("trigger turn = %d" % turn)
	if (turn % 5 == 0):
		print("election!")
		return true
	return false
	
	# This is ignored because of "return false" above
	if (player.getCivilizationType() == gc.getInfoTypeForString("CIVILIZATION_AMERICA")):
		return true
	return false

def expireGeneralElection1(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	turn = kTriggeredData.iTurn
	
	print("triggered turn = %d, current turn = %d" % (turn, gc.getGame().getGameTurn()))
	if gc.getGame().getGameTurn() >= turn + 4:
		print("expired")
		return true
	
	return false

def expireGeneralElection2(argsList):
	iEvent = argsList[0]
	kTriggeredData = argsList[1]
	turn = kTriggeredData.iTurn
	
	print("triggered turn = %d, current turn = %d" % (turn, gc.getGame().getGameTurn()))
	if gc.getGame().getGameTurn() >= turn + 4:
		print("expired")
		return true
	
	return false

As to how to undo the effects your events apply, I cannot help you without knowing what and how you apply the effects in the first place. Did I miss that above?
 
Great, thank you!

Ill check out this fix.

The xml fiels were attached in the last post- they have the events etc that show the effects (additional health/production etc).
ie effects are set up through xml.
they then need to be undone through python, in the background as it were, as I want to avoid too many popups to do with elections.

Regards,
HDK
 
So somewhere in Python or C++ (the SDK) is code that takes the information in the XML (EventInfo) and applies it to the game. You'll need to write code to undo those effects. In many cases it should be a simple matter of calling the same function with a negative value of what is in the EventInfo.

I haven't looked at that code, so I don't know if it's one giant function that applies all information in an EventInfo or if there are separate functions for each event type that apply only the EventInfo fields that they expect. If the latter, creating an undo function is easier.
 
hmmmm.....

I doubt its one giant function- that would be very inefficient.

To achieve the undo effect I imagine it would be done exactly as you say- a python run that causes a negative versions of the effects called in the xml eventinfos.

this,hopefully, is where You come in..:)

HDK
 
The reason to have it be one monolithic function that does everything is because the EventInfo is just a huge blob of data. If the effects are applied in separate functions, then only the "expected" effects can be applied for any given event.

Take the Truffles event that adds +1:commerce: to a plot IIRC. If you wanted to modify that event to also add +1:food: (assuming it doesn't do that already) would require modifying the function as well as the XML. With a single function to apply all effects, you could add any existing type of effect to any event without changing Python code.

If you can find and post the code that applies an event's effects, I can help. Otherwise you'll need to wait until next week when I have access to the Civ code.
 
Top Bottom