Civilization II Combat Guide, v2.0 updates

Knighttime

Prince
Joined
Sep 20, 2002
Messages
364
Greetings, fellow Civ Fanatics! I hope this lengthy post regarding the Civilization II combat logic is useful and interesting to you. Any feedback, comments, or corrections are welcome -- I'll do my best to answer any questions or update this information if necessary.

INTRODUCTION

"The Complete Civilization II Combat Guide v1.1" has long been the most complete documentation available regarding the inner workings of the Civ II combat formula. With the addition of the civ.scen.onInitiateCombat() function to Lua events in TOTPP 0.16, though, scenario designers now have direct visibility to the strength and firepower of both the attacker and defender for every battle, as determined by the game after applying all combat modifiers. This means that many of the numeric values and adjustments which previously could only be determined by educated guesses and extensive testing, can now be analyzed and known for certain with far less work. This inspired me to begin working through the contents of that guide, with a goal of verifying and/or expanding it.

Along the way, though, I received some very significant help. @TheNamelessOne himself was kind enough to review my first attempt at a complete set of battle logic documentation. By examining the code within the game itself, he was able to provide some clarifications, identify rules that were never previously known in the community, and even track down a bug. Thanks to his involvement, I feel as though we can be rather confident that the documentation posted here is truly complete and accurate -- although, as TNO pointed out, it's still possible that there are additional bugs in the battle logic which haven't been discovered yet. Furthermore, I take full responsibility for any errors or omissions, which shouldn't be considered as any type of shortcoming on his part.

I would like to provide a sincere thank you to everyone who contributed to the original Civilization II Combat Guide, especially Marquis de Sodaq (a.k.a. Sodak) as the primary author. Considering the painstaking effort required to put together those rules, by running many battle simulations, they are remarkably thorough and accurate. Then I owe a huge thank you to @TheNamelessOne, not only for creating TOTPP in the first place but specifically for his detailed analysis of the rules posted below. His contributions mean that the accuracy and completeness of this documentation far exceed what I could ever have assembled on my own.

APPROACH

In the title, I described this post as "v2.0 updates" to "The Complete Civilization II Combat Guide v1.1". To clarify, though, I'm not attempting here to replace the entirety of that document, but primarily to update and expand upon sections 2 and 3, "Unit Combat Factors" and "Adjustment Factors". The contents of all other sections seem to be correct, at least reasonably so and to the best of my knowledge and understanding.

Section 2 of the Combat Guide v1.1 states that there are four basic factors considered in the calculation of a combat result, although I believe it would be more clear to refer to six separate numbers. Two of them are the remaining Hit Point values of the attacking and defending units; these are not subject to any adjustment factors, and as a result will not be mentioned further in this post.

The documentation below is therefore divided into four sections, one for each of the remaining numbers, detailing all of the adjustment factors that are possible for each. This is intentionally a slightly different organizational approach than section 3 of the Combat Guide v1.1, which is arranged by concepts (some of which affect multiple numbers). For each adjustment factor, though, I have provided a reference to the corresponding entry in the Combat Guide v1.1 if one exists, using italicized blue text.

Units with an attack strength of 99 have special "nuclear strike" behavior, and normal combat logic does not apply. (This is documented in (3)(o) of the Combat Guide.) All of the information below concerns non-nuclear battles, in which the attack strength of the attacking unit is 98 or lower.

ATTACKER STRENGTH
  • Labeled the "attackerDie" parameter in the Lua documentation for onInitiateCombat()
  • Base value is the attack strength of the attacking unit (a.k.a. "attacker")
  • This base value is multiplied by a fixed value of 8, as correctly documented in section 4 of the Combat Guide
