diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 8fa6c1a617d6bc0129d6e3745863eeec5f9c271c..d82da5f86e6434236fb3f37b5efceacd7cb6ab66 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -73,10 +73,10 @@ _M.stats_per_level = 3
 
 -- Speeds are multiplicative, not additive
 _M.temporary_values_conf.global_speed_add = "newest"
-_M.temporary_values_conf.movement_speed = "mult0"
-_M.temporary_values_conf.combat_physspeed = "mult0"
-_M.temporary_values_conf.combat_spellspeed = "mult0"
-_M.temporary_values_conf.combat_mindspeed = "mult0"
+_M.temporary_values_conf.movement_speed = "add" -- Prevent excessive movement speed compounding
+_M.temporary_values_conf.combat_physspeed = "add" -- Prevent excessive attack speed compounding
+_M.temporary_values_conf.combat_spellspeed = "add" -- Prevent excessive spell speed compounding
+_M.temporary_values_conf.combat_mindspeed = "add" -- Prevent excessive mind speed compounding
 
 -- Damage cap takes the lowest
 _M.temporary_values_conf.flat_damage_cap = "lowest"
@@ -1042,7 +1042,7 @@ function _M:move(x, y, force)
 
 	-- Try to detect traps
 	if self:knowTalent(self.T_HEIGHTENED_SENSES) then
-		local power = self:getTalentLevel(self.T_HEIGHTENED_SENSES) * self:getCun(25, true)
+		local power = self:callTalent(self.T_HEIGHTENED_SENSES,"trapPower")
 		local grids = core.fov.circle_grids(self.x, self.y, 1, true)
 		for x, yy in pairs(grids) do for y, _ in pairs(yy) do
 			local trap = game.level.map(x, y, Map.TRAP)
@@ -1388,8 +1388,8 @@ function _M:colorStats(stat)
 		return "#0080FF#"..score
 	elseif score <= 99 then
 		return "#8d55ff#"..score
-	elseif score == 100 then
-		return "#8d55ff#**"
+	elseif score >= 100 then
+		return "#8d55ff#"..score  -- Enable longer numbers
 	end
 end
 
@@ -2052,7 +2052,7 @@ function _M:onTakeHit(value, src)
 
 	-- Second Life
 	if self:isTalentActive(self.T_SECOND_LIFE) and value >= self.life then
-		local sl = self.max_life * (0.05 + self:getTalentLevelRaw(self.T_SECOND_LIFE)/25)
+		local sl = self:callTalent(self.T_SECOND_LIFE,"getLife")
 		value = 0
 		self.life = sl
 		game.logSeen(self, "%s has been saved by a blast of positive energy!", self.name:capitalize())
@@ -2094,7 +2094,7 @@ function _M:onTakeHit(value, src)
 	if self:knowTalent(self.T_DUCK_AND_DODGE) then
 		local t = self:getTalentFromId(self.T_DUCK_AND_DODGE)
 		if value >= self.max_life * t.getThreshold(self, t) then
-			self:setEffect(self.EFF_EVASION, t.getDuration(self, t), {chance=t.getEvasionChance(self, t)})
+			self:setEffect(self.EFF_EVASION, t.getDuration(self, t), {chance=t.getEvasionChance(self, t), defense = t.getDefense(self)})
 		end
 	end
 
@@ -2533,8 +2533,7 @@ function _M:resolveLevelTalents()
 	if not self.start_level or not self._levelup_talents then return end
 
 	local maxfact = 1  -- Balancing parameter for levels > 50: maxtalent level = actorlevel/50*maxfact * normal max talent level
-	--I5 remove the following statement once all talent scaling is in:
-	--if game.zone.short_name == "infinite-dungeon" then maxfact=math.max(maxfact,self.level/50) end
+	maxfact=math.max(maxfact,self.level/50) 
 
 	for tid, info in pairs(self._levelup_talents) do
 		if not info.max or (self.talents[tid] or 0) < math.floor(info.max*maxfact) then
@@ -2557,8 +2556,8 @@ function _M:levelup()
 		self.unused_generics = self.unused_generics + 1
 		if self.level % 5 == 0 then self.unused_talents = self.unused_talents + 1 end
 		if self.level % 5 == 0 then self.unused_generics = self.unused_generics - 1 end
-		-- At levels 10, 20 and 36 we gain a new talent type
-		if self.level == 10 or self.level == 20 or self.level == 36 then
+		-- At levels 10, 20 and 36 and then every 30 levels, we gain a new talent type
+		if self.level == 10 or self.level == 20 or self.level == 36 or (self.level > 50 and (self.level - 6) % 30 == 0) then
 			self.unused_talents_types = self.unused_talents_types + 1
 		end
 		if self.level == 30 or self.level == 42 then
@@ -2638,7 +2637,10 @@ function _M:levelup()
 	end
 
 	-- Force levelup of the golem
-	if self.alchemy_golem then self.alchemy_golem:forceLevelup(self.level) end
+	if self.alchemy_golem then
+		self.alchemy_golem.max_level = self.max_level  -- make sure golem can level up with master
+		self.alchemy_golem:forceLevelup(self.level)
+	end
 
 	-- Notify party levelups
 	if self.x and self.y and game.party:hasMember(self) and not self.silent_levelup then
@@ -2703,8 +2705,8 @@ function _M:onStatChange(stat, v)
 end
 
 function _M:recomputeGlobalSpeed()
-	if self.global_speed_add > 0 then self.global_speed = self.global_speed_base + self.global_speed_add
-	else self.global_speed = self.global_speed_base * math.exp(self.global_speed_add)
+	if self.global_speed_add >= 0 then self.global_speed = self.global_speed_base + self.global_speed_add
+	else self.global_speed = self.global_speed_base / (1 + math.abs(self.global_speed_add)) -- Symmetric scaling
 	end
 	self.global_speed = math.max(self.global_speed, 0.1)
 end
@@ -4576,7 +4578,7 @@ function _M:on_set_temporary_effect(eff_id, e, p)
 	end
 
 	if e.status == "detrimental" and self:knowTalent(self.T_RESILIENT_BONES) then
-		p.dur = math.ceil(p.dur * (1 - util.bound(self:getTalentLevel(self.T_RESILIENT_BONES) / 12, 0, 1)))
+		p.dur = math.ceil(p.dur * (1 - self:callTalent(self.T_RESILIENT_BONES,"durresist")))
 	end
 	if e.status == "detrimental" and e.type ~= "other" and self:attr("reduce_detrimental_status_effects_time") then
 		local power = util.bound(self.reduce_detrimental_status_effects_time, 0, 100)
diff --git a/game/modules/tome/class/Store.lua b/game/modules/tome/class/Store.lua
index 5c79f27576e1cdc6e976e5b167ce08555015ef5f..96a979cb4d25adb8c01fc97f85bf05c6670b85af 100644
--- a/game/modules/tome/class/Store.lua
+++ b/game/modules/tome/class/Store.lua
@@ -31,7 +31,9 @@ end
 
 function _M:init(t, no_default)
 	t.store.buy_percent = t.store.buy_percent or function(self, o) if o.type == "gem" then return 40 else return 5 end end
-	t.store.sell_percent = t.store.sell_percent or function(self, o) return 120 + 3 * (o.__store_level or 0) end -- Stores prices goes up with item level
+	t.store.sell_percent = t.store.sell_percent or function(self, o) -- Store prices goes up with item level
+			return mod.class.interface.Combat:combatTalentScale(math.max(1, o.__store_level or 1), 123, 135, "log") 
+		end
 	t.store.nb_fill = t.store.nb_fill or 10
 	t.store.purse = t.store.purse or 20
 	Store.init(self, t, no_default)
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index 68455a0ad3d546455598101bf4032ead00b9891b..4b046bf574c2951b96d4ef18c71d5683edc92e69 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -384,7 +384,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 	elseif self:checkHit(atk, def) and (self:canSee(target) or self:attr("blind_fight") or rng.chance(3)) then
 		local pres = util.bound(target:combatArmorHardiness() / 100, 0, 1)
 		if target.knowTalent and target:hasEffect(target.EFF_DUAL_WEAPON_DEFENSE) then
-			local deflect = target:callTalent(target.T_DUAL_WEAPON_DEFENSE, "doDeflect")
+			local deflect = math.max(dam, target:callTalent(target.T_DUAL_WEAPON_DEFENSE, "doDeflect"))
 			if deflect > 0 then
 				game.logSeen(target, "%s parries %d damage from %s's attack.", target.name:capitalize(), deflect, self.name:capitalize())
 				dam = math.max(dam - deflect,0)
@@ -1121,11 +1121,11 @@ function _M:combatCrit(weapon)
 	weapon = weapon or self.combat or {}
 	local addcrit = 0
 	if weapon.talented and self:knowTalent(Talents.T_LETHALITY) then
-		addcrit = 1 + self:getTalentLevel(Talents.T_LETHALITY) * 1.3
+		addcrit = 1 + self:callTalent(Talents.T_LETHALITY, "getCriticalChance")
 	end
 	local crit = self.combat_physcrit + (self:getCun() - 10) * 0.3 + (self:getLck() - 50) * 0.30 + (weapon.physcrit or 1) + addcrit
 
-	return util.bound(crit, 0, 100)
+	return math.max(crit, 0) -- note: crit > 100% may be offset by crit reduction elsewhere
 end
 
 --- Gets the damage range
@@ -1352,7 +1352,7 @@ function _M:combatPhysicalpower(mod, weapon, add)
 	mod = mod or 1
 	add = add or 0
 	if self:knowTalent(Talents.T_ARCANE_DESTRUCTION) then
-		add = add + self:combatSpellpower() * self:getTalentLevel(Talents.T_ARCANE_DESTRUCTION) / 7
+		add = add + self:combatSpellpower() * self:callTalent(Talents.T_ARCANE_DESTRUCTION, "getSPMult")
 	end
 	if self:isTalentActive(Talents.T_BLOOD_FRENZY) then
 		add = add + self.blood_frenzy
@@ -1372,7 +1372,7 @@ function _M:combatPhysicalpower(mod, weapon, add)
 
 	add = add + 10 * self:combatCheckTraining(weapon)
 
-	local d = (self.combat_dam > 0 and self.combat_dam or 0) + add + self:getStr()
+	local d = math.max(0, self.combat_dam + add) + self:getStr() -- allows strong debuffs to offset strength
 	if self:attr("dazed") then d = d / 2 end
 	return self:rescaleCombatStats(d) * mod
 end
@@ -1396,7 +1396,7 @@ function _M:combatSpellpower(mod, add)
 		add = add + self:callTalent(self.T_SHADOW_CUNNING,"getSpellpower") * self:getCun() / 100
 	end
 	if self:hasEffect(self.EFF_BLOODLUST) then
-		add = add + self:hasEffect(self.EFF_BLOODLUST).dur
+		add = add + self:hasEffect(self.EFF_BLOODLUST).power
 	end
 
 	local am = 1
