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

Скриптование


Svoboда

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

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

 

Отдельно хочу ещё раз - в дополнение со своей стороны - указать на то, что написал @Zander_driver. Всё это тут не раз объяснялось, но лишний раз повторить не помешает.

Скрытый текст

 

Данные любого объекта, в том числе актора, передаются от клиента к серверу и обратно посредством так называемого нет-пакета, имеющего весьма ограниченный размер. В ТЧ это примерно 8 КБ, в ЧН и ЗП - 16. Учитывая то, что в пстор актора, а значит и в соответствующий нет-пакет, уже пишутся данные в добром десятке, если не больше, модулей оригинала, на долю скриптёра остаётся совсем немного доступного объёма. Именно поэтому:
- добавить в пстор одну-две-три переменные, имеющие простой тип (число, логическое значение, строку, гарантированно имеющую небольшую длину), и на этом остановиться - можно.

- добавить в пстор сегодня 1-2-3 переменные, завтра ещё столько же, потом забыть и через месяц добавить ещё пяток, потом ещё - нельзя! Смотреть в сторону модуля se_stor или нового движка.

- добавить в пстор переменную типа "строка", предельная длина которой неизвестна (и может составлять тысячи знаков) - нельзя! Смотреть в сторону модуля se_stor или нового движка.

- добавить в пстор переменную типа "таблица" - нельзя! Смотреть в сторону модуля se_stor или нового движка.

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

Можно ещё контролировать размер нет-пакетов посредством лога (см. штатные вызовы метода set_save_marker(...), особенно в ответственном за актора модуле bind_stalker), но это уже более высокий пилотаж.

Тогда в большинстве случаев проблем с переполнением нет-пакетов удастся избежать.

 

 

  • Нравится 1
  • Полезно 2

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


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

@_Sk8_AsTeR_, либо они, либо искажение/потеря каких-либо данных после загрузки. (Второе, как я уже писал, не всегда заметно сразу! Поэтому контроль и ещё раз контроль записи чего-либо куда-либо.)

Например, в одном из модов для ЧН это приводило к тому, что инвентарь какого-либо НПС начинал содержать абсолютно невразумительный набор предметов, а при взаимодействии с этим НПС (с целью, скажем, торговли) следовал вылет.

Поройся в поиске, тут хватает нужных сведений.

Изменено пользователем Kirgudu
  • Спасибо 1

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


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

@Norman Eisenherz, начать можно с того, что первая функция (в твоём случае check) должна вернуть true, чтобы была запущена вторая функция. Ты же возвращаешь db.actor... Поведение коллбэка предсказать в таких условиях не возьмусь; хорошо ещё, что вылета не было.

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

  • Согласен 1

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


Ссылка на сообщение
7 часов назад, Winsor сказал:

функция проверяется всегда, до тех пор пока не вызван remove_call

И всё-таки позволю себе усомниться в данном утверждении. Проведём маленький эксперимент:

Скрытый текст

local test_counter = 0
local callback_counter = 0
function f1()
	if test_counter < 10 then test_counter = test_counter + 1 end
	log("test_counter: %s", test_counter)
	return test_counter >= 10
end
function f2()
	if callback_counter < 10 then
		callback_counter = callback_counter + 1
		log("callback_counter: %s", callback_counter)
	end
end

Код вставил в actor_proxy.script в событие net_spawn
Лог:


! Cannot find saved game :>test_counter: 1
! Cannot find saved game :>test_counter: 2
! Cannot find saved game :>test_counter: 3
! Cannot find saved game :>test_counter: 4
! Cannot find saved game :>test_counter: 5
! Cannot find saved game :>test_counter: 6
! Cannot find saved game :>test_counter: 7
! Cannot find saved game :>test_counter: 8
! Cannot find saved game :>test_counter: 9
! Cannot find saved game :>test_counter: 10
! Cannot find saved game :>callback_counter: 1
* MEMORY USAGE: 428273 K
* End of synchronization A[1] R[1]
@ flush

На этом вывод в лог полностью прекратился.

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

Ремарка: эксперимент проведён в ЧН, так как у меня нет сейчас установленных Теней. Может быть, конечно, разрабы что-то доработали во второй части, не исключаю. В противном случае либо метод remove_call не является обязательным для прерывания действия коллбэка, либо неявно вызывается где-то в движке после запуска второй функции.

  • Полезно 2

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


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

@Winsor, всё так.

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

Скрытый текст

void CPHCommander::update()
{
	for(u32 i=0; i<m_calls.size(); i++)
	{
		try
		{
			m_calls[i]->check();
		} 
		catch(...)
		{
			remove_call(m_calls.begin()+i);
			i--;
			continue;
		}

		if(m_calls[i]->obsolete())
		{
			remove_call(m_calls.begin()+i);
			i--;
			continue;
		}
	}
}

void CPHCall::check()
{
	if(m_condition->is_true())m_action->run();
}

void CPHScriptAction::run()
{
	(*m_lua_function)();
	b_obsolete=true;
}

 

Собственно, тут всё видно.

 

