Skip to content
Snippets Groups Projects
ActorProject.lua 9.37 KiB
Newer Older
-- TE4 - T-Engine 4
-- Copyright (C) 2009, 2010 Nicolas Casalini
--
-- 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

require "engine.class"
local Map = require "engine.Map"
local Target = require "engine.Target"
local DamageType = require "engine.DamageType"

--- Handles actors projecting damage to zones/targets
module(..., package.seeall, class.make)

function _M:init(t)
end

--- Project damage to a distance
-- @param t a type table describing the attack, passed to engine.Target:getType() for interpretation
-- @param x target coords
-- @param y target coords
-- @param damtype a damage type ID from the DamageType class
-- @param dam damage to be done
-- @param particles particles effect configuration, or nil
function _M:project(t, x, y, damtype, dam, particles)
	-- Call the on project of the target grid if possible
	if not t.bypass and game.level.map:checkAllEntities(x, y, "on_project", self, t, x, y, damtype, dam, particles) then
		return
	end

	if type(particles) ~= "table" then particles = nil end

--	if type(dam) == "number" and dam < 0 then return end
	local typ = Target:getType(t)

	local grids = {}
	local function addGrid(x, y)
		if not grids[x] then grids[x] = {} end
		grids[x][y] = true
	end

	local srcx, srcy = t.x or self.x, t.y or self.y

	-- Stop at range or on block
	local lx, ly = x, y
	local l = line.new(srcx, srcy, x, y)
	lx, ly = l()
	local initial_dir = lx and coord_to_dir[lx - srcx][ly - srcy] or 5
	while lx and ly do
		if not typ.no_restrict then
			if typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then break
			elseif game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move") then break end
			if typ.range and math.sqrt((srcx-lx)^2 + (srcy-ly)^2) > typ.range then break end
		end

		-- Deam damage: beam
		if typ.line then addGrid(lx, ly) end

		lx, ly = l()
	end
	-- Ok if we are at the end reset lx and ly for the next code
	if not lx and not ly then lx, ly = x, y end

	if typ.ball then
		core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
			-- Deal damage: ball
			addGrid(px, py)
			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
		end, function()end, nil)
		addGrid(lx, ly)
	elseif typ.cone then
		core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
			-- Deal damage: cone
			addGrid(px, py)
			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
		end, function()end, nil)
		addGrid(lx, ly)
	else
		-- Deam damage: single
		addGrid(lx, ly)
	end

	-- Now project on each grid, one type
	local tmp = {}
	if type(damtype) == "function" then
		local stop = false
		for px, ys in pairs(grids) do
			for py, _ in pairs(ys) do
				if particles then
					game.level.map:particleEmitter(px, py, 1, particles.type)
				end
				if damtype(px, py, tg, self) then stop=true break end
			end
			if stop then break end
		end
	else
		for px, ys in pairs(grids) do
			for py, _ in pairs(ys) do
				-- Call the projected method of the target grid if possible
				if not game.level.map:checkAllEntities(x, y, "projected", self, t, x, y, damtype, dam, particles) then
					-- Friendly fire ?
					if px == self.x and py == self.y then
						if t.friendlyfire then
							DamageType:get(damtype).projector(self, px, py, damtype, dam, tmp)
							if particles then
								game.level.map:particleEmitter(px, py, 1, particles.type)
							end
						end
					else
						DamageType:get(damtype).projector(self, px, py, damtype, dam, tmp)
						if particles then
							game.level.map:particleEmitter(px, py, 1, particles.type)
						end
					end
				end
			end
		end
	end
	return grids
end

--- Can we project to this grid ?
-- @param t a type table describing the attack, passed to engine.Target:getType() for interpretation
-- @param x target coords
-- @param y target coords
function _M:canProject(t, x, y)
	local typ = Target:getType(t)

	-- Stop at range or on block
	local lx, ly = x, y
	local l = line.new(self.x, self.y, x, y)
	lx, ly = l()
	while lx and ly do
		if not typ.no_restrict then
			if typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then break
			elseif game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move") then break end
			if typ.range and math.sqrt((self.x-lx)^2 + (self.y-ly)^2) > typ.range then break end
		end

		lx, ly = l()
	end
	-- Ok if we are at the end reset lx and ly for the next code
	if not lx and not ly then lx, ly = x, y end

	if lx == x and ly == y then return true, lx, ly end
	return false, lx, ly
end

_M.projectile_class = "engine.Projectile"

