Malandrinus 615 Опубликовано 8 Июля 2009 (изменено) В данной теме собраны сведения по скриптовой модели сталкера: функции и классы, методы и свойства, взаимосвязь классов и последовательность работы с ними, связь работы классов и файлов конфигураций. К наполнению темы приглашаются все желающие. В наполнении темы непосредственно участвовали и существенно мне помогли: @Monnoroch, @Kolmogor, @Unnamed Black Wolf, @меченый(стрелок), @IQDDD, @Kirag, @Taroz, @dan, @7.9, @Garry_Galler, @AKKK1, @Bak и много других людей. Оглавление (Показать) Основы: (Показать) класс alife_simulator. Базовые операции с серверными объектами. Пространства имён. Глобальные функции для большого числа задач. "Создание своего класса" и "Наследование от экспортированных классов". Базовые сведения об объектно-ориентированном программировании для сталкера. Необходимо прочитать, для понимания темы про биндер и некоторых других. В одном посте: Общие слова об архитектуре и скриптовой модели сталкера "Класс object_binder" расширение онлайновых объектов, колбеки, сохранение состояния. "Класс net_packet" Регистрация скриптовых классов с помощью object_factory Серверные классы. Часть 1 Иерархия серверных классов, описание не закончено. Серверные классы. Часть 2 Картинка структуры наследования и несколько заключительных слов Клиентские классы Непосредственное управление игровыми объектами: (Показать) Класс game_object Интерфейс ко всем онлайновым (клиентским объектам) Класс hit для нанесения урона скриптом и другая информация (IQDDD) Некоторая информация по управлению путями патрулирования здесь (Kirag) и здесь (Taroz) Неполная информация по управлению памятью неписей с примером здесь (Bak) Физическая оболочка объектов (Garry_Galler) Пост о выборе (подборе) оружия НПС и стрельбы (*Shoker*) Всё, что относится к управлению сюжетом: (Показать) Управление заданиями Класс CGameTask и другие вспомогательные классы и функции. Управление инфопорциями Функции, колбеки, форматы файлов Список специальных системных инфопорций (Unnamed Black Wolf) Система профилей и алгоритм генерации имён. Форматы файлов, функции Дополнительная информация по параметрам профилей terrain_sect (Kolmogor) Диалоги. Часть 1 Форматы файлов, базовые сведения Диалоги. Часть 2 Скриптовые диалоги Диалоги. Часть 3 Тематическая подборка функций управления диалогами Управление игровым временем: (Показать) Функции времени Тематическая подборка функций, связанных с управлением игровым временем. Класс CTime Вспомогательный класс для управления игровым временем Полезная скриптовая функция с использованием CTime (Garry_Galler) Информация россыпью: (Показать) class ini_file (меченый(стрелок)) Класс FS и CSavedGameWrapper Бинарный доступ к файлам, в том числе в игровых архивах, управление сохранёнными играми. Скрипт уровня. Забытая фишка с колбеком на заход на уровень Класс vector Некоторая полезная информация о разных вещах (меченый(стрелок)) "Класс render_device" Направление и положение камеры, характеристики экрана, программная пауза игры и др. Некоторая информация о различиях между ТЧ и ЗП в системе оконных классов и колбеков. (lekzd) Неплохо бы развить эту тему! Некоторая полезная информация о скриптовых функциях из модуля _g.script. (lekzd) Также требует развития! Полезные функции для работы с графом игры (Garry_Galler) В одном посте: Класс profile_timer Отладочные измерения скорости работы фрагментов программы Класс client_spawn_manager Колбек на выход в онлайн без использования биндера. Работа с консолью. Класс CConsole Анимации цвета. Класс color_animator Всякие моргающие элементы в окнах и пр. Управление постэффектами. Скриптовые постэффекты. Класс effector Класс sound_object. Проигрывание звуков в игре в произвольном месте, от произвольного объекта, в голове актора. Создание и управление худом и пользовательским интерфейсом: (Показать) Оконные классы Некоторая общая информация о создании окон Список методов, XML-тегов и событий для оконных классов (ТЧ/ЧН/ЗП) (Norman Eisenherz) Представление материала в моих статьях оптимизировано для онлайнового просмотра. Если кому не хочется лазить по спойлерам, а нужно просмотреть текст какого-либо поста "потоком", то могу рекомендовать просмотр в режиме "текстовая версия". В этом же режиме удобно сохранять содержимое темы на диск. (прим. Malandrinus) Изменено 17 Февраля пользователем Опричник 5 5 16 Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 8 Июля 2009 (изменено) Ну тогда уж так: 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 комментарий (Показать) З.Ы.: Monnoroch писал(а): не пробовал false ставить?Есть мнение,что может тогда обьект не удалится... Не пробовал. Да и звучит не логично. Какой смысл в таком вызове? 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() -- получаем серверный объект для актораlocal sim = alife() -- получаем сам объект класса alife_simulator local sactor = sim:actor() -- получаем серверный объект для актора В придачу про класс vector. vector - вспомогательный класс, содержащий три координаты, и позволяющий выполнять с ними различные манипуляции. Объекты класса vector являются аргументами многих функций и возвращаются многими функциями. См. например выше про метод create класса alife_simulator. Отдельный объект класса вектор создаётся вызовом глобальной функции vector(). При создании имеет координаты [0,0,0]. Список методов класса vector (Показать) 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)local v1 = vector() v1:set(1,2,3) local v2 = vector():set(4,5,6) local dist = v1:distance_to(v2) Изменено 25 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 10 Июля 2009 (изменено) В языке Lua нет классов. Их поддержка добавлена с помощью технологии Luabind. С одной стороны - это конечно имитация "настоящих классов", с другой - расширение выглядит настолько органичным, что практически воспринимается как часть синтаксиса языка. Создание своего класса (Показать) Свой класс можно добавить так: class "my_cool_class"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-- это специальный метод - конструктор. Будет вызван при создании класса. 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()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(<список аргументов>) endfunction class_name:fun_name(<список аргументов>) end на самом деле эквивалентна конструкции function class_name.fun_name(self, <список аргументов>) endfunction 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-- конструктор 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() не требует вызова этого же метода для базового класса, поскольку тот будет вызван автоматически при удалении объекта сборщиком мусора. Собственно и всё. Теперь можно пользоваться созданным классом: создавать объекты этого класса, вызывать их методы и пр. Здесь уже всё зависит от конкретных целей и требует знания конкретных классов. Изменено 19 Июля 2024 пользователем Kirgudu 1 1 Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 11 Июля 2009 (изменено) RvP писал(а): выносите продвижения в шапку плиз Делать мегашапку пока рано, ещё даже одной страницы темы нет =) Monnoroch писал(а): а не мог бы ты дополнительно рассмотреть пару примеров собственно из сталкера? С такими же детальными пояснениями. На очереди 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() -- получили все значенияlocal t = game.CTime() -- создания объекта с дефолтовыми значнеиями t:set(2009, 7, 11, 8, 11, 22, 333) -- установили все значения local y,m,d,h,min,sec,ms = t:get() -- получили все значения Класс CTime разобран в этом посте Изменено 25 Ноября 2010 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 24 Июля 2009 Добил 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[alife] switch_distance = 150 switch_factor = 0.1 переход в онлайн произойдёт на расстоянии switch_distance * (1 - switch_factor) Данная статья посвящёна классу object_binder и, кроме того, является в каком-то смысле иллюстрацией к одной из предыдущих статей "Наследование от экспортированных классов". Описание класса 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*); };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) -- присобачиваем его к объекту endfunction 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.objectnet_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() - назначение неизвестно. Если его определяют, то как возвращающий всегда truenet_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. Теперь пачкой вызываются методы биндераreloadreinitload - если вообще вызывается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') endfunction 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. А вот для гранат похоже ничего не работает. Списка работающих коллбеков для каждого типа объектов пока ещё никто не составил. Теперь простой пример, который я сделал для ящика Содержимое файла my_cool_binder.script (Показать) 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") endfunction 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[inventory_box_my]:inventory_box script_binding = my_cool_binder.init это будет ящик с визуалом ящика с динамитом с одним только отличием от стандартного - у него будет нестандартный наш биндер. На очереди net_packet. Простой, но весьма важный класс. Описание класса 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&); };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) - устанавливает позицию чтения в posr_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)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 (Показать) Собственно, законное использование нетпакетов - это буфер, в котором объект сохраняет своё состояние. Скорее всего это используется в первую очередь при передаче данных по сети. Потому и 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() -- получим строку "моя строка" endclass "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()) -- записали в объект изменённое состояние обратно, имитируя процесс его загрузкиlocal packet = net_packet() -- создаём пустой пакет sobj:STATE_Write(packet) -- загрузили в наш пакет состояние серверного объекта -- используя методы класса net_packet меняем нужные нам значения. sobj:STATE_Read(packet, packet:w_tell()) -- записали в объект изменённое состояние обратно, имитируя процесс его загрузки Несколько замечаний: - Все эти манипуляции надо проводить над объектами в оффлайне, поскольку онлайновый объект регулярно обновляет серверную часть и полностью его переписывает, так что любые изменения такого рода будут потеряны. - Надо знать структуру нетпакета для того объекта, который Вы собираетесь изменить. При таких хирургических операциях запросто можно всё испортить. Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 2 Августа 2009 Несколько уточнений: Kirag писал(а): Этот класс отвечает за движение неписей по путям. Информацию о путях берет только из алл.спавна. Переопределить это нельзя без ковыряния в движке, откуда следует невозможность задавать пути извне алл.спавна. Для решения этой задачи надо как-то обойти класс patrol. Файл all.spawn можно дописывать, добавляя туда дополнительные точки переходов. Вопреки распространённому мнению, новую игру при этом начинать не надо. Цитата index(string) - нет ни примеров использования, ни предположений, что это могло бы значить. Вроде как это метод, обратный name. Т.е. по имени точки возвращает её номер. Цитата Как обрабатываются связи между точками, до конца не ясно, могу только предположить, что для этого служит одна из этих команд: patrol... Но которая, и что она возвращает, и что требует в качестве аргументов - непонятно. Это такие же конструкторы, как и patrol с одним только именем пути. Просто у них ещё есть дополнительные аргументы. И, как мне думается, за само движение отвечает класс entity_action, которому передаётся объект класса move, у которого patrol - это аргумент. Короче, копать и копать ещё, но начало неплохое. Там глядишь, и разберёмся, как свои скриптовые схемы ваять. Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 2 Августа 2009 (изменено) Kolmogor писал(а): Про новую игру при добавлении путей вроде никто и не говорил Про новую игру говорят при добавлении новых объектов, изменении старых Я добавил level_changer, доспавнил его функцией create(<номер>) и ничего мне за это не было =) Если кому это надо, то добавлю вот что. Я делал тестовый объект на кордоне, и сначала попробовал добавить секцию в файл для кордона. Выяснил, сколько всего объектов, сделал секцию с номером, на один большим... У меня заспавнился не тот. Начал выяснять, откуда он взялся, и понял, что это последний объект из последнего уровня (ящик какой-то). Тогда я просто добавил новый объект в конец последнего уровня, и все получилось. Мораль, номер объекта из all.spawn - это просто его номер в порядке компиляции. Зачем acdc разбирает объекты по уровням, не знаю. Но на мой взгляд, после этого они перетасованы. Если каждый раз начинать игру заново, то это и без разницы. Но если мы хотим использовать пересобранный all.spawn не начиная игру заново, то видимо acdc не годится. Изменено 2 Августа 2009 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 10 Августа 2009 (изменено) В качестве разминки разберём класс render_device. Один из тех классов, про который должен знать каждый модостроитель, а между тем практически незамеченный. Описание класса render_device (Показать) Класс render_device позволяет получить свойства экрана и камеры: разрешение, направление взгляда и т.п. Объект такого класса всего один и возвращает его глобальная функция device(): local dev = device() Описание класса из 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); // остановить/возобновить игру };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 <устройство не готово> endif 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 endif 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 endfunction 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 Функция учитывает возможное разрешение экрана, режим зума и то, что камера может наклоняться. Можно использовать в различных целеуказателях. Изменено 29 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 17 Сентября 2009 (изменено) На очереди класс FS. Если кому интересно, то внутри самого движка он имеет имя CLocatorAPI. Это класс для работы с файловой системой и файлами. Описание класса FS (Показать) Экземпляр класса 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_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; };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(); // количество файлов в списке };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(); // количество файлов в списке };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(); // размер в байтах };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. С помощью этого класса можно получить информацию о сохранённой игре Описание класса CSavedGameWrapper (Показать) class CSavedGameWrapper { CSavedGameWrapper (<имя сохранёнки без расширения>); string level_name(); -- системное имя уровня int level_id(); -- номер уровня CTime* game_time(); -- игровое время number actor_health(); -- здоровье игрока };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) endlocal 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 Изменено 25 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 20 Сентября 2009 (изменено) Попробуем приступить к game_object. Это класс для доступа к онлайновым (клиентским) объектам. Причём один класс является интерфейсом для совершенно разных объектов. Разработчики не придумали ничего лучше, как взять и объединить в одном классе все интерфейсы всех клиентских объектов: актора, сталкеров, монстров, физических объектов, автомобилей и лампочек, вообще всех. Более странного и вообще говоря уродливого объектно-ориентированного дизайна я ещё не видел. Во-первых, класс вышел совершенно необозримым - три сотни методов! Во-вторых, вызов не подходящего метода для произвольно взятого объекта приводит к совершенно непредсказуемым результатам. В лучшем случае не будет ничего, а чаще всего - будет вылет, причём обычно без лога. Наконец, описание этого класса в lua_help совершенно невнятное (как впрочем и всех остальных классов): типы возвращаемых значений опущены, типы входных аргументов указаны не всегда, а о назначении большинства методов можно только гадать. Предлагаю несколько более внятное описание. Источником информации служил в первую очередь документ из билда 1935, где описаны (весьма лаконично) многие методы этого класса. Во вторую очередь использована отладочная информация из мультиплеерных билдов, что позволило довольно точно восстановить типы аргументов и возвращаемых значений. Для некоторых функций имеются описания, полученные либо мной лично либо подсмотренные на форумах. Так что предлагаю рассматривать всё это как плод коллективного творчества. Пара замечаний: 1. Информация по-любому неполная. Многие (скорее даже большинство) методы не описаны, хотя типы аргументов и возвращаемых значений имеются полностью. 2. Вследствие большого объёма причёсывать сил не осталось, так что выкладываю как есть. В некоторых местах осталась информация стороннего характера - сравнение с ранними билдами, внутренние названия функций и некоторые мои собственные домыслы. 3. Я перетасовал список методов с целью их осмысленной группировки "по родству". Очевидно, что мог и заблуждаться насчёт "родства" методов, так что смотрите в оба. 4. Поскольку описание не закончено, то предлагаю всем поучаствовать в завершении этого этапа. Описание класса game_object: (Показать) 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 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 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 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 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*); }; Изменено 20 Декабря 2009 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 28 Сентября 2009 (изменено) Регистрация скриптовых классов с помощью 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_для_скриптов>); };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") ... endfunction 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 там уже задействованы. Впрочем, можно создавать какие-либо хитрые специализации уже знакомых объектов. Изменено 19 Июля 2024 пользователем Kirgudu 1 Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 14 Декабря 2009 (изменено) Небольшой апдейт. Сегодня разберём глобальные функции или иначе функции из пространств имён. Такая функция в любом месте вызывается следующим образом: <пространство имён>.<имя функции>(<список аргументов>) Пространства имён - это всего лишь способ сгруппировать функции по некоторому признаку. Хотя надо заметить, что логика группировки функций в сталкере иной раз не поддаётся объяснению. Всего есть несколько пространств имён: безымянное (т.е. функции их него вызываются просто по именам как обычные глобальные функции), 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) Пространство имён 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 -- если там такой строки нет, то возвращает свой аргумент - исходную строку Пространство имён 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; }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) Изменено 19 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 20 Декабря 2009 (изменено) Чуть дополнил описание game_object (см. пост ранее). Продублирую изменения в этом посте. дополнения к описанию 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) -- только сталкеры. вертекс уровня, куда надо идти. Изменено 19 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 25 Декабря 2009 (изменено) Сначала в двух словах, как пользоваться механизмом выдачи заданий. Для выдачи задания есть метод класса game_object (естественно, это должен быть актор) void give_task( CGameTask* <задание>, -- подготовленный объект задания, см. далее int <время до истечения>, -- время от начального момента до истечения срока задания в ms boolean <проверять на наличие такого>) -- если true и задание с таким идентификатором уже есть, то не будет добавленоvoid give_task( CGameTask* <задание>, -- подготовленный объект задания, см. далее int <время до истечения>, -- время от начального момента до истечения срока задания в ms boolean <проверять на наличие такого>) -- если true и задание с таким идентификатором уже есть, то не будет добавлено Комментарии: 1. Нетрудно посчитать, что в 32-х разрядное целое влазит миллисекунд примерно на месяц. Соответственно - это и будет максимально возможным временем для задания. 2. Третий параметр вероятно имеет смысл использовать для разных категорий заданий. Одноразовые квестовые должны иметь параметр true, а повторяющиеся однотипные - false Выдаём задания так: 1. Сначала надо подготовить объект типа CGameTask. Самый простой способ - воспользоваться уже готовыми заданиями, описанными в xml файлах. Для этого у класса CGameTask есть метод loadlocal new_task = CGameTask()new_task:load("gar_car_graveyard_quest") -- задание "Предложить помощь Бесу" 2. Собственно выдаём заданиеdb.actor:give_task(new_task, 3600*1000, true) -- срок выполнения 1 час, если уже выдано, то второй раз не получим Для получения статуса отдельной подзадачи задания есть метод класса get_task_state.enum ETaskState get_task_state(string <идентификатор задания>, int <номер подзадачи>) перечисление ETaskState экспортировано в виде класса task (см. далее) Пример использованияlocal task_status = db.actor:get_task_state("gar_car_graveyard_quest", 0) Для установки статуса отдельной подзадачи есть метод set_task_state:void set_task_state(enum ETaskState <статус>, string <идентификатор задания>, int <номер подзадачи>) -- если такого задания у актера нет, то выдаст ошибку Это всё было для уже готовых заданий. Если надо лепить задания скриптами, то необходимо использовать все возможности класса CGameTask. Далее я привожу описание классов CGameTask, вспомогательного класса SGameTaskObjective и task. Если кому это на самом деле надо, этого описания должно быть достаточно. Описание класса CGameTask (Показать) C++ class CGameTask { CGameTask() -- конструктор. Как и для большинства классов, конструктор - это просто глобальная функция с именем класса, которая возвращает свежесозданный объект. void load(string) -- загрузить конфигурацию задачи из файла config\gameplay\game_tasks.xml -- этот файл содержит несколько инклюдов, так что фактически нужное описание может -- находиться и не в нём string get_id() -- даёт строку-идентификатор задачи. Это атрибут id из тега game_task в XML -- если не был выполнен метод load или не была добавлена хоть одна подзадача, то вернёт nil -- если вручную добавили подзадачу, то вернёт "user_task" SGameTaskObjective get_objective(number) -- получить параметры подзадачи по номеру -- каждая подзадача в xml-файле описывается отдельным тегом objective -- можно добавлять свои с помощью add_objective void add_objective(SGameTaskObjective*) -- добавить подзадачу int get_objectives_cnt() -- число подзадач string get_title() -- содержимое тега title или значение, установленное ранее set_title, -- или nil, если никак не инициализировали void set_title(string) -- установить заголовок задачи (внятное имя) };C++ class CGameTask { CGameTask() -- конструктор. Как и для большинства классов, конструктор - это просто глобальная функция с именем класса, которая возвращает свежесозданный объект. void load(string) -- загрузить конфигурацию задачи из файла config\gameplay\game_tasks.xml -- этот файл содержит несколько инклюдов, так что фактически нужное описание может -- находиться и не в нём string get_id() -- даёт строку-идентификатор задачи. Это атрибут id из тега game_task в XML -- если не был выполнен метод load или не была добавлена хоть одна подзадача, то вернёт nil -- если вручную добавили подзадачу, то вернёт "user_task" SGameTaskObjective get_objective(number) -- получить параметры подзадачи по номеру -- каждая подзадача в xml-файле описывается отдельным тегом objective -- можно добавлять свои с помощью add_objective void add_objective(SGameTaskObjective*) -- добавить подзадачу int get_objectives_cnt() -- число подзадач string get_title() -- содержимое тега title или значение, установленное ранее set_title, -- или nil, если никак не инициализировали void set_title(string) -- установить заголовок задачи (внятное имя) }; Описание класса SGameTaskObjective (Показать) Класс [b]SGameTaskObjective[/b] описывает одну подзадачу C++ class SGameTaskObjective { SGameTaskObjective (CGameTask*, number) -- загрузить подзадачу из конкретной задачи -- поскольку конструктора без аргументов нет, то выходит так, что сделать свою подзадачу -- полностью с нуля не получится. С другой стороны ничто не мешает взять из любой задачи -- произвольную подзадачу и изуродовать её по своему вкусу int get_idx() -- номер подзадачи в составе общей задачи int get_state() -- состояние подзадачи. Возвращает один из элементов перечисления task (см. далее) void set_description(string) -- установка имени подзадачи (точнее идентификатора, который -- ещё может быть сконвертирован в строку через механизм локализации) string get_description() -- даёт значение тега <text> или ранее установленное функцией set_description -- при выдаче задания будет тренькающий звук и на экране появится имя первой подзадачи -- (а не имя общей задачи, как можно было бы подумать). При этом, если в таблице строк есть -- строка с соответствующим идентификатором, то будет выдана именно эта строка -- если такой строки нет, то будет выдано исходное значение (иными словами все строки перед выводом -- проходят конвертацию с помощью функции game.translate_string()) void set_article_key(string) -- не знаю что такое void set_article_id(string <article_id>) -- установка статьи в PDA для подзадачи. -- эквивалентно значению тега <article> -- Будет отображаться в правой части PDA при просмотре информации о задании -- article_id должен быть зарегистрированной статьёй void set_object_id(number) -- вроде как story_id целевого объекта, определяет координаты метки void set_map_hint(string) -- надпись на метке (не проверял) void set_map_location(string) -- иконка (не проверял) -- с помощью указанных ниже функций можно добавлять предусловия и действия на успех и провал задач -- можно добавлять несколько -- инфопорция предусловия успешного завершение задачи, соответствует тегу "infoportion_complete" void add_complete_info(string); // -- скриптовая функция предусловия успешного завершение задачи, соответствует тегу "function_complete" void add_complete_func(string); -- инфопорция, выдаваяемая при успешном завершении задачи, соответствует тегу "infoportion_set_complete" void add_on_complete_info(string); -- скриптовая функция, выполняемая при успешном завершении задачи, соответствует тегу "function_call_complete" void add_on_complete_func(string); -- инфопорция предусловия провала задачи, соответствует тегу "infoportion_fail" void add_fail_info(string); -- скриптовая функция предусловия провала задачи, соответствует тегу "function_fail" void add_fail_func(string); -- инфопорция, выдаваяемая при провале задачи, соответствует тегу "infoportion_set_fail" void add_on_fail_info(string); -- скриптовая функция, выполняемая при провале задачи, соответствует тегу "function_call_fail" void add_on_fail_func(string); };Класс [b]SGameTaskObjective[/b] описывает одну подзадачу C++ class SGameTaskObjective { SGameTaskObjective (CGameTask*, number) -- загрузить подзадачу из конкретной задачи -- поскольку конструктора без аргументов нет, то выходит так, что сделать свою подзадачу -- полностью с нуля не получится. С другой стороны ничто не мешает взять из любой задачи -- произвольную подзадачу и изуродовать её по своему вкусу int get_idx() -- номер подзадачи в составе общей задачи int get_state() -- состояние подзадачи. Возвращает один из элементов перечисления task (см. далее) void set_description(string) -- установка имени подзадачи (точнее идентификатора, который -- ещё может быть сконвертирован в строку через механизм локализации) string get_description() -- даёт значение тега <text> или ранее установленное функцией set_description -- при выдаче задания будет тренькающий звук и на экране появится имя первой подзадачи -- (а не имя общей задачи, как можно было бы подумать). При этом, если в таблице строк есть -- строка с соответствующим идентификатором, то будет выдана именно эта строка -- если такой строки нет, то будет выдано исходное значение (иными словами все строки перед выводом -- проходят конвертацию с помощью функции game.translate_string()) void set_article_key(string) -- не знаю что такое void set_article_id(string <article_id>) -- установка статьи в PDA для подзадачи. -- эквивалентно значению тега <article> -- Будет отображаться в правой части PDA при просмотре информации о задании -- article_id должен быть зарегистрированной статьёй void set_object_id(number) -- вроде как story_id целевого объекта, определяет координаты метки void set_map_hint(string) -- надпись на метке (не проверял) void set_map_location(string) -- иконка (не проверял) -- с помощью указанных ниже функций можно добавлять предусловия и действия на успех и провал задач -- можно добавлять несколько -- инфопорция предусловия успешного завершение задачи, соответствует тегу "infoportion_complete" void add_complete_info(string); // -- скриптовая функция предусловия успешного завершение задачи, соответствует тегу "function_complete" void add_complete_func(string); -- инфопорция, выдаваяемая при успешном завершении задачи, соответствует тегу "infoportion_set_complete" void add_on_complete_info(string); -- скриптовая функция, выполняемая при успешном завершении задачи, соответствует тегу "function_call_complete" void add_on_complete_func(string); -- инфопорция предусловия провала задачи, соответствует тегу "infoportion_fail" void add_fail_info(string); -- скриптовая функция предусловия провала задачи, соответствует тегу "function_fail" void add_fail_func(string); -- инфопорция, выдаваяемая при провале задачи, соответствует тегу "infoportion_set_fail" void add_on_fail_info(string); -- скриптовая функция, выполняемая при провале задачи, соответствует тегу "function_call_fail" void add_on_fail_func(string); }; Вспомогательный класс task с константами статуса (Показать) C++ class task { const fail = 0; const in_progress = 1; const completed = 2; const task_dummy = -1; };C++ class task { const fail = 0; const in_progress = 1; const completed = 2; const task_dummy = -1; }; Начиная с ЧН система заданий несколько изменилась. Исчезли подзадачи и соответственно перестал быть нужным класс SGameTaskObjective. Его методы по сути просто перешли в состав класса CGameTask. На мой взгляд всё стало проще, и если Вы поняли, как это работает в ТЧ, то экстраполировать это на ЧН и ЗП труда не составит. Изменено 29 Июля 2024 пользователем Kirgudu 1 1 Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 30 Декабря 2009 lekzd, можно попытаться по разнице между position и center. Это типа по наклону туловища =) Но там тоже нестабильные значения. Вообще-то по скорости не так уж и плохо работает. Если учесть, что апдейты идут нерегулярно и вычислять скорость по настоящему, т.е. учитывая параметр delta, то точность повышается. Также вроде как лучше вычислять скорость только с учётом координат xz, т.е. игнорировать вертикальную компоненту. Кроме того, можно ввести фильтрацию шума - усреднение по нескольким точкам. У меня усреднение по трём дало достаточно различимую картину: стоит, идёт, бежит. Но иногда таки бывают выбросы. По пяти точкам - различие практически идеальное. Хотя и появляется некоторая микроскопическая задержка, связанная с размазыванием переходов. Вопрос ещё, для чего это надо. Если скажем надо просто отобразить состояние бега на худе, то и так вполне сойдёт. Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 4 Января 2010 (изменено) Введение Показать При первом взгляде на lua_help.script возникает впечатление "дофига всего - объять нереально". Особенно это касается больших иерархий классов. Наиболее "развесистыми" являются три: оконные классы для создания пользовательского интерфейса и модификации элементов худа, серверные классы и клиентские классы для серверной и клиентской частей игровых объектов соответственно.Про оконные классы поговорим в другой раз, а сейчас займёмся серверными и клиентскими классами. Что потребуется:Первое. Необходимо представлять себе что такое классы и с чем их едят. Основное понятие, которое надо понимать - это наследование классов. Неплохо бы понимать идею базового класса и интерфейса.Второе. Необходимо достаточно хорошо понимать природу разделения классов на серверные и клиентские. Можете почитать мой трёп здесь в статье про object_binder.Третье. Вероятно имеет смысл почитать мою статью о реализации классов в сталкере.Четвёртое. Между серверными и клиентскими классами есть взаимосвязь. Кое что я попробую описать, кое-что уже описывал в статье о классе object_factory. Но это не полная информация.Пятое. Я вынужден ввести некоторые искусственные соглашения при описании классов. Это необходимо для сокращения объёма дублирующей информации. Внимательно прочитайте это вступление!Шестое. Информация приводится для Зова Припяти. Впрочем, отличия от ЧН и ТЧ минимальны. Я постарался пометить отличающиеся места, но где-то мог и не сделать этого. Смотрите внимательней!Наконец. Данное описание - это не то чтобы описание в полном смысле этого слова. Я не в состоянии описать в одиночку более сотни классов и несколько сотен методов и свойств. Это скорее попытка привести в некоторую систему помойку из lua_help, убрать смысловой шум и создать основу для дальнейшего полного описания. Я постарался дать по возможности точную информацию о типах аргументов и возвращаемых значений функций, но назначение их не описано. Итак. Все игровые сущности (сталкеры, монстры, предметы и пр.) в общем случае существуют одновременно на двух сторонах - на сервере и на клиенте. На каждой стороне для этого есть соответствующие классы. На серверной - это классы, имя которых начинается на "cse". С некоторыми поправками можно считать, что все серверные классы происходят от cse_abstract. На клиентской стороне есть соответствующая иерархия классов. Можно считать, что все они происходят от CGameObject. На клиентской стороне есть ещё и game_object, который также является интерфейсом к клиентским объектам, причём основным интерфейсом. Его описание уже начато, но оно далеко от завершения.Классы как серверные, так и клиентские образуют иерархию наследования классов. На самом примитивном уровне можно считать, что наследование означает включение всех членов базового класса (т.е. свойств и методов) в производный класс. В свою очередь это означает, что нет необходимости описывать унаследованные члены для всех производных классов, достаточно это сделать один раз для базового. Кроме того я ввожу важное соглашение для описания классов: я не буду приводить полный список членов класса для каждого из них, а только список методов и свойств, которые добавляет конкретный класс сверх унаследованных. Поэтому, если по описанию в классе нет ничего, то смотрите на классы от которых он унаследован и смотрите список их методов и список методов их базовых классов и т.д. Я вынужден так сделать во-первых в силу ограничения форума, во вторых для убирания избыточности при описании. Я также буду пользоваться спойлерами: после описания класса будет идти спойлер с его производными классами, в них спойлеры с их производными и т.д. (наподобие матрёшки =)Начнём с серверных.Можно обратить внимание, что имеется некоторое количество базовых классов и интерфейсов, которые вообще не содержат никаких методов или свойств. Наследование от них не даёт конкретной информации о свойствах серверного класса, но парадоксальным образом может дать некоторые догадки о применимости методов game_object. Вот список этих базовых интерфейсов: Базовые интерфейсы Показать // все приведённые интерфейсы не содержат ни методов ни свойств, поэтому // приводится только их список и отношение их наследования // в принципе, можно погадать и на тему ipure_alife_load_object ipure_alife_save_object ipure_alife_load_save_object : ipure_alife_load_object,ipure_alife_save_object ipure_server_object : ipure_alife_load_save_object cpure_server_object : ipure_server_object // все вообще классы наследованы от cpure_server_object, // его можно описать, как "объект, который умеет сохраняться и восстанавливать своё состояние" ipure_schedulable_object cse_alife_schedulable : ipure_schedulable_object // класс cse_alife_schedulable - это вроде как объекты, с которыми работает планировщик cse_visual // для этих объектов нужна модель. Их "видно" cse_motion // эти объекты умеют двигаться cse_ph_skeleton // у этих есть кости cse_shape // вроде как объекты, с которыми связана некоторая область пространства: аномалии, смарты и т.п. cse_alife_group_abstract // без понятия, зачем нужен cse_alife_inventory_item // объекты, которые можно взять в инвентарь // этот интерфейс является некоторым исключением, поскольку содержит методы // судя по использованию отвечает за возможность торговать: актор и сталкеры C++ class cse_alife_trader_abstract { string profile_name(); int reputation(); int rank(); int community() const; };// все приведённые интерфейсы не содержат ни методов ни свойств, поэтому // приводится только их список и отношение их наследования // в принципе, можно погадать и на тему ipure_alife_load_object ipure_alife_save_object ipure_alife_load_save_object : ipure_alife_load_object,ipure_alife_save_object ipure_server_object : ipure_alife_load_save_object cpure_server_object : ipure_server_object // все вообще классы наследованы от cpure_server_object, // его можно описать, как "объект, который умеет сохраняться и восстанавливать своё состояние" ipure_schedulable_object cse_alife_schedulable : ipure_schedulable_object // класс cse_alife_schedulable - это вроде как объекты, с которыми работает планировщик cse_visual // для этих объектов нужна модель. Их "видно" cse_motion // эти объекты умеют двигаться cse_ph_skeleton // у этих есть кости cse_shape // вроде как объекты, с которыми связана некоторая область пространства: аномалии, смарты и т.п. cse_alife_group_abstract // без понятия, зачем нужен cse_alife_inventory_item // объекты, которые можно взять в инвентарь // этот интерфейс является некоторым исключением, поскольку содержит методы // судя по использованию отвечает за возможность торговать: актор и сталкеры C++ class cse_alife_trader_abstract { string profile_name(); int reputation(); int rank(); int community() const; }; Несколько предваряющих комментариев к дальнейшему материалу. Показать 1. Почти каждый из описанных классов имеет конструктор, который по традиции языка C++ имеет имя, совпадающее с именем класса, и не возвращает никакого значения. У каждого объекта из иерархии cse_abstractвсего один конструктор, который всегда принимает один аргумент - строку. Очевидно, что аргумент - это имя секции из system.ltx. Как и для остальных классов, экспортированных с помощью технологии Luabind, это означает, что имеется глобальная функция с именем класса, которая принимает такие же аргументы, как и конструктор, и возвращает объект класса. И такие функции на самом деле есть для каждого серверного класса. Однако попытка их вызвать у меня лично приводит к неизменному вылету. Так и должно быть! Серверные объекты создаются опосредованно, с помощью вызова метода create класса alife_simulator. См. эту статью.Итак, конструктор всегда один, имеет определённое имя и один и тот-же аргумент, да и толку от него нет. Кроме того, его наличие в составе класса - вообще изрядная условность, поскольку он не внутри класса, а снаружи. Так что в качестве дополнительного соглашения, позволяющего экономить пространство форума, я не буду приводить эти конструкторы в описании классов.Если класс не добавляет ничего к базовому, то ради экономии места его пишу по сокращённому варианту - одной строкой с указанием только его имени и базовых классов.2. Серверные классы - это классы экспортированные таким образом, что на их основе можно создавать свои скриптовые классы. Опять же см. эту статью. Примеры таких скриптовых классов в игре можно найти в файлах, которые в основном начинаются с "se_": se_artefact.script, se_item.script и т.д. Вся иерархия серверных классов Показать class cse_abstract : cpure_server_object { vector* angle; // начиная с ЗП int id; int parent_id; vector* position; int script_version; void UPDATE_Read(net_packet&); void STATE_Read(net_packet&, number); string name(const cse_abstract*); void UPDATE_Write(net_packet&); void STATE_Write(net_packet&); int clsid() const; ini_file* spawn_ini(); string section_name(); cse_abstract* init(); // // FillProps и OnEvent начиная с ЧН убраны void FillProps(string, class xr_vector<class PropItem *,class xalloc<class PropItem *> >&); void OnEvent(net_packet&, number, number, ClientID); };class cse_abstract : cpure_server_object { vector* angle; // начиная с ЗП int id; int parent_id; vector* position; int script_version; void UPDATE_Read(net_packet&); void STATE_Read(net_packet&, number); string name(const cse_abstract*); void UPDATE_Write(net_packet&); void STATE_Write(net_packet&); int clsid() const; ini_file* spawn_ini(); string section_name(); cse_abstract* init(); // // FillProps и OnEvent начиная с ЧН убраны void FillProps(string, class xr_vector<class PropItem *,class xalloc<class PropItem *> >&); void OnEvent(net_packet&, number, number, ClientID); }; От класса cse_abstract унаследованы ВСЕ остальные классы. Иногда примешиваются дополнительные интерфейсы. Список классов унаследованных от cse_abstract Показать class cse_alife_graph_point : cse_abstract // так выглядит сокращённое описание, класс ничего не добавляет к родительскомуclass cse_spectator : cse_abstractclass cse_temporary : cse_abstractclass cse_alife_object_climable : cse_shape,cse_abstract class CSE_AbstractVisual : cse_visual,cse_abstract { string getStartupAnimation(); // добавлен всего один метод. };class CSE_AbstractVisual : cse_visual,cse_abstract { string getStartupAnimation(); // добавлен всего один метод. }; class cse_alife_object : cse_abstract { int m_game_vertex_id; int m_level_vertex_id; int m_story_id; bool online; bool used_ai_locations() const; ??void?? use_ai_locations(boolean); // начиная с ЗП bool can_save() const; bool can_switch_online() const; void can_switch_online(boolean); bool interactive() const; bool visible_for_map() const; void visible_for_map(boolean); bool can_switch_offline() const; void can_switch_offline(boolean); bool move_offline() const; void move_offline(boolean); };class cse_alife_object : cse_abstract { int m_game_vertex_id; int m_level_vertex_id; int m_story_id; bool online; bool used_ai_locations() const; ??void?? use_ai_locations(boolean); // начиная с ЗП bool can_save() const; bool can_switch_online() const; void can_switch_online(boolean); bool interactive() const; bool visible_for_map() const; void visible_for_map(boolean); bool can_switch_offline() const; void can_switch_offline(boolean); bool move_offline() const; void move_offline(boolean); }; От класса cse_alife_object унаследован всего один класс cse_alife_dynamic_object, так что нет смысла прятать его под спойлер. class cse_alife_dynamic_object : cse_alife_object { void switch_offline(); void switch_online(); bool keep_saved_data_anyway() const; // либо synchronize_location, либо redundant void on_spawn(); void on_before_register(); void on_register(); void on_unregister(); };class cse_alife_dynamic_object : cse_alife_object { void switch_offline(); void switch_online(); bool keep_saved_data_anyway() const; // либо synchronize_location, либо redundant void on_spawn(); void on_before_register(); void on_register(); void on_unregister(); }; Список классов, унаследованных от cse_alife_dynamic_object Показать class cse_alife_space_restrictor : cse_alife_dynamic_object,cse_shape Список классов, унаследованных от cse_alife_space_restrictor Показать class cse_alife_level_changer : cse_alife_space_restrictorclass cse_alife_team_base_zone : cse_alife_space_restrictor class cse_alife_smart_zone : cse_alife_space_restrictor,cse_alife_schedulable { float detect_probability(); void smart_touch(cse_alife_monster_abstract*); void unregister_npc(cse_alife_monster_abstract*); void update(); // явно идёт от cse_alife_schedulable function register_npc(cse_alife_monster_abstract*); float suitable(cse_alife_monster_abstract*) const; CALifeSmartTerrainTask* task(cse_alife_monster_abstract*); bool enabled(cse_alife_monster_abstract*) const; };class cse_alife_smart_zone : cse_alife_space_restrictor,cse_alife_schedulable { float detect_probability(); void smart_touch(cse_alife_monster_abstract*); void unregister_npc(cse_alife_monster_abstract*); void update(); // явно идёт от cse_alife_schedulable function register_npc(cse_alife_monster_abstract*); float suitable(cse_alife_monster_abstract*) const; CALifeSmartTerrainTask* task(cse_alife_monster_abstract*); bool enabled(cse_alife_monster_abstract*) const; }; class cse_alife_online_offline_group : cse_alife_dynamic_object,cse_alife_schedulable { // все методы класса экспортированы только начиная с ЗП void update(); void register_member(number); void unregister_member(number); function clear_location_types(); function get_current_task(); function commander_id(); function squad_members() const; function force_change_position(vector); function add_location_type(string); ??int?? npc_count() const; };class cse_alife_online_offline_group : cse_alife_dynamic_object,cse_alife_schedulable { // все методы класса экспортированы только начиная с ЗП void update(); void register_member(number); void unregister_member(number); function clear_location_types(); function get_current_task(); function commander_id(); function squad_members() const; function force_change_position(vector); function add_location_type(string); ??int?? npc_count() const; }; class cse_smart_cover : cse_alife_dynamic_object { function description() const; function set_available_loopholes(object); };class cse_smart_cover : cse_alife_dynamic_object { function description() const; function set_available_loopholes(object); }; class cse_custom_zone : cse_alife_dynamic_object,cse_shape Список классов, унаследованных от cse_custom_zone Показать class cse_torrid_zone : cse_custom_zone,cse_motion class cse_anomalous_zone : cse_custom_zone class cse_zone_visual : cse_anomalous_zone,cse_visual class cse_alife_dynamic_object_visual : cse_alife_dynamic_object,cse_visual Список классов, унаследованных от cse_alife_dynamic_object_visual Показать class cse_alife_car : cse_alife_dynamic_object_visual,cse_ph_skeleton class cse_alife_helicopter : cse_alife_dynamic_object_visual,cse_motion,cse_ph_skeleton// класс cse_alife_inventory_box появился в ЗПclass cse_alife_inventory_box : cse_alife_dynamic_object_visual class cse_alife_mounted_weapon : cse_alife_dynamic_object_visual class cse_alife_object_breakable : cse_alife_dynamic_object_visual class cse_alife_object_hanging_lamp : cse_alife_dynamic_object_visual,cse_ph_skeleton class cse_alife_object_physic : cse_alife_dynamic_object_visual,cse_ph_skeleton class cse_alife_object_projector : cse_alife_dynamic_object_visual class cse_alife_ph_skeleton_object : cse_alife_dynamic_object_visual,cse_ph_skeleton class cse_alife_trader : cse_alife_dynamic_object_visual,cse_alife_trader_abstract class cse_alife_creature_abstract : cse_alife_dynamic_object_visual { int team; int group; int squad; void on_death(cse_abstract*); bool alive() const; int g_team(); int g_group(); int g_squad(); float health() const; // o_torso появилась начиная с ЧН Rotation o_torso(); };class cse_alife_creature_abstract : cse_alife_dynamic_object_visual { int team; int group; int squad; void on_death(cse_abstract*); bool alive() const; int g_team(); int g_group(); int g_squad(); float health() const; // o_torso появилась начиная с ЧН Rotation o_torso(); }; Список классов, унаследованных от cse_alife_creature_abstract Показать class cse_alife_creature_actor : cse_alife_creature_abstract,cse_alife_trader_abstract,cse_ph_skeletonclass cse_alife_creature_crow : cse_alife_creature_abstract // начиная с ЧНclass cse_alife_creature_phantom : cse_alife_creature_abstract // начиная с ЧН class cse_alife_monster_abstract : cse_alife_creature_abstract,cse_alife_schedulable { // начиная с ЗП int group_id; // начиная с ЧН int m_smart_terrain_id; function kill(); // начиная с ЗП void update(); function force_set_goodwill(number, number); // начиная с ЗП function clear_smart_terrain(); float travel_speed(); // начиная с ЧН void travel_speed(float); // начиная с ЧН function smart_terrain_task_deactivate(); function smart_terrain_task_activate(); float current_level_travel_speed(); // начиная с ЧН void current_level_travel_speed(float); // начиная с ЧН CALifeMonsterBrain* brain(); bool has_detector(); // начиная с ЗП int smart_terrain_id(); // всегда тоже самое, что и m_smart_terrain_id int rank(); };class cse_alife_monster_abstract : cse_alife_creature_abstract,cse_alife_schedulable { // начиная с ЗП int group_id; // начиная с ЧН int m_smart_terrain_id; function kill(); // начиная с ЗП void update(); function force_set_goodwill(number, number); // начиная с ЗП function clear_smart_terrain(); float travel_speed(); // начиная с ЧН void travel_speed(float); // начиная с ЧН function smart_terrain_task_deactivate(); function smart_terrain_task_activate(); float current_level_travel_speed(); // начиная с ЧН void current_level_travel_speed(float); // начиная с ЧН CALifeMonsterBrain* brain(); bool has_detector(); // начиная с ЗП int smart_terrain_id(); // всегда тоже самое, что и m_smart_terrain_id int rank(); }; Список классов, унаследованных от cse_alife_monster_abstract Показать class cse_alife_monster_rat : cse_alife_monster_abstract,cse_alife_inventory_item class cse_alife_monster_zombie : cse_alife_monster_abstract class cse_alife_monster_base : cse_alife_monster_abstract,cse_ph_skeleton class cse_alife_psydog_phantom : cse_alife_monster_base class cse_alife_human_stalker : cse_alife_human_abstract,cse_ph_skeleton class cse_alife_human_abstract : cse_alife_trader_abstract,cse_alife_monster_abstract { // rank из интерфейса cse_alife_trader_abstract конфликтует с такой же из cse_alife_monster_abstract // непонятно, какая из них "побеждает" и непонятно, к чему тогда относится вот эта void set_rank(int); // };class cse_alife_human_abstract : cse_alife_trader_abstract,cse_alife_monster_abstract { // rank из интерфейса cse_alife_trader_abstract конфликтует с такой же из cse_alife_monster_abstract // непонятно, какая из них "побеждает" и непонятно, к чему тогда относится вот эта void set_rank(int); // }; class cse_alife_item : cse_alife_dynamic_object_visual,cse_alife_inventory_item { bool bfUseful(); };class cse_alife_item : cse_alife_dynamic_object_visual,cse_alife_inventory_item { bool bfUseful(); }; Список классов, унаследованных от cse_alife_item Показать class cse_alife_item_ammo : cse_alife_item class cse_alife_item_artefact : cse_alife_item class cse_alife_item_bolt : cse_alife_item class cse_alife_item_custom_outfit : cse_alife_item class cse_alife_item_detector : cse_alife_item class cse_alife_item_document : cse_alife_item class cse_alife_item_explosive : cse_alife_item class cse_alife_item_grenade : cse_alife_item class cse_alife_item_pda : cse_alife_item class cse_alife_item_torch : cse_alife_item class cse_alife_item_weapon : cse_alife_item { // clone_addons появилась в ЗП function clone_addons(cse_alife_item_weapon*); };class cse_alife_item_weapon : cse_alife_item { // clone_addons появилась в ЗП function clone_addons(cse_alife_item_weapon*); }; // иерархия наследования от cse_alife_item_weapon простая, так что без спойлера// класс cse_alife_item_weapon_auto_shotgun появился в ЗПclass cse_alife_item_weapon_auto_shotgun : cse_alife_item_weapon class cse_alife_item_weapon_magazined : cse_alife_item_weapon class cse_alife_item_weapon_magazined_w_gl : cse_alife_item_weapon_magazined class cse_alife_item_weapon_shotgun : cse_alife_item_weapon Изменено 29 Июля 2024 пользователем Kirgudu Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 5 Января 2010 (изменено) Полагаю, красивая картинка никому не помешает. Вот вам диаграмма серверных классов с высоты птичьего полёта =) Обращаю внимание на нотацию записи: большая пустая стрелка означает отношение обобщения (или наследования иными словами) и направлена от унаследованного класса к базовому. Включение абстрактного интерфейса обозначается стрелкой с кружочком и указанием, какой интерфейс наследуется. Это сокращённая форма записи, позволяющая избавиться от нагромождения линий и лучше понять основную линию наследования. Генеалогия серверных классов (Показать) С серверными классами пока всё. Некоторые выводы по данному этапу. 1. Большинство классов не добавляют новых методов и свойств. В целом, информации существенно меньше, нежели кажется на первый взгляд. 2. Между ТЧ, ЧН и ЗП не так уж и много отличий. Хотя отличия имеются: добавлено несколько новых классов, изрядно добавлено новых методов в существующих классах и пара методов исчезла. 3. Я не описывал ни назначение классов, ни назначение их методов. В некоторых местах (в основном для новых методов в ЧН и ЗП) не удалось пока получить информации о параметрах и возвращаемых значениях. Наверняка где-то упустил продокументировать различие между играми. В принципе, практически все методы классов до cse_alife_object включительно известны, как и некоторые из остальных классов. Однако у меня сейчас нет возможности заниматься документированием каждого из нескольких десятков методов. Надеюсь на помощь сообщества. Продолжение следует... Изменено 5 Января 2010 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 7 Января 2010 (изменено) 7.9, всё верно, только не 40 млн., а 4 мрд., точнее максимальное 32-х разрядное беззнаковое целое: 4294967295 что в миллисекундах составляет примерно 1193 часа, т.е. 49 дней. Это известная проблема: после примерно месяца игрового времени часть функций, связанных с использованием этого счётчика, начинают работать неправильно. Вроде как это даже лечили. Хотя если этот счётчик используется не только скриптами, но и движком, то всё не вылечить. Изменено 13 Января 2010 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 8 Января 2010 (изменено) Разбираем систему клиентских классов. Сначала немного лирики. Показать Итак, на клиентской стороне есть своя иерархия классов, отвечающих за онлайновые (т.е. клиентские) объекты. Все они унаследованы от CGameObject. Дерево наследования там не столь сложное, как для серверных классов. Тем не менее для каждого (почти) типа игровых объектов есть свой класс, и при первом взгляде всё смотрится вполне прилично. Но это только на первый взгляд. При более внимательном рассмотрении выясняется, что классы хоть и есть, но добраться до произвольного клиентского объекта невозможно. Т.е. нет никаких функций, с помощью которых можно было бы получить произвольный клиентский объект, производный от CGameObject. Соответственно, нет и возможности вызывать их методы. Кроме того, эти классы не предназначены для создания на их основе скриптовых классов. Таким образом, все общие пути для использования объектов этого класса закрыты. Соответственно, основным и почти единственным интерфейсом к объектам на клиентской стороне остаётся game_object. Однако не всё так трагично =) Есть исключения. Если в классе добавлены какие-либо методы сверх основного набора из CGameObject, то для получения объектов такого класса в game_object имеются специальные методы. В ТЧ исходно всего три таких класса: CCar, CHelicopter и hanging_lamp. Соответственно в game_object есть методы:get_car() - для CCarget_helicopter() - для CHelicopterget_hanging_lamp() - для hanging_lampВ ЧН методы добавлены в классы CPhysicObject, CArtefact, CZoneCampfire и CAI_Bloodsucker. Соответственно появились и новые методы в game_object: get_physics_object(), get_artefact() и get_campfire(). А вот для кровососа почему-то функции нет.К этому могу добавить, что посмотрев на состав методов CGameObject, можно увидеть их сходство с набором методов object_binder. Таким образом скорее всего методы CGameObject опосредованно доступны через биндер. Для тех, что недоступны, вроде как имеются некие аналоги в game_object, и даже для работы с визуалом похоже начиная с ЗП появились функции (но это неподтверждённая информация).Наконец надо заметить, что главным использованием системы этих классов является не прямое использование, а опосредованное - через механизм регистрации скриптовых серверных классов. Я об этом рассказывал здесь, рекомендую почитать. Возможность регистрации своего скриптового серверного класса определяется наличием соответствующего ему экспортированного клиентского. Достаточно, чтобы он просто был. И вот как раз здесь имеется приличный прорыв: по сравнению с ТЧ в ЧН и ЗП добавлено много новых классов - теперь охвачены почти все игровые объекты (почти, но таки не все).Теперь можно делать скриптовые серверные классы для ящиков, PDA, аддонов и аномалий. В первую очередь это означает, что теперь можно без проблем хранить серверное состояние этих объектов, что существенно облегчает создание модов, где эти объекты используются в оффлайне. Также добавлен актор, что означает возможность сделать серверного актора. Как-то это используется в игре. Довольно лирики. Далее представлена система клиентских классов. Используются те-же соглашения, что и раньше: конструктор не показан, если класс не добавляет своих методов, то показан одной строкой. Иерархия клиентских классов Сперва базовые интерфейсы. Их не так много, как у серверных. Показать class DLL_Pure { DLL_Pure* _construct(); }; class DLL_Pure { DLL_Pure* _construct(); }; Как будет видно далее, на самом деле самым базовым классом является DLL_Pure. Похоже, что именно этот класс является необходимым для механизма регистрации скриптовых классов. Есть ещё классы, не относящиеся к игровым объектам, но унаследованные от DLL_Pure, которые тоже можно использовать с классом object_factory.ICollidableIRenderableISheduledНепосредственно от DLL_Pure наследованы всего три класса.class ce_smart_zone : DLL_Pureclass ce_script_zone : DLL_Pure class CGameObject : DLL_Pure,ISheduled,ICollidable,IRenderable { ??string?? Visual() const; function getEnabled() const; void net_Export(net_packet&); void net_Import(net_packet&); function getVisible() const; int net_Spawn(cse_abstract*); bool use(CGameObject*); }; class CGameObject : DLL_Pure,ISheduled,ICollidable,IRenderable { ??string?? Visual() const; function getEnabled() const; void net_Export(net_packet&); void net_Import(net_packet&); function getVisible() const; int net_Spawn(cse_abstract*); bool use(CGameObject*); }; Все остальные классы имеют своим предком CGameObject Классы, производные от CGameObject Показать CActor // начиная с ЧНCAI_StalkerCAI_Trader// монстры class CAI_Bloodsucker : CGameObject { function force_visibility_state(number); }; class CAI_Bloodsucker : CGameObject { function force_visibility_state(number); }; CAI_Boar CAI_Dog CAI_Flesh CAI_PseudoDog CBurer CCat CChimera CController CFracture CPoltergeist CPseudoGigant CPsyDog CPsyDogPhantom CSnork CTushkano CZombie//съедаемые предметыCAntirad CBottleItem CFoodItem CMedkit//устройстваCTorchCSimpleDetector -- класс есть, но не документирован в lua_helpCAdvancedDetector -- класс есть, но не документирован в lua_helpCEliteDetector -- класс есть, но не документирован в lua_helpCScientificDetector -- класс есть, но не документирован в lua_help//аддоныCGrenadeLauncher CScope CSilencer//разные объекты hanging_lamp Показать class hanging_lamp : CGameObject { hanging_lamp (); void turn_on(); void turn_off(); }; class hanging_lamp : CGameObject { hanging_lamp (); void turn_on(); void turn_off(); }; CCar Показать class holder { bool engaged(); void Action(int, int); void SetParam(int, vector); }; class CCar : CGameObject,holder { // CCarWeapon::__unnamed const eWpnDesiredDir = 1; const eWpnDesiredPos = 2; const eWpnActivate = 3; const eWpnFire = 4; const eWpnAutoFire = 5; const eWpnToDefaultDir = 6; CCar (); float GetfHealth() const; float SetfHealth(float); vector CurrentVel(); bool IsObjectVisible(game_object*); int ExplodeTime(); void SetExplodeTime(int); bool HasWeapon(); float FireDirDiff(); void CarExplode(); bool CanHit(); }; class holder { bool engaged(); void Action(int, int); void SetParam(int, vector); }; class CCar : CGameObject,holder { // CCarWeapon::__unnamed const eWpnDesiredDir = 1; const eWpnDesiredPos = 2; const eWpnActivate = 3; const eWpnFire = 4; const eWpnAutoFire = 5; const eWpnToDefaultDir = 6; CCar (); float GetfHealth() const; float SetfHealth(float); vector CurrentVel(); bool IsObjectVisible(game_object*); int ExplodeTime(); void SetExplodeTime(int); bool HasWeapon(); float FireDirDiff(); void CarExplode(); bool CanHit(); }; CHelicopter Показать class CHelicopter : CGameObject { //EHeliState const eAlive = 0; const eDead = 1; //EHeliBodyState const eBodyByPath = 0; const eBodyToPoint = 1; //EHeliHuntState const eEnemyNone = 0; const eEnemyPoint = 1; const eEnemyEntity = 2; //EHeilMovementState const eMovNone = 0; const eMovToPoint = 1; const eMovPatrolPath = 2; const eMovRoundPath = 3; const eMovLanding = 4; const eMovTakeOff = 5; bool m_dead; bool m_exploded; bool m_flame_started; bool m_light_started; float m_max_mgun_dist; float m_max_rocket_dist; float m_min_mgun_dist; float m_min_rocket_dist; bool m_syncronize_rocket; int m_time_between_rocket_attack; bool m_use_mgun_on_attack; bool m_use_rocket_on_attack; CHelicopter (); void Die(); // красиво падать, потом взорваться void Explode(); // сразу взорваться float GetfHealth() const; float SetfHealth(float); EHeliHuntState GetHuntState(); EHeliBodyState GetBodyState(); EHeilMovementState GetMovementState(); ??EHeliState?? GetState(); void ClearEnemy(); // ни в кого не стрелять function SetEnemy(game_object*); // в кого стрелять function SetEnemy(vector*); // куда стрелять float GetCurrVelocity(); vector GetCurrVelocityVec(); float GetMaxVelocity(); void SetMaxVelocity(float); float GetDistanceToDestPosition(); float GetOnPointRangeDist(); float GetRealAltitude(); // абсолютная высота float GetSafeAltitude(); float GetSpeedInDestPoint(number); void SetSpeedInDestPoint(float); void SetBarrelDirTolerance(float); function SetLinearAcc(number, number); void GoPatrolByPatrolPath(string, int); void GoPatrolByRoundPath(vector, float, boolean); bool isVisible(game_object*); void LookAtPoint(vector, boolean); // развернуться носом в точку void SetDestPosition(vector*); // куда лететь bool UseFireTrail(); void UseFireTrail(boolean); void SetFireTrailLength(float); void SetOnPointRangeDist(float); void StartFlame(); // начать гореть void TurnEngineSound(boolean); // звук двигателя void TurnLighting(boolean); // включить прожектор }; class CHelicopter : CGameObject { //EHeliState const eAlive = 0; const eDead = 1; //EHeliBodyState const eBodyByPath = 0; const eBodyToPoint = 1; //EHeliHuntState const eEnemyNone = 0; const eEnemyPoint = 1; const eEnemyEntity = 2; //EHeilMovementState const eMovNone = 0; const eMovToPoint = 1; const eMovPatrolPath = 2; const eMovRoundPath = 3; const eMovLanding = 4; const eMovTakeOff = 5; bool m_dead; bool m_exploded; bool m_flame_started; bool m_light_started; float m_max_mgun_dist; float m_max_rocket_dist; float m_min_mgun_dist; float m_min_rocket_dist; bool m_syncronize_rocket; int m_time_between_rocket_attack; bool m_use_mgun_on_attack; bool m_use_rocket_on_attack; CHelicopter (); void Die(); // красиво падать, потом взорваться void Explode(); // сразу взорваться float GetfHealth() const; float SetfHealth(float); EHeliHuntState GetHuntState(); EHeliBodyState GetBodyState(); EHeilMovementState GetMovementState(); ??EHeliState?? GetState(); void ClearEnemy(); // ни в кого не стрелять function SetEnemy(game_object*); // в кого стрелять function SetEnemy(vector*); // куда стрелять float GetCurrVelocity(); vector GetCurrVelocityVec(); float GetMaxVelocity(); void SetMaxVelocity(float); float GetDistanceToDestPosition(); float GetOnPointRangeDist(); float GetRealAltitude(); // абсолютная высота float GetSafeAltitude(); float GetSpeedInDestPoint(number); void SetSpeedInDestPoint(float); void SetBarrelDirTolerance(float); function SetLinearAcc(number, number); void GoPatrolByPatrolPath(string, int); void GoPatrolByRoundPath(vector, float, boolean); bool isVisible(game_object*); void LookAtPoint(vector, boolean); // развернуться носом в точку void SetDestPosition(vector*); // куда лететь bool UseFireTrail(); void UseFireTrail(boolean); void SetFireTrailLength(float); void SetOnPointRangeDist(float); void StartFlame(); // начать гореть void TurnEngineSound(boolean); // звук двигателя void TurnLighting(boolean); // включить прожектор }; CPhysicObject Показать class CPhysicObject : CGameObject { function anim_time_get(); function anim_time_set(number); function play_bones_sound(); function run_anim_back(); function run_anim_forward(); function set_door_ignore_dynamics(); function stop_anim(); function stop_bones_sound(); function unset_door_ignore_dynamics(); }; class CPhysicObject : CGameObject { function anim_time_get(); function anim_time_set(number); function play_bones_sound(); function run_anim_back(); function run_anim_forward(); function set_door_ignore_dynamics(); function stop_anim(); function stop_bones_sound(); function unset_door_ignore_dynamics(); }; CDestroyablePhysicsObject CExplosiveItem CWeaponAmmoCInventoryBox CPda//ГранатыCF1 CRGD5//оружиеCWeaponAK74 CWeaponAutomaticShotgun CWeaponBinoculars CWeaponBM16 CWeaponFN2000 CWeaponFORT CWeaponGroza CWeaponHPSA CWeaponKnife CWeaponLR300 CWeaponPM CWeaponRG6 CWeaponRPG7 CWeaponShotgun CWeaponSVD CWeaponSVU CWeaponUSP45 CWeaponVal CWeaponVintorez CWeaponWalther//аномалииCMincer CMosquitoBald CHairsZone CRadioactiveZone CTorridZone// Разные области class CZoneCampfire : CGameObject { function is_on(); function turn_on(); function turn_off(); }; class CZoneCampfire : CGameObject { function is_on(); function turn_on(); function turn_off(); }; CLevelChanger CSpaceRestrictor smart_cover_object//ШкуркиCStalkerOutfitCHelmet -- класс есть, но не документирован в lua_help Артефакты Показать class CArtefact : CGameObject { function SwitchVisibility(boolean); function FollowByPath(string, number, vector); function GetAfRank() const; }; class CArtefact : CGameObject { function SwitchVisibility(boolean); function FollowByPath(string, number, vector); function GetAfRank() const; }; Производные от CArtefactCBastArtefact CBlackDrops CBlackGraviArtefact CDummyArtefact CElectricBall CFadedBall CGalantineArtefact CGraviArtefact CMercuryBall CRustyHairArtefact CThornArtefact CZudaArtefact Рисовать картинку с иерархией наследования смысла нет, поскольку и так всё довольно очевидно. За исключением малого числа классов остальные к CGameObject ничего не добавляют. Соответственно и описывать нечего. Названия классов более или менее говорят о том, для каких объектов они предназначены. Где мог, указал точные сигнатуры функций (CCar, CHelicopter и hanging_lamp). Для новых классов и методов по сравнению с ТЧ такой информации к сожалению нет.Несколько рассуждений на тему взаимодействия серверного и клиентского объектов. Попробуем рассуждать логически. Есть сервер, есть клиент. Это в общем случае разные компьютеры. Каким может быть взаимодействие объектов, находящихся по разную сторону сети? Могут ли они, к примеру, вызывать методы друг друга, или непосредственно читать данные своего "напарника"? Очевидно нет. Моё мнение такое, что их взаимодействие сводится в основном к посылке данных друг другу. При этом, данные посылаются "пачкой", т.е. обо всём состоянии сразу. То, что я наблюдал, выглядит так: при переходе в онлайн создаётся клиентский объект, серверный его инициализирует, посылая ему при создании нетпакет. Этот момент можно отловить в биндере клиентского класса. Все дальнейшие движения происходят от клиентского объекта к серверному. Это означает, что периодически клиентский затирает данные серверного. Точные моменты, когда это происходит, периодичность, а также механизм передачи данных мне лично понять пока не удалось. По крайней мере на серверной стороне ничего не видно, просто тихо меняются данные, и понять, когда это происходит, крайне непросто.Однако некоторые выводы таки можно сделать. Если взаимодействие классов примерно такое, как я здесь предположил, то совместимость классов определяется совместимостью бинарных данных, которые они друг другу посылают. Ну в общем классы должны друг другу соответствовать, хотя, как далее будет видно, это правило на практике (очень редко) нарушается. Если не забыли ещё, то зарегистрированные пары классов задаются идентификаторами класса. В файлах конфигурации (в секциях объектов) этим идентификаторам соответствует параметр class. В скриптах - это один из членов clsid. В движке уже зарегистрированы соответствующие пары классов, и вы можете добавлять свои пары с помощью класса object_factory. Я это всё описывал здесь.Далее я привожу таблицу соответствия классов. Для полноты картины в ней указаны классы, которые не экспортированы в Lua. Если класс не экспортирован, то это означает, что вам не удастся зарегистрировать на его основе свою пару классов. Однако это не мешает (теоретически) создавать объекты этого класса при условии, что имеется и известен его идентификатор.Приведённая таблица собрана из разных источников, в том числе основана на примерах регистрации классов из игры. Видно, что иногда клиентский класс использует в качестве "напарника" серверный, который является базовым для своего "родного" серверного. Видимо, это технически в норме вещей и так можно поступать и нам. Из этого ряда выбивается CZombie, для которого вроде бы родным должен быть серверный класс cse_alife_monster_zombie, а регистрируют его с cse_alife_monster_base. Это вроде как нарушает вышеописанную логику, поскольку cse_alife_monster_base не является базовым для cse_alife_monster_zombie. С другой стороны, он так зарегистрирован в самом движке. Возможно cse_alife_monster_zombie попросту не задействован и никакого отношения к CZombie на самом деле не имеет. Таблица соответствия серверных и клиентских классов Показать Серверный класс | Клиентский класс ---------------------------------------+------------------------------------------------- cse_abstract | cse_alife_graph_point | cse_spectator | CSpectator -- не экспортированный класс cse_temporary | CExplosiveRocket -- не экспортированный класс cse_alife_object_climable | CClimableObject -- не экспортированный класс CSE_AbstractVisual | cse_alife_object | cse_alife_dynamic_object | cse_smart_cover | smart_cover_object cse_alife_online_offline_group | cse_custom_zone | CCustomZone -- не экспортированный класс cse_alife_space_restrictor | ce_smart_zone -- cse_alife_space_restrictor - это базовый | для cse_alife_smart_zone | видимо так можно делать | CSpaceRestrictor | ce_script_zone cse_alife_dynamic_object_visual | CScriptObject -- не экспортированный класс cse_alife_creature_abstract | cse_alife_inventory_box | CInventoryBox cse_alife_mounted_weapon | cse_alife_object_breakable | CBreakableObject -- не экспортированный класс cse_alife_object_projector | CProjector -- не экспортированный класс cse_alife_item | CAntirad | CBottleItem | CFoodItem | CMedkit | CGrenadeLauncher | CScope | CSilencer | CInventoryItemObject -- не экспортированный класс | CMPPlayersBag -- не экспортированный класс | CFlare -- не экспортированный класс cse_alife_trader | CAI_Trader cse_alife_helicopter | CHelicopter cse_alife_car | CCar cse_alife_object_hanging_lamp | hanging_lamp cse_alife_object_physic | CDestroyablePhysicsObject | CPhysicObject cse_alife_ph_skeleton_object | CPhysicsSkeletonObject -- не экспортированный класс cse_alife_creature_crow | CAI_Crow -- не экспортированный класс cse_alife_creature_phantom | CPhantom -- не экспортированный класс cse_alife_creature_actor | CActor cse_alife_monster_abstract | cse_alife_monster_zombie | CZombie -- вроде должен быть здесь cse_alife_monster_rat | CAI_Rat -- не экспортированный класс cse_alife_monster_base | CAI_Bloodsucker | CAI_Boar | CAI_Dog | CAI_Flesh | CAI_PseudoDog | CBurer | CCat | CChimera | CController | CFracture | CPoltergeist | CPseudoGigant | CPsyDog | CPsyDogPhantom | CSnork | CTushkano | CZombie -- это нарушает логику. | cse_alife_monster_base - не базовый для cse_alife_monster_zombie cse_alife_psydog_phantom | CPsyDogPhantom // должно быть так, вместо | этого записан в cse_alife_monster_base cse_alife_human_abstract | cse_alife_human_stalker | CAI_Stalker cse_alife_item_ammo | CWeaponAmmo cse_alife_item_artefact | CElectricBall | CBastArtefact | CBlackDrops | CBlackGraviArtefact | CDummyArtefact | CFadedBall | CGalantineArtefact | CGraviArtefact | CMercuryBall | CRustyHairArtefact | CThornArtefact | CZudaArtefact | CtaGameArtefact -- не экспортированный класс cse_alife_item_bolt | CBolt -- не экспортированный класс cse_alife_item_custom_outfit | CStalkerOutfit | CScientificOutfit -- не экспортированный класс | CMilitaryOutfit -- не экспортированный класс | CExoOutfit -- не экспортированный класс cse_alife_item_helmet | CHelmet cse_alife_item_detector | CAdvancedDetector | CEliteDetector | CScientificDetector | CSimpleDetector cse_alife_item_document | CInfoDocument -- не экспортированный класс cse_alife_item_explosive | CExplosiveItem cse_alife_item_grenade | CF1 | CRGD5 cse_alife_item_pda | CPda cse_alife_item_torch | CTorch cse_alife_item_weapon | CWeaponKnife cse_alife_item_weapon_auto_shotgun | CWeaponAutomaticShotgun cse_alife_item_weapon_magazined | CWeaponBinoculars | CWeaponHPSA | CWeaponLR300 | CWeaponMagazined | CWeaponPM | CWeaponRPG7 | CWeaponSVD | CWeaponSVU | CWeaponUSP45 | CWeaponVal | CWeaponVintorez | CWeaponWalther | CWeaponMagazined -- не экспортированный класс | CWeaponFORT -- не экспортированный класс cse_alife_item_weapon_shotgun | CWeaponBM16 | CWeaponRG6 | CWeaponShotgun cse_alife_item_weapon_magazined_w_gl | CWeaponAK74 | CWeaponGroza | CWeaponFN2000 | CWeaponMagazinedWGrenade -- не экспортированный класс cse_alife_level_changer | CLevelChanger cse_alife_team_base_zone | CTeamBaseZone -- не экспортированный класс cse_alife_smart_zone | ce_smart_zone cse_anomalous_zone | CMincer | CMosquitoBald | CRadioactiveZone | CNoGravityZone -- не экспортированный класс | CZoneCampfire cse_zone_visual | CHairsZone cse_torrid_zone | CTorridZone CSE_ALifeStationaryMgun | CWeaponStatMgun -- не экспортированный класс -- серверный также не экспортирован | Серверный класс | Клиентский класс ---------------------------------------+------------------------------------------------- cse_abstract | cse_alife_graph_point | cse_spectator | CSpectator -- не экспортированный класс cse_temporary | CExplosiveRocket -- не экспортированный класс cse_alife_object_climable | CClimableObject -- не экспортированный класс CSE_AbstractVisual | cse_alife_object | cse_alife_dynamic_object | cse_smart_cover | smart_cover_object cse_alife_online_offline_group | cse_custom_zone | CCustomZone -- не экспортированный класс cse_alife_space_restrictor | ce_smart_zone -- cse_alife_space_restrictor - это базовый | для cse_alife_smart_zone | видимо так можно делать | CSpaceRestrictor | ce_script_zone cse_alife_dynamic_object_visual | CScriptObject -- не экспортированный класс cse_alife_creature_abstract | cse_alife_inventory_box | CInventoryBox cse_alife_mounted_weapon | cse_alife_object_breakable | CBreakableObject -- не экспортированный класс cse_alife_object_projector | CProjector -- не экспортированный класс cse_alife_item | CAntirad | CBottleItem | CFoodItem | CMedkit | CGrenadeLauncher | CScope | CSilencer | CInventoryItemObject -- не экспортированный класс | CMPPlayersBag -- не экспортированный класс | CFlare -- не экспортированный класс cse_alife_trader | CAI_Trader cse_alife_helicopter | CHelicopter cse_alife_car | CCar cse_alife_object_hanging_lamp | hanging_lamp cse_alife_object_physic | CDestroyablePhysicsObject | CPhysicObject cse_alife_ph_skeleton_object | CPhysicsSkeletonObject -- не экспортированный класс cse_alife_creature_crow | CAI_Crow -- не экспортированный класс cse_alife_creature_phantom | CPhantom -- не экспортированный класс cse_alife_creature_actor | CActor cse_alife_monster_abstract | cse_alife_monster_zombie | CZombie -- вроде должен быть здесь cse_alife_monster_rat | CAI_Rat -- не экспортированный класс cse_alife_monster_base | CAI_Bloodsucker | CAI_Boar | CAI_Dog | CAI_Flesh | CAI_PseudoDog | CBurer | CCat | CChimera | CController | CFracture | CPoltergeist | CPseudoGigant | CPsyDog | CPsyDogPhantom | CSnork | CTushkano | CZombie -- это нарушает логику. | cse_alife_monster_base - не базовый для cse_alife_monster_zombie cse_alife_psydog_phantom | CPsyDogPhantom // должно быть так, вместо | этого записан в cse_alife_monster_base cse_alife_human_abstract | cse_alife_human_stalker | CAI_Stalker cse_alife_item_ammo | CWeaponAmmo cse_alife_item_artefact | CElectricBall | CBastArtefact | CBlackDrops | CBlackGraviArtefact | CDummyArtefact | CFadedBall | CGalantineArtefact | CGraviArtefact | CMercuryBall | CRustyHairArtefact | CThornArtefact | CZudaArtefact | CtaGameArtefact -- не экспортированный класс cse_alife_item_bolt | CBolt -- не экспортированный класс cse_alife_item_custom_outfit | CStalkerOutfit | CScientificOutfit -- не экспортированный класс | CMilitaryOutfit -- не экспортированный класс | CExoOutfit -- не экспортированный класс cse_alife_item_helmet | CHelmet cse_alife_item_detector | CAdvancedDetector | CEliteDetector | CScientificDetector | CSimpleDetector cse_alife_item_document | CInfoDocument -- не экспортированный класс cse_alife_item_explosive | CExplosiveItem cse_alife_item_grenade | CF1 | CRGD5 cse_alife_item_pda | CPda cse_alife_item_torch | CTorch cse_alife_item_weapon | CWeaponKnife cse_alife_item_weapon_auto_shotgun | CWeaponAutomaticShotgun cse_alife_item_weapon_magazined | CWeaponBinoculars | CWeaponHPSA | CWeaponLR300 | CWeaponMagazined | CWeaponPM | CWeaponRPG7 | CWeaponSVD | CWeaponSVU | CWeaponUSP45 | CWeaponVal | CWeaponVintorez | CWeaponWalther | CWeaponMagazined -- не экспортированный класс | CWeaponFORT -- не экспортированный класс cse_alife_item_weapon_shotgun | CWeaponBM16 | CWeaponRG6 | CWeaponShotgun cse_alife_item_weapon_magazined_w_gl | CWeaponAK74 | CWeaponGroza | CWeaponFN2000 | CWeaponMagazinedWGrenade -- не экспортированный класс cse_alife_level_changer | CLevelChanger cse_alife_team_base_zone | CTeamBaseZone -- не экспортированный класс cse_alife_smart_zone | ce_smart_zone cse_anomalous_zone | CMincer | CMosquitoBald | CRadioactiveZone | CNoGravityZone -- не экспортированный класс | CZoneCampfire cse_zone_visual | CHairsZone cse_torrid_zone | CTorridZone CSE_ALifeStationaryMgun | CWeaponStatMgun -- не экспортированный класс -- серверный также не экспортирован | Изменено 17 Декабря 2017 пользователем Murarius Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение
Malandrinus 615 Опубликовано 9 Января 2010 (изменено) dimos, Это «Отбойник» - автоматический дробовик из ЗП. Секция wpn_protecta в файле configs\weapons\w_protecta.ltx. Я думаю, что не получится сделать оружие, стреляющее двумя типами патронов. При выстреле из подствольника спавнится граната, обрабатывается её физика и т.д. Это не превратить в выстрел из дробовика. Ну по крайней мере я не знаю, как это сделать. W_GLAUNC - это вообще никак не поможет, поскольку это объект подствольника, а все аддоны - это просто инвентарные предметы. Они не стреляют, стреляют стволы. При надевании аддона сам объект аддона исчезает, а у ствола просто устанавливается соответствующий флажок. Изменено 13 Января 2010 пользователем malandrinus Полезный утиль (Показать) Плагины Total Commander для работы с игровыми архивами: Архиваторный плагин (для работы с одиночным архивом): link1 link2 Системный плагин (для распаковки установленной игры): link1 link2 Поделиться этим сообщением Ссылка на сообщение