diff --git a/game/engines/default/engine/Zone.lua b/game/engines/default/engine/Zone.lua
index 8f612c0f2b67a8c0917bb470c098b1bdee2d6166..6335ed3816cad996544e85049dee0c8d6d14748f 100644
--- a/game/engines/default/engine/Zone.lua
+++ b/game/engines/default/engine/Zone.lua
@@ -30,6 +30,18 @@ module(..., package.seeall, class.make)
 
 _no_save_fields = {temp_memory_levels=true, _tmp_data=true}
 
+
+--- List of rules to run through when applying an ego to an entity.
+_M.ego_rules = {}
+
+--- Adds an ego rule.
+-- Static method
+function _M:addEgoRule(kind, rule)
+	self.ego_rules[kind] = self.ego_rules[kind] or {}
+	table.insert(self.ego_rules[kind], rule)
+end
+
+
 --- Setup classes to use for level generation
 -- Static method
 -- @param t table that contains the name of the classes to use
@@ -361,7 +373,7 @@ function _M:makeEntity(level, type, filter, force_level, prob_filter)
 	-- Generate a specific probability list, slower to generate but no need to "try and be lucky"
 	elseif filter then
 		local base_list = nil
-		if filter.base_list then 
+		if filter.base_list then
 			if _G.type(filter.base_list) == "table" then base_list = filter.base_list
 			else
 				local _, _, class, file = filter.base_list:find("(.*):(.*)")
@@ -433,7 +445,7 @@ local pick_ego = function(self, level, e, eegos, egos_list, type, picked_etype,
 	if _G.type(etype) == "number" then etype = "" end
 
 	local egos = e.egos and level:getEntitiesList(type.."/"..e.egos..":"..etype)
-	
+
 	if not egos then egos = self:generateEgoEntities(level, type, etype, eegos, e.__CLASSNAME) end
 
 	if self.ego_filter then ego_filter = self.ego_filter(self, level, type, etype, e, ego_filter, egos_list, picked_etype) end
@@ -475,8 +487,8 @@ function _M:finishEntity(level, type, e, ego_filter)
 				ego.instant_resolve = nil
 				-- Void the uid, we dont want to erase the base entity's one
 				ego.uid = nil
-				-- Merge additively but with array appending, so that nameless resolvers are not lost
-				table.mergeAddAppendArray(e, ego, true)
+				-- Merge according to Object's ego rules.
+				table.ruleMergeAppendAdd(e, ego, self.ego_rules[type] or {})
 				e.name = newname
 				e.egoed = true
 			end
@@ -563,8 +575,8 @@ function _M:finishEntity(level, type, e, ego_filter)
 				ego.instant_resolve = nil
 				-- Void the uid, we dont want to erase the base entity's one
 				ego.uid = nil
-				-- Merge additively but with array appending, so that nameless resolvers are not lost
-				table.mergeAddAppendArray(e, ego, true)
+				-- Merge according to Object's ego rules.
+				table.ruleMergeAppendAdd(e, ego, self.ego_rules[type] or {})
 				e.name = newname
 				e.egoed = true
 			end
diff --git a/game/engines/default/engine/utils.lua b/game/engines/default/engine/utils.lua
index 97f4a598ea02569646bc734ae3a5fb3254bbc1bc..a2601c2ea6f6a865774a231f0e045bfdc5fd2988 100644
--- a/game/engines/default/engine/utils.lua
+++ b/game/engines/default/engine/utils.lua
@@ -286,6 +286,83 @@ function table.readonly(src)
    });
 end
 