Potential modifications are:
  1. Insufficient moves remaining. If the attacker had less than 1 MP remaining when it initiated the attack, it receives a proportionate adjustment (penalty). For example, if a unit attacked with only 1/3 MP remaining, it would receive a x0.3333 adjustment (penalty). Not previously documented in the Combat Guide, although widely understood since the game generates a popup window whenever the human player attempts this action, asking them to confirm or cancel.
  2. Veteran. If the attacker has veteran status, it receives a x1.5 adjustment (bonus). Correctly documented in (3)(a).
  3. Partisans. If the attacker is unit type 9 (Partisans in the base game) and the defender has an attack value of 0, the attacker receives a x8 adjustment (bonus). Note that this is the only situation in which the attack value of the defender is ever relevant. Correctly documented in (3)(i).
  4. Paradrop. If the attacker has completed a paradrop operation on the current turn, it receives a x1.5 adjustment (bonus). Not previously documented.
  5. Sneak attack. If the attacker is conducting a sneak attack (i.e., breaking a Cease Fire or Peace Treaty) and the defender belongs to a tribe controlled by a human player, the attacker receives a x2 adjustment (bonus). Identified in (3)(m) but precise scope and bonus amount were previously unknown.
  6. Easiest level, human attacker. If the game's difficulty level is the easiest one, "Chieftain", and the attacker belongs to a tribe controlled by a human player, the attacker receives a x2 adjustment (bonus). Not previously documented.
  7. Easy levels, human defender. If the game's difficulty level is one of the two easiest, "Chieftain" or "Warlord", and the defender belongs to a tribe controlled by a human player, the attacker receives a x0.5 adjustment (penalty). Not previously documented.
  8. Barbarian attacker vs. All of the following adjustments apply only when the attacking unit belongs to tribe 0, the barbarians.
    1. Human defender. If the defender belongs to a tribe controlled by a human player, the barbarian attacker receives an adjustment based on the game's difficulty level. The adjustment may be less than 1 (i.e. a penalty), equal to 1 (no effect), or greater than 1 (i.e. a bonus) depending on that value. The adjustments are: Chieftain x0.25, Warlord x0.5, Prince x0.75, King x1, Emperor x1.25, Deity x1.5. Documented in (3)(n), although the implication there is that this adjustment is applied regardless of the tribe of the defender, when in fact it is only applied when the defender belongs to a human player.
    2. AI defender. If the defender belongs to a tribe controlled by an AI player, the barbarian attacker receives a x0.5 adjustment (penalty). Not previously documented, but correctly noted in a separate thread.
    3. Defender's only city. If the defender is located in the only city belonging to the defender's tribe, the barbarian attacker receives a x0 adjustment (full penalty) -- in other words, its attack strength is set to 0, rendering all other adjustments irrelevant. Not previously documented, but essentially noted correctly in a separate thread.
    4. Defender's capital city. If the defender is located in a city with the Palace improvement, but this is not the defender's only city, the barbarian attacker receives a x0.5 adjustment (penalty). Not previously documented; noted as an adjustment in a separate thread but with an incorrect formula.
    5. Defender with Great Wall. If the defender's tribe controls the Great Wall wonder and it has not expired, the attacker receives a x0.5 adjustment (penalty). Not previously documented in the Combat Guide, but understood in a general sense due to the description of the Great Wall wonder, which says, "Combat strength doubled against barbarians." Technically the attack strength is halved, rather than the defense strength being doubled.
  9. Great Wall vs. barbarian defender. If the attacker's tribe controls the Great Wall wonder and it has not expired, and the defender is a barbarian, the attacker receives a x2 adjustment (bonus). Not previously documented in the Combat Guide, but understood in a general sense due to the description of the Great Wall wonder, which says, "Combat strength doubled against barbarians."

ATTACKER FIREPOWER
  • Labeled the "attackerPower" parameter in the Lua documentation for onInitiateCombat()
  • Base value is the firepower of the attacking unit (a.k.a. "attacker")
Potential modifications are:
  1. Shore bombardment. If the attacker's domain is 2 (Sea) and the defender's domain is 0 (Ground) then the attacker's firepower is set to 1 (potential penalty, if the attacker natively has a larger firepower value). Correctly documented in (3)(j).
  2. Caught in port. If the attacker's domain is 0 (Ground) or 1 (Air) and the defender's domain is 2 (Sea) and the defender is not located on an Ocean tile, then the attacker's firepower receives a x2 adjustment (bonus). Correctly documented in (3)(k).

DEFENDER STRENGTH
  • Labeled the "defenderDie" parameter in the Lua documentation for onInitiateCombat()
  • Base value is the defense strength of the defending unit (a.k.a. "defender")
  • This base value is multiplied by a fixed value of 8, as correctly documented in section 4 of the Combat Guide
