Unitanalysis

From Wesnoth

Overview

The Wesnoth Unit Analysis Tool is found at utils/unit.pl. The tool takes a formula and will run it over all the game's units, displaying results.

To run the script in its simplest form we need two things: a formula, and a copy of Wesnoth's cache to get the unit information from. Cache files are found at ~/.wesnoth/cache/ on Unix-like systems and in the Wesnoth installation under userdata/cache on Windows.

A Simple Example

Suppose we wanted to see the hitpoints of all units in the game. We could run this:

perl unit.pl '$hitpoints' < ~/.wesnoth/cache/game.cfg-cache-v1.0-MEDIUM-NORMAL

This will output the following:

80
80
57
42
50
52
24
...lots more numbers...

The hitpoints of each unit in the game. Now, suppose we wanted to see something more interesting: the attack 'power' of each unit's most powerful melee and ranged weapon. We define 'power' as number of strikes multiplied by damage for each strike. We also want to see the damage types of these attacks. Suppose we also wanted to see the names of the units next to their power (assuming we're a complete newbie to Wesnoth who doesn't have all the units memorized yet):

perl unit.pl '$id,$melee_strikes*$melee_damage,$melee_type,$ranged_strikes*$ranged_damage,$ranged_type'

(I omit the feeding of the cache file to the script for brevity, and will do so in all future examples).

We can expect to get results that look like this:

Ancient Lich           24  cold    65  cold
Ancient Wose           50  impact  0
Arch Mage              14  impact  48  fire
Assassin               24  blade   15  blade
Bandit                 32  impact  0
Battle Princess        52  blade   0
Blood Bat              18  blade   0
Bone Shooter           12  blade   27  pierce
Bowman                 12  blade   18  pierce
Cavalier               40  blade   17  pierce
Cavalryman             18  blade   0
Giant Spider           36  blade   24  impact
Chocobone              18  pierce  0
Cockatrice             0           4   cold

Details

The formulae which are entered are actually Perl expressions. Thus you can consult your favorite source of Perl documentation for syntax rules. For those who don't know Perl, most normal mathematical expressions will work.

The variables available to manipulate are,

  • there is a variable for each attribute inside [unit] in WML with the same name and value.
  • the most powerful (using the formula strikes*damage) attack is represented by the variables attack_<attr> where <attr> is name, damage, strikes, type, range, or special. So for instance, attack_name gives the name of the most powerful attack, attack_damage the damage, and so forth.
  • the most powerful attack that has a different range to the most powerful attack overall has variables secondary_<attr>
  • the most powerful melee attack has variables melee_<attr>
  • the most powerful ranged attack has variables ranged_<attr>
  • all attacks are found in the array @attacks, which is an array of references to hashes. Each hash has a key for each attribute found in the [attack] tag.
  • move_<terrain> to represent the movement cost of moving over each terrain type. e.g. move_deep_water is the cost of moving over deep water
  • defense_<terrain> to represent the unit's defense for each terrain type. e.g. defense_deep_water is the defense in deep water
  • resist_<attack type> to represent the unit's resistance to each attack type. e.g. resist_blade is the resistance to blade


Options

The tool has a number of options available to enhance operation. These include,

--filter <expression>: will make the script only display units where <expression> is true. For instance, to display only level one units one could use --filter '$level == 1'

--sortby <expression>: will make the script sort the results by the given expression. For instance, to sort by hitpoints one could use --sortby '$hitpoints'. Multiple comma-separated expressions are allowed, in which case the results are sorted by each expression in turn.

--heading <list>: a comma-separated list of headings to display for each field

--where <expression>: will execute <expression> before executing any formula. This enables one to set up variables that are used in different places in a single place.

--fold <expression>: <expression> will be executed on each column in the results. Takes an input in @items which contains all the items in the column. Returns a single value which will be displayed at the bottom of the column. For instance, to display averages at the bottom one could use --fold 'my $sum = 0; foreach my $item (@items) { $sum += $item; } $sum/@items;'

--prelude <filename>: this operates like --where but it opens the given filename and executes the script found within. For elaborate formulae it is useful to put most of the 'working' into a prelude file, and then change the command line to format the results exactly as wanted.

