Предназначение:
Прочее
Порой, имеющегося функционала игры не хватает, а реализовывать дополнительные возможности посредством 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.
список изменений
  • 04.04.2023
    • Добавил функции CreateJassString, DestroyJassString и SetJassStringContent на замену небезопасной функции GetJassString.
  • 02.04.2023
    • Добавил событие происходящее после уничтожения главного потока.
    • Дополнительный мод, позволяющий использовать нативки, принимающие параметры с типом code.
  • 18.03.2023
    • Не ожидал, что функция из стандартной библиотеки для получения индекса элемента в словаре выбросит исключение. Игра больше не должна падать.
    • Исправил неинициализирвоанные локальные переменные в UnregisterJassNative, GetJassNative.
    • Функции для работы со строками теперь проверяют индексы перед использованием.

JASS API

С этим вам поможет библиотека, которая предлагается к скачиванию в этом ресурсе.
Она предоставляет набор функций, достаточный для достижения поставленной цели.
В примерах используются JASS и Pascal.

Работа с нативными функциями

Вызов нативных функций

Для вызова нативных функций используется конвенция вызова cdecl.
Следующая таблица покажет, какие типы принимает или возвращает нативка, в зависимости от типов аргументов и результата, указаных при её объявлении в JASS-скрипте.
Таблица типов
JASS типТип параметраТип результата
integerInt32Int32
realPSingleTSingle (костыль, чтобы значение возвращалось как и целые числа, через регистр процессора EAX, а не через специальный стэк FPU)
booleanLongBoolLongBool
stringPJassStringTJassStringHandle
handleTJassObjectHandleTJassObjectHandle
codeTJassFunctionHandleУказатель на байт-код (на деле нет ни одной нативки, что возвращает такое значение)
При получении от нативки хэндла строки, не забудьте уменьшить её счетчик ссылок, когда она вам не будет более нужна, при помощи функции DecrementHandleReferences.

Register​Jass​Native

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

Unregister​Jass​Native

function UnregisterJassNative(Name: PChar): LongBool; stdcall;
Удаляет функцию с именем Name из списка зарегистрированных.
Если функция была ранее зарегистрирована, то возвращает 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

Get​String​From​Handle

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;

Create​Jass​String

function CreateJassString(AContent: PChar): PJassString; stdcall;
Создает новый объект-строку с указанным содержимым AContent и возвращает указатель на него.
Такие объекты затем можно передавать нативкам, требующим строки.
Примечание: функция использует объектный пул, поэтому можете не переживать о падении производительности из-за частого создания и уничтожения строк.
Пример использования смотрите в описании функции GetJassNative.

Destroy​Jass​String

procedure DestroyJassString(AString: PJassString); stdcall;
Уничтожает объект-строку AString.
Пример использования смотрите в описании функции GetJassNative.

Set​Jass​String​Content

procedure SetJassStringContent(AString: PJassString; AContent: PChar); stdcall;   
Устанавливает значение строки AString равным AContent.
Не используйте её для изменения значения строковых параметров нативных функций.

Get​Jass​String​Content

function GetJassStringContent(JassString: PJassString): PChar; stdcall;
Извлекает строку из элемента таблицы строк главного потока, на который ссылается JassString.
Применяется для извлечения строк из получаемых аргументов в нативных функциях.
Пример смотрите в описании функции RegisterJassNative.

Increment​String​References

procedure IncrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Увеличивает счетчик ссылок у строки с хэндом StringHandle.
Для того, чтобы гарантировать что строка, хэндлом которой вы владеете, не будет внезапно уничтожена, необходимо увеличить её счетчик ссылок.

Decrement​String​References

procedure DecrementStringReferences(StringHandle: TJassStringHandle); stdcall;
Уменьшает счетчик ссылок у строки с хэндом StringHandle.
Если счетчик упадет до нуля, то строка будет уничтожена и её хэндл будет переиспользован.
Если ранее вы увеличили счетчик ссылок строки, но теперь она вам уже не нужна, то важно уменьшить её счетчик, а иначе она никогда не будет уничтожена.

Хэндлы

Increment​Handle​References

procedure IncrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall;
Увеличивает счетчик ссылок на хэндл ObjectHandle, препятствуя его преждевременному уничтожению.

Decrement​Handle​References

procedure DecrementHandleReferences(ObjectHandle: TJassObjectHandle); stdcall; 
Уменьшает счетчик ссылок на хэндл ObjectHandle.
При падении счетчика до нуля, хэндл освобождается и может быть переиспользован.
Также, в некоторых случаях, агент, скрывающийся за ним, также может быть уничтожен.

Полезные события

AddEventHandler

function AddEventHandler(EventType: TJassEventType; Callback: TEventCallback; Parameter: Pointer): TEventRegistrationHandle; stdcall;
Добавляет в реестр функцию Callback и вызывает её с параметром Parameter при событии EventType.
Всегда создает новую запись и возвращает её хэндл.
Возможные события:
ИмяОписание
jeMainBeingCalledСобытие создается перед вызовом функции "main" в главном потоке. Здесь можно инициализировать какие-либо данные перед игрой.
jeMainThreadDestructionСобытие создается перед уничтожением главного потока. Может пригодиться для очистки накопившихся за прошедшую игру данных.
jeMainThreadDestroyedСобытие создается после уничтожения главного потока. Не вызывайте нативные функции на этой стадии.

Remove​Event​Handler

function RemoveEventHandler(ID: TEventRegistrationHandle): LongBool; stdcall;
Отменяет подписку с хэндлом ID, который был возвращен функцией AddEventHandler.

PostEvent

procedure PostEvent(EventType: TJassEventType); stdcall;
Уведомляет всех подписчиков, зарегистрированных при помощи функции AddEventHandler о событии EventType.
Используется внутри библиотеки для создания событий, но может пригодится и пользователям.
Например, если библиотека JassApi.dll и другие, что добавляют нативки, были загружены после вызова главной функции, то событие уже не произойдет и они не инициализируются.
В таком случае, можно вручную создать событие:
PostEvent(jeMainBeingCalled);

Передача функций в качестве параметров

Для того, чтобы создавать более сложные нативки, а то и вовсе писать весь сценарий на внешнем языке, может потребоваться получать события от игры, но триггеры в качестве действий принимают только JASS-функции.
Эту проблему решает дополнительный мод RedirectCalls. Смените расширение файла из архива на ".mix" и киньте в папку с игрой, после чего все нативки начнут принимать в качестве параметров указатели на внешние функции (указатель на машинные инструкции реального процессора).
При этом 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.
3 comments deleted
0
Голосов: 0
ScorpioT1000 - 2 months ago
0
Голосов: 0
Строки ведь не уничтожаются до конца игры
1
Голосов: 1
IceFog - 2 months ago
1
Голосов: 1
Строки ведь не уничтожаются до конца игры
В этой версии виртуальной машины работа со строками идет таким образом, что они не утекают.
0
Голосов: 0
Vampir_kolik - 2 weeks ago
Edited by
0
Голосов: 0
IceFog, Пожалуйста выложи свой JassApi,dll, а то твоя библиотека RedirectCalls.dll требует функции AddEventHandler у библиотеки JassApi.dll. Вот скриншот.
Uploaded files
1
Голосов: 1
IceFog - 2 weeks ago
Edited by
1
Голосов: 1
IceFog, Пожалуйста выложи свой JassApi,dll
Исправил. Теперь кнопка скачать ссылается на правильный архив.
Похоже, что главным файлом ресурса считается идущий последним в списке, а не первым.
To leave a comment please sign in to the site.