Skip to content
Snippets Groups Projects
Combat.lua 58.4 KiB
Newer Older
dg's avatar
dg committed
-- ToME - Tales of Maj'Eyal
dg's avatar
dg committed
-- Copyright (C) 2009, 2010, 2011, 2012 Nicolas Casalini
dg's avatar
dg committed
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org

dg's avatar
dg committed
require "engine.class"
dg's avatar
dg committed
local DamageType = require "engine.DamageType"
local Map = require "engine.Map"
local Chat = require "engine.Chat"
dg's avatar
dg committed
local Target = require "engine.Target"
dg's avatar
dg committed
local Talents = require "engine.interface.ActorTalents"
dg's avatar
dg committed

--- Interface to add ToME combat system
module(..., package.seeall, class.make)

--- Checks what to do with the target
-- Talk ? attack ? displace ?
function _M:bumpInto(target, x, y)
	local reaction = self:reactionToward(target)
	if reaction < 0 then
		if target.encounterAttack and self.player then self:onWorldEncounter(target, x, y) return end
		if game.player == self and ((not config.settings.tome.actor_based_movement_mode and game.bump_attack_disabled) or (config.settings.tome.actor_based_movement_mode and self.bump_attack_disabled)) then return end
		return self:useTalent(self.T_ATTACK, nil, nil, nil, target)
	elseif reaction >= 0 then
		-- Talk ?
		if self.player and target.can_talk then
dg's avatar
dg committed
			local chat = Chat.new(target.can_talk, target, self, {npc=target, player=self})
			chat:invoke()
			if target.can_talk_only_once then target.can_talk = nil end
		elseif target.player and self.can_talk then
dg's avatar
dg committed
			local chat = Chat.new(self.can_talk, self, target, {npc=self, player=target})
			chat:invoke()
			if target.can_talk_only_once then target.can_talk = nil end
		elseif self.move_others and not target.cant_be_moved then
			if target.move_others and self ~= game.player then return end

			-- Check we can both walk in the tile we will end up in
			local blocks = game.level.map:checkAllEntitiesLayersNoStop(target.x, target.y, "block_move", self)
			for kind, v in pairs(blocks) do if kind[1] ~= Map.ACTOR and v then return end end
			blocks = game.level.map:checkAllEntitiesLayersNoStop(self.x, self.y, "block_move", target)
			for kind, v in pairs(blocks) do if kind[1] ~= Map.ACTOR and v then return end end

			-- Displace
dg's avatar
dg committed
			local tx, ty, sx, sy = target.x, target.y, self.x, self.y
			target.x = nil target.y = nil
			self.x = nil self.y = nil
			target:move(sx, sy, true)
			self:move(tx, ty, true)
		end
	end
end

--- Makes the death happen!
dg's avatar
dg committed
--[[
The ToME combat system has the following attributes:
dg's avatar
dg committed
- attack: increases chances to hit against high defense
- defense: increases chances to miss against high attack power
dg's avatar
dg committed
- armor: direct reduction of damage done
- armor penetration: reduction of target's armor
- damage: raw damage done
]]
function _M:attackTarget(target, damtype, mult, noenergy)
dg's avatar
dg committed
	local speed, hit = nil, false
dg's avatar
dg committed
	local sound, sound_miss = nil, nil
dg's avatar
dg committed

	-- Break before we do the blow, because it might start step up, we dont want to insta-cancel it
	self:breakStepUp()

dg's avatar
dg committed
	if self:attr("feared") then
		if not noenergy then
			self:useEnergy(game.energy_to_act * speed)
			self.did_energy = true
		end
		game.logSeen(self, "%s is too afraid to attack.", self.name:capitalize())
		return false
	end
	if self:attr("terrified") and rng.percent(self:attr("terrified")) then
		if not noenergy then
			self:useEnergy(game.energy_to_act)
			self.did_energy = true
		end
		game.logSeen(self, "%s is too terrified to attack.", self.name:capitalize())
		return false
	end
dg's avatar
dg committed

dg's avatar
dg committed
	-- Cancel stealth early if we are noticed
	if self:isTalentActive(self.T_STEALTH) and target:canSee(self) then
		self:useTalent(self.T_STEALTH)
		self.changed = true
		game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize())
	end

