Proposal: Spell System

Terkhen

King
Joined
Aug 1, 2011
Messages
917
Location
Granada
One of the things I would love to see in More Naval AI is an improved usage of spells by the AI. I believe that the current spell system obscures spell effects so much that teaching the AI about spells in a generic way would be too complicated as they are implemented now. Because of this, I think that a rework of the spell system should be considered and I'm turning my ideas into a proposal. As I'm not entirely certain if this proposal will really be helpful from an AI point of view, I'm not sure if it will be deemed as something useful. Even if it would work, it may certainly be possible that the amount of work it implies (not only the reimplementation of the spell system, but also the changes to how the AI values spells) may also be considered as too much for the benefit it provides.

The full proposal along with XML examples can be checked in the link below. I'll be awaiting your feedback :)

https://bitbucket.org/Terkhen/extramodmod/wiki/ProposalSpellSystem
 
stupid question:
you propose
PromotionAndPrereqs: Section with the list of promotions that a unit must have in order to be able to cast this spell. This section may have up to NUM_SPELL_PROMOTIONS Promotion sections, where NUM_SPELL_PREREQ_PROMOTIONS is a new global constant defined in GlobalDefinesAlt (set to 2 in More Naval AI).
PromotionNotAndPrereqs: Section with the list of promotions that a unit must not have in order to be able to cast this spell. This section may have up to NUM_SPELL_PROMOTIONS Promotion sections, where NUM_SPELL_PREREQ_PROMOTIONS is a new global constant defined in GlobalDefinesAlt (set to 2 in More Naval AI).
UnitOrPrereqs: Section with a list of unit types. The caster must be of one of them in order to cast the spell. This section may have up to NUM_SPELL_PREREQ_UNITS UnitClass sections, where NUM_SPELL_PREREQ_UNITS is a new global constant defined in GlobalDefinesAlt.
UnitClassOrPrereqs: Section with a list of unit classes. The caster must have one of them in order to cast the spell. This section may have up to NUM_SPELL_PREREQ_UNITS UnitClass sections, where NUM_SPELL_PREREQ_UNITS is a new global constant defined in GlobalDefinesAlt.
UnitCombatOrPrereqs: Section with a list of unit combats. The caster must have one of them in order to cast the spell. This section may have up to NUM_SPELL_PREREQ_UNITS UnitCombat sections, where NUM_SPELL_PREREQ_UNITS is a new global constant defined in GlobalDefinesAlt.
UnitCombatsNotAndPrereqs: Section with a list of unit combats. The caster must not have any of them in order to cast the spell. This section may have up to NUM_SPELL_PREREQ_UNITS UnitCombat sections, where NUM_SPELL_PREREQ_UNITS is a new global constant defined in GlobalDefinesAlt.
UnitInStackPrereq: This UnitType must be present in the caster's stack in order to cast the spell.
BuildingPrereq: This building must be present in the caster's tile.
I suppose that most of those tags are at the moment "AND"... and that OR nor NAND component are your propositions that do not exist yet?

further, having Building as buildingOrPrereq might also be good.... as in "this building OR that building"... and not "this promotion OR that building...."
bAllowAI: Optional tag (will assume 1 if not present). When set to 1, AI players cannot cast the spell.
that true or bAllowAI .... ALLOWS the AI to use it ?

EDIT: I'm for your proposition... but mainly for the modding aspect more than the AI aspect (especially as I have no idea on how to evaluate AI :D)
 
Wow.
Are you aware that there was an attempt on XMLising the terraform spells by red-key?

Comments:
bAllowAI: "When set to 1, AI players cannot cast the spell." should probably be 0. Also, I'm not sure about 1 as default value. I'd see if I can re-word all tags with default 1 to have default 0. It's just more intuitive. Of course only makes sense if you really do that for all tags.
PromotionAndPrereqs (etc.): I don't like that restriction NUM_SPELL_PREREQ_PROMOTIONS, like it is often done in the SDK. I made a function for BarbsPlus (CvXMLLoadUtility::SetVariableList()) that reads a list (not a fake map with <bEnabled> or the like, that is actually only a list) but stores it as an bool array (like the fake maps are stored). This way it needs more memory and performance would be probably worse for promotions (since you have to loop every promotion to find one the unit has/has not and is in the tag), but better for units and unitcombat (since you already know the unit or unitcombat type and just have to access the array at the unit/unitcombat's index). The XML syntax would be the same, except that you can have any number of prereqs. Anyway, your proposal is certainly better than PromotionPrereq1 etc.
XXXNotAndPrereqs: I find that wording a bit confusing. You obviously mean ((NOT a) AND (NOT b)). "NotAnd" could also mean (NOT (a AND b)) respectively NAND (although the first admittedly makes more sense). XXXAndNotPrereqs would be not much better in my opinion. I'd go with something like XXXAndBlocks, replacing "Blocks" with a better word that means the opposite of "Prereqs" ("Blocks" sounds like the spell is blocking something).
bAllowAuto: there was an autocasting feature in FfH, which is enabled in RifE AFAIK. I once gave it a try, to see if it's easy to re-enable, but wasn't able to do so (I'm not really sure how it worked, ask Gekko, he requested it numerous times :)).

I really like Affected area, together with python and tags like <TerrainOrPrereqs> you could do cool things very elegantly.
PyTargetTiles: Why should the AI not be able to get the exact tiles directly before casting a spell (save randomness)?
Maybe a <bRandom> tag could be added for f.e. creating different units on random nearby tiles; all effects would share the same random tile this way (my new tag suggestions are only brainstorming and should only be included if deemed useful).

PlotTypeOrPrereqs, PlotTypeOr"Blocks", bRiver could be additional tags for effects (thinking of Magisters implementation of Tsunami which IIRC affects plots near rivers, but not plots on hills).

bCausesWar maybe belongs to effects (ENTITY_UNIT only)

iDamage: It's "about X% damage" in vanilla, I think between (1/2)*X and (3/2)*X.
AddPromotion: Specify "Not possible" :). Has already this promotion, or this AND can get this promotion (prereq promotions, unitcombat...). I think the latter should be an optional tag at most.
ConvertUnitType: I don't understand the world unit part.

Isn't there a <iDuration> tag missing in ENTITY_TILE? This way you'd probably not need bPermanentUnitCreate.

SPELL_TAKE_CROWN_OF_AKHARIEN_BUILDING: doesn't bInCityOnly imply bHasCasted?
SPELL_BLAZE: Why don't you use bFlammableFeature?

I think it may be difficult to make the AI smart about certain effects that occur after each other, like:
Code:
<SpellEffect>
    <TargetEntity>ENTITY_TILE</TargetEntity>
    <BonusOrPrereqs>
        <Bonus>BONUS_CORN</Bonus>
    </BonusOrPrereqs>
    <ConvertBonus>BONUS_WHEAT</ConvertBonus>
    <bNotRequired>1</bNotRequired>
</SpellEffect>
<SpellEffect>
    <TargetEntity>ENTITY_TILE</TargetEntity>
    <BonusOrPrereqs>
        <Bonus>BONUS_WHEAT</Bonus>
    </BonusOrPrereqs>
    <ConvertBonus>BONUS_RICE</ConvertBonus>
    <bNotRequired>1</bNotRequired>
