Я пытался разместить это в обучающие статьи, но застрял на модерации, поэтому разместил сюда, поправив пару опечаток
Возможность снять UD, Snipe, Stealth или CoV — отличная штука, особенно в Interlude, где эти умения не имеют заточки на штраф. Многие уже привыкли отменять Chant of Revenge перед нажатием Mirage или хилиться, снимая Celestial Shield
Нам понадобятся- исходники сервера
- исходники interface.u с компилятором
Всё необходимое есть в свободном доступе
Серверная часть
Сервер в нашем случае — ACIS 36x, но подойдет любой (возможно и PTS). Всё, что потребуется — добавить команду, которая будет снимать указанный бафф. Для простоты примера обойдемся байпасом
Для снятия баффа более современные клиенты отправляют пакет
RequestDispel, поэтому будем использовать термин
dispel
Сперва в
L2Character добавим метод диспела. По аналогии с более свежими версиями игры он не будет снимать эффекты, которые должны оставаться после смерти (напр., пенальти), дебаффы, денсы и сонги:
// L2Character.java
public final void dispelSkillEffect(int skillId, int skillLevel) {
// Find skill with matching ID and level
final L2Skill skill = SkillTable.getInstance().getInfo(skillId, skillLevel);
// Skill wasn't found and can't be dispelled
if (skill == null)
{
return;
}
// Penalty-like or debuff skill effect can't be dispelled
if (skill.isStayAfterDeath() || skill.isDebuff())
{
return;
}
// Dance-like skill effect can't be dispelled
if (skill.isDance())
{
return;
}
// Stop skill effect by ID
_effects.stopSkillEffects(skill.getId());
}
Code: Java
Теперь добавим обработку байпаса в
runImpl сетевого пакета
RequestBypassToServer, который приходит от клиента. Поскольку метод
dispelSkillEffect в качестве аргументов требует ID и уровень скилла, клиент должен передать их в качестве параметров к команде
dispel:
// RequestBypassToServer.java
// Usage: _dispel:<int:skill_id>,<int:skill_level>
// Example: _dispel:313,8
else if (_command.startsWith("_dispel"))
{
// Cut out command params
String params = _command.substring(_command.indexOf(":") + 1);
// Split params into tokens
StringTokenizer st = new StringTokenizer(params, ",");
// Get skill ID from first token
int id = Integer.parseInt(st.nextToken());
// Get skill level from second token
int level = Integer.parseInt(st.nextToken());
// Dispel skill effect on current character
activeChar.dispelSkillEffect(id, level);
}
Code: Java
Пример вызова:
_dispel:313,8
Рекомендую вместо байпаса сделать команду с аналогичными параметрами. Кроме того, тогда игроки смогут писать макросы на снятие баффа
Клиентская часть
К сожалению,
в клиенте Interlude нет простого способа отследить нажатие Alt+Click, поэтому используем для снятия баффа обычный двойной клик левой кнопки мыши. Обрабатывать событие будет окно
AbnormalStatusWnd, которое отображает иконки баффов и дебаффов
Алгоритм:
- Слушаем в окне AbnormalStatusWnd событие двойного клика (OnLButtonDblClick)
- Определяем бафф, по которому кликнули (через StatusIcon.GetItem)
- Определяем ID и уровень скилла этого баффа (через GetSkillInfo)
- Отправляем запрос на сервер (через RequestBypassToServer или ExecuteCommand)
- Вызываем на сервере dispelSkillEffect с полученными ID и уровнем скилла
Событие двойного клика левой кнопкой мыши
OnLButtonDblClick получает в качестве аргументов только координаты клика. При этом
StatusIcon.GetItem требует указания ряда и столбца ячейки. Соответственно, необходимо определить, в какой ряд и в какой столбец наших баффов кликнул игрок
Поскольку мы знаем, что размер ячейки баффа 24 пикселя, а размер ручки, за которое перетаскивается окно — 12 пикселей, то вычислить ряд и ячейку легко: достаточно определить координаты окна с баффами, вычесть все значения и поделить остаток на размер ячейки. При приведении к int значения будут правильно округлены
Сперва добавим константу
NSTATUSICON_SIZE, описывающую размер ячейки баффа в начало скрипта. Остальные константы разработчки уже описали:
// AbnormalStatusWnd.uc
class AbnormalStatusWnd extends UIScript;
const NSTATUSICON_FRAMESIZE = 12;
const NSTATUSICON_MAXCOL = 12;
const NSTATUSICON_SIZE = 24;
// ...
Code: Javascript
Теперь в любое место (например, сразу после функции OnEvent), добавим обработку события двойного клика:
// AbnormalStatusWnd.uc
function OnLButtonDblClick(int X, int Y) {
local Rect windowBounds;
local int targetRow;
local int targetCol;
local StatusIconInfo info;
local SkillInfo skillInfo;
// Find window position
windowBounds = Me.GetRect();
// Process clicks outside of window frame only
if (X > (windowBounds.nX + NSTATUSICON_FRAMESIZE)) {
// Calc row and col of targeted icon
targetRow = (Y - windowBounds.nY) / NSTATUSICON_SIZE;
targetCol = (X - windowBounds.nX - NSTATUSICON_FRAMESIZE) / NSTATUSICON_SIZE;
// Store status info of targeted icon
StatusIcon.GetItem(targetRow, targetCol, info);
// Store actual skill info and make sure it is exists
if (GetSkillInfo(info.ClassID, info.Level, skillInfo)) {
// Request server to stop skill effect
// Usage: _dispel:<int:skill_id>,<int:skill_level>
// Example: _dispel:313,8
RequestBypassToServer("_dispel:" $ string(skillInfo.SkillID) $ "," $ string(skillInfo.SkillLevel));
}
}
}
Code: Javascript
Компилируем interface.u, копируем в клиент, запускаем игру
Готово!