BUG 4.0 and the EventManager

Afforess

The White Wizard
Joined
Jul 31, 2007
Messages
12,239
Location
Austin, Texas
Okay, so EmperorFool, advised me that I wasn't supposed to be editing the CvEventManager in BUG 4.0. So, I'm trying to move the modcomp, War Prizes into my own module and load it from the init.xml file. I've got the War Prizes code pretty well isolated, in a python file named WarPrizes.py. Then, I added WarPrizes to the init.xml. I load the game and start a map. Initially, I got a few errors, but once I fixed those, I tested out War Prizes setting the odds to 100%. Nothing happened. (For those who aren't familiar, War Prizes gives a percent chance, out of 100, that an enemy ship will be captured after successful naval combat.) So, I'm assuming WarPrizes isn't really being loaded, or at least not called when it needs to be, but, because I pretty much have no python abilities, and am very new to BUG 4.0, I haven't the slightest clue as to what I'm doing wrong.

The Init.xml addition is this:

Code:
<events module="WarPrizes">    </events>

Here's the contents of WarPrizes.py

Code:
from CvPythonExtensions import *
import CvEventInterface
import CvUtil
import BugUtil

gc = CyGlobalContext()

class WarPrizes:
    def __init__(self, eventManager):
    
        eventManager.addEventHandler("Combat Result", self.onCombatResult)

    def onCombatResult(self, argsList):
        'Combat Result'
        pWinner,pLoser = argsList
        playerX = PyPlayer(pWinner.getOwner())
        unitX = PyInfo.UnitInfo(pWinner.getUnitType())
        playerY = PyPlayer(pLoser.getOwner())
        unitY = PyInfo.UnitInfo(pLoser.getUnitType())
## mechaerik War Prize ModComp START##
        pPlayer = gc.getPlayer(pWinner.getOwner())
        pPlayerLoser = gc.getPlayer(pLoser.getOwner())
        if not (gc.getPlayer(pWinner.getOwner()).isBarbarian()):
            if (unitX.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_WOODEN_SHIPS")) or (unitX.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_STEAM_SHIPS")) or (unitX.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_DIESEL_SHIPS")) or (unitX.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_NUCLEAR_SHIPS")):
                if (unitY.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_WOODEN_SHIPS")) or (unitY.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_STEAM_SHIPS")) or (unitY.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_DIESEL_SHIPS")) or (unitY.getUnitCombatType() == gc.getInfoTypeForString("UNITCOMBAT_NUCLEAR_SHIPS")):
                    if not (unitX.getUnitClassType() == gc.getInfoTypeForString("UNITCLASS_PRIVATEER")):
                        if not (unitY.getUnitClassType() == gc.getInfoTypeForString("UNITCLASS_PRIVATEER")):
                            if CyGame().getSorenRandNum(100, "Bob") <= 100:
                                iUnit = pLoser.getUnitType()
                                newUnit = pPlayer.initUnit(pLoser.getUnitType(), pWinner.getX(), pWinner.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.NO_DIRECTION)
                                newUnit.finishMoves()
                                newUnit.setDamage(50, pWinner.getOwner())
                                CyInterface().addMessage(pWinner.getOwner(),false,20,CyTranslator().getText("We've taken an enemy ship as a prize!",()),'',0,'Art/Interface/Buttons/General/happy_person.dds',ColorTypes(gc.getInfoTypeForString("COLOR_BLUE")), pWinner.getX(), pWinner.getY(), True,True)
                                CyInterface().addMessage(pLoser.getOwner(),false,20,CyTranslator().getText("One of our ships has been taken as a prize!",()),'',0,'Art/Interface/Buttons/General/warning_popup.dds',ColorTypes(gc.getInfoTypeForString("COLOR_RED")), pLoser.getX(), pLoser.getY(), True,True)  
## War Prize Modcomp END##
 
The event type (first parameter to addEventHandler() must match the one in CvEventManager exactly. In this case you want

Code:
eventManager.addEventHandler("[B]combatResult[/B]", self.onCombatResult)

Note that a) there is no space and b) it starts with a lowercase c.
 
Oh, one last thing. If I don't want to edit the Init.xml file, can I just set up a new xml file in the config folder and add that
<events module="WarPrizes"> </events>
to it? How exactly would I set that up?
 
I also need help with my other python mod, a modified version of the Circus Hagenbeck (from The_J) and can't seem to get it to work.

Here's the python.

Spoiler :
Code:
from CvPythonExtensions import *
import CvEventInterface
import CvUtil
import BugUtil
import PyHelpers


gc = CyGlobalContext()
localText = CyTranslator()
PyPlayer = PyHelpers.PyPlayer
PyInfo = PyHelpers.PyInfo
###################################################