</SpellEffect>
(Dumb example.)
I think there is currently no way for the civ AI to efficiently "simulate" outcomes of actions, as humans do (1. what is the new situation after I do this? 2. Is the new situation better than the old one?). I think given the variety of effects possible, this might become quite complex.

For Escape, I'm not sure if extra AI tags would make sense (especially they should not only be used for one spell):
<iMinDanger> - I think there is a concept of plot danger in the AI
<iMaxGroupSize> - If the unit is well protected, it shouldn't escape
...
Well, escape is probably a pretty difficult spell anyway.

Great work at all. This appears to be very well thought out. I'm willing to help if this is implemented.
 
actually
"bCausesWar " is necessary out of "ENTITY_UNIT" as you may need the effect if you destroy improvements (using tsunami for example), or do "hostile terraforming" if you cast "snowfall" and transform grass into snow... or scorch plains into desert

@Therken: for vitalize, you complicated things (within the version of your proposition) : using "bFinal" that you proposed, you could have the order of the terraforming actions be random instead of "calculated". indeed, even if plain-->grass is positioned after desert--> plain, if there is a bfinal in the desert-plain effect, the spell would close... and you would only have desert--> plain and not desert-->grass (in the same way that fertility doesn't do cron-->wheat-->rice in one go.
(but that's a detail)
 
I suppose that most of those tags are at the moment "AND"... and that OR nor NAND component are your propositions that do not exist yet?

further, having Building as buildingOrPrereq might also be good.... as in "this building OR that building"... and not "this promotion OR that building...."

I don't understand the question. Each group of tags, for example PromotionAndPrereqs, is a prerrequisite by itself and is not related to other "Or" or "And" or Not" groups. I need to rethink the "Building" and "PromotionInGroup" caster requirements. I believe that they could be turned into spell effect only requirements for all spells that use them.

I also think that all other caster requirements which require the caster to be in a certain position could be changed into spell effect requirements. They are costly to calculate, and having static-only caster requirements would allow to introduce a cache of "spells I can cast" in units that would only be recalculated after the unit or the civilization changes in a way that could affect spell casting (unit gets new promotion, player gets new technology and so on). This would make spell prerequisite calculation way faster. This may cause certain spells to behave slightly differently, though.

that true or bAllowAI .... ALLOWS the AI to use it ?

It was a mistake. Thank you, it has been fixed.

EDIT: I'm for your proposition... but mainly for the modding aspect more than the AI aspect (especially as I have no idea on how to evaluate AI :D)

Although I tried to keep it as less annoying as possible for modding and to improve the spell system a bit in the rough edges when possible, I don't think that this proposal is specially helpful for modders. It forces modders to reimplement all spells, to translate part of their existing python spell code into XML in order to benefit from the AI improvements, and to adapt the rest because the new spell system uses different python callbacks. Anything that can be done with the XML schema of the proposal, and a lot more, can already be done in python with current More Naval AI, at the cost of the AI not understanding it. The proposal assumes that things that the AI would not be able to understand should be done in python instead of in XML tags.

@Therken: for vitalize, you complicated things (within the version of your proposition) : using "bFinal" that you proposed, you could have the order of the terraforming actions be random instead of "calculated". indeed, even if plain-->grass is positioned after desert--> plain, if there is a bfinal in the desert-plain effect, the spell would close... and you would only have desert--> plain and not desert-->grass (in the same way that fertility doesn't do cron-->wheat-->rice in one go.

bFinal complicates the logic of spell effect evaluation and should be avoided if possible. It is only intended to break loops, which Fertility does not have. In Fertility, without bFinal, it would be impossible to code the bonus switching loops. The only spell I can think that would require bFinal is Fertility itself, which is not even a vanilla spell. Because of this, I wouldn't mind removing Fertility from ExtraModMod or changing it (check my next post for details) in order to remove bFinal from the proposal and simplify the spell effect evaluation logic as much as possible.
 
Are you aware that there was an attempt on XMLising the terraform spells by red-key?

I wasn't aware of this. I wasn't able to find any information after a few searches either. Could you tell me where to get more information about this?

bAllowAI: "When set to 1, AI players cannot cast the spell." should probably be 0. Also, I'm not sure about 1 as default value. I'd see if I can re-word all tags with default 1 to have default 0. It's just more intuitive. Of course only makes sense if you really do that for all tags.

Yes, it was meant to be the inverse. I think that it is the only boolean tag which has a value of 1 as default.

PromotionAndPrereqs (etc.): ...

Wow, I wasn't aware of the SetVariableList method. Sounds quite useful :)

I agree, all groups that do not need a loop such as Unit, UnitCombat, Terrain, Feature, Improvement and Bonus should use SetVariableList.

The SetVariableList method and the Promotion problem gave me an idea. A new data structure, aimed specifically at storing prerequisites, could be created for spells, which would also be aimed at checking them faster. CvConditionArray would be initialized with a maximum size (NUM_MAX_PROMOTIONS in this example). For each number between 0 and its maximum size (all promotions in the game), CvConditionArray can store -1 (you MUST NOT have this promotion), 0 (neutral) and 1 (you MUST have this promotion).

Internally, CvConditionArray would be using std::map<int, bool> to implement a sparse array in which having "false" stored for a certain entity means -1 and having "true" means 1. Being neutral to the entity means 0. CvConditionArray would allow to iterate through values which are not 0 in a fast way. In order to check if a caster meets the promotion requirements of a spell, the game would only need to iterate through the CvConditionArray and check if the caster has the promotion (in case of getting 1) or not (in case of getting -1).

Since using CvConditionArray would add a memory consumption overhead, it would not be worth for conditions that have a small number of elements (such as UnitCombat, Terrain, Feature or Improvement) but it could also be used to reduce memory consumption for unit or building spell prerrequisites.

I want to do more performance analysis before designing the DLL changes, but the checks I did a year ago demonstrated that the game lost a noticeable amount of time each turn just checking spell prerrequisites (about 10%, I think). This means that all changes of spell prerequisites in the DLL should be made with special care.

Since now I believe that we can avoid using the NUM_SPELL_ conditions, I have removed all of them except NUM_SPELL_EFFECTS from the proposal.

XXXNotAndPrereqs: I find that wording a bit confusing. You obviously mean ((NOT a) AND (NOT b)). "NotAnd" could also mean (NOT (a AND b)) respectively NAND (although the first admittedly makes more sense). XXXAndNotPrereqs would be not much better in my opinion. I'd go with something like XXXAndBlocks, replacing "Blocks" with a better word that means the opposite of "Prereqs" ("Blocks" sounds like the spell is blocking something).

I agree, they have been changed to "AndBlocks" for now, until we get a better suggestion.

PyTargetTiles: Why should the AI not be able to get the exact tiles directly before casting a spell (save randomness)?

That was poorly worded, I meant that the AI will not be able to get information about how to place its units in order to use the spell better. It gets a list of tiles, but it cannot make any assumptions about where those tiles will be.

Maybe a <bRandom> tag could be added for f.e. creating different units on random nearby tiles; all effects would share the same random tile this way

I think that randomization should be avoided when determining tiles. Otherwise the AI would not be able to get a clear list of affected entities in order to evaluate what will happen. I kept random chances for effects because the AI could always vary its weight according to the probability (for example: since this unit has a 25% chance of getting this bad promotion, I'll give this effect 25% of its normal weight).

PlotTypeOrPrereqs, PlotTypeOr"Blocks", bRiver could be additional tags for effects (thinking of Magisters implementation of Tsunami which IIRC affects plots near rivers, but not plots on hills).

I agree, and they could be used for vanilla spells like the Khazad world spell. I have added them to the proposal.

bCausesWar maybe belongs to effects (ENTITY_UNIT only)

I agree, bCausesWar should be defined on a per-effect basis. But spells like Tsunami could still cause war if they destroy an improvement in the territory of other civilizations, so it won't be entity specific.

iDamage: It's "about X% damage" in vanilla, I think beween (1/2)*X and (3/2)*X.

I have added a ToDo in order to clarify how it works.

AddPromotion: Specify "Not possible" :). Has already this promotion, or this AND can get this promotion (prereq promotions, unitcombat...). I think the latter should be an optional tag at most.

Good point. I was confused about how the existing AddPromotionType1 tags worked. I have changed it to work as you mention.

ConvertUnitType: I don't understand the world unit part.

I reworded it and added an example:

ConvertUnitType: Unit type to which affected units will be converted. National Unit and World Unit limits will be enforced (it is not possible for a player to get more than four Phalanxes, or get a Bambur when another player already has one).

Isn't there a <iDuration> tag missing in ENTITY_TILE? This way you'd probably not need bPermanentUnitCreate.

That is a good idea. I have added it :)

SPELL_TAKE_CROWN_OF_AKHARIEN_BUILDING: doesn't bInCityOnly imply bHasCasted?

No, it only means that the spell must be cast in cities. You could create a special version of Guybrush's Sing spell that only works in cities, for example :)

Since the default value of bHasCasted with the proposal changes is 1, I have removed it from the example.

SPELL_BLAZE: Why don't you use bFlammableFeature?

It is a direct translation of the python code. Ancient Forests are (strangely) not flammable). I am assuming that this is to protect Ancient Forests against fire spread, but you can still blaze them directly to make them burn. I added bFlammableFeature anyways because most of the other smoke causing spell effects use it.

I think there is currently no way for the civ AI to efficiently "simulate" outcomes of actions, as humans do (1. what is the new situation after I do this? 2. Is the new situation better than the old one?). I think given the variety of effects possible, this might become quite complex.

My idea for terraformation spells is to get the tile value before and after simulating an application of the spell, and let the AI decide which one it wants to keep. If it prefers the new one, it will apply terraforming. Fertility is admittedly a bad example for this, since the AI would not be able to know that it can apply it multiple times. Since it is an ExtraModMod only spell the proposal does not need to take it into account, but ExtraModMod could always lock the normal Fertility from AI usage and let it use a group of AI-only Fertility spells which would do direct transformations.

Great work at all. This appears to be very well thought out. I'm willing to help if this is implemented.

Thank you very much for the review, and for the help offer! :)
 
