diff --git a/game/engines/default/engine/Module.lua b/game/engines/default/engine/Module.lua
index 5c19bf02e69963e098e01909404f8b31b1cfecbc..00ef183d0897ae5a540a2f0ec69251f4f16aba3f 100644
--- a/game/engines/default/engine/Module.lua
+++ b/game/engines/default/engine/Module.lua
@@ -289,7 +289,7 @@ end
 --- List all available addons
 function _M:listAddons(mod, ignore_compat)
 	local adds = {}
-	local load = function(dir, teaa)
+	local load = function(dir, teaa, teaac)
 		local add_def = loadfile(dir.."/init.lua")
 		if add_def then
 			local add = {}
@@ -299,6 +299,7 @@ function _M:listAddons(mod, ignore_compat)
 			if (ignore_compat or engine.version_nearly_same(mod.version, add.version)) and add.for_module == mod.short_name then
 				add.dir = dir
 				add.teaa = teaa
+				add.teaac = teaac
 				add.natural_compatible = engine.version_nearly_same(mod.version, add.version)
 				add.version_txt = ("%d.%d.%d"):format(add.version[1], add.version[2], add.version[3])
 				if add.dlc and not profile:isDonator(add.dlc) then add.dlc = "no" end
@@ -309,7 +310,7 @@ function _M:listAddons(mod, ignore_compat)
 	end
 
 	local parse = function(basedir)
-		for i, short_name in ipairs(fs.list(basedir)) do if short_name:find("^"..mod.short_name.."%-") then
+		for i, short_name in ipairs(fs.list(basedir)) do if short_name:find("^"..mod.short_name.."%-") or short_name:find(".teaac$") then
 			local dir = basedir..short_name
 			print("Checking addon", short_name, ":: (as dir)", fs.exists(dir.."/init.lua"), ":: (as teaa)", short_name:find(".teaa$"), "")
 			if fs.exists(dir.."/init.lua") then
@@ -321,6 +322,16 @@ function _M:listAddons(mod, ignore_compat)
 					load("/testload", dir)
 				end
 				fs.umount(fs.getRealPath(dir))
+			elseif short_name:find(".teaac$") then
+				fs.mount(fs.getRealPath(dir), "/testload", false)
+				for sdir in fs.iterate("/testload", function(p) return p:find("%-") end) do
+					print(" * Addon collection subaddon", sdir)
+					local mod
+					if fs.exists("/testload/"..sdir.."/init.lua") then
+						load("/testload/"..sdir, dir, sdir)
+					end
+				end
+				fs.umount(fs.getRealPath(dir))
 			end
 		end end
 	end
@@ -454,13 +465,15 @@ You may try to force loading if you are sure the savefile does not use that addo
 
 		if add.data then
 			print(" * with data")
-			if add.teaa then fs.mount("subdir:/data/|"..fs.getRealPath(add.teaa), "/data-"..add.short_name, true)
+			if add.teaac then fs.mount("subdir:/"..add.teaac.."/data/|"..fs.getRealPath(add.teaa), "/data-"..add.short_name, true)
+			elseif add.teaa then fs.mount("subdir:/data/|"..fs.getRealPath(add.teaa), "/data-"..add.short_name, true)
 			else fs.mount(base.."/data", "/data-"..add.short_name, true)
 			end
 		end
 		if add.superload then 
 			print(" * with superload")
-			if add.teaa then fs.mount("subdir:/superload/|"..fs.getRealPath(add.teaa), "/mod/addons/"..add.short_name.."/superload", true)
+			if add.teaac then fs.mount("subdir:/"..add.teaac.."/superload/|"..fs.getRealPath(add.teaa), "/mod/addons/"..add.short_name.."/superload", true)
+			elseif add.teaa then fs.mount("subdir:/superload/|"..fs.getRealPath(add.teaa), "/mod/addons/"..add.short_name.."/superload", true)
 			else fs.mount(base.."/superload", "/mod/addons/"..add.short_name.."/superload", true)
 			end
 			
@@ -468,12 +481,14 @@ You may try to force loading if you are sure the savefile does not use that addo
 		end
 		if add.overload then
 			print(" * with overload")
-			if add.teaa then fs.mount("subdir:/overload/|"..fs.getRealPath(add.teaa), "/", false)
+			if add.teaac then fs.mount("subdir:/"..add.teaac.."/overload/|"..fs.getRealPath(add.teaa), "/", false)
+			elseif add.teaa then fs.mount("subdir:/overload/|"..fs.getRealPath(add.teaa), "/", false)
 			else fs.mount(base.."/overload", "/", false)
 			end
 		end
 		if add.hooks then
