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

Прозекторская


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

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

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

Изменено пользователем Dennis_Chikin
Ссылка на комментарий
26 минут назад, Dennis_Chikin сказал:

может пойти, а может не пойти.

А разве здесь не вполне взаимоисключающая конструкция?

Если есть поршень +agroprom_military_case_have - то не пойти в назначенный смарт непись вроде как только по двум причинам может:
1) емкость того смарта уже полностью занята - и все, кто там на данный момент есть - это эксклюзивы ( и тогда это вылет, видимо)
2) или если как в NLC ( тут спасибо dsh за данные когда-то пояснения )  - любой смарт не принимает моба с локации, которая не находится в той же группе, что и локация, на которой расположен этот смарт, потому что  в smart_terrain.script есть вот такая проверка.

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

if level_groups[smart_level_group]~=level_groups[npc_level_group] then return false end

 
А какие другие варианты ?

След от кругов на воде - это тоже след (с)

Ссылка на комментарий

Это в следующей части.

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

 

А вот в том куске, что разобрал - либо if name == "none" then return disagreed, либо

elseif name == self:name() then return agreed_exclusive

Что найдем в цикле первым.

 

Как бы по-хорошему - none надо хранить и проверять отдельно, а уж если не выполнилось, то уже потом выбирать условия по

local t = obj.smart_terrain_conditions[self:name()]

if t  and xr_logic.pick_section_from_condlist( db.actor_proxy, obj, t ) then return agreed_exclusive end

return disagreed

 

ну или типа того.

Хотя на самом деле вообще не так, ибо дальше опять же сельхозсклад.

 

Ссылка на комментарий

Следующая проверка:

if self.accepted_communities and not self.accepted_communities[community] then return false end

здесь, вроде, все очевидно - берется из custom data смарта, строчка communities = чего-то-там, через запятые, если не совпало ни с одним - не пускаем, если строчки нет вообще - проверяем дальше.
Понятно, что никакие назначения не конкретного непися или конкретного монстра в конкретный смарт ничем не помогут.

 

if obj_agreement ~= agreed_exclusive and not self:check_preset( community, obj:rank(), self.gparams.preset_name ) then
    return false
end
А вот здесь назначение уже учитывается, и непись с принудительно назначенным смартом проверку пройдет. Если кто-то скрипт не "доработал" так, чтобы не проходили.
В чем смысл самой проверки?

function se_smart_terrain:check_preset( npc_community, npc_rank, preset_name )
    local preset = smart_terrain_params.get_preset( preset_name )
    if preset == false then return true
    else
        local t = preset[npc_community]
        if t and ( npc_rank >= t[1] and npc_rank <= t[2] ) then return true
        else return false
        end
    end
end

preset_name - это у нас опять же custom data, строка вида preset = l01_escape или типа того.
smart_terrain_params.sctipt ищет это в misc\\smart_terrain_presets.ltx, и вставляет в табличку, по которой опять же проверяются допустимое коммунити и ранги для всей локации. И если мы пишем, чтобы капитана с уникальным пистолетом и рангом мастер на Кордон не пускать - его на Кордоне и не будет.

 

Далее, если и эту проверку благополучно прошли, то
if not xr_gulag.checkNpc( community, is_stalker, self.gparams.type, obj:rank(), obj ) then return false end

- через 100500 скриптов перебирается список level_gulags = {} в xr_gulag.script, и в каждом из скриптов списка дергаются checkStalker()/checkMonster() на предмет опять же проверки коммунити, ранга, имени и т.д. в надежде, что они вернут нам true.

 

Ну и, наконец, return self.gulag:is_there_any_suitable_job( self:fill_npc_info( obj ), obj_agreement == agreed_exclusive ) - ищутся свободные работы, чтобы запихнуть на них претендента.

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

 

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


