RAILWORKS сообщество


Всё для фаната Train Simulator 2017 и Train Sim World!

Язык программирования LUA. Скрипты.

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

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 02 июн 2016, 22:05

Можно ли полностью выполнять расчет тяги в скрипте и выдавать для симулятора?
Да, причем очень просто. Функция достаточно простая. Её легко интегрировать в готовый скрипт. В готовом варианте занимает 60 строк для 8 позиций.
Возможно ли заменить стандартные тормоза на скриптованные?
Да. Можно сделать тормоза, работа которых будет очень сильно зависеть от длины состава, а можно сделать систему, когда работа тормозов будет зависеть не только от длины состава, а и от типа вагонов, их состояния, сезона, профиля пути. К сожалению, второй вариант требует правки скриптов всех вагонов, поэтому он теоретический. А первый - вполне реальный. Готового варианта нет, так как пока в нём не было необходимости.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
Dmitryi JD

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 13 июн 2016, 17:33

Всем доброго дня :)

Представляю вниманию разработчиков фрагменты кода, которыми я предлагаю дополнять новые вагоны/локомотивы. Также, было бы нелишне, ИМХО, дополнить такими функциями уже выпущенный ПС.

[+] дополнение для вагона
Код: Выделить всё
--------------------------------------------------------------------------------------
-- ON GAME LOAD

function Initialise ()

   -- Consist Message IDs
   REPORT_MSGID = 1818190301       -- Метка сообщения, определяющая содержимое пакета

   WagonName = Call("GetRVNumber") -- Номер вагона (присваиваемый в редакторе сценария)
   WagonLength = 10800            -- Длина вагона
   AmountAxle = 4               -- Количество осей вагона
   TimeRelease_1 = math.random (20, 60) -- Время отпуска тормозов вагона (задается случайно внутри возможных значений для вагона)
   TimeRelease_2 = math.random (40, 60) --[[ Время отпуска тормозов вагона для уклона больше 4 тысячных (задается
                                 случайно внутри возможных значений для вагона) ]]
end

function Update ( time )

   local Length = Call("GetConsistLength")
   local Mass = Call("GetTotalMass");

   if Length ~= prevLength or Mass ~= prevMass then
      
      local TransMass = math.floor(Mass*1000)         
      local TransMess = WagonName..":"..AmountAxle..":"..WagonLength..":"..TransMass..":"
      
      if Call ("GetGradient") > 0.4 then
         TransMess = TransMess..TimeRelease_2..":"
      else
         TransMess = TransMess..TimeRelease_1..":"
      end
      
      Call( "SendConsistMessage", REPORT_MSGID, TransMess, 0 );
      Call( "SendConsistMessage", REPORT_MSGID, TransMess, 1 );
      
      prevLength = Length
      prevMass = Mass
   end

end -- Update ( time )

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

Передаваемые данные.
В текущей версии пересылаются следующие данные вагона:
- Номер
- Количество осей
- Длина
- Полный вес
- Время отпуска тормозов
(Список отсылаемых данных можно легко изменить/пополнить)

Зачем это нужно?
В первую очередь для реализации полноценных тормозов, когда реакция тормозной магистрали будет на 100% зависеть от данных состава. Благодаря случайно заданному времени отпуска для каждого вагона, поведение состава в каждой сессии сценария будет отличаться. Примененный метод позволяет очень легко реализовать зависимость поведения тормозной системы от времени года (и, возможно, погодных условий).
Также информация о составности поезда и оперативно измеряемом количестве осей может пригодится при постройке продвинутых систем локомотива.
Полный вес состава может помочь решить проблему с самоходом груженых вагонов - вагон может для движка числиться пустым (по конфигах) и при том, значение, переданное им локомотиву может напрямую влиять на поведение тяги локомотива, создавая иллюзию тяжелющего состава.

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

[+] дополнение для локомотива
Код: Выделить всё
---------------------------------------------------------------------------------------------
-- ON GAME LOAD

function Initialise ()

-- Consist Message IDs
   REPORT_MSGID = 1818190301

   prevLength = 1
   TabIndex = { [1] = "Wagonname", [2] = "AmountAxle", [3] = "Length", [4] = "Mass", [5] = "TimeRelease" }
   transtimer = 0
   readenable = false

end
---------------------------------------------------------------------------------------

function OnConsistMessage ( msg, argument, direction )

   if msg == REPORT_MSGID then
      
      local Length = Call("GetConsistLength")
      
      if Length ~= prevLength then
         transtimer = 0
         TabAmountAxle = {}
         TabLength = {}
         TabMass = {}
         TabTimeRelease = {}
         prevLength = Length
      end
      
      allocation (argument)
      return
   end
end
---------------------------------------------------------------------------------------

function Update ( time )

   if readenable == true then
      if transtimer < 1 then
         transtimer = transtimer + time
      else
         if transtimer > 0.5 then
            transtimer = 2
            readenable = false

-- реакция систем на изменение состояния состава

         end
      end
   end
end -- Update ( time )
---------------------------------------------------------------------------------------

function allocation (data)
   TabAmmountchar = {}

   local Length = string.len (data)
   local index, indexparameter = 1, 1
   local temp = ""
   
   while index < Length + 1 do
      temp = string.sub(data, index, index);
      if temp == ":" then
         TabAmmountchar[TabIndex[indexparameter]] = index - 1
         indexparameter = indexparameter + 1
      end
      index = index + 1
   end
   
   local keywagnum = string.sub(data, 1, TabAmmountchar.Wagonname);
   TabAmountAxle[keywagnum] = tonumber(string.sub(data, TabAmmountchar.Wagonname + 2, TabAmmountchar.AmountAxle));
   TabLength[keywagnum] = tonumber(string.sub(data, TabAmmountchar.AmountAxle + 2, TabAmmountchar.Length));
   TabMass[keywagnum] = tonumber(string.sub(data, TabAmmountchar.Length + 2, TabAmmountchar.Mass));
   TabTimeRelease[keywagnum] = tonumber(string.sub(data, TabAmmountchar.Mass + 2, TabAmmountchar.TimeRelease));

   readenable = true
end

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

Ресурсоемкость обработчика незначительная - обсчет производится разово при получении данных, а данные отсылаются вагоном один раз при изменении состояния.

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

[+] лог-файл испытательной поездки
Код: Выделить всё
00:00:00   ТЭМ2 запуск
00:00:13   Вагоны прицепленны. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 2
   Общее количество осей: 8
   Суммарная длина вагонов (без локомотива), мм: 21600
   Суммарное время отпуска тормозов всех вагонов, сек: 79
   Полный вес всех вагонов, кг: 45400

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523512 значение - 4
 № вагона - 30523570 значение - 4

   Длина:
 № вагона - 30523512 значение - 10800
 № вагона - 30523570 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523512 значение - 53
 № вагона - 30523570 значение - 26

   Вес вагона:
 № вагона - 30523512 значение - 22700
 № вагона - 30523570 значение - 22700

================================================================



00:00:17   Вагоны прицепленны. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 6
   Общее количество осей: 24
   Суммарная длина вагонов (без локомотива), мм: 64800
   Суммарное время отпуска тормозов всех вагонов, сек: 235
   Полный вес всех вагонов, кг: 256200

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523579 значение - 4
 № вагона - 30523596 значение - 4
 № вагона - 30523570 значение - 4
 № вагона - 30523512 значение - 4
 № вагона - 30523588 значение - 4
 № вагона - 30523539 значение - 4

   Длина:
 № вагона - 30523579 значение - 10800
 № вагона - 30523596 значение - 10800
 № вагона - 30523570 значение - 10800
 № вагона - 30523512 значение - 10800
 № вагона - 30523588 значение - 10800
 № вагона - 30523539 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523579 значение - 27
 № вагона - 30523596 значение - 26
 № вагона - 30523570 значение - 26
 № вагона - 30523512 значение - 53
 № вагона - 30523588 значение - 53
 № вагона - 30523539 значение - 50

   Вес вагона:
 № вагона - 30523579 значение - 22700
 № вагона - 30523596 значение - 82700
 № вагона - 30523570 значение - 22700
 № вагона - 30523512 значение - 22700
 № вагона - 30523588 значение - 22700
 № вагона - 30523539 значение - 82700

