DLL/C++ tutorial discussion thread

whoward69

DLL Minion
Joined
May 30, 2011
Messages
8,721
Location
Near Portsmouth, UK
Thread for asking questions about the "Replacing hard-coded constants with database values", "Extending the Lua API", "Adding GameEvents", "Communicating with the player", "Adding new columns to the database", "Adding new tables to the database" "Adding new primary tables to the database" and "Persisting data with a saved game" DLL/C++ tutorials. I'll update the main tutorial threads with any relevant information raised here.

Please note that the tutorials are NOT about basic C/C++ programming, so please do not ask questions about the syntax, semantics or usage of the C++ language in this thread - there are many excellent resources for learning C++ available (most of which can be found in your local bookshop) - or start a separate thread.

Also, please do NOT ask questions about specific issues for your own mods and/or ask for someone else to write your code for you on these threads - start a new thread either in the main C&C forum or the SDK / Lua sub-forum to ask your specific question/request, as that way you'll get specific answers ;) And finally, please note that I do NOT take requests for writing code for others' mods!
 
One thing to point out is that if you don't want to go and implement custom logging you can go to your project properties menu (right-click the project and select 'Properties'), then to 'Configuration Properties', 'C/C++', and then to 'Preprocessor', and in the 'Preprocessor Definitions' field add CVASSERT_ENABLE. This will allow you to use the macros defined in CvAssert.h to do runtime checks (and will generally assist you in debugging).
 
