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

Народная 2010 разработка


n6260

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

Shadowman,

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

 

local t1 = {}
lcoal t2 = {}

log(tostring(t1 == t2)) -- вернёт false

t1.a = 3
t2.a = 3

local mt = {}
mt.__eq = function(op1, op2)
    return op1.a == op2.a -- сравниваем по содержанию
end
setmetatable(t1, mt)
setmetatable(t2, mt)

log(tostring(t1 == t2)) -- вернёт true

Пример упрощённый, но рабочий. И надо понимать, что никакой выгоды по скорости я не имею. Просто сделал синтаксическую обёртку для своего кода сравнения.

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)

Shadowman, sapsan,

крайне не рекомендуется пользоваться магическими числами, как это сделано в примере выше. Мне точно известно, что константы из класса/перечисления clsid отнюдь не постоянны. При добавлении в class_registrator новых сетов новые идентификаторы могут втыкаться в совершенно произвольное место и, таким образом, смещать существующие. Логики назначения я не понял, хотя, если честно, сильно и не искал. Не обязательно в этом случае, но это может стать причиной ошибок.

 

И да, так проверять совершенно недопустимо. Надо примерно так, как это делается в функции IsMonster (в ЗП версии)

local anomaly_classes = {
    [clsid.zone_electra_s] = true, -- <<== никаких магических чисел, только именованные константы !!!!
    [clsid.nogravity_zone] = true,
    и т.д.
}

function IsAnomaly(obj)
    return anomaly_classes[obj:clsid()] == true
end

Код понятнее, ошибок меньше и работает быстрее.

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
Т.е нет разницы,если я 1 раз определю

obj = my_class()

и 100 раз вызову

obj:any_func()

 

или если я 100 раз вызову

my_class():any_func()

Разница есть, если my_class() создаёт новый объект. В этом случае self будет всякий раз новый.

 

Добавлю свои пять копеек во флейм. Поддерживаю общее мнение насчёт невозможности совершенно новой разработки. Призывать мне верить не буду, в качестве аргумента могу предложить взглянуть на заглавную страницу нашего уважаемого портала в раздел "новости". Ещё могу предложить вспомнить о судьбе Netscape Navigator-а (если о таком кто-то ещё помнит). Его помнится на каком-то этапе тоже решили с нуля переписать. Байтораздирающее зрелище было =)

 

По поводу переноса на новый движок. По-моему - это почти нереально. Со скриптами будет проблема в основном только с окошечной частью. Остальное вроде как не сильно изменилось. Но это же не всё. Есть ещё объекты из all.spawn, модели, анимации? Фактически - это будет новая разработка (см. выше).

 

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)
Что-то табличка та с константами, перерисованными из луа_хелп вызывает вылеты.

До определённого момента класса clsid не существует. В частности, его ещё нет в самом класс-регистраторе. Если проблема в этом, то надо добиться того, чтобы код инициализации сработал позднее.

 

Добавил:

 

Да, точно. Причина именно в этом. Вероятно поэтому в ТЧ функции типа IsMonster и реализованы так убого. В ЗП эту проблему решили как положено: код инициализации помещён в start_game_callback. Срабатывает не сразу, поэтому clsid уже существует.

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

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

1. самое глобальное различие - по классу

2. менее глобально - по секции (с одним классом могут быть разные секции)

3. ещё менее глобально - по story_id или по id

 

где-то между 2 и 3 лежит различие по наличию инфопоршена. Все прочие способы классификации сами уже классификации не поддаются =)

 

 

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
Но чекер не мог не выдать ошибку - "actors\stalker_psihiator_master" - так писать низя.Ругань на символ "\" - он служебный для \n \f и т.д.

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

"A\MK" == "AMK"

 

 

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

conv_vis = {
["actors\\hero\\stalker_novice"] = [[actors\hero\stalker_novice]],
..
}

 

Что-то вы, товарищи, маетесь кое-чем. Строки "actors\\hero\\stalker_novice" и [[actors\hero\stalker_novice]] равны.

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

 

 

 

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)

sapsan,

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

 

Кроме того, для переменных типа userdata всё не так просто как для скажем таблиц или строк. Таблицы и строки создаются самим Lua, им же и удаляются (с помощью сборщика мусора). А userdata как выясняется бывают двух типов full userdata и light userdata. Разницу видно со стороны хост-программы. Со стороны скрипта заметить сложно.

