Quick Modding Questions Thread

[...] The length is given like this (though I have an idea of how to make this more pretty/useful):
PHP:
inline unsigned int ArrayLength(PlayerTypes var)
[...]
And if someone adds an enum type, defines an EnumMap<MyEnum,int> and forgets to add an ArrayLength function, there should be a compiler error. Not that enum types are frequently added anyway. That sounds very good. Sure would like to merge those memory optimizations eventually if time allows.
 
How do I get the world history replay, at the end of a game, to tell me what leader founded what city?

I've already found the TXT_KEY_MISC_CITY_IS_FOUNDED and TXT_KEY_MISC_CITY_HAS_BEEN_FOUNDED tags in CIV4GameTextInfos.xml.

Also, I've found the def onCityBuilt in the CvEventManager.py.

No matter what I change or add, I don't get the history to show me WHO (leader-name) founded the cities.

I'm giving up. How do I do it?

My failures shown in the attached files...
Spoiler Long Numbers :

WHFail1.jpg


Spoiler Question Marks :

WHFail2.jpg


Spoiler Short Number :

WHFail3.jpg



PS!
Yes, yes, yes... before you even mention it: I DID COPY THE FILES INTO MY MOD!!! I did not edit the original game-files. I'm a noob, not an idiot. <- frustration shining through clearly enough?
 
[...] Also, I've found the def onCityBuilt in the CvEventManager.py.
It seems that CvGame::addReplayMessage isn't even exposed to Python. So I wonder how you've managed to do anything with replay messages in onCityBuilt. :confused:


Side note to maybe ease the frustration a little: Shift+F1 can bring up the Replay screen from within a running game. This requires the chipotle cheat code, and it looks like it's one of the cheat tools that the BUG mod broke. But in a non-BUG mod it should work.
 
PHP:
void CvCity::doFoundMessage()
{
   CvWString szBuffer;

   szBuffer = gDLL->getText("TXT_KEY_MISC_CITY_HAS_BEEN_FOUNDED", getNameKey());
   gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), false, -1, szBuffer, ARTFILEMGR.getInterfaceArtInfo("WORLDBUILDER_CITY_EDIT")->getPath(), MESSAGE_TYPE_MAJOR_EVENT, NULL, NO_COLOR, getX_INLINE(), getY_INLINE());

   szBuffer = gDLL->getText("TXT_KEY_MISC_CITY_IS_FOUNDED", getNameKey());
   GC.getGameINLINE().addReplayMessage(REPLAY_MESSAGE_CITY_FOUNDED, getOwnerINLINE(), szBuffer, getX_INLINE(), getY_INLINE(), (ColorTypes)GC.getInfoTypeForString("COLOR_ALT_HIGHLIGHT_TEXT"));
}
Looks like that's what you need to edit. More specifically getText("TXT_KEY_MISC_CITY_IS_FOUNDED", getNameKey()). You need to add the player's name as an additional argument and then change the text to include the added argument.

The tricky part is that you need to recompile the DLL after doing this.
 
It seems that CvGame::addReplayMessage isn't even exposed to Python. So I wonder how you've managed to do anything with replay messages in onCityBuilt. :confused:

In onCityBuilt I have:
Code:
    def onCityBuilt(self, argsList):
        'City Built'
        city, iPlayer = argsList[0]
        player = PyPlayer(iPlayer)
        if (city.getOwner() == gc.getGame().getActivePlayer()):
            self.__eventEditCityNameBegin(city, False)   
        CvUtil.pyPrint('City Built Event: %s %s %d' %(city.getName(), player.getActivePlayer(), iPlayer))

