Forum in READ ONLY mode! All questions and discussions on Discord official server, invite link: https://discord.gg/VxsGzJ7

Тонкости скриптинга

Часто задаваемые вопросы
Post Reply
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Тонкости скриптинга

Post by NoSilence »

Массивы функций
Была ли у кого-то после изучения материала по массивам мысль: "а можно ли создать массив функций?":?:

Даже если и не была, я все равно расскажу как это сделать. :)

Начнем с того, что можно создавать тип-функцию (процедуру):

Code: Select all

type
  MyProc = procedure();
Простая процедура, которая ничего не принимает. Добавим параметр:

Code: Select all

type
  MyProc = procedure(Text: string);
Теперь мы можем объявить массив нашей процедуры:

Code: Select all

var
  ProcArray: array [0..1] of MyProc;
Следом будут идти сами процедуры. Важно чтобы количество параметров не отличалось от параметров нашего типа.

Code: Select all

procedure First(Text: string);
begin
  AddToSystemJournal('Первая процедура: '+Text);
end;

procedure Second(Text: string);
begin
  AddToSystemJournal('Вторая процедура: '+Text);
end;
Готово. Добавляем их в массив:

Code: Select all

ProcArray[0]:= @First;
ProcArray[1]:= @Second;
Теперь, чтобы вызвать процедуру First, достаточно указать ее индекс в массиве. Полный код будет выглядить так:

Code: Select all

type
  MyProc = procedure(Text: string);

var
  ProcArray: array [0..1] of MyProc;

procedure First(Text: string);
begin
  AddToSystemJournal('Первая процедура: '+Text);
end;

procedure Second(Text: string);
begin
  AddToSystemJournal('Вторая процедура: '+Text);
end;

begin
ProcArray[0]:= @First;
ProcArray[1]:= @Second;

ProcArray[0]('1'); // = First('1')
ProcArray[0]('2'); // = First('2')
ProcArray[1]('3'); // = Second('3')
end.
Это было как бы вводным примерном, возможно, кто-то найдет этому применение. Далее Callback.
///
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Post by NoSilence »

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

Рассмотрим мою функцию FindTypeC из темы Includes

Code: Select all

function FindTypeC(ItemTypes: array of word; Container: cardinal; CallBackFunc: function(FindItem: cardinal):boolean):integer; 
var 
  i: integer; 
  FoundItems: array of cardinal; 
begin
if not Assigned(CallBackFunc) then exit;
result:= FindTypeB(ItemTypes, Container, FoundItems);
for i:= 0 to result-1 do 
  if not CallBackFunc(FoundItems[i]) then break; 
end;
Последним параметром функция принимает некий "тип" function(FindItem: cardinal):boolean. Т.е. с легкостью можно написать свою функцию, не ковыряясь в инклудах или делая циклы, и лишь передать указатель на нее (значок @ перед названием функции).

Code: Select all

function KillTarget(FindItem: cardinal):boolean; 
begin 
UOSay('.kill'); 
if WaitForTarget(3000) then TargetToObject(FindItem); 
result:= true; 
end; 

begin 
FindTypeC([$0190, $0191], Ground, @KillTarget); 
end.

Code: Select all

if not Assigned(CallBackFunc) then exit;
или

Code: Select all

if CallBackFunc = nil then exit;
Проверяет был ли передан указатель на функцию.

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

Code: Select all

if not CallBackFunc(FoundItems[i]) then break; 
///
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Post by NoSilence »

Динамические массивы
На ранних стадиях каждый сталкивался с проблемой установки пределов массива. Эта проблема решается динамическими массивами. Начнем с массива ИД предметов:

Code: Select all

var
  MyArray: array of cardinal;
Вроде все просто, но при внесении каких-либо данных в него, нужно устанавливать размер. Размер массива устанавливается процедурой SetLength, которая принимает два параметра: в первом указывается наш массив, второй - новая длина. Спешу заметить, что отсчет в динамических массивах всегда начинается с 0.

Code: Select all

SetLength(MyArray, 3)
Устанавливает длину массива - 3 ячейки. Т.е. теперь нам доступны ячейки MyArray[0], MyArray[1] и MyArray[2].

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

Code: Select all

MyArray:= [$99881234, $12332112, $00A98877, Backpack]
Длина массива автоматически станет равной 4-м. А в ячейки 0, 1, 2 и 3 запишутся данные указанные в квадратных скобках.