full userdata - это такой объект, память для которого выделяет Lua, соответственно и освобождает он же с помощью сборщика мусора.

light userdata - это в чистом виде указатели на объекты, созданные вне Lua. Для Lua - это просто указатель, о природе которого он ничего не знает, и никакого удаления он естественно не делает. Как созданием так и удалением в этом случае занимается хост-программа.

Я попробовал выяснить и получилось, что вроде как все объекты userdata - это full userdata, поскольку они имеют метатаблицу и в ней поле __gc, что указывает на собираемость этого объекта сборщиком мусора. С другой стороны, это ничего не значит, поскольку для экспорта классов здесь используется технология Luabind, где походу для всех реальных объектов создаются дополнительно обёртки, и объекты userdata, которые мы наблюдаем - это не внутренние объекты, а эти Luabind-овые временные объекты. Они и впрямь собираются сборщиком мусора, но что происходит при этом с "настоящими" объектами не вполне ясно. Это в данном случае зависит от Luabind. Там есть соответствующая настройка при экспорте класса. Естественно, что именно включено при экспорте конкретного класса - не известно.

 

Остаётся полагаться на здравый смысл и известное поведение: vector и прочее в этом роде создаётся из Lua и собирается сборщиком мусора, когда не нужен. game_object и серверные объекты создаются вне Lua и соответственно их удаление явно (alife():release()) или неявно (выход в оффлайн) выполняется в движке. Не вполне понятно, как себя ведут объекты типа звуков и партиклов. К примеру particles_object. Вроде создаётся из скрипта наподобие того же vector. Но потом можно запустить его играть и забыть о нём. Вот не помню, надо ли хранить ссылку на объект в этом случае. Если не надо, то кто тогда займется его удалением? И когда?

 

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

sapsan,

Они то погибают, но место своё не освобождают

это не так. Рассмотрим простой пример:

local b = 1 -- (1)
do
    local a = {1,2,3} -- (2)
    local b -- (3)
    ...
    b = 2 -- (4)
end -- (5)
print(b) -- (6)

В этом примере есть блок, который ограничивает область существования переменной a. Теперь по порядку:

в точке (2) происходит создание двух сущностей: локальной переменной a и таблицы {1,2,3}. В этой же строке в переменную a пишется ссылка на эту таблицу (переменная инициализируется таблицей). Таблица создаётся в динамической памяти и за её удаление отвечает сборщик мусора, а переменная a создаётся в стеке блока и существует до конца этого блока. Поскольку стек по выходу из блока освобождается, то переменная a прекращает своё существование в точке (5). Именно в этой точке, не раньше и не позже. И память освобождается в этот же момент. И это не имеет вообще никакого отношения к сборщику мусора.

Ещё пример. Переменная b, которая создаётся в точке (3), не инициализируется ничем, а значит принимает значение по умолчанию nil. И она опять же живёт в стеке, а значит существует до конца блока. То, что она существует и никто её не удаляет подтверждает простой эксперимент. В точке (4) я пишу в переменную b значение. Если бы b удалялась, то я бы писал в переменную, объявленную в строке (1), но если проверить, то в строке (6) выводится по прежнему значение 1, а не 2. Т.е. я писал в b из строки (3), которая всё ещё существует, но в точке (5) удаляется и вместо ней становится видна b из строки (1).

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

sapsan,

то, что локальные переменные живут в стеке из документации понять можно. Загляни в часть, где описывается интерфейс с СИ. Хотя это и так довольно очевидно. Локальные переменные в стеке - это настолько очевидная идея, что делать на этом акцент никому даже в голову не придёт, разве что на особенностях реализации. Вот например здесь стек реализован свой, а не используется процессорный. Что они точно удаляются там конечно не пишут, поскольку это самоочевидно. Ты представляешь себе, что такое стек? Как может там остаться значение, когда мы его освободили?

 

Сборщик мусора обрабатывает все объекты Lua: таблицы, данные типа userdata, функции, потоки и строки.

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

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

Конечно, в документации нет такого уж акцента на этом разделении, поскольку это в сущности детали реализации. Опять же многословность не помогает новичкам в освоении нового языка.

 

