Класс биндера позволяет "присоединиться" к какому-либо объекту, для того, чтобы можно было реагировать на выход в online/offline данного объекта, сохранения/загрузки данных, использования коллбэков и реализации апдейта
Для создания своего класса биндера, нужно унаследоваться от стандартного биндера и зарегистрировать свой класс в секции предмета.
Методы класса биндера:
Код
class object_binder {
game_object* object;
-- конструктор
object_binder(game_object*);
-- вызывается при сохранении
void save(net_packet*);
-- апдейт
void update(number);
-- вызывается при создании объекта
void reload(string);
void net_export(net_packet*);
bool net_save_relevant();
-- вызывается при загрузке
void load(reader*);
-- вызывается при переходе в оффлайн
void net_destroy();
void reinit();
void net_Relcase(game_object*);
bool net_spawn(cse_alife_object*);
void net_import(net_packet*);
};
Ну а теперь - пример использования. Постэффект от хита по ГГ
Сам мод прост до безобразия - если ГГ ранили, то экран краснеет, или же, если ранили сильно, то вращается камера и, возможно, роняется оружие.
Фишка довольно простая и многим известная, но в этот раз мы её немного "освежим" нестандартным исполнением. Для её выполнения нам понадобится всего 3 файла - один конфиг и 2 скрипта.
Для начала опишу просто выполнение мода, а потом - описание его работы.
Начнём с конфига. Нам необходима секция описания ПДА, которая лежит в gamedata/config/misc/items.ltx, называется [device_pda]. Когда найдём её, в самый конец описания ПДА, перед [device_torch] вставим такую строчку
Код
script_binding = bind_pda.init
Далее, нам необходимо в gamedata/scripts создать текстовый документ с именем "bind_pda", и в него помещаем такой текст:
Код
function init(obj)
local new_binder = generic_pda_binder(obj)
obj:bind_object(new_binder)
end
class "generic_pda_binder" (object_binder)
-- конструктор. Здесь выполняются действия при создании объекта
function generic_pda_binder:__init(obj) super(obj)
end
-- вызывается при создании объекта. Аргумент - имя секции, на основе которой создан объект.
function generic_pda_binder:reload(section)
object_binder.reload(self, section)
end
-- метод, который вызывается в самом начале. Аргументов нет. Обычно, именно в этом методе устанавливаются --- колбеки
function generic_pda_binder:reinit()
object_binder.reinit(self)
end
-- вызывается постоянно
function generic_pda_binder:update(delta)
object_binder.update(self, delta)
if db.actor ~= nil then -- если актор ещё жив (а иначе будет вылет, у трупа проверять здоровье)
effect_blood.update()
end
end
-- вызывается при переходе объекта в онлайн
function generic_pda_binder:net_spawn(data)
if not object_binder.net_spawn(self, data) then
return false
end
return true
end
--происходит при переходе артефакта в offline
function generic_pda_binder:net_destroy()
end
-- вызывается при сохранении
function generic_pda_binder:save(packet)
end
-- вызывается при загрузке
function generic_pda_binder:load(reader)
end
Когда вставили код, меняем расширение на .script у файла bind_pda.txt
Ну и наконец, сам постэффект, его скрипт. Точно также создаём тектовый файл effect_blood.txt в gamedata/scripts
и вставляем в него такой текст
Код
lite_treshold = 0.05 -- насколько должно уменьшиться здоровье с предыдущего обновления чтоб экран окрасился в красный
crit_treshold = 0.15 -- насколько должно уменьшиться здоровье с предыдущего обновления чтоб ГГ начало шатать, и начался болевой шок
drop_item_on_crit_prob = 0.20 -- вероятность того что ГГ выронит оружие
effector_power_coeff = 0.7
prev_health = -1
chk_h_t = 0
function update()
if prev_health > (db.actor.health + lite_treshold) then -- если здоровье сейчас больше, чем здоровье чуть ---- --раньше + lite_treshold
level.add_pp_effector("fire_hit.ppe", 2011, false) -- создаём эффектор (т.е. ощущения)
local effector_power = (prev_health - db.actor.health)*100*effector_power_coeff -- задаём его силу
level.set_pp_effector_factor(2011, effector_power) -- устанавливаем эффектор
-- тоже самое, но для большего ранения
if prev_health > db.actor.health + crit_treshold then
level.add_cam_effector("camera_effects\\fusker.anm", 999, false, "") -- ещё эффектор, но другого -- типа, "камера" - вращение камеры
local snd_obj = xr_sound.get_safe_sound_object([[actor\pain_3]]) -- создадим ещё и звук
snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0) -- проигрываем его
if math.random() < drop_item_on_crit_prob then -- вычисляем вероятность выронить оружие
local active_item = db.actor:active_item() -- получаем предмет в руках
if active_item and active_item:section() ~= "bolt" and active_item:section()~= "wpn_knife" then
db.actor:drop_item(active_item) -- выбрасываем предмет, если это не нож или болт
end
end
db.actor.psy_health = -0.45
end
end
prev_health = db.actor.health -- запоминаем текущее состояние здоровья
end
Меняем расширение файла на .script
Принцип действия мода прост - постоянно проверять, не упало ли здоровье ГГ, и если упало - то включать эффекторы. Но тогда нужно придумать, как постоянно проверять здоровье ГГ (а вообще говоря, проверять так можно что угодно, по 30-60 раз в секунду). Простой и общеизвестный подход - добавить свою проверку в bind_stalker.script, после строки
Код
function actor_binder:update(delta)
Наверное, вы заметили, что есть что-то общее в названии метода - actor_binder:update и generic_pda_binder:update. Да-да, смысл у них одинаков, постоянно что-то проверять, или выполнять у данного объекта - актора (т.е.ГГ) или ПДА. Однако, если все действия вешать на actor_binder, то это ухудшает совмещаемость модов и производительность (старайтесь как можно меньше вешать действий на апдейт актора, особенно "тяжёлых", вроде перебора инвентаря или с записью/чтением из pstor - сильно падает производительность, игра начинает лагать).
Так вот, с биндером разобрались. Но, изначально в игре на ПДА нет никакого биндера, поэтому его надо "зарегистрировать" - вот для этого мы в секции ПДА написали
Код
script_binding = bind_pda.init
bind_pda - наш скрипт, init - функция.
Дополнение 1Ну что ж, функционал мода выполнен, однако не разобранными оказались другие возможности биндера.
Пусть нам теперь необходимо запоминать секцию объекта, который нанёс урон. Для этого в конструкторе создадим переменную, которая хранит эту секцию
Код
function generic_pda_binder:__init(obj) super(obj)
-- создали поле (переменную), которое хранит секцию последнего нанесшего хит объекта
self.who_last_hit_section = "nobody"
end
Реализуем методы для сохранения этой секции, если объект вышел в оффлайн
Код
function generic_pda_binder:save(packet)
object_binder.save(self, packet)
-- сохраним наше поле того, кто последний нанёс хит в нет-пакет
packet:w_stringZ(self.who_last_hit_section)
end
function generic_pda_binder:load(reader)
object_binder.load(self, reader)
-- прочитаем секцию из нет-пакета, кто последний нанёс хит
self.who_last_hit_section = reader:r_stringZ()
end
Примечание - в этих методах используются методы класса net_packet
Ну и теперь нужно реализовать саму реакцию на урон по объекту
Код
function generic_pda_binder:reinit()
object_binder.reinit(self)
-- устанавливаем коллбэк, hit - тип коллбэка, hit_callback - название метода, который будет выполняться как реакция на каллбэк
self.object:set_callback(callback.hit, self.hit_callback, self)
end
function my_binder:hit_callback(obj, who)
--здесь выполняю действия, которые надо сделать при хиту по объекту
self.who_last_hit_section = who:section()
db.actor:give_game_news("\\n%c[255,0,255,0] ".."hit for "..obj:section().." by "..who:section(), "ui\\ui_iconsTotal", Frect():set(50,40,83,47), 0, 20*1000)
end
Ну что ж, функционал выполнили, пробуем в моде. И тут получается, что секция предмета, нанёсшего хит, не сохраняется, потому, что сам каллбэк не срабатывает. А случилось это потому, что нет подробного описания, для какого предмета какой коллбэк срабатывает.
Комментарий. Перечисление возможных коллбэков
Код
C++ class callback {
const action_animation = 20;
const action_movement = 18;
const action_object = 23;
const action_particle = 22;
const action_sound = 21;
const action_watch = 19;
const actor_sleep = 24;
const article_info = 12;
const death = 8;
const helicopter_on_hit = 26;
const helicopter_on_point = 25;
const hit = 16;
const inventory_info = 11;
const inventory_pda = 10;
const level_border_enter = 7;
const level_border_exit = 6;
const map_location_added = 14;
const on_item_drop = 28;
const on_item_take = 27;
const patrol_path_in_point = 9;
const script_animation = 29;
const sound = 17;
const take_item_from_box = 33;
const task_state = 13;
const trade_perform_operation = 3;
const trade_sell_buy_item = 2;
const trade_start = 0;
const trade_stop = 1;
const trader_global_anim_request = 30;
const trader_head_anim_request = 31;
const trader_sound_end = 32;
const use_object = 15;
const zone_enter = 4;
const zone_exit = 5;
};
Для того, чтобы всё-таки увидеть воочию работу коллбэка, можно перевесить биндер, к примеру, на антирад, тип коллбэка установить в use_object и использовать антирад. Тогда коллбэк срабатывает.
Ну и под конец, почему был выбран ПДА - потому, что он всегда в инвентаре, поэтому всегда онлайн, а значит и апдейт будет выполняться всегда.
Дополнение 2Т.к. в комментариях есть ещё обсуждение того, как можно подключать/отключать функции от биндера, разберём немного и этот вопрос.
Чтобы не реализовывать эту возможность самим, воспользуемся уже готовым и удобным решением от xStream.
Называется скрипт xr_s.script, скачать можно здесь
Яндекс диск.
Он позволяет подключать/отключать функции от биндера в любой момент. Профит - на апдейте не висит постоянно проверка/выполнение действий, которые нужные лишь короткий промежуток времени. А это улучшает производительность и возможность адаптации (особенно, при использовании своего биндера).
Для включения своей функции в апдейт биндера, используется функция
Код
xr_s.register_callback(name,func,userobj).
Параметры: name - название (например, "update")
func - название функции
userobj - объект для передачи в функцию параметров.
Для выключения функции из апдейта, используется
Код
xr_s.unregister_callback(name,func)
name - название, что дали при регистрации
func - название функции, что передали при регистрации
Ну и для того, чтобы всё это работало, нужно вставить в метод bind_pda.update(delta)
вызов
Код
xr_s.on_actor_update(delta)
В нашем примере, мы в апдейте нашего биндера изменим строки
Код
if db.actor ~= nil then
effect_blood.update()
end
на
Код
xr_s.on_actor_update(delta)
Для того, чтобы включать апдейт, нам нужно вызвать функцию
Код
xr_s.register_callback("update",effect_blood.update)
Для выключения функции
Код
xr_s.unregister_callback("update",effect_blood.update)
Немного о том, для чего нужен параметр userobj.
Служит он для "опосредственной" передачи методов в функцию (ведь она у нас передаётся без параметров).
Поэтому, если мы используем функцию с параметрами, то выглядеть она будет так
Код
function some_function(object)
object.param1 -- получение первого параметра
object.param2 -- получен второго параметра
object.another_param -- получение 3его параметра
end
а регистрация её будет выполняться вот так:
Код
xr_s.register_callback("update", название_скрипта.some_function, {param1 = "op-op", param2 = time_global(), another_param = 110})
Чтобы лучше разобраться с действиями включения/выключения функций в апдейт, можно скачать готовый мод, примеру - Медицина от ColR_iT.
Также, можно почитать подробный разбор actor_binder'а и скрипта xr_s.script
ЗдесьУух, большой получился урок, а начиналось всё очень просто. Вообще говоря, для изучения скриптов хорошо бы пытаться разобраться самому в чужих скриптах, постоянно при этом изучая сам синтаксис Lua. Ещё рекомендую сайт АМК, там много шпаргалок. Собираюсь выкладывать такие небольшие уроки с теорией, но как часто - пока не знаю.