View Full Version : [PYTHONCOMP] Python utility library (includes INI file reading)
Dr Elmer Jiggle Feb 01, 2006, 07:42 AM While developing my alerts mod, I had occasion to implement several Python modules that I think will be useful to a wider audience. The modules attached here are also available within that mod, but I've packaged them separately here to keep out the code that's unique to the alerts mod. This thread will also serve as a more appropriate place for questions about these modules.
I've included a brief description of each module below, but the code is extensively documented, so I'm not going to duplicate that here.
CvPath.py
Exposes variables that point to various interesting directories within the Civilziation 4 hierarchy. For example, there are variables for the install directory, the user directory, and the complete Python search path.
CvConfigParser.py
Provides convenient INI file handling. Usage is as simple as
config = CvConfigParser.CvConfigParser("Foo.ini")
# default to 5 if not specified in the .INI file
myInt = config.getint("My Section", "My Int Option", 5)
Advantages of INI files over other approaches such as embedding the settings directly in Python or using an XML file include
Users tend to be less intimidated by INI files
Users tend to be less likely to completely mess up an INI file
The game's anti-cheating mechanism prevents editing of mod source files but not INI files, so this method is compatible with Game of the Month
CvCustomEventManager.py
Implements an extensible event manager. If multiple mods exist that all override the default event manager, you can simply register their needs with calls such as
em1.addEventHandler("cityGrowth", em1.onCityGrowth)
em1.addEventHandler("cityDoTurn", em1.onCityDoTurn)
em2.addEventHandler("cityGrowth", em2.onCityGrowth)
Enjoy.
Changes:
2006-Aug-11
Updated for Warlords compatibility.
2006-May-1, v1.4
Added validity checking for event types in the add, remove, and setEventHandler functions. An exception is thrown if the event type string is incorrect.
Added a setPopupHandler function for defining new popup dialog handlers.
2006-Mar-12, v1.3
Fixed a bug with determining the user directory when the installation directory has been renamed. Thanks to 12monkeys
2006-Feb-28, v1.2
Fixed the search path for .INI files. The parser was searching the Assets directories instead of their respective parent directories. Thanks to jray
Restored the 6 Boolean state flags to the event handler front end. Thanks to jray
2006-Feb-2, v1.1
Added the CvModName feature to CvConfigParser to allow discovery of the active mod name under certain circumstances.
Fixed exception handling in INI file reader.
DrEJlib.zip (http://forums.civfanatics.com/downloads.php?do=file&id=2292)
TheLopez Feb 01, 2006, 08:19 AM FYI, I highly recommend using this library and I am going to implemented in my MODCOMPs that provide configuration options. One key learning I have to give to the group that I learned though adding INI parsing in one of my MODCOMPs:
If you want to use INI files but don't want to have them in the install dir you should change the line in CvPath.py:
assetsPath = [userAssetsDir, installAssetsDir]
to
assetsPath = [userAssetsDir, installAssetsDir, installModsDir]
This way all of the mod component INI files can live in the mod directory until we can figure out a way to get the active mod name.
Dr Elmer Jiggle Feb 01, 2006, 08:43 AM If you want to use INI files but don't want to have them in the install dir you should change the line in CvPath.py:
assetsPath = [userAssetsDir, installAssetsDir]
to
assetsPath = [userAssetsDir, installAssetsDir, installModsDir]
This way all of the mod component INI files can live in the mod directory until we can figure out a way to get the active mod name.
Good point. I've actually had an equivalent change at various points, and I keep going back and forth on whether I like it or not. If you want it to work that way though, I think I'd suggest a few slight alterations in your approach.
1) You should probably include userModsDir as well, since users would typically be installing their mods there. To clarify, userModsDir is C:/Documents and Settings/User/My Documents/My Games/Sid Meier's Civilization 4/Mods. installModsDir is C:/Program Files/Firaxis Games/Sid Meier's Civilization 4/Mods.
2) The mods directories should maybe come first in the list, before the other directories. In general, when loading Python and XML files, the mods directories override the regular Assets and CustomAssets directories, so I think that would make sense for INI files too. The only real problem with that is since there's no way of knowing when a mod is active, your Mod INI file would override the main one even when you aren't actually running a mod.
3) Finally, another thought is that it might be better to make the change in the CvConfigParser code instead of in CvPaths. By changing assetsPath directly, you impact anything else that uses assetsPath, including, for example, the pythonPath variable. With your change, pythonPath will include all Python directories in every mod. One way to get around this would be to make the following change around line 63 in the CvConfigParser constructor.
iniPath = [userModsDir, installModsDir] + assetsPath
filenames = [os.path.join(dir, filename)
for dir in iniPath]
I haven't tested that, but I think that code is correct and would do what you want.
rcuddy Feb 01, 2006, 09:12 AM It seems it would not take much to turn this into an extensions type of framework. For example:
Mods don't mess with the events manager directly at all
A standard events manager in the 'framework' looks at all files in a standard named directory say 'exts' and creates and instance of any classes it finds there that extends correct base class.
As part of the init for these classes they register their events.That would make merging of python mods very simple without any toe stepping. It's a pretty common design and I'm sure I'm glossing over any number of issues needing consideration but it seems a reasonable approach.
TheLopez Feb 01, 2006, 09:19 AM 3) Finally, another thought is that it might be better to make the change in the CvConfigParser code instead of in CvPaths. By changing assetsPath directly, you impact anything else that uses assetsPath, including, for example, the pythonPath variable. With your change, pythonPath will include all Python directories in every mod. One way to get around this would be to make the following change around line 63 in the CvConfigParser constructor.
iniPath = [userModsDir, installModsDir] + assetsPath
filenames = [os.path.join(dir, filename)
for dir in iniPath]
I haven't tested that, but I think that code is correct and would do what you want.
Dr Elmer Jiggle, ok I tried making the suggested changes and tested them. There were a couple of issues. Here is the fixed code:
iniPath = [CvPath.userModsDir, CvPath.installModsDir] + CvPath.assetsPath
filenames = [os.path.join(dir, filename)
for dir in iniPath]
Another issue I ran into is when a key=value pair does not exist in the INI file the game still throws the exception and the default value does not get set. Any ideas?
Dr Elmer Jiggle Feb 01, 2006, 09:27 AM A standard events manager in the 'framework' looks at all files in a standard named directory say 'exts' and creates and instance of any classes it finds there that extends correct base class.
That's how I'd prefer for this to work, actually. There are a few other areas where you could take a similar approach. For example, the options screen could be extended to allow mods to plug in a tab with their custom settings.
SimCutie has actually implemented something like that for event managers already. I'll see if I can find a link for that.
The main problem (really the only problem I can think of right now) with that is there's no known way to determine the active mod. So the idea of searching through all files doesn't work, because you don't know where to search. For example, should you look in Mods/Foo/Assets/Python, Mods/Bar/Assets/Python, or neither because this is just a regular non-modded game?
SimCutie's code has an interesting way of dealing with that problem. Basically it attempts to load Python modules from the Mod directories, and when one succeeds it assumes that means it found the active mod directory. At first glance that seems like an incredibly devious but also ingenous solution, but I'm convinced that it doesn't work.
The problem is, what happens if several mods contain a Python module with the same name? This is actually quite likely. Consider for example the number of mods that will contain a CvCustomEventManager and/or a CvEventInterface module. Now when you load "CvEventInterface" successfully, how do you know which mod that came from or if it came from a mod at all? So for this idea to work, you would need each mod to contain at least one uniquely named Python module that isn't present in any other mod.
I tried running with that idea by implementing some nasty code that dynamically creates a uniquely named Python module in each mod directory. I made files like a.py, aa.py, etc. They wouldn't load, though. I think the game's module loader caches the list of available files, so if you create one while the game is running, it doesn't pick up on that.
Anyway, the point of this long essay is that it's a good idea, but I think it needs a little help from Firaxis before it can really work the way it should. If Firaxis provides an API function to give the active mod name, or if someone figures out a way to get that, then I think we've got something to work with.
Edit: SimCutie's event manager code is in this thread: http://forums.civfanatics.com/showthread.php?t=147018
TheLopez Feb 01, 2006, 09:30 AM Anyway, the point of this long essay is that it's a good idea, but I think it needs a little help from Firaxis before it can really work the way it should. If Firaxis provides an API function to give the active mod name, or if someone figures out a way to get that, then I think we've got something to work with.
Dr Elmer Jiggle, Do you know what gets loaded first? XML files or python files?
Dr Elmer Jiggle Feb 01, 2006, 09:35 AM Another issue I ran into is when a key=value pair does not exist in the INI file the game still throws the exception and the default value does not get set. Any ideas?
I think I've tested that, but I'll have to see. Maybe I never verified that it actually works. :blush: Do you have a copy of the exception backtrace from a PythonErr.log file? That might help give me an idea of what's wrong. Just looking at the code it seems like it should be working.
Dr Elmer Jiggle Feb 01, 2006, 10:04 AM Dr Elmer Jiggle, Do you know what gets loaded first? XML files or python files?
I think it can depend on the situation, but definitely some Python files get loaded before the first XML file. I noticed this at one point when I was trying to configure some settings using GlobalDefinesAlt.xml. The settings in the XML weren't being picked up properly unless I delayed looking for them until after the onInit event handler was called. But as far as I know, Python files are loaded whenever you hit the first "import Foo", so if that happens later, then the XML files have probably already been loaded.
Dr Elmer Jiggle Feb 01, 2006, 12:14 PM Another issue I ran into is when a key=value pair does not exist in the INI file the game still throws the exception and the default value does not get set. Any ideas?
That does seem to be working for me. I temporarily moved my INI file, and the code still ran successfully and without exceptions.
I don't think my mod actually uses anything besides the getint function. Are you having problems with a different one? They all work mostly the same way, but there are slight variations (especially in the boolean one) that might be causing an unexpected problem since they haven't been tested carefully.
Do you have a code snippet you can post? You need to pass 3 arguments to each get* function if you want to include a default value.
get(sectionName, optionName, default = None)
getint(sectionName, optionName, default = None)
getfloat(sectionName, optionName, default = None)
getboolean(sectionName, optionName, default = None)
where sectionName and optionName are strings that refer to the INI file value you want. default is whatever type you expect as the result of the get method (ex. int for getint, boolean for getboolean, etc.).
TheLopez Feb 01, 2006, 01:14 PM I think I've tested that, but I'll have to see. Maybe I never verified that it actually works. :blush: Do you have a copy of the exception backtrace from a PythonErr.log file? That might help give me an idea of what's wrong. Just looking at the code it seems like it should be working.
I don't have access to upload it right now, my laptop doesn't want to connect to our corporate network for whatever reason. I will add it
<HERE>
when I get home
TheLopez Feb 01, 2006, 01:19 PM I think it can depend on the situation, but definitely some Python files get loaded before the first XML file. I noticed this at one point when I was trying to configure some settings using GlobalDefinesAlt.xml. The settings in the XML weren't being picked up properly unless I delayed looking for them until after the onInit event handler was called. But as far as I know, Python files are loaded whenever you hit the first "import Foo", so if that happens later, then the XML files have probably already been loaded.
Ok, then I know how we can get the active mod name added. What if we added the active mod name in an XML info file like you are doing so in your Civ4AlertTextInfo.xml file and extract it using the CyTranslator class and its getText method?
TheLopez Feb 01, 2006, 01:23 PM That does seem to be working for me. I temporarily moved my INI file, and the code still ran successfully and without exceptions.
I don't think my mod actually uses anything besides the getint function. Are you having problems with a different one? They all work mostly the same way, but there are slight variations (especially in the boolean one) that might be causing an unexpected problem since they haven't been tested carefully.
Do you have a code snippet you can post? You need to pass 3 arguments to each get* function if you want to include a default value.
get(sectionName, optionName, default = None)
getint(sectionName, optionName, default = None)
getfloat(sectionName, optionName, default = None)
getboolean(sectionName, optionName, default = None)
where sectionName and optionName are strings that refer to the INI file value you want. default is whatever type you expect as the result of the get method (ex. int for getint, boolean for getboolean, etc.).
Here is the code i am using:
bHighlightForcedSpecialists = config.getboolean("Specialist Stacker", "HighlightForcedSpecialists",True)
The key=value pair does not exist in my INI file.
EDIT:
Here is another couple of lines. I tried:
SPECIALISTS_STACK_WIDTH = config.getint("Specialist Stacker", "SpecialistStackWidth", default = 15)
and this:
SPECIALISTS_STACK_WIDTH = config.getint("Specialist Stacker", "SpecialistStackWidth", 15)
neither worked.
Dr Elmer Jiggle Feb 01, 2006, 02:55 PM Ok, then I know how we can get the active mod name added. What if we added the active mod name in an XML info file like you are doing so in your Civ4AlertTextInfo.xml file and extract it using the CyTranslator class and its getText method?
Yes! I think that will work. I'm not sure if you're suggesting that the mod author should create an XML file with the correct setting in it or that we should create one dynamically at startup for each mod and see which one gets loaded.
The first approach is obviously much easier, but it will only work if the mod author goes through the trouble of creating the right XML file. At that point, there are lots of ways you could do it. You could, for example, try to "import ActiveModName" which would be a trivial Python file that does nothing except set a variable to the active mod name. Once you're willing to force mod authors to cooperate with the protocol, it's easy. The problem is making it also work with mods like "American Revolution" that you have no control over.
Creating a special XML file for each mod during startup is godawful ugly and at the same time absolutely brilliant. ;) It might take some experimentation to figure out the best way of making sure the XML files get created before the game tries to read the XML files, but it seems like it might do the job.
I'm going to see if I can get a prototype of this working tonight.
Dr Elmer Jiggle Feb 01, 2006, 02:59 PM Here is another couple of lines. I tried:
SPECIALISTS_STACK_WIDTH = config.getint("Specialist Stacker", "SpecialistStackWidth", default = 15)
and this:
SPECIALISTS_STACK_WIDTH = config.getint("Specialist Stacker", "SpecialistStackWidth", 15)
I'm not sure if the first way is supposed to work or not (I don't know enough Python to be sure), but the second definitely should. That's exactly how I'm using it in my code. I also realized that I'm using both getint and getboolean, so as far as I know both of those work correctly.
I have a theory. How/where are you initializing config? Are you sure it has a value at the point where you're using it? Do you perhaps need to use self.config or some other variation to make it work? What kind of exception are you getting?
TheLopez Feb 01, 2006, 05:42 PM I'm not sure if the first way is supposed to work or not (I don't know enough Python to be sure), but the second definitely should. That's exactly how I'm using it in my code. I also realized that I'm using both getint and getboolean, so as far as I know both of those work correctly.
I have a theory. How/where are you initializing config? Are you sure it has a value at the point where you're using it? Do you perhaps need to use self.config or some other variation to make it work? What kind of exception are you getting?
Here's the exception trace:
Traceback (most recent call last):
File "CvScreensInterface", line 63, in showMainInterface
File "CvMainInterface", line 190, in interfaceScreen
File "CvConfigParser", line 83, in getint
File "CvConfigParser", line 102, in _wrappedGet
File "D:\main\Civilization4\Assets\Python\System\ConfigP arser.py", line 321, in getint
File "D:\main\Civilization4\Assets\Python\System\ConfigP arser.py", line 318, in _get
File "CvConfigParser", line 78, in get
File "CvConfigParser", line 102, in _wrappedGet
File "D:\main\Civilization4\Assets\Python\System\ConfigP arser.py", line 520, in get
ConfigParser.NoOptionError: No option 'specialist stack width' in section: 'Specialist Stacker'
Yes! I think that will work. I'm not sure if you're suggesting that the mod author should create an XML file with the correct setting in it or that we should create one dynamically at startup for each mod and see which one gets loaded.
The mod author should create an XML file with the correct setting in it. It really isn't that much to ask from mod authors, epecially when you balance the benefits of being able to provide them with the ability to let people configure their mods using INI files instead modifying python files.
Dr Elmer Jiggle Feb 01, 2006, 06:02 PM I think I see what the problem is. Try changing the _wrappedGet function in CvConfigParser as shown below. The change is the addition of parentheses in the except clause. Apparently if you leave out the parentheses it's still correct syntax, but the meaning is completely different.
def _wrappedGet(self, getter, section, option, default, *args, **kwargs):
"""Wraps the specified getter function with an exception handler
and returns a default value if NoSectionError or NoOptionError
is raised.
"""
try:
return getter(section, option, *args, **kwargs)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
if (default != None):
return default
else:
raise
Assuming this fixes it, I'll upload a revised version (much) later tonight.
TheLopez Feb 01, 2006, 07:24 PM I think I see what the problem is. Try changing the _wrappedGet function in CvConfigParser as shown below. The change is the addition of parentheses in the except clause. Apparently if you leave out the parentheses it's still correct syntax, but the meaning is completely different.
def _wrappedGet(self, getter, section, option, default, *args, **kwargs):
"""Wraps the specified getter function with an exception handler
and returns a default value if NoSectionError or NoOptionError
is raised.
"""
try:
return getter(section, option, *args, **kwargs)
except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
if (default != None):
return default
else:
raise
Assuming this fixes it, I'll upload a revised version (much) later tonight.
Dr Elmer Jiggle, I have validated the code fix and it works just like it should. Thanks for your help.
Dr Elmer Jiggle Feb 02, 2006, 08:04 AM The mod author should create an XML file with the correct setting in it. It really isn't that much to ask from mod authors, epecially when you balance the benefits of being able to provide them with the ability to let people configure their mods using INI files instead modifying python files.
Yeah, I agree with you. I keep reminding myself that since the perfect solution isn't possible, the goal is to create the least bad solution, and this would clearly be less bad than having no way at all of getting the active mod name.
I tried some experiments with using an XML file, and they weren't working out well. There are timing issues that make the system very fragile. As I mentioned before, the XML files seem to be loaded fairly late in the startup process, so if you try to initialize the active mod name too early, you get the wrong value.
I'm thinking of using a Python module instead. For example, I can do something like (this code is untested, just roughed in now as an example):
try:
import CvModName
activeModName = CvModName.modName
except:
pass
So if the module loads successfully (and contains a variable named modName), the active mod name is set. Otherwise it just fails quietly, and activeModName stays set to None just like it does now. An example CvModName module would be as simple as:
# CvModName.py
modName = "My Mod Name"
I find this approach kind of ugly and klugy looking, but like I said before, it might be the least bad option available. At least it would give you some way of getting an INI file to load from the Mods/My Mod Name directory. If I can verify that it works, I'll probably fire something up at lunch or later today.
TheLopez Feb 02, 2006, 09:24 AM Dr Elmer Jiggle,
Following your example I tested your code and it seems to work.
Basically, I created created the file CvModuleName.py
# CvModName.py
modName = "Specialist Stacker"
I added this code after line 98 in the CvPath.py file to use the active mod name.
try:
import CvModName
activeModName = CvModName.modName
except:
pass
Changed the CvConfigParser __init__ method so it will only use the CvPath.installActiveModDir to look for the variables.
To test this change I left my "Stacked Specialists Config.ini" file in the "Stacked Specialists" mod directory and did two tests, one with the highlight forced specialists set to true and with it set to false, both providing the expected results.
Anyone using or planning to use the "Stacked Specialists" mod component, expect a new release tonight using this new methodology.
Dr Elmer Jiggle Feb 02, 2006, 03:33 PM Following your example I tested your code and it seems to work.
Thanks. I've uploaded a new version of the Zip file. This contains the fix to the exception handling for missing INI file options and the updates for limited discovery of the active mod name.
I have a few ideas for how to improve the mod name discovery, but they may turn out to be too complicated to be worthwhile. Really, although I don't like this approach because it basically boils down to "I don't know, you tell me the name," the truth is it's probably adequate for any real situation.
TheLopez Feb 04, 2006, 09:54 AM Really, although I don't like this approach because it basically boils down to "I don't know, you tell me the name," the truth is it's probably adequate for any real situation.
I agree that this isn't the optimal solution but at least it lets mod makers get the active mod name programatically now.
12monkeys Mar 11, 2006, 06:24 PM Today I used that INI Parser the first time and had the problem, that the INI file I created has not been found in my user directory. After some investigation I found out, that the reason is my non-default installation directory.
Usually the directory name of the installation is "Sid Meier's Civilization 4". During the installation I changed that to "Civilization 4". There is no problem with the install directory, because the name is saved in the regisitry and is read out by your function _getInstallDir(). But the problem is, that the user directory in "My Documents" is also renamed into "Civilization 4" and this is not handeled by the _getUserDir() function.
I did made a small change in the _getUserDir() function and attached it. The change is marked with "#12monkeys begin/end". Maybe you want to take it over into your version.
However, the rest is working great and I will start to use it for my other mods as well. Good Job :goodjob:.
TheLopez Mar 11, 2006, 06:50 PM :clap: :clap: And we have another convert... :clap: :clap:
Dr Elmer Jiggle Mar 11, 2006, 10:11 PM However, the rest is working great and I will start to use it for my other mods as well. Good Job :goodjob:.
Thanks. I'm going to fold your fix into the official version.
There was another bug in CvConfigParser.py that had to do with the directory search path too. I didn't think I had updated the attachment in this thread to fix that yet, but you seem to have the fix in your pathfinder mod. Did you find that fix on your own, or am I confused? Maybe you got the files from my Alerts mod? That has the latest code.
12monkeys Mar 12, 2006, 02:51 AM No, I took the code from the Mercenaries Mod from TheLopez, because he had the idea to implement the INI stuff (was the quickest path to it ;))
TheLopez Mar 12, 2006, 05:23 AM Merc Mod is/was using the latest and greatest version. Let me know if I need to update it with newer code.
Dr Elmer Jiggle Mar 12, 2006, 08:24 AM Thanks again. I updated the attachment (also the one for my Alerts mod) to include this fix. FYI, I used os.path.basename instead of rfind to get the directory name, but otherwise it's pretty much the same as what you did.
TheLopez, you didn't before, but now you need to update. :)
12monkeys Mar 12, 2006, 03:06 PM ... FYI, I used os.path.basename instead of rfind to get the directory name, but otherwise it's pretty much the same as what you did. ...
I'm fine with that, of course. I don' have much experience with those standard python libs, so I somtimes invent the wheel again :)
TheLopez Mar 12, 2006, 05:15 PM Dr Elmer Jigger,
Before I begin, I want to give credit where credit is due, most of the code did come from Vadus's posting here: http://forums.civfanatics.com/showthread.php?p=3746502#post3746502.
Here is some new code that you might want to fold into your official release. Basically it allows players to define their own custom events and have them handled. The primary reason why I did these changes was to allow generic handling of popup code. Here are the changes:
class CvCustomEventManager(CvEventManager.CvEventManager , object):
# < NEW CODE START >
CustomEvents = {}
# < NEW CODE END >
...
...
and
# < NEW CODE START >
def beginEvent( self, context, argsList=-1 ):
"Begin Event"
if(self.CustomEvents.has_key(context)):
return self.CustomEvents[context][2](argsList)
else:
return CvEventManager.CvEventManager.beginEvent(self, context, argsList)
def applyEvent( self, argsList ):
'Apply the effects of an event '
context, playerID, netUserData, popupReturn = argsList
if(self.CustomEvents.has_key(context)):
entry = self.CustomEvents[context]
# the apply function
return entry[1]( playerID, netUserData, popupReturn )
else:
return CvEventManager.CvEventManager.applyEvent(self, argsList)
def addCustomEventDefinition(self, eventType, eventDefinition):
self.CustomEvents[eventType] = eventDefinition
def removeCustomEventDefinition(self, eventType):
del self.CustomEvents[eventType]
def setCustomEventDefinition(self, eventType, eventDefinition):
self.CustomEvents[eventType] = eventDefinition
# < NEW CODE END >
I have been testing the code all weekend and works for me. Here is an example of how the custom event definition is set up in one of my classes:
class CvGreatStatesmanEventManager:
def __init__(self, eventManager):
# initialize base class
eventDefinition = ('DiplomaticsRelationsPopupEvent', self.__eventDiplomaticsRelationsPopupApply, self.__eventDiplomaticsRelationsPopupBegin)
eventManager.addCustomEventDefinition(CvGreatState smanGameUtils.DIPLOMATIC_RELATIONS_POPUP, eventDefinition)
Again, I want to give credit where credit is due, most of the code did come from Vadus's posting here: http://forums.civfanatics.com/showthread.php?p=3746502#post3746502.
J_Period Mar 14, 2006, 02:23 PM I'm needin' a bit of help, if someone would be so kind... I'm trying to figure out python by doing a simple little (actually overly complicated :D) project using Dr. Jiggle's event manager to instantiate it, however I keep getting a stupid "Failed to load python module CvEventInterface" error message. I'm pretty sure I just missed something simple and stupid, but can someone just point out to me what it is? ;)
thanks
j
TheLopez Mar 14, 2006, 02:49 PM I'm needin' a bit of help, if someone would be so kind... I'm trying to figure out python by doing a simple little (actually overly complicated :D) project using Dr. Jiggle's event manager to instantiate it, however I keep getting a stupid "Failed to load python module CvEventInterface" error message. I'm pretty sure I just missed something simple and stupid, but can someone just point out to me what it is? ;)
thanks
j
Come on J, you know better than to just post something like this and not provide the source files or at least a code snippet for use to look at to help you at. Blah, blah, blah... sorry my mind wandered. Anyways, please post your files, or at least look into your log files and let us know what the error is. But from what you are describing you should post your CvEventInterface.py file for us to look at.
Nexushyper Mar 14, 2006, 03:12 PM I get that error when I goofed something in any one of my python files. Check the python log file in the logs folder. It will tell you the exact file that errored out as well as what caused it and what line it happened on.
I have noticed that anything that loads from CvEventInterface.py and fails give you the error "Failed to load python module CvEventInterface". It can be 20 files later with the previous 19 loading fine, and that one line tabed over one extra tab will cause it all to fail. :(
Anyways, check the logs
TheLopez Mar 14, 2006, 03:31 PM ... and that one line tabed over one extra tab will cause it all to fail. :(
Programmers will understand the next statement...
Well that's what you get when you develop a language that depends on spacing in source files to define the scope.
J_Period Mar 14, 2006, 10:56 PM Come on J, you know better than to just post something like this and not provide the source files or at least a code snippet for use to look at to help you at. Blah, blah, blah... sorry my mind wandered. Anyways, please post your files, or at least look into your log files and let us know what the error is. But from what you are describing you should post your CvEventInterface.py file for us to look at.
I know, but the reason I just posted that is that if I just put the event manager (unmodified) into its own mod folder and run that, it gets the same error, so I figured I had just done something stupid that everyone knew about :D
thanks for pointing out the logs though. Duh. Looking at them I think I can figure out whats wrong. If I have further difficulties, I'll post AND out up my logs, etc ;)
later guys
I figured it all out BTW...
Nexushyper Mar 15, 2006, 08:35 PM I have followed this word for word and am using the latest download. I can not get my mod to read the ini file and load the options.
import CvConfigParser
config = CvConfigParser.CvConfigParser("Unit Naming Config.ini")
g_CN = config.getint("Unit Naming", "Custom Name", 2)
In Unit Naming Config.ini if I change Custom Name to = 1 it defaults to 2. I know because I did a print g_CN and the log showed 2 and also the code for option 2 executed.
Any ideas? I am using default install directories.
TheLopez Mar 15, 2006, 08:49 PM Hmmmm... did you create a CvModName.py file with your mods name in it?
I am assuming that your mod name is "Unit Naming" correct? then you would need a file named "CvModName.py" with:
modName = "Unit Naming"
Nexushyper Mar 15, 2006, 09:32 PM Mods name is "Nexus Mod"
And I thought I had checked that file, but alas, I didn't update it (or I did but saved it to the wrong place). So I did just now. CvModName.py" with:
modName = "Nexus mod"
And it works.
See, always those little things that had I not posted and you not replied, I would be spending days trying to figure out!
TheLopez Mar 15, 2006, 09:36 PM Well I'm glad it worked :D
Dr Elmer Jiggle Mar 18, 2006, 09:37 PM Hmm. My subscription to this thread seems to have died. I haven't received an email about any of about the last dozen posts. Guess I have a lot to look into, including a bunch of custom event definition code from TheLopez.
TheLopez Mar 18, 2006, 09:57 PM I know, all I do is cause more work for you Dr Elmer Jiggle, :D.
Spocko Mar 19, 2006, 08:25 AM I've just finished a marathon game that incorporates Dr. Jiggle's Alert mod, and I'm sold. These alerts greatly improved my ability to keep my head in the game, so I'm going to rebuild my mod around Dr. Jiggle's CvEventManager. He has already given me much help in rebuilding my mod, although there remain a few forked mods that no longer work. More later - I'll be working on this throughout the evening.
And I picked up a few Python books to help me better understand what everyone is saying here in this thread :lol:
I'm glad to have found this thread.
Dr Elmer Jiggle Mar 19, 2006, 08:32 AM Thanks, Spocko.
Someday I'd like to add a bunch more alerts (see the comments in Civ4lerts.py) for things like trade opportunities and terrain improvement issues, but I haven't been playing Civ4 a lot recently, so my motivation has been low. Just the city growth alert alone makes the mod indispensible for me, but it could really be so much more.
TheLopez Mar 19, 2006, 08:34 AM Thanks, Spocko.
Someday I'd like to add a bunch more alerts (see the comments in Civ4lerts.py) for things like trade opportunities and terrain improvement issues, but I haven't been playing Civ4 a lot recently, so my motivation has been low. Just the city growth alert alone makes the mod indispensible for me, but it could really be so much more.
If you want when I have some time I can take a look at the events you aren't reporting and write some code for them and send you those changes.
Spocko Mar 19, 2006, 12:50 PM I'm trying to figure out why only a few (out of 15 or so) forked mods have ceased to work upon my incorporating Dr. Jiggle's CvCustomEventManager into my personal mod.
I'm wondering if my problem is due to my using three eventmanager files: Dr. Jiggle's CvCustomEventManager, my own CvSpockoEventManager, and a modified version of the original CvEventManager. How do these three files work together?
I've described below how they relate to each other.
My specific question is how does the system reconcile the fact that both CvEventManager and CvSpockoEventManager have the function "onLoadGame" although they are different?
CvSpockoEventManager has the following code:
def onLoadGame(self, argsList):
'Called when game is loaded'
self.parent.onLoadGame(self, argsList)
self.initValues()
CvEventManager has the following code:
def onLoadGame(self, argsList):
## 12monkeys - Re-Init Interceptor Mission - begin
RIM.firsttime = true
## 12monkeys - Re-Init Interceptor Mission - end
return 0
... and it seems that 12monkeys' RIM mod is working fine.
Here's how the three eventmanagers are related to each other, starting with CvEventInterface:
CvEventInterface has the following statements:
import CvCustomEventManager
customEventManager = CvCustomEventManager.CvCustomEventManager()
def getEventManager():
return customEventManager
CvCustomEventManager (from Dr. Jiggle) has the following statements:
import CvSpockoEventManager
class CvCustomEventManager(CvSpockoEventManager.CvSpocko EventManager, object):
def __init__(self, *args, **kwargs):
super(CvCustomEventManager, self).__init__(*args, **kwargs)
CvSpockoEventManager has the following statements:
import CvEventManager
class CvSpockoEventManager(CvEventManager.CvEventManager ):
def __init__(self):
# initialize base class
self.parent = CvEventManager.CvEventManager
self.parent.__init__(self)
CvEventManager has the following statements:
class CvEventManager:
def __init__(self):
I think that once I understand this, I'll begin to see how I can clean up my mod.
Thanks for your time!
EDIT: To post these attachments, I had to rename CvEventManager to be CvEventMgrTEXT... While it is annoying that we cannot post the same file to more than one posting :crazyeye: I can see how this limitation prevents spamming ;)
TheLopez Mar 19, 2006, 03:33 PM A couple of things.
1) Can you post all of the event manager classes that are causing you problems so we can debug it easier.
2) Wrap your code using the code tags so your code looks like:
<UnitCombat>
<UnitCombatType>UNITCOMBAT_RECON</UnitCombatType>
<bUnitCombat>1</bUnitCombat>
</UnitCombat>
Spocko Mar 19, 2006, 05:53 PM A couple of things.
1) Can you post all of the event manager classes that are causing you problems so we can debug it easier.
2) Wrap your code using the code tags so your code looks like:
<UnitCombat>
<UnitCombatType>UNITCOMBAT_RECON</UnitCombatType>
<bUnitCombat>1</bUnitCombat>
</UnitCombat>
Whoa! Thanks for the tip about using [CODE] tags! I've attached the three eventmanager files to my earlier post - thanks TheLopez!!
Dr Elmer Jiggle Mar 20, 2006, 06:06 PM One thing I've noticed that isn't going to solve your problem but that will be a good idea in general is that you're calling the superclass event handler from all of your handlers. You don't need to do that anymore. CvCustomEventManager already does that, so you're actually calling all those handlers twice. It probably won't make a difference in most cases, but it could cause strange behavior depending on exactly what those handler functions do.
I'll look at this again later, but I don't see anything obvious at first glance. My intuition is that it's something about your modified CvEventManager class. That's the base class of both CvSpockoEventManager and CvCustomEventManager, and I wouldn't be surprised if one of the modifications is causing a problem for one or both of those classes. Can you post that file?
Dr Elmer Jiggle Mar 20, 2006, 06:20 PM My specific question is how does the system reconcile the fact that both CvEventManager and CvSpockoEventManager have the function "onLoadGame" although they are different?
The normal flow of control for any event handler, including onLoadGame would be something like the sequence shown below. I'll use onLoadGame as the example, but there's not much that varies from event type to event type.
CvCustomEventManager.handleEvent is called
A dispatcher for the event type is looked up in a dictionary. Usually this returns _handleDefaultEvent, but for onLoadGame it will return _handleOnLoadEvent
The dispatcher is called to process all registered event handlers
Call the base class event handler, CvEventManager.onLoad
Call CvSpockoEventManager.onLoad
... call any other registered onLoadGame handler functions
Some additional comments ...
You don't really need to worry much about the dispatcher thing. It's just a way of dealing with the fact that some event handlers return values or have other special semantics that require slightly different processing. For example, if a keyboard handler returns 1, then subsequent handlers are ignored.
The inner list of event handler invocations in step 3 is deliberately unnumbered. Although I think the current implementation does call the handlers in the same order in which they were registered, and with the default handler first, I'm not sure I want to make that guarantee. Just assume that your handler will be called at some point but not necessarily first, second, or last. For example, it's conceivable that the base class handler would be called after yours.
Having written all that, I'm not convinced that the problems you're seeing are related to event handling, unless those other mods also use a custom event handler. There really shouldn't be any crossover effects on non-event based mods.
Spocko Mar 20, 2006, 08:22 PM One thing I've noticed that isn't going to solve your problem but that will be a good idea in general is that you're calling the superclass event handler from all of your handlers. You don't need to do that anymore. CvCustomEventManager already does that, so you're actually calling all those handlers twice. It probably won't make a difference in most cases, but it could cause strange behavior depending on exactly what those handler functions do.
I'll look at this again later, but I don't see anything obvious at first glance. My intuition is that it's something about your modified CvEventManager class. That's the base class of both CvSpockoEventManager and CvCustomEventManager, and I wouldn't be surprised if one of the modifications is causing a problem for one or both of those classes. Can you post that file?
What you say rings true, because when I start a new game, the Dawn of Man window displays twice. But, being a newbie to OOP and Python, I don't know how to take action on this - I'd like to not be calling all those handlers twice, but I don't know what to comment out in which EventManager file.
The inner list of event handler invocations in step 3 is deliberately unnumbered. Although I think the current implementation does call the handlers in the same order in which they were registered, and with the default handler first, I'm not sure I want to make that guarantee. Just assume that your handler will be called at some point but not necessarily first, second, or last. For example, it's conceivable that the base class handler would be called after yours.
I'm interpreting this as meaning that several files can include a "def takeAction" block of statements, and all of these blocks would eventually be called by the system. For example, if three files that are called via imports have the following three blocks:
{CvEventManager}
def takeAction
statement A
statement B
{CvCustomEventManager}
def takeAction
statement J
statement K
{CvSpockoModEventManager}
def take Action
statement X
statement Y
... then the system would see "def takeAction" as being a block for all six statements, A,B,J,K,X, and Y - and that the system would not accept only the last block as an overriding of any previous encounters with a "def takeAction" block. If so, I see how I am not having problems just because def statements are defined differently in the three EventManager files. And it thus may not be helpful for me to try to fuse these three files together. What do you think? Am I learning? :crazyeye:
My previous post (three or four posts above) have attached text files that include the contents of my three EventManager files.
My specific problem is with respect to the "Reminder" mod that is embedded in CvEventManager - pressing Shift-R no longer invokes this handy mod. You can see in CvEventManager five blocks of edits each preceded by a comment such as "# reminder addition 1/5".
I've been waiting for the new patch to use the new files to rebuild SpockoMod. But since the patch has yet to be published, I'm thinking of rebuilding SpockoMod (which is a conglomeration of 30 or so forked mods, many of which do not touch the EventManager) this week starting with the original Civ4 CvEventManager and your CvCustomEventManager. I would just add one mod at a time and test each time to verify that each additional mod works. But before I do this, I want to work toward a greater understanding of how your CvCustomManager works so that I can rebuild SpockoMod in an informed way and so I can add new forked mods later in an informed way.
Oh, incidentally, the crown jewel of SpockoMod is that I've combined Alilum's "Alt Religion" mod and Kidinnu's "True Prophet" mod - because of this, religions are founded by Great Prophets (not by technology discovery) and the religions founded are abstract religions, not named after Earth's religions. It has been hard to see Berlin a center of Hinduism - oh, and I've renamed all cities to be abstract names that sound like they came from their respective cultures, but they're not real names (an idea forked from Alilum's "Generic City Names" mod).
Thank you for your time,
Spocko
Dr Elmer Jiggle Mar 20, 2006, 09:40 PM What you say rings true, because when I start a new game, the Dawn of Man window displays twice. But, being a newbie to OOP and Python, I don't know how to take action on this - I'd like to not be calling all those handlers twice, but I don't know what to comment out in which EventManager file.
In your CvSpockoEventManager class, all of the event handler functions look something like the example shown below.
def onUnitMove(self, argsList):
self.parent.onUnitMove(self, argsList)
# rf.onUnitMove(argsList)
ass.onUnitMove(argsList)
sa.onUnitMove(argsList)
The "self.parent..." line is simply calling the base class's onUnitMove function. That's what you can remove. That's no longer necessary. Your event handlers only need to provide the functionality that's unique to your mod and nothing else.
I'm interpreting this as meaning that several files can include a "def takeAction" block of statements, and all of these blocks would eventually be called by the system.
They also need to register themselves with the event manager. Each of those classes in your example (well, not CvCustomEventManager, because that's the master event manager) would need something like the registerEventHandlers function that you have in CvSpockoEventManager. That would really probably be the best way to integrate the 3 classes you have now.
Rename CvEventManager to something else (so it's not redefining the base class anymore). Then go through more or less the same procedure on that class that you did with CvSpockoEventManager. Create a registerEventHandlers function and register each separate event handler function with the main event manager.
It's going to be a little more complicated, because there's a lot of redundant code that can be removed. For the most part, the only thing that really needs to stay in that class is the stuff that was added to it -- all the stuff marked by change comments. The rest is already built into CvCustomEventManager via the base class.
My specific problem is with respect to the "Reminder" mod that is embedded in CvEventManager - pressing Shift-R no longer invokes this handy mod. You can see in CvEventManager five blocks of edits each preceded by a comment such as "# reminder addition 1/5".
I'll see if I can make a simple test case that uses a keyboard event handler. It might be a problem with those.
naf4ever Mar 27, 2006, 09:41 AM Cool mod. Im starting to mess around with it now and learn this stuff so i can make my python files read from a .ini .
This is slightly off topic but two things popped into my head while working on this:
1) If you can make a python file read from a .ini can you make it write to one as well?
2) Can XML files be made to read from an .ini?
TheLopez Mar 27, 2006, 09:54 AM My specific problem is with respect to the "Reminder" mod that is embedded in CvEventManager - pressing Shift-R no longer invokes this handy mod. You can see in CvEventManager five blocks of edits each preceded by a comment such as "# reminder addition 1/5".
I'll see if I can make a simple test case that uses a keyboard event handler. It might be a problem with those.
That's wierd, the keyboard event handler works just fine in the mercenaries mod.
Dr Elmer Jiggle Mar 27, 2006, 09:57 AM 1) If you can make a python file read from a .ini can you make it write to one as well?
Yes. The INI file stuff leverages the built in Python ConfigParser module. Along with various other things, the RawConfigParser class includes a write method to save the data.
http://www.python.org/doc/2.4.2/lib/module-ConfigParser.html
CvConfigParser extends SafeConfigParser which extends ConfigParser which extends RawConfigParser, so it picks up all the functionality of those base classes through inheritance. The only things I've added are some code to automatically search for your INI file in the various game directories (the base classes require you to tell it where to look) and support for default values (which IMHO is a stupid omission that should already be built into the base classes).
2) Can XML files be made to read from an .ini?
I don't think so, but I'm not sure I understand the question. Can you explain in more detail what you're trying to do?
naf4ever Mar 27, 2006, 10:05 AM I guess im still clinging on to my dream to be able to make XML content that has its values based off of a variable instead of just being static. That way you could do cool stuff like make a unit that has its power based on certain factors occurring in the game or a building with bonuses based off other changing criteria.
I figured if you could write to an .ini file with python and then make those values read to be used in the XML this could be accomplished.
I don't think so, but I'm not sure I understand the question. Can you explain in more detail what you're trying to do?
Danget.... I thought i read that Kael was going to implement some sort of xml parsing in his next FfH version that allows the xml values to be read off of a 3rd party file. I'll have to look into this more.
Nexushyper Mar 27, 2006, 10:16 AM I don't know about writing to an ini file but using python you can create a storage device to store info in that stays with the save game.
SD toolkit by Stone-D (http://forums.civfanatics.com/showthread.php?t=146130)
Several people use this for many things, like Teg_Navanis stores unit statistics. I use it determine the next name of a unit built based on land, sea or air. You can use this in many ways. Hope that helps you.
TheLopez Apr 19, 2006, 09:18 AM Hmm. My subscription to this thread seems to have died. I haven't received an email about any of about the last dozen posts. Guess I have a lot to look into, including a bunch of custom event definition code from TheLopez.
Dr Elmer Jiggle, have you had a chance to review my suggestions for your library? Are you going to add them into your code base? Please let me know so I can get the "official" version of your library instead of having custom coded added to your code.
Thanks.
Dr Elmer Jiggle Apr 19, 2006, 09:34 AM Dr Elmer Jiggle, have you had a chance to review my suggestions for your library?
Thanks for reminding me. I had forgotten about that stuff.
My intitial impression is that I don't understand why you need the CustomEvents dictionary and special handling for it. Wouldn't it work just as well if you added an entry to self.Events and let the default handlers deal with it?
It would still probably be useful to keep the add and remove functions as hooks for manipulating self.Events, but I'm not sure I see a need for overriding beginEvent and applyEvent. And the set method wouldn't make sense anymore without CustomEvents.
I'll have to look back at some of Spocko's old posts. I think I remember another case or two where something similar to this was going to be useful. Some other add/remove functions that were going to expose some of the CvEventManager parts.
TheLopez Apr 19, 2006, 09:38 AM Basically the custom event code is to handle the situations when call backs are needed to handle popup windows.
Dr Elmer Jiggle Apr 19, 2006, 10:14 AM Basically the custom event code is to handle the situations when call backs are needed to handle popup windows.
I understand. What I'm asking, more specifically, is would the code shown below work just as well? If not, why? I don't have a good test case to run through this, but just from looking at it, it seems like they would both ultimately do the same thing.
# removed beginEvent and addEvent overloads
def addCustomEventDefinition(self, eventType, eventDefinition):
# changed CustomEvents to Events
self.Events[eventType] = eventDefinition
def removeCustomEventDefinition(self, eventType):
# changed CustomEvents to Events
del self.Events[eventType]
The basic idea is that the Events dictionary already exists, and it appears to do exactly what you want from CustomEvents. Adding more entries to that dictionary for custom events shouldn't break the built in events, and the default popup handling code should still work with the custom events.
Underneath the complexity of CvEventManager, Events is really just a dictionary that maps integer event ID's to popup handler functions. Your CustomEvents is the same thing. There's really no difference that I can see between a "built in" event and a "custom" event.
TheLopez Apr 19, 2006, 10:20 AM I understand. What I'm asking, more specifically, is would the code shown below work just as well? If not, why? I don't have a good test case to run through this, but just from looking at it, it seems like they would both ultimately do the same thing.
# removed beginEvent and addEvent overloads
def addCustomEventDefinition(self, eventType, eventDefinition):
# changed CustomEvents to Events
self.Events[eventType] = eventDefinition
def removeCustomEventDefinition(self, eventType):
# changed CustomEvents to Events
del self.Events[eventType]
The basic idea is that the Events dictionary already exists, and it appears to do exactly what you want from CustomEvents. Adding more entries to that dictionary for custom events shouldn't break the built in events, and the default popup handling code should still work with the custom events.
Underneath the complexity of CvEventManager, Events is really just a dictionary that maps integer event ID's to popup handler functions. Your CustomEvents is the same thing. There's really no difference that I can see between a "built in" event and a "custom" event.
That should work. Could you see the way that I am doing it check out the Great Statesman Mod and let me know if you see any issues with using the above methods?
Dr Elmer Jiggle Apr 19, 2006, 01:18 PM That should work. Could you see the way that I am doing it check out the Great Statesman Mod and let me know if you see any issues with using the above methods?
Note: I didn't actually try install and run the mod, but I looked at the code.
As far as I can tell, it should work fine. The only way I see a problem is if the integer you use as an event ID (ex. DIPLOMATIC_RELATIONS_POPUP = 4203) isn't unique, but that's actually a problem with your original implementation too. I don't think there's any obvious way around that requirement. You just need to be careful about your choice of integer.
When I implement this, I might stick a check in the add method to make sure the key is unique. That should be easy to do, and that way you'd at least get a warning if you did something stupid.
jdog5000 Apr 21, 2006, 01:55 PM First off, a very useful event manager mod! I use it now in my TechCost Mod (http://forums.civfanatics.com/showthread.php?t=168120), and it works great.
I am definitely looking forward to your implementation for adding new popup event handlers, as for an upcoming mod I currently have modified you CEM similar to how TheLopez originally did. I have a question stemming from this new mod as well.
I want to create a new keyboard command (currently using shift-ctrl-x). Is there a way to access whether the shift and ctrl keys are down from my own file? Like the normal event manager, you copy their state into some private bits:
self.bDbg, self.bMultiPlayer, self.bAlt, self.bCtrl, self.bShift, self.bAllowCheats = argsList[flagsIndex:]
Currently the _handleConsumableEvent doesn't pass these on to the event handler:
result = eventHandler(argsList[:len(argsList) - 6])
I assume this is done to play well with existing handlers. Is there a good way to access those bits from my own function, let's call it AIAutoPlay.onKbdEvent()? My current hack is to add getbShift() etc to the CEM code ...
Thanks for your help!
Dr Elmer Jiggle Apr 21, 2006, 02:21 PM Is there a way to access whether the shift and ctrl keys are down from my own file? Like the normal event manager, you copy their state into some private bits:
Currently the _handleConsumableEvent doesn't pass these on to the event handler
Hmm. :hmm: I'm not using any keyboard handlers, so I hadn't thought about that issue.
My first inclination would be the same hack you used. I don't think Python even has any real public/private access control, so you could probably just access the "bShift" variable directly, but it's obviously cleaner and more object oriented to use a getter function.
I also think it's legal to pass extra, unused arguments to the event handlers. If so, then all those little extra fiddly bits could be added to the arguments for onKbdEvent, and it wouldn't break handlers that don't expect or use those values.
I'm not really certain this will work, but it seems like it has to if you think about how the event handlers are declared. They're all some form of "f(self, argsList)" but they all take different arguments. It seems like it ought to be legal to just jam as much extra garbage as you feel like onto the end of the list.
Can you try that and let me know if it works? Something like:
change _handleConsumableEvent to pass the whole list (get rid of the -6)
change your handler function to get bShift and so forth from the argsList
eventType,key,mx,my,px,py,bDbg,bDummy,... = argsList
If that works, it I'll stick something like that into my version to fix this problem.
TheLopez Apr 21, 2006, 03:17 PM Actually, the answer is easier than that DEJ. Look at how I handle this case in the mercenaries mod, the important code is in bold:
class CvMercEventManager:
mercenaryManager = None
def __init__(self, eventManager):
self.EventKeyDown=6
self.eventManager = eventManager
...
...
# This method handles the key input and will bring up the mercenary manager screen if the
# player has at least one city and presses the 'M' key.
def onKbdEvent(self, argsList):
'keypress handler - return 1 if the event was consumed'
# TO DO: REMOVE THE FOLLOWING LINE BEFORE RELEASE.
#gc.getPlayer(0).setGold(20000)
eventType,key,mx,my,px,py = argsList
theKey=int(key)
if ( eventType == self.EventKeyDown and theKey == int(InputTypes.KB_M) and self.eventManager.bAlt and gc.getActivePlayer().getNumCities() > 0 and gc.getActivePlayer().getCurrentEra() >= g_iStartingEra):
self.mercenaryManager.interfaceScreen()
...
jdog5000 Apr 21, 2006, 03:46 PM @ DrEJ
I will try that out and report back, although I think it may fail when the uninitiated handlers try and unpack the argList with something like
iTechType, iPlayer = argsList
If that suddenly has a extra entry ... seems like it wouldn't work. I'll check it later. But regardless ... I think TheLopez's idea works. Might add some comments to your CEM to help out future people.
@ TheLopez
Thanks! I think that takes care of it.
Dr Elmer Jiggle Apr 21, 2006, 05:48 PM I think it may fail when the uninitiated handlers try and unpack the argList
Yeah, it does fail. I thought the extra values would be silently ignored, but it's an error. Oh well.
Denniz Apr 22, 2006, 03:54 AM I want to create a new keyboard command (currently using shift-ctrl-x). Is there a way to access whether the shift and ctrl keys are down from my own file?
The below is from eotinb's AutoLog which I modified to run as an eventmanager out of the CEM for the HOF Mod. The basic handling of the Alt-E has been passed unchanged from the original.
def onKbdEvent(self, argsList):
if hof.get_boolean('AUTOLOG', 'Enabled', False):
eventType,key,mx,my,px,py = argsList
if ( eventType == self.eventMgr.EventKeyDown ):
theKey=int(key)
'Check if ALT + E was hit == echoes to text log and in-game log'
if (theKey == int(InputTypes.KB_E) and self.eventMgr.bAlt):
self.eventMgr.beginEvent(CvUtil.EventCustomLogEntr y)
return 1
return 0
As you can see there are booleans for Ctrl and Shift as well. So I would think you could do something similar to AutoLog. The main thing seems to be to stay away from Civ4's hot keys.
class CvEventManager:
def __init__(self):
#################### ON EVENT MAP ######################
#print "EVENTMANAGER INIT"
self.bCtrl = False
self.bShift = False
self.bAlt = False
self.bAllowCheats = False
# OnEvent Enums
self.EventLButtonDown=1
self.EventLcButtonDblClick=2
self.EventRButtonDown=3
self.EventBack=4
self.EventForward=5
self.EventKeyDown=6
self.EventKeyUp=7
If you are looking for popup handling, AutoLog has working examples of that too.
Or am I missing something?
Dr Elmer Jiggle Apr 22, 2006, 06:57 AM If you are looking for popup handling, AutoLog has working examples of that too.
Or am I missing something?
No, I don't think you're missing anything. In fact, I think this validates my suggestion that the build in Events dictionary can be used to do this. Including add/remove methods in the CEM class isn't necessary (as you've shown), but it's probably a nice convenience.
For the sake of clarification about what we're talking about, here's a clip from Denniz' code. You can see that he's adding his dialog handlers directly to the Events dictionary which then gets handled automatically by the built in CvEventManager dialog logic.
def __init__(self, eventManager):
AutoLogEvent(eventManager)
# additions to self.Events
moreEvents = {
CvUtil.EventLogOpen : ('LogOpenPopup', self.__eventLogOpenApply, self.__eventLogOpenBegin),
CvUtil.EventCustomLogEntry : ('', self.__eventCustomLogEntryApply, self.__eventCustomLogEntryBegin),
}
eventManager.Events.update(moreEvents)
I'm also thinking about another idea for the keyboard handlers. This approach of saving a reference to the event manager obviously works, but it feels like a bit of a hack to me. I'm not sure why, but it does. Probably partly the cyclic reference with the event manager pointing to your handler object and your handler object also pointing back to the event manager.
Anyway, my thought is that I could implement some kind of new, custom, alternative keyboard handler type. So instead of "em.addEventHandler("kbdEvent", self.onKbdEvent)" you would do something like "em.addEventHandler("newKbdEvent", self.onKbdEvent)." Then there would be some special logic in the CEM so "newKbdEvent" handlers get the additional boolean flags passed to them.
I'm going to experiment with this, but I'm not sure I'll end up liking it. It might turn out that all the special case handling for this winds up being uglier than any other alternative. But the advantage is that I think it would internalize the hack, so it could be done once in the CEM and then never again. The way it works now, anyone that wants to implement a keyboard handler needs to repeat the hack.
Jeckel Apr 28, 2006, 02:54 AM I'm having a problem and no matter how hard I bang my head against the wall, I just can't figure it out.
So I decided my first python mod attempt would be a very simple Fort mod based off of Bhuric's RealFort. I want my mod to be easy as possible ot combine with other mods(even if mostly by me) so I'm using this CustomEvent structure from the start. I managed to make some progress, but I have hit a snag.
Here is the pertinate part from the CvCustomEventManager.py
import CvEventManager
import CvGreatStatesmanEventManager
import CvGDEventManager
import CvFieldHospitalEventManager
import CvGreatGeneralEventManager
import CvGPTrickleEventManager
import CvJFortEventManager
class CvCustomEventManager(CvEventManager.CvEventManager , object):
# < NEW CODE START >
CustomEvents = {}
# < NEW CODE END >
def __init__(self, *args, **kwargs):
super(CvCustomEventManager, self).__init__(*args, **kwargs)
# map the initial EventHandlerMap values into the new data structure
for eventType, eventHandler in self.EventHandlerMap.iteritems():
self.setEventHandler(eventType, eventHandler)
# --> INSERT EVENT HANDLER INITIALIZATION HERE <--
CvGreatStatesmanEventManager.CvGreatStatesmanEvent Manager(self)
CvGDEventManager.CvGDEventManager(self)
CvFieldHospitalEventManager.CvFieldHospitalEventMa nager(self)
CvGreatGeneralEventManager.CvGreatGeneralEventMana ger(self)
CvGPTrickleEventManager.CvGPTrickleEventManager(se lf)
CvJFortEventManager.CvJFortEventManager(self)
Just to note, this is from TheLopez's GP Trickle Plus Mod(I don't that matters, but doesn't hurt to mention it). The only thing I altered was the addition of the two bolded lines.
Here is my entire CvJFortEventManager.py
from CvPythonExtensions import *
import Popup as PyPopup
import CvUtil
import sys
import CvMainInterface
import CvGameInterface
import CvJFortGameUtils
# globals
gc = CyGlobalContext()
jf = CvJFortGameUtils.JFort()
# globals
################################################## #
class CvJFortEventManager:
def __init__(self, eventManager):
# initialize base class
eventManager.addEventHandler("unitMove", self.onUnitMove)
eventManager.addEventHandler("ImprovementBuilt", self.onImprovementBuilt)
def onUnitMove(self, argsList):
jf.onUnitMove(argsList)
def onImprovementBuilt(self, argsList):
jf.onImprovementBuilt(argsList)
And this is my entire CvJFortGameUtils.py
from CvPythonExtensions import *
import Popup as PyPopup
import CvUtil
import sys
# globals
gc = CyGlobalContext()
class JFort:
def onUnitMove(self, argsList):
'unit move'
pPlot,pUnit = argsList
impFort = gc.getInfoTypeForString("IMPROVEMENT_FORT")
promFort1 = gc.getInfoTypeForString("PROMOTION_FORT1")
if (pPlot.getImprovementType() == impFort):
pUnit.setHasPromotion(promFort1, true)
def onImprovementBuilt(self, argsList):
'Improvement Built'
iImprovement, iX, iY = argsList
impFort = gc.getInfoTypeForString("IMPROVEMENT_FORT")
promFort1 = gc.getInfoTypeForString("PROMOTION_FORT1")
pMap = CyMap()
pPlot = pMap.plot(iX, iY)
if (iImprovement == impFort):
for iUnit in range(pPlot.getNumUnits()):
pUnit = pPlot.getUnit(iUnit)
pUnit.setHasPromotion(promFort1, true)
else:
for iUnit in range(pPlot.getNumUnits()):
pUnit = pPlot.getUnit(iUnit)
if (pUnit.isHasPromotion(promFort1)):
pUnit.setHasPromotion(promFort1, false)
My problem is in the CvJFortEventManager.py, specifically the bolded line. When I load Civ4 with that code I get all kinds of unhelpful error messages when it loads the python stuff. But if I comment that line out it runs, except the Fort1 promotion doesn't get put on the units until they move the first time.
I at first figured the onImprovementBuilt method was changed in the last patch or somthing, but I check in the CvEventManager.py and it seemed to be there and to work just as I'm calling it.
I don't know if I'm not setting this up right or maybe I'm just overlooking somthing simple, but any help would be appriceated. :)
Denniz Apr 28, 2006, 03:05 AM It's case sensitive. You have to copy the string from the original exactly.
'improvementBuilt' : self.onImprovementBuilt,
eventManager.addEventHandler("improvementBuilt", self.onImprovementBuilt)
I did that a few times before I figured it out. ;)
Jeckel Apr 28, 2006, 03:17 AM *grins* I figured it was some little syntax error, if I had a nickle for every hour I've spent fighting with code when the only problem was a misspelling or incorrect case. Thanks for the quick response, I will now sleep peacefully tonight. :D
Dr Elmer Jiggle Apr 28, 2006, 06:29 AM It's case sensitive. You have to copy the string from the original exactly.
'improvementBuilt' : self.onImprovementBuilt,
eventManager.addEventHandler("improvementBuilt", self.onImprovementBuilt)
I did that a few times before I figured it out. ;)
If I remember to look back through this thread for ideas when I get around to making improvements, I'm going to add something in the addEventHandler function to test the validity of the name. This is just too common a mistake, and it's way too easy to make it. I've done it a few times myself.
Padmewan Apr 29, 2006, 10:07 AM Sorry to bring the conversation here down a notch. I am trying to implement my custom coding the "right" way using this component (you'll be horrified at what I was doing before :eek: ) but I now find myself in a maze of twisty passages all alike.
I am doing my best to learn from other mods that utilize this framework, e.g. Civ4Alerts and Great Statesman, but they are all a tad too complicated for me to deconstruct. Partly I'm not a great OOP thinker, and partly I'm still getting my head around Python syntax, and partly I'm trying to learn the hierarchy of Civ Python classes/functions/etc.
What is the minimum that I need to do to get this component to "work"? To the best of my knowledge, it is:
1) Put D.E.G.'s CvCustomEventManager.py file in the root Python directory.
2) Assuming my custom .py file will be called TestMod, change the CvCustomEventManager.py code to read:
import CvEventManager
import TestMod
def __init__(self, *args, **kwargs):
super(CvCustomEventManager, self).__init__(*args, **kwargs)
# map the initial EventHandlerMap values into the new data structure
for eventType, eventHandler in self.EventHandlerMap.iteritems():
self.setEventHandler(eventType, eventHandler)
# --> INSERT EVENT HANDLER INITIALIZATION HERE <--
TestMod.TestMod(self)
3) Create TestMod.py . This file can be created and put anywhere under the root Python directory, yes, and "import TestMod" will know where to find it? For example, I see that CvGreatStatesmanEventManager.py is in a subdirectory called "GreatStatesman."
4) Now what must TestMod.py import in order to function properly? It seems funny to me that TestMod must now import CvEventManager again, but that seems to be the case across all the mods I've seen. Presumably you also need to import all functions called by this package?
Now that I've asked my general question, here is one specific example which I hope you can help me with to understand how to do more like it. This is the exact code that I inserted straight into CvEventManager (not even CvCustomEventManager -- yes, I know, do not pass Go, do not collect $200!):
def onImprovementBuilt(self, argsList):
'Improvement Built'
iImprovement, iX, iY = argsList
#################### BEGIN CUSTOM TERRAFORM ##################
# Moves terrain "up" one level (relies on terrain being sequential)
# Replaces the fake terraform improvement with a real improvement
pPlot = CyMap().plot(iX,iY)
iTerraform = gc.getInfoTypeForString('IMPROVEMENT_TERRAFORM1')
iHothouse = gc.getInfoTypeForString('IMPROVEMENT_HOTHOUSE')
iFTerran = gc.getInfoTypeForString('FEATURE_TERRAN1')
if (iImprovement==iHothouse):
pPlot.setFeatureType(iFTerran, -1)
elif (iImprovement==iTerraform):
iOldTerrain = CyMap().plot(iX, iY).getTerrainType()
pPlot.setTerrainType(iOldTerrain+1, 1, 1)
pPlot.setImprovementType(iHothouse)
pPlot.setFeatureType(-1, -1)
#################### END TERRAFORM ##################
if (not self.__LOG_IMPROVEMENT):
return
CvUtil.pyPrint('Improvement %s was built at %d, %d'
%(PyInfo.ImprovementInfo(iImprovement).getDescript ion(), iX, iY))
OK, so how would I refactor this the "right" way?
Thanks for all of your efforts to make Civ Modding a safe and healthy environment for everyone :)
Padmewan Apr 29, 2006, 10:10 AM At the risk of making myself sound even more ignorant, how do you turn on Python debugging and where does the log file go? Also, is there some thread, wiki, whatever where all of this information is collected that I just am not tapped into?
Dr Elmer Jiggle Apr 29, 2006, 10:56 AM 4) Now what must TestMod.py import in order to function properly? It seems funny to me that TestMod must now import CvEventManager again, but that seems to be the case across all the mods I've seen. Presumably you also need to import all functions called by this package?
I don't think you should need to. Civ4lerts doesn't do that.
The rest of the steps you wrote all look right up to this point. Then your final job is to write TestMod.py. Something like this should do the job (I might have some syntax errors here, I'm just typing this from memory).
class TestMod:
def __init__(self, eventManager):
eventManager.addEventHandler("improvementBuilt", self.onImprovementBuilt)
def onImprovementBuilt(self, argsList):
# no changes required
Padmewan Apr 29, 2006, 11:40 AM Then your final job is to write TestMod.py. Something like this should do the job (I might have some syntax errors here, I'm just typing this from memory)...
OK, then just pay attention to case and all that, per the rest of this thread? Whew.... this is going to be arduous but I think worthwhile :)
And just to confirm: I can put my custom functions ANYWHERE in the Python directory and as long as I name (1) the file AND/OR (2) the main class in that file correctly, the Civ4 engine will find it?
THANKS A MILLION.
Dr Elmer Jiggle Apr 29, 2006, 12:08 PM OK, then just pay attention to case and all that, per the rest of this thread?
Right. Later today or tomorrow I'm going to post a new version that will throw an exception if you make a mistake in the event name, but that still means you need to get it right. It just makes it easier to notice when you've made a mistake.
And just to confirm: I can put my custom functions ANYWHERE in the Python directory and as long as I name (1) the file AND/OR (2) the main class in that file correctly, the Civ4 engine will find it?
Right. That's actually sort of strange. Normally with Python, if you put a module in foo/bar/Zap.py, you need to import it as "import foo.bar.Zap", but Civ4 uses a custom class loader that ignores the directory paths.
As far as the logging goes, look in your personal Civ4 directory (not the main game installation directory), typically something like C:\Documents and Settings\User\My Documents\My Games\Sid Meier's Civilization 4. You should see a file called CivilizationIV.ini. If you search through that file, you'll find a bunch of options related to logging. They aren't well documented, and I don't know exactly what all of them do, but basically you want to set LoggingEnabled to 1. You can fiddle with the other options too if you like.
In that same directory, you'll find a subdirectory called Logs which contains the various log files. PythonDbg.log, PythonErr.log, and PythonErr2.log seem to be the most useful. I don't know what the difference is between each one of those, especially Err and Err2.
There are a few sites with information on modding, but honestly I haven't found many of them to be of much help. The main one I've used is http://civilization4.net/files/modding/PythonAPI which lists most of the functions available in the API. There are some other sites that I don't seem to have bookmarked that have descriptive articles about what some of the files are for, but they all seemed extremely incomplete last time I looked. They were mostly just full of empty stub articles.
Denniz Apr 29, 2006, 01:05 PM A couple of notes:
Locutus' PythonAPI link is out. You can use this one instead: http://civilization4.net/files/modding/PythonAPI_v160/
Must have for Python modding:
; Set to 1 for no python exception popups
HidePythonExceptions = 0
Logging options:
; Enable the logging system
LoggingEnabled = 1
; Enable synchronization logging
SynchLog = 0
; Overwrite old network and message logs
OverwriteLogs = 1
; Enable rand event logging
RandLog = 0
; Enable message logging
MessageLog = 0
I like to turn on Overwrite logs so I don't have to search around for the last error and they don't grow continuously. ;)
Watch out if you have McAfee anti-virus. Logging with it on seems to slow things down for some people.
Dr Elmer Jiggle May 01, 2006, 07:41 AM Finally, at long last, the much anticipated changes for popup dialogs have been uploaded!!! :dance: Sorry about the delays. I also put in simple checking for the event type strings passed to addEventHandler.
jdog5000 May 03, 2006, 01:21 PM Awesome! I'll have a couple new mods coming out soon that will be using this ... just doing the final tweaking.
One thing I noticed with the new popup handler implementation ... when I use my popups this way, I get an interface message and a python debug output saying "DEBUG Event: abdicatePopup (jdog5000)" where my popup name is abdicatePopup. Is this because I have python debug output turned on? Or am I overiding some builtin event type and this is how it complains (using 7000 and 7001)? Where can you find a list of all the taken event type numbers anyway?
Thanks DEJ for making this great library.
Dr Elmer Jiggle May 03, 2006, 01:40 PM One thing I noticed with the new popup handler implementation ... when I use my popups this way, I get an interface message and a python debug output saying "DEBUG Event: abdicatePopup (jdog5000)" where my popup name is abdicatePopup. Is this because I have python debug output turned on?
Yes, I think it's just normal debugging output. What you're really doing when you call setPopupHandler is adding an item to the self.Events dictionary in the event manager. The three values that you pass in as the handler definition are:
a name that's used by reportEvent for debugging output
a function that's called by applyEvent
a function that's called by beginEvent
This is all part of the base event manager that comes with the game, not something I added. The name, in particular, is used by the reportEvent function which is shown below. As you can see, it prints a message that looks like "DEBUG Event: <eventName> (<userName>)", which is exactly the message you're seeing.
def reportEvent(self, entry, context, argsList):
'Report an Event to Events.log '
message = "DEBUG Event: %s (%s)" %(entry[0], gc.getActivePlayer().getName())
CyInterface().addImmediateMessage(message,"")
CvUtil.pyPrint(message)
return 0
Or am I overiding some builtin event type and this is how it complains (using 7000 and 7001)? Where can you find a list of all the taken event type numbers anyway?
That isn't what's happening here, but it's a good question anyway. The built in ones seem to mostly be in CvUtil.py. Look in CvEventManager.py (again, the base one that comes with the game) to see where it initializes self.Events. Those are the existing ID's.
Of course, what I should have done is added the same checking I put into addEventHandler to throw an exception if the key already exists in the dictionary. I have no idea why I didn't do that :blush:. I guess one reason is that you might want to override an existing dialog handler, and if I throw an exception then you can't do that. Maybe what's needed is some kind of assertion function where you can check it separately. Or an overload where there are two different setPopupHandler functions, only one of which does the check.
Caesium Jul 13, 2006, 04:31 AM I also found a bug.
Many mods using your CvCustomEventManager.py also use your CvConfigParser.py.
So there is in almost every mod a code similar to following code:
def __init__(self, eventManager, config):
global g_bModChooser
if (config != None):
g_bModChooser = config.getint("Mod Chooser", "Mod Chooser", True)
eventManager.addEventHandler("GameStart", self.onGameStart)But, config never is None, so always the code in the if sequence is executed.
With a little addition to the "(mod name) Config ini" and the config-reading python file, this if sequence is executed the right way:
In "(mod name) Config ini":
[Config Verifier]
; verifies this config
; if this value is false, the default values will be used
; Default value is true
Config Verifier = True
in the python file there must be this addition:
config = CvConfigParser.CvConfigParser("(mod name) Config.ini")
bConfigVerifier = config.getboolean("Config Verifier", "Config Verifier", False)
if not bConfigVerifier:
config = None
Now, the code for all implemented codes will be used correctly.
As attachment a Config.ini and the CvCustomEventManager.py
Dr Elmer Jiggle Jul 13, 2006, 11:24 AM def __init__(self, eventManager, config):
global g_bModChooser
if (config != None):
g_bModChooser = config.getint("Mod Chooser", "Mod Chooser", True)
eventManager.addEventHandler("GameStart", self.onGameStart)But, config never is None, so always the code in the if sequence is executed.
Why is that a bug? It should work fine. g_bModChooser will be set to the default value of True if there is no "Mod Chooser" section. I'm guessing that you would prefer the default value to be False, which might be a legitimate issue, but that would be a problem with the mod, not the config file parsing.
Caesium Jul 13, 2006, 11:45 AM Why is that a bug? It should work fine. g_bModChooser will be set to the default value of True if there is no "Mod Chooser" section. I'm guessing that you would prefer the default value to be False, which might be a legitimate issue, but that would be a problem with the mod, not the config file parsing.You misunderstand me.
My intention was, that there is in most of the mods following code:
if (config != None):
But the CvConfigParser doesn't return None, if there is no ini, so the if thread is worked, even if we don't want it.
So I added an entry to the ini, that is read, and its value is "True".
But, if there is no ini, the returned value is "False".
If the returned value is "False", then I define:config = NoneSo, for the code[code]if (config != None):[code]while config is "None", the if thread isn't worked.
Or to say it more easy, though the code if (config != None): is rubbish, we could delete this line, or extend the ini and the CvCustomEventManger.py with posted code.
TheLopez Jul 28, 2006, 11:17 AM FYI, to make this mod compatible with the Warlords expansion you will need to change the _getInstallDir method in CvPath.py from:
def _getInstallDir():
return __getRegValue(_winreg.HKEY_LOCAL_MACHINE,
r"Software\Firaxis Games\Sid Meier's Civilization 4",
"INSTALLDIR")
to
def _getInstallDir():
civ4Dir = __getRegValue(_winreg.HKEY_LOCAL_MACHINE,
r"Software\Firaxis Games\Sid Meier's Civilization 4",
"INSTALLDIR")
civ4Dir = os.path.join(civ4Dir,"Warlords")
return civ4Dir
It's that easy. Happy Warlords modding :D
Jeckel Jul 31, 2006, 12:18 AM Does this change make it able to work for both Civ4 and Warlords?
TheLopez Jul 31, 2006, 05:47 AM No just Warlords.
Jeckel Aug 01, 2006, 11:24 AM Hmm, I don't like the idea of having to release two copys of each version of my mods when most are only going to have this one method different.
What I'm doing is, in the CvPath.py file, changeing this
def _getInstallDir():
return __getRegValue(_winreg.HKEY_LOCAL_MACHINE,
r"Software\Firaxis Games\Sid Meier's Civilization 4",
"INSTALLDIR")
and this
activeModName = None
try:
import CvModName
activeModName = CvModName.modName
except:
pass
to this
def _getInstallDir():
civ4Dir = __getRegValue(_winreg.HKEY_LOCAL_MACHINE,
r"Software\Firaxis Games\Sid Meier's Civilization 4",
"INSTALLDIR")
if (activeExpansionWarlords):
civ4Dir = os.path.join(civ4Dir,"Warlords")
return civ4Dir
and this
activeModName = None
activeExpansionWarlords = False
try:
import CvModName
activeModName = CvModName.modName
activeExpansionWarlords = CvModName.expansionWarlords
except:
pass
Then, ofcourse, I add a variable in CvModName.py file called expansionWarlords. Then I can just release my mods with the expansionWarlords variable set to False and it will work like normal with Civ4.
For those that want to play it with Warlords I will offer a version of the CvModName.py file with expansionWarlords variable set to True. They can then download and install this one file and it will work for warlords.
Now this is probly not the most elegant way of doing it, so if someone knows an easyer way to check if the game is warlords or Civ4 (I don't know, say checking the version number of the game or somthing), please post up, would be much helpfull. :)
Gaurav Aug 03, 2006, 09:42 AM Now this is probly not the most elegant way of doing it, so if someone knows an easyer way to check if the game is warlords or Civ4 (I don't know, say checking the version number of the game or somthing), please post up, would be much helpfull. :)
What is possible is checking using os.path.file.exists() whether CvPath.py exists where it is supposed to be in the regular and Warlords directories across all possibilities in the assetsPath. Then if it is only one of the two, automatically setting activeExpansionWarlords appropriately.
That code is complicated enough. However, what do you do if someone has installed your mod in both directories? (I could fall back on that flag you made for CvModName.py...) Or worse, different versions of your mod? (I could add a version number to CvModName.py...)
Jeckel Aug 03, 2006, 10:20 AM What is possible is checking using os.path.file.exists() whether CvPath.py exists where it is supposed to be in the regular and Warlords directories across all possibilities in the assetsPath. Then if it is only one of the two, automatically setting activeExpansionWarlords appropriately.
That code is complicated enough. However, what do you do if someone has installed your mod in both directories? (I could fall back on that flag you made for CvModName.py...) Or worse, different versions of your mod? (I could add a version number to CvModName.py...)
Nods, yea, I had thought of this basic idea, but anyone that has warlords installed will also have civ4 installed, so you then run into that problem of not knowing which they are acually playing. You would think there would be some CyGame method that would tell you the version of civ, or if it is vanilla or an expansion.
As it stands now, if someone
1) has the JUnitReligion mod installed in both vanilla and warlords
2) has expansionWarlords in CvModName.py in the warlords dir set to false
3) loads and plays the mod in warlords
then the mod will use the config files in the vanilla directory, not the warlords.
If the above, except there is no mod in vanilla direcotry then the mod will not see any config files and will use default values.
As for having different versions of the mod, my goal is to get some form of CvPath that is compatiable with both warlords and civ4. If that is accomplished then redundent versions of my mods is unneccessary. :)
I am really interested to hear what, if anything, the Dr. has planned to upgrade it to warlords compatability. I'm just trying to find a way to rig it up until then. :D
Dr Elmer Jiggle Aug 07, 2006, 07:47 AM I am really interested to hear what, if anything, the Dr. has planned to upgrade it to warlords compatability. I'm just trying to find a way to rig it up until then. :D
I just got back from a vacation and haven't had a chance to install Warlords, but I hope to look at it sometime this week. I'm thinking that there might be a way to probe for the existence of one of the new Warlords features and use that as the trigger for which directory you use.
For example, maybe you can see whether there's a "Temple of Artemis" wonder or whether there is a Great General great person. Or there might be some values defined in GlobalDefs.xml. Something like that.
Jeckel Aug 08, 2006, 12:01 PM Welcome back, hope you had fun on your vacation. :)
That is a good idea, checking for somthing in warlords, but I would try to stay away from checking for units, building, wonders, ect. as they could conflict with vanilla mods. For example, a few of TheLopez's mods have a Great General unit and the Lost Wonders Mod has most of the wonders from Warlords, but right off hand checking for somthing in the globaldefines file sounds like a good solution to me. :D
Glad to hear your on the case and looking forward to see how you work it out. :cool:
Gaurav Aug 09, 2006, 01:56 PM Welcome back, hope you had fun on your vacation. :)
That is a good idea, checking for somthing in warlords, but I would try to stay away from checking for units, building, wonders, ect. as they could conflict with vanilla mods. For example, a few of TheLopez's mods have a Great General unit and the Lost Wonders Mod has most of the wonders from Warlords, but right off hand checking for somthing in the globaldefines file sounds like a good solution to me. :D
Glad to hear your on the case and looking forward to see how you work it out. :cool:
I suppose you could also use a try/catch block to check for the existence of a (new) exposed python function from the SDK.
Dr Elmer Jiggle Aug 11, 2006, 11:11 AM There is a CIV4_VERSION define in GlobalDefines.xml. In Warlords it is currently 200, and in an updated Classic it is 161. I'm making the assumption that patches to Warlords will continue to be 2xx and Classic will be 1xx. Thus, if CIV4_VERSION / 100 is 2, we're looking at Warlords.
def _getInstallDir():
gc = CyGlobalContext()
subkey = r"Software\Firaxis Games\Sid Meier's Civilization 4"
if ((gc.getDefineINT("CIV4_VERSION") / 100) == 2):
subkey += r" - Warlords"
return __getRegValue(_winreg.HKEY_LOCAL_MACHINE, subkey, "INSTALLDIR")
This is the only necessary change. Of course, ideally this check would be done in some kind of isWarlords utility function, but I didn't want to create a new CvUtil.py unit just for one utility function, so it is what it is.
DrEJlib.zip (http://forums.civfanatics.com/downloads.php?do=file&id=2292)
jdog5000 Aug 24, 2006, 12:25 PM DrEJ -
I'm having some interesting challenges with this new version in Warlords. Here's what happens:
My mod, Revolution, is set to automatically load in the Warlords Civilization4.ini file. I start up Warlords and choose Custom Game or whatever and I get a popup from my mod saying it was unable to find the .ini config file. I added a line to CvPath.py to print out installDir, and when I check it after this initial game launch it's all vanilla directories.
But, if I then modify some Python line to force the game to reload Python, then check the print out again, it's now all Warlords directories!
Anyone else experiencing this?
EDIT: A little more info, it seems like on the initial game launch load, the value returned from gc.getDefineINT("CIV4_VERSION") is 0, I guess because XML hasn't been properly loaded yet. So this probably wouldn't be a problem if you started up the game, then loaded a mod? Still would be nice to have a good solution to this ... but I don't have any good ideas.
Dr Elmer Jiggle Aug 24, 2006, 01:00 PM Try printing out the value of CIV4_VERSION too. Since that's what's determining which registry key to use for the install directory, that's really the critical question.
Does your mod include a custom version of GlobalDefines.xml? That's where CIV4_VERSION comes from. Maybe your mod has an old value for that?
jdog5000 Aug 24, 2006, 02:16 PM Try printing out the value of CIV4_VERSION too. Since that's what's determining which registry key to use for the install directory, that's really the critical question.
Does your mod include a custom version of GlobalDefines.xml? That's where CIV4_VERSION comes from. Maybe your mod has an old value for that?
Did that just after posting and editted it into my previous post ... my mod contains no xml at all, and my custom assets folder is empty.
On the initial game load, the value returned is 0 but on subsequent loads the value is 200 as it should be.
AlanH Sep 02, 2006, 11:20 AM Hi
As a result of debugging Civ4Lerts on a Mac, I have posted a modified version of your CvPath.py module in the Civ4lerts thread. As this seems to be the main thread covering this module, I thought I should post the details here.
If it detects Mac OS X (sys.platform = 'darwin') it provides Mac-specific paths. Otherwise it provides the existing Windows registry-based paths. Assuming this still works OK on Windows (I can't test it myself), I recommend you update this module so that future mods using CvPath.py are more likely to work on Macs.
I replaced the three function defs that use _winreg with:
import sys
if (sys.platform == 'darwin'):
""" Mac OS X """
def _getUserDir():
return os.path.join(os.environ['HOME'], "Documents", "Civilization IV")
def _getInstallDir():
import commands
civ4name = "Civilization IV.app/Contents/MacOS/Civilization IV"
str = commands.getoutput("ps -xo 'command' | grep " + "'" + civ4name + "'")
m = str.find(civ4name)
if (m >= 0):
installDir = str[0:m]
return installDir
else:
import _winreg
""" Windows """
def __getRegValue(root, subkey, name):
key = _winreg.OpenKey(root, subkey)
try:
value = _winreg.QueryValueEx(key, name)
return value[0]
finally:
key.Close()
def _getUserDir():
myDocuments = __getRegValue(_winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer \Shell Folders",
"Personal")
civ4Dir = os.path.basename(_getInstallDir())
return os.path.join(os.path.join(myDocuments, "My Games"), civ4Dir)
def _getInstallDir():
gc = CyGlobalContext()
subkey = r"Software\Firaxis Games\Sid Meier's Civilization 4"
if ((gc.getDefineINT("CIV4_VERSION") / 100) == 2):
subkey += r" - Warlords"
return __getRegValue(_winreg.HKEY_LOCAL_MACHINE, subkey, "INSTALLDIR")
The modified file is attached to my post here
Teg_Navanis Sep 02, 2006, 11:27 AM Thanks for the file. I can't make my mod mac-compatible though, since it also has a custom CvGameCoreDLL. :(
AlanH Sep 02, 2006, 11:32 AM Thanks for the file. I can't make my mod mac-compatible though, since it also has a custom CvGameCoreDLL. :(
If the dll is essential to the plot of your mod then we just have to wait for Aspyr and Firaxis to work out a way to allow Mac users to join in the fun. Meanwhile, if you guys get used to providing Mac file path support, it will be one less issue to worry about when and if the time comes when we can use an SDK.
jray Nov 03, 2006, 07:59 AM It seems that .INI files sitting in My Documents\My Games\Warlords\MODS\my mod are not parsed. They need to be in Program Files\Firaxis Games\Sid Meier's Civilization 4\Warlords\MODS\my mod (even if the rest of the mod is in the aforementioned directory). Any chance you could post a fix for this? I'm trying, but I don't seem to be familiar enough with Python and registry paths to hit on the right combination. Thanks!
|
|