Если же кто думает, что список граблей, а также прочих борон, сеялок и молотилок исчерпан - он думает неправильно.
То есть, если нашелся единственный смарт, который только и подходит нашему монстру или неписю - в какой-то момент монстронепись пойдет в него. Если же таких несколько, то:

function se_smart_terrain:suitable( obj )
    local v = 0
    if self:obj_accepts_smart_terrain(obj) == agreed_exclusive then v = 100000 end

    for id, strn in pairs( smart_terrains[self:get_level_name()] ) do
        if strn:is_gulag_available() then
            v = v + strn.gulag.capacity - strn.gulag:get_population()
        end
    end
    return v
end

Мы видим некое очень сильное колдунство: перебор всех смартов на локации, в которые претендента в принципе могут пустить, с вычислением некоего приоритета в зависимости от их заполненности. Даже думать не хочу, каким может получиться результат, но именно сюда очень любят лазить разные шаловливые ручки, и править так, чтобы все живое пыталось лезть туда, где максимальная емкость, но в смарты с емкостью на 1-2 объекта шли только тогда, когда совсем уж податься некуда.

 

Еще очень популярное исправление - проверка на то, можно ли объекту ходить в онлайн.
Суть здесь вот в чем: есть у нас, допустим, в олспавне на генераторах некий кровосос, которому в онлайн можно только после некоего события. Если этот кровосос окажется, допустим, в смарте в кровососовке на Складах, а мы взяли задание на острел кровососов - мы его не сможем выполнить, пока не произойдет искомое событие на генераторах. Логичное решение - не пускать таких в смарты вообще, пока им не разрешили онлайн.

Но, это, опять же, кто как правит, и как проверяет это самое разрешение на онлайн. см., например, вариант амк:

function se_smart_terrain:obj_accepts_smart_terrain( obj )
    if obj.smart_terrain_conditions then
        local any_exclusive = false
        local s
        for name, condlist in pairs(obj.smart_terrain_conditions) do
            s = xr_logic.pick_section_from_condlist( db.actor_proxy, obj, condlist )
            if s ~= nil then
                if name == "none" then return disagreed
                elseif name == self:name() then return agreed_exclusive
                end
            else
                if name == self:name() then return disagreed end
            end
        end
-- Если объекту запрещено переходить в online и эксклюзивные смарты недоступны, то не пускаем его никуда.
        if obj:can_switch_online() == false then return disagreed end
    end
    return agreed
end

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

 

Вопрос о максимальной вместимости смартов - это отдельная тема, пока лишь скажу, что лучше бы задавать в самих смартах такую, чтобы не превышала максимальное количество имеющихся работ для ВООБЩЕ любого возможного случая, и для невозможного - тоже. Иначе - вылеты, и советы "знатотов" "снизить настройки графики и добавить памяти".

 

Вот теперь, вроде, все.

 

Изменено пользователем Dennis_Chikin
  • Полезно 3
Ссылка на комментарий

"В книге речь пойдет в осовном о хоббитах..." (C) JRRT

 

Точнее, о такой штуке, как "импульс", многообразно представленной в разных конфигах.

Подозреваю, что нижеприводящиеся эмпирические данные зависят от чего-попало еще из тех же конфигов, но в движок за подтверждением или опровержением лазить лень, так что имеем то, что имеем.

 

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

Для аномалий упомянутая связка выглядит как

hit_type =, hit_impulse_scale =

+

min_start_power =, max_start_power =

+ расстояние от центра,

 

для оружия -

hit_impulse  =, hit_type  =

+

k_impulse = патрона

+ расстояние

 

В скриптах -

local h = hit()
h.impulse =
h.type =

 

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

И еще важный момент: живые и дохлые монстры/неписи - это абсолютно разные вещи, и если в результате воздействия на что-то живое получился труп, то труп о произошедшем уже "ничего не знает". Хотя вот труп актора - знает даже слишком хорошо.

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

 

