Module:Attacks

Revision as of 00:24, 6 March 2024 by Auron956 (talk | contribs) (Support Lifesteal)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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", "'")
	name = string.gsub(name, "'", "'")
	name = string.gsub(name, "'", "'")
    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