class WorldFair:
	def __init__(self, eventManager):
	
		eventManager.addEventHandler("GameStart", self.onGameStart)
		eventManager.addEventHandler("BeginGameTurn", self.onBeginGameTurn)
		self.oldcity = [-1,-1]

	def onGameStart(self, argsList):
		'Called at the start of the game'
		if (gc.getGame().getGameTurnYear() <> gc.getDefineINT("START_YEAR")):
			for i in range (gc.getMAX_PLAYERS()):
				player = gc.getPlayer(iPlayer)
			if player.isAlive():
				numbuildings = player.countNumBuildings(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"))
				if numbuildings>0:
					for iCity in range(player.getNumCities()):
						pCity = player.getCity(iCity)
						if pCity.getNumBuilding(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"))>0:
							self.oldcity = [iPlayer,iCity]
							return                                                     
                                        
 
	def onBeginGameTurn(self, argsList):
		'Called at the beginning of the end of each turn'
		iGameTurn = argsList[0]	
		if (CyGame().getTurnYear(iGameTurn)>=1851) and ( iGameTurn % 3 ==0 ):
			counter = 0
			while True:
				counter = counter+1
				if counter>=100:break
				dice = gc.getGame().getMapRand()
				iPlayer = dice.get(gc.getMAX_PLAYERS (), "Players")
				pPlayer = gc.getPlayer(iPlayer)
				if pPlayer.isNone():continue
				if pPlayer.isAlive():
					iCity = dice.get(pPlayer.getNumCities () , "Cities" )
					pCity = pPlayer.getCity(iCity)
					if pCity.isNone():continue
					if pCity.getPopulation ()<=10:continue
					pCity.setNumRealBuilding(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"),1)
					CyInterface().addMessage(iPlayer,false,20,CyTranslator().getText("TXT_KEY_CIRCUS_MOVED",(pCity.getName (),pCity.getName ())),'',0,'Art/Interface/Buttons/General/happy_person.dds',ColorTypes(gc.getInfoTypeForString("COLOR_GREEN")), pCity.getX(), pCity.getY(), True,True) 
					if self.oldcity <>[-1,-1]:
						otherplayer = gc.getPlayer(self.oldcity[0])
						othercity = otherplayer.getCity(self.oldcity[1])
						othercity.setNumRealBuilding(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"),0)
						CyInterface().addMessage(self.oldcity[0],false,20,CyTranslator().getText("TXT_KEY_CIRCUS_LOST",(othercity.getName (),othercity.getName ())),'',0,'Art/Interface/Buttons/General/warning_popup.dds',ColorTypes(gc.getInfoTypeForString("COLOR_RED")), othercity.getX(), othercity.getY(), True,True)
						self.oldcity = [iPlayer,iCity]                                 
                                        
					break

Here are the errors:
Spoiler :

Traceback (most recent call last):

File "CvEventInterface", line 30, in onEvent

File "BugEventManager", line 330, in handleEvent

File "BugEventManager", line 335, in _dispatchEvent

File "BugEventManager", line 347, in _handleDefaultEvent

File "WorldFair", line 41, in onGameStart

NameError: global name 'iPlayer' is not defined
ERR: Python function onEvent failed, module CvEventInterface
Traceback (most recent call last):

File "CvEventInterface", line 30, in onEvent

File "BugEventManager", line 330, in handleEvent

File "BugEventManager", line 335, in _dispatchEvent

File "BugEventManager", line 347, in _handleDefaultEvent

File "BugEventManager", line 419, in onGameUpdate

File "BugEventManager", line 399, in checkActivePlayerTurnStart

File "BugEventManager", line 325, in fireEvent

File "BugEventManager", line 335, in _dispatchEvent

File "BugEventManager", line 347, in _handleDefaultEvent

File "Civ4lerts", line 737, in onBeginActivePlayerTurn

File "Civ4lerts", line 759, in _getMaxGoldTrade

AttributeError: GoldTrade instance has no attribute 'maxGoldTrade'
ERR: Python function onEvent failed, module CvEventInterface
 
The onGameStart() function is a bit broken. The indentation--while all tabs so that's good--is incorrect, and the loop counter is i but used as iPlayer. Also, self.oldcity should be reset first and then set only if a WorldsFair building is found to keep it from remembering the city from a previous game. I suspect this function should also be called when a game is loaded, too.

Code:
	def onGameStart(self, argsList):
		'Called at the start of the game'
		self.oldcity = [-1,-1]
		if (gc.getGame().getGameTurnYear() <> gc.getDefineINT("START_YEAR")):
			for iPlayer in range (gc.getMAX_PLAYERS()):
				player = gc.getPlayer(iPlayer)
				if player.isAlive():
					numbuildings = player.countNumBuildings(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"))
					if numbuildings>0:
						for iCity in range(player.getNumCities()):
							pCity = player.getCity(iCity)
							if pCity.getNumBuilding(gc.getInfoTypeForString("BUILDING_WORLDSFAIR"))>0:
								self.oldcity = [iPlayer,iCity]

The second exception in Civ4lerts is caused by the GameStart event failing. The latest version of BUG in SVN fixes this by catching errors in each event handler separately and continuing to call other event handlers.
 
If I don't want to edit the Init.xml file, can I just set up a new xml file in the config folder.

Yes, but you need to load it from init.xml using

Code:
<load mod="WarPrizes"/>

where "WarPrizes" is the name of the file minus the ".xml" file type extension. All of the BUG features are loaded this way, so there are plenty of examples.
 
Yes, but you need to load it from init.xml using

Code:
<load mod="WarPrizes"/>
where "WarPrizes" is the name of the file minus the ".xml" file type extension. All of the BUG features are loaded this way, so there are plenty of examples.

Dang, I was hoping I could add a new file in the config folder, with the same type of entry, so I didn't have to overwrite the existing file. See, it's very important to us modmoders to allow interoperability with minimal user input. If two modmod's edit the init.xml, one will overwrite the other, and a some user will be slightly shocked.

I always strive to never edit an existing file unless absolutely necessary. All my XML changes are modular, and my CvGameCoreDLL may add new features, but they are all gameoptions.

Once I learned about the new python loading system, I was hopeful that this would allow me to be completely free from overwriting existing files, but I guess my hopes were premature.

Anyways, I would like you to put this in as a feature request, so that I could just add an "Afforess.xml" file into the config folder, with only my events modules in it, and be read when the game starts.
 
I have long considered adding an auto-loading feature. The main reason I have not done it yet is that some features in BUG require a specific load order due to dependencies. I have an idea to solve this problem that doesn't involve file naming to achieve ordering, but it is non-trivial.

My initial design is to have three folders inside Config:

  • Core
  • BUG
  • Mods
  • User
Core contains only "BUG Core.xml" and "BUG Config.xml" initially. These files set up the objects used by mod features.

BUG contains all of the BUG features and modcomps merged by the BUG team: Scoreboard, Strategy Layer, Civ4lerts, Reminders, etc.

Mods is for mods based on BUG. It will be shipped by BUG empty.

User is for end-users to add their mods and will also ship empty.

It might be overkill to separate Mods and User, but I may as well maximize flexibility now, eh?

For dependency my thought is to allow any XML file to specify the XML files that must be processed before it using this:

Code:
<mod ... requires="Reminder, Scoreboard, ...">

If any mod listed in the requires attribute fails to load or isn't present, the mod will not load. Otherwise the mod will be forced to load after all those listed have loaded.

Does this seem like it will satisfy the modmod community?
 
Does this seem like it will satisfy the modmod community?

It will (for me. I can't speak for everyone). Python has long been a sore point of contention with modmods.

I can't wait to see that feature in the next iteration of BUG.
 
I have long considered adding an auto-loading feature. The main reason I have not done it yet is that some features in BUG require a specific load order due to dependencies. I have an idea to solve this problem that doesn't involve file naming to achieve ordering, but it is non-trivial.

My initial design is to have three folders inside Config:

  • Core
  • BUG
  • Mods
  • User
Core contains only "BUG Core.xml" and "BUG Config.xml" initially. These files set up the objects used by mod features.

BUG contains all of the BUG features and modcomps merged by the BUG team: Scoreboard, Strategy Layer, Civ4lerts, Reminders, etc.

Mods is for mods based on BUG. It will be shipped by BUG empty.

User is for end-users to add their mods and will also ship empty.

It might be overkill to separate Mods and User, but I may as well maximize flexibility now, eh?

For dependency my thought is to allow any XML file to specify the XML files that must be processed before it using this:

Code:
<mod ... requires="Reminder, Scoreboard, ...">
If any mod listed in the requires attribute fails to load or isn't present, the mod will not load. Otherwise the mod will be forced to load after all those listed have loaded.

Does this seem like it will satisfy the modmod community?

Any progress on this?

I also think a simpler fix would be to not give error messages or fail loading the python if an entry in the Init.xml references a Python file that doesn't exist.

Kind of like the way MLF files work.
 
None yet. My job search has finally taken off--I've been on six interviews in the past 3 days plus have more this week. Most likely the amount of time I can spend on BUG will significantly decrease in the near future. I still want to get this and the UI nailed down; everything else on the list is new game features which can always be added later.
 
None yet. My job search has finally taken off--I've been on six interviews in the past 3 days plus have more this week. Most likely the amount of time I can spend on BUG will significantly decrease in the near future. I still want to get this and the UI nailed down; everything else on the list is new game features which can always be added later.

You've been unemployed? That really sucks. Well, I hope those interviews go well.

I was just thinking that a simple hack-y way to get around the init.xml issues for now before you devolepe a new system would be to force it to not display an error messages if a python file it referenced was missing, and force it to keep loading even if files were missing.
 
Well, good to hear that you're closer to getting a job! :)

What does this mean for BULL, I wonder? I've noticed that you removed BULL from your sig...
 
I removed BULL from my sig only because it was next to the announcement of Mac support. AlanH thought (and I agreed) that this might give Mac users false hope that BULL would work on the Mac. I still want to release BULL when once the job hunt dust settles.
 
Back
Top Bottom