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

Архив старых тем форума, потерявших актуальность.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

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

Сообщение Света »

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

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

Код: Выделить всё

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

================================================================
Приглашаю всех, кого интересует подобное введение, к его воплощению. Данный пример - всего лишь заготовка, реальные возможности такого метода намного шире. Приветствуются все пожелания и предложения по повышению эффективности и расширению возможностей.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
BooYa
Разработчик
Сообщения: 317
Зарегистрирован: 17 окт 2015
Откуда: Екатеринбург
Репутация: 439

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

Сообщение BooYa »

Я в деле. Когда будет уже на тестах лок с такой системой, предоставлю вагоны с нужным скриптом. Если результаты всех тестов будут положительные, сделаю патч на своё и RRSное. Заодно и косяк попробуем исправить с тормозами у ботов.
Аватара пользователя
Dmitryi JD
Специалист
Специалист
Сообщения: 348
Зарегистрирован: 25 янв 2015
Откуда: Россия
Репутация: 1174
Контактная информация:

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

Сообщение Dmitryi JD »

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

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

Сообщение Света »

На тему cinematic camera можно почитать здесь.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Dmitryi JD
Специалист
Специалист
Сообщения: 348
Зарегистрирован: 25 янв 2015
Откуда: Россия
Репутация: 1174
Контактная информация:

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

Сообщение Dmitryi JD »

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

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

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

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

Сообщение Света »

Спойлер
Ну это все, что именно я могу предложить :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(А, В).
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Dmitryi JD
Специалист
Специалист
Сообщения: 348
Зарегистрирован: 25 янв 2015
Откуда: Россия
Репутация: 1174
Контактная информация:

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

Сообщение Dmitryi JD »

Да нет Светлана,всё нормально.Спасибо за наводку,я ещё раз немного почитал и понял где я совершал ошибки,там еще с бинарниками надо было разобраться.и привязка камеры к сценарию,а не просто тупо написать скрипт как я делал.Ну как говорится,на ошибках учатся.Просто это мои первые шаги,к познанию работы скриптов.
Обзор и поск дополнений в Railworks Youtube
Аватара пользователя
djus
Разработчик
Сообщения: 346
Зарегистрирован: 13 мар 2012
Откуда: Объект 19
Репутация: 790

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

Сообщение djus »

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

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

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

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

Сообщение Dmitryi JD »

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

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

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

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

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

Ещё раз отдельная благодарность,протестировал.Все работает на ура.С этим всё понятно,теперь осталось научится править камеру в кабине локомотива.
Обзор и поск дополнений в Railworks Youtube
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

Представляю вниманию разработчиков функцию 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
Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

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

*Желательно, чтобы у сцепленных секций эти элементы находились как можно ближе друг к другу, иначе возможна ошибка, когда измерение произойдет в тот момент, когда они будут в разных тайлах. Событие редкостное, но возможное.
Спойлер
Файл "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 метров.

Скрипт оттестирован. Тем не менее, приветствуется информация по результатах практического применения этого кода :)
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

Очередная заготовка, облегчившая мне написание скрипта, которой я хочу поделиться. Это скрипт-модуль, предназначенный для управления яркостью источниками света (ИС).
Спойлер
Управлять яркостью ИС можно, воздействуя на параметры каналов цветности 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.
С благодарностью приму советы, отзывы и пожелания.

У вас нет необходимых прав для просмотра вложений в этом сообщении.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

Представляю вниманию разработчиков дополненную функцию 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 без дополнительных правок скрипта :)
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

Представляю вниманию разработчиков функцию 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
Приветствуются пожелания по усовершенствованию. Также, несмотря на успешное тестирование, приветствуется любая информация по практическому использованию функции.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

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

Как поступить? Может у кого-нибудь будут ещё варианты? Спасибо за участие.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Света
Разработчик
Сообщения: 752
Зарегистрирован: 13 мар 2015
Репутация: 1470

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

Сообщение Света »

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

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

Сообщение Света »

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

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

Код: Выделить всё

function Update (interval)
	
	local simulationTime = Call("*:GetSimulationTime")	
	interval = simulationTime - prevST
	prevST = simulationTime
Вроде как все просто - использовать прямой просчет, который покажет, сколько времени прошло с последнего прохода функции. Те же данные, но полученные явным методом, независимо от движковых непоняток.
Но этот метод тоже может сделать пакость.
Если проехать на локомотиве, в котором используется такой прием, потом "пересесть" на другой состав, а затем снова взять управление первым составом, то игрока будет ждать сюрприз.
Скрипт симуляции работает только в составе игрока. Если состав покинуть, а потом снова захватить, скрипт проинициализируется заново. В результате все переменные получат стартовые значения.
А вызов Call( "*:GetSimulationTime") возвращает время, прошедшее с самого начала игры. Как следствие, в первый проход после второго захвата состава переменная interval получит значение, равное всему времени от начала управления этим составом. А это уже не доли секунд, а минуты, или даже часы.
Представьте, что будет со всеми таймингами, использующими для вычисления переменную interval, значение которой может быть в пределах тысячи.
Вроде как и небольшой баг, но у мне пришлось потратить полдня на то, чтобы понять, почему приборы зашкаливают при повторном захвате. Надеюсь, мой опыт поможет вам обойти эти грабли.
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Аватара пользователя
Cross
Разработчик
Сообщения: 6330
Зарегистрирован: 14 дек 2011
Откуда: Москва
Репутация: 2985

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

Сообщение Cross »

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

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

Сообщение Света »

Представляю вашему вниманию завершенный модуль-функцию (драйвер), предназначенную упростить и автоматизировать управление локомотивным светофором С-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
-------------------------------------------------------------------------------------
Сильные люди - это не те, у которых всё хорошо, а те, у которых всё хорошо несмотря ни на что.
Закрыто