Quick Modding Questions Thread

Hi.
I try to add sort of random event, but can't get it to work.
I want [unit] to randomly spawn in cities with [building]. Additionaly the new unit is supposed to recive [promotion1] when [building1] is present in the spawning city. Adding promotins 'might' work, but spawning does not.
This is pretty much the same as 'Sheaim' civilization's 'Planar Gate' from 'RIFE' (if anyone knows the mod). I just can't find this script in python files to compare with my scribbles...
There's one more thing i can think of, that i could (should?) add to the script, but are not sure if it will help.
I'll be grateful for any help with this. A to-do checklist maybe? If someone would be willing to give me larger scale aid, i can send the entirity of my scribbles.
Yes, I am totally green in modding...
 
I want [unit] to randomly spawn in cities with [building]
First you need to set up an event trigger condition. Sounds like all you need to do is to set the tag BuildingsRequired and it should trigger only in cities with the selected building class.

I want [unit] to randomly spawn in cities with [building]. Additionaly the new unit is supposed to recive [promotion1] when [building1] is present in the spawning city. Adding promotins 'might' work, but spawning does not.
I have no idea what you have done, but it sounds fairly strait forward to me. Make a python callback in the event. The events are in python/EntryPoints/CvRandomEventInterface.py

Look at applyTsunami1
PHP:
def applyTsunami1(argsList):
   iEvent = argsList[0]
   kTriggeredData = argsList[1]
  
   player = gc.getPlayer(kTriggeredData.ePlayer)
   city = player.getCity(kTriggeredData.iCityId)
  
   city.kill()
Obviously you don't need the last line as it kills the city, but the rest gives you what you need: kTriggeredData, player and city.

The full list of variables is in CyStructInterface1.cpp (yes the DLL code, but you don't have to compile it to just read lists like this)
Spoiler :
PHP:
    python::class_<EventTriggeredData>("EventTriggeredData")
       .def_readwrite("iId", &EventTriggeredData::m_iId)
       .def_readwrite("eTrigger", &EventTriggeredData::m_eTrigger)
       .def_readwrite("iTurn", &EventTriggeredData::m_iTurn)
       .def_readwrite("ePlayer", &EventTriggeredData::m_ePlayer)
       .def_readwrite("iCityId", &EventTriggeredData::m_iCityId)
       .def_readwrite("iPlotX", &EventTriggeredData::m_iPlotX)
       .def_readwrite("iPlotY", &EventTriggeredData::m_iPlotY)
       .def_readwrite("iUnitId", &EventTriggeredData::m_iUnitId)
       .def_readwrite("eOtherPlayer", &EventTriggeredData::m_eOtherPlayer)
       .def_readwrite("iOtherPlayerCityId", &EventTriggeredData::m_iOtherPlayerCityId)
       .def_readwrite("eReligion", &EventTriggeredData::m_eReligion)
       .def_readwrite("eCorporation", &EventTriggeredData::m_eCorporation)
       .def_readwrite("eBuilding", &EventTriggeredData::m_eBuilding)
Now you need to create a new unit using the player interface:
PHP:
.def("initUnit", &CyPlayer::initUnit, python::return_value_policy<python::manage_new_object>(), "CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex)  - place Unit at X,Y   NOTE: Always use UnitAITypes.NO_UNITAI")
I read it as you need to write:
PHP:
unittype = gc.getDefineINT("UNIT_TYPE") # obviously use the type from xml
unit = player.initUnit(unittype, kTriggeredData.plotX, kTriggeredData.plotY, UnitAITypes.NO_UNITAI)
Next add the promotion if the city has the building in question:
PHP:
building = gc.getDefineINT("building1")
if city.isHasBuilding(building):
    promotion1 = gc.getDefineINT("PROMOTION_promotion1")
    unit.setHasPromotion(promotion1)
I haven't tested this, but I think that's how to do it, or at least one way to do it ;)
 
As for 'add promotion', it seems like i allready did it right.
I also use the
if CyGame().getSorenRandNum(100, "SpawnMbomb") <= iSpawnChance: - not sure, but 'spawnmbomb' (mbomb is the name of my unit) seems to be for ease of read only, that's the feeling i get from reading different scripts.
line to make it random. Do i need it here as well?

kTriggeredData = argsList[1]
What is 'argsList' abaut? Like does it use a 'premade' list, or something, or the lines below make the list?

.def("initUnit", &CyPlayer::initUnit, python::return_value_policy<python::manage_new_object>(), "CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex) - place Unit at X,Y NOTE: Always use UnitAITypes.NO_UNITAI")
Not sure how much i understand here... I truly feel dumb looking at this line. Everything below is quite simple even for me.

