Руководство по изготовлению модов для казаков

Модификация игры Казаки, Создание мода к игре Казаки

РУКОВОДСТВО ПО ИЗГОТОВЛЕНИЮ МОДОВ ДЛЯ КАЗАКОВ
 Вступление: Первое правило создания МОД-ов — НЕ БОЙТЕСЬ ЭКСПЕРИМЕНТИРОВАТЬ.Измените немножко здесь и чуть-чуть там. Вначале вы будете делать ошибки, но только так вы научитесь. Именно так научился я. Совершенство достигается практикой. Поэтому откройте ваш Notepa/Wordpad и приступайте. Только, прежде чем что-либо сделать убедитесь, что вы сохранили резервные копии оригинальных файлов Казаков. Также обратите внимание, что в этой статье термин юнит употребляется как в отношении собственно юнита, так и здания. 

Содержание:
1. Типы файлов
2. Анатомия NDS-файлов
3. Как изменить графику юнита
4. Как добавить юнит
5. Как вставить отредактированные файлы в игру

1. Типы файлов: (Back to top)

NRES.DAT : Этот тип нам интересен. Его можно редактировать текстовым редактором, например Notepad. Он управляет многими вещами. UNITS/FARM например это величина устанавливающая сколько юнитов можно иметь в еденичном обиталище. Он также устанавливает позицию управляющей иконки и многие другие вещи.

GP-файлы : Это графические файлы для спрайтов юнитов и других картинок. В настоящее время полный формат этих файлов не известен и поэтому их нельзя редактировать без рабочих инструментов GSC.

LST-файлы : Среди этих файлов представляют интерес NATIONS.LST и ORDERS.LST. Если вы хотите добавить дополнительные нации (если ОЧЕНЬ хотите, даже Baddog не пытался осуществить такую масштабную работу), то NATIONS.LST это файл которым вы должны редактировать первым. ORDERS.LST также легко редактируем и интересен постольку, поскольку задает размер формации, ее форму и бонусы построения. Оба эти файла вполе доступны для понимания.

MD-файлы : Это файлы спецификации юнитов. Возможно это именно те фалы, ковряя которые вы потратите набольшее количество времени. Вы должны хорошо знать их. Большая часть содержания понятна сама по себе. Особо следует отметить переменную NAME в этом файле. Именно этот NAME будет использован в NDS файле для получения характеристик юнита включая его графику, хит-поинты, атаку и др.

NDS-файлы :Файл спецификации нации. Подробности ниже. 

TXT-файлы : Представляют интерес COMMENT.TXT, TEXT/MDLIST.TXT и NMLIST.TXT. COMMENT.TXT это типа файл-перевода который задает описания различных кнопок, типа апгрейдов. Конечно в различных языковых версиях он будет различаться. TEXT/MDLIST.TXT то же самое, но для названий юнитов. Остается NMLIST.TXT. Это важный файл. Если вы хотите добавить новые юниты (т.е. новые MD-файлы), Вы должны добавить новую строку в этот файл, в противном случае игра рухнет.

*** Важно помнить, что все эти файлы взаимосвязанны. Изменения в одном могут повлиять на работу других. ***

2. Анатомия NDS-файлов: (Back to top)

Это файлы наций. Эти файлы заслуживают подробного рассмотрения. Файлы имеют текстовый формат и редактируются любым текстовым редактором. Они разделены на несколько секций. Последующие примеры взяты из файла AUSTRIA.NDS

[MEMBERS] : эта секция имеет отношение к юнитам и зданиям доступным данной нации. Слева стоят действительные имена игорвых едениц (юнитов и зданий, далее ИЕ) которые будут использоваться позднее в NDS-файлах. Не важно какое идентификатор вы присвоите ИЕ, главное, чтобы его окончание в скобках соответствовало признаку нации например, (AU) для Австрии. Правая часть соответствует отображаемому в игре имени ИЕ описанному в соответствующем MD-файле как переменная NAME для данной еденицы. Например, в AUSTRIA.NDS : Kreposnoi_pruss(au) Kreposnoi_pruss относится к ИЕ под именем Kresposnoi_pruss(au), использующей характеристики юнита Kreposnoi_prus, описаного в файле KRP.MD.

[FIXED_PRODUCE] : эта секция устанавливает, что можно построить/произвести и отвечает за наличие соответствующих иконок. Например, в файле AUSTRIA.NDS первая после заголовка [FIXED_PRODUCE] строка : Kreposnoi_pruss(au) 18. Это означает, что ИЕ Kresposnoi_pruss(au) (Австрийский крестьянин) может осуществить 18 действий. Следующие 18 строк как раз и описывают его возможности. Следующей строкой стоит : Center_Austria(au) 2 2 H. ГорЦентр может быть построен крестьянином и иконка горцентра будет помещена во вторую колонку и второй ряд соответствующего меню. Буква H обозначает категорию ГорЦентр, (H — ГорЦентр, A — Академия и др. ).

[ENABLED] : список ИЕ доступных в начале игры . Например доступны казармы 17в, но не 18в.

[UPGRADES] : этот обширный раздел включает различные апгрейды для данной нации. Не беспокойтесь о том, где эти апгрейды делаются — это описано в следующем разделе файла. типичный вид строки: Pikiner_evro(au)ATTACK ATAKA1 #POSITION 0 1 #LEVEL 2 Pikiner_evro(au)ATTACK #COST 2 FOOD 200 GOLD 50 #TIME 500 DAMAGE WEAPONKIND PIKA Pikiner_evro(au) +1

Здесь, Pikiner_evro(au)ATTACK = имя апгрейда, ATAKA1 = иконка для апгрейда, #POSITION 0 1 = позиция иконки (колонка, ряд), #LEVEL 2 = уровень апгрейда, Pikiner_evro(au)ATTACK = имя используемое в файле COMMENT.TXT, #COST 2 FOOD 200 GOLD 50 = само собой, #TIME 500 = время на апгрейдж, DAMAGE WEAPONKIND PIKA Pikiner_evro(au) = действие апгрейда, +1 = количественный эффект апгрейда.

[UPGRADEPLACE] : указывает место где апгрейд происходит. Формат сходен с Fixed_Produce, сначала указывается ИЕ и число его апгрейдов, следующие строчки характеризуют апгрейды данной ИЕ.Например, PorE(au) 5 определяет 5 апгрейдов Австрийского порта. Апгрейды определяются непосредственно за этим.

[UPGRADEENABLE] : апгрейды доступные в начале игры.

[UPGRADELINKS] : описывает последовательность апгрейдов. Наиболее сложный пример MAINAU 13 Kirasir(au)ATTACK Kirasir(au)SHIELD Gusar_evro(au)ATTACK Gusar_evro(au)SHIELD Dragun_18(au)ATTACK Dragun_18(au)SHIELD KUZ04AU Melnica(au)GETRES2 shahta(au)INSIDE5 shahta(au)INSIDE6 shahta(au)INSIDE7 AKA25AU PRS(au)INSIDE. Сие означает, что по завершении апгрейда MAINAU (переход в 18в) сиановятся доступны 13 апгрейдов. Большинство других строк определяют только один последующий апгрейд.

[DISABLED_UPGRADES] : секция определяет доступность апгрейдов по определенным условиям. Хороший пример, MAINAU (18век), закрашен серым пока не выполнены все условия перехода.

[ACCESSCONTROL] : определяет условия которые необходимо выполнить для получения доступа к апгрейду/ИЕ. Например, MAINAU 7 Kuznica(au) Konushnia_Swesair(au) akademia_E(au) Kazarma_evro(au) Cercov_Poland(au) Rinok(au) artileri_depo(au) означает, что до того как вы сможете нажать кнопку MAINAU, вы должны построить семь зданий перечисленных после цифры 7.

[UNITLOCK] : определяет юниты которые должны производиться в ограниченном количестве. Например, PERES_KOR(au) 10 PERES_KOR(au) означает, что вы не сможете построить больше 10 паромов.

[CANSETDEST] : здания перечисленные здесь будут иметь точку сбора.

[PRIVATE] : апгрейды указанные здесь будут влиять на одну ИЕ для которой произведен апгрейд. Это используется для шахт и башен.

[SPECIAL_UPGRADE] и [SPECIAL_UNIT] : применимы только для ПДК. Устанавливает флаги для различных видов начала игры «С большой армией», «Уже в 18-м веке», «Без пушек, башен и стен» и других. Юниты и апгрейды перечисленные здесь позволяют движку игры знать исключать ли их при выполнении определенных условий.

[COUNTRY] : видимо предназнаен для редактора. Содержит юниты данной нации сгруппированные по типам: здания, кавалерия, артиллерия и др.

[OFFICERS] : определяет условия построения формаций.

3. Как изменить графику юнита: (Back to top)

Разобравшись с различными типами файлов, давайте попробуем что-нибудь простенькое, например изменим графику наемника-драгуна. Для этого вам нужен файл DR2DIP.md, определяющий характеристики этого юнита. Допустим, мы меняем его графику на графику французкого драгуна . Французу соответствует DRF.MD. Вам потребуется изменить строчки от ICON до GEOMETRY. Любители острых компьютерных ошушений могут попробовать и отредактировать некоторые характеристики. После этого вернните отредактированный файл DR2DIP.MD в игру и вы обнаружите новую графику ВАШЕГО наемного драгуна. Как вставлять файлы в игру, читайте дальше.

4. Как добавлять юниты: (Back to top)

Это уже продвинутый уровень МОДа. Сначала надо продумать что вы хотите добавить к какой нации вы хотите добавить юнит и каком виде (доступность юнита, уровни апгрейдов и т.д.). Тут появляется дополнительный шаг который вам также необходимо сделать. Поскольку вы вводите новые файлы вам необходимо помнить что новые MD-файлы должны быть приведены в файле NMLIST.TXT и его описании данном в TEXTMDLIST.TXT. Теперь переходите к NDS-файлу нации к которой вы собираетесь добавлять новый юнит. К этому процессу надо относиться как к рождению ребенка в вашей семье. Вы должны решить каким будет его имя, где он будет рождаться, какое положение он займет в семье, а так же его потенциальная роль когда он вырастет. Имя указывается в секции [MEMBERS]. Место рождения в [FIXED_PRODUCE]. Доступность в начале игры в [ENABLED] и так далее по всем разделам NDS-файла. После редактирования верните эти файлы в игру и (если все зделано правильно) вы должны обнаружить в игре свой новый юнит.

5. Как вернуть отредактированные файлы в игру: (Back to top)