Potential modifications are:
  1. Veteran. If the defender has veteran status, it receives a x1.5 adjustment (bonus). Correctly documented in (3)(a).
  2. Scrambling fighter vs. bomber. If the defender's domain is 1 (Air) and defender has the "Can attack air units (fighter)" flag and defender is located in a city, and the attacker's domain is 1 (Air) and the attacker's range is not equal to 1, then the defender receives a x4 adjustment (bonus). Correctly documented in (3)(h)(iii)(1).
  3. Scrambling fighter vs. fighter. If the defender's domain is 1 (Air) and defender has the "Can attack air units (fighter)" flag and defender is located in a city, and the attacker has the "Can attack air units (fighter)" flag, then the defender receives a x2 adjustment (bonus). Correctly documented in (3)(h)(iii)(2).
  4. Helicopter. If the defender's domain is 1 (Air) and its range is 0 (which gives it "Helicopter behavior"), and the attacker's role is 3 (Air Superiority), then the defender receives a x0.5 adjustment (penalty). Documented in (3)(h)(iii)(3), although the reference there is to an attack by a fighter; the check is actually for attackers with the "Air Superiority" role, not the "Can attack air units (fighter)" flag.
  5. The defender is eligible to receive only one of the following three adjustments, which are checked in the following order:
    1. City Walls. If the defender's domain is 0 (Ground) and the defender is located in a city that has the City Walls improvement (or currently receives the benefit of City Walls due to the Great Wall wonder), and the attacker's domain is 0 (Ground) and the attacker does not have the "Negates city walls (howitzer)" flag, then the defender receives a configurable adjustment. By default this is a x3 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "CityWallsDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(e) plus (3)(h)(i).
    2. Fortress. If the defender's domain is 0 (Ground) and the defender is located on a tile with the Fortress improvement, and the attacker's domain is 0 (Ground) or 2 (Sea) and the attacker does not have the "Negates city walls (howitzer)" flag, then the defender receives a configurable adjustment. By default this is a x2 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "FortressDefense" key. Documented, other than TOTPP support for a configurable adjustment, in (3)(c). However, the reference there excludes the fact that an attacker with the "Negates city walls (howitzer)" flag also negates the Fortress bonus, in addition to its more obvious ability to negate the City Walls bonus.
    3. Fortified. If the defender's domain is 0 (Ground) and the defender is Fortified, it receives a configurable adjustment. By default this is a x1.5 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "FortifyDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(b).
  6. Pikemen flag. If the defender has the "x2 on defense versus horse (pikemen)" flag, and the attacker's domain is 0 (Ground) and the attacking unit's hitpoints (when fully healthy) is 10 (which is entered as "1h" in Rules.txt) then:
    1. The final requirement is that the attacker has 2 movement points (MP), however there is a bug in this implementation. The game actually checks the MP that the attacking unit received at the beginning of its current turn, after the movement adjustment applied due its damage level or remaining hitpoints, rather than checking the MP of the attacking unit's type (i.e., the full MP the unit would receive when completely healthy). Thus an attacker with native 3 MP could sometimes trigger this defender bonus, if damage lowered its effective MP to 2; and an attacker with native 2 MP could sometimes fail to trigger this defender bonus, if damage lowered its effective MP below 2. TheNamelessOne has promised that TOTPP 0.17 (coming soon) will contain a fix for this bug and with that fix enabled, the implementation will then look at the MP of the attacking unit's type, making any damage previously received by the attacking unit irrelevant.
    2. If all criteria are met, the defender receives a x1.5 adjustment (bonus). Documented in (3)(h)(ii), however the requirement that the attacker has firepower equal to 1 is incorrect.
  7. AEGIS flag vs. missile. If the defender has the "x2 on defense versus air (AEGIS)" flag, and the attacker's domain is 1 (Air) and the attacker has the "Destroyed after attacking (missiles)" flag, then the defender receives a configurable adjustment. By default this is a x5 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "AegisVSMissileDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(h)(iv).
  8. AEGIS flag vs. other air. If the defender has the "x2 on defense versus air (AEGIS)" flag, and the attacker's domain is 1 (Air) and the attacker does not have the "Destroyed after attacking (missiles)" flag, then the defender receives a configurable adjustment. By default this is a x3 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "AegisVSAirDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(h)(iv).
  9. SDI Defense vs. missile. If the defender is located in a city that has the SDI Defense improvement, and the attacker's domain is 1 (Air) and the attacker has the "Destroyed after attacking (missiles)" flag, then the defender receives a configurable adjustment. By default this is a x2 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "SDIDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(l).
  10. SAM Missile Battery. If the defender is located in a city that has the SAM Missile Battery improvement, and the attacker's domain is 1 (Air), and either the attacker has the "Destroyed after attacking (missiles)" flag or the defender does not have the "Can attack air units (fighter)" flag, then the defender receives a configurable adjustment. By default this is a x2 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "SAMDefense" key. Correctly documented, other than TOTPP support for a configurable adjustment, in (3)(f) plus (3)(h)(iii)(4).
  11. Coastal Fortress. If the defender's domain is 0 (Ground) or 1 (Air) and the defender is located in a city with the "Coastal Fortress" improvement, and the attacker's domain is 2 (Sea), then the defender receives a configurable adjustment. By default this is a x2 adjustment (bonus) but TOTPP permits this to be modified in the @COSMIC2 section, using the "CoastalFortressDefense" key. Documented, other than TOTPP support for a configurable adjustment, in (3)(g). However, the reference there is to any unit defending a city, which is technically incorrect since defenders with domain 2 (Sea) are excluded.
  12. Terrain. This adjustment is the only one in which there may be two components which are added together, first, before the final result is multiplied alongside any and all other adjustments.
    1. Base terrain type. The defender receives an adjustment determined by the base terrain of the tile on which it is located. The amount of the adjustment is defined in the @TERRAIN section Rules.txt for each terrain type; it may be less than 1 (i.e. a penalty), equal to 1 (no effect), or greater than 1 (i.e. a bonus) depending on that value. Furthermore, TOTPP supports two additional configuration elements. If the defender's domain is 1 (Air) and the @COSMIC2 section contains the "TerrainDefenseForAir" key set to 0, then the adjustment is automatically equal to 1 (meaning the terrain has no effect). Similarly, if the defender's domain is 2 (Sea) and the @COSMIC2 section contains the "TerrainDefenseForSea" key set to 0, then the adjustment is automatically equal to 1 (meaning the terrain has no effect). Correctly documented in (3)(d)(i) through (iii), other than TOTPP support for suppressing the adjustment in certain situations. Also, the reference there implies that terrain factors are static and doesn't note that they are configurable for each terrain type, although this is widely known.
    2. River. If the tile on which the defender is located contains a river, the amount of the terrain adjustment is increased by 0.5 (as noted above, this is an addition that occurs as part of the calculation of the terrain adjustment, before it is multiplied with other adjustments). Note that the @COSMIC2 keys referenced in the previous point do not eliminate the river bonus. Correctly documented in (3)(d)(iv), except that a "%" sign should not be included near the end of the line.
  13. Barbarian defender. Both of the following adjustments apply only when the defender belongs to tribe 0, the barbarians.
    1. Archers. If the barbarian defender is unit type 4 (Archers in the base game), it receives a x0.5 adjustment (penalty). Documented in (3)(n), but the reference there is somewhat unclear as to the actual behavior. Also it incorrectly states that other barbarian units have normal defense values, since there is one further exception:
    2. Legion. If the barbarian defender is unit type 5 (Legion in the base game), and no human-controlled tribe has tech 39 (Iron Working in the base game), the defender receives a x0.5 adjustment (penalty). Not previously documented.

