Перейти к контенту
Malandrinus

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

Рекомендуемые сообщения

malandrinus

Все же информация о наличии бага в исходном классе CTime скорее всего ошибочна.

1. При каких (общих) условиях проявляется наличие этого бага: сбой старших разрядов счетчика?

а) Версия игры SHoC и версия патча 1.0006;

б) Использование расширителя x-Ray Extension;

 

Не могу уверждать за ваших тестеров (OGSE), но (судя по выложенным кодам работы с CTime) вероятно также сюда можно отнести:

в) Использование функции get_value() добавленной в класс CTime, читающей разряды счетчика.

А точнее, ее обязательное использование при каждом(!) определении дельты времени.

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

 

2. В моих попытках выяснить причину сбоя определилось еще одно обязательное условие:

г) Работа с "zone_informer", т.е. отображение партиклами различных "шейдерных" объектов. Хотя ... это может быть и неким катализатором, ускоряющем появление сбоя.

 

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

Если же отсутствует условие в) (т.е. не читаются разряды счетчиков), то наличие многочисленных "ловушек" бага во всех кодах игры (это сотни счетчиков класса CTime) ни разу не подтвердило сбоя этого класса (при наличии/отсутствии условий а,б,г).

 

Учитывая что собственно баг из разряда "такого не может быть", но он все же стал появляться - следует отнести его к багу именно изменения класса расширителем библиотеки и сбою именно функции get_value, которая вероятно вкупе с использование из той же библиотеки функций работы с шейдерами/матрицами/... и провоцирует наступление сбоя.

Далее - только анализ кода правленной DLL'ки и эксперименты с нею могут подтвердить/опровергнуть ...

 

 

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

Функция get_value ничего с объектом не делает, только читает из него данные. Ошибку я увидел сперва безо всякого применения этой функции, а использовал её только потом, чтобы увидеть, что именно происходит.

Кстати, а что такое zone_informer? Не "Zones Editor" часом имеешь в виду? Если так, то у нас в команде его никто не использует, тестеры и подавно. Он даже не включён в код мода.

Изменено пользователем malandrinus
 

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

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

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

 

Ссылка на комментарий

Однако именно включение данного вызова в код проверки дельты времени в каждый цикл апдейта как раз и дает 100%-ную повторяемость проявления бага.

Вот код, эквивалентный твоему, и которым ловлю баг:

local oCTime = nil --/ запомненный счетчик времени (CTime)
local bGetValue = nil --/ флаг подключения x-Ray Extension

function OnSpawn(e)
  oCTime = game.get_game_time()
  bGetValue = (type(oCTime.get_value) == 'function')
end

function OnUpdate(e)
  --/ проверка наличия 'бага CTime'
  local oCTimeCur = game.get_game_time() --/ счетчик текущего игрового времени
  local iDiffSec = oCTimeCur:diffSec(oCTime) --/ длительность паузы между апдейтами (sec: ~0.1 ... 1.xx ...)
  if iDiffSec > 999 or iDiffSec < 0 then --/ обнаружен сбой (баг CTime)?
    local CTimeToLog = function(time,typ)
      local Y,M,D,h,m,s,ms = time:get(0,0,0,0,0,0,0)
      local sStr = Y.."."..M.."."..D.." "..h..":"..m..":"..s.." :"..ms
      if type(time.get_value) == 'function' then
        local lo,hi = time:get_value() --/ читаем биты счетчика времени
        log("%s:Test_CTime(%s)=>[%s]: hi|lo=[ %s | %s ]", sModule, typ, sStr, string.format("%08x",hi), string.format("%08x",lo) ) --/#~#
      else
        log("%s:Test_CTime(%s)=>[%s]:<%s>", sModule, typ, sStr, "Info") --/#~#
      end
    end
    log( string.rep("~",78) ) --/#~#
    log("%s:Test_CTime:[%s %s]:DiffSec=[%s]/(%s)", sModule, oCTimeCur:dateToString(0), oCTimeCur:timeToString(3), string.format("%5.3f",iDiffSec), string.format("%5.3f", e.delta) ) --/#~#
    CTimeToLog(oCTime,"pre") --/ вывод в лог значений предыдущего счетчика времени
    CTimeToLog(oCTimeCur,"cur") --/ вывод в лог значений текущего счетчика времени
    log( string.rep("~",78) ) --/#~#
    if os then
      log("%s:Test_CTime:[%s]:os.clock=[%s]:%s", sModule, os.date(), string.format("%5.3f",os.clock()), Get_MemUsage(true)) --/#~#
    end
    abort("%s:Test_CTime:DiffSec=[%s]~?:<%s>", sModule, tostring(iDiffSec), "Error!")
  end
  oCTime = oCTimeCur --/ (пере)запоминаем счетчик текущего времени, заменяя прежний
  --/ #!?!# опционально: предположительно провоцирует баг! ----------
  if bGetValue then
    local lo,hi = oCTime:get_value() --/#?# читаем биты счетчика запомненного (а значит текущего!) времени
  end
  --/ ---------------------------------------------------------------