In the xml-file I have:
Code:
    <TEXT>
        <Tag>TXT_KEY_MISC_CITY_HAS_BEEN_FOUNDED</Tag>
        <English>%s1_CityName has been founded by %d3_PlyrName.</English>
        <French>Fondation de %s1_CityName.</French>
        <German>Die Stadt %s1_CityName wurde gegr&#252;ndet.</German>
        <Italian>%s1_CityName &#232; stata fondata.</Italian>
        <Spanish>Se ha fundado %s1_CityName.</Spanish>
    </TEXT>
    <TEXT>
        <Tag>TXT_KEY_MISC_CITY_IS_FOUNDED</Tag>
        <English>%s1_CityName is founded by %d3_PlyrName.</English>
        <French>La ville de %s1_CityName est fond&#233;e.</French>
        <German>Die Stadt %s1_CityName wird gegr&#252;ndet.</German>
        <Italian>%s1_CityName fondata.</Italian>
        <Spanish>Se funda %s1_CityName.</Spanish>
    </TEXT>

And the result is:
Spoiler Long Number :

WHFail4.jpg



Technically, I have managed to get the message I need. But how I make the history write "Moscow is founded by Catherine" instead of "Moscow is founded by 232802444" is beyond my comprehension.

It works half-way in H2H, AI vs AI games, but it soon becomes difficult if playing normal games with many leaders involved.

Since I am going to make my own little H2H league, I have what I need. But it is not good-looking... and I have to remember nine-digit numbers....
 
Looks like that's what you need to edit. More specifically getText("TXT_KEY_MISC_CITY_IS_FOUNDED", getNameKey()). You need to add the player's name as an additional argument and then change the text to include the added argument.

The tricky part is that you need to recompile the DLL after doing this.


Hm. Not too uplifting news, in other words. No DLL-recompiling for me, I'm afraid.
If you see my post above this one, I show what I have in XML and CvEventManager.py for now.
If you have any idea at all how to make the game say "Catherine" instead of "232802444" I would be utmost grateful and happy. If it is at all possible?
 
Code:
%s1_CityName has been founded by %d3_PlyrName.

The %d3_PlyrName is causing the error. %d is for displaying numbers. If you input a string, the game will turn it into a number. Change %d to %s and it should replace the name of the civ.
 
PlyrName is just a variable name. The %d vs %s part is relevant, because this determines if the output text is a number or a string.

EDIT:
After seeing the C++ code Nightinggale's post, I see that my solution is not sufficient. You have to also edit the DLL so there are enough input arguments in the text.
 
PlyrName is just a variable name. The %d vs %s part is relevant, because this determines if the output text is a number or a string.

EDIT:
After seeing the C++ code Nightinggale's post, I see that my solution is not sufficient. You have to also edit the DLL so there are enough input arguments in the text.

Yes, you are correct in that it doesn't work, even if I do as you suggested in changing "%d" into "%s" in the XML-file. Neither does it work if I change the PY to "%s."

