Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Обсуждение моддинга
DF2 :: ФОРУМЫ > Игровые форумы > Heroes of Might & Magic III > Моды
Страницы: 1, 2, 3, 4
MasterOfPuppets
Не забывайте, что на то, чтобы первый раз копнуть, нужно выйти из города, потом переждать ещё день... Кто будет рестартить игру с таким мизерным шансом достать из земли артефакт, да ещё и крутой? И сейв-лоад тоже будет горьким трудом. А карта "как после бомбёжки" - это только эстетика. Я бы даже сказал, будет смешно на это поглядеть.
К тому же, раскопки - противовес человека той самой неконтролируемой скупке ИИ артефактов в ТА и ЧР. К тому же, у ИИ бесконечные ресурсы.
Меня устраивает.

Залатал всякие дырки в отрисовке пункта «E» шаблона DL (там чуть ли не половина параметров в вог-функции перепутана), сделал (через задницу, но стабильно) получение введенного текста в z-переменную от любого такого пункта в диалоге. Также можно ограничивать не просто ширину элемента, но и длину введённого текста.
Потом сделал поддержку русской раскладки. Довольно дилетантски: хук на вызовы sub_4EC7C0. В хуке – вызов её, затем получение имени текущей раскладки (GetKeyboardLayoutNameA). Если оно равно 00000419 – замена вводимого символа на русский из специальной таблицы, основываясь на смещении, равном машкоду введённого символа. Если кто знает лучший способ – можете посоветовать. Я ж ламер.
А под клэшовский «Bankrobber» украл прямо из HiRez.dll длиннокод вставки текста из буфера обмена и запилил в недавно начатую MoP.dll.
Всё это, конечно, не только к DL применимо, но и ко всем исконным игровым диалогам со вводом. Например, ввод имён игроков для хот-сита.
Так что можно, наконец, избавиться от ограниченных вог-диалогов, которые только тем и удерживали, что вводом букофф.
Остаётся крохотная бяка: при вводе/стирании текста в DL где-то работает какая-то жирная воговская функция, заметно тормозящая действие. Надо выпилить.
Первые плоды:


Решил не париться с созданием шаблонов для рандомок, а предоставить выбор шаблона игроку, наподобие HD. В MoP.ini есть теперь секция, имеющая вид:
[RMG Template]
TxtFileName=rmg.txt
Ну и всё какбэ ясно. Перед генерацией рандомки (не обязательно перед запуском игры) пишите туда имя нужного вам файла, который вы положили в data или в лод – и все.
По умолчанию, как видно, стоит оригинальный текстовик.
В поставку шаблоны не входят.
Algor
Цитата(tolich @ 19 Jul 2011, 13:25) *
По крайней мере, от размеров армии это время не зависит: вообще без армии герой копает ровно столько же времени, сколько с тысячей крестьян.


"По крайней мере, от размеров армии" не зависит очень много вещей, таких как расходы на еду, затраты маны на левитацию/полет и пр. и пр. - это и называется "игровой условностью".

Думал, тут не возникнет вопросов, но теперь поясню: первичная цель любой армии - сражения.
Т.к. герой ежедневно ничего не отстегивает армии, армия должна "кормиться с поля боя" (мясом, трофеями, мародерством - не важно).
Соответственно, если герой вместо похода за добычей решает копать земляных червей (это не поиск святого грааля, заметьте), армию это должно несколько расстраивать, как и любое другое занятие, не свойственное доблестным воинам (бухать в таверне неделями, "защищая" город - занятие доблестное, не путать!).
Haart of the Abyss
Цитата
первичная цель любой армии - сражения.
Даже тех двух феечек, что даются герою-разведчику для разгона (и чтобы в случае внезапного нападения он успел сделать ноги, пожертвовав этими двумя феечками — в этом их главное преимущество над полным отсутствием армии там, где последнее легально)?
MasterOfPuppets
Цитата(Algor @ 19 Jul 2011, 11:33) *
И да... опцию, уберите это в опцию. Хотя бы до второго релиза, а там будет видно оставлять вшитым наглухо или нет.