end

 

Обращу внимание(!), что "на сейчас" оставил только один последний вызов проверки битов запоминаемого счетчика (по сути баласт).

СтОит закомментировать нижние строки с get_value, любые манипуляции в игре, в том числе и с окнами и с zone_informer'ом не дают сбоев.

Если же при этих строках на сэйве, который уже упоминал, поиграться с шейпами и инвентарными окнами - вылет считай гарантирован именно по сбою старших битов счетчика.

Также, вот этот код:

function Check_CTime(oCTime, sMsg, is_abort)
  local lo,hi = nil,nil
  if bGetValue then --/#!# опционально: предположительно провоцирует баг!
    lo,hi = oCTime:get_value() --/#?# читаем биты счетчика времени
  end
  local Y,M,D,h,m,s,ms = oCTime:get(0,0,0,0,0,0,0)
  if Y < 2002 or Y > 2013 then
    local sTxt = sModule..":Check_CTime=["..Y.."."..M.."."..D.." "..h..":"..m..":"..s.." :"..ms.."]"
    if lo and hi then
      sTxt = sTxt..":hi["..string.format("%08x",hi).."]:lo["..string.format("%08x",lo).."]"
    end
    sTxt = sTxt..":"..tostring(sMsg)..":<Warning!>"
    if is_abort then
      abort( (debug and debug.traceback(sTxt)) or sTxt )
    elseif debug then
      _G.to_log( debug.traceback(sTxt) )
    else
      _G.to_log( sTxt )
    end
  end
end

 

- стоИт на всех(!) кодах игры/мода, где проверяется дельта времени по CTime, что по примерным подсчетам дает работу с более сотней-двумя объектов, некоторые из которых почти также апдейтятся, хотя с несколько иными частотами и в других циклах/потоках. И ни разу пока в этих объектах не был зарегистрирован сбой.

 

ИМХО, все же налицо связь бага с именно расширителем библиотеки + использование zone_informer'а. По-меньшей мере оба условия, как ранее выразился - катализатор проявления этого бага. Ну а "очень редкий" баг - следствие одной из этих причин.

 

P.S. Сорри, под "zone_informer"ом имею ввиду несколько модифицированные коды именно твоего "Zones Editor"а.

У меня этот скрипт адаптирован под актуальные для меня варианты "xrLua Extensiion" (функционал кейлоггера) и модуль нет-пакетов m_netpk (читалка). Порой в игре/разработке просматриваю (функционал информера!) формы и расположения различных объектов. Функционал редактора не использую, так что в данном контексте только отображение шейпов партиклами и их очистка может играть какую-то роль.

Подметил, что чаще и быстрее всего баг с Ctime насупает после показа шейпов перехода со Складов на Бар + их очистке + открытий/закрытий инвентаря.

- ну а то, что вашими тестерами не используется - несколько меняет картину и ... остается вероятным виновником именно расширитель, а в моем случае информер (кстати, также использует расширитель) - является именно катализатором.

 

 

Добавлено через 134 мин.:

 

P.P.S. Еще немного пищи для размышлений:

Если заменить вызов проверки бага из циклов апдейта актора на иной - спровоцировать ошибку не удается:

function Start_Test_CTime() --/ проверка: CTime
    --event("actor_update"):register(Test_CTime) --/ for debug CTime
    --/ заменяем апдейт актора на иной:
    level.add_call(Test_CTime, function() abort("Test_CTime:~>FATAL_ERROR!") end)