Получить длину массива можно воспользовавшись функцией Length

Так же есть функции определения пределов массива:
Low - показывает минимальный индекс массива (в нашем случае он всегда будет равен 0)
High - показывает максимальный индекс массива (т.е. Length - 1)
///
User avatar
Vizit0r
Developer
Developer
Posts: 3958
Joined: 24.03.2005 17:05
Contact:

Post by Vizit0r »

мощно, очень мощно.



моя "тонкость" конечно попроще, но тоже порой полезно.


как вы знаете (наверное) - если у вас есть класс, то при написании
with Class do вы можете обращаться к его полям и функциям напрямую, без указания класса.

например, для класса TStringList можно обратиться так

Code: Select all

tempstringlist : tstringlist;

with tempstringlist do
  begin
    if Count = 0 then
      Add('fff');
  end;
таким образом, вам ненадо писать tempstringlist.Count и tempstringlist.Add.


но трюк не совсем в этом.

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

Code: Select all

with Class_Name.Create do

вот вам полный пример, после разберем подробно

Code: Select all

Program Test; 
var
 IP: array [0..3] of Byte;
 I: Byte;
 s : String;
begin
s := '127.0.0.1';
with TStringList.Create do
   try
     Delimiter := '.';
     DelimitedText := s;
     if Count = 4 then
     begin
       for I := 0 to 3 do IP[I] := StrToInt(Strings[I]);
       for I := Low(IP) to High(IP) do AddToSystemJournal(IntToStr(IP[i]));
     end;
   finally
     Free;
   end;
end.
Здесь мы как раз наблюдаем работу с строковым списком, не привязанным к переменной.

итак, конструкция try finally end; используется для того, чтобы освобождение класса выполнялось ВСЕГДА, даже при возникновении ошибок.
В процессе работы входящая строка делится на строки по разделителю (символ-разделитель - точка). В качестве образца полученные куски строк (куски айпи-адреса) заносятся в массив IP, а так же выводятся построчно в SystemJournal.

Кстати, здесь же показана работа с High-Low прменительно к массиву.
Last edited by Vizit0r on 28.10.2010 13:17, edited 2 times in total.
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете". (с) Макконнелл, "Совершенный код".
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Post by NoSilence »

try
Как указывалось выше, "конструкция try finally end; используется для того, чтобы освобождение класса выполнялось ВСЕГДА, даже при возникновении ошибок". А теперь подробнее:

try
<ЛЮБЫЕ ДЕЙСТВИЯ>
finally
<ЗАВЕРШАЮЩИЕ ДЕЙСТВИЯ>
end

Если при выполнении действий между try и finally произойдет ошибка, то сразу выполнится завершающая часть. Даже если ошибки не будет рано или поздно она все равно выполняется.
Но есть один минус: при ошибке выскакивают окна (Невозможно открыть файл или др.), которые зачастую нам вообще не нужны. Для таких мест я использую

try
<ЛЮБЫЕ ДЕЙСТВИЯ>
except
<ДЕЙСТВИЯ ПРИ ОШИБКЕ>
end

Даже такая связка:

Code: Select all

with TStringList.Create do begin
  try
    LoadFromFile('C:\asdsadsa');
  except
  end;
  try
    tempstr:= Lines[0];
  except
  end;
  Free;
end;
Не будет вас обременять какими-либо ненужными окнами, а просто будет продолжать работу. При желании мы всегда можем вывести ошибку в журнал добавив строку

Code: Select all

except
  AddToSystemJournal('Ай-ай-ай, а кто это сделал?')
end;
Last edited by NoSilence on 12.06.2011 20:58, edited 1 time in total.
///
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Post by NoSilence »

Variant
Мечтали ли вы когда-нибудь забить на все эти типы переменных с их "IntToStr", "StrToInt" и т.д.? Variant дарует вам эту возможность, потому что он всетипны, а точнее имеет неопределенный тип.

Всего лишь одно объявление переменной:

Code: Select all

var
  MyVar: variant;
и мы получаем integer, boolean, string и т.п. в одном флаконе! Вот полный список подтипов этого типа:

Code: Select all

  vtInteger    = 0;
  vtBoolean    = 1;
  vtChar       = 2;
  vtExtended   = 3;
  vtString     = 4;
  vtPointer    = 5;
  vtPChar      = 6;
  vtObject     = 7;
  vtClass      = 8;
  vtWideChar   = 9;
  vtPWideChar  = 10;
  vtAnsiString = 11;
  vtCurrency   = 12;
  vtVariant    = 13;
  vtInterface  = 14;
  vtWideString = 15;
  vtInt64      = 16;