Indeed, but there are some cases where the game core asserts incorrectly during normal game play (giving units missions via Lua was one, unless they've fixed it) and also your messages will be interleaved with all the standard Firaxis ones - I prefer having all my messages in my own log file - but it comes down to personal choice, so YMMV :)
 
Whew... I think I just leveled-up after reading that, haha.

Very comprehensive, I loved that you included how to make a custom log file too -- that's so cool! If I get one of those up and running, it'll definitely make me feel better knowing any modified C++ code is running and producing the correct results, instead of being in the dark.

The tips on commenting are nice too. I'm going to start marking my modified/new blocks of code with "end" comments now, along with including my name for more unique terms to search for.

- - - - - -

I was googling things as I was reading (like #pragma once, #ifndef, etc.) to get a better understanding of things, but I didn't see an example of one thing in my searches.

Forgive me if this question isn't acceptable here, but it does pertain to pre-processor if statements:

Can you have a block of code after the #if, #elif, or #else? Or is it only for replacing a single statement?

For example, if you're replacing a whole block of code, not just the variables, you can comment out your #define and things will revert back to the original block of code.
 
Can you have a block of code after the #if, #elif, or #else? Or is it only for replacing a single statement?

It can be as much code as you need - and because it is processed before the compiler gets anywhere near the code it can even split syntactic units, provided that the final effect is valid C++, eg

Code:
#if defined(MOD_BUGFIX_NAVAL_FREE_UNITS)
  if (pkUnitInfo->GetDomainType() == DOMAIN_SEA) {
    if (pLoopPlot != NULL && pLoopPlot->isWater()) {
      if (!pLoopPlot->isImpassable()) {
        if (!(pLoopPlot->isUnit())) {
          pBestPlot = pLoopPlot;
          break;
        }
      }
    }
  } else {
#endif
    if(pLoopPlot != NULL && pLoopPlot->getArea() == pStartingPlot->getArea())
    {
      if(!pLoopPlot->isImpassable() && !pLoopPlot->isMountain())
      {
        if(!(pLoopPlot->isUnit()))
        {
          if(!(pLoopPlot->isGoody()))
          {
            pBestPlot = pLoopPlot;
            break;
          }
        }
      }
    }
#if defined(MOD_BUGFIX_NAVAL_FREE_UNITS)
  }
#endif

You can also nest them, eg

Code:
  case DOMAIN_IMMOBILE:
    // Only hover over shallow water or ice
#if defined(MOD_BUGFIX_HOVERING_PATHFINDER)
    { bool bOK = (!isWater() || (unit.IsHoveringUnit() && isShallowWater()) || unit.canMoveAllTerrain() || unit.isEmbarked() || IsAllowsWalkWater());
#if defined(MOD_PROMOTIONS_CROSS_ICE)
    bOK = bOK || (unit.IsHoveringUnit() && isIce());
#endif
    return bOK; }
#else
    return (!isWater() || unit.IsHoveringUnit() || unit.canMoveAllTerrain() || unit.isEmbarked() || IsAllowsWalkWater());
#endif
    break;
 
Is it possible to push a text string out through a GameEvents hook?


Code:
/// Initiate diplo screen with default state
void CvDiplomacyAI::DoBeginDiploWithHuman()
{
	if(!GC.getGame().isOption(GAMEOPTION_ALWAYS_WAR))
	{
		LeaderheadAnimationTypes eAnimation = LEADERHEAD_ANIM_NEUTRAL_HELLO;
		const char* szText = GetGreetHumanMessage(eAnimation);

		// Paz
#ifdef EA_LEADER_SCENE_BYPASS
		ICvEngineScriptSystem1* pkScriptSystem = gDLL->GetScriptSystem();
		if (pkScriptSystem)
		{
			CvLuaArgsHandle args;
			args->Push(GetPlayer()->GetID());
			args->Push(DIPLO_UI_STATE_DEFAULT_ROOT);
			args->Push([COLOR="Red"]szText[/COLOR]);
			args->Push(eAnimation);
			bool bResult;
			LuaSupport::CallHook(pkScriptSystem, "EaLeaderSceneBypass", args.get(), bResult);
		}
#else
		gDLL->GameplayDiplomacyAILeaderMessage(GetPlayer()->GetID(), DIPLO_UI_STATE_DEFAULT_ROOT, szText, eAnimation);
#endif

	}
}

The above resolves to 1, 0, true, 1, -1 on the Lua side. Events can pass text args (e.g., AILeaderMessage) but I couldn't find an example of a GameEvents doing this. Clearly, something needs to be done to szText to get it into a form that Lua understands (assuming args can hold such a thing).

Yes, I know what I'm doing above is crazy (there are like 100 instances of GameplayDiplomacyAILeaderMessage).
 
Push() is an overloaded method. If you hover the mouse over it, you'll get a popup of possible method signatures, from which you can see that to push a string you have to give both the pointer to the string and the length of the string

attachment.php
 
Great tutorials whoward69!

I'm a beginner but this documentation has allowed me to do a lot of things!
I've read the primary tables tutorial but now I'm trying to go a little further by creating a "CvGovernanceClasses" object, similar to CvPolicyClasses etc... Is it possible?

Actually I've managed to do it by copying and adapting every bit of code related to Policies, including interface stuff that I don't completely understand: the code is compiling correctly but with this dll file the mod won't launch (CTD I mean).
 
Most things related to game play are possible with C++ in the game core DLL ... if you have the knowledge/time to figure it all out.

Copying every bit of code, while not understanding it, almost certainly has very little chance of (ever) working correctly. Start tiny, build up to small, then go for medium. Large and huge are probably months if not years away (it took me over a year to get to a point where I was comfortable to mod the path-finding code, but I still "flinch" every time I look at it.)

If you're getting CTD's make sure you have the minidump code installed and enabled, and know how to use it to work out where the CTD occurred.
 
How can I(a): Remove the Max(Player, Team, or Global)Instances columns from the UnitClasses and BuildingClasses tables, and (b) put them into the Units and Buildings tables? That way a unique unit could be set to have 1 MaxGlobalInstances while other units in the same unitclass could be unlimited in number (until you check your GPT!).
Thanks!
 
a) with SQL b) by replicating the C++ code from the BuildingClass/UnitClass classes onto the corresponding Building/Unit classes
 
