From a2f63b7e4f64ea6066664341456bf10e189188c5 Mon Sep 17 00:00:00 2001
From: dg <dg@51575b47-30f0-44d4-a5cc-537603b46e54>
Date: Sat, 23 Oct 2010 16:16:23 +0000
Subject: [PATCH] New class: Geomancer, earth specialist Archmages (no unlock
 yet)

git-svn-id: http://svn.net-core.org/repos/t-engine4@1607 51575b47-30f0-44d4-a5cc-537603b46e54
---
 game/engines/default/engine/Store.lua         |  38 ++--
 .../engine/interface/ActorInventory.lua       |   1 +
 game/modules/tome/class/Actor.lua             |   4 +
 game/modules/tome/class/NPC.lua               |   4 +-
 game/modules/tome/class/Store.lua             |  33 ++-
 game/modules/tome/data/birth/classes/mage.lua |  52 +++++
 game/modules/tome/data/damage_types.lua       |  31 +++
 game/modules/tome/data/general/grids/sand.lua |   2 +-
 .../data/gfx/particles/crystalline_focus.lua  |  45 +++++
 .../modules/tome/data/gfx/particles/quake.lua |  46 +++++
 .../tome/data/gfx/particles/stone_shards.lua  |  50 +++++
 game/modules/tome/data/lore/tol-falas.lua     |   2 +-
 .../tome/data/talents/spells/spells.lua       |   2 +
 .../tome/data/talents/spells/stone.lua        | 189 ++++++++++++++++++
 14 files changed, 469 insertions(+), 30 deletions(-)
 create mode 100644 game/modules/tome/data/gfx/particles/crystalline_focus.lua
 create mode 100644 game/modules/tome/data/gfx/particles/quake.lua
 create mode 100644 game/modules/tome/data/gfx/particles/stone_shards.lua
 create mode 100644 game/modules/tome/data/talents/spells/stone.lua

diff --git a/game/engines/default/engine/Store.lua b/game/engines/default/engine/Store.lua
index e2caaa818a..cec31a6b21 100644
--- a/game/engines/default/engine/Store.lua
+++ b/game/engines/default/engine/Store.lua
@@ -86,7 +86,7 @@ function _M:interact(who)
 		else
 			if o:getNumber() > 1 then
 				local q
-				q = GetQuantity.new(nil, nil, o:getNumber(), o:getNumber(), function(qty) print("plop", qty) self:doSell(who, o, item, qty, d) end)
+				q = GetQuantity.new(nil, nil, o:getNumber(), o:getNumber(), function(qty) self:doSell(who, o, item, qty, d) end)
 				game:registerDialog(q)
 			else
 				self:doSell(who, o, item, 1, d)
@@ -100,22 +100,23 @@ function _M:interact(who)
 	game:registerDialog(d)
 end
 
+function _M:transfer(src, dest, item, nb)
+	local src_inven, dest_inven = src:getInven("INVEN"), dest:getInven("INVEN")
+	for i = 1, nb do
+		local o = src:removeObject(src_inven, item)
+		dest:addObject(dest_inven, o)
+	end
+	self:sortInven(store)
+	who:sortInven(inven)
+end
+
 function _M:doBuy(who, o, item, nb, store_dialog)
-	local max_nb = o:getNumber()
-	nb = math.min(nb, max_nb)
+	nb = math.min(nb, o:getNumber())
 	nb = self:tryBuy(who, o, item, nb)
 	if nb then
 		Dialog:yesnoPopup("Buy", ("Buy %d %s"):format(nb, o:getName{do_color=true, no_count=true}), function(ok) if ok then
 			self:onBuy(who, o, item, nb, true)
-			local store, inven = self:getInven("INVEN"), who:getInven("INVEN")
-			for i = 1, nb do
-				local o = self:removeObject(store, item)
-				who:addObject(inven, o)
-			end
-			self:sortInven(store)
-			who:sortInven(inven)
-			self.changed = true
-			who.changed = true
+			self:transfer(self, who, item, nb)
 			self:onBuy(who, o, item, nb, false)
 			if store_dialog then store_dialog:updateStore() end
 		end end, "Buy", "Cancel")
