Перейти к содержанию

Next Generation Combat


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

https://www.nexusmods.com/morrowind/mods/46993?tab=description

 

Улучшатель боя для МВСЕ, основан на луа-скриптах.

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

Ссылка на комментарий
Поделиться на другие сайты

  • 2 недели спустя...

Я понемногу продираюсь через все те дебри кода, который он понаворотил. То что должно было поместиться в одном файле, он разнёс на целых 8 разных файлов. Синтаксис жуткий, читать надо с конца к началу, повсюду перекрёстные ссылки с середины одного файла на функцию где-то в жопе совсем другого файла, да ещё и не ясно где то что делает сама игра, а где то что добавляет скрипт. В общем без литра древнего бренди Дагот не разобраться.

Однако потихоньку картина начинает вырисовываться. Я уже смог отменить 100% шанс попасть, не ломая логику самого скрипта. В дальнейшем перспективы таковы, что раскурочив как следует этот мод, я смогу не только модифицировать урон для каждого типа оружия с блекджеком и критами, но и добавить по автоматическому прокасту какого-нибудь баффа на атакующего и дебаффа на поражённого (в виде спеллов) при каждой успешной атаке.

 

Основное ядро мода:
 
 

--
local version = 1.0
 
-- modules
local common = require('ngc.common')
local multistrike
local critical
local bleed
local stun
local momentum
local attackBonusSpell = "ngc_ready_to_strike"
 
-- set up the always hit spell for player
local function updatePlayer(e)
    if not mwscript.getSpellEffects({reference = tes3.player, spell = attackBonusSpell}) then
        mwscript.addSpell({reference = tes3.player, spell = attackBonusSpell})
    end
end
 
-- set up the always hit spell for NPCs
local function onActorActivated(e)
    local hasSpell = mwscript.getSpellEffects({reference = e.reference, spell = attackBonusSpell})
    if not hasSpell then
        mwscript.addSpell({reference = e.reference, spell = attackBonusSpell})
        if common.showDebugMessages then
            tes3.messageBox({ message = "Adding ready to strike to " .. e.reference.id })
        end
    end
end
 
-- clean up after combat
local function onCombatEnd(e)
    if (e.actor.reference == tes3.player) then
        -- reset multistrike counters
        common.multistrikeCounters = {}
        common.currentArmorCache = {}
        -- remove expose weakness on all currently exposed
        for targetId,spellId in pairs(common.currentlyExposed) do
            mwscript.removeSpell({reference = targetId, spell = spellId})
        end
        common.currentlyExposed = {}
        -- remove all bleeds and cancel all timers
        for targetId,_ in pairs(common.currentlyBleeding) do
            common.currentlyBleeding[targetId].timer:cancel()
        end
        common.currentlyBleeding = {}
    end
end
 
local function damageMessage(damageType, damageDone)
    if common.config.showMessages then
        local msgString = damageType
        if (common.config.showDamageNumbers and damageDone) then
            msgString = msgString .. " Extra damage: " .. math.round(damageDone, 2)
        end
        tes3.messageBox({ message = msgString })
    end
end
 
-- core damage features
local function attackBonusMod(attackBonus)
    return (((attackBonus - 10) * common.config.attackBonusModifier) / 100)
end
 
local function coreBonusDamage(damage, weaponoSkillLevel, attackBonus)
    local damageMod
 
    -- modify damage for weapon skill bonus
    local weaponSkillMod = ((weaponoSkillLevel * common.config.weaponSkillModifier) / 100)
 
    -- modify damage for Fortify Attack bonus
    local fortifyAttackMod = attackBonusMod(attackBonus)
 
    damageMod = damage * (weaponSkillMod + fortifyAttackMod)
 
    return damageMod
end
 
-- vanilla game strength modifier
local function strengthModifier(physicalDamage, strength)
    return physicalDamage * (0.5 + (strength / 100))
end
 
