Jump to content

Recommended Posts

Колебался между этой темой и "общими вопросами программирования", решил запостить вопрос сюда; если ошибся, прошу перенести.

 

При работе над HARDWARMOD (CS) я столкнулся вот с какой штукой.

Имеем таблицу вида

local t = {
    Alpha   = {a=3,b=13},
    Bravo   = {a=1,b=12},
    Charlie = {a=5,b=11},
    Delta   = {a=9,b=17},
    Echo    = {a=7,b=16}
}

 

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

table.sort(t, function(v1,v2) return v1.b < v2.b end)

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

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

Из положения я вышел: написал собственную функцию для получения упорядоченной как мне надо таблицы:

-- Выборка из таблицы с сортировкой по value (быстрая сортировка с разбивкой на две части).
-- Работает также в случае если value - тоже таблица (простая).
-- Выводимая таблица представляет собой простой массив, индекс исходной таблицы перемещается в поле "__key".
-- t - исходная таблица
-- s - значение, по которому должна произойти сортировка. Может принимать вид:
--     "", nil или без параметра - для случаев, когда vаlue представляет собой простое значение. Пример:
--       t = {3,7,1,9,4}; вызов сортировки: t2 = sort_table_by_value(t, "")
--     "key" - для случаев, когда value представляет собой таблицу, указывается название поля этой таблицы для сортировки по нему. Пример:
--       t = {{a=3,b=7,c=1},{a=8,b=2,c=5}}; вызов сортировки: t2 = sort_table_by_value(t, "b")
function sort_table_by_value(t, s)
    local function quick_sort(t, s, low, high)
        local i, j, x = low, high, t[math.floor((low+high)/2)]
        while (i <= j) do
            if s == "" then
                while (t[i] < x) do i = i + 1 end
                while (t[j] > x) do j = j - 1 end
            else
                while (t[i][s] < x[s]) do i = i + 1 end
                while (t[j][s] > x[s]) do j = j - 1 end
            end
            if (i <= j) then
                local temp = t[i]
                t[i] = t[j]
                t[j] = temp
                i, j = i + 1, j - 1
            end
        end
        if (low < j) then quick_sort(t, s, low, j) end
        if (i < high) then quick_sort(t, s, i, high) end
    end

    s = s or ""
    if not t or type(t) ~= "table" then return nil, "sort_table_by_value: wrong source" end
    if type(s) ~= "string" then return nil, "sort_table_by_value: wrong sort parameter" end
    if t.__key then return nil, "sort_table_by_value: source table already has '__key' field" end
    local t1, t2, low, high = deepcopy(t), {}, 1, 0
    for k,v in pairs(t1) do
        high = high + 1
        v.__key = k
        table.insert(t2, v)
    end
    if high <= 1 then return t2, nil end
    quick_sort(t2, s, low, high)
    return t2, nil
end

-- Полное копирование переменной любого типа, включая таблицу, по значению
function deepcopy(object)
    local lookup_table = {}
    local function _copy(object)
        if type(object) ~= "table" then
            return object
        elseif lookup_table[object] then
            return lookup_table[object]
        end
        local new_table = {}
        lookup_table[object] = new_table
        for index, value in pairs(object) do
            new_table[_copy(index)] = _copy(value)
        end
        return setmetatable(new_table, _copy(getmetatable(object)))
    end
    return _copy(object)
end

 

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

 

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

Из se_stor вызываются методы расширения для таблиц clone, serialize и другие, описанные в m_helpers.script. Но в момент регистрации se_stor файл m_helpers.script ещё не обработан движком, поэтому вызов тех методов приводит к вылету. Чтобы этого не происходило, я добавил в функцию se_custom_storage:on_register() проверку на то, что m_helpers.script существует, после чего вылеты прекратились. Вот модифицированная функция регистрации:

function se_custom_storage:on_register()
    cse_alife_dynamic_object.on_register(self)
    --/#+# загрузка: чтение tail-пакета в хранилище (из объекта хранения загруженного из сэйва)
