Artificial Unintelligence

Do you think it would be a good idea to fully divorce the purchase-consideration algorithm for buildings, worker/settler, and combat units into separate routines? I'm currently experimenting with expanding the extant unit purchase algorithm MilitaryAI has (e.g. current GS, treasury/GPT, etc) and plan to limit the EconomicAI to non-combat purchases (when DoHurry() is working).

An spend-thrift AI might end up spending all its gold on whichever algorithm comes first (assuming the AI can now purchase multiple things per turn) and have no gold left over for the others, however (though that might not be a bad thing, and can be controlled by spending caps and whatnot).

Firaxis sort of unified all purchasing types via a purchasing priority queue (you can find it in the CvTreasury class, the AI is basically supposed to send an invoice to the Treasury, and in case the AI does not have enough Gold to satisfy all invoices, it will save up for higher priority invoices even if there are lower priority invoices it could satisfy), but the two problems there are 1) the order in which AI routines are executed often overrides any treasury prioritization, and 2) only the direct effects of the purchase are taken into account for prioritization, so differing goals are ignored (eg. buying a tile to steal it away from another city vs. buying a tile for more production, diplomacy gold spent to buy strategic resources from another player vs. diplomacy gold spent to sue for peace).

There's no way to truly divorce all purchasing considerations of the AI, because they all draw from the same pool. Remember, it's not just unit/building purchases you have to consider, but also tile purchasing, CS gifting, diplomacy gold, and unit upgrading. Usually if the AI would like to spend gold to achieve a certain goal, there would be at least two ways to go about it, so both ways would have to be considered and weighed against one another. For example, if the AI wants to spend gold to help end a war, they can purchase a military unit, purchase an important production building to speed up military unit construction, purchase a key defensive building, gold ally a CS near the opponent, purchase an obsolete UU and upgrade it the next turn, bribe another player to declare war on the common opponent, or use the gold to try to sue for peace; all of these possibilities would need to be weighed against one another, and things get even more complicated if you also want to support possible modded-in uniques that rely on purchasing.

Organization would definitely be advantageous, but I think that organizing based on purchasing goals is both more flexible and allows for more emergent behavior than organizing based on the purchased entity; it's also probably harder to build when the AI isn't already set up in a planning tree-like system.
Since Civ5 AI's gold allocation is implemented in an extremely ad-hoc fashion anyway, any sort of proper organization would require building up a system from scratch.
 
Firaxis sort of unified all purchasing types via a purchasing priority queue (you can find it in the CvTreasury class, the AI is basically supposed to send an invoice to the Treasury, and in case the AI does not have enough Gold to satisfy all invoices, it will save up for higher priority invoices even if there are lower priority invoices it could satisfy).

It's in the EconomicAI (the CvTreasury class doesn't have any AI functions afaik, just a bunch of data storage/processing routines)

Spoiler :
Code:
bool CvEconomicAI::CanWithdrawMoneyForPurchase(PurchaseType ePurchase, int iAmount, int iPriority)
{
	int iBalance = m_pPlayer->GetTreasury()->GetGold();
	// Update this item's priority
	if(iPriority != -1)
	{
		int iIndex = (int)ePurchase;
		m_RequestedSavings[iIndex].m_iPriority = iPriority;
	}
	// Copy into temp array and sort by priority
	m_TempRequestedSavings = m_RequestedSavings;
	std::stable_sort(m_TempRequestedSavings.begin(), m_TempRequestedSavings.end());
	for(int iI = 0; iI < (int)m_TempRequestedSavings.size(); iI++)
	{
		CvPurchaseRequest request = m_TempRequestedSavings[iI];
		// Is this higher priority than the request we care about?
		if(request.m_eType != ePurchase)
		{
			iBalance -= request.m_iAmount;
			// No money left?
			if(iBalance <= 0)
			{
				return false;
			}
		}
		// Is this the one, if so, check balance remaining
		else if(request.m_eType == ePurchase)
		{
			return (iBalance >=iAmount);
		}
	}
	CvAssert(false);
	return false;  // Should never reach here
}

The main issue I find is that the request weights are often just whatever is defined in
Spoiler :

