From ddaf6b900053cf34be36a03b3d8688d3a85a508b Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sat, 28 Nov 2009 00:14:17 +0000
Subject: [PATCH] better targetting manathrust works excusive targetting mode
 targetting with display of future damage projection damage projection

git-svn-id: http://svn.net-core.org/repos/t-engine4@55 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engine/Actor.lua                         |   1 +
 game/engine/GameEnergyBased.lua               |   1 -
 game/engine/KeyCommand.lua                    |   3 +
 game/engine/Level.lua                         |   5 +
 game/engine/Map.lua                           |   7 +
 game/engine/Module.lua                        |   1 +
 game/engine/Target.lua                        |  77 ++++++--
 game/engine/init.lua                          |   5 -
 game/engine/interface/ActorAbilities.lua      |  25 ++-
 game/modules/tome/class/Actor.lua             |  26 ++-
 game/modules/tome/class/Game.lua              | 172 ++++++++++--------
 game/modules/tome/class/Player.lua            |  19 ++
 game/modules/tome/class/interface/Combat.lua  |  32 +++-
 game/modules/tome/data/abilities.lua          |  13 +-
 game/modules/tome/data/damage_types.lua       |   2 +-
 .../tome/data/zones/ancient_ruins/zone.lua    |   6 +-
 16 files changed, 282 insertions(+), 113 deletions(-)

diff --git a/game/engine/Actor.lua b/game/engine/Actor.lua
index 980bd7324c..b11b2dc40b 100644
--- a/game/engine/Actor.lua
+++ b/game/engine/Actor.lua
@@ -47,6 +47,7 @@ end
 function _M:useEnergy(val)
 	val = val or game.energy_to_act
 	self.energy.value = self.energy.value - val
+	if self.player and self.energy.value < game.energy_to_act then game.paused = false end
 end
 
 --- What is our reaction toward the target
diff --git a/game/engine/GameEnergyBased.lua b/game/engine/GameEnergyBased.lua
index 437475fda6..b750ef2292 100644
--- a/game/engine/GameEnergyBased.lua
+++ b/game/engine/GameEnergyBased.lua
@@ -24,7 +24,6 @@ function _M:tick()
 		for uid, e in pairs(self.level.entities) do
 			if e.energy and e.energy.value < self.energy_to_act then
 				e.energy.value = (e.energy.value or 0) + self.energy_per_tick * (e.energy.mod or 1)
---				print(e.uid, e.energy.value)
 				if e.energy.value >= self.energy_to_act and e.act then
 					e:act(self)
 				end
diff --git a/game/engine/KeyCommand.lua b/game/engine/KeyCommand.lua
index 54a67c41dd..b158990005 100644
--- a/game/engine/KeyCommand.lua
+++ b/game/engine/KeyCommand.lua
@@ -8,6 +8,9 @@ function _M:init()
 	engine.Key.init(self)
 	self.commands = {}
 	self.on_input = false
+
+	-- Fullscreen toggle
+	self:addCommand(self._RETURN, {"alt"}, function() core.display.fullscreen() end)
 end
 
 function _M:receiveKey(sym, ctrl, shift, alt, meta, unicode)
diff --git a/game/engine/Level.lua b/game/engine/Level.lua
index aaa8de905c..a2f6ae0d46 100644
--- a/game/engine/Level.lua
+++ b/game/engine/Level.lua
@@ -25,6 +25,11 @@ function _M:removeEntity(e)
 	if e.deleteFromMap then e:deleteFromMap(self.map) end
 end
 
+--- Is the entity on the level?
+function _M:hasEntity(e)
+	return self.entities[e.uid]
+end
+
 function _M:loaded()
 	-- Loading the game has defined new uids for entities, yet we hard referenced the old ones
 	-- So we fix it
diff --git a/game/engine/Map.lua b/game/engine/Map.lua
index 9ebbe2b5f8..49ede6760c 100644
--- a/game/engine/Map.lua
+++ b/game/engine/Map.lua
@@ -310,3 +310,10 @@ function _M:checkMapViewBounded()
 	if self.my > self.h - self.viewport.mheight then self.my = self.h - self.viewport.mheight self.changed = true end
 end
 