Ну сделал опцией.
На квартиру к автору следующего предложения по опциональности в ближайшие сутки будет направлен наряд НКВД, после чего он будет в скорейшем порядке отправлен по этапу на Колыму.
Да, у нас тут такая шуточка ходит: "Вот ведь жалко - и сослать-то тебя некуда...".
feanor
Цитата
после чего он будет в скорейшем порядке отправлен по этапу на Колыму.

К тебе на стажировку?
tolich
Именно, колымить на него. spiteful.gif
MasterOfPuppets
Ну да. Я асматик (кхе-кхе).
Метод ужасен, но для меня главное - результат, а не удобство. А скорость, компактность и стабильность асм даёт по максимуму.

Цитата
Оп, дак это еще и у МоР'a сорцы стащить можно будет?

Не, этого вы никогда не увидите. tongue.gif

Длл-кой больше, длл-кой меньше. Выпилил EraUtils.dll, написав вместо неё свои расово ассемблерные, ультракомпактные и супермогучие функции по выделению/освобождению памяти, загрузке/сохранению файлов.
И как-то сразу после этого мод стал постабильнее... А, что я сказал? shok.gif Ну да, у мода есть пара-тройка багов, но все они (слава сотоне!) связаны со скриптами, а не с экзешником. Так вот, я написал эти функции, потому что краши с шансом 1/7-1/10 на старт карты постоянно указывали на EraUtils.dll, а места в скрипте вечно показывались разные. Убив на отловку бага целые месяцы, но так и не выяснив причины, решил сделать всё по-своему.
Тем не менее, я не обвиняю Берса в том, что длл содержит ошибки. Может, её поведение в моде чем-то нарушается.
MasterOfPuppets
Самовольно и не спросив человека, добавил Aleee в члены нашей команды. Думаю, за популяризацию мода на HC он того вполне заслуживает. А ещё перевод впереди.
Вообще, когда сам себя рекламируешь - появляется чувство гадливости. А вот когда кто-то другой, просто по собственному желанию - приятно.
Нас теперь трио. Надо бы уже и в турне отправляться...
Berserker
EraUtils чиста как слеза ангела. Скорее всего у тебя где-то типичные ошибки работы с памятью (двойное освобождение, использование висячих указателей или перезапись в место больше блока памяти). В итоге это вызывает случайные вылеты рано или поздно. Вот код:

Код
LIBRARY EraUtils;
USES Win, Utils, Classes;

VAR
    Mem: Classes.TList;

FUNCTION MemAlloc (Size: INTEGER): POINTER; PASCAL;
BEGIN
    GetMem(RESULT, Size);
    Mem.Add(RESULT);
END; // .function MemAlloc

PROCEDURE MemFree (Ptr: POINTER); PASCAL;
BEGIN
    Mem.Delete(Mem.IndexOf(Ptr));
    FreeMem(Ptr);
END; // .procedure MemFree

PROCEDURE MemClear; PASCAL;
VAR
    (* TEMP *)
    i: INTEGER;
    
BEGIN
    FOR i:=0 TO Mem.Count - 1 DO BEGIN
        FreeMem(Mem[i]);
    END; // .for
    Mem.Clear;
END; // .procedure MemClear

FUNCTION FileToMem (Path: PCHAR): POINTER; PASCAL;
VAR
    S: STRING;
    hFile: INTEGER;
    Size: INTEGER;
    
BEGIN
    S:=Path;
    hFile:=Utils.FileOpen(S, Utils.fmOpenRead OR Utils.fmShareDenyWrite);
    IF hFile < 0 THEN BEGIN
        RESULT:=NIL; EXIT;
    END; // .if
    Size:=Win.GetFileSize(hFile, NIL);
    IF Size <= 0 THEN BEGIN
        RESULT:=NIL; EXIT;
    END; // .if
    GetMem(RESULT, Size);
    Mem.Add(RESULT);
    Utils.FileRead(hFile, RESULT^, Size);
    Utils.FileClose(hFile);
END; // .function FileToMem

PROCEDURE MemToFile (Path: PCHAR; Buf: POINTER; Num: INTEGER); PASCAL;
VAR
    S: STRING;
    hFile: INTEGER;
    
