1
1
forked from 0ad/0ad

Update/revamp my template analyzer tool.

Remove the useless "fancy" data in favor of a simpler visualization of
templates, adding an in-place tool to filter and sort (for convenience).

This was SVN commit r17331.
This commit is contained in:
wraitii 2015-11-29 19:19:20 +00:00
parent ff30dc2ba4
commit 61e5e92b14
53 changed files with 420 additions and 758 deletions

View File

@ -1,81 +0,0 @@
import xml.etree.ElementTree as ET
import os
# This script creates the RM demo map "Balance Test" with units defined as such:
Civs = ["rome","pers"]
if len (Civs) != 2:
sys.exit("You should only input two civilizations to compare")
CivBuildings = ["civil_centre", "barracks","gymnasion", "stables", "elephant_stables", "fortress", "embassy_celtic", "embassy_italiote", "embassy_iberian"]
#CivBuildings = ["civil_centre", "barracks"]
# Remote Civ templates with those strings in their name.
FilterOut = ["hero", "mecha", "support"]
############################################################
# Load Civ specific templates
# Will be loaded by loading buildings, and templates will be set the Civ building, the Civ and in general.
# Allows to avoid special units.
CivData = {};
os.chdir(os.path.realpath(__file__).replace("CreateRMTest.py","") + "../../../binaries/data/mods/public/simulation/templates/")
for Civ in Civs:
CivData[Civ] = {}
for building in CivBuildings:
bdTemplate = "./structures/" + Civ + "_" + building + ".xml"
TrainableUnits = []
if (os.path.isfile(bdTemplate)):
Template = ET.parse(bdTemplate)
if (Template.find("./ProductionQueue/Entities") != None):
TrainableUnits = Template.find("./ProductionQueue/Entities").text.replace(" ","").replace("\t","").split("\n")
# We have the templates this building can train.
for UnitFile in TrainableUnits:
breakIt = False
for filter in FilterOut:
if UnitFile.find(filter) != -1: breakIt = True
if breakIt: continue
if (os.path.isfile(UnitFile + ".xml")):
if UnitFile not in CivData[Civ]:
CivData[Civ][UnitFile] = UnitFile
print (CivData[Civs[0]])
basePath = os.path.realpath(__file__).replace("CreateRMTest.py","") + "../../maps/random/"
f = open(basePath + 'demo_testBalance.js', 'w')
f.write("RMS.LoadLibrary(\"rmgen\");\n\nlog(\"Initializing map...\");\n\nInitMap();\n\nvar fx = fractionToTiles(0.5);\nvar fz = fractionToTiles(0.5);\n\nplaceObject(fractionToTiles(0.4), fz, \"special/trigger_point_A\", 0, 0);\n\nplaceObject(fractionToTiles(0.6), fz, \"special/trigger_point_B\", 0, 0);\n\nplaceObject(fx, fz, \"special/trigger_point_C\", 0, 0);\n\n// Export map data\nExportMap();\n")
f = open(basePath + 'demo_testBalance.json', 'w')
f.write("{\n \"settings\" : {\n \"Name\" : \"Balance Demo\",\n \"Script\" : \"demo_testBalance.js\",\n \"Description\" : \"Test the unit Balance in a trigger map. Change the triggers to change the units created.\",\n \"BaseTerrain\" : [\"medit_sea_depths\"],\n \"BaseHeight\" : 30,\n \"CircularMap\" : true,\n \"Keywords\": [\"demo\"],\n \"TriggerScripts\": [\n \"scripts/TriggerHelper.js\",\n \"random/demo_testBalance_triggers.js\"\n ],\n \"XXXXXX\" : \"Optionally define other things here, like we would for a scenario\"\n }\n}\n")
f = open(basePath + 'demo_testBalance_triggers.js', 'w')
f.write("Trigger.prototype.StartAWave = function()\n{\n var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);\n var attackerEntity = [")
start = True
for Unit in CivData[Civs[0]]:
if start == False:
f.write( ",")
if start == True:
start = False
f.write("\"" + Unit + "\"")
f.write("];\n var defenderEntity = [")
start = True
for Unit in CivData[Civs[1]]:
if start == False:
f.write( ",")
if start == True:
start = False
f.write("\"" + Unit + "\"")
f.write("];\n var count = 75;\n\n var pos = Engine.QueryInterface(this.GetTriggerPoints(\"C\")[0], IID_Position).GetPosition();\n\n var cmd = {\"x\" : pos.x, \"z\" : pos.z};\n cmd.type = \"attack-walk\";\n cmd.queued = true;\n cmd.entities = [];\n\n // spawn attackers\n for (var o = 0; o < count; ++o)\n {\n var rand = Math.floor(Math.random() * attackerEntity.length);\n if (rand == attackerEntity.length) rand = attackerEntity - 1;\n var attackers = TriggerHelper.SpawnUnitsFromTriggerPoints(\"A\", attackerEntity[rand], 1, 1);\n for each (var i in attackers)\n cmd.entities.push(i[0]);\n ProcessCommand(1, cmd);\n }\n\n cmd.entities = [];\n for (var o = 0; o < count; ++o)\n {\n var rand = Math.floor(Math.random() * defenderEntity.length);\n if (rand == defenderEntity.length) rand = attackerEntity - 1;\n var defenders = TriggerHelper.SpawnUnitsFromTriggerPoints(\"B\", defenderEntity[rand], 1, 2);\n for each (var i in defenders)\n cmd.entities.push(i[0]);\n ProcessCommand(2, cmd);\n }\n\n cmpTrigger.DoAfterDelay(180000, \"StartAnEnemyWave\", {}); // The next wave will come in 2 minutes\n}\n\nTrigger.prototype.InitGame = function()\n{\n}\n\nvar cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);\ncmpTrigger.DoAfterDelay(0, \"StartAWave\", {});\n")

View File