+--- Gets the tile under the mouse
+function _M:getMouseTile(mx, my)
+--	if mx < self.display_x or my < self.display_y or mx >= self.display_x + self.viewport.width or my >= self.display_y + self.viewport.height then return end
+	local tmx = math.floor((mx - self.display_x) / self.tile_w) + self.mx
+	local tmy = math.floor((my - self.display_y) / self.tile_h) + self.my
+	return tmx, tmy
+end
diff --git a/game/engine/Module.lua b/game/engine/Module.lua
index 1baa31687b..98dfe7b4c7 100644
--- a/game/engine/Module.lua
+++ b/game/engine/Module.lua
@@ -38,6 +38,7 @@ function _M:loadDefinition(dir)
 
 		-- Make a function to activate it
 		mod.load = function()
+			core.display.setWindowTitle(mod.long_name)
 			self:setupWrite(mod)
 			fs.mount(fs.getRealPath(dir), "/mod", false);
 			fs.mount(fs.getRealPath(dir).."/data/", "/data", false);
diff --git a/game/engine/Target.lua b/game/engine/Target.lua
index 63854dcb95..5c8f843f73 100644
--- a/game/engine/Target.lua
+++ b/game/engine/Target.lua
@@ -8,15 +8,19 @@ function _M:init(map, source_actor)
 	self.w, self.h = map.viewport.width, map.viewport.height
 	self.tile_w, self.tile_h = map.tile_w, map.tile_h
 	self.active = false
+	self.target_type = {}
 
 	self.cursor = core.display.loadImage(engine.Tiles.prefix.."target_cursor.png")
 
 	self.sr = core.display.newSurface(map.tile_w, map.tile_h)
-	self.sr:alpha(125)
+	self.sr:alpha(90)
 	self.sr:erase(255, 0, 0)
 	self.sb = core.display.newSurface(map.tile_w, map.tile_h)
-	self.sb:alpha(125)
+	self.sb:alpha(90)
 	self.sb:erase(0, 0, 255)
+	self.sg = core.display.newSurface(map.tile_w, map.tile_h)
+	self.sg:alpha(90)
+	self.sg:erase(0, 255, 0)
 
 	self.source_actor = source_actor
 
@@ -25,7 +29,7 @@ function _M:init(map, source_actor)
 	-- but it means if the entity field is set to an entity, when it disappears this link wont prevent
 	-- the garbage collection
 	self.target = {x=self.source_actor.x, y=self.source_actor.y, entity=nil}
-	setmetatable(self.target, {__mode='v'})
+--	setmetatable(self.target, {__mode='v'})
 end
 
 function _M:display()
@@ -42,43 +46,88 @@ function _M:display()
 	local lx, ly = l()
 	while lx and ly do
 		if not game.level.map.seens(lx, ly) then s = self.sr end
+		if self.target_type.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then s = self.sr end
+		if self.target_type.range and math.sqrt((self.source_actor.x-lx)^2 + (self.source_actor.y-ly)^2) > self.target_type.range then s = self.sr end
 		s:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w, self.display_y + (ly - game.level.map.my) * self.tile_h)
 		lx, ly = l()
 	end
 	self.cursor:toScreen(self.display_x + (self.target.x - game.level.map.mx) * self.tile_w, self.display_y + (self.target.y - game.level.map.my) * self.tile_h)
+
+	if self.target_type.ball and s == self.sb then
+		core.fov.calc_circle(self.target.x, self.target.y, self.target_type.ball, function(self, lx, ly)
+			self.sg:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w, self.display_y + (ly - game.level.map.my) * self.tile_h)
+		end, function()end, self)
+	end
 end
 
-function _M:setActive(v)
+--- Returns data for the given target type
+-- Hit: simple project in LOS<br/>
+-- Beam: hits everything in LOS<br/>
+-- Bolt: hits first thing in path<br/>
+-- Ball: hits everything in a ball aounrd the target<br/>
+-- Cone: hits everything in a cone in the direction<br/>
+function _M:getType(t)
+	if not t or not t.type then return {} end
+	t.range = t.range or 20
+	if t.type == "hit" then
+		return {range=t.range}
+	elseif t.type == "beam" then
+		return {range=t.range, line=true}
+	elseif t.type == "bolt" then
+		return {range=t.range, stop_block=true}
+	elseif t.type == "ball" then
+		return {range=t.range, ball=t.radius}
+	elseif t.type == "cone" then
+		return {range=t.range, cone=t.radius}
+	else
+		return {}
+	end
+end
+
+function _M:setActive(v, type)
 	if v == nil then
 		return self.active
 	else
 		self.active = v