Простейший способ — использовать Cossacks Mod Menager который я разработал. Все, что вам нужно — это сгенерировать GSC-файл с отредактированными вами файлами используя GSC File Utility, но назвать ваш файл YOURNAME.MODS (замените YOURNAME на ТО-ЧТО-ВАМ-НРАВИТСЯ). Затем скопируйте файл в директорию Cossacks и выйдите из Cossacks Mod Manager. С другой стороны, вы можете использовать традиционный путь создавая новый patch0*.gs1 или mods01.gs1 (если уже инсталлирован MOD1) затем копировать это добро в директорий Cossacks. Если вы поступаете так, не забудьте сохранить оригинальные файлы. При использовании patch0*.gs1 или mods01.gs1 соблюдайте следующие правила. Движок Казаков ищет файлы с характеристиками юнитов сначала в patch0*.gs1. Эти значения имеют более высокий приоритет, чем в ALL.GSC. Конечно если характеристики юнита не найдены в PATCH0*.GS1 движек возьмет эти значения из ALL.GSC. Если соответствующие значения не будут найдены в одном из NDS файлов игршка рухнет. Это правило слегка изменяется если установлен MOD1. Вначале просматривается MODS01.GS1 и MODS.GS1, затем — PATCH02.GS1, и в наконце — ALL.GSC. GS1 и GSC файлы под другими именами просто игнорируются движком. Если у вас только ЕВ также игнорируются MODS01.GS1 и MODS.GS1.

 Было немало написано и прочитано — остается только засучить рукава и попытаться самому модифицировать Казаков. Happy Cossacking my fellow friends.

  1. [​IMG]
    Основная информация и подсказки для моддинга Казаков 3


    Специально для группы Cossacks 3 Модификации

    1. Для начала всего и вся, ознакомьтесь со структурой файлов в игре. Папка с игрой расположена в вашей директории Steam. Обычно это: C:Program Files (x86)SteamsteamappscommonCossacks 3
    Рекомендую делать бэкапы всех изменяемых файлов, или добавлять модифицированные файлы средствами мод-менеджера.
    Но если случилось непоправимое, вы всегда сможете восстановить файлы игры средствами Steam (ПКМ по игре в библиотеке — Свойства — Локальные файлы — Проверить целостность кэша).
    Это полностью восстановит все измененные игровые файлы.

    2. В независимости от того, что вы собираетесь сделать, вам понадобиться один или несколько инструментов:

    • Во первых это текстовый редактор. Лично я рекомендую Sublime Text. Но вы так же можете использовать Vim, Atom или Notepad++. Это условно бесплатные или полностью бесплатные редакторы, которые сильно упростят вам жизнь. И не только в плане моддинга Казаков 3
    • Во вторых это хороший графический редактор, умеющий в BMP формат. Я рекомендую Adobe Photoshop CC. Так же подойдет GIMP, Pixlr или Paint.NET.
    • В третьих, если вы используете фотошоп, вам пригодиться NVIDIA Texture Tools for Adobe Photoshop. Это расширение поможет открыть и редактировать файлы текстур (.dds).
    • В четвертых, конечно же, софт для 3D-моделирования. Рекомендую использовать 3DS Max. Есть ещё аналоги: Maya, Poser, Blender (бесплатный).

    Разработчики так же не оставили мододелов без внимания, и сделали собственные инструменты для создания модификаций:

    • Во первых это, конечно же, мод-менеджер. Который встроен в игру.
    • Во вторых это игровой редактор, который так же можно найти в папке с игрой.
    • В третьих это плагин для 3DS Max.
    • В четвертых это официальный редактор языка.

    О неофициальных инструментах так же есть смысл сообщить:

    • Неофициальный инструмент для замены звуков от [WW]Prototype
    • Неофициальный инструмент для осмотра моделей из игры от egnaro

    3. Не забывайте про игровой редактор. Он поможет вам протестировать многие вещи в реальном времени, и покажет вам логи игры, и возникшие ошибки.
    Особенно полезно если изменяется графическая составляющая игры.
    Конечно же это также хороший инструмент для редактирования карт.

    4. Включите «логгирование». Делается это в файле cossacks.ini в корневой папке игры. Выставите в нем параметры:

    LogFileEnabled = true
    LogFileRoot = true
    LogFileName = cos

    Тоже самое можно сделать и в editor.ini, чтобы сохранялись журналы из редактора.
    Теперь все логи вы сможете найти в папке Logs.

    5. Игровые файлы и сценарии написаны на подобии языка Pascal.
    Прочитайте уроки по этому языку. Научитесь основному синтаксису. Или хотя бы примерно поймите что это такое. Это очень поможет в дальнейшем.

    Ну и закончим Подсказками для начинающих модостроителей:

    — Для лучшего понимания того, что вы делаете будет полезно хорошо знать игру. Понимать различие между игровыми файлами и записями. Вы так же сможете лучше понимать последствия ваших действий и быстро находить ошибки, которые их вызвали.
    — Читайте туториалы по инструментам. Это никогда не помешает.
    — Пробуйте. Всегда пробуйте воплотить в жизнь какую-то идею. Даже если ничего не получается — вы научитесь чему-то новому.
    — Не нужно начинать сразу же с огромных модификаций, которыми вы хотите изменить весь игровой сеттинг, или переделать игру ко всем чертям. Делайте маленькие модификации, даже если они были сделаны до вас. Это поможет избежать кучи проблем в будущем.
    — Скачайте парочку чужих модов, и посмотрите как они сделаны. Возможно вам станет понятны некоторые вещи, и вы сможете использовать это в своих модификациях.
    — Делайте бэкапы своих модов. Много бэкапов. Рано или поздно вы скажете себе спасибо за это.
    — Не следует публиковать сырые версии своих модов. Лучше хорошенько протестируйте всё.
    — Держите рабочее место в чистоте. Помещайте все файлы с модом в различные папки. Не ленитесь давать названия этим папкам.
    — Структуризируйте версии своих модификаций. Если вы обновляете свой мод — давайте ему новый номер версии, и добавьте к этому список изменений.
    — Рекомендую ознакомиться со всеми аспектами графических файлов BMP. Просмотрите туториал по альфа-каналам. Это поможет изменять элементы интерфейса без вреда для игры.

    Прошу смело добавлять или исправлять этот гайд в этой теме.
    Так же жду отзывов, спасибо!
    _____

    Отдельное спасибо Ebel, за гайды на английской ветке форума, которые вдохновили меня на создание этой статьи.

    Last edited: Dec 20, 2016


    19ivan92 and Ebel like this.

  2. о аллах, убери этот позорный дотерский сленг, ты же инструкцию пишешь

  3. Стесняюсь спросить, причем здесь дота?
    Да и кроме прочего, инструкция или нет — это не обязательно должен быть официальный стиль.

    Но спасибо за замечание.

  4. не стесняйся… при том, что только недодотеры используют такую словарную конструкцию, не имеющую смысловой нагрузки (и как стадо повторяют такие фразы либо за комментатором, либо за стримером)

    нормальный человек не поймет, зачем и какой ему нужен фотошоп, который «должен уметь в графический формат» :)

    «должен уметь в…» «сейчас бы не…» «в 2к16…»

  5. Дело в том, что такие конструкции используют явно не толко Дотеры. Я не буду отрицать что имею отношение к этой игре, но я уверяю тебя в том, что к доте это не имеет никакого отношения.
    Так же не отрицаю что среди дотеров популярны такие фразы, но это не делает их исключительно «дотерскими».

    Что касается фразы «должен уметь в…», то я лично видел такую конструкцию в обзорах на программы, так что без каких либо проблем стал использовать её и в своём гайде.

    Повторюсь, даже если я сейчас не прав — я вполне имею право использовать упомянутые тобой фразы в своих гайдах, ибо не считаю это ошибкой, из за которой люди могут меня не понять или понять неправильно.

  6. Небольшая инструкция на тему где лежат характеристики юнитов и что с ними делать?

    ВАЖНО: перед любыми правками сделайте себе копию рабочего файла.
    А ещё лучше сделайте свои правки отдельным подключаемым модом. Для этого создайте путь «папка казаков»/mods/»папка вашей модификации»/data/scripts/lib/ и разместите там копии данных файлов .
    В дальнейшем его можно будет включить через modman.exe

    ВАЖНО: после // идет описательная часть строки, которая ан игру не влияет. Комментарии разработчиков и т.д.

    ————————- units.scripts—————————————————

    Начнём с файла: units.scripts по адресу: «папка казаков»/data/scripts/lib/
    Данный файл содержит в себе характеристики всех юнитов игры а так же кучу сопутствующей информации.

    Через поиск ищем: // _unit_InitBase
    разработчики оставили информацию что данный раздел инициализирует все настройки и различия в прототипах юнитов, в зависимости от нации и типа юнита.
    Здесь собраны изначальные настройки для зданий и юнитов в игре.

    например:

       procedure SetObjBasePrice(var objbase : TObjBase; food, wood, stone, gold, iron, coal : Integer);
       begin
          objbase.price[gc_resource_type_food] := food;
          objbase.price[gc_resource_type_wood] := wood;
          objbase.price[gc_resource_type_stone] := stone;
          objbase.price[gc_resource_type_gold] := gold;
          objbase.price[gc_resource_type_iron] := iron;
          objbase.price[gc_resource_type_coal] := coal;
       end;
       

    Содержит данные о используемой игрой системе ресурсов, но здесь нам важен сам порядок: еда, дерево, камень, золото, железо, уголь.

    Так же:

     procedure SetObjBaseProtection(var objbase : TObjBase; pike, sword, bullet, cannister, arrow, cannonball : Integer);
       begin
          objbase.protection[gc_obj_weapon_kind_pike] := pike;
          objbase.protection[gc_obj_weapon_kind_sword] := sword;
          objbase.protection[gc_obj_weapon_kind_bullet] := bullet;
          objbase.protection[gc_obj_weapon_kind_cannister] := cannister;
          objbase.protection[gc_obj_weapon_kind_arrow] := arrow;
          objbase.protection[gc_obj_weapon_kind_cannonball] := cannonball;
       end;

    Типы используемых атак, порядок так же важен, так как защита юнита идет в таком же порядке: пики, мечи, пули, шрапнель, стрелы, ядра.

    Или:

     procedure SetObjBaseWeapon(var objprop : TObjProp; var objbase : TObjBase; index, damage, pause, radiusmin, radiusmax, detectradiusmin, detectradiusmax, kind : Integer; bSearchMinAttackRadius : Boolean);
       begin
          objprop.weapon[index].enabled := True;
          if (damage<>-1) then
          objbase.weapon[index].damage := damage;
          if (radiusmin<>-1) then
          objprop.weapon[index].radiusmin := _misc_PixelsToTiles(radiusmin);
          if (radiusmax<>-1) then
          objbase.weapon[index].radiusmax := _misc_PixelsToTiles(radiusmax);
          if (detectradiusmin<>-1) then
          objprop.weapon[index].detectradiusmin := _misc_PixelsToTiles(detectradiusmin);
          if (detectradiusmax<>-1) then
          objprop.weapon[index].detectradiusmax := _misc_PixelsToTiles(detectradiusmax);
          if (kind<>-1) then
          objprop.weapon[index].kind := kind;
          if (pause<>-1) then
          objbase.weapon[index].pause := _misc_FramesToTime(pause);
          if (bSearchMinAttackRadius) then
          objprop.minattackradius := _unit_GetMinAttackRadiusByObjProp(objprop);
       end;

    Данный код содержит важные для изменения юнита понятия индекса атаки, атака, пауза, минимального и максимального радиуса, радиуса обнаружения минимального и максимального и типа атаки. Запоминаем порядок.

    Для нетерпеливых ищем сразу: // setup юнитов
    Юниты разделены на подразделы:
    // INFANTRY — пехота
    // CAVALRY — кавалерия
    // ARTILLERY — пушки
    // SHIPS — корабли (после кораблей идут уникальные юниты, такие как генерал и ослик)

    Важно помнить что здесь и далее «по умолчанию» образцом юнита считается европейский, частности разных национальностей расписаны после общей части.

    Возьмем обычного пикинера 17 века:
    // INFANTRY
    ‘pikeman’, ‘pikemanpol’, ‘pikemantur’, ‘pikemanrus’

    'pikeman', 'pikemanpol', 'pikemantur', 'pikemanrus' : begin
                objbase.maxhp := 95;
                SetObjBaseWeapon(objprop, objbase, 0, 8, 0, 35, 100, 0, 100000, gc_obj_weapon_kind_pike, True);
                SetObjBasePrice(objbase, 25, 0, 0, 0, 20, 0);
                SetObjBaseProtection(objbase, 2, 4, 5, 255, 1, 7);
                SetObjBaseMaterialCanKill(objprop, gc_obj_material_body, gc_obj_material_body, gc_obj_material_woodwall, 0, 0, 0);
                SetObjBaseSearchBuildVisionScore(objprop, objbase, 700, 150, 1, 10);
                objprop.bstandground := True;
                objprop.usage := gc_obj_usage_lightinfantry;
                objprop.aiforce := 10;
                objprop.walkintervalfactor := cWalkIntervalInf;
                case nation of
                   'pol' : begin
                      objbase.maxhp := 100;
                      SetObjBaseWeapon(objprop, objbase, 0, 7, default, default, 110, default, default, default, True);
                      SetObjBasePrice(objbase, 45, 1, 0, 0, 0, 0);
                      SetObjBaseProtection(objbase, 0, 2, 0, 0, 0, 0);
                      SetObjBaseSearchBuildVisionScore(objprop, objbase, default, 100, default, default);
                   end;
                   'tur', 'alg' : begin
                      objbase.maxhp := 100;
                      SetObjBaseWeapon(objprop, objbase, 0, 9, default, default, 110, default, default, default, True);
                      SetObjBasePrice(objbase, 55, 0, 0, 0, 2, 0);
                      SetObjBaseProtection(objbase, 0, 2, 0, 0, 0, 0);
                   end;
                   'rus' : begin
                      objbase.maxhp := 90;
                      SetObjBaseWeapon(objprop, objbase, 0, 8, default, 45, 90, default, default, default, True); // c1 damage 9, max range was 100, hp was 100. it have much greater attack speed then in c1
                      SetObjBasePrice(objbase, 55, 0, 0, 0, 15, 0);
                      SetObjBaseProtection(objbase, 2, 3, 4, 150, 1, 4);
                      SetObjBaseSearchBuildVisionScore(objprop, objbase, default, 180, default, default);
                   end;
                end;

    Что мы тут видим:

    'pikeman', 'pikemanpol', 'pikemantur', 'pikemanrus'

    собственно и показывает раздел в котором содержатся пикинеры европейские, польский, турецкий (он же алжирский) и русский.

    Количество здоровья 95.

    SetObjBaseWeapon(objprop, objbase, 0, 8, 0, 35, 100, 0, 100000, gc_obj_weapon_kind_pike, True);

    Важная строчка, полное описание атаки: индекс 0, атака 8, пауза 0, минимальный радиус 35, максимальный 100, радиус обнаружения минимальный 0, максимальный 100000, тип оружия пика.
    Например если изменить значение gc_obj_weapon_kind_pike на gc_obj_weapon_kind_sword пикинер будет наносить урон от меча а не пики.

    SetObjBasePrice(objbase, 25, 0, 0, 0, 20, 0);

    Сколько стоит, 25 еды и 20 железа.

    SetObjBaseProtection(objbase, 2, 2, 5, 255, 1, 7);

    Защита по типам: пики и мечи по 2, пули 5, картечь 255, стрелы 1, ядра 7.

    SetObjBaseMaterialCanKill(objprop, gc_obj_material_body, gc_obj_material_body, gc_obj_material_woodwall, 0, 0, 0);

    Кого может атаковать — объекты из плоти (других солдат) и деревянный частокол.

    SetObjBaseSearchBuildVisionScore(objprop, objbase, 700, 150, 1, 10);

    Важная строчка, нас интересует второй числовой параметр 150 — время строительства (1 = 0,03 секунды)

    После:

    идут отличия пикинеров какой либо конкретной нации, в нашем случае
    ‘pol’ — поляки, ‘tur’, ‘alg’ -турки и алжир, ‘rus’ — русские.
    Например мы видим что польские, турецкие/алжирские пикинеры не имеют начальной брони, Польский пикинер строится на 1/3 быстрее (100. а не 150) и у всех здоровье отличное от европейского пикинера.

    В дальнейшем все идет так же — общий раздел юнита и частности отдельных наций.

    Так же у отдельных юнитов будут встречаться строчки, характерные только для них, например:
    после легкой пехоты ‘lightinfantry’ идет сразу переменная if (bmercenary) then, которая показывает характеристики наемной легкой пехоты. (Так же у рундашира, лучника, сечевого казака и драгуна)

    Для наемников, офицеров и некоторых юнитов характерна строчка

    objprop.consume[gc_resource_type_gold] := 3;

    которая показывает сколько ресурса (в данном случае золота) расходует данный юнит.

    У стрелков есть строчки:

                  objprop.weapon[weapInd].cost[gc_resource_type_iron] := 3;
                  objprop.weapon[weapInd].cost[gc_resource_type_coal] := 3;

    Которые показывают расход железа и угля на выстрел (в данном случае по 3 угля и железа).
    У лучников:

                objbase.weapon[0].dispertion := _misc_PixelsToTiles(250);

    которая отвечает за разброс стрел (Можете выключить ее (//) и посмотреть в каких терминаторов превратятся обычные алжирские лучники. )

    Last edited: Dec 20, 2016

  7. Продолжим:

    ————————- country.scripts—————————————————

    Следующий файл: country.scripts по адресу: «папка казаков»/data/scripts/lib/
    Данный файл содержит в себе улучшения всех юнитов, зданий (в том числе улучшения а академии, кузнице и т.п.) игры а так же кучу сопутствующей информации.

    Ищем строчку: // _country_InitUnitsUpgrades
    В данном разделе находится информация по используемым в улучшениях юнитов типам оружия и защиты.

    procedure _country_InitUnitsUpgrades(var country : TCountry; var ind, linkind : Integer);
    begin
       const bAddIfNotExist = True;
       const cTrue = True; // for better formating and reading
       const ctypeDamagePike = 0;
       const ctypeDamageSword = 1;
       const ctypeDamageBullet = 2;
       const ctypeProtection = 3;
       const ctypeProtectionOnlyPikeArrow = 4;
       const ctypeProtectionOnlySword = 5;

    Это значит что для юнитов применяются следующие типы улучшений:
    Урон от пики
    Урон от меча
    Урон от пули
    Защиты (пика, меч, стрела) — самый распространенный тип защиты.
    Защиты только от пик и стрел
    Защиты только от меча
    В данном случае данный код нужен не сам по себе, а в связке с другим.
    Смотрим чуть дальше:

    begin
          var j : Integer;
          for j:=0 to 5 do
          begin
             if (upgstruct.value[j]<>0) then
             begin
                var iupgtype : Integer = gc_upg_type_none;
                var iupgtype2 : Integer = 1;
                var iparam1, iparam2, iparam3 : Integer;
                case upgradetype of
                   ctypeDamagePike : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_pike;
                   end;
                   ctypeDamageSword : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_sword;
                   end;
                   ctypeDamageBullet : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_bullet;
                   end;
                   ctypeProtection : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;
                   end;
                   ctypeProtectionOnlyPikeArrow : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;
                   end;
                   ctypeProtectionOnlySword : begin
                      iupgtype := gc_upg_type_protection;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iupgtype2 := 2;
                   end;
                   else
                   ErrorLog('AddUpgradePack : Unknown type');
                end;
                _country_AddUpgradeWithAccessControl(country, upgstruct.place+'.'+upgstruct.member+'.'+IntToStr(iupgtype2)+'.'+IntToStr(j+1), j+2, tooltiptype, iupgtype, upgstruct.value[j], (j=0) and bEnabled, 500, x, y, ind, upgstruct.food[j], upgstruct.wood[j], upgstruct.stone[j], upgstruct.gold[j], upgstruct.iron[j], upgstruct.coal[j], bAddIfNotExist, iparam1, iparam2, iparam3, upgstruct.member, '', '', '', '', '', '', '', '', '', True, upgstruct.req[0], upgstruct.req[1], upgstruct.req[2], upgstruct.req[3], upgstruct.req[4], upgstruct.req[5], '', '');
                if (j<5) and (upgstruct.value[j+1]<>0) then
                _country_AddUpgradeLink(country, upgstruct.place+'.'+upgstruct.member+'.'+IntToStr(iupgtype2)+'.'+IntToStr(j+1), upgstruct.place+'.'+upgstruct.member+'.'+IntToStr(iupgtype2)+'.'+IntToStr(j+2), linkind);
             end;
          end;
       end;

    Вот и полное раскрытие информации по улучшениям.
    Например как устроена строчка защиты:
    Это наиболее используемый тип защиты.

    ctypeProtection : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;

    Броня от пик, мечей и стрел. 3 строчки каждая отвечает за определенный тип защиты.

    Все нам понадобится для понимания в дальнейшем работы данной системы.

    Самые нетерпеливые идут сразу сюда: // barracks
    Типы юнитов по подразделам разделены почти так же как и в units.scripts, но количество подразделов гораздо больше, так как улучшения разделены по векам и для уникальных юнитов выделены в отдельные подразделы.
    // barracks — улучшения юнитов в бараках.
    // barracks 18 century — улучшения юнитов в бараках 18 века.
    // stables — улучшения юнитов в конюшнях.
    // cavalry 18 century — улучшения юнитов в конюшнях 18 века.
    // barrack 18 century unique units — улучшения уникальных юнитов в бараках.
    // stable unique units — улучшения уникальных юнитов в конюшнях.

    Возьмемся например за пикинеров 17 века:
    // barracks
    Самые первые:

    case cid of
          _rus : member := 'pikemanrus';
          _ukr : member := '';
          _pol : member := 'pikemanpol';
          _tur : member := 'pikemantur';
          _alg : member := 'pikemantur';
          else
          member := 'pikeman';
       end;
       if (member<>'') then
       begin
          case cid of
             _aus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1300, 300, True, 3, 3600, 450, True, 1, 7200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _fra : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 100, 25, True, 2, 1400, 325, True, 3, 4600, 650, True, 2, 6200, 1650, True, 1, 15300, 2075, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _eng : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1250, 310, True, 4, 3900, 650, True, 1, 7200, 1850, True, 1, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _spa : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1300, 300, True, 3, 3600, 450, True, 1, 7200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _rus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1300, 300, True, 3, 3600, 450, True, 1, 7200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pik_rus
             _ukr : ;
             _pol : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 500, 50, True, 2, 1400, 100, True, 3, 3200, 450, True, 1, 8200, 2220, True, 2, 15030, 1800, True, 0, 0, 0, blacksmith, ''); // Pikiner_polan
             _swe : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 100, 90, True, 2, 300, 450, True, 3, 4600, 300, True, 1, 9200, 1250, True, 2, 14030, 2600, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _pru : SetUpgStructFoodGold(upgstruct, upgplace, member, 2, 3600, 450, True, 2, 1300, 300, True, 2, 200, 50, True, 1, 6800, 1950, True, 2, 15030, 2300, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _ven : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1300, 300, True, 3, 3600, 450, True, 1, 7200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _tur : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 600, 300, True, 3, 1200, 450, True, 1, 2200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_turki
             _alg : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 600, 300, True, 3, 1200, 450, True, 1, 2200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_turki
             _den : SetUpgStructFoodGold(upgstruct, upgplace, member, 2, 3600, 450, True, 2, 1300, 300, True, 2, 200, 50, True, 1, 6800, 1950, True, 2, 15030, 2300, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _net : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 900, 150, True, 2, 700, 400, True, 3, 3100, 250, True, 1, 6700, 1950, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
          end;
          AddUpgradePack(country, upgstruct, ctypeDamagePike, gc_upg_tooltiptype_infdmg, 0, 1, True, ind, linkind);
          case cid of
             _aus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _fra : SetUpgStructFoodGold(upgstruct, upgplace, member, 2, 350, 90, True, 1, 1000, 135, True, 3, 4200, 500, True, 1, 11075, 310, True, 2, 15050, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _eng : SetUpgStructFoodGold(upgstruct, upgplace, member, 2, 990, 50, True, 1, 200, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 17010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _spa : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _rus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pik_rus
             _ukr : ;
             _pol : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 250, 75, True, 2, 800, 150, True, 3, 3500, 225, True, 3, 9005, 407, True, 9, 19010, 2975, True, 0, 0, 0, blacksmith, ''); // Pikiner_polan
             _swe : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 350, 50, True, 2, 700, 275, True, 3, 2500, 200, True, 1, 13005, 997, True, 2, 16010, 2550, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _pru : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 175, 40, True, 1, 990, 275, True, 4, 4700, 280, True, 1, 9505, 707, True, 2, 17510, 2950, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _ven : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _tur : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_turki
             _alg : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 150, 50, True, 2, 900, 175, True, 3, 4500, 300, True, 1, 9005, 507, True, 2, 18010, 3050, True, 0, 0, 0, blacksmith, ''); // Pikiner_turki
             _den : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 175, 40, True, 1, 990, 275, True, 4, 4700, 280, True, 1, 9005, 707, True, 2, 17510, 2950, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
             _net : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 250, 150, True, 2, 800, 275, True, 3, 4200, 100, True, 1, 9305, 707, True, 2, 17890, 2850, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro
          end;
          var tmptype : Integer = ctypeProtection;
          if (pol) then // poland pikeman got unique, upgrade mechanic. he upgrade protection from pike and arrow. and only last upgrade, improve protection from sword
          tmptype := ctypeProtectionOnlyPikeArrow;
          AddUpgradePack(country, upgstruct, tmptype, gc_upg_tooltiptype_infdef, 0, 2, True, ind, linkind);
          if (pol) then
          begin
             country.upgrade[ind-1].iarrparam1[0] := gc_obj_weapon_kind_none;
             country.upgrade[ind-1].iarrparam1[1] := gc_obj_weapon_kind_sword;
             country.upgrade[ind-1].iarrparam1[2] := gc_obj_weapon_kind_none;
          end;
       end;

    ВНИМАНИЕ: меняем все аккуратно, стараясь не допустить отсутствие пробелов или потерю запятых — иначе ничего не будет работать. Сохраняйте рабочие копии файла почаще.

    Что мы тут видим:

    _aus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 200, 50, True, 2, 1300, 300, True, 3, 3600, 450, True, 1, 7200, 1850, True, 2, 16030, 2000, True, 0, 0, 0, blacksmith, ''); // Pikiner_evro

    Каждая строчка начинается с буквенного обозначения страны, в данном случае _aus — Австрия, Дальше идет обозначение ресурсов используемых для улучшения (SetUpgStructFoodGold) еда и золото. Дальше сами значения по уровням улучшений:
    , 1, 200, 50, — улучшение атаки на 1 за 200 еды и 50 золота (второй уровень)
    , 2, 1300, 300, — улучшение атаки на 2 за 1300 еды и 300 золота (третий уровень) и так далее.
    в конце строчки будет обозначение здания, необходимого для возможности проведения улучшений, в данном случае blacksmith (кузница).
    И так для каждого пикинера каждой страны, кроме украинцев, у них нет пикинера потому строчка выглядит так:

    Дальше видим следующую строчку:

    AddUpgradePack(country, upgstruct, ctypeDamagePike, gc_upg_tooltiptype_infdmg, 0, 1, True, ind, linkind);

    В данном случае нас интересует что для улучшения всех вышеперечисленных юнитов используется улучшения атаки пикой: ctypeDamagePike. Цифры: 0, 1, обозначают местоположение улучшения для юнита: нулевой ряд, 1 строчка (не нулевая строчка, на нулевой сам пикинер в бараках).

    Само собой, если мы изменили для какого либо юнита тип атаки в units.scripts то улучшения не будут работать пока мы не изменим тип атаки, применяемый в улучшениях.
    Ниже идут строчки для защиты нашего многострадального пикинера, принцип тот же.
    А вот потом начинается самое интересное:

    var tmptype : Integer = ctypeProtection;
          if (pol) then // poland pikeman got unique, upgrade mechanic. he upgrade protection from pike and arrow. and only last upgrade, improve protection from sword
          tmptype := ctypeProtectionOnlyPikeArrow;
          AddUpgradePack(country, upgstruct, tmptype, gc_upg_tooltiptype_infdef, 0, 2, True, ind, linkind);
          if (pol) then
          begin
             country.upgrade[ind-1].iarrparam1[0] := gc_obj_weapon_kind_none;
             country.upgrade[ind-1].iarrparam1[1] := gc_obj_weapon_kind_sword;
             country.upgrade[ind-1].iarrparam1[2] := gc_obj_weapon_kind_none;
          end;

    Разработчики разъясняют:
    // poland pikeman got unique, upgrade mechanic. he upgrade protection from pike and arrow. and only last upgrade, improve protection from sword
    // Польский пикинер имеет уникальную механику улучшений, он улучшает защиту против пик и стрел, и только последнее улучшение повышается защиту против мечей.
    Что тут для нас важного:

    var tmptype : Integer = ctypeProtection;

    переключаемый тип защиты — защита против пик, мечей и стрел.

    переключатель — в данном случае польский пикинер.

    tmptype := ctypeProtectionOnlyPikeArrow;

    используемый после переключения тип защиты — защита против пик и стрел.
    И самое вкусное:

             country.upgrade[ind-1].iarrparam1[0] := gc_obj_weapon_kind_none;
             country.upgrade[ind-1].iarrparam1[1] := gc_obj_weapon_kind_sword;
             country.upgrade[ind-1].iarrparam1[2] := gc_obj_weapon_kind_none;

    Из защиты вычитаются строчки первая (надеюсь вы читали раздел для ознакомления?)- защита от пик и третья — защита от стрел, остается только вторая — защита от мечей.

    Например мы захотели что бы наши юниты на последнем уровне улучшения своей защиты улучшали ее только против только стрел. Делаем так:

          AddUpgradePack(country, upgstruct, ctypeProtection, тут данные вашей строчки);
             country.upgrade[ind-1].iarrparam1[0] := gc_obj_weapon_kind_none;
             country.upgrade[ind-1].iarrparam1[1] := gc_obj_weapon_kind_none;
             country.upgrade[ind-1].iarrparam1[2] := gc_obj_weapon_kind_arrow;
    

    В данном случае мы вычитаем защиту от пик и мечей из защиты (ctypeProtection) от пик, мечей и стрел.
    ВАЖНО: вычитать можно те строки, которые содержатся в данной защите!

    Last edited: Dec 21, 2016

  8. Идем дальше:
    В основном все однотипно но вот мы доходим до мушкетеров (member := ‘musketeer’;) и видим следующее:

    case cid of
          _aus : member := 'musketeeraus';
          _spa : member := 'musketeerspa';
          _rus : member := 'strelet';
          _ukr : member := 'serdiuk';
          _pol : member := 'musketeerpol';
          _tur : member := 'jannisary';
          _alg : member := '';
          _net : member := 'musketeernet';
          else
          member := 'musketeer';
       end;
       if (member<>'') then
       begin
          var posx : Integer = 2;
          if (cid=_ukr) then
          posx := 0;
          case cid of
             _aus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_avstr
             _fra : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 100, 50, True, 2, 3000, 500, True, 3, 2500, 750, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _eng : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 1900, 150, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _spa : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Strelec_Spain
             _rus : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Strelec
             //_ukr : SetUpgStructFoodGoldIronCoal(upgstruct, upgplace, member, 2, 22000, 800, 0, 0, True, 3, 32400, 5800, 0, 0, True, 4, 42010, 6800, 0, 0, True, 5, 52300, 1800, 7400, 0, True, 6, 45000, 300, 14500, 0, True, 10, 62400, 0, 0, 39200, blacksmith, ''); // Kozacki_Strelec
             _ukr : SetUpgStructFoodGoldIronCoal(upgstruct, upgplace, member, 2, 22000, 800, 0, 0, True, 3, 32400, 5800, 0, 0, True, 4, 42010, 6800, 0, 0, True, 5, 52300, 1800, 7400, 0, True, 6, 45000, 300, 14500, 0, True, 0, 0, 0, 0, 0, blacksmith, ''); // C3 changes (remove last upg to damage and protection
             _pol : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 500, 125, True, 1, 1250, 275, True, 2, 2500, 650, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_polsha
             _swe : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 1000, 200, True, 1, 2000, 200, True, 2, 100, 200, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _pru : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _ven : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _tur : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Ianithar
             _alg : ;
             _den : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
             _net : SetUpgStructFoodGold(upgstruct, upgplace, member, 1, 2000, 100, True, 1, 1000, 300, True, 2, 500, 700, True, 0, 0, 0, True, 0, 0, 0, True, 0, 0, 0, blacksmith, ''); // Mushketer_ev
          end;
          AddUpgradePack(country, upgstruct, ctypeDamageBullet, gc_upg_tooltiptype_shooterdmg, posx, 1, True, ind, linkind);
          case cid of
             _aus : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_avstr
             _fra : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 2, 200, 75, 200, True, 2, 705, 250, 250, True, 2, 4560, 300, 450, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_ev
             _eng : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 220, 50, 100, True, 2, 505, 140, 200, True, 3, 3670, 100, 350, True, 1, 1000, 1720, 100, True, 1, 2060, 1900, 400, True, 1, 5900, 1150, 700, blacksmith, ''); // Mushketer_ev
             _spa : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Strelec_Spain
             _rus : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Strelec
             //_ukr : SetUpgStructFoodGoldIronCoal(upgstruct, upgplace, member, 1, 3706, 0, 2350, 0, True, 2, 12060, 0, 7850, 0, True, 3, 36706, 0, 14000, 0, True, 4, 36706, 0, 22350, 0, True, 5, 37060, 0, 34350, 0, True, 6, 55706, 0, 0, 51350, blacksmith, ''); // Kozacki_Strelec
             _ukr : SetUpgStructFoodGoldIronCoal(upgstruct, upgplace, member, 1, 3706, 0, 2350, 0, True, 2, 12060, 0, 7850, 0, True, 3, 36706, 0, 14000, 0, True, 4, 36706, 0, 22350, 0, True, 5, 37060, 0, 34350, 0, True, 0, 0, 0, 0, 0, blacksmith, ''); // C3 changes (remove last upg to damage and protection
             _pol : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 125, 150, 100, True, 2, 375, 100, 200, True, 3, 2570, 300, 450, True, 1, 2556, 1150, 400, True, 1, 3060, 1550, 100, True, 1, 3700, 1850, 600, blacksmith, ''); // Mushketer_polsha
             _swe : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 450, 550, 300, True, 2, 405, 150, 20, True, 3, 3570, 100, 290, True, 1, 1956, 1250, 700, True, 1, 1660, 1750, 400, True, 1, 4700, 1750, 100, blacksmith, ''); // Mushketer_ev
             _pru : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_ev
             _ven : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_ev
             _tur : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Ianithar
             _alg : ;
             _den : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_ev
             _net : SetUpgStructFoodGoldIron(upgstruct, upgplace, member, 1, 170, 50, 100, True, 2, 405, 150, 200, True, 3, 3570, 100, 350, True, 1, 1556, 1350, 100, True, 1, 1060, 2050, 400, True, 1, 5700, 1350, 700, blacksmith, ''); // Mushketer_ev
          end;
          AddUpgradePack(country, upgstruct, ctypeProtection, gc_upg_tooltiptype_infdef, posx, 2, True, ind, linkind);
       end;

    Различия:

    var posx : Integer = 2;
          if (cid=_ukr) then
          posx := 0;
          AddUpgradePack(country, upgstruct, ctypeDamageBullet, gc_upg_tooltiptype_shooterdmg, posx, 1, True, ind, linkind);

    Это переключатель, который показывает, что для Украины (cid=_ukr) улучшения сердюка будут располагаться в нулевом столбце (posx := 0) а не во втором (Integer = 2), так как у Украины нет пикинеров и сердюк расположен в нулевом столбце.
    Так же будет и для других юнитов с отличным от используемого массива положением.

    Хорошо идем дальше до гренадеров: member := ‘grenadier’

             AddUpgradePack(country, upgstruct, ctypeDamagePike, gc_upg_tooltiptype_infdmg, 3, 1, True, ind, linkind);
             // make last attack upgrade to increase grenade damage
             country.upgrade[ind-1].tooltiptype := gc_upg_tooltiptype_grenadedmg;
             country.upgrade[ind-1].iarrparam1[0] := gc_obj_weapon_kind_mortarball;


    Разработчики пишут:
    // make last attack upgrade to increase grenade damage
    // последний апгрейд атаки улучшает урон от гранат.

    Тот же принцип, что и с польским пикинером, только для атаки.
    Само собой улучшать таким образом можно только тот тип атаки, который у юнита уже есть.

    Дальше идем до кода для казаков реестровых и донских, мамлюков, сипахов и затесавшегося туда польского рейтара (ищем: _rus : member := ‘cossackdon’;).
    Что тут интересного, разработчики показывают новый тип переключателя:
    //var cUpgType : Integer = _misc_SwitchInt(ctypeDamagePike, ctypeDamageSword, (cid=_pol));
    Что он делает, ну если бы был включен, переключает для польского рейтара((cid=_pol)) улучшения атаки с используемого типа пики на тип мечи.

    Например заменим польскому рейтару (это который легкий с сабелькой) в units.scripts атаку с пики на меч. Добавим в раздел где он делит соседство с мамлюками и козаками на замену:

          AddUpgradePack(country, upgstruct, ctypeDamagePike, gc_upg_tooltiptype_infdmg, posx, 1, True, ind, linkind);

    такие строчки:

    var cUpgType : Integer = _misc_SwitchInt(ctypeDamagePike, ctypeDamageSword, (cid=_pol));
          AddUpgradePack(country, upgstruct, cUpgType, gc_upg_tooltiptype_infdmg, posx, 1, True, ind, linkind);

    Что получим на выходе — мамлюки, сипахи и казаки будут по прежнему улучшать атаку пикой, а вот у польского рейтара начнут работать улучшения на атаку мечом.
    Принцип прост: первый тип улучшения атаки: ctypeDamagePike заменяется на второй ctypeDamageSword для указанного (cid=_pol) юнита. В самом AddUpgradePack заменяем тип атаки с ctypeDamagePike, как ранее использовавшийся для всех на cUpgType — переключаемый.

    Ну и последнее для юнитов:
    Что же делать если мы хотим добавить свой тип защиты для дальнейшего использования?
    Идем по адресу: // _country_InitUnitsUpgrades
    Добавляем после кода:

    procedure _country_InitUnitsUpgrades(var country : TCountry; var ind, linkind : Integer);
    begin
       const bAddIfNotExist = True;
       const cTrue = True; // for better formating and reading
       const ctypeDamagePike = 0;
       const ctypeDamageSword = 1;
       const ctypeDamageBullet = 2;
       const ctypeProtection = 3;
       const ctypeProtectionOnlyPikeArrow = 4;
       const ctypeProtectionOnlySword = 5;

    Свою строчку, например — защиту только от стрел:

    const ctypeProtectionOnlyАrrow = 6

    Идем ниже:
    procedure AddUpgradePack(var country : TCountry; upgstruct : TUpgStruct; upgradetype, tooltiptype, x, y : Integer; bEnabled : Boolean; var ind, linkind : Integer);
    Добавляем после:

    begin
          var j : Integer;
          for j:=0 to 5 do
          begin
             if (upgstruct.value[j]<>0) then
             begin
                var iupgtype : Integer = gc_upg_type_none;
                var iupgtype2 : Integer = 1;
                var iparam1, iparam2, iparam3 : Integer;
                case upgradetype of
                   ctypeDamagePike : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_pike;
                   end;
                   ctypeDamageSword : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_sword;
                   end;
                   ctypeDamageBullet : begin
                      iupgtype := gc_upg_type_damage;
                      iparam1 := gc_obj_weapon_kind_bullet;
                   end;
                   ctypeProtection : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;
                   end;
                   ctypeProtectionOnlyPikeArrow : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;
                   end;
                   ctypeProtectionOnlySword : begin
                      iupgtype := gc_upg_type_protection;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iupgtype2 := 2;
                   end;
                   ctypeProtectionOnlySwordPike : begin
                      iupgtype := gc_upg_type_protection;
                      iparam1 := gc_obj_weapon_kind_pike;
                      iparam2 := gc_obj_weapon_kind_sword;
                      iupgtype2 := 2;
                   end;

    Свою расшифровку типа используемой брони:

                   ctypeProtectionOnlyArrow : begin
                      iupgtype := gc_upg_type_protection;
                      iparam3 := gc_obj_weapon_kind_arrow;
                      iupgtype2 := 2;
                   end

    И в начале этого кода добавляем 6 вместо 5, так как у нас добавился новый тип защиты.
    Все, теперь вы можете выдавать юнитам улучшения защиты только против стрел.

    Last edited: Dec 21, 2016

  9. А как менять файл с расширением » PALETTE «?

  10. Вручную, в текстовом редакторе.
    Чтобы понимать как именно его менять, можно зайти в Игровой редактор. Там есть меню с палитрами и таблица.

    Могу подробнее описать, но только позже.

  11. Спасибо;)

  12. Mark Kandaurov подскажи где писать логику миссии?

  13. Лично я не знаю. Однажды пытался сделать хотя бы что то простое, но в редакторе черт ногу сломит, а гайдов от разработчиков пока нету.

  14. M.K.даёте обещаный ПЫСами RPG!:D