local function onDamage(e)
    local attacker = e.attacker
    local defender = e.mobile
 
    local source = e.attackerReference
    local target = e.reference
    local sourceActor = attacker
    local targetActor = defender
 
    local damageTaken = e.damage
    local damageAdded
    local damageReduced
 
    if e.source == 'attack' then
        if attacker then
            -- roll for blind first
            if attacker.blind > 0 then
                local missChanceRoll = math.random(100)
                if attacker.blind >= missChanceRoll then
                    -- you blind, you miss
                    if (common.config.showMessages and source == tes3.player) then
                        tes3.messageBox({ message = "Missed!" })
                    end
                    -- no damage
                    return
                end
            end
        end
 
        if defender then
            -- reduction from sanctuary
            local scantuaryMod = (((defender.agility.current + defender.luck.current) - 30) * common.config.sanctuaryModifier) / 100
            local reductionFromSanctuary
            if (scantuaryMod >= 0.1) then
                reductionFromSanctuary = (defender.sanctuary * scantuaryMod) / 100
            else
                reductionFromSanctuary = (defender.sanctuary * 0.1) / 100 -- minimum sanctuary reduction
            end
 
            if reductionFromSanctuary then
                damageReduced = damageTaken * reductionFromSanctuary
                damageTaken = damageTaken - damageReduced
            end
        end
 
        if attacker then
            -- core damage values
            local weapon = e.attacker.readiedWeapon
            local sourceAttackBonus = sourceActor.attackBonus
 
            if attacker.actorType == 0 then
                -- standard creature bonus
                local fortifyAttackMod = attackBonusMod(sourceAttackBonus)
                local creatureStrengthMod = ((sourceActor.strength.current * common.config.creatureBonusModifier) / 100)
                damageAdded = damageTaken * (fortifyAttackMod + creatureStrengthMod)
            elseif weapon then
                -- handle player/NPC attacks
 
                if weapon.object.type > 8 then
                    -- ranged hit
                    local weaponSkill = sourceActor.marksman.current
                    -- core bonus damage for ranged hits
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
                elseif weapon.object.type > 6 then
                    -- axe
                    local weaponSkill = sourceActor.axe.current
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
 
                    if common.config.toggleWeaponPerks then
                        local damageDone = bleed.perform(damageTaken, target, targetActor, weaponSkill)
                        if (damageDone ~= nil and source == tes3.player) then
                            damageMessage("Bleeding!", damageDone)
                        end
                    end
                elseif weapon.object.type > 5 then
                    -- spear
                    local weaponSkill = sourceActor.spear.current
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
 
                    if common.config.toggleWeaponPerks then
                        local damageDone = momentum.perform(damageTaken, source, sourceActor, targetActor, weaponSkill)
                        if damageDone ~= nil then
                            if (source == tes3.player and common.config.showDamageNumbers) then
                                damageMessage("Momentum!", damageDone)
                            end
                            damageAdded = damageAdded + damageDone
                        end
                    end
                elseif weapon.object.type > 2 then
                    -- blunt
                    local weaponSkill = sourceActor.bluntWeapon.current
                    local stunned
                    local damageDone
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
 
                    if common.config.toggleWeaponPerks then
                        stunned, damageDone = stun.perform(damageTaken, target, targetActor, weaponSkill)
                        if (stunned and source == tes3.player) then
                            damageMessage("Stunned!", damageDone)
                        elseif (source == tes3.player and common.config.showDamageNumbers) then
                            -- just show extra damage for blunt weapon if no stun
                            damageMessage("", damageDone)
                        end
                        damageAdded = damageAdded + damageDone
                    end
                elseif weapon.object.type > 0 then
                    -- long blade
                    local weaponSkill = sourceActor.longBlade.current
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
 
                    if common.config.toggleWeaponPerks then
                        common.multistrikeCounters = multistrike.checkCounters(source.id)
                        if common.multistrikeCounters[source.id] == 3 then
                            local damageDone = multistrike.perform(damageTaken, source, weaponSkill)
                            common.multistrikeCounters[source.id] = 0
                            if source == tes3.player then
                                damageMessage("Multistrike!", damageDone)
                            end
                            damageAdded = damageAdded + damageDone
                        end
                    end
                elseif weapon.object.type > -1 then
                    -- short blade
                    local weaponSkill = sourceActor.shortBlade.current
                    damageAdded = coreBonusDamage(damageTaken, weaponSkill, sourceAttackBonus)
 
                    if common.config.toggleWeaponPerks then
                        local damageDone = critical.perform(damageTaken, target, weaponSkill)
                        if damageDone ~= nil then
                            if source == tes3.player then
                                damageMessage("Critical strike!", damageDone)
                            end
                            damageAdded = damageAdded + damageDone
                        end
                    end
                end
            end
        end
 
        if damageAdded then
            -- we already have damageReduced taken into account with damageTaken
            e.damage = damageTaken + damageAdded
            if common.config.showDebugMessages then
                local showReducedDamage = 0
                if damageReduced then
                    showReducedDamage = damageReduced
                end
                tes3.messageBox({ message = "Final damage: " .. math.round(e.damage, 2) .. ". Reduced: " .. math.round(showReducedDamage, 2) .. ". Added: " .. math.round(damageAdded, 2)  })
            end
        elseif damageReduced then
            -- we don't have any damage added but we still have damage reduced
            e.damage = e.damage - damageReduced
            if common.config.showDebugMessages then
                tes3.messageBox({ message = "Reduced: " .. math.round(damageReduced, 2) })
            end
        end
    end