+		if v and type then
+			self.target_type = self:getType(type)
+		else
+			self.target_type = {}
+		end
 	end
 end
 
-function _M:scan(dir, radius)
+function _M:scan(dir, radius, sx, sy)
+	sx = sx or self.target.x
+	sy = sy or self.target.y
 	radius = radius or 20
 	local actors = {}
 	local checker = function(self, x, y)
-			if game.level.map.seens(x, y) and game.level.map(x, y, engine.Map.ACTOR) then
-				table.insert(actors, {
-					a = game.level.map(x, y, engine.Map.ACTOR),
-					dist = math.abs(self.target.x - x)*math.abs(self.target.x - x) + math.abs(self.target.y - y)*math.abs(self.target.y - y)
-				})
-			end
-			return false
+		if sx == x and sy == y then return false end
+		if game.level.map.seens(x, y) and game.level.map(x, y, engine.Map.ACTOR) then
+			local a = game.level.map(x, y, engine.Map.ACTOR)
+
+			table.insert(actors, {
+				a = a,
+				dist = math.abs(sx - x)*math.abs(sx - x) + math.abs(sy - y)*math.abs(sy - y)
+			})
+			actors[a] = true
+		end
+		return false
 	end
 
 	if dir ~= 5 then
 		-- Get a list of actors in the direction given
-		core.fov.calc_beam(self.target.x, self.target.y, radius, dir, 55, checker, function()end, self)
+		core.fov.calc_beam(sx, sy, radius, dir, 55, checker, function()end, self)
 	else
 		-- Get a list of actors all around
-		core.fov.calc_circle(self.target.x, self.target.y, radius, checker, function()end, self)
+		core.fov.calc_circle(sx, sy, radius, checker, function()end, self)
 	end
 
 	table.sort(actors, function(a,b) return a.dist<b.dist end)
 	if #actors > 0 then
 		self.target.entity = actors[1].a
+		self.target.x = self.target.entity.x
+		self.target.y = self.target.entity.y
 	end
 end
diff --git a/game/engine/init.lua b/game/engine/init.lua
index 3f129c9f92..8705b87e24 100644
--- a/game/engine/init.lua
+++ b/game/engine/init.lua
@@ -15,11 +15,6 @@ engine.Tiles.prefix = "/data/gfx/"
 local key = engine.KeyCommand.new()
 key:setCurrent()
 
--- Exit the game, this is brutal for now
-key:addCommand(key._x, {"ctrl"}, function() os.exit() end)
--- Fullscreen toggle
-key:addCommand(key._RETURN, {"alt"}, function() core.display.fullscreen() end)
-
 -- Load the game module
 game = false
 
diff --git a/game/engine/interface/ActorAbilities.lua b/game/engine/interface/ActorAbilities.lua
index 18cf5b871b..c05a875026 100644
--- a/game/engine/interface/ActorAbilities.lua
+++ b/game/engine/interface/ActorAbilities.lua
@@ -55,6 +55,29 @@ function _M:useAbility(id)
 	assert(ab, "trying to cast ability "..tostring(id).." but it is not defined")
 
 	if ab.action then
-		ab.action(self)
+		if not self:preUseAbility(ab) then return end
+		local co = coroutine.create(function()
+			local ret = ab.action(self)
+
+			if not self:postUseAbility(ab, ret) then return end
+		end)
+		coroutine.resume(co)
 	end
 end
+
+--- Called before an ability is used
+-- Redefine as needed
+-- @param ab the ability (not the id, the table)
+-- @return true to continue, false to stop
+function _M:preUseAbility(ab)
+	return true
+end
+
+--- Called before an ability is used
+-- Redefine as needed
+-- @param ab the ability (not the id, the table)
+-- @param ret the return of the ability action
+-- @return true to continue, false to stop
+function _M:postUseAbility(ab, ret)
+	return true
+end
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 254e56e896..6ac0f4ea28 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -30,7 +30,7 @@ function _M:move(x, y, force)
 	local moved = false
 	if force or self:enoughEnergy() then
 		moved = engine.Actor.move(self, game.level.map, x, y, force)