================================================================



00:00:25   Вагоны отцепленны. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 5
   Общее количество осей: 20
   Суммарная длина вагонов (без локомотива), мм: 54000
   Суммарное время отпуска тормозов всех вагонов, сек: 185
   Полный вес всех вагонов, кг: 173500

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523579 значение - 4
 № вагона - 30523596 значение - 4
 № вагона - 30523570 значение - 4
 № вагона - 30523588 значение - 4
 № вагона - 30523512 значение - 4

   Длина:
 № вагона - 30523579 значение - 10800
 № вагона - 30523596 значение - 10800
 № вагона - 30523570 значение - 10800
 № вагона - 30523588 значение - 10800
 № вагона - 30523512 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523579 значение - 27
 № вагона - 30523596 значение - 26
 № вагона - 30523570 значение - 26
 № вагона - 30523588 значение - 53
 № вагона - 30523512 значение - 53

   Вес вагона:
 № вагона - 30523579 значение - 22700
 № вагона - 30523596 значение - 82700
 № вагона - 30523570 значение - 22700
 № вагона - 30523588 значение - 22700
 № вагона - 30523512 значение - 22700

================================================================



00:00:30   Вагоны отцепленны. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 2
   Общее количество осей: 8
   Суммарная длина вагонов (без локомотива), мм: 21600
   Суммарное время отпуска тормозов всех вагонов, сек: 79
   Полный вес всех вагонов, кг: 45400

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523512 значение - 4
 № вагона - 30523570 значение - 4

   Длина:
 № вагона - 30523512 значение - 10800
 № вагона - 30523570 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523512 значение - 53
 № вагона - 30523570 значение - 26

   Вес вагона:
 № вагона - 30523512 значение - 22700
 № вагона - 30523570 значение - 22700

================================================================



00:01:22   Произведена погрузка. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 2
   Общее количество осей: 8
   Суммарная длина вагонов (без локомотива), мм: 21600
   Суммарное время отпуска тормозов всех вагонов, сек: 79
   Полный вес всех вагонов, кг: 105400

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523512 значение - 4
 № вагона - 30523570 значение - 4

   Длина:
 № вагона - 30523512 значение - 10800
 № вагона - 30523570 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523512 значение - 53
 № вагона - 30523570 значение - 26

   Вес вагона:
 № вагона - 30523512 значение - 22700
 № вагона - 30523570 значение - 82700

================================================================



00:01:53   Произведена погрузка. Текущее состояние:

----------------------------------------------------------------
   СВОДНАЯ ИНФОРМАЦИЯ:

   Всего прицеплено вагонов, штук: 2
   Общее количество осей: 8
   Суммарная длина вагонов (без локомотива), мм: 21600
   Суммарное время отпуска тормозов всех вагонов, сек: 79
   Полный вес всех вагонов, кг: 165400

----------------------------------------------------------------
   ИНФОРМАЦИЯ ПО ОТДЕЛЬНЫМ ВАГОНАМ:

   Количество осей:
 № вагона - 30523512 значение - 4
 № вагона - 30523570 значение - 4

   Длина:
 № вагона - 30523512 значение - 10800
 № вагона - 30523570 значение - 10800

   Время отпуска тормозов:
 № вагона - 30523512 значение - 53
 № вагона - 30523570 значение - 26

   Вес вагона:
 № вагона - 30523512 значение - 82700
 № вагона - 30523570 значение - 82700

================================================================


Приглашаю всех, кого интересует подобное введение, к его воплощению. Данный пример - всего лишь заготовка, реальные возможности такого метода намного шире. Приветствуются все пожелания и предложения по повышению эффективности и расширению возможностей.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, Dmitryi JD, fav39, le Sandro, Linx, Romazan67, taurus1016

Re: Язык программирования LUA. Скрипты.

Сообщение BooYa » 21 июн 2016, 20:12

Я в деле. Когда будет уже на тестах лок с такой системой, предоставлю вагоны с нужным скриптом. Если результаты всех тестов будут положительные, сделаю патч на своё и RRSное. Заодно и косяк попробуем исправить с тормозами у ботов.
Владение русской орфографией - это как владение кунг-фу, настоящие мастера не применяют его без необходимости.
BooYa
 
Аватара пользователя
Разработчик
 
Сообщения: 153
Зарегистрирован:
Откуда: Екатеринбург
Баллы репутации: 32
За это сообщение пользователю BooYa сказали "спасибо":
Света

Re: Язык программирования LUA. Скрипты.

Сообщение Dmitryi JD » 04 июл 2016, 05:07

Привет,есть такой вопрос.Как написать скрипт для cinematic camera,просто насколько я понял.Сей скрипт нужен только для того что бы активировать камеру.Но самого скрипта найти не могу.Смотрел видео у одного,англоязычного товарища.Нифига не понял.Может кто-то помочь?
Обзор и поск дополнений в Railworks Youtube
Dmitryi JD
 
Аватара пользователя
Специалист
Специалист
 
Сообщения: 257
Зарегистрирован:
Откуда: Россия
Баллы репутации: 6
 
 

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 04 июл 2016, 16:59

На тему cinematic camera можно почитать здесь.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
Dmitryi JD

Re: Язык программирования LUA. Скрипты.

Сообщение Dmitryi JD » 08 июл 2016, 02:52

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

--- добавлено позже в 08 июл 2016, 16:28 ---

С грехом пополам,почти разобрался.Но изучать ещё много!.
А от той ссылки на форум,с пояснением толку мало.Новичку в нём не разобраться.Проще самому методом тыка,проб и ошибок.
Нет не одного толкового видео пособия,чтоб посмотреть на практике.Тот скрипт который там выложен,он вообще не работает.Брал скрипт у чехов,он работает.Но проблема в том,что после проезда камеры до конечного пункта.Отрубается всё,HUD и ескейп тоже невозможно нажать,как буд то,что-то блокирует.
Обзор и поск дополнений в Railworks Youtube
Dmitryi JD
 
Аватара пользователя
Специалист
Специалист
 
Сообщения: 257
Зарегистрирован:
Откуда: Россия
Баллы репутации: 6
 
 

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 09 июл 2016, 20:27

[+] Dmitryi JD
Ну это все, что именно я могу предложить :unknown: Я знаю о существовании той статьи, потому даю ссылку, но на практике мне работать с этой камерой не приходилось, потому качество и достоверность этого материала мне не известны.

Всем доброго дня :)

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

[+] совсем чуть-чуть теории
Принцип вычисления тяги заключается в том, что для каждого значения текущей скорости сопоставляется уровень тяги, развиваемой локомотивом. В идеале должно быть столько пар "скорость - тяга", сколько может быть значений скорости, включая дробные значения. Но это очень много, поэтому применяется метод, сходный с усреднением при оцифровке аналогового сигнала. Весь диапазон скорости (от 0 до максимальной) делится на несколько участков (чем больше, тем лучше, как и битрейт). Потом берутся крайние точки скорости по краях участка, рассчитывается, на каком "расстоянии" от меньшего значения находится текущая скорость. Применив значение "расстояния", можно определить, насколько надо сместиться между крайними точками тяги.
Думаю, теории достаточно. Мне трудно передать эти мысли без рисунков, а рисовать, не имея уверенности, что это кому-нибудь действительно интересно, я не очень хочу :oops:

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

В блоге, ссылку на который я привожу, показано, что собой представляет таблица и какие правки необходимо сделать в файлах Tractive Effort VS Speed , Tractive Effort vs Throttle, поэтому я не буду повторяться.

Вот функция:
Код: Выделить всё
function TractionValue(currentSpeed, PosThrottle)

   local   MaxSpeed = 0
   local   TrDownLim, TrUpLim, SpUpLim, SpDownLim = 0, 0, 0, 0
   local   TractionPercent = 0

   for k,v in ipairs(POS_S[PosThrottle]) do
      if(v ~= nill) then
         MaxSpeed = v
      else
         break
      end
   end

   if currentSpeed < MaxSpeed and PosThrottle > 0 then
      for k,v in ipairs(POS_S[PosThrottle]) do
         if(v < currentSpeed) then
            TrDownLim = POS_T[PosThrottle][k]
            SpDownLim = v
         else
            TrUpLim = POS_T[PosThrottle][k]
            SpUpLim = v
            break
         end
      end
      TractionPercent = (TrDownLim + (1 - (SpUpLim - currentSpeed) / (SpUpLim - SpDownLim)) * (TrUpLim - TrDownLim))
   else
      TractionPercent = 0
   end
   
   return TractionPercent/100
