Calculating Combat Power

Freeform discussion about anything related to modding Transcendence.
0xABCDEF
Militia Lieutenant
Posts: 110
Joined: Thu May 19, 2016 12:58 am
Location: Was destroyed by a Phobos-class dreadnought in the Eridani system
The function objGetCombatPower is not very useful. It may be able to give you a clue about how strong a Phobos-class dreadnought is and the number it returns may reflect how much you should fear the capital ship's plasma and ion DPS. However, objGetCombatPower will not tell you how what might happen if that ship were to encounter another ship that just somehow happens to be completely immune to both plasma and ion damage. It does not tell you how many Aquilas the Commonwealth would need to destroy one Phobos.

Now there is no such thing as "absolute" combat power because the complexity of all the game mechanics leaves everything up to context. High maneuverability does not always help against a ship armed with many omnidirectional weapons. Shield buster weapons do nothing extra when you face a enemy with strong armor and no shields.

The best that we can do in terms of calculating combat strength is by comparing ships to each other. In order to do this, I have created a function called shpGetMatchup. It compares two ships based on their damage output by type, total hp, and resistances by type. Although it leaves out everything besides those two factors, it is relatively simple Here is the procedure:
• We get the total hp of each ship, including all the interior compartments, armor segments and shield generators.
• We average out the damage adjustments of every armor segment and shield generator for each ship.
• For both ships, we take the maximum damage output per 180 ticks for each damage type of all the ship's non-launcher, non-linked, primary weapons. Only one weapon can be used at a time, so these values are "competitive." If a ship has two blast weapons and one of them is stronger, then that weapon's output becomes the returned value in the "blast" struct field.
• For both ships, we take the total damage output per 180 ticks for each damage type of all the ship's launcher/linked weapons. Given that all of these weapons can often be fired together, we consider their damage outputs to be "additive." If a ship has two linked ion weapons, then the returned value is both of the ion weapons' outputs added together.
• We take each value of a ship's primary-damage-output-per-type and adjust it to the other ship's resistances.
• We take each value of a ship's launcher/linked-damage-output-per-type and adjust it to the other ship's resistances. Then we add all the values together.
• We then take the minimum survival time of each ship, assuming full fire rate and 100% hit rate. We calculate the time it would take a ship to destroy the other using each primary weapon exclusively (we assume that the ships never change primary weapons and have infinite ammo) along with the launcher/linked weapons.
• If the minimum survival time for one ship is Nil, that means that the ship is either immune to the other ship's attacks or the other ship is unarmed. If both ships have Nil survival time, we have a "Definite Stalemate." If one ship has Nil survival time, the other ship gets a "Definite Win." Otherwise, we return the ratio of the first ship's survival time to the second ship's survival time.
Here is the above procedure implemented in code. Edit: Sorry, it seems that the code formatting broke. To see the original, right-click and use View Page Source then use Ctrl+F with the string "shpGetShieldHP"

Code: Select all