-		if not force then self:useEnergy() end
+		if not force and moved then self:useEnergy() end
 	end
 	return moved
 end
@@ -55,3 +55,27 @@ end
 function _M:attack(target)
 	self:bumpInto(target)
 end
+
+--- Tries to get a target from the user
+function _M:getTarget()
+	return self.target.x, self.target.y
+end
+
+--- Called before an ability is used
+-- Check the actor can cast it
+-- @param ab the ability (not the id, the table)
+-- @return true to continue, false to stop
+function _M:preUseAbility(ab)
+	return self:enoughEnergy()
+end
+
+--- Called before an ability is used
+-- Check if it must use a turn, mana, stamina, ...
+-- @param ab the ability (not the id, the table)
+-- @param ret the return of the ability action
+-- @return true to continue, false to stop
+function _M:postUseAbility(ab, ret)
+	if ret == nil then return end
+	self:useEnergy()
+	return true
+end
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index a9ce7476cf..28f9763439 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -100,6 +100,8 @@ end
 
 
 function _M:tick()
+	if self.target.target.entity and not self.level:hasEntity(self.target.target.entity) then self.target.target.entity = false end
+
 	engine.GameTurnBased.tick(self)
 
 	if not self.day_of_year or self.day_of_year ~= self.calendar:getDayOfYear(self.turn) then
@@ -124,7 +126,7 @@ function _M:display()
 
 		-- Display a tooltip if available
 		local mx, my = core.mouse.get()
-		local tmx, tmy = math.floor((mx - Map.display_x) / self.level.map.tile_w) + self.level.map.mx, math.floor((my - Map.display_y) / self.level.map.tile_h) + self.level.map.my
+		local tmx, tmy = self.level.map:getMouseTile(mx, my)
 		local tt = self.level.map:checkAllEntities(tmx, tmy, "tooltip")
 		if tt and self.level.map.seens(tmx, tmy) then
 			self.tooltip:set(tt)
@@ -144,29 +146,88 @@ function _M:display()
 	engine.GameTurnBased.display(self)
 end
 
-function _M:targetMode(v, msg)
+function _M:targetMode(v, msg, co, typ)
 	if not v then
 		Map:setViewerFaction(nil)
-		if msg then self.log("Tactical display disabled. Press 't' or right mouse click to enable.") end
+		if msg then self.log(type(msg) == "string" and msg or "Tactical display disabled. Press 't' or right mouse click to enable.") end
 		self.level.map.changed = true
 		self.target:setActive(false)
+
+		if tostring(self.target_mode) == "exclusive" then
+			self.key = self.normal_key
+			self.key:setCurrent()
+			if self.target_co then
+				local co = self.target_co
+				self.target_co = nil
+				local ok, err = coroutine.resume(co, self.target.target.x, self.target.target.y)
+				if not ok and err then error(err) end
+			end
+		end
 	else
 		Map:setViewerFaction("players")
-		if msg then self.log("Tactical display enabled. Press 't' to disable.") end
+		if msg then self.log(type(msg) == "string" and msg or "Tactical display enabled. Press 't' to disable.") end
 		self.level.map.changed = true
-		self.target:setActive(true)
-	end
-end
+		self.target:setActive(true, typ)
 
-function _M:getTarget()
-	if self.target.target.entity then
-		return self.target.target.entity.x, self.target.target.entity.y
-	else
-		return self.target.target.x, self.target.target.y
+		-- Exclusive mode means we disable the current key handler and use a specific one
+		-- that only allows targetting and resumes ability coroutine when done
+		if tostring(v) == "exclusive" then
+			self.target_co = co
+			self.key = self.targetmode_key
+			self.key:setCurrent()
+
+			if self.target.target.entity and self.level.map.seens(self.target.target.entity.x, self.target.target.entity.y) and self.player ~= self.target.target.entity then
+			else
+				self.target:scan(5, nil, self.player.x, self.player.y)
+			end
+		end
 	end
+	self.target_mode = v
 end
 
 function _M:setupCommands()