+-- Make a new table with each k, v = f(k, v) in the original.
+function table.map(f, source)
+	local result = {}
+	for k, v in pairs(source) do
+		k2, v2 = f(k, v)
+		result[k2] = v2
+	end
+	return result
+end
+
+-- Make a new table with each k, v = k, f(v) in the original.
+function table.mapv(f, source)
+	local result = {}
+	for k, v in pairs(source) do
+		result[k] = f(v)
+	end
+	return result
+end
+
+-- Find the keys that are only in left, only in right, and are common
+-- to both.
+function table.compareKeys(left, right)
+	local result = {left = {}, right = {}, both = {}}
+	for k, _ in pairs(left) do
+		if right[k] then
+			result.both[k] = true
+		else
+			result.left[k] = true
+		end
+	end
+	for k, _ in pairs(right) do
+		if not left[k] then
+			result.right[k] = true
+		end
+	end
+	return result
+end
+
+--[=[
+  Decends recursively through a table by the given list of keys.
+
+  1st return: The first non-table value found, or the final value if
+  we ran out of keys.
+
+  2nd return: If the list of keys was exhausted
+
+  Meant to replace multiple ands to get a value:
+  "a and a.b and a.b.c" turns to "rget(a, 'b', 'c')"
+]=]
+function table.get(table, ...)
+	if type(table) ~= 'table' then return table, false end
+	for _, key in ipairs({...}) do
+		if type(table) ~= 'table' then return table, false end
+		table = table[key]
+	end
+	return table, true
+end
+
+--[=[
+  Set the nested value in a table, creating empty tables as needed.
+]=]
+function table.set(table, ...)
+	if type(table) ~= 'table' then return false end
+	local args = {...}
+	for i = 1, #args - 2 do
+		local key = args[i]
+		local subtable = table[key]
+		if not subtable then
+			subtable = {}
+			table[key] = subtable
+		end
+		table = subtable
+	end
+	table[args[#args - 1]] = args[#args]
+end
+
+
 -- Taken from http://lua-users.org/wiki/SortedIteration and modified
 local function cmp_multitype(op1, op2)
 	local type1, type2 = type(op1), type(op2)
@@ -318,6 +395,22 @@ function table.orderedPairs(t)
 	end
 end
 
+-- ordering is a function({k1, v1}, {k2, v2}), it should return true
+-- when left is < right.
+function table.orderedPairs(t, ordering)
+	if not next(t) then return function() end end
+	t = table.listify(t)
+	if #t > 1 then table.sort(t, ordering) end
+	local index = 1
+	return function()
+		if index <= #t then
+			value = t[index]
+			index = index + 1
+			return value[1], value[2]
+		end
+	end
+end
+
 --- Shuffles the content of a table (list)
 function table.shuffle(t)
 	local n = #t
@@ -328,6 +421,89 @@ function table.shuffle(t)
 	return t
 end
 
+-- Common table rules.
+table.rules = {}
+
+--[[
+Applies a series of rules to a pair of tables. rules should be a list of functions
+to be applied, in order, until one returns true.
+
+All keys in the table src are looped through, starting with array
+indices, and then the rest of the keys. The rules are given the
+following arguments:
+The dst table's value for the current key.
+The src table's value for the current key.
+The current key.
+The dst table.
+The src table.
+The list of rules.
+A state table which the rules are free to modify.
+--]]
+function table.applyRules(dst, src, rules, state)
+	if not dst or not src then return end
+	state = state or {}
+	local used_keys = {}
+	-- First loop through with ipairs so we get the numbers in order.
+	for k, v in ipairs(src) do
+		used_keys[k] = true
+		for _, rule in ipairs(rules) do
+			if rule(dst[k], src[k], k, dst, src, rules, state) then
+				break
+			end
+		end
+	end
+	-- Then loop through with pairs, skipping the ones we got with ipairs.
+	for k, v in pairs(src) do
+		if not used_keys[k] then
+			for _, rule in ipairs(rules) do
+				if rule(dst[k], src[k], k, dst, src, rules, state) then
+					break
+				end
+			end
+		end
+	end
+end
+
+-- Simply overwrites the value.
+table.rules.overwrite = function(dvalue, svalue, key, dst)
+	dst[key] = svalue
+	return true
+end
+-- Does the recursion.
+table.rules.recurse = function(dvalue, svalue, key, dst, src, rules, state)
+	if type(dvalue) ~= 'table' or type(svalue) ~= 'table' then return end
+	state = table.clone(state)
+	state.path = table.clone(state.path) or {}
+	table.insert(state.path, key)
+	table.applyRules(dvalue, svalue, rules, state)
+	return true
+end
+-- Appends indices.
+table.rules.append = function(dvalue, svalue, key, dst, src, rules, state)
+	if type(key) ~= 'number' then return end
+	table.insert(dst, svalue)
+	return true
+end
+-- Adds numbers
+table.rules.add = function(dvalue, svalue, key, dst)
+	if type(dvalue) ~= 'number' or type(svalue) ~= 'number' then return end
+	dst[key] = dvalue + svalue
+	return true
+end
+
+--[[
+A convenience method for merging tables, appending numeric indices,
+and adding number values in addition to other rules.
+--]]
+function table.ruleMergeAppendAdd(dst, src, rules)
+	rules = table.clone(rules)
+	for _, rule in pairs {'append', 'recurse', 'add', 'overwrite'} do
+		table.insert(rules, table.rules[rule])
+	end
+	table.applyRules(dst, src, rules)
+end
+
+
 function string.ordinal(number)
 	local suffix = "th"
 	number = tonumber(number)
@@ -1921,7 +2097,7 @@ function util.showMainMenu(no_reboot, reboot_engine, reboot_engine_version, rebo
 			core.steam.cancelGrabSubscribedAddons()
 			core.steam.sessionTicketCancel()
 		end
-		
+
 		-- Tell the C engine to discard the current lua state and make a new one
 		print("[MAIN] rebooting lua state: ", reboot_engine, reboot_engine_version, reboot_module, reboot_name, reboot_new)
 		core.game.reboot("te4core", -1, reboot_engine or "te4", reboot_engine_version or "LATEST", reboot_module or "boot", reboot_name or "player", reboot_new, reboot_einfo or "")
@@ -2041,4 +2217,3 @@ function require_first(...)
 	end
 	return nil
 end
-
diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua
index 32c5c9a24dbca88d81a33cc0fed8cefa079236eb..7712b7e69ceef7828c93b56cc18e7ee2cb3e59f3 100644
--- a/game/modules/tome/class/Object.lua
+++ b/game/modules/tome/class/Object.lua
@@ -443,7 +443,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		end
 	end
 
-	local compare_table_fields = function(item1, items, infield, field, outformat, text, kfunct, mod, isinversed)
+	local compare_table_fields = function(item1, items, infield, field, outformat, text, kfunct, mod, isinversed, filter)
 		mod = mod or 1
 		isinversed = isinversed or false
 		local ret = tstring{}
@@ -467,6 +467,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		end
 		local count1 = 0
 		for k, v in pairs(tab) do
+			if filter and not filter(k, v) then goto filtered end
 			local count = 0
 			if isinversed then
 				ret:add(("%s"):format((count1 > 0) and " / " or ""), (v[1] or 0) > 0 and {"color","RED"} or {"color","LIGHT_GREEN"}, outformat:format((v[1] or 0)), {"color","LAST"})
@@ -501,6 +502,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 				ret:add(")")
 			end
 			ret:add(kfunct(k))
+			::filtered::
 		end
 
 		if add then
@@ -556,7 +558,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		if combat.wil_attack then
 			desc:add("Accuracy is based on willpower for this weapon.", true)
 		end
-		
+
 		if combat.is_psionic_focus then
 			desc:add("This weapon will act as a psionic focus.", true)
 		end
@@ -592,7 +594,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		for tid, data in pairs(talents) do
 			desc:add(talents[tid][3] and {"color","WHITE"} or {"color","GREEN"}, ("When this weapon hits: %s (%d%% chance level %d)."):format(self:getTalentFromId(tid).name, talents[tid][1], talents[tid][2]), {"color","LAST"}, true)
 		end
-		
+
 		local talents = {}
 		if combat.talent_on_crit then
 			for tid, data in pairs(combat.talent_on_crit) do
@@ -630,82 +632,125 @@ function _M:getTextualDesc(compare_with, use_actor)
 		end
 		--]]
 
-		-- Store the DamageTypes with tdesc defined in dt_string
-		-- Store the DamageTypes without tdesc in combat2 (to be displayed normally)
-		local found = false
-		local dt_string = tstring{}
-		local combat2 = { melee_project = {} }
-		for i, v in pairs(combat.melee_project or {}) do
-			local def = DamageType.dam_def[i]
-			if def and def.tdesc then
-				local d = def.tdesc(v)
-				found = true
-				dt_string:add(d, {"color","LAST"}, true)
-				
-			else
-				combat2.melee_project[i] = v
+		-- get_items takes the combat table and returns a table of items to print.
+		-- Each of these items one of the following:
+		-- id -> {priority, string}
+		-- id -> {priority, message_function(this, compared), value}
+		-- header is the section header.
+		local compare_list = function(header, get_items)
+			local priority_ordering = function(left, right)
+				return left[2][1] < right[2][1]
 			end
-		end
 
+			if next(compare_with) then
+				-- Grab the left and right items.
+				local left = get_items(combat)
+				local right = {}
+				for i, v in ipairs(compare_with) do
+					for k, item in pairs(get_items(v[field])) do
+						if not right[k] then
+							right[k] = item
+						elseif type(right[k]) == 'number' then
+							right[k] = right[k] + item
+						else
+							right[k] = item
+						end
+					end
+				end
 
-		local ranged_string = tstring{}
-		local ranged_combat = { ranged_project = {} }
-		for i, v in pairs(combat.ranged_project or {}) do
-			local def = DamageType.dam_def[i]
-			if def and def.tdesc then
-				local d = def.tdesc(v)
-				found = true
-				ranged_string:add(d, {"color","LAST"}, true)
-				
+				-- Exit early if no items.
+				if not next(left) and not next(right) then return end
+
+				desc:add(header, true)
+
+				local combined = table.clone(left)
+				table.merge(combined, right)
+
+				for k, _ in table.orderedPairs(combined, priority_ordering) do
+					l = left[k]
+					r = right[k]
+					message = (l and l[2]) or (r and r[2])
+					if type(message) == 'function' then
+						desc:add(message(l and l[3], r and r[3] or 0), true)
+					elseif type(message) == 'string' then
+						prefix = '* '
+						color = 'WHITE'
+						if l and not r then
+							color = 'GREEN'
+							prefix = '+ '
+						end
+						if not l and r then
+							color = 'RED'
+							prefix = '- '
+						end
+						desc:add({'color',color}, prefix, message, {'color','LAST'}, true)
+					end
+				end
 			else
-				ranged_combat.ranged_project[i] = v
+				local items = get_items(combat)
+				if next(items) then
+					desc:add(header, true)
+					for k, v in table.orderedPairs(items, priority_ordering) do
+						message = v[2]
+						if type(message) == 'function' then
+							desc:add(message(v[3]), true)
+						elseif type(message) == 'string' then
+							desc:add({'color','WHITE'}, '* ', message, {'color','LAST'}, true)
+						end
+					end
+				end
 			end
 		end
 
-		-- Add the on hit section only if theres a tdesc DT or a special_on_hit
-		if found or special ~= ""  then
-			desc:add({"color","ORANGE"}, "When this weapon hits: ", {"color","LAST"}, true)
+		local get_special_list = function(combat, key)
+			local special = combat[key]
 
-		end
+			-- No special
+			if not special then return {} end
 
-		-- Add the special_on_hit desc first always in green
-		if special ~= "" then
-			desc:add({"color","GREEN"}, special:capitalize(), {"color","LAST"}, true)
-		end
-
-		-- Add the extended melee_project descriptions after special_on_hit, colors defined by tdesc
-		if found then
-			desc:merge(dt_string)
-			desc:merge(ranged_string)
-		end
+			-- Single special
+			if special.desc then
+				return {[special.desc] = {10, special.desc}}
+			end
 
-		-- only special_on_hit display is modified, not special_on_crit and so on
-		special = ""
-		if combat.special_on_crit then
-			special = combat.special_on_crit.desc
-		end
-		found = false
-		for i, v in ipairs(compare_with or {}) do
-			if v[field] and v[field].special_on_crit then
-				if special ~= v[field].special_on_crit.desc then
-					desc:add({"color","RED"}, "When this weapon crits: "..v[field].special_on_crit.desc, {"color","LAST"}, true)
-				else
-					found = true
+			-- Multiple specials
+			local list = {}
+			for _, special in pairs(special) do
+				list[special.desc] = {10, special.desc}
+			end
+			return list
+		end
+
+		compare_list(
+			"On weapon hit:",
+			function(combat)
+				local list = {}
+				-- Get complex damage types
+				for dt, amount in pairs(combat.melee_project or combat.ranged_project or {}) do
+					local dt_def = DamageType:get(dt)
+					if dt_def and dt_def.tdesc then
+						list[dt] = {0, dt_def.tdesc, amount}
+					end
 				end
+				-- Get specials
+				table.merge(list, get_special_list(combat, 'special_on_hit'))
+				return list
 			end
-		end
-		if special ~= "" then
-			desc:add(found and {"color","WHITE"} or {"color","ORANGE"}, "When this weapon crits: "..special, {"color","LAST"}, true)
-		end
+		)
 
-		local special = ""
-		if combat.special_on_kill then
-			special = combat.special_on_kill.desc
-		end
+		compare_list(
+			"On weapon crit:",
+			function(combat)
+				return get_special_list(combat, 'special_on_crit')
+			end
+		)
 
-		if special ~= "" then
-			desc:add(found and {"color","WHITE"} or {"color","ORANGE"}, "When this weapon kills: "..special, {"color","LAST"}, true)
-		end
+		compare_list(
+			"On weapon kill:",
+			function(combat)
+				return get_special_list(combat, 'special_on_kill')
+			end
+		)
 
 		found = false
 		for i, v in ipairs(compare_with or {}) do
@@ -719,7 +764,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		elseif found then
 			desc:add({"color","RED"}, "When used from stealth a simple attack with it will not break stealth.", {"color","LAST"}, true)
 		end
-		
+
 		if combat.crushing_blow then
 			desc:add({"color", "YELLOW"}, "Crushing Blows: ", {"color", "LAST"}, "Damage dealt by this weapon is increased by half your critical multiplier, if doing so would kill the target.", true)
 		end
@@ -727,23 +772,30 @@ function _M:getTextualDesc(compare_with, use_actor)
 		compare_fields(combat, compare_with, field, "travel_speed", "%+d%%", "Travel speed: ", 100, false, false, add_table)
 
 		compare_fields(combat, compare_with, field, "phasing", "%+d%%", "Damage Shield penetration (this weapon only): ", 1, false, false, add_table)
-		
+
 		compare_fields(combat, compare_with, field, "lifesteal", "%+d%%", "Lifesteal (this weapon only): ", 1, false, false, add_table)
 
 		if combat.tg_type and combat.tg_type == "beam" then
 			desc:add({"color","YELLOW"}, ("Shots beam through all targets."), {"color","LAST"}, true)
 		end
 
-		-- Use the second combat table for melee_project so we don't repeat the tdesc entries
-		compare_table_fields(combat2, compare_with, field, "melee_project", "%+d", "Damage (Melee): ", function(item)
+		compare_table_fields(
+			combat, compare_with, field, "melee_project", "%+d", "Damage (Melee): ",
+			function(item)
 				local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
 				return col[2], (" %s"):format(DamageType.dam_def[item].name),{"color","LAST"}
-			end)
+			end,
+			nil, nil,
+			function(k, v) return not DamageType.dam_def[k].tdesc end)
 
-		compare_table_fields(ranged_combat, compare_with, field, "ranged_project", "%+d", "Damage (Ranged): ", function(item)
+		compare_table_fields(
+			combat, compare_with, field, "ranged_project", "%+d", "Damage (Ranged): ",
+			function(item)
 				local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
 				return col[2], (" %s"):format(DamageType.dam_def[item].name),{"color","LAST"}
-			end)
+			end,
+			nil, nil,
+			function(k, v) return not DamageType.dam_def[k].tdesc end)
 
 		compare_table_fields(combat, compare_with, field, "burst_on_hit", "%+d", "Burst (radius 1) on hit: ", function(item)
 				local col = (DamageType.dam_def[item] and DamageType.dam_def[item].text_color or "#WHITE#"):toTString()
@@ -1181,7 +1233,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		compare_fields(w, compare_with, field, "infravision", "%+d", "Infravision radius: ")
 		compare_fields(w, compare_with, field, "heightened_senses", "%+d", "Heightened senses radius: ")
 		compare_fields(w, compare_with, field, "sight", "%+d", "Sight radius: ")
-		
+
 		compare_fields(w, compare_with, field, "see_stealth", "%+d", "See stealth: ")
 
 		compare_fields(w, compare_with, field, "see_invisible", "%+d", "See invisible: ")
@@ -1218,10 +1270,10 @@ function _M:getTextualDesc(compare_with, use_actor)
 
 		compare_fields(w, compare_with, field, "nature_summon_max", "%+d", "Max wilder summons: ")
 		compare_fields(w, compare_with, field, "nature_summon_regen", "%+.2f", "Life regen bonus (wilder-summons): ")
-		
+
 		compare_fields(w, compare_with, field, "shield_dur", "%+d", "Damage Shield Duration: ")
 		compare_fields(w, compare_with, field, "shield_factor", "%+d%%", "Damage Shield Power: ")
-		
+
 		compare_fields(w, compare_with, field, "iceblock_pierce", "%+d%%", "Ice block penetration: ")
 
 		compare_fields(w, compare_with, field, "slow_projectiles", "%+d%%", "Slows Projectiles: ")
@@ -1229,13 +1281,13 @@ function _M:getTextualDesc(compare_with, use_actor)
 		compare_fields(w, compare_with, field, "paradox_reduce_fails", "%+d", "Reduces paradox failures(equivalent to willpower): ")
 
 		compare_fields(w, compare_with, field, "damage_backfire", "%+d%%", "Damage Backlash: ", nil, true)
-		
+
 		compare_fields(w, compare_with, field, "resist_unseen", "%-d%%", "Reduce all damage from unseen attackers: ")
 
 		if w.undead then
 			desc:add("The wearer is treated as an undead.", true)
 		end
-		
+
 		if w.demon then
 			desc:add("The wearer is treated as a demon.", true)
 		end
@@ -1243,7 +1295,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		if w.blind then
 			desc:add("The wearer is blinded.", true)
 		end
-		
+
 		if w.sleep then
 			desc:add("The wearer is asleep.", true)
 		end
@@ -1251,7 +1303,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		if w.blind_fight then
 			desc:add({"color", "YELLOW"}, "Blind-Fight: ", {"color", "LAST"}, "This item allows the wearer to attack unseen targets without any penalties.", true)
 		end
-		
+
 		if w.lucid_dreamer then
 			desc:add({"color", "YELLOW"}, "Lucid Dreamer: ", {"color", "LAST"}, "This item allows the wearer to act while sleeping.", true)
 		end
@@ -1259,7 +1311,7 @@ function _M:getTextualDesc(compare_with, use_actor)
 		if w.no_breath then
 			desc:add("The wearer no longer has to breathe.", true)
 		end
-		
+
 		if w.quick_weapon_swap then
 			desc:add({"color", "YELLOW"}, "Quick Weapon Swap:", {"color", "LAST"}, "This item allows the wearer to swap to their secondary weapon without spending a turn.", true)
 		end
diff --git a/game/modules/tome/class/Zone.lua b/game/modules/tome/class/Zone.lua
index 69c025dd50aca570c6325fb383d4c1a321704d88..efa81099d127abc667fea694c26b934c6fffc1b5 100644
--- a/game/modules/tome/class/Zone.lua
+++ b/game/modules/tome/class/Zone.lua
@@ -25,6 +25,24 @@ module(..., package.seeall, class.inherit(Zone))
 
 _M:enableLastPersistZones(3)
 
+-- Merge special_on_crit values.
+_M:addEgoRule("object", function(dvalue, svalue, key, dst, src, rules, state)
+	-- Only work on the special_on_* keys.
+	if key ~= 'special_on_hit' and key ~= 'special_on_crit' and key ~= 'special_on_kill' then return end
+	-- If the special isn't a table, make it an empty one.
+	if type(dvalue) ~= 'table' then dvalue = {} end
+	if type(svalue) ~= 'table' then svalue = {} end
+	-- If the special is a single special, wrap it to allow multiple.
+	if dvalue.fct then dvalue = {dvalue} end
+	if svalue.fct then svalue = {svalue} end
+	-- Save changes to the specials.
+	dst[key] = dvalue
+	src[key] = svalue
+	-- Return false so we can let the normal table merge take care of
+	-- the rest.
+	return false
+end)
+
 --- Called when the zone file is loaded
 function _M:onLoadZoneFile(basedir)
 	-- Load events if they exist
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index cc75d8f9dc4995e9ec688ccaff7f4e01eb734998..aa9c71f16228575a171ea2fd56050800bd0b7813 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -342,7 +342,7 @@ end
 function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 	damtype = damtype or (weapon and weapon.damtype) or DamageType.PHYSICAL
 	mult = mult or 1
-	
+
 	--Life Steal
 	if weapon and weapon.lifesteal then
 		self:attr("lifesteal", weapon.lifesteal)
@@ -351,7 +351,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 
 	local mode = "other"
 	if self:hasShield() then mode = "shield"
-	elseif self:hasTwoHandedWeapon() then mode = "twohanded" 
+	elseif self:hasTwoHandedWeapon() then mode = "twohanded"
 	elseif self:hasDualWeapon() then mode = "dualwield"
 	end
 	self.turn_procs.weapon_type = {kind=weapon and weapon.talented or "unknown", mode=mode}
@@ -426,7 +426,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 				game:delayedLogDamage(self, target, 0, ("%s(%d parried#LAST#)"):format(DamageType:get(damtype).text_color or "#aaaaaa#", deflect), false)
 				dam = math.max(dam - deflect,0)
 				print("[ATTACK] after DUAL_WEAPON_DEFENSE", dam)
-			end 
+			end
 		end
 		if target.knowTalent and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) and not target:attr("encased_in_ice") then
 			local deflected = math.min(dam, target:callTalent(target.T_GESTURE_OF_GUARDING, "doGuard")) or 0
@@ -487,10 +487,10 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 		if weapon and weapon.phasing then
 			self:attr("damage_shield_penetrate", weapon.phasing)
 		end
-	
+
 		local oldproj = DamageType:getProjectingFor(self)
 		if self.__talent_running then DamageType:projectingFor(self, {project_type={talent=self.__talent_running}}) end
-		
+
 		if weapon and weapon.crushing_blow then self:attr("crushing_blow", 1) end
 
 		-- Damage conversion?
@@ -515,7 +515,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 		if dam > 0 then
 			DamageType:get(damtype).projector(self, target.x, target.y, damtype, math.max(0, dam))
 		end
-		
+
 		if weapon and weapon.crushing_blow then self:attr("crushing_blow", -1) end
 
 		if self.__talent_running then DamageType:projectingFor(self, oldproj) end
@@ -658,7 +658,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 		self:incStamina(-8)
 		self.shattering_impact_last_turn = game.turn
 	end
-	
+
 	-- Damage Backlash
 	if dam > 0 and self.attr and self:attr("damage_backfire") then
 		local hurt = math.min(dam, target.life) * self.damage_backfire / 100
@@ -772,13 +772,35 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 	end
 
 	-- Special effect
-	if hitted and weapon and weapon.special_on_hit and weapon.special_on_hit.fct and (not target.dead or weapon.special_on_hit.on_kill) then
-		weapon.special_on_hit.fct(weapon, self, target, dam)
+	if hitted and weapon and weapon.special_on_hit then
+		local specials = weapon.special_on_hit
+		if specials.fct then specials = {specials} end
+		for _, special in ipairs(specials) do
+			if special.fct and (not target.dead or special.on_kill) then
+				special.fct(weapon, self, target, dam)
+			end
+		end
+	end
+
+	if hitted and crit and weapon and weapon.special_on_crit then
+		local specials = weapon.special_on_crit
+		if specials.fct then specials = {specials} end
+		for _, special in ipairs(specials) do
+			if special.fct and (not target.dead or special.on_kill) then
+				special.fct(weapon, self, target, dam)
+			end
+		end
 	end
 
-	if hitted and crit and weapon and weapon.special_on_crit and weapon.special_on_crit.fct and (not target.dead or weapon.special_on_crit.on_kill) then
-		weapon.special_on_crit.fct(weapon, self, target, dam)
-      end
+	if hitted and weapon and weapon.special_on_kill and target.dead then
+		local specials = weapon.special_on_kill
+		if specials.fct then specials = {specials} end
+		for _, special in ipairs(specials) do
+			if special.fct then
+				special.fct(weapon, self, target, dam)
+			end
+		end
+	end
 
 	if hitted and weapon and weapon.special_on_kill and weapon.special_on_kill.fct and target.dead then
 		weapon.special_on_kill.fct(weapon, self, target, dam)
@@ -836,9 +858,9 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 		if cadam then
 			game.logSeen(self, "%s counters the attack!", target.name:capitalize())
 			target:attackTarget(self, nil, cadam, true)
-		end 
-	end 
-	
+		end
+	end
+
 	-- Gesture of Guarding counterattack
 	if hitted and not target.dead and not target:attr("stunned") and not target:attr("dazed") and not target:attr("stoned") and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) then
 		local t = target:getTalentFromId(target.T_GESTURE_OF_GUARDING)
