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.
Posted: 18.09.2010 11:17
by NoSilence
Callback
Как вы уже догадались (наверное), если функции можно записать в переменные, то их можно и передать в другие функции. Так вот, вызов переданной функции из другой и называется колбеком.
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. Т.е. с легкостью можно написать свою функцию, не ковыряясь в инклудах или делая циклы, и лишь передать указатель на нее (значок @ перед названием функции).
function KillTarget(FindItem: cardinal):boolean;
begin
UOSay('.kill');
if WaitForTarget(3000) then TargetToObject(FindItem);
result:= true;
end;
begin
FindTypeC([$0190, $0191], Ground, @KillTarget);
end.
Далее, получая некий список предметов (в моем случае), запускается цикл передачи ИД в колбек функцию. Для удобности я использовал именно функцию (возвращающую boolean), а не процедуру, чтобы в любой момент можно было остановить цикл.
Динамические массивы
На ранних стадиях каждый сталкивался с проблемой установки пределов массива. Эта проблема решается динамическими массивами. Начнем с массива ИД предметов:
Вроде все просто, но при внесении каких-либо данных в него, нужно устанавливать размер. Размер массива устанавливается процедурой SetLength, которая принимает два параметра: в первом указывается наш массив, второй - новая длина. Спешу заметить, что отсчет в динамических массивах всегда начинается с 0.
Длина массива автоматически станет равной 4-м. А в ячейки 0, 1, 2 и 3 запишутся данные указанные в квадратных скобках.
Получить длину массива можно воспользовавшись функцией Length
Так же есть функции определения пределов массива: Low - показывает минимальный индекс массива (в нашем случае он всегда будет равен 0) High - показывает максимальный индекс массива (т.е. Length - 1)
Posted: 18.09.2010 12:04
by Vizit0r
мощно, очень мощно.
моя "тонкость" конечно попроще, но тоже порой полезно.
как вы знаете (наверное) - если у вас есть класс, то при написании
with Class do вы можете обращаться к его полям и функциям напрямую, без указания класса.
например, для класса TStringList можно обратиться так
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 прменительно к массиву.
Posted: 18.09.2010 12:56
by NoSilence
try
Как указывалось выше, "конструкция try finally end; используется для того, чтобы освобождение класса выполнялось ВСЕГДА, даже при возникновении ошибок". А теперь подробнее:
try
<ЛЮБЫЕ ДЕЙСТВИЯ> finally
<ЗАВЕРШАЮЩИЕ ДЕЙСТВИЯ> end
Если при выполнении действий между try и finally произойдет ошибка, то сразу выполнится завершающая часть. Даже если ошибки не будет рано или поздно она все равно выполняется.
Но есть один минус: при ошибке выскакивают окна (Невозможно открыть файл или др.), которые зачастую нам вообще не нужны. Для таких мест я использую
try
<ЛЮБЫЕ ДЕЙСТВИЯ> except
<ДЕЙСТВИЯ ПРИ ОШИБКЕ> end
with TStringList.Create do begin
try
LoadFromFile('C:\asdsadsa');
except
end;
try
tempstr:= Lines[0];
except
end;
Free;
end;
Не будет вас обременять какими-либо ненужными окнами, а просто будет продолжать работу. При желании мы всегда можем вывести ошибку в журнал добавив строку
except
AddToSystemJournal('Ай-ай-ай, а кто это сделал?')
end;
Posted: 09.11.2010 21:10
by NoSilence
Variant
Мечтали ли вы когда-нибудь забить на все эти типы переменных с их "IntToStr", "StrToInt" и т.д.? Variant дарует вам эту возможность, потому что он всетипны, а точнее имеет неопределенный тип.
При данной оперции дробная часть не откидывается, а округляется. В журнале: "8".
Posted: 11.11.2010 1:12
by admir
Мне тоже понравилось! Спасиб за дельные советы! прям как на лекции)
Re: Тонкости скриптинга
Posted: 27.07.2016 10:29
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) когда надо проверить вхождение строки в массив строк
Сегодня я попробую вам рассказать о такой специфической теме, как события.
Они пришли к нам, если так можно выразиться, на замену потокам.
Многие из вас, фантазируя себе невероятные возможности и маневры, хотели бы запустить кучу параллельных процессов (скриптов).
Я был таким же. Хочу остудить ваш пыл и донести до вас, что это совсем не нужно.
Для понимания материала стоило бы уже уметь писать что-то сложнее лор-скрипта.
Начнем с жизненного примера.
На шарде, где я играю (когда нападает ностальгия ), раз в сутки отображается гамп с сайтами рейтингов.
Нажимая на кнопки, я получаю бонусы, а в браузере открываются ссылки.
В обычной практике нам нужно было бы делать в рабочем цикле проверку по гампам.
И далее просто нажимать последовательно на кнопки.
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;
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, значение которой будем повышать с нажатием кнопок. Благодаря ей и будем отслеживать ход голосования.
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.
Для этого создадим этот самый модуль:
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.
- Э, Вуася, подожди, а как быть, если мне нужно ловить несколько гампов? Ведь стелс не дает устанавливать значение одного события несколько раз.
- А, точно. Ну, это классика. Сек.
Чтобы иметь возможность обрабатывать гампы из нескольких мест (модулей), нам нужно как-то централизовать все наши функции обработчиков событий.
Для этого организуем их сбор с добавлением в некий массив:
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.
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.
Готово! Добавляем этот модуль в наш скрипт и другие модули. Все они бесконфликтно работают.
Переделаем же наш рекламный гамп на лад новых технологий:
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, и пр.
Просто для каждого придется прописывать функцию обработки и добавления. Ну, это уже сами.
На самом деле, как вы могли заметить, содержимое этого "рассказа" не соответствует вступлению. Верно, потому что это всё вступление.
Если оно вам надо, ждите второй части.