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

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


Svoboда

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

В классе FS используются 2 таинственных класса "reader" и "IWriter". 

Кто может что-нибудь сказать про них?

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


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

Раз началась чистка форума, то хочу завершить нашу подвисшую дискуссию об использовании текстовых файлов в модах на движке ТЧ. Это актуально потому, что все мои посты, кроме самого первого с вопросом, уже убраны, а остались только 2 сумбурных по содержанию ответа от Struck, в которых, к тому же, второй противоречит первому. Это говорит лишь о том, что товарищ не достаточно глубоко разобрался в этом вопросе, но, тем не менее, легко берётся учить других. А вот полезной информацией по этому вопросу, как оказалось, не владеет.
Учитывая вышеизложенное, немного предыстории, которую можно и пропустить, т.к. в этом сообщении ни она главная цель.
 

При игре в Сталкера меня всегда утомляла порой бессмысленная и бестолковая беготня ГГ по локациям. Поэтому возникла потребность как-то оптимизировать этот процесс путём быстрого перемещения ГГ по тем точкам, в которых он уже побывал ранее. Этот, казалось бы, простой аспект (только по собственным точкам)на самом деле является очень важным, т.к. перемещение только по пройденному тобой пути, а не по предварительно кем-то оцифрованному, гарантирует выполнение сюжетной линии Авторов. Для этого нужен был «перемещатель».


У меня, при разработке скрипта по перемещению ГГ в пределах локации, возникла задача сохранения координат этих точек перемещения. При этом на этот процесс были сформулированы следующие ограничения:
1. количество сохранённых точек перемещения должно быть сколь угодно большим;
2. минимальное, а лучше полное отсутствие, вмешательство скрипта в штатную архитектуру;
3. сохранность информации о координатах перехода в независимости от любых форс-мажоров (внезапные вылеты, перезагрузка с более ранней сохранки и т.п.);
4. работа с любыми модами, независимо от их собственных примочек и наворотов;
5. стабильность и устойчивость в работе;
6. быстрое и простое подключение к любой модификации.

Недолгие раздумья привели к выводу, что это задача решается только с использованием внешних файлов, что и было быстро реализовано для модов на движке ЗП. (для ЧН тоже, скорее всего, подойдёт, но в моды ЧН я практически не играю и, поэтому данный аспект не проверял).
А вот с ТЧ вышел облом, т.к. этот движок отказался работать с внешними файлами. Только сохранки, картинки и логи. Меня это совершенно не устраивало. Последующий длительный поиск информации в Интернете и по сталкерским сайта показал, что подавляющее число людей, так или иначе связанных с разработками по Сталкеру, почти консолидировано считали – такое не возможно, потому, что не возможно никогда. Однако всё же нашёлся один разработчик, который, видимо, придерживается и моего любимого принципа – «Если это нельзя, но очень хочется, то значит это можно. Просто надо найти какой-то другой путь решения». Этим человеком был Artos, который, судя по описанию, ещё в 2010 году решил эту проблему путём возвращения в пространство имён Lua для Сталкера класса io и исправления глобального пространства для класса FS. Там и ещё много другого важного и интересного сделано, но здесь речь только о работе с файлами. Я пока не стал её реализовывать (даже не смотрел ничего кроме описания, хотя скачал), т.к., в моём мелком случае, она несколько «тяжеловата». Однако взял на заметку для возможного будущего использования. Странно, что профи мододелы, так важно и авторитетно говорящие всякие красивые слова на форумах того же сайта, где эта разработка описана и опубликована, про это не подозревают или молчат(?), а я, дилетант в этих вопросах по сути дела – знаю и говорю.
Однако вернёмся к нашим проблемам.
Основных, да и единственных, предложений в ответах оппонентов было два. Т.к. эти посты уже тоже вычищены, то напомню: 1 - использовать статические таблицы точек перехода (такие действительно применяются во многих поделках) и 2 - использовать нет-пакеты. Я начал возражать, мне, не очень аргументировано, продолжали доказывать свою правоту. Всея эта «плодотворная дискуссия» завершилась моим «баном» и последующей чисткой. Сильный аргумент ничего не скажешь.
Приведу свои доводы против обоих предложений.
Статические таблицы. Чтобы их заполнить самому, надо долго побегать/полетать по всем локациям, собрать координаты в не понятно каких точках (в игру я же ещё не играл, а если уже играл, то зачем мне это?) и проделать большую работу по заполнению таблиц. Можно воспользоваться таблицами других авторов (если они найдутся), поковыряться в скриптах, а потом бегать, как собачка на поводке по меткам кем-то для тебя приготовленными. Фу… Это было явно не для меня и отпало сразу.
Нет-пакеты. Их можно было бы и использовать, но они не соответствовали моим ограничениям по следующим соображениям:
1. Нет-пакеты привязаны к объектам. К какому объекту мог бы их привязать я? Выбор не большой – а) ГГ, б) локация, в) созданное для этих целей новое устройство. Всё это не реально и нарочито появлением непонятных вылетов в процессе игры.
2. Системные ограничения по размеру нет-пакетов ещё никто не отменял.
3. Они сохраняются и восстанавливаются для реперных точек игры. И, если я восстановлюсь с более ранней сохранки, то мне придётся обновлять свою базу переходов.
4. Требуется серьёзная правка исходных кодов игры, причём для каждого нового мода своя.