Cossacks 3 Community Forum

#1

Maxim Suvorov

    Значний Радец

  • Генеральна Cтаршина
  • 9 327 сообщений
  • Откуда:Киев — Бруклин, южный Бруклин…
  • Прозвище:Архистратиг
  • Награды:
  • Создатель:Ogniem i Mieczem:TW; XIII век:Русич; M&B:ОиМ; Cossacks3

Регистрация: 29.окт.06

Отправлено 22 Сентябрь 2016 — 13:07

1) Скрипты в игре

Все скрипты в игре находятся в открытом виде в ввиде файлом *.aix, *.inc

Краткая структура віглядит так:

..datagui -> тут находятся скрипты игрового меню

..datascripts -> тут находятся скрипты игровой логики

Расмотрим их повнимательнее:

dmscripts.global -> тут находятся глобальные константы для всех скриптов которые будут видны в любом файле игровой логики (как в гуях, так и в «скриптах»)

scriptslib : (это библиотечные функции которые тоже видны в любых скриптах игры)

ai.script => общие функции для игрового АИ (править там особо нечего)
classes.script => базовые класы описания юнитов, фракций, игровой карты, в общем почти всех логических объектов в игре
unit.script => тут находится игровая логика юнитов — как идёт расчёт урона, нанечения повреждений и всего такого
country.script => тут находится описания фракций, кто какие апгрейды умеет делать, какие юниты строить и так далее
serial.script => функции обработки загрузки и сохранения игровых данных. (например вы добавили какой-то супер-глоабальный апгрейд и теперь хотите что бы его можно было сохранять/загружать — тогда это сюда)
misc.script => различные функции которые «не влезли» в другие файлы