@@ -1542,9 +1542,9 @@ function _M:physicalCrit(dam, weapon, target, atk, def, add_chance, crit_power_a
 		chance = chance - target:callTalent(target.T_SCOUNDREL,"getCritPenalty")
 	end
 
-	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
+	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and not target:canSee(self) then -- bug fix
 		chance = 100
-		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
+		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
 	end
 
 	chance = util.bound(chance, 0, 100)
@@ -1572,9 +1572,9 @@ function _M:spellCrit(dam, add_chance, crit_power_add)
 	local chance = self:combatSpellCrit() + (add_chance or 0)
 	local crit = false
 
-	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
+	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and not target:canSee(self) then -- bug fix
 		chance = 100
-		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
+		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
 	end
 
 	print("[SPELL CRIT %]", chance)
@@ -1619,9 +1619,9 @@ function _M:mindCrit(dam, add_chance, crit_power_add)
 	local chance = self:combatMindCrit() + (add_chance or 0)
 	local crit = false
 
-	if self:isTalentActive(self.T_STEALTH) and self:knowTalent(self.T_SHADOWSTRIKE) then
+	if self:attr("stealth") and self:knowTalent(self.T_SHADOWSTRIKE) and not target:canSee(self) then -- bug fix
 		chance = 100
-		crit_power_add = crit_power_add + self:getTalentLevel(self.T_SHADOWSTRIKE) / 7
+		crit_power_add = crit_power_add + self:callTalent(self.T_SHADOWSTRIKE,"getMultiplier")
 	end
 
 	print("[MIND CRIT %]", chance)
@@ -1721,7 +1721,7 @@ function _M:combatPhysicalResist(fake)
 		add = add + self:getCon() / 3
 	end
 	if self:knowTalent(self.T_POWER_IS_MONEY) then
-		add = add + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
+		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
 	end
 
 	-- To return later
@@ -1749,7 +1749,7 @@ function _M:combatSpellResist(fake)
 		add = add + self:getCon() / 3
 	end
 	if self:knowTalent(self.T_POWER_IS_MONEY) then
-		add = add + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
+		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
 	end
 
 	-- To return later
@@ -1781,11 +1781,16 @@ function _M:combatMentalResist(fake)
 		add = add + t.getMental(self, t)
 	end
 	if self:knowTalent(self.T_POWER_IS_MONEY) then
-		add = add + util.bound(self.money / (90 - self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 5), 0, self:getTalentLevelRaw(self.T_POWER_IS_MONEY) * 7)
+		add = add + self:callTalent(self.T_POWER_IS_MONEY, "getSaves")
 	end
 
 	local d = self.combat_mentalresist + (self:getCun() + self:getWil() + (self:getLck() - 50) * 0.5) * 0.35 + add
 	if self:attr("dazed") then d = d / 2 end
+	
+	local nm = self:hasEffect(self.EFF_CURSE_OF_NIGHTMARES)
+	if nm and rng.percent(20) then
+		d = d * (1-self.tempeffect_def.EFF_CURSE_OF_NIGHTMARES.getVisionsReduction(nm, nm.level)/100)
+	end	
 	return self:rescaleCombatStats(d)
 end
 
@@ -1808,8 +1813,8 @@ function _M:combatGetResist(type)
 		power = self.force_use_resist_percent or 100
 	end
 
-	local a = (self.resists.all or 0) / 100
-	local b = (self.resists[type] or 0) / 100
+	local a = math.min((self.resists.all or 0) / 100,1) -- Prevent large numbers from inverting the resist formulas
+	local b = math.min((self.resists[type] or 0) / 100,1)
 	local r = math.min(100 * (1 - (1 - a) * (1 - b)), (self.resists_cap.all or 0) + (self.resists_cap[type] or 0))
 	return r * power / 100
 end
@@ -1862,7 +1867,7 @@ function _M:combatSeeStealth()
 	if self:knowTalent(self.T_PIERCING_SIGHT) then bonus = bonus + self:callTalent(self.T_PIERCING_SIGHT,"seePower") end
 	if self:knowTalent(self.T_PRETERNATURAL_SENSES) then bonus = bonus + self:callTalent(self.T_PRETERNATURAL_SENSES, "sensePower") end
 	-- level 50 with 100 cun ==> 50
-	return self:combatScale(self.level/2 + self:getCun(25, true) + (self:attr("see_stealth") or 0), 0, 0, 50, 50) + bonus -- Note bonus scaled separately from talents
+	return self:combatScale(self.level/2 + self:getCun()/4 + (self:attr("see_stealth") or 0), 0, 0, 50, 50) + bonus -- Note bonus scaled separately from talents
 end
 
 --- Computes see invisible
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 9539ee7131bcf96b1b58047d64284beb389fcf71..2e558872e0a2a5469c3d6a2cebaa8ff26645984f 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -1639,9 +1639,8 @@ newDamageType{
 		if target and (target:attr("undead") or target.retch_heal) then
 			target:heal(dam * 1.5)
 
-			if src.getTalentLevel then
-				local tl = src:getTalentLevel(src.T_RETCH)
-				if rng.percent(tl * 5) then
+			if src.callTalent then
+				if rng.percent(src:callTalent(src.T_RETCH, "getPurgeChance")) then
 					local effs = {}
 					local status = "detrimental"
 					for eff_id, p in pairs(target.tmp) do
@@ -1659,9 +1658,8 @@ newDamageType{
 		elseif target then
 			DamageType:get(DamageType.BLIGHT).projector(src, x, y, DamageType.BLIGHT, dam)
 
-			if src.getTalentLevel then
-				local tl = src:getTalentLevel(src.T_RETCH)
-				if rng.percent(tl * 5) then
+			if src.callTalent then
+				if rng.percent(src:callTalent(src.T_RETCH, "getPurgeChance")) then
 					local effs = {}
 					local status = "beneficial"
 					for eff_id, p in pairs(target.tmp) do
diff --git a/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua b/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua
index 19cef2f6867606667b65d0602b24f4a7ccda6f1b..83f0f47c15a93de96c70722f791acb84019117dd 100644
--- a/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua
+++ b/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua
@@ -49,7 +49,7 @@ defineTile('S', "FLOOR", nil, mod.class.NPC.new{
 	faction = "sunwall", hard_faction = "sunwall",
 	body = { INVEN = 10, MAINHAND=1, OFFHAND=1, BODY=1 },
 	desc = [[A Human in shining plate armour.]],
-	level_range = {10, 50}, exp_worth = 1,
+	level_range = {10, nil}, exp_worth = 1,
 	rank = 2,
 	size_category = 3,
 	autolevel = "warriormage",
@@ -64,14 +64,14 @@ defineTile('S', "FLOOR", nil, mod.class.NPC.new{
 		{type="armor", subtype="massive", autoreq=true},
 	},
 	resolvers.talents{
-		[Talents.T_ARMOUR_TRAINING]=3,
-		[Talents.T_CHANT_OF_FORTRESS]=3,
-		[Talents.T_SEARING_LIGHT]=2,
-		[Talents.T_MARTYRDOM]=2,
-		[Talents.T_WEAPON_OF_LIGHT]=2,
-		[Talents.T_FIREBEAM]=2,
-		[Talents.T_WEAPON_COMBAT]=4,
-		[Talents.T_HEALING_LIGHT]=2,
+		[Talents.T_ARMOUR_TRAINING]={base = 3, every = 4, max = 10},
+		[Talents.T_CHANT_OF_FORTRESS]={base = 1, every = 6, max = 5},
+		[Talents.T_SEARING_LIGHT]={base = 1, every = 5, max = 5},
+		[Talents.T_MARTYRDOM]={base=2, every= 7, max = 5},
+		[Talents.T_WEAPON_OF_LIGHT]={base = 1, every = 5, max = 5},
+		[Talents.T_FIREBEAM]={base = 5, every = 7, max = 5},
+		[Talents.T_WEAPON_COMBAT]={base = 4, every = 7, max = 5},
+		[Talents.T_HEALING_LIGHT]={base = 2, every = 7, max = 5},
 	},
 	on_added = function(self)
 		self.energy.value = game.energy_to_act self:useTalent(self.T_WEAPON_OF_LIGHT)
diff --git a/game/modules/tome/data/talents/cunning/lethality.lua b/game/modules/tome/data/talents/cunning/lethality.lua
index 33a30ff20fc6a844b69639b777a24737b6723019..d442e5678f407fee8f5bae167b0700305dd81f53 100644
--- a/game/modules/tome/data/talents/cunning/lethality.lua
+++ b/game/modules/tome/data/talents/cunning/lethality.lua
@@ -24,6 +24,7 @@ newTalent{
 	points = 5,
 	require = cuns_req1,
 	critpower = function(self, t) return self:combatTalentScale(t, 7.5, 25, 0.75) end,
+	-- called by _M:combatCrit in mod.class.interface.Combat.lua
 	getCriticalChance = function(self, t) return self:combatTalentScale(t, 2.3, 7.5, 0.75) end,
 	passives = function(self, t, p)
 		self:talentTemporaryValue(p, "combat_critical_power", t.critpower(self, t))
diff --git a/game/modules/tome/data/talents/cursed/gestures.lua b/game/modules/tome/data/talents/cursed/gestures.lua
index fa5fa98cdabaa59c29a2b118915e3576fc59cdda..d36fe3fe0fa725a34d9a73705b5b9521635a2811 100644
--- a/game/modules/tome/data/talents/cursed/gestures.lua
+++ b/game/modules/tome/data/talents/cursed/gestures.lua
@@ -79,7 +79,7 @@ newTalent{
 	getStunChance = function(self, t) return self:combatTalentLimit(t, 50, 12, 20) end, -- Limit < 50%
 	preAttack = function(self, t, target)
 		if not canUseGestures(self) then
-			game.logPlayer(self, "You do not have a free or mindstar-equipped hand to use Gesture of Pain.")
+			game.logPlayer(self, "You require two free or mindstar-equipped hands to use Gesture of Pain.")
 			return false
 		end
 
@@ -87,7 +87,6 @@ newTalent{
 	end,
 	attack = function(self, t, target)
 		local hit = false
-
 		local mindpower = self:combatMindpower()
 		local baseDamage = t.getBaseDamage(self, t)
 		local bonusDamage = t.getBonusDamage(self, t)
diff --git a/game/modules/tome/data/talents/misc/horrors.lua b/game/modules/tome/data/talents/misc/horrors.lua
index 03bbc1894ae086e7bd0acfefa1e1c9420110754b..44d945f5baa87a6cdd26edc40ba95e78b274c41b 100644
--- a/game/modules/tome/data/talents/misc/horrors.lua
+++ b/game/modules/tome/data/talents/misc/horrors.lua
@@ -41,7 +41,7 @@ newTalent{
 	on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_FRENZY) then return false end return true end,
 	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1, 1.7) end,
 	getBleedDamage = function(self, t) return self:combatTalentWeaponDamage(t, 1.5, 3) end,
-	getHealingPenalty = function(self, t) return self:getTalentLevel(t) * 10 end,
+	getHealingPenalty = function(self, t) return self:combatTalentLimit(t, 100, 15, 50) end, -- Limit to <100%
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -73,7 +73,7 @@ newTalent{
 	tactical = { CLOSEIN = 3 },
 	direct_hit = true,
 	message = "@Source@ leaps forward in a frenzy!",
-	range = function(self, t) return math.floor(2 + self:getTalentLevel(t)) end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 2.4, 10)) end,
 	requires_target = true,
 	on_pre_use = function(self, t, silent) if not self:hasEffect(self.EFF_FRENZY) or self:attr("encased_in_ice") or self:attr("never_move") then return false end return true end,
 	action = function(self, t)
@@ -119,7 +119,9 @@ newTalent{
 	tactical = { ATTACK = { PHYSICAL = 2 } },
 	getDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.5, 1) end,
 	getBleedDamage = function(self, t) return self:combatTalentWeaponDamage(t, 0.8, 1.5) end,
-	getPower = function(self, t) return self:combatTalentStatDamage(t, "con", 10, 50) end,
+	-- Limit crit, speed increase, -health to <100%
+	getPower = function(self, t) return self:combatLimit(self:combatTalentStatDamage(t, "con", 10, 50), 1, 0, 0, 0.357, 35.7) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	do_devourer_frenzy = function(self, target, t)
 		game.logSeen(self, "The scent of blood sends the %ss into a frenzy!", self.name:capitalize())
 		-- frenzy devourerers
@@ -140,7 +142,7 @@ newTalent{
 							reapplied = true
 						end
 					end
-					target:setEffect(target.EFF_FRENZY, math.floor(2 + self:getTalentLevel(t)), {crit = t.getPower(self, t), power=t.getPower(self, t)/50, dieat=t.getPower(self, t)/50}, reapplied)
+					target:setEffect(target.EFF_FRENZY, t.getDuration(self, t), {crit = t.getPower(self, t)*100, power=t.getPower(self, t), dieat=t.getPower(self, t)}, reapplied)
 				end
 			end
 		end)
@@ -164,8 +166,11 @@ newTalent{
 	info = function(self, t)
 		local damage = t.getDamage(self, t) * 100
 		local bleed = t.getBleedDamage(self, t) * 100
+		local power = t.getPower(self, t) *100
 		return ([[Bites the target for %d%% weapon damage, potentially causing it to bleed for %d%% weapon damage over five turns.
-		If the target is affected by the bleed it will send the devourer into a frenzy (which in turn will frenzy nearby devourers).]]):format(damage, bleed)
+		If the target is affected by the bleed it will send the devourer into a frenzy for %d turns (which in turn will frenzy other nearby devourers).
+		The frenzy will increase global speed by %d%%, physical crit chance by %d%%, and prevent death until -%d%% life.]]):
+		format(damage, bleed, t.getDuration(self, t), power, power, power)
 	end,
 }
 
@@ -187,8 +192,8 @@ newTalent{
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 40) end,
 	getDarknessPower = function(self, t) return self:combatTalentSpellDamage(t, 15, 40) end,
-	getLiteReduction = function(self, t) return self:getTalentLevelRaw(t) end,
-	getDuration = function(self, t) return 4 + math.ceil(self:getTalentLevel(t)) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
+	getLiteReduction = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -270,7 +275,7 @@ newTalent{
 	is_summon = true,
 	getDamage = function(self, t) return self:combatTalentMindDamage(t, 5, 50) end,
 	getExplosion = function(self, t) return self:combatTalentMindDamage(t, 20, 200) end,
-	getSummonTime = function(self, t) return 6 + math.ceil(self:getTalentLevel(t)) end,
+	getSummonTime = function(self, t) return math.floor(self:combatTalentScale(t, 7, 11)) end,
 	action = function(self, t)
 		local tg = {type="bolt", nowarning=true, range=self:getTalentRange(t), nolock=true, talent=t}
 		local tx, ty, target = self:getTarget(tg)
@@ -356,7 +361,7 @@ newTalent{
 	tactical = { ATTACK = { ACID = 1, BLIGHT = 1 }, DISABLE = 4 },
 	getBurstDamage = function(self, t) return self:combatTalentSpellDamage(t, 30, 300) end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 50) end,
-	getDuration = function(self, t) return 4 + math.ceil(self:getTalentLevel(t)) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
 	proj_speed = 6,
 	spawn_carrion_worm = function (self, target, t)
 		local x, y = util.findFreeGrid(target.x, target.y, 10, true, {[Map.ACTOR]=true})
@@ -419,7 +424,9 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false}
 	end,
 	getDamage = function(self, t) return self:combatTalentMindDamage(t, 5, 85) end,
-	getDuration = function(self, t) return 3 + self:combatMindpower(0.05) + self:getTalentLevel(t)/2 end,
+	getDuration = function(self, t) return
+		math.floor(self:combatScale(self:combatMindpower(0.05) + self:getTalentLevel(t)/2, 3, 0, 8.33, 5.33))
+	end,
 	action = function(self, t)
 		-- Add a lasting map effect
 		game.level.map:addEffect(self,
@@ -441,7 +448,7 @@ newTalent{
 	info = function(self, t)
 		local damage = t.getDamage(self, t)
 		local duration = t.getDuration(self, t)
-		return ([[Summon a storm of swirling blades to slice your foes, inflicting physical damage and bleeding to anyone who approaches.
+		return ([[Summon a storm of swirling blades to slice your foes, inflicting %d physical damage and bleeding to anyone who approaches for %d turns.
 		The damage and duration will increase with your Mindpower.]]):format(damDesc(self, DamageType.PHYSICAL, damage), duration)
 	end,
 }
@@ -567,7 +574,7 @@ newTalent{
 	tactical = { DISABLE = 1, CLOSEIN = 3 },
 	requires_target = true,
 	getDamage = function(self, t) return self:mindCrit(self:combatTalentMindDamage(t, 5, 50)) end,
-	getDuration = function(self, t) return 2 + math.ceil(self:getTalentLevel(t)) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
@@ -640,9 +647,7 @@ newTalent{
 	cooldown = 20,
 	tactical = { CLOSEIN = 2 },
 	requires_target = true,
-	range = function(self, t)
-		return 5 + self:getTalentLevel(t)
-	end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	radius = function(self, t)
 		return 1-- util.bound(4 - self:getTalentLevel(t) / 2, 1, 4)
 	end,
diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua
index cd3834446e65f901985694fc4f038fa16e632f57..0c10a3d2c657c31f7a9f18fac1bc73298f8fdaab 100644
--- a/game/modules/tome/data/talents/misc/npcs.lua
+++ b/game/modules/tome/data/talents/misc/npcs.lua
@@ -82,18 +82,20 @@ newTalent{
 	range = 1,
 	requires_target = true,
 	tactical = { ATTACK = { NATURE = 1, poison = 1} },
+	getMult = function(self, t) return self:combatTalentScale(t, 3, 7, "log") end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
 		if not x or not y or not target then return nil end
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		self.combat_apr = self.combat_apr + 1000
-		self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true)
+		self:attackTarget(target, DamageType.POISON, t.getMult(self, t), true)
 		self.combat_apr = self.combat_apr - 1000
 		return true
 	end,
 	info = function(self, t)
-		return ([[Crawl onto the target, covering it in poison.]])
+		return ([[Crawl onto the target, doing %d%% damage and covering it in poison.]]):
+		format(100*t.getMult(self, t))
 	end,
 }
 
@@ -132,6 +134,7 @@ newTalent{
 	range = 1,
 	tactical = { DISABLE = { blind = 2 } },
 	requires_target = true,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -139,10 +142,10 @@ newTalent{
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		local hit = self:attackTarget(target, DamageType.LIGHT, self:combatTalentWeaponDamage(t, 1, 1.8), true)
 
-		-- Try to stun !
+		-- Try to blind !
 		if hit then
 			if target:canBe("blind") then
-				target:setEffect(target.EFF_BLINDED, math.ceil(5 + self:getTalentLevel(t)), {apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_BLINDED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the blindness blow!", target.name:capitalize())
 			end
@@ -151,7 +154,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Releases blinding spores at the target.]])
+		return ([[Releases stinging spores at the target, blinding it for %d turns.]]):
+		format(t.getDuration(self, t))
 	end,
 }
 
@@ -165,18 +169,20 @@ newTalent{
 	range = 1,
 	tactical = { ATTACK = { NATURE = 1, poison = 1} },
 	requires_target = true,
+	getMult = function(self, t) return self:combatTalentScale(t, 3, 7, "log") end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
 		if not x or not y or not target then return nil end
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		self.combat_apr = self.combat_apr + 1000
-		self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true)
+		self:attackTarget(target, DamageType.POISON, t.getMult(self, t), true)
 		self.combat_apr = self.combat_apr - 1000
 		return true
 	end,
 	info = function(self, t)
-		return ([[Releases poisonous spores at the target.]])
+		return ([[Releases poisonous spores at the target, doing %d%% damage and poisoning it.]]):
+		format(100 * t.getMult(self, t))
 	end,
 }
 
@@ -189,6 +195,7 @@ newTalent{
 	require = { stat = { str=12 }, },
 	tactical = { ATTACK = { PHYSICAL = 1 }, DISABLE = { stun = 2 } },
 	requires_target = true,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -199,7 +206,7 @@ newTalent{
 		-- Try to stun !
 		if hit then
 			if target:canBe("stun") then
-				target:setEffect(target.EFF_STUNNED, 2 + self:getTalentLevel(t), {apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_STUNNED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the stunning blow!", target.name:capitalize())
 			end
@@ -208,7 +215,9 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage. If the attack hits, the target is stunned. The chance to stun improves with your Physical Power.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage. If the attack hits, the target is stunned for %d turns.
+		The chance to stun improves with your Physical Power.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1), t.getDuration(self, t))
 	end,
 }
 
@@ -221,6 +230,7 @@ newTalent{
 	require = { stat = { str=12 }, },
 	requires_target = true,
 	tactical = { ATTACK = { PHYSICAL = 1 }, DISABLE = { disarm = 2 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -229,7 +239,7 @@ newTalent{
 		local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true)
 
 		if hit and target:canBe("disarm") then
-			target:setEffect(target.EFF_DISARMED, 2 + self:getTalentLevel(t), {apply_power=self:combatPhysicalpower()})
+			target:setEffect(target.EFF_DISARMED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()})
 			target:crossTierEffect(target.EFF_DISARMED, self:combatPhysicalpower())
 		else
 			game.logSeen(target, "%s resists the blow!", target.name:capitalize())
@@ -238,7 +248,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage and trying to disarm the target. The chance improves with your Physical Power.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage and trying to disarm the target for %d turns. The chance improves with your Physical Power.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1), t.getDuration(self, t))
 	end,
 }
 
@@ -251,6 +262,7 @@ newTalent{
 	require = { stat = { str=12 }, },
 	requires_target = true,
 	tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = { stun = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 15, 52)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -258,10 +270,10 @@ newTalent{
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.5, 1), true)
 
-		-- Try to stun !
+		-- Try to constrict !
 		if hit then
 			if target:canBe("pin") then
-				target:setEffect(target.EFF_CONSTRICTED, (2 + self:getTalentLevel(t)) * 10, {src=self, power=1.5 * self:getTalentLevel(t), apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_CONSTRICTED, t.getDuration(self, t), {src=self, power=1.5 * self:getTalentLevel(t), apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the constriction!", target.name:capitalize())
 			end
@@ -270,7 +282,9 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage. If the attack hits, the target is constricted. The constriction power improves with your Physical Power.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage. If the attack hits, the target is constricted for %d turns.
+		The constriction power improves with your Physical Power.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1), t.getDuration(self, t))
 	end,
 }
 
@@ -317,16 +331,17 @@ newTalent{
 	range = 1,
 	tactical = { ATTACK = { NATURE = 1, poison = 1} },
 	requires_target = true,
+	getMult = function(self, t) return self:combatTalentScale(t, 3, 7, "log") end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
 		if not x or not target then return nil end
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
-		self:attackTarget(target, DamageType.POISON, 2 + self:getTalentLevel(t), true)
+		self:attackTarget(target, DamageType.POISON, t.getMult(self, t), true)
 		return true
 	end,
 	info = function(self, t)
-		return ([[Bites the target, infecting it with poison.]])
+		return ([[Bites the target, doing %d%% damage and injecting it with poison.]]):format(100 * t.getMult(self, t))
 	end,
 }
 
@@ -401,6 +416,7 @@ newTalent{
 	message = "@Source@ diseases @target@.",
 	requires_target = true,
 	tactical = { ATTACK = { BLIGHT = 2 }, DISABLE = { disease = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 13, 25)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -411,7 +427,7 @@ newTalent{
 		-- Try to rot !
 		if hit then
 			if target:canBe("disease") then
-				target:setEffect(target.EFF_ROTTING_DISEASE, 10 + self:getTalentLevel(t) * 3, {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, con=math.floor(4 + target:getCon() * 0.1), apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_ROTTING_DISEASE, t.getDuration(self, t), {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, con=math.floor(4 + target:getCon() * 0.1), apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
 			end
@@ -420,7 +436,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage. If the attack hits, the target is diseased.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage. If the attack hits, the target is afflicted with a disease, inflicting %d blight damage per turn for %d turns and reducing constitution.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1),damDesc(self, DamageType.BLIGHT,self:getStr() / 3 + self:getTalentLevel(t) * 2),t.getDuration(self, t))
 	end,
 }
 
@@ -432,6 +449,7 @@ newTalent{
 	message = "@Source@ diseases @target@.",
 	tactical = { ATTACK = { BLIGHT = 2 }, DISABLE = { disease = 1 } },
 	requires_target = true,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 13, 25)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -442,7 +460,7 @@ newTalent{
 		-- Try to rot !
 		if hit then
 			if target:canBe("disease") then
-				target:setEffect(target.EFF_DECREPITUDE_DISEASE, 10 + self:getTalentLevel(t) * 3, {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, dex=math.floor(4 + target:getDex() * 0.1), apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_DECREPITUDE_DISEASE, t.getDuration(self, t), {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, dex=math.floor(4 + target:getDex() * 0.1), apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
 			end
@@ -451,7 +469,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage. If the attack hits, the target is diseased.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage. If the attack hits, the target is afflicted with a disease, inflicting %d blight damage per turn for %d turns and reducing dexterity.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1),damDesc(self, DamageType.BLIGHT,self:getStr() / 3 + self:getTalentLevel(t) * 2),t.getDuration(self, t))
 	end,
 }
 
@@ -463,6 +482,7 @@ newTalent{
 	message = "@Source@ diseases @target@.",
 	requires_target = true,
 	tactical = { ATTACK = { BLIGHT = 2 }, DISABLE = { disease = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 13, 25)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -473,7 +493,7 @@ newTalent{
 		-- Try to rot !
 		if hit then
 			if target:canBe("disease") then
-				target:setEffect(target.EFF_WEAKNESS_DISEASE, 10 + self:getTalentLevel(t) * 3, {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, str=math.floor(4 + target:getStr() * 0.1), apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_WEAKNESS_DISEASE, t.getDuration(self, t), {src=self, dam=self:getStr() / 3 + self:getTalentLevel(t) * 2, str=math.floor(4 + target:getStr() * 0.1), apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
 			end
@@ -482,7 +502,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage. If the attack hits, the target is diseased.]]):format(100 * self:combatTalentWeaponDamage(t, 0.5, 1))
+		return ([[Hits the target doing %d%% damage. If the attack hits, the target is afflicted with a disease, inflicting %d blight damage per turn for %d turns and reducing strength.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 0.5, 1),damDesc(self, DamageType.BLIGHT,self:getStr() / 3 + self:getTalentLevel(t) * 2),t.getDuration(self, t))
 	end,
 }
 
@@ -496,15 +517,16 @@ newTalent{
 	direct_hit = true,
 	requires_target = true,
 	tactical = { DISABLE = { confusion = 3 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.CONFUSION, {dur=2+self:getTalentLevel(t), dam=50+self:getTalentLevelRaw(t)*10}, {type="manathrust"})
+		self:project(tg, x, y, DamageType.CONFUSION, {dur=t.getDuration(self, t), dam=50+self:getTalentLevelRaw(t)*10}, {type="manathrust"})
 		return true
 	end,
 	info = function(self, t)
-		return ([[Try to confuse the target's mind for a while.]])
+		return ([[Try to confuse the target's mind for %d turns.]]):format(t.getDuration(self, t))
 	end,
 }
 
@@ -518,17 +540,19 @@ newTalent{
 	reflectable = true,
 	tactical = { ATTACK = { COLD = 1 } },
 	requires_target = true,
+	getDamage = function(self, t) return self:combatScale(self:combatSpellpower() * self:getTalentLevel(t), 12, 0, 78.25, 265, 0.67) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.COLD, self:spellCrit(12 + self:combatSpellpower(0.25) * self:getTalentLevel(t)), {type="freeze"})
+		self:project(tg, x, y, DamageType.COLD, self:spellCrit(t.getDamage(self, t)), {type="freeze"})
 		game:playSoundNear(self, "talents/ice")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Condenses ambient water on a target, damaging it for %0.2f.
-		The damage will increase with your Spellpower.]]):format(12 + self:combatSpellpower(0.25) * self:getTalentLevel(t))
+		return ([[Condenses ambient water on a target, inflicting %0.1f cold damage.
+		The damage will increase with your Spellpower.]]):
+		format(damDesc(self, DamageType.COLD,t.getDamage(self, t)))
 	end,
 }
 
@@ -543,17 +567,19 @@ newTalent{
 	reflectable = true,
 	requires_target = true,
 	tactical = { DISABLE = { stun = 2 }, ATTACK = { COLD = 1 } },
+	getDamage = function(self, t) return self:combatScale(self:combatSpellpower() * self:getTalentLevel(t), 12, 0, 65, 265, 0.67) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.COLDSTUN, self:spellCrit(12 + self:combatSpellpower(0.20) * self:getTalentLevel(t)), {type="freeze"})
+		self:project(tg, x, y, DamageType.COLDSTUN, self:spellCrit(t.getDamage(self, t)), {type="freeze"})
 		game:playSoundNear(self, "talents/ice")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Condenses ambient water on a target, damaging it for %0.2f and stunning it for 4 turns.
-		The damage will increase with your Spellpower]]):format(12 + self:combatSpellpower(0.20) * self:getTalentLevel(t))
+		return ([[Condenses ambient water on a target, inflicting %0.1f cold damage and stunning it for 4 turns.
+		The damage will increase with your Spellpower]]):
+		format(damDesc(self, DamageType.COLD,t.getDamage(self, t)))
 	end,
 }
 
@@ -597,7 +623,7 @@ newTalent{
 		end
 		return nb
 	end },
-	getCureCount = function(self, t) return math.floor(self:getTalentLevel(t)) end,
+	getCureCount = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
 	action = function(self, t)
 		local target = self
 		local effs = {}
@@ -660,6 +686,7 @@ newTalent{
 	require = { stat = { str=12 }, },
 	requires_target = true,
 	tactical = { DISABLE = { pin = 2 }, ATTACK = { PHYSICAL = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
 		local x, y, target = self:getTarget(tg)
@@ -667,10 +694,10 @@ newTalent{
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		local hit = self:attackTarget(target, nil, self:combatTalentWeaponDamage(t, 0.8, 1.4), true)
 
-		-- Try to stun !
+		-- Try to pin !
 		if hit then
 			if target:canBe("pin") then
-				target:setEffect(target.EFF_PINNED, 1 + self:getTalentLevel(t), {apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_PINNED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the grab!", target.name:capitalize())
 			end
@@ -679,7 +706,7 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target doing %d%% damage; if the attack hits, the target is pinned to the ground.]]):format(100 * self:combatTalentWeaponDamage(t, 0.8, 1.4))
+		return ([[Hits the target doing %d%% damage; if the attack hits, the target is pinned to the ground for %d turns.]]):format(100 * self:combatTalentWeaponDamage(t, 0.8, 1.4), t.getDuration(self, t))
 	end,
 }
 
@@ -691,17 +718,13 @@ newTalent{
 	cooldown = 12,
 	message = "@Source@ projects ink!",
 	range = 0,
-	radius = function(self, t)
-		return 4 + self:getTalentLevelRaw(t)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
 	direct_hit = true,
 	requires_target = true,
 	target = function(self, t)
 		return {type="cone", range=self:getTalentRadius(t), radius=self:getTalentRadius(t), selffire=false, talent=t}
 	end,
-	getDuration = function(self, t)
-		return 2 + self:getTalentLevelRaw(t)
-	end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	tactical = { DISABLE = { blind = 2 } },
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
@@ -727,19 +750,22 @@ newTalent{
 	range = 10,
 	requires_target = true,
 	tactical = { ATTACK = { NATURE = 1, poison = 1} },
+	getDamage = function(self, t)
+		return self:combatScale(math.max(self:getStr(), self:getDex())*self:getTalentLevel(t), 20, 0, 420, 500)
+	end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t)}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		local s = math.max(self:getDex(), self:getStr())
-		self:project(tg, x, y, DamageType.POISON, 20 + (s * self:getTalentLevel(t)) * 0.8, {type="slime"})
+		self:project(tg, x, y, DamageType.POISON, t.getDamage(self,t), {type="slime"})
 		game:playSoundNear(self, "talents/slime")
 		return true
 	end,
 	info = function(self, t)
-		local s = math.max(self:getDex(), self:getStr())
 		return ([[Spit poison at your target, doing %0.2f poison damage over six turns.
-		The damage will increase with your Strength or Dexterity (whichever is higher).]]):format(20 + (s * self:getTalentLevel(t)) * 0.8)
+		The damage will increase with your Strength or Dexterity (whichever is higher).]]):
+		format(damDesc(self, DamageType.POISON, t.getDamage(self,t)))
 	end,
 }
 
@@ -752,17 +778,20 @@ newTalent{
 	range = 10,
 	requires_target = true,
 	tactical = { ATTACK = { BLIGHT = 2 } },
+	getDamage = function(self, t)
+		return self:combatScale(self:getMag()*self:getTalentLevel(t), 20, 0, 420, 500)
+	end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t)}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.BLIGHT, 20 + (self:getMag() * self:getTalentLevel(t)) * 0.8, {type="slime"})
+		self:project(tg, x, y, DamageType.BLIGHT, t.getDamage(self,t), {type="slime"})
 		game:playSoundNear(self, "talents/slime")
 		return true
 	end,
 	info = function(self, t)
 		return ([[Spit blight at your target doing %0.2f blight damage.
-		The damage will increase with your Magic.]]):format(20 + (self:getMag() * self:getTalentLevel(t)) * 0.8)
+		The damage will increase with your Magic.]]):format(t.getDamage(self,t))
 	end,
 }
 
@@ -775,7 +804,7 @@ newTalent{
 	cooldown = 15,
 	tactical = { DISABLE = 2, CLOSEIN = 3 },
 	requires_target = true,
-	range = function(self, t) return math.floor(5 + self:getTalentLevelRaw(t)) end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	action = function(self, t)
 		if self:attr("never_move") then game.logPlayer(self, "You cannot do that currently.") return end
 
@@ -827,17 +856,19 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
 	end,
 	tactical = { ATTACK = { PHYSICAL = 2 } },
+	getDamage = function(self, t) return self:combatScale(self:getStr()*self:getTalentLevel(t), 20, 0, 420, 500) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.BLEED, 20 + (self:getStr() * self:getTalentLevel(t)) * 0.8, {type="archery"})
+		self:project(tg, x, y, DamageType.BLEED, t.getDamage(self, t), {type="archery"})
 		game:playSoundNear(self, "talents/earth")
 		return true
 	end,
 	info = function(self, t)
 		return ([[Throws a pack of bones at your target doing %0.2f physical damage as bleeding.
-		The damage will increase with the Strength stat]]):format(20 + (self:getStr() * self:getTalentLevel(t)) * 0.8)
+		The damage will increase with the Strength stat]]):
+		format(damDesc(self, DamageType.PHYSICAL, t.getDamage(self, t)))
 	end,
 }
 
@@ -851,16 +882,19 @@ newTalent{
 	range = 10,
 	requires_target = true,
 	tactical = { DISABLE = { stun = 1, pin = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local dur = 2 + self:getTalentLevel(t)
 		local trap = mod.class.Trap.new{
 			type = "web", subtype="web", id_by_type=true, unided_name = "sticky web",
 			display = '^', color=colors.YELLOW, image = "trap/trap_spiderweb_01_64.png",
 			name = "sticky web", auto_id = true,
-			detect_power = 6 * self:getTalentLevel(t), disarm_power = 10 * self:getTalentLevel(t),
+			detect_power = 6 * self:getTalentLevel(t), disarm_power = 10 * self:getTalentLevel(t), --Trap Params
 			level_range = {self.level, self.level},
 			message = "@Target@ is caught in a web!",
 			pin_dur = dur,
+			temporary = dur * 5,
+			summoner = self,
 			faction = false,
 			canTrigger = function(self, x, y, who)
 				if who.type == "spiderkin" then return false end
@@ -879,7 +913,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Lay an invisible web under you, trapping all non-spiderkin that pass.]]):format()
+		return ([[Lay an invisible web under you, pinning all non-spiderkin that pass for %d turns.]]):
+		format(t.getDuration(self, t))
 	end,
 }
 
@@ -890,21 +925,20 @@ newTalent{
 	equilibrium = 4,
 	cooldown = 6,
 	range = 0,
-	radius = function(self, t)
-		return 2 + self:getTalentLevelRaw(t) / 1.5
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2.7, 5,3)) end,
 	direct_hit = true,
 	tactical = { DISABLE = 3 },
 	requires_target = true,
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t}
 	end,