@@ -868,8 +890,8 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 	-- Roll with it
 	if hitted and target:attr("knockback_on_hit") and not target.turn_procs.roll_with_it and rng.percent(util.bound(dam, 0, 100)) then
 		local ox, oy = self.x, self.y
-		game:onTickEnd(function() 
-			target:knockback(ox, oy, 1) 
+		game:onTickEnd(function()
+			target:knockback(ox, oy, 1)
 			if not target:hasEffect(target.EFF_WILD_SPEED) then target:setEffect(target.EFF_WILD_SPEED, 1, {power=200}) end
 		end)
 		target.turn_procs.roll_with_it = true
@@ -959,7 +981,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 
 	self.turn_procs.weapon_type = nil
 	self.__global_accuracy_damage_bonus = nil
-	
+
 	--Life Steal
 	if weapon and weapon.lifesteal then
 		self:attr("lifesteal", -weapon.lifesteal)
@@ -1005,7 +1027,7 @@ function _M:combatGetTraining(weapon)
 	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
-			if self:knowTalent(tid) then 
+			if self:knowTalent(tid) then
 				if self:getTalentLevel(tid) > max then
 					ktid = tid
 					max = self:getTalentLevel(tid)
@@ -1063,7 +1085,7 @@ function _M:combatDefenseBase(fake)
 	end
 	local d = math.max(0, self.combat_def + (self:getDex() - 10) * 0.35 + (self:getLck() - 50) * 0.4)
 	local mult = 1
-	
+
 	if self:hasLightArmor() and self:knowTalent(self.T_MOBILE_DEFENCE) then
 		mult = mult + self:callTalent(self.T_MOBILE_DEFENCE,"getDef")
 	end
@@ -1310,7 +1332,7 @@ function _M:combatTalentScale(t, low, high, power, add, shift, raw)
 	if power == "log" then -- always >= 0
 		return math.max(0, m * math.log10(tl + shift) + b + add)
 --		return math.max(0, m * math.log10(tl + shift) + b + add), m, b
-	else 
+	else
 		return math.max(0, m * (tl + shift)^power + b + add)
 --		return math.max(0, m * (tl + shift)^power + b + add), m, b
 	end
@@ -1339,7 +1361,7 @@ function _M:combatStatScale(stat, low, high, power, add, shift)
 	if power == "log" then -- always >= 0
 		return math.max(0, m * math.log10(stat + shift) + b + add)
 --		return math.max(0, m * math.log10(stat + shift) + b + add), m, b
-	else 
+	else
 		return math.max(0, m * (stat + shift)^power + b + add)
 --		return math.max(0, m * (stat + shift)^power + b + add), m, b
 	end
@@ -1901,11 +1923,11 @@ function _M:combatMentalResist(fake)
 
 	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) and not fake then
 		d = d * (1-self.tempeffect_def.EFF_CURSE_OF_NIGHTMARES.getVisionsReduction(nm, nm.level)/100)