dg's avatar
dg committed
	-- Change attack type if using gems
	if not damtype and self:getInven(self.INVEN_GEM) then
		local gems = self:getInven(self.INVEN_GEM)
		local types = {}
		for i = 1, #gems do
			if gems[i] and gems[i].attack_type then types[#types+1] = gems[i].attack_type end
		end
		if #types > 0 then
			damtype = rng.table(types)
		end
dg's avatar
dg committed
	elseif not damtype and self:attr("force_melee_damage_type") then
		damtype = self:attr("force_melee_damage_type")
dg's avatar
dg committed
	end

dg's avatar
dg committed
	local break_stealth = false
dg's avatar
dg committed

	local hd = {"Combat:attackTarget", target=target, damtype=damtype, mult=mult, noenergy=noenergy}
	if self:triggerHook(hd) then
dg's avatar
dg committed
		speed, hit, damtype, mult = ht.speed, hd.hit, hd.damtype, hd.mult
dg's avatar
dg committed
		if ht.stop then return hit end
	end

	if not speed and self:isTalentActive(self.T_GESTURE_OF_PAIN) then
		print("[ATTACK] attacking with Gesture of Pain")
		local t = self:getTalentFromId(self.T_GESTURE_OF_PAIN)
dg's avatar
dg committed
		if not t.preAttack(self, t, target) then return false end
		speed, hit = t.attack(self, t, target)
		break_stealth = true
	end

	if not speed and not self:attr("disarmed") and not self:isUnarmed() then
dg's avatar
dg committed
		-- All weapons in main hands
		if self:getInven(self.INVEN_MAINHAND) then
			for i, o in ipairs(self:getInven(self.INVEN_MAINHAND)) do
				if o.combat and not o.archery then
dg's avatar
dg committed
					print("[ATTACK] attacking with", o.name)
					local s, h = self:attackTargetWith(target, o.combat, damtype, mult)
					speed = math.max(speed or 0, s)
					hit = hit or h
					if hit and not sound then sound = o.combat.sound
					elseif not hit and not sound_miss then sound_miss = o.combat.sound_miss end
dg's avatar
dg committed
					if not o.combat.no_stealth_break then break_stealth = true end
dg's avatar
dg committed
				end
dg's avatar
dg committed
			end
		end
dg's avatar
dg committed
		-- All weapons in off hands
		-- Offhand attacks are with a damage penalty, that can be reduced by talents
dg's avatar
dg committed
		if self:getInven(self.INVEN_OFFHAND) then
dg's avatar
dg committed
			local offmult = self:getOffHandMult(mult)
dg's avatar
dg committed
			for i, o in ipairs(self:getInven(self.INVEN_OFFHAND)) do
dg's avatar
dg committed
				local combat = o.combat
				if o.special_combat and o.subtype == "shield" and self:knowTalent(self.T_STONESHIELD) then combat = o.special_combat end
				if combat and not o.archery then
dg's avatar
dg committed
					print("[ATTACK] attacking with", o.name)
dg's avatar
dg committed
					local s, h = self:attackTargetWith(target, combat, damtype, offmult)
dg's avatar
dg committed
					speed = math.max(speed or 0, s)
					hit = hit or h
dg's avatar
dg committed
					if hit and not sound then sound = combat.sound
					elseif not hit and not sound_miss then sound_miss = combat.sound_miss end
					if not combat.no_stealth_break then break_stealth = true end
dg's avatar
dg committed
				end
dg's avatar
dg committed
			end
		end
	end

	-- Barehanded ?
dg's avatar
dg committed
	if not speed and self.combat then
dg's avatar
dg committed
		print("[ATTACK] attacking with innate combat")
dg's avatar
dg committed
		local s, h = self:attackTargetWith(target, self.combat, damtype, mult)
		speed = math.max(speed or 0, s)
		hit = hit or h
dg's avatar
dg committed
		if hit and not sound then sound = self.combat.sound
		elseif not hit and not sound_miss then sound_miss = self.combat.sound_miss end
dg's avatar
dg committed
		if not self.combat.no_stealth_break then break_stealth = true end
dg's avatar
dg committed
	end

	-- We use up our own energy
	if speed and not noenergy then
dg's avatar
dg committed
		self:useEnergy(game.energy_to_act * speed)
		self.did_energy = true
	end
dg's avatar
dg committed

dg's avatar
dg committed
	if sound then game:playSoundNear(self, sound)
	elseif sound_miss then game:playSoundNear(self, sound_miss) end
dg's avatar
dg committed

	game:playSoundNear(self, self.on_hit_sound or "actions/melee_hit_squish")
	if self.sound_moam and rng.chance(7) then game:playSoundNear(self, self.sound_moam) end

dg's avatar
dg committed
	-- cleave second attack
	if self:isTalentActive(self.T_CLEAVE) then
dg's avatar
dg committed
		local t = self:getTalentFromId(self.T_CLEAVE)
		t.on_attackTarget(self, t, target)
dg's avatar
dg committed
	end
Loading
Loading full blame...