a = {}
local x = 20
for i=1,10 do
     local y = 0 -- (1)
     a[i] = function () y=y+1; return x+y end -- (2)
end -- (3)

Цикл создает 10 экземпляров функции, в которых используются различные переменные y и один и тот же x.

 

Само повторное объвление в цикле local y к чему ведёт в плане использования стека/памяти?

по примеру. на каждой итерации:

(1) создаётся ссылка на y (в стеке) + значение "0" (в динамической памяти)

(2) создаётся (инстанцируется) новая функция (в динамической памяти), внутри функции идёт ссылка на значение "0"

(3) освобождается стек тела цикла, ссылка из (1) теряется, но значение остаётся, поскольку на него есть ссылка из функции, а сама функция остаётся потому, что на неё есть ссылка из массива

всё это повторяется 10 раз: десять разных стеков, 10 дубликатов функций, 10 дубликатов значения "0"

Все ссылки на значение x естественно ссылаются на одно и то же значение "20"

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Ray,

если тебе нужна аналогия из С++, то это будет нет так:

for (int i=1; i <=10; i++)
{
    int y = 0;
    ...
}

а вот так:

for (int i=1; i <=10; i++)
{
    int *y = new int(0);
    ...
}

На каждой итерации я объявляю и инициализирую переменную y и вывожу ее адрес (комбинация &y). В результате я вижу 10 раз один и тот же адрес.

Ну и что? Просто новый кадр стека при очередной итерации попал в точности на место старого. Так уж вышло из-за того, что старый кусок стека был в точности того же размера, что и новый.

 

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

Ray,

нет, в этот раз fun инстанцирована всего один раз

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Monnoroch,

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

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

Я бы разделял проверки как минимум на две категории по типу ошибок: исправляемые и неисправляемые.

Проверка неисправляемых ошибок - это то, что называют assertion. Выглядит примерно так:

if <условие ошибки> then <обрушить игру с сообщением> end

смысл в том, чтобы так проверять ошибки, которые принципиально исправить нельзя. Ни в коем случае нельзя ставить в этом месте некую заглушку и продолжить выполнение. Разумеется, такие ошибки должны быть исправлены до выхода релиза, а все проверки в релизной версии могут быть удалены. В языках C/C++ это обычно оформляется в макрос ASSERT(), который в релизной версии удалается препроцессором. В Lua препроцессора нет, поэтому придётся удалять вручную.

 

С исправляемыми ошибками сложнее. Собственно они и не ошибки даже, а просто разные нестандартные ситуации. Например получаем объект по имени, а такого не найдено. Быть может это и нормально, но требует дополнительной обработки. Главная мега-сложность в том, что ошибка определяется в одном месте, а реагировать на неё надо в другом. Вот простой пример. Функция деления одного числа на другое. На 0 делить нельзя, поэтому эту ситуацию надо проверять. assert в этом месте можно поставить только тогда, когда можно алгоритмически обеспечить, что в этом месте в знаменатель 0 не попадёт. Если нельзя, то надо как-то реагировать не обрушивая программу. И вот сложность. В самой функции деления нет возможности обеспечить адекватную реакцию. Наивный подход был бы типа такого:

fun_div(a,b)
    if b == 0 then return 0 end
    return a/b
end

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

 

А что делать здесь? Логичным выглядит утверждение, что в этой функции ничего проверять не надо. Это надо сделать перед вызовом этой функции, где можно как-то загодя исправить ситуацию. Если и там этого сделать нельзя из тех же соображений отсутствия информации и полномочий, то значит подымаемся ещё выше, пока не найдем место, где есть возможность обеспечить непрохождение этого злополучного нуля дальше в программу. Слабое место этого подхода в том, что функции могут вызывать разные люди, в том числе и те, кто понятия не имеет, что функция делает некие предположения о безопасности аргументов.

 

Другой вариант - коды возврата. Функция возвращает дополнительный результат (в Lua это можно сделать именно как второе возвращаемое значение). Вызываюшая функция обязана этот второй аргумент проверить, в свою очередь вернёт код возврата в её вызывающую функцию и т.д. пока не вернёмся в функцию, которая способна исправить ситуацию. Слабое место такого подхода очевидно. Да кто вообще проверяет коды возврата? Крайне сложно будет заставить всю цепочку вызовов отрабатывать это взаимодействие.

 

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

 