DEFENDER FIREPOWER
  • Labeled the "defenderPower" parameter in the Lua documentation for onInitiateCombat()
  • Base value is the firepower of the defending unit (a.k.a. "defender")
Potential modifications are:
  1. Helicopter. If the defender's domain is 1 (Air) and its range is 0 (which gives it "Helicopter behavior"), and the attacker's role is 3 (Air Superiority), then the defender's firepower is set to 1 (potential penalty, if the defender natively has a larger firepower value). Documented in (3)(h)(iii)(3), although the reference there is to an attack by a fighter; the check is actually for attackers with the "Air Superiority" role, not the "Can attack air units (fighter)" flag.
  2. Shore bombardment. If the attacker's domain is 2 (Sea) and the defender's domain is 0 (Ground) then the defender's firepower is set to 1 (potential penalty, if the defender natively has a larger firepower value). Correctly documented in (3)(j).
  3. Caught in port. If the attacker's domain is 0 (Ground) or 1 (Air) and the defender's domain is 2 (Sea) and the defender is not located on an Ocean tile, then the defender's firepower is set to 1 (potential penalty, if the defender natively has a larger firepower value). Correctly documented in (3)(k).
  4. Submarine flag. If the defender has the "Submarine advantages/disadvantages" flag, then its firepower is set to 1 (potential penalty, if the defender natively has a larger firepower value). Not previously documented, but correctly noted in a separate thread.

LUA IMPLEMENTATION

All of the above information relates to the base game of Civ II, and therefore should be relevant to MGE as well as TOT (except for the fixes and additions provided by TOTPP).

Given all of the above information, though, it is now possible to write Lua code that would exactly recreate the game's native battle logic. Including this code in a scenario's events would essentially give the designer carte blanche to add, modify, or delete any adjustment factors, by carefully editing the code. However, this brings up the topics of "helper functions" and how they could or should be utilized in such code. My efforts to write such code follow the framework I developed and utilized for my Medieval Millennium mod, and work well in that context, but wouldn't run (without modifications) in a scenario using Prof. Garfield's Lua Scenario Template. Would it be most valuable to provide code that was adapted and/or fully integrated into the LST? Or instead should the goal be to provide code that is truly framework-independent, and could be copied into any set of Lua events without requiring adaptations? @Prof. Garfield what are your thoughts about codifying these rules?
 
Last edited:

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,906
Location
Ontario
Given all of the above information, it is now possible to write Lua code that would exactly recreate the game's native battle logic. Including this code in a scenario's events would essentially give the designer carte blanche to add, modify, or delete any adjustment factors, by carefully editing the code. However, this brings up the topics of "helper functions" and how they could or should be utilized in such code. My efforts to write such code follow the framework I developed and utilized for my Medieval Millennium mod, and work well in that context, but wouldn't run (without modifications) in a scenario using Prof. Garfield's Lua Scenario Template. Would it be most valuable to provide code that was adapted and/or fully integrated into the LST? Or instead should the goal be to provide code that is truly framework-independent, and could be copied into any set of Lua events without requiring adaptations? @Prof. Garfield what are your thoughts about codifying these rules?

I'm inclined to say that for readability purposes, it makes sense to use the General Library to check for unit type attributes and original improvement/unit names (there is a gen.original table with appropriate names for that purpose). Some equivalent code that you may already have would also make sense.

The General Library has no dependencies, so it can be passed around with a framework independent implementation. Any time the General Library uses a feature that needs information from somewhere else, it relies on a function to register that information, and then keeps it in a local variable. If we're going to rely on reading a rules.txt file for some information, I would generate the scenario folder path within the file with the debug.getInfo, since that is a feature that only works if the General Library registers information. It wouldn't be too difficult to cut and paste the relevant portions of the General Library into the top of an independent module, if we want to keep everything in a single file.


I can think of a few use cases for a combat calculator that we're going to want to accommodate.

1. Someone wants to know what the combat statistics for units will be without (further) Lua intervention (i.e. using the rules.txt and in game logic). They want to predict the values for initiate combat. For example, in Cold War I wrote code to simulate combat between air units (to avoid air protected stacks), and this would be useful in that context.

2. Someone wants to modify attack/defence of the combatants, and then apply the normal rules of combat without further intervention. They want values to feed to the initiateCombat event. For example, an anti-tank gun gets +3 defence versus tanks, and then apply combat as normal.

3. Someone wants to modify combat bonuses/penalties. For example, maybe they only want veteran status to be +30%. They want values to feed to the initiateCombat event.

4. Someone wants to make substantial modification to the entire combat calculation system, and feed the resultant values to the initiateCombat event.

Points 1, 2, and 3 can (and probably should) be 'black boxes' to the end user. Point 4 requires well written and documented code for the end user.