--   Call( "*:SetControlValue", "Regulator", 0, TractionPercent/100);
end


Для использования функции необходимо скопировать её в любое место скрипта. Вызов производится из функции Update. Как правило, в функции Update используются данные о позиции регулятора тяги и о текущей скорости, поэтому я не привожу их вычисление, думаю, за этим дело не станет. Вызывается функция TractionValue(А, В) в зависимости от того, как будет использоваться результат её работы дальше. Если значение сразу передается на системный контрол тяги, то нужно раскомментировать строку
Код: Выделить всё
Call( "*:SetControlValue", "Regulator", 0, TractionPercent/100);
и закомментировать ту, что выше. Тогда вызов имеет такую форму:
Код: Выделить всё
 TractionValue(А, В)
где А - это текущая скорость, В - позиция.

Если же дальше надо будет проводить ещё какие-то вычисления (шунтирование, блокировки или т.п.), то функцию править не надо, а вызов будет иметь такую форму:
Код: Выделить всё
 local Tr_Percent = TractionValue(А, В)
где Tr_Percent - результат вычисления, который возвращается. А - это текущая скорость, В - позиция.

В процессе работы функция использует массивы пар "скорость - тяга", которые нужно составить к конкретной модели по её графику ТХ. Структуру функции, содержащей эти, а также вспомогательные таблицы, я представляю на примере разработанной мной к ТЭМ2:
Код: Выделить всё
function TractionValueTab()

POSITION1_S = {0,   2,      4,      6,      8,      10}
POSITION1_T = {12,   8.5,   5.7,   2.8,   1.4,   0}

POSITION2_S = {0,   2,      4,      6,       8,       10,      15,      20}
POSITION2_T = {24,   19,      14.2,   10,      7,      3.5,   1.4,    0}

POSITION3_S = {0,   2,      4,      6,       8,       10,      15,      20}
POSITION3_T = {75,   48.5,   20,      14,      8.5,   4.2,   1.5,   0}

POSITION4_S = {0,   2,      4,      6,       8,       10,      15,      20,      30,      40}
POSITION4_T = {100,   53.5,   36,      25,      21.4,   14,      10,      6.4,   3.2,   0}

POSITION5_S = {0,   2,      4,      6,       8,       10,      15,      20,      30,      40,      50,      60}
POSITION5_T = {100,   100,   50,      45,      35.7,   21.4,   15,      11.4,   8.5,   5.7,   3.5,   0}

POSITION6_S = {0,   2,      4,      6,       8,       10,      15,      20,      30,      40,      50,      60,      70}
POSITION6_T = {100,   100,   92,      70,      50,      32,      24,      15.7,   11.4,   8.6,   5.7,   2.1,   0}

POSITION7_S = {0,   2,      4,      6,       8,       10,      15,      20,      30,      40,      50,      60,      70}
POSITION7_T = {100,   100,   100,   82,      66,      37,      30,      22.8,   15,      10.7,   7.7,   3,      0}

POSITION8_S = {0,   2,      4,      6,       8,       10,      15,      20,      30,      40,      50,      60,      70}
POSITION8_T = {100,   100,   100,   90,      67,      45,      35.7,   23.5,   17.1,   13.5,   10,      5,      0}

POS_S = {POSITION1_S, POSITION2_S, POSITION3_S, POSITION4_S, POSITION5_S, POSITION6_S, POSITION7_S, POSITION8_S}
POS_T = {POSITION1_T, POSITION2_T, POSITION3_T, POSITION4_T, POSITION5_T, POSITION6_T, POSITION7_T, POSITION8_T}
end

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

В ТЭМ2 8 позиций, поэтому здесь 8 пар массивов "скорость - тяга", по одной паре массивов на каждую позицию. Последние 2 таблицы (POS_S и POS_T) - это ссылки на конкретные массивы в зависимости от того, номер какой позиции был передан при вызове функции TractionValue(А, В).
Функция TractionValueTab() вставляется в скрипт ниже функции TractionValue(А, В).
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik

Re: Язык программирования LUA. Скрипты.

Сообщение Dmitryi JD » 09 июл 2016, 20:34

Да нет Светлана,всё нормально.Спасибо за наводку,я ещё раз немного почитал и понял где я совершал ошибки,там еще с бинарниками надо было разобраться.и привязка камеры к сценарию,а не просто тупо написать скрипт как я делал.Ну как говорится,на ошибках учатся.Просто это мои первые шаги,к познанию работы скриптов.
Обзор и поск дополнений в Railworks Youtube
Dmitryi JD
 
Аватара пользователя
Специалист
Специалист
 
Сообщения: 257
Зарегистрирован:
Откуда: Россия
Баллы репутации: 6
 
 

Re: Язык программирования LUA. Скрипты.

Сообщение djus » 10 июл 2016, 16:17

Dmitryi JD.С камерой в сценариях всё достаточно просто,ни каких скриптов писать не надо,достаточно скопировать 3 файла
(ScenarioScript.lua : ScenarioScript.luac : ScenarioScript.luac.MD5) в папку сценария.
Причём скрипты стандартные подходят для всех маршрутов,проверял сам на 10 сценах.
Скачать можно Тут

Далее ставишь камеру в сцене, и называешь её camera1
Изображение

Идёшь в меню расписания,выбираешь (Триггер инструкция. -1)
В меню инструкции находишь поле (триггер событие. -2) пишешь camcamera1
Изображение
Всё должно летать :)
Это пример вступления,вариантов много подробнее Вот.
Отдельный Респект автору статьи sergz692
djus
 
Аватара пользователя
Специалист
Специалист
 
Сообщения: 294
Зарегистрирован:
Откуда: Объект 19
Баллы репутации: 25
За это сообщение пользователю djus сказали "спасибо":
Dmitryi JD

Re: Язык программирования LUA. Скрипты.

Сообщение Dmitryi JD » 10 июл 2016, 16:29

djus писал(а):Dmitryi JD.С камерой в сценариях всё достаточно просто,ни каких скриптов писать не надо,достаточно скопировать 3 файла
(ScenarioScript.lua : ScenarioScript.luac : ScenarioScript.luac.MD5) в папку сценария.
Причём скрипты стандартные подходят для всех маршрутов,проверял сам на 10 сценах.
Скачать можно Тут

Далее ставишь камеру в сцене, и называешь её camera1
Изображение

Идёшь в меню расписания,выбираешь (Триггер инструкция. -1)
В меню инструкции находишь поле (триггер событие. -2) пишешь camcamera1
Изображение
Всё должно летать :)
Это пример вступления,вариантов много подробнее Вот.
Отдельный Респект автору статьи sergz692



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

--- добавлено позже в 10 июл 2016, 16:49 ---

Ещё раз отдельная благодарность,протестировал.Все работает на ура.С этим всё понятно,теперь осталось научится править камеру в кабине локомотива.
Обзор и поск дополнений в Railworks Youtube
Dmitryi JD
 
Аватара пользователя
Специалист
Специалист
 
Сообщения: 257
Зарегистрирован:
Откуда: Россия
Баллы репутации: 6
 
 
За это сообщение пользователю Dmitryi JD сказали "спасибо":
djus

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 16 авг 2016, 15:44

Всем доброго дня :)

Представляю вниманию разработчиков функцию Timer, предназначенную для вставки в скрипт.

