StarCraft 2: Работа с РО в промышленных масштабах - Часть II

Работа с РО в промышленных масштабах - Введение

Итак, мы научились описывать различные структуры. Теперь перейдём к написанию инициализатора.

План работы на сегодня

Итак, план:
  1. (*) Научиться извлекать файлы из Mpq-архивов.
  2. Написать инициализатор типа "Показатель", который:
    • принимает 2 аргумента (тип атрибута и является ли он основным).
    • читал бы из архивов Варкрафта соответствующие поля и записывал их значения.
  3. Написать инициализатор типа "Получение опыта", который:
    • принимает аргумент - id героя Варкрафта.
    • возвращает алгоритм, соответствующий именно этому герою.
    • при этом читает архив вц3, и достаёт оттуда всю нужную инфу.
    • и после этого обрабатывает её (считает).
  4. Автоматически прогнать все алгоритмы.
  5. Конвертировать сии структуры в xml-код старкрафта.
  6. Забить всё это дело в архив карты.
Что ж, приступим...

Часть 2, Археологическая и рабоче-крестьянская

Почему археологическая? Мы ведь залезем в архивы варкрафта. =)

Шаг 3.

Вообще на этом и следующим за ним шагах мы должны найти нужные нам источники и использовать их. То есть, если бы делали конвертер эффектов урона из строковой формулы типа "15 + 3D12" (15 базово + 3 случайных значения от 1 до 12) или "2*8 + 5" (2 атаки по 8 ед., и после этого ещё одна на 5), то на выходе мы должны были получить массив объектов типа Эффект разных подтипов (урон, периодический и т.д.).
А в нашем случае - это пункты №1 и №2 из нашего плана.
Для начала мы создаём инициализатор-полупустышку. Понимаю, звучит странно (страшно?), но попытаюсь объяснить, что это за зверь. Мы создаём инициализатор-пустышку, и начинаем присваивать значения так же, как и в предположительно конечном варианте. А поля присвоения оставляем пустыми, кое-где оставляя комменты. Некоторые поля можно сразу же заполнить константными или легко вычисляемыми значениями.
» Образец:
Для большей наглядности взят пример из способа исполнения с помощью наследования.
/// <summary>
/// Создаст 1 из 3 алгоритмов основных параметров героя.
/// </summary>
/// <param name="Par">
/// Индекс атрибута. Введите:
/// 0 - Сила;
/// 1 - Ловкость;
/// 2 - Разум.
/// </param>
/// <param name="Main">Является ли параметр основным.</param>
public Attribute(int Id, bool Main)
{
    Attribute Result = new Attribute();

    Result.Type = "CBehaviorAttribute";
    Result.MaxPoints = 999999999;
    Result.Permanent = true;
    Result.AbilityorEffectType = "Unit";
    Result.IsMain = Main;
    Result.Modification = new Modifications();

    switch (Id)
    {
        #region Strength
        case 0:
            Result.Id = "Strength";
            Result.NameValue;

            Result.Modification.VitalBonus;
            Result.Modification.VitalRegenBonus;

            break;
        #endregion
        #region Agility
        case 1:
            Result.Id = "Agility";
            Result.NameValue;

            Result.Modification.MovementSpeedBonus;
            Result.Modification.AttackSpeedBonus;
            Result.Modification.VitalArmorBonus;

            break;
        #endregion
        #region Intellect
        case 2:
            Result.Id = "Intellect";
            Result.NameValue;

            Result.Modification.EnergyBonus;
            Result.Modification.EnergyRegenBonus;

            break;
        #endregion
    }
    Result.Name = "Behavior/HeroAttribute/" + Result.Id;
    if (Main)
    {
        Result.Modification.AttackBonus;
        Result.Id += "Main";
        EditorSuffix = "Behavior/HeroAttribute/Main/Suffix";
    }

    EditorPrefix = "Behavior/HeroAttribute/Prefix";

    this = Result;
}
Затем мы переходим к заполнению этих полей.

Шаг 4.