+	darkPower = function(self, t) return self:combatTalentScale(t, 10, 50) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
 		self:project(tg, x, y, function(px, py)
-			local g = engine.Entity.new{name="darkness", show_tooltip=true, block_sight=true, always_remember=false, unlit=self:getTalentLevel(t) * 10}
+			local g = engine.Entity.new{name="darkness", show_tooltip=true, block_sight=true, always_remember=false, unlit=t.darkPower(self, t)}
 			game.level.map(px, py, Map.TERRAIN+1, g)
 			game.level.map.remembers(px, py, false)
 			game.level.map.lites(px, py, false)
@@ -914,8 +948,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Weave darkness, blocking all light but the most powerful and teleporting you a short range.
-		The damage will increase with the Dexterity stat]]):format(20 + (self:getDex() * self:getTalentLevel(t)) * 0.3)
+		return ([[Weave darkness (power %d) in a radius of %d, blocking all light but the most powerful and teleporting you a short range.]]):
+		format(t.darkPower(self, t), self:getTalentRadius(t))
 	end,
 }
 
@@ -933,17 +967,19 @@ newTalent{
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), talent=t}
 	end,
+	getDam = function(self, t) return self:combatScale(self:getStr() * self:getTalentLevel(t), 12, 0, 262, 500) end,
+	getDist = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.PHYSKNOCKBACK, {dist=3+self:getTalentLevelRaw(t), dam=self:mindCrit(12 + self:getStr(50, true) * self:getTalentLevel(t))}, {type="archery"})
+		self:project(tg, x, y, DamageType.PHYSKNOCKBACK, {dist=t.getDist(self, t), dam=self:mindCrit(t.getDam(self, t))}, {type="archery"})
 		game:playSoundNear(self, "talents/ice")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Throws a huge boulder at a target, damaging it for %0.2f and knocking it back.