--    printf("se_custom_storage:on_register:ID=[%s]:sid=[%s]%s", self.id, self.m_story_id, "") --/#~#
    if self.m_story_id ~= 4294967296 then --/ элемент ext-modules
        tStorPks[self.m_story_id] = self.tail_pk --/ запоминаем нет-пакет для 'отложенного прочтения'
        alife():release(self,true) --/ clear (удаляем объект 'загруженный из сэйва')
    else --/ элемент общего хранилища?
        if not m_helpers then return end -- <-- добавил тут
        local sini = self:spawn_ini()
        if sini:section_exist("metka") then --/ элемент загружен из сэйва
            local build = (sini:line_exist("metka","d") and sini:r_s32("metka","d")) or nil --/метка-дата
            local f_nocompress = sini:line_exist("metka","s") --/ флаг несжатия данных
            read_tail_packet( self.tail_pk, tonumber(build), f_nocompress ) --/ чтение в общую таблицу db.storehouse
            alife():release(self,true) --/ clear (удаляем 'прочитанный' объект)
        else --/ создан новый элемент
            table.insert(tStorIDs,self.id) --/ запоминаем в списке IDs
        end
    end
end

 

Возможно, это решение не идеально, но какое есть.

Share this post


Link to post
Share on other sites

Вампир35, можно попробовать в xml-конфиг окна с этими подсказками добавить элемент hint_wnd с атрибутом complex_mode="1". Примерно так:

<w>
    <!-- остальное содержимое -->
    <hint_wnd x="0" y="0" width="210" height="100">
        <background x="0" y="0" width="210" height="100" border="10">
            <texture a="100">ui_temp_frame</texture>
        </background>
        <text x="20" y="20" width="190" height="100">
            <text font="letterica16" color="ui_6" complex_mode="1" align="l" vert_align="t"/>
        </text>
    </hint_wnd>
</w>

 

А дальше пробовать "\n" и "\\n".

Edited by Kirgudu

Share this post


Link to post
Share on other sites

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

Вот небольшая иллюстрация из лога сбойного цикла записи-загрузки.

Тут записывается 7 custom_storage объектов, по которым оказались распределены данные из хранилища:

! Cannot find saved game ~:create_storage_element:id=[25778],sid=[9901]:cnt=[1]+
! Cannot find saved game ~:create_storage_element:id=[25779],sid=[nil]:cnt=[2]+
! Cannot find saved game ~:create_storage_element:id=[25780],sid=[nil]:cnt=[3]+
! Cannot find saved game ~:create_storage_element:id=[25781],sid=[nil]:cnt=[4]+
! Cannot find saved game ~:create_storage_element:id=[25782],sid=[nil]:cnt=[5]+
! Cannot find saved game ~:create_storage_element:id=[25783],sid=[nil]:cnt=[6]+
! Cannot find saved game ~:create_storage_element:id=[25784],sid=[nil]:cnt=[7]+

 

А тут читается только 6 таких объектов, седьмой (второй по id) пропущен.

! Cannot find saved game ~:se_custom_storage:on_register:id=[25778]:sid=[9901]
! Cannot find saved game ~:se_custom_storage:on_register:id=[25780]:sid=[4294967296]
! Cannot find saved game ~:se_custom_storage:on_register:id=[25781]:sid=[4294967296]
! Cannot find saved game ~:se_custom_storage:on_register:id=[25782]:sid=[4294967296]
! Cannot find saved game ~:se_custom_storage:on_register:id=[25783]:sid=[4294967296]
! Cannot find saved game ~:se_custom_storage:on_register:id=[25784]:sid=[4294967296]

 

В чём может быть причина? Очень надеюсь, что кто-нибудь, кто "в теме", сможет мне помочь. К сожалению, автор используемого мной универсального хранилища, Artos, уже 2 недели не появляется на форуме.

Edited by Kirgudu

Share this post


Link to post
Share on other sites
Непросто ответить в отрыве от применяемого контекста конкретного мода, тем более приведены логи только по созданию и чтению элементов хранилища. Честно говоря, меня несколько удивляет об'ем записи (6х8kB) и не является ли факт "пропажи" сбоем при записи некорректных данных.

Даже не знаю, как показать контекст. Вот архив с модулями, в которые я внедрил хранилище (все файлы, которые к нему относятся)

https://dl.dropbox.com/u/56394435/HWM/HWM_plus_store.7z