I don't understand the question. Each group of tags, for example PromotionAndPrereqs, is a prerrequisite by itself and is not related to other "Or" or "And" or Not" groups. I need to rethink the "Building" and "PromotionInGroup" caster requirements. I believe that they could be turned into spell effect only requirements for all spells that use them.
When I said AND or OR I meant the "UnitCombatOrPrereq" or the "PromotionOrPrereq" or "ImprovementOrPrereq"... etc, which, I know, works as OR only in their group and not with the other groups... but that's what I like.
Currently I'm only finding in the xml : PromotionPrereq1 / PromotionPrereq2 / UnitCombatPrereq / ImprovementPrereq... which I assume are all AND functions :

Unless I'm wrong I can only choose 1 unitcombat ("melee" or all: no UnitCombatPrereq), but not chose "melee OR recon";
further I cannot say "need City_GarrisonI OR (city_Raider1 AND Drill2) ... I can only have the City_Raider1 AND Drill2 part.
I cannot have the same spell adressing : "Fort or Castle or Citadel or mine".
Indeed, the actual "explore lair" spell is in fact a array of a big number of spells; each allowing to explore 1 particular lair.
However, with "ImprovementOrPrereq", it would only need 1 spell.

Currently I'm cheating by creating multiple spells for each conditions:
-1 spell for melee on mine
-1 spell for recon on mine
-1 spell for units with garrison 1 on mine (thus only archer and disciple)
-1 spell for archers on Fort
-1 spell for archers on Castle (thus with 2 spells I used the fact to have different effects)
-1 spell for all units with drill2 and combat2 on citadel
-1 spell for archers with garrison 1 on citadel