I'm sure you've probably adjusted these values in your XML/SQL component (the values below are the values found in the base game files).
Code:
UPDATE Defines SET Value = 150 WHERE Name = "AI_GOLD_PRIORITY_UPGRADE_BASE"; 
UPDATE Defines SET Value = 25 WHERE Name = "AI_GOLD_PRIORITY_UPGRADE_PER_FLAVOR_POINT"; 
UPDATE Defines SET Value = 150 WHERE Name = "AI_GOLD_PRIORITY_DIPLOMACY_BASE"; 
UPDATE Defines SET Value = 25 WHERE Name = "AI_GOLD_PRIORITY_DIPLOMACY_PER_FLAVOR_POINT"; 
UPDATE Defines SET Value = 500 WHERE Name = "AI_GOLD_PRIORITY_UNIT"; 
UPDATE Defines SET Value = 400 WHERE Name = "AI_GOLD_PRIORITY_DEFENSIVE_BUILDING"; 
UPDATE Defines SET Value = 350 WHERE Name = "AI_GOLD_PRIORITY_BUYOUT_CITY_STATE";

, multiplied sometimes by flavors (in turn modified by AI strategies). It does not directly take into consideration any in-situ conditions (i.e. state of wars or diplomacy, current economic situation apart from whether it's broke), nor does it actually do much of a comparison between the requests (other than performing 1-on-1 evaluation of "is this request higher than my other request? ok next item").

------------------------------------------------------------------------------

Also if I may ask, in your v10 code, what changes (at least on paper) are considered non-stable for porting (aside from DangerPlots)?
 
It's in the EconomicAI (the CvTreasury class doesn't have any AI functions afaik, just a bunch of data storage/processing routines)

Spoiler :
Code:
bool CvEconomicAI::CanWithdrawMoneyForPurchase(PurchaseType ePurchase, int iAmount, int iPriority)
{
	int iBalance = m_pPlayer->GetTreasury()->GetGold();
	// Update this item's priority
	if(iPriority != -1)
	{
		int iIndex = (int)ePurchase;
		m_RequestedSavings[iIndex].m_iPriority = iPriority;
	}
	// Copy into temp array and sort by priority
	m_TempRequestedSavings = m_RequestedSavings;
	std::stable_sort(m_TempRequestedSavings.begin(), m_TempRequestedSavings.end());
	for(int iI = 0; iI < (int)m_TempRequestedSavings.size(); iI++)
	{
		CvPurchaseRequest request = m_TempRequestedSavings[iI];
		// Is this higher priority than the request we care about?
		if(request.m_eType != ePurchase)
		{
			iBalance -= request.m_iAmount;
			// No money left?
			if(iBalance <= 0)
			{
				return false;
			}
		}
		// Is this the one, if so, check balance remaining
		else if(request.m_eType == ePurchase)
		{
			return (iBalance >=iAmount);
		}
	}
	CvAssert(false);
	return false;  // Should never reach here
}

The main issue I find is that the request weights are often just whatever is defined in
Spoiler :

I'm sure you've probably adjusted these values in your XML/SQL component (the values below are the values found in the base game files).
Code:
UPDATE Defines SET Value = 150 WHERE Name = "AI_GOLD_PRIORITY_UPGRADE_BASE"; 
UPDATE Defines SET Value = 25 WHERE Name = "AI_GOLD_PRIORITY_UPGRADE_PER_FLAVOR_POINT"; 
UPDATE Defines SET Value = 150 WHERE Name = "AI_GOLD_PRIORITY_DIPLOMACY_BASE"; 
UPDATE Defines SET Value = 25 WHERE Name = "AI_GOLD_PRIORITY_DIPLOMACY_PER_FLAVOR_POINT"; 
UPDATE Defines SET Value = 500 WHERE Name = "AI_GOLD_PRIORITY_UNIT"; 
UPDATE Defines SET Value = 400 WHERE Name = "AI_GOLD_PRIORITY_DEFENSIVE_BUILDING"; 
UPDATE Defines SET Value = 350 WHERE Name = "AI_GOLD_PRIORITY_BUYOUT_CITY_STATE";

