Jump to content
Malandrinus

Справочник по функциям и классам

Recommended Posts

Monnoroch    6

Помнишь мою идею откомментировать луа_хелп?

Считаю,мастхэв.

 

Например первый вопрос:

function release(alife_simulator*, cse_abstract*, boolean);

Никогда не понимал,зачем тут boolean?

И,как я понимаю,оно ведь не обязательное?

 

И написать в луа_хелп коммент

function release(alife_simulator*, cse_abstract*, boolean); -- Удаление обьекта(№переменной: ее значение)

 

malandrinus, не пробовал false ставить?Есть мнение,что может тогда обьект не удалится...

Edited by Monnoroch

Share this post


Link to post
Share on other sites
Malandrinus    600

Ну тогда уж так:

alife_simulator - класс для управления серверными объектами. В игре существует всего один экземпляр такого класса, и получить его можно с помощью вызова глобальной функции alife().

level_name(level_id) -- возвращает имя уровня по его id

create_ammo(<имя секции патронов>, position, lvid, gvid, id, amount) - создание пачек с патронами

create(<имя секции объекта>, position, lvid, gvid, id) -- создание объектов в инвентаре

create(<имя секции объекта>, position, lvid, gvid) -- создание объектов на уровне

во всех функция создания объектов

position - объект класса vector. Не может быть nil, иначе вылет

lvid - level vertex id

gvid - game vertex id

id - это куда спавним. Если в инвентарь, то указываем id непися или ноль (для актора)

release(sobj, boolean) -- удаляет объект sobj. Зачем второй булевский аргумент - не известно. Во всех примерах он либо отсутствует, либо установлен в true

object(id) -- возвращает серверный объект по его id. Если не нашёл nil

story_object(sid) -- возвращает серверный объект по его story id. Если не нашёл nil

actor() -- возвращает серверный объект актора

set_switch_online(id, boolean) -- переводит в онлайн объект с идентификатором id

set_switch_offline(id, boolean) -- то же , но в онлайн

 

switch_distance() -- радиус переключения в оффлайн?

switch_distance(number) -- установка этого радиуса?

level_id() -- текущий номер уровня ?

object(string) -- получение объекта оп его имени. Не помню точно, что за имя.

object(number, boolean) - то же, что и object(number)? на что влияет второй аргумент?

valid_object_id(number) -- проверка наличия объекта по id?

alife():create( <индекс объекта в all.spawn> ) --

spawn_id(number) -- индекс объекта в all.spawn для объекта с заданным id ?

 

dont_has_info(const number&, string);

add_out_restriction(cse_alife_monster_abstract*, number);

set_interactive(number, boolean);

add_in_restriction(cse_alife_monster_abstract*, number);

function remove_in_restriction(cse_alife_monster_abstract*, number);

remove_out_restriction(cse_alife_monster_abstract*, number);

kill_entity(cse_alife_monster_abstract*, const number&, cse_alife_schedulable*);

kill_entity(alife_simulator*, cse_alife_monster_abstract*, const number&);

kill_entity(alife_simulator*, cse_alife_monster_abstract*);

has_info(const number&, string);

remove_all_restrictions(number, const enum RestrictionSpace::ERestrictorTypes&);

 

 

 

 

Пример использования:

local sim = alife() -- получаем сам объект класса alife_simulator
local sactor = sim:actor() -- получаем серверный объект для актора

 

В придачу про класс vector.

vector - вспомогательный класс, содержащий три координаты, и позволяющий выполнять с ними различные манипуляции. Объекты класса vector являются аргументами многих функций и возвращаются многими функциями. См. например выше про метод create класса alife_simulator.

Отдельный объект класса вектор создаётся вызовом глобальной функции vector(). При создании имеет координаты [0,0,0].

v:set_length(number) - оставить направление, изменить длину

v:normalize(); - сделать длину единичной

 

v:magnitude() -- длина вектора v

v1:distance_to(v2) -- расстояние между v1 и v2

v1:distance_to_sqr(v2) -- квадрат расстояния между v1 и v2

v1:crossproduct(v2) -- векторное произведение. Возвращает вектор

v1:dotproduct(v2) -- скалярное произведение. Возвращает число

v:sub(number); -- вычесть из каждого компонента v число number

v1:sub(v2); -- вычесть почленно вектор v2 из v1 и поместить в v1

v1:sub(v2, v3) -- вычесть из v2 - v3 и поместить в v1

аналогично:

add - почленное сложение

mul - почленное умножение

div - почленное деление

max/min - почленный максимум/минимум

v1:clamp(v2) -- почленное обрезание первого вторым

average -- почленное среднее

invert -- почленная инверсия знака

 

v1:abs(v2); -- копирует почленно положительные компоненты в v1 из v2

lerp(v1, v2, number) -- линейная интерполяция между двумя векторами

-- если number больше 1, то выходит за них - экстраполяция

 

v1:reflect(v2, v3) - должно быть отражение одного вектора от плоскости, определяемой другим. Не проверял

v1:slide(v2, v3); - вероятно, проекция одного вектора на плоскость, определяемую другим. Не уверен

v1:distance_to_xz(v2) - учитывая, что плоскость XZ - это плоскость параллельная земле, то возможно это длина проекции на землю пути от точки до точки. Не проверял

 

v:getP() - зенитный угол в радианах (угол наклона над плоскостью XZ )

v:getH() - азимутальный угол в радианах (угол в плоскости XZ между проекцией вектора на эту плоскость и осью Z)

setHP - соответственно для установки этих значений

v1:align() - выравнивает вектор вдоль оси x или z и нормирует его. Значение по y игнорируется. В итоге всегда возвращается одно из четырёх значений ([1,0,0], [-1,0,0], [0,0,1], [0,0,-1])

v4:mad(v1,v2,v3) -- бешеная операция =) умножить почленно второе на третье и сложить с первым, т.е. v4 = v1 + v2*v3 (название операции mad == mul + add)

v1:inertion(v2, number) -- v1 = v2 + (v1-v2)*number (тоже почленно)

v1:similar(v2, number) -- сравнение векторов на равенство с погрешностью number. Выдаёт 1 или 0

 

Пример использования:

local v1 = vector()
v1:set(1,2,3)
local v2 = vector():set(4,5,6)
local dist = v1:distance_to(v2)

 

 

З.Ы.:

не пробовал false ставить?Есть мнение,что может тогда обьект не удалится...
Не пробовал. Да и звучит не логично. Какой смысл в таком вызове?
Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites

НУ не знаю надо это или нет,но напишу....

 

Lua(в Сталкере,дальше буду просто писать Lua ) можно использовать не только для игрового процесса...

Доступны основные(а может и все)

команды(функции)

к примеру Оператор ввода/вывода..работа с фаилами и т.д.(Возможно,только в ЧН....)

вот простой пример:

function to_my_log(text)

io.open("gamedata\\solinx_log.txt","w"):write( text, "\n" )

io.open("gamedata\\solinx_log.txt","w"):close()

end

Рассмотрим подробнее

io.open(filename) - "открывает" для каких либо целей filename

:write(text,"\n") - оператор вывода,выводит(читай вписывает) в фаил открытый с помощью io.open , строку text...после переходит на следующую строку(есть ещё:"\t","\w")

:read(text) - оператор ввода,вводит (читай "читает") из фаила открытого с помощью io.open,строку текст

:close() - закрывает фаил открытый с помощью io.open

 

статьи по Lua(основам) можно найти тут http://www.lua.ru/doc/

 

Также есть функции "преобразования" ,как в любом языке(скриптовом,программирования)

Преобразование числового значения(любого) в строку:

tostring(c)

где с - это число

Преобразование строки в число:

tonumber(string)

где string - это строка (вдруг кто не в курсе :))

 

Особенность Lua(по моему только в нём так,хотя кроме Lua только питон смотрел):

что после основных операторов(циклов,условного и т.д.)(if ,for,while...)

ставится end

пример:

 

if then

else

end

 

for i,c do

end

 

while "true" do

end

 

 

Ещё одной особенность LUА ,можно назвать работу с массивами(читай "таблицами") без цикла

 

пример:

local tbl={}

 

--заполнение

function objparams_to_tbl(obj)

if isstalker(obj) then

tbl.sec =obj:section()

tbl.hp=obj:health

....

end

end

 

 

--применение

function objparams_to_msg()

news_manager.send_tip(db.actor,"obj:section ="..tbl.sec.."obj:health ="..tostring(obj.hp),nil,nil,1000)

end

 

Массив можно очистить/заполнить/прочитать/преобразовать....

 

И совет:Пользуйтесь SCiTE или любой другой редактор с выделением синтаксиса....

 

по alife_simulator

dont_has_info(const number&, string); - проверка на Неполученный инфопоршень(db.actor:dont_has_info("infoportion") или alife():actor():dont_has_info("infoportion")).
has_info - противоположная

 

Работа с фаилами:

Через системный фаил(systems.ltx)

 

system_ini():r_string(section,key) -- получение строки из секции[section ] и ключа key

system_ini():r_bool(section,key) --получение булевого значения (true,false) из секции[section ] и ключа key

system_ini():r_s32(section,key) --получение (целого?)числа из секции[section ] и ключа key

system_ini():r_float(section,key) --получение числа(с плавающей точкой) числа из секции[section ] и ключа key

 

Через другой фаил(который не прописан в системе)

 

local ini =ini_file("") -- путь до фаила (обьявляем переменную которая хранит имя и путь).Дальше также как и с системным фаилом

 

ini:r_string(section,key) -- получение строки из секции[section ] и ключа key

ini:r_bool(section,key) --получение булевого значения (true,false) из секции[section ] и ключа key

ini:r_s32(section,key) --получение (целого?)числа из секции[section ] и ключа key

ini:r_float(section,key) --получение числа(с плавающей точкой) числа из секции[section ] и ключа key

 

Вспомогательные функции

 

system_ini():line_exist( section, key ) -- проверяет существует ли в секции [section ] ключ key

system_ini():section_exist(section) --проверяет существует ли секция [section ]

 

EG:

0function eg()

1local ini=ini_file("gamedata\\test.ltx")

2if not ini then ini=system_ini() end

3if ini:section_exist("test_section") then

4if ini:line_exist("test_section","test_line") then

5return true

6end

7end

8end

 

1. - присваиваем переменной фаил

2. - проверяем существует ли фаил ,если нет то работаем с системой

3. - проверяем наличие секции в фаиле(системе)

4. - проверяем наличие ключа в секции из фаила(системы)

5. - возвращаем истину(нашли ключ)

 

Проверка ,что из себя представляет переменная

 

function check_type(obj)

if obj then

if type(obj)=="string" then

return "string"

elseif type(obj)=="number" then

return "number"

elseif type(obj)=="function" then

return "function"

elseif type(obj)=="boolean" then

return "boolean"

elseif type(obj)=="userdata" then

return "userdata"

elseif type(obj)=="table" then

return "table"

end

end

end

 

 

 

 

Теперь по порядку...

Если obj это строка ,то возвращаем "string"

Если же obj это число ,то возвращаем "number"

Если же obj это функция ,то возвращаем "function"

Если же obj это булево значение ,то возвращаем "booolean"

Если же obj это юзердата(хз что это),то возвращаем "userdata"

Если же obj это таблица, то возвращаем "table"

 

Это поможет в таких случаях ,когда неизвестно какой тип данных поступает в то или иное место...

 

 

класс CUIScriptWnd (); --для работы с окном....

Где знаю или догадываюсь поставил коментарий ....

    function _construct();
    function Register(CUIWindow*);
    function Register(CUIWindow*, string);
    function Enable(boolean);        --вкл/выкл
    function SetHeight(number);  --установка высоты
    function GetFrameLineWnd(string);
    function SetHolder(CDialogHolder*);
    function GetWidth() const;  --получение ширины контрола
    function GetCheckButton(string);--Получение состояния checkBox ,на выходе true или false?
    function DetachChild(CUIWindow*);
    function SetPPMode();
    function SetFont(CGameFont*);  --установка шрифта по имени
    function IsShown();                --проверка видно ли контрол иль нет?
    function Show(boolean);            --показывать контолили нет?
    function GetHeight() const;        --возвращает высоту
    function SetWidth(number);        --устанавливает ширину
    function GetListWndEx(string);
    function IsEnabled();
    function ResetPPMode();
    function GetPropertiesBox(string);
    function GetFont();                --возвращает шрифт установленный setFont?
    function Update();
    function AddCallback(string, number, const function<void>&); --соответские контрола с действие?
    function AddCallback(string, number, const function<void>&, object);--соответские контрола с действие?
    function GetButton(string);--Получаем последнюю нажатую кнопку?
    function SetAutoDelete(boolean);
    function OnKeyboard(number, enum EUIMessages);--Колбек нажатия на клавиатуре
    function Dispatch(number, number);
    function GetListWnd(string);--получение окна ListBoxa?
    function AttachChild(CUIWindow*);
    function GetStatic(string);--получение текста метки
    function SetWndPos(number, number);--установка позиции окна(x,y)
    function GetTabControl(string);
    function GetRadioButton(string);--состояние RadioButtton на выходите true или false
    function Init(number, number, number, number);--инициализация окна...читай FormInitializatione (x,y,width,height)
    function Init(Frect*);--инициализация окна
    function GetFrameWindow(string);
    function WindowName();
    function GetDialogWnd(string);
    function GetHolder();
    function SetWndRect(Frect);--установка координат углов
    function SetWndRect(number, number, number, number);--установка координат углов
    function GetEditBox(string);--получение текста из ЭдитБокса
    function SetWindowName(string);--Устанавливаем имя форме(caption)
    function GetProgressBar(string);
    function GetMessageBox(string);
    function SetWndSize(number, number);
    function Load(string);
    function IsAutoDelete();

Edited by меченый(стрелок)
  • Like 1

Share this post


Link to post
Share on other sites
Malandrinus    600

В языке Lua нет классов. Их поддержка добавлена с помощью технологии Luabind. С одной стороны - это конечно имитация "настоящих классов", с другой - расширение выглядит настолько органичным, что практически воспринимается как часть синтаксиса языка.

 

Свой класс можно добавить так:

class "my_cool_class"

Здесь "my_cool_class" - имя моего нового класса. Записывается в кавычках, т.е. это строка.

 

Потом можно добавлять к этому классу свои методы:

 

-- это специальный метод - конструктор. Будет вызван при создании класса.
function my_cool_class:__init(num)
    self.my_param = num -- добавили в свой класс переменную
end

-- обычный метод
function my_cool_class:cool_method_of_my_class()
    get_console():execute("lua_bind_in_action_"..self.my_param)
end

-- деструктор, вызывается при сборке объекта сборщиком мусора. Аргументов не имеет (кроме скрытого self. об этом см. далее)
function my_cool_class:__finalize()
    get_console():execute("good_by_"..self.my_param)
end

 

Потом можно создавать экземпляры своего класса, вызывать их методы и пр.:

local obj1 = my_cool_class(1) -- здесь создаётся новый объект. При этом вызывается конструктор и ему передаются аргументы этого вызова (в данном случае число 1).
local obj2 = my_cool_class(4)
obj1:cool_method_of_my_class()
obj2:cool_method_of_my_class()

 

