Here is an attack bonus library which offers an attack bonus when a unit is activated in the presence of a complementary unit (or units). This is an implementation of the attack bonus from the Napoleon Scenario by @tootall_2012 and @Knighttime (though I implemented it without reference to their actual code).
Additions, bug reports, feature requests, etc. are all welcome.
-- ============================================================================
--
-- simpleAttackBonus(activeUnit,simpleAttackBonusTable,defaultAttackTable)-->nil
--
-- only one bonus applies
--
-- =============================================================================
--
--
--
-- defaultAttackTable[unitType.id]=integer
-- gives the attack value of the unit type without a bonus. Only necessary for
-- units that might receive a bonus, but no problem if all use it
--
--simpleAttackBonusTable[activeUnit.type.id] ={[bonusUnitType.id]=bonusNumber}
--simpleAttackBonusTable.type = string
--if simpleAttackBonusTable.type == "addbonus" then
-- add the bonusNumber to the base attack
--if simpleAttackBonusTable.type == "addpercent"
-- add bonusNumber percent to the unit's attack
-- i.e. attack 6, bonusNumber 50, new attack 9
--if simpleAttackBonusTable.type == "addfraction"
-- add the fraction of the attack value to the attack,
-- i.e. attack 6, bonusNumber 0.5, new attack 9
-- if simpleAttackBonusTable.type == "multiplypercent" then
-- multiply the unit's attack by the bonusNumber precent
-- i.e. attack 6, bonusNumber 150, new attack 9
-- if simpleAttackBonusTable.type == "multiply" then
-- multiply the unit's attack by bonusNumber
-- i.e. attack 6, bonusNumber 1.5, new attack 9
--simpleAttackBonusTable.round = "up" or "down" or "standard" or nil
-- nil means "standard"
-- "up" means a fractional attack value after a bonus is rounded up
-- "down" means a fractional attack value after a bonus is rounded down
-- "standard" means a fractional attack value is rounded down
-- if fraction part is less than 0.5, and rounded up otherwise
-- usage:
-- local function doOnActivateUnit(unit,source)
-- attackBonus.simpleAttackBonus(unit,simpleAttackBonusTable,defaultAttackTable)
-- end
-- ============================================================================
--
-- categoryAttackBonus(activeUnit,categoryAttackBonusTable,defaultAttackTable)-->nil
--
-- =============================================================================
--
--
--
-- defaultAttackTable[unitType.id]=integer
-- gives the attack value of the unit type without a bonus. Only necessary for
-- units that might receive a bonus, but no problem if all use it
--
-- A "bonusCategory" is a table
-- bonusCategory = {[bonusUnitType.id]=bonusNumber, maxBonusUnits=integer or nil, nextBonusValue = fraction or nil}
-- In a bonusCategory, the value for bonusUnitType.id tells what the base bonus for that unit type is,
-- maxBonusUnits is the maximum number of units that can provide a bonus in this category,
-- nextBonusValue tells how much to reduce the bonus for each subsequent unit,
-- e.g. nextBonusValue = 0.7, and 5 units, which each have a bonusNumber=1
-- The total bonus is 1(0.7)^0+.7+(.7)(.7)+(.7)^3+(.7)^4 = 1+.7+.49+.343+.2401=2.7731
-- (which would be rounded as specified)
-- nil means nextBonusValue=1
--
-- If a unit type is in two bonus categories for the same unit, there is no guarantee that the
-- best bonus will be achieved
--
--categoryAttackBonusTable[activeUnit.type.id] =table of bonusCategory
--categoryAttackBonusTable.type = string
--if categoryAttackBonusTable.type == "addbonus" then
-- add the bonusNumber to the base attack
--if categoryAttackBonusTable.type == "addpercent"
-- add bonusNumber percent to the unit's attack
-- i.e. attack 6, bonusNumber 50, new attack 9
-- for multiple unit bonuses, add up all the percents, then compute the bonus
-- i.e. 50% bonus and 50% bonus is 100% bonus, not 125%
-- attack 6 --> attack 12
-- nextBonusValue applied directly to bonusNumber, i.e. nextBonusValue=.5 and 2 50% bonus units become 50% + 25%
-- so attack 6 --> 10.5
--if categoryAttackBonusTable.type == "addfraction"
-- add the fraction of the attack value to the attack,
-- i.e. attack 6, bonusNumber 0.5, new attack 9
-- for multiple unit bonuses, add up the fractions, then compute the bonus
-- i.e. .5 bonus and .5 bonus is 1+.5+.5= 2x bonus, not 2.25x
-- attack 6 --> attack 12
-- nextBonusValue applied directly to bonusNumber, i.e. nextBonusValue=.5 and 2 .5 bonus units become .5+.25
-- so attack 6 --> 10.5
-- if categoryAttackBonusTable.type == "multiplypercent" then
-- multiply the unit's attack by the bonusNumber precent
-- i.e. attack 6, bonusNumber 150, new attack 9
-- bonuses are multiplied together, i.e. 150% and 150% yields 225% of original value
-- attack 6 --> attack 13.5
-- nextBonusValue applied to bonusNumber in excess of 100, i.e. nextBonusValue=.5 2 150% bonuses become 150% and 125%
-- so attack 6 --> 11.25
-- if categoryAttackBonusTable.type == "multiply" then
-- multiply the unit's attack by bonusNumber
-- i.e. attack 6, bonusNumber 1.5, new attack 9
-- bonuses are multiplied together, i.e. 1.5 and 1.5 yields 2.25x
-- attack 6 --> attack 13.5
-- nextBonusValue applied to bonusNumber in excess of 1, i.e. nextBonusValue=.5 and two 1.5 bonuses become 1.5 and 1.25
-- so attack 6 --> 11.25
--categoryAttackBonusTable.round = "up" or "down" or "standard" or nil
-- nil means "standard"
-- "up" means a fractional attack value after a bonus is rounded up
-- "down" means a fractional attack value after a bonus is rounded down
-- "standard" means a fractional attack value is rounded down
-- if fraction part is less than 0.5, and rounded up otherwise
-- If the active unit can get a bonus, go through each category and compute the bonus for that category.
-- Then, combine the bonuses for all categories to get the overall bonus, and round the result
-- plusFixed({[unitType1.id]=fixedBonus,[unitType2.id]=fixedBonus},categoryAttackBonusTable) --> void
-- changes the bonus given by unitTypeI.id to a unit to be equivalent to
-- to adding number to that unit's attack power. If the unit doesn't receive a bonus
-- from the unitType, then it won't after applying plusFixed.
-- The value of the existing bonus is ignored.
Additions, bug reports, feature requests, etc. are all welcome.
-- ============================================================================
--
-- simpleAttackBonus(activeUnit,simpleAttackBonusTable,defaultAttackTable)-->nil
--
-- only one bonus applies
--
-- =============================================================================
--
--
--
-- defaultAttackTable[unitType.id]=integer
-- gives the attack value of the unit type without a bonus. Only necessary for
-- units that might receive a bonus, but no problem if all use it
--
--simpleAttackBonusTable[activeUnit.type.id] ={[bonusUnitType.id]=bonusNumber}
--simpleAttackBonusTable.type = string
--if simpleAttackBonusTable.type == "addbonus" then
-- add the bonusNumber to the base attack
--if simpleAttackBonusTable.type == "addpercent"
-- add bonusNumber percent to the unit's attack
-- i.e. attack 6, bonusNumber 50, new attack 9
--if simpleAttackBonusTable.type == "addfraction"
-- add the fraction of the attack value to the attack,
-- i.e. attack 6, bonusNumber 0.5, new attack 9
-- if simpleAttackBonusTable.type == "multiplypercent" then
-- multiply the unit's attack by the bonusNumber precent
-- i.e. attack 6, bonusNumber 150, new attack 9
-- if simpleAttackBonusTable.type == "multiply" then
-- multiply the unit's attack by bonusNumber
-- i.e. attack 6, bonusNumber 1.5, new attack 9
--simpleAttackBonusTable.round = "up" or "down" or "standard" or nil
-- nil means "standard"
-- "up" means a fractional attack value after a bonus is rounded up
-- "down" means a fractional attack value after a bonus is rounded down
-- "standard" means a fractional attack value is rounded down
-- if fraction part is less than 0.5, and rounded up otherwise
-- usage:
-- local function doOnActivateUnit(unit,source)
-- attackBonus.simpleAttackBonus(unit,simpleAttackBonusTable,defaultAttackTable)
-- end
-- ============================================================================
--
-- categoryAttackBonus(activeUnit,categoryAttackBonusTable,defaultAttackTable)-->nil
--
-- =============================================================================
--
--
--
-- defaultAttackTable[unitType.id]=integer
-- gives the attack value of the unit type without a bonus. Only necessary for
-- units that might receive a bonus, but no problem if all use it
--
-- A "bonusCategory" is a table
-- bonusCategory = {[bonusUnitType.id]=bonusNumber, maxBonusUnits=integer or nil, nextBonusValue = fraction or nil}
-- In a bonusCategory, the value for bonusUnitType.id tells what the base bonus for that unit type is,
-- maxBonusUnits is the maximum number of units that can provide a bonus in this category,
-- nextBonusValue tells how much to reduce the bonus for each subsequent unit,
-- e.g. nextBonusValue = 0.7, and 5 units, which each have a bonusNumber=1
-- The total bonus is 1(0.7)^0+.7+(.7)(.7)+(.7)^3+(.7)^4 = 1+.7+.49+.343+.2401=2.7731
-- (which would be rounded as specified)
-- nil means nextBonusValue=1
--
-- If a unit type is in two bonus categories for the same unit, there is no guarantee that the
-- best bonus will be achieved
--
--categoryAttackBonusTable[activeUnit.type.id] =table of bonusCategory
--categoryAttackBonusTable.type = string
--if categoryAttackBonusTable.type == "addbonus" then
-- add the bonusNumber to the base attack
--if categoryAttackBonusTable.type == "addpercent"
-- add bonusNumber percent to the unit's attack
-- i.e. attack 6, bonusNumber 50, new attack 9
-- for multiple unit bonuses, add up all the percents, then compute the bonus
-- i.e. 50% bonus and 50% bonus is 100% bonus, not 125%
-- attack 6 --> attack 12
-- nextBonusValue applied directly to bonusNumber, i.e. nextBonusValue=.5 and 2 50% bonus units become 50% + 25%
-- so attack 6 --> 10.5
--if categoryAttackBonusTable.type == "addfraction"
-- add the fraction of the attack value to the attack,
-- i.e. attack 6, bonusNumber 0.5, new attack 9
-- for multiple unit bonuses, add up the fractions, then compute the bonus
-- i.e. .5 bonus and .5 bonus is 1+.5+.5= 2x bonus, not 2.25x
-- attack 6 --> attack 12
-- nextBonusValue applied directly to bonusNumber, i.e. nextBonusValue=.5 and 2 .5 bonus units become .5+.25
-- so attack 6 --> 10.5
-- if categoryAttackBonusTable.type == "multiplypercent" then
-- multiply the unit's attack by the bonusNumber precent
-- i.e. attack 6, bonusNumber 150, new attack 9
-- bonuses are multiplied together, i.e. 150% and 150% yields 225% of original value
-- attack 6 --> attack 13.5
-- nextBonusValue applied to bonusNumber in excess of 100, i.e. nextBonusValue=.5 2 150% bonuses become 150% and 125%
-- so attack 6 --> 11.25
-- if categoryAttackBonusTable.type == "multiply" then
-- multiply the unit's attack by bonusNumber
-- i.e. attack 6, bonusNumber 1.5, new attack 9
-- bonuses are multiplied together, i.e. 1.5 and 1.5 yields 2.25x
-- attack 6 --> attack 13.5
-- nextBonusValue applied to bonusNumber in excess of 1, i.e. nextBonusValue=.5 and two 1.5 bonuses become 1.5 and 1.25
-- so attack 6 --> 11.25
--categoryAttackBonusTable.round = "up" or "down" or "standard" or nil
-- nil means "standard"
-- "up" means a fractional attack value after a bonus is rounded up
-- "down" means a fractional attack value after a bonus is rounded down
-- "standard" means a fractional attack value is rounded down
-- if fraction part is less than 0.5, and rounded up otherwise
-- If the active unit can get a bonus, go through each category and compute the bonus for that category.
-- Then, combine the bonuses for all categories to get the overall bonus, and round the result
-- plusFixed({[unitType1.id]=fixedBonus,[unitType2.id]=fixedBonus},categoryAttackBonusTable) --> void
-- changes the bonus given by unitTypeI.id to a unit to be equivalent to
-- to adding number to that unit's attack power. If the unit doesn't receive a bonus
-- from the unitType, then it won't after applying plusFixed.
-- The value of the existing bonus is ignored.