end

Вероятно все же все дело в стеке. При различных манипуляциях с окнами и пр. наличие в стеке постоянно перечитываемого счетчика приводит к его сбою/затиранию старшего бита (u64 - вероятно хранится не в общих регистрах). Вынос/смещение счетчика по отношению к апдейту актора - изменило условия/структуру стека, сохранив ту же частоту опроса (у меня DiffSec ~0.070 game-second).

Хотя ... вероятно теперь что-то иное может давать сбой (???), т.к. если были сбои/затирания стека - то врядли они куда исчезли. Просто в каких то вариантах они не критичны.

А вот что корежит стек - можно только предполагать. ;-)

 

 

Изменено пользователем Artos

"Но иногда найдется вдруг чудак, этот чудак все сделает не так ..."© Машина времени

Ссылка на комментарий

В ТЧ есть функция game_object.invulnerable(), которая не внесена в lua_help. Работает и на чтение, и на изменение - только что проверял.

 

Ссылка на комментарий

Недавно столкнулся с одной неприятной особенностью параметра m_smart_terrain_id, который есть у всех классов, наследуемых от cse_alife_monster_abstract. Я пробовал переделать скрипт smart_terrain.script так, чтобы регистрировать в них эксклюзивных NPC с помощью присвоения m_smart_terrain_id и последующего вызова register_npc из смарта. Появилась куча проблем с двойными регистрациями: несмотря на то, что я непосредственно перед регистрацией в смарт какого-нибудь NPC отключаю ему автопоиск смартов вызовом brain():can_choose_alife_tasks(false), он каким-то образом уже оказывается зарегистрированным в другом смарте ещё до этого вызова. А причина оказалась простой: у NPC, только что заспавненных скриптом, при запросе значения из этого параметра вызывается обновление планировщика brain():update(). Даже если я использую функцию smart_terrain_id() для запроса значения - тот же результат. Пример:

 

function se_monster:on_register()
    cse_alife_monster_base.on_register(self)
    self.initialized = true
    local smart_id = self:smart_terrain_id()
    get_console():execute(self:name().."_smart_id_"..tostring(smart_id))
    ...

 

 

При скриптовом спавне первой попавшейся собаки выводит:

! Unknown command: dog_normal17423_smart_id_637

 

То есть NPC уже зарегистрирован в смарте, у которого id равен 637.

 

Проблема решается выключением автопоиска смартов непосредственно перед запросом значения - тогда просто вернёт 65535. Пока не проверял, но предполагаю, что вызов can_choose_alife_tasks меняет flUseSmartTerrains в object_flags.

Изменено пользователем Полтергейст
Ссылка на комментарий

Ошибочку обнаружил в описании "Пространство имён level":

bool map_has_object_spot(int <id>, string <тип метки>) -- проверить наличие метки

в действительности возвращает не bool, а 0 или число.

Ссылка на комментарий

Заметил странность при работе с функцией level.vertex_in_direction.

У меня она возвращает переданный ранее в нее вертекс(то есть ни о каком расстоянии по направлению не может быть и речи), если в векторе направления x - отрицательное. Для положительных x работает нормально.

Ссылка на комментарий

Real Wolf, в теме скриптования есть посты на странице 223 в самом начале и пост 4489 про получение левел вертекса по позиции.

В целом - не в тему, хотя там есть пояснение с картинкой. Если в направлении нужного нам лв будет разрыв аи-сетки (камень, дерево, стена и т.п.) то возвратится граничный лв. Прикол в том, что если лв, от которого считаем, и есть граничный, и направление в сторону разрыва, то вернется он сам. Не умеет level.vertex_in_direction перескакивать через пустоту в аи-сетке.

ТЧ 1.0004. SAP и Trans mod

github

Ссылка на комментарий

Ребят, подскажите плз или дайте ссылку на то, как пользоваться функцией level.add_dialog_to_render(CUIDialogWnd*) (ЗП) нид срочно прикрепить к окну инвентаря элемент, просто создать кастомстатик не выход, так как в любом случае окно инвентаря накладывается поверх кастомстатика элемента

 

Добавлено через 34 мин.:

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

Изменено пользователем Viнt@rь
Ссылка на комментарий