Как только объект становится ненужным (т.е. на него больше нет ссылок) он удаляется сборщиком мусора. При этом вызывается его метод __finalize

 

При чём здесь self.

self - это скрытый первый аргумент. Конструкция вида:

function class_name:fun_name(<список аргументов>)
end

на самом деле эквивалентна конструкции

function class_name.fun_name(self, <список аргументов>)
end

Обратите внимание на двоеточие в первом случае и точку во втором.

При вызове метода конструкция вида:

object_name:fun_name(<список аргументов>)

эквивалентна конструкции

object_name.fun_name(object_name, <список аргументов>)

Это означает, что в качестве self как правило передаётся сам же объект. "Как правило" - это потому, что можно в принципе передать что-то другое. Но лучше этого соглашения не нарушать.

 

Всё это здорово, но какая от этого польза? Ну сделал я свой класс, и что с ним делать?

 

В общем, классы - это неплохое средство организации своего кода. Владеющие объектно-ориентированным подходом найдут это расширение весьма удобным. В частности такие, ни к чему не привязанные классы, в скриптах сталкера используются в некоторых местах. Однако, настоящая сила этой технологии не в этом. С помощью этой технологии можно расширять классы, экспортированные в Lua со стороны движка игры. Об этом пойдет речь в следующей части.

 

Итак, кроме создания собственных классов технология Luabind позволяет создавать класы на основе уже существующих. Это реализовано в виде своеобразного наследования. Делается это с помощью следующей конструкции:

 

class "my_derived_class" (base_class)

 

Здесь:

"my_derived_class" как и раньше - это имя нового класса.

base_class - это уже существующий класс, который будет базовым для вновь созданного. Имя выбрано произвольно, но подразумевается, что такой класс экспортирован и предназначен для наследования.

Далеко не любой класс из экспортированных со стороны хост-приложения можно использовать в качестве базового. На стороне хост-приложения должен быть создан специальный класс-обёртка. Если его нет, то ничего хорошего не выйдет. Скорее всего при попытке использовать унаследованный класс в Lua будут малопонятные проблемы и вылеты, так что надо знать совершенно точно, можно ли от конкретного класса наследовать.

Вопрос, какой глубины может быть наследование, т.е. можно ли наследовать от производного класса, мне пока до конца не ясен. С другой стороны, в этом практически нет необходимости.

 

Итак после выполнения указанной конструкции появляется класс my_derived_class, который является копией класса base_class. У него есть в точности те же методы, что и у его базового класса. Пока от этого мало толку. Но теперь можно переопределить методы нашего класса, изменив таким образом его поведение. Делается это так же, как и ранее, но с некоторыми дополнениями.

 

-- конструктор
function my_derived_class:__init(num) super(num)
end
-- обычный метод
function my_cool_class:some_method(<список аргументов>)
    base_class.some_method(self, <список аргументов>)
end
-- деструктор
function my_cool_class:__finalize()
end

 

На что стОит обратить внимание.

Во-первых, конструкция super(num). Это вызов конструктора базового класса.

Во-вторых, для вызова метода базового класса из метода унаследованного используется другая конструкция base_class.some_method(self, ). Здесь надо использовать альтернативную форму вызова метода (с точкой и явным указанием первого аргумента self).

Наконец, метод __finalize() не требует вызова этого же метода для базового класса, поскольку тот будет вызван автоматически при удалении объекта сборщиком мусора.

 

Собственно и всё. Теперь можно пользоваться созданным классом: создавать объекты этого класса, вызывать их методы и пр. Здесь уже всё зависит от конкретных целей и требует знания конкретных классов.

 

  • Like 1
  • Полезно 1

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600
выносите продвижения в шапку плиз
Делать мегашапку пока рано, ещё даже одной страницы темы нет =)

а не мог бы ты дополнительно рассмотреть пару примеров собственно из сталкера?

С такими же детальными пояснениями.

На очереди object_binder. Там и рассмотрю.

 

А пока вот:

time_global() -- реальное время (в миллисекундах) с начала запуска программы

game.time() -- игровое время (в игровых миллисекундах) с начала игры (т.е. с начала прохождения игры)

level.get_time_days() -- день месяца по игровому времени

level.get_time_hours() -- час текущего игрового дня

level.get_time_minutes() -- минута текущего игрового часа

 

level.get_time_factor() -- возвращает отношение скорости течения игрового времени к скорости реального (game_time_speed / real_time_speed)

level.set_time_factor(number) -- устанавливает это отношение

 

game.get_game_time() -- возвращает игровое время в виде объекта класса CTime. Класс CTime я опишу подробно как-нибудь потом, а пока опишу только пару его методов.

конструктор. Вызывается через пространство имён game

game.CTime()

Дефолтовые значения

year, month, day = 1

hour, min, sek, ms = 0

CTime.set(year, month, day, hour, min, sek, ms) -- устанавливает все данные о времени

метод CTime.get описан так:

function get(number&, number&, number&, number&, number&, number&, number&);

что наводит на мысль о семи аргументах. однако с точки зрения Lua этот метод не принимает аргументов, зато возвращает семь значений. Пример использования обоих методов:

 

local t = game.CTime() -- создания объекта с дефолтовыми значнеиями
t:set(2009, 7, 11, 8, 11, 22, 333) -- установили все значения
local y,m,d,h,min,sec,ms = t:get() -- получили все значения

 

Класс CTime разобран в этом посте

 

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
IQDDD    4
C++ class [класс] {
     const [описание1] = [константа1];
     const [описание2] = [константа2];
     ...
     [константы заменяют описания при вызове методов классов];
     
     property [свойство1];
     property [свойство2];
     ...
     [свойства (property) - это характеристика какого-либо класса, которая может подвергаться мат. операциям, слияниям, присвояниям и пр.
     читаются через точку: obj.health];

     [глобальная(-ые) функция(-ии), которой вызывается класс];
     function [метод1] ([аргумент 1 функции 1], [аргумент 2 функции 1]...);
     function [метод2] ([аргумент 1 функции2]...);
             ...
     [методы - это специальные функции, выполняющие операции с классами и возвращающие значения после операций над классами.
     вызываются через двоеточие: obj:add_animation(...)];
}

 

 

C++ class fcolor {
    property a; --альфа-цвет, отвечающий за прозрачность.
    property b; --blue-цвет, отвечающий за синий оттенок цвета.
    property g; --green-цвет, отвечающий за зелёный оттенок цвета.
    property r; --red-цвет, отвечающий за красный оттенок цвет.

    fcolor ();

    function set(number, number, number, number); --установка цвета (r,g,b,a)
    function set(const fcolor&); --установка цвета по уже имеющемуся классу "fcolor()"
    function set(number); -- не знаю. предполагаю, что есть несколько заранее подготовленных цветов с индексами для каждого.

};

 

Например:

color = fcolor()
color.a = 50
color.g = 255
color.r = 100
color.b = 50

или

color = fcolor()
color:set(100,255,50,50)

 

 

Но где и как это использовать?

 

Бывают случаи, при которых необходимо нанести некоторый урон неписю или мобу, при этом имитировав попадание в него. Для этого существует метод класса game_object:

function hit(hit*);

Чтобы использовать этот метод, нужно указать аргументы. В данном случае имеет объект другого класса - hit. Вот его описание:

 

C++ class hit {
    const burn = 0; --здесь идут константы типов урона, используемых в свойстве "type". Все мы прекрасно знаем, что означает каждый.
    const chemical_burn = 6;
    const dummy = 11;
    const explosion = 7;
    const fire_wound = 8;
    const radiation = 4;
    const shock = 1;
    const strike = 2;
    const telepatic = 5;
    const wound = 3;

    property direction; --дирекция урона
    property draftsman; --обозначает персонажа, нанёсшего урон.
    property impulse; --импульс урона
    property power; --сила урона
    property type; -- тип урона (см. константы)

    hit ();
    hit (const hit*);

    function bone(string); --метод с указанием кости урона

};

 

Пример использования:

hit = hit() -- создание объекта класса hit
hit.direction = vector():set(1,0,0) --дирекция по оси x
hit.draftsman = db.actor --нанёс урон актор
hit.impulse = 600 --импульс
hit.power = 1.45 --хит
hit.type = 2 --тип урона - выстрел
hit:bone("bip01_head") -- удар приходится на голову
obj:hit(hit)

-- наносим урон

 

 

Edited by IQDDD

Share this post


Link to post
Share on other sites
Malandrinus    600

Добил object_binder. Получилось многовато, но короче никак. Кроме того, пришлось разбить на две части. Для начала - некоторое введение в ситуацию со скриптовой моделью вообще. В основном трёп всякий =)


Факт первый. Сингловая игра построена на основе мультиплеерного движка. Мультиплеер подразумевает наличие сервера, к которому подключаются клиенты. Все данные (т.е. все объекты игры) существую в первую очередь на сервере, а у клиентов имеются копии этих данных. Естественным образом имеется разграничение обязанностей между клиентом и сервером: сервер отвечает в целом за создание, удаление, хранение объектов, а клиент - за всевозможные _игровые_ манипуляции с ними: отображение, проигрывание анимации и звуков, движение, убиение (но не удаление) и т.п.


Следующий факт. Существует такое понятие онлайн/оффлайн. Что это такое? Дело в том, что загрузить в память все объекты игрового мира сразу невозможно. Для решения этой проблемы конкретно здесь сделаны две вещи:
1. Игра разбита на уровни, и в каждый момент времени загружен только один уровень и, соответственно, загружены только объекты уровня (и давайте не будем обсуждать, как это делают в современных движках). Думаю, с этой идеей всё более или менее ясно.
2. Загружать весь уровень целиком также оказалось накладно. Поэтому в память загружаются только те объекты, которые находятся на некотором разумном расстоянии от игрока. Загрузку объекта называют переходом в онлайн, а выгрузку - переходом в оффлайн. Если конкретнее, то расстояние перехода в онлайн управляется двумя параметрами switch_distance и switch_factor секции alife из файла config\alife.ltx. Их обычные значения:
переход в оффлайн произойдёт на расстоянии switch_distance * (1 + switch_factor)
Теперь вспомним про нашу архитектуру. Нетрудно догадаться, что описанный процесс хорошо ложится на эту архитектуру. А именно: загрузка объекта (переход в онлайн) - это создание его клиентской части, а выгрузка (переход в оффлайн) - это удаление клиентского объекта. Серверный при этом остаётся постоянно. Отсюда и второе название серверного и клиентского объектов - оффлайновый и онлайновый соответственно.
Пошли дальше. Скриптовая модель (т.е. вся совокупность доступных классов) отражает эту архитектуру. Есть классы для серверных объектов, есть классы для клиентских. Есть вспомогательные классы общего назначения. Есть и ещё другие, но не будем забегать вперёд.
Пример серверных классов и функций:
- Часть функций объявленных в пространстве имён game и в глобальном пространстве имён (например alife()).
- Крупная иерархия классов, унаследованных от cse_abstract (который сам по себе ещё унаследован от нескольких классов, но в основном все значимые для скриптования серверные классы унаследованы от него)
Пример клиентских классов и функций:
- Все или почти все функции из пространства имён level
- Иерархия классов, порождённых от CGameObject
- Класс game_object (надеюсь, мы ещё разберём этого уродца)
О других пока не будем.
Важно понять, что в сингловой игре это разделение объектов на серверные и клиентские никуда не делось. Есть всё тот же сервер и есть клиент. Клиент разумеется всего один, и сервер - это разумеется этот-же компьютер. Объекты, которые раньше существовали по разные стороны сети, теперь находятся в одном адресном пространстве. Но их взаимодействие осталось прежним: при создании клиентский объект копирует себе нужные ему данные с серверной стороны, при удалении - сохраняет. В процессе жизни клиентского объекта он синхронизируется с серверным, в основном записывая в него свои данные. Используются ли при этом сетевые технологии, или путь передачи данных несколько оптимизирован для сингла - не известно.

Ещё немного лирики.
В голом мультиплее не так уж и много можно делать: бегать за артефактами и палить друг в друга. Совершенно очевидно, что иначе и быть не могло. Синхронизация всех копий клиентских объектов через сервер явно требует приличных затрат. Синхронизировать в совокупности около 30 игроков ещё возможно, но если наполнить уровень хоть вполовину так, как он наполнен в сингле, то явно ничего уже не выйдет.
Глядя на это или по каким иным причинам искусственный интеллект игры исходно создавался в расчёте исключительно на сингл. Вполне может быть, что ваяли его в спешке и просто не продумали архитектурно. Поэтому пресловутый A-Life вышел и не серверным и не клиентским. Он как бы размазан между этими частями. Так что совместное сафари за кабанчиками в мультиплее нам не светит.

К чему я всё это? К тому, что часть классов довольно сложно отнести к серверным или клиентским.

Наконец, существует также довольно обширная иерархия оконных классов для создания пользовательского интерфейса: окна, кнопочки, списки и т.п. Это чистые клиентские классы в том смысле, что в контексте сервера они вообще лишены смысла.

Несколько подробнее о клиентских объектах.
Для непосредственного управления клиентскими объектами существует два типа классов:
- классы, унаследованные от CGameObject. С ними есть одна проблема, большинство этих классов обладают весьма бедным интерфейсом и вообще говоря недоступны для программистов. Исключением являются три класса CHelicopter, CCar и hanging_lamp. До всех остальных попросту никак не добраться.
- Класс game_object, который используется для управления всеми онлайновыми игровыми объектами. Убогий уродец, страшный сон архитектора и головная боль скриптёров. Этот класс объединяет в себе все интерфейсы всех объектов, для управления которыми предназначен. В этом чуде около 300 (три сотни) методов! Вызов метода, не соответствующего типу объекта, может быть просто проигнорирован, а скорее всего приведёт к вылету, причём чаще всего без лога. И при этом никак и нигде не документировано, какие же методы относятся к каким типам объектов. Создать этого монстра - это был несомненно дьявольский план, который пришёл в головы творцов игры вероятно по большой укурке.
Что можно делать с game_object? В основном он позволяет получить разную информацию об онлайновом объекте, изменять некоторые его свойства, а также выполнять над объектом разные действия. Например, можно пнуть объект, проиграть анимацию, вылечить, убить (но не воскресить) и т.п.
Это всё хорошо, но помимо изменения свойств довольно часто надо решить следующую задачу: надо выполнить некое действие, когда с объектом происходит что-то конкретное, например объект переходит в онлайн/оффлайн, получил удар/повреждение, взял/выбросил предмет из инвентаря, наконец помер и т.п..

Как раз для этого у game_object есть методы set_callback, set_patrol_extrapolate_callback и set_enemy_callback, которые позволяют решить эту задачу. Но во-первых, некоторые события так не отследить, например переход в онлайн/оффлайн, а во-вторых, независимое использование этих методов не очень-то удобно. Не буду объяснять почему. Знатоки ООП поймут это сразу, а если не верите, попробуйте наладить мало-мальски структурированную обработку событий только этим методом и всё поймёте сами.
Посему эти методы как правило используются в совокупности с классом object_binder, о чём и пойдёт речь в следующей статье. См. следующую часть.