end
 
local function initialized(e)
if tes3.isModActive("Next Generation Combat.esp") then
        common.loadConfig()
 
        -- load modules
        multistrike = require("ngc.perks.multistrike")
        critical = require("ngc.perks.critical")
        bleed = require("ngc.perks.bleed")
        stun = require("ngc.perks.stun")
        momentum = require("ngc.perks.momentum")
 
        -- register events
        event.register("loaded", updatePlayer)
        event.register("cellChanged", updatePlayer)
        event.register("mobileActivated", onActorActivated)
        event.register("combatStopped", onCombatEnd)
        event.register("damage", onDamage)
 
mwse.log("[Next Generation Combat] Initialized version v%d", version)
        mwse.log(json.encode(common.config, {indent=true}))
end
end
event.register("initialized", initialized)

 

Ссылка на комментарий
Поделиться на другие сайты

То что должно было поместиться в одном файле, он разнёс на целых 8 разных файлов.

Нормальная практика в программировании.

Синтаксис жуткий

Lua же. Операторы напомнили мне помесь Pascal и C. Для остального достаточно знать английский язык, чтобы понять, о чем речь.

Я уже смог отменить 100% шанс попасть

Шанс реализован криво, кстати. Если на персонаже висели большие штрафы к атаке или противник очень ловкий и со светочем, то от заклинания на +100 пунктов автоматического попадания не появится.

 

Мне кажется, там стоит улучшить механику с blind'ом. Чтобы ослепленный персонаж получал не только штраф к атаке (как в ванили), но и штраф к защите.

 

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

Изменено пользователем Муурн Шепард
Ссылка на комментарий
Поделиться на другие сайты

Мод обновлён, активная блокировка (дожили!) и переделка рукопашного боя.

____

Боги.... башка закипает!

Ну он и понаворотил...

 

Пытаюсь сделать зависимость урона от стамины. Если удастся, то мод можно считать мастхевным.

Изменено пользователем Dagot_Prolaps
Ссылка на комментарий
Поделиться на другие сайты

А если сделать так?

 

 

  local function damageReductionFromSanctuary(defender, damageTaken)
    local damageReduced
    -- reduction from sanctuary
    local scantuaryMod = (((defender.agility.current + defender.luck.current) - 30) * common.config.sanctuaryModifier) / 100
    local reductionFromSanctuary
    if (scantuaryMod >= 0.1) then
        reductionFromSanctuary = (defender.sanctuary * scantuaryMod) / 100
    else
        reductionFromSanctuary = (defender.sanctuary * 0.1) / 100 -- minimum sanctuary reduction
    end

    if reductionFromSanctuary then
        damageReduced = damageTaken * reductionFromSanctuary
    end

    return damageReduced
end

local function damageReductionFromFatigue(attacker, damageTaken)
    local damageReduced
    -- reduction from fatigue
    local MaxFatigue = attacker.fatigue.base
    local CurrentFatigue = attacker.fatigue.current
    damageReduced = damageTaken - ( CurrentFatigue / MaxFatigue * damageTaken )

    tes3.messageBox({ message = "Fatigue Damage Reduction: " .. math.round(damageReduced, 2)})
    return damageReduced