-	end	
+	end
 	return self:rescaleCombatStats(d)
 end
 
@@ -1923,8 +1945,8 @@ end
 --- Returns the resistance
 function _M:combatGetResist(type)
 	local power = 100
-	if self.force_use_resist and self.force_use_resist ~= type then 
-		type = self.force_use_resist 
+	if self.force_use_resist and self.force_use_resist ~= type then
+		type = self.force_use_resist
 		power = self.force_use_resist_percent or 100
 	end
 
@@ -2321,5 +2343,5 @@ end
 function _M:logCombat(target, style, ...)
 	if not game.uiset or not game.uiset.logdisplay then return end
 	local visible, srcSeen, tgtSeen = game:logVisible(self, target)  -- should a message be displayed?
-	if visible then game.uiset.logdisplay(game:logMessage(self, srcSeen, target, tgtSeen, style, ...)) end 
+	if visible then game.uiset.logdisplay(game:logMessage(self, srcSeen, target, tgtSeen, style, ...)) end
 end
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index a0a9cfff412a79a46be5e1b7427962f15ae48dbb..022d695c2cefea79fb7993ce720d2223ae5d9da6 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -47,7 +47,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 		src.elemental_mastery = old
 		return dam
 	end
-	
+
 	if src:attr("twilight_mastery") then
 		local ndam = dam * src.twilight_mastery
 		local old = src.twilight_mastery
