UnitAnalysis
From Wesnoth
Contents |
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'