As for now, it seems like my worst mistake was not making any event triggers at all... Never played with them before, time to change that.

Thank You very much, I'd still know nothing if it wasn't for you.
Have a nice day. :)
 
kTriggeredData = argsList[1]
What is 'argsList' abaut? Like does it use a 'premade' list, or something, or the lines below make the list?
Whenever the DLL file makes a call to python, it generates a list (array) of arguments. Simply put, it's a list of variables the DLL wants to give to python. There are multiple locations in the DLL, which calls python and each tend to have a unique setup of arguments. However it's less tricky than it sounds because vanilla gets the arguments right. This means you can look up some vanilla event, which has a python callback and then you copy paste the argsList code from that one. I picked applyTsunami1, though any event with a python callback could be used for this.

The contents of EventTriggeredData is a bit more tricky. Experience made me look it up in CyStructInterface1.cpp, though I also read CvStructs.h too.

.def("initUnit", &CyPlayer::initUnit, python::return_value_policy<python::manage_new_object>(), "CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex) - place Unit at X,Y NOTE: Always use UnitAITypes.NO_UNITAI")
Not sure how much i understand here... I truly feel dumb looking at this line. Everything below is quite simple even for me.
All the game data is in the DLL. This means when you run code in python, you need to call DLL functions to read and modify the game data. This is done by getting a pointer to an object and then call member functions.

Luckily it's not as tricky as it sounds. Take for instance:
PHP:
player = gc.getPlayer(kTriggeredData.ePlayer)
gc is CyGlobalContext, which is an object, which is always available.
getPlayer is a member function of CyGlobalContext. It returns a CyPlayer object, which is stored in player. player can then use the member functions for CyPlayer.

The member functions are listed in Cy*Interface*.cpp. The first * is the object (like player) and the last is a number (which can be skipped). Due to a compiler limitation, the interface files can't exceed 64 kB, which is why some of them have the functions split into multiple files.

In the interface files, each member function is defined in a single line.
PHP:
.def("initUnit", &CyPlayer::initUnit, python::return_value_policy<python::manage_new_object>(), "CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex)  - place Unit at X,Y   NOTE: Always use UnitAITypes.NO_UNITAI")
It's in CyPlayer, which means it's a function, which can be used by player objects.
  1. "initUnit"
    is the name of the function, as in what you need to write in python
  2. &CyPlayer::initUnit
    is the name of the function inside the DLL to call. You can look up the function if you like
  3. python::return_value_policy<python::manage_new_object>()
    is return policy as in memory management. Forget about this unless you mod the DLL interface itself
  4. "CyUnit* initUnit(UnitTypes iIndex, plotX, plotY, UnitAITypes iIndex) - place Unit at X,Y NOTE: Always use UnitAITypes.NO_UNITAI"
    the last is always a comment. It tells how to use the function
Figuring out how to read the python interface in the DLL should help a lot when writing python code. People talk about python exposed functions and not exposed functions. The functions in the interface are the exposed functions while the non-exposed are those present in the DLL, but aren't mentioned in the interface files, meaning python can't access them.
 
After reading your post i looked back on my code, and realised it had no chances of working as intended. :)

If i understand it correctly, i'll have to find out wich argslist fit my needs.
My current scribble is in spoiler, if You want to see how bad it is. :)
Spoiler code :

def SpawnMbomb(argsList):
iEvent = argsList[0]
kTriggeredData = argsList[1]

pPlayer = gc.getPlayer(kTriggeredData.ePlayer)
pCity = pPlayer.getCity(kTriggeredData.iCityId)
iSpawnChance = 100

if pCity.getNumRealBuilding(gc.getInfoTypeForString('BUILDING_MBOMBMOD1')) > 0:
iPromotion = gc.getInfoTypeForString('PROMOTION_MBOMBMOD1')
if pCity.getNumRealBuilding(gc.getInfoTypeForString('BUILDING_MBOMBMOD2')) > 0:
iPromotion = gc.getInfoTypeForString('PROMOTION_MBOMBMOD2')

if pCity.getNumRealBuilding(gc.getInfoTypeForString('BUILDING_MANABOMBWORKSHOP')) > 0:
if CyGame().getSorenRandNum(100, "SpawnMbomb") <= iSpawnChance:
iMbomb = gc.getInfoTypeForString('UNIT_MBOMB')
newUnit = pPlayer.initUnit(iMbomb, pCity.getX(), pCity.getY(), UnitAITypes.NO_UNITAI, DirectionTypes.DIRECTION_SOUTH)
newUnit.setHasPromotion(iPromotion, True)