[+] Назначение
Функция предназначена упростить контроль временных интервалов в процессе выполнения кода. Каждый, кто писал скрипт, знает, что для того, чтобы сделать задержку, нужно создать переменную, поместить в неё значение задержки, потом периодически вычитать из этой переменной какое-то число, отслеживая, когда значение задержки станет равным или меньше нуля.
Дополнительное неудобство - если до окончания отсчета эту переменную заново перезапустить, отсчет начнется сначала. Чтобы этого избежать, приходится выдумывать новые защитные переменные.
Когда такой "таймер" использован, он продолжает опрашиваться ЦП, хотя в этом нет необходимости.
Каждый такой таймер (а их в скрипте локомотива предостаточно) - это дополнительный множитель всех неудобств, описанных выше.
Мне это надоело. Предлагаемая функция предназначена несколько упростить эту процедуру.

[+] Адаптация в скрипт
Функция вставляется в конец скрипта. Никакие подгонки/правки не нужны.

Для инициализации рабочей таблицы, вписываем её в функцию Initialise():
Код: Выделить всё
TabTimerData = {}

Для обеспечения счёта периодически делаем вызов из функции Update ( time ):
Код: Выделить всё
 Timer (time)

Всё, таймер прописан и готов к работе.

[+] Использование/вызов
Для того, чтобы создать канал таймер, надо придумать ему любое имя и отправить его в функцию (например, timer_1)вместе со значением нужной задержки в секундах (например, 15 сек):
Код: Выделить всё
Timer ("timer_1", 15)

Важно: пока таймер отсчитывает выдержку, его рестарт такой командой невозможен. Это сделано умышленно. Если нужно перезапустить отсчет сначала, применяется другая команда:
Код: Выделить всё
Timer ("timer_1", 15, "restart")

Контролировать окончание отсчета можно 2 способами. Первый способ - это контроль фактического окончания отсчета. Вариант использования:
Код: Выделить всё
   if Timer ( "timer_1" ) then
      -- блок операторов, если время вышло
   else
      -- блок операторов, если отсчет не завершен
   end

Обратите внимание - если опрашивается несуществующий канал таймера, функция также возвратит "false", как и при незавершенном отсчете.
Второй способ - контроль времени, которое осталось до окончания отсчета. Вариант использования:
Код: Выделить всё
   Pause = Timer ("timer_1", "ctrl")

В переменную Pause будет загружено оставшееся время (сек) в числовом формате. Если отсчет закончился, будет загружена строка "timeroff". В случае опроса несуществующего канала будет возвращено значение "nil".

Если необходимости в каком-либо канале таймера нет, его можно разрушить:
Код: Выделить всё
   Timer ("timer_1", "clear")

Такая команда уничтожит канал "timer_1". Это сэкономит время, затрачиваемое на обработку.
Автоматического удаления каналов с завершенным отсчетом нет.

Количество каналов ограничено объемом оперативной памяти.
Разрешающая способность (сек, грубо) - 1/fps.

Сам код:
Код: Выделить всё
function Timer (data_1, data_2, data_3)
   if type(data_1) == "number" then
      for k,v in pairs(TabTimerData) do
         if type(v) ~= "string" then
            v = v - data_1
            if v <= 0 then
               TabTimerData[k] = "timeroff"
            else
               TabTimerData[k] = v
            end
         end
      end
      return
   end

   if type(data_1) == "string" then
      if not data_2 then
         if TabTimerData[data_1] == "timeroff" then
            return true
         else
            return false
         end
      elseif data_2 == "ctrl" then
         return TabTimerData[data_1]
      elseif data_2 == "clear" then
         TabTimerData[data_1] = nil
      elseif not TabTimerData[data_1] or TabTimerData[data_1] == "timeroff" or data_3 == "restart" then
         TabTimerData[data_1] = data_2
      end
   end
end


Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, Linx, taurus1016, Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 11 окт 2016, 20:08

Всем доброго дня :)

Представляю вниманию разработчиков скрипт, назначение которого - определять положение дополнительных секций локомотивов относительно секции игрока.
[+] зачем это?
Скриптеры знают, что обмен информацией между единицами ПС происходит пересылкой сообщений. Но у этого метода есть большой недостаток. Если наш локомотив получает сообщение, невозможно определить, на каком расстоянии находится отправитель и есть между локомотивами вагоны или локомотивы имеют прямой контакт.
Этот недостаток может проявится в том, что если сделать передачу, например, температуры масла из второй секции на первую для отображения на термометре, то при штатной сцепке локомотивов система будет работать корректно, а при добавлении вагона между ними (маловероятно) или третьей секции система даст сбой. Например, как определить, из какой секции ловить сообщения, если слать будут обе?
Одно из решений - заблокировать весь набор сообщений обмена в вагонах. Это решило бы проблему, если бы для этого не пришлось перелопатить скрипты всех(!) существующих вагонов.
Эта проблема побудила меня написать скрипт, который помогает определить секции, которые находятся непосредственно возле управляющего локомотива. При этом править сторонние дополнения нет необходимости, так как основным критерием является расстояние между секциями.


[+] подготовка модели
В функциях определения положения применен принцип вычисления расстояний между координатами ключевых точек локомотива. Соответственно, необходимо создать простые дочерние объекты и расположить их впереди и сзади модели.
Идеальный вариант - замки сцепок. На испытаниях использовались координаты лампочек АЛСН, сбоев также не было. Хотя, точность незначительно ухудшается. Координаты этих элементов и будут использоваться при расчете.

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


[+] Подключение скрипта
Файл "LOCOMOTIVES_LOCATION" размещается в папке Engine дополнения. В основном скрипте в функции Initialise необходимо сделать ссылку:
Код: Выделить всё
   require ("Assets/[...]/Engine/"LOCOMOTIVES_LOCATION.lua")

Если используется скомпилированный файл, .lua заменяется на .out.

Для инициализации скрипта в функции Initialise создается вызов:
Код: Выделить всё
   SetupControlLink ()


Также необходимо заменить имена дочерних элементов на имеющиеся у Вашей модели:
Код: Выделить всё
      XF, _, YF = Call("1-pointlight_alsn-white-l:getNearPosition")
      XR, _, YR = Call("2-pointlight_alsn-white-r:getNearPosition")

Соответственно "1-pointlight_alsn-white-l" заменить на дочерний элемент, расположенный спереди, а "2-pointlight_alsn-white-r" - сзади.

Для получения нужных данных добавляем обработчик сообщений в OnConsistMessage ( msg, argument, direction ):
Код: Выделить всё
   if msg == POSITION_DATA then
      ControlLink ("Update", argument)
      return
   end


Ну и надо передавать свои данные. Для этого я использую проверку длины состава - ведь это значение меняется при сцепке/расцепке, этого момента вполне достаточно, чтобы составить схему состава, которая не изменится пока снова не прицепить/отцепить что-нибудь. Нет смысла делать эти замеры чаще:
Код: Выделить всё
function Update ( time )
   local Length = Call("GetConsistLength")
   if Length ~= prevLength then
      prevLength = Length
      ControlLink ("Update")
   end
end

*Переменную prevLength необходимо проинициализировать в функции Initialise ()


[+] Использование
Управляющие коды для LOCOMOTIVES_LOCATION:
Код: Выделить всё
   ControlLink ("Update")

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

Код: Выделить всё
   ControlLink ("Update", argument)

Обновление, но без рассылки. Команда предназначена для установки в функцию OnConsistMessage ( msg, argument, direction ) (описано выше) и рассчитана на прием отформатированных данных. Поэтому в других местах не используется.

Код: Выделить всё
   ControlLink ("GetLocoPosition", data)

Самая ходовая команда. Передается имя ПС, расположение которого надо распознать. Именем является № ПС, заданный в свойствах сценария.
Результатом выполнения этой команды будет возврат 1 из 5 возможных ответов:
- false - если проверяемый ПС не сцеплен с нашим, ответ будет такой;
- ff - этот ответ означает, что локомотивы сцеплены передними частями;
- fr - перед нашего локомотива сцеплен с задом проверяемого;
- rf - зад нашего локомотива сцеплен с передом проверяемого;
- rr - локомотивы сцеплены штатно.
Например, если надо проверить, может ли ПС, передавший данные о температуре и свое имя (ID), проверка выглядит так:
Код: Выделить всё
      if ControlLink ("GetLocoPosition", "ID") == "rr" then
         -- Да, данные температуры можно использовать
      else
         -- Нет, ПС или далеко (не сцеплен с нашим локомотивом) или ориентирован неправильно
      end

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


В настройках заданы константы:
Код: Выделить всё
-- Constants
   POSITION_DATA = 1818190202
   MAX_DISTANSE = 10

POSITION_DATA - это метка сообщения обмена, которое инициирует запуск определителя. На данный момент не является стандартизированным.
MAX_DISTANSE - это максимальное расстояние. Если расстояние между самыми близкими точками ПС превышает это число, принимается решение, что эти ПС не сцеплены. Число "10" выбрано исходя из соображений, что длина самого короткого вагона больше 10 метров.

Скрипт оттестирован. Тем не менее, приветствуется информация по результатах практического применения этого кода :)
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 05 ноя 2016, 12:34