Здесь мы закончим заполнение полей инициализатора.
Мы расставляем все формулы, юзаем функции итд итп.
Как я делал атрибуты:
  1. Поиск полей.
    1. Запустил World Editor.
    2. Зашёл в игровые константы.
    3. Нашёл раздел "Характеристики героев".
    4. Нажал Ctrl+D, тем самым показав названия переменных.
    5. Записал их.
  2. Поиск того, где они лежат.
    1. Запустил браузер Mpq-архивов (я использовал W3ME).
    2. Открыл "war3patch.mpq".
    3. Открыл "(listfile)".
    4. Вбил в поисковик по файлу "misc" - фрагмент названия файла констант в карте.
    5. Нашёл какой-то файл. Это оно. ("Units\MiscGame.txt")
  3. Написал функцию, которая:
    1. Открыла mpq.
    2. Открыла этот файл.
    3. Построчно его прочитала.
    4. Нашла нужное значение.
    5. Вернула его.
  4. Присвоил полям эту функцию с выписанными ранее аргументами.
  5. Повторил то же самое с геройскими левелами. Только там лазал по РО и slk-шкам.
Итого, мы получили 2 полноценных инициализатора.
По идее. Но вот если вы использовали только один тип - алгоритм - и поле XmlCode, то у вас непременно должен был возникнуть вопрос: куда вписывать значения?
Их надо писать по такому принципу:
Result.XmlCode[0] = "        <Modification>";
Result.XmlCode[1] = "            <VitalMaxArray index=\"Life\" value=\""
    + Mpq.File.ReadFromConstants("StrHitPointBonus") + "\"/>";