-		The damage will increase with your Strength.]]):format(12 + self:getStr(50, true) * self:getTalentLevel(t))
+		return ([[Throws a huge boulder at a target, damaging it for %0.2f and knocking it back %d tiles.
+		The damage will increase with your Strength.]]):format(damDesc(self, DamageType.PHYSICAL, t.getDam(self, t)), t.getDist(self, t))
 	end,
 }
 
@@ -957,8 +993,9 @@ newTalent{
 	range = 10,
 	tactical = { ATTACK = 3 },
 	direct_hit = true,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	action = function(self, t)
-		local rad = self:getTalentLevel(t) + 5
+		local rad = self:getTalentRadius(t)
 		for i = self.x - rad, self.x + rad do for j = self.y - rad, self.y + rad do if game.level.map:isBound(i, j) then
 			local actor = game.level.map(i, j, game.level.map.ACTOR)
 			if actor and not actor.player then
@@ -975,7 +1012,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Howl to call your hunting pack.]])
+		return ([[Howl (radius %d) to call your hunting pack.]]):
+		format(self:getTalentRadius(t))
 	end,
 }
 
@@ -989,8 +1027,9 @@ newTalent{
 	range = 10,
 	direct_hit = true,
 	tactical = { ATTACK = 3 },
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 6, 10)) end,
 	action = function(self, t)
-		local rad = self:getTalentLevel(t) + 5
+		local rad = self:getTalentRadius(t)
 		for i = self.x - rad, self.x + rad do for j = self.y - rad, self.y + rad do if game.level.map:isBound(i, j) then
 			local actor = game.level.map(i, j, game.level.map.ACTOR)
 			if actor and not actor.player then
@@ -1007,7 +1046,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Shriek to call your allies.]])
+		return ([[Shriek (radius %d) to call your allies.]]):
+		format(self:getTalentRadius(t))
 	end,
 }
 
@@ -1020,6 +1060,7 @@ newTalent{
 	stamina = 12,
 	requires_target = true,
 	tactical = { ATTACK = { PHYSICAL = 1 }, DISABLE = { stun = 2 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local weapon = self:hasTwoHandedWeapon()
 		if not weapon then
@@ -1033,10 +1074,10 @@ newTalent{
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
 		local speed, hit = self:attackTargetWith(target, weapon.combat, nil, self:combatTalentWeaponDamage(t, 1, 1.4))
 
-		-- Try to stun !
+		-- Try to pin !
 		if hit then
 			if target:canBe("pin") then
-				target:setEffect(target.EFF_PINNED, 2 + self:getTalentLevel(t), {apply_power=self:combatPhysicalpower()})
+				target:setEffect(target.EFF_PINNED, t.getDuration(self, t), {apply_power=self:combatPhysicalpower()})
 			else
 				game.logSeen(target, "%s resists the crushing!", target.name:capitalize())
 			end
@@ -1045,7 +1086,8 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Hits the target with a mighty blow to the legs doing %d%% weapon damage. If the attack hits, the target is unable to move for %d turns.]]):format(100 * self:combatTalentWeaponDamage(t, 1, 1.4), 2+self:getTalentLevel(t))
+		return ([[Hits the target with a mighty blow to the legs doing %d%% weapon damage. If the attack hits, the target is unable to move for %d turns.]]):
+		format(100 * self:combatTalentWeaponDamage(t, 1, 1.4), t.getDuration(self, t))
 	end,
 }
 
@@ -1059,16 +1101,17 @@ newTalent{
 	direct_hit = true,
 	requires_target = true,
 	tactical = { DISABLE = { silence = 3 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
 		if not x or not y then return nil end
-		self:project(tg, x, y, DamageType.SILENCE, {dur=math.floor(4 + self:getTalentLevel(t))}, {type="mind"})
-		game:playSoundNear(self, "talents/spell_generic")
+		self:project(tg, x, y, DamageType.SILENCE, {dur=t.getDuration(self, t)}, {type="mind"})
 		return true
 	end,
 	info = function(self, t)
-		return ([[Sends a telepathic attack, silencing the target for %d turns.]]):format(math.floor(4 + self:getTalentLevel(t)))
+		return ([[Sends a telepathic attack, silencing the target for %d turns.]]):
+		format(t.getDuration(self, t))
 	end,
 }
 
@@ -1113,8 +1156,9 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
 	end,
 	tactical = { ATTACKAREA = { BLIGHT = 2 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
-		local duration = self:getTalentLevel(t) + 2
+		local duration = t.getDuration(self, t)
 		local dam = self:combatTalentSpellDamage(t, 4, 65)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -1133,8 +1177,9 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Corrupted vapour rises at the target location doing %0.2f blight damage every turn for %d turns.
-		The damage will increase with Magic stat.]]):format(self:combatTalentSpellDamage(t, 5, 65), self:getTalentLevel(t) + 2)
+		return ([[Corrupted vapour rises at the target location (radius 4) doing %0.2f blight damage every turn for %d turns.
+		The damage will increase with Magic stat.]]):
+		format(damDesc(self, engine.DamageType.BLIGHT, self:combatTalentSpellDamage(t, 5, 65)), t.getDuration(self, t))
 	end,
 }
 
@@ -1195,19 +1240,21 @@ newTalent{
 	range = 1,
 	requires_target = true,
 	tactical = { ATTACK = { LIGHT = 1 } },
+	getDamage = function(self, t) return self:combatScale(self:combatSpellpower() * self:getTalentLevel(t), 0, 0, 66.25 , 265, 0.67) end,
 	action = function(self, t)
 		local tg = {type="bolt", range=1}
 		local x, y, target = self:getTarget(tg)
 		if not x or not y then return nil end
 		if core.fov.distance(self.x, self.y, x, y) > 1 then return nil end
-		self:project(tg, x, y, DamageType.LIGHT, math.floor(self:combatSpellpower(0.25) * self:getTalentLevel(t)), {type="light"})
+		self:project(tg, x, y, DamageType.LIGHT, t.getDamage(self, t), {type="light"})
 		game.level.map:particleEmitter(self.x, self.y, 1, "ball_fire", {radius = 1, r = 1, g = 0, b = 0})
 		self:die(self)
 		game:playSoundNear(self, "talents/arcane")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Explodes in a blinding light.]])
+		return ([[Causes the user to explode (killing it) in a blinding light burst for %d damage.]]):
+		format(damDesc(self, DamageType.LIGHT, t.getDamage(self, t)))
 	end,
 }
 
@@ -1247,6 +1294,7 @@ newTalent{
 	proj_speed = 2,
 	requires_target = true,
 	tactical = { ATTACK = 2 },
+	getDamage = function(self, t) return self:combatScale(self:getMag() * self:getTalentLevel(t), 0, 0, 450, 500) end,
 	action = function(self, t)
 		local tg = {type = "bolt", range = 20, talent = t}
 		local x, y = self:getTarget(tg)
@@ -1262,13 +1310,14 @@ newTalent{
 				{DamageType.ARCANE, "manathrust"},
 				{DamageType.DARKNESS, "dark"},
 			}
-			tg.display={particle="bolt_elemental", trail="generictrail"}
-		self:projectile(tg, x, y, elem[1], math.floor(self:getMag(90, true) * self:getTalentLevel(t)), {type=elem[2]})
+		tg.display={particle="bolt_elemental", trail="generictrail"}
+		self:projectile(tg, x, y, elem[1], t.getDamage(self, t), {type=elem[2]})
 		game:playSoundNear(self, "talents/arcane")
 		return true
 	end,
 	info = function(self, t)
-		return ([[Fire a slow bolt of a random element. Damage raises with magic stat.]])
+		return ([[Fire a slow bolt of a random element for %d damage. Damage increases with the magic stat.]]):
+		format(t.getDamage(self, t))
 	end,
 }
 
@@ -1283,6 +1332,9 @@ newTalent{
 	proj_speed = 2,
 	requires_target = true,
 	tactical = { ATTACK = { FIRE = 1, PHYSICAL = 1 } },
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 5, 9)) end,
+	nbProj = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
+	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 80) end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t), nolock=true, talent=t}
 		local x, y = self:getTarget(tg)
