1. We have added a Gift Upgrades feature that allows you to gift an account upgrade to another member, just in time for the holiday season. You can see the gift option when going to the Account Upgrades screen, or on any user profile screen.
    Dismiss Notice

Quick Modding Questions Thread

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

  1. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    Ah, thank you. I believe I have been recommended this idiom before and it might even already exist somewhere in my code. I guess my instincts are always to go for the "elegant" solution even though I really have no idea what I'm doing when it comes to things like virtual declarations and the like. But your explanation why adding virtual declarations messes with the memory model makes sense. Oh well, on to the ugly-but-works solution we go. Thanks!
     
  2. Zeta Nexus

    Zeta Nexus Deity

    Joined:
    Jan 23, 2014
    Messages:
    3,373
    Location:
    In a constant brainstorm...
    Is it possible to make unit (slave) cause the city receive :c5angry: on completion?
    • It should be 1:c5angry: for 8 turns per slave trained.
    • It should scale with game speed.
    • Training more slaves should stack the :c5angry: faces individually instead of just prolonging unhappiness. So the more slaves you train, the greater the cities anger is.
    I'm only interested in a python code - but I need help even with that. The best I can do is merging. If someone could write the code for me, I'd be grateful but I understand that every one is busy, so I don't expect anything. Just asking :)
     
  3. Nightinggale

    Nightinggale Deity Supporter

    Joined:
    Feb 2, 2009
    Messages:
    4,151
    Taken from WTP develop (CvPlayer.h)
    PHP:
    class CvPlayerAI;
    class 
    CvPlayer
    {
    public:
        
    __forceinline CvPlayerAIAI() { return (CvPlayerAI*)this; }
        
    __forceinline const CvPlayerAIAI() const { return (CvPlayerAI*)this; }
    This allows CvPlayer to use AI() to get a pointer to access the CvPlayerAI part of itself. As already mentioned this works because all instances of CvPlayer are of CvPlayerAI. If not, then this would be able to crash the game. Don't take it as general C++ coding.

    I have mentioned this a few times on the forum (in fact I might have been the first to do so on this forum) and I post this because in the past people have had issues with not including the CvPlayerAI class "announcement" and have included CvPlayerAI.h instead, which won't work due to header circular inclusion. For this reason I think it's best to add this small code example with all the "noise" removed to really point out what's needed to make this work.

    Two versions are included because according to the C++ standard, const functions/pointers will use the const function while non-const will use the non-const if both are available through function overloading. In other words doing it like this means AI() will work in both cases and the rules of const applies (only using const CvPlayerAI functions). It seems this specific part of the C++ standard isn't as widely known as it should be.

    And yeah I'm not saying it's pretty or that I like it. It's just the best we can do with the dll/exe setup we have to use.

    Also generally don't use __forceinline. Use plain inline instead. __forceinline is a stronger encouragement to actually inline and should only be used if you think you know better than the compiler (but it's still not certain that it will inline!). In this case it will always return the first argument (the this pointer is the invisible first argument, again according to the C++ standard. The first argument you write in the code is technically the second argument). If __forceinline should ever be used, this is likely the place to do so.
     
    Leoreth likes this.
  4. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    AdvCiv does
    Code:
    return *reinterpret_cast<CvCityAI*>(this);
    instead, is there an important difference to cast that way?
     
  5. Nightinggale

    Nightinggale Deity Supporter

    Joined:
    Feb 2, 2009
    Messages:
    4,151
    Looks like reinterpret_cast is better/less prone to bugs in certain cases. However in this specific case they appear to compile identically and result in the same dll file.

    After thinking hard for a case where the difference will make a difference, I came up with template functions. If you cast from a pointer to an instance of T (not a pointer to T, but an instance of T) and sizeof(T) > sizeof(pointer), then the C style approach will crash while reinterpret_cast should cause the compiler to fail. Since we are always casting between two pointers (same size), then this shouldn't matter at all. I would however argue that you should let pointers be pointers and not cast them to be non-pointers.

    It's a good question, but I feel like it only really matters if you write dirty code. Sure maybe you need to really optimize the code for some reason, but that's rare with the civ4 engine. Yeah I know EnumMap is the exception, but it uses a union to "cast" between the different types of pointers, hence removing the need to actually cast the pointer. That's another approach to prevent casting from one type to another bigger type and then use more memory than allocated because a union will always allocate enough memory for the biggest variable. Sure it can be a waste, but it will not cause memory related issues/crashes.
     
    Leoreth likes this.
  6. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    Thanks for the explanation. C++ has so many idioms that I've never seen before, it's interesting to learn more about it.
     
  7. devolution

    devolution Warlord

    Joined:
    Oct 7, 2016
    Messages:
    263
    Gender:
    Male
    Location:
    Stavanger, Norway
    Just a quick note: dynamic_cast is the proper approach for a downcast in general. However, in this case we know that we really have a pointer to CvPlayerAI and we can thus use static_cast and forego some of the type checks of the compiler. Reinterpret_cast on the other hand is telling the compiler to take a bunch of bits and then interpret it as an entirely different type (like turning an int into a pointer). It essentially disables all compiler checks concerning the result of the cast, a little bit like an expert mode (or segfault mode if you prefer to think of it that way).
    Like @Nightinggale pointed out, the resulting code is the same (except for dynamic_cast!) it's only the type checking effort of the compiler that differs. Personally i get very suspicious when I see a reinterpret_cast \ const_cast since these are often early warning signs of unsafe practice or a lingering bug.

    @Leoreth : If I am not mistaken you are an experienced java developer. All casts in java (and C# for that matter) are effectively the same as dynamic_cast in C++.
     
    Last edited: Nov 21, 2019
  8. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    That's right, my professional experience is mostly with Java (and increasingly Go these days, but I wouldn't call myself experienced with it yet). My only interaction with C++ has been in modding Civ4 so I basically made do with what I know from C and half translated knowledge from Java. It's a hobby so I never really went out of my way to get a complete understanding of everything in C++. For instance, I just quietly assumed that Java and C++ casting are equivalent. It's probably one of those assumptions that work in most cases until the code explodes into your face and you suddenly don't understand why.
     
    devolution likes this.
  9. Nightinggale

    Nightinggale Deity Supporter

    Joined:
    Feb 2, 2009
    Messages:
    4,151
    It's always dangerous to assume programming languages to be the same just because they look the same. One giant pitfall is structs and classes in C#. Now if you have a struct in C++ and you use it as an argument to a function, then the function will get a copy, meaning changing data in it will not affect the original. If you want the original to change, you use a reference or pointer (& or *). Changing the struct to a class will not change this.
    In C# structs are the same. You pass a copy and you use the keyword ref if you want the changes to affect the original. However changing a struct to a class means changes will affect the original even without the ref keyword because classes in C# are always references to memory in heap and you just pass the memory address, not the data. This means unlike C++, changing a struct to a class in C# can change the behavior quite a lot as well as performance because structs are on the stack while classes are in the heap. There are a bunch of other differences as well.

    I think the purpose of this is to make memory management easier, which should result in less bugs. However it's like on the top of the list of bugs C++ programmers will make when switching to C#. Even though I'm aware of this, I do find the C++ approach easier because & and * are simple to read in the argument list while say the argument CvTradeInfo will be by value if it's a struct, but a reference if it's a class, but you have to look up CvTradeInfo in another file to figure out if it's a class or struct. It also eludes me how to use C# to place memory in a sane manner in memory for reducing the impact of RAM latency. This kind of optimization seems to be much easier in C++. I'm not saying C# is bad as such, but designing the language to prevent programmers from some of the crazy buggy mess some people have made in C++ isn't free. Also this is the first thing which comes to my mind when having something going completely wrong due to assuming two programming languages to do the same because they use the same keywords and yes I have lost a number of hours until I figured out that C# ctructs aren't C# classes.

    This has turned from civ4 modding into computer science issues. We should consider making a quick programming questions thread or something else of that nature. Writing about non-intro programming issues here might scare people from asking xml questions because "it's too simple compared to what else goes on here". At the same time I think it's wrong to not have a proper place for programming issues because I feel it's a kind of overlooked aspect of modding. It seems to me that there has been a mentality of "if it works, then it's fine", which hurts stability and performance. We should exchange ideas and knowledge about best practices for dll modders because programming is too complex for any single person to be an expert in every single aspect or get all the best ideas on designing something. Sadly we aren't enough people to fill all modding teams with highly skilled programmers (or even programmers at all), meaning we are in a position where we are to gain a lot from exchange of ideas and knowledge between teams.
     
    MattCA and devolution like this.
  10. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    I completely agree, although unfortunately I would likely mostly be on the consuming side of such a thread.
     
  11. f1rpo

    f1rpo plastics

    Joined:
    May 22, 2014
    Messages:
    510
    Location:
    Germany
    Right. Just two -admittedly unimportant- clarifications (I hope) regarding the pure virtual functions:
    Spoiler :
    Adding virtual functions to CvCity and CvUnit is, as far as I can tell safe, i.e. the EXE doesn't call any of those. The EXE does call for example CvPlayer::read, which explains why it'll crash upon loading a savegame. One can add virtual functions to CvPlayer (and CvSelectionGroup, CvGame) if needs be by replacing an existing virtual function that the EXE (apparently) doesn't call; e.g. by removing the virtual keyword from CvPlayer::AI_init and placing the new function right above or below. I'd prefer a cast over a redundant virtual function declaration though. For exposing CvPlayerAI functions to Python, I've changed the type of CyPlayer::m_pPlayer to CvPlayerAI* in AdvCiv. That requires a cast in CyPlayer::CyPlayer(CvPlayer*), but then all CvPlayerAI functions can be called.
    In this specific case, static_cast is only possible if CvPlayer.h includes CvPlayerAI.h so that the compiler is aware of the inheritance relationship.
    My intention for using reinterpret_cast instead of a C-style cast was to make the reader aware that there's a quirk in the software design. On that note, after removing the virtual functions, it's actually pretty easy to replace inheritance with composition for CvUnit(AI) and CvCity(AI). However, much of the CvUnitAI code then needs to be prefixed – e.g. getUnit().bombardRate() instead of just bombardRate(). I guess the whole design would need more structure for composition to work well.

    So that it doesn't get buried: This Python request is still unaddressed:
     
    Zeta Nexus likes this.
  12. Vokarya

    Vokarya Deity

    Joined:
    Mar 25, 2011
    Messages:
    6,101
    I had to look into this. Python doesn't quite allow that.
    • changeExtraHappiness can add an arbitrary amount of happiness or unhappiness to a city, but it's permanent.
    • changeHappinessTimer can add a happiness bonus to a city for a certain length of time, but it only allows a flat +1 :). The Wedding Feud and Great Beast events in BTS use this.
    • changeConscriptAngerTimer and changeHurryAngerTimer can add temporary unhappiness to a city and it stacks, but it also scales with city size.
    If you are willing to accept the scaling from city size, then this is all the code you need:
    Code:
    def onUnitBuilt(argsList):
               pCity = argsList[0]
               pUnit = argsList[1]
               if pUnit.getUnitClass() == gc.getInfoTypeForString("UNITCLASS_XXX"):
                          gamespeed = CyGame.getGameSpeedType()
                          gameSpeedModifier = gc.getGameSpeedInfo(gamespeed).getGrowthPercent()
                          iSlave = 8 * gameSpeedModifier / 100
                          pCity.changeConscriptAngerTimer(iSlave)
    XXX is the unit class of the unit that you want to trigger the unhappiness addition when it is built. GrowthPercent is serving as the scaling factor. If you want to go strictly by turns you have to calculate the length of the game for both your current speed and normal speed.
     
    f1rpo and Zeta Nexus like this.
  13. Underdog Ally

    Underdog Ally Chieftain

    Joined:
    Feb 22, 2019
    Messages:
    48
    Could someone help me with a better understanding of the .kfm files? I have an anti-aircraft gun (taken from neoteric world) and the unit-specific kfm doesn’t work/do anything. During combat it just sits there with no animation. From what I understand the kfm file is the animation right? So could I just replace the kfm for this with the standard artillery or mobile Sam kfm? Any help is appreciated :D
     
  14. SaibotLieh

    SaibotLieh Emperor

    Joined:
    Sep 25, 2009
    Messages:
    1,456
    The animation files in Civ4 usually consist of one kfm file and a number of kf files. The kfm file is basically an encoded xml file, which directs to the kf file which should be trigger when an animation order comes in. The kf files are the files where the actual animations are stored. As far as I understand it, the kfm file will first look within the directory of the used nif file for the kf file that should be triggered. If there is no file with the right name in the nif directory, it will look within its own folder (does not need to be the same as the nif file). If it has again no luck there, it probably will also look into the Shared folder, but I am not sure about that. After that it will give up and no animation will be triggered.

    Anyhow, you can try to animate a unit with a kfm file of a different name, belonging to a different model. However, most likely the animation will not fit properly, since the "skeletons" (the nodes which will be animated and to which the models mesh is rigged) of the different units normaly have similar named "bones", but their position and orientation often differ.

    So, the problem with your unit might be that you have the kfm file, but are missing the kf files in the same or the nif folder. If you want to test one of the standard kfm files, I would not copy any files, but copy the <KFM> line of that unit for your unit. For example, the line for the Mobile SAM would be this:
    Code:
    <KFM>Art/Units/MobileSam/MobileSam.kfm</KFM>
    
    And by the way, the kfm files normally needs a nif file of the same name in the same folder to work properly. For example, the "MobileSam.kfm" file needs a "MobileSam.nif" in the same folder. This does not even need to be the nif file that will show up as a model in the game (in fact, most of the time you will rather see the shadered version ending with "_fx").

    Hope this helps a bit.
     
    Underdog Ally likes this.
  15. Underdog Ally

    Underdog Ally Chieftain

    Joined:
    Feb 22, 2019
    Messages:
    48
    that helps a lot, thank you for explaining all that in detail to me. The nif and kf files all seem to be there and named properly, so maybe they just don’t work. I might try and see if any other animation is close enough to work for it or worst case just leave it without an animation.
     
  16. LPlate2

    LPlate2 Chieftain

    Joined:
    Dec 27, 2018
    Messages:
    56
    Hi,

    Is there a tutorial/thread that explains how to make information available against pPlayer or pPlot, when it's defined in a relevant associated xml?
    E.g. if a new boolean is created for TraitInfos, how do you make this readable when using pPlayer or similarly for a new integer created in TerrainInfos how do you set it up to access this with pPlot?
     
  17. Nightinggale

    Nightinggale Deity Supporter

    Joined:
    Feb 2, 2009
    Messages:
    4,151
    XML data is stored using CvInfos.cpp/h. You get the into like GC.getTerrainInfo(eTerrain).getWhatever(). Most of those commands are available in python too, though for some reason it's named gc in python.

    pPlot is an instance of a plot, meaning you do something like:
    PHP:
    TerrainTypes eTerrain pPlot->getTerrain();
    GC.getTerrainInfo(eTerrain).getWhatever();
    // or the one line version
    GC.getTerrainInfo(pPlot->getTerrain()).getWhatever();
    Same with pPlayer. You need to use GC.getTrait(eTrait) and you need code to read which traits pPlayer has.
     
  18. Leoreth

    Leoreth 心の怪盗団 Moderator

    Joined:
    Aug 23, 2009
    Messages:
    33,207
    Gender:
    Male
    Location:
    Leblanc
    Not that I'm aware of, but to give you a quick rundown (apologies if some things are already obvious to you):

    1. CIV4BuildingInfos.xml comes with CIV4BuildingInfoSchema.xml that describes the allowed XML tags, sometimes multiple XML files share a schema file. New XML entries need to be added there first or the game will throw an error.
    2. All CIV4SomthingInfos.xml correspond to a SomethingInfo class you can find in CvInfos.h and CvInfos.cpp.
    3. This class reads the XML content and creates a C++ object to represent one element. They all come with a read() function where you have to add the new XML tag so that it will be read. Of course that also requires a new member variable (don't forget to also add it to the constructor) and a getter function as well.
    4. CvPlayer and CvPlot etc. are not inherently linked to any XML entry. But instead the content of a SomethingInfo class can be applied to these objects. This is different depending on what info type we are talking about. For example, CvCity::processBuilding(BuildingTypes eBuilding) adds all the properties of a building (as defined in the XML) to the city. Here I recommend to just search for usages of your info class of interest to see how the game applies it to another object.
    5. Often the new info property also requires a new member variable in CvCity, CvPlayer, etc. In that case don't forget to also add is to the ::read() and ::write() methods so it is persisted during save/load.

    It's tedious by modern file-to-object standards but once you get the hang of it it's pretty straightforward. If you have a more specific example of what you want to do I can give more specific help.
     
  19. LPlate2

    LPlate2 Chieftain

    Joined:
    Dec 27, 2018
    Messages:
    56
    Thanks,

    I worked out what I needed to do.
    In CvPlot.h I, I used,
    Code:
        bool isNeedsTechToClaim() const;
    In CvPlot.cpp, I used,
    Code:
    bool CvPlot::isNeedsTechToClaim() const
    
    {
       FAssertMsg(getTerrainType() != NO_TERRAIN, "TerrainType is not assigned a valid value");
    
       return GC.getTerrainInfo(getTerrainType()).isNeedsTechToClaim();
    }
    At least this seems to be working for me to pull the information from the TerrainInfos.xml to the CvPlot to use in the DLL. I have not got it out of there to python yet. Not sure if I need to do something with CyPlot.h, CyPlot.cpp and CyPlotInterface1.cpp.
     
  20. MattCA

    MattCA Warlord

    Joined:
    Jan 25, 2019
    Messages:
    166
    Gender:
    Male
    If that's all you've got for code so far there's a bit more you'll need to do.
    GC.getTerrainInfo(getTerrainType()) will return an object that was created using the xml.
    If you havn't done it already, you'll need to add code to read you're new xml and store it in memory.
    This code will be in CvInfos.

    I assume this is for python modding and once you get it there, you're good to go.
    So if you want, you could forget about CvPlot and CyPlot.
    Just figure out how to make isNeedsTechToClaim() in CvInfos and you can expose it to python using the CyInfoInterface files.
    Then you could write something like that in python.
    GC.getTerrainInfo(CyPlot.getTerrainType()).isNeedsTechToClaim()
     

Share This Page