Всем доброго дня :)

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

[+] назначение
Управлять яркостью ИС можно, воздействуя на параметры каналов цветности RGB. Если установить значение всех каналов в 0, источник будет полностью погашен, если же в значения, заданные в конфиге ИС - будет светить максимально заданной яркостью и цветом.
Установкой значений RGB в промежуточные значения можно изменять яркость ИС от 0 до максимума.

Основные, ИМХО, трудности в этом методе управления:
1. Значения каналов одной модели практически всегда различные. Для того, чтобы при изменении яркости ИС не потерять баланс (и цвет), все значения каналов нужно изменять синхронно и пропорционально.
2. Необходимо иметь точные данные о установленных в конфиге значениях RGB - это определяет максимальные уровни яркости и цветовой баланс.
3. Управление ИС таким методом подразумевает отсчет временных интервалов. А это автоматически требует создания счетчиков-переменных, их инкрементирование/декрементирование, слежение за результатом, разрешение/запрет перезапуска и другие "прелести" ручной таймеризации в программе.
4. Нельзя обойтись без защитных механизмов, которые заблокируют обработку ИС в случае серии однотипных команд (если, например, будет сформировано 10 команд "выключить ИС", то должна обработаться только первая, остальные должны игнорироваться, причем, чем раньше, тем оптимальнее.
5. Все вышеуказанное требует создания достаточно запутанной схемы, которая для одного ИС ещё приемлема, но с увеличением количества ИС сложность и запутанность растет в разы.

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

[+] принцип работы
При запуске программы в функции StartSetupLight () происходит начальная инициализация всех переменных. Также, используя данные из таблицы TabNames (где перечислены имена конфигов ИС как дочерних элементов), производится поиск и загрузка ключевых данных (в данном случае значения RGB).
Также создаются таблицы, используемые в процессе работы.
Одна из таблиц - LampSpeed. Здесь размещены имена ИС и их время на включение (ig) и выключение (qu) в нормальных условиях.

Для того, чтобы сделать управление каналами ИС пропорциональным и синхронным, логично использовать один множитель, который изменял бы свое значение от 0 до 1 и на который в процессе обработки следует умножать максимальное значение каждого канала.
Это переменная factor. По сути, её значение (от 0 до 1) определяет состояние ИС от полностью выключенного до включенного на полную яркость. Это значение постоянно хранится в таблице LampData.Factor_c[_имя ИС_] (c = current = "текущий").

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

Логически блок обработчика состоит из двух веток: function Lamp_Set (name, level, time) (эта функция запускает процесс изменения яркости) и function LampUpdate (time) (обработка изменения ИС во времени).

Lamp_Set (name, level, time)
При вызове этой функции первым делом подгружается текущее состояние ИС и сравнивается с заданным при вызове. Если эти значения равны (дубль команды), происходит завершение запуска обработки. В противном случае производится перезапуск ИС.
Для того, чтобы перезапустить ИС, сначала сравниваются текущее состояние и заданное. Это указывает, в какую сторону надо изменять factor - увеличивать или уменьшать яркость. Затем создаются контрольные точки - текущее состояние сохраняется как начальная точка (LampData.Factor_a[ch_n]), а требуемый уровень (level) - как конечная (LampData.Factor_b[ch_n]). Далее запускается таймер - на время, заданное в команде, или, если время не задано - на время по умолчанию, указанное в таблице LampSpeed. Дефолтное время также будет загружено в том случае, если значением time будет "none".
Также сбрасывается флаг LampSet - это будет свидетельствовать, что есть ИС, требующий обработку.

LampUpdate (time)
В первую очередь проверяется состояние флага LampSet. Если он установлен, это значит, что все ИС в статичном состоянии. В обработке нет необходимости, поэтому выход.
Если же флаг сброшен, значит есть как минимум 1 ИС, требующий просчета. В таком случае первым делом флаг LampSet устанавливается (авансом, чтобы если это ложный сбой, то при следующем входе в эту функцию не отрабатывать её снова) и запускается цикл, который последовательно переберет все ИС, прописанные в таблице TabNames.
В процессе перебора для каждого ИС считывается его состояние, если ИС статичен (status = "on" или status = "off"), этот ИС не обрабатывается. Иначе флаг LampSet сбрасывается (подтверждая, что есть необработанные ИС), и, в зависимости от статуса ИС ("ignition" или "quenching", то есть увеличение или снижение яркости) выполняется необходимая ветка, где на основании состояния таймера и ключевых точек производится расчет нового значения множителя factor. Если нужная точка яркости достигнута, это отмечается в статусе ИС, а канал таймера уничтожается за ненадобностью.
Дальше максимальные значения каналов цвета умножаются на множитель и передаются движку на управление ИС. Новое состояние ИС сохраняется.

Примечания
1. Количество подконтрольных ИС ограничено только здравым смыслом;
2. Яркость ИС можно "сдвинуть" в любую сторону в пределах от 0 до 1 (от минимума к максимуму) независимо от текущего;
3. Яркость может изменяться с любой скоростью, а если скорость не задана (или = "none"), подхватывается дефолтное значение;
4. Если все ИС статичны, обрабатывается всего 2 строки функции LampUpdate (time) за кадр;
5. Время изменения яркости стабильно, не зависит от fps. Но на очень слабых ПК не исключено ступенчатое изменение яркости;
6. Главный недостаток обработчика - время изменения яркости не зависит от начального состояния ИС. То есть, при заданном по умолчанию времени на выключение 0.2 сек, ИС будет выключатся именно 0.2 секунды, независимо от того, включен он на полную яркость или на 50%. Мне это не показалось критичным. В то же время, это исправимо;
7. Все ИС совершенно независимы друг от друга.

[+] подключение к скрипту и подготовка к использованию
Модуль можно разместить в любом удобном месте. В основном скрипте делается его подключение:
Код: Выделить всё
require "Assets/[...]/light_driver_script_v21.lua"
Если модуль скомпилирован, *.lua заменить на *.out.

В функцию инициализации основного скрипта необходимо вписать вызов функции инициализации:
Код: Выделить всё
StartSetupLight ()

А в функцию Update (time) вписать вызов функций таймера и обработчика:
Код: Выделить всё
   LampUpdate (time)
   Timer (time)


Дальше необходимо заполнить таблицу TabNames информацией о используемых ИС. Первое поле таблицы - это имя ИС, которое будет использоваться в командах. Любое удобное. Второе поле - это имя чайлда, задаётся в блоке child в поле "ChildName" основного конфига.
Затем для всех перечисленых в TabNames ИС нужно указать время на розжиг и на гашение в таблице LampSpeed.

В качестве примера я привожу 2 ИС, которые имеют имена в конфиге "Light_1" и "Light_2", я их буду называть в скрипте как Lampa_1 и Lampa_2, время розжига у обеих 0.2 сек, гашения - 0.3.

[+] использование
Для управления ИС используется вызов
Код: Выделить всё
   Lamp_Set (name, level, time),
где
   name - имя ИС, заданное Вами;
   level - требуемый уровень яркости, от 0 до 1;
   time - время, отведенное на это.


Время задается в секундах, допускаются дробные значения. Если время не задано или указан параметр "none", будет использовано время, заданное по умолчанию.


В модуле использована функция Timer (data_1, data_2, data_3), описанная мной раньше. Если Вы уже использовали её у себя, из модуля её нужно удалить. Это строки 21, 131 - 168.
Использование таймера не ограничивается только управлением ИС, его можно применять для контроля времени в других частях скрипта. Подробнее здесь.

Скрипт оттестирован.
За помощь в разработке формулы искренне благодарю пользователя maestro.
Также за поддержку и тестирование благодарю пользователя Cross.

С благодарностью приму советы, отзывы и пожелания.
У вас нет необходимых прав для просмотра вложений в этом сообщении.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, Cross, le Sandro, Гидростроитель, Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 04 дек 2016, 10:48

Всем доброго дня :)