[alife]
    switch_distance     = 150
    switch_factor       = 0.1
переход в онлайн произойдёт на расстоянии switch_distance * (1 - switch_factor)


Данная статья посвящёна классу object_binder и, кроме того, является в каком-то смысле иллюстрацией к одной из предыдущих статей "Наследование от экспортированных классов".

Один из важнейших классов в скриптовой модели сталкера - это object_binder. Он используется для того, чтобы присоединять к клиентским объектам собственные обработчики событий.
Для того, чтобы использовать object_binder надо:
1. Создать на его основе свой класс. Теорбазис смотри в статье "Наследование от экспортированных классов".
2. В нужном месте с помощью функций set_callback указать методы класса в качестве функций-обработчиков событий.
3. Указать этот класс в свойстве script_binding в секции объекта в файле *.ltx
После создания объекта на основе этой секции будет автоматически создан объект, написанного Вами класса, а его методы начнут вызываться по указанным событиям. Получается, что наш объект как-бы присоединился к клиентскому. Отсюда и его название "биндер" от слова bind (англ. присоединять, привязывать)

Теперь подробности. Класс object_binder - это экспортированный класс CScriptBinderObject.


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*);
};


Прежде, чем разобраться с коллбеками, разберём методы класса. В каком-то смысле - они тоже обработчики событий, поскольку вызываются в определённые моменты жизни объекта.
object_binder(game_object*) - конструктор. Хоть и показан на манер C++ как метод, но, как и для многих других классов, вызывается как отдельная глобальная функция. Аргумент - клиентский объект, к которому будем биндиться.

В каком-либо модуле, обычно в том-же, где и класс биндера, должна быть функция следующего вида:

function init(obj)
    local new_binder = my_binder(obj) -- создаём объект биндера
    obj:bind_object(new_binder) -- присобачиваем его к объекту
end

здесь my_binder - это новый класс биндера.
Теперь эту функцию надо указать в секции объекта. Допустим, модуль называется my_cool_binder.script. В этом случае в секции объекта будет такая запись:
script_binding = my_cool_binder.init
При создании объекта на основе этой секции будет вызвана эта функция, которая с помощью метода bind_object класа game_object присоединит наш биндер к свежесозданному объекту.


object - свойство на чтение. Указывает на объект типа game_object, к которому присоединён биндер. Обращаться к свойству надо так: self.object
net_spawn(cse_alife_object*) - вызывается при переходе объекта в онлайн. Аргумент - серверный объект для того клиентского, к которому присоединились. Метод должен вернуть логическое значение. true, если успешно, false, если что-либо не срослось, и в этом случае объект не будет создан, а в логе появится запись: Failed to spawn entity '<имя секции>'
net_destroy() - аналогично вызывается при уходе в оффлайн, т.е. при удалении данного объекта.
update(delta) - очень важный метод. Для большинства объектов этот метод вызывается постоянно, причём период его срабатывания зависит от расстояния до актора. Минимальный период составлял около 40 мс при нахождении предмета в инвентаре ГГ (если он там конечно может находиться). При удалении объекта от ГГ период растёт линейно вплоть до расстояния в 200 метров. На таком расстоянии период составляет одну секунду. После этого расстояния остаётся равным примерно одной секунде. Аргумент этого метода - целое число, которое в точности равно числу миллисекунд, прошедших с прошлого вызова. Правда в точности оно равно только если меньше 1000. Если выше, то оно просто равно 1000, при том, что реальное время может несколько отличаться (может быть больше на пару десятков миллисекунд). Вероятно, это позволяет точно просчитывать физику движения (в том случае, если я решил управлять ею из скрипта). Судя по комментариям в скриптах для некоторых объектов метод update не вызывается.
load(reader*) - метод для загрузки состояния объекта
save(net_packet*) - метод для сохранения состояния объекта
Здесь есть некоторый непонятки. Точно известно, что вызываются эти методы не для всех объектов. Как минимум вызываются для монстров, сталкеров и ГГ. А вот для, скажем, гранаты вызываться не хочет (ни load, ни save). C парностью вызова этих методов судя по всему такая ситуация. save вызывается при сохранении игры (если вообще вызывается), а load вызывается при создании биндера (как описано выше) только в том случае, если до этого был сделан хоть один save. Ну или иными словами, если есть чего загружать. А есть чего загружать, если до этого что-то сохранили.
reload(section) - вызывается при создании объекта. Аргумент - имя секции, на основе которой создан объект.
reinit() - ещё один метод, который вызывается в самом начале. Аргументов нет. Обычно, именно в этом методе устанавливаются колбеки.
net_export(net_packet*) - назначение неизвестно. примеров использования нет
net_import(net_packet*) - назначение неизвестно. примеров использования нет
net_save_relevant() - назначение неизвестно. Если его определяют, то как возвращающий всегда true
net_Relcase(game_object*) - назначение неизвестно. Один пример использования в bind_smart_terrain.script

Кроме указанных методов есть и ещё два, которые есть в любом скриптовом классе на основе luabind, __init и __finalize
__finalize обычно не используется, но для полноты картины стоит упомянуть.
__init - вызывается при создании объекта и получает те-же аргументы, что и конструктор. Фактически - это и есть конструктор (см. теорбазис)
__finalize - вызывается при удалении объекта сборщиком мусора
Кроме того, поскольку мы пишем свой класс, то кроме указанных методов ничто не мешает добавить и свои. См. дальше про коллбеки. Также можно добавить и свои свойства. Это можно сделать так.
self.my_property = 1 -- добавили своё свойство
Надо только помнить, что если не сохранить это свойство в save, то оно пропадёт при уходе объекта в оффлайн. Что делать при этом с теми объектами, у которых save и/или load не вызывается, непонятно.


1. Объект переходит в онлайн и вызывается функция init, прописанная в секции.
2. Там создаётся объект биндера, при этом вызывается конструктор __init
3. Биндер присоединяется к объекту и работа функции init заканчивается
4. Теперь пачкой вызываются методы биндера
reload
reinit
load - если вообще вызывается
netspawn
5. затем начинает вызываться update до тех пор, пока объект находится в пределах радиуса a-life.
6. при уходе объекта в оффлайн вызывается метод net_destroy (апдейты естественно до этого прекращаются)
7. наконец, несколько секунд спустя, объект удаляется окончательно сборщиком мусора. При этом вызывается __finalize

метод save, если вообще вызывается, вызывается при сохранении игры.
Для прочих методов не удалось выяснить ни назначение, ни момент вызова.


Теперь о коллбеках. Для того, чтобы с помощью функции set_callback и биндера сделать свой обработчик события надо:
1. Создать дополнительный метод в нашем классе биндера.
2. Указать этот метод как обработчик определённого события. Как правило, это делается в методе reinit.
function my_binder:reinit()
    object_binder.reinit(self)
    self.object:set_callback(callback.use_object, self.use_callback,   self)
end
function my_binder:use_callback(obj, who)
    --здесь выполняю действия, которые надо сделать при использовании объекта (нажатии на нём клавиши 'F')
end


Подробно разбирать функцию set_callback не будем. Только важные сейчас моменты:
- первый аргумент - один из членов перечисления callback.


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;
};


В общем видно, что можно сделать. Даже иногда понятно, для какого объекта применимо. Надо заметить, что для конкретного типа объектов будут работать только конкретный набор коллбеков. Например, точно известно, что для неписей работает death и hit. Для ГГ работает death, on_item_drop и on_item_take. Для ящиков - use_object. А вот для гранат похоже ничего не работает.
Списка работающих коллбеков для каждого типа объектов пока ещё никто не составил.

Теперь простой пример, который я сделал для ящика


function init(obj)
    local new_binder = my_binder(obj)
    obj:bind_object(new_binder)
end

class "my_binder" (object_binder)
function my_binder:__init(obj) super(obj)
    get_console():execute("my_binder:__init")
end

function my_binder:reload(section)
    get_console():execute("my_binder:reload")
    object_binder.reload(self, section)
end

function my_binder:reinit()
    get_console():execute("my_binder:reinit")
    object_binder.reinit(self)
    self.object:set_callback(callback.use_object, self.use_callback,   self)
end

function my_binder:update(delta)
    local actor_pos = db.actor:position()
    local obj_pos = self.object:position()
    local dist = actor_pos:distance_to(obj_pos)
    get_console():execute("my_binder:update_dist="..dist.."_delta="..delta)
    object_binder.update(self, delta)
end

function my_binder:net_spawn(data)
    get_console():execute("my_binder:net_spawn")
    return object_binder.net_spawn(self, data)
end

function my_binder:net_destroy()
    get_console():execute("my_binder:net_destroy")
    object_binder.net_destroy(self)
end

function my_binder:net_save_relevant()
    get_console():execute("my_binder:net_save_relevant")
    return true
end

function my_binder:save(packet)
    get_console():execute("my_binder:save")
    object_binder.save(self, packet)
end

function my_binder:load(reader)
    get_console():execute("my_binder:load")
    object_binder.load(self, reader)
end

function my_binder:use_callback(obj, who)
    get_console():execute("my_binder:use_callback")
end



кроме того в файле gamedata\config\misc\devices.ltx
добавил новую секцию

[inventory_box_my]:inventory_box
script_binding      = my_cool_binder.init


это будет ящик с визуалом ящика с динамитом с одним только отличием от стандартного - у него будет нестандартный наш биндер.


На очереди net_packet. Простой, но весьма важный класс.

net_packet - это один из вспомогательных классов. Представляет собой буфер размером ровно 8 кбайт (т.е. 8192 байта). Из буфера можно последовательно читать и записывать данные, используя методы класса. Имеется текущая позиция чтения и записи. Теперь подробности.


class net_packet {
    net_packet ();

    number w_tell();
    number r_tell();
    void w_begin(number);
    function r_begin(number&);
    function r_advance(number);
    function r_seek(number);
    number r_elapsed();
    bool r_eof();

    number r_u8();
    void w_u8(number);
    number r_s8();
    
    number r_u16();
    void w_u16(number);
    number r_s16();
    void w_s16(number);
    number r_u24();
    void w_u24(number);
    
    number r_u32();
    void w_u32(number);
    number r_s32();
    void w_s32(number);

    number r_u64();
    void w_u64(unsigned __int64);
    number r_s64();
    void w_s64(__int64);
    
    number r_float();
    function w_float(number);
    string r_stringZ();
    void w_stringZ(string);
    bool r_bool();
    void w_bool(bool);

    vector r_vec3();
    void w_vec3(const vector&);
    matrix r_matrix();
    void w_matrix(matrix&);

    vector r_sdir();
    void w_sdir(const vector&);
    vector r_dir();
    void w_dir(const vector&);
    
    number r_angle8();
    void w_angle8(number);
    number r_angle16();
    void w_angle16(number);

    function r_float_q8(number&, number, number);
    function w_float_q8(number, number, number);
    function r_float_q16(number&, number, number);
    function w_float_q16(number, number, number);

    function w_chunk_open8(number&);
    function w_chunk_open16(number&);
    function w_chunk_close16(number);
    function w_chunk_close8(number);

    ClientID r_clientID();
    void w_clientID(ClientID&);
};

Я немного изменил описание по сравнению с оригинальным из lua_help.script. Добавил типы возвращаемых значений, перегруппировал и убрал некоторую шелуху.

Более подробно о методах класса:
net_packet() - конструктор, вызывается в виде глобальной функции так:
local packet = net_packet()
созданный пакет по умолчанию имеет позиции чтения и записи установленные в 0
Не всегда надо создавать свой пакет. Часто приходится иметь дело с уже готовым пакетом (см. следующую статью об использовании нетпакетов)

w_tell() - возвращает текущую позицию записи
r_tell() - возвращает текущую позицию чтения
r_advance(shift) - смещает позицию чтения на shift байт. Смещение может быть отрицательным.
r_seek(pos) - устанавливает позицию чтения в pos
r_elapsed() - возвращает w_tell() - r_tell()
r_eof() - возвращает true, если r_tell() < w_tell(), иначе false

w_begin(number) - пишет двухбайтовое число в начало пакета и устанавливает позицию записи равной 2. Единственным другим способом начать запись с начала пакета - это создать новый пакет, у которого позиция записи установлена в 0.
r_begin(number&) - Непонятный метод. По аналогии с w_begin должен читать с начала пакета два байта и устанавливать позицию чтения в 2. Однако у меня приводит к вылету. Ну, в любом случае можно обойтись и без него.

Важный момент! Нет никакого способа узнать, что в процессе чтения или записи позиция чтения или записи вышла за пределы пакета. При чтении подразумевается, что прочитать можно столько, сколько записано. Для этого и есть функции r_elapsed() и r_eof(). А вот при записи никакой границы сверху нет, поэтому можно запросто записать больше, чем 8 кбайт. Ясно, что при этом произойдёт переполнение буфера с почти обязательным последующим вылетом игры. Так что надо самостоятельно следить за размером позиции записи и проверять, чтобы при последующей записи она не вышла бы за размеры пакета. Для этого надо знать, сколько мы запишем, ещё до того, как запишем. В особенности это важно для строк, которые имеют переменную длину. Поступаем примерно так:

if string.len(s) + 1 + packet:w_tell() > 8192 then
    -- здесь делаем что-то, например крашим игру и выводим сообщение, что не надо жадничать =)
end
packet:w_stringZ(s)

Обратите внимание на "+ 1" в вычислении новой позиции записи. Строки имеют дополнительный невидимый нулевой символ в конце, и их физическая длина на один больше, чем длина в символах, которую возвращает функция string.len().

Остальные методы предназначены собственно для чтения и записи. Описывать их все в подробностях нет смысла, только общий принцип. Читаем так:
local value = packet:r_XXX()
здесь XXX - это тип значения, которое читается. Значение читается из буфера начиная с текущей позиции чтения, а позиция чтения увеличивается на размер читаемого значения. В переменную value будет помещено значение того типа, которое прочитано.
аналогично пишем в буфер:
packet:w_XXX(value)
Значение пишется начиная с текущей позиции записи, а позиция записи увеличивается на размер типа XXX.
для целых типов:
s - знаковое значение
u - беззнаковое значение
8, 16, 24, 32, 64 - один, два, три, четыре, восемь байт соответственно
Обратите внимаение, что для типа s8 (знаковый байт) нет метода записи. Однако, вместо него без проблем можно использовать соответствующий метод для беззнакового типа w_u8.
float - число с плавающей запятой одинарной точности, 4 байта
stringZ - строка (размер равен длине + 1 байт)
bool - логическое значение (1 байт)
vec3 - объект типа vector - вектор из трёх float (12 байт)
matrix - объект типа matrix. Состоит из 4-х векторов (48 байт)
sdir - ? непонятно, на запись берёт вектор и пишет 6 байт
dir - ? аналогично, но пишет 2 байта
angle8 - ? берёт float и пишет 1 байт
angle16 - ? аналогично, но пишет 2 байта
четыре последних метода при чтении у меня вызывают вылет. зачем нужны, неизвестно.
ClientID - объект класа ClientID. Судя по всему, это надо для сетевой игры.
float_q8, float_q16 - пока непонятно
Назначение методов с w_chunk_ вообще непонятно, тем более, что для них отсутствуют соответствующие методы на чтение.