@@ -123,21 +124,12 @@ function _M:doBuy(who, o, item, nb, store_dialog)
 end
 
 function _M:doSell(who, o, item, nb, store_dialog)
-	local max_nb = o:getNumber()
-	nb = math.min(nb, max_nb)
+	nb = math.min(nb, o:getNumber())
 	nb = self:trySell(who, o, item, nb)
 	if nb then
 		Dialog:yesnoPopup("Sell", ("Sell %d %s"):format(nb, o:getName{do_color=true, no_count=true}), function(ok) if ok then
 			self:onSell(who, o, item, nb, true)
-			local store, inven = self:getInven("INVEN"), who:getInven("INVEN")
-			for i = 1, nb do
-				local o = who:removeObject(inven, item)
-				self:addObject(store, o)
-			end
-			self:sortInven(store)
-			who:sortInven(inven)
-			self.changed = true
-			who.changed = true
+			self:transfer(who, self, item, nb)
 			self:onSell(who, o, item, nb, false)
 			if store_dialog then store_dialog:updateStore() end
 		end end, "Sell", "Cancel")
diff --git a/game/engines/default/engine/interface/ActorInventory.lua b/game/engines/default/engine/interface/ActorInventory.lua
index a7c4c11953..aba61105f8 100644
--- a/game/engines/default/engine/interface/ActorInventory.lua
+++ b/game/engines/default/engine/interface/ActorInventory.lua
@@ -409,6 +409,7 @@ function _M:sortInven(inven)
 			return ta < tb
 		end
 	end)
+	self.changed = true
 end
 
 --- Finds an object by name in an inventory
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 90bc7fca4c..510b3d1ffe 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -292,6 +292,10 @@ function _M:move(x, y, force)
 		end end
 	end
 
+	if moved and self:isTalentActive(self.T_BODY_OF_STONE) then
+		self:forceUseTalent(self.T_BODY_OF_STONE, {ignore_energy=true})
+	end
+
 	return moved
 end
 
diff --git a/game/modules/tome/class/NPC.lua b/game/modules/tome/class/NPC.lua
index 1e9628b5bc..b359d00650 100644
--- a/game/modules/tome/class/NPC.lua
+++ b/game/modules/tome/class/NPC.lua
@@ -80,7 +80,7 @@ function _M:onTakeHit(value, src)
 		self.ai_target.actor = src
 	end
 
-	if Faction:get(self.faction) and Faction:get(self.faction).hostile_on_attack then
+	if src and Faction:get(self.faction) and Faction:get(self.faction).hostile_on_attack then
 		Faction:setFactionReaction(self.faction, src.faction, Faction:factionReaction(self.faction, src.faction) - self.rank * 5, true)
 	end
 
@@ -88,7 +88,7 @@ function _M:onTakeHit(value, src)
 end
 
 function _M:die(src)
-	if Faction:get(self.faction) and Faction:get(self.faction).hostile_on_attack then
+	if src and Faction:get(self.faction) and Faction:get(self.faction).hostile_on_attack then
 		Faction:setFactionReaction(self.faction, src.faction, Faction:factionReaction(self.faction, src.faction) - self.rank * 15, true)
 	end
 
diff --git a/game/modules/tome/class/Store.lua b/game/modules/tome/class/Store.lua
index 299b977b63..d9a33b4648 100644
--- a/game/modules/tome/class/Store.lua
+++ b/game/modules/tome/class/Store.lua
@@ -30,7 +30,6 @@ function _M:loadStores(f)
 end
 
 function _M:init(t, no_default)
-	t.allow_sell = function() Dialog:simplePopup("Can not sell", "Do you really think I will buy your filthy wreckages found in the wilds? Ah!") end
 	t.buy_percent = t.buy_percent or 10
 	t.sell_percent = t.sell_percent or 100
 	Store.init(self, t, no_default)