end
 if defender and common.config.toggleAlwaysHit then
            -- reduction from sanctuary
            local reductionFromSanctuary = damageReductionFromSanctuary(defender, damageTaken)

            if reductionFromSanctuary then
                damageTaken = damageTaken - reductionFromSanctuary
                damageReduced = reductionFromSanctuary
            end
        end
    
    if attacker and common.config.toggleAlwaysHit then
        local reductionFromFatigue = damageReductionFromFatigue(attacker, damageTaken)
            if reductionFromFatigue then
                damageTaken = damageTaken - reductionFromFatigue
                damageReduced = reductionfromSanctuary + reductionFromFatigue
            end

    end

        damageAdded = coreBonusDamage(damageTaken, handToHandAttacker.weaponSkill, handToHandAttacker.attackBonus)
    end

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

 

 

Изменено пользователем Муурн Шепард
Ссылка на комментарий
Поделиться на другие сайты

Не робит, увы(

 

Бью на нулевой стамине.

Сообщения о порезке урона на гигантские суммы (фактически на всю величину урона) появляются, но реальный урон наносится в полном непорезанном объёме.

Ссылка на комментарий
Поделиться на другие сайты

 

Поправил на такое. Damage reduced присваивалась локальная переменная из другого блока, могло глючить из-за этого.

       if attacker and common.config.toggleAlwaysHit then
            local reductionFromFatigue = damageReductionFromFatigue(attacker, damageTaken)
                if reductionFromFatigue then
                      damageTaken = damageTaken - reductionFromFatigue
                      damageReduced = damageReduced + reductionFromFatigue
                end

        end

 

 

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

Изменено пользователем Муурн Шепард
Ссылка на комментарий
Поделиться на другие сайты

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

 

Вот это будет великолепно, и появится смысл в разных ударах.

Ссылка на комментарий
Поделиться на другие сайты

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

 

С броней разных типов будет сложно (или невозможно) - что-то не вижу где бы игра могла считать этот параметр.

А вот с ударами я нашёл очень интересную фишку. У объекта mobilePlayer есть куча разных считываемых параметров в том числе isJumping, isMovingBack, isMovingForward, isMovingRight...

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

Но сперва надо разобраться с основной стаминной зависимостью. Автор обещала ввести её в следующем релизе вместе с игровым настроечным меню. Вот тогда мод станет точно мастхевом!

Ссылка на комментарий
Поделиться на другие сайты

С броней разных типов будет сложно (или невозможно) - что-то не вижу где бы игра могла считать этот параметр.

Тип брони определяется даже стандартными средствами. Направление для атаки ловится через Action Data.

Но сперва надо разобраться с основной стаминной зависимостью. Автор обещала ввести её в следующем релизе вместе с игровым настроечным меню. Вот тогда мод станет точно мастхевом!

Дык выше же рабочий вариант. Формулу можно прикрутить любую.

Ссылка на комментарий
Поделиться на другие сайты

Тип брони определяется даже стандартными средствами.

 

Можно конечно считать веса и рассчитать тип по формулам, а что насчёт события damage? Как узнать, по какой части доспеха попал удар?

Ссылка на комментарий
Поделиться на другие сайты

Как узнать, по какой части доспеха попал удар?

Хм, тут без костылей никак. Можно через развитие навыка, других способов я не вижу.

Ссылка на комментарий
Поделиться на другие сайты

Добавить шанс уклонения от атаки в размере 30%*Ловкость, если защищающийся актёр по время события атаки движется влево или вправо. Обычно режущие удары слабее рубящих - это будет прекрасная компенсация и повод применять именно их.

 

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

 

А вот при движении вперёд надо что-то изменить. Тем более, учитывая то, что обычно используется движение сразу и вперёд и в сторону для нанесения рубящего удара в движении.

Изменено пользователем Dagot_Prolaps
Ссылка на комментарий
Поделиться на другие сайты

Для публикации сообщений создайте учётную запись или авторизуйтесь

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

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Регистрация нового пользователя

Войти

Уже есть аккаунт? Войти в систему.

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

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