An Idiots Guide to Editing the DLL

Conqueror's Delight had both Invisible and SeeInvisible with the promotions (as well as a bunch of other stuff); unfortunately it does not have a 3.19 version, but if you want to port those over you can have a look at the SDK source for my Star Trek mod (I recommend using a file comparison utility like Beyond Compare or WinMerge because it makes finding the changes much easier).
 
Commerces and Yields from combat are available in a Modcomp already out there, but I cannot remember if he had included them in only UnitInfos or also in PromotionInfos. I wound up expanding his code quite a bit when I imported it a long time ago, so you can see them in the Fall Further code, along with SeeInvis and Invis. Just remember that importing is always bad if it is a straight copy/paste. Never allow a single line of code into your mod which you do not understand and couldn't write for yourself (except in RARE cases where it is very useful, far beyond your abilities, certainly stable, and you know the limits of what it affects so that you don't blame every bug that ever comes up in the future on it. Otherwise that is precisely what you will find yourself doing. The imported code becomes your scapegoat/gremlin)

Dom Pedro II's Conqueror's Delight was a fantastic source of code, as well as History of the Three Kingdoms. Both are quite worth checking the source of for inspiration and samples while you write functions of your own.

Anyway, for the vectors the main trick which you have to do right, but can just clone line for line from SeeInvisible, is the parsing of the string based on tokens. The keyword token is utilized in the code so that you won't easily miss when it happens. All it does is take the one long string which exists in the XML and look for any commas. When one is found, the comma is removed and everything before it is used in a lookup to decide which invis type was just added. Repeat till end of string.

And the Yields/Commerces from kills was Food From Animals Modcomp as I recall it. Also by Dom Pedro II, so probably a part of Conqueror's Delight eventually.
 
Conqueror's Delight had both Invisible and SeeInvisible with the promotions (as well as a bunch of other stuff); unfortunately it does not have a 3.19 version, but if you want to port those over you can have a look at the SDK source for my Star Trek mod (I recommend using a file comparison utility like Beyond Compare or WinMerge because it makes finding the changes much easier).

Thank you sir, always (of recent at least) interested in looking at mod sources

Commerces and Yields from combat are available in a Modcomp already out there, but I cannot remember if he had included them in only UnitInfos or also in PromotionInfos. I wound up expanding his code quite a bit when I imported it a long time ago, so you can see them in the Fall Further code, along with SeeInvis and Invis. Just remember that importing is always bad if it is a straight copy/paste. Never allow a single line of code into your mod which you do not understand and couldn't write for yourself (except in RARE cases where it is very useful, far beyond your abilities, certainly stable, and you know the limits of what it affects so that you don't blame every bug that ever comes up in the future on it. Otherwise that is precisely what you will find yourself doing. The imported code becomes your scapegoat/gremlin)

Dom Pedro II's Conqueror's Delight was a fantastic source of code, as well as History of the Three Kingdoms. Both are quite worth checking the source of for inspiration and samples while you write functions of your own.

Anyway, for the vectors the main trick which you have to do right, but can just clone line for line from SeeInvisible, is the parsing of the string based on tokens. The keyword token is utilized in the code so that you won't easily miss when it happens. All it does is take the one long string which exists in the XML and look for any commas. When one is found, the comma is removed and everything before it is used in a lookup to decide which invis type was just added. Repeat till end of string.

And the Yields/Commerces from kills was Food From Animals Modcomp as I recall it. Also by Dom Pedro II, so probably a part of Conqueror's Delight eventually.

First off, I noticed I screwed up in my last post, I meant :gold: not :commerce:, sorry. I've downloaded DPII's Conqueror's Delight, will download HoTK once I finish this post. Thank you for the tip on vectors. I am aware of Food From Animals, nice modcomp, though not really what I am looking for right now (I think it would be a less extensive change to add it to the promotion itself instead of every unit). The Fall Further sources :drool:, I will be pillaging them back to the stone age probably, several of the ideas that I had to include, you've already done, make no mistake, I will properly credit you where ever I use your code. Thank you, gentlemen.
 