-			if add.teaa then fs.mount("subdir:/hooks/|"..fs.getRealPath(add.teaa), "/hooks/"..add.short_name, true)
+			if add.teaac then fs.mount("subdir:/"..add.teaac.."/hooks/|"..fs.getRealPath(add.teaa), "/hooks/"..add.short_name, true)
+			elseif add.teaa then fs.mount("subdir:/hooks/|"..fs.getRealPath(add.teaa), "/hooks/"..add.short_name, true)
 			else fs.mount(base.."/hooks", "/hooks/"..add.short_name, true)
 			end
 
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index eb432ea61fbe95db992faa81f3f5d4fc2444b283..578a387908c001e107fcf57121e962fc9f62ca82 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -988,6 +988,20 @@ function core.display.loadImage(path)
 	return oldloadimage(path)
 end
 
+function fs.iterate(path, filter)
+	local list = fs.list(path)
+	if filter then
+		for i = #list, 1, -1 do if not filter(list[i]) then
+			table.remove(list, i)
+		end end
+	end
+	local i = 0
+	return function()
+		i = i + 1
+		return list[i]
+	end
+end
+
 local oldfsexists = fs.exists
 function fs.exists(path)
 	if virtualimages[path] then return true end
diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua
index 6cd0143bde60eb7199818d95fcd16506fbed0a9b..0eb8cf1aabc52d772a00d5e0d6632b3b68095fb8 100644
--- a/game/modules/tome/class/Object.lua
+++ b/game/modules/tome/class/Object.lua
@@ -548,7 +548,9 @@ function _M:getTextualDesc(compare_with, use_actor)
 		combat = combat[field] or {}
 		compare_with = compare_with or {}
 		local dm = {}
-		for stat, i in pairs(combat.dammod or {}) do
+		local dammod = {}
+		if next(combat.dammod or {}) then dammod = use_actor:getDammod(combat) end
+		for stat, i in pairs(dammod) do
 			local name = Stats.stats_def[stat].short_name:capitalize()
 			if use_actor:knowTalent(use_actor.T_STRENGTH_OF_PURPOSE) then
 				if name == "Str" then name = "Mag" end
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index e927d0b9a587229f8c612930b47567926404188d..59a621c71da1ed15f385b3021669c190c5c10aef 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -1061,6 +1061,10 @@ _M.weapon_talents = {
 }
 
 --- Static!
+-- Training Talents can have the following fields:
+-- getMasteryPriority(self, t, kind) - Only the talent with the highest is used. Defaults to the talent level.
+-- getDamage(self, t, kind) - Extra physical power granted. Defaults to level * 10.
+-- getPercentInc(self, t, kind) - Percentage increase to damage overall. Defaults to sqrt(level / 5) / 2.
 function _M:addCombatTraining(kind, tid)
 	local wt = _M.weapon_talents
 	if not wt[kind] then wt[kind] = tid return end
@@ -1079,35 +1083,42 @@ function _M:combatGetTraining(weapon)
 	if not weapon.talented then return nil end
 	if not _M.weapon_talents[weapon.talented] then return nil end
 	if type(_M.weapon_talents[weapon.talented]) == "table" then
-		local ktid, max = _M.weapon_talents[weapon.talented][1], self:getTalentLevel(_M.weapon_talents[weapon.talented][1])
-		for i, tid in ipairs(_M.weapon_talents[weapon.talented]) do
+		local get_priority = function(tid)
+			local t = self:getTalentFromId(tid)
+			if t.getMasteryPriority then return util.getval(t.getMasteryPriority, self, t, weapon.talented) end
+			return self:getTalentLevel(t)
+		end
+		local max_tid -- = _M.weapon_talents[weapon.talented][1]
+		local max_priority = -math.huge -- get_priority(max_tid)
+		for _, tid in ipairs(_M.weapon_talents[weapon.talented]) do
 			if self:knowTalent(tid) then
-				if self:getTalentLevel(tid) > max then
-					ktid = tid
-					max = self:getTalentLevel(tid)
+				local priority = get_priority(tid)
+				if priority > max_priority then
+					max_tid = tid
+					max_priority = priority
 				end
 			end
 		end
-		return self:getTalentFromId(ktid)
+		return self:getTalentFromId(max_tid)
 	else
 		return self:getTalentFromId(_M.weapon_talents[weapon.talented])
 	end
 end
 