@@ -1299,11 +1351,11 @@ newTalent{
 			name = "raging volcano", image = oe.image, add_mos = {{image = "terrain/lava/volcano_01.png"}},
 			display = '&', color=colors.LIGHT_RED, back_color=colors.RED,
 			always_remember = true,
-			temporary = 4 + self:getTalentLevel(t),
+			temporary = t.getDuration(self, t),
 			x = x, y = y,
 			canAct = false,
-			nb_projs = math.floor(self:getTalentLevel(self.T_VOLCANO)),
-			dam = self:combatTalentSpellDamage(self.T_VOLCANO, 15, 80),
+			nb_projs = t.nbProj(self, t),
+			dam = t.getDamage(self, t),
 			act = function(self)
 				local tgts = {}
 				local grids = core.fov.circle_grids(self.x, self.y, 5, true)
@@ -1342,9 +1394,10 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Summons a small raging volcano for %d turns. Every turn, it will fire %d molten boulders toward your foes, dealing %0.2f fire and %0.2f physical damage.
+		local dam = t.getDamage(self, t)
+		return ([[Summons a small raging volcano for %d turns. Every turn, it will fire a molten boulder towards up to %d of your foes, dealing %0.2f fire and %0.2f physical damage.
 		The damage will scale with your Spellpower.]]):
-		format(4 + self:getTalentLevel(t), math.floor(self:getTalentLevel(self.T_VOLCANO)), damDesc(self, DamageType.FIRE, self:combatTalentSpellDamage(self.T_VOLCANO, 15, 80) / 2), damDesc(self, DamageType.PHYSICAL, self:combatTalentSpellDamage(self.T_VOLCANO, 15, 80) / 2))
+		format(t.getDuration(self, t), t.nbProj(self, t), damDesc(self, DamageType.FIRE, dam/2), damDesc(self, DamageType.PHYSICAL, dam/2))
 	end,
 }
 
@@ -1395,11 +1448,12 @@ newTalent{
 	},
 	direct_hit = true,
 	range = 0,
-	radius = function(self, t) return 1 + self:getTalentLevelRaw(t) end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	target = function(self, t)
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=true, talent=t}
 	end,
-	getPower = function(self, t) return self:combatTalentSpellDamage(t, 10, 50) end,
+	getPower = function(self, t) return self:combatLimit(self:combatTalentSpellDamage(t, 10, 50), 1, 0, 0, 0.329, 32.9) end, -- Limit < 100
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		self:project(tg, self.x, self.y, function(px, py)
@@ -1418,7 +1472,7 @@ newTalent{
 							reapplied = true
 						end
 					end
-					target:setEffect(target.EFF_FRENZY, self:getTalentLevel(t), {crit = t.getPower(self, t)/10, power=t.getPower(self, t)/100, dieat=t.getPower(self, t)/100}, reapplied)
+					target:setEffect(target.EFF_FRENZY, t.getDuration(self, t), {crit = t.getPower(self, t)*100, power=t.getPower(self, t), dieat=t.getPower(self, t)}, reapplied)
 				end
 			end
 		end)
@@ -1429,8 +1483,11 @@ newTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Speeds up nearby Dredges.
-		]]):format()
+		local range = t.radius(self,t)
+		local power = t.getPower(self,t) * 100
+		return ([[Sends Dredges in a radius of %d into a frenzy for %d turns.
+		The frenzy will increase global speed by %d%%, physical crit chance by %d%%, and prevent death until -%d%% life.]]):		
+		format(range, t.getDuration(self, t), power, power, power)
 	end,
 }
 
@@ -1590,7 +1647,7 @@ newTalent{
 		return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t)}
 	end,
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 4, 50) end,
-	getDuration = function(self, t) return self:getTalentLevel(t) + 2 end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local tg = self:getTalentTarget(t)
 		local x, y = self:getTarget(tg)
@@ -1645,9 +1702,7 @@ newTalent{
 	cooldown = 20,
 	tactical = { ATTACK = { FIRE = 1 }, HEAL = 1, },
 	range = 0,
-	radius = function(self, t)
-		return 3 + self:getTalentLevelRaw(t)
-	end,
+	radius = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	requires_target = true,
 	target = function(self, t)
 		return {type="cone", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t}
@@ -1717,7 +1772,7 @@ newTalent{
 	mana = 70,
 	tactical = { ATTACKAREA = { FIRE=2, PHYSICAL=2 } },
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 15, 250) end,
-	getNb = function(self, t) return 3 + math.floor(self:getTalentLevel(t) / 3) end,
+	getNb = function(self, t) return math.floor(self:combatTalentScale(t, 3.3, 4.8, "log")) end,
 	radius = 2,
 	range = 5,
 	target = function(self, t) return {type="ball", range=self:getTalentRange(t), radius=self:getTalentRadius(t), selffire=false, talent=t} end,
@@ -1962,6 +2017,7 @@ newTalent{
 	range = 10,
 	tactical = { DISABLE = 1, CLOSEIN = 3 },
 	requires_target = true,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t), talent=t}
 		local x, y = self:getTarget(tg)
@@ -1976,16 +2032,16 @@ newTalent{
 			target:pull(self.x, self.y, tg.range)
 
 			DamageType:get(DamageType.COLD).projector(self, target.x, target.y, DamageType.COLD, dam)
-			target:setEffect(target.EFF_SLOW_MOVE, math.floor(3 + self:getTalentLevel(t)), {apply_power=self:combatSpellpower(), power=0.5})
+			target:setEffect(target.EFF_SLOW_MOVE, t.getDuration(self, t), {apply_power=self:combatSpellpower(), power=0.5})
 		end)
 		game:playSoundNear(self, "talents/arcane")
 
 		return true
 	end,
 	info = function(self, t)
-		return ([[Grab a target and teleport it to your side, covering it with frost, reducing its movement speed by 50%% for %d turns.
+		return ([[Grab a target and transport it next to you, covering it with frost, reducing its movement speed by 50%% for %d turns.
 		The ice will also deal %0.2f cold damage.
 		The damage will increase with your Spellpower.]]):
-		format(math.floor(3 + self:getTalentLevel(t)), damDesc(self, DamageType.COLD, self:combatTalentSpellDamage(t, 5, 140)))
+		format(t.getDuration(self, t), damDesc(self, DamageType.COLD, self:combatTalentSpellDamage(t, 5, 140)))
 	end,
 }
diff --git a/game/modules/tome/data/talents/misc/objects.lua b/game/modules/tome/data/talents/misc/objects.lua
index dbd0e9b4b4a9cbc0574b6749f1d951e52ab2db2f..71e876962dfaa30b44f7364ec6e6879cad5c8951 100644
--- a/game/modules/tome/data/talents/misc/objects.lua
+++ b/game/modules/tome/data/talents/misc/objects.lua
@@ -299,13 +299,17 @@ newTalent{
 	target = function(self, t)
 		return {type="hit", range=1, talent=t}
 	end,
+	getpower = function(self, t) return 8 end,
+	maxpower = function(self, t) return self:combatTalentLimit(t, 100, 45, 70) end, -- Limit spell failure < 100%
 	action = function(self, t)
 	self:getTalentLevel(t)
 		local tg = self:getTalentTarget(t)
 		local x, y, target = self:getTarget(tg)
 		if not x or not y then return nil end
+		dispower = t.getpower(self,t)
+		dismax = t.maxpower(self, t)
 		self:project(tg, x, y, function(px, py)
-			target:setEffect(target.EFF_SPELL_DISRUPTION, 8, {src=self, power = 8, max = 45+self:getTalentLevel(t)*5, apply_power=self:combatMindpower()})
+			target:setEffect(target.EFF_SPELL_DISRUPTION, 8, {src=self, power = dispower, max = dismax, apply_power=self:combatMindpower()})
 			if rng.percent(30) and self:getTalentLevel(t)>2 then
 
 			local effs = {}
@@ -335,18 +339,18 @@ newTalent{
 					end
 				end
 			end
-				if self:getTalentLevel(t)>4 then
-					if target.undead or target.construct then
-						self:project({type="hit"}, target.x, target.y, engine.DamageType.ARCANE, 40+self:combatMindpower())
-						if target:canBe("stun") then target:setEffect(target.EFF_STUNNED, 5, {apply_power=self:combatMindpower()}) end
-						game.logSeen(self, "%s's animating magic is disrupted!", target.name:capitalize())
-					end
+			if self:getTalentLevel(t)>=5 then
+				if target.undead or target.construct then
+					self:project({type="hit"}, target.x, target.y, engine.DamageType.ARCANE, 40+self:combatMindpower())
+					if target:canBe("stun") then target:setEffect(target.EFF_STUNNED, 5, {apply_power=self:combatMindpower()}) end
+					game.logSeen(self, "%s's animating magic is disrupted!", target.name:capitalize())
 				end
+			end
 		end, nil, {type="slime"})
 		return true
 	end,
 	info = function(self, t)
-		return ([[Inflict various status effects on the target, depending on the level.]]):format(7 + self:getWil() * 0.5)
+		return ([[The target has a %d%% chance (stacking to a maximum of %d%%) to fail to cast any spell.  At level 2 magical effects may be disrupted, at level 3 magical sustains may be disrupted, and at level 5 magical constructs and undead may be stunned.]]):format(t.getpower(self, t),t.maxpower(self,t))
 	end,
 }
 
diff --git a/game/modules/tome/data/talents/misc/races.lua b/game/modules/tome/data/talents/misc/races.lua
index ff99deca2a2781a160d026861ced8a9a8e806562..e97c39f47acdbc251eb4a117706f2b3e60be91bc 100644
--- a/game/modules/tome/data/talents/misc/races.lua
+++ b/game/modules/tome/data/talents/misc/races.lua
@@ -43,7 +43,7 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 45, 25)) end, -- Limit >10
 	tactical = { HEAL = 2 },
 	on_pre_use = function(self, t) return not self:hasEffect(self.EFF_REGENERATION) end,
 	action = function(self, t)
@@ -62,23 +62,20 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self:attr("blind_immune", 0.08)
-		self.sight = self.sight + 1
-		self.heightened_senses = (self.heightened_senses or 0) + 1
-		self.infravision = (self.infravision or 0) + 1
-	end,
-	on_unlearn = function(self, t)
-		self.sight = self.sight - 1
-		self.heightened_senses = (self.heightened_senses or 0) - 1
-		self.infravision = (self.infravision or 0) - 1
-		self:attr("blind_immune", -0.08)
+	getSight = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
+	getESight = function(self, t) return math.ceil(self:combatTalentScale(t, 0.3, 2.3, "log", 0, 2)) end,
+	getImmune = function(self, t) return self:combatTalentLimit(t, 1, 0.1, 0.4) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "blind_immune", t.getImmune(self, t))
+		self:talentTemporaryValue(p, "sight", t.getSight(self, t))
+		self:talentTemporaryValue(p, "infravision", t.getESight(self, t))
+		self:talentTemporaryValue(p, "heightened_senses", t.getESight(self, t))
 	end,
 	info = function(self, t)
 		return ([[While Highers are not meant to rule other humans - and show no particular will to do so - they are frequently called to higher duties.
 		Their nature grants them better senses than other humans.
 		Increase blindness immunity by %d%%, maximum sight range by %d, and increases existing infravision, and heightened senses range by %d.]]):
-		format(self:getTalentLevelRaw(t) * 8, self:getTalentLevelRaw(t), math.ceil(self:getTalentLevelRaw(t)/2))
+		format(t.getImmune(self, t) * 100, t.getSight(self, t), t.getESight(self, t))
 	end,
 }
 
@@ -88,24 +85,23 @@ newTalent{
 	require = racial_req3,
 	points = 5,
 	mode = "passive",
-	cooldown = function(self, t) return 22 - 3 * self:getTalentLevelRaw(t) end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 0, 19, 7)) end, -- Limit > 0
+	getSave = function(self, t) return self:combatTalentScale(t, 5, 25, 0.75) end,
+	power = function(self, t) return self:combatTalentScale(t, 7, 25) end,
 	trigger = function(self, t, damtype)
 		self:startTalentCooldown(t)
 		self:setEffect(self.EFF_BORN_INTO_MAGIC, 5, {damtype=damtype})
 	end,
-	on_learn = function(self, t)
-		self.combat_spellresist = self.combat_spellresist + 5
-		self.resists[DamageType.ARCANE] = (self.resists[DamageType.ARCANE] or 0) + 5
-	end,
-	on_unlearn = function(self, t)
-		self.combat_spellresist = self.combat_spellresist - 5
-		self.resists[DamageType.ARCANE] = (self.resists[DamageType.ARCANE] or 0) - 5
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "combat_spellresist", t.getSave(self, t))
+		self:talentTemporaryValue(p, "resists",{[DamageType.ARCANE]=t.power(self, t)})
 	end,
 	info = function(self, t)
+		local netpower = t.power(self, t)
 		return ([[Highers were originally created during the Age of Allure by the human Conclave. They are imbued with magic at the very core of their being.
 		Increase spell save by +%d and arcane resistance by %d%%.
-		Also when you cast a spell dealing damage, you gain a 15%% bonus to the damage type for 5 turns (this effect has a cooldown)]]):
-		format(self:getTalentLevelRaw(t) * 5, self:getTalentLevelRaw(t) * 5, self:getTalentLevelRaw(t) * 5)
+		Also when you cast a spell dealing damage, you gain a 15%% bonus to the damage type for 5 turns. (This effect has a cooldown.)]]):
+		format(t.getSave(self, t), netpower)
 	end,
 }
 
@@ -115,9 +111,9 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 3 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 20, 47, 35)) end, -- Limit >20
 	tactical = { MANA = 2, VIM = 2, EQUILIBRIUM = 2, STAMINA = 2, POSITIVE = 2, NEGATIVE = 2, PARADOX = 2, PSI = 2 },
-	getDuration = function(self, t) return 1 + self:getTalentLevelRaw(t) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentLimit(t, 10, 2, 6.1)) end,  --  Limit to < 10
 	action = function(self, t)
 		self:setEffect(self.EFF_HIGHBORN_S_BLOOM, t.getDuration(self, t), {})
 		return true
@@ -141,16 +137,17 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 4 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 46, 30)) end,  -- Limit to >10 turns
+	getSpeed = function(self, t) return self:combatStatScale(math.max(self:getDex(), self:getMag()), 0.1, 0.476, 0.75) end,
 	tactical = { DEFEND = 1 },
 	action = function(self, t)