I look forward to seeing the uproar/activity if you ever make CityBonuses or AutoAcquire/MustMaintain available to general BtS. Or the Commander/Minion system. Each led to lots of activity among the FfH based mods which decided to use them (but I'm a bit too busy to export to BtS and share for the "glory" of the thing. Especially since that means updating all of the code each patch)
 
What file must I tinker in to have a promotion give a notification similar to the collateral damage notification (you know, the "your [blank] has caused collateral damage (X units)")? Oh, and I will definitely be adding the ones you mentioned (I really like the Commander/Minion idea) at some point in the near future (along with iSlaveGenerationChance and CivicPrereqs both essential for a slaver promotion)
 
For that you would make the announcement happen when you perform the action. So you would be triggering gDLL->getInterfaceIFace()->addMessage. The specific place where Collateral is called would be

Code:
	if (iDamageCount > 0)
	{
		szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_SUFFER_COL_DMG", iDamageCount);
		gDLL->getInterfaceIFace()->addMessage(pSkipUnit->getOwnerINLINE(), (pSkipUnit->getDomainType() != DOMAIN_AIR), GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_RED"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE(), true, true);

		szBuffer = gDLL->getText("TXT_KEY_MISC_YOU_INFLICT_COL_DMG", getNameKey(), iDamageCount);
		gDLL->getInterfaceIFace()->addMessage(getOwnerINLINE(), true, GC.getEVENT_MESSAGE_TIME(), szBuffer, "AS2D_COLLATERAL", MESSAGE_TYPE_INFO, getButton(), (ColorTypes)GC.getInfoTypeForString("COLOR_GREEN"), pSkipUnit->getX_INLINE(), pSkipUnit->getY_INLINE());
	}

In CvUnit::collateralCombat.

You send the player number who gets the message, a boolean to say if you want to force the view with the message (ie - zoom the interface to the active tile specified later), the length of time for which the message should display before the next item in the queue shows (remember you only see up to... 4 messages?... at a time), the message to use, a sound to play when the message displays, what type of message it is (if it will be included in Replay save, if it will get added to the Combat Log, if it will get added to event log, if it will just display and not be available if missed...), an image icon to display, what color to have the message display in, a tile to highlight (X and Y Co-ords), boolean about showing an arrow pointing toward the tile if the tile is offscreen, a boolean about showing an arrow pointing toward the tile if the tile is onscreen.
 
Thank you sir, if you wouldn't mind, I am having a little trouble with this particular bit of code (I can't seem to get it to work for the life of me). This is from CvUnit.cpp ::resolveCombat this is right before the last break;

Spoiler :
Code:
/******************************************************************************************/
/*  Author: TheLadiesOgre (Original Concept By: Tsentom1)                                 */
/*  Date: 03.09.2009                                                                      */
/*  ModComp: Promotions DLL                                                               */
/*  Intended to: Add functionality for iVictory________Heals                              */
/******************************************************************************************/
				if ((getVictoryAdjacentTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Hospital Die Roll")) 
				{
					int iI;
					for (iI = 0; iI < NUM_DIRECTION_TYPES; iI++)
					{
						CvPlot* pLoopPlot = plotDirection(pPlot->getX_INLINE(), pPlot->getY_INLINE(), ((DirectionTypes)iI));

						if (pLoopPlot != NULL)
						{
							if (pLoopPlot->area() == pPlot->area())
							{
								CLLNode<IDInfo>* pUnitNode = pLoopPlot->headUnitNode();

								while (pUnitNode != NULL)
								{
									CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
									pUnitNode = pLoopPlot->nextUnitNode(pUnitNode);

									if (pLoopUnit->getTeam() == getTeam())
									{
										pLoopUnit->doHeal();
									}
								}
							}
						}
					}
				}

				if ((getVictorySameTileHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Surgeon Die Roll"))
				{
					CLLNode<IDInfo>* pUnitNode = pPlot->headUnitNode();

					while (pUnitNode != NULL)
					{
						CvUnit* pLoopUnit = ::getUnit(pUnitNode->m_data);
						pUnitNode = pPlot->nextUnitNode(pUnitNode);

						if (pLoopUnit->getTeam() == getTeam())
						{
							pLoopUnit->doHeal();
						}
					}
				}

				if ((getVictoryHeal()) >= GC.getGameINLINE().getSorenRandNum(100, "Field Medic Die Roll"))
				{
					doHeal();
				}

				[B]if (getVictoryMoveCount() > 0)
				{
					setMoves(movesLeft() + 1);
				}[/B]
/******************************************************************************************/
/*  Promotions DLL End; TheLadiesOgre; 03.09.2009                                         */
/******************************************************************************************/

I've bolded the part I'm having trouble with, it is most of the way down. The victory heals are custom, they seem to be working (they are what I wanted to know how to notifiy the player about in the last post). I've followed the guide, entered it everywhere I needed to, but it just refuses to work.
 
At the very end of CvUnit::doTurn() you will find:
Code:
	setMoves(0);

So I am inclined to believe that you want the unit to have the ability to move MORE after winning the battle, and thus want to SUBTRACT 1 from the current number of moves. You could also just use changeMoves(-1) to save some typing.
 
btw, that fix worked like a charm, now it works and it is not quite so powerful (blitz/skirmisher was a ridiculous combo) as it only fires once per turn, thanks again. I will be finishing adding the promotions I want to before releasing v0.3 (upload 0.2 a little while ago)
 
CivicPrereq is giving me a headache, I decided to clone StateReligionPrereq into CivicPrereq (as well as adding bAutoAcquire and bMustMaintain), compiled fine, no errors that I couldn't take care of easily (#$^#$%&%^#@ Punctuation Marks!!!!). I've tried testing it out but everytime I load the mod, the XML complains about Tag: CIVIC_THEOCRACY in Info class is incorrect. I've also tried THEOCRACY, CIVIC_VASSALAGE, VASSALAGE-- nothing is working. Any inklings as to what I am doing wrong?
 
1) Will I have to add the readPass3 (for PromotionInfos) to CvXMLLoadUtilitySet? I am assuming that is a yes, but it doesn't hurt to ask.

2) Just to double check, I was figuring that I'd add bAutoAcquire under ::setHasPromotion along with any Prereqs I wanted to check, sound about right?

Spoiler :
Code:
	if (GC.getPromotionInfo(ePromotion).isAutoAcquire())
	{
		if (GC.getPromotionInfo(ePromotion).getPrereqPromotion() != NO_PROMOTION)
		{
			if (!isHasPromotion((PromotionTypes)(GC.getPromotionInfo(ePromotion).getPrereqPromotion())))
			{
				return false;
			}
		}

		if (GC.getPromotionInfo(ePromotion).getPrereqOrPromotion1() != NO_PROMOTION)
		{
			if (!isHasPromotion((PromotionTypes)(GC.getPromotionInfo(ePromotion).getPrereqOrPromotion1())))
			{
				if ((GC.getPromotionInfo(ePromotion).getPrereqOrPromotion2() == NO_PROMOTION) || !isHasPromotion((PromotionTypes)(GC.getPromotionInfo(ePromotion).getPrereqOrPromotion2())))
				{
					return false;
				}
			}
		}

		if (GC.getPromotionInfo(ePromotion).getTechPrereq() != NO_TECH)
		{
			if (!(GET_TEAM(getTeam()).isHasTech((TechTypes)(GC.getPromotionInfo(ePromotion).getTechPrereq()))))
			{
				return false;
			}
		}

		if (GC.getPromotionInfo(ePromotion).getStateReligionPrereq() != NO_RELIGION)
		{
			if (GET_PLAYER(getOwnerINLINE()).getStateReligion() != GC.getPromotionInfo(ePromotion).getStateReligionPrereq())
			{
				return false;
			}
		}

		if (GC.getPromotionInfo(ePromotion).getCivicPrereq() != NO_CIVIC)
		{
			for (int iI = 0; iI < GC.getNumCivicInfos(); ++iI)
			{
				CivicTypes eCivic = GET_PLAYER(getOwnerINLINE()).getCivics((CivicOptionTypes)iI);
				if (GC.getPromotionInfo(ePromotion).getCivicPrereq() != eCivic)
				{
					return false;
				}
			}
		}
	}

I was figuring that I'd slide that into the list where I put bAutoAcquire in the schema or should I save it for last? In the same vein, I would add bMustMaintain to ::isPromotionValid using similar code as is in the spoiler only replacing bAutoAcquire with bMustMaintain, do you think this would work like this?
 
1) Yes. And make sure you put it somewhere after Civics, I don't see any harm at putting it after EVERYTHING personally, not sure why any readpasses are placed earlier in the order.

2) What I had done with AutoAcquire and MustMaintain was to place checks against isPromotionValid in various locations (::doTurn, ::setXY, ::setHasPromotion...) where you can have your prereqs suddenly no longer be maintained. isPromotionValid had to be modified though to ignore the claim that a promotion you already have is automatically invalid when doing a MM check, if I got a false, I forced a ::setHasPromotion(eProm, false). And for AutoAcquire, I tested (in much the same locations) against ::canPromote, if I got a true, I forced a ::promote to run. This allowed me to force a unit to spend the XP gained for a level if desired, though I only use AA/MM on bNoXP promotions thus far so that the user doesn't have their level selection forced upon them.

The way you describe your way doesn't sound like it would quite work, as you cannot remove a promotion from a unit during ::isPromotionValid since it is a CONST function and cannot alter data. And doing the checks in ::setHasPromotion would only check against a promotion they are already in the process of gaining/losing. So maybe I am reading what you wrote wrong.
 
1) Yes. And make sure you put it somewhere after Civics, I don't see any harm at putting it after EVERYTHING personally, not sure why any readpasses are placed earlier in the order.