С историей на этом всё, переходим к практике.


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

Спросим себя – что такое файл? По определению Википедии - файл это «именованная область данных на носителе информации». Такая область данных может содержать информацию в любом виде, обязательным требованием для которого является только однотипность единицы множества этой информации и наличия описания их параметров. В качестве носителя информации может выступать что угодно, например: книжная полка (единица информации – книга), книга (единица информации – лист), папка для документов (единица информации – документ). Не зря же все иностранцы называют привычные для нас пластиковые папочки с бумажками - файлами. Кто не верит, поверьте мне на слово, т.к. я долго в этом варился. В нашем же случае, носителем информации на вернем уровне является диск, а некой единицей информации на нём - файл. Всё, дальше в это углубляться не будем. Всё остальное все желающие могут найти сами, а нам для дальнейших рассуждений этого вполне хватит.
Что такое текстовый файл? Это область данных, которая хранит информацию в виде набора строк символов. Вся информация о структуре этих строк является служебной и хранится в отдельной области. Нас эта область сейчас ну ни разу не интересует. Нас интересуют только сами строки.
Что такое каталог/папка с файлами? Это область данных, которая хранит информацию в виде набора отдельных файлов, которые для самого каталога представляется только как набор строк с именами этих файлов. Вся служебная информация об этих файлах/строках также хранится в отдельной области. И здесь эта область нас не интересует. Главное мы выяснили – имя файла это строка символов, на которые, правда, накладываются определённые ограничения и то, что каталог это тоже файл.
Разумеется, это не строгое описание, но, чтобы двигаться дальше, этого достаточно.
Итак, договорились, что под обычным «файлом» мы будем понимать каталог, а под «строкой» такого псевдо файла собственно сам физический файл, имя которого и будет являться для нас привычной строкой.
Теперь перейдём к Сталкеру. Что нам в «открытом доступе» оставили Разработчики ТЧ для работы с файлами? Да, собственно говоря, почти ничего полезного для практического использования. Да и это, я думаю, осталось только из-за обычного дефицита времени на подчистку «хвостов» и борьбы за размер дистрибутива. Однако всегда у всех что-то случается, дело до конца не доводится, ну а мы попробуем извлечь из этого выгоду для себя.
И так мы имеем полные возможности читать каталоги с файлами и сохранять их в памяти в виде списков. Замечательно, т.к. половина проблемы решена. С записью хуже. Записать мы можем только определенные структуры данных (сохранки, картинки, логи). Но, поскольку продукт делали наши ребята, то, как часто случается, «хлопнули ушами» и оставили нам дырочку в своём заборе. Я имею в виду метод copy в классе FS. Отлично, значит, мы имеем возможность переименовать любой файл, присвоить ему любое имя и поместить его в любое место. Вам уже понятно, какой был у меня ход мысли?
Тогда вперёд, к реализации наших теоретических изысканий.



Опишу словами то, что потом покажу в коде.