Синтаксис и логика скриптов — это обычный Object Pascal (с некоторыми оговорками)

  • 0

Жизнь любит всех, но некоторых в стиле садо-мазо.

За великим рахунком це психопатологія. Жити в Україні і не любити Україну. Зробити з мови політику, за мовною ознакою дискримінувати націю.
© Ліна Костенко, «Записки українського самашедшого»

— Та фашист и бандеровец!
— Да, я знаю, у нас вся синагога такая.

  • Наверх


#2 ЗВЕРОБОЙ

ЗВЕРОБОЙ

    Козак

  • CиЧевик
  • 192 сообщений
  • Откуда:Центральная Украина.

Регистрация: 19.ноя.12

Отправлено 24 Сентябрь 2016 — 13:06

Что слышно о инструментале? Разработчики ещё не анонсировали?

  • 0

— Ты можешь покрыть себя славой?
— Слава – это когда один на один… или один против ста. А это не слава… Это война!

  • Наверх


#3 Maxim Suvorov

Maxim Suvorov

    Значний Радец

  • Генеральна Cтаршина
  • 9 327 сообщений
  • Откуда:Киев — Бруклин, южный Бруклин…
  • Прозвище:Архистратиг
  • Награды:
  • Создатель:Ogniem i Mieczem:TW; XIII век:Русич; M&B:ОиМ; Cossacks3