BEGIN
    Win.DeleteFile(Path);
    S:=Path;
    hFile:=Utils.FileCreate(S);
    IF hFile < 0 THEN BEGIN
        EXIT;
    END; // .if
    Utils.FileWrite(hFile, Buf^, Num);
    Utils.FileClose(hFile);
END; // .procedure MemToFile

EXPORTS
    MemAlloc, MemFree, MemClear, FileToMem, MemToFile;

BEGIN
    Mem:=Classes.TList.Create();
END.


VirtualAlloc выделяет память гораздо медленнее и кратно страницам, поэтому это может сглаживать ошибки типа AccessViolation.
MasterOfPuppets
Так я и думал, что дело не в ней. Тем более, что её использование экзешником, а не скриптами, никогда не давало сбоя.
Просто ошибки ужас какие странные. Всегда указывает на выделение памяти скриптом, но всегда разный участок. Выпиливал участки, добавлял по одному, смотрел во все глаза до слёз - ничего не увидел. Закономерность всего одна: чем больше выделений - тем больше ошибок.
При запуске процесса через Олли она что-то шептала именно про EraUtils (не помню). Про Angel и пару других - ничего.
Berserker
В принципе, можно кое-что сделать. Например для тестирования я могу добавить обработчики исключений к каждой функции. Тогда можно будет хотя бы знать, в каком месте вылет и увидеть нормальную ошибку. В частности, двойное освобождение ловится, когда указатель не находится в списке.
MasterOfPuppets
В принципе, уже неважно, но вот такие вопросы:
после MemToFile память нужно освобождать? Я освобождал. Но, даже если не надо, выделения памяти на старте карты с файлами не связаны.
Разве, если память освободить нельзя (её не существует), функция не должна просто терпеть неудачу, возвращая EAX=0 и не вызывая ошибок?
Berserker
Цитата
после MemToFile память нужно освобождать? Я освобождал.

Если память больше не нужна, то да. Иначе нет. MemClear вообще все выделения освобождает.

Цитата
Разве, если память освободить нельзя (её не существует), функция не должна просто терпеть неудачу, возвращая EAX=0 и не вызывая ошибок?

Нет. В данном случае происходит поиск указателя в списке и исключение, если такого не найдено. Можно сделать отдельно обработку адреса 0, чтобы он игнорировался, но если убрать проверку на наличие в списке, ты не сможешь отлавливать многие ошибки, когда случайные данные подаются функции под видом указателя. В принципе, реализацию можно сделать любую, только если есть ошибки в моде, это вряд ли повысит его стабильность.
MasterOfPuppets
Ладно, забей. Мои проблемы.
Berserker
В Эре 1.9 есть команда SN:M:

Цитата
<< Работа с дополнительной памятью >>
!!SN:M[...];

ЕРМ переменные статичны и ограничены в количестве. Статичность приводит к невозможности организовывать динамические структуры данных (например, списки), для которых нужны функции выделения и освобождения памяти, а ограниченное количество ведёт к необходимости строгого учёта индексов без возможности выйти за их пределы. Более того, ЕРМ строки в виде z-переменных занимают ровно 512 байт каждая в независимости от размера их содержимого. Эра предоставляет программисту до 2 млрд. слотов под массивы новых переменных (числовых или строковых). Размер массивов может изменяться средствами ЕРМ. Поскольку работа с динамическими структурами предполагает автоматическое выделение номеров слотов, то такая возможность присутствует. Слоты с положительными индексами принадлежат пользователю, а с отрицательными используются при автовыделении памяти.

<< Удаление слота памяти >>
!!SN:M[номер слота, начиная с 0];

Пример:
!!SN:M5; удалить слот 5

<< Получение/установка размера слота >>
!!SN:M[номер слота]/[?][количество элементов];

Размер слота - это количество элементов в массиве. Команда возвращает -1, если слот не существует.
Пример:
!!SN:M2/5; установить количество элементов в слоте 2 равное 5.
!!SN:My1/?y2; получить размер слота y1 в переменную y2.