I'm thinking that to accommodate points 1, 2, and 3, we have a module with 2 copies of the code. The first version serves point 1, while the second version serves points 2 and 3.

To serve point 2, attack/defence overrides are provided as optional arguments. For point 3, we provide functions to register changes.

Code:
local combat = {}
-- code
local vetStatusBonus = 0.5
function combat.registerVetBonus(num)
    vetStatusBonus = num
end
-- code
function combat.flexibleCalculation(attacker,defender,attackOverride,defenseOverride)
    local attackValue = attackOverride or attacker.type.attack
    local defenseValue = defenceOverride or defender.type.defense
    -- code
    if attacker.veteran then
        attackValue = attackValue*vetStatusBonus
    end
    -- code
end
return combat

For point 4, I think we're going to have to ask our more recent Lua adopters how they would prefer to have their reference implementation presented. To me, it would seem to make more sense to have each bonus be its own helper function, and have the main function simply call each helper in turn. Then, specific changes need only be concerned with a particular function, and modifiers could be removed wholesale if the designer wants to do something different. However, someone else might find it easier not to have helper functions, and be able to follow everything that happens without having to backtrack to see a helper.

I'll probably have more thoughts later.
 

Knighttime

Prince
Joined
Sep 20, 2002
Messages
364
I'm inclined to say that for readability purposes, it makes sense to use the General Library to check for unit type attributes and original improvement/unit names (there is a gen.original table with appropriate names for that purpose). Some equivalent code that you may already have would also make sense.

The General Library has no dependencies, so it can be passed around with a framework independent implementation.
I fully agree that using helper functions makes the code far more readable. I wasn't 100% sure that the General Library had no dependencies and could be simply added to any framework, so thanks for clarifying that.

I can think of a few use cases for a combat calculator that we're going to want to accommodate.

1. Someone wants to know what the combat statistics for units will be without (further) Lua intervention (i.e. using the rules.txt and in game logic). They want to predict the values for initiate combat. For example, in Cold War I wrote code to simulate combat between air units (to avoid air protected stacks), and this would be useful in that context.

2. Someone wants to modify attack/defence of the combatants, and then apply the normal rules of combat without further intervention. They want values to feed to the initiateCombat event. For example, an anti-tank gun gets +3 defence versus tanks, and then apply combat as normal.

3. Someone wants to modify combat bonuses/penalties. For example, maybe they only want veteran status to be +30%. They want values to feed to the initiateCombat event.

4. Someone wants to make substantial modification to the entire combat calculation system, and feed the resultant values to the initiateCombat event.

Points 1, 2, and 3 can (and probably should) be 'black boxes' to the end user. Point 4 requires well written and documented code for the end user.

I'm thinking that to accommodate points 1, 2, and 3, we have a module with 2 copies of the code. The first version serves point 1, while the second version serves points 2 and 3.

To serve point 2, attack/defence overrides are provided as optional arguments. For point 3, we provide functions to register changes.
I'm always hesitant to go down the road of "2 copies of the code" (DRY principle) although I surely do it myself from time to time anyway. Do you think a single version of the code could serve all three purposes? Perhaps a boolean flag could enforce "native" calculations and ignore all code-registered changes, thereby filling purpose #1, or those could be checked and utilized to fulfill purposes #2 and #3.

Also I don't see a lot of difference between #2 and #3 -- the overrides in #2 are "at a higher level" (so to speak) since they would update input parameter values rather than function logic, but I don't think the mechanism would have to be substantially different as far as the end user is concerned.

For point 4, I think we're going to have to ask our more recent Lua adopters how they would prefer to have their reference implementation presented. To me, it would seem to make more sense to have each bonus be its own helper function, and have the main function simply call each helper in turn. Then, specific changes need only be concerned with a particular function, and modifiers could be removed wholesale if the designer wants to do something different. However, someone else might find it easier not to have helper functions, and be able to follow everything that happens without having to backtrack to see a helper.
I think if the helper functions are scoped very small (checking unit type flags etc.) and basically just serve to replace magic numbers with readable text, they would be beneficial to everyone.

That being said, I agree that #4 can wait until more Lua adopters are ready to dive into this and can express preferences or share concerns. I don't think that needs to block the creation of Lua code to handle points #1 - #3.

Something else I'd like to try is a system where you can override the selected defender of a tile. Basically, you could "select" a different unit to defend the tile, and manually assign damage to either the attacker or selected defender, perhaps showing a text box to advise the player of what is happening. That would be almost as good as an event that explicitly allowed selecting the unit to defend a tile.
Did you notice that the Lua Function Reference was recently updated to contain a new trigger coming in 0.17 named onChooseDefender()? :) So that's actually one more requirement I'd like to throw out there, as far as the Lua implementation of combat logic: it has to be able to be called from this new trigger, as well as from onInitiateCombat(), so that you could write your own code to choose a tile's defender with full access to the way the actual battle stats will be calculated -- in other words, incorporating your points #2 and #3 and even (eventually) #4, as applicable.

I have quite a bit of this written out in Lua already, but it's not quite in share-able condition yet. I'm hesitant to go too far in fitting this into the LST framework, but in the not-too-distant future I could probably post something here that would make that task pretty straightforward for you, if that sounds OK. If you have any additional thoughts in the meantime, please let me know.
 