Это не нужно объяснять, это нужно показывать!

Code: Select all

MyVar:= true;
if MyVar then
  AddToSystemJournal(MyVar);
Покажет вам "true";

Code: Select all

var
  MyVar: variant;
  i: integer;

begin
i:= 4;
MyVar:= true;
if MyVar then
  AddToSystemJournal(IntToStr(i+MyVar));
end.
True считается как -1, поэтому данный код выведет в журнал "3".

Code: Select all

MyVar:= 7.7;
AddToSystemJournal(IntToStr(MyVar));
При данной оперции дробная часть не откидывается, а округляется. В журнале: "8".
///
admir
Novice
Novice
Posts: 97
Joined: 28.10.2008 20:44

Post by admir »

Мне тоже понравилось! Спасиб за дельные советы! прям как на лекции)
User avatar
Vizit0r
Developer
Developer
Posts: 3958
Joined: 24.03.2005 17:05
Contact:

Re: Тонкости скриптинга

Post by Vizit0r »

пришел тут вопрос на днях:
XXX (17:15:22 23/07/2016)
if (EasyUoGlobalWeapon = 'EventWeapon') or (EasyUoGlobalRing = 'EventRing') or (EasyUoGlobalBracelet = 'EventBracelet') or (EasyUoGlobalStam = 'EventStam') or (EasyUoGlobalHits = 'EventHits') then
XXX (17:15:32 23/07/2016)
this sentence is too long. How to seperate one sentence to two sentence for reading well?
Допилил и прикрутил кой-чего к PAX'у, так что теперь есть 2 варианта:

1) когда надо проверить вхождение строки в массив строк

Code: Select all

if IndexStr (EasyUoGlobalWeapon,['EventWeapon', 'EventWeapon','EventWeapon','EventWeapon']) <> -1 then
  true
else
  false

2) когда на каждый вариант надо свое действие - case для строк.

Code: Select all

 program new;
var x : String;

type
  TEventEUO = (eEUO_eventweapon,
               eEUO_eventring,
               eEUO_eventbracelet,
               eEUO_eventstam,
               eEUO_eventhits);

Begin
  x := 'EventRing';
  case TEventEUO(GetEnumValue(TypeInfo(TEventEUO), 'eEUO_'
                                    +LowerCase(x))) of
    eEUO_eventweapon   : AddToSystemJournal('EventWeapon detected');
    eEUO_eventring   : AddToSystemJournal('EventRing detected');
    eEUO_eventbracelet     : AddToSystemJournal('EventBracelet detected');      
    eEUO_eventstam     : AddToSystemJournal('EventStam detected');      
    eEUO_eventhits     : AddToSystemJournal('EventHits detected');      
  end;     
  x := 'EventHits';
  case TEventEUO(GetEnumValue(TypeInfo(TEventEUO), 'eEUO_'
                                    +LowerCase(x))) of
    eEUO_eventweapon   : AddToSystemJournal('EventWeapon detected');
    eEUO_eventring   : AddToSystemJournal('EventRing detected');
    eEUO_eventbracelet     : AddToSystemJournal('EventBracelet detected');      
    eEUO_eventstam     : AddToSystemJournal('EventStam detected');      
    eEUO_eventhits     : AddToSystemJournal('EventHits detected');      
  end;      
end.
На выходе будет
08:57:23:176 [xxx]: Compiling
08:57:23:213 [xxx]: Compiled succesfully
08:57:23:214 [xxx]: EventRing detected
08:57:23:214 [xxx]: EventHits detected
08:57:23:214 [xxx]: Succesfully executed
08:57:23:215 [xxx]: Script test.sc stopped successfuly
"Пишите код так, как будто сопровождать его будет склонный к насилию психопат, который знает, где вы живете". (с) Макконнелл, "Совершенный код".
NoSilence
Novice
Novice
Posts: 94
Joined: 02.01.2010 13:55

Re: Тонкости скриптинга

Post by NoSilence »

Здравствуйте, товаристчи ботоводы. :)