@@ -47,7 +46,7 @@ end
 function _M:tryBuy(who, o, item, nb)
 	local price = o:getPrice() * self.sell_percent / 100
 	if who.money >= price * nb then
-		return nb
+		return nb, price * nb  --FINISH ME !!!!
 	else
 		Dialog:simplePopup("Not enough gold", "You do not have enough gold!")
 	end
@@ -62,7 +61,7 @@ end
 function _M:trySell(who, o, item, nb)
 	local price = o:getPrice() * self.buy_percent / 100
 	if price <= 0 or nb <= 0 then return end
-	return nb
+	return nb, price
 end
 
 --- Called on object purchase
@@ -95,6 +94,34 @@ function _M:onSell(who, o, item, nb, before)
 	who:incMoney(price * nb)
 end
 
+--- Override the default
+function _M:doBuy(who, o, item, nb, store_dialog)
+	nb = math.min(nb, o:getNumber())
+	nb = self:tryBuy(who, o, item, nb)
+	if nb then
+		Dialog:yesnoPopup("Buy", ("Buy %d %s for %0.2f gold"):format(nb, o:getName{do_color=true, no_count=true}), function(ok) if ok then
+			self:onBuy(who, o, item, nb, true)
+			self:transfer(self, who, item, nb)
+			self:onBuy(who, o, item, nb, false)
+			if store_dialog then store_dialog:updateStore() end
+		end end, "Buy", "Cancel")
+	end
+end
+
+--- Override the default
+function _M:doSell(who, o, item, nb, store_dialog)
+	nb = math.min(nb, o:getNumber())
+	nb = self:trySell(who, o, item, nb)
+	if nb then
+		Dialog:yesnoPopup("Sell", ("Sell %d %s for %0.2f gold"):format(nb, o:getName{do_color=true, no_count=true}), function(ok) if ok then
+			self:onSell(who, o, item, nb, true)
+			self:transfer(who, self, item, nb)
+			self:onSell(who, o, item, nb, false)
+			if store_dialog then store_dialog:updateStore() end
+		end end, "Sell", "Cancel")
+	end
+end
+
 --- Called to describe an object, being to sell or to buy
 -- @param who the actor
 -- @param what either "sell" or "buy"
diff --git a/game/modules/tome/data/birth/classes/mage.lua b/game/modules/tome/data/birth/classes/mage.lua
index e562350f42..2a3c94ef01 100644
--- a/game/modules/tome/data/birth/classes/mage.lua
+++ b/game/modules/tome/data/birth/classes/mage.lua
@@ -33,6 +33,7 @@ newBirthDescriptor{
 			Pyromancer = function() return profile.mod.allow_build.mage_pyromancer and "allow" or "disallow" end,
 			Cryomancer = function() return profile.mod.allow_build.mage_cryomancer and "allow" or "disallow" end,
 			Tempest = function() return profile.mod.allow_build.mage_tempest and "allow" or "disallow" end,
+			Geomancer = function() return profile.mod.allow_build.mage_geomancer and "allow" or "disallow" end,
 		},
 	},
 	copy = {
@@ -318,3 +319,54 @@ newBirthDescriptor{
 		life_rating = -4,
 	},
 }