Раз под файлом будем понимать папку, то нам надо её иметь. Для простоты её можно создать в каталоге самой игры, а можно и в любом другом месте. Я создал её там же, где хранятся сохранки, логи и картинки (у меня это папка users в корневой папке игры). В этой папке создается наша рабочая папка (псевдо файл), которую я назвал "scroll", а в ней любым способом создаем файл шаблон нулевой длины с простым коротким именем и без расширения. Я ему дал имя «01» и установил атрибут «только для чтения», чтобы случайно не удалить. Этот файл будет использоваться в скрипте для сохранения наших точек перехода.
При запуске скрипта он будут читать эту папку формировать список имеющихся точек перехода, т.е. имеющихся в ней файлов. На старте же будет фиксироваться текущая позиция ГГ. Эти координаты также будут показаны. Имя выбранной в списке или новой точки перехода выводится в редактируемом поле, которое может быть очищено нажатием соответствующей кнопки. Если в этом поле имеется имя точки, то можно перейти на неё, сохранить
или удалить. При сохранении наш файл шаблон стандартным методом копируется в новый с присвоением ему в качестве имени строки содержащей следующие поля (в качестве разделителя случит запятая):

имя точки, координата X, координата Y, координата Z, угол доворота камеры.имя локации

Заметили, что в качестве имени файла применяется имя локации. Это позволяет нам при формировании списка на старте сразу отфильтровывать и загружать только те точки перехода, которые относятся к текущей локации.
Выход обычным способом или по Esc, или по отдельной кнопке «Выход»



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

И наконец, сама реализация
 

 
-- перемещение по уровню для ТЧ (2014)

local tnam = {"nm","px","py","pz","ag"}
local fs = getFS() -- класс FS
local is_fs = fs:exist("$scroll$","01") -- проверка на начичие шаблона
local full_path = is_fs.name -- запомним абсолютный путь файла-шаблона
local tf = {point} -- внутренняя таблица файлов

-- получаем количество полей и позиции разделителя
local function GetPosSep(str)
if str == "" or str == nil then return {} end
local t, pos, n = {}, 0, 0
while pos ~= nil do
pos = string.find(str, '[%|%,]', pos+1)
n = n + 1
if pos ~= nil then table.insert(t,pos) end
end
return {sp = n, tbl = t}
end

-- получаем поле по индексу
local function GetPol(str,num)
if str == "" then return str end
if num == nil then num = 1 end
local pol, t = nil, GetPosSep(str).tbl
if num == 1 then -- первый
pol = string.sub(str,1,t[num]-1)
elseif num == GetPosSep(str).sp then -- последний
pol = string.sub(str,t[num-1]+1)
else
pol = string.sub(str,t[num-1]+1,t[num]-1)
end
return pol
end

class "load_item" (CUIListItemEx)

function load_item:__init() super()
self.text_name = "name"
self:SetWndRect(0,0,230,22)

self.sn = CUIStatic()
self.sn:SetAutoDelete(true)
self:AttachChild (self.sn)
self.sn:SetWndRect (0,0,200,22)
self.sn:SetText("name")
self.sn:SetFont(GetFontLetterica18Russian())
self.sn:SetTextColor(255,216,186,140)
end

function load_item:__finalize()
end

-- точка входа из внешних скриптов (ui_scroll_wnd.main())
function main()
super_dlg = ui_scroll_wnd.scroll()
level.start_stop_menu(super_dlg, true)
end

class "scroll" (CUIScriptWnd)

function scroll:__init() super()
self:InitControls()
self:InitCallBacks()
end

function scroll:__finalize()
end

-- заполняем список точек перехода на текущей локации
function scroll:FillList()

local flist_ex = fs:file_list_open_ex("$scroll$",FS.FS_ListFiles ,"*."..self.lev)
local f_cnt = flist_ex:Size()
local str, nam
if f_cnt > 0 then
for it = 0, f_cnt-1 do
str = flist_ex:GetAt(it):NameFull()
nam = GetPol(str,1)
self:AddItemToList(nam)
local vp = vector():set(tonumber(GetPol(str,2)),tonumber(GetPol(str,3)),tonumber(GetPol(str,4)))
local age = tonumber(GetPol(string.gsub (str, "."..self.lev, ''),5))
local t = {}; t.fn = str; t.pos = vp; t.dir = age
tf['point'] = nam; tf[nam] = t
end
end