Данные в хранилище пишутся в модулях ogsm_notepad.script, ogsm_rare_items_manager и ogsm_rt_manager, остальное - часть комплекта хранилища или его подключение. Но в отрыве от мода запустить их вряд ли получится, если только код посмотреть. Могу архивнуть и выложить мод целиком.

Логи я привёл только как иллюстрацию пропажи объекта, а не для показа конкретной ошибки. Ошибки-то я как раз и не обнаружил, о чём ниже.

Что же касается объёма записи, то ничего удивительного тут нет. Он, кстати, даже больше - 6x16kB, - ведь речь идёт о моде для CS. Просто в хранилище должны были сохраняться изменяемые данные статей экспериментального КПК - дата создания и изменения, статус, всяческие примечания, заметки игрока. Особенно заметки игрока, уж очень их игроки просили. Пока пришлось отказаться.

 

Если такая ситуация возникает (с чем еще ни разу не сталкивался), то следует вывести в лог и строки собственно функции записи в создаваемые элементы, их очистки/удаления элементов хранилица, и их спавна при загрузке сэйва и анализировать.

Это было первое, что я сделал. Поставил вывод в лог во всех возможных закоулках как в se_stor и m_helpers, так и в bind_stalker, а также в модулях, где использовал хранилище. Всё чисто, никаких ошибок, некорректных данных нет (я специально оптимизировал сохраняемые таблицы так, чтобы в них были только простые типы данных или, максимум, вложенные таблицы с простыми типами собственных данных). Потом, при сужении области поиска, как раз и обнаружил, что пропадает custom объект вместе со своим tail пакетом, причём именно при чтении, а при записи по всем фронтам вроде бы порядок...

 

И уточни версию скриптов универсального хранилища, т.к. вполне может быть что используешь устаревшую.

Комплект хранилища брал отсюда: http://www.amk-team.ru/forum/index.php?sho...st&p=695329

Внедрял в мод с небольшой модификацией, описанной здесь (в конце поста): http://www.amk-team.ru/forum/index.php?s=&...st&p=707932

 

На вопрос о прекращении табличной сортировки штатным методом, могу только сказать, что если в скриптах этот метод не затрагивается (а в m_helpers.script этого как раз нет), то причины изменения функционирования следует искать в ином месте. У меня и у других метод table.sort работает вполне корректно.

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

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

А что в Lua подразумевается под "странными таблицами"? Вложенные таблицы, индексы, отличные от последовательной нумерации, метатаблицы, что-то ещё?

Share this post


Link to post
Share on other sites

Shredder, да-да, точно такая сортировка, по одному из полей значений, и у меня перестала работать, но при этом без понятных (мне) внешних причин.

В изначальном посте, где я про сортировку спрашивал, всё есть. И описание, и сама функция (с доп. функцией поддержки), и примеры использования.

Вот здесь: http://www.amk-team.ru/forum/index.php?s=&...st&p=707932

Share this post


Link to post
Share on other sites

Viнt@rь, там проблема не с сохранением и последующим чтением имени доп. файла, а с тем, как определить это имя. Есть 2 категории сэйвов, для которых сделать это затруднительно - квиксэйвы пользователя и автосэйвы, которые происходят при переходе на другую локацию (после диалога "вы уверены, что хотите перейти?..").

Share this post


Link to post
Share on other sites

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

Впрочем, действительно, раз нет решения, то и обсуждать нечего.

Share this post


Link to post
Share on other sites

@boryan67, просто в данном комплекте в модуле m_helpers.script не хватает реализации глобальной функции size пространства имён table.
Добавь где-нибудь в модуле (например, в разделе expansion classes functionality) следующее:

function fGetSizeTable(tTbl)
	if type(tTbl) == 'table' then --/ не таблица?
		if next(tTbl) then --/ не пустая?
			local iCnt = 0
			for _,_ in pairs(tTbl) do
				iCnt = iCnt +1
			end
			return iCnt --/>
		end
	else
		printf("fGetSizeTable:arg(%s)=[%s]~NOT_table:<%s>", type(tTbl), tTbl, "Warning!")
	end
	return 0 --/>
end
if not table.size then
	table.size = this.fGetSizeTable
end

 


 

Edited by Kirgudu
  • Like 2

