Добавлен IceFog,
опубликован
Программы
Предназначение:
Прочее
Порой, имеющегося функционала игры не хватает, а реализовывать дополнительные возможности посредством JASS-сценариев с мемхаком неудобно да и результат не может похвастаться высокой производительностью.
В таком случае, может помочь добавление собственных нативных функций, которые будут написаны на более подходящих языках программирования, а то и вовсе поместить весь сценарий во внешнюю библиотеку.
Исходники с примером использования этой библиотеки можете скачать здесь.
заголовочный файл
unit jassapi;
{$mode objfpc}{$H+}
interface
const
JASS_API_DLL_NAME = 'JassApi.dll';
type
PJassString = Pointer;
TJassStringHandle = Dword;
TJassObjectHandle = Dword;
TJassFunctionHandle = Dword;
TSingle = record Value: Single end;
TJassEventType = (
jeMainBeingCalled = 0,
jeMainThreadDestruction = 1,
jeMainThreadDestroyed = 2
);
TJassEventCallback = procedure (Parameter: Pointer); stdcall;
TEventRegistrationHandle = Dword;
function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall; external JASS_API_DLL_NAME;
function UnregisterJassNative(Name: PChar): LongBool; stdcall; external JASS_API_DLL_NAME;
function GetJassNative(Name: PChar): Pointer; stdcall; external JASS_API_DLL_NAME;
function GetStringHandle(Value: PChar): TJassStringHandle; stdcall; external JASS_API_DLL_NAME;
function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall; external JASS_API_DLL_NAME;
function CreateJassString(AContent: PChar): PJassString; stdcall; external JASS_API_DLL_NAME;
procedure DestroyJassString(AString: PJassString); stdcall; external JASS_API_DLL_NAME;
procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall; external JASS_API_DLL_NAME;
function GetJassStringContent(JassString: PJassString): PChar; stdcall; external JASS_API_DLL_NAME;
procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall; external JASS_API_DLL_NAME;
procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;
procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; external JASS_API_DLL_NAME;
function AddEventHandler(EventType: TJassEventType; Callback: TJassEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall; external JASS_API_DLL_NAME;
function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall; external JASS_API_DLL_NAME;
procedure PostEvent(EventType: TJassEventType); stdcall; external JASS_API_DLL_NAME;
implementation
end.
список изменений
- 03.06.2023
- Изменения в RedirectCalls
- В случае, если мод был загружен прямо во время игры, он конвертирует всё ранее созданые функции из кэша в новый формат.
- Если мод загружен второй раз, что может произойти при одновременной установке его в папку с игрой и встраивании в карту, игра не будет модифицироваться повторно.
- Изменения в RedirectCalls
- 04.04.2023
- Добавил функции CreateJassString, DestroyJassString и SetJassStringContent на замену небезопасной функции GetJassString.
- 02.04.2023
- Добавил событие происходящее после уничтожения главного потока.
- Дополнительный мод, позволяющий использовать нативки, принимающие параметры с типом code.
- 18.03.2023
- Не ожидал, что функция из стандартной библиотеки для получения индекса элемента в словаре выбросит исключение. Игра больше не должна падать.
- Исправил неинициализирвоанные локальные переменные в UnregisterJassNative, GetJassNative.
- Функции для работы со строками теперь проверяют индексы перед использованием.
JASS API
С этим вам поможет библиотека, которая предлагается к скачиванию в этом ресурсе.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
В примерах используются JASS и Pascal.
Работа с нативными функциями
Вызов нативных функций
Для вызова нативных функций используется конвенция вызова cdecl.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Таблица типов
JASS тип | Тип параметра | Тип результата |
---|---|---|
integer | Int32 | Int32 |
real | PSingle | TSingle (костыль, чтобы значение возвращалось как и целые числа, через регистр процессора EAX, а не через специальный стэк FPU) |
boolean | LongBool | LongBool |
string | PJassString | TJassStringHandle |
handle | TJassObjectHandle | TJassObjectHandle |
code | TJassFunctionHandle | Указатель на байт-код (на деле нет ни одной нативки, что возвращает такое значение) |
Нативки увеличивают счетчик ссылок возвращаемых строк, поэтому не забудьте уменьшить его функцией DecrementStringReferences, когда строка уже не будет нужна.
RegisterJassNative
function RegisterJassNative(Name: PChar; Signature: PChar; Address: Pointer): LongBool; stdcall;
Регистрирует нативную функцию с адресом Address под именем Name и сигнатурой Signature (Подробнее о сигнатурах).
Пример
Предположим, имеется следующая функция, которую нужно добавить в игру:
function SetMinimapImage(FilePath: String): Boolean;
В таком случае, для неё нужно будет сделать адаптированную версию:
function jass_SetMinimapImage(FilePath: PJassString): LongBool; cdecl;
begin
Result:= SetMinimapImage(GetJassStringContent(FilePath));
end;
И затем зарегистрировать:
RegisterJassNative('SetMinimapImage', '(S)B', @jass_SetMinimapImage);
Теперь в игре можно будет воспользоваться ею:
native SetMinimapImage takes string FilePath returns boolean
UnregisterJassNative
function UnregisterJassNative(Name: PChar): LongBool; stdcall;
Удаляет функцию с именем Name из списка зарегистрированных.
Если функция была ранее зарегистрирована, то возвращает True, а иначе False.
Если функция была ранее зарегистрирована, то возвращает True, а иначе False.
Пример
UnregisterJassNative("SetMinimapImage");
GetJassNative
function GetJassNative(Name: PChar): Pointer; stdcall;
Возвращает указатель на нативную функцию с именем Name.
В случае, если функция с указанным именем не была найдена, возвращает нулевой указатель.
В случае, если функция с указанным именем не была найдена, возвращает нулевой указатель.
Пример
type
TSetMinimapImageFunction = function (FilePath: PJassString): LongBool; cdecl;
var
pSetMinimapImage: TSetMinimapImageFunction;
jFilePath: PJassString;
Pointer(pSetMinimapImage):= GetJassNative("SetMinimapImage");
jFilePath:= CreateJassString('war3mapImported/minimap.blp');
pSetMinimapImage(jFilePath);
DestroyJassString(jFilePath);
Работа со строками
GetStringHandle
function GetStringHandle(Value: PChar): TJassStringHandle; stdcall;
Возвращает хэндл строки со значение Value в таблице строк главного потока.
Если такой строки не существует, то создается новая.
Если такой строки не существует, то создается новая.
Не увеличивает счетчик ссылок, так что, прежде чем возвращать хэндл скрипту, следует воспользоваться функцией IncrementStringReferences. В оригинальном интерпретаторе строки продолжат утекать, но с альтернативным эта проблема будет устранена.
Пример
Если нужно создать нативку, возвращающую строку скрипту.
function jass_GetMinimapImage: TJassStringHandle;
begin
Result:= GetStringHandle(FilePath);
IncrementStringReferences(Result);
end;
RegisterJassNative('GetMinimapImage', '()S', @jass_GetMinimapImage);
В карте:
native GetMinimapImage takes nothing returns string
GetStringFromHandle
function GetStringFromHandle(StringHandle: TJassStringHandle): PChar; stdcall;
Возвращает указатель на содержимое строки с хэндлом StringHandle из таблицы главного потока.
Если главный потока еще не был создан или в его таблицы нет такого хэндла, то возвращается нулевой указатель.
Если главный потока еще не был создан или в его таблицы нет такого хэндла, то возвращается нулевой указатель.
Пример
Если нужно воспользоваться строкой, возвращенной другой нативной функцией.
procedure OnChatMessage;
var
hMessage: TJassStringHandle;
Message: PChar;
begin
hMessage:= pGetEventPlayerChatString();
Message:= GetStringFromHandle(hMessage);
case Message of
'-start': Start();
'-stop': Stop();
end;
end;
CreateJassString
function CreateJassString(AContent: PChar): PJassString; stdcall;
Создает новый объект-строку с указанным содержимым AContent и возвращает указатель на него.
Такие объекты затем можно передавать нативкам, требующим строки.
Такие объекты затем можно передавать нативкам, требующим строки.
Примечание: функция использует объектный пул, поэтому можете не переживать о падении производительности из-за частого создания и уничтожения строк.
Пример использования смотрите в описании функции GetJassNative.
DestroyJassString
procedure DestroyJassString(AString: PJassString); stdcall;
Уничтожает объект-строку AString.
Пример использования смотрите в описании функции GetJassNative.
SetJassStringContent
procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall;
Устанавливает значение строки AString равным AContent.
Не используйте её для изменения значения строковых параметров нативных функций.
Не используйте её для изменения значения строковых параметров нативных функций.
GetJassStringContent
function GetJassStringContent(JassString: PJassString): PChar; stdcall;
Извлекает строку из элемента таблицы строк главного потока, на который ссылается JassString.
Применяется для извлечения строк из получаемых аргументов в нативных функциях.
Пример смотрите в описании функции RegisterJassNative.
IncrementStringReferences
procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Увеличивает счетчик ссылок у строки с хэндом StringHandle.
Для того, чтобы гарантировать что строка, хэндлом которой вы владеете, не будет внезапно уничтожена, необходимо увеличить её счетчик ссылок.
DecrementStringReferences
procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Уменьшает счетчик ссылок у строки с хэндом StringHandle.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если ранее вы увеличили счетчик ссылок строки, но теперь она вам уже не нужна, то важно уменьшить её счетчик, а иначе она никогда не будет уничтожена.
Хэндлы
IncrementHandleReferences
procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Увеличивает счетчик ссылок на хэндл ObjectHandle, препятствуя его преждевременному уничтожению.
DecrementHandleReferences
procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Уменьшает счетчик ссылок на хэндл ObjectHandle.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.
Полезные события
AddEventHandler
function AddEventHandler(EventType: TJassEventType; Callback: TEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall;
Добавляет в реестр функцию Callback и вызывает её с параметром Parameter при событии EventType.
Всегда создает новую запись и возвращает её хэндл.
Всегда создает новую запись и возвращает её хэндл.
Возможные события:
Имя | Описание |
---|---|
jeMainBeingCalled | Событие создается перед вызовом функции "main" в главном потоке. Здесь можно инициализировать какие-либо данные перед игрой. |
jeMainThreadDestruction | Событие создается перед уничтожением главного потока. Может пригодиться для очистки накопившихся за прошедшую игру данных. |
jeMainThreadDestroyed | Событие создается после уничтожения главного потока. Не вызывайте нативные функции на этой стадии. |
RemoveEventHandler
function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall;
Отменяет подписку с хэндлом ID, который был возвращен функцией AddEventHandler.
PostEvent
procedure PostEvent(EventType: TJassEventType); stdcall;
Уведомляет всех подписчиков, зарегистрированных при помощи функции AddEventHandler о событии EventType.
Используется внутри библиотеки для создания событий, но может пригодится и пользователям.
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
PostEvent(jeMainBeingCalled);
Передача функций в качестве параметров
Для того, чтобы создавать более сложные нативные функции или сценарии, может потребоваться получать события от игры, но, в качестве действий, триггеры принимают только JASS-функции.
Эту проблему решает дополнительный мод RedirectCalls. Смените расширение файла из архива на ".mix" и киньте в папку с игрой, после чего все нативки начнут принимать в качестве параметров указатели на внешние функции (указатель на машинные инструкции реального процессора).
При этом JASS-скрипт продолжает работать как ни в чем не бывало.
При этом JASS-скрипт продолжает работать как ни в чем не бывало.
Поддерживается загрузка в процессе игры: мод откорректирует ранее созданые функции.
Также, не будет проблем, если мод одновременно и установлен в папку с игрой и встроен в загружаемую карту: активным будет лишь первый загруженый экземпляр.
Также, не будет проблем, если мод одновременно и установлен в папку с игрой и встроен в загружаемую карту: активным будет лишь первый загруженый экземпляр.
Становится возможным создавать триггеры на равне со скриптом.
Пример нативного скрипта, использующего триггеры.
В ответ на сообщение "hello" от игрока красного пишет "Good bye.".
library NativeScript;
{$mode objfpc}{$H+}
uses
jassapi;
type
TAgentHandle = Dword;
TPlayerHandle = TAgentHandle;
TTriggerHandle = TAgentHandle;
TTriggerActionHandle = TAgentHandle;
TEventHandle = TAgentHandle;
TNativeScriptActionFunction = procedure;
TNativeScriptConditionFunction = function: LongBool;
var
pPlayer: function (number: Int32): TPlayerHandle; cdecl;
pDisplayTimedTextToPlayer: procedure (toPlayer: TPlayerHandle; x, y, duration: PSingle; message: PJassString); cdecl;
pCreateTrigger: function: TTriggerHandle; cdecl;
pTriggerAddAction: function (whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle; cdecl;
pTriggerRegisterPlayerChatEvent: function (whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: PJassString; exactMatchOnly: LongBool): TEventHandle; cdecl;
hChatTrigger: TTriggerHandle;
function Player(Number: Integer): TPlayerHandle;
begin
Result:= pPlayer(Number);
end;
procedure DisplayTimedTextToPlayer(toPlayer: TPlayerHandle; x, y, duration: Single; message: String);
var
jMessage: PJassString;
begin
jMessage:= CreateJassString(PChar(Message));
pDisplayTimedTextToPlayer(toPlayer, @x, @y, @duration, jMessage);
DestroyJassString(jMessage);
end;
function CreateTrigger: TTriggerHandle;
begin
Result:= pCreateTrigger();
end;
function TriggerAddAction(whichTrigger: TTriggerHandle; actionFunc: TNativeScriptActionFunction): TTriggerActionHandle;
begin
Result:= pTriggerAddAction(whichTrigger, actionFunc);
end;
function TriggerRegisterPlayerChatEvent(whichTrigger: TTriggerHandle; whichPlayer: TPlayerHandle; chatMessageToDetect: String; exactMatchOnly: Boolean): TEventHandle;
var
jChatMessageToDetect: PJassString;
begin
jChatMessageToDetect:= CreateJassString(PChar(chatMessageToDetect));
Result:= pTriggerRegisterPlayerChatEvent(whichTrigger, whichPlayer, jChatMessageToDetect, exactMatchOnly);
DestroyJassString(jChatMessageToDetect);
end;
procedure OnChatMessage;
begin
DisplayTimedTextToPlayer(Player(0), 0.0, 0.0, 60.0, 'Good bye.');
end;
procedure InitTrigger;
begin
hChatTrigger:= CreateTrigger();
IncrementHandleReferences(hChatTrigger);
TriggerAddAction(hChatTrigger, @OnChatMessage);
TriggerRegisterPlayerChatEvent(hChatTrigger, Player(0), 'hello', True);
end;
procedure InitNatives;
begin
Pointer(pPlayer):= GetJassNative('Player');
Pointer(pDisplayTimedTextToPlayer):= GetJassNative('DisplayTimedTextToPlayer');
Pointer(pCreateTrigger):= GetJassNative('CreateTrigger');
Pointer(pTriggerAddAction):= GetJassNative('TriggerAddAction');
Pointer(pTriggerRegisterPlayerChatEvent):= GetJassNative('TriggerRegisterPlayerChatEvent');
end;
procedure OnInit({%H-}Parameter: Pointer); stdcall;
begin
InitNatives;
InitTrigger;
end;
begin
AddEventHandler(jeMainBeingCalled, @OnInit, nil);
end.
`
ОЖИДАНИЕ РЕКЛАМЫ...
Чтобы оставить комментарий, пожалуйста, войдите на сайт.
Отредактирован Vampir_kolik
Отредактирован IceFog
Похоже, что главным файлом ресурса считается идущий последним в списке, а не первым.
Отредактирован IceFog
Отредактирован IceFog
Учитывая, что и обычный и ИИ скрипты исполняются ею, то почему бы и нет?
Единственная проблема — регистрация должна произойти до компиляции, а иначе компилятор откажется работать, сетуя на несуществующую нативку.
Отредактирован Vampir_kolik
Я добавляю свою нативку в common.ai
native fBeginGameProcessAI takes nothing returns integer
Запускаю её у человека human,ai
call fBeginGameProcessAI()
Инициализирую библиотеку до:
call InitBlizzard()
call MeleeStartingAI()
И скрипт ИИ не работает.
Или её надо редеректом вызывать?
Раньше это было нужно, потому что там еще был код, который пересоздавал главный поток, а делать это пока тот исполняется — плохая идея.