@@ -236,7 +236,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 		if dam ~= lastdam then
 			game:delayedLogDamage(src, target, 0, ("%s(%d to psi shield)#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", lastdam-dam), false)
 		end
-		
+
 		--target.T_STONE_FORTRESS could be checked/applied here (ReduceDamage function in Dwarven Fortress talent)
 
 		-- Damage Smearing
@@ -263,8 +263,8 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 				if src:checkClassification(tostring(k)) then res = math.max(res, v) end
 			end
 
-			res = math.min(res, target.resists_cap_actor_type or 90) 
-			
+			res = math.min(res, target.resists_cap_actor_type or 90)
+
 			if res ~= 0 then
 				print("[PROJECTOR] before entity", src.type, "resists dam", dam)
 				if res >= 100 then dam = 0
@@ -310,7 +310,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 			dam= dam - demon_block
 			target:incVim((-demon_block)/20)
 		end
-		
+
 		-- Static reduce damage
 		if dam > 0 and target.isTalentActive and target:isTalentActive(target.T_ANTIMAGIC_SHIELD) then
 			local t = target:getTalentFromId(target.T_ANTIMAGIC_SHIELD)
@@ -359,16 +359,16 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 			local def = src.tempeffect_def[src.EFF_CURSE_OF_MISFORTUNE]
 			dam = def.doUnfortunateEnd(src, eff, target, dam)
 		end