Представляю вниманию разработчиков дополненную функцию TIMER v2.0, предназначенную для вставки в скрипт.

С назначением и общим описанием функции можно ознакомиться здесь.
Отличие от первой версии: добавлен режим остановки счета (пауза).

[+] Адаптация в скрипт
Функция вставляется в конец скрипта. Никакие подгонки/правки не нужны.

Для инициализации рабочих таблиц, вписываем их в функцию Initialise():
Код: Выделить всё
-- Data TIMER
TabTimerData = {}
TabTimerStatus = {}


Для обеспечения счёта периодически делаем вызов из функции Update ( time ):
Код: Выделить всё
 Timer (time)

Всё, таймер прописан и готов к работе.

[+] Использование/вызов
Для того, чтобы создать канал таймер, надо придумать ему любое удобное имя и отправить запрос в его функцию (например, timer_1) вместе со значением нужной задержки в секундах (например, 15 сек):
Код: Выделить всё
Timer ("timer_1", 15)

Важно: пока таймер отсчитывает выдержку, его рестарт такой командой невозможен. Это сделано умышленно. Если нужно перезапустить отсчет сначала (а также снять таймер с паузы с обновлением задержки), применяется другая команда:
Код: Выделить всё
Timer ("timer_1", 15, "restart")

Контролировать окончание отсчета можно 2 способами. Первый способ - это контроль фактического окончания отсчета. Вариант использования:
Код: Выделить всё
   if Timer ( "timer_1" ) then
      -- блок операторов, если время вышло
   else
      -- блок операторов, если отсчет не завершен
   end

Обратите внимание - если опрашивается несуществующий канал таймера, функция также возвратит "false", как и при незавершенном отсчете.
Второй способ - контроль времени, которое осталось до окончания отсчета. Вариант использования:
Код: Выделить всё
   Pause = Timer ("timer_1", "ctrl")

В переменную Pause будет загружено оставшееся время (сек) в числовом формате. Если отсчет закончился, будет загружена строка "timeroff". В случае опроса несуществующего канала будет возвращено значение "nil".

Если необходимости в каком-либо канале таймера нет, его можно разрушить:
Код: Выделить всё
   Timer ("timer_1", "clear")

Такая команда уничтожит канал "timer_1". Это сэкономит время, затрачиваемое на обработку.
Автоматического удаления каналов с завершенным отсчетом нет.

Если надо остановить отсчет, используем команду
Код: Выделить всё
   Timer ("timer_1", "pause")

Для продолжения отсчета используем команду
Код: Выделить всё
   Timer ("timer_1", "run")
или 
   Timer ("timer_1", время, "restart")

Количество каналов ограничено объемом оперативной памяти.
Разрешающая способность (сек, грубо) - 1/fps.

Сам код:
Код: Выделить всё
function Timer (data_1, data_2, data_3)
   if type(data_1) == "number" then
      for k,v in pairs(TabTimerData) do
         if type(v) ~= "string" and TabTimerStatus[k] then
            v = v - data_1
            if v <= 0 then
               TabTimerData[k] = "timeroff"
            else
               TabTimerData[k] = v
            end
         end
      end
      return
   end

   if type(data_1) == "string" then
      if not data_2 then
         if TabTimerData[data_1] == "timeroff" then
            return true
         else
            return false
         end
      elseif data_2 == "ctrl" then
         return TabTimerData[data_1]
      elseif data_2 == "clear" then
         TabTimerData[data_1] = nil
      elseif data_2 == "pause" then
         TabTimerStatus[data_1] = false
      elseif data_2 == "run" then
         TabTimerStatus[data_1] = true
      elseif not TabTimerData[data_1] or TabTimerData[data_1] == "timeroff" or data_3 == "restart" then
         TabTimerData[data_1] = data_2
         TabTimerStatus[data_1] = true
      end
   end
end



Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.

Версия 2.0 успешно заменяет версию 1.0 без дополнительных правок скрипта :)
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, RetroPaladin, taurus1016, Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 01 мар 2017, 17:24

Всем доброго дня :)

Представляю вниманию разработчиков функцию SetValueDelay, предназначенную для вставки в скрипт.

Функция предназначена для автоматической таймеризации управления контролами, также может использоваться для изменения значений контролов по нелинейных законах.
Зачем это нужно? Например, нужно включить насос для прокачки масла. Если создать условие, где при включенном тумблере насос включен, это будет рабочая схема, но не реалистична. Потому что между включением тумблера и непосредственным запуском насоса должно пройти некоторое время, обусловленное переходными процессами и инертностью системы.
Другой пример - включаем питание пульта, все стрелки должны с нулевых значений переместится на рабочие. Желательно, плавно. В идеале - по нелинейному закону, так как при правильной физике вначале должен быть разгон, в конце - замедление хода.
Решить эти вопросы для единичных случаев не проблема. Но если надо создать тайминги/задержки/преобразователи для многих приборов, скрипт начинает очень сильно захламляться. Ещё одна проблема персональных обработчиков - постоянная обработка каждого из них в функции обновления, что, так или иначе, сказывается на оптимизации.
Предлагаемая функция позволяет несколько упростить и оптимизировать этот процесс.

Особенности функции
1. Функция готова к использованию. Подгонки и правки не нужны.
2. Количество обслуживаемых контролов ограничивается лишь здравым смыслом.
3. Все контролы обслуживаются параллельно.
4. Имеется автоматическая очистка, данные об установленных контролах удаляются. Когда список контролов будет пуст, функция будет работать в холостом режиме, потребляя ресурсы только на 2 сравнения и 1 вызов.
5. Три варианта изменения контрола - триггерный, линейный, нелинейный.

Варианты изменения контролов
Контролы, в зависимости от физических свойств управляемых ими объектов, должны изменяться по разных законах. Для этого функция имеет три режима.
1. Триггер. В этом режиме контрол скачком примет заданное значение, когда истечет заданное время. Самый простой обработчик. Например, упомянутый выше масляный насос может заработать спустя некоторое время после нажатия кнопки.
2. Линейный. Здесь контрол будет менять свое значение от текущего к заданному по линейному закону в течении времени задержки. Может применяться для управления безинерционными приборами, например, цифровыми шкалами.
3. Кривая. Самый сложный режим. Значение контрола меняется по нелинейному закону. Например, может применяться для управления дворниками. Или упомянутым выше стрелочным прибором. В этом режиме функция работает по описанной через таблицу кривой.

[+] Адаптация в скрипт
Функция вставляется в любое место скрипта. Никакие подгонки/правки не нужны. Также в любое место скрипта вставляется функция curve_yval.

Для инициализации в функцию Initialise() вписываем рабочую таблицу и контрольную переменную:
Код: Выделить всё
-- SetValueDelay
   ControlNameObj = {}
   record_mark = false

Для обеспечения счёта периодически делаем вызов из функции Update ( time ):
Код: Выделить всё
 SetValueDelay(time)

Всё,функция прописана и готова к работе.

[+] Использование/вызов
Для установки контрола используется вызов:
Код: Выделить всё
SetValueDelay("имя контрола", target, time, "method", tab_curve),
   где
      "имя контрола" - имя, задаваемое контролу в конфиге
      target - конечное значение, в которое необходимо установить контрол
      time - время, в течение которого необходимо установить контрол
      "method" - режим установки
      tab_curve - имя таблицы, описывающей кривую (только для установки по кривой)

Примеры:
Установка контрола в триггерном режиме:
Код: Выделить всё
   SetValueDelay("oil_pump", 1, 3.5, "trigger")
Эта команда включит насос для прокачки масла (переведет в 1) через 3,5 секунды после вызова.

Установка контрола в линейном режиме:
Код: Выделить всё
   SetValueDelay("supply", 1500, 1.3, "linear")
