Module:Attacks

From Melvor Idle
Revision as of 23:54, 9 November 2021 by Auron956 (talk | contribs) (Implement support for multiple definitions per effect)

Documentation for this module may be created at Module:Attacks/doc

-- For tables & other outputs generated with attack data, see: Module:Attacks/Tables

local p = {}

local AttackData = mw.loadData('Module:Attacks/data')

local Shared = require('Module:Shared')

p.effectDefinition = {
    ["Burn"] = {
        {
            ["type"] = 'DOT',
            ["subtype"] = 'Burn'
        },
        -- Alternative definition specifically for familiars like Dragon
        {
            ["type"] = 'Modifier',
            ["subtype"] = 'Familiar',
            ["modifiers"] = {
                ["include"] = { 'increasedChanceToApplyBurn' }
            }
        }
    },
    ["Poison"] = {
        ["type"] = 'DOT',
        ["subtype"] = 'Poison'
    },
    ["Slow"] = {
        ["type"] = 'Modifier',
        ["modifiers"] = {
            ["include"] = { 'increasedAttackIntervalPercent' },
            ["exclude"] = { 'increasedFrostburn' }
        }
    },
    ["Bleed"] = {
        ["type"] = 'DOT',
        ["subtype"] = 'Bleed'
    },
    ["Frostburn"] = {
        ["type"] = 'Modifier',
        ["modifiers"] = {
            ["include"] = { 'increasedFrostburn', 'increasedAttackIntervalPercent' }
        }
    },
    ["Mark of Death"] = {
        ["type"] = 'Stacking',
        ["modifiers"] = {
            ["include"] = { 'decreasedDamageReductionPercent' }
        }
    },
    ["Affliction"] = {
        ["type"] = 'Modifier',
        ["modifiers"] = {
            ["include"] = { 'decreasedMaxHitpoints' }
        }
    },
    ["Sleep"] = {
        ["type"] = 'Sleep'
    },
    ["Freeze"] = {
        ["type"] = 'Stun',
        ["flavour"] = 'Freeze'
    },
    ["Stun"] = {
        ["type"] = 'Stun',
        ["flavour"] = 'Stun'
    },
    ["Regen"] = {
        ["type"] = 'DOT',
        ["subtype"] = 'Regen'
    }
}

function p.getAttackByID(ID)
    return AttackData.Attacks[ID + 1]
end

function p.getAttack(name)
    name = string.gsub(name, "%%27", "'")
    name = string.gsub(name, "'", "'")
    name = string.gsub(name, "'", "'")
    for i, attack in ipairs(AttackData.Attacks) do
        if name == attack.name then
            return attack
        end
    end
end

function p.getAttacks(checkFunc)
    local result = {}
    for i, attack in ipairs(AttackData.Attacks) do
        if checkFunc(attack) then
            table.insert(result, attack)
        end
    end
    return result
end

function p.getAttackEffects(attack)
    local attackEffects = {}
    for effectName, effectDefn in pairs(p.effectDefinition) do
        if p.attackHasEffect(attack, effectDefn) then
            table.insert(attackEffects, effectName)
        end
    end
    return attackEffects
end

-- Determines if attack applies the effect defined in effectDefinition
function p.attackHasEffect(attack, effectDefn)
    if type(attack) == 'table' and type(effectDefn) == 'table' then
        -- Process pre-hit effects
        for i, effect in ipairs(attack.prehitEffects) do
            if p.effectMatchesDefn(effect, effectDefn) then
                return true
            end
        end
        -- Process on hit effects
        for i, effect in ipairs(attack.onhitEffects) do
            if p.effectMatchesDefn(effect, effectDefn) then
                return true
            end
        end
    end
    return false
end

function p.effectMatchesDefn(effect, effectDefnIn)
    -- Some effects (e.g. Burn) have multiple definitions, so handle these correctly
    local effectDefnList = nil
    if effectDefnIn[1] ~= nil and type(effectDefnIn[1]) == 'table' then
        -- Definition is actually multiple definitions
        effectDefnList = effectDefnIn
    else
        -- Definition is singular, wrap it within a table so we can iterate
        effectDefnList = { effectDefnIn }
    end

    for i, effectDefn in pairs(effectDefnList) do
        if p.effectMatchesDefnSingle(effect, effectDefn) then
            return true
        end
    end
    return false
end

function p.effectMatchesDefnSingle(effect, effectDefn)
    if effectDefn.type ~= effect.type then
        -- Effect's type doesn't match that of the effect definition
        return false
    elseif (effectDefn.subtype ~= nil and (effect.subtype == nil or effect.subtype ~= effectDefn.subtype))
           or (effectDefn.flavour ~= nil and (effect.flavour == nil or effect.flavour ~= effectDefn.flavour)) then
        -- Effect's subtype or flavour doesn't match that of the effect definition
        return false
    elseif type(effectDefn.modifiers) == 'table' and (effectDefn.modifiers.include ~= nil or effectDefn.modifiers.exclude ~= nil) then
        -- Definition contains modifiers which need to be checked
        local modsIncl, modsExcl = (effectDefn.modifiers.include or {}), (effectDefn.modifiers.exclude or {})
        local modsInclFound = {}
        if Shared.tableCount(modsIncl) > 0 and (type(effect.modifiers) ~= 'table' or Shared.tableCount(effect.modifiers) < Shared.tableCount(modsIncl)) then
            -- Definition has 1+ included modifiers but effect has fewer modifiers than the definition
            return false
        end

        for modName, modVal in pairs(effect.modifiers) do
            if Shared.contains(modsExcl, modName, false) then
                -- Effect contains a modifier on the exclusion list
                return false
            elseif Shared.contains(modsIncl, modName, false) then
                -- Flag included modifier as found
                modsInclFound[modName] = true
            end
        end
        if Shared.tableCount(modsInclFound) < Shared.tableCount(modsIncl) then
            -- Effect doesn't have all of the included modifiers
            return false
        end
    end
    return true
end

return p