Формат файлов скриптов.
При сравнительном анализе множества файлов удалось установить следующее.
1. Все они имеют одинаковую структуру.
Номер |
Наименование |
Границы |
Описание |
1 |
Заголовок |
Смещение от 0h до 2Dh |
Кусочек кода, основная задача которого – передача управления на главную точку входа. Двойное слово по смещению 2Ah означает общее количество функций в таблице. |
2 |
Таблица функций |
(24 байта * кол-во функций) от окончания заголовка |
Список функций скрипта. Каждая запись включает смещение от окончания таблицы функций на имя функции и адрес точки входа. |
3 |
Интерфейс |
От окончания таблицы функций до главной точки входа, адрес которой определен двойным словом по смещению Ch |
Делится на два подраздела, каждый из которых завершается двойным словом FFFFFFFF и состоит из длинных строк в дельфийском понимании. Первый раздел – имена локальных переменных, функций, определенных в таблице и, возможно, что-то еще, второй – хранит строки-названия параметров реакции криттера на игрока, предположительно, для целей отладки. |
4 |
Код |
От главной точки входа до конца файла |
Содержательная часть скрипта. |
2. Код скрипта состоит из элементов двух типов.
Имя |
Вид |
Примечание |
Стековая команда |
C001 XXXX XXXX или
9001 XXXX XXXX |
ХХХХ – аргумент |
Команда обработки |
8XXX |
ХХХ – код команды |
Команда С001 служит, по всей видимости, для помещения аргумента на вершину стека (как мне представляется, язык скриптов основан на бесскобочной логике с реализацией механизма стека).
Команда 9001 помещает на вершину стека не число, а строку, адрес которой определяется аргументом – это смещение во втором подразделе раздела 3 (Интерфейс).
Команд обработки теоретически может быть 512, но на практике используется куда меньше (например, в прилагаемой расшифровке скрипта Клинта – 60).
В процессе экспериментов удалось выяснить назначение некоторых из них.
А) Команды передачи управления.
8004 – команда безусловного перехода. Адрес перехода всегда помещается в стек непосредственно перед самой командой.
802F – команда условного перехода. Перед ней в стек помещается адрес перехода, а затем вычисляется условие перехода – переход по адресу произойдет, если результат этого вычисления – ноль.
Для дальнейшего изложения удобно будет определить следующий способ записи кода:
CV, 9V, CA – стековые команды, причем CA – помещение в стек аргумента, могущего быть адресом.
004, 02F и т.д. – трехзначные (без восьмерки) коды команд обработки.
Все команды отделяются друг от друга точкой с запятой.
Итак, команда 8005. В скрипте Клинта встречается два раза в форме:
CA;00D;CV
;CV;005 – предположить, что командой ветвления является 00D, сложно, хотя такая возможность и не исключается.
Команда 00D входит также в последовательность, завершающую любую функцию:
CV;00D;019;02A;029;00C;01C;02A;029;01C, для краткости предлагаю записывать ее как RETF(X), где Х – значение аргумента, помещенное в стек первой командой.
Кроме того, в некоторых функция посреди кода встечается последовательность
CV;00D;019;02A;029;00C;01C, которую по аналогии предлагаю называть RETN(X).
Б) Команды вывода:
Вывод сообщений в левое нижнее окно организуется следующим образом:
0В9;<формирование строки вывода>;0B8
Есть четыре основных способа получения строки:
- Из файла сообщений
CV;CV;105, где первый аргумент – номер имени этого файла в scripts.lst (у msg-файлов нет собственного списка, поэтому имя можно взять отсюда и поменять расширение), а второй – номер строки, указанный в фигурных скобках.
- Из файла скрипта
путем указания смещения на строку командой 9V.
- Из идентификатора объекта
последовательности 0BC;0A4, 0BD;0A4, 0BE;0A4, 0BF; 0A4 –выводят в стек - 1-я и 3-я – имя криттера, а 2-я и 4-я – имя игрока.
- Из числа
можно просто вычислить некоторое числовое выражение, а затем присоединить его к уже имеющейся строке.
Строки, формируемые указанными способами, объединяются командой 8039, которая в таком контексте выполняет операцию конкатенации, причем для последнего способа еще и конвертает число в строку.
Организация диалога осуществляется так:
- Открытие формы диалога
выполняется командой 80DE, у которой есть пять аргументов: номер скрипта криттера, идентификатор криттера (80BC), четверка(просто помещается в стек), номер морды говорящего (если нет морды, то –1) и номер фона (-1 в аналогичном случае).
- Сам диалог
обрамляется командами 811C и 811D.
- После чего
следует описание структуры диалога в виде фраз криттера и вариантов ответа игрока.
- Фразы криттера
бывают двух типов – подразумевающие ответ и без такового.
- Фраза с последующим ответом
выводится последовательностью <формирование строки>;11E. Далее следует описание ответов.
2-й вариант - <номер мсг-файла>;<номер строки>;11E.
Фраза без ответа выводится последовательностью <формирование строки>;32h;120.
Ответ игрока формируется последовательностью <условие включения ответа в список ответов (например –3 для дегенерата или 4 для нормального)>;<Строка ответа>;<Номер функции, выполняемой при выборе ответа (чаще всего это номер функции, описывающей следующий диалог или пустой функции), отсчитываемой от нуля по таблице функций, помещенной в начале скрипта>;32h;121.
Первое окно диалога может быть описано непосредственно между командами 811C и 811D, а может путем вызова описывающей его функции. Этот вызов организуется такой последовательностью: <адрес возврата, т.е. адрес команды, следующей непосредственно за данной последовательностью>;00D;0h;<номер функции диалога, определяемый вышеописанным способом>;005. Роль команды 801A, на которую в скрипте Клинта ссылается адрес возврата, пока не выяснена.
Закрытие окна диалога выполняется командой 80DF.
В) Команды манипуляции переменными.
При вычислении различных параметров в скрипте Клинта используются три группы переменных, каждая из которых для вызова и записи значений использует свою пару команд.
Первая группа – помещение в стек значения производится командой 8012 с единственным аргументом – номером переменной, а запись из стека последовательностью <значение>;<номер переменной>;013.
Вторая группа – помещение в стек значения производится командой 80С1 с номером переменной, а запись из стека последовательностью <номер переменной>;<значение>;0С2.
Третья группа – помещение в стек значения производится командой 80С5 с номером переменной, а запись из стека последовательностью <номер переменной>;<значение>;0С6.
Логично было бы предположить также пару 80С3 и 80С4 и, может быть, еще и другие пары, но из скрипта Клинта этого не вытянуть.
Команда 80СА позволяет получать основные характеристики персонажа. Аргументы - ИД персонажа (для игрока можно вызвать командой 80BF, для того, с кем он базарит - 80BC) и номер характеристики: 0 - Strength, 1 - Pe
rception, 2 - Endurance etc.
Команда 80F3 служит для проверки наличия у персонажа traits и perks. Последовательность 0;80BF;X;80F3 выводит перк (его наличие), а 2;80BF;X;80F3 - трэйт.
Г) Числовые и логические операции.
8046 – меняет знак числа на вершине стека на противоположный (т.е. умножает это число на –1).
8039 – сложение (для строк - конкатенация), 803A – вычитание, 803B – умножение, 803C – деление, 803D – остаток от деления.
Команда 8033 проверяет два верхних элемента стека, переход по 2F произойдет, если элементы не равны (т.е. возвращает ноль, если элементы не равны).
Команда 8034 – переход, если 1-й равен 2-му.
Команда 8035 – переход, если 1-й больше 2-го.
Команда 8036 – переход, если 1-й меньше 2-го.
Команда 8037 – переход, если 1-й больше или равен 2-му.
Команда 8038 – переход, если 1-й меньше или равен 2-му.
803E и 803F – логические операции AND и OR:
|
|
803E |
803F |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
1 |
1 |
1 |
1 |
1 |
8040 - побитовый AND, 8041 - побитовый OR, 8042 - побитовый XOR, 8043 - побитовый NOT, 8044 - операция над аргументом не производится (?) - на фига такое надо… , 8045 - логический NOT.
Д) Прочие команды:
80BC и 80BЕ – помещает на вершину стека очень большое число, а в сочетании с 80А4 возвращает строку – имя криттера.
80BD и 80BF – аналогично для имени игрока.
8105 – возвращает строку по двум аргументам – номеру msg-файла и номеру строки в нем.
Кое-что новенькое по поводу команд типа 9V: эти команды напрямую связаны с третьим (интерфейсным) разделом файла скриптов.
Третий раздел необходимо разделить на два подраздела, границей между которыми является максимальное двойное слово FFFFFFFF.
Так вот, команды типа 9V помещают в стек смещения от последнего байта этого слова на строки (в дельфийском понимании длинной строки как комбинации паскалевской строки со строкой с нуль-терминатором) на имена параметров, относящихся к реакции криттера на нашего подопечного Избранного.
Потом в коде к ним линкуются вычисляемые значения и выполняется команда 8154, которая. думается, и присваивает соответствующим параметрам сии вычисляемые значения.
Нет, эта команда, похоже, задумана для отладки и выводит промежуточные результаты расчетов куда-то, правда, у меня ни хрена не выводит.
Итак, по поводу переменных.
Пара 80С5, 80С6 работает с глобальными переменными, которые в текстовиках называются GVAR_*. Часть массива пуста и используется в скриптах для хранения промежуточных результатов, а некоторые переменные хранят постоянные результаты.
Например, 0 – общая репутация, 9 – стадия выполнения квеста на прополку огорода, 11(Bh) – работорговец ли и т.д.
Пара 8012, 8013 работает с локальными переменными, имена которых приведены в первом подразделе интерфейсного раздела. Начало массива предположительно на Scenery_Creation (с нулевым индексом). Пример: вычисление в 6-й функции (считая от единицы) отношения Клинта к игроку. Основная переменная, к которой прибавляются результаты всех проверок, имеет индекс 13h, что соответствует Static_Reaction. Прослеживаются также соответствия для Evil_Critter, Slavery_Tolerant,
reaction_bonus_town_rep и reaction_bonus_karma.
Обработчики событий в скриптах (красным выделены те, с которыми все ясно, для остальных комментарии гипотетичны):
no_p_proc |
start |
spatial_p_proc |
description_p_proc - при выборе подробного описания (бинокль ) |
pickup_p_proc - при поднимании с земли |
drop_p_proc - при бросании |
use_p_proc - при использовании |
use_obj_on_p_proc - при использовании другого объекта на этом |
use_skill_on_p_proc - при использовании скилла (лечения, воровства, ремонта и т.д.) на этом объекте |
none_x_bad |
talk_p_proc - организация разговора |
critter_p_proc - постоянно выполняется, отвечает за поведение в целом. |
combat_p_proc - выполняется ПЕРЕД каждым ходом криттера в бою |
damage_p_proc - при нанесении криттеру повреждений |
map_enter _p_proc - при входе чузена в локацию |
map_exit_p_proc - при покидании чузеном локации |
create_p_proc |
destroy_p_proc - при убийстве |
look_at_p_proc - при наведении курсора на объект (краткое описание) |
timed_event_p_proc - с каким-то (?) периодом |
map_update_p_proc |
push_p_proc - при попытке сдвинуть криттера с дороги |
is_dropping_p_proc |
combat_is_starting_p_proc - в начале боя |
combat_is_over_p_proc - после боя |
Открытия 26 Февраля 2000 г.
- 80A1 - получение экспы. В стеке один аргумент - добавляемое кол-во. Если достигается новый уровень, то автоматом возникает соотв. надпись.
- Пара 80С3, 80С4 используется для манипуляций с переменными MAP_GLOBAL_VAR текущей карты.
Например, после ремонта колодца :
C001->00000004; C001->00000001; 80C4, а 4-я переменная в ARVILLAG.GAM - MVAR_Fixed_Well. Кроме того, единица заносится в 0-ю переменную для пары С1,С2, думается, что это локальный массив для скрипта или для карты.
- 80FA - в контексте use_skill_on_p_proc возвращает номер использованного скилла. Номер можно определить по номеру в skill.msg за вычетом сотни. Например: 6 для первой помощи, 7 для доктора, 10 для воровства, 13 для ремонта и т.д.
- 810A - вывод фразы рядом с фигуркой криттера. 3 аргумента: CritterID (80BC), выводимая строка, цвет надписи.
- Побитовые операции 8040, 8041, 8042 - AND, OR и XOR соответственно.
- Денежками любого персонажа (игрока или криттера) можно манипулировать следующим образом: <Ид персонажа>;8138 - в стек помещается количество денег. <Ид персонажа>;<Деньги>;8139;801A - "Деньги" (м.б. отрицательное число) добавляются к уже имеющимся. Вообще, довольно интересная команда 801А - ее отсутствие в некоторых случаях (в этом, например) наглухо вешает тачку. Она используется и в паре с 800D при организации вызова подпрограмм через 8005.
- Количество предметов какого-либо вида у персонажа можно узнать так: <Ид персонажа>;<Ид предмета>;80BA. Ид предмета можно получить, например, из pro_item.msg, отбросив последние две цифры.
- Величину какого-либо скилла у персонажа можно узнать так: <Ид персонажа>;<Номер скилла>;80AA.
НЕМНОГО О ВНУТРЕННОСТЯХ |
Установлены положение и структура стека: |
Стек Клинта начинается по адресу 340BF58. Т.к. сам скрипт хранится начиная с 3409688, то можно сказать, что стек находится "почти сразу за скриптом" (возможно, что между ними - локальные переменные скрипта). Нужно отметить, что адрес таких областей данных - вещь непостоянная |
Если прервать фолаут в точке между, скажем, 46CD99 и 46CDAA, то в EBX будет находиться адрес блока переменных скрипта, выполняемого в любой текущий момент. Указатель вершины стека лежит по смещению 24h от этого адреса. Текущая точка выполнения скрипта (смещение внутри скрипта) по смещению 10h, а адрес самого скрипта по смещению 4. |
Адрес процедуры выборки очередной команды - 46CD27. |
Открытия 4 Марта 2000 г.
- Открыт один из способов передачи предметов (изучен не до конца) от одного персонажа другому.
Для передачи используется адрес записи в памяти, описывающей данный предмет (или предметы одного типа). Этот адрес получается командой 810D: <CritterID(типа 80BC или 80BF)>;<номер предмета (можно определить, например, из pro_item.msg)>; 810D.
Обозначим полученный адрес как ItemAddr, тогда предмет(ы) можно забрать у персонажа последовательностью <CritterId>;<ItemAddr>;80D9, и передать другому последовательностью <CritterID>;<ItemAddr>;80D8.
Кстати, для сохранения и восстановления значения ItemAddr в изученных скриптах используется пара 8031, 8032 с различными индексами, чаще нулевым. О подробностях работы этой пары см. п. 3 настоящих Открытий
J .
Изучен способ передвижения предметов в локации.
Предметы адресуются посредством SceneryID, т.е. ида, у которого старший байт равен двум. Вероятно можно использовать и другие иды, скажем CritterID или ItemID, но это предположение пока не проверено.
Проверить, находится ли объект данного типа в данных координатах можно следующей последовательностью: <координаты(принцип вычисления лучше объяснят те, кто занимается картами, но, скажем палка с черепами в начале игры стоит в координатах 0х5337, а после прохождения испытания
- в 0х4CF2)>;0;<SceneryID>;80BB.
Получить вышеупомянутый адрес данного объекта (назовем его SceneryAddr), можно так: <координаты>;0;<SceneryID>;80A7.
Изменение координат объекта выполняется таким образом: <SceneryAddr>;<новые координаты>;0;80B6;801A.
Еще одно непроверенное предположение - 801А каким-то образом корректирует стек после некоторых операций, возможно очищает его.
Подробности работы команд 8031 и 8032. Команда 8031 берет два числа с вершины стека и помещает нижнее в стек по смещению на верхнее от начала стека:

Команда 8032 извлекает число из корня стека по заданному смещению:

Нужно отметить, что для таких манипуляций сначала выделяется место в стеке путем помещения туда необходимого количества нулей (С001->00000000). Насколько я понял, стек после заверешения манипуляций не очищается, видимо это выполняется одной из команд, завершающих процедуру.
Передача некоторого количества (не всех) объектов от одного персонажа к другому.
Объекты забираются командой 8117: <CritterID>;<ItemAddr>;<кол-во забираемых объектов>;8117, причем команда возвращает какое-то значение, пока невыясненное.
Объекты добавляются другому персонажу командой 8116: <CritterID>;<ItemAddr - тот же, что и для предыдущей команды>;<кол-во добавляемых объектов>;8116.
Создание нового предмета.
Предмет создается последовательностью <ItemID>;0;0;-1;80B7, которая возвращает ItemAddr вновь созданного объекта. ItemAddr можно сохранить и использовать в команде 8116 для добавления произвольного количества созданных предметов какому-либо персонажу. При создании предметов не определяется количество таковых - оно определяется добавлением. Что означают 2-й, 3-й и 4-й аргументы команды 80В7 - пока не известно.
Добавление скиллпойнтов выполняется командой 813C: <CritterID>;<Index of skill>;<number of skillpoints to add>;813C.
Интерфейсные прикольчики: <произвольный аргумент>;8136 - гашение экрана, <произвольный аргумент>;8137 - восстановление экрана после гашения, <время в десятых долях секунды>;80FC - столько игрового времени пройдет при выполнении этой команды, обычно используется между командами гашения-восстановления экрана.
Открытия 11 Марта 2000 г.
- Странная группа команд - 8014, 8015, 8016. Используется в сочетании со смещением на строку (имя объекта) от начала 3-го (интерфейсного) раздела, передаваемого командой 9001. Следует отметить, что смещения на строки, передаваемые этой командой и используемые для отладки командой 8154, вычисляются от начала 2-го подраздела интерфейсного раздела. Таким образом, с помещением командой 9001 строки на вершину стека я поторопился - помещает она скорее указатель, причем база для него зависит от команды, его использующей.
8014 - используется в последовательности 9001;<адрес строки - имени объекта>;8014, возвращает адрес (типа описанного выше ItemAddr
) объекта.
8015 - используется в последовательности <ItemID (в скрипте самого объекта получается командой 80BC)>;9001;<адрес строки - имени объекта>;8015, видимо, связывает имя объекта с его идом.
8016 - используется в последовательности 9001;<адрес строки - имени объекта>;8014, похоже, что это просто инициализация объекта.
Анимация осуществляется двумя командами - 810Е и 8126, у каждой по два параметра, первый параметр команды 8126 - криттер-ид, смысл остальных невыяснен.
Команда 8128 возвращает 0 в обычном режиме и 1 - в бою.
Команда 8030 идентична 802F - тот же условный переход, если аргумент равен нулю. Отличий найти не удалось.
Команда 80D4 использует один операнд типа CritterID и возвращает координаты данного криттера.
Команда 814С использует два операнда - координаты точек в локации и возвращает направление от 1-й точки ко 2-й по системе: 0 - северо-восток, 1 - восток, 2 - юго-восток, 3 - юго-запад, 4 - запад, 5 - северо-запад.
Команда 80DC: аргументы - два CritterID'а, результат - 0, если первый персонаж стоит спиной ко второму; 1, если лицом.
Последовательность <CritterID>;<SceneryAddr>;8147 - переносит все вещи персонажа в контейнер по адресу.
Команда 8153 прекращает бой.
Последовательность <CritterID>;<кол-во снимаемых хитпойнтов>;<маска эффектов при ударе>;80EF - наносит персонажу удар на определенное количество хитпойнтов с различными эффектами (слегка покачнулся, упал и т.д.)
Последовательность <x>;<y>;80B4 генерирует случайное число в диапазоне [x,y].
Код 80BD возвращает не обязательно ид чузена, а ид того персонажа, который вызвал это действие (например, применил скилл).
Последовательность <ItemAddr>;<CritterID>;8145 вызывает использование предмета на персонаже.
Команда 80CE перемещает персонажа в другие координаты. Формат: <CritterID>;<новые координаты>;<способ передвижения: 0 - шагом, другие значения - бегом>;80CE.
Команда 80D2 возвращает расстояние между точками. Формат: <координаты 1-й точки>;<координаты 2-й точки>;80D2.
Команда 80D3 возвращает расстояние между персонажами. Формат: <CritterID>;<CritterID>;80D3.
Команда 80F9 (без параметров) инициирует разговор с персонажем, в чьем скрипте она выполняется - фактически она вызывает talk_p_proc.
Команда 80E3 используется как методы Show/Hide для персонажа, причем при исчезновении он на самом деле остается в этой локации. Формат: <CritterID>;<0 - Show, 1 - Hide>;80E3.
Функция critter_p_proc выполняется для каждого объекта постоянно - в некотором роде аналог OnTimerEvent, хотя период выполнения неизвестен, он крайне мал.
Команды 8131 и 8132 открывают и закрывают двери. Аргумент - SceneryAddr двери.
Команды 812E и 812F блокируют и разблокируют двери. Аргумент - SceneryAddr двери.
Странная команда 80F2 - возвращает аргумент (единственный), умноженный на 10.
80BD - вообще аргумент, передаваемый функции!!! Что-то типа TObject. Например, в функции is_dropping_p_proc скрипта obj_dude он возвращает ItemAddr выброшенной вещи.
8100 - преобразует ItemAddr в ObjectID без типа (т.е. возвращает младшие два байта).
80EA - время в десятых долях секунды с начала игры. Правда, к моменту первого появления перед местным Шаолинь-сы уже натикивает порядка 300000 (где-то 8,5 часов), видимо потому, что игра начинается с утра, в девятом часу, а время отсчитывается с полуночи.
8101 - возвращает номер текущей карты, описание карты по этому номеру можно найти в maps.txt.
Последовательность <номер Area из city.txt>;1;80B2 заносит информацию об этом городе в пипбой. Что означает единица - не выяснено.
8115 с аргументом - номером mve - проигрывает соответствующий клип.
814B с аргументом типа CritterID (значения можно найти в party.txt, а команды 80BC - 80BF возвращают не CritterID, а CritterAddr) возвращает ноль, если криттер не в команде?
Примечание для Dims: по смещению 1A в протофайле (верно, по крайней мере, для итемов) хранится маска действий с итемами. Известно назначение 1-го бита: 0000Х000, где Х отвечает за возможность использования этого предмета (если по смещению 1C стоит 3, то далее номер строки в scripts.lst.)
Результаты от 23 Марта 2000 г.
80D5 - вычисляет новые координаты по аргументам <старые коодинаты>;<направление>;<расстояние>. Можно использовать, чтобы передвинуть что-либо или кого-либо в новые координаты относительно старых.
80F7 - без аргументов, используется в critter_p_proc и в timed_event_p_proc. В первой возвращает величину повреждений, нанесенных криттеру в последний ход.
80F6 - возвращает время дня в виде десятичного числа ЧЧММ.
80E9 - задает уровень освещения в локации.
80AB - два аргумента - CritterID (на самом деле Addr) и 8 (при всех других значениях возвращает Addr, отличный от передаваемого), возвращает 1, если включен Sneak, в противном случае - 0.
80AE - генератор случайных чисел. Формат <CritterAddr>;<Stat - номер одной из основных характеристик>;0;80AE. Возвращает 1 или 2. Мат. ожидание равно 1+(Stat/10).
80AC - аналогично 80AE, только для skills, а не stats.
80AF (1 аргумент) - преобразует результаты вышеописанных генераторов в булевские значения (для условного перехода) по следующему правилу: 0 и 1 в 0, 2 и 3 в 1, остальные значения в -1.
80B0 - аналогично предыдущей команде, только правило другое: 0 и 3 в 1, 1 и 2 в 0, остальные значения в -1.
80С0 - ItemID использованного предмета в контексте use_obj_on_p_proc. По материалам исследованным скриптов (было произвольно выбрано 7) можно сказать, что команда используется только в этом контексте. Аргументов нет.
80С7 (без аргументов) - этот код не употребляется в скриптах, перечисленных в scripts.lst, всегда возвращает 21(15Н).
8106, формат: <CritterAddr (ранее ошибочно назывался ID)>;<-2|0|1|2>;8106. При 0 возвращает ItemAddr надетого на человека броника, при 1 - ItemAddr предмета в слоте Item2 (если он активен), при 2 ItemAddr предмета в слоте Item1 (если он активен), во всех остальных случаях возвращает -1. При -2 возвращает количество типов предметов у криттера.
80CB - изменение основных характеристик (stats) персонажа. Формат: <CritterAddr>;<номер характеристики>;<новое значение>;80CB.
80D7 с аргументом типа ItemAddr вызывает сбрасывание предметов персонажем на землю.
80A3 - воспроизведение файла acm. Аргумент один - строка-имя файла. В стек помещается через 9001 смещение во второй части интерфейса.
80A6 с одним аргументом, легальные значения - 0, 1 и 2. 0 - возвращает количество нераспределенных скиллпойнтов. 1 - текущий уровень игрока. 2 - количество накопленной экспы.
80A9 - мгновенное перемещение чузена в произвольную точку локации. Формат: <x - координата>;<y - координата >;<z - вероятно, но не точно, этаж>;<ориентация>;80A9. Команда используется исключительно в функциях map_enter_p_proc скриптов локаций, хотя может использоваться и в любых других.
80A8 - работает как 80A9, только не чузена перемещает, а устанавливает экран так, чтобы точка с упомянутыми координатами была в центре.
Эксперимент по командам 80C8 и 80C9 не ставился, но на основе анализа скриптов можно предположить, что они отвечают за определение таких параметров объекта, как ObjectType и ObjectSubtype (см. документацию по pro), соответственно. Один аргумент - ObjectAddr.
810C - анимация. Формат: <CritterAddr>;<Param1>;<Param2>;810C. Проверялось на Клинте (как всегда J ). Для Клинта имеют смысл два значения Param1 - Ah и Bh. В первом случае он нагибается к земле (типа что-то поднимает) и встает. Во втором шевелит ручками (типа ворует). Если Param2 отличен от нуля, то не возвращается в исходную позицию, а застывает как дурак и более шевелиться не хочет (причем работа скрипта не прерывается, все в норме).
80СС - команда заражения криттера педикулезом J . По крайней мере он по этой команде чешется. Один аргумент - CritterAddr. Результат не возвращает.
80CF - определяет принадлежность точки прямоугольнику. Формат: <Point1>;<Point2>;<Point3>;<Point4>;<PointX>;80CF. Точки 1..4 определяют прямоугольник. Х - произвольная точка. Результат: 1 - входит, 0 - не входит.
80FF - помещает криттера (возможно, что не только криттера) в определенную точку локации. Формат: <CritterAddr>;<координаты>;<этаж(предположительно)>;80FF. Возвращает какой-то результат, который обычно удаляется из стека посредством 801A.
Уточнение к формату 80B7: четвертый аргумент - номер скрипта создаваемого объекта. Создавать можно не только вещи, но и криттеров, которые после этого помещаются в локацию вышеописанной командой. Таким образом 1-й аргумент это "тело" криттера, а 4-й - его "душа". 2-й аргумент - если не 0, то координаты в локации.
80DA - вооружает криттера тем, что у него есть, в том смысле, что он берет оружие из инвентаря в руки. Формат: <CritterAddr>;<ItemAddr>;80DA.
80DB - команда криттеру открыть/закрыть дверь с данным адресом. Передается один параметр - ObjectAddr двери. Криттер подбегает к ней и закрывает (если открыта) или открывает (в противном случае). Подразумевается тот криттер, в чьем скрипте выполняется команда. НЕ ЗАБУДЬ ПРОВЕСТИ ЭКСПЕРИМЕНТ С ИТЕМАМИ.
80E4 - переносит игрока в другую локацию. Формат: <Номер локации - см. maps.txt>;<Значение для GVAR_LOAD_MAP_INDEX>;80E4. При переносе в глобальную переменную GVAR_LOAD_MAP_INDEX (индекс 1B) заносится второй аргумент, что затем используется в скрипте локации, чтобы определить, как игрок туда попал. Для большинства случаев использования команды это значение равно нулю. При написании собственных скриптов его можно использовать произвольно.
80E7 - проверка на движение. Формат: <CritterAddr>;80E7. Возвращает -1, если в момент выполнения скрипта криттер движется (идет, бежит, идет со sneak - все едино), и 0, если стоит.
80E8 - лечение. Формат: <CritterAddr пациента>;<скоко хитов добавить>;80E8. Просто пролечивает криттера на указанное количество хитпойнтов. Полное лечение в скриптах (типа шамана или волтовского доктора) организуется именно через это дело, причем второй аргумент вычисляется как 80CA(7)-80CA(23h).
80EC - этаж. Формат: <ObjectAddr>;80EC. Возвращает этаж в локации (считая от нуля), на котором располагается данный объект/криттер.
80ED - убивает криттера и определяет вид останков. Формат: <CritterAddr>;<вид трупа>;80ED. После выполнения команды криттер падает замертво в позе, определяемой 2-м аргументом. Более сказать невозможно, пока не будет результатов по системе frm.
80EE - убивает всех криттеров в локации с данным идом и определяет вид останков. Формат: <CritterID>;<вид трупа>;80EE. После выполнения команды все криттеры с данным идом умирают в одинаковых позах.
80F4 - нечто аналогичное оператору delete или, скорее, деструктору объекта. Формат: <ObjectAddr>;80F4. Уничтожает объект и, вероятно, освобождает память.
814B - у команды один параметр - CritterID криттера, который может быть в команде. Если он в команде, то возвращает его CritterAddr для использования другими командами, иначе 0.
По контексту команды 80FE (формат: <CritterID>;<radiation level to discount>;80FE) очевидно, что она применяется для снижения уровня облученности перса на заданную величину (в скриптах применяется докторами и говорящей башкой).
Про команду 8102 можно написать поэму посильнее "Мертвых душ". Видимо, в дальнейшем описании я еще неоднократно вернусь к ней. Итак, команда имеет четыре параметра, да еще и какой-то результат возвращает. Первый параметр - CritterID. Эксперименты ставились как на чузене, так и на кроликах. Для чузена получается установить второй параметр в 0, тогда 3-й параметр - номер перка (считая от нуля, нужно делать поправку относительно номеров в perks.msg), а 4-й - сколько перков чузену добавить. Добавлять удавалось не только реальные перки, но и типа "какой-то навороченный армор", но в этом случае характеристики чузена не менялись. Именно этот вариант команды используется, чтобы загнать ему железяки под кожу. Что касается кроликов, то в изучаемых скриптах 2-й параметр всегда равен единице, 3-й - 6 или 5, а 4-й принимает самые разные значения. Если 3-й параметр равен 6, а 4-й нулю, то в бою криттер отображается с зеленым контуром (вроде свой), при прочих значениях 4-го параметра - красный контур, причем для Клинта 4-й параметр выставляется в единицу, а для патрулей Анклава больше сотни. Думаю, что это "злобность". Когда 3-й параметр равен 5, 4-й параметр определяет каким-то образом боевые качества криттера. Когда ставил Клинту 0, он постоянно мазал, а если и вышибал, то в пределах 6 хитов. А вот если ему же установить порядка 200, то почти всегда попадает и сносит каждый раз по 11 хитов. Пока все.
Команда 8104 с двумя параметрами - ObjectID и индекс. Возвращает различные характеристики из протофайла. Причем для минимальной силы индекс равен 0CH, для веса - 0DH, для базовой цены - 0EH и т.д., и совершенно непонятно откуда считать этот индекс, если он - смещение от какой-то точки.
8107 - установка источника света. Формат: <ObjectAddr>;<интенсивность>;<радиус>;8107. Объект может быть любой - предмет, существо. Если выставить радиус 1, то даже при высокой интенсивности будет освещен только объект и земля под ним. Большие значения радиуса ничего не дают, максимально объект освещает круг радиусом 4-5 гексов с постепенным уменьшением освещенности к краю.
8030 - как известно, эта команда практически идентична 802F. Но у нее есть одна особенность - если переход по ней не происходит, то адрес перехода остается в стеке. Используется это для организации циклов в таком виде:
00000000: C001 00000006
00000001: <вычисление условия>
00000002: 8030
00000003: <тело цикла>
00000004: C001 00000001 - переход сразу на вычисление условия, т.к. адрес сохранился в стеке.
00000005: 8004
0000000
6: <выход из цикла>
8109 - получение ItemAddr вещи из инвентаря по номеру. Формат: <CritterAddr>;<какой-то параметр всегда равный D>;<номер вещи>;8109. Номера вещей считаются от нуля, причем нулевая лежит в рюкзаке ниже всех. Почему второй параметр всегда равен Dh - ахез, но при других его значения команда всегда возвращает минус единицу. Для анализа слишком мало материала, т.к. команда используется только воришками из Дена и Мерком в НКР.
Сложнейшая команда 810B. У нее два аргумента, причем первый может принимать лишь строго определенные значения (SoftICE так говорит J ). Варианты первого аргумента - от Dh до 13h, 16h, 1Eh, 1Fh, 20h, 28h и от 2Ah до 35h. На каждое из перечисленных значений есть свой кусочек кода, причем второй аргумент (предположительно) используется не во всех вариантах. Если первый аргумент не равен ни одному из приведенных значений, то команда не помещает в стек никакого результата. Далее будем раскапывать ее действия по отдельным значениям первого аргумента. Итак, для 28h второй аргумент - номер скилла чузена. В этом случае команда возвращает 1, если на данный скилл установлен тэг, и 0, если не установлен. Для 1E (второй аргумент не используется) возвращает содержимое ячейки по адресу 672E68, а именно - номер города (из city.txt), в котором в данный момент находится машина. Для 20: берет содержимое ячейки по адресу 672E6C и складывает со вторым аргументом. Если полученное значение меньше или равно 80000 (десятичных), то сумма помещается в указанную ячейку и возвращается 0, а если сумма больше 80000, то в ячейку заносится 80000, и возвращается сумма за вычетом 80000. Это не деньги, т.к. проверялось на чузене без копейки (вполне обычном, без раскачанных характеристик) и у него в ячейке 672E6C было 80000. Для 11 второй аргумент - номер города (area из city.txt)! Начало блоков описания городов хранится в ячейке 51DDE8. Размер блока - 360 байт (причем в начале блока - название города). Если в блоке ячейка 38h равна единице и ячейка 40h не равна нулю, то возвращается значение из ячейки 40h, иначе 0. Связано это, видимо, с наличием города в пипбое. Проверялось на Арройо, Клэмате и Наварро. Для Арройо [38h]=1, [40h]=2, для Клэмата [38h]=1, [40h]=1, для Наварро [38h]=0, [40h]=0. Кстати, в этом коде есть проверка, не превышает ли номер города 30h, т.е. предельное количество городов на карте - 49, как и описано в city.txt. По контексту скриптов локаций - возвращает 0, если город не отмечен на карте. Для 0D (второй аргумент не используется) помещает 2 в ячейку 5186BC, после чего бирюля выходит в главное меню (с Анклавной мордой). Для 0E (второй аргумент не используется) выбирается байт из ячейки 631D7C. Если он четный, то возвращается 1, иначе 0. По контексту скриптов локаций можно предположить, что единица возвращается, если чузен оказывается в локации в первый раз, и 0 - во все последующие. Для 10 (второй аргумент не используется) возвращает количество активных спутников чузена, считая его самого. Например, если чузен один (в смысле совсем один, а не Chosen One J ), то возвращается единица, если он с Суликом, Виком, Маркусом, то возвращается 4, но достаточно кого-нибудь из них попросить подождать тебя туточки, как их станет трое не отходя от кассы. Численность личного состава хранится по адресу 519D9C. Для 0F вызывает лифтовое окно. Второй параметр определяет какой это лифт. Для artemple удается получить доступ к самым разнообразным местам, в том числе к таким, где лифт не предусмотрен (пастбище Торра, предместье Волт-Сити). Значения 4 и 5 дают доступ к двум лифтам Military Base, а 8 - к лифту 13-го Волта. Определенной системы в доступе к лифтам не найдено. Предположительно, лифты описываются в map. Нужно отметить, что панель лифта вызывается не самой командой, а после завершения скрипта, в котором была вызвана эта команда. Для 12 выяснить смысл не удалось. Перебирается цепочка записей. Адрес первой записи берется из 6648C0. В записи обрабатываются двойные слова со смещениями 4, 8 и 10h. Проверяется равно ли двойное слово по смещению 8 второму аргументу и равно ли двойное слово по смещению 4 нулю. Если так, то возвращается единица, в противном случае переходят к следующей записи, адрес которой выбирается из двойного слова по смещению 10. Если же этот адрес равен нулю, то перебор заканчивается и возвращается 0. Второй аргумент, по всей видимости, должен быть CritterAddr спутников чузена, т.к. двойное слово по смещению 8 в данной цепочке равно либо нулю, либо этому самому^^^^^^^^^. Двойное слово по смещению 4 в наблюдавшихся ситуациях никогда не было равно нулю. Наиболее частые его значения - 3, 4, Ch, Dh. Для 16 (второй аргумент не используется) - просто возвращает двойное слово из 5194B4. Там всегда 0. Непосредственно за этой ячейкой следует строка ARTEMPLE.MAP, причем и тогда, когда чузен находится в любой другой локации. За сим исследования этой команды прекращаю. Задолбался. Как говорит Чиграков, "может в следущий раз".
812D - один аргумент - ObjectAddr двери. Возвращает 0, если дверь не заперта, и 2000000H, если заперта. Почему именно это значение - ахез, но в наблюдавшихся случаях именно так.
8130 - один аргумент - ObjectAddr двери. Возвращает 0, если дверь закрыта, и 1, если открыта.
Еще одно уточнение к формату команды 80B7 - 3-й аргумент (после 2-го - координат) означает этаж (он же уровень) в локации.
8133 и 8134 - блокировка и разблокировка ввода соответственно. Аргументов не имеют и ничего не возвращают. Ввод блокируется начисто - и мыша и клава. Именно это используется в мультике о расстреле Хорриганом со товарищи какого-то несчастного семейства.
8124 - команда с одним аргументом типа CritterAddr. Привязывает данного криттера к чузену следующим образом: при переходе в другую локацию (или на другой уровень в этой же) привязанный криттер появится рядом с чузеном, а при вступлении чузена в бой будет драться на его стороне (естественно, выделяется зеленым цветом). Нужно отметить, что привязанный криттер не ходит за чузеном, т.к. удержание дистанции между ними организуется в critter_p_proc и при отсутствии соответствующего кода он просто появляется в локации и стоит столбом, передвигаясь только в бою. Но даже в такой ситуации мне удалось пройти весь местный Шаолинь с Клинтом, так и не ударив ни единого врага. Просто если бой начинался далеко от Клинта, нужно было бежать к нему. Он и финального бойца отделал. Причем, странная вещь, ни этот боец, ни раки-тараканы, не обращали никакого внимания на то, что их бьет Клинт. Они охотились только за мной.
8125 - формат аналогичен 8124. Отменяет действие команды 8124.
80FD - облучает криттера. Формат: <CritterAddr>;<доза>;80FD.
8129 - открывает диалог бартера. Формат: <x - величина скидок-накруток>;8129. Цена, по которой чузен продает товар другим, не меняется, а вот цена покупки зависит от barter skill. Пусть есть базовая цена покупки, определенная по данной величине barter skill. Тогда фактическая цена покупки составит:
(базовая цена покупки)+(базовая цена покупки - цена продажи)*x/100
Причем x может принимать и отрицательные значения.
811A - взрыв. Формат: <координаты>;<этаж>;<сила взрыва - предельная величина хитов в эпицентре (фактически м.б. меньше,т.е. величина случайная)>;811A.
8149 - преобразование ObjectAddr во FrameID. У FrameID обнаружена одна интересная особенность: скажем у Клинта он равен 01000040, а у чузена 0100003E. Так вот, когда ребят вооружаешь копьем, FrameID становится равным 01004040 и 0100403E соответственно.
814A - преобразует FrameID в MoveID, как я решил назвать ту часть FrameID'а, которая определяет изображение движущегося криттера. В целом структура FrameID получается такой: AABBCCDD, где AA - ObjectType, BB - MoveID, CC - ID оружия, а DD - ID основного изображения. Варианты значений MoveID, которые удалось выявить: 0 - просто стоит, 1 -идет, 13H - бежит. Больше выяснить не удалось ввиду отсутствия внятного описания структуры фрм криттеров. Фактически же эта команда берет AABBCCDD и возвращает 000000CC.
8143 - организует нападение одного криттера на другого. Формат: <CritterAddr атакующего>;<CritterAddr атакуемого>;8143. Результат не возвращает.
8118 и 8119 (без аргументов) - возвращают текущие номер месяца и день месяца соответственно.