Nothing in the DLL is that simple. Half the problem is tracking the code down, but you need to do that in order to gain the knowledge of how it interacts with other methods so you can port it from one set of classes to another.
 
So I'm using your macro SHOW_PLOT_POPUP(pPlot, ePlayer, szMessage, fDelay) with Lua API to add "float up" text for various things. It's been a long time since I looked at this and for some reason I only added the plot method (though I use it for a lot of unit stuff).

The problem I'm having is that no matter how much I fiddle with fDelay, I'm always having messages on top of each other, or else uncomfortable delays between messages. The problem is that it's hard to know how many messages the dll is going to send. For example, on unit combat there could be experience, HPs, gold, culture, and probably other things I can't think of. My mod has another 6 or 7 possibilities, although most often only 1 or 2 occur from a given event. But I'm trying to figure out how to have them each 1 sec (or whatever) apart without knowing which texts the dll is going to float up from a plot. Right now, I have a particular kind of flaot up text with fDelay = 3, and another with fDelay = 4. But I still get overlap if dll is putting up 3 float texts, or I get uncomfortable delay before mod text if dll puts up 0.

If it was only my mod texts to worry about, then I could solve this with my own GetFloatUpTextDelay(iPlot) function that keeps track of last message time from a given plot. But that doesn't help me with the situation where the dll might be putting up some unknown number of texts and I want my mod's texts to follow the last one by 1 sec, or start immediately if there are no dll texts.

Any ideas how to solve this?
 
Ummm. It looks like Firaxis just picked numbers that probably wouldn't overlap!

+0.0 Attrition
+0.0 Sell exotic goods
+0.0 Religious conversion
+0.0 Tourism blast
+0.0 Plundered trade route
+0.0 Unit damage
+0.0 City damage
+0.5 Gold from city conversion
+0.5 Citadel damage
+1.0 Unit XP
+1.5 GA from Kills
+2.0 Science from kills
+2.0 Unit healing
+2.0 City healing
+2.0 Post combat promotion
+2.0 Yield from kills +additional 0.5 delays for subsequent yields
+3.0 Plundered city
+3.0 Gold from NW

The only generic solution I can think of is to add a method on CvPlot to send the popup and replace all the direct calls to the DLL to send the popup to go via the new method (there are 15 calls, so not too onerous). Then add a new member to the plot to store the delay, ignore any requested delay and use the stored value, increment it after send a message and also in the CvPlot:: DoTurn() method reset the delay to 0
 
Yeah, that sounds like best solution for me. I don't need to reset anything if I just keep time (in tenth seconds) of the last plot text. Then I can calculate proper delay (if any needed) from that: last text + standard delay - current time or 0, whichever is greater.

Edit: I guess the advantage of the current system however is that order is controlled, rather than being an arbitrary consequence of code implementation. But then the possible pile-up problem. I'll have to think about this for a while.

Thanks!
 
If you go this route, due to a bug in CvPlot:: setRevealed(), you'll get 22 messages when you discover El Dorado.

To fix these, move the display of
Code:
sprintf_s(text, "[COLOR_YELLOW]+%d[ENDCOLOR][ICON_GOLD]", iFinderGold);
outside of the
Code:
for(iI = 0; iI < MAX_MAJOR_CIVS; ++iI)
loop
 
Hey your tutorials are great! Quick question tho, in the tut where you replace a hardcoded value with a DB one, what's the function of this (the red part)?
#if defined(WH_AI_TILES_PER_SHIP)
GD_INT_INIT(AI_TILES_PER_SHIP, 5),
#endif

It doesn't seem to serve a use (as you only need/use the called db value). Unless it's how the engine determines defaults, but I thought that the xml handled that (when filling the DB).
 
The DB/XML table definition only gives defaults for columns in incomplete rows. If the row is completely missing (as will be the case with the Defines table) there is no DB default.

The highlighted value is what the C++ member is initialised with, that is, its default value.
 
Back
Top Bottom