Module:Attacks: Difference between revisions

From Melvor Idle
(Implement support for multiple definitions per effect)
(Use tabs instead of spaces for indentation)
Line 8: Line 8:


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


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


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


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


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


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


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


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


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


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


return p
return p

Revision as of 21:29, 8 January 2022

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", "&apos;")
	name = string.gsub(name, "'", "&apos;")
	name = string.gsub(name, "&#39;", "&apos;")
	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 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