Skip to content
Snippets Groups Projects
Combat.lua 50.5 KiB
Newer Older
dg's avatar
dg committed
end

--- Computes spell crit for a damage
function _M:spellCrit(dam, add_chance)
dg's avatar
dg committed
	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
dg's avatar
dg committed
		return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true
dg's avatar
dg committed
	end

	local chance = self:combatSpellCrit() + (add_chance or 0)
dg's avatar
dg committed
	local crit = false
dg's avatar
dg committed
	print("[SPELL CRIT %]", chance)
dg's avatar
dg committed
	if rng.percent(chance) then
dg's avatar
dg committed
		dam = dam * (1.5 + (self.combat_critical_power or 0) / 100)
dg's avatar
dg committed
		crit = true
		game.logSeen(self, "#{bold}#%s's spell attains critical power!#{normal}#", self.name:capitalize())
dg's avatar
dg committed

dg's avatar
dg committed
		if self:attr("mana_on_crit") then self:incMana(self:attr("mana_on_crit")) end

dg's avatar
dg committed
		if self:isTalentActive(self.T_BLOOD_FURY) then
			local t = self:getTalentFromId(self.T_BLOOD_FURY)
			t.on_crit(self, t)
		end
dg's avatar
dg committed

		if self:isTalentActive(self.T_CORONA) then
			local t = self:getTalentFromId(self.T_CORONA)
dg's avatar
dg committed
			t.on_crit(self, t)
dg's avatar
dg committed
	end
dg's avatar
dg committed
	return dam, crit
dg's avatar
dg committed
end
dg's avatar
dg committed

--- Computes mind crit for a damage
function _M:mindCrit(dam, add_chance)
	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
		return dam * (1.5 + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7), true
	end

	local chance = self:combatMindCrit() + (add_chance or 0)
	local crit = false

	print("[MIND CRIT %]", chance)
	if rng.percent(chance) then
		dam = dam * (1.5 + (self.combat_critical_power or 0) / 100)
		crit = true
		game.logSeen(self, "#{bold}#%s's mind surges with critical power!#{normal}#", self.name:capitalize())
	end
	return dam, crit
end

--- Do we get hit by our own AOE ?
function _M:spellFriendlyFire()
	local chance = (self:getLck() - 50) * 0.2
	if self:isTalentActive(self.T_SPELLCRAFT) then chance = chance + self:getTalentLevelRaw(self.T_SPELLCRAFT) * 20 end
	chance = 100 - chance
	print("[SPELL] friendly fire chance", chance)
	return chance
end

--- Gets mindpower
function _M:combatMindpower(mod)
	mod = mod or 1
	local add = 0

	if self:knowTalent(self.T_GESTURE_OF_COMMAND) then
		local t = self:getTalentFromId(self.T_GESTURE_OF_COMMAND)
		add = t.getMindpowerChange(self, t)
	end

dg's avatar
dg committed
	return self:rescaleCombatStats((self.combat_mindpower > 0 and self.combat_mindpower or 0) + add + self:getWil() * 0.7 + self:getCun() * 0.4) * mod
end

--- Gets damage based on talent
function _M:combatTalentMindDamage(t, base, max)
	-- Compute at "max"
	local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
	-- Compute real
dg's avatar
dg committed
	return self:rescaleDamage((base + (self:combatMindpower())) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod)
--- Gets damage based on talent
function _M:combatTalentStatDamage(t, stat, base, max)
	-- Compute at "max"
	local mod = max / ((base + 100) * ((math.sqrt(5) - 1) * 0.8 + 1))
	-- Compute real
dg's avatar
dg committed
	local dam = (base + (self:getStat(stat))) * ((math.sqrt(self:getTalentLevel(t)) - 1) * 0.8 + 1) * mod
	dam =  dam * (1 - math.log10(dam * 2) / 7)
	dam = dam ^ (1 / 1.04)
	return self:rescaleDamage(dam)
dg's avatar
dg committed
--- Gets damage based on talent, basic stat, and interval
function _M:combatTalentIntervalDamage(t, stat, min, max, stat_weight)
	local stat_weight = stat_weight or 0.5