Ok, I'm assuming I attach it to PromotionInfos and reference CivicTypes in the "for" statement. Should I comment out the preexisting PromotionInfos entry?

The way you describe your way doesn't sound like it would quite work, as you cannot remove a promotion from a unit during ::isPromotionValid since it is a CONST function and cannot alter data. And doing the checks in ::setHasPromotion would only check against a promotion they are already in the process of gaining/losing. So maybe I am reading what you wrote wrong.

You're right, it doesn't. You weren't reading it wrong, I am just not nearly as familiar with CivIV's guts as you are (though I am trying to gain familiarity by doing this). Part of me is half-tempted to just port it directly over (as it is too good of an idea not to add). I wasn't planning on adding bNoXP as I was only planning on using bAutoAcquire for one promotion (Frekk's Enslaver, make the Prereqs CIVIC_SLAVERY and Combat I, though force the XP expenditure in an effort to balance it somewhat), however, looking at it from the user friendly POV, I probably should include it and just leave it set to "0". Which brings to mind another question: Is the Commander/Minion System closer to Beginner or Advanced? (I have an idea concerning things like slave armies and such :D)
 
I don't understand your first question. What you need to do is create a third readpass for promotioninfos, if one doesn't already exist, and ensure that the calls for it come after Civics are loaded.