Эта команда запустит процесс, при котором значение контрола "supply" будет изменятся с фиксированной скоростью и через 1,3 секунды после старта достигнет значения 1500.

Установка контрола по нелинейному закону:
Код: Выделить всё
   SetValueDelay("voltmeter", 50, 1, "curve", tab_voltmeter)
После запуска стрелка вольтметра будет двигаться к отметке 50 в течении секунды, скорость движения будет меняться по кривой, описанной в таблице tab_voltmeter.
Обратите внимание, для расчета кривых используется функция (curve_yval(val.curve, х)), используемая в паках от АР. Её я также привожу здесь. Эта функция представлена в оригинальном виде.

В последнем случае необходимо создать таблицу. Вот формат такой таблицы:
Код: Выделить всё
tab_voltmeter = {
   {x = 0, y = 0},
   {x = 1, y = 1}
}

Что она собой представляет? "х" - это относительное время, оставшееся на изменение контрола. Его диапазон всегда будет от 1 до 0 и будет линейно меняться с момента вызова функции. Разница между начальным и конечным значениями контрола - это диапазон изменения. В таблице эти значения масштабированы к дробным значениям "у". Если нам надо, чтобы контрол остался неизменным, берем у=1, если надо, чтобы контрол получил полностью новое значение, берем у=0. Соответственно, промежуточные числа установят в контрол значение, которое можно вычислить:

контрол = конечное_значение - диапазон * у
отсюда
у = (конечное_значение - текущее_значение) / диапазон

Чем больше промежуточных точек, тем качественнее кривая.
Обязательное условие - таблица обязательно должна перекрывать значения для "х" от 0 до 1 и значения "у" не должны выходить за диапазон от 0 до 1.

Разрешающая способность функции(сек, грубо) - 1/fps.

Код функции SetValueDelay:
Код: Выделить всё
function SetValueDelay(argument, target, time, method, tab_curve)
   if type(argument) == "string" then
      ControlNameObj[argument] = {}
      ControlNameObj[argument].control_name = argument
      ControlNameObj[argument].start_parameter = Call("*:GetControlValue", argument, 0)
      ControlNameObj[argument].end_parameter = target
      ControlNameObj[argument].time = time
      ControlNameObj[argument].full_time = time      
      ControlNameObj[argument].method = method
      if method == "curve" then
         ControlNameObj[argument].curve = tab_curve
      end
      record_mark = true
   else
      if record_mark then
         local count = 0
         for k, val in pairs (ControlNameObj) do
            val.time = val.time - argument
            if val.time <= 0 then
               if val.method == "trigger" then
                  Call("SetControlValue", k, 0, val.end_parameter)
               end
               ControlNameObj[k] = nil
            else         
               local diapasone = val.end_parameter - val.start_parameter
               local factor = val.end_parameter - diapasone * val.time/val.full_time
               if val.method == "linear" then
                  Call("SetControlValue", k, 0, factor)
               elseif val.method == "curve" then
                  factor = curve_yval(val.curve, val.time/val.full_time)
                  Call("SetControlValue", k, 0, val.end_parameter - diapasone * factor)
               end
            end
            count = count + 1
         end
         if count == 0 then
            record_mark = false
         end
      end
   end
end


Код функции curve_yval:
Код: Выделить всё
function curve_yval(curve, x)
   local low, high_index, high, last

   for i, v in ipairs(curve) do
      if x <= v.x then
         high = v
         high_index = i
         break
      end
      last = i
   end

   if not high then
      return curve[last].y
   end

   low = curve[high_index - 1]
   if not low then
      return high.y
   end

   return low.y + (high.y - low.y) * (x - low.x) / (high.x - low.x)
end


Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
animalkosmik, XEPMETKOB, Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 06 мар 2017, 13:36

Приветствую всех участников форума :)

Возник вопрос по разработке скриптованной тяги для локомотива.
Многим известно, что на данный момент существует несколько вариантов расчета тягового усилия локомотива. Это готовые модули. Несмотря на то, что их несколько и они имеют разные формы написания, алгоритм у всех (известных мне, по крайней мере) одинаков. Суть расчета заключается в том, что из нескольких кривых "скорость-тяга" выбирается кривая, соответствующая текущей позиции контроллера тяги.
Тем не менее, это грубая ошибка.
Дело в том, что тяговое усилие зависит от тока ТЭД-ов, ток зависит от напряжения генератора, напряжение - от скорости вращения якоря (= скорости вращения двигателя), а скорость вращения - от позиции контроллера. То есть, логично, что в то время, как позиция меняется мгновенно, тяговый ток будет меняться в зависимости от оборотов двигателя, но никак не "ступенькой", которая неизбежно возникнет, если кривую выбирать по позиции.
Написанное проиллюстрирую графиком. Это тестовая поездка, суть которой заключалась в последовательном наборе позиций:
Изображение
На графике четко видно, что в то время, как частота вращения двигателя начинала увеличиваться после переключения позиции (цифры внизу), тяговое усилие растет ступенькой, а только после этого плавно снижается (это из-за того, что скорость локомотива увеличивается). То есть, явно видно, как в процесс вступает другая кривая ТХ.
Это ещё не все.
Вот ещё один рисунок - это увеличенный фрагмент графика:
Изображение
Здесь хорошо видно, как скорость вращения двигателя доходит до максимума и дальше идет горизонтальный отрезок, а тяговое усилие имеет стабильное снижение на всем участке. То есть, явно видно, что двигатель живет своей жизнью, а тяговый ток - своей.
Понятно, что так быть не должно и такой алгоритм не годится.
Возникает вопрос: что делать?
У меня есть два варианта.
1. Переписать всю таблицу ТХ, подставив вместо позиций обороты двигателя.
Это самый очевидный и простой в программном смысле вариант. Такой подход позволит сделать тягу на 100% зависимой от оборотов двигателя. Единственный недостаток - у меня нет совершенно никаких данных, какое усилие должно быть при каких оборотах, и нет методов, которые позволили бы получить эти данные.
2. Используя готовые таблицы тяговых характеристик, рассчитывать тяговое усилие по соседних кривых (для предыдущей позиции и текущей) и интерполировать значение с привязкой к скорости вращения двигателя. Недостаток метода - количество вычислений процента тяги вырастает в три раза.

Как поступить? Может у кого-нибудь будут ещё варианты? Спасибо за участие.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 09 мар 2017, 12:52

Всем хорошего дня :)
В общем, как я понимаю, других вариантов нет.
Ладно, в таком случае я применю вариант "2" из предыдущего поста, но с некоторыми хитростями. Я буду проводить интерполяцию только при изменении скорости вращения двигателя. Таким образом количество операций вычисления тяги будет возрастать лишь при смене позиций, а в оставшееся время будут те же расчеты, что обычно. Это максимум, что я могу выжать в плане оптимизации.
Сейчас картина уже другая: Изображение
Это при неизменной скорости 20 км/ч. Скорость задана константой. Считывание данных проводилось на "макете".
Позиции с 5 по 7 пропущены - это для того, чтобы воспроизвести поведение системы в случае резкого перевода контроллера тяги через несколько позиций сразу (я знаю, так нельзя, но судя по видеороликах, есть "специалисты", которые могут подтормаживать экстренным, трогать с места на максимальной позиции, мне приходится учитывать все случаи). В общем, тяга на 100% зависит от скорости вращения двигателя, оставляю такой алгоритм.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
За это сообщение пользователю Света сказали "спасибо":
Витя

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 14 апр 2017, 12:37

Приветствую всех :)

Выявлен странный и необъяснимый нюанс в работе функции "Update" скрипта симуляции локомотива.
Дело в том, что передаваемое в функцию значение интервала времени не соответствует действительности. На самом деле, это значение почему-то завышено в 2 раза. Причины пока непонятны.
Для решения этого недостатка мне показалось уместным применить конструкцию
Код: Выделить всё
function Update (interval)
   
   local simulationTime = Call("*:GetSimulationTime")   
   interval = simulationTime - prevST
   prevST = simulationTime