Собственно, законное использование нетпакетов - это буфер, в котором объект сохраняет своё состояние. Скорее всего это используется в первую очередь при передаче данных по сети. Потому и net_packet, т.е. буквально "сетевой пакет". Сначала объект записывает себя в пакет, затем он отправляется по сети. Похоже, однако, что используется не только при передаче по сети, но и для сохранения объектов вообще.
Теперь конкретнее.
1. Сохранение состояния серверного класса
На серверной стороне есть классы, унаследованные от cse_abstract. У них есть методы STATE_Read и STATE_Write.
метод STATE_Read вызывается при загрузке состояния объекта из сохранёнки, в нём данные читаются из переданного методу пакета. STATE_Write вызывается при сохранении объекта, в нём данные объекта сохраняются в пакет.
Если создать свой класс и перегрузить эти методы, то увидим такую картину:

class "se_my_server_class"    (<имя_базового_класса>)

function se_my_server_class:STATE_Write(packet)
    <имя_базового_класса>.STATE_Write(self, packet) -- базовый класс сохраняет свои данные
    -- здесь можно сохранить какие-то данные, в дополнение к данным базового класса
    packet:w_stringZ("моя строка")
end

function se_my_server_class:STATE_Read(packet, size)
    <имя_базового_класса>.STATE_Read(self, packet) -- здесь базовый класс читает своё состояние из пакета
    -- здесь можно прочитать состояние своего класса, которое было сохранено ранее
    local s = packet:r_stringZ() -- получим строку "моя строка"
end


2. Сохранение состояния клиентского класса
На клиентской стороне также имеется нечто подобное. Это реализуется методами биндера save и load. При создании биндера (см. мою статью насчёт класса object_binder) можно в этих методах что-то сохранить. Однако, нетпакет используется для сохранения в методе save. в этом он подобен методу STATE_Write серверного класса. А вот при загрузке почему-то вместо нетпакета передаётся поток на чтение (класс reader). Таким образом трюкачество, описанное в следующем пункте для клиентских объектов не выйдет.

3. Перепаковка серверных объектов с целью изменения их параметров
Предыдущие два варианта использования нетпакетов - это так сказать "законное" их использование. Но это не всё. Ничто не мешает вызывать методы STATE_Read и STATE_Write в произвольный момент времени, имитируя процесс сохранения и загрузки объекта. При этом можно сделать следующее:

local packet = net_packet() -- создаём пустой пакет
sobj:STATE_Write(packet) -- загрузили в наш пакет состояние серверного объекта
-- используя методы класса net_packet меняем нужные нам значения.
sobj:STATE_Read(packet, packet:w_tell()) -- записали в объект изменённое состояние обратно, имитируя процесс его загрузки


Несколько замечаний:
- Все эти манипуляции надо проводить над объектами в оффлайне, поскольку онлайновый объект регулярно обновляет серверную часть и полностью его переписывает, так что любые изменения такого рода будут потеряны.
- Надо знать структуру нетпакета для того объекта, который Вы собираетесь изменить. При таких хирургических операциях запросто можно всё испортить.



 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Kirag    38

Кое-что по классу patrol, то, что сам понял.

 

C++ class patrol {

const continue = 1;

const custom = 3;

const dummy = -1;

const nearest = 2;

const next = 4;

const start = 0;

const stop = 0;

 

patrol (string);

patrol (string, enum PatrolPathManager::EPatrolStartType);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType, boolean);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType, boolean, number);

 

function level_vertex_id(number) const;

function point(const patrol*, number);

function flag(number, number) const;

function game_vertex_id(number) const;

function flags(number) const;

function name(number) const;

function index(string) const;

function terminal(number) const;

function count() const;

function get_nearest(const vector&) const;

};

 

 

Этот класс отвечает за движение неписей по путям. Информацию о путях берет только из алл.спавна. Переопределить это нельзя без ковыряния в движке, откуда следует невозможность задавать пути извне алл.спавна. Для решения этой задачи надо как-то обойти класс patrol. Для вертолетов это удалось, но с другими НПС это гораздо сложнее :(

 

patrol (string) - создает объект класса.

 

Методы:

 

count() - возвращает количество точек в пути. Аргументов не требуется.

 

name(number) - возвращает строку - название заданной точки пути. Аргумент - номер точки, начиная с 0 (pN:name = ... в алл.спавне).

point(const patrol*, number) - возвращает вектор - координаты заданной точки пути. Аргумент - номер точки, начиная с 0 (pN:position = ... в алл.спавне).

game_vertex_id(number) - возвращает число - гейм вертекс заданной точки пути. Аргумент - номер точки, начиная с 0 (pN:game_vertex_id = ... в алл.спавне).

level_vertex_id(number) - возвращает число - левел вертекс заданной точки пути. Аргумент - номер точки, начиная с 0 (pN:level_vertex_id = ... в алл.спавне).

flags(number) - возвращает объект класса flags16 или flags32 (как их отличить не знаю, методы точно такие же) - флаг данной точки пути (pN:flags = ... в алл.спавне). Аргумент - номер точки, начиная с 0. Значение флага можно узнать методом get() класса flags16 (или flags32)

terminal(number) - возвращает логическое значение - true, если из данной точки нет переходов на другие точки и false, если есть (есть ли pN:links = ... в алл.спавне). Аргумент - номер точки, начиная с 0.

 

flag(number1, number2) - возвращает логическое значение - true, если значение флага в точке number1 равно number2, иначе false. Агрументы: первый - номер точки, второй - число (в оригинальных скриптах почему-то только от 1 до 32). Уверен не до конца.

 

get_nearest(const vector&) - примеров использования нет, предположительно ищет ближайшую к заданным вектором коордтам точку пути. Не проверял.

index(string) - нет ни примеров использования, ни предположений, что это могло бы значить.

 

Как обрабатываются связи между точками, до конца не ясно, могу только предположить, что для этого служит одна из этих команд:

 

patrol (string, enum PatrolPathManager::EPatrolStartType);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType, boolean);

patrol (string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType, boolean, number);

 

Но которая, и что она возвращает, и что требует в качестве аргументов - непонятно.


Мои работы:

Ночные прицелы + смена ножевого слота

AI вертолетов + ПЗРК

Soul Cube

 

Работаю только с ТЧ. С ковырянием ЧН/ЗП не связываюсь ни в какой форме. Совсем.

Share this post


Link to post
Share on other sites
Malandrinus    600

Несколько уточнений:

Этот класс отвечает за движение неписей по путям. Информацию о путях берет только из алл.спавна. Переопределить это нельзя без ковыряния в движке, откуда следует невозможность задавать пути извне алл.спавна. Для решения этой задачи надо как-то обойти класс patrol.
Файл all.spawn можно дописывать, добавляя туда дополнительные точки переходов. Вопреки распространённому мнению, новую игру при этом начинать не надо.

 

index(string) - нет ни примеров использования, ни предположений, что это могло бы значить.
Вроде как это метод, обратный name. Т.е. по имени точки возвращает её номер.

 

Как обрабатываются связи между точками, до конца не ясно, могу только предположить, что для этого служит одна из этих команд:

patrol...

Но которая, и что она возвращает, и что требует в качестве аргументов - непонятно.

Это такие же конструкторы, как и patrol с одним только именем пути. Просто у них ещё есть дополнительные аргументы.

 

И, как мне думается, за само движение отвечает класс entity_action, которому передаётся объект класса move, у которого patrol - это аргумент. Короче, копать и копать ещё, но начало неплохое. Там глядишь, и разберёмся, как свои скриптовые схемы ваять.


 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600
Про новую игру при добавлении путей вроде никто и не говорил :unsure: Про новую игру говорят при добавлении новых объектов, изменении старых

Я добавил level_changer, доспавнил его функцией create(<номер>) и ничего мне за это не было =)

 

Если кому это надо, то добавлю вот что. Я делал тестовый объект на кордоне, и сначала попробовал добавить секцию в файл для кордона. Выяснил, сколько всего объектов, сделал секцию с номером, на один большим...

У меня заспавнился не тот. Начал выяснять, откуда он взялся, и понял, что это последний объект из последнего уровня (ящик какой-то). Тогда я просто добавил новый объект в конец последнего уровня, и все получилось.

Мораль, номер объекта из all.spawn - это просто его номер в порядке компиляции. Зачем acdc разбирает объекты по уровням, не знаю. Но на мой взгляд, после этого они перетасованы. Если каждый раз начинать игру заново, то это и без разницы. Но если мы хотим использовать пересобранный all.spawn не начиная игру заново, то видимо acdc не годится.

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
 dan    4
malandrinus, Годиться просто, нужно после компиляции, декомпилировать all.spawn и посмотреть номер секции нужного тебе объекта.

Share this post


Link to post
Share on other sites
Taroz    0

Вот кст рабочая команда... по партулю.

local objt = level.object_by_id("5822")
objt:set_patrol_path("bar_bar_guard_walk", patrol.nearest, patrol.continue, true)

делает патрульный путь у парня в баре, который стоит у табличке при повороте направо.

Только вылет происходит.

! Unknown command: Succesfull!

DEFAULT OUT RESTRICTIONS :

DEFAULT IN RESTRICTIONS :

bar_arena_restrictor

bar_dolg_bunker_restrictor

bar_restrictor

camp_fire_0000

camp_fire_0001

camp_fire_0002

camp_fire_0003

camp_fire_0004

camp_fire_0005

mil_camp_fire_res_0002

OUT RESTRICTIONS :

IN RESTRICTIONS :

bar_arena_restrictor

bar_dolg_bunker_restrictor

bar_restrictor

camp_fire_0000

camp_fire_0001

camp_fire_0002

camp_fire_0003

camp_fire_0004

camp_fire_0005

mil_camp_fire_res_0002

 

FATAL ERROR

 

[error]Expression : vertex || show_restrictions(m_object)

[error]Function : CPatrolPathManager::select_point

[error]File : E:\stalker\patch_1_0004\xr_3da\xrGame\patrol_path_manager.cpp

[error]Line : 155

[error]Description : any vertex in patrol path [bar_bar_guard_walk] in inaccessible for object [bar_bar_stalker]

 

Кому интересно, кто капает...

 

 

А вот рабочий вариант, делает из bar_visitor_15 , второго часового на повороте в бар у табличке.

local objt = level.object_by_id("5908")
    objt:set_patrol_path("bar_bar_1_walk", patrol.nearest, patrol.continue, true)

Edited by Taroz

Share this post


Link to post
Share on other sites
Malandrinus    600

В качестве разминки разберём класс render_device. Один из тех классов, про который должен знать каждый модостроитель, а между тем практически незамеченный.

[spoiler=Описание класса render_device:]Класс render_device позволяет получить свойства экрана и камеры: разрешение, направление взгляда и т.п.

Объект такого класса всего один и возвращает его глобальная функция device():

local dev = device()

[spoiler=Описание класса из lua_help.script (с некоторыми моими дополнениями)]

class render_device {
    int height; // вертикальное разрешение
    int width; // горизонтальное разрешение
    float aspect_ratio; // отношение height/width
    vector cam_pos; // вектор положения камеры - точка, откуда глядим
    vector cam_dir; // единичный вектор направления взгляда
    float fov; // угол обзора в градусах
    vector cam_right; // единичный вектор, перпендикулярный направлению камеры и направленный вправо
    vector cam_top; // единичный вектор, перпендикулярный направлению камеры и направленный вверх
    float f_time_delta; // время между кадрами в с.
    int frame; // номер выведенного кадра
    int precache_frame; // число не выведенных кадров
    int time_delta; // время между кадрами в мс.

    int time_global(); // реальное время в мс.
    bool is_paused(); // состояние остановленности
    void pause(bool); // остановить/возобновить игру
};

Далее предполагаю, что объект типа render_device получен и записан в переменную dev.

Свойства (все только для чтения):

dev.height и dev.width - вертикальное и горизонтальное разрешение соответственно. Обращаю внимание, что независимо от настоящего разрешения экрана, элементы пользовательского интерфейса (статики и пр.) рисуются на экране в сетке 1024*768. Но при этом их размеры всё-таки указываются в реальных пикселях. Не запутались? Вот к примеру, если вы рисуете статик по координатам [512,384], то он нарисуется всегда в центре, независимо от разрешения, но его размеры при разрешении 1600*1200 будут в два раза меньше, чем при разрешении 800*600.

dev.fov - угол обзора актора в градусах. Это угол, который меряется из точки взгляда и определяется высотой экрана (как наименьшей из сторон). Дефолтовое значение этого угла составляет 67.5 градусов. Это число забито в код движка и мне встречались утилитки для его исправления. При масштабировании (с биноклем или прицелом) это число меняется. Собственно это можно использовать для определения входа в режим увеличения. Также это число можно использовать для вычисления проекции точки на экран.

dev.cam_pos и dev.cam_dir - положение камеры и направление взгляда. Это в точности та точка, откуда стреляем, и то направление, в котором стреляем. Есть отличие от методов position() и direction() класса game_object. position() - это положение точки на земле, а direction() определяет не направление взгляда, а направление модели актора (или непися). А направление это меняется рывками и не может служить для точного определения направления взгляда.

При проецировании точки на экран:

dev.cam_pos соответствует центру экрана (всегда точка 512*384)

dev.cam_dir направлен перпендикулярно экрану.

dev.cam_right совпадает с плоскостью экрана и направлен строго вправо

dev.cam_top совпадает с плоскостью экрана и направлен строго вверх

dev.frame - номер отрисованного кадра. Растёт постоянно. Можно определять FPS из скрипта. Зачем ещё может понадобиться - без понятия.

dev.precache_frame - не вполне понятное значение. Возможно, количество фреймов, готовых для вывода, но ещё не выведенных на экран. Могу здесь и ошибаться, но свойство это используется для определения готовности устройства рендеринга примерно так:

if dev.precache_frame >1 then <устройство не готово> end

Судя по всему, некоторые действия не имеет смысла (а может и просто нельзя) делать до готовности вывести очередной кадр немедленно.

dev.f_time_delta - число с плавающей запятой, время между кадрами в секундах, т.е. значение, обратное FPS

dev.time_delta - аналогично, но целое и измеренное в мс.

 

Функции:

dev:time_global() - возвращает в точности то-же значение, что и глобальная функция time_global(). В силу этого вероятно не имеет особенного смысла её использовать, хотя в скрипте _g.script имеются вот такие строчки:

if nil == time_global then
  time_global = function () return device():time_global() end
end

т.е. если такой глобальной функции нет, то она подменяется вот этой. Уж не знаю, для какой версии движка эта заплатка, но в текущей она не используется.

dev:pause(true/false) - позволяет программно выполнить паузу, как если бы вы нажали кнопку "Pause/Break".

dev:is_paused() - соответственно, возвращает состояние остановленности.

 

 

Подправил описание render_device. Неизвестных методов и свойств практически не осталось. В дополнение привожу пример практического использования - функцию, которая проецирует произвольную точку в пространстве на экран.