end

function scroll:InitControls()
self:Init(350,100,296,561) -- задаем позицию на экране
local xml = CScriptXmlInit() -- создаем класс для файла описания
xml:ParseFile("ui_scroll_wnd.xml") -- подключаем файл описания элементов окна
xml:InitStatic("scr_background", self) -- устанавливаем статик фона

-- регистрируем элементы окна
-- поле ввода позиции
self.scr_fr_name = xml:InitEditBox("scr_fr_name", self)
self.scr_fr_name:SetFont (GetFontLetterica18Russian())
-- self.scr_fr_name:SetTextColor(255,0,0,0)
self:Register(self.scr_fr_name ,"scr_fr_name")

-- кнопки
self:Register(xml:Init3tButton("scr_btn_1", self),"scr_btn_1") -- перенести
self:Register(xml:Init3tButton("scr_btn_2", self),"scr_btn_2") -- сохранить
self:Register(xml:Init3tButton("scr_btn_3", self),"scr_btn_3") -- удалить
self:Register(xml:Init3tButton("scr_btn_4", self),"scr_btn_4") -- очистить поле ввода
self:Register(xml:Init3tButton("scr_btn_5", self),"scr_btn_5") -- выход

-- получение текущей позиции и направления
self.st = xml:InitStatic("scr_pos", self)
self.lv = xml:InitStatic("scr_lev", self)
self.lev = level.name()
self.pos = db.actor:position() -- позиция ГГ
self.age = -db.actor:direction():getH() -- угол доворота камеры от нулевой точки локации
self.lv:SetFont (GetFontLetterica16Russian())
-- self.lv:SetText(string.format("Локация: %s", ))
self.lv:SetTextST(string.format("Локация: %s", self.lev))
self.st:SetText(string.format("X=%-7.2f Y=%-7.2f Z=%-7.2f", self.pos.x, self.pos.y, self.pos.z))

-- поле списка точек перехода
local ctrl = CUIWindow()
xml:InitWindow ("scr_item:main",0,ctrl)

-- сам список
xml:InitFrame("list_frame",self)
self.list_box = xml:InitList("list",self)
self.list_box:ShowSelectedItem(true)
self:Register(self.list_box, "scr_list")
self:FillList() -- заполняем список
end

function scroll:InitCallBacks()
self:AddCallback("scr_list", ui_events.LIST_ITEM_CLICKED, self.OnListItem_clicked, self)
self:AddCallback("scr_list", ui_events.WINDOW_LBUTTON_DB_CLICK, self.OnListItemDb_clicked, self)
self:AddCallback("scr_btn_1", ui_events.BUTTON_CLICKED, self.OnButton_scroll_clicked, self)
self:AddCallback("scr_btn_2", ui_events.BUTTON_CLICKED, self.OnButton_save_clicked, self)
self:AddCallback("scr_btn_3", ui_events.BUTTON_CLICKED, self.OnButton_del_clicked, self)
self:AddCallback("scr_btn_4", ui_events.BUTTON_CLICKED, self.OnButton_CLEAR_clicked, self)
self:AddCallback("scr_btn_5", ui_events.BUTTON_CLICKED, self.OnButton_CANCEL_clicked, self)
end

-- очистить поле ввода имени
function scroll:OnButton_CLEAR_clicked()
self.scr_fr_name:SetText("")
end

-- Перейти
function scroll:OnButton_scroll_clicked()
local sc_name = self.scr_fr_name:GetText()
if sc_name ~= "" then
db.actor:set_actor_position(tf[sc_name].pos)
db.actor:set_actor_direction(tf[sc_name].dir)
level.start_stop_menu(self)
end
end

-- Сохранить
function scroll:OnButton_save_clicked()
local sc_name = self.scr_fr_name:GetText()
if sc_name == "" then return end
local abs_path = string.gsub(full_path, '01', '') -- получаем абсолютный путь для нового файла
local new_path = abs_path..sc_name..','..tostring(self.pos.x)..','..tostring(self.pos.y)..','..tostring(self.pos.z)..','..tostring(self.age)..'.'..self.lev
fs:file_copy(full_path, new_path) -- создаём новый файл точки перехода
level.start_stop_menu(self)
end