Можно подойти к проблеме расстановки проверок с иной стороны. Проверка - это компенсация нашего незнания о ситуации. Проверять надо там, где мы в чём-то не уверены. Вот например функция alife():create(). после неё можно не проверять наличие созданного объекта, поскольку если он не будет создан, то игра попросту вылетит ещё при выполнениие create. Проверять надо разуеется аргументы перед вызовом. А вот alife():object() - это другое дело, это функция поиска объекта. Может и не найти, поэтому нужно проверить результат.

 

Если есть участок кода, о котором я знаю всё, то внутри можно и не ставить проверки. Например идёт сквозь мой алгоритм некий объект. Зачем проверять его существование, если я точно знаю, что ему деваться некуда? А потом я стыкуюсь с чужим кодом. Я о нём не всё знаю, поэтому ставлю проверки на границе моего и чужого кода. Когда узнаю о чужом коде больше, то можно многие проверки удалить. Поэтому так важно документировать интерфейсы.

 

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

 

Герою, дочитавшему до конца :ny_z_1:

 

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

Ранее я здесь написал, что в биндере save вызывается всегда, а load только после первого save. Перепроверил сейчас. Всё выглядит сложнее. load не вызывается при первом переходе в онлайн после создания объекта. Далее при переходе в онлайн вызывается всегда. Логика работы save пока для меня выглядит необъяснимой: вызывается всегда в момент сохранения, а при выходе в оффлайн не вызывается. Кроме того, я уже не уверен, что так работает всегда. Возможно есть параметры и функции, которые на этот механизм влияют. Проверял для аптечки. И, честно говоря, тогда не понимаю уже логики этого механизма. Может кто может объяснить? Как тогда сохраняются мои данные при выходе в оффлайн, если я не сохранялся специально?

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
Но никакого перевода в оффлайн нету.Ну я не видел.

Да ведь шутка это была насчёт маскхалата, смайл там стоит =)

Если серьёзно, актор переходит в оффлайн при выходе из игры или при смене уровня. Делает это последним из всех объектов (как и в онлайн первый). Вероятно потому, что у него нулевой id.

 

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

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

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

 

Поделиться этим сообщением


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

Допустим сдох непись и в момент смерти имел некий набор инфорпоршенов. Я создаю такого же с тем же набором. Что ещё может влиять на ситуацию? Можно предположить, что инфопоршены актора, инвентарь непися. Что-то мне подсказывает, что вряд-ли в диалогах с неписем как-то учитываются инфопоршены о смерти этого самого непися. Инвентарь при смерти удаляется, но квестовые предметы вроде как остаются. Что ещё?

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

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

Честно говоря, я бы вместо этого делал передачу квеста при смерти квестоносителя. Вроде как это даже было сделано, или обсуждалось как минимум. А неписи пусть воскресают "с амнезией" =)

 

Знает ли кто как проиграть звук на перемещающемся объекте

Думаю, это можно сделать так же, как играется речь. Сначала звук надо к неписю добавить

npc:add_sound()

затем играть

npc:play_sound()

 

с использованием этих функций признаться не до конца разобрался. Вот вроде как рабочий пример:

cobj:add_sound("script_replics\\radio\\commander\\radio_finish_", 
    30, snd_type.talk, 2, 2, xr_sounds_id.zmey_bodyguard_base + 2)
cobj:play_sound(
    xr_sounds_id.zmey_bodyguard_base + 2, 
    1, 0, 1, 0, 
    4)

 

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)

Заглянул в репозитарий. На мой взгляд, нет никакого смысла хранить там бинарные файлы. Надо хранить распакованные + батничек для сборки. Это касается как минимум аллспавна, партиклов и глобального графа. Более того, при некоторой аккуратности изменение этих файлов и добавление к ним данных даже не приведёт к необходимости начинать новую игру.

 

Добавил.

 

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

 

Могу дать доступ на запись в хранилище для создания своей ветки. sapsan

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)
Мысли по ходу:

использование math.random() нужно заменить на random_number() так как в последнем перед вызовом math.random() вызывается math.randomseed(device ():time_global()), что даст действительно случайный результат, а то, например, у игроков и у меня часто ЧУ не начинается подряд много раз...