, multiplied sometimes by flavors (in turn modified by AI strategies). It does not directly take into consideration any in-situ conditions (i.e. state of wars or diplomacy, current economic situation apart from whether it's broke), nor does it actually do much of a comparison between the requests (other than performing 1-on-1 evaluation of "is this request higher than my other request? ok next item").

------------------------------------------------------------------------------

Also if I may ask, in your v10 code, what changes (at least on paper) are considered non-stable for porting (aside from DangerPlots)?

Yeah, it's been a while since I've checked up on classes/functions related to purchasing, I misremembered them being in CvTreasury.

An odd thing I realized while looking at the surrounding code in CvEconomicAI is that Firaxis actually wrote a function that would tell the AI how much gold it can spend on a type of purchase, but this function is never used anywhere even though it should be: it's CvEconomicAI::AmountAvailableForPurchase(). Even if the prioritization method is highly flawed, use of this function could at least address the issue of the AI not considering purchasing priorities for certain types of gold expenditures (eg. it wouldn't spend all of its gold to buy a unit for a sneak attack operation when it's saving up for unit upgrades).
The "is this request higher priority than the other request" logic would be fine if the functions that established said priorities worked well enough. Adjustments for game-dependent situations are supposed to be done by EconomicAI Strategies, but we both know how well those work.

If you go forward with your original plan of keeping the prioritization of different types of gold purchases separate, you will most likely end up with a system similar to the Flavor system: it will definitely work better than Civ5's original system and the AI will be able to handle obvious cases well (eg. buying units for a war, buying city-states for a diplomatic victory), but you would need to manually program in factors for all the different ways a certain type of purchase could be used, which may become overwhelming as you want the AI to be able to handle more and more complex results of a gold purchase (eg. a CS purchase would need to factor in extra yields received, the CS's resources, the CS's location, any bonuses the AI gets for allied CS's, how well the CS could help in any of the player's current wars, and more).
A goal-oriented system would probably be the ideal solution, but such a system would need to be integrated into all aspects of Civ5's AI to work properly, otherwise you'll get situations where one goal-oriented AI part will perform an action based on the best A* "path" it finds connecting world states, but the non-goal-oriented AI part that would be responsible for the next action in that path might end up following a different path. Decisions, decisions...

As for non-stable bits of v10, DangerPlots and TacticalAI changes are most likely not stable, everything else may or may not be fine. For example, I had two fatal typos in the Great Works swapping code I originally wrote in February, and I only noticed them in late June because of my discovery of static code analysis; they were two instances of accessing an array element outside of the array's dimensions, and they did not produce errors or warnings at compile-time, so I had no idea they were present until VS2013's Level 4 Warnings picked up the fact that something wasn't right (VS2008's Level 4 Warnings did not pick it up).
 
If you go forward with your original plan of keeping the prioritization of different types of gold purchases separate, you will most likely end up with a system similar to the Flavor system: it will definitely work better than Civ5's original system and the AI will be able to handle obvious cases well (eg. buying units for a war, buying city-states for a diplomatic victory), but you would need to manually program in factors for all the different ways a certain type of purchase could be used, which may become overwhelming as you want the AI to be able to handle more and more complex results of a gold purchase (eg. a CS purchase would need to factor in extra yields received, the CS's resources, the CS's location, any bonuses the AI gets for allied CS's, how well the CS could help in any of the player's current wars, and more).

I did something similar with the PolicyAI (trying a new take on the Policy system with new features & interactions), and you're right in that a lot of things need to be semi-manually coded in (often as an extra column in the policy table, e.g. "this column represents a policy that gives you bonuses from trade routes! now for every trade route you have, increase the value of this policy by a factor of Y!").

But short of writing up a new ("true AI") system from the ground up (which would be rather time consuming... and would quite likely require knowledge at the Post-Graduate level in Computer Science... although I'm probably over-thinking this), some sort of fuzzy-logic into a "meh, good enough" result may be what's most feasible.

The innate flavor & modification based on strategies system does work in theory.... except to become effective there would need to be many, many more "strategies" to be checked & evaluated each turn, to the point where you might as well toss all those out and have weights modified directly anyway for each AI.

Going into 'ECONOMICAISTRATEGY_NEED_HAPPINESS' (for example), how would we know whether we should multiply our happiness value by 250%, or increase it by a flat amount of 10? What would be 'enough', what its effect on 'crowding out' the other flavors would be, what happens when the happiness flavor for players/techs/buildings/policies/etc are modified, what happens when happiness/unhappiness sources are changed? The math/statistics that would need to be crunched out to calibrate the system would be extremely complicated (e.g. a lot harder to assess changes across the system when tweaking input parameters, which is not exactly a good thing in a moddable game).

For example, I had two fatal typos in the Great Works swapping code I originally wrote in February, and I only noticed them in late June because of my discovery of static code analysis; they were two instances of accessing an array element outside of the array's dimensions, and they did not produce errors or warnings at compile-time, so I had no idea they were present until VS2013's Level 4 Warnings picked up the fact that something wasn't right (VS2008's Level 4 Warnings did not pick it up).

Question: when a player "dies" (i.e. a city state having its city captured), does the game remove all the extant attributes of the CvPlayer object instance (i.e. destroys the object and de-allocates memory)?

Spoiler :

