Module:Attacks: Difference between revisions

From Melvor Idle
(Implement support for multiple definitions per effect)
(Support Lifesteal)
 
(8 intermediate revisions by the same user not shown)
Line 3: Line 3:
local p = {}
local p = {}


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


p.effectDefinition = {
p.effectDefinition = {
    ["Burn"] = {
["Burn"] = {
        {
{
            ["type"] = 'DOT',
["effectType"] = '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"] = {
["effectType"] = 'Poison'
        ["type"] = 'DOT',
},
        ["subtype"] = 'Poison'
["Deadly Poison"] = {
    },
["effectType"] = 'DeadlyPoison'
    ["Slow"] = {
},
        ["type"] = 'Modifier',
["Slow"] = {
        ["modifiers"] = {
["type"] = 'Modifier',
            ["include"] = { 'increasedAttackIntervalPercent' },
["modifiers"] = {
            ["exclude"] = { 'increasedFrostburn' }
["include"] = { 'increasedAttackIntervalPercent' },
        }
["exclude"] = { 'increasedFrostburn' }
    },
}
    ["Bleed"] = {
},
        ["type"] = 'DOT',
["Bleed"] = {
        ["subtype"] = 'Bleed'
["type"] = 'DOT',
    },
["subtype"] = 'Bleed'
    ["Frostburn"] = {
},
        ["type"] = 'Modifier',
["Frostburn"] = {
        ["modifiers"] = {
["effectType"] = 'Frostburn'
            ["include"] = { 'increasedFrostburn', 'increasedAttackIntervalPercent' }
},
        }
["Mark of Death"] = {
    },
["type"] = 'Stacking',
    ["Mark of Death"] = {
["modifiers"] = {
        ["type"] = 'Stacking',
["include"] = { 'decreasedDamageReductionPercent' }
        ["modifiers"] = {
}
            ["include"] = { 'decreasedDamageReductionPercent' }
},
        }
["Affliction"] = {
    },
{ ["effectType"] = 'Affliction' },
    ["Affliction"] = {
{
        ["type"] = 'Modifier',
["type"] = 'Modifier',
        ["modifiers"] = {
["modifiers"] = {
            ["include"] = { 'decreasedMaxHitpoints' }
["include"] = { 'increasedAfflictionChance' }
        }
}
    },
}
    ["Sleep"] = {
},
        ["type"] = 'Sleep'
["Sleep"] = {
    },
{
    ["Freeze"] = {
["type"] = 'Sleep'
        ["type"] = 'Stun',
},
        ["flavour"] = 'Freeze'
-- Alternative definition specifically for familiars like Siren
    },
{
    ["Stun"] = {
["type"] = 'Modifier',
        ["type"] = 'Stun',
["subtype"] = 'Familiar',
        ["flavour"] = 'Stun'
["modifiers"] = {
    },
["include"] = { 'increasedGlobalSleepChance' }
    ["Regen"] = {
}
        ["type"] = 'DOT',
}
        ["subtype"] = 'Regen'
},
    }
["Slow"] = {
["effectType"] = 'Slow'
},
["Freeze"] = {
["type"] = 'Stun',
["flavour"] = 'Freeze'
},
["Stun"] = {
["type"] = 'Stun',
["flavour"] = 'Stun'
},
["Regen"] = {
["type"] = 'DOT',
["subtype"] = 'Regen'
},
["Lifesteal"] = {
["attFunc"] = function(attack)
return type(attack.lifesteal) == 'number' and attack.lifesteal > 0
end
}
-- TODO Implement curses
}
}


function p.getAttackByID(ID)
function p.getAttackByID(ID)
     return AttackData.Attacks[ID + 1]
     return GameData.getEntityByID('attacks', ID)
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
     return GameData.getEntityByName('attacks', name)
        if name == attack.name then
            return attack
        end
    end
end
end


function p.getAttacks(checkFunc)
function p.getAttacks(checkFunc)
     local result = {}
     return GameData.getEntities('attacks', checkFunc)
    for i, attack in ipairs(AttackData.Attacks) do
        if checkFunc(attack) then
            table.insert(result, attack)
        end
    end
    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
-- Convert effect definition into list of definitions to be checked
function p.attackHasEffect(attack, effectDefn)
function p.getEffectDefnList(effectDefn)
    if type(attack) == 'table' and type(effectDefn) == 'table' then
-- Some effects (e.g. Burn) have multiple definitions, so handle these correctly
        -- Process pre-hit effects
if type(effectDefn[1]) == 'table' then
        for i, effect in ipairs(attack.prehitEffects) do
-- Definition is actually multiple definitions
            if p.effectMatchesDefn(effect, effectDefn) then
return effectDefn
                return true
else
            end
-- Definition is singular, wrap it within a table so we can iterate
        end
return { effectDefn }
        -- Process on hit effects
end
        for i, effect in ipairs(attack.onhitEffects) do
            if p.effectMatchesDefn(effect, effectDefn) then
                return true
            end
        end
    end
    return false
end
end


function p.effectMatchesDefn(effect, effectDefnIn)
-- Determines if attack applies the effect defined in effectDefinition
    -- Some effects (e.g. Burn) have multiple definitions, so handle these correctly
function p.attackHasEffect(attack, effectDefnRaw)
    local effectDefnList = nil
if type(attack) == 'table' and type(effectDefnRaw) == 'table' then
    if effectDefnIn[1] ~= nil and type(effectDefnIn[1]) == 'table' then
local effectDefnList = p.getEffectDefnList(effectDefnRaw)
        -- Definition is actually multiple definitions
for i, effectDefn in pairs(effectDefnList) do
        effectDefnList = effectDefnIn
if effectDefn.attFunc ~= nil then
    else
-- Attack level check, for effects like lifesteal
        -- Definition is singular, wrap it within a table so we can iterate
if effectDefn.attFunc(attack) then
        effectDefnList = { effectDefnIn }
return true
    end
end
 
else
    for i, effectDefn in pairs(effectDefnList) do
-- Process pre-hit effects
        if p.effectMatchesDefnSingle(effect, effectDefn) then
for i, effect in ipairs(attack.prehitEffects) do
            return true
if p.effectMatchesDefn(effect, effectDefn) then
        end
return true
    end
end
    return false
end
-- Process on hit effects
for i, effect in ipairs(attack.onhitEffects) do
if p.effectMatchesDefn(effect, effectDefn) then
return true
end
end
end
end
end
return false
end
end


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

Latest revision as of 00:24, 6 March 2024

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 GameData = require('Module:GameData')
local Shared = require('Module:Shared')

p.effectDefinition = {
	["Burn"] = {
		{
			["effectType"] = 'Burn'
		},
		-- Alternative definition specifically for familiars like Dragon
		{
			["type"] = 'Modifier',
			["subtype"] = 'Familiar',
			["modifiers"] = {
				["include"] = { 'increasedChanceToApplyBurn' }
			}
		}
	},
	["Poison"] = {
		["effectType"] = 'Poison'
	},
	["Deadly Poison"] = {
		["effectType"] = 'DeadlyPoison'
	},
	["Slow"] = {
		["type"] = 'Modifier',
		["modifiers"] = {
			["include"] = { 'increasedAttackIntervalPercent' },
			["exclude"] = { 'increasedFrostburn' }
		}
	},
	["Bleed"] = {
		["type"] = 'DOT',
		["subtype"] = 'Bleed'
	},
	["Frostburn"] = {
		["effectType"] = 'Frostburn'
	},
	["Mark of Death"] = {
		["type"] = 'Stacking',
		["modifiers"] = {
			["include"] = { 'decreasedDamageReductionPercent' }
		}
	},
	["Affliction"] = {
		{ ["effectType"] = 'Affliction' },
		{
			["type"] = 'Modifier',
			["modifiers"] = {
				["include"] = { 'increasedAfflictionChance' }
			}
		}
	},
	["Sleep"] = {
		{
			["type"] = 'Sleep'
		},
		-- Alternative definition specifically for familiars like Siren
		{
			["type"] = 'Modifier',
			["subtype"] = 'Familiar',
			["modifiers"] = {
				["include"] = { 'increasedGlobalSleepChance' }
			}
		}
	},
	["Slow"] = {
		["effectType"] = 'Slow'
	},
	["Freeze"] = {
		["type"] = 'Stun',
		["flavour"] = 'Freeze'
	},
	["Stun"] = {
		["type"] = 'Stun',
		["flavour"] = 'Stun'
	},
	["Regen"] = {
		["type"] = 'DOT',
		["subtype"] = 'Regen'
	},
	["Lifesteal"] = {
		["attFunc"] = function(attack)
			return type(attack.lifesteal) == 'number' and attack.lifesteal > 0
		end
	}
	-- TODO Implement curses
}

function p.getAttackByID(ID)
    return GameData.getEntityByID('attacks', ID)
end

function p.getAttack(name)
	name = string.gsub(name, "%%27", "&apos;")
	name = string.gsub(name, "'", "&apos;")
	name = string.gsub(name, "&#39;", "&apos;")
    return GameData.getEntityByName('attacks', name)
end

function p.getAttacks(checkFunc)
    return GameData.getEntities('attacks', checkFunc)
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

-- Convert effect definition into list of definitions to be checked
function p.getEffectDefnList(effectDefn)
	-- Some effects (e.g. Burn) have multiple definitions, so handle these correctly
	if type(effectDefn[1]) == 'table' then
		-- Definition is actually multiple definitions
		return effectDefn
	else
		-- Definition is singular, wrap it within a table so we can iterate
		return { effectDefn }
	end
end

-- Determines if attack applies the effect defined in effectDefinition
function p.attackHasEffect(attack, effectDefnRaw)
	if type(attack) == 'table' and type(effectDefnRaw) == 'table' then
		local effectDefnList = p.getEffectDefnList(effectDefnRaw)
		for i, effectDefn in pairs(effectDefnList) do
			if effectDefn.attFunc ~= nil then
				-- Attack level check, for effects like lifesteal
				if effectDefn.attFunc(attack) then
					return true
				end
			else
				-- 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
		end
	end
	return false
end

function p.effectMatchesDefn(effect, effectDefn)
	if effectDefn.type ~= effect.type then
		-- Effect's type doesn't match that of the effect definition
		return false
	elseif (effectDefn.effectType ~= nil and (effect.effectType == nil or effect.effectType ~= effectDefn.effectType)) then
		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