Это лишено смысла. К чему может привести предлагаемая стратегия иллюстрирует вот такой пример:

for i=1,10 do
    get_console():execute(tostring(random_number()))
end

Если в начале random_number стоит вызов math.randomseed, то этот код выводит 10 одинаковых чисел. Вызов math.randomseed должен быть сделан один раз за всю игру в начале.

Почему-либо что-то не происходит с нужной частотой надо разбираться, но уж точное не грешить на недостаточное качество псевдослучайной последовательности. И надо убрать math.randomseed из random_number - это просто неправильно.

 

Проверил - да, такой цикл выведет одно и то же число 10 раз. А вот если поставить get_console():execute(tostring(random_number())) в апдейт актора, то будем получать по кругу плавно возрастающие значения, для примера, от 0.00033570360392332 до 0.99887079000473. Тоесть math.randomseed() есть смысл поставить в том же апдейте актора, а random_number() вообще нигде не использовать. sapsan

 

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

math.randomseed(1000)

a1 = math.random()

и

math.randomseed(1001)

a2 = math.random()

то по идее могу рассчитывать, что a1 и a2 не будут иметь никакой видимой взаимосвязи. А здесь зависимость совершенно очевидная, что заметно даже невооружённым взглядом. Очевидно, что для целей криптографии этот генератор использовать не стоит =)

 

Второй вывод - не стоит делать этот генератор хуже, чем он есть. randomseed имеет смысл сделать, но один раз за достаточно большое время. Скажем, при старте игры. В этом случае можно видеть, что math.random() выводит при каждом апдейте на самом деле внешне довольно случайные числа.

 

Согласен. sapsan

 

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)
у игроков и у меня часто ЧУ не начинается подряд много раз...

Кстати, а о каком "много" вообще идёт речь? Если оценивать вероятность возникновения цепочек отказов, то получим:

1 раз - 50%

2 - 25%

3 - 12.5%

4 - 6.25%

5 - 3.125%

и т.д.

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

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

 

Такое запросто бывает при загрузке одного и того же сохранения. sapsan

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)

sapsan,

2. Если obj был получен с помощью level.object_by_id(i), то нужно проверять его не только на nil, но и на присутствие на сервере с помощью alife():object(i)

на мой взгляд это имеет смысл только если перебираешь все возможные id циклом. Тогда действительно встречаются чисто клиентские объекты (например фейковые ракеты вертолёта, РПГ и родственные им гранаты после броска). Но всё же слишком усердствовать с этими проверками не стоит.

 

3. game:time() больше за АМК-ное игровое время, полученное из start_time alife.ltx, приблизительно на 4 с половиной дня (попадает на 26 апреля :) ) и непонятно зачем при установке переменной StartTime от времени из конфига отнимаются сутки ?

Если честно, то ничего не понял. Можно подробнее про смещение в 4 дня?

Разница между game:time() и миллисекундами вычесленными

function game_milliseconds()
    if StartTime == nil then
        getStartTime()
        if StartTime == nil then
            return 0
        end
    end
    local gtime = game.get_game_time()
    local seconds = gtime:diffSec(StartTime)
    local y,m,d,h,min,sec,ms = gtime:get()
    return (seconds * 1000 + ms)
end