<< Работа со значениями элементов слотов >>
!!SN:M[номер слота]/[номер элемента, начиная с 0]/[?][значение];

Пример:
!!SN:M1/3; размер слота 1 - 3 элемента
!!SN:M1/0/111 M1/1/222 M1/2/333; содержимое слота 1: 111, 222, 333
!!SN:M1/1/?y5; y5 - содержимое 1-го элемента слота 1
!!IF:M^%Y5^; выведет: "222"

<< Получение адреса элемента слота >>
!!SN:M[номер слота]/?[адрес элемента]/[номер элемента];

! Внимание ! При удалении слота или изменении его размера адрес станет недействительным!

Пример:
!!SN:M1/?y1/2; y1 содержит адрес 2-го элемента слота 1

<< Создание нового слота >>
!!SN:M[номер слота]/[количество элементов]/[тип элементов]/[запоминать ли значения в сохранёнках];

Старое содержимое слота, если оно было, уничтожается.
*номер слота* - "-1" для автовыделения свободного номера и помещения его в v1.
*тип элементов*:
- 0 (число)
- 1 (строка)
*запоминать ли значения в сохранёнках*
- 0 (нет, при загрузке игры содержимое элементов будет представлять собой случайный мусор)
- 1 (да, содержимое нужно сохранять как есть)
Примечание: при 0 экономится место в файле и возрастает скорость сохранения.

Пример:
!!SN:M0/4/1/1; выделить массив из 4-х строк в слоте 0. Сохранять их содержимое при загрузке
!!SN:M0/2/^привет^; установить значение 2-й строки слота 0
!!SN:M0/3/^мир^; установить значение 3-й строки слота 0
!!SN:M0/2/?z1 M0/3/?z2; получить значения 2-й и третьей строк в z1, z2
!!IF:M^%Z1 %Z2^; выведет "привет мир"
!!SN:M0; удалить слот 0
!!SN:M-1/0/0/0; выделить пустой слот под временный массив чисел
!!VRy1:S1 R6; сгенерировали случайное число 1..6
!!SN:Mv1/y1; установили размер нового слота в это число
!!VRy1:-1; y1 - индекс последнего элемента в слоте
!!SN:Mv1/y1/777; значение последнего элемента слота - 777
MasterOfPuppets
Угу, читал. Но я всё ещё терзаюсь сомнениями, стоит ли на неё переходить.

Доводы за:
1. Перезагрузка erm-скриптов и DL-шаблонов. Полезная вещь.
2. Многокомандность эровских SN.

Доводы против:
1. Писать скрипты в длл-ках, использовать те же вог-функции… Не люблю половинчатых решений и всегда стремлюсь к прямой переделке скриптов в быстрый асм-код. В общем, не приму такого подхода.
2. До, после… У меня фактически нет этих Before/After – все хуки ВОГа встроены, все копирования деактивированы. Что мешало – вырезал. Есть только копирование 5-ти структур, да и то лишь потому, что в VirtualData. Со структурами этими ничего делать не собираюсь.
3. No-CD уже есть.
4. Дополнительная память... ну, есть.
5. Высокое разрешение – фиолетово.
6. Обработка ini-файлов – фиолетово. Почти все данные храню в собственных конфигах. Да и написать свои функции ничего не стоит.
7. Цветной текст – фиолетово. Так и не пользуюсь. А в DL свои средства есть.
8. Сейвы – фиолетово. Всё равно мои ни к какому другому моду не подойдут.
9. w-переменные у меня имеют другой адрес, так как их кол-во возросло до 1000. Адреса придётся менять.
10. Ну и правка хуков…

Когда-то мне придётся разойтись с тобой. Когда-то - со всеми. Это неминуемо.
Berserker
Цитата
1. Перезагрузка erm-скриптов и DL-шаблонов. Полезная вещь.

Это можно вычленить отдельной dll. Да и кодам там как раз немного.

Код
PROCEDURE ReloadErm;
CONST
  SUCCESS_MES:  STRING  = '{~white}ERM is updated{~}';

VAR
  LocalizationPath: STRING;
  i:                INTEGER;

