Proposal/Announcement of a new prereq system for MNAI

lfgr

Emperor
Joined
Feb 6, 2010
Messages
1,093
Concerning some of the tag wishes from @MagisterCultuum, I have been thinking of adapting a system I implemented for ExtraModMod's events. Before I go through with that, I thought it would be best to sketch it here and gather opinions from Magister and potentially other modders. I do this here in a new thread to avoid cluttering the MNAI-U thread.

One can observe that "Prerequisites", whether they are done in XML or python, usually are of the simple form "OBJECT must satisfy CONDITION". For example, consider the following conditions found in CIV4SpellInfos.xml:

Code:
<UnitClassPrereq>UNITCLASS_MANES</UnitClassPrereq>
   -> OBJECT: Caster, CONDITION: has class MANES
<PromotionPrereq1>PROMOTION_CHANNELING2</PromotionPrereq1>
   -> OBJECT: Caster, CONDITION: has promotion CHANNELING2
<bCasterMustBeAlive>1</bCasterMustBeAlive>
   -> OBJECT: Caster, CONDITION: is alive
<bImmuneNotAlive>1</bImmuneNotAlive>
   -> OBJECT: Affected unit, CONDITION: is alive
<bInCityOnly>1</bInCityOnly>
   -> OBJECT: Caster's plot, CONDITION: has a city
<bInBordersOnly>1</bInBordersOnly>
   -> OBJECT: Caster's plot, CONDITION: has the same owner as caster

As you can see, with exception of <bInBordersOnly>, these conditions are fairly stand-alone: they could apply to any unit respectively Plot. Indeed, the condition "must be alive" is used not only for the caster and affected unit in spells, but also in PromotionInfos, as the <bPrereqAlive> tag.

Now what the idea of my UniversalPrereq framework is to have a universal "UnitPrereq" object that either consists of a single condition, or is the negation (NOT), conjunction (AND) or disjunction (OR) of child conditions. For example, the following condition is satisfied by any Archer of Warrior that is not alive and doesn't have the Combat I promotion:

Code:
<And>
   <Or>
       <IsUnitClass>UNITCLASS_WARRIOR</IsUnitClass>
       <IsUnitClass>UNITCLASS_ARCHER</IsUnitClass>
   </Or>
   <bAlive>0</bAlive>
   <Not>
       <HasPromotion>PROMOTION_COMBAT1</HasPromotion>
   </Not>
</And>

Note that <bAlive>0</bAlive> here means that the unit must not be alive, not that we don't care whether it's alive or not, as in <bCasterMustBeAlive>0</bCasterMustBeAlive>.

Conditions for plots/players/... look similar. Now these condition objects can be used in multiple places where we want to check it agains a unit/plot/player/... For example, the following would mean a spell can be cast by alive warriors and its effects only affect archers that don't have Combat I:

Code:
...
<CasterPrereq>
   <IsUnitClass>UNITCLASS_WARRIOR</IsUnitClass>
   <bAlive>1</bAlive>
</CasterPrereq>
<TargetPrereq>
   <IsUnitClass>UNITCLASS_ARCHER</IsUnitClass>
   <Not>
       <HasPromotion>PROMOTION_COMBAT1</HasPromotion>
   </Not>
</TargetPrereq>
...

Note that inside <CasterPrereq> and <TargetPrereq>, there is an implicit <And>.

Now let's say we want to make a spell castable only in cities by units of a leader with the Financial trait. We could of course add new tags to UnitPrereqs to do this, but we might already have a <bCity> tag for PlotPrereqs and a <HasTrait> for players. For these cases, we use a special delegate tag, like this:
Code:
<CasterPrereq>
   <UnitPlotPrereq>
       <bIsCity>1</bIsCity>
   </UnitPlotPrereq>
   <UnitOwnerPrereq>
       <HasTrait>TRAIT_FINANCIAL</HasTrait>
   </UnitOwnerPrereq>
</CasterPrereq>

Here <UnitPlotPrereq> means: The unit has to be on a plot that satisfies all of my child requirements (again, we have an implicit <And>).

For any 1-to-1 relation, we could have a delegate, even something from player to another player, like <PlayerWorstEnemy>, although I doubt that specific one would be really useful.

Advantages