--- Project damage to a distance using a moving projectile
-- @param t a type table describing the attack, passed to engine.Target:getType() for interpretation
-- @param x target coords
-- @param y target coords
-- @param damtype a damage type ID from the DamageType class
-- @param dam damage to be done
-- @param particles particles effect configuration, or nil
function _M:projectile(t, x, y, damtype, dam, particles)
	-- Call the on project of the target grid if possible
--	if not t.bypass and game.level.map:checkAllEntities(x, y, "on_project", self, t, x, y, damtype, dam, particles) then
--		return
--	end

	if type(particles) ~= "function" and type(particles) ~= "table" then particles = nil end

--	if type(dam) == "number" and dam < 0 then return end
	local typ = Target:getType(t)

	local proj = require(self.projectile_class):makeProject(self, t.display, {x=x, y=y, start_x = t.x or self.x, start_y = t.y or self.y, damtype=damtype, tg=t, typ=typ, dam=dam, particles=particles})
	game.zone:addEntity(game.level, proj, "projectile", self.x, self.y)
end

function _M:projectDoMove(typ, tgtx, tgty, x, y, srcx, srcy)
	-- Stop at range or on block
	local l = line.new(srcx, srcy, tgtx, tgty)
	local lx, ly = srcx, srcy
	-- Look for our current position
	while lx and ly and not (lx == x and ly == y) do lx, ly = l() end
	-- Now get the next position
	if lx and ly then lx, ly = l() end

	if lx and ly then
		if not typ.no_restrict then
			if typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then return lx, ly, false, true
			elseif game.level.map:checkEntity(lx, ly, Map.TERRAIN, "block_move") then return lx, ly, false, true end
			if typ.range and math.sqrt((srcx-lx)^2 + (srcy-ly)^2) > typ.range then return lx, ly, false, true end
		end

		-- End of the map
		if lx < 0 or lx >= game.level.map.w or ly < 0 or ly >= game.level.map.h then return lx, ly, false, true end

		-- Deam damage: beam
		if typ.line then return lx, ly, true, false end
	end
	-- Ok if we are at the end
	if (not lx and not ly) then return lx, ly, false, true end
	return lx, ly, false, false
end


function _M:projectDoAct(typ, tg, damtype, dam, particles, px, py, tmp)
	-- Now project on each grid, one type
	if type(damtype) == "function" then
		if particles and type(particles) == "table" then
			game.level.map:particleEmitter(px, py, 1, particles.type)
		end
		if damtype(px, py, tg, self) then return true end
		return false
	else
		-- Call the projected method of the target grid if possible
		if not game.level.map:checkAllEntities(px, py, "projected", self, typ, px, py, damtype, dam, particles) then
			-- Friendly fire ?
			if px == self.x and py == self.y then
				if tg.friendlyfire then
					DamageType:get(damtype).projector(self, px, py, damtype, dam, tmp)
					if particles and type(particles) == "table" then
						game.level.map:particleEmitter(px, py, 1, particles.type)
					end
				end
			else
				DamageType:get(damtype).projector(self, px, py, damtype, dam, tmp)
				if particles and type(particles) == "table" then
					game.level.map:particleEmitter(px, py, 1, particles.type)
				end
			end
		end
	end
end

function _M:projectDoStop(typ, tg, damtype, dam, particles, lx, ly, tmp)
	local grids = {}
	local function addGrid(x, y)
		if not grids[x] then grids[x] = {} end
		grids[x][y] = true
	end

	if typ.ball then
		core.fov.calc_circle(lx, ly, typ.ball, function(_, px, py)
			-- Deal damage: ball
			addGrid(px, py)
			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
		end, function()end, nil)
		addGrid(lx, ly)
	elseif typ.cone then
		local initial_dir = lx and util.getDir(lx, ly, x, y) or 5
		core.fov.calc_beam(lx, ly, typ.cone, initial_dir, typ.cone_angle, function(_, px, py)
			-- Deal damage: cone
			addGrid(px, py)
			if not typ.no_restrict and game.level.map:checkEntity(px, py, Map.TERRAIN, "block_move") then return true end
		end, function()end, nil)
		addGrid(lx, ly)
	else
		-- Deam damage: single
		addGrid(lx, ly)
	end

	for px, ys in pairs(grids) do
		for py, _ in pairs(ys) do
			if self:projectDoAct(typ, tg, damtype, dam, particles, px, py, tmp) then break end
		end
	end
	if particles and type(particles) == "function" then
		particles(self, tg, lx, ly, grids)
	end
end