-		local power = 0.1 + math.max(self:getDex(), self:getMag()) / 210
-		self:setEffect(self.EFF_SPEED, 8, {power=power})
+		self:setEffect(self.EFF_SPEED, 8, {power=t.getSpeed(self, t)})
 		return true
 	end,
 	info = function(self, t)
 		return ([[Call upon the grace of the Eternals to increase your general speed by %d%% for 8 turns.
-		The speed bonus will increase with your Dexterity or Magic (whichever is higher).]]):format((0.1 + math.max(self:getDex(), self:getMag()) / 210) * 100)
+		The speed bonus will increase with your Dexterity or Magic (whichever is higher).]]):
+		format(t.getSpeed(self, t) * 100)
 	end,
 }
 
@@ -160,21 +157,18 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self.combat_physcrit = self.combat_physcrit + 2
-		self.combat_spellcrit = self.combat_spellcrit + 2
-		self.combat_mindcrit = self.combat_mindcrit + 2
-		self.combat_critical_power = (self.combat_critical_power or 0) + 5
-	end,
-	on_unlearn = function(self, t)
-		self.combat_physcrit = self.combat_physcrit - 2
-		self.combat_spellcrit = self.combat_spellcrit - 2
-		self.combat_mindcrit = self.combat_mindcrit - 2
-		self.combat_critical_power = (self.combat_critical_power or 0) - 5
+	critChance = function(self, t) return self:combatTalentScale(t, 3, 10, 0.75) end,
+	critPower = function(self, t) return self:combatTalentScale(t, 6, 25, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "combat_physcrit", t.critChance(self, t))
+		self:talentTemporaryValue(p, "combat_spellcrit", t.critChance(self, t))
+		self:talentTemporaryValue(p, "combat_mindcrit", t.critChance(self, t))
+		self:talentTemporaryValue(p, "combat_critical_power", t.critPower(self, t))
 	end,
 	info = function(self, t)
 		return ([[Reality bends slightly in the presence of a Shaloren, due to their inherent magical nature.
-		Increases critical chance by %d%% and critical strike power by %d%%.]]):format(self:getTalentLevelRaw(t) * 2, self:getTalentLevelRaw(t) * 5)
+		Increases critical chance by %d%% and critical strike power by %d%%.]]):
+		format(t.critChance(self, t), t.critPower(self, t))
 	end,
 }
 
@@ -183,15 +177,17 @@ newTalent{
 	type = {"race/shalore", 3},
 	require = racial_req3,
 	points = 5,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 3 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 47, 35)) end, -- Limit > 5
+	getChance = function(self, t) return self:combatTalentLimit(t, 100, 21, 45) end, -- Limit < 100%
+	getInvis = function(self, t) return self:combatStatScale("mag" , 7, 25) end,
 	mode = "sustained",
 	no_energy = true,
 	activate = function(self, t)
 		self.invis_on_hit_disable = self.invis_on_hit_disable or {}
 		game:playSoundNear(self, "talents/spell_generic2")
 		local ret = {
-			invis = self:addTemporaryValue("invis_on_hit", 15 + self:getTalentLevelRaw(t) * 6),
-			power = self:addTemporaryValue("invis_on_hit_power", 5 + self:getMag(20, true)),
+			invis = self:addTemporaryValue("invis_on_hit", t.getChance(self, t)),
+			power = self:addTemporaryValue("invis_on_hit_power", t.getInvis(self, t)),
 			talent = self:addTemporaryValue("invis_on_hit_disable", {[t.id]=1}),
 		}
 		return ret
@@ -205,7 +201,7 @@ newTalent{
 	info = function(self, t)
 		return ([[As the only immortal race of Eyal, Shaloren have learnt, over the long years, to use their innate inner magic to protect themselves.
 		%d%% chance to become invisible (power %d) for 5 turns, when hit by a blow doing at least 10%% of their total life.]]):
-		format(15 + self:getTalentLevelRaw(t) * 6, 5 + self:getMag(20, true))
+		format(t.getChance(self, t), t.getInvis(self, t))
 	end,
 }
 
@@ -215,7 +211,9 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 3 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 20, 47, 35)) end, -- Limit to >20
+	getEffectGood = function(self, t) return math.floor(self:combatTalentScale(t, 1, 5, "log")) end,
+	getEffectBad = function(self, t) return math.floor(self:combatTalentScale(t, 2.9, 10.01, "log")) end,
 	tactical = {
 		BUFF = function(self, t, target)
 			local nb = 0
@@ -241,9 +239,9 @@ newTalent{
 			local e = target.tempeffect_def[eff_id]
 			if e.type ~= "other" then
 				if e.status == "beneficial" then
-					p.dur = p.dur + self:getTalentLevelRaw(t)
+					p.dur = math.min(p.dur*2, p.dur + t.getEffectGood(self, t))
 				elseif e.status == "detrimental" then
-					p.dur = p.dur - self:getTalentLevelRaw(t) * 2
+					p.dur = p.dur - t.getEffectBad(self, t)
 					if p.dur <= 0 then todel[#todel+1] = eff_id end
 				end
 			end
@@ -260,7 +258,7 @@ newTalent{
 		while #tids > 0 do
 			local tt = rng.tableRemove(tids)
 			if not tt then break end
-			self.talents_cd[tt.id] = self.talents_cd[tt.id] - self:getTalentLevelRaw(t)
+			self.talents_cd[tt.id] = self.talents_cd[tt.id] - t.getEffectGood(self, t)
 			if self.talents_cd[tt.id] <= 0 then self.talents_cd[tt.id] = nil end
 		end
 
@@ -269,8 +267,8 @@ newTalent{
 	end,
 	info = function(self, t)
 		return ([[The world grows old as you stand through the ages. To you, time is different.
-		Reduces the time remaining on detrimental effects by %d, cooling down talents by %d, and increases the time remaining on beneficial effects by %d.]]):
-		format(self:getTalentLevelRaw(t) * 2, self:getTalentLevelRaw(t), self:getTalentLevelRaw(t))
+		Reduces the time remaining on detrimental effects by %d, cooling down talents by %d, and increases the time remaining on beneficial effects by %d (up to 2 times the current duration).]]):
+		format(t.getEffectBad(self, t), t.getEffectGood(self, t), t.getEffectGood(self, t))
 	end,
 }
 
@@ -285,15 +283,17 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 45, 25)) end, -- Limit > 5
+	getPower = function(self, t) return self:combatStatScale("wil", 11, 20) end,
 	tactical = { ATTACK = 1, DEFEND = 1 },
 	action = function(self, t)
-		self:setEffect(self.EFF_ETERNAL_WRATH, 5, {power=10 + self:getWil(10, true)})
+		self:setEffect(self.EFF_ETERNAL_WRATH, 5, {power=t.getPower(self, t)})
 		return true
 	end,
 	info = function(self, t)
 		return ([[Call upon the power of the Eternals, increasing all damage by %d%% and reducing all damage taken by %d%% for 5 turns.
-		The bonus will increase with your Willpower.]]):format(10 + self:getWil(10, true), 10 + self:getWil(10, true))
+		The bonus will increase with your Willpower.]]):
+		format(t.getPower(self, t), t.getPower(self, t))
 	end,
 }
 
@@ -303,17 +303,15 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self.combat_physresist = self.combat_physresist + 5
-		self.combat_mentalresist = self.combat_mentalresist + 5
-	end,
-	on_unlearn = function(self, t)
-		self.combat_physresist = self.combat_physresist - 5
-		self.combat_mentalresist = self.combat_mentalresist - 5
+	getSave = function(self, t) return self:combatTalentScale(t, 6, 25, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "combat_physresist", t.getSave(self, t))
+		self:talentTemporaryValue(p, "combat_mentalresist", t.getSave(self, t))
 	end,
 	info = function(self, t)
 		return ([[Thaloren have always been a free people, living in their beloved forest and never caring much about the world outside.
-		Increase Physical and Mental Save by +%d.]]):format(self:getTalentLevelRaw(t) * 5)
+		Increase Physical and Mental Save by +%d.]]):
+		format(t.getSave(self, t))
 	end,
 }
 
@@ -323,19 +321,18 @@ newTalent{
 	require = racial_req3,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self:attr("disease_immune", 0.2)
-		self.resists[DamageType.BLIGHT] = (self.resists[DamageType.BLIGHT] or 0) + 2
-		self.resists.all = (self.resists.all or 0) + 1.3
-	end,
-	on_unlearn = function(self, t)
-		self:attr("disease_immune", -0.2)
-		self.resists[DamageType.BLIGHT] = (self.resists[DamageType.BLIGHT] or 0) - 2
-		self.resists.all = (self.resists.all or 0) - 1.3
+	getDiseaseImmune = function(self, t) return self:combatTalentLimit(t, 1, 0.2, 0.75) end, -- Limit < 100%
+	getBResist = function(self, t) return self:combatTalentScale(t, 3, 10) end,
+	getAllResist = function(self, t) return self:combatTalentScale(t, 2, 6.5) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "disease_immune", t.getDiseaseImmune(self, t))
+		self:talentTemporaryValue(p, "resists",{[DamageType.BLIGHT]=t.getBResist(self, t)})
+		self:talentTemporaryValue(p, "resists",{all=t.getAllResist(self, t)})
 	end,
 	info = function(self, t)
 		return ([[You are part of the wood; it shields you from corruption.
-		Increase disease immunity by %d%%, blight resistance by %d%%, and all resistances by %d%%.]]):format(self:getTalentLevelRaw(t) * 20, self:getTalentLevelRaw(t) * 2, self:getTalentLevelRaw(t) * 1.3)
+		Increase disease immunity by %d%%, blight resistance by %0.1f%%, and all resistances by %0.1f%%.]]):
+		format(t.getDiseaseImmune(self, t)*100, t.getBResist(self, t), t.getAllResist(self, t))
 	end,
 }
 
@@ -345,7 +342,7 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 4 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 8, 46, 34)) end, -- limit >8
 	tactical = { ATTACK = { PHYSICAL = 2 }, DISABLE = { stun = 1, knockback = 1 } },
 	range = 4,
 	action = function(self, t)
@@ -383,8 +380,11 @@ newTalent{
 				ai = "summoned", ai_real = "tactical", ai_state = { talent_in=2, },
 				stats = {str=0, dex=0, con=0, cun=0, wil=0, mag=0},
 				combat = { dam=resolvers.levelup(resolvers.rngavg(15,25), 1, 1.3), atk=resolvers.levelup(resolvers.rngavg(15,25), 1, 1.6), dammod={str=1.1} },
-				inc_stats = { str=25 + self:getWil() * self:getTalentLevel(t) / 5, dex=18, con=10 + self:getTalentLevel(t) * 2, },
-
+				inc_stats = {
+					str=25 + self:combatScale(self:getWil() * self:getTalentLevel(t), 0, 0, 100, 500, 0.75),
+					dex=18,
+					con=10 + self:combatTalentScale(t, 3, 10, 0.75),
+				},
 				level_range = {1, nil}, exp_worth = 0,
 				silent_levelup = true,
 
@@ -424,19 +424,24 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 8, 45, 25)) end, -- Limit >8
+	getParams = function(self, t)
+		return {
+			armor = self:combatStatScale("con", 7, 25),
+			physical = self:combatStatScale("con", 12, 30, 0.75),
+			spell = self:combatStatScale("con", 12, 30, 0.75),
+		}
+	end,
 	tactical = { DEFEND = 2 },
 	action = function(self, t)
-		self:setEffect(self.EFF_DWARVEN_RESILIENCE, 8, {
-			armor=5 + self:getCon() / 5,
-			physical=10 + self:getCon() / 5,
-			spell=10 + self:getCon() / 5,
-		})
+		self:setEffect(self.EFF_DWARVEN_RESILIENCE, 8, t.getParams(self, t))
 		return true
 	end,
 	info = function(self, t)
+		local params = t.getParams(self, t)
 		return ([[Call upon the legendary resilience of the Dwarven race to increase your Armor (+%d), Spell (+%d) and Physical (+%d) saves for 8 turns.
-		The bonus will increase with your Constitution.]]):format(5 + self:getCon() / 5, 10 + self:getCon() / 5, 10 + self:getCon() / 5)
+		The bonus will increase with your Constitution.]]):
+		format(params.armor, params.physical, params.spell)
 	end,
 }
 
@@ -446,15 +451,14 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self:attr("auto_stoneskin", 6)
-	end,
-	on_unlearn = function(self, t)
-		self:attr("auto_stoneskin", -6)
+	armor = function(self, t) return self:combatTalentScale(t, 6, 30) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "auto_stoneskin", t.armor(self, t))
 	end,
 	info = function(self, t)
 		return ([[Dwarf skin is a complex structure, it can automatically react to physical blows to harden itself.
-		15%% chance when hit in melee to increase Armour total by %d for 5 turns.]]):format(self:getTalentLevelRaw(t) * 6)
+		When you are hit in melee, you have a 15%% chance to increase your Armour total by %d for 5 turns.]]):
+		format(t.armor(self, t))
 	end,
 }
 
@@ -464,10 +468,17 @@ newTalent{
 	require = racial_req3,
 	points = 5,
 	mode = "passive",
+	getMaxSaves = function(self, t) return self:combatTalentScale(t, 8, 35) end,
+	getGold = function(self, t) return self:combatTalentLimit(t, 40, 85, 65) end, -- Limit > 40
+	-- called by _M:combatPhysicalResist, _M:combatSpellResist, _M:combatMentalResist in mod.class.interface.Combat.lua
+	getSaves = function(self, t)
+		return util.bound(self.money / t.getGold(self, t), 0, t.getMaxSaves(self, t))
+	end,
 	info = function(self, t)
 		return ([[Money is the heart of the Dwarven Empire; it rules over all other considerations.
 		Increases Physical, Mental and Spell Saves based on the amount of gold you possess.
-		+1 save every %d gold, up to +%d.]]):format(90 - self:getTalentLevelRaw(t) * 5, self:getTalentLevelRaw(t) * 7)
+		+1 save every %d gold, up to +%d. (currently +%d)]]):
+		format(t.getGold(self, t), t.getMaxSaves(self, t), t.getSaves(self, t))
 	end,
 }
 
@@ -476,10 +487,12 @@ newTalent{
 	type = {"race/dwarf", 4},
 	require = racial_req4,
 	points = 5,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 45, 25)) end, -- Limit >5
 	range = 1,
 	no_npc_use = true,
-	getRange = function(self, t) return math.floor(1 + self:getCon(4, true) + self:getTalentLevel(t)) end,
+	getRange = function(self, t)
+		return math.max(1, math.floor(self:combatScale(0.04*self:getCon() + self:getTalentLevel(t), 2.4, 1.4, 10, 9)))
+	end,
 	action = function(self, t)
 		local tg = {type="bolt", range=self:getTalentRange(t), nolock=true, talent=t}
 		local x, y = self:getTarget(tg)