it is a bit roundabout... instead of having 1 spell only as your proposed system would allow.
Although I tried to keep it as less annoying as possible for modding and to improve the spell system a bit in the rough edges when possible, I don't think that this proposal is specially helpful for modders. It forces modders to reimplement all spells, to translate part of their existing python spell code into XML in order to benefit from the AI improvements, and to adapt the rest because the new spell system uses different python callbacks. Anything that can be done with the XML schema of the proposal, and a lot more, can already be done in python with current More Naval AI, at the cost of the AI not understanding it. The proposal assumes that things that the AI would not be able to understand should be done in python instead of in XML tags.
I understand but I disagree.
It would be a bother for modders to make the transition from the actual mechanics to your proposal.
However, once that is done, it will be much easier to create new spells (you don't need a different spell/or a py function each time you have different prerequists or different results)

bFinal complicates the logic of spell effect evaluation and should be avoided if possible. It is only intended to break loops, which Fertility does not have. In Fertility, without bFinal, it would be impossible to code the bonus switching loops. The only spell I can think that would require bFinal is Fertility itself, which is not even a vanilla spell. Because of this, I wouldn't mind removing Fertility from ExtraModMod or changing it (check my next post for details) in order to remove bFinal from the proposal and simplify the spell effect evaluation logic as much as possible.
ah ok, I understand. thanks for the explanation
 
I wasn't aware of this. I wasn't able to find any information after a few searches either. Could you tell me where to get more information about this?

In the Bug reports thread, from post #252 on, there is some discussion about it. There was a mysterious crash, therefore Tholal removed all changes related to this in rev 1508.

The SetVariableList method and the Promotion problem gave me an idea. A new data structure, aimed specifically at storing prerequisites, could be created for spells, which would also be aimed at checking them faster. CvConditionArray would be initialized with a maximum size (NUM_MAX_PROMOTIONS in this example). For each number between 0 and its maximum size (all promotions in the game), CvConditionArray can store -1 (you MUST NOT have this promotion), 0 (neutral) and 1 (you MUST have this promotion).

Internally, CvConditionArray would be using std::map<int, bool> to implement a sparse array in which having "false" stored for a certain entity means -1 and having "true" means 1. Being neutral to the entity means 0. CvConditionArray would allow to iterate through values which are not 0 in a fast way. In order to check if a caster meets the promotion requirements of a spell, the game would only need to iterate through the CvConditionArray and check if the caster has the promotion (in case of getting 1) or not (in case of getting -1).

Since using CvConditionArray would add a memory consumption overhead, it would not be worth for conditions that have a small number of elements (such as UnitCombat, Terrain, Feature or Improvement) but it could also be used to reduce memory consumption for unit or building spell prerrequisites.

I want to do more performance analysis before designing the DLL changes, but the checks I did a year ago demonstrated that the game lost a noticeable amount of time each turn just checking spell prerrequisites (about 10%, I think). This means that all changes of spell prerequisites in the DLL should be made with special care.

Since now I believe that we can avoid using the NUM_SPELL_ conditions, I have removed all of them except NUM_SPELL_EFFECTS from the proposal.

Do you know if FfH has normally issues with the 3 GB of memory available? I think in C2C they have problems with that, so they not only have worry about performance but also about needed memory.

CvConditionArray sounds interesting. With NUM_MAX_PROMOTIONS, do you mean all promotions, or a constant like NUM_SPELL_ (i.e., do you want a map with dynamic size or with static size? If the latter, you could use just an int array and a bool array for that purpose).
Another way to circumvent the NUM_SPELL_ constants would be to first store everything in std::vectors (because of their dynamic size), in the meanwhile find the highest number of elements and take that as the "constant".

That was poorly worded, I meant that the AI will not be able to get information about how to place its units in order to use the spell better. It gets a list of tiles, but it cannot make any assumptions about where those tiles will be.

Ah, OK. That's true.

I think that randomization should be avoided when determining tiles. Otherwise the AI would not be able to get a clear list of affected entities in order to evaluate what will happen. I kept random chances for effects because the AI could always vary its weight according to the probability (for example: since this unit has a 25% chance of getting this bad promotion, I'll give this effect 25% of its normal weight).
Yes, sounds reasonable.
I agree, bCausesWar should be defined on a per-effect basis. But spells like Tsunami could still cause war if they destroy an improvement in the territory of other civilizations, so it won't be entity specific.

I edited my post in an external text editor and forgot to add two changes before posting. Each effect save GLOBAL is would I (finally) wanted to write.

I reworded it and added an example:

But when you convert a unit, you don't increase it's worldwide number, no?

No, it only means that the spell must be cast in cities. You could create a special version of Guybrush's Sing spell that only works in cities, for example :)

Another mistake I didn't change. I meant <bInBordersOnly>, but I just found out that means in your own borders only, so it makes sense :).

It is a direct translation of the python code. Ancient Forests are (strangely) not flammable). I am assuming that this is to protect Ancient Forests against fire spread, but you can still blaze them directly to make them burn. I added bFlammableFeature anyways because most of the other smoke causing spell effects use it.

Ah, OK.

My idea for terraformation spells is to get the tile value before and after simulating an application of the spell, and let the AI decide which one it wants to keep. If it prefers the new one, it will apply terraforming. Fertility is admittedly a bad example for this, since the AI would not be able to know that it can apply it multiple times. Since it is an ExtraModMod only spell the proposal does not need to take it into account, but ExtraModMod could always lock the normal Fertility from AI usage and let it use a group of AI-only Fertility spells which would do direct transformations.

Maybe it's simpler than I thought, and you're right, spells to complex for the AI to understand can be disabled for AI in case of doubt.
 
Haven't had a chance to look over the whole thing yet. Seems like an interesting system. A couple of quick comments.

I agree with lfgr on the iDuration tag. Looks like you agree as well.

You have to be careful with the building/equipment spells. If you set the number of created buildings to 0, I think that will allow someone to rebuild it and you could end up with multiple copies.

Red key's initial terraform stuff can be found in revision 1425. Additional changes are in r1451, r1452, r1454 and r1455

I removed or commented out the code in r1508. It should be in a branch but I couldn't figure out how to set that up (Im a programming novice). It was removed because data corruption was creeping into the game and neither Red Key nor I could located the problem.
 
Hey guys I still lurk on here. When I tried to add some array XML tags for spells so that multiple terrain conversions could be specified for a spell there was some data corruption which led to strange crashes as others said. I received no response when I posted a question about it in the SDK subforum. Please read that for more details.

I noticed that CIV4UnitSpellSchema.xml only has boolean, integer, and string tags and none of the more complicated array style tags like I was trying to add. Maybe there is a technical issue because the spell XML files did not exist in the original game. There might be limits on what you can put in custom XML files like this.
 
When I said AND or OR I meant the "UnitCombatOrPrereq" or the "PromotionOrPrereq" or "ImprovementOrPrereq"... etc, which, I know, works as OR only in their group and not with the other groups... but that's what I like.
Currently I'm only finding in the xml : PromotionPrereq1 / PromotionPrereq2 / UnitCombatPrereq / ImprovementPrereq... which I assume are all AND functions :

Yes, I think that in the current system all XML tags are "AND". I suggest that you use python in order to create a single spell with multiple conditions. For example, see reqTakeEquipmentBuilding in CvSpellInterface.py.

I understand but I disagree.
It would be a bother for modders to make the transition from the actual mechanics to your proposal.
However, once that is done, it will be much easier to create new spells (you don't need a different spell/or a py function each time you have different prerequists or different results)

Well, of course the proposal is intended to make things better after it is implemented. I hope that it is perceived like that :)

In the Bug reports thread, from post #252 on, there is some discussion about it. There was a mysterious crash, therefore Tholal removed all changes related to this in rev 1508.

Thank you for the reference to the discussion. Red Key raises an interesting point which I wasn't taking into account: terrain modification spells follow different rules.

  • Vitalize can be cast on flood plains but Spring cannot
  • When Vitalize is cast on flood plains it changes the desert to a plains and the flood plains is still there
  • Scrubs are the only terrain feature that is affected by spells (besides Spring's ability to remove flames which is clearly intended). Both Vitalize and Spring remove scrubs when terraforming a desert. Yet the previous point mentions that Vitalize leaves flood plains alone, and it is possible to Scorch a plains/forest and make it a desert/forest.

I could add another conditional tag to allow to select between "remove features which are not valid" and "keep features even if they are not valid", but IMO we should try to reduce these conditional tags as possible. As it was mentioned in that discussion, it may be simpler to just unify the behavior of terraform spells and always remove features which are not valid. Other tags such as bEnforceAddPromotionPrereqs or bImprovementUnique need a similar discussion.

Do you know if FfH has normally issues with the 3 GB of memory available? I think in C2C they have problems with that, so they not only have worry about performance but also about needed memory.

I know that mods with a lot of content run into this problem sooner than later. I don't know what is the situation with Caveman 2 Cosmos, but other mods such as Realism Invictus directly state that they don't support 32 bit operative systems. Since it happens in mods with a lot of game entities, each one of them with a lot of extra tags, I reckon that eventually the 3 GB limit would be surpassed. I don't think this would be a problem with FFH2, though. I never did actual memory tests but I don't believe I ever saw ExtraModMod going over 2 or even 1 GB. Since it has less game entities, More Naval AI should have lower memory usage.

CvConditionArray sounds interesting. With NUM_MAX_PROMOTIONS, do you mean all promotions, or a constant like NUM_SPELL_ (i.e., do you want a map with dynamic size or with static size? If the latter, you could use just an int array and a bool array for that purpose).
Another way to circumvent the NUM_SPELL_ constants would be to first store everything in std::vectors (because of their dynamic size), in the meanwhile find the highest number of elements and take that as the "constant".

I propose a dynamic size. I have added a more detailed description of this class to the end of the proposal. It includes a very rough performance analysis of how efficient it would be (my knowledge on those is a bit rusty so corrections are welcome). Memorywise it should be great for entities with a lot of different types but very few types actually assigned, such as promotions.

But when you convert a unit, you don't increase it's worldwide number, no?

I did not take into account that problem properly, yes. I rewrote the Convert and Create effects in order to take this into account and to explain them correctly.

Haven't had a chance to look over the whole thing yet. Seems like an interesting system.

There is no need to hurry. This is a proposal for the long term :)

You have to be careful with the building/equipment spells. If you set the number of created buildings to 0, I think that will allow someone to rebuild it and you could end up with multiple copies.

Oh, I see. I rewrote the building affecting tags in a way which I believe will take care of this problem.

Red key's initial terraform stuff can be found in revision 1425. Additional changes are in r1451, r1452, r1454 and r1455

Thank you for the links! Judging strictly from the XML tags, it seems like the schema is quite similar to what I have in mind.

It should be in a branch but I couldn't figure out how to set that up (Im a programming novice). It was removed because data corruption was creeping into the game and neither Red Key nor I could located the problem.

I never managed to understand how to use branches in Subversion either. It is somewhat strange having them on a separate folder of the repository, and because of that they always felt to me as "fake" branches. It is quite simpler to manage branches in Mercurial, which is the CVS I use for ExtraModMod. Mercurial even uses repository history in order to take care of merges almost automatically, which is how I manage to release versions so fast after a new More Naval AI release.

If this proposal is finally deemed useful I intend to create a branch kept in separate repository to develop it, so it does not get in the way of normal MNAI development. I would just pull from the MNAI repository periodically in order to keep the branch updated. As development progresses, beta releases of the branch could be made available in order to let testers try it. Once that the implementation is done and fully tested, it could get merged into MNAI.

Hey guys I still lurk on here. When I tried to add some array XML tags for spells so that multiple terrain conversions could be specified for a spell there was some data corruption which led to strange crashes as others said. I received no response when I posted a question about it in the SDK subforum. Please read that for more details.

It is the first time I check the XML parsing code of the DLL but I don't see anything immediately wrong in the source code. The description makes it sound as if one of the arrays wasn't properly initialized and had garbage values at some point, but I can see that the source code initializes the array. It is hard to judge only with plain code, though. In that situation I would prefer to debug the read code step by step in order to see what is happening more clearly. A for loop guarded by a #ifdef _DEBUG precompilation tag, placed after the read code that throws an assert if any of the values is out of its intended range would also allow to test it.

I noticed that CIV4UnitSpellSchema.xml only has boolean, integer, and string tags and none of the more complicated array style tags like I was trying to add. Maybe there is a technical issue because the spell XML files did not exist in the original game. There might be limits on what you can put in custom XML files like this.

lfgr created XML schemas with many nested arrays for Wilderness' SpawnInfos and SpawnPrereqInfos (used to manage unit spawns). You can check one of the files here. Checking these made me realize that the spell XML schema could be improved a lot.

By the way, did you check what I propose for terraform spells? Given that you already tackled the problem before me I'm very interested in your feedback :)

I have updated the proposal with the following changes:

Spoiler :
  • Added a detailed description of CvConditionArray at the end of the proposal.
  • It is now possible to define spell effects without effect. If a spell effect has no effect, it is considered valid as long as its prerequisites are met even if it does not affect any entities. This is used for example in Tsunami.
  • Added UnitOrPrereqs, UnitClassOrPrereqs, UnitCombatOrPrereqs, PromotionAndPrereqs and PromotionAndBlocks as spell effect requirements for ENTITY_UNITS.
  • Removed BuildingPrereq from caster prerequisites. Taken to spell effect prerequisites as BuildingOrPrereqs.
  • Removed UnitInStackPrereq from caster prerequisites. It should now be possible to code these spells without requiring this check. Added SPELL_ADD_TO_WOLF_PACK to the examples to demonstrate this.
  • Removed PromotionInStackPrereq from caster prerequisites. It should now be possible to code these spells without requiring this check. Added SPELL_TAKE_CROWN_OF_AKHARIEN_PROMOTION to the examples to demonstrate this.
  • Removed bInCityOnly from caster prerequisites. It is the same as having a spell effect for cities.
  • Removed bInBordersOnly from caster prerequisites. It is equivalent to using the bImmunePlayer, bImmuneTeam, bImmuneNeutral and bImmuneEnemy tags in spell effects.
  • Removed bAdjacentToWaterOnly. See tsunami example.
  • As a result of these changes, all caster prerequisites (except iCost) are now static. This means that they could be cached in a per unit basis. The cache of a unit would need to be emptied when the unit changes promotions, unit type or religion, if it stops or start being alive (I'm not sure if this is even possible without promotion changes) or if its level changes. The cache of all units of a player should be emptied when the owner changes his state religion, gets a new technology or approves a new council vote.
  • Added bNoCapital and bCapital as spell effect requirements for ENTITY_CITY.
  • Clarified how the National Unit, World Unit and World Wonder limits are enforced. Removed bRemoveWorldWonder. Added bCreateWorldWonder.
  • Added SPELL_DROP_CROWN_OF_AKHARIEN to show how the World Wonder limits work.
  • Added bGraphicalOnly.
 
I know that mods with a lot of content run into this problem sooner than later. I don't know what is the situation with Caveman 2 Cosmos, but other mods such as Realism Invictus directly state that they don't support 32 bit operative systems. Since it happens in mods with a lot of game entities, each one of them with a lot of extra tags, I reckon that eventually the 3 GB limit would be surpassed. I don't think this would be a problem with FFH2, though. I never did actual memory tests but I don't believe I ever saw ExtraModMod going over 2 or even 1 GB. Since it has less game entities, More Naval AI should have lower memory usage.

I don't really understand the thing that 64 bit doesn't crash but 32 bit does, but I'm sure you can't use more memory than the 32 bit limitation, so we should try to stay below 3 GB, and shouldn't value performance too high over memory.

I propose a dynamic size. I have added a more detailed description of this class to the end of the proposal. It includes a very rough performance analysis of how efficient it would be (my knowledge on those is a bit rusty so corrections are welcome). Memorywise it should be great for entities with a lot of different types but very few types actually assigned, such as promotions.

O Notation! Didn't think of that. I was thinking about a finer analysis (not that I'm an expert in these things, but O Notation is pretty rough), but it's a really good idea to find potential O(n^2) first.

However, I think the O(n^2) complexity isn't necessary (if I understood your example correctly). We just have to loop through all promotions once, check if the array says -1 or 1 on that promotions, check if the unit has it, thus O(N) (compared to O(n) with a list of required promotions in the spell).

I'm not sure if I understood CvConditionArray. I got the impression that CvConditionArray is a wrapper for std::map<int, bool>, defining additional methods, is that right?

I'll write down my thoughts on this. Again, I'm no expert and it's mostly guessing (in fact, I wouldn't have known what O Notation is three months ago). Especially the memory consumption varies greatly with different environments AFAIK, so don't take that too serious.