+
+newBirthDescriptor{
+	type = "subclass",
+	name = "Geomancer",
+	desc = {
+		"A Geomancer is an archmage specialized in earth and stone magic.",
+		"They gain access to the special Stone talents whose main purpose is to crush and destroy.",
+		"They can even learn to pierce through physical resistance and immunity.",
+		"Most archmagi lack basic skills that others take for granted (like general fighting sense), but they make up for it by their raw magical power.",
+		"Archmagi know all schools of magic but the more intricate (Temporal and Meta) from the start. They however usually refuse to have anything to do with Necromancy.",
+		"All archmagi have been trained in the secret town of Angolwen and posses a unique spell to teleport to it directly.",
+		"Their most important stats are: Magic and Willpower",
+		"#GOLD#Stats modifiers:",
+		"#LIGHT_BLUE# * +0 Strength, +0 Dexterity, +0 Constitution",
+		"#LIGHT_BLUE# * +5 Magic, +3 Willpower, +1 Cunning",
+	},
+	stats = { mag=5, wil=3, cun=1, },
+	talents_types = {
+		["spell/arcane"]={true, 0.2},
+		["spell/earth"]={true, 0.3},
+		["spell/stone"]={true, 0.4},
+		["spell/water"]={true, 0.2},
+		["spell/phantasm"]={true, 0.3},
+		["spell/temporal"]={false, 0.3},
+		["spell/meta"]={false, 0.3},
+		["spell/divination"]={true, 0.3},
+		["spell/conveyance"]={true, 0.3},
+		["cunning/survival"]={false, -0.1},
+	},
+	talents = {
+		[ActorTalents.T_ARCANE_POWER] = 1,
+		[ActorTalents.T_STONE_SKIN] = 1,
+		[ActorTalents.T_STALACTITIC_MISSILES] = 1,
+		[ActorTalents.T_PHASE_DOOR] = 1,
+		[ActorTalents.T_TELEPORT_ANGOLWEN]=1,
+	},
+	copy = {
+		-- All mages are of angolwen faction
+		faction = "angolwen",
+		max_life = 90,
+		life_rating = -4,
+		resolvers.equip{ id=true,
+			{type="weapon", subtype="staff", name="elm staff", autoreq=true},
+			{type="armor", subtype="cloth", name="linen robe", autoreq=true},
+		},
+		resolvers.inventory{ id=true,
+			{type="potion", subtype="potion", name="potion of lesser mana", ego_chance=-1000},
+			{type="potion", subtype="potion", name="potion of lesser mana", ego_chance=-1000},
+		},
+	},
+}
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 07b9861242..5c0b6f36b9 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -841,3 +841,34 @@ newDamageType{
 	hideMessage=true,
 	hideFlyer=true
 }