Регистрация: 29.окт.06

Отправлено 25 Сентябрь 2016 — 20:08

Что слышно о инструментале? Разработчики ещё не анонсировали?

Редактор уже выложен, притом выложен и редактор для скриптов и прочего контента. 

  • 0

Жизнь любит всех, но некоторых в стиле садо-мазо.

За великим рахунком це психопатологія. Жити в Україні і не любити Україну. Зробити з мови політику, за мовною ознакою дискримінувати націю.
© Ліна Костенко, «Записки українського самашедшого»

— Та фашист и бандеровец!
— Да, я знаю, у нас вся синагога такая.

  • Наверх


Реверс-инжиниринг «Казаков», часть последняя: второе дыхание

Время на прочтение
9 мин

Количество просмотров 44K

После нескольких месяцев работы над исходным кодом игры «Казаки: Снова война» я наконец-то могу умыть руки и представить результат своих трудов. В этой статье мне хотелось бы поделиться с вами опытом рефакторинга этого незаурядного проекта, в частности кодовыми курьёзами. Всем любителям некро-программирования посвящается…

Начало

Первое и одно из самых неприятных препятствий было не в коде, а в самих проектах. Всего их четыре:

  • статическая библиотека CommCore.lib (сетевой протокол GSCp на базе UDP)
  • динамическая библиотека IntExplorer.dll (игровое лобби на сервере)
  • динамическая библиотека IChat.dll (чат в игровом лобби)
  • исполняемый файл dmcr.exe

И эти ребята повидали некоторого кода! Из-за круговой зависимости между проектами IChat.dll и dmcr.exe для линковки требуется хотя бы один lib-файл из предыдущей сборки. Сами файлы проектов неверно конвертируются в Visual Studio 2015: они содержат ссылки к библиотекам, которые не отображаются в свойствах проекта, абсолютные пути к файлам в системе одного из разработчиков и прочие сюрпризы. В конце концов мне надоели танцы с бубнами, и я создал все проекты заново, попутно узнав, что в архиве присутствуют лишние файлы с устаревшим исходным кодом, который при включении в проект приводит к конфликтам. Ну и куда уж тут без особенностей линковки, для которой обязательно нужно исключить libc.lib.

Стартуем

Так, с проектами разобрались, теперь можно браться за дело. Компилятор рад за нас и приветствует множеством ошибок C2065: необъявленный идентификатор. Смотрим код и видим повсюду такую картину:

for (int i = 0; i < max1; i++) { /* Цикл №1 */ }
for (i = 0; i < max2; i++) { /* C2065 */ }
//Или такую:
for (int i = 0; i < max && arr[i] != value; i++); /* Пустой цикл для поиска индекса */
if (i < max) { /* Есть совпадение в массиве. И C2065 тоже есть */ }