-- Удалить
function scroll:OnButton_del_clicked()
local sc_name = self.scr_fr_name:GetText()
if sc_name == "" then return end
fs:file_delete("$scroll$",tf[sc_name].fn)
level.start_stop_menu(self)
end

-- Выйти
function scroll:OnButton_CANCEL_clicked()
level.start_stop_menu(self)
end

-- Выбор (щелчок в списке)
function scroll:OnListItem_clicked()
local list_box = self:GetListWnd("scr_list")
if list_box:GetSize() == 0 then return end
local item_id = list_box:GetFocusedItem()
local _itm = list_box:GetItem(item_id)
if _itm == nil then return end
local item_text = _itm.sn:GetText()
self.scr_fr_name:SetText(item_text)
end

-- Двойной щелчок
function scroll:OnListItemDb_clicked()
self:OnListItem_clicked()
self:OnButton_scroll_clicked()
level.start_stop_menu(self)
end

-- клавишы
function scroll:OnKeyboard(dik, keyboard_action)
CUIScriptWnd.OnKeyboard(self,dik,keyboard_action)
local bind = dik_to_bind(dik)
local console = get_console()
if keyboard_action == ui_events.WINDOW_KEY_PRESSED then
if dik == DIK_keys.DIK_ESCAPE then
level.start_stop_menu(self)
end
end
return true
end

-- добавляем строку в список
function scroll:AddItemToList(text)
local _itm = load_item()
_itm.sn:SetText(text)
local list_box = self:GetListWnd("scr_list")
list_box:AddItem(_itm)
end


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

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

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

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


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

Принцип тот же, имеем какой-то шаблон, который или переименовываем (как в Вашем примере),  или копируем под другим именем (как у меня).

Только я тогда не знал (я ж любитель), как создать исходный файл-шаблон определенной структуры и поэтому сделал, как мог - просто создал пустой файл и работал с ним.

Сейчас я понял, что могу прогнать такую схему в ЗП и, если всё получится, то работать по ней в ТЧ. Буду пробовать

Где ж Вы раньше были, когда вдвоём уговаривали меня использовать не подходящие средства, а не рассказали про это?

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


Ссылка на сообщение
(изменено)
Требуется из строки вида, например, «aaa_bbb_ccc_ddd» вытащить подстроку от первого символа и до последнего подчёркивания включительно, то есть «aaa_bbb_ccc_», и присвоить это значение переменной средствами сталкеровского Lua.

 

просто Lua это сделать просто, вот так, например,

string.sub (str,1,string.len(str)-string.find (string.reverse (str),"_")+1)

 

а присвоить "сталкеровской переменной" можно попробовать вот так:

local str = "aaa_bbb_ccc_ddd"

local sep = '_'

local new_str = string.sub (str,1,string.len(str)-string.find (string.reverse (str),sep)+1)

 

разделитель (sep) может быть любой.

 

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

Но не хотелось оставлять столь монструозное выражение, поэтому и спросил про регулярки.

Собственно, @Карлан и @Shredder дали выше исчерпывающий ответ.

Kirgudu

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

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


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

 

 

Desertir сказал(а)почему у тебя проверка на пустоту таблицы это сравнением с nil? Пустая таблица это вполне себе живая переменная, nil'ом не являетсяПотому что в начале игры "levelname" не существует. (см. конструкцию)

 

Во избежании всяких побочных моментов я обычно использую такую конструкцию

local isBool = function(var) return var ~= nil and var ~= 0 and var ~= false and var ~= '' and var ~= {} end

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


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

 

 

какие побочные моменты может вызывать функция type(value)?

Самые непредсказуемые, если в ненужное время, в ненужном месте вернет, например, тип "userdata".

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

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


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

 

 

Почему тебе следующий вариант не нравится?
function isBool(value)
    type(value) == 'boolean'
end

да хотя-бы потому, что это не рабочий вариант. Вы сами то попробовали её запустить? Я попробовал, у меня не получилось ее даже в SciTE запихнуть. Ругается. А так все нормально, все буковки правильные.

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

