Я пытался разместить это в обучающие статьи, но застрял на модерации, поэтому разместил сюда, поправив пару опечаток
Возможность снять UD, Snipe, Stealth или CoV — отличная штука, особенно в Interlude, где эти умения не имеют заточки на штраф. Многие уже привыкли отменять Chant of Revenge перед нажатием Mirage или хилиться, снимая Celestial Shield
Нам понадобятся
Серверная часть
Сервер в нашем случае — ACIS 36x, но подойдет любой (возможно и PTS). Всё, что потребуется — добавить команду, которая будет снимать указанный бафф. Для простоты примера обойдемся байпасом
Для снятия баффа более современные клиенты отправляют пакет RequestDispel, поэтому будем использовать термин dispel
Сперва в L2Character добавим метод диспела. По аналогии с более свежими версиями игры он не будет снимать эффекты, которые должны оставаться после смерти (напр., пенальти), дебаффы, денсы и сонги:
[SRC="java"]// 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());
}
[/SRC]
Теперь добавим обработку байпаса в runImpl сетевого пакета RequestBypassToServer, который приходит от клиента. Поскольку метод dispelSkillEffect в качестве аргументов требует ID и уровень скилла, клиент должен передать их в качестве параметров к команде dispel:
[SRC="java"]// RequestBypassToServer.java
// Usage: _dispel:<intkill_id>,<intkill_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);
}
[/SRC]
Пример вызова: _dispel:313,8
Рекомендую вместо байпаса сделать команду с аналогичными параметрами. Кроме того, тогда игроки смогут писать макросы на снятие баффа
Клиентская часть
К сожалению, в клиенте Interlude нет простого способа отследить нажатие Alt+Click, поэтому используем для снятия баффа обычный двойной клик левой кнопки мыши. Обрабатывать событие будет окно AbnormalStatusWnd, которое отображает иконки баффов и дебаффов
Алгоритм:
Поскольку мы знаем, что размер ячейки баффа 24 пикселя, а размер ручки, за которое перетаскивается окно — 12 пикселей, то вычислить ряд и ячейку легко: достаточно определить координаты окна с баффами, вычесть все значения и поделить остаток на размер ячейки. При приведении к int значения будут правильно округлены
Сперва добавим константу NSTATUSICON_SIZE, описывающую размер ячейки баффа в начало скрипта. Остальные константы разработчки уже описали:
[SRC="javascript"]// AbnormalStatusWnd.uc
class AbnormalStatusWnd extends UIScript;
const NSTATUSICON_FRAMESIZE = 12;
const NSTATUSICON_MAXCOL = 12;
const NSTATUSICON_SIZE = 24;
// ...
[/SRC]
Теперь в любое место (например, сразу после функции OnEvent), добавим обработку события двойного клика:
[SRC="javascript"]// 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:<intkill_id>,<intkill_level>
// Example: _dispel:313,8
RequestBypassToServer("_dispel:" $ string(skillInfo.SkillID) $ "," $ string(skillInfo.SkillLevel));
}
}
}
[/SRC]
Компилируем interface.u, копируем в клиент, запускаем игру
Готово!
Возможность снять UD, Snipe, Stealth или CoV — отличная штука, особенно в Interlude, где эти умения не имеют заточки на штраф. Многие уже привыкли отменять Chant of Revenge перед нажатием Mirage или хилиться, снимая Celestial Shield
Нам понадобятся
- исходники сервера
- исходники interface.u с компилятором
Серверная часть
Сервер в нашем случае — ACIS 36x, но подойдет любой (возможно и PTS). Всё, что потребуется — добавить команду, которая будет снимать указанный бафф. Для простоты примера обойдемся байпасом
Для снятия баффа более современные клиенты отправляют пакет RequestDispel, поэтому будем использовать термин dispel
Сперва в L2Character добавим метод диспела. По аналогии с более свежими версиями игры он не будет снимать эффекты, которые должны оставаться после смерти (напр., пенальти), дебаффы, денсы и сонги:
[SRC="java"]// 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());
}
[/SRC]
Теперь добавим обработку байпаса в runImpl сетевого пакета RequestBypassToServer, который приходит от клиента. Поскольку метод dispelSkillEffect в качестве аргументов требует ID и уровень скилла, клиент должен передать их в качестве параметров к команде dispel:
[SRC="java"]// RequestBypassToServer.java
// Usage: _dispel:<intkill_id>,<intkill_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);
}
[/SRC]
Пример вызова: _dispel:313,8
Рекомендую вместо байпаса сделать команду с аналогичными параметрами. Кроме того, тогда игроки смогут писать макросы на снятие баффа
Клиентская часть
К сожалению, в клиенте Interlude нет простого способа отследить нажатие Alt+Click, поэтому используем для снятия баффа обычный двойной клик левой кнопки мыши. Обрабатывать событие будет окно AbnormalStatusWnd, которое отображает иконки баффов и дебаффов
Алгоритм:
- Слушаем в окне AbnormalStatusWnd событие двойного клика (OnLButtonDblClick)
- Определяем бафф, по которому кликнули (через StatusIcon.GetItem)
- Определяем ID и уровень скилла этого баффа (через GetSkillInfo)
- Отправляем запрос на сервер (через RequestBypassToServer или ExecuteCommand)
- Вызываем на сервере dispelSkillEffect с полученными ID и уровнем скилла
Поскольку мы знаем, что размер ячейки баффа 24 пикселя, а размер ручки, за которое перетаскивается окно — 12 пикселей, то вычислить ряд и ячейку легко: достаточно определить координаты окна с баффами, вычесть все значения и поделить остаток на размер ячейки. При приведении к int значения будут правильно округлены
Сперва добавим константу NSTATUSICON_SIZE, описывающую размер ячейки баффа в начало скрипта. Остальные константы разработчки уже описали:
[SRC="javascript"]// AbnormalStatusWnd.uc
class AbnormalStatusWnd extends UIScript;
const NSTATUSICON_FRAMESIZE = 12;
const NSTATUSICON_MAXCOL = 12;
const NSTATUSICON_SIZE = 24;
// ...
[/SRC]
Теперь в любое место (например, сразу после функции OnEvent), добавим обработку события двойного клика:
[SRC="javascript"]// 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:<intkill_id>,<intkill_level>
// Example: _dispel:313,8
RequestBypassToServer("_dispel:" $ string(skillInfo.SkillID) $ "," $ string(skillInfo.SkillLevel));
}
}
}
[/SRC]
Компилируем interface.u, копируем в клиент, запускаем игру
Готово!