Вроде как все просто - использовать прямой просчет, который покажет, сколько времени прошло с последнего прохода функции. Те же данные, но полученные явным методом, независимо от движковых непоняток.
Но этот метод тоже может сделать пакость.
Если проехать на локомотиве, в котором используется такой прием, потом "пересесть" на другой состав, а затем снова взять управление первым составом, то игрока будет ждать сюрприз.
Скрипт симуляции работает только в составе игрока. Если состав покинуть, а потом снова захватить, скрипт проинициализируется заново. В результате все переменные получат стартовые значения.
А вызов Call( "*:GetSimulationTime") возвращает время, прошедшее с самого начала игры. Как следствие, в первый проход после второго захвата состава переменная interval получит значение, равное всему времени от начала управления этим составом. А это уже не доли секунд, а минуты, или даже часы.
Представьте, что будет со всеми таймингами, использующими для вычисления переменную interval, значение которой может быть в пределах тысячи.
Вроде как и небольшой баг, но у мне пришлось потратить полдня на то, чтобы понять, почему приборы зашкаливают при повторном захвате. Надеюсь, мой опыт поможет вам обойти эти грабли.
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148

Re: Язык программирования LUA. Скрипты.

Сообщение Cross » 14 апр 2017, 13:25

Света
По теме скрипта симуляции локомотива я тебе говорил, что этот скрипт будет использоваться только под управлением игрока. Почему вылезают аномалии? Ну вроде бы ответ в твоём посте кроется - происходит сброс всех значений в данном скрипте, так как игрок взял управление. Я думаю, что с момента взятия управления и начинается каждый раз отсчёт.
Этот скрипт хорош тем, что в него можно набросать код, который будет взаимодействовать при непосредственном управлении и тот не будет никак связан с ботами. Своего рода там хранятся пользовательские функции.
Я бы очень рекомендовал пользоваться общим скриптом для локомотива/поезда, а в симуляцию писать наиболее важные моменты
*Train Simulator 2018*
Изображение
Cross
 
Аватара пользователя
Разработчик
 
Сообщения: 6082
Зарегистрирован:
Откуда: Москва
Баллы репутации: 478

Re: Язык программирования LUA. Скрипты.

Сообщение Света » 03 сен 2017, 15:01

Представляю вашему вниманию завершенный модуль-функцию (драйвер), предназначенную упростить и автоматизировать управление локомотивным светофором С-2-5М.

Особенности драйвера:
1. Автоматическая обработка команды.
2. Автоматический переход в режим ожидания после выполнения всех команд.
3. Стабильность временных отсчетов.
4. Встроенный стек команд.
5. Смена показаний светофора происходит с фиксированной скоростью, между отключением прежнего показания и зажиганием нового предусмотрено полное отключение огней, имитирующее переходные процессы в блоке АЛСН.
6. Простота использования.

Описание принципа работы:
Алгоритм драйвера состоит из 2 блоков - тактового и командного.

Командный блок предназначен для обработки команд.
При поступлении команды производится сверка текущего режима светофора или, если светофор находится в состоянии переключения - следующего режима; в случае совпадения команда игнорируется. Благодаря этому нет надобности контролировать отправку команд на драйвер - командный блок с этим справится, отсеяв дубликаты.
Также командный блок формирует последовательности команд и загружает их в стек. Благодаря этому можно отправлять любые команды в любой последовательности и с любой скоростью, все они будут помещены в стек и выполняться по заданному времени.
Если поступила команда на новое показание, в стек предварительно будет загружена команда полного гашения. На практике это даст такой эффект работы - гашение предыдущего показания, задержка, включение нового показания.
Если поступила команда тестирования, в стек предварительно будут загружены: команда полного гашения, команда теста, команда полного гашения и текущая выполняемая команда. Эффект работы - гашение текущего показания, задержка, включение всех огней (тест), гашение всех огней, задержка, восстановление предыдущего показания.
При получении команды, командный блок переводит драйвер в активный режим.

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

Для обеспечения таймеризации драйвер использует функцию Timer (), опубликованную ранее. По завершении отсчетов производится удаление использованных каналов таймера.

Использование:
Светофор АЛСН должен обслуживаться одним контролом, интерфейс которого состоит из вкладок Interior visibility object - по одной вкладке на каждый режим работы:
Изображение
В моем случае это 7 вкладок - З, Ж, КЖ, К, Б, КЖ+Б и "тест".
Как видно по скрину, в каждой вкладке нужно прописать имя нода огонька светофора и значение контрола, при котором этот огонек будет активироваться.

В скрипте нужно создать таблицу, в которой будут указаны команды управления и соответствующие им значения контрола, при которых активируется требуемый нод:
Код: Выделить всё
TabALSN = {
   ["red"]         =   {node = 1},
   ["white"]      =   {node = 2},
   ["yellow-red"]   =   {node = 3},
   ["yellow"]      =   {node = 4},
   ["green"]      =   {node = 5},
   ["YRW"]         =   {node = 6},
   ["test"]      =   {node = 7},
   ["clear"]      =   {node = 0}
   }

Команды, за исключением жестко заданных "clear" и "test", могут быть любые, но, естественно, удобнее применять очевидные имена.
Также надо проинициализировать используемые драйвером переменные:
Код: Выделить всё
   TabComALSN = {}
   StatusALSN = "none"
   ProcessALSN = "waiting"
   
   DisplayAdjustment ("clear") -- гашение светофора

В функции Update (time) делаем тактирование драйвера:
Код: Выделить всё
DisplayAdjustment (time)

Для отсылки команды делаем вызов, указав команду (какую команду указываем, такой нод по таблице TabALSN и включится, например:
Код: Выделить всё
   DisplayAdjustment ("green"), для включения зеленого огня,
   DisplayAdjustment ("red"), для включения красного огня,
   DisplayAdjustment ("clear"), для гашения всех огней.


Код функции:
Код: Выделить всё
-------------------------------------------------------------------------------------
--
-- Обработчик локомотивного светофора
function DisplayAdjustment (data)
   if type (data) == "number" then
      if ProcessALSN == "none" then
         return
      elseif ProcessALSN == "waiting" then
         if TabComALSN[1] then
            StatusALSN = TabComALSN[1]
            ProcessALSN = "transition"
            if StatusALSN == "clear" then
               Call( "SetControlValue", "ALSN", 0, 0)
               Timer ("alsn_clear", 0.7)      -- Время ожидания при переключении огней
            elseif StatusALSN == "test" then
               Call("SetControlValue", "ALSN", 0, 7)
               Timer ("alsn_test_delay", 3)   -- Время свечения всех ламп (тест)
            else
               Timer ("alsn_on", 0.20)
               Call( "SetControlValue", "ALSN", 0, TabALSN[StatusALSN].node )
            end
            table.remove (TabComALSN, 1)
         else
            ProcessALSN = "none"
            return
         end
      elseif ProcessALSN == "transition" then
         if StatusALSN == "clear" then
            if Timer ("alsn_clear") then
               Timer ("alsn_clear", "clear")
               Call( "SetControlValue", "ALSN", 0, 0)
               ProcessALSN = "waiting"
            end
         elseif StatusALSN == "test" then
            if Timer ("alsn_test_delay") then            
               ProcessALSN = "waiting"
               Timer ("alsn_test_delay", "clear")
            end         
         else
            if Timer ("alsn_on") then
               ProcessALSN = "waiting"
               Timer ("alsn_on", "clear")
            end
         end
      end
   else
      local len = 0
      for k, v in pairs (TabComALSN) do
         len = k
      end
      if StatusALSN == data or TabComALSN[len] == data then
         return
      else
         if data ~= "clear" then
            table.insert (TabComALSN, "clear")
         end
         table.insert (TabComALSN, data)
         
         if data == "test" then
            table.insert (TabComALSN, "clear")
            table.insert (TabComALSN, StatusALSN)
         end
         
         if ProcessALSN == "none" then
            ProcessALSN = "waiting"
         end
      end
   end
end
-------------------------------------------------------------------------------------
Все люди умеют летать, но, как это ни печально, только в одном направлении.
Света
 
Аватара пользователя
Разработчик
 
Сообщения: 636
Зарегистрирован:
Баллы репутации: 148
Пред.

Вернуться в Внутриигровой редактор и разработка дополнений

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1