-		
+
 		if src:attr("crushing_blow") and (dam * (1.25 + (src.combat_critical_power or 0)/200)) > target.life then
 			dam = dam * (1.25 + (src.combat_critical_power or 0)/200)
 			game.logPlayer(src, "You end your target with a crushing blow!")
 		end
-		
+
 		if target:attr("resist_unseen") and not target:canSee(src) then
 			dam = dam * (1 - math.min(target.resist_unseen,100)/100)
 		end
-		
+
 		-- Sanctuary: reduces damage if it comes from outside of Gloom
 		if target.isTalentActive and target:isTalentActive(target.T_GLOOM) and target:knowTalent(target.T_SANCTUARY) then
 			if tmp and tmp.sanctuaryDamageChange then
@@ -397,7 +397,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 					print("[PROJECTOR] Chant of Fortress (source) dam", dam)
 				end
 			end
-		end		
+		end
 
 		-- Psychic Projection
 		if src.attr and src:attr("is_psychic_projection") and not game.zone.is_dream_scape then
@@ -408,7 +408,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 			end
 		end
 
-		if src.necrotic_minion_be_nice and src.summoner == target then 
+		if src.necrotic_minion_be_nice and src.summoner == target then
 			dam = dam * (1 - src.necrotic_minion_be_nice)
 		end
 