I played around with the AI city-razing algorithm, and amongst one of the new conditions checked was whether the city came from a minor civ. Game crashed at "isMinorCiv()" for what is presumably a freshly-killed city state, after the city state has presumably already died.)
 
Hey Delnar, sorry for the walls of text :lol: :
- Some irregularities/issues regarding your code & pre-processor-defines
- This is your v10 experimental code, and just for the stuff your CvReligionClasses.cpp changes.

----------------------------------------------------------
Line 5812 in CvReligionClasses.cpp

Code:
pLoopCity->GetCityStrategyAI()->ConstructRushList(YIELD_FAITH);
needs
Code:
AUI_ECONOMIC_FIX_DO_HURRY_REENABLED_AND_REWORKED
-----------------------------------------------------------
Line 5876 of CvReligionClasses.cpp
Code:
dRtnValue *= 1 + pow((double)GC.getGame().getJonRandNumBinom(257, "Belief score randomizer.") / 128.0 - 1.0, 3.0);
needs
Code:
AUI_BINOM_RNG

---------------------------------------------------------
Lines 6017 * 6018 in CvReligionClasses.cpp
Code:
	int iNewPlotValue = pForCity->GetCityCitizens()->GetPlotValue(pPlot, true, adExtraYields);
	double dRtnValue = iNewPlotValue - pForCity->GetCityCitizens()->GetPlotValue(pPlot, true);
appears to need one of

Code:
AUI_CITIZENS_GET_VALUE_FROM_STATS
AUI_CITIZENS_IGNORE_FOOD_FOR_CITIZEN_ASSIGN_AFTER_GROW

-----------------------------------------------------------
Line 6149 in CvReligionClasses.cpp
Code:
if (pLoopPlot && m_pPlayer->IsPlotUnderImmediateThreat(*pLoopPlot, pCity->getOwner()))
seems to need
Code:
AUI_DANGER_PLOTS_REMADE
-----------------------------------------------------------
With a couple of your for loops, some of your uints, such as
Code:
for (uint jJ = 0; jJ < GC.getNumBuildingClassInfos(); jJ++)
will provoke
Code:
warning C4018: '<' : signed/unsigned mismatch

If using uint, you'll need to cast them back into regular integers (or whatever the relevant ______Types pseudo-integer is being used) before passing it as an index argument.