function point_projection(point)
    local dev = device()
    local scr_w = dev.width
    local scr_h = dev.height

    local fov2 = (dev.fov/2) * (math.pi/180)
    local scr_dist = 0.5 * scr_h / math.tan(fov2)

    local ppp = vector():sub(point, dev.cam_pos)
    local dp = dev.cam_dir:dotproduct(ppp)

    local x = 512 + dev.cam_right:dotproduct(ppp) * scr_dist / dp * (1024/scr_w)
    local y = 384 - dev.cam_top:dotproduct(ppp)   * scr_dist / dp * (768/scr_h)
    return x, y
end

Функция учитывает возможное разрешение экрана, режим зума и то, что камера может наклоняться. Можно использовать в различных целеуказателях.

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600

На очереди класс FS. Если кому интересно, то внутри самого движка он имеет имя CLocatorAPI. Это класс для работы с файловой системой и файлами.

Экземпляр класса FS можно получить с помощью глобальной функции getFS()

local fs = getFS()

class FS {
    // флажки, используются в методах file_list_open и file_list_open_ex
    const FS_ListFiles = 1;
    const FS_ListFolders = 2;
    const FS_ClampExt = 4;
    const FS_RootOnly = 8;
    // константы используются в методе Sort класса FS_file_list_ex
    const FS_sort_by_name_up = 0;
    const FS_sort_by_name_down = 1;
    const FS_sort_by_size_up = 2;
    const FS_sort_by_size_down = 3;
    const FS_sort_by_modif_up = 4;
    const FS_sort_by_modif_down = 5;

    fs_file* exist(<абсолютный путь до файла>); // если файл с абсолютным путём существует, то возвращает объект типа fs_file, иначе nil
    fs_file* exist(<корень>, <путь относительно корня>); // аналогично, но для корень+относительный путь
    DWORD get_file_age(<абсолютный путь до файла>); // количество секунд, прошедших с 1 января 1970 года
    string get_file_age_str(<абсолютный путь до файла>); // строка со временем создания файла в виде: "Thu Sep 17 03:39:24 2009"
    int file_length(<абсолютный путь до файла>); // получить длину файла
    void file_copy(<абсолютный путь до файла 1>, <абсолютный путь до файла 2>); // копировать файл
    void file_rename(<абсолютный путь до файла 1>, <абсолютный путь до файла 2>, boolean); // третий логический аргумент определяет, будет ли файл замещать существующий
    void file_delete(<корень>, <путь относительно корня>);
    void file_delete(<абсолютный путь до файла>);

    function append_path(string, string, string, number); // не понятно, что делает
    string update_path(<корень>, <путь относительно корня>) // составляет абсолютный путь из корня и относительного пути
    void dir_delete(<абсолютный путь>, number); // назначение второго вргумента непонятно
    void dir_delete(<корень>, <путь относительно корня>, number); // аналогично
    FS_file_list* file_list_open(<корень>, number); // все три функции получают список файлов для указанной директории
    FS_file_list* file_list_open(<корень>, <маска>, number);
    FS_file_list_ex* file_list_open_ex(<корень>, number, <маска>);
    // во всех трёх функциях числовой аргумент - это набор флажков, составленных из FS_ListFiles, FS_ListFolders, FS_ClampExt и FS_RootOnly
    //их название говорит за себя, но эффект у меня был только от 
    //FS_ListFiles - получить список файлов
    //FS_ClampExt - убрать из имён файлов расширение
    bool path_exist(<корень>); // проверяет существование корня
    function get_path(string); // не работает

    reader* r_open(<корень>, <путь относительно корня>); // открыть файл на чтение
    reader* r_open(<абсолютный путь до файла>);// открыть файл на чтение
    void r_close(reader*&); // закрыть поток чтения. У меня всегда вызывало вылет
    IWriter* w_open(string, string); // не работает
    IWriter* w_open(string); // не работает
    void w_close(class IWriter*&); // не работает
};

Дополнительные классы:

class fs_file {
    int modif; // время создания в секундах, прошедших с 1 января 1970 года
    string name; // полный путь
    number ptr;
    int size_compressed; // пожатый размер (не совсем ясно, что имеется в виду. Возможно - не используемая сейчас фишка упаковки файлов в игровых архивах)
    int size_real; // реальный размер в байтах
    int vfs;
};

Объект типа fs_file возвращает метод exist. Если файл не найден, то возвращается nil. С одной стороны это можно использовать для проверки существования файла, а с другой, не отходя от кассы можно посмотреть свойства файла. Свойство name рекомендуется использовать в дальнейшем в тех методах FS, где требуется полный абсолютный путь к файлу. У меня случались вылеты, когда я прописывал путь руками.

class FS_file_list {
    void Free(); // надо вызвать после использования
    string GetAt(number); // получить имя файла в списке как строку
    int Size(); // количество файлов в списке
};

Объект типа FS_file_list возвращает метод file_list_open. Это класс, который хранит список файлов в виде обычного списка строк. По каким-то причинам после использования этого класса надо вызывать его метод Free.

class FS_file_list_ex {
    function Sort(number); // сортировать по признаку. Указывается одна из констант FS.FS_sort_by_xxx
    FS_item* GetAt(number); // получить информацию о файле в виде объекта типа FS_item.
    int Size(); // количество файлов в списке
};

Объект типа FS_file_list_ex возвращает метод file_list_open_ex. Обладает большими возможностями, чем FS_file_list. Позволяет сортировать список файлов и получать расширенную информацию о каждом в виде объекта типа FS_item.

class FS_item {
    string Modif(); // время изменения в виде: "Thu Sep 17 04:15:08 2009"
    string ModifDigitOnly(); // время изменения в виде: "17:09:2009 04:15"
    string NameShort(); // имя файла без пути (и без расширения, если использован флажок FS.FS_ClampExt)
    string NameFull(); // почему-то никакой разницы с NameShort
    int Size(); // размер в байтах
};

Дополнительная информация:

1. В тех методах класса FS, где указаны два аргумента <корень> и <путь относительно корня>, под корнем имеются в виду строки вида "$<имя_корня>$". Эти стандартные (для игры) пути в основном прописаны в файле fsgame.ltx, который расположен в каталоге установки игры. Причем один из таких путей, а именно "$fs_root$" там не прописан, поскольку игра и так его знает из реестра. Это и есть каталог установки игры. Почти все остальные являются от него производными. Есть ещё отдельный путь "$app_data_root$", который обычно прописан явно в системном профиле пользователя и является базовым для таких путей, как место сохранения, логи и скриншоты.

Важным является то, что в этот файл можно прописать и свои пути. Таким образом есть возможность обратиться к совершенно произвольному файлу или каталогу на любом диске.

2. Класс FS позволяет обращаться к файлам, находящимся в игровых архивах.

3. Не любой файл удаётся открыть на чтение. Почему-то некоторые файлы при попытке открыть их вызывают бессловесный вылет. Надо пробовать.

4. Класс reader я здесь не описываю. В принципе там и так всё ясно, но можно почитать описание для net_packet. В части для чтения список методов очень похож.

5. Если на чтение из файла есть класс reader то вот на запись класса нет. Метод w_open возвращает не экспортированный класс IWriter, и у меня в любом случае приводил к вылетам. Так что с записью облом. Кстати, два метода класса FS - get_path и append_path судя по всему тоже возвращают экземпляры не экспортированного класса (FS_Path) и тоже приводят (у меня по крайнем мере) к вылетам.

 

 

Ещё один класс, имеющий отношение к файловой системе - это CSavedGameWrapper. С помощью этого класса можно получить информацию о сохранённой игре

class CSavedGameWrapper {
    CSavedGameWrapper (<имя сохранёнки без расширения>);

    string level_name(); -- системное имя уровня
    int level_id(); -- номер уровня
    CTime* game_time(); -- игровое время
    number actor_health(); -- здоровье игрока
};

local flist = getFS():file_list_open_ex("$game_saves$",bit_or(bit_or(FS.FS_ListFiles,FS.FS_RootOnly), FS.FS_ClampExt) , "*.sav")
local f_cnt = flist:Size()
local con = get_console()
if f_cnt > 0 then
    local file = flist:GetAt(0)
    con:execute("FullName="..file:NameFull())
    local sg = CSavedGameWrapper(file:NameFull())
    
    local y,m,d,h,min,sec,ms = sg:game_time():get()
    local level_name = sg:level_name()
    con:execute("level_name="..level_name)
    local level_id = sg:level_id()
    con:execute("level_id="..level_id)
    local actor_health = sg:actor_health()
    con:execute("actor_health="..actor_health)
end

 

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600

Попробуем приступить к game_object. Это класс для доступа к онлайновым (клиентским) объектам. Причём один класс является интерфейсом для совершенно разных объектов. Разработчики не придумали ничего лучше, как взять и объединить в одном классе все интерфейсы всех клиентских объектов: актора, сталкеров, монстров, физических объектов, автомобилей и лампочек, вообще всех. Более странного и вообще говоря уродливого объектно-ориентированного дизайна я ещё не видел. Во-первых, класс вышел совершенно необозримым - три сотни методов! Во-вторых, вызов не подходящего метода для произвольно взятого объекта приводит к совершенно непредсказуемым результатам. В лучшем случае не будет ничего, а чаще всего - будет вылет, причём обычно без лога. Наконец, описание этого класса в lua_help совершенно невнятное (как впрочем и всех остальных классов): типы возвращаемых значений опущены, типы входных аргументов указаны не всегда, а о назначении большинства методов можно только гадать. Предлагаю несколько более внятное описание. Источником информации служил в первую очередь документ из билда 1935, где описаны (весьма лаконично) многие методы этого класса. Во вторую очередь использована отладочная информация из мультиплеерных билдов, что позволило довольно точно восстановить типы аргументов и возвращаемых значений. Для некоторых функций имеются описания, полученные либо мной лично либо подсмотренные на форумах. Так что предлагаю рассматривать всё это как плод коллективного творчества.

Пара замечаний:

1. Информация по-любому неполная. Многие (скорее даже большинство) методы не описаны, хотя типы аргументов и возвращаемых значений имеются полностью.

2. Вследствие большого объёма причёсывать сил не осталось, так что выкладываю как есть. В некоторых местах осталась информация стороннего характера - сравнение с ранними билдами, внутренние названия функций и некоторые мои собственные домыслы.

3. Я перетасовал список методов с целью их осмысленной группировки "по родству". Очевидно, что мог и заблуждаться насчёт "родства" методов, так что смотрите в оба.

4. Поскольку описание не закончено, то предлагаю всем поучаствовать в завершении этого этапа.

 

class game_object {

 

const dialog_pda_msg = 0;

const info_pda_msg = 1;

const no_pda_msg = 2;

 

 

// enum MovementManager::EPathType {

// ePathTypeGamePath,

// ePathTypeLevelPath,

// ePathTypePatrolPath,

// ePathTypeNoPath,

// ePathTypeDummy,

// };

const game_path = 0;

const level_path = 1;

const patrol_path = 2;

const no_path = 3;

const dummy = -1;

 

//

const friend = 0;

const neutral = 1;

const enemy = 2;

 

//

const relation_kill = 0;

const relation_attack = 1;

const relation_fight_help_human = 2;

const relation_fight_help_monster = 4;

//

// enum ScriptEntity::EActionType {

// eActionTypeMovement,

// eActionTypeWatch,

// eActionTypeAnimation,

// eActionTypeSound,

// eActionTypeParticle,

// eActionTypeObject,

// eActionTypeCount,

// }

 

const movement = 0;

const watch = 1; ????????????

const animation = 2;

const sound = 3;

const particle = 4;

const object = 5;

const action_type_count = 6;

//

Члены класса:

//visible – видимость -- нет такого

//enabled – доступность -- нет такого

//satiety – сытость -- нет такого

//circumspection – осторожность -- нет такого

---------------------------------- свойства --------------------------------------------

property health; // здоровье 0..1

property morale; // мораль

property power; // сила

property psy_health;

property radiation; // радиация

// все свойства на запись работают не так, как на чтение. При записи значение имеет смысл

// изменения соответствующего свойства. Т.е. если записать 0.1 в health, то это увеличит его на 0.1

---------------------------------- методы ----------------------------------------------

// самые основные

vector position() – получить позицию

int id() -- уникальный идентификатор объекта. 1..65534, 0 - актор, 65535 - ничей, имеет специальное значение

int clsid() –- идентификатор класса (один из членов класса clsid)

int story_id() -- сюжетный идентификатор. Также уникальный, если есть. Если нет, то -1

string section() -– секция в system.ltx

string profile_name() -- профиль (тот, что в XML)

string name() -- имя. Это системное имя, обычно уникальное. Является таким же идентификатором, как и id

ini_file *spawn_ini() -- возвращает указатель на custom data

int game_vertex_id() -- номер текущей вершины глобального графа,

-- который в частности используется в оффлайноыой навигации

int level_vertex_id() -- номер текущей вершины локального графа уровня,

-- который используется только для навигации клиентских (онлайновых) объектов

vector direction() -- направление

int parent() –- кому принадлежит. Для предметов в инвентаре и в ящиках. Если никому, то -1

float mass() - масса

float accuracy() -- точность (вроде бы сталкеров)

int cost() - стоимость

float condition() -- состояние предмета

void set_condition(float) -- установить состояние предмета

void kill(game_object*) -- убить, аргумент - кто убийца

int death_time() – время смерти, если помер

float get_bleeding() -- кровотечение. Для изменения функции нет, но можно использовать отрицательный хит

 

vector center() -- центр физической оболочки

 

float fov() //угол зрения неписей

void set_fov(float) //установить угол зрения неписей

float range() – максимальная видимость

void set_range(float)

// функции перемещения и установки направления для актора. Можно использовать для телепорта актора в пределах уровня

void set_actor_position(vector);

void set_actor_direction(number);

// функции для работы со слотами. Слоты есть и у сталкеров, не только у актора

int active_slot(); // текущий активный слот

game_object* item_in_slot(number) // объект в слоте

void activate_slot(number); // выбрать слот текущим

game_object* active_item(); // объект в текущем активном слоте

 

int object_count() -- количество объектов в инвентаре

game_object* object(string) -- возвращает объект из инвентаря по секции

game_object* object(number) -- возвращает объект из инвентаря по индексу. Индекс в диапазоне от 0 до object_count()-1

-- такой код эквивалентен использованию приведённой далее функции iterate_inventory()

    local cnt = st:object_count()
    for i=0,cnt-1 do
        local item = st:object(i)
        ...
    end

bool is_inv_box_empty() -- для инвентаря ящика

// функции перебора предметов в инвентаре

void iterate_inventory(function* iterator, game_object* obj) -- перебрать предметы, принадлежащие NPC

-- перебирает все предметы, независимо от возможности продажи. Работает для монстров

-- iterator - это функция вида:

    function iterate_inventory_action(obj, item)
    --  здесь:
    --   obj - объект, переданный ранее в функцию iterate_inventory
    --   item - текущий предмет
    end

 

void inventory_for_each(function *iterator) -- перебрать предметы в инвентаре

-- работает только для актора и сталкеров. Не работает для монстров (при наличии предмета у монстра - вылет).

-- iterator - это функия вида:

    function inventory_for_each_action(item, npc)
    --  здесь:
    --   npc - сталкер или актор, для которого перебираем
    --   item - текущий предмет
    end

// Есть несколько отличий от функции iterate_inventory.

// Перебираются не все предметы. Для актора пропускаются предметы в невидимых слотах: нож, бинокль, болт, фонарик и ПДА. Кроме того, для актора определён порядок перебора предметов: сначала перебираются предметы в рюкзаке, потом на поясе, потом в слотах (только те, что видны). На этом основан алгоритм определения предметов на поясе (в слотах для артов). Спасибо Колмогору за дополнение!

// Для сталкеров inventory_for_each возвращает список предметов, которые они выставляют на продажу. По крайней мере у меня так всегда выходит. Пояса у сталкеров нет вообще. Слоты есть, но предметы из слотов в список, перебираемый inventory_for_each, не попадают.

// обе указанных функции не работают для ящиков. Вылета не дают, но и не перебирают. Единственным известным мне способом определения списка предметов в ящике является полный перебор объектов и сравнение их parent с id ящика.

 

int get_ammo_total() – функция вызывается для оружия. Если ствол не в инвентаре, то возвращает количество патронов в обойме. Если в инвентаре, то возвращает суммарное количество доступных для этого ствола патронов как в его обойме, так и в инвентаре хозяина.

int get_ammo_in_magazine() -– для стволов. Вернуть количество патронов в магазине

void set_ammo_elapsed(int) -- установить число патронов в магазине. Патроны берутся из ниоткуда, настройки ствола по максимальной вместимости магазина игнорируются, т.е. можно к примеру в ПМ зарядить 100 патронов

//

void unload_magazine() -- разрядить магазин. Точнее установить число патронов в магазине равным нулю. Патроны при этом пропадают. Это эквивалентно вызову set_ammo_elapsed(0)

 

void change_team(int, int, int);

int team() – команда

int squad() – отряд

int group() – группа

 

// состояние

float max_health() – максимальное здоровье

bool critically_wounded();

bool alive() –- живой или неживой

bool wounded() -- состояние ранености

void wounded(boolean) -- изменить состояние ранености

//

enum MovementManager::EPathType path_type()

void set_path_type(enum MovementManager::EPathType)

enum DetailPathManager::EDetailPathType detail_path_type()

void set_detail_path_type(enum DetailPathManager::EDetailPathType)

//

enum MonsterSpace::EMentalState mental_state()

enum MonsterSpace::EMentalState target_mental_state()

void set_mental_state(enum MonsterSpace::EMentalState);

//

enum MonsterSpace::EBodyState body_state()

enum MonsterSpace::EBodyState target_body_state()

void set_body_state(enum MonsterSpace::EBodyState);

//

enum MonsterSpace::EMovementType movement_type()

enum MonsterSpace::EMovementType target_movement_type()

void set_movement_type(enum MonsterSpace::EMovementType);

//movement

void movement_enabled(boolean); // enable_movement

bool movement_enabled();

//

void bind_object(object_binder*) -- установить биндер

object_binder* binded_object() -- вернуть текущий биндер

//

// возвращает специальные объекты

CCar* get_car()

CHelicopter* get_helicopter()

hanging_lamp* get_hanging_lamp()

// только для аномалий

float get_anomaly_power();

void set_anomaly_power(float);

void enable_anomaly();

void disable_anomaly();

//

bool weapon_strapped() -- только сталкеры. Показывает, что оружие висит за спиной (если есть)

bool weapon_unstrapped() -- только сталкеры. Значение, обратное weapon_strapped.

--Хотя логично было бы в случае отсутствия оружия показывать false

-- управление оружием выполняется через метод set_item

void restore_weapon() -- только для актора. Достать оружие из активного слота

void hide_weapon() -- только для актора. Прячет оружие.

// функции с memory в имени имеют отношение к "памяти" неписей. Можно получать

// разную информацию о том, что видел, слышал непись и т.п.

int memory_time(const game_object&);

vector memory_position(const game_object&);

// возвращают таблицу из объектов – можно итерировать как по таблице LUA.

<таблица объектов not_yet_visible_object> not_yet_visible_objects() -– объекты из памяти, которые видны или собираются стать видны.

<таблица объектов CVisibleObject> memory_visible_objects() –- объекты из памяти, которые видны или были когда-то видны.

<таблица объектов CSoundObject> memory_sound_objects() -– объекты из памяти, которые слышны или были когда-то слышны.

<таблица объектов CHitObject> memory_hit_objects() -– объекты из памяти, которые наносят или наносили повреждения.

void enable_memory_object(game_object*, bool enable) -- активизировать/деактивизировать объект из памяти

// почему аргументом стоит game_object? непонятно... вот старое описание из 1935

//void enable_memory_object(memory_object&, bool enable) – активизировать/деактивизировать объект из памяти

//

void set_trader_global_anim(string);

void set_trader_sound(string, string);

void set_trader_head_anim(string);

//

game_object* best_item() –- возвращает объект

game_object* best_enemy() -– возвращает объект

danger_object* best_danger();

game_object* best_weapon() -- получить от объекта его лучшее оружие (только для сталкеров)

 

//best_hit() – возвращает hit_memory_object -- теперь такого нет

//best_sound() – возвращает snd_type.object -- теперь такого нет

 

//

void info_add(string);

void info_clear();

bool has_info(string);

bool dont_has_info(string);

CTime* get_info_time(string);

 

bool give_info_portion(string);

bool disable_info_portion(string);

//

void set_tip_text(string) – установка строки-подсказки, которая будет показана при наведении на предмет курсором

void set_tip_text_default() – возвращение строки-подсказки по умолчанию

// анимация

int animation_count()

int animation_slot()

void clear_animations();

void add_animation(string);

void add_animation(string, boolean);

//

void play_sound(int);

void play_sound(int, int);

void play_sound(int, int, int);

void play_sound(int, int, int, int);

void play_sound(int, int, int, int, int);

void play_sound(int, int, int, int, int, int);

SoundInfo get_sound_info();

void remove_sound(int);

void active_sound_count();

int active_sound_count(boolean);

void set_sound_mask(int);

void add_sound(string, int, enum ESoundTypes, int, int, int);

void add_sound(string, int, enum ESoundTypes, int, int, int, string);

string sound_voice_prefix()

string sound_prefix()

void sound_prefix(string);

void external_sound_start(string);

void external_sound_stop();

//money

int money() -- сколько денег

void transfer_money(int <сколько>, game_object* <кому>) -- передать деньги

void give_money(int) -- дать денег

//

void transfer_item(game_object* <что>, game_object* <кому>) -- передать предмет

//

void enable_attachable_item(boolean) -- активирует батоны, гитару, радио, фонарики и пр.

bool attachable_item_enabled()

//

bool marked_dropped(game_object*);

void mark_item_dropped(game_object*);

void drop_item(game_object*);

void drop_item_and_teleport(game_object*, vector);

 

game_object* get_current_outfit() -- броня

float get_current_outfit_protection(int);

 

int character_reputation();

// void set_character_reputation(<int 0 to 100>); -- не определена (была в 1935)

void change_character_reputation(number);

int rank() -- получить ранг сталкера

int character_rank();

void set_character_rank(<int 0 to 100>);

string character_name(); -- внятно читаемое имя "Бес", "Волк", "Вася Пупкин"

string character_community(); -- группировка

void set_character_community(string, number, number); -- сменить группировку

enum ALife::ERelationType relation(game_object*) -– отношение в объекту (friend,neutral,enemy,dummy)

void set_relation(enum ALife::ERelationType <relation>, game_object* <who>) –- установка отношения

flags32 get_actor_relation_flags()

void set_actor_relation_flags(flags32)

int goodwill(game_object*) -- текущая благосклонность

void set_goodwill(<goodwill> int, game_object* <who>) -- установка благосклонности к кому-либо

void change_goodwill(int, game_object* <who>)

int general_goodwill(game_object*);

 

void jump(const vector&, float);

void explode(number) -– взорвать объект (гранаты, канистры, бочки и пр.)

 

 

vector bone_position(string <bone_name>) -– возвращает позицию кости.

void eat(game_object*); -- зажевать хавчик

//

holder* get_current_holder();

holder* get_holder_class(); // CHolderCustom* CScriptGameObject::get_custom_holder();

//

physics_shell* get_physics_shell() -- получить физическую оболочку. Можно работать с отдельными костями и сочленениями.

vector get_current_direction();

//hit

MonsterHitInfo get_monster_hit_info()

string who_hit_section_name() -– возвращает имя секции того типа объектов, нанесших последний хит

string who_hit_name() -– возвращает имя объекта, нанесшего последний хит

void hit(hit*) -– нанести хит

 

vector head_orientation()

 

game_object* get_enemy()

//path

bool path_completed()

int location_on_path(float, vector*);

//patrol

void set_patrol_path(string, enum PatrolPathManager::EPatrolStartType, enum PatrolPathManager::EPatrolRouteType, boolean);

void patrol_path_make_inactual();

string patrol() –- получить текущий патрульный путь // char* CScriptGameObject::GetPatrolPathName();

//

void set_patrol_extrapolate_callback();

void set_patrol_extrapolate_callback(const function<boolean>&);

void set_patrol_extrapolate_callback(const function<boolean>&, object);

//sight -- устанавливает направление зрения неписей

CSightParams sight_params()

// куда смотрит

void set_sight(enum SightManager::ESightType, const vector*, number);

void set_sight(enum SightManager::ESightType, boolean, boolean);

void set_sight(enum SightManager::ESightType, const vector&, boolean);

void set_sight(enum SightManager::ESightType, const vector*);

// на кого смотрит

void set_sight(game_object*);

void set_sight(game_object*, boolean);

void set_sight(game_object*, boolean, boolean);

void set_sight(game_object*, boolean, boolean, boolean);

//

float extrapolate_length()

void extrapolate_length(float)

void play_cycle(string)

void play_cycle(string, boolean);

//

void set_start_dialog(string <dialog_id>) – установка приветственного диалога NPC

???? get_start_dialog() – текущий диалог (функция точно ничего не возвращает, вероятно, вызывает диалог при определённых условиях)

void restore_default_start_dialog() – восстановление диалога, заданного в профиле персонажа

//

int get_enemy_strength()

//

void show_condition(ini_file*, string);

void sell_condition(ini_file*, string);

void sell_condition(float, float);

void buy_condition(ini_file*, string);

void buy_condition(float, float);

//

void buy_supplies(ini_file*, string);

// для кровососа

void set_alien_control(boolean);

void set_manual_invisibility(boolean);

void set_invisible(boolean);

//

bool inside(const vector&, float)

bool inside(const vector&)

 

//function debug_planner(const action_planner*); -- нет такой функции

void set_const_force(const vector& <направление>, float <импульс>, int <время действия ????>);

//

void skip_transfer_enemy(boolean);

//

bool see(game_object* target) -– проверить, виден ли target тому, для кого вызываем метод

bool see(string “section_name”) -– проверить, видит ли заданный класс, в кавычках – имя секции из system.ltx

// это вроде как установка предмета, который непись держит в руках, хотя могу и ошибаться. Есть какая-то разница с attachable предметами

// я видел, как непись одновременно держит ствол и один из attachable предмет (рация, водка и пр.)

void set_item(enum MonsterSpace::EObjectAction);

void set_item(enum MonsterSpace::EObjectAction, game_object*);

void set_item(enum MonsterSpace::EObjectAction, game_object*, int);

void set_item(enum MonsterSpace::EObjectAction, game_object*, int, int);

//

void set_home(

string <путь патрулирования>,

float minr, -- какой-то радиус

float maxr, -- ещё какой-то радиус

boolean <агрессия>) -- вероятно задаёт агрессию

void remove_home() -- удаляет ограничение

-- комментарии к set_home/remove_home

-- у меня сложилось впечатление, что монстры управляются не смартами, а гораздо проще. Им просто задаётся патрульный путь и вроде как необходимость агрессии при приближении сталкеров. Возможно, два радиуса как раз и управляют поведением в случае агрессии (но я не уверен). Во всяком случае при выполнении remove_home монстры у меня начинали разбегаться по всей локации.

// restrictions

bool active_zone_contact(int);

//

string out_restrictions() -- даёт список имён рестрикторов через запятую или пустую строку. Хотя название функции и переводится "ограничения", всё равно непонятно, может ли вообще быть больше одного рестриктора.

string in_restrictions() -- эта функция у меня вызывает вылет и для сталкеров и для монстров

string base_out_restrictions() -- исходные рестрикторы. Если убрать или добавить рестрикторы, то так можно узнать исходные значения.

string base_in_restrictions() -- вылета не вызывает, но у меня возвращает непустую строку только для монстров. В строке - список аномалий. Рассуждая от противного - если out_restrictions - список рестрикторов "где надо быть", то in_restrictions похоже на список мест, куда "ходить не надо".

void remove_restrictions(string <out_restrictions_names>, string <in_restrictions_names>) -- вероятно убрать рестрикторы, заданные списком

void remove_all_restrictions() -- убрать все рестрикторы

void add_restrictions(string <out_restrictions_names>, string <in_restrictions_names>) -- аналогично, добавить рестрикторы списками

//

int action_count() -– возвращает количество action-ов в очереди

entity_action* action_by_index(<action_index>) -– получает по индексу action

entity_action* action() // GetCurrentAction –- получить текущую команду

void command(const entity_action*, boolean) -– отдать команду

void reset_action_queue();

//

void set_queue_size(int <queue_size>) – установить размер очереди при стрельбе из автоматического оружия

//

void berserk(); -- вопреки некоторым предположениям - это не наследие "Oblivion Lost", поскольку функция новая, а в 1935 её не было. Однако её назначение не вполне понятно.

//

bool is_body_turning()

//script

//void CScriptGameObject::SetNonscriptUsable(bool, );

void set_nonscript_usable(<true/false>) – разрешение/запрещение стандартных (нескриптовых)

-- действий над объектом (взять объект в интерфейс, говорить с персонажем и т.д.)

//char* CScriptGameObject::GetScriptControlName();

string get_script_name() –- получить имя скрипта

//bool CScriptGameObject::GetScriptControl();

bool get_script() -– получить скриптовый режим

//void CScriptGameObject::SetScriptControl(bool, char*, );

void script(boolean, <script_name>) -– установить в скриптовый режим // SetScriptControl(bool, char*, );

//bool CScriptGameObject::can_script_capture();

bool can_script_capture()

//для зомби

void fake_death_stand_up();

void fake_death_fall_down();

//

void set_fastcall(function *fun, obj) -- установка "быстрого" обработчика

-- fun - это функция, принимающая один аргумент