@@ -509,19 +522,24 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 45, 25)) end, -- Limit >5
+	getParams = function(self, t)
+		return {
+			physical = self:combatStatScale("cun", 15, 60, 0.75),
+			spell = self:combatStatScale("cun", 15, 60, 0.75),
+			mind = self:combatStatScale("cun", 15, 60, 0.75),
+			}
+	end,
 	tactical = { ATTACK = 2 },
 	action = function(self, t)
-		self:setEffect(self.EFF_HALFLING_LUCK, 5, {
-			physical=10 + self:getCun() / 2,
-			spell=10 + self:getCun() / 2,
-			mind=10 + self:getCun() / 2,
-		})
+		self:setEffect(self.EFF_HALFLING_LUCK, 5, t.getParams(self, t))
 		return true
 	end,
 	info = function(self, t)
-		return ([[Call upon the luck and cunning of the Little Folk to increase your physical, mental, and spell critical strike chance by %d%% and saves by %d for 5 turns.
-		The bonus will increase with your Cunning.]]):format(10 + self:getCun() / 2, 10 + self:getCun() / 2)
+		local params = t.getParams(self, t)
+		return ([[Call upon the luck and cunning of the Little Folk to increase your physical, mental, and spell critical strike chance by %d%% and your saves by %d for 5 turns.
+		The bonus will increase with your Cunning.]]):
+		format(params.mind, params.mind)
 	end,
 }
 
@@ -532,15 +550,20 @@ newTalent{
 	points = 5,
 	mode = "passive",
 	getThreshold = function(self, t) return math.max(10, (15 - self:getTalentLevelRaw(t))) / 100 end,
-	getEvasionChance = function(self, t) return self:getStat("lck") end,
-	getDuration = function(self, t) return 1 + math.ceil(self:getTalentLevel(t)/2) end,
+	getEvasionChance = function(self, t) return 50 end,
+	getDuration = function(self, t) return math.ceil(self:combatTalentScale(t, 1.3, 3.3)) end,
+	-- called by _M:onTakeHit function in mod.class.Actor.lua for trigger 
+	getDefense = function(self) 
+		local oldevasion = self:hasEffect(self.EFF_EVASION)
+		return self:getStat("lck")/200*(self:combatDefenseBase() - (oldevasion and oldevasion.defense or 0)) -- Prevent stacking
+	end,
 	info = function(self, t)
 		local threshold = t.getThreshold(self, t)
 		local evasion = t.getEvasionChance(self, t)
 		local duration = t.getDuration(self, t)
 		return ([[Your incredible luck kicks in at just the right moment to save your skin.
-		Whenever you take %d%% or more of your life from a single attack, you gain Evasion equal to your luck stat (currently %d%%) for the next %d turns.]]):
-		format(threshold * 100, evasion, duration)
+		Whenever you take %d%% or more of your life from a single attack, you gain Evasion (%d%%) and %d additional defense (based on your luck and other defensive stats) for the next %d turns.]]):
+		format(threshold * 100, evasion, t.getDefense(self), duration)
 	end,
 }
 
@@ -563,10 +586,10 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 5 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 45, 25)) end, -- limit >10
 	tactical = { DEFEND = 1,  CURE = 1 },
-	getRemoveCount = function(self, t) return 1 + self:getTalentLevel(t) end,
-	getDuration = function(self, t) return 1 + self:getTalentLevel(t) end,
+	getRemoveCount = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6, "log")) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 2, 6)) end,
 	action = function(self, t)
 		local effs = {}
 
@@ -593,7 +616,7 @@ newTalent{
 	info = function(self, t)
 		local duration = t.getDuration(self, t)
 		local count = t.getRemoveCount(self, t)
-		return ([[Halflings are one of the more powerful military force of the known world; they have been at war with most other races for thousand of years.
+		return ([[Halflings have one of the most powerful military forces in the known world and they have been at war with most other races for thousand of years.
 		Removes %d stun, daze, or pin effects, and makes you immune to stuns, dazes and pins for %d turns.
 		This talent takes no time to use.]]):format(duration, count)
 	end,
@@ -610,15 +633,17 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 4 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 5, 46, 30)) end, -- Limit to >5 turns
+	getPower = function(self, t) return self:combatStatScale("wil", 12, 30) end,
 	tactical = { ATTACK = 2 },
 	action = function(self, t)
-		self:setEffect(self.EFF_ORC_FURY, 5, {power=10 + self:getWil(20, true)})
+		self:setEffect(self.EFF_ORC_FURY, 5, {power=t.getPower(self, t)})
 		return true
 	end,
 	info = function(self, t)
-		return ([[Summons your lust for blood and destruction, increasing all damage dealt by %d%% for 5 turns.
-		The bonus will increase with your Willpower.]]):format(10 + self:getWil(20, true))
+		return ([[Summons your lust for blood and destruction, increasing all damage you deal by %d%% for 5 turns.
+		The bonus will increase with your Willpower.]]):
+		format(t.getPower(self, t))
 	end,
 }
 
@@ -628,17 +653,15 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self.combat_physresist = self.combat_physresist + 5
-		self.combat_mentalresist = self.combat_mentalresist + 5
-	end,
-	on_unlearn = function(self, t)
-		self.combat_physresist = self.combat_physresist - 5
-		self.combat_mentalresist = self.combat_mentalresist - 5
+	getSaves = function(self, t) return self:combatTalentScale(t, 6, 25, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "combat_physresist", t.getSaves(self, t))
+		self:talentTemporaryValue(p, "combat_mentalresist", t.getSaves(self, t))
 	end,
 	info = function(self, t)
-		return ([[Orcs have been the prey of the other races for thousands of years, with or without reasons. They have learnt to withstand things that would break weaker races.
-		Increase physical and mental save by +%d.]]):format(self:getTalentLevelRaw(t) * 5)
+		return ([[Orcs have been the prey of the other races for thousands of years, with or without justification. They have learnt to withstand things that would break weaker races.
+		Increase physical and mental save by +%d.]]):
+		format(t.getSaves(self, t))
 	end,
 }
 
@@ -648,15 +671,14 @@ newTalent{
 	require = racial_req3,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self.resists_pen.all = (self.resists_pen.all or 0) + 5
-	end,
-	on_unlearn = function(self, t)
-		self.resists_pen.all = (self.resists_pen.all or 0) - 5
+	getPen = function(self, t) return self:combatTalentLimit(t, 50, 7, 25) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "resists_pen", {all = t.getPen(self, t)})
 	end,
 	info = function(self, t)
 		return ([[Orcs have seen countless battles, and won many of them.
-		Increase all damage penetration by %d%%.]]):format(self:getTalentLevelRaw(t) * 5)
+		Increase all damage penetration by %d%%.]]):
+		format(t.getPen(self, t))
 	end,
 }
 
@@ -666,7 +688,9 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 4 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 46, 30)) end, -- Limit to >10
+	remcount  = function(self,t) return math.ceil(self:combatTalentScale(t, 0.5, 3, "log", 0, 3)) end,
+	heal = function(self, t) return 25 + 2.3* self:getCon() + self:combatTalentLimit(t, 0.1, 0.01, 0.05)*self.max_life end,
 	is_heal = true,
 	tactical = { DEFEND = 1, HEAL = 2, CURE = function(self, t, target)
 		local nb = 0
@@ -690,7 +714,7 @@ newTalent{
 			end
 		end
 
-		for i = 1, math.ceil(self:getTalentLevel(t) * 3 / 5) do
+		for i = 1, t.remcount(self,t) do
 			if #effs == 0 then break end
 			local eff = rng.tableRemove(effs)
 
@@ -699,14 +723,15 @@ newTalent{
 			end
 		end
 		self:attr("allow_on_heal", 1)
-		self:heal(25 + self:getCon() * 2.3)
+		self:heal(t.heal(self, t))
 		self:attr("allow_on_heal", -1)
 		return true
 	end,
 	info = function(self, t)
-		return ([[Call upon the will of all the Orc Prides to survive this battle.
-		Heals you for %d life, and removes up to %d detrimental effects.
-		The bonus will increase with your Constitution.]]):format(25 + self:getCon() * 2.3, math.ceil(self:getTalentLevel(t) * 3 / 5))
+		return ([[Call upon the will of all of the Orc Prides to survive this battle.
+		You heal for %d life, and remove up to %d detrimental effects.
+		The healing will increase with your Constitution.]]):
+		format(t.heal(self, t), t.remcount(self,t))
 	end,
 }
 
@@ -721,7 +746,8 @@ newTalent{
 	require = racial_req1,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 3 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 47, 35)) end, -- Limit >10
+	getduration = function(self) return math.floor(self:combatStatScale("wil", 5, 14)) end,
 	range = 4,
 	no_npc_use = true,
 	requires_target = true,
@@ -741,15 +767,15 @@ newTalent{
 			target:takeHit(1, self)
 			target:takeHit(1, self)
 			target:takeHit(1, self)
-			target:setEffect(target.EFF_DOMINANT_WILL, 4 + self:getWil(10), {src=self})
+			target:setEffect(target.EFF_DOMINANT_WILL, t.getduration(self), {src=self})
 		end)
 		return true
 	end,
 	info = function(self, t)
 		return ([[Shatters the mind of your victim, giving you full control over its actions for %s turns.
 		When the effect ends, you pull out your mind and the victim's body collapses, dead.
-		This effect does not work on rares, bosses or undeads.
-		The duration will increase with your Willpower.]]):format(4 + self:getWil(10))
+		This effect does not work on rares, bosses, or undeads.
+		The duration will increase with your Willpower.]]):format(t.getduration(self))
 	end,
 }
 
@@ -759,19 +785,17 @@ newTalent{
 	require = racial_req2,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self:attr("confusion_immune", 0.12)
-		self:attr("silence_immune", 0.12)
-		self.combat_mentalresist = self.combat_mentalresist + 4
-	end,
-	on_unlearn = function(self, t)
-		self:attr("confusion_immune", -0.12)
-		self:attr("silence_immune", -0.12)
-		self.combat_mentalresist = self.combat_mentalresist - 4
+	getImmune = function(self, t) return self:combatTalentLimit(t, 1, 0.17, 0.6) end, -- Limit < 100%
+	getSave = function(self, t) return self:combatTalentScale(t, 5, 20, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "confusion_immune", t.getImmune(self, t))
+		self:talentTemporaryValue(p, "silence_immune", t.getImmune(self, t))
+		self:talentTemporaryValue(p, "combat_mentalresist", t.getSave(self, t))
 	end,
 	info = function(self, t)
 		return ([[Your mind becomes more attuned to the Way, and is shielded from outside effects.
-		Increase confusion and silence immunities by %d%%, and your Mental Save by +%d.]]):format(self:getTalentLevelRaw(t) * 12, self:getTalentLevelRaw(t) * 4)
+		Increase confusion and silence immunities by %d%%, and your Mental Save by +%d.]]):
+		format(100*t.getImmune(self, t), t.getSave(self, t))
 	end,
 }
 
@@ -781,17 +805,14 @@ newTalent{
 	require = racial_req3,
 	points = 5,
 	mode = "passive",
-	on_learn = function(self, t)
-		self.global_speed_base = self.global_speed_base + 0.03
-		self:recomputeGlobalSpeed()
-	end,
-	on_unlearn = function(self, t)
-		self.global_speed_base = self.global_speed_base - 0.03
+	speedup = function(self, t) return self:combatTalentScale(t, 0.04, 0.15, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "global_speed_base", t.speedup(self, t))
 		self:recomputeGlobalSpeed()
 	end,
 	info = function(self, t)
 		return ([[Yeeks live fast, think fast, and sacrifice fast for the Way.
-		Increase global speed by %d%%.]]):format(self:getTalentLevelRaw(t) * 3)
+		Increase global speed by %0.1f%%.]]):format(100*t.speedup(self, t))
 	end,
 }
 
@@ -801,7 +822,7 @@ newTalent{
 	require = racial_req4,
 	points = 5,
 	no_energy = true,
-	cooldown = function(self, t) return 50 - self:getTalentLevel(t) * 3 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 6, 47, 35)) end, -- Limit >6
 	range = 4,
 	no_npc_use = true,
 	action = function(self, t)
@@ -838,8 +859,14 @@ newTalent{
 				autolevel = "none",
 				ai = "summoned", ai_real = "tactical", ai_state = { talent_in=2, },
 				stats = {str=0, dex=0, con=0, cun=0, wil=0, mag=0},
-				inc_stats = { str=25 + self:getWil() * self:getTalentLevel(t) / 5, mag=10, cun=25 + self:getWil() * self:getTalentLevel(t) / 5, wil=25 + self:getWil() * self:getTalentLevel(t) / 5, dex=18, con=10 + self:getTalentLevel(t) * 2, },
-
+				inc_stats = {
+					str=self:combatScale(self:getWil() * self:getTalentLevel(t), 25, 0, 125, 500, 0.75),
+					mag=10,
+					cun=self:combatScale(self:getWil() * self:getTalentLevel(t), 25, 0, 125, 500, 0.75),
+					wil=self:combatScale(self:getWil() * self:getTalentLevel(t), 25, 0, 125, 500, 0.75),
+					dex=18,
+					con=10 + self:combatTalentScale(t, 2, 10, 0.75),
+				},
 				resolvers.equip{
 					{type="weapon", subtype="longsword", autoreq=true},
 					{type="weapon", subtype="dagger", autoreq=true},
@@ -869,7 +896,7 @@ newTalent{
 	end,
 	info = function(self, t)
 		return ([[Reach through the collective psionic gestalt of the yeeks, the Way, to call for immediate help.
-		Summons up to 3 yeek mindslayers to your side.]])
+		Summons up to 3 yeek mindslayers to your side for 6 turns.]])
 	end,
 }
 