Ну а для скриптёра резюме будет простым: можно сколько угодно использовать level.add_call(f1,f2) в скриптах без необходимости принудительно удалять коллбэки, лишь бы функция f1 рано или поздно вернула true.

Изменено пользователем Kirgudu
  • Согласен 1
  • Полезно 2

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


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

@Norman Eisenherz, коллбэк on_trade(item, sell_mode, cost) вызывается для каждого предмета перед тем, как он передан новому владельцу. При этом сначала пачкой обрабатываются все предметы, которые актор продаёт (при наличии), а затем все предметы, которые актор покупает. Направление передачи предмета отображается при срабатывании коллбэка в параметре sell_mode так:

sell_mode = true - предмет у актора, parent_id предмета равен id актора, после коллбэка передаётся НПС.

sell_mode = false - предмет у НПС, parent_id предмета равен id НПС, после коллбэка передаётся актору.
Таким образом, если есть хотя бы один покупаемый у НПС предмет, id НПС определить достаточно просто: берём item, когда sell_mode = false, его item:parent() будет равен id НПС.
Но что делать, если покупаемых предметов в этот раз нет, а есть только продаваемые? На момент продажи их владелец - актор, и он нам не подходит. Вот тут как раз может прийти на помощь обсуждавшийся выше метод level.add_callback. А именно: запоминаем в какой-нибудь переменной сам предмет (item), устанавливаем признак того, что надо проверить предмет попозже, запускаем коллбэк, при срабатывании которого во время следующего апдейта опять же возьмём id родителя, которым будет уже не актор, а НПС. Примерно так:
 

Скрытый текст

local sell_item = nil
local make_checking = false
function on_trade(item, sell_mode, cost)
	if sell_mode == true then --/ продажа
		sell_item = item
	end

	if make_checking == false then
		--/ инициируем запуск проверки при первом апдейте после торговли
		make_checking = true
		level.add_call(function() return make_checking end, check_trade_items)
	end
end
function check_trade_items()
	--/ получаем id НПС
	local npc_id = sell_item:parent()

	--/ обнуляем проверочные переменные
	sell_item = nil
	make_checking = false
end

 

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

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

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

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


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

Во! Я ж чувствовал, что есть что-то значительно проще, только не получилось вспомнить.

Ну и ладно, зато поупражнялся. :biggrin:

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

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


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

@Norman Eisenherz,

Скрытый текст

function get_torch_state()
	local torch = db.actor:object("device_torch")
	local s_obj = alife():object(torch:id())
	local pk = get_netpk(s_obj, 2)
	if pk:isOk() then
		local data = pk:get()
		return bit_and(data.upd.torch_flags, torch_flags.Active) ~= 0
	end
end

 

Что я сделал? Взял готовый модуль из https://www.amk-team.ru/forum/topic/13216-sborochnyy-ceh/?do=findComment&comment=971137, пробежал глазами скрипт на предмет того, где именно в нет-пакете лежат флаги фонарика согласно его классу - up_props, значит в update части нет-пакета, вспомнил по приложенной инструкции, как именно читать только update часть, и как в результатах она будет называться, накидал базовый скрипт для чтения текущего флага и сравнения его с флагом включения.

Естественно, ещё и модуль подключить надо, но на это есть исчерпывающие сведения в посте по вышеприведённой ссылке, как инструкция Артоса, так и дополнения к ней.

На всё потребовалось не больше 10 минут. Куда меньше, чем глубокое изучение трёх с половиной килострок кода.

Не изобретай велосипед - его уже вылизали со всех сторон за эти годы.

Изменено пользователем Kirgudu
  • Согласен 1

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


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

@Zander_driver, не всем можется и, главное, хочется ковыряться в движке.

 

@Norman Eisenherz, функция create_ini_file создаёт объект в памяти, к которому можно применять все методы этого класса, такие как ini:line_count(), ini:r_bool() и др. Но это не значит, что этот объект автоматически подключён в качестве игрового ресурса аналогично инклюдам в system.ltx.

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

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


Ссылка на сообщение
6 часов назад, Norman Eisenherz сказал:

но вроде бы такой виртуальный файл можно сохранить на жестком диске. Как это сделать?

local file = io.open() / file:write() / file:close() - для ЧН/ЗП. Для ТЧ я такого способа не знаю. В любом случае сохранение на лету скорее всего не даст автоматического подключения к ресурсам, а сохранение с целью последующего чтения при запуске игры равносильно формированию нужного конфига заранее.

 

Но соглашусь с @Zander_driver. Совершенно непонятны цели данных усилий. Если для самообразования - это похвально, вот только начинать (и продолжать) тогда нужно с того, что страницей раньше написал @dsh: чтения этой и соседних (справочник, общие вопросы) тем от начала и до конца. В противном случае лучше таки обратить своё внимание на доработанные движки или модули Артоса. Ведь они и вправду "из коробки" обеспечат то, что ты пытаешься сделать.

  • Согласен 1

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


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

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

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

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


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

@Norman Eisenherz,

news_manager.send_tip(act, table.concat(pk_dump, "\n"))