И так можно перебрасываться очень долго, пока меня окончательно не забанят. По сему на этом всё.

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


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

 

 

Ну типы то надо знать...

Типы то я знаю, а вот Вы так и не поняли, что функуция проверяет не типы, а объекты. Ведь это две большие разницы, не правда ли? Или Вы и с этим не согласны?

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


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

 

 

он путает перевод в булево с проверкой существования переменной

я не собираюсь ни в чем, никого обвинять, просто прошу напомнить где я говорил о "переводе в булево"? Может я что-то забыл, тогда я не прав. Я всё время твержу о проверке зачения объекта, а не его типа. Переименуйте функцию в thisObjIs(var) и забудьте про тип возвращаемого ею значения. Тогда может вопросы отпадут?

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


Ссылка на сообщение
ты сам постарался понять свою проблему.

У меня конкретно в этом случае нет проблемы, это раз.
Два, в последний раз повторяю, я не проверяю типы, не пытаюсь перевести их в булево, а проверяю значение объекта на факт его существования и на то, что он не пустой (number не 0, string не "" и т.п.). И только результат такой проверки получаю в виде булевского значения. Других слов снять вашу зашторенность у меня просто нет, извините. Разве что переименуйте мысленно эту функцию из isBool(var) в thisIsObj(var) и тогда возможно станет понятнее моя цель.
А за вопросы, особенно наводящие, большое человеческое спасибо.

 

в Луа нет работы напрямую с типами

добавлю. И здесь Вы не правы. В Lua допускается и имеется возможность изменения типа значения, ограниченная правда, но имеется.
Примеры: 1. указанное Карлан двойное отрицание. 2. tonumber(var) - перевод в тип number. 3. tostring(var) - перевод в тип string.
Продолжить? или этого достаточно?

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


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

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

 

 

Переменная — поименованная, либо адресуемая область памяти, адрес которую можно использовать для осуществления доступа к данным. Данные, находящиеся в переменной (то есть по данному адресу памяти), называются значением этой переменной.
В некоторых языках переменная определяется как имя, с которым может быть связано значение, или даже как место для хранения значения.
Важно понимать разницу между именем переменной и значением этой переменной, и не забывать о ней.
Значение, к которому привязана переменная (значение переменной) может быть объектом любого типа (допустимого синтаксисом), который может быть возвращен выражением.
Например (для Lua):
local tbl - это переменная
а
local tbl = () - это значение, к которому привязана переменная tbl. Это значение имеет тип table, а сама переменная (tbl) в отрыве от контекста присваивания никакого значения не имеет (nil в терминах Lua). Типы значения привязанные к переменным могут быть любые определённые синтаксисом компилятора.
Отсюда вытекает, что нет никакой возможности проверять или переопределять типы самих переменных (их просто не существует), а можно выполнить проверку или преобразование только для значений, привязанных к этим переменным. Причем, при этом подразумевается не изменение типа значения (это невозможно по определению), а просто привязывание определённой ранее переменной к значению другого типа.
В тоже время выражение
var == {}
является вполне допустимым (хоть и не имеющим никакого практического смысла) не смотря на то, что конструкция {} не привязана ни к какой переменной, но всё равно является вполне корректной для Lua, т.к. является ссылкой на область памяти под размещение массива (таблицы) и поэтому реальна, а, следовательно, не определяется компилятором как nil или ошибка. Это так называемая "повисшая" ссылка или "дырка", которая напрасно расходует ресурсы, но не вызовет вылетов. С ней позже разберётся сборщик мусора.
Поэтому проверка переменных на их существование и проверка значений, привязанных к этим переменным, на наличие (пустоту) и тип - это всё суть разные понятия и действия.
Объект, в понятиях "Сталкера", это нечто более аморфное, чем простые типы Lua и его рассмотрение потребует значительного времени и места. Поэтому пока оставим это. Да и к тому же форум "Скриптование" не самое подходящее место для подобных словопрений.

 

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


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

FonSwong, Charsi дал простой и верный намёк на решение проблемы. Надо всего лишь изменить структуру массива tbl_sos. Сделать его не ассоциативным, а индексированным, т.е. вида