составляет приблизительно 400499000 миллисекунд (вот же.... а в скрипте забыл дописать 000 в хвосте значения разницы :() sapsan

4. Если в начале игры ... создать довольно большую таблицу со случайными значениями и по всем скриптам обращатся к функции, которая будет последовательно выдавать значения из неё, то можно, теоретически, получить прирост производительности

ловля блох. Потери на вычислении случайных чисел мизерны. Гораздо больше плохо организованной (или никак не организованной) работы с таблицами. До сих пор ещё густо рассыпаны цепочки if-elseif-elseif..., которые можно было бы при желании заменить на выборки из таблиц, что дало бы прирост в разы или даже порядки.

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

 

(нужно сравнить скорость math.random() и функции выборки из таблицы готовых значений).

Это очень просто сделать. Для этого есть класс profile_timer. Я описывал его в "справочнике"

Проверил:

! Cannot find saved game ~~~ time spend tbl[i]: 1416.4332275391
! Cannot find saved game ~~~ time spend math.random(): 5592.96875
! Cannot find saved game ~~~ time spend tbl[i]: 1451.8170166016
! Cannot find saved game ~~~ time spend math.random(): 5583.2153320313
! Cannot find saved game ~~~ time spend tbl[i]: 1484.1917724609
! Cannot find saved game ~~~ time spend math.random(): 5778.8422851563

Преимущество очевидно. Правда не ясно как там будет с глобальной таблицей... sapsan

 

Это сравнение с рабочим вариантом? Я представляю это себе примерно так:

random_nums = {} -- массив случайных чисел
-- здесь инициализация массива непременно с помощью table.insert() и ни в коем случае не с помощью random_nums[i] = ...
random_count = table.getn(random_nums)... -- количество элементов в нем
current_num = 1 -- текущее число

function my_rand()
    local res = random_nums[current_num]
    current_num = current_num + 1
    if current_num > random_count then current_num = 1 end
    return res
end

Всё наваял экспромтом и не проверял.

Но выходит не так уж мало кода. Вот если это сравнить с просто вызовом math.random(), будет такой-же результат?

    local t = profile_timer()
    local tbl = {}
    for i = 1, 65534 do
        table.insert(tbl, math.random())
    end
    t:start()
    for i = 1, 65535 do
        local a = tbl[i]
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND tbl[i]: "..t:time())
    
    t = profile_timer()
    t:start()
    for i = 1, 65534 do
        local a = math.random()
    end
    t:stop()
    get_console():execute("load ~~~ TIME SPEND math.random(): "..t:time())

и так трижды.

Завтра попробую по-взрослому :)sapsan

 

Эх, баловство это всё. Однако проверил.

local random_nums = {} -- массив случайных чисел
random_count = 0
current_num = 1 -- текущее число
function random_init()
    random_nums = {}
    math.randomseed(game.time())
    for i=0,100000 do
        table.insert(random_nums, math.random())
    end
    random_count = table.getn(random_nums) 
    current_num = 1
end

function my_rand()
    local res = random_nums[current_num]
    current_num = current_num + 1
    if current_num > random_count then current_num = 1 end
    return res
end

 

 

random_init()
local t = profile_timer()
t:start()
for i = 1, 10000 do
    local a =0
end
t:stop()
log(t:time())
local t = profile_timer()
t:start()
for i = 1, 10000 do
    local a = my_rand()
end
t:stop()
log(t:time())
local t = profile_timer()
t:start()
for i = 1, 10000 do
    local a = math.random()
end
t:stop()
log(t:time())

 

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

Результаты.

потери на организацию цикла t1 = 130.6

цикл с самопальной функцией t2 = 1134.6

цикл с с встроенной функцией t3 = 1044.6

Меряем в убитых енотах, но они сокращаются.

коэффициент "замедления" = (t3-t1)/(t2-t1) = 0.91

 

По времени вышел паритет, однако учитывая все обстоятельства вердикт однозначный - самопал в топку =)

 

Что для меня было интересно, что хотя методика с предвычислением и оказалась медленнее, но не настолько, как я ожидал. Это косвенно подтверждает репутацию Lua как одного из самых быстрых скриптовых языков. Во многих случаях, особенно в комбинации с JIT компилятором, Lua код уступает компилированному не более чем в несколько раз. В отдельных случаях, когда Lua код интенсивно использует готовые скомпилированные функции можно ожидать почти равной производительности.

Чуть оптимизировал код:

local random_nums = {} -- массив случайных чисел
local current_num = 1 -- текущее число
function random_init()
    random_nums = {}
    math.randomseed(game.time())
    for i=1,1024 do
        table.insert(random_nums, math.random())
    end
    current_num = 1
end

function my_rand()
    current_num = current_num + 1
    if current_num == 1025 then current_num = 1 end
    return random_nums[current_num]
end

для игры будет достаточно 1024 случайных значения и, даже, меньше. Кроме того чуть упростил сам код

 

t:start()
local rnd = my_rand
local a
for i = 1, 10000 do
    a = rnd()
end
t:stop()
get_console():execute("load ~~~ TIME SPEND a = my_rand(): "..t:time())

Результат:

time spend a = 0: 32.611957550049

time spend a = my_rand(): 571.32788085938

time spend a = math.random(): 795.11920166016

Получаем "ускорение" в 1.41. Но оно того не стоит из-за того, что была локализированна функция my_rand(), а основной выигрыш именно из-за этого. sapsan

 

хе хе. Тогда уж надо по честному так же поступить и для math.random. В этом случае получим

служебные расходы: 119.4

локализованная my_rand: 937.3

локализованная math.random: 688.8

И имеем по прежнему замедление, только ещё больше чем раньше: 1.44 раза. Это вполне понятно, поскольку при вызове math.random надо дважды искать элемент в таблице: math в глобальной таблице имён и random в таблице math.

 

Кстати, почему "не стоит"? Это хороший резерв оптимизации - запоминание глобальных многоэтажных ссылок в локальных переменных. Это рекомендуют делать и авторы Lua.

 

Я имел ввиду, что не стоит делать таблицу с наперёд заданными случайными значениями. sapsan

 

З.Ы.: Интересно, это самый длинный пост на форуме или ещё нет?

 

Думаю, что вопросы исчерпаны. :)sapsan

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
(изменено)

Я пытался понять логику работы ЧУ из кода, но признаться не осилил до конца. Объясните мне плз, как должен проходить час ужаса? Если не трудно, подкиньте также сохранёнку желательно на момент перед началом ЧУ и на локации, где он есть.

 

ЧУ работает строго по игровым минутам + использует инфопорции как флаги.

Начинается

    -- звук обратного отсчета
    if timeh == horror_begin_time.h and timem >= horror_begin_time.m and timem <= horror_begin_time.m + 2 then

Тревожатся неписи - ключается состояние тревоги так

                    npc_position = npc:position()
                    position = vector():set(npc_position.x + math.random(-5,5), npc_position.y, npc_position.z + math.random(-5,5))
                    state_mgr.set_state(npc, "hide_s_right", nil, nil, {look_position = position})

Выдаётся случайное сообщение о начале ЧУ

presoobj()

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

Потом, если игра была загружена с сохранения, восстанавливаем спецеффекты.

В конце ЧУ убираются спецеффекты и живые монстры.

И в самом конце отбираются инфопорции. sapsan

 

Два вопроса:

1. Сирена в начале должна просто проиграться один раз?

Да.

2. Не понял с "успокаиванием" неписей. Почему они успокаиваются в случае удачного начала ЧУ? Не наоборот?

Они "успокаиваются" сразу же перед спавном монстров или в случае "поломки" установки. Если их не "отпустить", то будут большие потери - монстры их начнут рвать. А само "успокоение" не заметно, так как сразу же получаем кучу спецеффектов, звуков и монстров. Зато неписи моментально реагируют на монстров.

 

В качестве предварительного мнения:

1. Проверка на начало выглядит ненадёжной. На мой взгляд можно запросто её "проспать", поскольку во время сна игровое время меняется очень быстро.

Так было задумано или допущено.

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

Так был реализован звук обратного отсчета. Искать чесный звук мне было в лом - поэтому так и оставил.

3. Инфопоршенов что-то очень много (четыре). На мой взгляд двух достаточно.

А если учесть все фазы и возможность игрока сохраниться и загрузится или перейти на другую локацию? Я и так добавил восстановление спецеффектов для таких случаев.sapsan

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

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение

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

[spoiler=в файле _g.script]

counter = 0
prev_watch = 0
function watch_condition()
    counter = (bind_stalker.watch_value == prev_watch) and (counter + 1) or 0 
    prev_watch = bind_stalker.watch_value
    if counter > 5 then 
        if db.actor then
            local snd_obj = xr_sound.get_safe_sound_object("detectors\\da-2_beep1")
            snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 2.5)
        end
    end
    return false
end
function dummy_action()
    return false
end
function start_game_callback()
    level.add_call(watch_condition, dummy_action)
    ---
    ...
end

[spoiler=в файле bind_stalker.script]

watch_value = 0
function actor_binder:update(delta)
    watch_value = game.time()
    ...

 

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

 

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

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

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

 

Поделиться этим сообщением


Ссылка на сообщение
  • Недавно просматривали   0 пользователей

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

AMK-Team.ru

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