1. Firaxis celebrates the "Asian American and Pacific Islander Heritage Month", and offers a give-away of a Civ6 anthology copy (5 in total)! For all the details, please check the thread here. .
    Dismiss Notice

Quick Modding Questions Thread

Discussion in 'Civ4 - Creation & Customization' started by kiwitt, Jan 27, 2010.

  1. The Snug

    The Snug The Civ Heretic

    Joined:
    Dec 5, 2003
    Messages:
    1,006
    Gender:
    Male
    Location:
    Seattle
    This all looks like Greek to me. Is the 'PYTHON' supposed to be the name of the feature?
     
  2. <Nexus>

    <Nexus> Traveler of the Multiverse

    Joined:
    Jan 23, 2014
    Messages:
    4,852
    Gender:
    Male
    Location:
    In a constant brainstorm...
    Programing language to write the code in :lol:
    Civ4 has some parts written in python (these are easier to access to modders) and some in C+ (or C#? Whatever). Of course XML is the easiest to mod also the most limited.
     
    Nightinggale likes this.
  3. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    1,159
    Location:
    Germany
    The feature gets referenced through its ID (variable iI). The loop runs through all features. To obtain the ID of one particular feature, e.g.
    self.gc.getInfoTypeForString("FEATURE_JUNGLE")
    is used. (self.gc having been set to CyGlobalContext() in FeatureGenerator.__init__)

    The random number generator takes as its second argument a debug string that gets written to Logs\MPLog.txt (if RandLog is enabled in My Games\Beyond the Sword\CivilizationIV.ini). Those log entries are, among other things, useful for debugging synchronization problems in multiplayer games, I guess that's why they get written to the "MP" log. In this case, I think the message is supposed to indicate that a random number was used for (possibly) adding a feature, and that this happened in a Python script.

    My takeaway from this code fragment is that all features with a positive appearance probability (at per-myriad precision) set in Civ4FeatureInfos.xml get placed by all map scripts that use the standard FeatureGenerator – which may well be all you need. However, if your feature has placement requirements beyond those configurable in XML, e.g. appearing only at certain latitudes, then iAppearance is no help; will have to write some Python code. Oasis has all its restrictions set in XML, and it gets placed through iAppearance=500, i.e. on an expected 5.00% of the flat noncoastal nonriver desert tiles that aren't already adjacent to an oasis. Jungle can only appear on grassland, the rest is handled explicitly by the FeatureGenerator in Python.

    By the way, just noticed a small thing that I'll want to improve: The code I've posted gets executed in this order:
    Code:
    for iX in range(self.iGridW):
        for iY in range(self.iGridH):
            self.addFeaturesAtPlot(iX, iY)
    Since oases can't appear adjacent to each other, this order of execution biases oasis placement toward tiles with low x coordinates. If iAppearance were much higher, oases would appear noticeably more often one tile away from a western coast than from an eastern coast. I'm going to randomize the order of traversal. (The map generation code has these small arbitrary biases in a lot of places; trying to weed that out.) Edit: Had gotten some directions confused in this paragraph.
     
    Last edited: Jan 15, 2022
  4. The Snug

    The Snug The Civ Heretic

    Joined:
    Dec 5, 2003
    Messages:
    1,006
    Gender:
    Male
    Location:
    Seattle
    So, I've added a new terrain type, and got the mapscript to place it, the problem is, it's only plotting it in tundra areas. How do I control the latitude ranges for new terrains?
     
  5. <Nexus>

    <Nexus> Traveler of the Multiverse

    Joined:
    Jan 23, 2014
    Messages:
    4,852
    Gender:
    Male
    Location:
    In a constant brainstorm...
    Is there a way to make era music not when entering the city screen?
     
  6. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    1,159
    Location:
    Germany
    @<Nexus>: Someone recently suggested removing the <CitySoundscapes> tags from Civ4EraInfos.xml,
    but this causes the era music to restart upon opening a city screen. I'm not aware of any way of letting it continue uninterrupted.
     
    <Nexus> likes this.
  7. <Nexus>

    <Nexus> Traveler of the Multiverse

    Joined:
    Jan 23, 2014
    Messages:
    4,852
    Gender:
    Male
    Location:
    In a constant brainstorm...
    What really interesting is, that one can "bring back" the music by entering the city screen than open and close the pedia while still in the city.
     
  8. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    1,159
    Location:
    Germany
    Weird indeed. Foreign Advisor (F4) also has that effect. Maybe something that the EXE does upon closing a screen, perhaps tied to WidgetTypes.WIDGET_CLOSE_SCREEN (though closing the screen with Esc also works). Might be possible to create a hack based on that, but, actually, having just tried @Buffalo Solider's approach (the thread I had linked to), it seems that the music does not start over when no city soundscapes exist. Don't know how I got that idea. So this seems to work fine, apart from the change in volume caused by zooming in.
     
    <Nexus> likes this.
  9. P&nny

    P&nny Warlord

    Joined:
    Sep 1, 2007
    Messages:
    160
    Hello team, can you help with something I thought would be quite straightforward and turns out surprisingly difficult to find where to change :

    I would like to be able to add a line when you have several units of cargos.
    On top of "Cargo Space: 3/3"
    I would like to add "Total Cargo Space Selection: 5/15"

    upload_2022-2-1_21-18-16.png

    I would have assumed I can change it in this function : void CvGameTextMgr::setUnitHelp
    Since it's the only place in the whole C++ calling : "TXT_KEY_UNIT_HELP_CARGO_SPACE"

    However I noticed that none of the changes I do (there or elsewhere) in ::setUnitHelp are producing any changes.
    An ultra obvious test would be to double up the line and it doesn't do anything
    Code:
    ....
        {
            //2.25f end
            szTempBuffer = NEWLINE + gDLL->getText("TXT_KEY_UNIT_HELP_CARGO_SPACE", 5, pUnit->cargoSpace());//just checking twice
            szTempBuffer = NEWLINE + gDLL->getText("TXT_KEY_UNIT_HELP_CARGO_SPACE", pUnit->getCargo(), pUnit->cargoSpace());
        }
    I would be very surprised but is this not the location in the code ? Not in the C++ ? I've looked at the widgets and I don't see anything related either
     
  10. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    1,159
    Location:
    Germany
    It might work if you change the 2nd line from "=" to "+=".
    A debugger would generally help with such conundrums, would show whether the assignment is reached and how it affects the szTempBuffer variable. (There are instructions -in bold- at the end of Nightinggale's makefile thread.)

    By the way, regarding city sounds, through the DLL, it's easy enough to create a BUG option for that. Just need to return -1 from CvCity::getSoundscapeScriptId when the sounds are disabled.
     
    <Nexus> likes this.
  11. P&nny

    P&nny Warlord

    Joined:
    Sep 1, 2007
    Messages:
    160
    Can't believe it, so silly... excellent spot, it does work of course. Will make the loop for the count properly now!
    I will also read the link you sent ! Might help me a ton.

    I'll ask another for the road then... :

    MIN_WATER_SIZE_FOR_OCEAN

    I thought this would be a global define (which is set at 10) where if the "lake" is less than 10 tiles, you cannot build Boats in the lake,
    But if the lake is more than 10 you can.

    Well it's not I don't see what it changes to change this. Any idea how to change that you can make boats in EVERY city that has access to X amount of water tiles ? (am thinking 2 water tiles).
     
    f1rpo likes this.
  12. The Snug

    The Snug The Civ Heretic

    Joined:
    Dec 5, 2003
    Messages:
    1,006
    Gender:
    Male
    Location:
    Seattle
    That's controlled in the xml for unitinfos. Each water unit defines how many water tiles it needs.with the tag: MinAreaSize. If you make this low, though, however, then the AI will build entire navies on lakes.
     
  13. Leoreth

    Leoreth Vampire of the Blue Moon Moderator

    Joined:
    Aug 23, 2009
    Messages:
    35,730
    Gender:
    Male
    Location:
    Paris
    Scenario files (aka CivBeyondSwordWBSave) have this GameInfo block:
    Code:
    BeginGame
       Calendar=CALENDAR_DEFAULT
       Option=GAMEOPTION_AGGRESSIVE_AI
       Option=GAMEOPTION_NO_TECH_BROKERING
       GameTurn=181
       StartYear=-3000
       Description=TXT_KEY_RHYES_WB_DESC
       ModPath=Mods\RFC Dawn of Civilization
    EndGame
    I am particularly interested in the "GameTurn" entry. My understanding is that the file is parsed by the pyWB/CvWBDesc.py file. However, it doesn't actually seem to do anything with the parsed GameTurn value:
    Code:
    class CvGameDesc:
       "class for serializing game data"
       def __init__(self):
           self.eraType = "NONE"
           self.speedType = "NONE"
           self.calendarType = "CALENDAR_DEFAULT"
           self.options = ()
           self.mpOptions = ()
           self.forceControls = ()
           self.victories = ()
           self.gameTurn = 0
           self.maxTurns = 0
           self.maxCityElimination = 0
           self.numAdvancedStartPoints = 0
           self.targetScore = 0
           self.iStartYear = -4000
           self.szDescription = ""
           self.szModPath = ""
           self.iRandom = 0
           
       def apply(self):
           "after reading, apply the game data"
           gc.getGame().setStartYear(self.iStartYear)
           
       def write(self, f):
           "write out game data"
           f.write("BeginGame\n")
           f.write("\tEra=%s\n" %(gc.getEraInfo(gc.getGame().getStartEra()).getType(),))
           f.write("\tSpeed=%s\n" %(gc.getGameSpeedInfo(gc.getGame().getGameSpeedType()).getType(),))
           f.write("\tCalendar=%s\n" %(gc.getCalendarInfo(gc.getGame().getCalendar()).getType(),))
           
           # write options
           for i in range(gc.getNumGameOptionInfos()):
               if (gc.getGame().isOption(i)):
                   f.write("\tOption=%s\n" %(gc.getGameOptionInfo(i).getType()))
                   
           # write mp options
           for i in range(gc.getNumMPOptionInfos()):
               if (gc.getGame().isMPOption(i)):
                   f.write("\tMPOption=%s\n" %(gc.getMPOptionInfo(i).getType()))
                   
           # write force controls
           for i in range(gc.getNumForceControlInfos()):
               if (gc.getGame().isForcedControl(i)):
                   f.write("\tForceControl=%s\n" %(gc.getForceControlInfo(i).getType()))
                   
           # write victories
           for i in range(gc.getNumVictoryInfos()):
               if (gc.getGame().isVictoryValid(i)):
                   if (not gc.getVictoryInfo(i).isPermanent()):
                       f.write("\tVictory=%s\n" %(gc.getVictoryInfo(i).getType()))
                   
           f.write("\tGameTurn=%d\n" %(gc.getGame().getGameTurn(),))
           f.write("\tMaxTurns=%d\n" %(gc.getGame().getMaxTurns(),))
           f.write("\tMaxCityElimination=%d\n" %(gc.getGame().getMaxCityElimination(),))
           f.write("\tNumAdvancedStartPoints=%d\n" %(gc.getGame().getNumAdvancedStartPoints(),))
           f.write("\tTargetScore=%d\n" %(gc.getGame().getTargetScore(),))
           
           f.write("\tStartYear=%d\n" %(gc.getGame().getStartYear(),))
           f.write("\tDescription=%s\n" % (self.szDescription,))
           f.write("\tModPath=%s\n" % (self.szModPath,))
           f.write("EndGame\n")
           
       def read(self, f):
           "read in game data"
           self.__init__()
           
           parser = CvWBParser()
           if (parser.findNextTokenValue(f, "BeginGame")!=-1):
               while (true):
                   nextLine = parser.getNextLine(f)
                   toks = parser.getTokens(nextLine)
                   if (len(toks)==0):
                       break
                       
                   v = parser.findTokenValue(toks, "Era")
                   if v!=-1:
                       self.eraType = v
                       continue
                       
                   v = parser.findTokenValue(toks, "Speed")
                   if v!=-1:
                       self.speedType = v
                       continue
    
                   v = parser.findTokenValue(toks, "Calendar")
                   if v!=-1:
                       self.calendarType = v
                       continue
    
                   v = parser.findTokenValue(toks, "Option")
                   if v!=-1:
                       self.options = self.options + (v,)
                       continue
                       
                   v = parser.findTokenValue(toks, "MPOption")
                   if v!=-1:
                       self.mpOptions = self.mpOptions + (v,)
                       continue
                       
                   v = parser.findTokenValue(toks, "ForceControl")
                   if v!=-1:
                       self.forceControls = self.forceControls + (v,)
                       continue
                       
                   v = parser.findTokenValue(toks, "Victory")
                   if v!=-1:
                       self.victories = self.victories + (v,)
                       continue
                       
                   v = parser.findTokenValue(toks, "GameTurn")
                   if v!=-1:
                       self.gameTurn = int(v)
                       continue
    
                   v = parser.findTokenValue(toks, "MaxTurns")
                   if v!=-1:
                       self.maxTurns = int(v)
                       continue
                       
                   v = parser.findTokenValue(toks, "MaxCityElimination")
                   if v!=-1:
                       self.maxCityElimination = int(v)
                       continue
    
                   v = parser.findTokenValue(toks, "NumAdvancedStartPoints")
                   if v!=-1:
                       self.numAdvancedStartPoints = int(v)
                       continue
    
                   v = parser.findTokenValue(toks, "TargetScore")
                   if v!=-1:
                       self.targetScore = int(v)
                       continue
    
                   v = parser.findTokenValue(toks, "StartYear")
                   if v!=-1:
                       self.iStartYear = int(v)
                       continue
                       
                   v = parser.findTokenValue(toks, "Description")
                   if v!=-1:
                       self.szDescription = v
                       continue
                       
                   v = parser.findTokenValue(toks, "ModPath")
                   if v!=-1:
                       self.szModPath = v
                       continue
    
                   v = parser.findTokenValue(toks, "Random")
                   if v!=-1:
                       self.iRandom = int(v)
                       continue
    
                   if parser.findTokenValue(toks, "EndGame") != -1:
                       break
    As you can see, the apply method actually doesn't set most of the values parsed here. But clearly the GameTurn entry works (and I am sure so do the others). When I set it to 181, the game will actually start with CyGlobalContext().getGame().getGameTurn() == 181.

    How does that work? Is there another part of the game that also parses the file? Where? Or does it happen in the exe? I am fairly sure it must be because even breakpoints at CvInitCore::setGameTurn do not get triggered with 181. Just checking that I am not missing anything I am unaware of.
     
  14. Sephi

    Sephi Deity

    Joined:
    Jan 25, 2009
    Messages:
    2,958
    I think what happens is this:
    exe will call getGameData in CvWBInterface.py which will call CvWBDesc.py to parse the scenario file. The gameturn with all other scenario data is passed to the exe via the read function.
    The gamedata is then later passed from the exe to the gamecore dll.
     
  15. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    1,159
    Location:
    Germany
    So I suppose this last part happens through some form of raw memory access to a blank instance of CvInitCore. Which might explain why changes to the memory layout of the "CORE PLAYER INIT DATA" of CvInitCore result in memory corruptions – despite all constructors being DLL-internal. (Exported constructors are usually the reason for memory corruptions when changing the size of a class in the DLL.)

    In the debugger, I see that iGameTurn is still 0 when I select the radio button of the Earth1000AD scenario (breakpoint in setMapScriptName). When I confirm that selection and the civ selection screen comes up (setCiv call assigning me the default civ, China), iGameTurn is 160. Some further rather pointless observations:
    Spoiler :
    Those setter calls modify CvGlobals::m_initCore, but there are two other global CvInitCore instances: m_loadedInitCore and m_iniInitCore. The latter seems to get modified (presumably with data from CivilizationIV.ini) only when BtS starts up. The "loaded" init core also gets a setCiv call upon loading the civ selection menu, but never seems to learn the map script name. So two instances are involved with the scenario setup screens, but the loaded init core seems to have a lesser role. The reset functions that take a CvInitCore pointer "pSource" seem to get used only for resetting both the regular and the loaded init core to the INI values when returning to the opening menu (i.e. pSource is always the ini init core). Edit: Upon loading a savegame, the regular init core gets reset with the loaded init core as the source pointer. So it looks like the loaded init core is mainly concerned with loading savegames.
     
    Last edited: Feb 2, 2022
  16. Sephi

    Sephi Deity

    Joined:
    Jan 25, 2009
    Messages:
    2,958
    I think so as well. Since python just passes a python tuple to the exe instead of using getters for all variables, the memory layout for it is hardcoded. They probably did something similar for the gamecore dll.
     
  17. Leoreth

    Leoreth Vampire of the Blue Moon Moderator

    Joined:
    Aug 23, 2009
    Messages:
    35,730
    Gender:
    Male
    Location:
    Paris
    Interesting, thanks for your answers. I was not aware of CvWBInterface.py and its related method, that makes sense. Since in my use case I am using a normal map script which is backed by a CivBeyondSwordWBSave, I guess that is never called. Not that any of the steps skipped are super complex, so I can just replicate them in Python. Just wanted to make sure I am not totally losing the plot here and needlessly writing duplicate code.
     
  18. MatteM

    MatteM Warlord

    Joined:
    Sep 7, 2015
    Messages:
    275
    Gender:
    Male
    Location:
    Stockholm
    Hello! I was wondering if anyone knows where I could edit the popup-window size for random events, the thing is I have a mod with event images implemented, it is possible to just size up the event images,
    since they are orginally made in 256x256, however for modern screens mostly in 1920x1080 they are too tiny for my taste, but the popup window dimensions need to change in order fit it properly, as you can see
    below I can scroll down so it adaps heightswise but not in width:

    eventpopupwindow.png

    Can I edit the size in some python-file perhaps?
     
  19. <Nexus>

    <Nexus> Traveler of the Multiverse

    Joined:
    Jan 23, 2014
    Messages:
    4,852
    Gender:
    Male
    Location:
    In a constant brainstorm...
    I'd like to know that too :)
     
  20. Sephi

    Sephi Deity

    Joined:
    Jan 25, 2009
    Messages:
    2,958
    The file Assets\python\pyHelper\Popup.py has a function setsize. I suggest to try to modify that.
     
    <Nexus> likes this.

Share This Page