tbl_sos = {{npc_id = id1, sos = 'SOS'},{ npc_id = id2, sos = ' '}, . . .}
ну и далее получаем по случайному индексу соответствующее ему значение, т.е. ассоциативный массив, а из него чего уж там требуется или tbl_sos[index].npc_id или строку tbl_sos[index].sos

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


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

Оболочки сделаны для более удобного вызова, так как в них сразу проверки на пустоту и тд.

Вот что я думаю по этому поводу

Про «

сделаны для более удобного вызова».

Вам придётся написать ещё пару страниц, чтобы убедить меня, что настучать на клавиатуре «get_substr_since_space_to_end (line)» мне удобнее, чем набрать знакомое «line:match('(%S+)%s?')», которое и короче самого имени и заодно сразу решает проблему. Это может бы быть не единственным примером.

 

Про «так как в них сразу проверки на пустоту».

Да нет там таких проверок. Все «If», которые там присутствуют, в лучшем случае просто дублируют последующие системные вызовы, а в большинстве просто бессмысленны. Но уж «пустоту» они точно не проверяют и не контролируют.

 

Про «и тд.».

Вот это «и тд.» есть самое опасное и тревожное. Посмотрите сами, некоторые Ваши функции/методы/оболочки (назовите, как хотите) порой вызывают простой системный метод через цепочку 3-4 вызовов промежуточных функций. А ведь ничего в этом мире не даётся просто так. Каждый вызов функции это ресурсные траты на очень многие скрытые от прямого взгляда вещи. Оправданы эти траты в данном конкретном случае? Однозначно нет. И никакое сомнительное удобство их не компенсирует.

 

Ну и теперь, чтобы не быть голословным покажу, как бы я решал те же проблемы. Это не истина в последней инстанции, «я ведь не волшебник, а только учусь» и спасибо Charsi, который в шутейной беседе здорово подтянул меня в этой области «кодоблудия». Весьма благодарен ему за это.

FonSwong, привожу только Ваше описание функции и мой вариант решения (везде line – строка, substring – подстрока, как они определении в оригинале текста)

 

Функция возвращает подстроку с 1 символа и до ближайшего пробела, не включая его

local var = line:match('(%S+)%s?')
Примечание: вот здесь проверка не нужна, т.к. любой конец строки (по определению, но не всегда по реализации) считается пробельным символом, и поэтому получите то, что заказали.

 

Функция возвращает подстроку с пробела (первого) и до конца

local var = (line:find(' ')) and line:gsub('%w*%s?','',1) or ''
Примечание: здесь проверка на наличие хоть одного пробела требуется по ТЗ

 

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

local word = 0 -- индекс слова, которое надо получить. Начинается с 0, а не с 1
local var = line: gsub('%w*%s?*','',word):match('(%S*)')
Вычисление длины строки

local var = line and line:len()
Примечание: здесь просто проверка на «дурака», чтобы избежать вылета.

 

Выделение подстроки по индексам

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

 

Находит первое вхождение в строку второго аргумента, возвращается не только первое вхождение, но и индекс, на котором вхождение заканчивается.

Примечание: здесь простое дублирование системного метода. Эквивалентно следующему:

local var1, var2 = line:find(substring)
Содержит ли строка данную подстроку.

Примечание: простое дублирование системного метода. Все проверки просто бессмысленны, т.к. реализуются системой. Эквивалентно следующему:

local var = (line:find(substr)) and true or false
Заменяет первое вхождение old в строке line на new

local var = line:gsub(old,new,1) or line
Заменяет все вхождения old в строке line на new

local var = line:gsub(old,new) or line
Проверяет, начинается ли строка с данной подстроки

local var = line:sub(1,substring:len()) == substring
Проверяет, заканчивается ли срока line последовательностью substring

local var = line:sub(-substring:len()) == substring
Проверяет, является ли строка пустой. true, если пустая, false иначе.

local var = line:len() == 0
Всё остальное не требует ни цитирования, ни комментариев, ни даже внимания, т.к. является простым дублированием системных методов, которые просто втихую съедают ресурсы.

 

Вот такое моё мнение про этот пакет «Служебные функции для удобной работы со строками»

 

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

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


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

 

 