    bool function fun(obj)
    --   здесь obj - это объект, переданный ранее в функцию set_fastcall
    -- return <true/false> -- как только возвращает true, обработчик снимается
    end

-- после установки обработчика он начинает периодически вызываться, чем напоминает работу метода update биндера

-- вызывается до тех пор, пока не вернёт true.

-- отличия два:

-- первое, вызывается с периодом около 17-18 мс., что более чем в два раза чаще, чем update в самом лучшем случае

-- (напоминаю, что update вызывается с периодом около 40 мс. при нахождении предмета в

-- инвентаре актора или для самого актора)

-- второе, период вызова не зависит от расстояния до актора, как это происходит в случае с update.

//

void give_game_news( -- выдать на экран сообщение

string, -- само сообщение. Может содержать некоторые форматирующие элементы

string, --текстура, из которой берется аватара "отправителя"

Frect, --координаты и размеры вырезаемого из текстуры изображения

int, --начало показа сообщения с текущего момента (в миллисекундах)

int --длительность показа сообщения (в миллисекундах)

)

//talk

void give_talk_message(string, string, Frect, string); // AddIconedTalkMessage

//talk

void enable_talk();

bool is_talk_enabled();

void disable_talk();

void stop_talk();

bool is_talking();

//trade

void enable_trade();

bool is_trade_enabled();

void disable_trade();

 

Во время разговора:

switch_to_trade() -- (для актора) переключение из режима диалога в режим торговли

switch_to_talk() -- (для актора) переключение из режима торговли в режим диалога

-----

run_talk_dialog(game_object* <to_who>) – (для актора) принудительный запуск диалога с актера с NPC, to_who – NPC персонаж

//point

void set_start_point(int);

void set_previous_point(int);

int get_current_point_index() -- только сталкеры. похоже на номер текущей точки патрулирования

//task (для актора)

//Состояние подцели

//<objective_state> = {task.fail, task.in_progress, task.completed, task.dummy}

void give_task(CGameTask*, int, boolean);

void set_task_state(enum ETaskState, string, int) -- если такого задания у актера нет, то выдаст ошибку

enum ETaskState get_task_state(string, int) -- если такого задания у актера нет, то вернется task.dummy

//

void set_callback(enum GameObject::ECallbackType, const function<void>&);

void set_callback(enum GameObject::ECallbackType, const function<void>&, object);

void set_callback(enum GameObject::ECallbackType);

//

game_object* get_corpse();

 

void set_dest_level_vertex_id(int) -- установить вертекс уровня, куда надо идти.

cover_point* best_cover(

vector <self_position>,

vector <enemy_position>,

float <radius>,

float <min_enemy_distance>,

float <max_enemy_distance> ) – возвращает лучшую точку прикрытия от врага

 

cover_point* safe_cover(const vector&, float, float);

 

int vertex_in_direction(number, vector, number) ;

// если судить по описанию из 1935 возвращает два значения, возможно второе - булевское

int accessible_nearest(const vector&, vector&) // u32,result accessible_nearest(const Fvector &position, Fvector &result)

bool accessible(const vector&);

bool accessible(level_vertex_id);

//

void make_object_visible_somewhen(game_object*);

//vision

void enable_vision(boolean);

bool vision_enabled()

 

//управляют порогами страбатывания

void set_sound_threshold(float);

void restore_sound_threshold();

//

void set_default_panic_threshold();

void set_custom_panic_threshold(float);

//

float visibility_threshold() -– получить пороговое значение.

// Если not_yet_visible_object::value имеет значение >= этого, то объект считается видимым.

void ignore_monster_threshold(float);

float ignore_monster_threshold()

void restore_ignore_monster_threshold();

//

float max_ignore_monster_distance()

void max_ignore_monster_distance(float);

void restore_max_ignore_monster_distance();

 

 

float level_vertex_light(const number&) -- начиная с ЧН исчезла

//

void set_enemy_callback();

void set_enemy_callback(const function<boolean>&);

void set_enemy_callback(const function<boolean>&, object);

//

void set_desired_direction()

void set_desired_direction(vector*)

void set_desired_position();

void set_desired_position(vector*);

//

action_planner* motivation_action_manager(game_object*);

};

 

 

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600

[spoiler=Регистрация скриптовых классов с помощью object_factory]Класс object_factory предназначен для регистрации пользовательских скриптовых классов. Конструктора у этого класса нет, нет и функции, с помощью которой можно его получить (по крайней мере я не знаю такой). Экземпляр этого класса доступен исключительно в качестве аргумента специальных функций, которые вызываются движком в определённое время. Функции эти прописаны в config\script.ltx в параметре class_registrators в секции common.

Например там прописана class_registrator.register. Эта функция вызывается в самом начале один раз ещё при старте программы.

В функцию register движок передаёт один аргумент типа object_factory. Здесь как раз и можно зарегистрировать свой скриптовый класс.

У объекта object_factory два метода. Один принимает четыре аргумента строкового типа и предназначен для регистрации связки серверный-клиентский классы + идентификатор. Второй метод принимает три аргумента строкового типа и предназначен для регистрации чисто клиентских классов.

class object_factory {
    // регистрация связки клиентский класс / серверный класс
    void register(
        <клиентский_класс>, <скриптовый_серверный_класс>, 
        <clsid_для конфигов>, <clsid_для_скриптов>); 
    // регистрация чисто клиентских классов
    void register(
        <скриптовый_клиентский_класс>, 
        <clsid_для конфигов>, <clsid_для_скриптов>); 
};

Чтобы объяснить, зачем нужен этот класс требуется немного лирики. Допустим, мы хотим создать некий объект (вертолёт к примеру). Игровые объекты вообще создаются с помощью метода create класса alife_simulator (см. ранее статью про этот класс). Первым и главным аргументов этого метода является имя секции в system.ltx. Даже если объект создаётся по индексу из all.spawn всё равно имя секции неявно присутствует (прописана в самом all.spawn). Что такое секция? Это просто именованный набор параметров, это все знают. Однако вопрос, как движок определяет, какой именно класс создаётся для конкретно этой секции? Есть мнение, что для этого там есть параметр class. Например, в секции вертолёта этот параметр имеет такое значение:

class = C_HLCP_S

Как теперь можно представить себе последовательность создания объекта:

1. В функцию create передаётся имя секции (например для вертолёта имя секции будет "helicopter")

2. Движок читает секцию и находит там параметр class (для вертолёта имеет значение C_HLCP_S). Теперь известно, объект какого класса надо создать.

3. Движок создаёт объект нужного класса и инициализирует его значениями, полученными из секции. Если набор параметров не соответствует созданному классу (т.е. входит в противоречие с clsid), то будет ошибка при создании.

После создания объекта ему назначается id и ссылка на него передаётся как результат работы функции create.

4. Всё это происходит на серверной стороне. Ещё один момент - это какой клиентский объект создать при переходе объекта в онлайн. Эта информация также должна быть связана с соответствующим clsid.

 

Если теперь проверить идентификатор класса свежесозданного объекта с помощью метода серверного класса cobj:clsid(), то мы получим некое число. Это число соответствует одной из констант класса clsid (для вертолёта это clsid.helicopter). С другой стороны это же число можно получить, прочитав значение параметра class из секции с помощью метода r_clsid() класса ini_file. Конкретное число не важно. Более того, вредно использовать конкретное число, поскольку оно может измениться при добавлении новых классов. Надо использовать вот эти идентификаторы.

 

Вроде как всё понятно. В движке имеется набор зарегистрированных сочетаний <серверный класс>/<клиентский класс>. Каждому такому сочетанию присвоен уникальный номер - идентификатор класса. Этот номер в секции файла system.ltx указывается через строковый идентификатор, а в коде ему же соответствует один из членов класса clsid. Набор этих сочетаний имеется в движке исходно, даже если ничего не делать.

 

К чему теперь объект object_factory? А он позволяет добавлять в этот набор свои пары, причём серверный класс там можно указывать свой, скриптовый (собственно ради чего это и делается). С помощью скриптового класса, унаследованного от одного из серверных классов, можно добавить на серверную сторону дополнительный функционал, такой как:

1. сохранение на серверной стороне дополнительных данных. Это происходит примерно так же, как сохранение в биндере.

2. можно дополнить или изменить некоторые аспекты поведения серверного класса. Например, можно изменить правила перехода в онлайн/оффлайн.

Пример реализации такого серверного класса (для того же вертолёта, в этом случае он создаётся на основе класса cse_alife_helicopter) можно посмотреть в файле se_car.script (он там правда весьма короткий и ненаглядный). Имя скриптового класса - se_heli. Имя клиентского класса для вертолёта - CHelicopter. Это один из классов, унаследованных от CGameObject. Создавать на их основе свои скриптовые нельзя.

 

Теперь можно посмотреть пример применения этого класса в модуле class_registrator.script. Выглядит это так (с сокращениями):

function cs_register(factory,client_object_class,server_object_class,clsid,script_clsid)
    factory:register    (client_object_class,server_object_class,clsid,script_clsid)
end

function c_register(factory,client_object_class,clsid,script_clsid)
    if (editor() == false) then
        factory:register(client_object_class,clsid,script_clsid)
    end
end
function register(object_factory) 
    c_register(object_factory, "ui_main_menu.main_menu", "MAIN_MNU", "main_menu")
    ...
    cs_register    (object_factory, "CHelicopter", "se_car.se_heli", "C_HLCP_S", "script_heli")
    ...
end

Здесь видим, что методы класса вызываются опосредованно с помощью двух вспомогательных функций:

c_register - для чисто клиентских классов

cs_register - для связки клиентский/серверный

Сделано это скорее всего для наглядности, и никто не мешает в функции register непосредственно использовать методы object_factory.

 

Здесь мы видим, что скриптовый серверный класс se_car.se_heli, связывается с клиентским классом CHelicopter, для указания этой связки в секциях system.ltx задаётся строковый идентификатор "C_HLCP_S" , а для регистрации его в классе clsid ему назначается имя script_heli.

 

Собственно и всё. Эта регистрация происходит в самом начале, ещё до запуска главного меню. После выполнения модуля класс clsid прирастает новыми константами, а при создании соответствующих объектов получают управление соответствующие зарегистрированные скриптовые классы.

 

Что касается варианта для регистрации чисто клиентских скриптовых классов. Единственный пример использования класса object_factory таким образом показан в коде выше. Это регистрация класса для главного меню. Окна, ясное дело, существуют только на клиентской стороне. Кроме того, идентификатор для меню используется уже знакомый системе - он вшит в движок. Можно ли использовать это как-либо иначе - неизвестно.

 

Несколько ремарок:

1. Идентификаторы для создаваемой связки могут быть любыми. Но если они равны существующим значениям, то регистрируемая связка заменяет существующую.

2. Естественно не получится комбинировать клиентский и серверный классы в произвольных сочетаниях. Они друг другу должны соответствовать. Кроме того, должен быть соответствующий экспортированный клиентский класс, а они имеются далеко не для всех игровых объектов. Например, для актора нет (появился только в CS). Учитывая это и изучив содержимое class_registrator.script, можно сделать вывод, что ресурс применения объекта object_factory практически исчерпан, поскольку почти все объекты, производные от CGameObject там уже задействованы. Впрочем, можно создавать какие-либо хитрые специализации уже знакомых объектов.

 

Edited by malandrinus
  • Like 1

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites

Nekt, Спасибо :)...

malandrinus, желаю приятной поездки...

Ну и чтобы как-то разбудить тему расписал малюсенький класс.Мало кто им пользуется(что ИМХО плохо).Этот класс позволяет хранить данные в конфиг-файлах(что даёт возможность пользователям более легко править под себя параметры и т.д.).Раньше я расписывал некоторые методы класса,но всё таки.

C++ class ini_file {

ini_file (string); --создать объект класса ini_file/за место string прописываем путь до файла

 

function line_count(string); --возвращает количество ключей в секции string/ return ini:line_count("my_section")

function r_bool(string, string); --возвращает логическое(булево) значение/(секция,ключ)

function section_exist(string); --"проверка" существует ли секция string/if ini:section_exist("my_section")

function r_float(string, string); --возвращает значение с плавающей точкой

function r_clsid(string, string);*********************

function r_s32(string, string); --возвращает целое число

function r_line(ini_file*, string, number, string&, string&); --возвращает полностью "линию" т.е. key1 = value1

function r_token(string, string, const token_list&); **********

function r_vector(string, string);********

function r_u32(string, string);--возвращает целое число(чуть-чуть медленне чем s32)

function r_string_wq(string, string);--возвращает строку

function r_string(string, string);--возвращает строку

function line_exist(string, string);--"проверка" существует ли ключ /if ini:line_exist("my_section","key1")

};

 

К сожалению, я не подписал всех методов.В скором времени исправлюсь...

Edited by меченый(стрелок)

Share this post


Link to post
Share on other sites
Malandrinus    600

Небольшой апдейт. Сегодня разберём глобальные фунции или иначе функции из пространств имён. Такая функция в любом месте вызывается следующим образом:

<пространство имён>.<имя функции>(<список аргументов>)

Пространства имён - это всего лишь способ сгруппировать функции по некоторому признаку. Хотя надо заметить, что логика группировки функций в сталкере иной раз не поддаётся объяснению.

Всего есть несколько пространств имён: безымянное (т.е. функции их него вызываются просто по именам как обычные глобальные функции), game, level, relation_registry, actor_stats. В ЧН (и ЗП) появилось пространство имён main_menu, кроме того есть незначительные изменения в остальных: некоторые функции исчезли, некоторые добавлены. Изменения однако незначительны.

Сейчас рассматриваем только три основных пространства имён (безымянное, game, level) и только для ТЧ.

 

-- функции, которые не работают или вызывают вылеты или непонятно назначение

function log(string) -- видимо для дебаговой версии

function error_log(string) -- видимо для дебаговой версии

function flush() -- видимо для дебаговой версии

bool app_ready() -- готовность чего-то к чему-то =)

-- пока игра не загружена, у меня возвращает false, после загрузки - true

bool editor() -- под редактором??? Это то, чего нам как ушей не видать???

int script_server_object_version() -- число, знать бы что означает

function verify_if_thread_is_running()

function xrRender_test_r2_hw()

------------- получение некоторых глобальных объектов --------------------------------------

alife_simulator* alife() -- см. описание alife_simulator

render_device* device() -- см. описание render_device

CGameGraph* game_graph() -- получение глобального графа игры

CConsole* get_console() -- получение объекта для управления консолью. Это и так все знают

CUIGameCustom* get_hud() -- получение объекта для управления худом.

-- Можно добавлять к худу свои окошки и элементы управления. Так делают всякие индикаторы.

FS* getFS() -- файловые операции. см. описание FS

string command_line() -- командная строка, включая полный путь к исполняемому файлу

string user_name() -- сетевое имя игрока (вроде бы имя активного пользователя в системе)

cef_storage* ef_storage() -- хз что такое

-------------- работа с файлами конфигурации

ini_file* create_ini_file(string) -- создаёт из строки объект типа ini_file в памяти

ini_file* game_ini() -- открытый файл "config\game.ltx" и все его вложения

ini_file* system_ini() -- открытый файл "config\system.ltx" и все его вложения

------------ побитовые операции --------------------------------------

function bit_and(number, number)

function bit_not(number)

function bit_or(number, number)

function bit_xor(number, number)

function dik_to_bind(number) -- я подозреваю, что преобразует константу

-- из набора DIK_keys в константу из набора key_bindings

DWORD GetARGB(number, number, number, number) -- формирует 32-х разрядное целое из

-- 4-х отдельных байт. для задания цвета. Где используется - точно не известно.

------------ всякий мелкий утиль -----------------