I'll use your definitions of N as the total number of entities of a type and n as the number of entries in a list. M is the limit defined for entities stored in array lists (such as NUM_SPELL_XXX).
I'll use t_a for the time required to access a specific entity in an array (very fast), t_v to access a specific entity in a std::vector (fast), t_lm to loop/iterate over a single entity in a std::map (fast), t_m to access a specific entity in a map (slow).

We have two cases of "entity lists" (promotion/unit/building/... lists):
1. We know the entity, and want to know if it's in the list. Examples: UnitOrPrereqs, UnitClassOrPrereqs, UnitCombatOrPrereqs
2. We don't know a specific entity, but instead have two lists of Entities to check against each other. The first "list" is implemented as a bool array with size N. Examples: TechPrereq (a player has a bool-array "list" of techs)
3. Same as 2, but we have another ("negative", could also something else) list of the entities. Examples: PromotionAndPrereqs, PromotionAndBlocks (a unit has a bool-array "list" of promotions)

We have roughly two strategies to store the spell/effect prerequisites:
A. An array of bools with size N. Direct access to an entity: t_a. Looping all entities in the list (that are true in the array) requires N single accesses, N*t_a. Size is N byte.
B. A list with size n.
  • Ba: std::vector<int>. Just a list. Access to a single element is, in the worst case, n*t_v, since we have to search all elements in an unsorted vector. Looping over a single entity is t_v, all elements is n*t_v.
  • Bb: int array, a non-dynamic list. Needs a limit M. Access to a single element is, in the worst case, (n+1)*t_a ( as we can stop iterating after we found the first "NONE"/-1 after n+1 iterations). Looping all elements is (n+1)*t_a. Size is 4*M bytes.
  • Bc: std::map<int, bool> (I assume this is CvConditionArray. It simulates two lists, a positive (true) and a negative (false) prerequisite. Access to a single element is t_m. Looping a single element is t_lm, but considering we can check two prereqs at a time it's t_lm/2, all elements is n*t_lm/2. Size is, as you stated, good for small lists, bad for big ones because of ELEMENT_OVERHEAD. We have 5 byte size because we have pairs of values (int, 4b and bool, 1b), but since we simulate two lists, we're at 2.5*n + n*ELEMENT_OVERHEAD/2 + CONTAINER_OVERHEAD/2 byte.
  • Bd: int array AND bool array, like a array of pair<int,bool> and similar to std::map. Needs a limit M (most probably higher than Bb, since two different prereqs are stored). Access to a single element is, in the worst case, ((n+1)*t_a) (we need two accesses but check two prerequisites at the time). Looping all elements is (n+1)*t_a. Size is 2.5*M bytes ((1+4)*M/2...).

So...
The obviously best solution for 1 is A. We only need direct access, and we get it at the best possible performance. Total size is f.e. numPromotions*numSpells, in MNAI about 70 KB.
Code:
for unit in units
	for spell in spells
		if( spell.isUnitOrPrereq( unit.type ) )
			OK

For 2 it's more difficult:
2A: Performance (loop): N*t_a; Size: N
2Ba: Performance (loop): n*t_v; Size: depends. AFAIK it allocates always 2^x fields at once.
2Bb: Performance (loop): (n+1)*t_a; Size: 4M; Requires M, therefore either more inelegant (constant M) or more complex (M determined at loading XML)

I did some testing and it seems accessing a vector is up to 2 times slower than accessing an array (using an vector::iterator to loop a vector is much slower). That means the vector is only less efficient if n >= N/2, which is highly unlikely. Bb is the fastest, but also most problematic solution.
Code:
// 2A
for unit in units
	for spell in spells
		for promotion in promotions
			if( spell.isPromotionOrPrereq( promotion ) and unit.hasPromotion( promotion ) )
				OK
// 2B
for unit in units
	for spell in spells
		for promotion in spell.promotionOrPrereqs
			if( unit.hasPromotion( promotion ) )
				OK

3 is much like 2 (performance and size is for one tag, so the Bc and Bd is halved):
3A: Performance (loop): N*t_a; Size: N
3Ba: Performance (loop): n*t_v; Size: depends. AFAIK it allocates always 2^x fields at once.
3Bb: Performance (loop): (n+1)*t_a; Size: 4*M; Requires limit of M
3Bc: Performance (loop): n*t_lm/2; Size: n + n*ELEMENT_OVERHEAD/2 + CONTAINER_OVERHEAD/2 (overheads for std::map)
3Bd: Performance (loop): (n+1)*t_a; Size: 2.5*M (2*M/2...); Requires limit of M

Iterating over a key-value pair of a map (reading both in a two new variables) was about 8-10 times slower than accessing an array, and about 5 times slower than accessing a vector. This would means Ba is 2.5 times faster than Bc.
What I didn't take into account is that we need only one method call for the map variant (f.e. "getPromotionAndPrereqBlock(int index)"), but two for the vector variant (get...Prereq(int index) and get...Block(int index)). The map is probably still worse than the vector, since the vector requires 2 times an array access, and what makes the vector actually slower is the method call AFAIK (since it uses an array internally), so a method call costs roughly 1 array access, but the map is 4 array accesses slower than the vector.

Again, A is only faster than Ba if we have half of all promotions in the list, which is unlikely.

Bb and Bd are clearly faster than the other options. Bb is less complex, but Bd could be faster, since it only requires one method call. Bd requires more memory (since its M is higher). EDIT/Correction: Bd could actually require less memory.

My performance measures are probably pretty inaccurate, but it seems using a map, hasn't better performance nor memory (while I really liked the idea). Using two arrays (int and bool) instead could be faster if I'm right about the method call costs.
It's also worth noting we're talking about differences of at most 1.5 microseconds (array access - vector access), so the amount of saved time (provided we use 1A, 2Bx, 3Bx) for one tag is 1.5e-6 * units * (total entities in lists of spells). Let's say most spells (200 of 270) need a promotion, so we have 0.0003 seconds saved per unit for the PromotionAndPrereqs tag. I'm not sure how many units a later game normally has, but this change would not be noticeable until you have about 1000 units (that can cast). If that is common in late games, we should try several methods and measure performance. If my calculation is correct and the spell prerequisites take 10% of turn time, I think details like these could affect performance noticeable.
If something is unclear, I'll code some examples.

EDIT: I made a mistake valuing an int 1 byte, while it's 4 bytes. This makes strategies using bools a bit better in comparison.
 
By the way, did you check what I propose for terraform spells? Given that you already tackled the problem before me I'm very interested in your feedback :)

I checked it now. I think your proposal would cover everything for terraform spells and I like it :goodjob:. I especially like how each of a spell's effects can have different requirements. This will come in very handy for spells like Spring which does not terraform flood plains or obsidian plains but can still put out fires if cast while standing on one of those features.

An overhaul of the spell XML schema is definitely needed to improve the AI and to make adding new spells or adjusting existing spells easier.
 
I don't really understand the thing that 64 bit doesn't crash but 32 bit does, but I'm sure you can't use more memory than the 32 bit limitation, so we should try to stay below 3 GB, and shouldn't value performance too high over memory.

I don't know the exact reasons either. From the Realism Invictus manual:

Spoiler :
MAF error
The memory allocation error (MAF error) is, unfortunately, the harsh reality we cannot really do anything with. This is the error inherent in the game itself; moreover, it is not even Civ4-specific. I encountered it while playing Half-Life 2 mods! Basically, this error happens due to the way 32-bit operating systems handle memory. The more memory the system needs to allocate, the more likely the dreaded Memory Allocation Failure is to occur. There is no real solution for this error on 32-bit systems, although there are certain methods that allow to postpone it. 64-bit systems and OSes are much luckier here – for BtS, no action is required at all, you will simply not run into MAF errors.

If you play our mod on 32-bit systems on larger maps, you will likely run into MAFs sooner or later. The only permanent solution for those is upgrading to 64 bit; nevertheless you can postpone those till much later in game if you choose smaller map sizes – late enough for the game to be decided at that point anyway. If you have a decent 32-bit system, there are certain methods that you can use to play without errors longer. For these, I’m directing you to CFC:

http://forums.civfanatics.com/showthread.php?t=225205

Once again, to drive the point home:

If you run a 32-bit OS, and play a big map, your game will crash, sooner or later.

I agree that keeping memory usage under 3 GB should be one of the goals.

O Notation! Didn't think of that. I was thinking about a finer analysis (not that I'm an expert in these things, but O Notation is pretty rough), but it's a really good idea to find potential O(n^2) first.