Хочу кое-что добавить

Спасибо, что Вы заметили эту проблему. А это именно проблема, т.к. в «Сталкере» строковые типы являются по сути основными (вспомните все идентификаторы, секции, просто текстовые строки, xml и т.д. и т.п) и самыми «перекидываемыми» между всеми другими методами данными. Но, вот внимания к ним, «подумаешь строка», прослеживается явно пренебрежительное. Напрасно. Я не собирал статистики, но просматривая сообщения на разных форумах, увидел, что доля вылетов в различных модах - так или иначе связаны с некорректной работой именно с данными типа “string”. Лично я в своих поделках использую тип, который для себя называю «суперстрока» - “sstring”. Это разумеется никакое не «супер», а просто некоторые расширения базового типа и много - много разных проверок. Лишние затраты ресурсов есть? Обязательно. Выгоды есть? Мне кажется да. Небольшую часть из этого типа я привёл в своём сообщении и то только в качестве примеров подхода, а не конечной реализации. Из этого надо и исходить.

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

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


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

 

 

а функции использования предмета неправильная.

в таком виде она не может быть правильной. Метод active_item() возвращает клиентский объект в активном слоте.

Правда, я ни разу не пробовал стрельнуть из "батона", но что-то мне подсказывает, что такое вряд-ли возможно и позтому act_it:section() == "bread" всегда будет false.

Но, я могу и ошибаться.

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


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

 

 

Может быть так?

Можно и так, но лучше немного причесать.

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

А сама процедура спавна или замены одного объекта другим тривиальна и очень подробно описана во многих местах.

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


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

 

 

это при условии, что там всегда 0

этот ноль меня удивил, т.к. я ждал, что там какой-нибудь мусор.

Никакие позиции не усанавлива, просто тупо скопировал строки из Вашего сообщения. Вот так:

local packet = net_packet()
news_manager.send_tip(db.actor," s -->"..tostring(packet:r_u8()))

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


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

конструкция entity_action( entity_action() ) - несколько странна ?

 

С точки зрения Lua в ней нет ничего ни странного, ни криминального, если вспомнить, что параметры вычисляются до вызова функции. Тут всё зависит не от конструкции вызова, а от содержания самой функции. Например, вот такая функция:

function entity_action(f) a = f and '2' or '1'; print('--> '..a); return true end 

отработает вполне корректно и выдаст предсказуемый результат.

entity_action(entity_action())
--> 1
--> 2

Так что здесь вопрос скорее не в форме, а в содержании. Так то так.

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


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

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

 

Я довольно долго переваривал эти ваши 3 строчки. Мне самому всё и понятно и используемо давно, а вот как сказать, чтобы было понятно и Вам, мне стало ясно не сразу.

Попробую. В Lua в чистом виде, разумеется, нет никаких перегрузок. Это априори. Но! Вполне возможно очень элегантно это эмулировать.

Я сейчас не буду даже трогать метатаблицы, а простенький пример, оперируя только со стандартной библиотекой Lua “string” и понятием, которое в Lua определяется как «замыкание» (я его уже как-то приводил, но мой пост был удалён на стадии премодерации, видимо).

Пишем такой код и проверяем его:

local old_len = function(str) return string.len(str) end
string.len = function(str, str1) return str1 and (str1..' равна = '..old_len(str)) or #str end

и смотрим результаты

string.len( 'bnm', 'длина' )  --> длина равна = 3
string.len( 'bnm' )           --> 3

Это перегрузка? И вот тут начинается игра слов. По внешнему виду функция вроде одна и та же, как Вы говорите, и список аргументов может быть тот же (вторая строчка). Но! Это разные функции. Так это перегрузка?

В чём Вы безусловно правы – внутри всего этого безобразия производится выбор из таблицы (а Lua по другому и не умеет), но вот значение этого ключа в этой таблице Вы имеете возможность подменить. Замечательная возможность!

Более сложно это реализуется с мета-таблицами или через «якобы» классы (хотя чистых классов, как их понимают в том же С++ конечно нет, т.к. такое и не было предусмотрено разработчиками), но главное это всё вполне реально. Lua очень расширяемый инструмент.

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


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

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

AMK-Team.ru

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