В оригинальном ЗП никак не приаттачить к окну. Решение есть - использовать X-Ray Extensions. В нем есть функция получения окна для дальнейшей работы с ним. Но работает эта идея только в полноэкранном режиме. К тому же есть там одна тонкость в получении - получить окно можно только на следующем апгрейде после получения инфопоршня об открытии

Freedom

Ссылка на комментарий
В общем все равно элемент остается под окном инвентаря,

А выводить свое окно после открытия инвентаря или на апдейте не пробовали?

Мод, где не бывает одинаковых путей - Судьба Зоны. (Лучшее, что у меня получилось на X-Ray) На базе модифицированного движка OGSR Engine.

Бывший мододел на X-Ray / Начинающий игродел на Unreal Engine. Программист.

AMD Ryzen 9 7950X (16 ядер, 5.7ГГц); RTX 3080; 128 ГБ DDR5; Arctic Liquid Freezer II-420; 3 ТБ SSD PCIe 4.0; 4ТБ HDD.

Ссылка на комментарий

Обнаружил в игре такой баг (проверял на патче 1.0006): при спавне какого-нибудь объекта скриптом, функция on_register серверного объекта может вызваться дважды. Этим и объясняются "странности" с параметром m_smart_terrain_id: в том примере объект регистрировался в каком-то смарте до второго вызова on_register, если ему вызовом :can_choose_alife_tasks(false) не было запрещено это сделать.

 

 

Обходить баг и выводить в консоль имена объектов, которые вызывают on_register дважды, можно как-то так (пример для se_monster):


function se_monster:on_register()
cse_alife_monster_base.on_register(self)
if self.initialized then
get_console():execute(self:name().."_already_registered_in_simulation")
return
end
self.initialized = true
...
end

 

 

 

 

Ещё выяснил, где появляется вот эта ошибка:

 

Expression : false

Function : CGameGraph::distance

File : e:\stalker\sources\trunk\xr_3da\xrgame\game_graph_inline.h

Line : 96

Description : There is no proper graph point neighbour!

 

 

 

Она может проявляться при вызове метода :task() из смарта. Этот метод вызывается, если вызвать метод :update() (если он приводит к вызову одноимённого метода движкового класса) из серверного объекта какого-нибудь NPC, который уже прописан в смарте и может передвигаться в оффлайне (метод move_offline() возвращает true). По каким-то причинам баг проявляется именно тогда, когда NPC в онлайне. И это не связано с недоступностью нужной вершины - проверка на доступность и использование индекса доступной точки пути (второй аргумент в конструкторе CAlifeSmartTerrainTask) ни к чему не приводит. Чаще всего проявляется после смены работ, то есть когда путь, переданный в конструктор CAlifeSmartTerrainTask отличается от пути, использованного для создания CAlifeSmartTerrainTask в прошлый раз. Возможно, нежелание разрабов использовать функцию xr_effects.smart_terrain_force_update без крайних случаев связано именно с этим.

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

 

Возвращает ли эта функция несколько значений которым равен ключ? тоесть

key1 = value1, value2, value3...

 

и еще: параметры number, string&, string& для чего здесь?

 

глядя на пример использования: result, idx, value = ltx:r_line(section,i,"",s) возникает вопрос, что функция возвращает в result и почему передаваемые параметры по типам не совпадают с тем что написано в описании функции, что выше в цитате

Изменено пользователем Viнt@rь
Ссылка на комментарий

Есть такое волшебное средство как поиск...

С этого поста и далее по тексту есть пояснения не только твоего вопроса

  • Нравится 1

ТЧ 1.0004. SAP и Trans mod

github

Ссылка на комментарий

Классы и функции, используемые для оффлайн передвижения

 

Классы:

 

CALifeMonsterBrain

CALifeHumanBrain

CALifeMonsterMovementManager

CALifeMonsterPatrolPathManager

CALifeMonsterDetailPathManager

CALifeSmartTerrainTask

 

 

Как это всё работает:

 

Каждому NPC соответствует свой объект класса CALifeMonsterBrain (или наследуемого от него CALifeHumanBrain), который можно получить так:

local brain = se_obj:brain()

где se_obj - серверный объект NPC.