This means in CvXMLLoadUtilitySet you want a loop over getNumPromotionInfos which executes readPass3 on each one. (kPromotion.readPass3())

I would call both Commander system and AA/MM intermediate/advanced systems. You need to understand most all of CvUnit for each
 
This means in CvXMLLoadUtilitySet you want a loop over getNumPromotionInfos which executes readPass3 on each one. (kPromotion.readPass3())

Like this?:

Spoiler :
Code:
	LoadGlobalClassInfo(GC.getCivicInfo(), "CIV4CivicInfos", "GameInfo", "Civ4CivicInfos/CivicInfos/CivicInfo", false, &CvDLLUtilityIFaceBase::createCivicInfoCacheObject);
	for (int i=0; i < GC.getNumVoteSourceInfos(); ++i)
	{
		GC.getVoteSourceInfo((VoteSourceTypes)i).readPass3();
	}
/******************************************************************************************/
/*  Author: TheLadiesOgre (Original Concept By: Xienwolf)                                 */
/*  Date: 09.09.2009                                                                      */
/*  ModComp: Promotions DLL                                                               */
/*  Intended to: Add readPass3 for PromotionInfos to enable CivicPrereqs                  */
/******************************************************************************************/
	for (int i=0; i < GC.getNumPromotionInfos(); ++i)
	{
		GC.getPromotionInfo((PromotionTypes)i).readPass3();
	}
/******************************************************************************************/
/*  Promotions DLL End; TheLadiesOgre; 09.09.2009                                         */
/******************************************************************************************/

I would call both Commander system and AA/MM intermediate/advanced systems. You need to understand most all of CvUnit for each

So are you recommending that I wait to implement such things as Slave Armies and Title Promotions (like: you are Sir Somebody, you get a Squire)?

