
-- Storage for battle instance data
Battles = {}

-- Calculate basic weapon damage vs target's armor
function Battle_ResolveDamage(Action, Level, Source, Target, Result)
	local Change = {}

	-- Reduce target's evasion by extra hit chance percent
	local ExtraHitChance = math.min(100, math.max(0, Source.HitChance - 100))
	local TargetEvasion = Target.Evasion
	if ExtraHitChance > 0 then
		TargetEvasion = math.ceil(TargetEvasion * (100 - ExtraHitChance) * 0.01)
	end

	-- Always hit self
	if Target.Pointer == Source.Pointer then
		TargetEvasion = 0
	end

	-- Check for hit
	local Hit
	if Random.GetInt(1, 100) <= Source.HitChance and Random.GetInt(1, 100) > TargetEvasion then

		-- Get damage
		Change.Damage, Crit = Action:GenerateDamage(Level, Source, Target)
		if Crit == true then
			Result.Target.Crit = true
		end

		-- Apply damage return before target reductions
		if Target.DamageReturn > 0 then
			local DamageReturnMultiplier = ((100 - Source.DamageReturnResist) * 0.01) * (Target.DamageReturnPower * 0.01) * (Target.DamageReturn * 0.01)
			local SelfDamage = math.floor(DamageReturnMultiplier * Change.Damage)
			local SelfPierce = math.floor(DamageReturnMultiplier * Action:GetPierce(Source))

			-- Reduce damage by source reductions
			SelfDamage = math.max(SelfDamage - Source.DamageBlock, 0)
			SelfDamage = math.floor(SelfDamage * Source.GetDamageReduction(Action:GetDamageType(Source)) + SelfPierce)

			-- Handle mana damage reduction
			local SelfUpdate = ResolveManaShield(Source, SelfDamage)
			Result.Source.Health = SelfUpdate.Health
			if SelfUpdate.Mana ~= 0 then
				Result.Source.Mana = SelfUpdate.Mana
			end
		end

		-- Call OnHit methods for buffs
		local StaminaChange = 0
		local BuffSound = 0
		for i = 1, #Target.StatusEffects do
			local Effect = Target.StatusEffects[i]
			if Effect.Buff.OnHit ~= nil then
				Effect.Buff:OnHit(Action, Target, Effect, Change, Result)
				if Change.Stamina ~= nil then
					StaminaChange = StaminaChange + Change.Stamina
				end
				if Change.BuffSound ~= nil then
					BuffSound = Change.BuffSound
				end
			end
		end

		-- Update stamina
		if StaminaChange ~= 0 then
			Result.Target.Stamina = StaminaChange
		end

		-- Set sound from buff
		if BuffSound ~= 0 then
			Result.Target.BuffSound = BuffSound
		end

		-- Apply damage block
		Change.Damage = math.max(Change.Damage - Target.DamageBlock, 0)

		-- Apply resistance
		Result.Target.DamageType = Action:GetDamageType(Source)
		Change.Damage = math.floor(Change.Damage * Target.GetDamageReduction(Action:GetDamageType(Source)) + Action:GetPierce(Source))

		-- Handle mana damage reduction
		local Update = ResolveManaShield(Target, Change.Damage)
		Result.Target.Health = Update.Health
		if Update.Mana ~= 0 then
			Result.Target.Mana = Update.Mana
		end

		Hit = true
	else
		Result.Target.Miss = true

		-- Call OnMiss methods for buffs
		local StaminaChange = 0
		for i = 1, #Target.StatusEffects do
			local Effect = Target.StatusEffects[i]
			if Effect.Buff.OnMiss ~= nil then
				Effect.Buff:OnMiss(Target, Effect, Change, Result)
				if Change.Stamina ~= nil then
					StaminaChange = StaminaChange + Change.Stamina
				end
			end
		end

		-- Update stamina
		if StaminaChange ~= 0 then
			Result.Target.Stamina = StaminaChange
		end

		Hit = false
	end

	return Hit
end

-- Convert damage into health/mana reduction based on target's mana reduction ratio
function ResolveManaShield(Target, Damage)
	local Update = {}
	Update.Health = 0
	Update.Mana = 0
	if Target.ManaShield > 0 then
		Update.Health = -Damage * (1.0 - Target.ManaShield * 0.01)
		Update.Mana = -Damage * Target.ManaShield * 0.01

		-- Remove fractions from damage
		local Fraction = math.abs(Update.Health) - math.floor(math.abs(Update.Health))
		Update.Health = math.ceil(Update.Health + Fraction)
		Update.Mana = math.ceil(Update.Mana - Fraction)

		-- Carry over extra mana damage to health
		local ResultingMana = Target.Mana + Update.Mana
		if ResultingMana < 0 then
			Update.Health = Update.Health + ResultingMana
			Update.Mana = Update.Mana - ResultingMana
		end
	else
		Update.Health = -Damage
	end

	return Update
end

-- Roll for proccing weapons
function WeaponProc(Source, Target, Result, IsSpell)
	if Target == nil then
		return
	end

	-- Check for main weapon
	local Weapon = Source.GetInventoryItem(BAG_EQUIPMENT, INVENTORY_HAND1)
	if Weapon == nil or Weapon.Proc == nil or (IsSpell == true and Weapon.SpellProc == 0) then
		return
	end

	-- Proc main weapon
	if (Weapon.SpellProc == 2) or (Weapon.Proc.SpellOnly == true and IsSpell == true) or (Weapon.Proc.SpellOnly == false and IsSpell == false) then
		Weapon.Proc:Proc(Random.GetInt(1, 100), Weapon, Source, Target, Result)
	end

	-- Check for off-hand
	local WeaponOffHand = Source.GetInventoryItem(BAG_EQUIPMENT, INVENTORY_HAND2)
	if WeaponOffHand == nil or WeaponOffHand.Proc == nil or (IsSpell == true and WeaponOffHand.SpellProc == 0) then
		return
	end

	-- Proc off-hand weapon
	if (WeaponOffHand.SpellProc == 2) or (WeaponOffHand.Proc.SpellOnly == true and IsSpell == true) or (WeaponOffHand.Proc.SpellOnly == false and IsSpell == false) then
		WeaponOffHand.Proc:Proc(Random.GetInt(1, 100), WeaponOffHand, Source, Target, Result)
	end
end