Сегодня я попробую вам рассказать о такой специфической теме, как события.
Они пришли к нам, если так можно выразиться, на замену потокам.
Многие из вас, фантазируя себе невероятные возможности и маневры, хотели бы запустить кучу параллельных процессов (скриптов).
Я был таким же. Хочу остудить ваш пыл и донести до вас, что это совсем не нужно.

Для понимания материала стоило бы уже уметь писать что-то сложнее лор-скрипта.

Начнем с жизненного примера.
На шарде, где я играю (когда нападает ностальгия :o), раз в сутки отображается гамп с сайтами рейтингов.
Нажимая на кнопки, я получаю бонусы, а в браузере открываются ссылки.
В обычной практике нам нужно было бы делать в рабочем цикле проверку по гампам.
И далее просто нажимать последовательно на кнопки.
FindGump, sync

Code: Select all

function sync:boolean;
begin
result:= CheckLag(20000);
wait(100);
end;

function FindGump(FirstLine: string):integer;
var
	i: integer;
	t: TStringList;
begin
result:= -1;
t:= TStringList.Create;
try
	for i:= 0 to GetGumpsCount - 1 do begin
		GetGumpTextLines(i, t);
		if t.Count > 0 then
			if t[0] = FirstLine then begin
				result:= i;
				exit;
			end;
		t.Clear;
	end;
finally
	t.Free;
end;
end;

Code: Select all

procedure CheckAdsGump;
var
	i: integer;
begin
i:= FindGump('Реклама');
if i >= 0 then begin
	WaitGump('1');
	sync;
	WaitGump('2');
	sync;
	WaitGump('3');
	sync;
	WaitGump('4');
	sync;
	WaitGump('5');
	sync;
	WaitGump('3000');
	AddToSystemJournal('"Проголосовали" за сервер.');
end;
end;
Здесь мы, обращаясь к процедуре CheckAdsGump где-то в теле скрипта, проверяем список гампов на наличие рекламного.
Далее, делая паузы, нажимаем на все ссылки гампа (1, 2, 3, 4, 5) и закрываем его (3000 - кнопка отмены на самом гампе).

Эту постоянную проверку можно исключить, добавив обработчик сообщения evIncomingGump.
При этом код процедуры голосования несколько изменится. Ведь "нажав" на одну из кнопок, мы "закроем" наш гамп, и сервер заново пришлет его в клиент, что повлечет повторный вызов evIncomingGump.
Следовательно, нам нужно как-то отследить, что этот гамп мы видим уже не первый раз. И что мы уже успели нажать на первую ссылку.
Добавим глобальную переменную AdsGumpStep, значение которой будем повышать с нажатием кнопок. Благодаря ей и будем отслеживать ход голосования.

Code: Select all

var
	AdsGumpStep: integer;

procedure CheckAdsGump(Serial, GumpID, X, Y: cardinal);
var
	i: integer;
begin
i:= FindGump('Реклама');
if i >= 0 then begin
	Inc(AdsGumpStep);
	case AdsGumpStep of
		1..6: NumGumpButton(i, AdsGumpStep);
		7: begin
			NumGumpButton(i, 3000);
			AdsGumpStep:= 0;
			AddToSystemJournal('"Проголосовали" за сервер.')
		end;
	end;
end;
end;

// ...
SetEventProc(evIncomingGump, 'CheckAdsGump');
// ...
И так, добавив одну строку SetEventProc в инициализацию нашего скрипта, мы избавили себя от лишних вызовов.
Теперь каждый приходящий гамп будет проверен на соответствие рекламному и обработан.

Но это еще не все. Можем вообще ограничиться лишь одним упоминанием нашего модуля в разделе uses.
Для этого создадим этот самый модуль:

Code: Select all

unit Ads;

interface

implementation

var
	AdsGumpStep: integer;

procedure CheckAdsGump(Serial, GumpID, X, Y: cardinal);
var
	i: integer;
begin
i:= FindGump('Реклама');
if i >= 0 then begin
	Inc(AdsGumpStep);
	case AdsGumpStep of
		1..6: NumGumpButton(i, AdsGumpStep);
		7: begin
				NumGumpButton(i, 3000);
				AdsGumpStep:= 0;
				AddToSystemJournal('"Проголосовали" за сервер.')
			end;
	end;
end;
end;

begin
SetEventProc(evIncomingGump, 'CheckAdsGump');
end.
Делаем так в нашем скрипте:

Code: Select all

uses
	Ads;
Получаем ежедневные бонусы и не паримся.