Вызов brain:can_choose_alife_tasks(true/false) включает или отключает автоматический выбор точки, в которую NPC пойдёт (подробности ниже).

Далее, можно получить объект класса CALifeMonsterMovementManager, соответствующий данному серверному объекту:

local movement_mgr = brain:movement()

По непонятным причинам вызов movement_mgr:start_type() приводит к зависанию.

Сам по себе этот объект не позволяет управлять передвижением NPC, зато с помощью него можно получить объекты классов CALifeMonsterPatrolPathManager и CALifeMonsterDetailPathManager:

local patrol_mgr = movement_mgr:patrol()
local detail_mgr = movement_mgr:detail()

Класс CALifeMonsterPatrolPathManager позволяет задать имя пути методом path(path_name), а также точку, с которой начинать движение по пути, вызовом start_vertex_index(vertex_index). Однако это ни на что не влияет, т.к. тип пути и другие параметры невозможно установить из-за того, что нижеперечисленные методы (не важно, с аргументами или без) просто зависают при вызове и вызывают вылеты "Error in error handling" или "C stack overflow":

 

start_type()

route_type()

target_game_vertex_id()

target_position()

target_level_vertex_id()

 

 

Реально же управлять оффлайн-передвижением позволяют классы CALifeMonsterDetailPathManager и CALifeSmartTerrainTask. Чтобы послать NPC в заданную точку пути, необходимо сначала создать объект класса CALifeSmartTerrainTask:

local my_task = CALifeSmartTerrainTask(path_name, point_index)

где path_name - имя пути, point_index - индекс точки пути. В ТЧ объект этого класса не может быть создан для произвольной точки, не привязанной к какому-либо пути. Чтобы узнать положение уже созданного объекта CALifeSmartTerrainTask в пространстве, можно использовать значения, возвращаемые при вызове level_vertex_id(), position() и game_vertex_id().

Далее нужно установить эту точку для NPC в качестве целевой. Это делается методом target() класса CALifeMonsterDetailPathManager:

detail_mgr:target(my_task)

В lua_help описаны ещё 2 варианта использования этого метода:

function target(const number&, const number&, const vector&);

function target(const number&);

Если в первом требуется передать game_vertex_id, level_vertex_id и координаты точки, то непонятно, в каком порядке передавать game_vertex_id и level_vertex_id. В каком бы порядке я их не передавал, игра вываливалась c ошибкой "There is no proper graph point neighbour".

Во втором непонятно, что за число туда передавать. Возможно это game_vertex_id, я не проверял.

Далее, метод completed() возвращает true, если NPC дошёл до точки, иначе false. А вот с методом failed() не совсем понятно. Нередко он возвращает true тогда, когда completed() тоже возвращает true.

Метод actual() возвращает true, если completed() и failed() возвращают false.

Метод speed () недоступен из-за опечатки, т.к. в конце его имени стоит символ табуляции. Судя по названию, должен возвращать скорость передвижения.

 

Теперь подробности о том, как всё это работает.

Оффлайн-передвижение в оригинальной игре завязано на smart_terrain (или просто "смарты") - объектах класса se_smart_terrain, наследуемого от cse_alife_smart_zone. У каждого серверного объекта NPC (людей или зверюшек) есть свойство m_smart_terrain_id - id смарта, в котором прописан NPC. У каждого смарта есть метод task(), который возвращает объект класса CALifeSmartTerrainTask.

 

Обновление оффлайн-передвижения происходит при вызове метода se_obj:brain():update(), где se_obj - серверный объект NPC. Также он вызывается движком при вызове se_obj:update(). Стоит отметить, что se_obj:update() периодически вызывается движком для всех NPC, а вот вызывать его искусственно (например, из биндера клиентского объекта) не стоит, будут ошибки "There is no proper graph point neighbour".

 

Алгоритм его работы примерно таков:

 

1. Проверить, включен ли автовыбор смартов. (см. can_choose_alife_tasks())

1.1. Если включен - перебрать все смарты, вызывать из них :enabled(). Если вернёт true, то вызвать suitable() и запомнить возвращённое значение. Из всех значений, возвращённых :suitable(), выбрать наибольшее. Прописать NPC в смарте, который соответствует этому значению, присвоением его id в m_smart_terrain_id и последующим вызовом register_npc(). Перейти к п.2.

