Hello everybody! As, EB for BI going to be ready for release soon I've decided to post the EDU parser and writer I wrote in C# in case somebody might be interested. Click on the spoiler to read it (it's about 1000 lines long):

Spoiler Alert, click show to read: 
PHP Code:
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace 
EBForBIHelper
{
    
internal sealed class Unit
    
{
        const 
int Indent 17;
        
enum UnitCategory byte
        
{
            
infantrycavalrysiegehandlershipnon_combatant
        
}

        
enum UnitClass byte
        
{
            
lightheavymissilespearmen
        
}

        
enum Voice byte
        
{
            
Light_1Medium_1Heavy_1General_1Female_1
        
}

        
enum LeashedAnimal byte
        
{
            
wardogs,
            
pigs
        
}

        
enum MountEffect byte
        
{
            
elephant,
            
camel,
            
horse,
            
chariot
        
}

        [
FlagsAttribute]
        
enum Attribute
        
{
            
/// <summary>
            /// can board ships
            /// </summary>
            
sea_faring                0x00000001,
            
hide_forest                0x00000002//defines where the unit can hide
            
hide_improved_forest    0x00000004,
            
hide_long_grass            0x00000008,
            
hide_anywhere            0x00000010,
            
/// <summary>
            /// Can dig tunnels under walls
            /// </summary>
            
can_sap                    0x00000020,
            
frighten_foot            0x00000040//Cause fear to certain nearby unit types
            
frighten_mounted        0x00000080,
            
/// <summary>
            /// Unit may go out of control when riders lose control of animals
            /// </summary>
            
can_run_amok            0x00000100,
            
/// <summary>
            /// The unit can be used for a named character's bodyguard
            /// </summary>
            
general_unit            0x00000200,
            
cantabrian_circle        0x00000400,
            
/// <summary>
            /// The unit may not be selected in custom battles
            /// </summary>
            
no_custom                0x00000800,
            
/// <summary>
            /// The unit carries a legionary eagle, and gives bonuses to nearby units
            /// </summary>
            
command                    0x00001000,
            
/// <summary>
            /// The unit is s mercenary unit available to all factions
            /// </summary>
            
mercenary_unit            0x00002000,
            
hardy                    0x00004000//The unit takes longer to suffer the effects of fatigue, and recovers from them quicker
            
very_hardy                0x00008000,
            
/// <summary>
            /// Grants attack bonus of +10. It takes 10 seconds to activate, and has a 30 second duration
            /// </summary>
            
warcry                    0x00010000,
            
/// <summary>
            /// The chanting ability of Druids
            /// </summary>
            
druid                    0x00020000,
            
/// <summary>
            /// The screeching ability of Screeching Women
            /// </summary>
            
screeching_women        0x00040000,
            
/// <summary>
            /// (BI only) The unit can swim
            /// </summary>
            
can_swim                0x00080000,
            
/// <summary>
            /// (BI only) This is a horde unit
            /// </summary>
            
can_horde                0x00100000,
            
/// <summary>
            /// (BI only) Gives a legionary name and number to the unit based on the region it is recruited from
            /// </summary>
            
legionary_name            0x00200000,
            
power_charge            0x00400000,
            
general_unit_upgrade    0x00800000
        
}

        
enum PrimaryFormation byte
        
{
            
/// <summary>
            /// The standard infantry formation, really a rectangle
            /// </summary>
            
square,
            
/// <summary>
            /// The round formation that units like Peasants use
            /// </summary>
            
horde
        
}

        
enum SecondaryFormation byte
        
{
            
phalanx,
            
testudo,
            
/// <summary>
            /// cavalry only
            /// </summary>
            
wedge,
            
/// <summary>
            /// BI only
            /// </summary>
            
schiltrom,
            
/// <summary>
            /// BI only
            /// </summary>
            
shield_wall
        
}

        
enum Projectile byte
        
{
            
/// <summary>
            /// Used when unit doesn't have a missile weapon
            /// </summary>
            
no,
            
arrow,
            
arrow_fiery,
            
head,
            
boulder,
            
fiery_boulder,
            
big_boulder,
            
big_fiery_boulder,
            
tarred_rock,
            
stone,
            
bolt,
            
bullet,
            
javelin,
            
pilum,
            
solifera,
            
dart,
            
axe,
            
ballista,
            
flaming_ballista,
            
tower_ballista,
            
tower_flaming_ballista,
            
scorpion,
            
flaming_scorpion,
            
repeating_ballista
        
}

        
enum WeaponType byte
        
{
            
nomeleethrownmissilesiege_missile
        
}

        
enum WeaponTech byte
        
{
            
nosimpleotherbladearcherysiege
        
}

        
enum DamageType byte
        
{
            
nopiercingbluntslashingfire
        
}

        
enum WeaponSound byte
        
{
            
noneknifemaceaxeswordspear
        
}

        [
FlagsAttribute]
        
enum WeaponAttribute short
        
{
            
/// <summary>
            /// Not applicable or none
            /// </summary>
            
no            0x0000,
            
/// <summary>
            /// Missile weapon is thrown just before charging into combat. It can still be used in "Fire At Will" mode.
            /// </summary>
            
prec 0x0001,
            
/// <summary>
            /// The missile type if thrown rather than fired.
            /// </summary>
            
thrown 0x0002,
            
/// <summary>
            /// armour piercing. Only counts half of target's armour
            /// </summary>
            
ap            0x0004,
            
/// <summary>
            /// body piercing. Missile can pass through men and hit those behind
            /// </summary>
            
bp            0x0008,
            
/// <summary>
            /// Used for long spears. +8 vs. cavalry and -4 defense vs infantry
            /// </summary>
            
spear        0x0010,
            
/// <summary>
            /// Same as spear, but unit has less pushing power than Spear.
            /// </summary>
            
light_spear    0x0020,
            
/// <summary>
            /// Use very long pikes. Phalanx capable units only.
            /// </summary>
            
long_pike    0x0040,
            
/// <summary>
            /// Use shorter than normal spears. Phalanx capable units only.
            /// </summary>
            
short_pike    0x0080,
            
/// <summary>
            /// attack may throw target men into the air.
            /// </summary>
            
launching    0x0100,
            
/// <summary>
            /// attack affects an area, not just one soldier
            /// </summary>
            
area        0x0200
        
}

        
enum ArmorSound byte
        
{
            
fleshleathermetal
        
}

        
enum Discipline byte
        
{
            
lownormaldisciplined,
            
/// <summary>
            /// Units may charge without orders
            /// </summary>
            
impetuous,
            
/// <summary>
            /// Berserker units can go berserk (BI Only)
            /// </summary>
            
berserker
        
}

        
enum Training byte
        
{
            
untrained,
            
trained,
            
highly_trained
        
}

        
bool m_updated;
        
/// <summary>
        /// The internal name of the unit. Note this not necessarily the same as the on screen name
        /// </summary>
        
string Type;
        
/// <summary>
        /// The tag used to look up the on screen name
        /// </summary>
        
string Dictionary;
        
/// <summary>
        /// Category and class define the rough type of the unit. They're used for setting some
        /// default attributes and for determining where units go in formation amongst other things
        /// </summary>
        
UnitCategory Category;
        
UnitClass Class;
        
/// <summary>
        /// Used to determine the type of voice used by the unit
        /// </summary>
        
Voice VoiceType;
        
/// <summary>
        /// Name of the soldier model to use (from descr_models_battle.txt)
        /// </summary>
        
string SoldierModel;
        
/// <summary>
        /// the number of ordinary soldiers in the unit
        /// </summary>
        
byte SoldierUnitSize;
        
/// <summary>
        /// the number of extras (pigs dogs, elephants, chariots artillery pieces etc attached to the unit)
        /// </summary>
        
byte SoldierExtras;
        
/// <summary>
        /// the collision mass of the men. 1.0 is normal. Only applies to infantry
        /// </summary>
        
decimal SoldierMass;
        
/// <summary>
        /// Name of officer model. There may be up to 0-3 officer lines per unit
        /// </summary>
        
List<stringOfficers = new List<string>();
        
/// <summary>
        /// Type of ship used if applicable
        /// </summary>
        
string Ship;
        
/// <summary>
        /// Type of siege engine used by unit
        /// </summary>
        
string Engine;
        
/// <summary>
        /// The type of non ridden on animals used by the unit
        /// </summary>
        
LeashedAnimalAnimal;
        
/// <summary>
        /// Type of animal or vehicle ridden on
        /// </summary>
        
string Mount;
        
/// <summary>
        /// Factors to add when in combat against enemy units that have the specified mounts
        /// Up to three factors may be specified, which may be classes of mount, or specific types
        /// </summary>
        
Dictionary<MountEffectshortMountEffects = new Dictionary<MountEffect,short>();
        
/// <summary>
        /// A miscellanious list of attributes and abilities the unit may have.
        /// </summary>
        
Attribute Attributes;

        
/// <summary>
        /// soldier spacing (in metres) side to side for close formation
        /// </summary>
        
decimal FormationNormalSide;
        
/// <summary>
        /// soldier spacing (in metres) front to back for close formation
        /// </summary>
        
decimal FormationNormalFront;
        
/// <summary>
        /// soldier spacing (in metres) side to side for loose formation
        /// </summary>
        
decimal FormationLooseSide;
        
/// <summary>
        /// soldier spacing (in metres) front to back for loose formation
        /// </summary>
        
decimal FormationLooseFront;
        
/// <summary>
        /// default number of ranks for the unit
        /// </summary>
        
byte FormationRanks;
        
/// <summary>
        /// formations possible for the unit
        /// </summary>
        
PrimaryFormation FormationPrimary;
        
SecondaryFormationFormationSecondary;

        
/// <summary>
        /// Hit points of man, followed by hit points of mount or attached animal (if applicable)
        /// Ridden horses and camels do not have separate hit points
        /// </summary>
        
byte[] StatHealth = new byte[2];

        
/// <summary>
        /// Details of unit's primary weapon. If the unit has a missile weapon it must be the primary.
        /// </summary>
        
byte StatPriAttack;
        
/// <summary>
        /// attack bonus factor if charging
        /// </summary>
        
byte StatPriAttackBonus;
        
/// <summary>
        /// missile type fired (no if not a missile weapon type)
        /// </summary>
        
Projectile StatPriProjectile;
        
/// <summary>
        /// range of missile
        /// </summary>
        
decimal StatPriRange;
        
/// <summary>
        /// amount of missile ammunition per man
        /// </summary>
        
ushort StatPriAmmunition;
        
WeaponType StatPriWeaponType;
        
WeaponTech StatPriWeaponTech;
        
DamageType StatPriDamageType;
        
/// <summary>
        /// Sound type when weapon hits
        /// </summary>
        
WeaponSound StatPriWeaponSound;
        
/// <summary>
        /// Min delay between attacks (in 1/10th of a second)
        /// </summary>
        
ushort StatPriAttackDelay;
        
decimal StatPriLethality;

        
WeaponAttribute StatPriAttribute;

        
/// <summary>
        /// Details of secondary weapons. If the unit rides on, or has attached animals or vehicles
        /// then the secondary weapon details refer to their attacks. If the unit has missile weapons
        /// the secondary weapon will be the one used for melee
        /// If the unit has a primary melee weapon, it may have a secondary side arm
        /// </summary>
        
byte StatSecAttack;
        
byte StatSecAttackBonus;
        
Projectile StatSecProjectile;
        
decimal StatSecRange;
        
ushort StatSecAmmunition;
        
WeaponType StatSecWeaponType;
        
WeaponTech StatSecWeaponTech;
        
DamageType StatSecDamageType;
        
WeaponSound StatSecWeaponSound;
        
ushort StatSecAttackDelay;
        
decimal StatSecLethality;

        
WeaponAttribute StatSecAttribute;

        
/// <summary>
        /// Details of the man's defences
        /// </summary>
        
byte StatPriArmourFactor;
        
/// <summary>
        /// defensive skill factor (not used when shot at)
        /// </summary>
        
byte StatPriArmourSkill;
        
/// <summary>
        /// shield factor (only used for attacks from the front of left)
        /// </summary>
        
byte StatPriArmourShield;
        
/// <summary>
        /// sound type when hit
        /// </summary>
        
ArmorSound StatPriArmourSound;

        
/// <summary>
        /// Details of animal's or vehicle's defenses (note riden horses do not have a separate defence)
        /// As per stat_pri_armour, except that the shield entry is ommited
        /// </summary>
        
byte StatSecArmourFactor;
        
byte StatSecArmourSkill;
        
ArmorSound StatSecArmourSound;

        
/// <summary>
        /// Extra fatigue suffered by the unit in hot climates
        /// </summary>
        
byte StatHeat;
        
/// <summary>
        /// Combat modifiers in scrub
        /// </summary>
        
short StatGroundScrub;
        
/// <summary>
        /// Combat modifiers in sand
        /// </summary>
        
short StatGroundSand;
        
/// <summary>
        /// Combat modifiers in forest
        /// </summary>
        
short StatGroundForest;
        
/// <summary>
        /// Combat modifiers in snow
        /// </summary>
        
short StatGroundSnow;

        
/// <summary>
        /// The base morale level., followed by discipline and training
        /// discipline may be normal, low, disciplined or imperuous. Impetuous units may charge without orders
        /// </summary>
        
byte StatMentalMorale;
        
/// <summary>
        /// determines how quickly a unit loses and regains morale
        /// </summary>
        
Discipline StatMentalDiscipline;
        
/// <summary>
        /// determines how tidy the unit's formation is
        /// </summary>
        
Training StatMentalTraining;

        
/// <summary>
        /// Distance from the enemy that the unit will begin charging
        /// </summary>
        
byte StatChargeDist;
        
/// <summary>
        /// An extra delay between missile attacks over that imposed by the animation.
        /// </summary>
        
byte StatFireDelay;
        
/// <summary>
        /// No longer used
        /// </summary>
        
string StatFood;

        
/// <summary>
        /// Number of turns to build
        /// </summary>
        
byte StatCostTurns;
        
/// <summary>
        /// Cost of unit to construct
        /// </summary>
        
ushort StatCostConstruct;
        
/// <summary>
        /// Cost of upkeep
        /// </summary>
        
ushort StatCostUpkeep;
        
/// <summary>
        /// Cost of upgrading weapons
        /// </summary>
        
ushort StatCostUpgradeWeapons;
        
/// <summary>
        /// Cost of upgrading armour
        /// </summary>
        
ushort StatCostUpgradeArmor;
        
/// <summary>
        /// Cost for custom battles
        /// </summary>
        
ushort StatCostCustomBattle;

        
/// <summary>
        /// List of factions and cultures that may have this unit
        /// </summary>
        
string[] Ownership;

        static 
T EnumParse<T>(string s)
        {
            return (
T)Enum.Parse(typeof(T), s);
        }

        
/// <summary>
        /// Parses a bunch of text to assign values to a unit.  If needed, the unit will be updated with new unit features.
        /// </summary>
        
public void Parse(string text)
        {
            foreach (
string szLine in text.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries))
            {
                
//Make sure to replace tabs with spaces in case some foolio threw them in there
                
string szTrimmed szLine.Replace("\t""    ").Substring(Indent);
                
string[] aszSubEntries;
                switch (
szLine.Replace("\t""    ").Substring(0Indent))
                {
                case 
"type             ":
                    
Type szTrimmed;
                    break;
                case 
"dictionary       ":
                    
Dictionary szTrimmed;
                    break;
                case 
"category         ":
                    
Category EnumParse<UnitCategory>(szTrimmed);
                    break;
                case 
"class            ":
                    Class = 
EnumParse<UnitClass>(szTrimmed);
                    break;
                case 
"voice_type       ":
                    
VoiceType EnumParse<Voice>(szTrimmed);
                    break;
                case 
"soldier          ":
                    
aszSubEntries szTrimmed.Split(',');
                    
SoldierModel aszSubEntries[0];
                    
SoldierUnitSize byte.Parse(aszSubEntries[1]);
                    
SoldierExtras byte.Parse(aszSubEntries[2]);
                    
SoldierMass decimal.Parse(aszSubEntries[3], NumberStyles.Float);
                    break;
                case 
"officer          ":
                    
Officers.Add(szTrimmed);
                    break;
                case 
"ship             ":
                    
Ship szTrimmed;
                    break;
                case 
"engine           ":
                    
Engine szTrimmed;
                    break;
                case 
"animal           ":
                    
Animal EnumParse<LeashedAnimal>(szTrimmed);
                    break;
                case 
"mount            ":
                    
Mount szTrimmed;
                    break;
                case 
"mount_effect     ":
                    
aszSubEntries szTrimmed.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    for (
int i 0aszSubEntries.Length+= 2)
                    {
                        
MountEffects.Add(EnumParse<MountEffect>(aszSubEntries[i]), short.Parse(aszSubEntries[1]));
                    }
                    break;
                case 
"attributes       ":
                    
int nComment szTrimmed.IndexOf(';');
                    if (
nComment >= 0)
                    {
                        
szTrimmed szTrimmed.Remove(nComment).TrimEnd(','' ');
                    }
                    
Attributes EnumParse<Attribute>(szTrimmed);
                    break;
                case 
"formation        ":
                    
aszSubEntries szTrimmed.Split(',');
                    
FormationNormalSide decimal.Parse(aszSubEntries[0], NumberStyles.Float);
                    
FormationNormalFront decimal.Parse(aszSubEntries[1], NumberStyles.Float);
                    
FormationLooseSide decimal.Parse(aszSubEntries[2], NumberStyles.Float);
                    
FormationLooseFront decimal.Parse(aszSubEntries[3], NumberStyles.Float);
                    
FormationRanks byte.Parse(aszSubEntries[4]);
                    
FormationPrimary EnumParse<PrimaryFormation>(aszSubEntries[5]);
                    if (
aszSubEntries.Length == 7)
                        
FormationSecondary EnumParse<SecondaryFormation>(aszSubEntries[6]);
                    break;
                case 
"stat_health      ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatHealth[0] = byte.Parse(aszSubEntries[0]);
                    
StatHealth[1] = byte.Parse(aszSubEntries[1]);
                    break;
                case 
"stat_pri         ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatPriAttack byte.Parse(aszSubEntries[0]);
                    
StatPriAttackBonus byte.Parse(aszSubEntries[1]);
                    
StatPriProjectile EnumParse<Projectile>(aszSubEntries[2]);
                    
StatPriRange decimal.Parse(aszSubEntries[3], NumberStyles.Float);
                    
StatPriAmmunition ushort.Parse(aszSubEntries[4]);
                    
StatPriWeaponType EnumParse<WeaponType>(aszSubEntries[5]);
                    
StatPriWeaponTech EnumParse<WeaponTech>(aszSubEntries[6]);
                    
StatPriDamageType EnumParse<DamageType>(aszSubEntries[7]);
                    
StatPriWeaponSound EnumParse<WeaponSound>(aszSubEntries[8]);
                    
StatPriAttackDelay ushort.Parse(aszSubEntries[9]);
                    
StatPriLethality decimal.Parse(aszSubEntries[10], NumberStyles.Float);
                    break;
                case 
"stat_pri_attr    ":
                    
StatPriAttribute EnumParse<WeaponAttribute>(szTrimmed);
                    break;
                case 
"stat_sec         ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatSecAttack byte.Parse(aszSubEntries[0]);
                    
StatSecAttackBonus byte.Parse(aszSubEntries[1]);
                    
StatSecProjectile EnumParse<Projectile>(aszSubEntries[2]);
                    
StatSecRange decimal.Parse(aszSubEntries[3], NumberStyles.Float);
                    
StatSecAmmunition ushort.Parse(aszSubEntries[4]);
                    
StatSecWeaponType EnumParse<WeaponType>(aszSubEntries[5]);
                    
StatSecWeaponTech EnumParse<WeaponTech>(aszSubEntries[6]);
                    
StatSecDamageType EnumParse<DamageType>(aszSubEntries[7]);
                    
StatSecWeaponSound EnumParse<WeaponSound>(aszSubEntries[8]);
                    
StatSecAttackDelay ushort.Parse(aszSubEntries[9]);
                    
StatSecLethality decimal.Parse(aszSubEntries[10], NumberStyles.Float);
                    break;
                case 
"stat_sec_attr    ":
                    
szTrimmed szTrimmed.TrimEnd(','' ');
                    
StatSecAttribute EnumParse<WeaponAttribute>(szTrimmed);
                    break;
                case 
"stat_pri_armour  ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatPriArmourFactor byte.Parse(aszSubEntries[0]);
                    
StatPriArmourSkill byte.Parse(aszSubEntries[1]);
                    
StatPriArmourShield byte.Parse(aszSubEntries[2]);
                    
StatPriArmourSound EnumParse<ArmorSound>(aszSubEntries[3]);
                    break;
                case 
"stat_sec_armour  ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatSecArmourFactor byte.Parse(aszSubEntries[0]);
                    
StatSecArmourSkill byte.Parse(aszSubEntries[1]);
                    
StatSecArmourSound EnumParse<ArmorSound>(aszSubEntries[2]);
                    break;
                case 
"stat_heat        ":
                    
StatHeat byte.Parse(szTrimmed);
                    break;
                case 
"stat_ground      ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatGroundScrub short.Parse(aszSubEntries[0]);
                    
StatGroundSand short.Parse(aszSubEntries[1]);
                    
StatGroundForest short.Parse(aszSubEntries[2]);
                    
StatGroundSnow short.Parse(aszSubEntries[3]);
                    break;
                case 
"stat_mental      ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatMentalMorale byte.Parse(aszSubEntries[0]);
                    
StatMentalDiscipline EnumParse<Discipline>(aszSubEntries[1]);
                    
StatMentalTraining EnumParse<Training>(aszSubEntries[2]);
                    break;
                case 
"stat_charge_dist ":
                    
StatChargeDist byte.Parse(szTrimmed);
                    break;
                case 
"stat_fire_delay  ":
                    
StatFireDelay byte.Parse(szTrimmed);
                    break;
                case 
"stat_food        ":
                    
StatFood szTrimmed;
                    break;
                case 
"stat_cost        ":
                    
aszSubEntries szTrimmed.Split(',');
                    
StatCostTurns byte.Parse(aszSubEntries[0]);
                    
StatCostConstruct ushort.Parse(aszSubEntries[1]);
                    
StatCostUpkeep ushort.Parse(aszSubEntries[2]);
                    
StatCostUpgradeWeapons ushort.Parse(aszSubEntries[3]);
                    
StatCostUpgradeArmor ushort.Parse(aszSubEntries[4]);
                    
StatCostCustomBattle ushort.Parse(aszSubEntries[5]);
                    break;
                case 
"ownership        ":
                    
Ownership szTrimmed.Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                    break;
                }
            }
        }

        
/// <summary>
        /// Adds BI-only features to this unit
        /// </summary>
        
public void UpdateToBI()
        {
            
//Append can_swim attribute to:
            //+ Cavalry units with armor less than or equal to 10
            //+ Infantry units with armor less than or equal to 5
            //    - Except for desert peoples: Garamantines, Maures, Numidian, African Slingers, arab (with space), Bnei Shevet Aravim, 
            //        Sabean,
            //    - Except for chariots
            
if((    (Category == UnitCategory.cavalry) && (StatPriArmourFactor <= 10) &&
                    !
Mount.Contains("chariot")) ||
                (    (
Category == UnitCategory.infantry) && (StatPriArmourFactor <= 5) &&
                    !
Dictionary.Contains("Numidian") &&
                    !
Dictionary.Contains("Garamantines") &&
                    !
SoldierModel.Contains("redsea_axemen") &&
                    !
Dictionary.Contains("Sabean") &&
                    !
Dictionary.Contains("Maures") &&
                    !
Dictionary.Contains("African Slingers") &&
                    !
Dictionary.Contains("arab ") &&
                    !
Dictionary.Contains("Bnei Shevet Aravim")))
            {
                
Attributes |= Attribute.can_swim;
                
m_updated true;
            }

            
//Append warcry attribute to:
            //+ various angry savages
            
if ((Category == UnitCategory.infantry) &&
                (    
SoldierModel.Contains("gaesatae") ||
                    
SoldierModel.Contains("wodanawulfoz") ||
                    
Dictionary.Contains("Caturiges Spearmen") ||
                    
Dictionary.Contains("Balroae") ||
                    
Dictionary.Contains("Taxeis Triballoi") ||
                    
Dictionary.Contains("Druhtiz Skandzisku") ||
                    
Dictionary.Contains("Chatti Clubmen") ||
                    
Dictionary.Contains("Wargoz") ||
                    
Dictionary.Contains("Goidilic Ordmhornaght") ||
                    
Dictionary.Contains("Roscaithrera") ||
                    
Dictionary.Contains("Voinu Spearmen")))
            {
                
Attributes |= Attribute.warcry;
                
m_updated true;
            }


            
//assign shield_wall as secondary formation to:
            //+ hoplite
            //+ pseudoshieldwall
            //+ disciplined / trained with large shields
            
if ((Category == UnitCategory.infantry) &&
                (
FormationSecondary != SecondaryFormation.phalanx) &&
                (
FormationSecondary != SecondaryFormation.testudo) &&
                ((
Attributes Attribute.warcry) == 0))
            {
                if(    
Dictionary.Contains("Hoplit") ||
                    
SoldierModel.Contains("hoplit") ||
                    (
FormationNormalSide 0.8m))
                {
                    
FormationNormalSide 1.2m;
                    
FormationNormalFront 1.5m;
                    
FormationLooseSide 2.4m;
                    
FormationLooseFront 3;
                    
FormationSecondary SecondaryFormation.shield_wall;
                    
m_updated true;
                }
                else if ((
StatPriArmourShield >= 3) &&
                    !
Type.Contains("skirmisher") &&
                    ((
StatMentalDiscipline == Discipline.disciplined) ||
                    (
StatMentalTraining == Training.highly_trained)))
                {
                    
FormationNormalSide 1.3m;
                    
FormationNormalFront 1.7m;
                    
FormationLooseSide 2.6m;
                    
FormationLooseFront 3.4m;
                    
FormationSecondary SecondaryFormation.shield_wall;
                    
m_updated true;
                }
            }
        }

        
/// <summary>
        /// Indicates that this unit has been modified with Barbarian Invasion features.
        /// </summary>
        
public bool Updated
        
{
            
get
            
{
                return 
m_updated;
            }
        }

        
/// <summary>
        /// Get the text representation of this unit, from 'type' to the end of the 'ownership' line.
        /// </summary>
        /// <returns></returns>
        
public override string ToString()
        {
            
StringBuilder sb = new StringBuilder();

            
sb.Append("type             ");
            
sb.AppendLine(Type);

            
sb.Append("dictionary       ");
            
sb.AppendLine(Dictionary);

            
sb.Append("category         ");
            
sb.Append(Category);
            
sb.AppendLine();

            
sb.Append("class            ");
            
sb.Append(Class);
            
sb.AppendLine();

            
sb.Append("voice_type       ");
            
sb.Append(VoiceType);
            
sb.AppendLine();

            
sb.Append("soldier          ");
            
sb.Append(SoldierModel);
            
sb.Append(", ");
            
sb.Append(SoldierUnitSize);
            
sb.Append(", ");
            
sb.Append(SoldierExtras);
            
sb.Append(", ");
            
sb.Append(SoldierMass);
            
sb.AppendLine();

            
Officers.ForEach(delegate(string szOfficer)
            {
                
sb.Append("officer          ");
                
sb.AppendLine(szOfficer);
            });

            if (!
string.IsNullOrEmpty(Ship))
            {
                
sb.Append("ship             ");
                
sb.AppendLine(Ship);
            }

            if (!
string.IsNullOrEmpty(Engine))
            {
                
sb.Append("engine           ");
                
sb.AppendLine(Engine);
            }

            if (
Animal.HasValue)
            {
                
sb.Append("animal           ");
                
sb.Append(Animal);
                
sb.AppendLine();
            }

            if (!
string.IsNullOrEmpty(Mount))
            {
                
sb.Append("mount            ");
                
sb.AppendLine(Mount);
            }

            if (
MountEffects.Count 0)
            {
                
sb.Append("mount_effect     ");
                
bool bFirst true;
                foreach (
KeyValuePair<MountEffectshortpair in MountEffects)
                {
                    if (
bFirst)
                        
bFirst false;
                    else
                        
sb.Append(", ");
                    
sb.Append(pair.Key);
                    
sb.Append(' ');
                    if (
pair.Value 0)
                        
sb.Append('+');
                    
sb.Append(pair.Value);
                }
                
sb.AppendLine();
            }

            
sb.Append("attributes       ");
            
sb.Append(Attributes);
            
sb.AppendLine();

            
sb.Append("formation        ");
            
sb.Append(FormationNormalSide);
            
sb.Append(", ");
            
sb.Append(FormationNormalFront);
            
sb.Append(", ");
            
sb.Append(FormationLooseSide);
            
sb.Append(", ");
            
sb.Append(FormationLooseFront);
            
sb.Append(", ");
            
sb.Append(FormationRanks);
            
sb.Append(", ");
            
sb.Append(FormationPrimary);
            if (
FormationSecondary.HasValue)
            {
                
sb.Append(", ");
                
sb.Append(FormationSecondary);
            }
            
sb.AppendLine();

            
sb.Append("stat_health      ");
            
sb.AppendLine(string.Join(", ", Array.ConvertAll<bytestring>(StatHealthConvert.ToString)));

            
sb.Append("stat_pri         ");
            
sb.Append(StatPriAttack);
            
sb.Append(", ");
            
sb.Append(StatPriAttackBonus);
            
sb.Append(", ");
            
sb.Append(StatPriProjectile);
            
sb.Append(", ");
            
sb.Append(StatPriRange);
            
sb.Append(", ");
            
sb.Append(StatPriAmmunition);
            
sb.Append(", ");
            
sb.Append(StatPriWeaponType);
            
sb.Append(", ");
            
sb.Append(StatPriWeaponTech);
            
sb.Append(", ");
            
sb.Append(StatPriDamageType);
            
sb.Append(", ");
            
sb.Append(StatPriWeaponSound);
            
sb.Append(", ");
            
sb.Append(StatPriAttackDelay);
            
//Lethality is messed up.  It needs a space and *then* a comma
            
sb.Append(" ,");
            
sb.Append(StatPriLethality);
            
sb.AppendLine();

            
sb.Append("stat_pri_attr    ");
            
sb.Append(StatPriAttribute);
            
sb.AppendLine();

            
sb.Append("stat_sec         ");
            
sb.Append(StatSecAttack);
            
sb.Append(", ");
            
sb.Append(StatSecAttackBonus);
            
sb.Append(", ");
            
sb.Append(StatSecProjectile);
            
sb.Append(", ");
            
sb.Append(StatSecRange);
            
sb.Append(", ");
            
sb.Append(StatSecAmmunition);
            
sb.Append(", ");
            
sb.Append(StatSecWeaponType);
            
sb.Append(", ");
            
sb.Append(StatSecWeaponTech);
            
sb.Append(", ");
            
sb.Append(StatSecDamageType);
            
sb.Append(", ");
            
sb.Append(StatSecWeaponSound);
            
sb.Append(", ");
            
sb.Append(StatSecAttackDelay);
            
//Lethality is messed up.  It needs a space and *then* a comma
            
sb.Append(" ,");
            
sb.Append(StatSecLethality);
            
sb.AppendLine();

            
sb.Append("stat_sec_attr    ");
            
sb.Append(StatSecAttribute);
            
sb.AppendLine();

            
sb.Append("stat_pri_armour  ");
            
sb.Append(StatPriArmourFactor);
            
sb.Append(", ");
            
sb.Append(StatPriArmourSkill);
            
sb.Append(", ");
            
sb.Append(StatPriArmourShield);
            
sb.Append(", ");
            
sb.Append(StatPriArmourSound);
            
sb.AppendLine();

            
sb.Append("stat_sec_armour  ");
            
sb.Append(StatSecArmourFactor);
            
sb.Append(", ");
            
sb.Append(StatSecArmourSkill);
            
sb.Append(", ");
            
sb.Append(StatSecArmourSound);
            
sb.AppendLine();

            
sb.Append("stat_heat        ");
            
sb.Append(StatHeat);
            
sb.AppendLine();

            
sb.Append("stat_ground      ");
            
sb.Append(StatGroundScrub);
            
sb.Append(", ");
            
sb.Append(StatGroundSand);
            
sb.Append(", ");
            
sb.Append(StatGroundForest);
            
sb.Append(", ");
            
sb.Append(StatGroundSnow);
            
sb.AppendLine();

            
sb.Append("stat_mental      ");
            
sb.Append(StatMentalMorale);
            
sb.Append(", ");
            
sb.Append(StatMentalDiscipline);
            
sb.Append(", ");
            
sb.Append(StatMentalTraining);
            
sb.AppendLine();

            
sb.Append("stat_charge_dist ");
            
sb.Append(StatChargeDist);
            
sb.AppendLine();

            
sb.Append("stat_fire_delay  ");
            
sb.Append(StatFireDelay);
            
sb.AppendLine();

            
sb.Append("stat_food        ");
            
sb.AppendLine(StatFood);

            
sb.Append("stat_cost        ");
            
sb.Append(StatCostTurns);
            
sb.Append(", ");
            
sb.Append(StatCostConstruct);
            
sb.Append(", ");
            
sb.Append(StatCostUpkeep);
            
sb.Append(", ");
            
sb.Append(StatCostUpgradeWeapons);
            
sb.Append(", ");
            
sb.Append(StatCostUpgradeArmor);
            
sb.Append(", ");
            
sb.Append(StatCostCustomBattle);
            
sb.AppendLine();

            
//note: after ownership there is no endline
            
sb.Append("ownership        ");
            
sb.Append(string.Join(", "Ownership));

            return 
sb.ToString();
        }
    }