dg's avatar
dg committed
	local dam = min + (max - min)*((stat_weight * self:getStat(stat)/100) + (1 - stat_weight) * self:getTalentLevel(t)/6.5)
	dam =  dam * (1 - math.log10(dam * 2) / 7)
	dam = dam ^ (1 / 1.04)
	return self:rescaleDamage(dam)
end

--- Gets damage based on talent, stat, and interval
function _M:combatStatTalentIntervalDamage(t, stat, min, max, stat_weight)
	local stat_weight = stat_weight or 0.5
	scaled_stat = self[stat](self)
	return self:rescaleDamage(min + (max - min)*((stat_weight * self[stat](self)/100) + (1 - stat_weight) * self:getTalentLevel(t)/6.5))
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Computes physical resistance
--- Fake denotes a check not actually being made, used by character sheets etc.
function _M:combatPhysicalResist(fake)
dg's avatar
dg committed
	local add = 0
dg's avatar
dg committed
	if not fake then
		add = add + (self:checkOnDefenseCall("physical") or 0)
	end
dg's avatar
dg committed
	if self:knowTalent(self.T_POWER_IS_MONEY) then
		add = add + util.bound(self.money / (80 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
	end
dg's avatar
dg committed
	return self:rescaleCombatStats(self.combat_physresist + (self:getCon() + self:getStr() + (self:getLck() - 50) * 0.5) * 0.35 + add)
dg's avatar
dg committed
end

--- Computes spell resistance
--- Fake denotes a check not actually being made, used by character sheets etc.
function _M:combatSpellResist(fake)
dg's avatar
dg committed
	local add = 0
dg's avatar
dg committed
	if not fake then
		add = add + (self:checkOnDefenseCall("spell") or 0)
	end
dg's avatar
dg committed
	if self:knowTalent(self.T_POWER_IS_MONEY) then
		add = add + util.bound(self.money / (80 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
dg's avatar
dg committed
	end
dg's avatar
dg committed
	return self:rescaleCombatStats(self.combat_spellresist + (self:getMag() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add)
dg's avatar
dg committed
end
dg's avatar
dg committed

--- Computes mental resistance
--- Fake denotes a check not actually being made, used by character sheets etc.
function _M:combatMentalResist(fake)
dg's avatar
dg committed
	local add = 0
dg's avatar
dg committed
	if not fake then
		add = add + (self:checkOnDefenseCall("mental") or 0)
	end
dg's avatar
dg committed
	if self:knowTalent(self.T_STEADY_MIND) then
		local t = self:getTalentFromId(self.T_STEADY_MIND)
		add = add + t.getMental(self, t)
	end
dg's avatar
dg committed
	if self:knowTalent(self.T_POWER_IS_MONEY) then
		add = add + util.bound(self.money / (80 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 10)
dg's avatar
dg committed
	end
dg's avatar
dg committed
	return self:rescaleCombatStats(self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add)
-- Called when a Save or Defense is checked
function _M:checkOnDefenseCall(type)
	local add = 0
	if self:knowTalent(self.T_SPIN_FATE) then
		print("Spin Fate", type)
		local t = self:getTalentFromId(self.T_SPIN_FATE)
		t.do_spin_fate(self, t, type)
	end
	return add
end

--- Returns the resistance
function _M:combatGetResist(type)
	local a = (self.resists.all or 0) / 100
	local b = (self.resists[type] or 0) / 100
	local r = math.min(100 * (1 - (1 - a) * (1 - b)), (self.resists_cap.all or 0) + (self.resists_cap[type] or 0))
	return r
end

dg's avatar
dg committed
--- Computes movement speed
function _M:combatMovementSpeed()
dg's avatar
dg committed
	local mult = 1
	if game.level and game.level.data.zero_gravity then
dg's avatar
dg committed
		mult = 3
	end
	return mult * (self.base_movement_speed or 1) / self.movement_speed
dg's avatar
dg committed
end

dg's avatar
dg committed
--- Check if the actor has a gem bomb in quiver
function _M:hasAlchemistWeapon()
	if not self:getInven("QUIVER") then return nil, "no ammo" end
	local ammo = self:getInven("QUIVER")[1]
	if not ammo or not ammo.alchemist_power then
		return nil, "bad or no ammo"
	end
	return ammo
end

dg's avatar
dg committed
--- Check if the actor has a staff weapon
function _M:hasStaffWeapon()
dg's avatar
dg committed
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

dg's avatar
dg committed
	if not self:getInven("MAINHAND") then return end
	local weapon = self:getInven("MAINHAND")[1]
	if not weapon or weapon.subtype ~= "staff" then
		return nil
	end
	return weapon
end

dg's avatar
dg committed
--- Check if the actor has an axe weapon
function _M:hasAxeWeapon()
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

	if not self:getInven("MAINHAND") then return end
	local weapon = self:getInven("MAINHAND")[1]
	if not weapon or (weapon.subtype ~= "battleaxe" and weapon.subtype ~= "waraxe") then
		return nil
	end
	return weapon
end

--- Check if the actor has a cursed weapon
function _M:hasCursedWeapon()
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

	if not self:getInven("MAINHAND") then return end
	local weapon = self:getInven("MAINHAND")[1]
	if not weapon or not weapon.curse then
	local t = self:getTalentFromId(self.T_DEFILING_TOUCH)
	if not t.canCurseItem(self, t, weapon) then return nil end
--- Check if the actor has a cursed weapon
function _M:hasCursedOffhandWeapon()
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

	if not self:getInven("OFFHAND") then return end
	local weapon = self:getInven("OFFHAND")[1]
	if not weapon or not weapon.combat or not weapon.curse then
		return nil
	end
	local t = self:getTalentFromId(self.T_DEFILING_TOUCH)
	if not t.canCurseItem(self, t, weapon) then return nil end
	return weapon
end

--- Check if the actor has a two handed weapon
dg's avatar
dg committed
function _M:hasTwoHandedWeapon()
dg's avatar
dg committed
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

dg's avatar
dg committed
	if not self:getInven("MAINHAND") then return end
dg's avatar
dg committed
	local weapon = self:getInven("MAINHAND")[1]
	if not weapon or not weapon.twohanded then
		return nil
	end
	return weapon
end

--- Check if the actor has a shield
function _M:hasShield()
dg's avatar
dg committed
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

dg's avatar
dg committed
	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return end
	local shield = self:getInven("OFFHAND")[1]
	if not shield or not shield.special_combat then
		return nil
	end
	return shield
dg's avatar
dg committed
end

dg's avatar
dg committed
-- Check if actor is unarmed
function _M:isUnarmed()
	local unarmed = true
	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return end
	local weapon = self:getInven("MAINHAND")[1]
	local offweapon = self:getInven("OFFHAND")[1]
	if weapon or offweapon then
		unarmed = false
	end
	return unarmed
end

-- Get the number of free hands the actor has
function _M:getFreeHands()
dg's avatar
dg committed
	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return 0 end
	local weapon = self:getInven("MAINHAND")[1]
	local offweapon = self:getInven("OFFHAND")[1]
	if weapon and offweapon then return 0 end
dg's avatar
dg committed
	if weapon and weapon.twohanded then return 0 end
dg's avatar
dg committed
--- Check if the actor dual wields
function _M:hasDualWeapon()
dg's avatar
dg committed
	if self:attr("disarmed") then
		return nil, "disarmed"
	end

dg's avatar
dg committed
	if not self:getInven("MAINHAND") or not self:getInven("OFFHAND") then return end
	local weapon = self:getInven("MAINHAND")[1]
	local offweapon = self:getInven("OFFHAND")[1]
	if not weapon or not offweapon or not weapon.combat or not offweapon.combat then
		return nil
	end
	return weapon, offweapon
end
dg's avatar
dg committed

dg's avatar
dg committed
--- Check if the actor has a light armor
function _M:hasLightArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or (armor.subtype ~= "cloth" and armor.subtype ~= "light") then
		return nil
	end
	return armor
end

dg's avatar
dg committed
--- Check if the actor has a heavy armor
function _M:hasHeavyArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or (armor.subtype ~= "heavy" and armor.subtype ~= "massive") then
dg's avatar
dg committed
		return nil
	end
dg's avatar
dg committed
	return armor
dg's avatar
dg committed
end

--- Check if the actor has a massive armor
function _M:hasMassiveArmor()
	if not self:getInven("BODY") then return end
	local armor = self:getInven("BODY")[1]
	if not armor or armor.subtype ~= "massive" then
		return nil
	end
dg's avatar
dg committed
	return armor
dg's avatar
dg committed
end
dg's avatar
dg committed

dg's avatar
dg committed
-- Unarmed Combat; this handles grapple checks and building combo points
-- Builds Comob; reduces the cooldown on all unarmed abilities on cooldown by one
function _M:buildCombo()
	local duration = 3
	local power = 1
	-- Combo String bonuses
	if self:knowTalent(self.T_COMBO_STRING) then
		local t = self:getTalentFromId(self.T_COMBO_STRING)
dg's avatar
dg committed
		if rng.percent(t.getChance(self, t)) then
			power = 2
		end
dg's avatar
dg committed
		duration = 3 + t.getDuration(self, t)
	end
dg's avatar
dg committed

dg's avatar
dg committed
	if self:knowTalent(self.T_RELENTLESS_STRIKES) then
		local t = self:getTalentFromId(self.T_RELENTLESS_STRIKES)
dg's avatar
dg committed
		self:incStamina(t.getStamina(self, t))
dg's avatar
dg committed
	end
dg's avatar
dg committed
	self:setEffect(self.EFF_COMBO, duration, {power=power})
end

function _M:getCombo(combo)
	local combo = 0
	local p = self:hasEffect(self.EFF_COMBO)
dg's avatar
dg committed
		combo = p.cur_power
	end
		return combo
end

function _M:clearCombo()
	if self:hasEffect(self.EFF_COMBO) then
		self:removeEffect(self.EFF_COMBO)
	end
end

-- Check to see if the target is already being grappled; many talents have extra effects on grappled targets
function _M:isGrappled(source)
	local p = self:hasEffect(self.EFF_GRAPPLED)
	if p and p.src == source then
dg's avatar
dg committed
		return true
	else
		return false
	end
end

-- Breaks active grapples; called by a few talents that involve a lot of movement
function _M:breakGrapples()
	if self:hasEffect(self.EFF_GRAPPLING) then
		local p = self:hasEffect(self.EFF_GRAPPLING)
		if p.trgt then
			p.trgt:removeEffect(p.trgt.EFF_GRAPPLED)
		end
dg's avatar
dg committed
		self:removeEffect(self.EFF_GRAPPLING)
	end
end

-- grapple size check; compares attackers size and targets size
function _M:grappleSizeCheck(target)
	size = target.size_category - self.size_category
	if size > 1 then
		game.logSeen(target, "%s fails because %s is too big!", self.name:capitalize(), target.name:capitalize())
		return true
	else
		return false
	end
end

-- Starts the grapple
function _M:startGrapple(target)
	-- pulls boosted grapple effect from the clinch talent if known
	if self:knowTalent(self.T_CLINCH) then
		local t = self:getTalentFromId(self.T_CLINCH)
		power = t.getPower(self, t)
		duration = t.getDuration(self, t)
		hitbonus = self:getTalentLevel(t)/2
	else
		power = 5
		duration = 4
		hitbonus = 0
	end
	-- Breaks the grapple before reapplying
	if self:hasEffect(self.EFF_GRAPPLING) then
		self:removeEffect(self.EFF_GRAPPLING, true)
		target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power}, true)
		self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target}, true)
dg's avatar
dg committed
		return true
dg's avatar
dg committed
		target:setEffect(target.EFF_GRAPPLED, duration, {src=self, power=power, apply_power=self:combatPhysicalpower()})
		target:crossTierEffect(target.EFF_GRAPPLED, self:combatPhysicalpower())
		self:setEffect(self.EFF_GRAPPLING, duration, {trgt=target})
dg's avatar
dg committed
		return true
	else
		game.logSeen(target, "%s resists the grapple!", target.name:capitalize())
		return false
	end