1.2. Если до вызова обновления оффлайн-передвижения для этого объекта была установлена целевая точка (см. описание CALifeMonsterDetailPathManager.target()), идти в неё. Иначе перейти к п.2.

2. Если объект прописан в каком-то смарте (m_smart_terrain_id < 65535), вызвать из этого смарта :task(), установить возвращенное значение в качестве целевой точки (опять же, метод target()) и идти в неё. Иначе ничего не делать.

 

 

 

Вот вроде бы и всё. Непонятно, зачем разрабы оставили этот алгоритм в самом движке, когда проще было бы делать всё вышеописанное скриптами. В ТЧ в оригинальной игре автовыбор смартов всегда включен, поэтому если вам нужно послать NPC в какую-то заданную точку (например, перевести через уровень), то автовыбор надо отключать, чтобы не возиться с созданием временных smart_terrain.

  • Нравится 2
  • Полезно 1
Ссылка на комментарий

Товарищи исследователи, может кто подскажет решение задачи. Есть точка (vector) и направление (vector) от неё, для моего случая это актор целящийся куда-либо из оружия. Хочется получить точку прицела на геометрии уровня/объекта, или указания что ничего нет по курсу.

Сойдут любые изощреные методы, включая вызов методов интефейсов Direct3D, работа с буфером вертексов или рендером движка. В теории должен быть самый легальный метод для частного случая актора, ведь движок имеет возможность показывать дистанцию до цели. Но вдруг есть и универсальный метод?

 

Типичную дистанцию отсечки, и косвенно через неё точку выцеливания можно посчитать так (псевдо-код):

g_hud := GetProcAddr ('xr_3da.exe', '?g_hud@@3PAVCCustomHUD@@A');
table := g_hud [$0034]; // + 52 byte
target_id := table[$0000]; // DWORD if game object
target_dist := table[$0004]; // float32
// lua code:
point = device().cam_pos
dir = device().cam_dir
dir:mul(target_dist)
point:add(dir)

Изменено пользователем alpet

Плавайте поездами аэрофлота!

Ссылка на комментарий

В таблице экспорта xr_3da.exe есть метод RayPick. Вот сигнатура:

int CObjectSpace::RayPick(CObjectSpace *this, _vector3<float> *start, _vector3<float> *dir, float range, collide::rq_target tgt, collide::rq_result *R, CObject *ignore_object)

Назначение аргументов, в целом, очевидно: start - стартовая точка, dir - направление трассировки (нормализованный вектор), range - диапазон трассировки. ignore_object - игнорируемый при трассировке объект, tgt - флаги геометрии:

 

enum collide::rq_target
{
rqtNone = 0x0, - не определять ничего
rqtObject = 0x1, - определять динамические объекты
rqtStatic = 0x2, - определять статическую геометрию
rqtShape = 0x4,
rqtObstacle = 0x8,
rqtBoth = 0x3,
rqtDyn = 0xD,
};

Результат доступен через R, там будет лежать объект типа collide::rq_result:

 

struct collide::rq_result
{
CObject *O;
float range;
int element;
};

В случае статической геометрии объект O будет пустой. Если нужно получить больше информации по статической геометрии, можно поковырять методы CDB::COLLIDER.

Если стоит задача работать с трассировкой луча из скриптов, можно посмотреть xray-extensions. В одной из последних ревизий malandrinus экспортировал RayPick в скрипты.

Изменено пользователем KD87
  • Нравится 2
Ссылка на комментарий

Спасибо! То что нужно. Кстати может разработчики движка ещё предусмотрели, как быстро определить статус точки indoor / outdoor (в смысле в здании или на улице)?

Плавайте поездами аэрофлота!

Ссылка на комментарий

alpet, В игре это делается путём постоянного рейтрессинга. Если в направлении дождя видишь небо (т.е. ничего не видишь), значит на улице. Это легко проверить. Во время дождя, если в помещении пройти под дырочкой в крыше, то сразу зашумит, как будто стоишь на улице =)

 

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

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

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

 

Ссылка на комментарий

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

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти
  • Недавно просматривали   0 пользователей

    Ни один зарегистрированный пользователь не просматривает эту страницу.

AMK-Team.ru

×
×
  • Создать...