...

...

...

- Э, Вуася, подожди, а как быть, если мне нужно ловить несколько гампов? Ведь стелс не дает устанавливать значение одного события несколько раз.
- А, точно. Ну, это классика. Сек.

Чтобы иметь возможность обрабатывать гампы из нескольких мест (модулей), нам нужно как-то централизовать все наши функции обработчиков событий.
Для этого организуем их сбор с добавлением в некий массив:

Code: Select all

type
	TIncomingGumpProc = procedure(Serial, GumpID, X, Y: cardinal);

const
	MaxListeners = $3F;

var
	IncomingGumpListeners: array [0..MaxListeners] of TIncomingGumpProc;
	IncomingGumpListenersCount: byte;

function AddIncomingGumpProc(Proc: TIncomingGumpProc):integer;
begin
result:= IncomingGumpListenersCount;
IncomingGumpListeners[IncomingGumpListenersCount]:= Proc;
Inc(IncomingGumpListenersCount);
end;

// ...
Окей, посредством AddIncomingGumpProc мы добавляем все наши функции гампов в массив IncomingGumpListeners.
Выглядеть это будет вот так:

Code: Select all

//...
AddIncomingGumpProc(@CheckAdsGump);
AddIncomingGumpProc(@CheckAntibot);
AddIncomingGumpProc(@CheckTrade);
// ...
Остается добавить функцию, ответственную за их вызов:

Code: Select all

procedure IncomingGumpProc(Serial, GumpID, X, Y: cardinal);
var
	i: integer;
begin
for i:= 0 to IncomingGumpListenersCount - 1 do
	IncomingGumpListeners[i](Serial, GumpID, X, Y);
end;

// ...
SetEventProc(evIncomingGump, 'IncomingGumpProc');
// ...
Вот и все. Теперь, когда клиент получит гамп, Stealth вызовет процедуру IncomingGumpProc, а она последовательно передаст данные в CheckAdsGump, CheckAntibot и CheckTrade.

Облачим все это в модуль:

Code: Select all

unit EventHandler;

interface

type
	TIncomingGumpProc = procedure(Serial, GumpID, X, Y: cardinal);

function AddIncomingGumpProc(Proc: TIncomingGumpProc):integer;

implementation

const
	MaxListeners = $3F;

var
	IncomingGumpListeners: array [0..MaxListeners] of TIncomingGumpProc;
	IncomingGumpListenersCount: byte;

function AddIncomingGumpProc(Proc: TIncomingGumpProc):integer;
begin
result:= IncomingGumpListenersCount;
IncomingGumpListeners[IncomingGumpListenersCount]:= Proc;
Inc(IncomingGumpListenersCount);
if IncomingGumpListenersCount = 1 then
	SetEventProc(evIncomingGump, 'IncomingGumpProc');
end;

procedure IncomingGumpProc(Serial, GumpID, X, Y: cardinal);
var
	i: integer;
begin
for i:= 0 to IncomingGumpListenersCount - 1 do
	IncomingGumpListeners[i](Serial, GumpID, X, Y);
end;

begin
end.
Готово! Добавляем этот модуль в наш скрипт и другие модули. Все они бесконфликтно работают.

Переделаем же наш рекламный гамп на лад новых технологий:

Code: Select all

unit Ads;

interface

uses
	EventHandler;

implementation

var
	AdsGumpStep: integer;

procedure CheckAdsGump(Serial, GumpID, X, Y: cardinal);
var
	i: integer;
begin
i:= FindGump('Реклама');
if i >= 0 then begin
	Inc(AdsGumpStep);
	case AdsGumpStep of
		1..6: NumGumpButton(i, AdsGumpStep);
		7: begin
				NumGumpButton(i, 3000);
				AdsGumpStep:= 0;
				echo('"Проголосовали" за сервер.')
			end;
	end;
end;
end;

begin
AddIncomingGumpProc(@CheckAdsGump);
end.
Вуаля, и реклама прожимается и другие гампы можем ловить.
Естественно, в наш EventHandler можно прописать функции для всех видов событий. И DrawContainer, и DrawObject, и пр.
Просто для каждого придется прописывать функцию обработки и добавления. Ну, это уже сами.


На самом деле, как вы могли заметить, содержимое этого "рассказа" не соответствует вступлению. Верно, потому что это всё вступление.
Если оно вам надо, ждите второй части.
///
Post Reply