A theoretical, "quick and dirty" O Notation analysis is useful when designing an algorithm or class which needs to be very performance efficient and manages high volumes of data. Although I agree that by itself it does not calculate exactly how efficient an implementation will be, it helps in discriminating possible solutions as long as we can assume high data numbers. Since the number of promotions we have to deal with is not exactly a great number, the analysis should be only orientative. Once that the proposal is more mature, I'd prefer using actual performance analysis over theoretical stuff.

However, I think the O(n^2) complexity isn't necessary (if I understood your example correctly). We just have to loop through all promotions once, check if the array says -1 or 1 on that promotions, check if the unit has it, thus O(N) (compared to O(n) with a list of required promotions in the spell).

Yes, it can be avoided even with far simpler solutions than the current CvConditionArray proposal. For something like what CvConditionArray intends to do, we need something which is blazing fast. Any quadratic (O(n^2)) efficiency would be horrible for checking these conditions. In fact, I'd argue that quadratic efficiency is horrible for anything in the DLL except maybe XML reading code. Unless the memory usage becomes prohibitive, I believe that we should settle for nothing less than O(1) for checking single values, and O(n) for checking all values.

I'm not sure if I understood CvConditionArray. I got the impression that CvConditionArray is a wrapper for std::map<int, bool>, defining additional methods, is that right?