``````(block Nil
(setq shpGetShieldHP (lambda (ship)
(- (shpGetShieldMaxHitPoints ship) (shpGetShieldDamage ship))
))
;Struct containing average damage adjustment percentage of each damage type for all installed armor segments and shield generators on the ship
(block ((defense (objGetItems ship "asI")) (defenseCount (count defense)) result)
(enum defense theDefense
(enum dmgNameList theDamageType
(setq result ([email protected] result theDamageType (@ damageAdj theDamageType)))
)
)
)
(enum dmgNameList theDamageType
([email protected] result theDamageType (/ (@ result theDamageType) defenseCount))
)
result
)
))
;Converts average damage adjustment percentage for each type to a multiplier
(setq shpGetAverageDamageMultiplierByType (lambda (ship)
(enum dmgNameList theDamageType
([email protected] result theDamageType (/ (@ result theDamageType) 100))
)
result
)
))
(setq shpGetTotalHP (lambda (ship)
(+ (apply add (map (objGetItems ship "aI") theArmor (itmGetProperty theArmor 'hp))) (shpGetShieldHP ship))
))
;Returns a struct with the total adjusted hp for each damage type
(setq shpGetTotalHPByType (lambda (ship)
(block	(
(interiorHP (objGetProperty ship 'interiorHP))
result
)
(objEnumItems ship "asI" theDefense
(block	(
)
(enum
dmgNameList
theDamageType
(setq result
([email protected] result theDamageType
)
)
)
)
)
(enum dmgNameList theDamageType (setq result ([email protected] result theDamageType interiorHP)))
result
)
))
;Primary weapon damage is "competitive" (i.e. only one primary is used at a time)
(setq shpGetPrimaryDamageByType (lambda (ship)
(block	(
result
)
(enum
(filter (objGetItems ship "pI") thePrimary (not (itmGetProperty thePrimary 'linkedFireOptions)))
thePrimary
(block ((damageName (itmGetDamageName thePrimary)))
(setq result ([email protected] result damageName
(max
(itmGetProperty thePrimary 'damage)
(@ result damageName)
)
))
)
)
result
)
))
;Secondary weapon damage is additive (i.e. all secondary weapons are used together)
(setq shpGetNonPrimaryDamageByType (lambda (ship)
(block	(
(launcher (shpGetLauncherDamageByType ship))
result
)
(enum dmgNameList theDamageType
(setq result ([email protected] result theDamageType (+ (@ linked theDamageType) (@ launcher theDamageType))))
)
)
))
(block (result)
(enum (filter (objGetItems ship "pI") thePrimary (itmGetProperty thePrimary 'linkedFireOptions)) thePrimary
(setq result ([email protected] result (itmGetDamageName thePrimary) (itmGetProperty thePrimary 'damage)))
)
result
)
))
(setq shpGetLauncherDamageByType (lambda (ship)
(block (result)
(objEnumItems ship "lI" theLauncher
(setq result ([email protected] result (itmGetDamageName theLauncher) (itmGetProperty theLauncher 'damage)))
)
result
)
))
(setq shpGetMatchup (lambda (ship1 ship2)
(block	(
(totalHP_ship1 (shpGetTotalHP ship1))
(totalHP_ship2 (shpGetTotalHP ship2))
(averageDamageMultiplierByType_ship1 (shpGetAverageDamageMultiplierByType ship1))
(averageDamageMultiplierByType_ship2 (shpGetAverageDamageMultiplierByType ship2))

;Ships can only use one non-linked primary at any time. Therefore, primary weapons compete with each other for the shortest destruction time.
(primaryDamageByType_ship1 (shpGetPrimaryDamageByType ship1))
(primaryDamageByType_ship2 (shpGetPrimaryDamageByType ship2))

(nonPrimaryDamageByType_ship1 (shpGetNonPrimaryDamageByType ship1))
(nonPrimaryDamageByType_ship2 (shpGetNonPrimaryDamageByType ship2))

;Minimum time to destroy the ship, assuming maximum fire rate and accuracy.
minSurvivalTime_ship1
minSurvivalTime_ship2
)
(enum dmgNameList theDamageType
(block Nil
;Multiply the damage by each type with the enemy's damage multiplier by each type.
(*
(@ primaryDamageByType_ship1 theDamageType)
(@ averageDamageMultiplierByType_ship2 theDamageType)
)
)
(*
(@ primaryDamageByType_ship2 theDamageType)
(@ averageDamageMultiplierByType_ship1 theDamageType)
)
)
(*
(@ nonPrimaryDamageByType_ship1 theDamageType)
(@ averageDamageMultiplierByType_ship2 theDamageType)
)
))
(*
(@ nonPrimaryDamageByType_ship2 theDamageType)
(@ averageDamageMultiplierByType_ship1 theDamageType)
)
))
)
)
(setq minSurvivalTime_ship1 (apply2 min
(map dmgNameList 'excludeNil theDamageType
(block	(
)
(if (gr totalDamage_ship2 0)
(/ totalHP_ship1 totalDamage_ship2)
)
)
)
))
(setq minSurvivalTime_ship2 (apply2 min
(map dmgNameList 'excludeNil theDamageType
(block	(
)
(if (gr totalDamage_ship1 0)
(/ totalHP_ship2 totalDamage_ship1)
)
)
)
))
(switch
(not (or minSurvivalTime_ship1 minSurvivalTime_ship2)) ;Both ships are either unarmed or unable to deal damage to each other because they are immune to each other
"Definite Stalemate"
(not minSurvivalTime_ship1) ;If ship2 cannot deal any damage (either because it is unarmed or because ship2 is immune to ship1's damage types)
"Definite Win"
(not minSurvivalTime_ship2) ;If ship1 cannot deal any damage (either because it is unarmed or because ship2 is immune to ship2's damage types)
"Definite Loss"
(/ minSurvivalTime_ship1 minSurvivalTime_ship2)
)
)
))
(setq unvGenerateAllMatchups (lambda Nil
(block	(
result
(ships (map (typFind "s") theClass (sysCreateShip theClass Nil &svPlayer;)))
(classCount (count classes))
)
(enum ships ship1
(enum ships ship2
(setq result (cat result (subst "%1% VS %2% -&gt; %3%:1\r\n" (objGetName ship1) (objGetName ship2) (shpGetMatchup ship1 ship2))))
)
)
(enum ships theShip
(objDestroy theShip)
)
(printTo 'log result)
result
)
))
(setq unvGenerateShipMatchupTable (lambda (standard)
(block	(
result
(theStandard (sysCreateShip (or standard &scCenturion;) Nil &svPlayer;))
(theStandardName (objGetName theStandard))
)
(enum (typFind "s") theShipClass
(block	(
(theShip (sysCreateShip theShipClass Nil &svPlayer;))
(theShipName (objGetName theShip))
(matchup (shpGetMatchup theShip theStandard))
)
(setq result (cat result (subst "%1% VS %2% -&gt; %3%:1\r\n" theShipName theStandardName matchup)))
)
)
(printTo 'log result)
result
)
))
)

``````
Here is a complete ship matchup table that I generated using the code above

The ratio between the estimated minimum survival times of a ship and another ship can be useful in identifying matchups. A 1:1 ratio means that two ships are evenly matched. A 3:1 ratio means that the first ship will likely survive fighting three clones of the second ship before falling. A 0.2:1 ratio means that it would take five clones of the first ship to destroy the second ship. To determine whether a squadron of small ships would be able to take on a large ship, just add up all the matchup values for each small ship against the large ship and if the total value is greater than one, there is a good chance that the squadron will win. [I have not yet found a way to determine the matchup of two squadrons using this function.]

I believe that this algorithm is fairly effective at calculating relative combat power. What do you think?

RPC