@@ -649,7 +649,7 @@ newDamageType{
 			else
 				local old = src.fire_convert_to
 				src.fire_convert_to = nil
-				dam = DamageType:get(old[1]).projector(src, x, y, old[1], dam * old[2] / 100) + 
+				dam = DamageType:get(old[1]).projector(src, x, y, old[1], dam * old[2] / 100) +
 				       DamageType:get(type).projector(src, x, y, type, dam * (100 - old[2]) / 100)
 				src.fire_convert_to = old
 				return dam
@@ -803,7 +803,7 @@ newDamageType{
 		end
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
-			local energyDrain = (game.energy_to_act * 0.2)	
+			local energyDrain = (game.energy_to_act * 0.2)
 			target.energy.value = target.energy.value - energyDrain
 		end
 	end,
@@ -1731,8 +1731,19 @@ newDamageType{
 -- Log entries are pretty limited currently because it can be quite spammy with the default messages already
 newDamageType{
 	name = "item mind gloom", type = "ITEM_MIND_GLOOM",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to cause #YELLOW#random insanity#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to cause #YELLOW#random insanity#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1762,8 +1773,19 @@ newDamageType{
 
 newDamageType{
 	name = "item darkness numbing", type = "ITEM_DARKNESS_NUMBING",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to inflict #GREY#damage reduction#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to inflict #GREY#damage reduction#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1777,18 +1799,29 @@ newDamageType{
 
 newDamageType{
 	name = "item temporal energize", type = "ITEM_TEMPORAL_ENERGIZE",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to gain #LIGHT_STEEL_BLUE#%d%% of a turn#LAST#"):format(dam, 10) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to gain #LIGHT_STEEL_BLUE#10%% of a turn#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and src and src.name and rng.percent(dam) then
-				if src.turn_procs and src.turn_procs.item_temporal_energize and src.turn_procs.item_temporal_energize > 3 then 
+				if src.turn_procs and src.turn_procs.item_temporal_energize and src.turn_procs.item_temporal_energize > 3 then
 					game.logSeen(src, "#LIGHT_STEEL_BLUE#%s can't gain any more energy this turn! ", src.name:capitalize())
 				return
 				end
 
-				local energy = (game.energy_to_act * 0.1)	
+				local energy = (game.energy_to_act * 0.1)
 				src.energy.value = src.energy.value + energy
 				--game.logSeen(target, "Time seems to bend and quicken energizing %s!", src.name:capitalize())
 
@@ -1799,8 +1832,19 @@ newDamageType{
 
 newDamageType{
 	name = "item acid corrode", type = "ITEM_ACID_CORRODE", text_color = "#GREEN#",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to #GREEN#corrode armor#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to #GREEN#corrode armor#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1814,8 +1858,19 @@ newDamageType{
 
 newDamageType{
 	name = "item light blind", type = "ITEM_LIGHT_BLIND",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to #YELLOW#blind#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to #YELLOW#blind#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1832,8 +1887,19 @@ newDamageType{
 
 newDamageType{
 	name = "item lightning daze", type = "ITEM_LIGHTNING_DAZE",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to #ROYAL_BLUE#daze#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to #ROYAL_BLUE#daze#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1848,11 +1914,22 @@ newDamageType{
 		end
 	end,
 }
-  
+
 newDamageType{
 	name = "item blight disease", type = "ITEM_BLIGHT_DISEASE", text_color = "#DARK_GREEN#",
-	tdesc = function(dam) 
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to #DARK_GREEN#disease#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to #DARK_GREEN#disease#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1866,8 +1943,19 @@ newDamageType{
 
 newDamageType{
 	name = "item manaburn arcane", type = "ITEM_ANTIMAGIC_MANABURN", text_color = "#PURPLE#",
-	tdesc = function(dam)
-		return ("#DARK_ORCHID#%d arcane resource#LAST# burn"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d#LAST#)"):format(diff)
+			end
+		end
+		return ("* #DARK_ORCHID#%d arcane resource#LAST# burn%s")
+			:format(dam or 0, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1897,8 +1985,19 @@ newDamageType{
 
 newDamageType{
 	name = "item nature slow", type = "ITEM_NATURE_SLOW", text_color = "#LIGHT_GREEN#",
-	tdesc = function(dam)
-		return ("Slows global speed by #LIGHT_GREEN#%d%%#LAST#"):format(dam) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* Slows global speed by #LIGHT_GREEN#%d%%#LAST#%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1911,8 +2010,19 @@ newDamageType{
 -- Reduces all offensive powers by 20%
 newDamageType{
 	name = "item antimagic scouring", type = "ITEM_ANTIMAGIC_SCOURING", text_color = "#ORCHID#",
-	tdesc = function(dam)
-		return ("#LIGHT_GREEN#%d%%#LAST# chance to #ORCHID#reduce powers#LAST# by %d%%"):format(dam, 20) 
+	tdesc = function(dam, oldDam)
+		parens = ""
+		dam = dam or 0
+		if oldDam then
+			diff = dam - oldDam
+			if diff > 0 then
+				parens = (" (#LIGHT_GREEN#+%d%%#LAST#)"):format(diff)
+			elseif diff < 0 then
+				parens = (" (#RED#%d%%#LAST#)"):format(diff)
+			end
+		end
+		return ("* #LIGHT_GREEN#%d%%#LAST# chance to #ORCHID#reduce powers#LAST# by %d%%%s")
+			:format(dam, parens)
 	end,
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -2118,17 +2228,17 @@ newDamageType{
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and not target:attr("undead") then
-			
+
 			target:setEffect(target.EFF_EMPOWERED_HEALING, 1, {power=(dam/200)})
 			if dam >= 100 then target:attr("allow_on_heal", 1) end
 			target:heal(dam, src)
-			
+
 			-- If the target is shielded already then add to the shield power, else add a shield
 			local shield_power = dam * util.bound((target.healing_factor or 1), 0, 2.5)
-			if not target:hasEffect(target.EFF_DAMAGE_SHIELD) then 
+			if not target:hasEffect(target.EFF_DAMAGE_SHIELD) then
 				target:setEffect(target.EFF_DAMAGE_SHIELD, 2, {power=shield_power})
 			else
-				-- Shields can't usually merge, so change the parameters manually 
+				-- Shields can't usually merge, so change the parameters manually
 				local shield = target:hasEffect(target.EFF_DAMAGE_SHIELD)
 				shield.power = shield.power + shield_power
 				target.damage_shield_absorb = target.damage_shield_absorb + shield_power
@@ -2162,8 +2272,8 @@ newDamageType{
 			if dam >= 100 then src:attr("allow_on_heal", 1) end
 			src:heal(dam / 2, src)
 			if dam >= 100 then src:attr("allow_on_heal", -1) end
-		
-			
+
+
 		end
 	end,
 }
@@ -3083,7 +3193,7 @@ newDamageType{
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
 			local realdam = DamageType:get(DamageType.SLIME).projector(src, x, y, DamageType.SLIME, {dam=dam.dam, power=0.30})
-			
+
 			if dam.nb > 0 then
 				dam.done = dam.done or {}
 				dam.done[target.uid] = true
@@ -3103,7 +3213,7 @@ newDamageType{
 				end
 			end
 			return realdam
-		end		
+		end
 	end,
 }