I don't understand why I actually DO get the game to show the number if the DLL determines only one input argument?
I know for a fact that I do get both the name of the city founded AND the number representing the leader founding the city (as you've seen in previous posts).

the only good news is that even if I play two leaders from the same civilization (Peter and Catherine in the spoiler-example), I do get two different numbers. I only have to pay attention to what the name of the "player" AI capital is, to figure out what number belongs to which leader. And I am frustrated that I cannot find a way to make that number into a string, using python.

Spoiler Same Civ Diff Num :

WHFail7SameCivDiffNum.jpg



Thank you for your input. That goes for the rest of you, as well (of course). Unfortunately, learning to program just to get one name added to the output is not worth the time it would take. Copying python and xml-entries is one thing. Actually understanding how to program is quite a different matter.

Oh, well. I have what I need. Even if it is a bit... ugly-looking.
 
I don't understand why I actually DO get the game to show the number if the DLL determines only one input argument?
I know for a fact that I do get both the name of the city founded AND the number representing the leader founding the city (as you've seen in previous posts).
I doubt you actually have a number reliably representing the leader. If you use more arguments (in XML) than you provide (in the DLL), AFAIK the C++ code just takes whatever is in memory directly after the last argument, which may or may not be related to the founding player. So don't be surprised if the numbers thing turns out to not always work.
 
If you have any idea at all how to make the game say "Catherine" instead of "232802444" I would be utmost grateful and happy. If it is at all possible?
The only way I see is to modify the replay message text right before it gets displayed by the replay screen. That would be after this block of code in CvReplayScreen.py:
Code:
szText = self.replayInfo.getReplayMessageText(iLoopEvent)
iX = self.replayInfo.getReplayMessagePlotX(iLoopEvent)
iY = self.replayInfo.getReplayMessagePlotY(iLoopEvent)
eMessageType = self.replayInfo.getReplayMessageType(iLoopEvent)
eColor = self.replayInfo.getReplayMessageColor(iLoopEvent)
Here's a quick attempt:
Spoiler :
Code:
bUseOwnerColor = True
bAppendOwnerName = True
if eMessageType == ReplayMessageTypes.REPLAY_MESSAGE_CITY_FOUNDED:
   iOwner = self.replayInfo.getReplayMessagePlayer(iLoopEvent)
   if bAppendOwnerName:
       if iOwner == self.replayInfo.getActivePlayer():
           szOwnerName = self.replayInfo.getLeaderName()
       else:
           szOwnerName = gc.getLeaderHeadInfo(self.replayInfo.getLeader(iOwner)).getDescription()
       szText = szText[:-1] # Remove full stop
       szText += " by " + szOwnerName
   if bUseOwnerColor:
       eColor = self.replayInfo.getColor(iOwner)
I'm attaching the modified file because the forum replaces tabs with spaces. It belongs in Assets\Python\Screens. I've taken it from Vanilla Civ 4 because the expansions didn't update it. My code isn't going to get the names of other human players right in multiplayer; the replay file doesn't store those names separately. Uses the stock leader names instead. And the "by" isn't translated, but that could be amended.
 

Attachments

The only way I see is to modify the replay message text right before it gets displayed by the replay screen.It belongs in Assets\Python\Screens. I've taken it from Vanilla Civ 4 because the expansions didn't update it. My code isn't going to get the names of other human players right in multiplayer; the replay file doesn't store those names separately. Uses the stock leader names instead. And the "by" isn't translated, but that could be amended.

Thank you, f1rpo!

This is perfect! Exactly what I wanted.

Stock leader names is what I want (I'm using this in AI vs AI, H2H, games).
And I'm using English, so no translations necessary.

I've removed my own changes from the mod, as your solution covers everything without the need for what I did.

The only thing I changed was setting the bUseOwnerColor to False, and everything looks amazing.

I don't understand all the if's and else's in your code, but it works. :thanx::trophy::dance::hatsoff:

Spoiler f1rpo did it! :

WHSuccess.jpg



I'm off to testing this out in a full game. Hopefully it stays perfect.
 
@Kjotleik: I had assumes that H2H was some form of multiplayer. Well – in a way it is; I've looked it up now. The if/else shows the custom name of the active human player (on whose machine the replay was created). Good that you found the color switch (personally, I use colors -through the DLL- instead of leader names). Testing is also a good thought; your screenshot basically shows all the testing that I've done. I hope you're aware that this approach does not modify the replay file, so if you send a replay to another participant, they won't see the city founders' names unless they also have the modified replay screen. Edit: Come to think of it, this can't really matter because a replay created with a mod can't be loaded without that mod in any case.
 
Last edited:
I doubt you actually have a number reliably representing the leader. If you use more arguments (in XML) than you provide (in the DLL), AFAIK the C++ code just takes whatever is in memory directly after the last argument, which may or may not be related to the founding player. So don't be surprised if the numbers thing turns out to not always work.
In C++ (and most other compiled languages), there is a standard for how to add arguments to function calls. The registers in the CPU are used for this, like the first is always in X1, the second in X2 etc (they aren't actually called X, but it tells the idea). If there aren't enough registers, the rest are stored on the stack (memory). When a function is called, it assumes the caller to have placed the arguments where they should be according to this standard. By adding that it should look for the 3rd argument and insert it into the string, it assumes the caller to have added the data in the 3rd register. However the dll code will ignore that register because at the time of compilation, it's unused. You will then get whatever was left there from some other function call.

This hurts modders from time to time if they add another argument to a function in the dll file. If the function is called from the exe, the exe file will not add the required data to the register in question and the dll will assume it is there, read it and do whatever it assumes to be correct based on this semi-random number. This is the main reason why it's important to keep track of which functions the exe is calling.

@f1rpo and others interested in EnumMap
Here is some work in progress. It seems to be working, but it's not complete yet. I ran into the issue where I needed more and more template parameters and adding 4 parameters to function arguments isn't nice. The result is that I renamed it to EnumMapBase and then added EnumMap with just 2 parameters (used as you would expect). That class will then set the other parameters at compile time according to what the first two are.

Feedback is welcome, but keep in mind that this has to be C++03. Most of the great template features were added in C++11 and as such can't be used. Also it's the best candidate for heavy usage of constexpr that I have ever written, yet that too is C++11. It is however working, it's just a question of how hard the compiler can optimize the code and as long as it's working and isn't performing poorly, I'm not complaining.
 
I don't know the full answer to any of these, but here's my best shot:

The attached screenshot shows how (I think) it works on Windows 8.1. That's all under HKEY_CLASSES_ROOT and was created by the installer. The start of "value data" that didn't fit in the box is just the full path ("C:\ ...") to the BtS EXE.
Thanks, I will compare how this looks in my registry.

@f1rpo and others interested in EnumMap
Here is some work in progress. It seems to be working, but it's not complete yet. I ran into the issue where I needed more and more template parameters and adding 4 parameters to function arguments isn't nice. The result is that I renamed it to EnumMapBase and then added EnumMap with just 2 parameters (used as you would expect). That class will then set the other parameters at compile time according to what the first two are.

Feedback is welcome, but keep in mind that this has to be C++03. Most of the great template features were added in C++11 and as such can't be used. Also it's the best candidate for heavy usage of constexpr that I have ever written, yet that too is C++11. It is however working, it's just a question of how hard the compiler can optimize the code and as long as it's working and isn't performing poorly, I'm not complaining.
That looks really cool and will probably remove tons of boilerplate from the constructors and savegame code.
 
Hello, again.

In CvReplayScreen.py I have a problem.

Why does the game ignore the self.X_SCREEN and self.Y_SCREEN settings?
Originally they are at 500 and 396, respectively.
No matter what numbers I replace them with, the replay screen's upper left corner is located at exactly the same place in the window running the game.

Both the self.W_SCREEN and self.H_SCREEN works. Those I've set to 1440 and 900 (my current window-size), and I see them go off-screen (I presume they ARE that large) to the right and down below.

Is there a secret setting somewhere?

Changing the self.W_HELP_AREA from 200 to 0 doesn't do anything.

What must I do to make the game respect the numbers I put into it?
 
That looks really cool and will probably remove tons of boilerplate from the constructors and savegame code.
Yes, but the amount of code isn't important. What's really important is the risk of bugs, though usage of CPU and memory are important too. By not having to allocate manually in the constructor means you can't forget and you can't accidentally allocate to the wrong size. You can't get memory leaks either as the EnumMap releases in its own deconstructor. When using, you don't have to check if the memory is allocated because EnumMap does that internally.
EnumMap are using strict types. For instance revealed owner in CvPlot is using EnumMap<TeamTypes, PlayerTypes> (in WTP for testing EnumMap). This means it's set(TeamTypes, PlayerTypes) and get(TeamTypes), which returns PlayerTypes. Mixing up the types (like swapping argument order) will cause a compiler error. Get and set will also assert check that the index is valid.

Bugfree code should be the top priority and this is a tool, which helps toward that goal. The fact that it's fast and is aggressive towards low memory usage and low disk space in savegames is a (well planned) bonus. The same goes for the ease of use for programmers. Often ease of use and lowering risk of bugs goes hand in hand, but they are still two different goals, which might be put up against each other once in a while.
 
Last edited:
That's true. I have added so many class attributes by now that it has become a rote task for me, I know exactly what I need to do to avoid memory leaks and misallocated variables. The main appeal to me really is avoiding all these mindless steps.
 
Back
Top Bottom