BEGIN
  IF ErmTriggerDepth = 0 THEN BEGIN
    ZvsClearErtStrings;
    ZvsClearErmScripts;
    IF Tweaks.ReadStrFromIni
    (
      'Alternate_Script_Location',
      'WoGification',
      'wog.ini',
      LocalizationPath
    )
    THEN BEGIN
      Utils.CopyMem(LENGTH(LocalizationPath) + 1, POINTER(LocalizationPath), AltScriptsPath);
    END; // .IF
    FOR i:=0 TO MAX_ERM_SCRIPTS_NUM - 1 DO BEGIN
      ZvsLoadErmScript(i);
    END; // .FOR
    ZvsIsGameLoading^ :=  TRUE;
    ZvsFindErm;
    Utils.CopyMem(LENGTH(SUCCESS_MES) + 1, POINTER(SUCCESS_MES), @z[1]);
    ExecErmCmd('IF:Lz1;');
  END; // .IF
END; // .PROCEDURE ReloadErm

PROCEDURE ExtractErm;
VAR
  Res:        BOOLEAN;
  Mes:        STRING;
  ScriptPath: STRING;
  i:          INTEGER;
  
BEGIN
  Files.DeleteDir(EXTRACTED_SCRIPTS_PATH);
  Res :=  SysUtils.CreateDir(EXTRACTED_SCRIPTS_PATH);
  IF NOT Res THEN BEGIN
    Mes :=  '{~red}Cannot recreate directory "' + EXTRACTED_SCRIPTS_PATH + '"{~}';
  END // .IF
  ELSE BEGIN
    i :=  0;
    WHILE Res AND (i < MAX_ERM_SCRIPTS_NUM) DO BEGIN
      IF ErmScripts[i] <> NIL THEN BEGIN
        ScriptPath  :=  EXTRACTED_SCRIPTS_PATH_PREFIX + SysUtils.Format('%.2d.erm', [i]);
        Res         :=  Files.WriteFileContents(ErmScripts[i] + #13#10, ScriptPath);
        IF NOT Res THEN BEGIN
          Mes :=  '{~red}Error writing to file "' + ScriptPath + '"{~}';
        END; // .IF
      END; // .IF
      INC(i);
    END; // .WHILE
  END; // .ELSE
  IF Res THEN BEGIN
    Mes :=  '{~white}Scripts were successfully extracted{~}';
  END; // .IF
  Utils.CopyMem(LENGTH(Mes) + 1, POINTER(Mes), @z[1]);
  ExecErmCmd('IF:Lz1;');
END; // .PROCEDURE ExtractErm

PROCEDURE ForceTxtUnload (Name: PCHAR);
VAR
{U} Txt:  PTxtFile;
  
BEGIN
  Txt :=  LoadTxt(Name);
  // * * * * * //
  IF Txt <> NIL THEN BEGIN
    Txt.RefCount  :=  1;
    ASM
      MOV ECX, Txt
      MOV EAX, UNLOAD_TXT_FUNC
      CALL EAX
    END; // .ASM
  END; // .IF
END; // .PROCEDURE ForceTxtUnload
Aleee
Цитата(MasterOfPuppets @ 06 Aug 2011, 14:36) *
Самовольно и не спросив человека, добавил Aleee в члены нашей команды.

Ну, спрашивать было и правда не обязательно: я с детства мечтал попасть в команду (любого рода), где мои обязанности стремятся к нулю, а имя в списках все-таки числится. А если без шуток, то, раз уж так вышло, не стесняйся меня как-нибудь использовать, а то у меня такое ощущение, будто мне выдали уже третий аванс, а работы я никакой не выполняю.
MasterOfPuppets
Берс, спасибо.
Aleee - пока уместно делать то же, что ты и делаешь - переводишь некоторую часть моих постов, выкладываешь скрины. Есть только одно неудобство - я не всегда подробно объясняю, что значит та или иная картинка. Потом тебе задают вопросы - а ты тоже не знаешь, что сказать. Вот в таких случаях можно обратиться ко мне в личку, я дам пояснение.
Касательно последнего скрина: апгрейд всегда идёт на максимальное кол-во ресурсов и в порядке старшинства монстров в слотах, а ПКМ-информация на кнопке "апгрейд всех" указывает, сколько ресурсов останется и кто будет апгрейжен. Кто уже апгрейд или не может быть улучшен (не хватает ресурсов, нет здания, город не того типа) не отмечается большой галкой.
Фиолетовые молнии на иконках городов - знак соц. напряжения. Фиолетовый - bad, жёлтый - neutral, зелёный - good.

Надо в следующий раз отредактировать "Кратко о проекте". Часть информации уже не соответствует действительности.
Berserker
Цитата
6. Обработка ini-файлов – фиолетово. Почти все данные храню в собственных конфигах. Да и написать свои функции ничего не стоит.

Сомневаюсь, что лексический разбор ничего не стоит, или что реализация ассоциативного массива с функциями балансировки тривиальна.

Цитата
Bes (02:02:37 8/08/2011)
[Berserker 01:55:56]
>> что за баг?
Делаю запись в ини. Для каждого героя завожу раздел в котором записываю 27 значений опций. На эра 1.8 процесс занимает 8 минут.. А на эра1.9 гдето 10 секунд! УЖАС

Bes (02:03:24 8/08/2011)
Это антибаг


Я попробую включить эти твики в версию для мора.
MasterOfPuppets
Мне ini мало нужны. Всего один файл - MoP.ini, и используется он исключительно для опций игры. А опций ведь в моде с гулькин нос.
В коде юзал это (отрывок MoP.dll):
CODE
VAR
Section: STRING = 'RMG Template';
Key: STRING = 'TxtFileName';
FileINI: STRING = '.\Data\MoP\MoP.ini';

PROCEDURE change_RMG; ASSEMBLER; {$FRAME-}
ASM
PUSHAD
PUSH FileINI
PUSH 100 //размер буфера
PUSH $697428 //буфер для возврата
PUSH 0
PUSH Key
PUSH Section
CALL Win.GetPrivateProfileString
POPAD
MOV ECX, $697428
PUSH $538505
END;

PROCEDURE Init;
BEGIN
Constants.HookCode(Ptr($538500), @change_RMG, C_HOOKTYPE_JUMP, 5);
END; // .procedure Init
END.


"Баг аллоков" был поставлен к стенке и расстрелян.
Причина: сразу две ошибки в коде генерации случайных дней завала Подземных Ходов.
1) Неверный номер строки для SN:G, оставшийся после переписывания его части.
2) Неверные промежутки генерации, в результате чего запись происходила за пределы массива.
Кстати, выяснилось это именно благодаря VirtualAlloc. LocalAlloc создаёт буфер посреди чего угодно, заполненный мусором, и перед/за ним - чаще всего годный для записи массив. Ошибка запросто скрывается. А VirtualAlloc... да, выделяет постранично. Может, и медленее, но на глаз не видно, да и часто мне не нужно. Да, за нужным массивом может лежать остаток страницы, годный к записи. Но страница всё же почти всегда лежит в океане неразмеченного адресного пространства, в результате чего шанс получить AccessViolation в случае ошибки гораздо выше. И память не нужно зеромеморить, и после освобождения массив пропадает бесследно.
А вообще, надо как-нибудь потом побаловаться со всеми (Local, Virtual, Heap, Global, что там ещё). А то с API только знакомлюсь.
В общем, переписал генерацию завалов на асме, тем самым ещё больше ускорив старт карты. Просмотрел дамп - ОК. Генерировал одну за другой огромные 2-уровневые рандомки, пока рука не устала. 0 крашей.
Есть ещё один крашевый баг - по 0x4FD314. Происходит после просмотра Replay Turn, нерегулярно, адрес структуры героя почему-то равен нулю. Навскидку, дело в посещении ИИ какого-то объекта. Впрочем, надо проверить - вдруг он был связан с "багом аллоков" и исчез вместе с ним.
Остальное - не баги, а просто недоработки, с которыми я хорошо знаком.
FallenAngel
MoP вышел
Официальный сайт
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Форум IP.Board © 2001-2025 IPS, Inc.