void prefetch(string <имя модуля>) -- вызывает загрузку и прекомпиляцию модуля.

-- Имя модуля указывается без расширения ".script"

int time_global() -- реальное время (в миллисекундах) с начала запуска программы

---------------------------------------------------------------------------

void buy_condition(ini_file*, string <имя секции>) -- прочитать настройки торговца

-- на покупку из файла конфигурации и определённой секции.

-- Только непонятно, для кого читаются эти настройки

void buy_condition(float, float) -- непонятная функция.

void sell_condition(ini_file*, string <имя секции>) -- аналогично на продажу

void sell_condition(float, float) -- непонятная функция

void show_condition(ini_file*, string <имя секции>) -- совсем непонятно

--

-- далее описанные функции позволяют преобразовать ссылку на базовый класс

-- в ссылку на производный для получения доступа к методам производного класса

-- за деталями надо обращаться в описание соответствущих иерархий.

-- надеюсь, руки до этого дойдут

action_planner* cast_action_to_planner(action_base*)

action_base* cast_planner_to_action(action_planner*)

cse_alife_creature_abstract* cast_alife_object_to_creature(cse_alife_object*)

cse_alife_trader_abstract* cast_alife_object_to_trader_abstract(cse_alife_object*)

--------------------- получение встроенных шрифтов ---------------------

CGameFont* GetFontDI()

CGameFont* GetFontGraffiti19Russian()

CGameFont* GetFontGraffiti22Russian()

CGameFont* GetFontGraffiti32Russian()

CGameFont* GetFontGraffiti50Russian()

CGameFont* GetFontLetterica16Russian()

CGameFont* GetFontLetterica18Russian()

CGameFont* GetFontLetterica25()

CGameFont* GetFontMedium()

CGameFont* GetFontSmall()

--------- некая информация о текстурах. В ЗП эти функции убраны

CGameFont* GetTextureInfo(string, string)

CGameFont* GetTextureName(string)

CGameFont* GetTextureRect(string)

[spoiler=Пространство имён game]CTime* get_game_time() -- возвращает игровое время в виде объекта класса CTime

void start_tutorial(string) -- запускает туториал, зарегистрированный

-- в файле ui\game_tutorials.xml. В частности, на туториалах сделаны сны

bool has_active_tutorial() -- запущен ли туториал

int time() -- игровое время (в игровых миллисекундах) с начала игры (т.е. с начала прохождения игры)

string translate_string(string) -- получает вменяемую строку по её строковому идентификатору

-- из одного из xml файлов, прописанных в файле \config\localization.ltx в секции

-- [string_table] в параметре files

-- если там такой строки нет, то возвращает свой аргумент - исходную строку

[spoiler=Пространство имён level]-- функции неопределённые или вызывающие непонятные вылеты

//function check_object(game_object*)-- выглядит хитро. Как бы поле level.check_object есть

-- и тип его "function"

-- однако значение этого поля nil и вызов его невозможен. Т.е. такой функции на самом деле нет

-- Видимо была в отладочной версии

function debug_actor() -- аналогично check_object. Нет такой функции

function debug_object(string) -- аналогично check_object. Нет такой функции

function physics_world() -- в ТЧ - вылет

function environment() -- в ТЧ - вылет

---------------- вылета не вызывают, но назначение неясно ---------------------------

int game_id() -- возвращает некое число (у меня 1). Вероятно для мультиплея.

client_spawn_manager* client_spawn_manager() -- возвращает некий объект,

-- с помощью которого можно ставить некие коллбеки на спаун. В игре нигде не используется

---------------- информация об уровне в целом, управление уровнем в целом -----------

bool present() -- наличие уровня. Так можно определять, что игра загружена.

-- Т.е. если мы в главном меню и игра не загружена, то функция возвращает false

string name() -- имя текущего уровня, точнее его идентификатор

Fbox* get_bounding_volume() -- даёт некий параллелепипед, видимо охватывающий уровень

------------------ коллбеки ----------------------------------------

void add_call(const function<boolean>&, const function<void>&)

-- устанавливаются два коллбека:

-- первый. Аргументов нет, вызывается периодически непосредственно после апдейта актора

-- должен возвращать false до тех пор, пока не решит прекратить работу.

-- второй. Аргументов нет, вызывается один раз сразу после последнего вызова

-- первого коллбека (после того, как тот вернёт true)

-- поскольку частота вызовов совпадает с апдейтами актора, то можно сделать вывод,

-- что коллбек ставится на актора

void remove_call(const function<boolean>&, const function<void>&) -- по идее должно убирать коллбеки, но как-то не работает

void add_call(object, const function<boolean>&, const function<void>&) -- аналогично для

-- произвольного объекта, но теперь в качестве первого аргумента колбека при его вызове

-- передаётся тот объект, на который его ставили

void remove_call(object, const function<boolean>&, const function<void>&)

--

void add_call(object, string, string) -- непонятно, как работает. Пытался передать имя функции,

-- но это не сработало.

void remove_call(object, string, string)

--

void remove_calls_for_object(object)

---------------- эффекторы ---------------------------------------------------

float add_cam_effector(

string <имя анимации>, -- имя файла *.anm, адресуемого от папки anims

int <идентификатор>, -- произвольное целое число, которое можно использовать для удаления

boolean <зациклить>, -- проигрывать бесконечно

string <коллбек на окончание>) -- имя функции, которая выполнится по окончании

-- действия эффекта. Функция не принимает аргументов и не возвращает значений.

-- Не вызывается при принудительном завершении эффектора функцией remove_cam_effector.

-- Функция возвращает некое число, для каждого файла анимации своё. Зачем нужно - не знаю.

float add_cam_effector2(string, number, boolean, string) -- в целом тоже самое,

-- что и предыдущая функция. Видимая разница в том, что предыдущая смещала позицию камеры

-- от текущего положения актора, а эта сначала перемещает камеру актора в некую стартовую позицию.

void remove_cam_effector(number <идентификатор>) -- убрать эффектор с ранее заданным номером

--

void add_pp_effector(

string <имя постэффекта>, -- имя файла *.ppe, адресуемого от папки anims

int <произвольный идентификатор>, -- для дальнейшего удаления

boolean <зациклить>) -- проигрывать бесконечно

void set_pp_effector_factor(

int <идентификатор>, -- число, ранее заданное при установке эффекта

float <интенсивность>) -- (0, 1) -- интенсивность эффекта

void set_pp_effector_factor(

int <идентификатор>,

float <интенсивность>,

number <скорость изменения>) -- не до конца ясно, но вроде как скорость перехода

-- от текущего состояния к заданному. По какому принципу считается время перехода - непонятно

void remove_pp_effector(int <идентификатор>) -- убрать эффект

--

-- следующие две функции практически не используются. Задействованы незначительно только в ЗП

void add_complex_effector(

string <секция>, -- имя секции в system.ltx, с описанием эффекта

int <идентификатор>)

void remove_complex_effector(int <идентификатор>) -- отмена эффектора

----------------------- функции времени --------------------------------

int get_time_days() -- день месяца по игровому времени

int get_time_hours() -- час текущего игрового дня

int get_time_minutes() -- минута текущего игрового часа

float get_time_factor() -- возвращает отношение скорости течения игрового времени к скорости реального (game_time_speed / real_time_speed)

void set_time_factor(number) -- устанавливает это отношение. Фактор времени в целом

-- не сказывается на физике мира. Т.е. скорость движения объектов остаётся естественной.

-- Пули летят с той же скоростью, что и раньше. Люди и монстры бегают как и раньше.

-- На чём точно сказывается фактор времени - это на смене дня и ночи. Установив его

-- очень большим можно воочию наблюдать смену дня и ночи в течении пары минут.

------------------ погода ----------------------------------------------------

string get_weather() -- получить идентификатор текущей погоды

void set_weather(string <weather>, boolean <now>) -- устанавливает погоду. Идентификатор погоды

-- должен присутствовать в файле config\weathers\environment.ltx в сеции [weathers] иначе вылет

-- второй параметр отвечает за немедленную смену погоды

-- если true - меняем прямо сейчас

-- если false - то непонятно. У меня также менялось сразу.

void set_weather_fx(string) -- устанавливает погодный эффект,

-- описанный в файле weathers\environment.ltx в секции [weather_effects]. Это всякие молнии,

-- ветер с туманом, затемнения и пр. Так в частности сделан выброс

bool is_wfx_playing() -- соответственно проигрывается ли сейчас погодный эффект

float rain_factor() -- степень дождливости. Зависит от выбранной погоды. Меняется видимо автоматически

-------------------------- HUD и интерфейс ---------------------------------------

function start_stop_menu(CUIDialogWnd*, boolean) -- в ЗП убрана

-- Эти функции практически не используются. Открытие окон делается через объект худа

function add_dialog_to_render(CUIDialogWnd*)

function remove_dialog_to_render(CUIDialogWnd*)

CUIWindow* main_input_receiver() -- Возвращает открытое окно(инвентаря, торговли, ПДА, диалога)

-- С ее помощью к стандартным окнам можно добавить новые элементы. В ЗП отсутствует!

-- (спасибо Колмогору за дополнение)

--

void show_indicators() -- показывает индикаторы и прицел

void hide_indicators() -- прячет индикаторы и прицел

void disable_input() -- блокирует мышь и клавиатуру

void enable_input() -- разблокирует мышь и клавиатуру

float get_snd_volume() -- уровень звука (0,1) с ползунками в опциях не связано

void set_snd_volume(number) -- установить уровень звука (0,1)

ESingleGameDifficulty get_game_difficulty() -- сложность игры, с ползунками в опциях не связано

void set_game_difficulty(enum ESingleGameDifficulty) -- установить сложность игры

-- Перечисление ESingleGameDifficulty экспортировано как класс game_difficulty

-- Его описание

C++ class game_difficulty {

const novice = 0;

const stalker = 1;

const veteran = 2;

const master = 3;

}

-- т.е. вызываем функцию так: level.set_game_difficulty(game_difficulty.veteran)

---------------------- работа с разными объектами на уровне --------------

game_object* object_by_id(number) -- клиентский объект по его id (или nil,

-- если объект не в онлайне или не существует)

void spawn_phantom(const vector&) -- создаёт фантомного монстра в указанной точке (как на радаре)

-- фантомный мостр начинает бежать к актору и добежав - исчезает

function patrol_path_exists(string)

function vertex_in_direction(number, vector, number)

vector vertex_position(number) -- положение левел-вертекса по его номеру

-- на каждом уровне свои вертексы. Нумерация на разных уровнях может

-- (и вообще говоря обязательно будет) пересекаться

function cover_in_direction(number, const vector&)

-------------------- управление картой -----------------------------------

void map_add_object_spot( -- установить метку

int <id>, -- идентификатор объекта

string <тип метки>, -- имя типа метки, зарегистрированное в файле config\ui\map_spots.xml и всех, что в него включены инклюдами

string <надпись>) -- надпись

 

void map_add_object_spot_ser(int <id>, string <тип метки>, string <надпись>) -- разница

-- между этой и предыдущей функцией в том, что вторая ставит метку постоянно,

-- или иными словами на серверный объект (потому и _ser в конце). При вызове вы сначала не заметите разницы,

-- поскольку и та и другая функция поставят метку как на объект на текущем уровне, так и на любом другом.

-- Однако после сохранения и загрузки метки, поставленные второй функцией останутся.

-- Очевидно, каждая из функций полезна в своём случае: постоянные метки нужно ставить на тайники и квесты,

-- а временные можно использовать к примеру для радаров и прочего в этом роде.

 

void map_change_spot_hint(int <id>, string <тип метки>, string <надпись>) -- сменить надпись на метке

bool map_has_object_spot(int <id>, string <тип метки>) -- проверить наличие метки

void map_remove_object_spot(int <id>, string <тип метки>) -- удалить метку

--------------------------------------------------------------------------

function iterate_sounds(string, number, function<void>)

function iterate_sounds(string, number, object, function<void>)

function prefetch_sound(string)

 

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites
Malandrinus    600

Чуть дополнил описание game_object (см. пост ранее). Продублирую изменения в этом посте

[spoiler=дополнения к описанию game_object]

int get_ammo_total() – функция вызывается для оружия. Если ствол не в инвентаре, то возвращает количество патронов в обойме. Если в инвентаре, то возвращает суммарное количество доступных для этого ствола патронов как в его обойме, так и в том-же инвентаре.

void unload_magazine() -- разрядить магазин. Точнее установить число патронов в магазине равным нулю. Патроны при этом пропадают. Это эквивалентно вызову set_ammo_elapsed(0)

set_ammo_elapsed(int) -- установить число патронов в магазине. Патроны берутся из ниоткуда, настройки ствола по максимальной вместимости магазина игнорируются, т.е. можно к примеру в ПМ зарядить 100 патронов.

 

bool weapon_strapped() -- только сталкеры. Показывает, что оружие висит за спиной (если есть)

bool weapon_unstrapped() -- только сталкеры. Значение, обратное weapon_strapped.

--Хотя логично было бы в случае отсутствия оружия показывать false

-- управление оружием выполняется через метод set_item

void restore_weapon() -- только для актора. Достать оружие из активного слота

void hide_weapon() -- только для актора. Прячет оружие.

 

string out_restrictions() -- даёт список имён рестрикторов через запятую или пустую строку. Хотя название функции и переводится "ограничения", всё равно непонятно, может ли вообще быть больше одного рестриктора.

string base_out_restrictions() -- исходные рестрикторы.

-- Если убрать или добавить рестрикторы, то так можно узнать исходные значения.

string in_restrictions() -- эта функция у меня вызывает вылет и для сталкеров и для монстров

string base_in_restrictions() -- вылета не вызывает, но у меня возвращает непустую строку только для монстров. В строке - список аномалий. Рассуждая от противного - если out_restrictions - список рестрикторов "где надо быть", то in_restrictions похоже на список мест, куда "ходить не надо".

void remove_restrictions(string <out_restrictions_names>, string <in_restrictions_names>) -- вероятно убрать рестрикторы, заданные списком

void set_home(

string <путь патрулирования>,

float minr, -- какой-то радиус

float maxr, -- ещё какой-то радиус

boolean <агрессия>) -- вероятно задаёт агрессию

void remove_home() -- удаляет ограничение

-- комментарии к set_home/remove_home

-- у меня сложилось впечатление, что монстры управляются не смартами, а гораздо проще. Им просто задаётся патрульный путь и вроде как необходимость агрессии при приближении сталкеров. Возможно, два радиуса как раз и управляют поведением в случае агрессии (но я не уверен). Во всяком случае при выполнении remove_home монстры у меня начинали разбегаться по всей локации.

 

int get_current_point_index() -- только сталкеры. похоже на номер текущей точки патрулирования

 

void set_dest_level_vertex_id(int) -- только сталкеры. вертекс уровня, куда надо идти.

 

 

Edited by malandrinus

 

Плагины Total Commander для работы с игровыми архивами:

Архиваторный плагин (для работы с одиночным архивом): link1 link2

Системный плагин (для распаковки установленной игры): link1 link2

 

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


  • Recently Browsing   0 members

    No registered users viewing this page.

AMK-Team.ru

×
×
  • Create New...