You're more than helpful to me, if it means anything. :)
-- Nevermind, i just reread what You wrote abaut argslists, id does't seem like this is any close to proper code. --
 
Last edited:
Question : where can I find how to adjust city maintence?
I found the distance modifier in global defines

<Define>
<DefineName>MAX_DISTANCE_CITY_MAINTENANCE</DefineName>
<iDefineIntVal>25</iDefineIntVal>
</Define>

However is there anywhere I can find the modifier for number of cities?
There is one set in the Handicap and another in the WorldInfo files in the XML/GameInfo folder
 
What are acceptable polygon counts for building models? I'm currently browsing Google 3D Warehouse, but all the polygon counts seem very daunting to me.
 
What are acceptable polygon counts for building models? I'm currently browsing Google 3D Warehouse, but all the polygon counts seem very daunting to me.
The more common the building will be the less polygons it should have. I think around 1000 polygons is a good number to aim for, but you should be able to go higher for wonders and such.
 
Alright, thanks. I'm looking for wonders, but I guess that rules out the ~20k models I've seen :)
 
Is there a way to change the default start era in the Custom Game screen?

The two mods I know of that add eras before Ancient have left the first era as ERA_ANCIENT and just changed its the displayed text to the earlier era and used another era for the ancient era.
 
GlobalDefines contains this, though I haven't tried changing it.
PHP:
<Define>
    <DefineName>STANDARD_ERA</DefineName>
    <DefineTextVal>ERA_ANCIENT</DefineTextVal>
</Define>
The start era is also mentioned in the ini file, which makes me wonder if it is a good idea not to have the same in all mods.
Tried that but it had no affect. :(
 
Why are the player colours of my new civilisations not working? I get errors like these:

YPIAbNJ.png


But, what does that even mean? All my code looks just like any other of the dozens of civilisations I have added:

IKjsU2a.png

sWLnRkI.png

81fNlZX.png
 
This error originates from CvXMLLoadUtility::FindInInfoClass, which in turn relies on GC.getInfoTypeForString. This one is a map (aka hash table), which contains all Type tags read until this point (the read code sets it) and it will return the index. The error occurs when the string isn't found. In other words this can only happen if the file with the Type in question is not read.

The next question is why is the file not read? One answer is file reading order, but since vanilla can read colors for civs, I assume that's not the case without actually checking (you can read the order in CvXMLLoadUtilitySet.cpp, though the list is spread across 7 functions, hence fragmented).

The next thing, which comes to mind is that there is some error in the player color file, which prevents it from reading the string, though I have no idea what it could be. It would be interesting to try to load with a debug DLL to see what it will assert on. If the assert approach fails, the only alternative I can think of is to debug and set a breakpoint when it loads the player color to step through and see what it does.

edit: try using Notepad++ on the xml files. Go into top menu->view->Show Symbols->Show All Characters. With this on, you will be able to detect if your xml file for some odd reason has ended up with an invisible character, either in Type or in PlayerColor, which will prevent them from being the same.
 
That is an extremely detailed answer!

I have never been able to successfully compile any kind of .dll, nor have I ever changed it - well, aside from using the 50+ civilisations one - so I will assume that .cpp is unchanged... If I can make that assumption?

With the debug .dll, do you mean enabling LoggingEnabled and the like, in CivilizationIV.ini?

I saw nothing out of the ordinary, but I did notice one new thing upon launching the game. I added SMAC's seven civilisations, and the player colour error messages are in the order of the player colour's appearance - but there is an eighth one about the Lord's Believers' colour (which is also the sixth one, as it should be; the civilisations are in the order of Sparta, Gaia, University, Peacekeepers, Hive, Lord, Morgan). I see nothing odd that could explain this though; there is only one single reference to this colour in CIV4CivilizationInfos.xml, and only one such colour in CIV4PlayerColorInfos.xml.

Pseudo-EDIT: Actually, I rewrote the entire thing, and put the new colours somewhere in the middle, and now it works... So far.

Well. Thank you for your help, really. It was a very detailed and informed answer. :)
 
One week later and one question more!

Could someone confirm whether this resource works: https://forums.civfanatics.com/resources/byzantine-greek-flamethrower.15680/

Specifically, when I try to use it, I get something similar to this: https://forums.civfanatics.com/threads/help-with-empty-civilopedia.383539/#post-9670976

The Civlopedia page only shows the unit's button. In-game, when I select the unit and attack with it, the text pop-ups ('99% of victory' and such) disappear, and entering and exiting the city interface doesn't actually close the city interface.
 
Hello! I'm trying to figure out if it is possible to rename your cities with more than 15 characters, I was looking through the xml files and couldn't find anything. Any help please? Thanks a lot!
 
Back
Top Bottom