That's what it is, yes. The point of using it is that we get a smaller memory consumption for entity categories with a lot of entities (such as promotions) without sacrificing O(n) efficiency.

I'll write down my thoughts on this. Again, I'm no expert and it's mostly guessing (in fact, I wouldn't have known what O Notation is three months ago). Especially the memory consumption varies greatly with different environments AFAIK, so don't take that too serious.

I'll use your definitions of N as the total number of entities of a type and n as the number of entries in a list. M is the limit defined for entities stored in array lists (such as NUM_SPELL_XXX).
I'll use t_a for the time required to access a specific entity in an array (very fast), t_v to access a specific entity in a std::vector (fast), t_lm to loop/iterate over a single entity in a std::map (fast), t_m to access a specific entity in a map (slow).

I reckon that the times will be like that too, yes.

We have two cases of "entity lists" (promotion/unit/building/... lists):
1. We know the entity, and want to know if it's in the list. Examples: UnitOrPrereqs, UnitClassOrPrereqs, UnitCombatOrPrereqs
2. We don't know a specific entity, but instead have two lists of Entities to check against each other. The first "list" is implemented as a bool array with size N. Examples: TechPrereq (a player has a bool-array "list" of techs)
3. Same as 2, but we have another ("negative", could also something else) list of the entities. Examples: PromotionAndPrereqs, PromotionAndBlocks (a unit has a bool-array "list" of promotions)

If we only take efficiency into account, CvConditionArray should only be used for cases 2 and 3. For case 1, my guess would be that nothing can beat a static array performance wise (strategy A).

So...
The obviously best solution for 1 is A. We only need direct access, and we get it at the best possible performance. Total size is f.e. numPromotions*numSpells, in MNAI about 70 KB.

I agree, although it has a relatively high memory cost, that should be the optimal solution for efficiency.

I did some testing and it seems accessing a vector is up to 2 times slower than accessing an array (using an vector::iterator to loop a vector is much slower). That means the vector is only less efficient if n >= N/2, which is highly unlikely. Bb is the fastest, but also most problematic solution.

Iterating over a key-value pair of a map (reading both in a two new variables) was about 8-10 times slower than accessing an array, and about 5 times slower than accessing a vector. This would means Ba is 2.5 times faster than Bc.

If something is unclear, I'll code some examples.

It has been quite helpful and interesting :)

Knowing the implicit constants inherent to each method has been very useful to realize why CvConditionArray was wrong. I hoped that using a map would be quite similar to using a static limit like M, but with those high implicit efficiency costs the map is probably not a good idea.

I don't think that we have a prerequisite for which we need to do 1, 2 and 3 at the same time, so we don't need to adopt the same solution for all prerequisites.

After rethinking the situation, I guess that we should prioritize efficiency with little regard to memory usage on entities that have a limited and fixed number of instances in memory, such as spells (70 KB is negligible by many orders of magnitude). Memory usage should be taken into account more without ignoring efficiency for entities which can reach huge numbers in games, such as units and buildings.

I don't intend to tackle memory consumption of other entities, but for spells I think that we only have to consider the following cases:

  • Single prerequisites.
  • Or list prerequisites, which are checked against a single entity.
  • OrPrereq/AndBlocks list prerequisites, which are checked against a single entity.
  • AndPrereq/AndBlocks list prerequisites, which are checked against a list of entities.

Single prerequisites can use the usual implementation.

Or list prerequisites list prerequisites which are going to be checked against a single entity, as you mentioned, should use a boolean array with a static size of N. I wonder what would be the performance hit of having a pointer to the array instead of declaring the array directly as an attribute of the class. That would allow to save memory whenever a spell or spell effect does not use this prerequisite. I'm seeing that this is the case for most ingame arrays already.

OrPrereq/AndBlocks list prerequisites that are going to be checked against a single entity should use a single int array with a static size of N. Using an int array allows to reduce the overhead of using multiple function calls. The game should enforce via FAsserts that the same entity is not in both the OrPrereq and AndBlocks sections. As mentioned in the previous paragraph, we should consider a pointer to array.

AndPrereq/AndBlocks list prerequisites that are checked against a list of entities need a specific solution in order to be efficient. As you proposed, a variant of the Bd solution should be the best option. I'd really like to avoid having NUM_PREREQ_... static limits if possible. I propose using a wrapper class for two dynamically sized arrays that, after finishing read, would be resized to the smallest size possible, and kept that way since these conditions will not change. That should allow us to not impose any limits on what is loaded, to consume as less memory as possible and to still check only require to check n values. Here is an example in pseudocode:

Code:
class CvConditionArray {
	// List of entities with a condition
	int* m_paiEntities;
	// True indicates that the entity is required. False that it isn't.
	bool* m_paiConditions;
	// Size of the arrays. If required, it could be an unsigned short int instead but I believe 255 should be enough prerequisites for anybody.
	unsigned char m_iSize;

	CvConditionArray::CvConditionArray()
	{
		this->m_irealSize = 0;
	}