---- Checks weapon training
-function _M:combatCheckTraining(weapon)
-	if not weapon then return 0 end
-	if not weapon.talented then return 0 end
-	if not _M.weapon_talents[weapon.talented] then return 0 end
-	if type(_M.weapon_talents[weapon.talented]) == "table" then
-		local max = 0
-		for i, tid in ipairs(_M.weapon_talents[weapon.talented]) do
-			max = math.max(max, self:getTalentLevel(tid))
-		end
-		return max
-	else
-		return self:getTalentLevel(_M.weapon_talents[weapon.talented])
-	end
+-- Gets the added damage for a weapon based on training.
+function _M:combatTrainingDamage(weapon)
+	local t = self:combatGetTraining(weapon)
+	if not t then return 0 end
+	if t.getDamage then return util.getval(t.getDamage, self, t, weapon.talented) end
+	return self:getTalentLevel(t) * 10
+end
+
+-- Gets the percent increase for a weapon based on training.
+function _M:combatTrainingPercentInc(weapon)
+	local t = self:combatGetTraining(weapon)
+	if not t then return 0 end
+	if t.getPercentInc then return util.getval(t.getPercentInc, self, t, weapon.talented) end
+	return math.sqrt(self:getTalentLevel(t) / 5) / 2
 end
 
 --- Gets the defense
@@ -1481,22 +1492,46 @@ function _M:combatStatLimit(stat, limit, low, high)
 	end
 end
 
+--- Gets the dammod table for a given weapon.
+function _M:getDammod(combat)
+	combat = combat or self.combat or {}
+
+	local dammod = table.clone(combat.dammod or {str = 0.6}, true)
+
+	local sub = function(from, to)
+		dammod[to] = (dammod[from] or 0) + (dammod[to] or 0)
+		dammod[from] = nil
+	end
+
+	if combat.talented == 'knife' and self:knowTalent('T_LETHALITY') then sub('str', 'cun') end
+	if combat.talented and self:knowTalent('T_STRENGTH_OF_PURPOSE') then sub('str', 'mag') end
+	if self:attr 'use_psi_combat' then
+		sub('str', 'wil')
+		sub('dex', 'cun')
+	end
+
+	-- Add stuff like lethality here.
+	local hd = {"Combat:getDammod:subs", combat=combat, dammod=dammod, sub=sub}
+	if self:triggerHook(hd) then dammod = hd.dammod end
+
+	local add = function(stat, val)
+		dammod[stat] = (dammod[stat] or 0) + val
+	end
+
+	if self:knowTalent(self.T_SUPERPOWER) then add('wil', 0.3) end
+	if self:knowTalent(self.T_ARCANE_MIGHT) then add('mag', 0.5) end
+
+	return dammod
+end
+
 --- Gets the damage
 function _M:combatDamage(weapon, adddammod)
 	weapon = weapon or self.combat or {}
 
-	local sub_cun_to_str = false
-	if weapon.talented and weapon.talented == "knife" and self:knowTalent(Talents.T_LETHALITY) then sub_cun_to_str = true end
-	local sub_mag_to_str = false
-	if weapon.talented and self:knowTalent(Talents.T_STRENGTH_OF_PURPOSE) then sub_mag_to_str = true end
+	local dammod = self:getDammod(weapon)
 
 	local totstat = 0
-	local dammod = weapon.dammod or {str=0.6}
 	for stat, mod in pairs(dammod) do
-		if sub_cun_to_str and stat == "str" then stat = "cun" end
-		if sub_mag_to_str and stat == "str" then stat = "mag" end
-		if self:attr("use_psi_combat") and stat == "str" then stat = "wil" end
-		if self:attr("use_psi_combat") and stat == "dex" then stat = "cun" end
 		totstat = totstat + self:getStat(stat) * mod
 	end
 	if adddammod then
@@ -1509,15 +1544,7 @@ function _M:combatDamage(weapon, adddammod)
 		totstat = totstat * (0.8 + self:callTalent(self.T_RESONANT_FOCUS, "bonus")/100)
 	end
 
-	if self:knowTalent(self.T_SUPERPOWER) then
-		totstat = totstat + self:getStat("wil") * 0.3
-	end
-
-	if self:knowTalent(self.T_ARCANE_MIGHT) then
-		totstat = totstat + self:getStat("mag") * 0.5
-	end
-
-	local talented_mod = math.sqrt(self:combatCheckTraining(weapon) / 5) / 2 + 1
+	local talented_mod = 1 + self:combatTrainingPercentInc(weapon)
 
 	local power = math.max((weapon.dam or 1), 1)
 	power = (math.sqrt(power / 10) - 1) * 0.5 + 1
@@ -1550,7 +1577,7 @@ function _M:combatPhysicalpower(mod, weapon, add)
 		if inven and inven[1] then weapon = self:getObjectCombat(inven[1], "mainhand") else weapon = self.combat end
 	end
 
-	add = add + 10 * self:combatCheckTraining(weapon)
+	add = add + self:combatTrainingDamage(weapon)
 
 	local str = self:getStr()
 	if self:knowTalent(Talents.T_STRENGTH_OF_PURPOSE) then