+	self.targetmode_key = engine.KeyCommand.new()
+	self.targetmode_key:addCommands
+	{
+		_t = function()
+			self:targetMode(false, false)
+		end,
+		_RETURN = {"alias", "_t"},
+		_ESCAPE = function()
+			self.target.target.entity = nil
+			self.target.target.x = nil
+			self.target.target.y = nil
+			self:targetMode(false, false)
+		end,
+		-- Targeting movement
+		[{"_LEFT","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x - 1 end,
+		[{"_RIGHT","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x + 1 end,
+		[{"_UP","shift"}] = function() self.target.target.entity=nil self.target.target.y = self.target.target.y - 1 end,
+		[{"_DOWN","shift"}] = function() self.target.target.entity=nil self.target.target.y = self.target.target.y + 1 end,
+		[{"_KP4","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x - 1 end,
+		[{"_KP6","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x + 1 end,
+		[{"_KP8","shift"}] = function() self.target.target.entity=nil self.target.target.y = self.target.target.y - 1 end,
+		[{"_KP2","shift"}] = function() self.target.target.entity=nil self.target.target.y = self.target.target.y + 1 end,
+		[{"_KP1","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x - 1 self.target.target.y = self.target.target.y + 1 end,
+		[{"_KP3","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x + 1 self.target.target.y = self.target.target.y + 1 end,
+		[{"_KP7","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x - 1 self.target.target.y = self.target.target.y - 1 end,
+		[{"_KP9","shift"}] = function() self.target.target.entity=nil self.target.target.x = self.target.target.x + 1 self.target.target.y = self.target.target.y - 1 end,
+
+		_LEFT = function() self.target:scan(4) end,
+		_RIGHT = function() self.target:scan(6) end,
+		_UP = function() self.target:scan(8) end,
+		_DOWN = function() self.target:scan(2) end,
+		_KP4 = function() self.target:scan(4) end,
+		_KP6 = function() self.target:scan(6) end,
+		_KP8 = function() self.target:scan(8) end,
+		_KP2 = function() self.target:scan(2) end,
+		_KP1 = function() self.target:scan(1) end,
+		_KP3 = function() self.target:scan(3) end,
+		_KP7 = function() self.target:scan(7) end,
+		_KP9 = function() self.target:scan(9) end,
+	}
+
+	self.normal_key = self.key
 	self.key:addCommands
 	{
 		-- ability test
@@ -174,77 +235,25 @@ function _M:setupCommands()
 			self.player:useAbility(ActorAbilities.AB_MANATHRUST)
 		end,
 
-		_LEFT = function()
-			if self.player:move(self.player.x - 1, self.player.y) then
-				self.paused = false
-			end
-		end,
-		_RIGHT = function()
-			if self.player:move(self.player.x + 1, self.player.y) then
-				self.paused = false
-			end
-		end,
-		_UP = function()
-			if self.player:move(self.player.x, self.player.y - 1) then
-				self.paused = false
-			end
-		end,
-		_DOWN = function()
-			if self.player:move(self.player.x, self.player.y + 1) then
-				self.paused = false
-			end
-		end,
-		_KP1 = function()
-			if self.player:move(self.player.x - 1, self.player.y + 1) then
-				self.paused = false
-			end
-		end,
-		_KP2 = function()
-			if self.player:move(self.player.x, self.player.y + 1) then
-				self.paused = false
-			end
-		end,
-		_KP3 = function()
-			if self.player:move(self.player.x + 1, self.player.y + 1) then
-				self.paused = false
-			end
-		end,
-		_KP4 = function()
-			if self.player:move(self.player.x - 1, self.player.y) then
-				self.paused = false
-			end
-		end,
-		_KP5 = function()
-			if self.player:move(self.player.x, self.player.y) then
-				self.paused = false
-			end
-		end,
-		_KP6 = function()
-			if self.player:move(self.player.x + 1, self.player.y) then
-				self.paused = false
-			end
-		end,
-		_KP7 = function()
-			if self.player:move(self.player.x - 1, self.player.y - 1) then
-				self.paused = false
-			end
-		end,
-		_KP8 = function()
-			if self.player:move(self.player.x, self.player.y - 1) then
-				self.paused = false
-			end
-		end,
-		_KP9 = function()
-			if self.player:move(self.player.x + 1, self.player.y - 1) then
-				self.paused = false
-			end
-		end,
+		_LEFT  = function() self.player:move(self.player.x - 1, self.player.y    ) end,
+		_RIGHT = function() self.player:move(self.player.x + 1, self.player.y    ) end,
+		_UP    = function() self.player:move(self.player.x    , self.player.y - 1) end,
+		_DOWN  = function() self.player:move(self.player.x    , self.player.y + 1) end,
+		_KP1   = function() self.player:move(self.player.x - 1, self.player.y + 1) end,
+		_KP2   = function() self.player:move(self.player.x    , self.player.y + 1) end,
+		_KP3   = function() self.player:move(self.player.x + 1, self.player.y + 1) end,
+		_KP4   = function() self.player:move(self.player.x - 1, self.player.y    ) end,
+		_KP5   = function() self.player:move(self.player.x    , self.player.y    ) end,
+		_KP6   = function() self.player:move(self.player.x + 1, self.player.y    ) end,
+		_KP7   = function() self.player:move(self.player.x - 1, self.player.y - 1) end,
+		_KP8   = function() self.player:move(self.player.x    , self.player.y - 1) end,
+		_KP9   = function() self.player:move(self.player.x + 1, self.player.y - 1) end,
+
 		[{"_LESS","anymod"}] = function()
 			local e = self.level.map(self.player.x, self.player.y, Map.TERRAIN)
 			if self.player:enoughEnergy() and e.change_level then
 				-- Do not unpause, the player is allowed first move on next level
 				self:changeLevel(self.level.level + e.change_level)
-				print(self.level.level)
 			else
 				self.log("There is no way out of this level here.")
 			end
@@ -319,17 +328,20 @@ function _M:setupMouse()
 	self.mouse:registerZone(Map.display_x, Map.display_y, Map.viewport.width, Map.viewport.height, function(button, mx, my, xrel, yrel)
 		-- Compute map coordonates
 		if button == "right" then
-			local tmx, tmy = math.floor((mx - Map.display_x) / self.level.map.tile_w) + self.level.map.mx, math.floor((my - Map.display_x) / self.level.map.tile_h) + self.level.map.my
+			local tmx, tmy = self.level.map:getMouseTile(mx, my)
 
 			local actor = self.level.map(tmx, tmy, Map.ACTOR)
 
 			if actor and self.level.map.seens(tmx, tmy) then
 				self.target.target.entity = actor
-				self:targetMode(true, true)
 			else
 				self.target.target.entity = nil
 				self.target.target.x = tmx
 				self.target.target.y = tmy
+			end
+			if tostring(self.target_mode) == "exclusive" then
+				self:targetMode(false, false)
+			else
 				self:targetMode(true, true)
 			end
 		elseif button == "left" and xrel and yrel then
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index ec9a6372c6..44f6c0306a 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -31,3 +31,22 @@ function _M:setName(name)
 	self.name = name
 	game.save_name = name
 end
+
+--- Tries to get a target from the user
+-- *WARNING* If used inside a coroutine it will yield and resume it later when a target is found.
+-- This is usualy just what you want so dont think too much about it :)
+function _M:getTarget(typ)
+	if coroutine.running() then
+		local msg
+		if type(typ) == "string" then msg, typ = typ, nil
+		elseif type(typ) == "table" then msg = typ.msg end
+		game:targetMode("exclusive", msg, coroutine.running(), typ)
+		return coroutine.yield()
+	end
+	return game.target.target.x, game.target.target.y
+end
+
+--- Quick way to check if the player can see the target
+function _M:canSee(entity)
+	if entity.x and entity.y and game.level.map.seens(entity.x, entity.y) then return true end
+end
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index 1b75a34d76..d111d1f47f 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -1,6 +1,7 @@
 require "engine.class"
 local DamageType = require "engine.DamageType"
 local Map = require "engine.Map"
+local Target = require "engine.Target"
 
 --- Interface to add ToME combat system
 module(..., package.seeall, class.make)
@@ -58,8 +59,35 @@ function _M:attackTarget(target)
 end
 
 --- Project damage to a distance
-function _M:project(x, y, type, dam)
+function _M:project(t, x, y, damtype, dam)
 	if dam < 0 then return end
+	local typ = Target:getType(t)
 
-	DamageType:get(type).projector(self, x, y, type, dam)
+	local lx, ly = x, y
+	if typ.stop_block then
+		local l = line.new(self.x, self.y, x, y)
+		lx, ly = l()
+		while lx and ly do
+			if typ.stop_block and game.level.map:checkAllEntities(lx, ly, "block_move") then break end
+			if typ.range and math.sqrt((self.source_actor.x-lx)^2 + (self.source_actor.y-ly)^2) > typ.range then break end
+
+			-- Deam damage: beam
+			if typ.line then DamageType:get(damtype).projector(self, lx, ly, damtype, dam) end
+
+			lx, ly = l()
+		end
+	end
+
+	if typ.ball then
+		core.fov.calc_circle(lx, ly, typ.ball, function(self, px, py)
+			-- Deam damage: ball
+			DamageType:get(damtype).projector(self, px, py, damtype, dam)
+--			self.sg:toScreen(self.display_x + (lx - game.level.map.mx) * self.tile_w, self.display_y + (ly - game.level.map.my) * self.tile_h)
+		end, function()end, self)
+		DamageType:get(damtype).projector(self, lx, ly, damtype, dam)
+	elseif typ.cone then
+	else
+		-- Deam damage: single
+		DamageType:get(damtype).projector(self, lx, ly, damtype, dam)
+	end
 end
diff --git a/game/modules/tome/data/abilities.lua b/game/modules/tome/data/abilities.lua
index 19fca6575d..4b75d296b4 100644
--- a/game/modules/tome/data/abilities.lua
+++ b/game/modules/tome/data/abilities.lua
@@ -1,20 +1,23 @@
 -- Mana spells
-newAbilityType{ type="spell/mana", name = "mana" }
+newAbilityType{ type="spell/arcane", name = "arcane" }
 
 newAbility{
 	name = "Manathrust",
-	type = "spell/mana",
+	type = "spell/arcane",
 	mana = 15,
 	tactical = {
 		ATTACK = 10,
 	},
 	action = function(self)
-		self:project(game.target.target.x, game.target.target.y, DamageType.MANA, 10 + self:getMag())
+		local t = {type="ball", range=20, radius=4}
+		local x, y = self:getTarget(t)
+		if not x or not y then return nil end
+		self:project(t, x, y, DamageType.ARCANE, 10 + self:getMag())
 		return true
 	end,
 	require = { stat = { mag=12 }, },
 	info = function(self)
-		return ([[Conjures up mana into a powerful bolt doing %d",
-		The damage is irresistible and will increase with magic stat]]):format(10 + self:getMag())
+		return ([[Conjures up mana into a powerful bolt doing %0.2f arcane damage",
+		The damage will increase with the Magic stat]]):format(10 + self:getMag())
 	end
 }
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 8d825a4620..af22895499 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -9,7 +9,7 @@ print(src, x, y, type, dam)
 end)
 
 newDamageType{
-	name = "mana", type = "MANA",
+	name = "arcane", type = "ARCANE",
 }
 newDamageType{
 	name = "fire", type = "FIRE",
diff --git a/game/modules/tome/data/zones/ancient_ruins/zone.lua b/game/modules/tome/data/zones/ancient_ruins/zone.lua
index 6445bed4ff..9eafda75a4 100644
--- a/game/modules/tome/data/zones/ancient_ruins/zone.lua
+++ b/game/modules/tome/data/zones/ancient_ruins/zone.lua
@@ -1,14 +1,13 @@
 return {
 	name = "ancient ruins",
 	max_level = 5,
-	width = 50, height = 30,
+	width = 40, height = 30,
 	all_remembered = true,
 	all_lited = true,
 	persistant = true,
-	level_npcs = {5, 10},
 	generator =  {
 		map = {
-			class= "engine.generator.map.Rooms",
+			class= "engine.generator.map.Empty",
 			floor = "FLOOR",
 			wall = "WALL",
 			up = "UP",
@@ -18,6 +17,7 @@ return {
 		actor = {
 			class = "engine.generator.actor.Random",
 			nb_npc = {40, 40},
+			levelup = {5, 10},
 		},
 	}
 }
-- 
GitLab