Конечно, можно было бы выставить /Zc:forScope- и забыть об этом, но мы же не кочегары и не плотники. Правим ручками больше сотни таких отрезков кода, продолжаем.

Следующее препятствие заключалось в графическом элементе, точнее в DirectDraw 7. Он активно использует механизм замены системной палитры. И если ранее это было повсеместной практикой, то начиная с Windows Vista такие фокусы больше не проходят. Дело в том, что DWM вместе с Windows Aero вплотную работают с палитрой и не терпят конкуренции. В итоге множество старых игр страдают от искажения цветов.

Не являясь экспертом по DirectX, я стал искать готовое решение и нашёл его в версии «Казаков», опубликованной на Steam в 2010 году. Помимо самой библиотеки ddraw.dll в папке с игрой присутствует дополнительная библиотека mdraw.dll, которая экспортирует функцию инициализации DirectDrawCreate(). Скажу честно – я не знаю, что именно ребята из GSC написали в их библиотеке DDemu DirectDraw Emulator в 2008 году, но она прекрасно справляется со своей задачей. Недолго думая я добавил соответствующую обёртку в Ddini.cpp и забыл об этой проблеме.

Затем встал вопрос об отладке полноэкранного приложения. Здесь мне снова повезло – в коде был предусмотрен отладочный режим, в котором игра запускалась в углу экрана в безрамном окне с фиксированным размером. Мне требовалось лишь довести его до ума, добавить смену разрешения, обработать захват и возврат курсора в зависимости от того, в меню ли игрок или в активной игре и добавить соответствующие параметры при старте. Теперь можно было удобно запускать игру в отладчике с ключом /window.

Небольшое отступление

Далее представлены странные, проблемные и ошибочные участки кода игры «Казаки: Снова Война», с которыми я столкнулся во время работы. Прошу учесть, что целью данной статьи ни в коем случае не является критика или высмеивание разработчиков данной игры. Я считаю, что «Казаки: Снова Война» представляют собой исключительный результат усердной роботы и кропотливой оптимизации небольшой команды разработчиков, которые очень высоко подняли планку производительности и размаха битв для игр жанра RTS. Спасибо вам, GSC!

Весёлая арифметика

Одной из моих целей было добавление настроек для многопользовательских игр, например, возможность отключать дипломатический центр и рынок или ограничивать доступные корабли в верфи. Расширив интерфейс игровой комнаты и добавив нужные ветки в коде, я увеличил массив PlayerInfo.UserParam[], в котором хранятся эти настройки, с семи до десяти элементов. Вот только протестировать новые опции никак не получалось — при старте игры ИИ начинал распоряжаться моими крестьянами вместо своих и играть за меня, при этом его крестьяне стояли неподвижно. Весело, но так не пойдёт.

Причина такого поведения ИИ крылась в следующем финте ушами при копировании настроек от хоста игры в буфер обмена:

//PlayerInfo PINFO[8];
//byte* BUFB = (byte*) ( BUF + 10 + 8 + 32 - 10 );
memcpy( BUFB, PINFO[HostID].MapName + 44 + 16, 16 );

А вот так декларирована структура PlayerInfo:

#pragma pack(1)
struct PlayerInfo {
  char name[32];
  DPID1 PlayerID;
  byte NationID;
  byte ColorID;
  byte GroupID;
  char MapName[36 + 8];
  int ProfileID;
  DWORD Game_GUID;
  byte UserParam[7];
  byte Rank;
  word COMPINFO[8];
  //… (ещё 12 элементов)
}

Как видим, по смещению MapName + 60 находится COMPINFO[8]. Соответственно, при увеличении массива UserParam[7] вызов memcpy() промахивается, и в буфер попадают неверные данные о том, за каких игроков должен играть ИИ. Проблема решается заменой офсетной математики на прямое обращение по адресу PINFO[HostID].COMPINFO.

В итоге я всё же принял решение не трогать UserParam[], а добавить массив UserParam2[3] в конце структуры, так как в одном из последних элементов хранится версия клиента и любое изменение структуры до него чревато неверным определением версий в игровом лобби. А так игроки с версией 1.35 будут видеть, что у других обновлённая версия игры.

Какие уроки можно почерпнуть для себя из этого?

  • В структуре, передающейся по сети, первым элементом должна быть версия клиента.
  • Никогда не исходить из того, что расположение структуры в памяти будет неизменным.
  • Писать функции сериализации, а не полагаться на #pragma pack(1) и побайтовое копирование.

Невидимый Джо Дефайн

Разбираясь с механикой отображения внутриигровых текстовых сообщений с целью увеличения времени отображения и максимального количества сообщений на экране, я наткнулся на занимательную константу:

#define SIGNBYTE ''
void ShowCharUNICODE( int x, int y, byte* strptr, lpRLCFont lpr ) {
  if (strptr[0] == SIGNBYTE) { /* юникод */ }
  else { /* ascii */ }
}

«Ну и что в этом такого? — спросите вы — Всего лишь пробел в качестве константы». Ну, во-первых, пробел был бы крайне странным выбором для идентификации чего-либо в строке текста, а во-вторых это вовсе не пробел. SIGNBYTE определён как 0x7F, или управляющий символ DEL. И если ваш браузер достаточно осмотрителен и хотя бы показывает, что между кавычками что-то есть, то Visual Studio 2015 вероломно рисует », между которыми курсор «спотыкается» на один символ.

Пожалуйста, если уж вы используете непечатаемые символы, то указывайте их в коде через шестнадцатеричное значение, а не как символ.

Правовой аспект

Всякий раз удивляюсь, когда для запуска игры требуются права администратора. И всякий раз думаю что-то вроде «ну как так можно игры программировать-то». Но в этот раз у меня и код на руках был, и собирал его я сам, а окно UAC всё так же не давало мне покоя.

Ответ нашёлся совершенно случайно, когда я подумал, что неплохо было бы вписать в свойства исполняемого файла информацию о том, что эта версия игры не оригинальная и не поддерживается разработчиками. Манифеста в проекте, естественно, не было, но был файл ресурсов Script1.rc. Каково же было моё удивление, когда после изменения блока VS_VERSION_INFO игре перестали требоваться права администратора!

Оказывается, ОС Windows начиная с Windows Vista применяет эвристический алгоритм для определения приложений, которым может потребоваться повышение привилегий. Называется эта функция «Технологией обнаружения установщика» (см. статью в ИТ-центре Windows), и обычно она реагирует на ключевые слова вроде install или setup. Но в нашем случае виновником оказался параметр CompanyName — если он содержит строку «-GSC-«, то просыпается UAC и требует прав администратора.

Как уберечь своё приложение от такой эвристики со стороны Майкрософт, существующей и грядущей? А никак. Сегодня вы разрабатываете игры, а завтра уже стоите в одном ряду с Inno Setup и InstallShield.

Партизанский sscanf()

На этот баг я наткнулся, когда после добавления дополнительных настроек игры при проигрывании записей прошедших игр возникала ошибка синхронизации, т.е. запись не шла по тому руслу, по которому развивалась игра. При этом ошибка появлялась, по всей видимости, случайно и я долго не мог найти причину.

Опущу скучные детали отладки и перейду к самому соку. Настройки игры, вид карты и данные о выбранных нациях передаются через имя файла случайной карты, которое имеет формат

RN0 1A3C 12345 0KFH31CJ 4501326.m3d

, где

  • RN0: префикс RN и размер карты (0 — 2)
  • 1A3C: значение ГПСЧ для инициализации случайной карты
  • 12345: Вид карты (ландшафт, горы, месторождения и пр.)
  • 0KFH31CJ: Нации, которые выбрали игроки (0 — K)
  • 4501326: Настройки игры (Артиллерия, PT и пр.)

А теперь посмотрим на отрезок кода, который обрабатывает настройки игры, считывая имя файла случайной карты при загрузке записи игры:

int v1, v2, v3, ADD_PARAM;
char ccc[32];
int z = sscanf( Name, "%s%x%x%x%d", ccc, &v1, &v2, &v3, &ADD_PARAM );
if ( z == 5 ) { /* Интерпретация настроек из ADD_PARAM */ }

Загвоздка здесь в том, что для четвёртой переменной указан тип %x, в то время как диапазон символов в ней выходит за рамки шестнадцатиричной системы и простирается до буквы K. Если в игре присутствуют игроки, которые выбрали нации с индексом выше F, то sprintf() преждевременно закончит парсинг и вернёт 4. Параметры не будут интерпретированы, у ИИ будет неправильная информация об игре и он будет принимать другие решения, что приведёт к рассинхронизации.

В дополнение к этому идёт тот факт, что sprintf() вызывается исключительно для ADD_PARAM — остальные переменные нигде не используются. Решение проблемы относительно простое:

int options = 0;
int z = sscanf( Name, "%*s %*s %*s %*s %d", &options );
if ( 1 == z ) { /* Интерпретация настроек из ADD_PARAM */ }

Флаг * указывает функции, что значение не следует сохранять в переменной. Кстати, пoсмотреть, каким образом я реализовал кодирование 10 игровых настроек на месте тех же 7 цифр можно здесь. «Зачем?» — спросите вы. А потому что менять длину строки с именем файла карты по своему усмотрению показалось мне не очень хорошей идеей (см. выше в «Весёлой арифметике»).

Самое интересное для меня здесь это тот факт, что баг проявил себя лишь при компиляции в

MSVC 14

. Получается, что реализация функции sscanf() в стандартной библиотеке за прошедшие годы стала построже и впредь не будет прощать такие вольности со стороны программистов.

Памятка: Следовать в первую очередь требованиям документации, а не принципу «рабочий код — правильный код».

Тонкости языка

Локализация это отдельная тема для любого разработчика, но такое я увидел впервые:

#define RUSSIAN

#define _CRYPT_KEY_ 0x78CD

#ifdef RUSSIAN
#undef _CRYPT_KEY_
#define _CRYPT_KEY_ 0x4EBA
#endif

Если вам этого мало, предлагаю заглянуть под спойлер и посмотреть, для чего же нужен этот «крипто-ключ».

Не делайте так. Пожалуйста.

VOID CGSCarch::MemDecrypt( LPBYTE lpbDestination, DWORD dwSize ) {
  BYTE Key = (BYTE) ~( HIBYTE( _CRYPT_KEY_ ) );
  isiDecryptMem( lpbDestination, dwSize, Key );
}

void isiDecryptMem( LPBYTE lpbBuffer, DWORD dwSize, BYTE dbKey ) {
  _asm {
    mov	ecx, dwSize
    mov	ebx, lpbBuffer
    mov	ah, dbKey

    next_byte:
    mov	al, [ebx]
    not	al
    xor	al, ah
    mov	[ebx], al
    inc	ebx
    loop next_byte
  }
}

Вот таким нехитрым образом можно запороть локализацию на этапе компилирования. Если, например, «английскому» dmcr.exe подсунуть архив с ресурсами из русской версии, то всё, что останется от игры — окно ошибки access violation. Потому что ни до, ни после «isi memory decryption» содержимое буфера не проверяется. А вот если мы распакуем архив all.gsc, заменим файлы и запакуем его обратно, то в игре нас будет ждать русский интерфейс.

Посмотрев на эту XOR-вакханалию я решил ограничиться английской версией, но с поддержкой кириллицы в чате. Так как весь текст отрисовывается через собственные шрифты игры, я скопировал из русской версии ресурс mainfont.gp. Осталось только отловить символы, выходящие за пределы диапазона ascii, и правильно сопоставить коды букв с «индексом кадров» этого файла (формат GP используется в игре повсеместно для хранения графики, в том числе и для анимации). Не самое элегантное решение, зато работает безотказно, причём на сервере в чате с игроками под версией 1.35 тоже.