Last edited:

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,906
Location
Ontario
I'm always hesitant to go down the road of "2 copies of the code" (DRY principle) although I surely do it myself from time to time anyway. Do you think a single version of the code could serve all three purposes? Perhaps a boolean flag could enforce "native" calculations and ignore all code-registered changes, thereby filling purpose #1, or those could be checked and utilized to fulfill purposes #2 and #3.

One function with a boolean flag makes sense. If we end up wanting two separate functions for the end user, we can just write a couple wrapper functions.

Did you notice that the Lua Function Reference was recently updated to contain a new trigger coming in 0.17 named onChooseDefender()? :) So that's actually one more requirement I'd like to throw out there, as far as the Lua implementation of combat logic: it has to be able to be called from this new trigger, as well as from onInitiateCombat(), so that you could write your own code to choose a tile's defender with full access to the way the actual battle stats will be calculated -- in other words, incorporating your points #2 and #3 and even (eventually) #4, as applicable.

I hadn't noticed, but since you had mentioned working with TNO and a v0.17, I was hoping that would be included (it was in the 'wish list' I submitted to the TOTPP thread a few months ago). I agree the code should be written to be usable by onChooseDefender, but I had envisioned it as code that wouldn't depend on the initiateCombat information anyway.

I have quite a bit of this written out in Lua already, but it's not quite in share-able condition yet. I'm hesitant to go too far in fitting this into the LST, but I could probably post something that would make that task pretty straightforward for you, if that sounds OK. If you have any additional thoughts in the meantime, please let me know.

Don't worry about fitting it into the LST, I'll handle that.

I think if the helper functions are scoped very small (checking unit type flags etc.) and basically just serve to replace magic numbers with readable text, they would be beneficial to everyone.

Since you're writing the code, I'll defer to your judgement.
 

JPetroski

Deity
Joined
Jan 24, 2011
Messages
4,538
I'm sorry I missed this thread until now. Really I haven't had my head in Civ2 much this fall. Fascinating and important work, however!

If I'm reading it right, it would (at some point) be possible to modify these at will? Of interest to me is the scrambling fighter behavior. It makes great sense for the base game but in a scenario where fighters launch from "airfields" (such as in OTR), it's counter to what I'd want (they should have a defense penalty in such cases).

I'm quite interested at some point to really explore all these new combat possibilities that have come out. I used "ammo" in OTR to try and make it so a unit didn't have to die in each combat but it naturally meant that units wouldn't vary much in attack strength. The ability to simply dictate results of combat is a better route, and would make building SP air scenarios much easier. So, it's great to see you two plugging away.
 

minipow01

Warlord
Joined
Jan 28, 2007
Messages
106
This is very helpful information, Knighttime! I had no idea that paradropping attackers get a 50% attack bonus. This will change how I treat the Batavian Horse unit in JPetroski's scenarios, which have the paradrop ability. They are much more valuable now.

It's too bad that the pikeman flag is limited to units with exactly 2 movement points and 1 hp. I see that TheNamelessOne addressed the bug you found in the pikeman flag, but I would have preferred to see the creation of a new "horse" flag. Most scenarios these days feature horse units with > 2 movement points. Ideally, we should be able to specify what a "horse" entails. In turn, some of the older scenarios with "anti-tank" units could actually live up to their name, given that tanks almost always have > 1 hp.

If I'm reading it right, it would (at some point) be possible to modify these at will? Of interest to me is the scrambling fighter behavior. It makes great sense for the base game but in a scenario where fighters launch from "airfields" (such as in OTR), it's counter to what I'd want (they should have a defense penalty in such cases).

I think the scrambling bonus only applies to fighters defending cities, not airfields. Fighters defending airfields get no bonus at all, but perhaps I misunderstood your comment.
 

JPetroski

Deity
Joined
Jan 24, 2011
Messages
4,538
It's my fault as I wasn't clear - I use the city walls to make certain cities look like airfields, so I'd like to turn this feature off.

I think the scrambling bonus only applies to fighters defending cities, not airfields. Fighters defending airfields get no bonus at all, but perhaps I misunderstood your comment.
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,906
Location
Ontario
If I'm reading it right, it would (at some point) be possible to modify these at will? Of interest to me is the scrambling fighter behavior. It makes great sense for the base game but in a scenario where fighters launch from "airfields" (such as in OTR), it's counter to what I'd want (they should have a defense penalty in such cases).