Практически на все, то есть, абсолютно на все, воздействует хит типа explosion. Так что если мы хотим, чтобы у трамплинов работала "сбыча мечт" - меняем им удар на взрыв. Но, на трупы, особенно, на труп актора, взрыв действует на много сильнее, и то, что слегка подбрасывает актора живого, мертвую тушку запускает в космос. Также, на тушку актора неплохо действует hit.shock

 

На все без исключения трупы, как и на неживые объекты, действует hit.fire_wound  - наверное, все заметили, что вертолет замечательно отправляет актора полетать даже без посадки в оный. ;)

На практике достаточно примерно hit_impulse    = 40 в характеристиках оружия, чтобы обеспечить этот самый полет. Почему оно срабатывает не всегда ? Как уже говорилось, труп - ничего не знает и не помнит ни про какой хит. Потому что во время смерти этого трупа просто не существовала, и замена ранее бодро бегавшего непися на этот самый труп происходит тогда, когда до него дойдет апдейт, каковой зависит от расстояния до актора, и может случиться чере 20ms, а может - аж через 2 секунды. Отсюда, если хотим, чтобы свежий  покойник как-то сдвинулся с того места, где образовался, хит следует повторить уже искусственно, после того, как удалось получить npc:get_physics_shell()  - что свидетельствует о наконец-то свершившейся замене.

 

Ну и внезапно, на живого актора очень хорошо действует hit.wound

 

Что же нам нужно, чтобы МЕЧТА наконец сбылась?

Во-первых, ставим трамплинам hit_type = explosion

Во-вторых, добавляем актору отслеживание хита. Поскольку коллбэка для этого в оригинале ТЧ нет - отслеживаем изменение здоровья.

Ну и далее все очевидно:
 

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



local hit_lvid
local hit_anom_id, hit_anom_name

function on_hit( victim, amount )
    if actor:level_vertex_id() ~= hit_lvid then
        hit_lvid = actor:level_vertex_id()
        local id, p, r, dist = amk_anoms.get_nearest_anomaly_for_pos( actor:position() )
        if id then
            local a = level.object_by_id( id )
            if a and dist < -0.05 and a:clsid() == clsid.zone_mbald_s and string.find( a:section(), "bald" ) then
                hit_anom_id, hit_anom_name = id, a:name()
            else hit_anom_id, hit_anom_name = false, false
            end
        else hit_anom_id, hit_anom_name = false, false
    end    end
    if hit_anom_id then
        local a = level.object_by_id( hit_anom_id )
        if a and a:name() == hit_anom_name then
            throw_dir = actor:position()
            throw_dir.y = throw_dir.y + 1
            throw_dir = throw_dir:sub( a:position() )
            throw_dir = throw_dir:add( throw_dir )
            throw_anom_id, throw_anom_name = hit_anom_id, hit_anom_name
            throw_count = 0
            throw_pos, throw_down = false, 0
            level.add_call( throw_actor, dummy )
    end    end
end


 

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

 

Сам полет:
 

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



local throw_anom_id, throw_anom_name, throw_dir, throw_count = false, false, false, 0
local throw_pos, throw_down = false, 0

function throw_actor()
    if actor.health < 0.02 then
        throw_count = 0
        return true
    end

    if throw_count >= 20 then
        if throw_pos then
            local pos = actor:position()
            if pos.y - throw_pos.y >= 0.1 then
                throw_pos = actor:position()
                return false
            elseif ( pos.y - throw_pos.y < 0.05 ) and ( throw_down == 0 ) then
                throw_down = throw_down + 1
                local dir = vector():set( throw_dir )
                local h = hit()
                h.draftsman = actor
                h.type = hit.wound
                h.direction = dir
                h.power = 0
                h.impulse = 100
                throw_pos = actor:position()
                throw_count = throw_count + 1
                if throw_count < 21 then return false end
            end
        end
        throw_pos = actor:position()
        throw_count = throw_count + 1
        if throw_count >= 60 or throw_down ~= 0 then
            throw_count = 0
            return true
        end
        return false
    end

    local a = level.object_by_id( throw_anom_id )
    if a and a:name() == throw_anom_name then
        throw_count = throw_count + 1
        local h = hit()
        h.draftsman = a
        h.type = hit.wound
        h.direction = vector():set( throw_dir )
        h.power = 0
        h.impulse = 100000 / throw_count
        actor:hit( h )
        return false
    end
    throw_count = 0
    return true