UDP без дырок

К сожалению, в оригинальных «Казаках» не был реализован механизм UDP hole punching, который позволил бы игрокам подключаться к игровым комнатам, даже если их хост находится по ту сторону NAT своего провайдера.

К счастью, товарищ, известный под ником [-RUS-]AlliGator, запустивший и поддерживающий сервер cossacks-server.net, выделил немного своего времени и мы договорились о дополнительном протоколе, по которому хост игры будет поддерживать UDP соединение с сервером, и по которому сервер сможет сообщать внешний UDP порт хоста игрокам, желающим к нему подключиться.

Все детали реализации можно посмотреть в классе UdpHolePuncher. Соединение инициализируется при создании игровой комнаты хостом, после чего он вплоть до старта игры поддерживает связь, отправляя небольшие пакеты. Это нужно, т.к. NAT может при каждом новом UDP соединении присваивать другой внешний порт, а так сервер наверняка знает, что в данный момент времени хост доступен из-за NAT по тому порту, с которого приходят пакеты.

Соответствующие изменения были внесены и в процедуру обработки команд сервера и в структуру RoomInfo в библиотеке IChat.dll. Поддерживаются следующие дополнительные переменные при создании игровой комнаты:

  • %PROF: идентификатор игрока. С помощью него сервер сможет различать хостов
  • %CG_HOLEHOST: адрес сервера, обрабатывающего UDP пакеты
  • %CG_HOLEPORT: порт сервера, на котором слушается UDP
  • %CG_HOLEINT: интервал, с которым клиент должен отправлять пакеты

Получив эти данные, хост игры открывает дополнительное соединение и поддерживает его. Желающие присоединиться получают дополнительную переменную %CG_PORT при подключении к комнате. И только если её нет, используется константа DATA_PORT.

Хотя весь этот механизм уже имплементирован в клиенте игры, протестировать мне его ещё не удалось, т.к. контакт с Аллигатором оборвался. Незадолго до этого он выложил в открытый доступ исходный код своего сервера, — спасибо тебе за это! — так что если кто-то готов поднять эстафетную палочку, я буду только за.

Послесловие

Статья и так уже вышла длиннее, чем я планировал, так что буду краток. Хотя этот проект занял продолжительное время и ощутимо истощил запас моего энтузиазма к реверс-инжинирингу и анализу исходного кода, я рад, что взялся за него. Рад, что написал тогда статью на Хабр с описанием своего первого, ассемблерного, костыля для «Казаков». Рад, что в комментарии пришёл Максим fsou11 и выложил в свободный доступ исходный код игры. Также я благодарен сообществу LCN за ценные советы, объяснения и помощь в тестировании.

Ссылки:

  • Репозиторий на GitHub с новым кодом
  • Репозиторий с оригинальным исходным кодом
  • Репозитории с исходным кодом сервера
  • Укороченный список внесённых изменений на форуме LCN
  • Дополнительная информация

Руководство для тех, кто хочет познакомится с моддингом C&C 3: TW и не знает, с чего начать.

Приготовления:

Сперва вам понадобится следующее:

  • Последняя версия игры Command & Conquer 3: Tiberium Wars (версия 1.9). Копия игры может быть как лицензионная, так и пиратская;
  • Последняя версия «MOD SDK» — пакета разработчика. Скачать можно здесь:
    • Версия с установщиком;
    • Версия без установщика;
      (Используйте второй вариант, если установщик не видит вашу игру, если она, например, куплена в Steam.)
    • Если у вас отсутствует исходный файл военного завода Нод, то скачать его можно здесь.  
  • C&С3 Mod SDK Expansion, можно скачать здесь или с сайта разработчика (Там также есть и другой полезный софт). Скачайте и разархивируйте C&C3 Mod SDK Expansion в папку MOD SDK с заменой файлов.

Текстовые файлы:
В процессе придется работать с текстовыми файлами, которые содержат код. Для их редактирования вполне подойдет встроенная программа «Блокнот», однако лучше установить удобную для написания и редактирования кода программу — Notepad++. Скачать можно с официального сайта.

3D — графика:
Для добавления в игру новых 3D моделей вам потребуется программа «3ds max 7» или «3ds max 9»/«3ds max 9 SP2», т.к. необходимые плагины имеются только для этих редакторов. Плагины находятся в MOD SDK.

Графический редактор:
Для создания и редактирования текстур вполне подойдет Adobe Photoshop CS6. Photoshop способен экспортировать изображение в приемлемый для игры формат — .tga или .dds. Чтобы экспортировать в .dds, для Photoshop’a необходимо скачать соответствующий плагин.

Установка MOD SDK:

Не имеет значения в какую директорию вы установите или разархивируете MOD SDK. Кто-то рекомендует устанавливать в папку с игрой, кто-то в Program Files. Моя папка находится просто на диске C.

Если вы не знаете, как запускать модификации, то читайте здесь.

Первые шаги. Mod.xml:

Поставим перед собой две задачи:

  • Первая — изменить скорость строительства и стоимость казармы Нод;
  • Вторая — увеличить запас здоровья мотоцикла Нод.

Примечание: голубым цветом выделяются оригинальные, не измененные коды, файлы, папки. Зеленый — то, что уже изменено или создано пользователем. Красный — важная информация.

Если вы все правильно установили, то у вас должна быть папка «MOD SDK» с вложенными в нее папками и прочими файлами.

Разделы папки MOD SDK

«Art» — в этой папке хранится вся графическая информация мода: 3D модели и текстуры;
«Audio» — как следует из названия, здесь находятся звуки и музыка, которые используются в проектах;
«BuiltMods» — папка, хранящая в себе asset’ы собранного вами проекта, кэш, прочие системные файлы. Кэш вашего проекта помогает собрать мод быстрее, так как часть информации, которая не была отредактирована с момента последней сборки, не обрабатывается, а берётся из кэша;
«CnC3Xml» — исходные коды объектов, задействованных в игре;
«DefaultUIScreens» — коды игрового UI. (User Interface);
«Documentation» — документация;
«INI» — .ini файлы, которые можно задействовать для проектов;
«LUA» — исходные коды скриптов;
«Mods» — каталог, содержащий проекты;
«Schemas» — системные файлы;
«Shaders» — исходники шейдеров;
«Tools» — различные инструменты, например, программа для просмотра .w3x — файлов.

Сейчас нас интересует папка Mods, которая находится в MOD SDK.

Изначально, зайдя в папку Mods, вы увидите папку SampleMod. Это мод, созданный составителями пакета. Не удаляйте его, так как он хранит исходные коды, которые помогут нам.

Создайте свою собственную папку в разделе Mods. Назовем ее, например, «MyFirstMod». Зайдите в MyFirstMod и создайте еще одну папку, которая должна называться «Data».

В папке Data, помимо остальных файлов мода, хранится главный файл — Mod.xml. Его можно взять из папки SampleMod/Data. Скопируйте и вставьте его в папку Data вашего мода.

Mod.xml — главный файл проекта, без него невозможно «собрать» мод. Через Mod.xml добавляются пути к остальным файлам проекта, не переименовывайте его.

Изменение параметров:

В этом пункте мы займемся изменением параметров завода и мотоцикла.

Если вы знакомы с языками программирования, то легко разберетесь, как все работает. Если нет, то в любом случае никто не заставит вас писать код с нуля, папка «CnCXml» предоставляет достаточно исходных ресурсов.

Вернитесь в папку MOD SDK и откройте CnCXml.

В CnCXml ищем папку NOD и заходим в нее. Далее открываем каталог Structures, в котором находится файл «NODHandOfNOD.xml», копируем его в папку нашего проекта к Mod.xml.
В каталоге Units находится файл «NODAttackBike.xml», копируем в папку к Mod.xml.

Все файлы в CnCXml недоступны для редактирования, так как в свойствах файла стоит галочка «Только чтение». Снимите её перед редактированием файла.

Сначала отредактируем файл казармы Нод — NODHandOfNOD.xml. Так как нас интересует время строительства этой постройки и её стоимость, то найдем строчки, которые отвечают за это.

Строка, отвечающая за время строительства:
BuildTime=“5”
Стоимость:
BuildCost=“500”

Заменим эти параметры на:
BuildTime= “10”
BuildCost=“250”

Основные параметры тега <GameObject>

«id» — идентификатор объекта. К идентификатору объекта можно обратится из прочих тегов;
«inheritFrom» — указание id объекта, из которого наследуется информация;
«SelectPortrait» — портрет объекта;
«ButtonImage» — иконка объекта;

Портрет и иконка может использовать одну и ту же картинку.

«BuildCost» — стоимость объекта;
«BuildTime» — время, за которое объект построится в производственной постройке;
«EnergyProduction» — производство энергии. Значение в скобках может быть положительным, отрицательным или равным нулю. Если параметр не добавлен в GameObject, то его значение равно 0. Юнит тоже может иметь EnergyProduction;
«KindOf» — особенности объекта. Например, SELECTABLE позволяет выделить объект с помощью курсора мыши;
«Description» — описание объекта;
«TypeDescription» — описание типа объекта.

Между <DisplayName> </DisplayName> находится идентификатор названия объекта.

Текст для DisplayName, Description и TypeDescription добавляется в Mod.str.

Сохраните измененный файл и откройте NODAttackBike.xml. Заметьте, что некоторые строки NODHandOfNOD.xml и NODAttackBike.xml совпадают. Например, «BuildTime» и «id».

Нам необходимо отредактировать запас здоровья байка, поэтому опускаемся в самый низ файла и ищем такую строчку:
MaxHealth=“800”
Заменим значение 800 на 2000 и получим такую строчку:
MaxHealth=“2000”

Сохраните измененный файл.

Совет: для поиска используйте функцию «Найти» в блокноте или в Notepad++. Для Notepad++ функция «Найти» вызывается сочетанием клавиш Ctrl + F.

Добавление файлов в Mod.xml:

Чтобы измененные файлы были «видны» для сборщика, их нужно объявить в файле Mod.xml. Откройте Mod.xml вашего проекта.

Отредактируйте файл так, как показано ниже:

Все, что находится внутри зелёной зоны — комментарий. Ни комментарий, ни текст внутри не учитывается. Так вы можете оставить заметку или закомментировать ненужную часть кода.

Не удаляйте и не изменяйте эти строчки:

Сборка проекта и запуск:

Зайдите папку MOD SDK и запустите приложение «EALAModStudio.exe». В выпадающем списке выберете MyFirstMod. 

Установите галочки напротив всех пунктов, кроме «Clear Built Mod» и «Clear Cache». Далее нажмите кнопку «Build Mod» и дождитесь конца сборки. Build Completed – строчка, подтверждающая, что сборка мода завершена.

Собранный мод находится в документах вашего компьютера в папке C:Users (Пользователи)(Имя пользователя)Documents (Документы)Command & Conquer 3 Tiberium WarsMods

И самое главное, не забудьте протестировать собранный вами мод в игре!

Рассматривается панель инструментов (в левой части экрана, Ctrl+»T»).

Режим перемещения мышкой (F9):

Выход из инструмента. Советую использовать при расстановке юнитов.

Режим текстурирования (F2):

Выбор текстуры. Если зажать Ctrl, то можно выбрать несколько текстур (миксы текстур) сразу. Зажав Shift, текстуры прономеруються. Shift+левая кнопка мыши использутся для текстурирования участков сложной формы. Min. высота, Max. высота, Min. угол, Max. угол — параметры «Автотекстурирования». Тек. высота — водите курсором по карте и высвечиваеться высота. Набор текстур №(1-10) — можно сохранить миксы текстур с параметрами «Автотекстурирования».Размер кисточки — (1-9). При текстурировании помогает (F7), когда возвышенность мешает наложить текстуру.