Да, получается, вам придётся так вот вбивать всю xml-структуру целиком. Хотя нет, не всю. Только ту, что содержится в параметрах.
Итого, мы получили 2 полноценных инициализатора.
» Код - способ 1:
Здесь показан пример без использования форматной строки. Посмотрев и сравнив его со следующим, вы сразу её полюбите. =)
/// <summary>
/// Создаст 1 из 3 алгоритмов основных параметров героя.
/// </summary>
/// <param name="Par">
/// Индекс атрибута. Введите:
/// 0 - Сила;
/// 1 - Ловкость;
/// 2 - Разум.
/// </param>
/// <param name="Main">Является ли параметр основным.</param>
public Behavior(int Id, bool Main)
{
    Behavior Result = new Behavior();

    Result.Type = "CBehaviorAttribute";
    Result.MaxPoints = 999999999;
    Result.Permanent = true;
    Result.AbilityorEffectType = "Unit";
    switch (Id)
    {
        #region Strength
        case 0:
            Result.Id = "Strength";
            Result.Name = Interface.FindInStringList("STRENGTH");

            Result.Icon = "";

            #region Xml
            Result.XmlCode = new string[4 + Convert.ToInt16(Main) * 3];
            Result.XmlCode[0] = "        <Modification>";
            Result.XmlCode[1] = "            <VitalMaxArray index=\"Life\" value=\""
                + Mpq.File.ReadFromConstants("StrHitPointBonus") + "\"/>";

            Result.XmlCode[2] = "            <VitalRegenArray index=\"Life\" value=\""
                + Mpq.File.ReadFromConstants("StrRegenBonus") + "\"/>";

            if (Main)
            {
                Result.XmlCode[3] = "            <DamageDealtScaled index=\"Melee\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
                Result.XmlCode[4] = "            <DamageDealtScaled index=\"Ranged\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
            }

            Result.XmlCode[3 + Convert.ToInt16(Main) * 2] = "        </Modification>";

            if (Main)
            {
                Result.XmlCode[6] = "            <Name value=\"" + "Behavior/Name/" + Result.Id + "\"/>";
                Result.Id += "Main";
            }
            #endregion

            break;
        #endregion
        #region Agility
        case 1:
            Result.Id = "Agility";
            Result.Name = Interface.FindInStringList("Agility");

            Result.Icon = "";

            #region Xml
            Result.XmlCode = new string[5 + Convert.ToInt16(Main) * 3];
            Result.XmlCode[0] = "        <Modification>";
            Result.XmlCode[1] = "            <MoveSpeedBonus value=\""
                + Mpq.File.ReadFromConstants("AgiMoveBonus") + "\"/>";

            Result.XmlCode[2] = "            <AttackSpeedMultiplier value=\""
                + (Convert.ToDouble(Mpq.File.ReadFromConstants("AgiAttackSpeedBonus")) + 1).ToString() + "\"/>";

            Result.XmlCode[3] = "            <LifeArmorBonus value=\""
                + Mpq.File.ReadFromConstants("AgiDefenseBonus") + "\"/>";

            if (Main)
            {
                Result.XmlCode[4] = "            <DamageDealtScaled index=\"Melee\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
                Result.XmlCode[5] = "            <DamageDealtScaled index=\"Ranged\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
            }

            Result.XmlCode[4 + Convert.ToInt16(Main) * 2] = "        </Modification>";

            if (Main)
            {
                Result.XmlCode[7] = "            <Name value=\"" + "Behavior/Name/" + Result.Id + "\"/>";
                Result.Id += "Main";
            }
            #endregion

            break;
        #endregion
        #region Intellect
        case 2:
            Result.Id = "Intellect";
            Result.Name = Interface.FindInStringList("Intellect");

            Result.Icon = "";

            #region Xml
            Result.XmlCode = new string[4 + Convert.ToInt16(Main) * 3];
            Result.XmlCode[0] = "        <Modification>";
            Result.XmlCode[1] = "            <VitalMaxArray index=\"Energy\" value=\""
                + Mpq.File.ReadFromConstants("IntManaBonus") + "\"/>";

            Result.XmlCode[2] = "            <VitalRegenArray index=\"Energy\" value=\""
                + Mpq.File.ReadFromConstants("IntRegenBonus") + "\"/>";

            if (Main)
            {
                Result.XmlCode[3] = "            <DamageDealtScaled index=\"Melee\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
                Result.XmlCode[4] = "            <DamageDealtScaled index=\"Ranged\" value=\""
                    + Mpq.File.ReadFromConstants("StrAttackBonus") + "\"/>";
            }
            Result.XmlCode[3 + Convert.ToInt16(Main) * 2] = "        </Modification>";

            if (Main)
            {
                Result.XmlCode[6] = "            <Name value=\"" + "Behavior/Name/" + Result.Id + "\"/>";
                Result.Id += "Main";
            }
            #endregion

            break;
        #endregion
    }

    if (Main)
        EditorSuffix = " - Основной";
    EditorPrefix = "Параметр героя - ";

    this = Result;
}
И второй инициализатор:
/// <summary>
/// Создаёт алгоритм типа "Получение опыта". Это для прокачки героев.
/// </summary>
/// <param name="HeroWarId">Id героя в варкрафте.</param>
public Behavior(string HeroWarId)
{
    Behavior Result = new Behavior();

    Warcraft3.RaceConstructor.Unit u = Warcraft3.RaceConstructor.GetUnitById(HeroWarId); // Эта функция возвращает загруженного заранее юнита по его Id.

    Result.Type = "CBehaviorVeterancy";
    Result.Id = u.StarId; // Это поле возвращает хитроумно вычисляемый Id юнита для старкрафта.
    Result.Permanent = true;
    Result.MaxPoints = 255; //По умолчанию.
    Result.AbilityorEffectType = "Units";
    Result.EditorSuffix = " - Уровни";
    Result.Race = u.Race;

    #region Xml

    Result.XmlCode = new string[0];

    string[] GrantXPBaseList = Mpq.File.ReadFromConstants("GrantHeroXP").Split(','); // Эта функция возвращает игровую константу.
    double[] GrantHeroXP = new double[GrantXPBaseList.Length + 1];

    for (int i = 0; i < GrantXPBaseList.Length; i++)
        GrantHeroXP[i + 1] = Convert.ToDouble(GrantXPBaseList[i]);

    double GrantHeroXPFormulaA = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaA"));
    double GrantHeroXPFormulaB = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaB"));
    double GrantHeroXPFormulaC = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaC"));
    // Это данные формулы. Формула приводится в статье "Осваиваем WorldEdit" (или как-то так).

    int MaxHeroLevel = Convert.ToInt32(Mpq.File.ReadFromConstants("MaxHeroLevel"));
    double Last = 0;

    int Agi = Convert.ToInt32(u.GetValue("Agi")); // Эта функция возвращает какое-то из значений юнита по id или названию этого поля.
    int Str = Convert.ToInt32(u.GetValue("Str"));
    int Int = Convert.ToInt32(u.GetValue("Int"));

    double AgiPlus = Convert.ToDouble(u.GetValue("AgiPlus"));
    double StrPlus = Convert.ToDouble(u.GetValue("StrPlus"));
    double IntPlus = Convert.ToDouble(u.GetValue("IntPlus"));

    string Strength = "Strength";
    string Agility = "Agility";
    string Intellect = "Intellect";

    string Main = u.GetValue("Primary").ToLower();
    switch (Main)
    {
        case "str":
            Strength += "Main";
            break;
        case "agi":
            Agility += "Main";
            break;
        case "int":
            Intellect += "Main";
            break;
    }

    for (int i = 0; i < MaxHeroLevel; i++)
    {
        Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 1);
        if (i == 0)
        {
            Result.XmlCode[Result.XmlCode.Length - 1] = "        <VeterancyLevelArray>";

            Result.XmlCode[Result.XmlCode.Length - 1] =
                String.Format("        <VeterancyLevelArray>", 0);

            Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 1);
            Result.XmlCode[Result.XmlCode.Length - 1] = "            <Modification>";


             Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 3);
             Result.XmlCode[Result.XmlCode.Length - 3] =
                String.Format("                <BehaviorLinkEnableArray value=\"{0}\"/>",
                Strength);

            Result.XmlCode[Result.XmlCode.Length - 2] =
                String.Format("                <BehaviorLinkEnableArray value=\"{0}\"/>",
                Agility);

             Result.XmlCode[Result.XmlCode.Length - 1] =
                String.Format("                <BehaviorLinkEnableArray value=\"{0}\"/>",
                Intellect);


            Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 3);
            Result.XmlCode[Result.XmlCode.Length - 3] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Strength, Convert.ToInt32(Str + StrPlus));

            Result.XmlCode[Result.XmlCode.Length - 2] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Agility, Convert.ToInt32(Agi + AgiPlus));

            Result.XmlCode[Result.XmlCode.Length - 1] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Intellect, Convert.ToInt32(Int + IntPlus));
        }
        else
        {
            if (i < GrantHeroXP.Length)
                Last = GrantHeroXP[i];
            else
                Last = Last * GrantHeroXPFormulaA + GrantHeroXPFormulaB * (i + 1) + GrantHeroXPFormulaC;

            Result.XmlCode[Result.XmlCode.Length - 1] =
                String.Format("        <VeterancyLevelArray MinVeterancyXP=\"{0}\">",
                Last);

            Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 1);
            Result.XmlCode[Result.XmlCode.Length - 1] = "            <Modification>";

            Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 3);
            Result.XmlCode[Result.XmlCode.Length - 3] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Strength,
                Convert.ToInt32(StrPlus * (i + 1)) - Convert.ToInt32(StrPlus * i));

            Result.XmlCode[Result.XmlCode.Length - 2] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Agility,
                Convert.ToInt32(AgiPlus * (i + 1)) - Convert.ToInt32(AgiPlus * i));

            Result.XmlCode[Result.XmlCode.Length - 1] =
                String.Format("                <AttributeChangeArray Attribute=\"{0}\" Points=\"{1}\"/>",
                Intellect,
                Convert.ToInt32(IntPlus * (i + 1)) - Convert.ToInt32(IntPlus * i));
        }

        Array.Resize(ref Result.XmlCode, Result.XmlCode.Length + 2);
        Result.XmlCode[Result.XmlCode.Length - 2] = "            </Modification>";
        Result.XmlCode[Result.XmlCode.Length - 1] = "        </VeterancyLevelArray>";
    }

    #endregion

    this = Result;
}
» И для наследования:
Описания действий функций смотреть в предыдущем блоке.
/// <summary>
/// Создаст 1 из 3 алгоритмов основных параметров героя.
/// </summary>
/// <param name="Par">
/// Индекс атрибута. Введите:
/// 0 - Сила;
/// 1 - Ловкость;
/// 2 - Разум.
/// </param>
/// <param name="Main">Является ли параметр основным.</param>
public Attribute(int Id, bool Main)
{
    this.Type = "CBehaviorAttribute";
    this.MaxPoints = 999999999;
    this.Permanent = true;
    this.AbilityorEffectType = "Unit";
    this.IsMain = Main;
    this.Modification = new Modifications();

    switch (Id)
    {
        #region Strength
        case 0:
            this.Id = "Strength";
            this.NameValue = Interface.FindInStringList("Strength");

            this.Modification.VitalBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("StrHitPointBonus"));
            this.Modification.VitalRegenBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("StrRegenBonus"));

            break;
        #endregion
        #region Agility
        case 1:
            this.Id = "Agility";
            this.Name = Interface.FindInStringList("Agility");

            this.Modification.MovementSpeedBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("AgiMoveBonus")) / 100;
            this.Modification.AttackSpeedBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("AttackSpeedMultiplier")) + 1;
            this.Modification.VitalArmorBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("AgiDefenseBonus"));

            break;
        #endregion
        #region Intellect
        case 2:
            this.Id = "Intellect";
            this.Name = Interface.FindInStringList("Intellect");

            this.Modification.EnergyBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("IntManaBonus"));
            this.Modification.EnergyRegenBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("IntRegenBonus"));

            break;
        #endregion
    }
    this.Name = "Behavior/HeroAttribute/" + this.Id;
    if (Main)
    {
        this.Modification.AttackBonus = Convert.ToDouble(Mpq.File.ReadFromConstants("StrAttackBonus"));
        this.Id += "Main";
        EditorSuffix = "Behavior/HeroAttribute/Main/Suffix";
    }

    EditorPrefix = "Behavior/HeroAttribute/Prefix";
}
И для Получения Опыта:
/// <summary>
/// Тип алгоритма "Получение опыта". Характеризует левелы героя.
/// </summary>
/// <param name="HeroWarId">Id героя из варкрафта, для которомого мы всё это делаем.</param>
public Veterancy(string HeroWarId)
{
    Warcraft3.RaceConstructor.Unit u = Warcraft3.RaceConstructor.GetUnitById(HeroWarId);

    this.Type = "CBehaviorVeterancy";
    this.Id = u.StarId;
    this.Permanent = true;
    this.AbilityorEffectType = "Units";
    this.EditorSuffix = " - Уровни";
    this.Race = u.Race;

    #region Values

    string[] GrantXPBaseList = Mpq.File.ReadFromConstants("GrantHeroXP").Split(',');
    double[] GrantHeroXP = new double[GrantXPBaseList.Length + 1];

    for (int i = 0; i < GrantXPBaseList.Length; i++)
        GrantHeroXP[i + 1] = Convert.ToDouble(GrantXPBaseList[i]);

    double GrantHeroXPFormulaA = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaA"));
    double GrantHeroXPFormulaB = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaB"));
    double GrantHeroXPFormulaC = Convert.ToDouble(Mpq.File.ReadFromConstants("GrantHeroXPFormulaC"));

    int MaxHeroLevel = Convert.ToInt32(Mpq.File.ReadFromConstants("MaxHeroLevel"));
    this.LevelModification = new Modifications[MaxHeroLevel];
    this.LevelMinXp = new ulong[MaxHeroLevel];

    double Last = 0;

    int Agi = Convert.ToInt32(u.GetValue("Agi"));
    int Str = Convert.ToInt32(u.GetValue("Str"));
    int Int = Convert.ToInt32(u.GetValue("Int"));

    double AgiPlus = Convert.ToDouble(u.GetValue("AgiPlus"));
    double StrPlus = Convert.ToDouble(u.GetValue("StrPlus"));
    double IntPlus = Convert.ToDouble(u.GetValue("IntPlus"));

    string Strength = "Strength";
    string Agility = "Agility";
    string Intellect = "Intellect";

    string Main = u.GetValue("Primary").ToLower();
    switch (Main)
    {
        case "str":
            Strength += "Main";
            break;
        case "agi":
            Agility += "Main";
            break;
        case "int":
            Intellect += "Main";
            break;
    }

    for (int i = 0; i < MaxHeroLevel; i++)
        if (i == 0)
        {
            Array.Resize(ref this.LevelModification[i].BehaviorsActivided, 3);
            Array.Resize(ref this.LevelModification[i].BehaviorChanged, 3);
            Array.Resize(ref this.LevelModification[i].BehaviorChangedToValue, 3);

            this.LevelModification[i].BehaviorsActivided[0] = Strength;
            this.LevelModification[i].BehaviorsActivided[1] = Agility;
            this.LevelModification[i].BehaviorsActivided[2] = Intellect;

            this.LevelModification[i].BehaviorChanged[0] = Strength;
            this.LevelModification[i].BehaviorChangedToValue[0] = Convert.ToInt32(Str + StrPlus);

            this.LevelModification[i].BehaviorChanged[1] = Agility;
            this.LevelModification[i].BehaviorChangedToValue[1] = Convert.ToInt32(Agi + AgiPlus);

            this.LevelModification[i].BehaviorChanged[2] = Intellect;
            this.LevelModification[i].BehaviorChangedToValue[2] = Convert.ToInt32(Int + IntPlus);
        }
        else
        {
            if (i < GrantHeroXP.Length)
                Last = GrantHeroXP[i];
            else
                Last = Last * GrantHeroXPFormulaA + GrantHeroXPFormulaB * (i + 1) + GrantHeroXPFormulaC;

            this.LevelMinXp[i] = (ulong)Last;

            Array.Resize(ref this.LevelModification[i].BehaviorChanged, 3);
            Array.Resize(ref this.LevelModification[i].BehaviorChangedToValue, 3);

            this.LevelModification[i].BehaviorChanged[0] = Strength;
            this.LevelModification[i].BehaviorChangedToValue[0] = Convert.ToInt32(Str + StrPlus);

            this.LevelModification[i].BehaviorChanged[1] = Agility;
            this.LevelModification[i].BehaviorChangedToValue[1] = Convert.ToInt32(Agi + AgiPlus);

            this.LevelModification[i].BehaviorChanged[2] = Intellect;
            this.LevelModification[i].BehaviorChangedToValue[2] = Convert.ToInt32(Int + IntPlus);
        }
    #endregion
}
Как мы видим, способ №2 гораздо лучше способа №1. Однако преимущества способа №1 ощущаются лишь в следующем шаге. В принципе есть ещё и способ №3 - делать всё только с xml. Но этот способ - поистине сатанисский.