Actually, while I am thinking about slaves, any suggestions for implementing a random chance you die after a build? In Frekk's Enslaver Promotion the slave dies after doing one thing, I would like to change that though. I would like to make it so that a slave has a small chance per build to die (ideally it would be cumulative so that the slave you take could die after their first build or could take 15 builds to be "worked to death") but unless gifted to another civ or released back to their home civ they will die sooner or later. Eventually, I'd want to also add two negative diplo hits one for "You've enslaved our people" and the second for "You've worked our people to death" which would both stack with Afforess' CivicAttitudeModifier (adding up to you being powerful but despised by the world for your civic choices).
 
Yes, exactly like that. As long as you had to create CvPromotionInfo::readPass3() yourself from scratch. If it already existed, just make sure it is listed after civics and use the one which already exists.


I wouldn't say you have to wait on them, but if you want to be able to do them from scratch then it is advisable that you get some more experience, or that you flowchart everything before you begin and request feedback to ensure you are predicting things properly. You could use them as an exercise to push yourself though by attempting to write them yourself, then comparing against my code and ensuring you understand why everything is where it is. Since things are not clearly labeled as being JUST for this system or that one, you would have to do some serious digging or know roughly where to look (or at least what to look for) anyway or you would miss some vital pieces. So even just a straight import, as long as you comment on each section of code precisely what it does, and ask questions when unsure, could be a learning experience.

You could implement a chance for death fairly easy on slaves. I assume you mean a chance to die each time they finish building an improvement in the normal method. In that case you would look for the lines of code which check work progress on a tile against the build cost of the improvement and run setHasImprovement on the tile if complete. In there (while still linked to the unit) you would check the unit for an integer value which denotes the chance to die per build and kill them if they fail. Or you could invent a hidden (or not) vitality system and make each improvement (or contribution of work TOWARD an improvement) cost the unit a certain amount of their vitality, and once all is spent, they die. You could even link such a vitality system to the number of units displayed for the slave on the map so that the player can guess roughly when a slave is about to die, and/or provide a method of regenerating (resting) slaves.
 
You could implement a chance for death fairly easy on slaves. I assume you mean a chance to die each time they finish building an improvement in the normal method. In that case you would look for the lines of code which check work progress on a tile against the build cost of the improvement and run setHasImprovement on the tile if complete. In there (while still linked to the unit) you would check the unit for an integer value which denotes the chance to die per build and kill them if they fail. Or you could invent a hidden (or not) vitality system and make each improvement (or contribution of work TOWARD an improvement) cost the unit a certain amount of their vitality, and once all is spent, they die. You could even link such a vitality system to the number of units displayed for the slave on the map so that the player can guess roughly when a slave is about to die, and/or provide a method of regenerating (resting) slaves.

I am really liking the idea of a hidden vitality system which is slightly drained each turn the slave works on an improvement, in addition to a small chance that they die outright. Which brings me to yet more ideas I have regarding slaves

1) Is it possible to have the unit not be killed but be made into a slave while still retaining the information concerning their prior incarnation? For example, I take a Chinese Swordsman as a slave, it gets logged that he was a Chinese Swordsman, remembering his strength (though not displaying it) so that this then becomes the slaves hidden vitality rating? If I later chose to "liberate" him back to the Chinese (or if he was captured by a Chinese combat unit), he would once again become a Swordsman (regaining his promotions and having the same number of hit points left that the slave did in vitality), however, if I gift him to the French, he remains a slave. I also like the idea of displaying the slaves vitality through the number of units displayed on the map, I also feel that the method for healing slaves should be as typical though at a rate about 20% of normal (after all they're slaves, even if they aren't slaving away building my improvements, they're slaving away serving my populace doing the jobs that are beneath my people).

2) How hard would it be to have the same Chinese Swordsman from the above example retain his asian UnitArtStyleType even though he is slaving over my greco-roman improvements?
 
You could use ::convert so that you capture the exact same unit, promotions and all. Then in a new variable, mark who his original owner was (could do this for all units on creation and have it carry with conversion, but change on gifting).

Then you set bypasses in various locations to force the stats of the unit to be those of a slave when the original owner != current owner. To get the ability to build improvements you would want to override the unittype, which may not be easy.

That would do most of what you want. All original unit stats would still be available for checking, but the unit would seem to be a slave.

To maintain artstyle, link art checks to CvUnit instead of unitinfos.
 
Back
Top Bottom