-----------------------------------------------------------
Line 6212 in CvReligionClasses.cpp
Code:
if (m_pPlayer->canConstruct(eBuilding, false, true, true, NULL, true, &iEras)
needs
Code:
AUI_PLAYER_CAN_CONSTRUCT_AI_HELPERS

-----------------------------------------------------------

*90 minutes later*... yay it compiled :D
 
But short of writing up a new ("true AI") system from the ground up (which would be rather time consuming... and would quite likely require knowledge at the Post-Graduate level in Computer Science... although I'm probably over-thinking this), some sort of fuzzy-logic into a "meh, good enough" result may be what's most feasible.

The innate flavor & modification based on strategies system does work in theory.... except to become effective there would need to be many, many more "strategies" to be checked & evaluated each turn, to the point where you might as well toss all those out and have weights modified directly anyway for each AI.

Going into 'ECONOMICAISTRATEGY_NEED_HAPPINESS' (for example), how would we know whether we should multiply our happiness value by 250%, or increase it by a flat amount of 10? What would be 'enough', what its effect on 'crowding out' the other flavors would be, what happens when the happiness flavor for players/techs/buildings/policies/etc are modified, what happens when happiness/unhappiness sources are changed? The math/statistics that would need to be crunched out to calibrate the system would be extremely complicated (e.g. a lot harder to assess changes across the system when tweaking input parameters, which is not exactly a good thing in a moddable game).
A goal-oriented AI is nowhere near a "real" AI, it's just an extension to an existing AI design. In the pure flavor-based approach, the AI scores all of its possible actions through a variety of scoring functions, then picks the one with the highest score. In the goal-oriented extension of a flavor-based approach, this scoring mechanism is used only to determine the AI's goals. Once it has a goal picked out, the AI then constructs an A* "path" to its goal: instead of tiles between a unit's starting position and a target tile, the goal-oriented A*'s "path" has world states between the AI's current world state and a set of world states where the AI's goal is complete (which doesn't need to be explicitly defined, it just needs to be some world state that passes a set of conditions). These world states are connected by actions the AI can take, which are the only things that need to be explicitly defined in this system (eg. buy a unit, make deal with player, get ideological tenet, etc.). Using these actions, the AI navigates possible world states, constructing an A* path until it finally reaches a world state that satisfies its goal. Personalities can be implemented by having the AI weigh different actions differently: a culture-oriented AI would assign lower costs to actions associated with getting culture, an aggressive AI would assign lower costs to actions that directly harm another player, etc. Basically, the idea is that instead of defining all possible pre-set behaviors for the AI, you only define the actions that an AI can take and you let the AI construct a path to its goal using those actions. This goal-oriented extension was popularized by F.E.A.R. (which uses a real-time variation of it) and is used in quite a few FPS's since, but I can see it being adapted quite well to a TBS environment.

The biggest issue with a goal-oriented AI in Civ5 is the complexity of world states: for the highest degree of accuracy, AIs would have to consider pathfinding and moving units around in tiles for even the most seemingly unrelated of goals. An approach that would split the goal-orientation into two versions would definitely help, ie. one goal-oriented AI would look only at actions that do not involve unit maneuvers (diplomacy, spending gold, spending faith, choosing production, choosing policies, etc.), and the other AI would look only at actions that involve unit maneuvers (eg. moving a unit to a tile to ZOC an enemy to save another unit near death, attacking units near an enemy city to clear the area before trying to actually take the city, etc.). This would remove the possibility for some actions (eg. buying a specific unit at a city to counter an offensive of a certain unit composition against that city) and there would still need to be some communication between the two systems (eg. moving a military unit to upgrade it, moving a settler to found a city, moving a missionary to convert a city so to give benefits to whatever goal the AI has planned, etc.).
The fact that multiple goals can have overlapping routes means that a strict approach of flavor-based goal selection followed by planning to that goal would omit weighing AI actions based on the ability of those actions to satisfy multiple goals. This could be solved by using the flavor system to apply base costs to actions depending on the change in distance to each goal from the action (instead of ignoring any changes that do not help with the main goal), but since this would greatly increase the amount of world states the AI would have to navigate, it would come at a huge cost in performance.
There is also the problem of Lua scripting: goal-oriented AI systems would need to constantly check for Lua code added in by mods that affect the world state, and not only are calls to the Lua script engine fairly expensive, but the actual Lua scripts can affect a wide variety of stuff, so Lua checks would need to be called constantly. Although the upside is that the AI would be able to take advantage of any mod's changes without any input from the modder (maybe if the mod adds new actions, the modder can just add in a new AI action via a Lua script, and the AI would be able to integrate it into its regular pool of actions without any problems), it would be at a huge performance cost, even more so than the pathfinder I think.

Question: when a player "dies" (i.e. a city state having its city captured), does the game remove all the extant attributes of the CvPlayer object instance (i.e. destroys the object and de-allocates memory)?

Spoiler :

I played around with the AI city-razing algorithm, and amongst one of the new conditions checked was whether the city came from a minor civ. Game crashed at "isMinorCiv()" for what is presumably a freshly-killed city state, after the city state has presumably already died.)
The player object shouldn't be destructed, seeing the existence of CvPlayer::isAlive(). If it were destructed, these lines in CvPlayer::DoLiberatePlayer() would crash the game:
Code:
if (!GET_PLAYER(ePlayer).isAlive())
{
	GET_PLAYER(ePlayer).setBeingResurrected(true);
If the player object were destructed, GET_PLAYER(ePlayer) would be a null reference.

Hey Delnar, sorry for the walls of text :lol: :
- Some irregularities/issues regarding your code & pre-processor-defines
- This is your v10 experimental code, and just for the stuff your CvReligionClasses.cpp changes.

----------------------------------------------------------
Line 5812 in CvReligionClasses.cpp

Code:
pLoopCity->GetCityStrategyAI()->ConstructRushList(YIELD_FAITH);
needs
Code:
AUI_ECONOMIC_FIX_DO_HURRY_REENABLED_AND_REWORKED
Fixed by adding in the necessary define in front of ConstructRushList().

Line 5876 of CvReligionClasses.cpp
Code:
dRtnValue *= 1 + pow((double)GC.getGame().getJonRandNumBinom(257, "Belief score randomizer.") / 128.0 - 1.0, 3.0);
needs
Code:
AUI_BINOM_RNG
Fixed by adding in a version that doesn't use the binomial RNG (no idea why I forgot to do that).

Lines 6017 * 6018 in CvReligionClasses.cpp
Code:
	int iNewPlotValue = pForCity->GetCityCitizens()->GetPlotValue(pPlot, true, adExtraYields);
	double dRtnValue = iNewPlotValue - pForCity->GetCityCitizens()->GetPlotValue(pPlot, true);
appears to need one of

Code:
AUI_CITIZENS_GET_VALUE_FROM_STATS
AUI_CITIZENS_IGNORE_FOOD_FOR_CITIZEN_ASSIGN_AFTER_GROW
Fixed by adding AUI_CITIZENS_GET_VALUE_FROM_STATS as a prerequisite for the function (there was a similar thing in the AUI_RELIGION_SCORE_BELIEF_AT_CITY_REMADE section).

Line 6149 in CvReligionClasses.cpp
Code:
if (pLoopPlot && m_pPlayer->IsPlotUnderImmediateThreat(*pLoopPlot, pCity->getOwner()))
seems to need
Code:
AUI_DANGER_PLOTS_REMADE
Fixed by changing the DangerPlots Remade code's PlayerTypes version of IsPlotUnderImmediateThreat() to accept one-argument calls (in which case the player being checked is m_pPlayer), which means the call will function with or without the remade DangerPlots.

With a couple of your for loops, some of your uints, such as
Code:
for (uint jJ = 0; jJ < GC.getNumBuildingClassInfos(); jJ++)
will provoke
Code:
warning C4018: '<' : signed/unsigned mismatch

If using uint, you'll need to cast them back into regular integers (or whatever the relevant ______Types pseudo-integer is being used) before passing it as an index argument.
Ah, you haven't enabled AUI_WARNING_FIXES. I didn't add compiler conditionals for AUI_WARNING_FIXES at those lines because I assumed AUI_WARNING_FIXES would always be enabled if you're using the "big" fixes. Among other things, all the GC.getNum<thingy>Infos() functions return uints with AUI_WARNING_FIXES enabled (like they should, because they're returning array sizes).

Line 6212 in CvReligionClasses.cpp
Code:
if (m_pPlayer->canConstruct(eBuilding, false, true, true, NULL, true, &iEras)
needs
Code:
AUI_PLAYER_CAN_CONSTRUCT_AI_HELPERS
Fixed by adding in an alternate version that does not rely on AUI_PLAYER_CAN_CONSTRUCT_AI_HELPERS.

Thanks for pointing those out, I'll have the commit uploaded as soon as my computer stops freezing up.
 
Hey Delnar, in

Code:
CvReligionAI::ScoreBeliefAtCity
you have the variable
Code:
int iEras;
which is never initialized to anything, but is used a little down as
Code:
if (iEras < 1)
	iEras = 1;
 
Hey Delnar, in

Code:
CvReligionAI::ScoreBeliefAtCity
you have the variable
Code:
int iEras;
which is never initialized to anything, but is used a little down as
Code:
if (iEras < 1)
	iEras = 1;

It's used in this call:
Code:
m_pPlayer->canConstruct(eBuilding, false, true, true, NULL, true, &iEras)
If the pointer is not null, it's set to 0 if the player meets the buildings tech prerequisites, otherwise it's set to 1 + (highest tech prerequisite's era) - (player's current era) with a minimum value of 1. This exists so that the AI can partially evaluate the building for the future. As a side note, I just noticed a few issues with that bit of functionality of canConstruct(), so I'll be uploading a commit that fixes them.
 
It's used in this call:
Code:
m_pPlayer->canConstruct(eBuilding, false, true, true, NULL, true, &iEras)
If the pointer is not null, it's set to 0 if the player meets the buildings tech prerequisites, otherwise it's set to 1 + (highest tech prerequisite's era) - (player's current era) with a minimum value of 1. This exists so that the AI can partially evaluate the building for the future. As a side note, I just noticed a few issues with that bit of functionality of canConstruct(), so I'll be uploading a commit that fixes them.

Ah, didn't notice the passing-by-reference.
 
Hi! Thank you for making this mod!

I'm interested in using the full version (as opposed to Lite), but I already use a dll mod (CSD) and I think you can only have one dll mod active at a time? Is there any way I can have both running?

Thanks!
 
I'm interested in using the full version (as opposed to Lite), but I already use a dll mod (CSD) and I think you can only have one dll mod active at a time? Is there any way I can have both running?

Indeed, only one DLL mod can be active at a time. This is because DLL mods all rely on overriding the single file that contains the majority of the game's C++ code, CvGameCoreDLL (or its G&K or BNW variations).
The only way you can have the functionality of multiple DLL mods is to merge their source codes, sort out all the conflicts, and compile a CvGameCoreDLL file from the resulting code. Unfortunately, this is all easier said than done, especially when the mods in question can have hundreds or thousands of conflicts that all need to be addressed on a case-by-case basis.

I know there are quite a few DLL mods, eg. the Community Patch Project and Acken's Balance Mod, that both borrow from AuI's code and feature the functionality of other DLL mods, and CSD is often one of those other DLL mods. If you search around the forums for mods that use code from AuI, you may encounter one to your liking. If you don't, I am also planning on doing a grand merger of sorts for AuI v11 where I merge Community Patch Project DLL's functionality with AuI (so any DLL mods whose functionality is in the Community Patch Project, eg. CSD, could be activated by users in one of AuI's files), but seeing as I'm still working on v10, I wouldn't hold my breath.
 
Can't wait for v10 to be released for that reason alone. Currently there's no way to both have AI that can talk to you in multiplayer and can move and shoot with ranged units.

There are DLL mods for both, but since nobody releases their source code...
 
That is incorrect. Most of these DLL's are on Github.
I couldn't find any DLL's on GitHub that implemented JdH's active AI in multiplayer last time I checked, which is why I developed my own (probably terrible) solution for v10.

Hey Delnar, for your experimental branch... is it safe to assume most of the "AUI_TACTICAL_FIX_" stuff is stable?
Those should be stable, yes, but I'd still be cautious. The current issue I'm wrestling with was caused by me assuming that the trade stuff was all stable, seeing as I was using the same code there as in code that is proven stable, but for some unknown reason, having international trade routes use CityCitizens' yield scoring corrupts HomelandAI's and TradeAI's pointers to their parent CvPlayer objects, which usually ends up resulting in an infinite loop around turn 40-ish.
 
A barbarian brute sat on the edge of my border, near my capital, and got barraged down every turn for no apparent reason. Maybe this should be looked at too.
 
A barbarian brute sat on the edge of my border, near my capital, and got barraged down every turn for no apparent reason. Maybe this should be looked at too.

Huh, I thought that was a new behavior caused by side-effects of my v10 DangerPlots. I guess it's a bug that exists in v9 then, but becomes a lot more prominent in my dev v10 version because of the new DangerPlots. The fact that the behavior occurs in v9 narrows down the possibilities quite a bit, and I think I know where to start looking once I sort out my current pointer corruption predicament (I'll start looking at the way barbarian functions interact with ExecuteAttack() if no attack is actually made as a result of the AI not wanting to sacrifice units).
 
Question Delnar:

Each time a unit "moves" (e.g. executes a move mission), is the "danger" value of that plot updated immediately, or is the plot-danger-value cached/static (and only refreshed on the subsequent turn)?
 
Question Delnar:

Each time a unit "moves" (e.g. executes a move mission), is the "danger" value of that plot updated immediately, or is the plot-danger-value cached/static (and only refreshed on the subsequent turn)?

The original DangerPlots updated all values at the beginning of the turn after diplomacy actions. All non-air military units who are visible at the beginning of the turn and are owned by players who should not be ignored (usually means they are at war with the current player) get processed. When a unit is processed, all tiles within possible movement radius around the unit (movement + attack range with move-and-shoot) get the unit's base combat strength added to the their danger values; if the unit is more than one turn away from the tile (ie. it could not attack the tile in one turn), the combat strength added to the tile's danger value is divided by however many turns it would take for the unit to attack the tile. If a unit cannot attack the tile in one turn, the last bit in the added danger value is set to 0, otherwise it is set to 1; note that this is done before the value is added into the tile's overall danger. This is supposed to feed into IsUnderImmediateThreat(), which checks the last bit of the overall danger... except that because the bit-setting is done before addition, it doesn't actually work (odd + odd = even, so the function will return false if an even number of units could reach the tile in one turn). In any case, once the old DangerPlots is done looping through units, it loops through enemy cities revealed to the current player, adding each city's defensive strength to the danger values of plots the given city could fire at (loop through players encloses a loop through the player's units followed by a loop through the player's cities). Finally, it loops through all revealed plots on the map to process citadels: 50 times the power of the current player's strongest buildable land unit (not unit strength, but the unit's power, which is much larger) is added to all tiles that can take damage from citadel attrition. Once all of these factors have been processed, tiles' danger values remain static until the AI's next turn. A tile's danger value is simply a stored integer, so access to it has very little overhead, but because of Civ5's combat mechanics and the various issues involved in the processing steps, danger values have very little meaning.

The new DangerPlots' structure is still evolving, but it's definitely different from the original DangerPlots. Instead of an array of integers representing danger, the main DangerPlots information is an array of structs that, among other things, contain two CvUnit* vectors, one CvCity* vector, and a CvPlot* that should point to a plot containing a citadel. At the beginning of the AI's turn after diplomatic actions, all military units (even air units and units that aren't visible or revealed) owned by enemy players get processed. When a unit is processed, all tiles within possible movement + attack radius are checked by the unit-ignoring pathfinder: if the unit could possibly attack a tile, it is added onto the plain "Units" CvUnit* vector, and if the unit could possibly move onto the tile, it is added onto the "MoveOnly" CvUnit* vector. Then, all cities owned by enemy players get processed in a similar fashion, with pointers to a city added onto the CvCity* vectors of the tiles at which they can fire. Finally, all tiles are checked for nearby citadels, and if a tile is found to be in attrition range of a citadel, the CvPlot* of the given tile is set to the plot containing the citadel. Once the DangerPlots vectors and pointers are built, they remain static until the AI's next turn.

However, a tile's danger value is dynamic in the new DangerPlots, at a significant increase in accuracy but a huge cost in performance. When a tile's danger value is checked, it is always checked against a specific "victim" unit or city (when none are available, it is checked for a dummy unit with a strength of 1). In the generic case, all elements of the plain CvUnit* vector and the CvCity* vector are looped through. In each case, if the item is alive and "visible" to the AI when the function is called, the pathfinder is checked to determine the best plot that unit could attack from (obviously no check is done for cities); cities and citadels in revealed plots count as "visible", while units who either attacked a visible plot last turn or are not invisible and are either air units or in a visible plot count as "visible". A list of plots "used up" for attacking is built up during this process, so a given attack plot can only be used once unless the unit attacking would have the ability to move out of the plot after the attack is made. If an attack plot is found, the amount of damage that could be dealt to the victim in the given tile by that unit from the attack plot is added to an overall danger value. If the overall danger value is less than the current HP of the victim and the tile is eligible for healing for the victim, the victim's heal rate is subtracted from the danger value (overheals are negated). Then, if the victim is a unit and the tile's CvPlot* points to a plot currently containing an active, enemy citadel, citadel attrition damage is added to the danger value, since this is the order in which the damage would be applied. Things are handled differently if the plot contains a city friendly to the victim: if the danger value for that city is greater than or equal to the city's current health assuming that the victim is the city's garrison (since garrisons change city strength), the function returns MAX_INT, otherwise it only calculates the healing and citadel bits. Danger values can also be calculated assuming that the unit will get one extra turn of fortification and/or that the unit will attack a target plot before the turn is over, in which case the outcome of that combat event may significantly alter the final danger value (eg. if an attacker would be killed or a city would be captured). Civilian units are handled differently in that if the civilian could be captured on the tile, only the "MoveOnly" CvUnit* vector is checked, and if even a single valid unit is found, the function instantly returns MAX_INT unless the tile also contains a city/unit defender whose current health is greater than the danger value of the plot for that defender. Air units are also a weird case, both as victims and as attackers. As attackers ie. when their damage is added into a danger values, their estimated dealt damage is calculated assuming they are intercepted by the Nth best interceptor, where N starts out at some number (0 by default) and increases with each air unit processed. As victims, their danger values are checked depending on the action selected by flags in GetDanger()'s arguments: the default action basically treats them like capturable civilians who can heal, while actions concerning Intercept, Air Sweep, and Bombard add the amount of damage the unit would take in the worst case scenario from that action in addition to the danger generated from the default action.

Besides the obvious performance issues, there are still plenty of holes in my current implementation. For one, only one CvPlot* is checked for citadel damage, so if a tile is in range of multiple citadels, things get inaccurate. Units who can attack multiple times are not supported properly. Units who would heal and/or take damage before the beginning of their turn are definitely not supported properly (the only thing supported is damage dealt by the victim unit). Enemy units who change tiles in the middle of the current player's turn (from a withdraw or a Winged Hussar forced retreat) don't update DangerPlots. Strength modifiers brought on by non-damage effects between the beginning of the current player's turn and the beginning of the enemy player's turn (eg. happiness changes, strategic resource shortages, influence levels) are not supported. There is no way of "remembering" units in fog of war who did not attack a currently visible tile in the previous turn. The overall system isn't very portable, either: any changes made to the combat mechanics in CvUnitCombat need to be manually coded into the DangerPlots system. Basically, the new DangerPlots system is my poor man's semi-implementation of world state simulation that is restricted to combat damage.
 
Back
Top Bottom