Шаг 5.

Теперь мы имеем нормальные алгоритмы. НО! Как мы будем вставлять их в стар? Пока это невозможно. Пока. Пришло время написания метода .ToXml().
А вот здесь те, кто мучились в предыдущие фазы, те, кто избрал частичную работу с xml, будут пировать. Им не надо писать это повторно. Чего не скажешь о тех, кто описывал все эти поля.
Наша задача такая написать этот метод для каждого из типов данных (а их у нас 3!), если Вы работали с наследованием, и один простой общий для тех, кто поленился и страдал:
public string[] ToXml(int TabCount)
{
    // ...
}
// TabCount - это количество табов перед текстом. Параметр сугубо добровольный.
Но сначала расскажу поподробнее о старовском XML'е.
Из Википедии
XML (англ. eXtensible Markup Language — расширяемый язык разметки; произносится [экс-эм-эл]) — рекомендованный Консорциумом Всемирной паутины язык разметки, фактически представляющий собой свод общих синтаксических правил. XML — текстовый формат, предназначенный для хранения структурированных данных (взамен существующих файлов баз данных), для обмена информацией между программами, а также для создания на его основе более специализированных языков разметки (например, XHTML). XML является упрощённым подмножеством языка SGML.
Все данные редактора разбиты на типы данных. Всего их куча - называть их все смысла не имеет. Каждый из предоставлен отдельный файликом в папке "Base.SC2Data", как правило в подкаталоге "GameData". Нас интересует "Base.SC2Data\GameData\BehaviorData.xml". Начиная с третьей строчки, там идут подряд (относительно подряд - после конца предыдущего) все алгоритмы (или что Вам надо) с указанием типа. Последняя строчка - тоже служебная. Мы должны написать программу, которая всё это и сгнерирует.
В принципе, существуют различные библиотеки для работы с xml. Нам же куда проще работать с xml напрямую - через строчки.
В этом методе мы должны заботиться и об экономии места на диске - не будем писать в код те значения, которые мы не использовали в этой структуре.
Покажу только один пример - тип "Modifications".
» Код:
/// <summary>
/// Конвертирует данную структуру в понятный старкрафту xml.
/// </summary>
/// <param name="TabCount">Количество символов табуляции, отложенных от начала строки. Для красоты.</param>
/// <returns>Возвращает массив строк с xml-кодом.</returns>
public string[] ToXml(int TabCount)
{
    string[] Result = new string[1];
    Result[0] =
        String.Format("{0}" + "<Modification>",
        "".PadLeft(4 * TabCount, ' '));

    for (int i = 0; i < this.BehaviorChanged.Length; i++)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<AttributeChangeArray Attribute=\"{1}\" Points=\"{2}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.BehaviorChanged[i],
            this.BehaviorChangedToValue[i]);
    }

    for (int i = 0; i < this.BehaviorsActivided.Length; i++)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<BehaviorLinkEnableArray value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.BehaviorsActivided[i]);
    }

    if (this.AttackBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 2);

        Result[Result.Length - 2] =
            String.Format("{0}" + "<DamageDealtScaled index=\"Melee\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.AttackBonus);

        Result[Result.Length - 1] =
            String.Format("{0}" + "<DamageDealtScaled index=\"Melee\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.AttackBonus);
    }

    if (this.AttackSpeedBonus != 1)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<AttackSpeedMultiplier value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.AttackSpeedBonus);
    }

    if (this.MovementSpeedBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<MoveSpeedBonus value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.MovementSpeedBonus);
    }

    if (this.VitalArmorBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<LifeArmorBonus value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.VitalArmorBonus);
    }

    if (this.VitalBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<VitalMaxArray index=\"Life\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.VitalBonus);
    }

    if (this.VitalRegenBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<VitalRegenArray index=\"Life\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.VitalRegenBonus);
    }

    if (this.EnergyBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<VitalMaxArray index=\"Energy\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.EnergyBonus);
    }

    if (this.EnergyRegenBonus != 0)
    {
        Array.Resize(ref Result, Result.Length + 1);
        Result[Result.Length - 1] =
            String.Format("{0}" + "<VitalRegenArray index=\"Energy\" value=\"{1}\"/>",
            "".PadLeft(4 * (TabCount + 1), ' '),
            this.EnergyRegenBonus);
    }

    return Result;
}
Остальные доделайте сами (если хотите, конечно же!), это несложно (особенно когда есть такой наглядный пример!).

