Несмотря на то, что в этой ветке уже многократно выкладывались всяческие хилеры-баферы, я посчитал своим долгом "вернуть в копилку OpenSource" то что взял оттуда
Итак, Пати Лекарь-Бафер Клерик 35 лвл.
Работа скрипта "как есть" гарантируется при выученных скилах соответствующих этому классу на 35 уровне и выше. Скрипт проверялся только на Интерлюде в пати с живым хозяином и локомотивом от nezabudkin (в катах без бега).
delphi Код:
{
Cleric party healer-buffer from MHz
v0.4.2
Благодарности:
Изначально делался из бота
mks "SE party healer" [url]http://coderx.ru/showpost.php?p=2252&postcount=176[/url]
Идея каста последовательности скилов
Grinch "Бешенный бафер" [url]http://coderx.ru/showpost.php?p=7169&postcount=513[/url]
Окно лога
VORON [url]http://coderx.ru/showpost.php?p=2648&postcount=217[/url]
Бег прицепом и разные фишки
nezabudkin "БОТ - Воин помошник от Alexus" [url]http://coderx.ru/showpost.php?p=6348&postcount=1[/url]
Инструкция по использованию
Вписываем в скрипт имена клера и командира, дополнительно можно подкорректировать время перебафа и
пороговые значения HP, свободно можно уменьшить константу LogicTimerInterval до 500, что сделает клера более "активным".
Запускаем скрипт.
Если клер еще не в пати, зовем его в пати (вообще-то должен работать и оффпати но я так его не проверял).
В окне командира появляются надписи "Now in party NNN members", кидаем на сопартийцев какой-нибудь полезный баф
типа ВВ пока кол-во NNN не будет соответствовать реальному кол-ву членов в пати.
Теперь бот готов к работе. Бот управляется социальными действиям со стороны командира. В ответ на социальные действия
бот переключает свои режимы
1. Режим хиляния. Если включен, то клер мониторит здоровье сопартийцев и при падении здоровья до 1-й границы хиляет
Heal, при падении здоровья ниже 2-й границы хиляет подряд Battle Heal + Heal
2. Режим бега. При включении клер "даблкликает" по командиру. Больше по командиру не долбит, поэтом в принципе может
отцепиться, если это произошло, снова выключите-включите режим бега. При выключении режима бега клер встает на том
месте где стоял и никуда больше не бегает.
3. Режим перебафа. При включении клер перебафывает всю пати в соответствии со своими "настройками" вообще имеет 3 вида
бафов "самобаф", баф бойца и баф "прицепа" (более детально см. листинг). Далее клер запоминает когда кого бафал и
перебафывает по мере необходимости. Режим перебафа менее приоритетен чем хил, т.е. если есть выбор хилять или бафать,
то клер будет хилять. Клер помнит когда кого бафал последний раз, так что можете свободно включать-выключать этот режим
дабы клер не "сорвался" с места и не побежал вас перебафывать потому что посчитал что пора.
4. Принудительный перебаф всей пати.
}
const
BotName = 'Cleric'; // Имя бота
MasterName = 'Master'; // Имя командира
AssistantName = 'Assistant'; // Необязательный ассистент
RebuffTime = 1000*60*18; // Время перебафывания в миллисекундах
HP1=1000; // Порог HP для Heal
HP2=300; // Порог HP для Battle Heal + Heal
tt_MillisecondsInDay = 86400000;
LogicTimerInterval = 1000;
Chaos=100;
SkillDelay = 10;
DEBUG = False;
//****************************************************************************************************
// Инфомационная таблица по скилам
SkillsSize = 32; // Максимальная длина последовательности кастуемых скилов
cst_BattleHeal = 1015;
cst_Heal = 1011;
cst_SelfHeal = 1216;
cst_WindWalk = 1204;
cst_Shield = 1040;
cst_Might = 1068;
cst_Focus = 1077;
cst_Acumen = 1085;
cst_HolyWeapon = 1043;
cst_MentalShield = 1035;
cst_Concentration = 1078;
cst_ResistFire = 1191;
cst_Regeneration = 1044;
cst_BerserkerSpirit = 1062;
// Смещения инфы о касте в пакте 48 MagicSkillUse
cstc_DoerIdOffset = 2;
cstc_SkillIdOffset = 10;
cstc_SkillTimeOffset = 18;
cstc_ReuseDelayOffset = 22;
var
frm: Tform;
memo: Tmemo;
//****************************************************************************************************
// Таблица каста
Skills: array [1..SkillsSize] of Integer; // Собственно буфер
SkillsLastIndex: Integer; // Используется записей
SkillsCurIndex: Integer; // Текущая запись
EndCastTimer: TTimer; // Таймер, разматывающий таблицу каста
ImCasting: Boolean; // Признак что я нахожусь в состоянии каста
CurRebuffedChar: Integer; // Индекс перебафываемого чара
TimeBeginRebuff: Integer; // Время начала перебафа тек. чара
OldRunKey: Boolean; // Старое состояние флага бега, чтобы при перецеливани при перебафе не сбивался прицел
AlreadyDoubleClicked: Boolean; // Флаг что уже дабликнули по мастеру
//****************************************************************************************************
// Эти переменные возможно используются в других модулях
BotId: Integer; // Id бота
BotIdx: Integer; // Индекс бота в массиве пати
BotTargetId: Integer; // Id цели бота
MyX, MyY, MyZ: Integer; // Координаты бота
MasterId: Integer; // Id командира
//****************************************************************************************************
// От se_healer
PartyCount: Integer; // Текущее кол-во обслуживаемых персов
CharName: array[1..9] of string;
CharID: array[1..9] of integer;
CurHP: array[1..9] of integer;
MaxHP: array[1..9] of integer;
CurMP: array[1..9] of integer;
MaxMP: array[1..9] of integer;
//Dev: array[1..9] of extended; // Пока не используется это % здоровья
LastBuffTime: array[1..9] of integer;
//xe, ye: extended;
e,t,x,y,z: integer;
HealKey, RunKey, BuffKey: boolean; // Флажки режимов: Лечения, Бега и Перебафа
LogicTimer: TTimer; // Таймер, организующий "разум" бота
//****************************************************************************************************
// "Разум" бота
// Подготовить посл скилов для бойцового бафа
procedure FighterBuffSequence;
begin
Skills[1] := cst_Shield;
Skills[2] := cst_Might;
Skills[3] := cst_WindWalk;
Skills[4] := cst_Focus;
Skills[5] := cst_HolyWeapon;
Skills[6] := cst_MentalShield;
Skills[7] := cst_ResistFire;
Skills[8] := cst_Regeneration;
Skills[9] := cst_BerserkerSpirit;
SkillsLastIndex := 9;
end;
// Подготовить посл скилов для мага
procedure WizardBuffSequence;
begin
Skills[1] := cst_Acumen;
Skills[2] := cst_Shield;
Skills[3] := cst_WindWalk;
Skills[4] := cst_MentalShield;
Skills[5] := cst_Concentration;
Skills[6] := cst_ResistFire;
Skills[7] := cst_Regeneration;
SkillsLastIndex := 7;
end;
// Подготовить посл скилов для "прицепа" сидящего в коридоре
procedure SitterBuffSequence;
begin
Skills[1] := cst_Shield;
Skills[2] := cst_WindWalk;
SkillsLastIndex := 2;
end;
// Выбираем как будем лечить и лечим
procedure Heal(idx: Integer);
begin
if (CurHP[idx] <= HP2) then begin
Skills[1] := cst_BattleHeal;
Skills[2] := cst_Heal;
SkillsLastIndex := 2;
CastSequence;
end else begin
Cast(cst_Heal);
end;
end;
// Выбираем как будем бафать и бафаем
procedure Rebuff(idx: Integer);
begin
// Здесь выбор варианта бафов по имени перса
if CharName[idx] = BotName then
WizardBuffSequence
else if (CharName[idx] = MasterName) or (CharName[idx] = AssistantName) then
FighterBuffSequence
else
SitterBuffSequence;
CurRebuffedChar := idx;
TimeBeginRebuff := GetTime;
CastSequence;
end;
// Поиск нуждающегося в лечении (возвращает его индекс)
function WhoNeedHPIndex: Integer;
var
i: Integer;
curMinHP, curNeeded: Integer;
begin
curMinHP := 100000;
curNeeded := -1;
for i:=1 to PartyCount do begin
if (CurHP[i] > 0) and (CurHP[i] <= HP1) and (CurHP[i] < MaxHP[i]) then begin
if CurHP[i] < curMinHP then begin
curNeeded := i;
curMinHP := CurHP[i];
end;
end;
end;
Result := curNeeded;
end;
// Поиск нуждающегося в перебафе (возвращает его индекс)
function WhoNeedBuffIndex: Integer;
var
i: Integer;
curTime: Integer;
curDeltaTime: Integer;
curMinTime: Integer;
curNeeded: Integer;
begin
curNeeded := -1;
curTime := GetTime;
curMinTime := 2147483647;
for i:=1 to PartyCount do begin
if CurHP[i] > 0 then begin
curDeltaTime := curTime - LastBuffTime[i];
if curDeltaTime > RebuffTime then begin
if curDeltaTime < curMinTime then begin
curNeeded := i;
curMinTime := curDeltaTime;
end;
end;
end;
end;
Result := curNeeded;
end;
// Выработка "общей" линии поведения бота (обработчик таймера логики)
procedure OnLogicTimer(Sender: TObject);
var
selectedForHeal: Integer;
selectedForBuff: Integer;
begin
if PartyCount <= 0 then exit; // Если еще ничего не знаем ни о ком, ничего не делаем
for t:=1 to PartyCount do begin // Id командира и бота
if CharName[t]=MasterName then MasterId:=CharID[t];
if CharName[t]=BotName then begin
BotID:=CharID[t];
BotIdx:=t;
end;
end;
if MyHP = 0 then exit; // Если я мертв - то все пофигу
// Если я что-то кастую, то докастую и будем выбирать что делать дальше
if ImCasting then begin
AddLog('************ Im casting ****************');
exit;
end;
// Не прерываем последовательность перебафа!
if CurRebuffedChar > 0 then begin
AddLog('************ Im rebuffing char ' + CharName[CurRebuffedChar] + '****************');
exit;
end;
// Если включен режим лечения, то это приоритетная задача, сначала займемся ей
if HealKey then begin
selectedForHeal := WhoNeedHPIndex;
if selectedForHeal > 0 then begin
if CharID[selectedForHeal] <> BotTargetId then // Проверяем прицел если надо переставляем его
Action(CharID[selectedForHeal]); // но не контролируем результат,
Heal(selectedForHeal); // и сразу ХИЛ!
exit; // Щас бой, не до бафов, оббафаю потом
end;
end;
// Если включен режим перебафа и время пришло, то запускаем перебаф
if BuffKey then begin
selectedForBuff := WhoNeedBuffIndex;
if selectedForBuff > 0 then begin // Нашелся перс для перебафа
AddLog('Im going to rebuff ' + CharName[selectedForBuff]);
OldRunKey := RunKey;
RunKey := False;
if BotTargetId = CharID[selectedForBuff] then // Не бафаем кого попало! Убеждаемся что выбрана правильная
Rebuff(selectedForBuff) // цель
else
Action(CharID[selectedForBuff]);
end;
end;
end;
//****************************************************************************************************
// Отладка и тестирование
procedure PrintLastBuffTime;
var
i: Integer;
begin
AddLog('');
PrintFlag('BuffKey', BuffKey);
PrintFlag('ImCasting', ImCasting);
AddLog('СurRebufedChar='+IntToStr(CurRebuffedChar));
AddLog('Time='+IntToStr(GetTime));
for i:=1 to PartyCount do begin
AddLog('Member='+CharName[i]+' Time='+IntToStr(LastBuffTime[i]));
end;
end;
procedure PrintFlag(caption: String; flag: Boolean);
begin
if flag then
AddLog(caption + '=True')
else
AddLog(caption + '=False');
end;
//****************************************************************************************************
// OnEndCastTimer
procedure OnEndCastTimer(Sender: TObject);
begin
AddLog('End casting');
// Выключаем таймер потому что не знаем сколько продлится следующий каст. Таймер включает обработчик MagicSkillUse
EndCastTimer.Enabled := False;
ImCasting := False;
Inc(SkillsCurIndex);
if SkillsCurIndex <= SkillsLastIndex then begin // Если еще есть касты в последовательности, то отправляем их дальше
Cast(Skills[SkillsCurIndex]);
end else begin
// Если последовательность кончилась, то проверяем, если это был режим перебафа,
// то фиксируем его окончание и окончательно фиксируем время когда начинали бафать перса
if CurRebuffedChar > 0 then begin
LastBuffTime[CurRebuffedChar] := TimeBeginRebuff;
TimeBeginRebuff := 0;
CurRebuffedChar := 0;
RunKey := OldRunKey; // Перебаф окончен, если надо, можно бегать
AlreadyDoubleClicked := False;
end;
end;
end;
// Обработчик получения инфы о текущем касте
procedure OnMagicSkillUse;
var
skillId, skillTime, reuseDelay: Integer;
curTime: Integer;
skillIndex: Integer;
begin
if (pck[1] <> #$48) or (ReadD(cstc_DoerIdOffset) <> BotID) then // Блокируем затупления
exit;
ImCasting := True;
skillId := ReadD(cstc_SkillIdOffset);
skillTime := ReadD(cstc_SkillTimeOffset);
reuseDelay := ReadD(cstc_ReuseDelayOffset);
AddLog('');
AddLog('MagicSkillUse');
AddLog('Id=' + IntToStr(skillId));
AddLog('Time=' + IntToStr(skillTime));
AddLog('Delay=' + IntToStr(reuseDelay));
EndCastTimer.Interval := skillTime + SkillDelay;
EndCastTimer.Enabled := True;
end;
procedure OnActionFail;
begin
AddLog('ActionFail');
end;
function MyHP: Integer;
begin
if BotIdx <= 0 then
Result := 0
else
Result := CurHP[BotIdx];
end;
// Кастуй последовательность скилов описаную в Skills массиве
procedure CastSequence;
begin
SkillsCurIndex := 1;
Cast(Skills[1]);
end;
procedure Init; //Вызывается при включении скрипта
var
i: Integer;
begin
if DEBUG then begin
frm := TForm.Create(nil);
frm.Caption := 'Chat';
frm.BorderStyle := bsSizeable;
frm.Position := poScreencenter;
frm.Width:=600;
frm.Height:=700;
//frm.FormStyle:=FsStayOnTop;
frm.Show;
memo :=TMemo.create(nil);
memo.parent:=frm;
memo.align:=alClient;
end;
AddLog('Init');
BotId := 0;
BotIdx := 0;
// Очистка содержимого таблиц
BotTargetId := 0; // Если сначала если бот дважды кликнет по кому-то то нестрашно
PartyCount := 0;
for i:=1 to 9 do begin
CharName[i] := '';
CharID[i] := 0;
CurHP[i] := 0;
MaxHP[i] := 0;
CurMP[i] := 0;
MaxMP[i] := 0;
LastBuffTime[i] := 0;
end;
ClearLastBuffTime;
BuffKey := False;
HealKey := False;
RunKey := False;
AlreadyDoubleClicked := False;
ImCasting := False;
CurRebuffedChar := 0;
TimeBeginRebuff := 0;
EndCastTimer := TTimer.Create(nil);
EndCastTimer.OnTimer := @OnEndCastTimer;
EndCastTimer.Enabled := False;
LogicTimer:=TTimer.Create(nil);
LogicTimer.OnTimer:=@OnLogicTimer;
LogicTimer.Interval:=LogicTimerInterval;
LogicTimer.Enabled:=True;
end;
procedure ClearLastBuffTime;
var
i: Integer;
begin
for i:=1 to 9 do begin
LastBuffTime[i] := 0;
end;
end;
// Взять цель в таргет или мочить ее пухой
procedure Action(targetId: integer);
begin
AddLog('Action on ID=' + IntToStr(targetId));
buf:=#$04;
WriteD(targetId);
WriteD(MyX);
WriteD(MyY);
WriteD(MyZ);
WriteC(0);
SendToServerEx(BotName);
end;
procedure Cast(skillId: integer);
begin
// Формируем пакет на каст и отправляем его
buf:=hstr('2F 00 00 00 00 00 00 00 00 00');
WriteD(skillId, 2);
SendToServerEx(BotName);
//ImCasting := True;
end;
function GetTime: Integer;
begin
Result := Round(Time*tt_MillisecondsInDay);
end;
procedure AddLog(msg: string);
begin
if DEBUG then
memo.Lines.Add(IntToStr(GetTime) + '; ' + msg);
end;
procedure Say(msg:string);
begin
buf:=hstr('4A 00 00 00 00');
WriteD(2);
WriteS(BotName);
WriteS(msg);
SendToClientEx(MasterName);
end;
procedure Free; //Вызывается при выключении скрипта
begin
if DEBUG then begin
memo.Free;
frm.Free;
end;
EndCastTimer.Free;
LogicTimer.free;
PartyCount:=0;
HealKey:=false;
RunKey:=false;
BuffKey:=False;
end;
procedure OnPartySmallWindowUpdate;
var
i, f: Integer;
exist: Boolean;
begin
exist := False;
i:=6;
for f:=1 to (PartyCount+1) do begin
exist := CharID[f]=ReadD(2);
if exist=true then begin
CharName[f]:=ReadS(i);
i:=i+8;
CurHP[f]:=ReadD(i);
MaxHP[f]:=ReadD(i);
CurMP[f]:=ReadD(i);
MaxMP[f]:=ReadD(i);
//Dev[f]:=CurHP[f]/MaxHP[f]; // Пока не используется это % здоровья
break;
end;
end;
if exist=false then begin
PartyCount:=PartyCount+1;
Say('Now in party '+IntToStr(PartyCount) + ' members');
f:=PartyCount;
CharID[f]:=ReadD(2);
CharName[f]:=ReadS(i);
i:=i+8;
CurHP[f]:=ReadD(i);
MaxHP[f]:=ReadD(i);
CurMP[f]:=ReadD(i);
MaxMP[f]:=ReadD(i);
//Dev[f]:=CurHP[f]/MaxHP[f]; // Пока не используется это % здоровья
end;
end;
procedure FollowMaster;
begin
if AlreadyDoubleClicked then // Не тычем по 100 раз в хозяина
exit;
if BotTargetId = MasterId then
Action(MasterId)
else begin
Action(MasterId); Action(MasterId);
end;
AlreadyDoubleClicked := True;
end;
procedure OnValidatePosition;
begin
MyX:=ReadD(2);
MyY:=ReadD(6);
MyZ:=ReadD(10);
end;
//****************************************************************************************************
// Обработка пакетов скриптом
begin
if pck='' then exit;
// Управление составом пати
if FromServer and (pck[1]=#$52) and ((ConnectName=MasterName) or (ConnectName=BotName)) then begin
OnPartySmallWindowUpdate;
exit;
end;
if FromClient and (ConnectName=MasterName) and (pck[1]=#$01) then begin
if RunKey and (not ImCasting) then // Прицепляемся в хвост только если включен режим, и ничего не кастуем
FollowMaster;
// Остаемся в скрипте
end;
// Обновляем свои координаты
if FromClient and (ConnectName=BotName) and (pck[1]=#$48) then begin
OnValidatePosition;
end;
if FromServer and (ConnectName = BotName) then begin
case pck[1] of
#$04: BotId:=ReadD(18); // StatusUpdate
#$48: begin // MagicSkillUse
if ReadD(cstc_SkillTimeOffset) = 0 then
exit;
if ReadD(cstc_DoerIdOffset) = BotId then begin
OnMagicSkillUse;
exit;
end;
end;
#$25: OnActionFail;
#$A6: begin //TargetSelected
BotTargetId := ReadD(2);
AddLog('Selected target ID=' + IntToStr(BotTargetId));
end;
#$2A: begin //TargetUnselected
BotTargetId := 0;
AddLog('UnSelected target');
end;
end;
end;
// Управление ботом со стороны командира через соц. действия
if FromClient and (ConnectName=MasterName) and (pck[1] = #$1B) then begin
case pck[2] of
#$02: begin
pck := '';
HealKey:=not(HealKey);
Case HealKey of
true: Say('Heal mode ON');
false: Say('Heal mode OFF');
end;
end;
#$03: begin
pck := '';
RunKey:=not(RunKey);
if RunKey then begin
Say('Run mode ON');
AlreadyDoubleClicked := False;
end else begin
Say('Run mode OFF');
Action(BotId); // Прицеливаемся на себя чтобы сбить "привязку хвостом к командиру"
Action(BotId);
end;
end;
#$04: begin
pck := '';
BuffKey:=not(BuffKey);
if BuffKey then begin
Say('Buff mode ON');
end else begin
Say('Buff mode OFF');
end;
end;
#$05: begin
pck := '';
ClearLastBuffTime;
Say('Buffs times table cleared');
end;
end;
end;
end.
Для тех, кто "асилил".
Основная сложность в написании скриптов для баферов это то, что у разных расс разные бафы. Что касается лекарей, то основные споры идут вокруг "принятия решения о том кого хилять". В вышеприведенном скрипте все эти "спорные" моменты выделены в отдельные функции, таким образом "заточка" под конкретные нужды не представляется проблематичной.
В боте расширена и "зарефакторена" идея каста последовательности скилов из бота Grinch, так что бот кастует практически со скоростью макроса.
Бот очень "трепетно" следит за своим таргетом. Возможно я чересчур уделил этому внимание и "перегрузил" код, если вы считаете это излишним, то можете выкинуть эту часть кода и значительно упростить логику.
Бот в отличие от других "не долбит" по командиру в ответ на каждое его перемещение. Я посчитал что это более "правильно" и менее палевно. Опять же вернуть код как в "помощнике от nezabudkin не составит труда.