We can already modify the attack, defence, and firepower of the combatants in the onInitiateCombat execution point (that also provides the final result of the game's calculation of attack, defence, and firepower). So right now, if you want, you can program your own combat calculator that takes into account terrain, veteran status, unit type interactions, etc.

However, if you want to use most of the standard calculations, then you will have to program them into your custom combat calculator. Knighttime is basically doing that work for you by programming the standard combat calculator in Lua. You can then start from that reference code, and make changes to remove parts you want to override.

This isn't the only way to deal with the situation if you want to remove a particular bonus. If you know for sure when the bonus does and does not apply, you can just divide away the bonus. Let's consider the fighter scramble bonus using the standard civ ii fighter. If you know when the bonus applies, you can just divide by 4 to remove it.

On initiate combat provides this information:
attacker:bomber
defender: fighter
attackerDie: 96 (12*8)
defenderDie: 96 (3*4*8)

if you detect the fighter is in a city and the attacker is a bomber, provide a new defenderDie by taking the original and dividing by 4, so that the defenderDie is 24. This will work even if the defender is veteran (division by 4 will return 36). So, you don't need to know the entire calculation, just if a particular multiplier has been applied or not.

Let me know if you need specific code for how to do this.
 

Knighttime

Prince
Joined
Sep 20, 2002
Messages
364
I'll have the code available to post soon, I promise! The way this is going to be set up is that the effect of any modifier can be adjusted with a single line of code. This adjustment can take the form of changing the degree of bonus/penalty that is applied, but you can also eliminate the effect of the modifier entirely (typically by setting the value to "1", since anything multiplied by 1 is unchanged; however a small number of modifiers need to be eliminated by setting the value to "0" or "false" -- this will be well-documented).

Changing the conditions of a modifier (i.e., the checks of what causes it to be applied) is different and more complicated. I'm not going to provide a quick way to override or alter individual conditions, because any system is going to be less flexible and more cumbersome than just writing a couple lines of code yourself. If you need to change a modifier's conditions, I think the best approach is to (a) eliminate the base modifier entirely, by setting the value to 1 (or 0 or false as appropriate); and then (b) writing a completely new modifier and set of conditions yourself, which really isn't that difficult -- you'll be able to copy from the base combat formula as a starting point.

Here's two examples:

It's too bad that the pikeman flag is limited to units with exactly 2 movement points and 1 hp. I see that TheNamelessOne addressed the bug you found in the pikeman flag, but I would have preferred to see the creation of a new "horse" flag. Most scenarios these days feature horse units with > 2 movement points. Ideally, we should be able to specify what a "horse" entails. In turn, some of the older scenarios with "anti-tank" units could actually live up to their name, given that tanks almost always have > 1 hp.
So if your events included this line:
utilCombat.setCombatModifier("dPikemenFlag", 1)
... that would take away any impact of the Pikemen flag upon combat values as calculated by the game's normal algorithm. Then you could write a couple lines of your own code to define the conditions where you want the Pikemen flag to matter. The default conditions look like this:
if attacker.type.domain == domain.ground and attacker.type.move / totpp.movementMultipliers.aggregate == 2 and attacker.type.hitpoints == 10 and gen.isBonusAgainstHorse(defender.type) then
So it would be pretty easy to copy that and change the "== 2" and "== 10" to whatever conditions you want, and then apply the degree of modifier that you want -- it could be x1.5 like the default Pikemen effect, but you could easily change that as well.

If I'm reading it right, it would (at some point) be possible to modify these at will? Of interest to me is the scrambling fighter behavior. It makes great sense for the base game but in a scenario where fighters launch from "airfields" (such as in OTR), it's counter to what I'd want (they should have a defense penalty in such cases).
... I use the city walls to make certain cities look like airfields, so I'd like to turn this feature off.
So one approach would be to do just like I described in the previous example: first turn off the base modifier entirely:
utilCombat.setCombatModifier("dScramblingFighterVsBomber", 1)
... and then write two new ones from scratch, one for when the fighter is in a "true" city and the other for when it's in an "airfield" city. But if you're not trying to delete or change any of the conditions, but rather add an additional one, instead of disabling the base modifier you could just be clever with how you set its effect:
if defender.location.city ~= nil and civ.hasImprovement(defender.location.city, gen.original.iCityWalls) then
utilCombat.setCombatModifier("dScramblingFighterVsBomber", 0.5) -- new effect, penalty
else
utilCombat.setCombatModifier("dScramblingFighterVsBomber", 4) -- base effect, bonus
end


All of the above code is subject to changes based on how Prof. Garfield integrates my upcoming module into the LST -- which will also address the issue of where to put each line of code. But hopefully you get the idea.

This isn't the only way to deal with the situation if you want to remove a particular bonus. If you know for sure when the bonus does and does not apply, you can just divide away the bonus. Let's consider the fighter scramble bonus using the standard civ ii fighter. If you know when the bonus applies, you can just divide by 4 to remove it.
Completely true. But, fair warning: when I started out down this road, trying to "divide back out" modifiers with multiple conditions and situations that sometimes overlapped and sometimes didn't, things got very complicated very quickly. (It's especially tricky when the issue involves the terrain and river bonus.) I think it's going to be much more straightforward for designers to start with the standard calculations, and either change or eliminate the effect as appropriate. Then any modifiers you write on your own are additional ones (even if they're logically replacements for ones you eliminated). It may seem a pointless distinction now, but trust me folks, you'll be glad you kept things as simple as possible when you're trying to figure out how to test every combination. :)
 
Last edited:

Knighttime