end


 

Собственно, разные значения для разных фаз полета и в зависимости от состояния тушки. Ограничение if actor.health < 0.02 then  - для прекращения действа, если актор и без того близок к состоянию трупа, на который, как мы помним, импульс действует иначе.

 

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

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

 


function stop_flight()
    if flight_pos then
        local pos = actor:position()
        if pos.y - flight_pos_start.y >= 0.6 and pos.y - flight_pos.y >= 0.01 then
            local dir = vector():set( 0, -1, 0 )
            flight_force = flight_force + 100
            actor:set_const_force( dir, flight_force, 65535 * 65535 )
            flight_pos = pos
            return false
        end
        if flight_force ~= 0 then
            actor:set_const_force( vector():set( 0, -1, 0 ), 9.81 * 80, 65535 * 65535 )
            flight_force = 0
        end
        flight_pos = pos
        return false
    end
    flight_pos = actor:position() or vector():set( 0, 0, 0 )
    flight_pos_start = flight_pos
    level.add_call( stop_flight, dummy )
end

 

 

 

Как-то так.

  • Нравится 1
  • Полезно 2
Ссылка на комментарий

@Dennis_Chikin, могу точно сказать, что по smart terrain есть ещё кучка движковых граблей.

Чтобы NPC шёл в смарт, надо чтобы все эти проверки вообще вызывались движком. А это происходит не всегда. ЕМНИП, для NPC должен быть выставлен флаг Interactive, без которого всё это работать не будет. Как минимум, в оффлайне.

Так же, надо чтобы can_choose_alife_tasks было выставлено в true. По умолчанию это так, и в оригинале оно нигде не меняется. В модах может и по-другому быть.

Например, я таким образом отключал поиск смартов для "эксклюзивных" NPC, чтобы задавать им id смарта в явном виде.

 

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

 

Ссылка на комментарий

Цитата

https://www.dropbox.com/s/wvub0j4ix30gecy/trade_manager.script?dl=0

 

В общем, вот. На ту же тему - "очистим от тормозов". При загрузке в частности, ну и вообще.

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

 

Смысл - тот же, что и с _g.script, но при загрузке в присутствии неписей, ну или при подходе к лагерям неписей. Плюс обновление ассортимента раз в сутки, либо сюжетное. Зато сюжетное - максимум в течение 2-х минут.

 

Как-то так.

 

upd: h24 - игровые часы между этими самыми обновлениями.

 

upd2:

Если нет файла _util.script - который поддерживает вывод логов, то замените строку

function log( ... ) _util.log( "trade_manager", ... ) end

на function log() end

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

 

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

@Dennis_Chikin, А вот ещё интересная инфа насчёт работы скрипта: если от какого-то непися убежать, чтобы он перешёл в оффлайн, а затем встретить его снова, то непись начнет торговать ВСЕМИ своими вещами, включая гитару, губную гармошку и КПК. И всё это можно будет у него купить. Но на старте игры у неписей этих вещей в продаже нет! Эксперимент проделан много раз с одинаковым результатом.

Ссылка на комментарий

Создайте аккаунт или авторизуйтесь, чтобы оставить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

Создать аккаунт

Зарегистрировать новый аккаунт в нашем сообществе. Это несложно!

Зарегистрировать новый аккаунт

Войти

Есть аккаунт? Войти.

Войти
  • Недавно просматривали   0 пользователей

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