Режим установки деревьев(F8)/

камней(F4):

Зажав Ctrl, можно выбрать несколько деревьев/камней.

Убрать деревья/камни — клавиша «N». Размер кисточки — (1-9). Игра не расчитана на большое количество деревьев/камней.

Добавление воды («C»):

«C», а потом F6 — удаление воды.

«C», а потом F7 — создание бликов на воде.

«C», а потом F8 — удаление бликов в воде.Размер кисточки — (1-9). Также можно создать направления течений (левой кнопкой мыши добавлять, а правой стирать). Направления течений можно указывать не только для самой воды, но и для большинства «водяных текстур».

Добавлять, убирать высоту возвышенностей:

Левая кнопка мыши — добавлять, правая — убирать. Также можно, редактировать удерживая Shift (появится окно выбора действия, если отжать Shift). Размер кисточки — (1-9).

Поднимать, понижать равнину:

Аналогично предыдущему, только по-другому изменяет рельеф.

Режим сглаживания:

Левая кнопка мыши — сглаживает рельеф (на какую высоту попадает больше кисточки, на ту и будет сглаживать). Правая — деформирует рельеф. Размер кисточки — (1-9).

Создать плато:

Левая кнопка мыши — делает плавнее переход из высокого с плато к ровной поверхности (советую использовать для спуска с плато). Правая — быстро делает плато (нужно нажимать на возвышенность). Размер кисточки — (1-9).

Режим редактирования областей рельефа:

Левой кнопкой мыши обводим определенный участок земли, а правой — заканчиваем обвождение. Появиться окно «выбор действия». Cubic line — плато без зубцов; Quad line — плато с зубцами; Softing — подровнять, немного понизить рельеф; Road(1-3) — дорога. Указываем высоту рельефа или ширину, для дороги. И нажимаем OK.

Вставить сохраненный участок ландшафта (Ctrl+»V», Ctrl+»C» — для сохранения участка):

Нажимаем Ctrl+»V», затем выбираем образцы (сэмплы). Все образцы лежат в папке «Корневой каталог»UserPieces*.smp. Чтобы сохранить — выделяем участок земли и нажимаем Ctrl+»C» (в выделеном участке должен быть изменен рельеф, иначе образец не сохранится). Появиться окно Save Sample, набираете имя и ОК.

Режимы блокирования (переключать «S»):

Установка непроходимых зон. При редактировании непроходимых зон, лучше избегать узких проходов, так как в подобных местах возможно застревание юнитов. Если такой проход есть, поставьте с двух сторон от него флажки, чтобы облегчить юнитам поиск пути (флажок это последний элемент в окне «Режима установки деревьев» ). Флажок — это служебный елемент и во время игры отображаться не будет.

Выбрать юнита («P»):

Расстановка юнитов. Плохо, что размер кисточки менять нельзя. Стены и частоколы нужно возводить крестьянами.

Создать зону для миссии (только с нажатым Scroll Lock):

Зоны — это некоторые територии на карте, нужны для создания «условий»/»действий» к редактору сценариев (миссий). Советую подбирать соответствующие имена.

Создать группу для миссий (только с нажатым Scroll Lock):

Выделяем юнитов, нажимаем «Создать группу…», именуем ее. Советую в названии писать имя юнита, цвет игрока и кол-во юнитов. Группы (юнитов) — также, как и зоны нужны для создания «условий»/»действий» к редактору сценариев (миссий).

Редактировать рельеф (F6):

С нажатием F6, открываеться окно «Редактор рельефа».

Типы выступа.

ровный, немного закругленный в конце и немного изогнутый внутрь в начале, тип выступа;

в отличии от предыдущего, в конце не закругленный;

противоположный предыдущему, т.е. в конце закругленный, а в начале нет;

создает какой-то непонятный ландшафт (см. скриншот), или это у меня редактор глючит, если у Вас что-нибудь другое, то скиньте мне по мылу;

создает ландшафт с выступами на склонах.

Типы нажатия.

из ровной земли делает возвышенность;

сначала уменьшает наивысшею возвышенность, и постепенно уменьшает другие возвышенности;

увеличивает возвышенность;

просто уменьшает возвышенность.

Типы кисти.

круглый тип кисти;

тип кисти с зубцами;

Высота.

вычисляет и запоминает (с нажатием левой кл. мыши) в параметре «Высота возвышенности» высоту возвышенности на которой находится курсор.

Параметры.
«Высота возвышенности» — без комментариев;
«Радиус возвышенности» — радиус поверхности возвышенности;
«Ширина пересечения» — радиус самых нижних точек поверхности, крутость склона;
«Кол-во, глубина и фаза выступов» — работает, только, для типа кисти.

Редактировать ландшафт:

Преобразование (генерация) из *.bmp картинок, которые лежат в каталоге «Корневой каталог»UserTerrain*.bmp, в карты. Сам *.bmp файл состоит из трех частей, которые можно отредактировать в графическом редакторе. Первая — вода (синий), земля (черный), горы (белый), деревья, а если точнее сохраненные участки, сэмплы, с деревьями(зеленый). Вторая — подтверждает горы (белый переливаеться с черным). Третья — сглаживание (красный). Чтобы загрузить созданную *.bmp картинку , нужно в окне «редактора поверхности», нажать кнопку «Загрузить Рисунок» и выбрать соответствующую *.bmp картинку.

Если не хотите редактировать *.bmp картинки в графическом редакторе, можно редактировать в редакторе карт, в окне «редактора поверхности». Здесь все те три части выглядят как одна карта. Созданные таким образом изображения можно сохранить на диске, нажав кнопку «Сохранить Рисунок». Нажатие кнопки «Создать» приведет к генерации карты по нарисованой *.bmp картинке.

Внимание! При генерации все объекты, ранее нанесенные на карту, будут уничтожены. Если не хотите создавать карту, нажмите кнопку «Отмена».

Список инструментов в окне «редактора поверхности».
«Добавить дерево» — добавить сохраненные участки, сэмплы, с деревьями (первая часть *.bmp, зеленый).
«Добавить воду» — вода (первая часть, синий).
«Добавить возвышенности» — добавляет участки для гор (первая часть, белый). Взаемодействует с «Редактировать высоту возвышенностей».
«Добавить зоны сглаживания» — сглаживание (третья часть, красный).
«Редактировать высоту возвышенностей» — подтверждает участки для гор (вторая, белый переливаеться с черным).
«Отменить действие («Z»)» — отменяет любое количество совершенных действий.

Подсчитать блокировку суши и воды:

Автоблокировка. Блокирует участки на воде и на горах (где надо). При автоблокировке разблокируються ранее заблокированые участки.

Советую пользоватся клавишей Ctrl+»X». В отличии от автоблокировки не разблокируються ранее заблокированые участки. И еще добавит на мини карту горы, которые не добавлены. При глюке когда делаете высокие горы (на горе появляются «прозрачные прямоугольники»).

Переключать между режимами 2D и 3D (F7):

Полезная вещь. Не ровная поверхность стает затемненной, а ровная — светлой. Советую использовать при текстурировании и расстановке юнитов (если юнита нужно поставить за горой). Иногда полезно, в этом режиме, ставить деревья и камни, но бывают побочные эффекты (например, деревья устанавливаються на горе, а не за горой). Лучше не использовать при добавлении воды.

Задать стартовые параметры («R»):

Здесь задаються начальные ресурсы для каждого игрока, а также параметры карты для игры с компъютером (AI).

Если хотите, чтобы какой-либо из сторон на карте управлял компъютер (AI). Нужно поставить на карту рабочих. Место их расположения будет стартовой точкой для компъютера (AI), здесь он начнет сторить свою базу. Стартовая точка должна находиться на достаточно открытом месте, чтобы компъютер (AI) имел пространство для строительства. Рядом со стартовой точкой должны находиться лес и камень, и, как минимум, по одному месторождению железа изолота. Лучше ставить по два месторождения каждого типа. Компъютер (AI) плохо развиваеться на замкнутых пространствах, кроме того постарайтесь не делать на карте узких проходов. Для нормальной работы компъютера (AI) необходимо изначально дать большое количество еды (где-то 5000).

Режим перемирия («W»):

Юниты разных цветов — не нападают друг на друга. Советую использовать перед установкой юнитов или перед загрузкой карты, в которой есть юниты.


21 февраля 2017


21.02.17

3

27K

Совсем недавно GSC прикрутили поддержку мастерской Steam в Казаках, поэтому теперь любой может с легкостью опробовать интересные моды для Казаков 3, о лучших из которых вы узнаете из этого видео.


Cossacks 3

Платформы

Жанр

Дата выхода

20 сентября 2016


+108

Лучшие комментарии


Dakart

24 февраля 2017, 14:44

Определенно игра-топ! А модов пока просто очень мало


DemonFrumpel

22 февраля 2017, 14:01

Моды супер, особенно когда фоны для главного меню установил, игра прямо новыми красками засияла, комплитли нью гейм икспириенс.
Если это лучшие, страшно даже худшие загружать.
А вообще я все жду, когда разрабы выпустят обещанный «невероятного удобства мод-кит», и камеру сделают с перспективой, как в роликах.
P.S. Не смотря ни на что, игра — топыч, вернули мне мой 2000.


Dakart

21 февраля 2017, 19:44

Предлагаю к ознаКомлению!

Как создать свой МОД с измененным балансом в Казаки снова война.

Как создать свой МОД с измененным балансом в Казаки снова война.

20. Казаки снова война 2 на 2. Сильвер (Дания), Кол (Украина) — Атаман (Дания), Кулебяка (Украина)Подробнее

20. Казаки снова война 2 на 2. Сильвер (Дания), Кол (Украина) - Атаман (Дания), Кулебяка (Украина)

Делаем из Казаки 3 Казаки Снова Война! Обзор модов Казаки 3!Подробнее

Делаем из Казаки 3 Казаки Снова Война! Обзор модов Казаки 3!

Казаки Снова Война Самые Нужные Комбинации Клавиш и Секретные ПриёмыПодробнее

Казаки Снова Война Самые Нужные Комбинации Клавиш и Секретные Приёмы

1000 МОРТИР VS 100 ПИКИНЕРОВ КАЗАКИ СНОВА ВОЙНАПодробнее

1000 МОРТИР VS 100 ПИКИНЕРОВ КАЗАКИ СНОВА ВОЙНА

Моя самая крутая миссия в игре «Казаки снова война»Подробнее

Моя самая крутая миссия в игре 'Казаки снова война'

Cossacks Back To War/Казаки Снова Война(Редактор Карт)Подробнее

Cossacks Back To War/Казаки Снова Война(Редактор Карт)

Великая Осада Мальты в Казаки снова война.Подробнее

Великая Осада Мальты в Казаки снова война.

Как Строить Склад Правильно в Казаки Снова Война — Гайд + Секретная ФишкаПодробнее

Как Строить Склад Правильно в Казаки Снова Война - Гайд + Секретная Фишка

Казаки снова война: урок по созданию карт в редактореПодробнее

Казаки снова война: урок по созданию карт в редакторе

События

Понравилась статья? Поделить с друзьями:
  • Льдогенератор gemlux gl im 88 инструкция
  • Письмо академика сахарова советскому руководству
  • Фуросемид ампулы инструкция по применению внутривенно
  • Майнкрафт руководство по выживанию читать
  • Secret agent x инструкция по установке