diff --git a/game/modules/tome/data/talents/undeads/ghoul.lua b/game/modules/tome/data/talents/undeads/ghoul.lua
index da73b6f20c110fcb1a0ed4d3a5fbdef2179d7d98..7fe1ce48ae04d96f063d105ac9af27dec899e545 100644
--- a/game/modules/tome/data/talents/undeads/ghoul.lua
+++ b/game/modules/tome/data/talents/undeads/ghoul.lua
@@ -23,26 +23,17 @@ newTalent{
 	mode = "passive",
 	require = undeads_req1,
 	points = 5,
-	on_learn = function(self, t)
-		self.inc_stats[self.STAT_STR] = self.inc_stats[self.STAT_STR] + 2
-		self:onStatChange(self.STAT_STR, 2)
-		self.inc_stats[self.STAT_CON] = self.inc_stats[self.STAT_CON] + 2
-		self:onStatChange(self.STAT_CON, 2)
-	end,
-	on_unlearn = function(self, t)
-		self.inc_stats[self.STAT_STR] = self.inc_stats[self.STAT_STR] - 2
-		self:onStatChange(self.STAT_STR, -2)
-		self.inc_stats[self.STAT_CON] = self.inc_stats[self.STAT_CON] - 2
-		self:onStatChange(self.STAT_CON, -2)
-	end,
+	statBonus = function(self, t) return self:combatTalentScale(t, 2, 10, 0.75) end,
 	getMaxDamage = function(self, t) return math.max(50, 100 - self:getTalentLevelRaw(t) * 10) end,
 	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "inc_stats", {[self.STAT_STR]=t.statBonus(self, t)})
+		self:talentTemporaryValue(p, "inc_stats", {[self.STAT_CON]=t.statBonus(self, t)})
 		self:talentTemporaryValue(p, "flat_damage_cap", {all=t.getMaxDamage(self, t)})
 	end,
 	info = function(self, t)
 		return ([[Improves your ghoulish body, increasing Strength and Constitution by %d.
 		Your body also becomes incredibly resilient to damage, you can never take a blow that deals more than %d%% of your maximum life.]])
-		:format(2 * self:getTalentLevelRaw(t), t.getMaxDamage(self, t))
+		:format(t.statBonus(self, t), t.getMaxDamage(self, t))
 	end,
 }
 
@@ -54,7 +45,7 @@ newTalent{
 	tactical = { CLOSEIN = 3 },
 	direct_hit = true,
 	cooldown = function(self, t) return math.max(10, 22 - self:getTalentLevelRaw(t) * 2) end,
-	range = function(self, t) return math.floor(4 + self:getTalentLevel(t) * 1.2) end,
+	range = function(self, t) return math.floor(self:combatTalentScale(t, 5, 10, 0.5, 0, 1)) end,
 	requires_target = true,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
@@ -96,8 +87,11 @@ newTalent{
 	tactical = { ATTACK = { BLIGHT = 1 }, HEAL = 1 },
 	range=1,
 	requires_target = true,
+	getduration = function(self, t) return self:combatTalentScale(t, 7, 15, 0.5) end,
+	getPurgeChance = function(self, t) return self:combatTalentLimit(t, 100, 5, 25) end, -- Limit < 100%
+	-- status effect removal handled in mod.data.damage_types (type = "RETCH")
 	action = function(self, t)
-		local duration = self:getTalentLevelRaw(t) * 2 + 5
+		local duration = t.getduration(self, t)
 		local radius = 3
 		local dam = 10 + self:combatTalentStatDamage(t, "con", 10, 60)
 		local tg = {type="ball", range=self:getTalentRange(t), radius=radius}
@@ -118,7 +112,7 @@ newTalent{
 		return ([[Vomit on the ground around you, healing any undead in the area and damaging anyone else.
 		Lasts %d turns, and deals %d blight damage or heals %d life.
 		Creatures standing in the retch also have %d%% chance to loose a physical effect each turn.
-		Undeads will be stripped from a detrimental effect while others will be stripped from a beneficial effect.]]):format(self:getTalentLevelRaw(t) * 2 + 5, damDesc(self, DamageType.BLIGHT, dam), dam * 1.5, self:getTalentLevel(t) * 5)
+		Undeads will be stripped from a detrimental effect while others will be stripped from a beneficial effect.]]):format(t.getduration(self, t), damDesc(self, DamageType.BLIGHT, dam), dam * 1.5, t.getPurgeChance(self, t))
 	end,
 }
 
@@ -131,8 +125,8 @@ newTalent{
 	tactical = { ATTACK = {BLIGHT = 2} },
 	range = 1,
 	requires_target = true,
-	getDamage = function(self, t) return 0.2 + self:getTalentLevel(t) / 12 end,
-	getDuration = function(self, t) return 3 + math.ceil(self:getTalentLevel(t)) end,
+	getDamage = function(self, t) return self:combatTalentScale(t, 0.28, 0.62) end,
+	getDuration = function(self, t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	getDiseaseDamage = function(self, t) return self:combatTalentStatDamage(t, "con", 5, 50) end,
 	getStatDamage = function(self, t) return self:combatTalentStatDamage(t, "con", 5, 20) end,
 	spawn_ghoul = function (self, target, t)
@@ -185,7 +179,7 @@ newTalent{
 				if target.dead and ghoulify > 0 then
 					t.spawn_ghoul(self, target, t)
 				end
-				target:setEffect(target.EFF_GHOUL_ROT, 3 + math.ceil(self:getTalentLevel(t)), {src=self, apply_power=self:combatPhysicalpower(), dam=t.getDiseaseDamage(self, t), str=str_damage, con=con_damage, dex=dex_damage, make_ghoul=ghoulify})
+				target:setEffect(target.EFF_GHOUL_ROT, t.getDuration(self,t), {src=self, apply_power=self:combatPhysicalpower(), dam=t.getDiseaseDamage(self, t), str=str_damage, con=con_damage, dex=dex_damage, make_ghoul=ghoulify})
 			else
 				game.logSeen(target, "%s resists the disease!", target.name:capitalize())
 			end
diff --git a/game/modules/tome/data/talents/undeads/skeleton.lua b/game/modules/tome/data/talents/undeads/skeleton.lua
index b077665143c3cdd8232e85703a6541d96cbca4e3..0148f9bf53954c7ca7c45890a3c2162dda51931a 100644
--- a/game/modules/tome/data/talents/undeads/skeleton.lua
+++ b/game/modules/tome/data/talents/undeads/skeleton.lua
@@ -23,20 +23,14 @@ newTalent{
 	mode = "passive",
 	require = undeads_req1,
 	points = 5,
-	on_learn = function(self, t)
-		self.inc_stats[self.STAT_STR] = self.inc_stats[self.STAT_STR] + 2
-		self:onStatChange(self.STAT_STR, 2)
-		self.inc_stats[self.STAT_DEX] = self.inc_stats[self.STAT_DEX] + 2
-		self:onStatChange(self.STAT_DEX, 2)
-	end,
-	on_unlearn = function(self, t)
-		self.inc_stats[self.STAT_STR] = self.inc_stats[self.STAT_STR] - 2
-		self:onStatChange(self.STAT_STR, -2)
-		self.inc_stats[self.STAT_DEX] = self.inc_stats[self.STAT_DEX] - 2
-		self:onStatChange(self.STAT_DEX, -2)
+	statBonus = function(self, t) return self:combatTalentScale(t, 2, 10, 0.75) end,
+	passives = function(self, t, p)
+		self:talentTemporaryValue(p, "inc_stats", {[self.STAT_STR]=t.statBonus(self, t)})
+		self:talentTemporaryValue(p, "inc_stats", {[self.STAT_DEX]=t.statBonus(self, t)})
 	end,
 	info = function(self, t)
-		return ([[Improves your skeletal condition, increasing Strength and Dexterity by %d.]]):format(2 * self:getTalentLevelRaw(t))
+		return ([[Improves your skeletal condition, increasing Strength and Dexterity by %d.]]):
+		format(t.statBonus(self, t))
 	end,
 }
 
@@ -47,14 +41,18 @@ newTalent{
 	points = 5,
 	cooldown = 30,
 	tactical = { DEFEND = 2 },
+	getShield = function(self, t)
+		return 3.5*self:getDex()+self:combatTalentScale(t, 120, 400) + self:combatTalentLimit(t, 0.1, 0.01, 0.05)*self.max_life
+	end,
+
 	action = function(self, t)
-		self:setEffect(self.EFF_DAMAGE_SHIELD, 10, {power=50 + 70 * self:getTalentLevel(t) + self:getDex(350, true)})
+		self:setEffect(self.EFF_DAMAGE_SHIELD, 10, {power=t.getShield(self, t)})
 		return true
 	end,
 	info = function(self, t)
 		return ([[Creates a shield of bones, absorbing %d damage. Lasts for 10 turns.
 		The total damage the shield can absorb increases with your Dexterity.]]):
-		format(50 + 70 * self:getTalentLevel(t) + self:getDex(350, true))
+		format(t.getShield(self, t))
 	end,
 }
 
@@ -65,9 +63,11 @@ newTalent{
 	points = 5,
 	mode = "passive",
 	range = 1,
+	-- called by _M:on_set_temporary_effect function in mod.class.Actor.lua
+	durresist = function(self, t) return self:combatTalentLimit(t, 1, 0.1, 5/12) end, -- Limit < 100%
 	info = function(self, t)
-		return ([[Your undead bones are very resilient, reducing the duration of all detrimental effects on you by %d%%.]]):
-		format(100 * (self:getTalentLevel(self.T_RESILIENT_BONES) / 12))
+		return ([[Your undead bones are very resilient, reducing the duration of all detrimental effects on you by up to %d%%.]]):
+		format(100 * t.durresist(self, t))
 	end,
 }
 
@@ -76,7 +76,10 @@ newTalent{ short_name = "SKELETON_REASSEMBLE",
 	type = {"undead/skeleton",4},
 	require = undeads_req4,
 	points = 5,
-	cooldown = function(self, t) return 45 - self:getTalentLevelRaw(t) * 4 end,
+	cooldown = function(self, t) return math.ceil(self:combatTalentLimit(t, 10, 41, 25)) end, -- Limit cooldown >10
+	getHeal = function(self, t)
+		return self:combatTalentScale(t, 100, 500) + self:combatTalentLimit(t, 0.1, 0.01, 0.05)*self.max_life
+	end,
 	tactical = { HEAL = 2 },
 	is_heal = true,
 	on_learn = function(self, t)
@@ -91,7 +94,7 @@ newTalent{ short_name = "SKELETON_REASSEMBLE",
 	end,
 	action = function(self, t)
 		self:attr("allow_on_heal", 1)
-		self:heal(100 * self:getTalentLevel(t), self)
+		self:heal(t.getHeal(self, t), self)
 		self:attr("allow_on_heal", -1)
 		game:playSoundNear(self, "talents/heal")
 		return true
@@ -99,6 +102,6 @@ newTalent{ short_name = "SKELETON_REASSEMBLE",
 	info = function(self, t)
 		return ([[Re-position some of your bones, healing yourself for %d.
 		At level 5, you will gain the ability to completely re-assemble your body should it be destroyed (can only be used once)]]):
-		format(100 * self:getTalentLevel(t))
+		format(t.getHeal(self, t))
 	end,
 }
diff --git a/game/modules/tome/data/timed_effects/mental.lua b/game/modules/tome/data/timed_effects/mental.lua
index 9f42e7e020b95564af67238b5205c812ebadbd9f..33809ed499cbbfcb4740e1ff779890fc719f3c57 100644
--- a/game/modules/tome/data/timed_effects/mental.lua
+++ b/game/modules/tome/data/timed_effects/mental.lua
@@ -1773,6 +1773,9 @@ newEffect{
 	on_gain = function(self, err) return nil, "+Bloodbath" end,
 	on_lose = function(self, err) return nil, "-Bloodbath" end,
 	on_merge = function(self, old_eff, new_eff)
+		
+		if old_eff.cur_regen + new_eff.regen < new_eff.max then	game.logSeen(self, "%s's blood frenzy intensifies!", self.name:capitalize()) end
+		new_eff.templife_id = old_eff.templife_id
 		self:removeTemporaryValue("max_life", old_eff.life_id)
 		self:removeTemporaryValue("life_regen", old_eff.life_regen_id)
 		self:removeTemporaryValue("stamina_regen", old_eff.stamina_regen_id)
@@ -1788,15 +1791,19 @@ newEffect{
 	activate = function(self, eff)
 		local v = eff.hp * self.max_life / 100
 		eff.life_id = self:addTemporaryValue("max_life", v)
-		self:heal(v)
+		eff.templife_id = self:addTemporaryValue("life",v) -- Prevent healing_factor affecting activation
 		eff.cur_regen = eff.regen
 		eff.life_regen_id = self:addTemporaryValue("life_regen", eff.regen)
 		eff.stamina_regen_id = self:addTemporaryValue("stamina_regen", eff.regen /5)
+		game.logSeen(self, "%s revels in the spilt blood and grows stronger!",self.name:capitalize())
 	end,
 	deactivate = function(self, eff)
 		self:removeTemporaryValue("max_life", eff.life_id)
 		self:removeTemporaryValue("life_regen", eff.life_regen_id)
 		self:removeTemporaryValue("stamina_regen", eff.stamina_regen_id)
+		
+		self:removeTemporaryValue("life",eff.templife_id) -- remove extra hps to prevent excessive heals at high level
+		game.logSeen(self, "%s no longer revels in blood quite so much.",self.name:capitalize())
 	end,
 }
 
diff --git a/game/modules/tome/data/timed_effects/other.lua b/game/modules/tome/data/timed_effects/other.lua
index 0e4a205d81554d3b48dfff7be41a6d19aea77075..dc58dd0526a5338c7291f9fd76374f2a3db68471 100644
--- a/game/modules/tome/data/timed_effects/other.lua
+++ b/game/modules/tome/data/timed_effects/other.lua
@@ -992,6 +992,7 @@ newEffect{
 	type = "other",
 	subtype = { time=true },
 	status = "detrimental",
+	no_stop_enter_worlmap = true,
 	no_stop_resting = true,
 	parameters = { power=10 },
 	activate = function(self, eff)
diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua
index c4de85efb48507cfaf3027f1162cbca5ba775185..11eaa88ff3fd7007fb1f7f40244405f01545b058 100644
--- a/game/modules/tome/data/timed_effects/physical.lua
+++ b/game/modules/tome/data/timed_effects/physical.lua
@@ -417,9 +417,11 @@ newEffect{
 	on_lose = function(self, err) return "#Target# is no longer evading attacks.", "-Evasion" end,
 	activate = function(self, eff)
 		eff.tmpid = self:addTemporaryValue("evasion", eff.chance)
+		eff.defid = self:addTemporaryValue("combat_def", eff.defense)
 	end,
 	deactivate = function(self, eff)
 		self:removeTemporaryValue("evasion", eff.tmpid)
+		self:removeTemporaryValue("combat_def", eff.defid)
 	end,
 }