Examples

Display average resistance for units, and sort by average resistance:

perl unit.pl '$id,$avg_resistance' --sortby '$avg_resistance' --where '$avg_resistance = $resist_blade + $resist_pierce + $resist_impact + $resist_fire + $resist_cold + $resist_holy)/6' --heading 'unit,resistance' --fold 'my $sum = 0; foreach my $item (@items) { $sum += $item; } $sum/@items;'

A prelude file which contains an attempt to assign 'ratings' to each unit:

$deep_water_mod = 0.6/$move_deep_water;
$shallow_water_mod = 1.0/$move_shallow_water;
$swamp_water_mod = 0.7/$move_swamp_water;
$grassland_mod = 1.0/$move_grassland;
$sand_mod = 0.4/$move_sand;
$forest_mod = 0.9/$move_forest;
$hills_mod = 0.7/$move_hills;
$mountains_mod = 0.6/$move_mountains;
$village_mod = 1.0/$move_village;
$castle_mod = 0.3/$move_castle;
$cave_mod = 0.7/$move_cave;
$tundra_mod = 0.4/$move_tundra;

@terrain_weights = (3.0,3.0,2.5,2.5,2.0,2.0,1.5,1.5,1.5,1.0,1.0,1.0);

@move_components = sort {$b <=> $a} ($deep_water_mod,$shallow_water_mod,$swamp_water_mod,$grassland_mod,$sand_mod,$forest_mod,$hills_mod,$mountains_mod,$village_mod,$castle_mod,$cave_mod,$tundra_mod);

$movement_ability = 0;
$terrain_weights_sum = 0;

for($x = 0; $x != @terrain_weights; ++$x) {
	$movement_ability += $movement * $move_components[$x] * $terrain_weights[$x];
	$terrain_weights_sum += $terrain_weights[$x];
}

$movement_ability /= $terrain_weights_sum;

@defense_components = sort {$b <=> $a} ((100/$defense_deep_water)*$deep_water_mod, (100/$defense_shallow_water)*$shallow_water_mod,(100/$defense_swamp_water)*$swamp_water_mod,(100/$defense_grassland)*$grassland_mod,(100/$defense_sand)*$sand_mod,(100/$defense_forest)*$forest_mod,(100/$defense_hills)*$hills_mod,(100/$defense_mountains)*$mountains_mod,(100/$defense_village)*$village_mod,(100/$defense_castle)*$castle_mod+(100/$defense_cave)*$cave_mod,(100/$defense_tundra)*$tundra_mod);

$avg_defense = 0;

for($x = 0; $x != @terrain_weights; ++$x) {
	$avg_defense += $defense_components[$x] * $terrain_weights[$x];
}

$avg_defense /= ($deep_water_mod+$shallow_water_mod+$swamp_water_mod+$grassland_mod+$sand_mod+$forest_mod+$hills_mod+$village_mod+$castle_mod+$cave_mod+$tundra_mod)*$terrain_weights_sum;

($blade_mod,$pierce_mod,$impact_mod,$fire_mod,$cold_mod,$holy_mod) = (120,100,100,50,50,20);

@resistance_components = sort {$b <=> $a} ($blade_mod/$resist_blade,$pierce_mod/$resist_pierce,$impact_mod/$resist_impact,$fire_mod/$resist_fire,$cold_mod/$resist_cold,$holy_mod/$resist_holy);

@resistance_weights = (0.5,0.7,0.8,1.0,1.0,1.0);

$avg_resistance = 0;

for($x = 0; $x != @resistance_weights; ++$x) {
	$avg_resistance += $resistance_components[$x] * $resistance_weights[$x];
}

$effective_hp = $avg_resistance*$avg_defense*$hitpoints;
$attack_power = $attack_damage*$attack_strikes + 0.5*$secondary_damage*$secondary_strikes;
$value = (2+$movement_ability)*(5+$effective_hp)*(4+$attack_power);
$rating = $value/$cost

And a usage of this prelude:

perl unit.pl --prelude prelude.txt --sortby '$rating' '$id,$value,$cost,$rating' --heading 'Unit,Value,Cost,Rating'
This page was last modified on 17 February 2010, at 00:53.