	void read(???) {
		int* entitiesTemp = new int[255];
		bool* conditionsTemp = new bool[255];
		m_iSize = 0;

		for_each_read_value {
			FAssert(m_iSize < 255)
			entitiesTemp[m_iSize] = entityId;
			conditionsTemp[m_Size] = entity_condition;
			m_iSize++;
		}

		m_paiEntities = new int[m_Size];
		m_paiConditions = new bool[m_Size];

		for (int iI = 0; iI < m_Size; iI++) {
			m_paiEntities[iI] = entitiesTemp[iI];
			m_paiConditions[iI] = conditionsTemp[iI];
		}

		SAFE_DELETE_ARRAY(entitiesTemp)
		SAFE_DELETE_ARRAY(conditionsTemp)
	}

	// In the worst case, it will check n conditions.
	bool checkCondition(int* m_pabHasEntity)
	{
		for (int iI = 0; iI < m_iSize; iI++) {
			if (m_pabHasEntity[m_paiEntities[iI]] ? !conditionsTemp[iI] : conditionsTemp[iI]) {
				return false;
			}
		}

		return true;
	}
}

I checked it now. I think your proposal would cover everything for terraform spells and I like it :goodjob:. I especially like how each of a spell's effects can have different requirements. This will come in very handy for spells like Spring which does not terraform flood plains or obsidian plains but can still put out fires if cast while standing on one of those features.

An overhaul of the spell XML schema is definitely needed to improve the AI and to make adding new spells or adjusting existing spells easier.

Thank you! :)

I tried to create a "bail out early" chain of prerequisites in order to allow keeping CvUnit::canCast as efficient as possible. After I managed to have them ordered in a [caster prerequisites] -> [must affect at least one tile] -> [loop through all spell effects checking their prerequisites and then trying to apply them], I realized that it was possible to abuse spell effects by adding a few tags which allowed to control the flow of the spell. I also think that this will be quite useful for all terraform spells.
 
I made a mistake in my previous post valuing an int 1 byte, while it's 4 bytes. This makes strategies using bools a bit better in comparison.

After rethinking the situation, I guess that we should prioritize efficiency with little regard to memory usage on entities that have a limited and fixed number of instances in memory, such as spells (70 KB is negligible by many orders of magnitude). Memory usage should be taken into account more without ignoring efficiency for entities which can reach huge numbers in games, such as units and buildings.

Agreed.

Or list prerequisites list prerequisites which are going to be checked against a single entity, as you mentioned, should use a boolean array with a static size of N.

In case you overlooked that like I did: if the spell doesn't use the prerequisite, we'd need to set all fields of the array to true (instead of false).

I wonder what would be the performance hit of having a pointer to the array instead of declaring the array directly as an attribute of the class. That would allow to save memory whenever a spell or spell effect does not use this prerequisite. I'm seeing that this is the case for most ingame arrays already.

You mean like "int* x = new int[N]"? I actually used that in my test. I'm not sure if it makes a difference if it's used in a class rather than locally, though. If we use a global default array, we should be careful not to delete it in the destructor.

OrPrereq/AndBlocks list prerequisites that are going to be checked against a single entity should use a single int array with a static size of N. Using an int array allows to reduce the overhead of using multiple function calls. The game should enforce via FAsserts that the same entity is not in both the OrPrereq and AndBlocks sections. As mentioned in the previous paragraph, we should consider a pointer to array.

Why an int array? For the function calls, i.e. the interface of the info class, we just need a bool function to indicate whether the single entity (unit/unticlass/...) is allowed or not. Internally we'd just need a single bool array. We'd have to check entities not being in both lists and the "corner" cases one or both lists being empty while loading XML.

Actually it's not even logical to use both lists at the same time. If you use OrPrereq in a spell, all entities not in the lists are blocked anyway. Technically, AndBlocks does the same as OrPrereq (for single entities), it's just shorter if you have less blocked entities than allowed (are we talking about the same thing :)? I assume you mean having both tags (in the schema) for the same entity). So we really won't need more than a bool array, like in the "or list" case.

Here is an example in pseudocode:

That looks really good. Passing the array directly should make up for the performance loss due to the extra function call. Still, I'm not sure if it's advisable to pass the real promotion array of a unit around (do you know is it possible to pass it as read-only?).
 
I made a mistake in my previous post valuing an int 1 byte, while it's 4 bytes. This makes strategies using bools a bit better in comparison.

Now that I think about it, I would rather use int for all spell prerequisites, including those that could be stored in a simple bool. This is because having a better memory alignment in these arrays by using word size data types such as int will increase performance on slower x86 CPUs and, as we already established, we should use as much memory as required in spell entities in order to make them faster. For reference, see http://lemire.me/blog/archives/2012/05/31/data-alignment-for-speed-myth-or-reality/ (for new CPUs, old CPUs are mentioned in the first comment).

Although it is quite possible that accessing misaligned memory has nearly no penalty, the processor still works with word sized data registers internally and it may be faster to just use ints instead of using booleans and forcing the processor to set the other values to zero internally. I don't know if this would be the case for sure (my argument is reaching too low level details of abstraction at this point) but what I know is that using ints will in the worst case have no penalty on x86 processors, while bool will at least have some penalty (I don't know how things would work on x64 processors using a 32 bit binary).

I have no proof (as in real performance analysis) so all of this is just an hypothesis, though. IMO our best option is to implement all of these conditional arrays in the DLL as wrapper classes in order to be able to switch their internal implementation once that everything is implemented in order to check which one is faster with proper performance analysis.

In case you overlooked that like I did: if the spell doesn't use the prerequisite, we'd need to set all fields of the array to true (instead of false).

In that case I'd rather not even initialize the array at all, keeping the pointer with a NULL value. If the pointer is NULL, there is no need to even check the array in the condition checking code, and we don't need to store memory for it either.

You mean like "int* x = new int[N]"? I actually used that in my test. I'm not sure if it makes a difference if it's used in a class rather than locally, though. If we use a global default array, we should be careful not to delete it in the destructor.

Yes, I meant that. A dynamic array can have any size we decide but at assembly level, the computer has to follow the x pointer to the place in which the int[N] is stored in memory. It's probably negligible, though. As I mentioned earlier, it may be better to just implement all of these arrays as wrapper classes in order to be able to test what works better in real games once that everything is running.

Why an int array? For the function calls, i.e. the interface of the info class, we just need a bool function to indicate whether the single entity (unit/unticlass/...) is allowed or not. Internally we'd just need a single bool array. We'd have to check entities not being in both lists and the "corner" cases one or both lists being empty while loading XML.

Actually it's not even logical to use both lists at the same time. If you use OrPrereq in a spell, all entities not in the lists are blocked anyway. Technically, AndBlocks does the same as OrPrereq (for single entities), it's just shorter if you have less blocked entities than allowed (are we talking about the same thing :)? I assume you mean having both tags (in the schema) for the same entity). So we really won't need more than a bool array, like in the "or list" case.

You are right, it is not possible to use both lists at the savegame (the proposal has been updated in order to clarify that this is not allowed). I would still use an int, though, as the check method can be identical for both sections. Again, we can just use a wrapper class and play with different implementations later.

That looks really good. Passing the array directly should make up for the performance loss due to the extra function call. Still, I'm not sure if it's advisable to pass the real promotion array of a unit around (do you know is it possible to pass it as read-only?).

You would want to switch the " int * m_pabHasEntity" parameter to "int const * const m_pabHasEntity" in the checkCondition method. That would disallow the method from both modifying any of the values of the array, and modifying the pointer to the array itself.
 
Top Bottom