First, everything you can do with UniversalPrereqs (and more), you can also do in python. The former has several advantages:
  • It is much faster. Some requirements are not checked often enough to make a difference, some, however, are.
  • It is (probably) easier to use; you don't need python knowledge.
  • It is (probably) easier to maintain and it is easier to avoid bugs; most errors should come up at the start of the game, instead of only when the code is actually run.

Second, I could implement all the condition tags I need individually in the conventional way (as is done currently for <bCasterMustBeAlive> and <bImmuneNotAlive>). UniversalPrereqs has the following Advantages:
  • It is much less code and implementation work. I designed the system such that a new tag is mostly not much work, and each added tag is immediately available wherever UniversalPrereqs are used.
  • It is (probably/sometimes) faster. I won't go into specifics, but the gist ist that if you have conventional condition tags, the game has to check every single one individually, whether the tags are present in your XML, or not. With UniversalPrereqs, the game only has to check the tags which are present, although each of these checks are a bit slower than the conventional checks.

Limitations - Tag Help

Conventional condition tags usually add some text to the unit help text/tooltip. For example, the <UnitClassPrereq> tag adds "Can only be used by a ...". For UniversalPrereqs, it would be desirable to do something similar, however, this is not completely feasible due to two reasons. First, a condition can be applied to different subjects, and thus you cannot just define a single help text for each condition. For example, <bIsAlive>1</bIsAlive> should read something like "Caster must be alive" if it is a <CasterPrereq>, and "Only affects living units" if it is a <TargetPrereq>. Basically, each combination of subject and condition possibly needs its own text. Note that, if I were to add these condition tags the traditional way, I'd still have to write all these texts - but I could decide that I want a condition only for some subjects.

Secondly, using <And>, <Or> and <Not>, the conditions can be arbitrarily complicated. I could make some very generic system like:
Code:
Caster must satisfy the following conditions:
   One of the following:
       Is a Warrior
       Is an Archer
   Is not alive
   Not the following:
       Has the Combat I promotion

But that's just ugly.

One compromise would be to just ignore all tags except the top-level tags and top-level negated tags, and have texts for all those. For any other condition, a modder would have to provide their own help text.

The solution I'm currently favouring is to just keep the whole help stuff out of the DLL, and instead prove a python interface to the UniversalPrereq objects. Then each condition subject (like Caster and Target) would get it's own python callback that computes the help text. This would make the whole thing much more flexible, but also a bit more error-prone.

Other Limitations

The AI would need to be teached how to handle UniversalPrereqs. Again, the main problem is how complex these can get - in general, solving formulas like the ones possible with <And>, <Or> and <Not> is very hard. I would restrict myself to adapting the current AI to UniversalPrereqs; so, if there is some top-level <HasPromotion> in <CasterPrereq>, the AI should handle it the same as it is currently handling <PromotionPrereq1>.

Further, you cannot elegantly implement something like <bInBordersOnly>, as it basically requires both a player and a plot. In this case, we can add this as a UnitPrereq. However, suppose we have a player and some independent plot (f.e. in an event). Then we cannot express (with the current system) that the plot has to be owned by the player.

Current state

I am currently using UniversalPrereqs for EventTriggers, so until now I didn't need and thus didn't implement any help text generation or AI. As an example, I currently have the following tags:

Code:
<And>, <Or>, <Not> for all current and future subject types

For the game itself:
Code:
<UnitNotCreated> - The specified unit wasn't ever created

For players:
Code:
<IsCivilization> - Is of the specified civilization
<HasCivic> - Has the specified trait
<HasTrait> - Has the specified civic

For plots:
Code:
<bOwned> - Is owned by any player

For units:
Code:
<bAlive> - Is alive
<HasPromotion> - Has the specified promotion
<UnitPlotPrereq> - Delegate to the unit's plot

New tags that are similar to the above are usually easy to implement.

When I add UniversalPrereqs to spells, I plan to leave the old tags for now. As soon as the respective AI part is done, the in-DLL parts of the tags will be remove, f.e. <PromotionPrereq1> would just add a HasPromotion to the CasterPrereq.

I'm happy to take questions, suggestions and feedback. In any case, I unfortunately cannot give any ETA on this due to real life obligations, but it will get done eventually.
 
Top Bottom