Share this post


Link to post
Share on other sites

Artos в октябре выкладывал этот комплект: m_netpk

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

  • Like 1

Share this post


Link to post
Share on other sites

 

 

И вот здесь меня мучат сомнения: допустим, кровососы убиты в порядке 4,2,1,3, будет ли в таком случае k = 4?

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

Вообще, это больше относится к азам программирования, чем к скриптам конкретно Сталкера.

Share this post


Link to post
Share on other sites
function give_esc_art(first_speaker, second_speaker)
  if db.actor:object("af_vyvert") ~= nil then
    dialogs.relocate_item_section(second_speaker, "af_vyvert", "out")
  elseif db.actor:object("af_gravi") ~= nil then
    dialogs.relocate_item_section(second_speaker, "af_gravi", "out")
  else
    return
  end
  dialogs.relocate_money(second_speaker, 1500, "in")
end

 

 

Edited by Kirgudu

Share this post


Link to post
Share on other sites

 

 

local news_relocate_item -- вывод сообщения о потере/получении предмета

Здесь стоит добавить для тех, кто не в теме, что news_relocate_item - функция или ссылка на функцию.

Share this post


Link to post
Share on other sites

@demon96, не надо её применять.

Надо внутри этой функции сделать проверку, а тот ли это obj, что тебе нужен, и если тот - выдать инфопорцию.

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

Share this post


Link to post
Share on other sites

, или так:

function round(num, idx)
  local coeff = 10^(idx or 0)
  return math.floor(num*coeff+0.5)/coeff
end

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

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

  • Thanks 1
  • Полезно 1

Share this post


Link to post
Share on other sites

@Хемуль36рус, немного дополню ответ @naxac.

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

Edited by Kirgudu
  • Полезно 1

Share this post


Link to post
Share on other sites

@Хемуль36рус, не могу сейчас посмотреть, как в ТЧ, но в ЧН в модуле dialogs.script есть такая функция:

function who_is_npc(first_speaker, second_speaker)
  local npc = second_speaker
  if db.actor:id() == npc:id() then
    npc = first_speaker
  end
  return npc
end 

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

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

function npc_set_killer(actor, npc)
  local npc = who_is_npc(actor, npc)
  printf("NPC [%s] NOW IN KILLER COMMUNITY", npc:name())
  npc:set_character_community("killer", 0, 0)
end

Share this post


Link to post
Share on other sites

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

Если же говорить конкретно, тебе для начала следует запомнить базовые конструкции языка, такие как function ... end, if ... then ... end, if ... then ... else ... end, for ... do ... end и другие. В частности, в твоих функциях вида

function dump_world_rain()
if level.get_weather() == "sect_default_weather_rain" then
get_console():execute("r2_gloss_factor 6")
end

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

function dump_world_rain()
  if level.get_weather() == "sect_default_weather_rain" then
    get_console():execute("r2_gloss_factor 6")
  end
end

иначе весь модуль будет невалидным.

  • Thanks 1

Share this post


Link to post
Share on other sites

@Barmolini

... weathers[level.get_weather()] and weathers[level.get_weather()] or ...

в этом есть какая-то великая сермяжная правда?

  • Thanks 1
  • Согласен 1

Share this post


Link to post
Share on other sites

Снова для тех, кто пользуется модулями от Artos.

На этот раз под прицел попали:

  • скрипт «Общие функции» (m_helper) от 28.09.2013 из этого поста;
  • модуль «Универсальные таймеры» (m_timers) от 23.09.2013 из этого поста.

В обоих модулях (в m_timers постольку, поскольку этот модуль содержит в себе m_helper) обнаружена и исправлена ошибка, приводившая к вылету при попытке определения типа патронов в стволе и режима стрельбы из подствольника. Какие-либо другие изменения авторского кода не производились.

 

Ссылки на скачивание архивов с исправленной ошибкой:

https://yadi.sk/d/HYBcS9iccfWMV - «Общие функции»

https://yadi.sk/d/DPa6ZiGscfWeY - «Универсальные таймеры»

  • Thanks 2
  • Like 1
  • Полезно 1

Share this post


Link to post
Share on other sites

AMK-Team.ru

×
×
  • Create New...