И кстати, это опять изобретение велосипеда повторение уже имеющегося инструмента. В m_netpk есть функция pk:dumpDesc(), как раз предназначенная для вывода всех свойств нет-пакета. Почему бы не использовать её?

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

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


Ссылка на сообщение
17 часов назад, aka_sektor сказал:

Это какая-то проверка, помогающая найти ID объекта?

Да не проверка это, не ищи там глубокого смысла. Цитата вырвана из контекста; речь идёт всего лишь о том, что имея id, можно получить сам объект.

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


Ссылка на сообщение
06.04.2020 в 19:58, aka_sektor сказал:

кста, вопрос к @Charsi тоже, он советовал

Вопросы Charsi задавать бесполезно - он уже больше года не интересуется Сталкером и сюда не ходит.
Лично я для проверки и локального запуска скриптов пользуюсь как раз его разработки плагином к N++: https://yadi.sk/d/UIMIb8_8vZtMD

  • Полезно 3

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


Ссылка на сообщение
(изменено)
3 часа назад, Expropriator сказал:

Кто шарит в ОГСМ ( кроме Kirgudu)?

А я тебе не подойду вместо Kirgudu? :crazy2:

Не было там никакой привязки к локациям, насколько я помню. К смартам - да, но не в заданиях типа "kill_stalker".
Проверь, что ты не почистил ненароком конфиг misc\rt_manager.ltx
При первом после загрузки обращении к менеджеру (например, как раз из se_stalker.on_register()) там считывалось содержимое секции list, затем по каждой строке этой секции читались уже свойства заданий и записывались в массив self.task_info. Далее делался перебор этого массива и в зависимости от значения свойства type добавлялось значение в один из массивов task_id_by_type[type]. Пустым он мог оказаться только в том случае, если из конфига не получено ни одного задания нужного типа.

Изменено пользователем Kirgudu
  • Нравится 1
  • Согласен 1
  • Полезно 1

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


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

@Expropriator, ну, тут уж только тебе ведомо, что ты там со стартом накуролесил. :) Впрочем, на правильную инициализацию менеджера заданий это влиять всё равно не должно. Там главное, чтобы вызов любого метода менеджера из другого модуля шёл только через получение синглтона инициализированного экземпляра менеджера, например:
ogsm_rt_manager.get_rt_manager():register_target(self)

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


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

@Expropriator, теоретически all.spawn, конечно, может влиять. Например, для тех же заданий типа "kill_stalker" одним из свойств является свойство parent, указывающее на того, кто может выдать задание. Это story_id определённого персонажа, и если ты удалил его из спавна, задание с таким родителем будет полностью пропущено при инициализации, а значит не будет учтено при составлении массива task_id_by_type[type]. Таким образом, получается, чтобы ты не наталкивался на вылет, описанный выше, необходимо выполнение трёх условий:
1. Конфиг misc\rt_manager.ltx должен содержать как минимум одно задание типа "kill_stalker";

2. All.spawn должен обеспечивать спавн как минимум одного персонажа со story_id, указанным в конфиге в заданиях типа "kill_stalker", причём этот спавн должен происходить до инициализации менеджера;

3. Вызов любого метода менеджера должен происходить только через получение его экземпляра: ogsm_rt_manager.get_rt_manager():method_name().

Конечно, менеджер можно доработать так, чтобы он был универсальным и стойким к разным условиям. Но на момент активной разработки ОГСМ такой задачи просто не возникало, поэтому имеем то, что имеем.

15 часов назад, Zander_driver сказал:

Может там достаточно проверку if type() == 'table' воткнуть, и всего делов...

И кстати, если ты действительно убрал весь классический спавн, тогда причина именно в этом согласно трём правилам, описанным выше. А значит, решение, предложенное @Zander_driver, скорее всего будет для тебя панацеей.

Куда воткнуть такую проверку, объяснять не нужно? Вроде все тут - люди опытные.

Изменено пользователем Kirgudu
  • Согласен 2
  • Полезно 1

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


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

@Norman Eisenherz, например https://www.amk-team.ru/forum/topic/6185-skriptovanie/?do=findComment&comment=1325557

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

  • Согласен 1

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


Ссылка на сообщение
31.07.2020 в 18:11, UriZzz сказал:

получается так что сохраняемые таймеры сохраняются и там, и там.?Потому как lua_helper дублирует методы SetWer, GetWer, DelWer...

Если сделал всё правильно, у тебя в _g.script должно быть что-то вроде этого:

function start_game_callback()
	--/ ... все предыдущие строки функции

	--/#+# [se_stor] ---------------------------------------------------
	se_stor.attach() -- инициализация модуля se_stor
	--/< ---------------------------------------------------------------
end

prefetch("lua_extension") --/#+# подключение модуля расширений Lua
prefetch("lua_helper") --/#+# подключение модуля 'общих' хелп-функций

Таким образом, определение функций SetVar и др. в lua_helper срабатывает уже при загрузке модуля, а определение тех же функций в se_stor - только при запуске функции start_game_callback (вызовом функции attach), то есть позже. И второе определение перезаписывает первое.

Так что не бойся, сохранение будет происходить только в универсальное хранилище.

  • Спасибо 2

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


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

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

AMK-Team.ru

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