@ -1,110 +1,29 @@
Template Analyzer.
This python tool has been written by wraitii. Its purpose is to help with balancing and "rock-paper-scissors" mechanic and provide some easy-to-read data about civilizations and units. Take its results with a grain of salt: the in-game results can vary based on micromanaging, pathfinding issues, and sheer luck. However, my testings so far have shown it's usually not too wrong, and I think that assuming two equal players the data will be somewhat accurate.
This python tool has been written by wraitii. Its purpose is to help with unit and civ balancing by allowing quick comparison between important template data.
Note that this doesn’t account for techs and building costs, or auras and things like that.
Run it using "python unitTables.py" or "pypy unitTables.py" if you have pypy installed.
The output will be located in an HTML file called "unit_summary_table.html" in this folder.
Mod makers that would like to compare your units with vanilla's: read "Customizing unitTables" below.
The script gives 3 informations:
-A comparison table of generic templates.
-A comparison table of civilization units (it shows the differences with the generic templates)
-A comparison of civilization rosters.
########################################################################
########################################################################
1. unitTables.py
The script can be customized to change the units that are considered, since loading all units make sit slightly unreadable.
By default it loads all citizen soldiers and all champions.
This is the main script, the one which returns the comparison tables. It has 6 kinds of output, and many parameters.
------------------------------------------------------------
- Unit Tables -
Those are shown for generic templates and can be activated for civs (see below). It basically lists some unit stats so you don't have to check the templates.
To change this, change the "LoadTemplatesIfParent" variable.
You can also consider only some civilizations.
You may also filter some templates based on their name, if you want to remove specific templates.
------------------------------------------------------------
- Unit Comparison Matrix -
Those are the two side-by-side big tables. The left one uses a Green-Yellow-Red color Scheme, the right one a Red-white-Green.
The HTML page comes with a JS extension that allows to filter and sort in-place, to help with comparisons. You can disable this by disabling javascript or by changing the "AddSortingOverlay" parameter in the script.
The matrix on the left shows how strong a unit is compared to another one in fight. This calculation does not take costs into account, its purpose is to yield an indicator of who would win in an arena fight. Results are somewhat inaccurate for ranged units, particularly in ranged vs melee fights, as micro takes a really big importance for those.
A completely red value means the unit cannot win this fight, and that 100 units would only do minimal damage to the enemy, whilst being completely destroyed.
Yellow means that the two units are fairly equivalent, and a well microed fight would end up in a stalemate, more or less.
Green means the unit is stronger. Full Green means 4 times stronger, and any higher value would still be shown as full green. Elephants for example can sometimes be up to 10/20 stronger, which will not be shown.
This extension, called TableFilter, is released under the MIT license. The version I used was the one found at https://github.com/koalyptus/TableFilter/
The matrix on the right shows hardcoded counters. Greener means higher counter. A value of 2.5 is full green. Red values show units that are less effective at attacking other units. Full Red can mean that the unit cannot attack this kind of enemy (for example women can't attack anything, basically).
------------------------------------------------------------
- Unit Worthiness -
Those are the Red/Blue pyramids. Those show how powerful, cost-wise, a unit is. Thus the longer the bar, the better the unit will be cost-wise. Red bars show offensive power, while blue bars show resiliency (HP and armor). This being a simple scalar, it does not reflect really accurately how units behave, since it eg does not take hard-coded counters into account. It can be however used eg to see if a champion version of a unit is effectively worth its heightened cost, or not.
------------------------------------------------------------
- Unit Specializations -
This table compares units that inherit from a generic template to this generic template (note that this means that barracks-specific versions of champion units don't count.). The graph shows the difference with the generic template, and the last column is how much this changes the worth or the unit (according to the script).
If you want to data mine here, I recommend you copy/paste this table in Excel or Numbers to be able to sort it by column.
------------------------------------------------------------
- Roster Variety -
This shows in a simple manner which civs have units inheriting from which template, which is a simple but accurate way of showing the roster variety of a civ. The less units, the variety the civ has.
------------------------------------------------------------
- Civilization Comparison Tables -
Those tables compare civilizations using the Unit Comparison Matrixes. The intent is to give a portrait of how good a civ's units are against another civ's, to check at a glance if civilizations are properly balanced. Note that this is all statistical analysis, and that however well the tool will work, this requires interpretation. Don't take this at face value. In particular, this doesn't take technologies or buildings into account, so you need to keep that in mind. Perhaps a super OP unit requires an insanely expensive tech, making it more balanced.
The 3 left-most columns do not take costs into account (but do take the "ranged efficiency" factor into account.) The others do.
It is often interesting to compare the columns taking costs into account and those not doing so.
I'll refer to the Civ being looked at as "Civ A" and the ones listed in the table as "Civ B"
The "Average Efficiency" Columns show, on average, how strong Civ A units are against Civ B's. Since this uses the Unit Comparison Matrices, any value above "1" means that on average, Civ A units are stronger. However some units can bring this value up (such as elephants, which are really strong), even in the "cost adjusted" version. Likewise, a unit that's particularly weak or easily countered would bring average efficiency down.
Still, values far above 1 (>2…) should be investigated using the Unit Comparison Matrices themselves, and checking unit costs. You can remove some units specifically from the tables to check if it changes something (see below).
The "Maximal Minimal Efficiency" columns show how strong the weakest unit of Civ A is. If the value is close to 1 (or even above), this means that civ A has a unit that Civ B cannot counter, ie Civ A has a unit that would win a fight against any of Civ B's units (note that for the column taking costs into account, this means that for the same amount of resources spent, Civ A could always train an army that Civ B cannot defeat). Obviously this is fairly bad, and again though this tool isn't perfect, such data is a pretty good indication that civs are unbalanced. On the other hand, a very low value means that Civ A has a unit Civ B counters perfectly. This is not necessarily an issue.
The "Minimal Maximal Efficiency" columns show how weak the strongest unit of Civ A is. As you can expect, contrarily to "Maximal Minimal Efficiency", this column should be above "1". A value near "1" or even below it means that Civ A's strongest unit would lose a fight against any of Civ B's units. Again, this is a fairly good indicator that balance is wrong. Check "Range Efficiency" and costs to see if those factors explain this discrepancy.
The "Ranged Efficiency" column show how strong the ranged units of Civ A are, compared to Civ B. Values above 100% mean that Civ A has better archers/javelineers/slingers, and would thus probably have an advantage in open fights, which thus affects its unit efficiency. This can explain why a civ appears unbalanced (generally too strong or too weak).
The "Countered the least" and "Counters the least" show, respectively, which unit "Maximal Minimal Efficiency" and "Minimal Maximal Efficiency" refer to. You can use this to easily check in the Unit Comparison Matrices which unit is apparently too strong or too weak, which can help you remove some templates for a fairer comparison in some cases, or simply rebalance this unit.
The "Balance" column gives an easy to read indicator of how balanced civs are. This is really just a basic reading of the values in the table, and should not be taken at face value.
Note that if "Max Min Efficiency" and "Min Max Efficiency" are both very close to "1", and average efficiency too, this most likely means the civs are fairly balanced, assuming techs and buildings do not mess this up.
------------------------------------------------------------
- Customizing UnitTables -
Since this script cannot hope to provide an objective verdict of how strong civs are against one another, it has a variety of easily adjustable parameters that you can use to get a more accurate idea of how civs behave.
The variables below "#Generic templates to load" can be used to load only some templates. They refer to the file names, and you can add your own. Those are for the generic templates.
"Civs" will tell the script which civilizations to compare. Modders, to add your own, add it here (and see below for changing BasePath)
"CivBuildings" will tell the script what buildings to look at. Those are the end of structure template files, of format {CivName}_{BuildingName}.xml
> You can for example restrict this to ["barracks"] to only load units from the barracks.
"FilterOut" can be used to prune Civ templates. Any template which has any of these strings in its name will be removed. You can use this to remove specific templates, or whole collections (such as templates having "mechanical_siege" in their name.)
Graphic Parameters are as such:
"ComparativeSortByCav" will sort the Unit Comparison Matrices so that Cavalry and Infantry are separate instead of mixed.
"ComparativeSortByChamp" will sort the Unit Comparison Matrices so that Champions and Citizen Soldiers are separate instead of mixed.
"SortTypes" gives the order in which to sort units. This refers to their classes, so put the rare-most classes in front (eg Pike before Spear)
"ShowCivLists" will display the unit lists for civilizations too and not only generic templates.
Actual script parameters are as such:
"paramIncludePopCost" tells wether to include pop cost in the costs or not. Units that take more pop will be seen as weaker.
"paramFactorCounters" tells wether to count hardcoded counters or not. This can be used to see if counters have the desired effect.
"paramDPSImp" affects how important Damage Per Second (ie attack) is compared to HP and armour in calculating a unit's worthiness.
"paramHPImp" affects how important HP is compared to DPS in calculating a unit's worthiness. Lower is more important.
"paramRessImp" can be tweaked to make some resource types appear more costly. This can be used to check civ balance on maps where wood is scarce, for example.
"paramBuildTimeImp" (range [0-1], >1 is OK) affects how Build Time changes unit cost. With higher values, units that are slow to build are seen as weaker.
"paramSpeedImp" (range [0-1]) affects how important Speed is. With higher values, faster units will be stronger (this also affects unit comparison matrices.)
"paramRangedCoverage" (range [0-1] >1 is OK) affects how much "Ranged Efficiency" counts when comparing civs with one another. Higher values mean more importance.
"paramRangedMode" is used to know how to calculate a civilisation's Ranged Efficiency, either by averaging its units or taking the strongest one.
"paramMicroAutoSpeed" is particular: it is used to give a base advantage to ranged units over slower melee units. This parameters is the difference threshold at which this advantage takes place. For example, with a melee unit having a speed of 10, if this parameter is 2.5, only ranged units whose speed is 12.5 or more will benefit of this advantage. This can be used to simulate micro. Only affects Unit Comparison Matrices.
"paramMicroPerfectSpeed" is similar, only in this case the advantage is extremely high. This is used to simulate "perfect" micro, as ranged units can kill any much slower melee units without taking any hits if properly microed. If you want to check civs on the whole, set this really high to disable it as in the heat of the moment it's rare for micro to be this perfect. Only affects Unit Comparison Matrices.
Modders, change "BasePath" to something else if you want to use civs from another mod than "Public". To compare your civs with those in public, I recommend you copy the "simulation/templates" folder of the public mod somewhere else, copy your units in, and point BasePath to that folder.
########################################################################
########################################################################
2. CreateRMTest.py
This simple script takes two civilizations, and parameters which work as in unitTable.py, and creates a Random Map script with triggers to easily compare armies in-game. The created files are in maps/random, named "test_demobalance". You can then change the attacking and defending units to whatever you see fit, as well as change the size of armies. Not that this creates armies randomly based on a list of templates, so comparing whole civs will usually come down to which civs has the most of its strongest units. It is more accurate to compare one-of-a-kind armies, and even then you can only micro one side, which makes a lot of difference.
All contents of this folder are under the MIT License.
Enjoy!

View File

@ -23,124 +23,6 @@ h2
font-size: 30px;
font-style: italic;
}
h3
{
border-bottom: 1px #33AAFF solid;
text-align: center;
font-size: 25px;
font-weight: normal;
}
.UnitList
{
border-collapse: collapse;
white-space:nowrap;
}
.UnitList th
{
text-align: center;
padding: 0px 8px 3px 8px;
font-weight: normal;
border-bottom: 1px #DDDDDD solid;
}
.UnitList td
{
text-align: center;
white-space:nowrap;
padding:3px;
}
.ComparisonTable
{
margin-top: 175px;
border-collapse: collapse;
border:1px black;
font-size: 18px;
margin-bottom: 40px;
}
.ComparisonTable td
{
width:20px;
height: 20px;
text-align: center;
}
.Separator
{
min-width:50px;
border: none;
}
.ComparisonTable th
{
text-align: right;
font-weight:normal;
padding-right:5px;
}
.ComparisonTable .vertical-text th {
-webkit-transform: rotate(-45deg);
-moz-transform: rotate(-45deg);
-ms-transform: rotate(-45deg);
-o-transform: rotate(-45deg);
transform: rotate(-45deg);
max-width: 20px;
height: 25px;
padding-right:0px;
font-size: 12px;
}
.desc
{
font-style: italic;
font-size: 13px;
margin-top: -15px;
}
.WorthList th
{
text-align: right;
font-weight:normal;
padding-right:2px;
}
.WorthList td
{
width: 400px;
color:white;
}
.WorthList td span
{
display:inline-block;
}
.AverageComp
{
border-collapse: collapse;
font-size: 16px;
margin-left: auto;
margin-right: auto;
}
.AverageComp tr
{
border-bottom: 1px #DDD solid;
height:28px;
}
.AverageComp th
{
font-weight: normal
}
.AverageComp td
{
text-align: center;
min-width: 150px;
}
.AverageComp .UnitTd
{
text-align: center;
width: 150px;
border: 1px #DDD solid;
padding: 0px 5px 0px 5px;
}
.CivRosterVariety
{
@ -152,6 +34,7 @@ h3
{
font-weight: normal;
width:100px;
height:50px;
margin-left: 50px;
font-size: 14px;
}
@ -179,26 +62,19 @@ h3
font-size: 20px;
}
.TemplateParentComp th
{
border-right: 1px #CCC solid;
border-bottom: 1px #CCC solid;
font-weight: normal;
font-size: 15px;
padding:4px;
margin: 2px;
}
.TemplateParentComp .Sub
table .Sub
{
padding: 4px;
font-size: 13px;
text-align: left;
}
.TemplateParentComp tr
table tr
{
border-bottom: 1px #F3F3F3 solid;
}
.TemplateParentComp .Label td
table td
{
text-align: center;
font-weight: normal;
@ -206,7 +82,24 @@ h3
padding: 3px 6px 3px 6px;
}
.TemplateParentComp
table th
{
border-bottom: 1px #CCC solid;
font-size: 15px;
padding:4px;
margin: 2px;
text-align: center;
font-weight: normal;
min-width: 25px;
border-left:1px black solid;
}
table .Label td, table .Label th
{
border-bottom: 1px #000 solid;
}
table
{
border: 2px black solid;
font-size: 12px;

View File

@ -0,0 +1,6 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
span.colVisSpan{text-align:left;}span.colVisSpan a.colVis{display:inline-block;padding:7px 5px 0;font-size:inherit;font-weight:inherit;vertical-align:top}div.colVisCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;border:1px solid #ccc;height:auto;width:250px;background-color:#fff;margin:35px 0 0 -100px;z-index:10000;padding:10px 10px 10px 10px;text-align:left;font-size:12px;}div.colVisCont:after,div.colVisCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.colVisCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.colVisCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.colVisCont p{margin:6px auto 6px auto}div.colVisCont a.colVis{display:initial;font-weight:inherit}ul.cols_checklist{padding:0;margin:0;list-style:none;}ul.cols_checklist label{display:block}ul.cols_checklist input{vertical-align:middle;margin:2px 5px 2px 1px}li.cols_checklist_item{padding:4px;margin:0;}li.cols_checklist_item:hover{background-color:#335ea8;color:#fff}.cols_checklist_slc_item{background-color:#335ea8;color:#fff}

View File

@ -0,0 +1,6 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
span.expClpFlt a.btnExpClpFlt{width:35px;height:35px;display:inline-block;}span.expClpFlt a.btnExpClpFlt:hover{background-color:#f4f4f4}span.expClpFlt img{padding:8px 11px 11px 11px}

View File

@ -0,0 +1,22 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
.activeHeader{background-color:#66afe9 !important;color:#fff !important}
.even{background-color:#fff}.odd{background-color:#f9f9f9}
.ezActiveRow{background-color:#2852a8 !important;color:#fff}.ezSelectedRow{background-color:#316ac5 !important;color:#fff}.ezActiveCell{background-color:#d9e8fb !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#ffdc61 !important;font-weight:bold;color:#000 !important}.ezUnselectable{-moz-user-select:-moz-none;-khtml-user-select:none;-webkit-user-select:none;-o-user-select:none;user-select:none}.ezInputEditor{width:95%;height:auto;font-size:inherit;border:1px solid #aaccf6}.ezTextareaEditor{width:95%;height:35px;font-size:inherit;border:1px solid #aaccf6}.ezSelectEditor{width:100%;font-size:inherit;border:1px solid #aaccf6}.ezModifiedCell{background:transparent url("themes/bg_mod_cell.png") 0 0 no-repeat}select[multiple="multiple"].ezSelectEditor{height:35px}.ezCommandEditor{margin:2px;}.ezCommandEditor button,.ezCommandEditor input[type="button"]{min-height:22px;margin:1px;padding:3px;border:1px solid #ccc;background:#fff;border-radius:4px 4px 4px 4px;-moz-border-radius:4px 4px 4px 4px;}.ezCommandEditor button:hover,.ezCommandEditor input[type="button"]:hover{border:1px solid #999}.ezCommandEditor img{border:0;vertical-align:middle;margin:2px}.ezOpacity{opacity:.6}.alignLeft{text-align:left}.alignCenter{text-align:center}.alignRight{text-align:right}
.div_checklist{width:100%;height:90px;border:1px solid #f4f4f4;overflow:auto;text-align:left;background-color:#fff;color:#444;}.div_checklist ul.flt_checklist{padding:0 !important;margin:0 !important;list-style:none !important}.div_checklist li.flt_checklist_item{padding:1px !important;margin:0 !important;font-size:10px !important;border-bottom:1px solid #f4f4f4 !important;}.div_checklist li.flt_checklist_item:hover{background-color:#335ea8 !important;color:#fff !important}.div_checklist label{display:block !important}.div_checklist input{vertical-align:middle !important;margin:2px 5px 2px 1px !important}.flt_checklist_item_disabled{background-color:#e5e5e5}.flt_checklist_slc_item{background-color:#335ea8 !important;color:#fff !important}
.fltrow{height:1em;background-color:#eaeaea;}.fltrow td{border-bottom:1px solid #ccc !important;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.2em !important;}.fltrow td:last-child{border-right:1px solid #ccc}.btnflt{height:35px;font-family:inherit;font-size:inherit;vertical-align:middle;margin:0 2px 0 2px;padding:0 1px 0 1px}.btnflt_icon{font-family:inherit;font-size:inherit;width:35px;height:35px;cursor:pointer !important;border:0 !important;vertical-align:middle;background:transparent url("themes/btn_filter.png") center center no-repeat !important}.flt,.flt_s,.single_flt{font-family:inherit;display:block;color:#444;background-color:#fff;border:1px inset #f4f4f4;margin:0;padding:0 0 0 .2em;width:100%;height:35px;vertical-align:middle;border-radius:2px;box-sizing:border-box;}.flt:focus,.flt_s:focus,.single_flt:focus{border-color:#66afe9;outline:0 none;-webkit-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);-moz-box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6);box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}select.flt_multi{font-family:inherit;color:#444;background-color:#fff;border:1px solid #f4f4f4;margin:0;padding:.2em;width:100%;height:90px;vertical-align:middle;box-sizing:border-box}.flt_s{width:60%;box-sizing:initial;display:initial}.single_flt{width:70%;box-sizing:initial;display:initial}div.popUpFilter{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;margin:30px auto 0 0;position:absolute;display:none;width:100px;background-color:#eaeaea;border:1px solid #eaeaea;padding:0}div.popUpFilter:after,div.popUpFilter:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.popUpFilter:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.popUpFilter:before{border-color:rgba(255,255,255,0);border-bottom-color:#eaeaea;border-width:12px;margin-left:-12px}
div.grd_Cont{-webkit-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);-moz-box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);box-shadow:4px 4px 10px 0 rgba(50,50,50,0.75);width:800px;height:auto;overflow:hidden;padding:3px 3px 3px 3px;background-color:#c8e0fb;border:1px solid #99bbe8;}div.grd_Cont .fltrow{background-color:transparent}div.grd_Cont .flt{border:1px solid #99bbe8;width:100%;}div.grd_Cont .flt :focus{border:1px solid #558dd9}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#dfe8f6}div.grd_tblCont{height:400px;width:800px;background:#fff;overflow-x:auto;overflow-y:scroll}div.grd_headTblCont{height:auto;width:800px;overflow:hidden;border-bottom:1px solid #99bbe8;background-color:#c8e0fb}div.grd_tblCont table,div.grd_headTblCont table{border-collapse:collapse;table-layout:fixed;box-sizing:initial}div.grd_tblCont table{border-right:1px solid #99bbe8;box-sizing:initial}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{height:35px;background-color:#c8e0fb;padding:.1em .5em;color:#333;border-right:1px solid #99bbe8 !important;overflow:hidden;text-overflow:ellipsis}div.grd_headTblCont table td{padding:.2em .2em}div.grd_tblCont table td{padding:.5em .7em;border-bottom:1px solid #99bbe8;overflow:hidden;text-overflow:ellipsis}.grd_inf{clear:both;width:auto;height:35px;background-color:#c8e0fb;margin:0;padding:1px 3px 1px 3px;border-top:1px solid #99bbe8;}.grd_inf a{color:#333;text-decoration:none;font-weight:bold;}.grd_inf a:hover{text-decoration:underline;background-color:transparent}.grd_inf input.reset:hover{background-color:transparent}.grd_inf .mdiv{width:40% !important}.grd_inf .ldiv div{border:0}.grd_inf .helpBtn{border:0 !important}.grd_inf div.status{position:absolute;float:none !important;height:auto !important;margin:19px 0 !important;font-size:12px;color:#333;border:0 !important}.grd_inf div.tot{border:0 !important}
.helpBtn{display:inline-block;height:27px;margin:0;padding:8px 15px 0 15px;vertical-align:top;}.helpBtn:hover{background-color:#f4f4f4}div.helpCont{position:relative;background:#fff;-webkit-box-shadow:3px 3px 2px #888;-moz-box-shadow:3px 3px 2px #888;box-shadow:3px 3px 2px #888;position:absolute;display:none;width:300px;padding:10px;margin:45px 0 0 -150px;border:1px solid #ccc;line-height:20px;font-size:inherit;color:#333;background:#fff;text-align:left;}div.helpCont:after,div.helpCont:before{bottom:100%;left:50%;border:solid transparent;content:" ";height:0;width:0;position:absolute;pointer-events:none}div.helpCont:after{border-color:rgba(255,255,255,0);border-bottom-color:#fff;border-width:10px;margin-left:-10px}div.helpCont:before{border-color:rgba(255,255,255,0);border-bottom-color:#ccc;border-width:12px;margin-left:-12px}div.helpCont a{color:#c00;text-decoration:underline;font-weight:normal}div.helpCont a.close{color:#333 !important;text-decoration:none !important;font-weight:bold;}div.helpCont a.close:hover{text-decoration:none}div.helpCont hr{border:1px solid #ccc}div.helpFooter{margin:10px 0 0 0;}div.helpFooter h4{margin:2px 2px 2px 2px;color:#333}
span.keyword{font-weight:700;font-style:italic;border-bottom:1px dotted #ccc}
.loader{position:absolute;padding:.5em .7em;margin:10em 0 0 3em;width:auto;z-index:1000;font-weight:600;background-color:#a7a7a8;vertical-align:middle;border-radius:10px;color:#fff;text-shadow:1px 1px #333}
select.pgSlc{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle}select.pgSlc:focus{border-color:#66afe9;outline:0 none;box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgNbInp{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;width:35px}input.pgNbInp:focus{border-color:#66afe9;outline:0 none;box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}input.pgInp,.nextPage,.previousPage,.firstPage,.lastPage{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;vertical-align:middle;width:35px;border:0;font-weight:bold}input.pgInp:focus,.nextPage:focus,.previousPage:focus,.firstPage:focus,.lastPage:focus{border-color:#66afe9;outline:0 none;box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}.nextPage{background:transparent url("themes/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background-color:#f4f4f4 !important}.previousPage{background:transparent url("themes/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background-color:#f4f4f4 !important}.firstPage{background:transparent url("themes/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background-color:#f4f4f4 !important}.lastPage{background:transparent url("themes/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background-color:#f4f4f4 !important}span.nbpg{padding:0 5px}select.rspg{height:35px;margin:0;border:1px solid #f4f4f4;background-color:#fff;margin:0 0 0 5px;vertical-align:middle}select.rspg:focus{border-color:#66afe9;outline:0 none;box-shadow:0 1px 1px rgba(0,0,0,0.075) inset,0 0 8px rgba(102,175,233,0.6)}span.rspgSpan{font-size:inherit}
input.reset{display:inline-block;width:35px;height:35px;border:0;background:transparent url("themes/btn_clear_filters.png") center center no-repeat;vertical-align:top;}input.reset:hover{background-color:#f4f4f4}
div.tot{float:left;overflow:hidden;min-width:150px;height:100%;margin:0;padding:.5em;vertical-align:middle;}div.tot span{font-weight:500}
.sort-arrow{width:11px;height:11px;margin:0 2px;background-position:center center;background-repeat:no-repeat}.descending{background-image:url("themes/downsimple.png")}.ascending{background-image:url("themes/upsimple.png")}
div.status{float:left;overflow:hidden;min-width:120px;height:100%;margin:0;padding:.5em;}div.status span{font-size:inherit}
table.TF{font-family:inherit;border-spacing:0;border:0;}table.TF th{height:35px;margin:0;background-color:#eaeaea;border-bottom:1px solid #ccc;border-top:1px solid #f4f4f4;border-left:1px solid #ccc;border-right:1px solid #f4f4f4;padding:.1em .7em;color:#333;}table.TF th:last-child{border-right:1px solid #ccc}table.TF td{margin:0;padding:.5em .7em;border-bottom:1px solid #c6c6c6}
.inf{clear:both;width:auto;height:35px;background-color:#fff;font-size:inherit;margin:0;padding:0;border-top:1px solid #ccc;border-bottom:1px solid #ccc;border-left:1px solid #ccc;border-right:1px solid #ccc;overflow:hidden;border-top-left-radius:3px;border-top-right-radius:3px;}.inf a{color:#333;text-decoration:none;font-weight:bold;box-sizing:initial;}.inf a:hover{text-decoration:underline}.ldiv{float:left;width:30%;position:inherit;text-align:left}.mdiv{float:left;width:38%;position:inherit;text-align:center;padding:0}.rdiv{float:right;width:30%;position:inherit;text-align:right}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

View File

@ -0,0 +1,6 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
table.TF{border-left:1px solid #ccc !important;border-top:none !important;border-right:none !important;border-bottom:none !important;}table.TF th{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important;color:#333 !important}table.TF td{border-bottom:1px dotted #999 !important;padding:5px !important}.fltrow{background-color:#ebecee !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #666 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #999 !important}input.flt{width:99% !important}.inf{height:$min-height;background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important}input.reset{background:transparent url("images/btn_eraser.gif") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;}.nextPage:hover{background:transparent url("images/btn_over_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important;}.previousPage:hover{background:transparent url("images/btn_over_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;}.firstPage:hover{background:transparent url("images/btn_over_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;}.lastPage:hover{background:transparent url("images/btn_over_last_page.gif") center center no-repeat !important}div.grd_Cont{background-color:#ebecee !important;border:1px solid #ccc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#d5d5d5}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important;}div.grd_headTblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#ebecee url("images/bg_th.jpg") left top repeat-x !important;border-bottom:1px solid #d0d0d0 !important;border-right:1px solid #d0d0d0 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #999 !important}.grd_inf{background:#d7d7d7 url("images/bg_infDiv.jpg") 0 0 repeat-x !important;border-top:1px solid #d0d0d0 !important}.loader{border:1px solid #999}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#fff}.odd{background-color:#d5d5d5}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 427 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 393 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -0,0 +1,6 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
table.TF{border-left:1px dotted #81963b !important;border-top:none !important;border-right:0 !important;border-bottom:none !important;}table.TF th{background:#39424b url("images/bg_headers.jpg") left top repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;color:#fff !important}table.TF td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b;padding:5px !important}.fltrow{background-color:#81963b !important;}.fltrow th,.fltrow td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #687830 !important}input.flt{width:99% !important}.inf{background:#d8d8d8;height:$min-height}input.reset{width:53px;background:transparent url("images/btn_filter.png") center center no-repeat !important}.helpBtn:hover{background-color:transparent}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important}.previousPage{background:transparent url("images/btn_previous_page.gif") center center no-repeat !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important}div.grd_Cont{background:#81963b url("images/bg_headers.jpg") left top repeat-x !important;border:1px solid #ccc !important;padding:0 1px 1px 1px !important;}div.grd_Cont .even{background-color:#bccd83}div.grd_Cont .odd{background-color:#fff}div.grd_headTblCont{background-color:#ebecee !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important;}div.grd_tblCont table td{border-bottom:1px dotted #81963b;border-right:1px dotted #81963b}div.grd_tblCont table th,div.grd_headTblCont table th{background:transparent url("images/bg_headers.jpg") 0 0 repeat-x !important;border-bottom:0 !important;border-right:1px dotted #d0d0d0 !important;border-left:0 !important;border-top:0 !important;padding:0 4px 0 4px !important;color:#fff !important;height:35px !important}div.grd_headTblCont table td{border-bottom:1px dotted #39424b !important;border-right:1px dotted #fff !important;border-left:0 !important;border-top:0 !important;background-color:#81963b !important;padding:1px 3px 1px 3px !important}.grd_inf{background-color:#d8d8d8;border-top:1px solid #d0d0d0 !important}.loader{border:0 !important;background:#81963b !important}.defaultLoader{width:32px;height:32px;background:transparent url("images/img_loading.gif") 0 0 no-repeat !important}.even{background-color:#bccd83}.odd{background-color:#fff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 847 B

View File

@ -0,0 +1,6 @@
/**
* tablefilter v0.0.11 by Max Guglielmi
* build date: 2015-11-21T07:50:21.705Z
* MIT License
*/
table.TF{padding:0;color:#000;border-right:1px solid #a4bed4;border-top:1px solid #a4bed4;border-left:1px solid #a4bed4;border-bottom:0;}table.TF th{margin:0;color:inherit;background:#d1e5fe url("images/bg_skyblue.gif") 0 0 repeat-x;border-color:#fdfdfd #a4bed4 #a4bed4 #fdfdfd;border-width:1px;border-style:solid}table.TF td{margin:0;padding:5px;color:inherit;border-bottom:1px solid #a4bed4;border-left:0;border-top:0;border-right:0}.fltrow{background-color:#d1e5fe !important;}.fltrow th,.fltrow td{padding:1px 3px 1px 3px !important}.flt,select.flt,select.flt_multi,.flt_s,.single_flt,.div_checklist{border:1px solid #a4bed4 !important}input.flt{width:99% !important}.inf{background-color:#e3efff !important;border:1px solid #a4bed4;height:$min-height;color:#004a6f}div.tot,div.status{border-right:0 !important}.helpBtn:hover{background-color:transparent}input.reset{background:transparent url("images/icn_clear_filters.png") center center no-repeat !important}.nextPage{background:transparent url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.nextPage:hover{background:#ffe4ab url("images/btn_next_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.previousPage{background:transparent url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.previousPage:hover{background:#ffe4ab url("images/btn_prev_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.firstPage{background:transparent url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.firstPage:hover{background:#ffe4ab url("images/btn_first_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.lastPage{background:transparent url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid transparent !important;}.lastPage:hover{background:#ffe4ab url("images/btn_last_page.gif") center center no-repeat !important;border:1px solid #ffb552 !important}.activeHeader{background:#ffe4ab !important;border:1px solid #ffb552 !important;color:inherit !important}div.grd_Cont{background-color:#d9eaed !important;border:1px solid #9cc !important;padding:0 !important;}div.grd_Cont .even{background-color:#fff}div.grd_Cont .odd{background-color:#e3efff}div.grd_headTblCont{background-color:#d9eaed !important;border-bottom:none !important}div.grd_tblCont table{border-right:none !important}div.grd_tblCont table th,div.grd_headTblCont table th,div.grd_headTblCont table td{background:#d9eaed url("images/bg_skyblue.gif") left top repeat-x;border-bottom:1px solid #a4bed4;border-right:1px solid #a4bed4 !important;border-left:1px solid #fff !important;border-top:1px solid #fff !important}div.grd_tblCont table td{border-bottom:1px solid #a4bed4 !important;border-right:0 !important;border-left:0 !important;border-top:0 !important}.grd_inf{background-color:#cce2fe;color:#004a6f;border-top:1px solid #9cc !important;}.grd_inf a{text-decoration:none;font-weight:bold}.loader{background-color:#2d8eef;border:1px solid #cce2fe;border-radius:5px}.even{background-color:#fff}.odd{background-color:#e3efff}span.expClpFlt a.btnExpClpFlt:hover{background-color:transparent !important}.ezActiveRow{background-color:#ffdc61 !important;color:inherit}.ezSelectedRow{background-color:#ffe4ab !important;color:inherit}.ezActiveCell{background-color:#fff !important;color:#000 !important;font-weight:bold}.ezETSelectedCell{background-color:#fff !important;font-weight:bold;color:#000 !important}

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -7,61 +7,69 @@ AttackTypes = ["Hack","Pierce","Crush"]
Resources = ["food", "wood", "stone", "metal"]
# Generic templates to load
Class = ["_champion",""]
Types = ["infantry","cavalry"];
Subtypes = ["spearman","pikeman","swordsman", "archer","javelinist","slinger"];
Fix = ["melee_","ranged_",""]
AddedTemplates = ["template_unit_champion_elephant_melee.xml"]
# The way this works is it tries all generic templates
# But only loads those who have one of the following parents
# EG adding "template_unit.xml" will load all units.
LoadTemplatesIfParent = ["template_unit_infantry.xml", "template_unit_cavalry.xml", "template_unit_champion.xml"];
# Those describe Civs to analyze and buildings to consider.
# That way you can restrict to some buildings specifically.
Civs = ["athen", "mace", "spart", "cart", "rome", "pers", "maur", "brit", "gaul", "iber"]
#Civs = ["rome","pers"]
CivBuildings = ["civil_centre", "barracks","gymnasion", "stables", "elephant_stables", "fortress", "embassy_celtic", "embassy_italiote", "embassy_iberian"]
#CivBuildings = ["civil_centre", "barracks"]
# Those describe Civs to analyze.
# The script will load all entities that derive (to the nth degree) from one of the above templates.
Civs = ["athen", "mace", "spart", "sele", "cart", "rome", "pers", "maur", "brit", "gaul", "iber"]
# Remote Civ templates with those strings in their name.
FilterOut = ["hero", "mecha", "support", "barracks"]
FilterOut = ["marian"]
# Graphic parameters: affects only how the data is shown
# Sorting parameters for the "roster variety" table
ComparativeSortByCav = True
ComparativeSortByChamp = True
SortTypes = ["Support","Pike","Spear","Sword", "Archer","Javelin","Sling","Elephant"]; # Classes
ShowCivLists = False
SortTypes = ["Support","Pike","Spear","Sword", "Archer","Javelin","Sling","Elephant"] # Classes
# Parameters: affects the data
paramIncludePopCost = False
paramFactorCounters = True; # False means attack bonuses are not counted.
paramDPSImp = 5
paramHPImp = 3 # Decrease to make more important
paramRessImp = { "food" : 1.0, "wood" : 1.0, "stone" : 2.0, "metal" : 1.5 }
paramBuildTimeImp = 0.5 # Sane values are 0-1, though more is OK.
paramSpeedImp = 0.2 # Sane values are 0-1, anything else will be weird.
paramRangedCoverage = 1.0 # Sane values are 0-1, though more is OK.
paramRangedMode = "Average" #Anything but "Average" is "Max"
paramMicroAutoSpeed = 2.5 # Give a small advantage to ranged units faster than melee units (by at least this parameter). Making this too high will disable this advantage. This simulates the advantage a bit of micro would give
paramMicroPerfectSpeed = 5.0 # Likewise, only this assumes perfect micro. Thus a ranged unit a lot faster is invincible. For example chariot v Elephant is a large win for elephants without this, but a large win for chariots with, since chariots can easily outmaneuver elephants if microed perfectly.
# Disable if you want the more compact basic data. Enable to allow filtering and sorting in-place.
AddSortingOverlay = True
# This is the path to the /templates/ folder to consider. Change this for mod support.
basePath = os.path.realpath(__file__).replace("unitTables.py","") + "../../../binaries/data/mods/public/simulation/templates/"
# For performance purposes, cache opened templates files.
globalTemplatesList = {};
def htbout(file, balise, value):
file.write("<" + balise + ">" + value + "</" + balise + ">\n" )
def htout(file, value):
file.write("<p>" + value + "</p>\n" )
def fastParse(templateName):
if templateName in globalTemplatesList:
return globalTemplatesList[templateName]
globalTemplatesList[templateName] = ET.parse(templateName)
return globalTemplatesList[templateName]
# This function checks that a template has the given parent.
def hasParentTemplate(UnitName, parentName):
Template = fastParse(UnitName)
found = False
Name = UnitName;
while found != True and Template.getroot().get("parent") != None:
Name = Template.getroot().get("parent") + ".xml"
if Name == parentName:
return True
Template = ET.parse(Name)
return False
# This function parses the entity values manually.
def CalcUnit(UnitName, existingUnit = None):
unit = { 'HP' : "0", "BuildTime" : "0", "Cost" : { 'food' : "0", "wood" : "0", "stone" : "0", "metal" : "0", "population" : "0"},
'Attack' : { "Melee" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 }, "Ranged" : { "Hack" : 0, "Pierce" : 0, "Crush" : 0 } },
'RepeatRate' : {"Melee" : "0", "Ranged" : "0"},'PrepRate' : {"Melee" : "0", "Ranged" : "0"}, "Armour" : {},
"Ranged" : "false", "Classes" : [], "AttackBonuses" : {}, "Restricted" : [],
"Civ" : None };
"Ranged" : False, "Classes" : [], "AttackBonuses" : {}, "Restricted" : [],
"Civ" : None }
if (existingUnit != None):
unit = existingUnit
Template = ET.parse(UnitName)
Template = fastParse(UnitName)
# Recursively get data from our parent which we'll override.
if (Template.getroot().get("parent") != None):
@ -81,6 +89,9 @@ def CalcUnit(UnitName, existingUnit = None):
for type in list(Template.find("./Cost/Resources")):
unit['Cost'][type.tag] = type.text
if (Template.find("./Cost/Population") != None):
unit['Cost']["population"] = Template.find("./Cost/Population").text
if (Template.find("./Attack/Melee") != None):
if (Template.find("./Attack/Melee/RepeatTime") != None):
unit['RepeatRate']["Melee"] = Template.find("./Attack/Melee/RepeatTime").text
@ -110,9 +121,11 @@ def CalcUnit(UnitName, existingUnit = None):
if (Template.find("./Attack/Ranged") != None):
unit['Ranged'] = "true"
unit['Ranged'] = True
if (Template.find("./Attack/Ranged/MaxRange") != None):
unit['Range'] = Template.find("./Attack/Ranged/MaxRange").text
if (Template.find("./Attack/Ranged/Spread") != None):
unit['Spread'] = Template.find("./Attack/Ranged/Spread").text
if (Template.find("./Attack/Ranged/RepeatTime") != None):
unit['RepeatRate']["Ranged"] = Template.find("./Attack/Ranged/RepeatTime").text
if (Template.find("./Attack/Ranged/PrepareTime") != None):
@ -170,175 +183,54 @@ def CalcUnit(UnitName, existingUnit = None):
return unit
def WriteUnit(Name, UnitDict):
rstr = "<tr>"
ret = "<tr>"
rstr += "<td style=\"text-align:right;\">" + Name + "</td>\n"
ret += "<td class=\"Sub\">" + Name + "</td>"
rstr += "<td>" + UnitDict["HP"] + "</td>\n"
ret += "<td>" + str(int(UnitDict["HP"])) + "</td>"
rstr += "<td>" + UnitDict["BuildTime"] + "</td>\n"
ret += "<td>" +str("%.0f" % float(UnitDict["BuildTime"])) + "</td>"
rstr += "<td>" + UnitDict["WalkSpeed"] + "</td>\n"
ret += "<td>" + str("%.1f" % float(UnitDict["WalkSpeed"])) + "</td>"
rstr += "<td>" + UnitDict["Cost"]["food"] + "F / " + UnitDict["Cost"]["wood"] + "W / " + UnitDict["Cost"]["stone"] + "S / " + UnitDict["Cost"]["metal"] + "M</td>\n"
for atype in AttackTypes:
PercentValue = 1.0 - (0.9 ** float(UnitDict["Armour"][atype]))
ret += "<td>" + str("%.0f" % float(UnitDict["Armour"][atype])) + " / " + str("%.0f" % (PercentValue*100.0)) + "%</td>"
if UnitDict["Ranged"] == "True":
rstr += "<td>" + str(UnitDict["Attack"]["Ranged"]["Hack"]) + " / " + str(UnitDict["Attack"]["Ranged"]["Pierce"]) + " / " + str(UnitDict["Attack"]["Ranged"]["Crush"]) + "</td>\n"
attType = ("Ranged" if UnitDict["Ranged"] == True else "Melee")
if UnitDict["RepeatRate"][attType] != "0":
for atype in AttackTypes:
repeatTime = float(UnitDict["RepeatRate"][attType])/1000.0
ret += "<td>" + str("%.1f" % (float(UnitDict["Attack"][attType][atype])/repeatTime)) + "</td>"
ret += "<td>" + str("%.1f" % (float(UnitDict["RepeatRate"][attType])/1000.0)) + "</td>"
else:
rstr += "<td>" + str(UnitDict["Attack"]["Melee"]["Hack"]) + " / " + str(UnitDict["Attack"]["Melee"]["Pierce"]) + " / " + str(UnitDict["Attack"]["Melee"]["Crush"]) + "</td>\n"
for atype in AttackTypes:
ret += "<td> - </td>"
ret += "<td> - </td>"
rstr += "<td>" + str(UnitDict["Armour"]["Hack"]) + " / " + str(UnitDict["Armour"]["Pierce"]) + " / " + str(UnitDict["Armour"]["Crush"]) + "</td>\n"
if UnitDict["Ranged"] == True:
ret += "<td>" + str("%.1f" % float(UnitDict["Range"])) + "</td>"
spread = (float(UnitDict["Spread"]) / float(UnitDict["Range"]))*100.0
ret += "<td>" + str("%.1f" % spread) + "</td>"
else:
ret += "<td> - </td><td> - </td>"
rstr += "<td style=\"text-align:left;\">"
for classe in UnitDict["Classes"]:
rstr += classe + " "
rstr += "</td>"
for rtype in Resources:
ret += "<td>" + str("%.0f" % float(UnitDict["Cost"][rtype])) + "</td>"
rstr += "<td style=\"text-align:left;\">"
ret += "<td>" + str("%.0f" % float(UnitDict["Cost"]["population"])) + "</td>"
ret += "<td style=\"text-align:left;\">"
for Bonus in UnitDict["AttackBonuses"]:
rstr += "["
ret += "["
for classe in UnitDict["AttackBonuses"][Bonus]["Classes"]:
rstr += classe + " "
rstr += ': ' + str(UnitDict["AttackBonuses"][Bonus]["Multiplier"]) + "] "
rstr += "</td>"
ret += classe + " "
ret += ': ' + str(UnitDict["AttackBonuses"][Bonus]["Multiplier"]) + "] "
ret += "</td>"
rstr += "<td>"
for classe in UnitDict["Restricted"]:
rstr += classe + " "
rstr += "</td>"
return "</tr>" + rstr
def CalcValue(UnitDict):
Worth = {};
# Try to estimate how "worth it" a unit is.
# We can't really differentiate between attack types and armor types and counters here, so values might fluctuate
# But this can be used to quickly estimate if some units are too costly.
Attack = "Melee"
if (UnitDict["Ranged"] == "true"):
Attack = "Ranged"
DPS = 0
for att in AttackTypes:
DPS += float(UnitDict['Attack'][Attack][att])
if (Attack == "Ranged"):
DPS += float(UnitDict["Range"]) / 10
DPS /= (float(UnitDict['RepeatRate'][Attack])+float(UnitDict['PrepRate'][Attack])+1)/1000
Worth["Attack"] = DPS * paramDPSImp;
if "Infantry" in UnitDict["Restricted"]:
Worth["Attack"] /= 5.0;
if "Cavalry" in UnitDict["Restricted"]:
Worth["Attack"] /= 5.0;
armorTotal = float(UnitDict['Armour']["Hack"]) + float(UnitDict['Armour']["Pierce"]) + float(UnitDict['Armour']["Crush"])/2.0
Worth["Defence"] = int(UnitDict['HP']) / (0.9**armorTotal)/paramHPImp;
TotalCost = 0
for ress in paramRessImp:
TotalCost += int(UnitDict['Cost'][ress]) * paramRessImp[ress]
if (paramIncludePopCost):
TotalCost *= (int(UnitDict['Cost']["population"])+1)/2.0
Worth["Cost"] = TotalCost/400.0;
Worth["Cost"] = Worth["Cost"] * int(UnitDict['BuildTime'])/12.0 * paramBuildTimeImp + (1.0-paramBuildTimeImp) * Worth["Cost"];
# Speed makes you way less evasive, and you can chase less.
# It's not really a cost so just decrease Attack and Defence
Worth["Attack"] = Worth["Attack"] * float(UnitDict["WalkSpeed"])/10.0 * paramSpeedImp + (1.0-paramSpeedImp) * Worth["Attack"];
Worth["Defence"] = Worth["Defence"] * float(UnitDict["WalkSpeed"])/10.0 * paramSpeedImp + (1.0-paramSpeedImp) * Worth["Defence"];
Worth["Total"] = (Worth["Attack"] + Worth["Defence"])/Worth["Cost"]
return Worth
def canAttack(Attacker, Defender):
ARestrictions = Attacker["Restricted"]
BClasses = Defender["Classes"]
for Classes in ARestrictions:
if Classes in BClasses:
return False
return True
def GetAttackBonus(Attacker, Defender):
if not canAttack(Attacker, Defender):
return 0.0
Abonuses = Attacker["AttackBonuses"]
BClasses = Defender["Classes"]
Bonus = 1.0;
for BonusTypes in Abonuses:
found = True
for classe in Abonuses[BonusTypes]["Classes"]:
if classe not in BClasses:
found = False;
for civi in Abonuses[BonusTypes]["Civs"]:
if Defender["Civ"] not in civi:
found = False;
if found:
Bonus *= Abonuses[BonusTypes]["Multiplier"]
return Bonus;
# Returns how much stronger unit A is compared to unit B in a direct fight. Uses counters, speed. Not perfect, but not widely inaccurate either.
def Compare2(UnitDictA,UnitDictB):
AWorth = 1.0;
BWorth = 1.0;
output = 1.0;
# Get some DPS up in here. Use only melee or Ranged if available.
AAttack = "Melee"
if (UnitDictA["Ranged"] == "true"):
AAttack = "Ranged"
BAttack = "Melee"
if (UnitDictB["Ranged"] == "true"):
BAttack = "Ranged"
ADPS = 0.0
BDPS = 0.0
# Exponential armor
for att in AttackTypes:
ADPS += float(UnitDictA['Attack'][AAttack][att]) * 0.9**float(UnitDictB['Armour'][att])
BDPS += float(UnitDictB['Attack'][BAttack][att]) * 0.9**float(UnitDictA['Armour'][att])
#Check if A is bonused against B and vice versa
if (paramFactorCounters == True):
ADPS *= GetAttackBonus(UnitDictA,UnitDictB)
BDPS *= GetAttackBonus(UnitDictB,UnitDictA)
if (AAttack == "Ranged"):
ADPS += float(UnitDictA["Range"]) / float(UnitDictB["WalkSpeed"])
if (BAttack == "Ranged"):
BDPS += float(UnitDictB["Range"]) / float(UnitDictA["WalkSpeed"])
ADPS /= (float(UnitDictA['RepeatRate'][AAttack])+float(UnitDictA['PrepRate'][AAttack])+1)/1000
BDPS /= (float(UnitDictB['RepeatRate'][BAttack])+float(UnitDictB['PrepRate'][BAttack])+1)/1000
#Ranged units can get a real advantage against slower enemy units.
if AAttack == "Ranged" and BAttack != "Ranged" and float(UnitDictA["WalkSpeed"]) - paramMicroAutoSpeed > float(UnitDictB["WalkSpeed"]):
ADPS += 20
elif BAttack == "Ranged" and AAttack != "Ranged" and float(UnitDictB["WalkSpeed"]) - paramMicroAutoSpeed > float(UnitDictA["WalkSpeed"]):
BDPS += 20
if AAttack == "Ranged" and BAttack != "Ranged" and float(UnitDictA["WalkSpeed"]) - paramMicroPerfectSpeed > float(UnitDictB["WalkSpeed"]):
ADPS += 5000
elif BAttack == "Ranged" and AAttack != "Ranged" and float(UnitDictB["WalkSpeed"]) - paramMicroPerfectSpeed > float(UnitDictA["WalkSpeed"]):
BDPS += 5000
AWorth += int(UnitDictA['HP']) / (BDPS+1);
BWorth += int(UnitDictB['HP']) / (ADPS+1);
SpeedRatio = 1.0-paramSpeedImp + paramSpeedImp * float(UnitDictA["WalkSpeed"]) / float(UnitDictB["WalkSpeed"])
return AWorth / BWorth * SpeedRatio
ret += "</tr>\n"
return ret
# Sort the templates dictionary.
def SortFn(A):
@ -347,142 +239,34 @@ def SortFn(A):
sortVal += 1
if classe in A[1]["Classes"]:
break
if (ComparativeSortByChamp == True and A[0].find("champion") == -1):
if ComparativeSortByChamp == True and A[0].find("champion") == -1:
sortVal -= 20
if (ComparativeSortByCav == True and A[0].find("cavalry") == -1):
if ComparativeSortByCav == True and A[0].find("cavalry") == -1:
sortVal -= 10
if (A[1]["Civ"] != None):
if A[1]["Civ"] != None and A[1]["Civ"] in Civs:
sortVal += 100 * Civs.index(A[1]["Civ"])
return sortVal
# Output a matrix of each units against one another, where the square is greener if the unit is stronger, redder if weaker.
def WriteComparisonTable(Units, OtherUnits = None):
f.write("<table class=\"ComparisonTable\">")
# helper to write coloured text.
def WriteColouredDiff(file, diff, PositOrNegat):
if OtherUnits == None:
OtherUnits = Units
sortedDict = sorted(Units.items(), key=SortFn)
sortedOtherDict = sorted(OtherUnits.items(), key=SortFn)
# First column: write them all
f.write ("<tr class=\"vertical-text\">")
f.write("<td></td>")
for unitName in sortedOtherDict:
f.write("<th>" + unitName[0] + "</th>")
f.write("<td class=\"Separator\"></td>")
for unitName in sortedOtherDict:
f.write("<th>" + unitName[0] + "</th>")
f.write("</tr>")
# Write the comparisons
for unitName in sortedDict:
f.write("<tr>\n<th>" + unitName[0] + "</th>")
for otherUnitName in sortedOtherDict:
outcome = Compare2(unitName[1],otherUnitName[1])
green = max(0.0, min(4.0,outcome))
if (green > 1.0):
f.write("<td style=\"background-color: rgb(" + str(int(85*(4.0-green))) + ",255,0)\"></td>")
def cleverParse(diff):
if float(diff) - int(diff) < 0.001:
return str(int(diff))
else:
f.write("<td style=\"background-color: rgb(255," + str(int(255*green)) + ",0" + ")\"></td>")
return str("%.1f" % float(diff))
f.write("<td class=\"Separator\"></td>")
for otherUnitName in sortedOtherDict:
outcome = GetAttackBonus(unitName[1],otherUnitName[1])
green = max(0.0, min(3.0,outcome))
if (green > 1.0):
f.write("<td style=\"background-color: rgb(" + str(int(126*(3.0-green))) + ",255," + str(int(126*(3.0-green))) + ")\"></td>")
if (PositOrNegat == "positive"):
file.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff > 0 else "0,150,0")) + ");\">" + cleverParse(diff) + "</span></td>")
elif (PositOrNegat == "negative"):
file.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + cleverParse(diff) + "</span></td>")
else:
f.write("<td style=\"background-color: rgb(255," + str(int(255*green)) + "," + str(int(255*green)) + ")\"></td>")
f.write("</tr>")
f.write("</table>")
complain
def WriteWorthList(Units):
def SortFn(A):
return CalcValue(A[1])["Total"]
sortedDict = sorted(Units.items(), key=SortFn, reverse=True)
f.write("<table class=\"WorthList\">")
for unitName in sortedDict:
value = CalcValue(unitName[1])
valueAtt = int(2*value["Attack"]/value["Cost"])
valueDef = int(2*value["Defence"]/value["Cost"])
f.write("<tr><th>" + unitName[0] + ": </th><td><span style=\"text-align:right; float:right; background-color:#DA0000; width:" + str(30 + valueAtt/5) + "px;\">" + str(valueAtt) + "</span></td>")
f.write("<td><span style=\"background-color:#0000DD; width:" + str(30 + valueDef/5) + "px;\">" + str(valueDef) + "</span></td>")
f.write("</tr>")
f.write("</table>")
def CivUnitComparisons(CivA, CivB):
averageEffNoCost = 0
MaxMinEffNoCost = 0
MinMaxEffNoCost = 999999
averageEff = 0
MaxMinEff = 0
MinMaxEff = 999999
MaxMinEffUnit = ""
MinMaxEffUnit = ""
for theirUnit in CivB["Units"]:
unitAverageNoCost = 0
unitMaxNoCost = 0
unitAverage = 0
unitMax = 0
for myUnit in CivA["Units"]:
eff = Compare2(CivA["Units"][myUnit],CivB["Units"][theirUnit])
eff = (eff * CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"] * paramRangedCoverage) + eff * (1.0-paramRangedCoverage)
unitAverageNoCost += eff
if (eff > unitMaxNoCost): unitMaxNoCost = eff
eff /= CalcValue(CivA["Units"][myUnit])["Cost"] / CalcValue(CivB["Units"][theirUnit])["Cost"]
unitAverage += eff
if (eff > unitMax): unitMax = eff
unitAverageNoCost /= len(CivB["Units"])
unitAverage /= len(CivB["Units"])
averageEffNoCost += unitAverageNoCost
averageEff += unitAverage
if (unitMax < MinMaxEff):
MinMaxEff = unitMax
MinMaxEffUnit = theirUnit
if (unitMaxNoCost < MinMaxEffNoCost): MinMaxEffNoCost = unitMaxNoCost
for myUnit in CivA["Units"]:
unitMinNoCost = 9999999
unitMin = 9999999
for theirUnit in CivB["Units"]:
eff = Compare2(CivA["Units"][myUnit],CivB["Units"][theirUnit])
eff = (eff * CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"] * paramRangedCoverage) + eff * (1.0-paramRangedCoverage)
if (eff < unitMinNoCost): unitMinNoCost = eff
eff /= CalcValue(CivA["Units"][myUnit])["Cost"] / CalcValue(CivB["Units"][theirUnit])["Cost"]
if (eff < unitMin): unitMin = eff
if (unitMin > MaxMinEff):
MaxMinEff = unitMin
MaxMinEffUnit = myUnit
if (unitMinNoCost > MaxMinEffNoCost): MaxMinEffNoCost = unitMinNoCost
f.write("<tr><th>" + oCiv + "</th>")
averageEffNoCost /= len(CivA["Units"])
averageEff /= len(CivA["Units"])
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEffNoCost-1) * 90)) + "," + str(int((averageEffNoCost-1) * 90)) + ",0,0.7);\">" + str("%.2f" % averageEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEffNoCost-0.3) * 360)) + "," + str(int((MaxMinEffNoCost-0.3) * 360))+ ",0,0.7);\">" + str("%.2f" % MaxMinEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEffNoCost-1) * 130)) + "," + str(int((MinMaxEffNoCost-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEffNoCost) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (averageEff-0.5) * 110)) + "," + str(int((averageEff-0.5) * 110)) + ",0,0.7);\">" + str("%.2f" % averageEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MaxMinEff-0.3) * 360)) + "," + str(int((MaxMinEff-0.3) * 360)) + ",0,0.7);\">" + str("%.2f" % MaxMinEff) + "</td>")
f.write("<td style=\"background-color:rgba(" + str(int(255 - (MinMaxEff-1) * 130)) + "," + str(int((MinMaxEff-1) * 130)) + ",0,0.7);\">" + str("%.2f" % MinMaxEff) + "</td>")
if MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.1:
f.write("<td style=\"background-color:rgb(0,255,0);\">Surely</td>")
elif MaxMinEff <= MinMaxEff and abs(MaxMinEff * MinMaxEff - 1.0) < 0.2:
f.write("<td style=\"background-color:rgb(255,160,0);\">Probably</td>")
elif MaxMinEff > MinMaxEff or MaxMinEff * MinMaxEff > 1.0:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too strong</td>")
else:
f.write("<td style=\"background-color:rgb(255,0,0);\">No, too weak</td>")
f.write("<td class=\"Separator\">" + str(int(CivA["RangedUnitsWorth"] / CivB["RangedUnitsWorth"]*100)) + "%</td><td class=\"UnitTd\">" + MaxMinEffUnit + "</td><td class=\"UnitTd\">" + MinMaxEffUnit + "</td></tr>")
############################################################
############################################################
# Create the HTML file
f = open(os.path.realpath(__file__).replace("unitTables.py","") + 'unit_summary_table.html', 'w')
@ -492,130 +276,137 @@ f.write("\n")
os.chdir(basePath)
#Load values up.
templates = {}; # Values
############################################################
#Whilst loading I also write them.
# Load generic templates
templates = {}
htbout(f,"h2", "Units")
f.write("<table class=\"UnitList\">")
f.write("<tr><th></th><th>HP</th><th>BuildTime</th><th>Speed</th><th>Costs</th><th>Attack (H/P/C)</th><th>Armour(H/P/C)</th><th>Classes</th><th>Efficient against</th><th>Cannot Attack</th></tr>")
for clss in Class:
for tp in Types:
for sbt in Subtypes:
for x in Fix:
template = "template_unit" + clss + "_" + tp + "_" + x + sbt + ".xml"
if (os.path.isfile(template)):
f.write("<table id=\"genericTemplates\">\n")
f.write("<thead><tr>")
f.write("<th></th><th>HP</th> <th>BuildTime</th> <th>Speed(walk)</th> <th colspan=\"3\">Armour</th> <th colspan=\"6\">Attack (DPS)</th> <th colspan=\"5\">Costs</th> <th>Efficient Against</th> </tr>\n")
f.write("<tr class=\"Label\" style=\"border-bottom:1px black solid;\">")
f.write("<th></th><th></th> <th></th> <th></th> <th>H</th><th>P</th><th>C</th> <th>H</th><th>P</th><th>C</th><th>Rate</th><th>Range</th><th>Spread\n(/100m)</th> <th>F</th><th>W</th><th>S</th><th>M</th><th>P</th> <th></th> </tr>\n</thead>\n")
for template in list(glob.glob('template_*.xml')):
if os.path.isfile(template):
found = False
for possParent in LoadTemplatesIfParent:
if hasParentTemplate(template, possParent):
found = True
break
if found == True:
templates[template] = CalcUnit(template)
f.write(WriteUnit(template, templates[template]))
for Tmp in AddedTemplates:
if (os.path.isfile(Tmp)):
templates[Tmp] = CalcUnit(Tmp)
f.write(WriteUnit(Tmp, templates[Tmp]))
f.write("</table>")
f.write("<h2>Unit Comparison Table</h2>\n")
f.write("<p class=\"desc\">The first table shows how strong a unit is against another one. Full green means 4 times as strong, Yellow means equal, Full red means infinitely worse.<br/>The second table shows hardcoded attack counters. Full Green is 3.0. White is 1.0. Red is 0.0.</p>")
if (not paramFactorCounters):
f.write("<p class=\"desc\">The left graph does not account for hardcoded counters here.</p>")
WriteComparisonTable(templates)
f.write("<h2>Worthiness</h2>\n")
f.write("<p class=\"desc\">This gives a representation of how efficient a unit is cost-wise.<br/>Red bars are offensive capabilities, blue bars defensive capabilites.</p>")
f.write("<p class=\"desc\">Costs are estimated as the sum of Food*" + str(paramRessImp["food"]) + " / Wood*" + str(paramRessImp["wood"]) + " / Stone*" + str(paramRessImp["stone"]) + " / Metal*" + str(paramRessImp["metal"]) + "<br/>")
f.write("Population cost is " + ("counted" if paramIncludePopCost else "not counted") + " in the costs.<br/>")
WriteWorthList(templates)
############################################################
# Load Civ specific templates
# Will be loaded by loading buildings, and templates will be set the Civ building, the Civ and in general.
# Allows to avoid special units.
CivData = {};
CivTemplates = {};
CivTemplates = {}
for Civ in Civs:
if ShowCivLists:
htbout(f,"h2", Civ)
f.write("<table class=\"UnitList\">")
CivData[Civ] = { "Units" : {}, "Buildings" : {}, "RangedUnits" : {} }
for building in CivBuildings:
CivData[Civ]["Buildings"][building] = {}
bdTemplate = "./structures/" + Civ + "_" + building + ".xml"
TrainableUnits = []
if (os.path.isfile(bdTemplate)):
Template = ET.parse(bdTemplate)
if (Template.find("./ProductionQueue/Entities") != None):
TrainableUnits = Template.find("./ProductionQueue/Entities").text.replace(" ","").replace("\t","").split("\n")
# We have the templates this building can train.
for UnitFile in TrainableUnits:
CivTemplates[Civ] = {};
# Load all templates that start with that civ indicator
for template in list(glob.glob('units/' + Civ + '_*.xml')):
if os.path.isfile(template):
# filter based on FilterOut
breakIt = False
for filter in FilterOut:
if UnitFile.find(filter) != -1: breakIt = True
if template.find(filter) != -1: breakIt = True
if breakIt: continue
if (os.path.isfile(UnitFile + ".xml")):
templates[UnitFile + ".xml"] = CalcUnit(UnitFile + ".xml") # Parse
CivData[Civ]["Buildings"][building][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
if UnitFile + ".xml" not in CivData[Civ]["Units"]:
CivTemplates[UnitFile + ".xml"] = templates[UnitFile + ".xml"]
CivData[Civ]["Units"][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
if ShowCivLists:
f.write(WriteUnit(UnitFile + ".xml", templates[UnitFile + ".xml"]))
if "Ranged" in templates[UnitFile + ".xml"]["Classes"]:
CivData[Civ]["RangedUnits"][UnitFile + ".xml"] = templates[UnitFile + ".xml"]
f.write("</table>")
# filter based on loaded generic templates
breakIt = True
for possParent in LoadTemplatesIfParent:
if hasParentTemplate(template, possParent):
breakIt = False
break
if breakIt: continue
unit = CalcUnit(template)
# Remove variants for now
if unit["Parent"].find("template_") == -1:
continue
# load template
CivTemplates[Civ][template] = unit
############################################################
f.write("\n\n<h2>Units Specializations</h2>\n")
f.write("<p class=\"desc\">This table compares each template to its parent, showing the differences between the two.<br/>Note that like any table, you can copy/paste this in Excel (or Numbers or ...) and sort it.</p>")
TemplatesByParent = {}
#Get them in the array
for CivUnitTemplate in CivTemplates:
parent = CivTemplates[CivUnitTemplate]["Parent"]
for Civ in Civs:
for CivUnitTemplate in CivTemplates[Civ]:
parent = CivTemplates[Civ][CivUnitTemplate]["Parent"]
if parent in templates and templates[parent]["Civ"] == None:
if parent not in TemplatesByParent:
TemplatesByParent[parent] = []
TemplatesByParent[parent].append( (CivUnitTemplate,CivTemplates[CivUnitTemplate]))
TemplatesByParent[parent].append( (CivUnitTemplate,CivTemplates[Civ][CivUnitTemplate]))
#Sort them by civ and write them in a table.
f.write("<table class=\"TemplateParentComp\">\n")
f.write("<tr class=\"Label\"><td></td><td></td><td style=\"border-left:1px black solid;\">HP</td><td style=\"border-left:1px black solid;\">BuildTime</td><td style=\"border-left:1px black solid;\">Speed(walk)</td><td colspan=\"3\" style=\"border-left:1px black solid;\">Armour</td><td colspan=\"3\" style=\"border-left:1px black solid;\">Attack</td><td colspan=\"5\" style=\"border-left:1px black solid;\">Costs</td><td style=\"border-left:1px black solid;\">Civ</td><td style=\"border-left:1px black solid;\">Worth</td></tr>\n")
f.write("<tr class=\"Label\" style=\"border-bottom:1px black solid;\"><td></td><td></td><td style=\"border-left:1px black solid;\"></td><td style=\"border-left:1px black solid;\"></td><td style=\"border-left:1px black solid;\"></td><td style=\"border-left:1px black solid;\">H</td><td>P</td><td>C</td><td style=\"border-left:1px black solid;\">H</td><td>P</td><td>C</td><td style=\"border-left:1px black solid;\">F</td><td>W</td><td>S</td><td>M</td><td>P</td><td style=\"border-left:1px black solid;\"></td><td style=\"border-left:1px black solid;\"></td></tr>\n<tr>")
f.write("<table id=\"TemplateParentComp\">\n")
f.write("<thead><tr>")
f.write("<th></th><th></th><th>HP</th> <th>BuildTime</th> <th>Speed</th> <th colspan=\"3\">Armour</th> <th colspan=\"6\">Attack</th> <th colspan=\"5\">Costs</th> <th>Civ</th> </tr>\n")
f.write("<tr class=\"Label\" style=\"border-bottom:1px black solid;\">")
f.write("<th></th><th></th><th></th> <th></th> <th></th> <th>H</th><th>P</th><th>C</th> <th>H</th><th>P</th><th>C</th><th>Rate</th><th>Range</th><th>Spread</th> <th>F</th><th>W</th><th>S</th><th>M</th><th>P</th> <th></th> </tr>\n<tr></thead>")
for parent in TemplatesByParent:
TemplatesByParent[parent].sort(key=lambda x : Civs.index(x[1]["Civ"]))
f.write("<th rowspan=\"" + str(len(TemplatesByParent[parent])) + "\">" + parent + "</th>")
for tp in TemplatesByParent[parent]:
f.write("<td class=\"Sub\">" + tp[0] + "</td>")
f.write("<th style='font-size:10px'>" + parent.replace(".xml","").replace("template_","") + "</th>")
f.write("<td class=\"Sub\">" + tp[0].replace(".xml","").replace("units/","") + "</td>")
# HP
diff = int(tp[1]["HP"]) - int(templates[parent]["HP"])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
WriteColouredDiff(f, diff, "negative")
# Build Time
diff = int(tp[1]["BuildTime"]) - int(templates[parent]["BuildTime"])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff > 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
WriteColouredDiff(f, diff, "positive")
# walk speed
diff = float(tp[1]["WalkSpeed"]) - float(templates[parent]["WalkSpeed"])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + str("%.1f" % diff) + "</span></td>")
WriteColouredDiff(f, diff, "negative")
# Armor
for atype in AttackTypes:
diff = float(tp[1]["Armour"][atype]) - float(templates[parent]["Armour"][atype])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
WriteColouredDiff(f, diff, "negative")
attType = ("Ranged" if tp[1]["Ranged"] else "Melee")
# Attack types (DPS) and rate.
attType = ("Ranged" if tp[1]["Ranged"] == True else "Melee")
if tp[1]["RepeatRate"][attType] != "0":
for atype in AttackTypes:
diff = float(tp[1]["Attack"][attType][atype]) - float(templates[parent]["Attack"][attType][atype])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
myDPS = float(tp[1]["Attack"][attType][atype]) / (float(tp[1]["RepeatRate"][attType])/1000.0)
parentDPS = float(templates[parent]["Attack"][attType][atype]) / (float(templates[parent]["RepeatRate"][attType])/1000.0)
WriteColouredDiff(f, myDPS - parentDPS, "negative")
WriteColouredDiff(f, float(tp[1]["RepeatRate"][attType])/1000.0 - float(templates[parent]["RepeatRate"][attType])/1000.0, "negative")
# range and spread
if tp[1]["Ranged"] == True:
WriteColouredDiff(f, float(tp[1]["Range"]) - float(templates[parent]["Range"]), "negative")
mySpread = (float(tp[1]["Spread"]) / (float(tp[1]["Range"]))*100.0)
parentSpread = (float(templates[parent]["Spread"]) / (float(templates[parent]["Range"]))*100.0)
WriteColouredDiff(f, mySpread - parentSpread, "positive")
else:
f.write("<td></td><td></td>")
else:
f.write("<td></td><td></td><td></td><td></td><td></td><td></td>")
for rtype in Resources:
diff = float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff > 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
WriteColouredDiff(f, float(tp[1]["Cost"][rtype]) - float(templates[parent]["Cost"][rtype]), "positive")
diff = float(tp[1]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"])
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff > 0 else "0,150,0")) + ");\">" + str(int(diff)) + "</span></td>")
WriteColouredDiff(f, float(tp[1]["Cost"]["population"]) - float(templates[parent]["Cost"]["population"]), "positive")
f.write("<td>" + tp[1]["Civ"] + "</td>")
diff = float(CalcValue(tp[1])["Total"]) / float(CalcValue(templates[parent])["Total"])-1.0
f.write("<td><span style=\"color:rgb(" +("200,200,200" if diff == 0 else ("180,0,0" if diff < 0.0 else "0,150,0")) + ");\">" + str(int(100*diff)) + "%</span></td>")
f.write("</tr>\n<tr>")
f.write("<table/>")
@ -633,7 +424,7 @@ sortedDict = sorted(templates.items(), key=SortFn)
for tp in sortedDict:
if tp[0] not in TemplatesByParent:
continue
f.write("<tr><th>" + tp[0] +"</th>\n")
f.write("<tr><td>" + tp[0] +"</td>\n")
for civ in Civs:
found = 0
for temp in TemplatesByParent[tp[0]]:
@ -650,50 +441,93 @@ for tp in sortedDict:
f.write("</tr>\n")
f.write("<tr style=\"margin-top:2px;border-top:2px #aaa solid;\"><th style=\"text-align:right; padding-right:10px;\">Total:</th>\n")
for civ in Civs:
f.write("<td style=\"text-align:center;\">" + str(len(CivData[civ]["Units"])) + "</td>\n")
count = 0
for units in CivTemplates[civ]: count += 1
f.write("<td style=\"text-align:center;\">" + str(count) + "</td>\n")
f.write("</tr>\n")
f.write("<table/>")
############################################################
# Writing Civ Specific Comparisons.
f.write("<h2>Civilisation Comparisons</h2>\n")
# Add a simple script to allow filtering on sorting directly in the HTML page.
if AddSortingOverlay:
f.write("<script src=\"tablefilter/tablefilter.js\"></script>\n\
\n\
<script data-config>\n\
\
var cast = function (val) {\n\
console.log(val);\
if (+val != val)\n\
return -999999999999;\n\
return +val;\n\
}\n\
\n\n\
var filtersConfig = {\n\
base_path: 'tablefilter/',\n\
col_0: 'checklist',\n\
alternate_rows: true,\n\
rows_counter: true,\n\
btn_reset: true,\n\
loader: false,\n\
status_bar: false,\n\
mark_active_columns: true,\n\
highlight_keywords: true,\n\
col_number_format: [\n\
'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US'\n\
],\n\
filters_row_index: 2,\n\
headers_row_index: 1,\n\
extensions:[{\
name: 'sort',\
types: [\
'string', 'us', 'us', 'us', 'us', 'us', 'us', 'mytype', 'mytype', 'mytype', 'mytype', 'mytype', 'mytype', 'us', 'us', 'us', 'us', 'us', 'string'\
],\
on_sort_loaded: function(o, sort) {\
sort.addSortType('mytype',cast);\
},\
}],\n\
col_widths: [\n\
null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,'120px'\n\
],\n\
};\n\
var tf = new TableFilter('genericTemplates', filtersConfig,2);\n\
tf.init();\n\
\n\
\
var secondFiltersConfig = {\n\
base_path: 'tablefilter/',\n\
col_0: 'checklist',\n\
col_19: 'checklist',\n\
alternate_rows: true,\n\
rows_counter: true,\n\
btn_reset: true,\n\
loader: false,\n\
status_bar: false,\n\
mark_active_columns: true,\n\
highlight_keywords: true,\n\
col_number_format: [\n\
null, null, 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', 'US', null\n\
],\n\
filters_row_index: 2,\n\
headers_row_index: 1,\n\
extensions:[{\
name: 'sort',\
types: [\
'string', 'string', 'us', 'us', 'us', 'us', 'us', 'us', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'typetwo', 'us', 'us', 'us', 'us', 'us', 'string'\
],\
on_sort_loaded: function(o, sort) {\
sort.addSortType('typetwo',cast);\
},\
}],\n\
col_widths: [\n\
null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null\n\
],\n\
};\n\
\
var tf2 = new TableFilter('TemplateParentComp', secondFiltersConfig,2);\n\
tf2.init();\n\
\n\
</script>\n")
f.write("<p class=\"desc\">The following graphs attempt to do some analysis of civilizations against each other. ")
f.write("Different kinds of data: A matrix of units efficiency in a 1v1 fight, a graph of unit worthiness (power/cost), and a statistical analysis of civilizations against each other:<br/>")
f.write("In this last one in particular, \"Minimal Maximal Efficiency\" is low if there is a unit this civ cannot counter, and \"Maximal Minimal Efficiency\" is high if there is a unit that cannot be countered. If the (minimal) maximal efficiency and the average maximal efficiency are close, this means there are several units for that civ that counter the enemy civ's unit.<br/>The Balance column analyses those two to give some verdict (consider the average effectiveness too though)<br/><br/>Note that \"Counter\" in this data means \"Any unit that is strong against another unit\", so a unit that's simply way stronger than another counts as \"countering it\".<br/>If a civ's average efficiency and average efficiency disregarding costs are very different, this can mean that its costly units are particularly strong (Mauryans fit the bill with elephants).</p>")
for Civ in Civs:
CivData[Civ]["RangedUnitsWorth"] = 0
for unit in CivData[Civ]["RangedUnits"]:
value = CalcValue(CivData[Civ]["RangedUnits"][unit])["Total"]
if paramRangedMode == "Average": CivData[Civ]["RangedUnitsWorth"] += value
elif value > CivData[Civ]["RangedUnitsWorth"]: CivData[Civ]["RangedUnitsWorth"] = value
if (paramRangedMode == "Average" and len(CivData[Civ]["RangedUnits"]) > 0):
CivData[Civ]["RangedUnitsWorth"] /= len(CivData[Civ]["RangedUnits"])
for Civ in Civs:
f.write("<h3>" + Civ + "</h3>")
z = {}
for oCiv in Civs:
if (oCiv != Civ):
z.update(CivData[oCiv]["Units"])
WriteComparisonTable(CivData[Civ]["Units"],z)
WriteWorthList(CivData[Civ]["Units"])
f.write("<p>This Civilization has " + str(len(CivData[Civ]["RangedUnits"])) + " ranged units for a worth of " + str(CivData[Civ]["RangedUnitsWorth"]) + " (method " + ("Average" if paramRangedMode == "Average" else "Max") + "), individually:<br/>")
for unit in CivData[Civ]["RangedUnits"]:
value = CalcValue(CivData[Civ]["RangedUnits"][unit])["Total"]
f.write(unit + " : " + str(value) + "<br/>")
f.write("<p class=\"desc\"><br/>The following table uses the value in the comparison matrix above, factoring in costs (on the 3 columns on the right), and the average efficiency of its ranged units (per ParamRangedCoverage - " + str(paramRangedCoverage) + " right now).</p>")
f.write("<table class=\"AverageComp\">")
f.write("<tr><td></td><td>Average Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Maximal Minimal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Minimal Maximal Efficiency<br/><span style=\"font-size:10px\">(disregards costs)</span></td><td>Average Efficiency</td><td>Maximal Minimal Efficiency</td><td>Minimal Maximal Efficiency</td><td>Balance</td><td>Ranged Efficiency</td><td>Countered the least</td><td>Counters the least</td></tr>")
for oCiv in Civs:
if (oCiv == Civ):
continue
CivUnitComparisons(CivData[Civ],CivData[oCiv])
f.write("</table>")
f.write("</body>\n</html>")