+
+-- Physical + Stun Chance
+newDamageType{
+	name = "physical stun", type = "PHYSICAL_STUN",
+	projector = function(src, x, y, type, dam)
+		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam)
+		local target = game.level.map(x, y, Map.ACTOR)
+		if target and rng.percent(25) then
+			if target:checkHit(src:combatSpellpower(), target:combatSpellResist(), 0, 95, 15) and target:canBe("stun") then
+				target:setEffect(target.EFF_STUNNED, 2, {src=src})
+			else
+				game.logSeen(target, "%s resists!", target.name:capitalize())
+			end
+		end
+	end,
+}
+
+-- Physical Damage/Cut Split
+newDamageType{
+	name = "split bleed", type = "SPLIT_BLEED",
+	projector = function(src, x, y, type, dam)
+		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam / 2)
+		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam / 12)
+		dam = dam - dam / 12
+		local target = game.level.map(x, y, Map.ACTOR)
+		if target and target:canBe("cut") then
+			-- Set on fire!
+			target:setEffect(target.EFF_CUT, 5, {src=src, power=dam / 11})
+		end
+	end,
+}
diff --git a/game/modules/tome/data/general/grids/sand.lua b/game/modules/tome/data/general/grids/sand.lua
index fb83ee1d48..4bfd4354e7 100644
--- a/game/modules/tome/data/general/grids/sand.lua
+++ b/game/modules/tome/data/general/grids/sand.lua
@@ -50,7 +50,7 @@ newEntity{
 					if a then
 						game.logPlayer(a, "You are crushed by the collapsing tunnel! You suffocate!")
 						a:suffocate(30, self)
-						engine.DamageType:get(engine.DamageType.PHYSICAL).projector(self, self.x, self.y, engine.DamageType.PHYSICAL, a.life / 2)
+						engine.DamageType:get(engine.DamageType.PHYSICAL).projector(self, self.x, self.y, engine.DamageType.PHYSICAL, a.life / 4)
 					end
 				end
 			end
diff --git a/game/modules/tome/data/gfx/particles/crystalline_focus.lua b/game/modules/tome/data/gfx/particles/crystalline_focus.lua
new file mode 100644
index 0000000000..185190aa71
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/crystalline_focus.lua
@@ -0,0 +1,45 @@
+-- ToME - Tales of Middle-Earth
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = math.rad(ad)
+	local r = rng.range(18, 22)
+	local dirchance = rng.chance(2)
+
+	return {
+		life = 20,
+		size = 4, sizev = -0.05, sizea = 0,
+
+		x = r * math.cos(a), xv = 0, xa = 0,
+		y = r * math.sin(a), yv = 0, ya = 0,
+		dir = dir, dirv = 0.1, dira = 0,
+		vel = dirchance and 0.2 or -0.2, velv = 0, vela = dirchance and -0.02 or 0.02,
+
+		r = rng.range(0, 0)/255, rv = rng.range(0, 10)/100, ra = 0,
+		g = rng.range(80, 200)/255,  gv = 0.005, ga = 0.0005,
+		b = 0,	bv = 0, ba = 0,
+		a = rng.range(70, 255)/255,	av = 0, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(4)
+end,
+80
diff --git a/game/modules/tome/data/gfx/particles/quake.lua b/game/modules/tome/data/gfx/particles/quake.lua
new file mode 100644
index 0000000000..d83f105e6a
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/quake.lua
@@ -0,0 +1,46 @@
+-- ToME - Tales of Middle-Earth
+-- 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
+
+return { generator = function()
+	local ad = rng.range(0, 360)
+	local a = math.rad(ad)
+	local dir = math.rad(ad + 90)
+	local r = rng.range(1, 20)
+	local dirv = math.rad(1)
+
+	return {
+		trail = 1,
+		life = 10,
+		size = 1, sizev = 0.5, sizea = 0,
+
+		x = r * math.cos(a), xv = -0.1, xa = 0,
+		y = r * math.sin(a), yv = -0.1, ya = 0,
+		dir = math.rad(rng.range(0, 360)), dirv = 0, dira = 0,
+		vel = 0.1, velv = 0, vela = 0,
+
+		r = rng.range(200, 230)/255,  rv = 0, ra = 0,
+		g = rng.range(130, 160)/255,  gv = 0, ga = 0,
+		b = rng.range(50, 70)/255,	 bv = 0, ba = 0,
+		a = rng.range(25, 220)/255,	av = 0, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(4)
+end,
+40
diff --git a/game/modules/tome/data/gfx/particles/stone_shards.lua b/game/modules/tome/data/gfx/particles/stone_shards.lua
new file mode 100644
index 0000000000..c9e70e781b
--- /dev/null
+++ b/game/modules/tome/data/gfx/particles/stone_shards.lua
@@ -0,0 +1,50 @@
+-- ToME - Tales of Middle-Earth
+-- 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
+
+local starts = {}
+for i = 1, 4 do
+	starts[#starts+1] = { a = math.rad(rng.range(0, 360)), r = rng.range(6, 20) }
+end
+
+-- Populate the beam based on the forks
+return { generator = function()
+	local rad = rng.range(-3,3)
+	local s = rng.table(starts)
+	local ra = s.a
+	local r = s.r
+
+	return {
+		life = 4,
+		size = rng.range(4, 6), sizev = -0.1, sizea = 0,
+
+		x = 2 * math.cos(ra), xv = 0, xa = 0,
+		y = 2 * math.sin(ra), yv = 0, ya = 0,
+		dir = 0, dirv = 0, dira = 0,
+		vel = 0, velv = 0, vela = 0,
+
+		r = 0xD7/255, rv = 0, ra = 0,
+		g = 0x8E/255, gv = 0, ga = 0,
+		b = 0x45/255, bv = 0, ba = 0,
+		a = rng.range(100, 220)/255,	av = 0.05, aa = 0,
+	}
+end, },
+function(self)
+	self.ps:emit(30 * 4)
+end,
+4*30*4
diff --git a/game/modules/tome/data/lore/tol-falas.lua b/game/modules/tome/data/lore/tol-falas.lua
index 7818208be7..77221ba375 100644
--- a/game/modules/tome/data/lore/tol-falas.lua
+++ b/game/modules/tome/data/lore/tol-falas.lua
@@ -26,7 +26,7 @@ newLore{
 	name = "note from the Master",
 	lore = [[MINIONS: Perhaps you feel your Master has been lax or absent? Well, I shall amend that. I have been studying an object of great import. It is of much greater interest than your foolish unlives. But do not think that I will let you get away with things because of this.
 
-Skeletons, you have been getting noticeably behind in your adventurer slaughtering quotas. The next skeleton archer I see drinking coffee and chatting with the wights shall be rended limb from limb and fed to the orcs. Also, as a punishment for your general laxness, 1,000 skeletons shall be remanded down to Amun Sul as punishment. A further 250 shall be slaughtered. These orders to be carried out by myself tomorrow at 3am.]],
+Skeletons, you have been getting noticeably behind in your adventurer slaughtering quotas. The next skeleton archer I see drinking coffee and chatting with the wights shall be rent limb from limb and fed to the orcs. Also, as a punishment for your general laxness, 1,000 skeletons shall be remanded down to Amun Sul as punishment. A further 250 shall be slaughtered. These orders to be carried out by myself tomorrow at 3am.]],
 }
 
 newLore{
diff --git a/game/modules/tome/data/talents/spells/spells.lua b/game/modules/tome/data/talents/spells/spells.lua
index 20ef23ac38..8ccf812714 100644
--- a/game/modules/tome/data/talents/spells/spells.lua
+++ b/game/modules/tome/data/talents/spells/spells.lua
@@ -24,6 +24,7 @@ newTalentType{ no_silence=true, is_spell=true, type="spell/arcane", name = "arca
 newTalentType{ no_silence=true, is_spell=true, type="spell/fire", name = "fire", description = "Harness the power of fire to burn your foes to ashes." }
 newTalentType{ no_silence=true, is_spell=true, type="spell/wildfire", name = "wildfire", description = "Harness the power of wildfire to burn your foes to ashes." }
 newTalentType{ no_silence=true, is_spell=true, type="spell/earth", name = "earth", description = "Harness the power of the earth to protect and destroy." }
+newTalentType{ no_silence=true, is_spell=true, type="spell/stone", name = "stone", description = "Harness the power of the stone to protect and destroy." }
 newTalentType{ no_silence=true, is_spell=true, type="spell/water", name = "water", description = "Harness the power of water to drown your foes." }
 newTalentType{ no_silence=true, is_spell=true, type="spell/ice", name = "ice", description = "Harness the power of ice to freeze and shatter your foes." }
 newTalentType{ no_silence=true, is_spell=true, type="spell/air", name = "air", description = "Harness the power of the air to fry your foes." }
@@ -75,6 +76,7 @@ load("/data/talents/spells/arcane.lua")
 load("/data/talents/spells/fire.lua")
 load("/data/talents/spells/wildfire.lua")
 load("/data/talents/spells/earth.lua")
+load("/data/talents/spells/stone.lua")
 load("/data/talents/spells/water.lua")
 load("/data/talents/spells/ice.lua")
 load("/data/talents/spells/air.lua")
diff --git a/game/modules/tome/data/talents/spells/stone.lua b/game/modules/tome/data/talents/spells/stone.lua
new file mode 100644
index 0000000000..096e56f58b
--- /dev/null
+++ b/game/modules/tome/data/talents/spells/stone.lua
@@ -0,0 +1,189 @@
+-- ToME - Tales of Middle-Earth
+-- 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
+
+newTalent{
+	name = "Stalactitic Missiles",
+	type = {"spell/stone",1},
+	require = spells_req1,
+	points = 5,
+	random_ego = "attack",
+	mana = 10,
+	cooldown = 3,
+	tactical = {
+		ATTACK = 10,
+	},
+	range = 20,
+	direct_hit = true,
+	reflectable = true,
+	proj_speed = 20,
+	requires_target = true,
+	action = function(self, t)
+		local tg = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="stone_shards", trail="earthtrail"}}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+		self:projectile(tg, x, y, DamageType.SPLIT_BLEED, self:spellCrit(self:combatTalentSpellDamage(t, 20, 120)), nil)
+		game:playSoundNear(self, "talents/earth")
+		--missile #2 (Talent Level 3 Bonus Missile)
+		if self:getTalentLevel(t) >= 3 then
+			local tg2 = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="stone_shards", trail="earthtrail"}}
+			local x, y = self:getTarget(tg2)
+			if not x or not y then return nil end
+			self:projectile(tg2, x, y, DamageType.SPLIT_BLEED, self:spellCrit(self:combatTalentSpellDamage(t, 20, 120)), nil)
+			game:playSoundNear(self, "talents/earth")
+		else end
+		--missile #3 (Talent Level 5 Bonus Missile)
+		if self:getTalentLevel(t) >= 5 then
+			local tg3 = {type="bolt", range=self:getTalentRange(t), talent=t, display={particle="stone_shards", trail="earthtrail"}}
+			local x, y = self:getTarget(tg3)
+			if not x or not y then return nil end
+			self:projectile(tg3, x, y, DamageType.SPLIT_BLEED, self:spellCrit(self:combatTalentSpellDamage(t, 20, 120)), nil)
+			game:playSoundNear(self, "talents/earth")
+		else end
+		return true
+	end,
+	info = function(self, t)
+		return ([[Conjures stalactite shaped rocks that you fire individually at any target or targets in range.  Each missile deals %0.2f physical damage and an additional %0.2f bleeding damage over six turns.
+		At talent level 1 you conjure a single missile, at talent level 3 two missiles, and at talent level 5 three missiles.
+		The damage will increase with the Magic stat]]):format(damDesc(self, DamageType.PHYSICAL, self:combatTalentSpellDamage(t, 20, 120)/2), damDesc(self, DamageType.PHYSICAL, self:combatTalentSpellDamage(t, 20, 120)/2))
+	end,
+}
+
+newTalent{
+	name = "Body of Stone",
+	type = {"spell/stone",2},
+	require = spells_req2,
+	points = 5,
+	mode = "sustained",
+	sustain_mana = 70,
+	cooldown = 12,
+	activate = function(self, t)
+		local fire = self:combatTalentSpellDamage(t, 5, 80)
+		local light = self:combatTalentSpellDamage(t, 5, 50)
+		local phys = self:combatTalentSpellDamage(t, 5, 20)
+		local kb = self:getTalentLevel(t)/10
+		local cdr = self:getTalentLevel(t)/2
+		local rad = 5 + self:combatSpellpower(0.1) * self:getTalentLevel(t)
+		game:playSoundNear(self, "talents/earth")
+		return {
+			particle = self:addParticles(Particles.new("stone_skin", 1)),
+			move = self:addTemporaryValue("never_move", 1),
+			knock = self:addTemporaryValue("knockback_immune", kb),
+			detect = self:addTemporaryValue("detect_range", rad),
+			tremor = self:addTemporaryValue("detect_actor", 1),
+			cdred = self:addTemporaryValue("talent_cd_reduction", {
+				[self.T_STALACTITIC_MISSILES] = cdr,
+				[self.T_STRIKE] = cdr,
+				[self.T_EARTHQUAKE] = cdr,
+			}),
+			res = self:addTemporaryValue("resists", {
+				[DamageType.FIRE] = fire,
+				[DamageType.LIGHTNING] = light,
+				[DamageType.PHYSICAL] = phys,
+			}),
+		}
+	end,
+	deactivate = function(self, t, p)
+		self:removeParticles(p.particle)
+		self:removeTemporaryValue("never_move", p.move)
+		self:removeTemporaryValue("knockback_immune", p.knock)
+		self:removeTemporaryValue("detect_actor", p.tremor)
+		self:removeTemporaryValue("detect_range", p.detect)
+		self:removeTemporaryValue("talent_cd_reduction", p.cdred)
+		self:removeTemporaryValue("resists", p.res)
+		return true
+	end,
+	info = function(self, t)
+		return ([[You root yourself into the earth and transform your flesh into stone.  While this spell is sustained you may not move and any forced movement will end the effect.
+		Your stoned form and your affinity with the earth while the spell is active has the following effects:
+		* Reduces the cooldown of Stalactitic Missiles, Earthquake, and Strike by %d
+		* Grants %d%% Fire Resistance, %d%% Lightning Resistance, %d%% Physical Resistance, and %d%% Knockback Resistance
+		* Sense foes around you in a radius of %d.
+		Resistances and Sense radius scale with the Magic Stat.]])
+		:format((self:getTalentLevel(t)/2), self:combatTalentSpellDamage(t, 5, 80), self:combatTalentSpellDamage(t, 5, 50), self:combatTalentSpellDamage(t, 5, 20), (self:getTalentLevel(t)*10), (5 + self:combatSpellpower(0.1) * self:getTalentLevel(t)))
+	end,
+}
+
+newTalent{
+	name = "Earthquake",
+	type = {"spell/stone",3},
+	require = spells_req3,
+	points = 5,
+	random_ego = "attack",
+	mana = 50,
+	cooldown = 30,
+	tactical = {
+		ATTACKAREA = 40,
+	},
+	range = 20,
+	direct_hit = true,
+	requires_target = true,
+	action = function(self, t)
+		local duration = 3 + self:getTalentLevel(t)
+		local radius = 2 + (self:getTalentLevel(t)/2)
+		local dam = self:combatTalentSpellDamage(t, 15, 70)
+		local tg = {type="ball", range=self:getTalentRange(t), radius=radius}
+		local x, y = self:getTarget(tg)
+		if not x or not y then return nil end
+		local _ _, x, y = self:canProject(tg, x, y)
+		-- Add a lasting map effect
+		game.level.map:addEffect(self,
+			x, y, duration,
+			DamageType.PHYSICAL_STUN, dam,
+			radius,
+			5, nil,
+			{type="quake"},
+			nil, self:spellFriendlyFire()
+		)
+
+		game:playSoundNear(self, "talents/earth")
+		return true
+	end,
+	info = function(self, t)
+		return ([[Causes a violent earthquake that deals %0.2f physical damage in a radius of %d each turn for %d turns and potentially stuns all creatures it affects.
+		The damage and duration will increase with the Magic stat]]):format(damDesc(self, DamageType.PHYSICAL, self:combatTalentSpellDamage(t, 15, 70)), 2 + (self:getTalentLevel(t)/2), 3 + self:getTalentLevel(t))
+	end,
+}
+
+newTalent{
+	name = "Crystalline Focus",
+	type = {"spell/stone",4},
+	require = spells_req4,
+	points = 5,
+	mode = "sustained",
+	sustain_mana = 80,
+	cooldown = 30,
+	activate = function(self, t)
+		game:playSoundNear(self, "talents/earth")
+		return {
+			dam = self:addTemporaryValue("inc_damage", {[DamageType.PHYSICAL] = self:getTalentLevelRaw(t) * 2}),
+			resist = self:addTemporaryValue("resists_pen", {[DamageType.PHYSICAL] = self:getTalentLevelRaw(t) * 10}),
+			particle = self:addParticles(Particles.new("crystalline_focus", 1)),
+		}
+	end,
+	deactivate = function(self, t, p)
+		self:removeParticles(p.particle)
+		self:removeTemporaryValue("inc_damage", p.dam)
+		self:removeTemporaryValue("resists_pen", p.resist)
+		return true
+	end,
+	info = function(self, t)
+		return ([[Concentrate on maintaining a Crystalline Focus, increasing all your physical damage by %d%% and ignoring %d%% physical resistance of your targets.]])
+		:format(self:getTalentLevelRaw(t) * 2, self:getTalentLevelRaw(t) * 10)
+	end,
+}
-- 
GitLab