Шаг 6.

Что ж, можете себя поздравить! Вы дошли до самого конца, и теперь осталось самое простое - затолкать всё в карту. Скажу честно, я так и не научился пользоваться функцией импорта файлов в mpq, поэтому загонял по старинке. Быть может, Вам повезёт?..
План:
  • Написать автоматический инициализатор всех алгоритмов. (Юниты у нас уже из варкрафта загружены.)
  • Конвертировать все их в текст.
  • Переписать файл внутри архива карты (модификации).
Все пункты настолько просты, что я даже не буду тратить на них внимание.

Подведём итоги.

Теперь Вы поняли принцип работ промышленных масштабов со StarCraft II Editor'ом. Поздравляю Вас!
» Итак, что мы получили...
BehaviorData.xml - алгоритмы с уровнями всех героев стандартного WarCraft III. Также содержит стандартные характеристики.
((кат P.S.
Я хотел выложить конечный код, но ужаснулся его размеру, да и так кто-то будет иметь шанс написать конвертер раньше меня. А мне это не улыбается.
))));
break;
#endregion
#region Intellect
case 2:
this.Id =

Просмотров: 1 221

Харгард #1 - 7 лет назад 0
Если честно - страшно.
Надеюсь мне это никогда не понадобится.
Или что все эти процессы будут оптимизированы в 1.5 или Сварме.
Hares #2 - 7 лет назад 0
GraTuraL, это способ только для работы с поистине сложными вещами, такими как конверт меж играми и написание совершенно нестандартных модификаций.