Prince
Joined
Sep 20, 2002
Messages
364
I'm attaching the Lua file containing combat logic that I've put together. @Prof. Garfield I think the next step is to integrate this into the LST, if you're willing to do so. Here's the situation:
  • You are welcome to rename this file, or functions/variables within it, in order to align with standards you're trying to follow in the LST. However, if you correct any logic errors, or add/improve functionality, I'd appreciate it if you would post details here.
  • I'm using multiple functions within generalLibrary.lua which is currently imported at the top of the file. I expect that in the LST that's already handled at a higher scope, so the import line can be removed. I only added one new helper function, knownByAnyHuman(). If you prefer, you could delete that from this file and move it into generalLibrary.lua -- I left it here because I was unsure about the possibility of wider usage, and there are no similar functions in the general library yet.
  • I'm aware that you recently added functionality to read the contents of Rules.txt into memory, but I didn't try to implement that. A cosmic2 table is defined here that shows the keys which are referenced in the combat logic (I'm aware that setting them to nil is the same as if they were never defined at all... the definitions serve as documentation for which keys are referenced.) You may be able to get rid of this local definition if you are already reading in this table. Please review the comments above it regarding timing and order of operations for initialization.
  • Hopefully by reviewing the file, the other functions and their intended usage will make sense to you. Please let me know if you have any questions!
Once the file has been loaded, the expected/supported usage would be from civ.scen.onChooseDefender() or civ.scen.onInitiateCombat(). In either case, the code should follow this sequence of steps. I'm not sure exactly how much of this will (can/should) be baked into the LST, vs. how much of this would be each designer's responsibility.

1. Make as many calls to utilCombat.setCombatModifier() as necessary, either to disable modifiers or alter their effects. These can be nested in "if/then/else" statements to provide more flexibility. Important note to designers: If you do this, don't forget the "else" portion of the code! For the next battle, you want to make sure that the modifier is reset to its normal or default value if your condition does not apply.
2. Call utilCombat.getCombatValues() and store its return values in local variables, something like this:
Code:
local attackerStrength, attackerFirepower, defenderStrength, defenderFirepower,
     attackerStrengthModifiersApplied, attackerFirepowerModifiersApplied, defenderStrengthModifiersApplied, defenderFirepowerModifiersApplied =
     utilCombat.getCombatValues(attacker, defender, isSneakAttack)
It's fine to omit the set of four "...ModifiersApplied" variables if that documentation isn't needed; these can be very useful for troubleshooting, however.
isSneakAttack is a new (in TOTPP 0.17) parameter added to civ.scen.onInitiateCombat(). At the time this trigger fires, any existing treaties have been canceled so you can't detect the situation in Lua; therefore, TNO very helpfully added this as an additional input parameter.
3. If necessary, define custom modifiers. If there are a lot of these, the complexity will probably be easiest to manage if they're organized these much like the code within utilCombat.getCombatValues() by first defining the conditions that alter attacker strength, then attacker firepower, then defender strength, then defender firepower.
4. Within civ.scen.onChooseDefender(), the final results can be used within custom code that selects a new tile defender. Within civ.scen.onInitiateCombat(), they need to be sent back to the game's battle generator via coroutine.yield().

Feedback is welcome and I'm happy to make further changes or provide additional documentation if necessary.
 

Attachments

  • utilCombat.zip
    4.9 KB · Views: 20

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,906
Location
Ontario
You are welcome to rename this file, or functions/variables within it, in order to align with standards you're trying to follow in the LST. However, if you correct any logic errors, or add/improve functionality, I'd appreciate it if you would post details here.

1. Make as many calls to utilCombat.setCombatModifier() as necessary, either to disable modifiers or alter their effects. These can be nested in "if/then/else" statements to provide more flexibility. Important note to designers: If you do this, don't forget the "else" portion of the code! For the next battle, you want to make sure that the modifier is reset to its normal or default value if your condition does not apply.

I'm intending to add an optional argument to getCombatValues to override the combatModifier for an individual function call. This seems like an easier way to modify bonuses for specific units than to use the setCombatModifier function (which makes sense for more long term changes).
 

Knighttime

Prince
Joined
Sep 20, 2002
Messages
364
I'm intending to add an optional argument to getCombatValues to override the combatModifier for an individual function call. This seems like an easier way to modify bonuses for specific units than to use the setCombatModifier function (which makes sense for more long term changes).
Is your extra parameter going to be a table of modifier keys and values, which will apply only to that function call without updating the combatModifier table? There's nothing wrong with having multiple approaches that work, so I have no objections, and I can see how it might be nice to have the flexibility of defining changes that either persist or do not.

In my last message, I implied that all calls to setCombatModifier() should go in civ.scen.onChooseDefender() or civ.scen.onInitiateCombat(), but that probably isn't the best place for modifiers that a designer wants to "set and forget" (because they'll always have the same value in every situation throughout the scenario). It sounds like that's primarily how you're envisioning that function being used, too -- so in that case, perhaps civ.scen.onScenarioLoaded() would be a better place to put those calls. Of course, you'll be able to provide much better documentation to LST users than I can, regarding the specific files and functions where each type of change ought to go.
 

Prof. Garfield

Deity
Supporter
Joined
Mar 6, 2004
Messages
3,906
Location
Ontario
Here are my changes to your code, @Knighttime . I named the main file combatCalculator.lua. The General Library is essential for the file to run. readRules.lua (which will interpret the file called rules.txt) is optional, but also requires the scenario directory to be set in the General Library. If either of these conditions is not met, a warning is printed to the console (with brief instructions), but the code should still work properly (except that default values are used instead of actual cosmic2 values).
 

Attachments

  • CombatCalculator.zip
    41.1 KB · Views: 16
Top Bottom