diff --git a/game/engines/default/engine/Map.lua b/game/engines/default/engine/Map.lua
index 9823ac71498b14cfd85789fe408a180ca14a35df..2e54bc5ff3ee682d7cb88cb4e4ccae8da717f75c 100644
--- a/game/engines/default/engine/Map.lua
+++ b/game/engines/default/engine/Map.lua
@@ -1007,6 +1007,7 @@ function _M:addEffect(src, x, y, duration, damtype, dam, radius, dir, angle, ove
 	table.insert(self.effects, e)
 
 	self.changed = true
+	return e
 end
 
 --- Display the overlay effects, called by self:display()
@@ -1041,7 +1042,9 @@ function _M:processEffects()
 				elseif act and e.src and e.src.reactionToward and (e.src:reactionToward(act) >= 0) and not ((type(e.friendlyfire) == "number" and rng.percent(e.friendlyfire)) or (type(e.friendlyfire) ~= "number" and e.friendlyfire)) then
 				-- Otherwise hit
 				else
+					e.src.__project_source = e -- intermediate projector source
 					DamageType:get(e.damtype).projector(e.src, lx, ly, e.damtype, e.dam)
+					e.src.__project_source = nil
 				end
 			end
 		end
diff --git a/game/engines/default/engine/Projectile.lua b/game/engines/default/engine/Projectile.lua
index 26732cc668a7aa28dffb3188e526e72766ad449c..31f5ee46787ee658c6a317828a1248dc8acc1800 100644
--- a/game/engines/default/engine/Projectile.lua
+++ b/game/engines/default/engine/Projectile.lua
@@ -205,6 +205,7 @@ function _M:act()
 		if self.project then
 			local x, y, act, stop = self.src:projectDoMove(self.project.def.typ, self.project.def.x, self.project.def.y, self.x, self.y, self.project.def.start_x, self.project.def.start_y)
 			if x and y then self:move(x, y) end
+			if self.src then self.src.__project_source = self end -- intermediate projector source
 			if act then self.src:projectDoAct(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj) end
 			if stop then
 				local block, hit, hit_radius = false, true, true
@@ -219,9 +220,11 @@ function _M:act()
 				end
 				self.src:projectDoStop(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj, radius_x, radius_y, self)
 			end
+			if self.src then self.src.__project_source = nil end -- intermediate projector source
 		elseif self.homing then
 			self:moveDirection(self.homing.target.x, self.homing.target.y)
 			self.homing.count = self.homing.count - 1
+			if self.src then self.src.__project_source = self end -- intermediate projector source
 			if (self.x == self.homing.target.x and self.y == self.homing.target.y) or self.homing.count <= 0 then
 				game.level:removeEntity(self, true)
 				self.dead = true
@@ -229,18 +232,21 @@ function _M:act()
 			else
 				self.homing.on_move(self, self.src)
 			end
+			if self.src then self.src.__project_source = nil end -- intermediate projector source
 		end
 	end
-
 	return true
 end
 
 --- Something moved in the same spot as us, hit ?
 function _M:on_move(x, y, target)
+	self.src.__project_source = self -- intermediate projector source
 	if self.project and self.project.def.typ.line then self.src:projectDoAct(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj) end
 	if self.project and self.project.def.typ.stop_block then
+
 		self.src:projectDoStop(self.project.def.typ, self.project.def.tg, self.project.def.damtype, self.project.def.dam, self.project.def.particles, self.x, self.y, self.tmp_proj, self.x, self.y, self)
 	end
+	self.src.__project_source = nil
 end
 
 --- Premature end
diff --git a/game/engines/default/engine/Trap.lua b/game/engines/default/engine/Trap.lua
index 3af64cfe1dd2f0917ad1dbaf0b79a0a3f3cc5498..3443301ed9e877c39ca49a69c4d662230a66f058 100644
--- a/game/engines/default/engine/Trap.lua
+++ b/game/engines/default/engine/Trap.lua
@@ -135,7 +135,9 @@ function _M:trigger(x, y, who)
 		game.logSeen(who, "%s", str)
 	end
 	local known, del = false, false
+	if self.summoner then self.summoner.__project_source = self end -- intermediate projector source
 	if self.triggered then known, del = self:triggered(x, y, who) end
+	if self.summoner then self.summoner.__project_source = nil end
 	if known then
 		self:setKnown(who, true)
 		game.level.map:updateMap(x, y)
diff --git a/game/engines/default/engine/interface/ActorProject.lua b/game/engines/default/engine/interface/ActorProject.lua
index 207280f1202ea96b488c59baf264e24bc23ba7fc..886ea386d3faaf6e58afbbbb3fdd35992ea1dd72 100644
--- a/game/engines/default/engine/interface/ActorProject.lua
+++ b/game/engines/default/engine/interface/ActorProject.lua
@@ -311,6 +311,7 @@ function _M:projectile(t, x, y, damtype, dam, particles)
 	game.zone:addEntity(game.level, proj, "projectile", typ.start_x, typ.start_y)
 
 	self:check("on_projectile_fired", proj, typ, x, y, damtype, dam, particles)
+	return proj
 end
 
 -- @param typ a target type table
diff --git a/game/engines/default/engine/interface/ActorTemporaryEffects.lua b/game/engines/default/engine/interface/ActorTemporaryEffects.lua
index 7a58d713da01f0864d390e2a209dcd792af2eee7..a057bafaea5b5ca87751af53578805c1704dde2b 100644
--- a/game/engines/default/engine/interface/ActorTemporaryEffects.lua
+++ b/game/engines/default/engine/interface/ActorTemporaryEffects.lua
@@ -73,9 +73,11 @@ function _M:timedEffects(filter)
 				todel[#todel+1] = eff
 			else
 				if def.on_timeout then
+					if p.src then p.src.__project_source = p end -- intermediate projector source
 					if def.on_timeout(self, p) then
 						todel[#todel+1] = eff
 					end
+					if p.src then p.src.__project_source = nil end
 				end
 			end
 			p.dur = p.dur - def.decrease
@@ -103,6 +105,7 @@ function _M:setEffect(eff_id, dur, p, silent)
 		if not p[k] then p[k] = e end
 	end
 	p.dur = dur
+	p.effect_id = eff_id
 	self:check("on_set_temporary_effect", eff_id, _M.tempeffect_def[eff_id], p)
 	if p.dur <= 0 then return self:removeEffect(eff_id) end
 
diff --git a/game/modules/tome/class/Actor.lua b/game/modules/tome/class/Actor.lua
index 97f583b835b9bed35f96c8e7729b094fe3020765..2c1d209e1062e2de31d05455679a38a8e97a65f9 100644
--- a/game/modules/tome/class/Actor.lua
+++ b/game/modules/tome/class/Actor.lua
@@ -1046,7 +1046,7 @@ function _M:move(x, y, force)
 		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)
-			if trap and not trap:knownBy(self) and self:checkHit(power, trap.detect_power) then
+			if trap and not trap:knownBy(self) and self:canSee(trap) and self:checkHit(power, trap.detect_power) then
 				trap:setKnown(self, true)
 				game.level.map:updateMap(x, y)
 				game.logPlayer(self, "You have found a trap (%s)!", trap:getName())
@@ -1539,7 +1539,7 @@ function _M:onHeal(value, src)
 		if eff.src == self then
 			game.logSeen(self, "%s heal is doubled!", self.name)
 		else
-			game.logSeen(self, "%s steals %s heal!", eff.src.name:capitalize(), self.name)
+			self:logCombat(eff.src, "#Target# steals #Source#'s heal!")
 			return 0
 		end
 	end
@@ -1663,7 +1663,7 @@ function _M:onTakeHit(value, src, death_note)
 
 		local a = rng.table(tgts)
 		if a then
-			game.logSeen(self, "Some of the damage has been displaced onto %s!", a.name:capitalize())
+			self:logCombat(a, "Some of the damage has been displaced onto #Target#!")
 			a:takeHit(value / 2, self)
 			value = value / 2
 		end
@@ -1741,7 +1741,7 @@ function _M:onTakeHit(value, src, death_note)
 			local a = game.level.map(src.x, src.y, Map.ACTOR)
 			if a and self:reactionToward(a) < 0 then
 				a:takeHit(math.ceil(reflect_damage * reflection), self)
-				game.logSeen(self, "The damage shield reflects %d damage back to %s!", math.ceil(reflect_damage * reflection), a.name:capitalize())
+				self:logCombat(a, "The damage shield reflects %d damage back to #Target#!", math.ceil(reflect_damage * reflection))
 			end
 		end
 		-- If we are at the end of the capacity, release the time shield damage
@@ -1755,7 +1755,7 @@ function _M:onTakeHit(value, src, death_note)
 		-- Absorb damage into the displacement shield
 		if rng.percent(self.displacement_shield_chance) then
 			if value <= self.displacement_shield then
-				game.logSeen(self, "The displacement shield teleports the damage to %s!", self.displacement_shield_target.name)
+				game:delayedLogMessage(self, src,  "displacement_shield", "#CRIMSON##Source# teleports some damage to #Target#!")
 				self.displacement_shield = self.displacement_shield - value
 				self.displacement_shield_target:takeHit(value, src)
 				value = 0
@@ -2003,7 +2003,7 @@ function _M:onTakeHit(value, src, death_note)
 			a:removeAllMOs()
 			a.x, a.y = nil, nil
 			game.zone:addEntity(game.level, a, "actor", x, y)
-			game.logSeen(self, "%s is split in two!", self.name:capitalize())
+			game.logSeen(self, "%s splits in two!", self.name:capitalize())
 			value = value / 2
 		end
 	end
@@ -2021,7 +2021,10 @@ function _M:onTakeHit(value, src, death_note)
 			damage_to_psi = self:getPsi()
 			self:incPsi(-damage_to_psi)
 		end
-		game.logSeen(self, "%s's mind suffers #YELLOW#%d psi#LAST# damage from the attack.", self.name:capitalize(), damage_to_psi*psi_damage_resist)
+		local mindcolor = DamageType:get(DamageType.MIND).text_color or "#aaaaaa#"
+		game:delayedLogMessage(self, nil, "Solipsism hit", mindcolor.."#Source# converts some damage to Psi!")
+		game:delayedLogDamage(src, self, damage_to_psi*psi_damage_resist, ("%s%d %s#LAST#"):format(mindcolor, damage_to_psi*psi_damage_resist, "to psi"), false)
+
 		value = value - damage_to_psi
 	end
 
@@ -2077,7 +2080,7 @@ function _M:onTakeHit(value, src, death_note)
 		local vt = self:getTalentFromId(self.T_LEECH)
 		self:incVim(vt.getVim(self, vt))
 		self:heal(vt.getHeal(self, vt))
-		game.logPlayer(self, "#AQUAMARINE#You leech a part of %s's vim.", src.name:capitalize())
+		if self.player then src:logCombat(src, "#AQUAMARINE#You leech a part of #Target#'s vim.") end
 	end
 
 	-- Invisible on hit
@@ -2121,7 +2124,7 @@ function _M:onTakeHit(value, src, death_note)
 		local leech = math.min(value, self.life) * src.life_leech_value / 100
 		if leech > 0 then
 			src:heal(leech)
-			game.logSeen(src, "#CRIMSON#%s leeches life from its victim!", src.name:capitalize())
+			game:delayedLogMessage(src, self, "life_leech"..self.uid, "#CRIMSON##Source# leeches life from #Target#!")
 		end
 	end
 
@@ -2139,7 +2142,7 @@ function _M:onTakeHit(value, src, death_note)
 		src:incStamina(leech * 0.65)
 		src:incHate(leech * 0.2)
 		src:incPsi(leech * 0.2)
-		game.logSeen(src, "#CRIMSON#%s leeches energies from its victim!", src.name:capitalize())
+		game:delayedLogMessage(src, self, "resource_leech", "#CRIMSON##Source# leeches energies from #Target#!")
 	end
 
 	if self:knowTalent(self.T_DRACONIC_BODY) then
@@ -4036,7 +4039,7 @@ function _M:postUseTalent(ab, ret, silent)
 			local t = rng.tableRemove(tids)
 			if not t then break end
 			self.talents_cd[t.id] = self:attr("random_talent_cooldown_on_use_turns")
-			game.log("%s talent '%s%s' is disrupted by the mind parasite.", self.name:capitalize(), (t.display_entity and t.display_entity:getDisplayString() or ""), t.name)
+			game.logSeen(self, "%s talent '%s%s' is disrupted by the mind parasite.", self.name:capitalize(), (t.display_entity and t.display_entity:getDisplayString() or ""), t.name)
 		end
 	end
 
@@ -4385,7 +4388,7 @@ function _M:suffocate(value, src, death_message)
 	return false, true
 end
 
---- Can the actor see the target actor
+-- Can the actor see the target actor (or other entity)
 -- This does not check LOS or such, only the actual ability to see it.<br/>
 -- Check for telepathy, invisibility, stealth, ...
 function _M:canSeeNoCache(actor, def, def_pct)
@@ -4415,7 +4418,7 @@ function _M:canSeeNoCache(actor, def, def_pct)
 	end
 
 	-- Check for stealth. Checks against the target cunning and level
-	if actor:attr("stealth") and actor ~= self then
+	if actor ~= self and actor.attr and actor:attr("stealth") then
 		local def = self:combatSeeStealth()
 		local hit, chance = self:checkHitOld(def, actor:attr("stealth") + (actor:attr("inc_stealth") or 0), 0, 100)
 		if not hit then
@@ -4424,7 +4427,7 @@ function _M:canSeeNoCache(actor, def, def_pct)
 	end
 
 	-- Check for invisibility. This is a "simple" checkHit between invisible and see_invisible attrs
-	if actor:attr("invisible") and actor ~= self then
+	if actor ~= self and actor.attr and actor:attr("invisible") then
 		-- Special case, 0 see invisible, can NEVER see invisible things
 		local def = self:combatSeeInvisible()
 		if def <= 0 then return false, 0 end
@@ -4569,6 +4572,8 @@ local save_for_effects = {
 
 --- Adjust temporary effects
 function _M:on_set_temporary_effect(eff_id, e, p)
+	p.getName = self.tempeffect_def[eff_id].getName
+	p.resolveSource = self.tempeffect_def[eff_id].resolveSource
 	if p.apply_power and (save_for_effects[e.type] or p.apply_save) then
 		local save = 0
 		p.maximum = p.dur
@@ -4669,7 +4674,7 @@ function _M:on_project_acquire(tx, ty, who, t, x, y, damtype, dam, particles, is
 		else
 			dir = "to the "..dir.."!"
 		end
-		game.logSeen(self, "%s deflects the projectile from %s %s", self.name:capitalize(), who.name, dir)
+		self:logCombat(who, "#Source# deflects the projectile from #Target# %s", dir)
 		return true
 	end
 end
diff --git a/game/modules/tome/class/FortressPC.lua b/game/modules/tome/class/FortressPC.lua
index f4e8d3e2575212832339dc675042cccdcded2100..13ebd56ac445f2b6929ec3ab4aebf211b9bddf3b 100644
--- a/game/modules/tome/class/FortressPC.lua
+++ b/game/modules/tome/class/FortressPC.lua
@@ -170,7 +170,7 @@ function _M:moveModActor(x, y, force)
 		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)
-			if trap and not trap:knownBy(self) and self:checkHit(power, trap.detect_power) then
+			if trap and not trap:knownBy(self) and self:canSee(trap) and self:checkHit(power, trap.detect_power) then
 				trap:setKnown(self, true)
 				game.level.map:updateMap(x, y)
 				game.logPlayer(self, "You have found a trap (%s)!", trap:getName())
diff --git a/game/modules/tome/class/Game.lua b/game/modules/tome/class/Game.lua
index 26fb61a1f1c821cd8b865a6b49286398355ae69a..3c61524ed49a83ea435d75f5ca10e2b99ae42b17 100644
--- a/game/modules/tome/class/Game.lua
+++ b/game/modules/tome/class/Game.lua
@@ -94,6 +94,7 @@ end
 function _M:runReal()
 
 	self.delayed_log_damage = {}
+	self.delayed_log_messages = {}
 	self.calendar = Calendar.new("/data/calendar_allied.lua", "Today is the %s %s of the %s year of the Age of Ascendancy of Maj'Eyal.\nThe time is %02d:%02d.", 122, 167, 11)
 
 	self.uiset:activate()
@@ -1132,6 +1133,7 @@ function _M:tick()
 	end
 
 	-- Check damages to log
+	self:displayDelayedLogMessages()
 	self:displayDelayedLogDamage()
 
 	if self.tick_loopback then
@@ -1144,28 +1146,125 @@ function _M:tick()
 --	if self.on_tick_end and #self.on_tick_end > 0 then return true end
 end
 
+-- Game Log management functions:
+-- logVisible to determine if a message should be visible to the player
+-- logMessage to add a message to the display
+-- delayedLogMessage to queue an actor-specific message for display at the end of the current game tick
+-- displayDelayedLogMessages() to display the queued messages (before combat damage messages)
+-- delayedLogDamage to queue a combat (damage) message for display at the end of the current game tick
+-- displayDelayedLogDamage to display the queued combat messages
+
+-- output a message to the log based on the visibility of an actor to the player
+function _M.logSeen(e, style, ...) 
+	if e and e.player or (not e.dead and e.x and e.y and game.level and game.level.map.seens(e.x, e.y) and game.player:canSee(e)) then game.log(style, ...) end 
+end
+
+-- determine whether an action between 2 actors should produce a message in the log and if the player
+-- can identify them
+-- output: src, srcSeen: source display?, identify?
+-- tgt, tgtSeen: target display?, identify?
+-- output: Visible? and srcSeen (source is identified by the player), tgtSeen(target is identified by the player)
+function _M:logVisible(source, target)
+	-- target should display if it's the player, an actor in a seen tile, or a non-actor without coordinates
+	local tgt = target and (target.player or (target.__is_actor and game.level.map.seens(target.x, target.y)) or (not target.__is_actor and not target.x))
+	local tgtSeen = tgt and (target.player or game.player:canSee(target)) or false
+	local src, srcSeen = false, false
+--	local srcSeen = src and (not source.x or (game.player:canSee(source) and game.player:canSee(target)))
+	-- Special cases
+	if not source.x then -- special case: unpositioned source uses target parameters (for timed effects on target)
+		if tgtSeen then
+			src, srcSeen = tgt, tgtSeen
+		else
+			src, tgt = nil, nil
+		end
+	else -- source should display if it's the player or an actor in a seen tile, or same as target for non-actors
+		src = source.player or (source.__is_actor and game.level.map.seens(source.x, source.y)) or (not source.__is_actor and tgt)
+		srcSeen = src and game.player:canSee(source) or false
+	end	
+	
+	return src or tgt or false, srcSeen, tgtSeen
+end
+
+-- Generate a message (string) for the log with possible source and target,
+-- highlighting the player and taking visibility into account
+-- srcSeen, tgtSeen = can player see(identify) the source, target?
+-- style text message to display, may contain:
+-- #source#|#Source# -> <displayString>..self.name|self.name:capitalize()
+-- #target#|#Target# -> target.name|target.name:capitalize()
+function _M:logMessage(source, srcSeen, target, tgtSeen, style, ...)
+	style = style:format(...)
+	local srcname = "something"
+	local Dstring
+		if source.player then
+			srcname = source.name.."(YOU)"
+		elseif srcSeen then
+			srcname = engine.Entity.check(source, "getName") or source.name or "unknown"
+		end
+		if srcname ~= "something" then Dstring = source.__is_actor and source.getDisplayString and source:getDisplayString() end
+	style = style:gsub("#source#", srcname)
+	style = style:gsub("#Source#", (Dstring or "")..srcname:capitalize())
+	if target then
+		local tgtname = "something"
+			if target.player then
+				tgtname = target.name.."(YOU)"
+			elseif tgtSeen then
+				tgtname = engine.Entity.check(target, "getName") or target.name or "unknown"
+			end
+		style = style:gsub("#target#", tgtname)
+		style = style:gsub("#Target#", tgtname:capitalize())
+	end
+	return style
+end
+
+-- log an entity-specific message for display later with displayDelayedLogDamage
+-- only one message (processed with logMessage) will be logged for each source and label
+-- useful to avoid spamming repeated messages
+-- target is optional and is used only to resolve the msg
+function _M:delayedLogMessage(source, target, label, msg, ...)
+	local visible, srcSeen, tgtSeen = self:logVisible(source, target)
+	if visible then
+		self.delayed_log_messages[source] = self.delayed_log_messages[source] or {}
+		local src = self.delayed_log_messages[source]
+		src[label] = self:logMessage(source, srcSeen, target, tgtSeen, msg, ...)
+	end
+end
+
+-- display the delayed log messages
+function _M:displayDelayedLogMessages()
+	if not self.uiset or not self.uiset.logdisplay then return end
+	for src, msgs in pairs(self.delayed_log_messages) do
+		for label, msg in pairs(msgs) do
+			game.uiset.logdisplay(self:logMessage(src, true, nil, nil, msg))
+		end
+	end
+	self.delayed_log_messages = {}
+end
+
+-- Note: There can be up to a 1 tick delay in displaying log information
 function _M:displayDelayedLogDamage()
+	if not self.uiset or not self.uiset.logdisplay then return end
+local newmessage = false --debugging
 	for src, tgts in pairs(self.delayed_log_damage) do
+newmessage = true -- debugging
 		for target, dams in pairs(tgts) do
 			if #dams.descs > 1 then
-				self.logSeen(target, "%s%s hits %s for %s damage (total %0.2f).", src:getDisplayString(), src.name:capitalize(), target.name, table.concat(dams.descs, ", "), dams.total)
+				game.uiset.logdisplay(self:logMessage(src, dams.srcSeen, target, dams.tgtSeen, "#Source# hits #Target# for %s damage (total %0.1f).", table.concat(dams.descs, ", "), dams.total))
 			else
-				self.logSeen(target, "%s%s hits %s for %s damage.", src:getDisplayString(), src.name:capitalize(), target.name, table.concat(dams.descs, ", "))
+				game.uiset.logdisplay(self:logMessage(src, dams.srcSeen, target, dams.tgtSeen, "#Source# hits #Target# for %s damage.", table.concat(dams.descs, ", ")))
 			end
-
 			local rsrc = src.resolveSource and src:resolveSource() or src
 			local rtarget = target.resolveSource and target:resolveSource() or target
 			local x, y = target.x or -1, target.y or -1
 			local sx, sy = self.level.map:getTileToScreen(x, y)
 			if target.dead then
-				if self.level.map.seens(x, y) and (rsrc == self.player or rtarget == self.player or self.party:hasMember(rsrc) or self.party:hasMember(rtarget)) then
+				if dams.tgtSeen and (rsrc == self.player or rtarget == self.player or self.party:hasMember(rsrc) or self.party:hasMember(rtarget)) then
 					self.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, rng.float(-2.5, -1.5), ("Kill (%d)!"):format(dams.total), {255,0,255}, true)
-					game.logSeen(target, "#{bold}#%s%s killed %s!#{normal}#", src:getDisplayString(), src.name:capitalize(), target.name)
+					self:delayedLogMessage(target, nil,  "death", self:logMessage(src, dams.srcSeen, target, dams.tgtSeen, "#{bold}##Source# killed #Target#!#{normal}#"))
 				end
 			else
-				if self.level.map.seens(x, y) and (rsrc == self.player or self.party:hasMember(rsrc)) then
+				if dams.tgtSeen and (rsrc == self.player or self.party:hasMember(rsrc)) then
 					self.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, rng.float(-3, -2), tostring(-math.ceil(dams.total)), {0,255,dams.is_crit and 200 or 0}, dams.is_crit)
-				elseif self.level.map.seens(x, y) and (rtarget == self.player or self.party:hasMember(rtarget)) then
+				elseif dams.tgtSeen and (rtarget == self.player or self.party:hasMember(rtarget)) then
 					self.flyers:add(sx, sy, 30, (rng.range(0,2)-1) * 0.5, -rng.float(-3, -2), tostring(-math.ceil(dams.total)), {255,dams.is_crit and 200 or 0,0}, dams.is_crit)
 				end
 			end
@@ -1176,17 +1275,23 @@ function _M:displayDelayedLogDamage()
 	self.delayed_log_damage = {}
 end
 
+-- log and collate combat damage for later display with displayDelayedLogDamage
 function _M:delayedLogDamage(src, target, dam, desc, crit)
-	self.delayed_log_damage[src] = self.delayed_log_damage[src] or {}
-	self.delayed_log_damage[src][target] = self.delayed_log_damage[src][target] or {total=0, descs={}}
-	local t = self.delayed_log_damage[src][target]
-	t.descs[#t.descs+1] = desc
-	t.total = t.total + dam
-	t.is_crit = t.is_crit or crit
+	src = src.__project_source or src -- assign message to indirect damage source if available
+	local visible, srcSeen, tgtSeen = self:logVisible(src, target)
+	if visible then -- only log damage the player is aware of
+		self.delayed_log_damage[src] = self.delayed_log_damage[src] or {}
+		self.delayed_log_damage[src][target] = self.delayed_log_damage[src][target] or {total=0, descs={}}
+		local t = self.delayed_log_damage[src][target]
+		t.descs[#t.descs+1] = desc
+		t.total = t.total + dam
+		t.is_crit = t.is_crit or crit
+		t.srcSeen = srcSeen
+		t.tgtSeen = tgtSeen
+	end
 end
 
 --- Called every game turns
--- Does nothing, you can override it
 function _M:onTurn()
 	if self.zone then
 		if self.zone.on_turn then self.zone:on_turn() end
diff --git a/game/modules/tome/class/Grid.lua b/game/modules/tome/class/Grid.lua
index b1f42ff7e547d1dacd2e5627559986db985c55e1..bd55114a1e36f716d52393a64bef105df78ca11e 100644
--- a/game/modules/tome/class/Grid.lua
+++ b/game/modules/tome/class/Grid.lua
@@ -22,9 +22,12 @@ require "engine.Grid"
 local Map = require "engine.Map"
 local Dialog = require "engine.ui.Dialog"
 local DamageType = require "engine.DamageType"
+local Combat = require "mod.class.interface.Combat"
 
 module(..., package.seeall, class.inherit(engine.Grid))
 
+_M.logCombat = Combat.logCombat
+
 function _M:init(t, no_default)
 	engine.Grid.init(self, t, no_default)
 
@@ -123,6 +126,24 @@ function _M:on_move(x, y, who, forced)
 	end
 end
 
+function _M:resolveSource()
+	if self.summoner_gain_exp and self.summoner then
+		return self.summoner:resolveSource()
+	else
+		return self
+	end
+end
+
+-- Gets the full name of the grid
+function _M:getName()
+	local name = self.name or "spot"
+	if self.summoner and self.summoner.name then
+		return self.summoner.name:capitalize().."'s "..name
+	else
+		return name
+	end
+end
+
 function _M:tooltip(x, y)
 	local tstr
 	local dist = nil
diff --git a/game/modules/tome/class/MapEffects.lua b/game/modules/tome/class/MapEffects.lua
new file mode 100644
index 0000000000000000000000000000000000000000..72ca171bdb4cea39f3e6a160f474dfd031ef52f2
--- /dev/null
+++ b/game/modules/tome/class/MapEffects.lua
@@ -0,0 +1,47 @@
+-- TE4 - T-Engine 4
+-- Copyright (C) 2009, 2010, 2011, 2012, 2013 Nicolas Casalini
+--
+-- This program is free software: you can redistribute it and/or modify
+-- it under the terms of the GNU General Public License as published by
+-- the Free Software Foundation, either version 3 of the License, or
+-- (at your option) any later version.
+--
+-- This program is distributed in the hope that it will be useful,
+-- but WITHOUT ANY WARRANTY; without even the implied warranty of
+-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+-- GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License
+-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
+--
+-- Nicolas Casalini "DarkGod"
+-- darkgod@te4.org
+
+local Map = require "engine.Map"
+
+local function getEffectName(self)
+	local name = self.name or self.damtype and engine.DamageType.dam_def[self.damtype].name.." area effect" or "area effect"
+	if self.src then
+		return self.src.name.."'s "..name
+	else
+		return name
+	end
+end
+
+local function resolveSource(self)
+	if self.src and self.src.resolveSource then
+		return self.src:resolveSource()
+	else
+		return self
+	end
+end
+
+local addEffect = Map.addEffect
+Map.addEffect = function (...)
+	local e = addEffect(...)
+	if e then
+		e.getName = getEffectName
+		e.resolveSource = resolveSource
+	end
+	return e
+end
diff --git a/game/modules/tome/class/NPC.lua b/game/modules/tome/class/NPC.lua
index 24dc91125c617cd09a1e6a0be45e02b43ec5d549..5e4bb8c3805a26531127dfec69670d35aed1b746 100644
--- a/game/modules/tome/class/NPC.lua
+++ b/game/modules/tome/class/NPC.lua
@@ -537,7 +537,7 @@ function _M:aiCanPass(x, y)
 				local check_dir = sides[side]
 				local sx, sy = util.coordAddDir(target.x, target.y, check_dir)
 				if target:canMove(sx, sy) and target:move(sx, sy) then
-					game.logSeen(target, "%s shoves %s forward.", self.name:capitalize(), target.name)
+					self:logCombat(target, "#Source# shoves #Target# forward.")
 					target.shove_pressure = nil
 					target._last_shove_pressure = nil
 					break
diff --git a/game/modules/tome/class/Object.lua b/game/modules/tome/class/Object.lua
index 9daf94f15646efb0f0d3f4059abf0735e958f189..10319a52b63cb56e1fe309f2f5c187fac354ada5 100644
--- a/game/modules/tome/class/Object.lua
+++ b/game/modules/tome/class/Object.lua
@@ -27,6 +27,7 @@ require "engine.interface.ObjectIdentify"
 local Stats = require("engine.interface.ActorStats")
 local Talents = require("engine.interface.ActorTalents")
 local DamageType = require("engine.DamageType")
+local Combat = require("mod.class.interface.Combat")
 
 module(..., package.seeall, class.inherit(
 	engine.Object,
@@ -37,6 +38,8 @@ module(..., package.seeall, class.inherit(
 
 _M.projectile_class = "mod.class.Projectile"
 
+_M.logCombat = Combat.logCombat
+
 function _M:init(t, no_default)
 	t.encumber = t.encumber or 0
 
@@ -237,6 +240,16 @@ function _M:getDisplayColor()
 	end
 end
 
+function _M:resolveSource()
+	if self.summoner_gain_exp and self.summoner then
+		return self.summoner:resolveSource()
+	elseif self.summoner_gain_exp and self.src then
+		return self.src:resolveSource()
+	else
+		return self
+	end
+end
+
 --- Gets the full name of the object
 function _M:getName(t)
 	t = t or {}
diff --git a/game/modules/tome/class/Party.lua b/game/modules/tome/class/Party.lua
index e8d8adb38476c9692cfed57c3b47ce6a96277666..1b18d8fd7633192a300d08bc209871d63a461ae8 100644
--- a/game/modules/tome/class/Party.lua
+++ b/game/modules/tome/class/Party.lua
@@ -354,7 +354,7 @@ function _M:giveOrder(actor, order)
 			local x, y, act = game.player:getTarget({type="hit", range=10})
 			if act then
 				actor:setTarget(act)
-				game.logPlayer(game.player, "%s targets %s.", actor.name:capitalize(), act.name)
+				game.player:logCombat(act, "%s targets #Target#.", actor.name:capitalize())
 			end
 		end)
 		local ok, err = coroutine.resume(co)
diff --git a/game/modules/tome/class/Player.lua b/game/modules/tome/class/Player.lua
index 82a10eeec4360fb122e27fa4b25b75116ca5c321..53af85e5c2ad5d1d56d2521c54df542995dbabfb 100644
--- a/game/modules/tome/class/Player.lua
+++ b/game/modules/tome/class/Player.lua
@@ -1409,7 +1409,7 @@ function _M:useOrbPortal(portal)
 	local spotted = spotHostiles(self)
 	if #spotted > 0 then
 		local dir = game.level.map:compassDirection(spotted[1].x - self.x, spotted[1].y - self.y)
-		game.logPlayer(self, "You can not use the Orb with foes in sight (%s to the %s%s)", spotted[1].actor.name, dir, game.level.map:isOnScreen(spotted[1].x, spotted[1].y) and "" or " - offscreen")
+		self:logCombat(spotted[1].actor, "You can not use the Orb with foes watching (#Target# to the %s%s)",dir, game.level.map:isOnScreen(spotted[1].x, spotted[1].y) and "" or " - offscreen")
 		return
 	end
 	if portal.on_preuse then portal:on_preuse(self) end
diff --git a/game/modules/tome/class/Projectile.lua b/game/modules/tome/class/Projectile.lua
index 419dba34bfba1bc378136c559d0e6bacef6ee8ee..a52c91a259bda5d689e2062a9a40fc7b31d53fd0 100644
--- a/game/modules/tome/class/Projectile.lua
+++ b/game/modules/tome/class/Projectile.lua
@@ -19,9 +19,12 @@
 
 require "engine.class"
 require "engine.Projectile"
+local Combat = require "mod.class.interface.Combat"
 
 module(..., package.seeall, class.inherit(engine.Projectile))
 
+_M.logCombat = Combat.logCombat
+
 function _M:init(t, no_default)
 	engine.Projectile.init(self, t, no_default)
 end
@@ -68,3 +71,21 @@ function _M:tooltip(x, y)
 	end
 	return tstr
 end
+
+function _M:resolveSource()
+	if self.src then
+		return self.src:resolveSource()
+	else
+		return self
+	end
+end
+
+--gets the full name of the projectile
+function _M:getName()
+	local name = self.name or "projectile"
+	if self.src and self.src.name then
+		return self.src.name:capitalize().."'s "..name
+	else
+		return name
+	end
+end
diff --git a/game/modules/tome/class/Trap.lua b/game/modules/tome/class/Trap.lua
index 64c798da3046388b5461fe8e684b2eae7fe4aa8a..757f00a2c3381358c8669433bf5cac974ff2fd34 100644
--- a/game/modules/tome/class/Trap.lua
+++ b/game/modules/tome/class/Trap.lua
@@ -21,12 +21,14 @@ require "engine.class"
 require "engine.Trap"
 require "engine.interface.ActorProject"
 require "engine.interface.ObjectIdentify"
+require "mod.class.interface.Combat"
 local Faction = require "engine.Faction"
 
 module(..., package.seeall, class.inherit(
 	engine.Trap,
 	engine.interface.ObjectIdentify,
-	engine.interface.ActorProject
+	engine.interface.ActorProject,
+	mod.class.interface.Combat
 ))
 
 _M.projectile_class = "mod.class.Projectile"
@@ -47,14 +49,26 @@ function _M:combatSpellpower() return mod.class.interface.Combat:rescaleCombatSt
 function _M:combatMindpower() return mod.class.interface.Combat:rescaleCombatStats(self.wil) end
 function _M:combatAttack() return mod.class.interface.Combat:rescaleCombatStats(self.dex) end
 
---- Gets the full name of the object
+function _M:resolveSource()
+	if self.summoner_gain_exp and self.summoner then
+		return self.summoner:resolveSource()
+	else
+		return self
+	end
+end
+
+-- Gets the full name of the trap
 function _M:getName()
-	local name = self.name
+	local name = self.name or "trap"
 	if not self:isIdentified() and self:getUnidentifiedName() then name = self:getUnidentifiedName() end
-	return name
+	if self.summoner and self.summoner.name then
+		return self.summoner.name:capitalize().."'s "..name
+	else
+		return name
+	end
 end
 
---- Returns a tooltip for the trap
+-- Returns a tooltip for the trap
 function _M:tooltip()
 	if self:knownBy(game.player) then
 		local res = tstring{{"uid", self.uid}, self:getName()}
@@ -121,18 +135,9 @@ end
 --- Trigger the trap
 function _M:trigger(x, y, who)
 	engine.Trap.trigger(self, x, y, who)
-
 	if who.runStop then who:runStop("trap") end
 end
 
-function _M:resolveSource()
-	if self.summoner_gain_exp and self.summoner then
-		return self.summoner:resolveSource()
-	else
-		return self
-	end
-end
-
 --- Identify the trap
 function _M:identify(id)
 	self.identified = id
diff --git a/game/modules/tome/class/WorldNPC.lua b/game/modules/tome/class/WorldNPC.lua
index 5d532df1ea516792a22b71e71c03ff6293fefcbc..3a4c7df7668bb63df8e01fc40eb5b9f1e411f690 100644
--- a/game/modules/tome/class/WorldNPC.lua
+++ b/game/modules/tome/class/WorldNPC.lua
@@ -152,7 +152,7 @@ end
 function _M:takePowerHit(val, src)
 	self.unit_power = (self.unit_power or 0) - val
 	if self.unit_power <= 0 then
-		game.logSeen(self, "%s kills %s.", src.name:capitalize(), self.name)
+		self.logCombat(src, self, "#Source# kills #Target#.")
 		self:die(src)
 	end
 end
@@ -174,11 +174,11 @@ function _M:encounterAttack(target, x, y)
 	end
 
 	if self.unit_power <= 0 then
-		game.logSeen(self, "%s kills %s.", target.name:capitalize(), self.name)
+		self:logCombat(target, "#Target# kills #Source#.")
 		self:die(target)
 	end
 	if target.unit_power <= 0 then
-		game.logSeen(target, "%s kills %s.", self.name:capitalize(), target.name)
+		self:logCombat(target, "#Source# kills #Target#.")
 		target:die(src)
 	end
 end
diff --git a/game/modules/tome/class/interface/Archery.lua b/game/modules/tome/class/interface/Archery.lua
index 5b8927e3884fc3d199aec4ee670801bff4d5b24d..06dd698ea7c01dbcd0c2a8afa10fad9ef7c5ba79 100644
--- a/game/modules/tome/class/interface/Archery.lua
+++ b/game/modules/tome/class/interface/Archery.lua
@@ -229,7 +229,7 @@ local function archery_projectile(tx, ty, tg, self, tmp)
 		end
 		print("[ATTACK ARCHERY] after hook", dam)
 
-		if crit then game.logSeen(self, "#{bold}#%s performs a critical strike!#{normal}#", self.name:capitalize()) end
+		if crit then self:logCombat(target, "#{bold}##Source# performs a ranged critical strike against #Target#!#{normal}#") end
 
 		-- Damage conversion?
 		-- Reduces base damage but converts it into another damage type
diff --git a/game/modules/tome/class/interface/Combat.lua b/game/modules/tome/class/interface/Combat.lua
index 703263f18b392769cdab791540565b4d554332f3..81894307c086769a5945b0433b5343c63f11c570 100644
--- a/game/modules/tome/class/interface/Combat.lua
+++ b/game/modules/tome/class/interface/Combat.lua
@@ -112,7 +112,7 @@ function _M:attackTarget(target, damtype, mult, noenergy, force_unharmed)
 	if self:isTalentActive(self.T_STEALTH) and target:canSee(self) then
 		self:useTalent(self.T_STEALTH)
 		self.changed = true
-		game.logPlayer(self, "%s notices you at the last moment!", target.name:capitalize())
+		if self.player then self:logCombat(target, "#Target# notices you at the last moment!") end
 	end
 
 	if target:isTalentActive(target.T_INTUITIVE_SHOTS) and rng.percent(target:callTalent(target.T_INTUITIVE_SHOTS, "getChance")) then
@@ -377,23 +377,23 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 	local crit = false
 	local evaded = false
 	if repelled then
-		game.logSeen(target, "%s repels an attack from %s.", target.name:capitalize(), self.name)
+		self:logCombat(target, "#Target# repels an attack from #Source#.")
 	elseif self:checkEvasion(target) then
 		evaded = true
-		game.logSeen(target, "%s evades %s.", target.name:capitalize(), self.name)
+		self:logCombat(target, "#Target# evades #Source#.")
 	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 = math.max(dam, target:callTalent(target.T_DUAL_WEAPON_DEFENSE, "doDeflect"))
+			local deflect = math.min(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())
+				self:logCombat(target, "#Target# parries %d damage from #Source#'s attack.", deflect)
 				dam = math.max(dam - deflect,0)
 				print("[ATTACK] after DUAL_WEAPON_DEFENSE", dam)
 			end 
 		end
 		if target.knowTalent and target:hasEffect(target.EFF_GESTURE_OF_GUARDING) then
 			local deflected = math.min(dam, target:callTalent(target.T_GESTURE_OF_GUARDING, "doGuard"))
-			game.logSeen(target, "%s dismisses %d damage from %s's attack with a sweeping gesture.", target.name:capitalize(), deflected, self.name:capitalize())
+			self:logCombat(target, "#Target# dismisses %d damage from #Source#'s attack with a sweeping gesture.", deflected)
 			dam = dam - deflected
 			print("[ATTACK] after GESTURE_OF_GUARDING", dam)
 		end
@@ -424,7 +424,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 			print("[ATTACK] after inc by type", dam)
 		end
 
-		if crit then game.logSeen(self, "#{bold}#%s performs a critical strike!#{normal}#", self.name:capitalize()) end
+		if crit then self:logCombat(target, "#{bold}##Source# performs a melee critical strike against #Target#!#{normal}#") end
 
 		-- Phasing, percent of weapon damage bypasses shields
 		-- It's done like this because onTakeHit has no knowledge of the weapon
@@ -474,9 +474,7 @@ function _M:attackTargetWith(target, weapon, damtype, mult, force_dam)
 
 		hitted = true
 	else
-		local srcname = game.level.map.seens(self.x, self.y) and self.name:capitalize() or "Something"
-		game.logSeen(target, "%s misses %s.", srcname, target.name)
-
+		self:logCombat(target, "#Source# misses #Target#.")
 		target:fireTalentCheck("callbackOnMeleeMiss", self)
 	end
 
@@ -2142,7 +2140,7 @@ end
 function _M:grappleSizeCheck(target)
 	size = target.size_category - self.size_category
 	if size > 1 then
-		game.logSeen(target, "%s fails because %s is too big!", self.name:capitalize(), target.name:capitalize())
+		self:logCombat(target, "#Source#'s grapple fails because #Target# is too big!")
 		return true
 	else
 		return false
@@ -2179,3 +2177,11 @@ function _M:startGrapple(target)
 	end
 end
 
+-- Display Combat log messages, highlighting the player and taking LOS and visibility into account
+-- #source#|#Source# -> <displayString> self.name|self.name:capitalize()
+-- #target#|#Target# -> target.name|target.name:capitalize()
+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 
+end
diff --git a/game/modules/tome/class/uiset/Classic.lua b/game/modules/tome/class/uiset/Classic.lua
index 9760e4164a167495cad3ace1a881d4a2a8cd110f..5d0e0c37183f947202c589f78f3c17c523f317ac 100644
--- a/game/modules/tome/class/uiset/Classic.lua
+++ b/game/modules/tome/class/uiset/Classic.lua
@@ -90,7 +90,7 @@ function _M:activate()
 		game.uiset.logdisplay(...) else game.uiset.logdisplay(style, ...) end
 		if game.uiset.show_userchat then game.uiset.logdisplay.changed = old end
 	end
-	game.logSeen = function(e, style, ...) if e and e.x and e.y and game.level.map.seens(e.x, e.y) then game.log(style, ...) end end
+--	game.logSeen = function(e, style, ...) if e and e.player or (not e.dead and e.x and e.y and game.level and game.level.map.seens(e.x, e.y) and game.player:canSee(e)) then game.log(style, ...) end end
 	game.logPlayer = function(e, style, ...) if e == game.player or e == game.party then game.log(style, ...) end end
 end
 
diff --git a/game/modules/tome/class/uiset/Minimalist.lua b/game/modules/tome/class/uiset/Minimalist.lua
index f663f8282c4e65772c0dc91e547b9b0bd36ab001..0a9bbff7d1d71635ffbe10578dc41bb54935bb6e 100644
--- a/game/modules/tome/class/uiset/Minimalist.lua
+++ b/game/modules/tome/class/uiset/Minimalist.lua
@@ -383,7 +383,7 @@ function _M:activate()
 		game.uiset.logdisplay(...) else game.uiset.logdisplay(style, ...) end
 		if game.uiset.show_userchat then game.uiset.logdisplay.changed = old end
 	end
-	game.logSeen = function(e, style, ...) if e and e.x and e.y and game.level and game.level.map.seens(e.x, e.y) then game.log(style, ...) end end
+--	game.logSeen = function(e, style, ...) if e and e.player or (not e.dead and e.x and e.y and game.level and game.level.map.seens(e.x, e.y) and game.player:canSee(e)) then game.log(style, ...) end end
 	game.logPlayer = function(e, style, ...) if e == game.player or e == game.party then game.log(style, ...) end end
 
 	self:boundPlaces()
diff --git a/game/modules/tome/data/chats/alchemist-last-hope.lua b/game/modules/tome/data/chats/alchemist-last-hope.lua
index bd662fdac891ce19e58837964060c9fd69bfa5ec..92122934f2fd8e982e8e710f0198b8d7c42fc058 100644
--- a/game/modules/tome/data/chats/alchemist-last-hope.lua
+++ b/game/modules/tome/data/chats/alchemist-last-hope.lua
@@ -553,7 +553,7 @@ It's strange what death can do to people, how it can take over their minds. Some
 				game.nicer_tiles:updateAround(game.level, spot.x, spot.y)
 				game.state:locationRevealAround(spot.x, spot.y)
 			end)
-			game.log("He points the location of the graveyard on your map.")
+			game.log("He points out the location of the graveyard on your map.")
 			player:grantQuest("grave-necromancer")
 		end},
 	}
diff --git a/game/modules/tome/data/damage_types.lua b/game/modules/tome/data/damage_types.lua
index 0f9b85d5f9fdc0551e9b77b3668b84c4960ab3aa..759c7ea7bd701d22c080228bf1687a9c33ab8718 100644
--- a/game/modules/tome/data/damage_types.lua
+++ b/game/modules/tome/data/damage_types.lua
@@ -164,7 +164,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 				game:onTickEnd(function() src:removeEffect(src.EFF_FROZEN) end)
 				eff.begone = game.turn
 			else
-				game:delayedLogDamage(src, {name="Iceblock", x=src.x, y=src.y}, dam, ("%s%d %s#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name))
+				game:delayedLogDamage(src, eff.ice, dam, ("%s%d %s#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name))
 				if eff.begone and eff.begone < game.turn and eff.hp < 0 then
 					game.logSeen(src, "%s forces the iceblock to shatter.", src.name:capitalize())
 					src:removeEffect(src.EFF_FROZEN)
@@ -174,11 +174,12 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 		end
 
 		-- dark vision increases damage done in creeping dark
-		if src and game.level.map:checkAllEntities(x, y, "creepingDark") then
+		if src and src ~= target and game.level.map:checkAllEntities(x, y, "creepingDark") then
 			local dark = game.level.map:checkAllEntities(x, y, "creepingDark")
 			if dark.summoner == src and dark.damageIncrease > 0 and not dark.projecting then
-				game.logPlayer(src, "You strike in the darkness. (+%d damage)", (dam * dark.damageIncrease / 100))
+				local source = src.__project_source or src
 				dam = dam + (dam * dark.damageIncrease / 100)
+				game:delayedLogMessage(source, target, "dark_strike"..(source.uid or ""), "#Source# strikes #Target# in the darkness (%+d%%%%%%%% damage).", dark.damageIncrease) -- resolve %% 3 levels deep
 			end
 		end
 
@@ -367,11 +368,14 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 
 		-- Log damage for later
 		if not DamageType:get(type).hideMessage then
-			local srcname = src.x and src.y and game.level.map.seens(src.x, src.y) and src.name:capitalize() or "Something"
-			if src.turn_procs and src.turn_procs.is_crit then
-				game:delayedLogDamage(src, target, dam, ("#{bold}#%s%d %s#{normal}##LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name), true)
-			else
-				game:delayedLogDamage(src, target, dam, ("%s%d %s#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name), false)
+			local visible, srcSeen, tgtSeen = game:logVisible(src, target)
+			if visible then -- don't log damage that the player doesn't know about
+				local source = src.__project_source or src
+				if src.turn_procs and src.turn_procs.is_crit then
+					game:delayedLogDamage(source, target, dam, ("#{bold}#%s%d %s#{normal}##LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name), true)
+				else
+					game:delayedLogDamage(source, target, dam, ("%s%d %s#LAST#"):format(DamageType:get(type).text_color or "#aaaaaa#", math.ceil(dam), DamageType:get(type).name), false)
+				end
 			end
 		end
 
@@ -412,7 +416,7 @@ setDefaultProjector(function(src, x, y, type, dam, tmp, no_martyr)
 		-- damage affinity healing
 		if not target.dead and affinity_heal > 0 then
 			target:heal(affinity_heal)
-			game.logSeen(target, "%s is healed by the %s%s#LAST# damage!", target.name:capitalize(), DamageType:get(type).text_color or "#aaaaaa#", DamageType:get(type).name)
+			game:delayedLogMessage(target, nil, "Affinity"..type, "#Source# heals from "..(DamageType:get(type).text_color or "#aaaaaa#")..DamageType:get(type).name.."#LAST# damage!")
 		end
 
 		if dam > 0 and src.damage_log and src.damage_log.weapon then
@@ -710,7 +714,7 @@ newDamageType{
 
 -- Temporal + Stun
 newDamageType{
-	name = "temporalstun", type = "TEMPORALSTUN",
+	name = "temporal stun", type = "TEMPORALSTUN",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.TEMPORAL).projector(src, x, y, DamageType.TEMPORAL, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -741,7 +745,7 @@ newDamageType{
 
 -- Break stealth
 newDamageType{
-	name = "break stealth", type = "BREAK_STEALTH",
+	name = "illumination", type = "BREAK_STEALTH",
 	projector = function(src, x, y, type, dam)
 		-- Dont lit magically unlit grids
 		local a = game.level.map(x, y, Map.ACTOR)
@@ -753,7 +757,7 @@ newDamageType{
 
 -- Silence
 newDamageType{
-	name = "SILENCE", type = "SILENCE",
+	name = "silence", type = "SILENCE",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -788,7 +792,7 @@ newDamageType{
 
 -- Silence
 newDamageType{
-	name = "% chance to silence target", type = "RANDOM_SILENCE",
+	name = "silence", type = "RANDOM_SILENCE",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and rng.percent(dam) then
@@ -883,7 +887,7 @@ newDamageType{
 	end,
 }
 newDamageType{
-	name = "fireburn", type = "GOLEM_FIREBURN",
+	name = "fire burn", type = "GOLEM_FIREBURN",
 	projector = function(src, x, y, type, dam)
 		local realdam = 0
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -905,12 +909,12 @@ newDamageType{
 
 -- Darkness + Stun
 newDamageType{
-	name = "darkstun", type = "DARKSTUN",
+	name = "darkness", type = "DARKSTUN",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.DARKNESS).projector(src, x, y, DamageType.DARKNESS, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
-			-- Set on fire!
+			-- try to stun
 			if target:canBe("stun") then
 				target:setEffect(target.EFF_STUNNED, 4, {apply_power=src:combatSpellpower()})
 			else
@@ -922,7 +926,7 @@ newDamageType{
 
 -- Darkness but not over minions
 newDamageType{
-	name = "minions darkness", type = "MINION_DARKNESS",
+	name = "darkness", type = "MINION_DARKNESS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and (not target.necrotic_minion or target.summoner ~= src) then
@@ -933,7 +937,7 @@ newDamageType{
 
 -- Fore but not over minions
 newDamageType{
-	name = "firey no friends", type = "FIRE_FRIENDS",
+	name = "fire", type = "FIRE_FRIENDS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and target.summoner ~= src then
@@ -944,7 +948,7 @@ newDamageType{
 
 -- Cold + Stun
 newDamageType{
-	name = "coldstun", type = "COLDSTUN",
+	name = "cold", type = "COLDSTUN",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.COLD).projector(src, x, y, DamageType.COLD, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -988,7 +992,7 @@ newDamageType{
 
 -- Cold damage + freeze ground
 newDamageType{
-	name = "coldnevermove", type = "COLDNEVERMOVE",
+	name = "cold ground", type = "COLDNEVERMOVE",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam, dur=4} end
 		DamageType:get(DamageType.COLD).projector(src, x, y, DamageType.COLD, dam.dam)
@@ -1088,7 +1092,7 @@ newDamageType{
 
 -- Lightning damage + daze chance
 newDamageType{
-	name = "lightning daze", type = "LIGHTNING_DAZE", text_color = "#ROYAL_BLUE#",
+	name = "lightning", type = "LIGHTNING_DAZE", text_color = "#ROYAL_BLUE#",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam, daze=25} end
 		dam.daze = dam.daze or 25
@@ -1111,7 +1115,7 @@ newDamageType{
 
 -- Cold/physical damage + repulsion; checks for spell power against physical resistance
 newDamageType{
-	name = "wave", type = "WAVE",
+	name = "cold repulsion", type = "WAVE",
 	projector = function(src, x, y, type, dam)
 		local srcx, srcy = dam.x, dam.y
 		local base = dam
@@ -1137,7 +1141,7 @@ newDamageType{
 
 -- Fireburn damage + repulsion; checks for spell power against physical resistance
 newDamageType{
-	name = "fire knockback", type = "FIREKNOCKBACK",
+	name = "fire repulsion", type = "FIREKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if _G.type(dam) ~= "table" then dam = {dam=dam, dist=3} end
@@ -1158,7 +1162,7 @@ newDamageType{
 
 -- Fireburn damage + repulsion; checks for mind power against physical resistance
 newDamageType{
-	name = "fire knockback mind", type = "FIREKNOCKBACK_MIND",
+	name = "burning repulsion", type = "FIREKNOCKBACK_MIND",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if _G.type(dam) ~= "table" then dam = {dam=dam, dist=3} end
@@ -1179,7 +1183,7 @@ newDamageType{
 
 -- Darkness damage + repulsion; checks for spell power against mental resistance
 newDamageType{
-	name = "darkness knockback", type = "DARKKNOCKBACK",
+	name = "darkness repulsion", type = "DARKKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if _G.type(dam) ~= "table" then dam = {dam=dam, dist=3} end
@@ -1200,7 +1204,7 @@ newDamageType{
 
 -- Physical damage + repulsion; checks for spell power against physical resistance
 newDamageType{
-	name = "spell knockback", type = "SPELLKNOCKBACK",
+	name = "physical repulsion", type = "SPELLKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		local realdam = 0
@@ -1223,7 +1227,7 @@ newDamageType{
 
 -- Physical damage + repulsion; checks for mind power against physical resistance
 newDamageType{
-	name = "mind knockback", type = "MINDKNOCKBACK",
+	name = "physical repulsion", type = "MINDKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		tmp = tmp or {}
@@ -1243,7 +1247,7 @@ newDamageType{
 
 -- Physical damage + repulsion; checks for attack power against physical resistance
 newDamageType{
-	name = "physknockback", type = "PHYSKNOCKBACK",
+	name = "physical repulsion", type = "PHYSKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		tmp = tmp or {}
@@ -1264,7 +1268,7 @@ newDamageType{
 
 -- Fear check + repulsion; checks for mind power against physical resistance
 newDamageType{
-	name = "fear knockback", type = "FEARKNOCKBACK",
+	name = "fear repulsion", type = "FEARKNOCKBACK",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		tmp = tmp or {}
@@ -1299,9 +1303,9 @@ newDamageType{
 	end,
 }
 
--- Inferno: fire and maybe remove suff
+-- Inferno: fire and maybe remove stuff
 newDamageType{
-	name = "inferno", type = "INFERNO",
+	name = "cleansing fire", type = "INFERNO",
 	projector = function(src, x, y, type, dam)
 		local realdam = DamageType:get(DamageType.FIRE).projector(src, x, y, DamageType.FIRE, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1378,7 +1382,7 @@ newDamageType{
 
 -- Physical damage + bleeding % of it
 newDamageType{
-	name = "physical + bleeding", type = "PHYSICALBLEED",
+	name = "physical bleed", type = "PHYSICALBLEED",
 	projector = function(src, x, y, type, dam)
 		local realdam = DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1390,7 +1394,7 @@ newDamageType{
 
 -- Slime damage
 newDamageType{
-	name = "slime", type = "SLIME", text_color = "#LIGHT_GREEN#",
+	name = "nature slow", type = "SLIME", text_color = "#LIGHT_GREEN#",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam, power=0.15} end
 		DamageType:get(DamageType.NATURE).projector(src, x, y, DamageType.NATURE, dam.dam)
@@ -1487,7 +1491,7 @@ newDamageType{
 
 -- Confusion
 newDamageType{
-	name = "% chance to confuse", type = "RANDOM_CONFUSION",
+	name = "confusion", type = "RANDOM_CONFUSION",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam} end
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1502,7 +1506,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "% chance to cause a gloom effect", type = "RANDOM_GLOOM",
+	name = "gloom", type = "RANDOM_GLOOM",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and rng.percent(dam) then
@@ -1530,7 +1534,7 @@ newDamageType{
 
 -- gBlind
 newDamageType{
-	name = "% chance to blind", type = "RANDOM_BLIND",
+	name = "blinding", type = "RANDOM_BLIND",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam} end
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1546,7 +1550,7 @@ newDamageType{
 
 -- Physical + Blind
 newDamageType{
-	name = "sand", type = "SAND",
+	name = "blinding physical", type = "SAND",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam.dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1562,7 +1566,7 @@ newDamageType{
 
 -- Physical + Pinned
 newDamageType{
-	name = "pinning", type = "PINNING",
+	name = "physical pinning", type = "PINNING",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam.dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1578,7 +1582,7 @@ newDamageType{
 
 -- Drain Exp
 newDamageType{
-	name = "drain experience", type = "DRAINEXP",
+	name = "regressive blight", type = "DRAINEXP",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam} end
 		local realdam = DamageType:get(DamageType.BLIGHT).projector(src, x, y, DamageType.BLIGHT, dam.dam)
@@ -1586,7 +1590,7 @@ newDamageType{
 		if target then
 			if target:checkHit((dam.power_check or src.combatSpellpower)(src), (dam.resist_check or target.combatMentalResist)(target), 0, 95, 15) then
 				target:gainExp(-dam.dam*2)
-				game.logSeen(target, "%s drains experience from %s!", src.name:capitalize(), target.name)
+				src:logCombat(target, "#Source# drains experience from #Target#!")
 			else
 				game.logSeen(target, "%s resists!", target.name:capitalize())
 			end
@@ -1597,14 +1601,14 @@ newDamageType{
 
 -- Drain Life
 newDamageType{
-	name = "drain life", type = "DRAINLIFE", text_color = "#DARK_GREEN#",
+	name = "draining blight", type = "DRAINLIFE", text_color = "#DARK_GREEN#",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam, healfactor=0.4} end
 		local target = game.level.map(x, y, Map.ACTOR) -- Get the target first to make sure we heal even on kill
 		local realdam = DamageType:get(DamageType.BLIGHT).projector(src, x, y, DamageType.BLIGHT, dam.dam)
 		if target and realdam > 0 then
 			src:heal(realdam * dam.healfactor)
-			game.logSeen(target, "%s drains life from %s!", src.name:capitalize(), target.name)
+			src:logCombat(target, "#Source# drains life from #Target#!")
 		end
 		return realdam
 	end,
@@ -1612,7 +1616,7 @@ newDamageType{
 
 -- Drain Vim
 newDamageType{
-	name = "drain vim", type = "DRAIN_VIM",
+	name = "enervating blight", type = "DRAIN_VIM",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam, vim=0.2} end
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1641,10 +1645,10 @@ newDamageType{
 
 -- Retch: heal undead; damage living
 newDamageType{
-	name = "retch", type = "RETCH",
+	name = "purging blight", type = "RETCH",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
-		if target and (target:attr("undead") or target.retch_heal) then
+		if target and (target:attr("undead") or target:attr(retch_heal)) then
 			target:heal(dam * 1.5)
 
 			if src.callTalent then
@@ -1713,7 +1717,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "healing power", type = "HEALING_POWER",
+	name = "healing light", type = "HEALING_POWER",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and not target:attr("undead") then
@@ -1745,7 +1749,7 @@ newDamageType{
 
 -- Corrupted blood, blight damage + potential diseases
 newDamageType{
-	name = "corrupted blood", type = "CORRUPTED_BLOOD", text_color = "#DARK_GREEN#",
+	name = "infective blight", type = "CORRUPTED_BLOOD", text_color = "#DARK_GREEN#",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam} end
 		DamageType:get(DamageType.BLIGHT).projector(src, x, y, DamageType.BLIGHT, dam.dam)
@@ -1759,7 +1763,7 @@ newDamageType{
 
 -- blood boiled, blight damage + slow
 newDamageType{
-	name = "blood boil", type = "BLOOD_BOIL",
+	name = "hindering_blight", type = "BLOOD_BOIL",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.BLIGHT).projector(src, x, y, DamageType.BLIGHT, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -1796,7 +1800,7 @@ newDamageType{
 
 -- Physical Damage/Cut Split
 newDamageType{
-	name = "split bleed", type = "SPLIT_BLEED",
+	name = "physical bleed", type = "SPLIT_BLEED",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam / 2)
 		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam / 12)
@@ -1811,7 +1815,7 @@ newDamageType{
 
 -- Temporal/Physical damage
 newDamageType{
-	name = "matter", type = "MATTER",
+	name = "temporal shear", type = "MATTER",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.TEMPORAL).projector(src, x, y, DamageType.TEMPORAL, dam / 2)
 		DamageType:get(DamageType.PHYSICAL).projector(src, x, y, DamageType.PHYSICAL, dam / 2)
@@ -1820,7 +1824,7 @@ newDamageType{
 
 -- Temporal/Darkness damage
 newDamageType{
-	name = "void", type = "VOID", text_color = "#GREY#",
+	name = "temporal darkness", type = "VOID", text_color = "#GREY#",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.TEMPORAL).projector(src, x, y, DamageType.TEMPORAL, dam / 2)
 		DamageType:get(DamageType.DARKNESS).projector(src, x, y, DamageType.DARKNESS, dam / 2)
@@ -1864,7 +1868,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "repulsion", type = "REPULSION",
+	name = "physical repulsion", type = "REPULSION",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		tmp = tmp or {}
@@ -1909,7 +1913,7 @@ newDamageType{
 
 -- Mosses
 newDamageType{
-	name = "grasping moss", type = "GRASPING_MOSS",
+	name = "pinning nature", type = "GRASPING_MOSS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and src:reactionToward(target) < 0 then
@@ -1925,7 +1929,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "nourishing moss", type = "NOURISHING_MOSS",
+	name = "healing nature", type = "NOURISHING_MOSS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and src:reactionToward(target) < 0 then
@@ -1936,7 +1940,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "slippery moss", type = "SLIPPERY_MOSS",
+	name = "impeding nature", type = "SLIPPERY_MOSS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and src:reactionToward(target) < 0 then
@@ -1947,7 +1951,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "hallucinogenic moss", type = "HALLUCINOGENIC_MOSS",
+	name = "confounding nature", type = "HALLUCINOGENIC_MOSS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and src:reactionToward(target) < 0 then
@@ -1979,7 +1983,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "shiftingshadows", type = "SHIFTINGSHADOWS",
+	name = "defensive darkness", type = "SHIFTINGSHADOWS",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -1993,7 +1997,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "blazinglight", type = "BLAZINGLIGHT",
+	name = "blazing light", type = "BLAZINGLIGHT",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2008,7 +2012,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "warding", type = "WARDING",
+	name = "prismatic repulsion", type = "WARDING",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2030,7 +2034,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "mindslow", type = "MINDSLOW",
+	name = "mind slow", type = "MINDSLOW",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2042,7 +2046,7 @@ newDamageType{
 
 -- Freezes target, checks for physresistance
 newDamageType{
-	name = "mindfreeze", type = "MINDFREEZE",
+	name = "mind freeze", type = "MINDFREEZE",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2074,7 +2078,7 @@ newDamageType{
 
 -- Temporal + Stat damage
 newDamageType{
-	name = "reverse aging", type = "CLOCK",
+	name = "regressive temporal", type = "CLOCK",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2088,7 +2092,7 @@ newDamageType{
 
 -- Temporal Over Time
 newDamageType{
-	name = "wasting", type = "WASTING", text_color = "#LIGHT_STEEL_BLUE#",
+	name = "wasting temporal", type = "WASTING", text_color = "#LIGHT_STEEL_BLUE#",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		local dur = 3
@@ -2097,7 +2101,7 @@ newDamageType{
 		local init_dam = dam * perc / 100
 		if init_dam > 0 then DamageType:get(DamageType.TEMPORAL).projector(src, x, y, DamageType.TEMPORAL, init_dam) end
 		if target then
-			-- Set on fire!
+			-- Set wasting effect
 			dam = dam - init_dam
 			target:setEffect(target.EFF_WASTING, dur, {src=src, power=dam / dur, no_ct_effect=true})
 		end
@@ -2120,7 +2124,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "rethread", type = "RETHREAD",
+	name = "debilitating temporal", type = "RETHREAD",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		local chance = rng.range(1, 4)
@@ -2170,7 +2174,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "devour life", type = "DEVOUR_LIFE",
+	name = "draining physical", type = "DEVOUR_LIFE",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {dam=dam} end
 		local target = game.level.map(x, y, Map.ACTOR) -- Get the target first to make sure we heal even on kill
@@ -2183,14 +2187,14 @@ newDamageType{
 			src.healing_factor = 1
 			src:heal(heal)
 			src.healing_factor = temp
-			game.logSeen(target, "%s consumes %d life from %s!", src.name:capitalize(), heal, target.name)
+			src:logCombat(target, "#Source# consumes %d life from #Target#!", heal)
 		end
 	end,
 	hideMessage=true,
 }
 
 newDamageType{
-	name = "chronoslow", type = "CHRONOSLOW",
+	name = "temporal slow", type = "CHRONOSLOW",
 	projector = function(src, x, y, type, dam)
 		DamageType:get(DamageType.TEMPORAL).projector(src, x, y, DamageType.TEMPORAL, dam.dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -2233,7 +2237,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "manaworm", type = "MANAWORM",
+	name = "manaworm arcane", type = "MANAWORM",
 	projector = function(src, x, y, type, dam)
 		local realdam = DamageType:get(DamageType.ARCANE).projector(src, x, y, DamageType.ARCANE, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -2252,7 +2256,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "void blast", type = "VOID_BLAST",
+	name = "arcane blast", type = "VOID_BLAST",
 	projector = function(src, x, y, type, dam)
 		local realdam = DamageType:get(DamageType.ARCANE).projector(src, x, y, DamageType.ARCANE, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
@@ -2285,7 +2289,7 @@ newDamageType{
 
 -- Darkness damage + speed reduction + minion damage inc
 newDamageType{
-	name = "rigor mortis", type = "RIGOR_MORTIS",
+	name = "decaying darkness", type = "RIGOR_MORTIS",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2297,7 +2301,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "abyssal shroud", type = "ABYSSAL_SHROUD",
+	name = "abyssal darkness", type = "ABYSSAL_SHROUD",
 	projector = function(src, x, y, type, dam)
 		--make it dark
 		game.level.map.remembers(x, y, false)
@@ -2320,7 +2324,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "% chance to summon an orc spirit", type = "GARKUL_INVOKE",
+	name = "Garkul spirit", type = "GARKUL_INVOKE",
 	projector = function(src, x, y, type, dam)
 		if not rng.percent(dam) then return end
 		local target = game.level.map(x, y, engine.Map.ACTOR)
@@ -2413,7 +2417,7 @@ newDamageType{
 
 -- Generic apply temporary effect
 newDamageType{
-	name = "temp effect", type = "TEMP_EFFECT",
+	name = "special effect", type = "TEMP_EFFECT",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2428,7 +2432,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "manaburn", type = "MANABURN", text_color = "#PURPLE#",
+	name = "manaburn arcane", type = "MANABURN", text_color = "#PURPLE#",
 	projector = function(src, x, y, type, dam)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target then
@@ -2472,7 +2476,7 @@ newDamageType{
 
 -- Distortion; Includes knockback, penetrate, stun, and explosion paramters
 newDamageType{
-	name = "distortion", type = "DISTORTION",
+	name = "distorting physical", type = "DISTORTION",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if not target then return end
@@ -2580,7 +2584,7 @@ newDamageType{
 
 
 newDamageType{
-	name = "mucus", type = "MUCUS",
+	name = "natural mucus", type = "MUCUS",
 	projector = function(src, x, y, type, dam, tmp)
 		local target = game.level.map(x, y, Map.ACTOR)
 		if target and not target.turn_procs.mucus then
@@ -2601,7 +2605,7 @@ newDamageType{
 }
 
 newDamageType{
-	name = "acid disarm", type = "ACID_DISARM", text_color = "#GREEN#",
+	name = "disarming acid", type = "ACID_DISARM", text_color = "#GREEN#",
 	projector = function(src, x, y, type, dam)
 		if _G.type(dam) == "number" then dam = {chance=25, dam=dam} end
 		local realdam = DamageType:get(DamageType.ACID).projector(src, x, y, DamageType.ACID, dam.dam)
diff --git a/game/modules/tome/data/general/grids/lava.lua b/game/modules/tome/data/general/grids/lava.lua
index e636daa9f48dc0f084d997aa83a8b28ffafb5fe0..c5d24bd8270fd4991c4aa8f05d01f7b7dfd7c81a 100644
--- a/game/modules/tome/data/general/grids/lava.lua
+++ b/game/modules/tome/data/general/grids/lava.lua
@@ -32,7 +32,8 @@ newEntity{
 	on_stand = function(self, x, y, who)
 		local DT = engine.DamageType
 		local dam = DT:get(DT.FIRE).projector(self, x, y, DT.FIRE, rng.range(self.mindam, self.maxdam))
-		if dam > 0 then game.logPlayer(who, "The lava burns you!") end
+		self.x, self.y = x, y
+		if dam > 0 and who.player then self:logCombat(who, "#Source# burns #Target#!") end
 	end,
 	nice_tiler = { method="replace", base={"LAVA_FLOOR", 100, 1, 16}},
 	nice_editer = lava_editer,
diff --git a/game/modules/tome/data/general/grids/water.lua b/game/modules/tome/data/general/grids/water.lua
index 986ceebabce2490ced46b86a177ea1a68b4b228f..1884bebabc94f6b2df82bd1b036d3559d08aeda8 100644
--- a/game/modules/tome/data/general/grids/water.lua
+++ b/game/modules/tome/data/general/grids/water.lua
@@ -168,7 +168,8 @@ newEntity{
 
 		local DT = engine.DamageType
 		local dam = DT:get(DT.POISON).projector(self, x, y, DT.POISON, rng.range(self.mindam, self.maxdam))
-		if dam > 0 then game.logPlayer(who, "The water poisons you!") end
+		self.x, self.y = x, y
+		if dam > 0 and who.player then self:logCombat(who, "#Source# poisons #Target#!") end
 	end,
 	combatAttack = function(self) return rng.range(self.mindam, self.maxdam) end,
 	nice_tiler = { method="replace", base={"POISON_DEEP_WATER", 100, 1, 6}},
diff --git a/game/modules/tome/data/general/npcs/horror.lua b/game/modules/tome/data/general/npcs/horror.lua
index 70c5fe3f621d5730fca58c8924c17c0dc9bfd44e..33c8bb659b119f90348af7e1757f555c05c2e657 100644
--- a/game/modules/tome/data/general/npcs/horror.lua
+++ b/game/modules/tome/data/general/npcs/horror.lua
@@ -271,7 +271,7 @@ newEntity{ base = "BASE_NPC_HORROR", define_as = "BASE_NPC_ELDRICTH_EYE",
 
 	on_die = function(self, src)
 		if not self.summoner or not self.summoner.is_headless_horror then return end
-		game.logSeen(self, "#AQUAMARINE#As %s falls %s seems to weaken!", self.name, self.summoner.name)
+		self:logCombat(self.summoner, "#AQUAMARINE#As #Source# falls #Target# seems to weaken!")
 		local damtype = next(self.resists)
 		self.summoner.resists.all = (self.summoner.resists.all or 0) - 30
 		self.summoner.resists[damtype] = nil
@@ -891,7 +891,7 @@ newEntity{ base="BASE_NPC_HORROR", define_as = "GRGGLCK_TENTACLE",
 
 	on_die = function(self, who)
 		if self.summoner and not self.summoner.dead and who then
-			game.logSeen(self, "#AQUAMARINE#As %s falls you notice that %s seems to shudder in pain!", self.name, self.summoner.name)
+			self:logCombat(self.summoner, "#AQUAMARINE#As #Source# falls you notice that #Target# seems to shudder in pain!")
 			if self.summoner.is_grgglck then
 				self.summoner:takeHit(self.max_life, who)
 			else
diff --git a/game/modules/tome/data/general/objects/boss-artifacts.lua b/game/modules/tome/data/general/objects/boss-artifacts.lua
index b4a9416aec31fe67c2fe1f4872a7546bca1ff3a3..7fb255bcc38b211384825ab25aafd37f789fa78f 100644
--- a/game/modules/tome/data/general/objects/boss-artifacts.lua
+++ b/game/modules/tome/data/general/objects/boss-artifacts.lua
@@ -1482,7 +1482,7 @@ newEntity{ base = "BASE_LONGBOW",
 			who:project(tg, a.x, a.y, engine.DamageType.LIGHTNING_DAZE, {daze=40, dam = rng.avg(1,3) * (40+ who:getMag() * 1.5)} )
 			game.level.map:particleEmitter(who.x, who.y, math.max(math.abs(a.x-who.x), math.abs(a.y-who.y)), "lightning", {tx=a.x-who.x, ty=a.y-who.y})
 			game:playSoundNear(self, "talents/lightning")
-			game.logSeen(who, "#GOLD#A bolt of lightning fires from %s's bow, striking %s!", who.name:capitalize(), a.name:capitalize())
+			who:logCombat(a, "#GOLD#A bolt of lightning fires from #Source#'s bow, striking #Target#!")
 		end
 	end,
 	on_wear = function(self, who)
diff --git a/game/modules/tome/data/general/objects/world-artifacts.lua b/game/modules/tome/data/general/objects/world-artifacts.lua
index 9a41b83fae42bee5c770e85cea49d751dc359244..0ec63f268fd0231edd1fc9663e7b9bfadc8bba98 100644
--- a/game/modules/tome/data/general/objects/world-artifacts.lua
+++ b/game/modules/tome/data/general/objects/world-artifacts.lua
@@ -1258,10 +1258,10 @@ newEntity{ base = "BASE_HELM", define_as = "HELM_KROLTAR",
 		self:specialSetAdd({"wielder","combat_spellresist"}, 15)
 		self:specialSetAdd({"wielder","combat_mentalresist"}, 15)
 		self:specialSetAdd({"wielder","combat_physresist"}, 15)
-		game.logPlayer(who, "#GOLD#As the Kroltar helm approches the scale mail, they begin to emit fumes and fires.")
+		game.logPlayer(who, "#GOLD#As the helm of Kroltar approaches the your scale armour, they begin to fume and emit fire.")
 	end,
 	on_set_broken = function(self, who)
-		game.logPlayer(who, "#GOLD#The funes and fires disappear.")
+		game.logPlayer(who, "#GOLD#The fumes and fire fade away.")
 	end,
 }
 
@@ -3455,7 +3455,7 @@ newEntity{ base = "BASE_WHIP",
 			who:project(blast, x, y, engine.DamageType.LIGHTNING, rng.avg(dam / 3, dam, 3))
 			game.level.map:particleEmitter(x, y, radius, "ball_lightning", {radius=blast.radius})
 			game:playSoundNear(self, "talents/lightning")
-			game.logSeen(who, "%s strikes %s, sending out an arc of lightning!", who.name:capitalize(), target.name)
+			who:logCombat(target, "#Source# strikes #Target#, sending out an arc of lightning!")
 			return {id=true, used=true}
 		end
 	},
@@ -3830,7 +3830,7 @@ newEntity{ base = "BASE_LONGSWORD",
 				if not rng.percent(20) then return end
 				if not who:checkHit(who:combatMindpower(), target:combatMentalResist()) then return end
 				target:setEffect(target.EFF_WEAKENED_MIND, 2, {power=5})
-				game.logSeen(who, "Anima's eye glares at %s, piercing their mind!", target.name:capitalize())
+				who:logCombat(target, "Anmalice focuses its mind-piercing eye on #Target#!")
 			end)
 	end,
 	on_takeoff = function(self, who)
@@ -3950,9 +3950,9 @@ newEntity{ base = "BASE_WHIP", define_as = "HYDRA_BITE",
 				o.running = 1
 				if tries >= 100 or #tgts==1 then twohits=nil end
 				if twohits then
-					game.logSeen(who, "%s's three headed flail lashes at %s and %s!",who.name:capitalize(), target1.name:capitalize(),target2.name:capitalize())
+					who:logCombat(target1, "#Source#'s three headed flail lashes at #Target#%s!",who:canSee(target2) and (" and %s"):format(target2.name:capitalize()) or "")
 				else
-					game.logSeen(who, "%s's three headed flail lashes at %s!",who.name:capitalize(), target1.name:capitalize())
+					who:logCombat(target1, "#Source#'s three headed flail lashes at #Target#!")
 				end
 				who:attackTarget(target1, engine.DamageType.PHYSICAL, 0.4,  true)
 				if twohits then who:attackTarget(target2, engine.DamageType.PHYSICAL, 0.4,  true) end
@@ -4976,7 +4976,7 @@ newEntity{ base = "BASE_SHIELD", --Thanks SageAcrin!
 		
 			who:project(burst, target.x, target.y, engine.DamageType.ICE, 30)
 			game.level.map:particleEmitter(who.x, who.y, burst.radius, "breath_cold", {radius=burst.radius, tx=target.x-who.x, ty=target.y-who.y})
-			game.logSeen(who, "A burst of chilling water launches from %s's shield to %s!", who.name:capitalize(), target.name:capitalize())
+			who:logCombat(target, "A wave of icy water bursts out from #Source#'s shield towards #Target#!")
 		end
 	end,
 }
diff --git a/game/modules/tome/data/maps/vaults/greater-crypt.lua b/game/modules/tome/data/maps/vaults/greater-crypt.lua
index 65403204335e77e9b6cd8932a467bd438172ce67..4e70e489192e1bf5d9a60cf66ea39a8f246c00df 100644
--- a/game/modules/tome/data/maps/vaults/greater-crypt.lua
+++ b/game/modules/tome/data/maps/vaults/greater-crypt.lua
@@ -80,7 +80,7 @@ defineTile('1', mod.class.Grid.new{
 		end
 		actor:move(fx, fy, true)
 
-		game.logPlayer(actor, "Something in the floor clicks ominously, and suddenly the world spins around you!")
+		game.logPlayer(actor, "Something in the floor clicks ominously%s", actor.lite > 0 and not actor:attr("blind") and ", and suddenly the world spins around you!" or ".")
 		local g = game.zone:makeEntityByName(game.level, "terrain", "FLOOR")
 		if not g then return end
 		game.zone:addEntity(game.level, g, "terrain", x, y)
@@ -111,8 +111,7 @@ defineTile('2', mod.class.Grid.new{
 		game.zone:addEntity(game.level, f, "terrain", x, y)
 		game.nicer_tiles:updateAround(game.level, x, y)
 
-
-		game.logPlayer(actor, "Something in the floor clicks ominously, and the crypt rearranges itself around you!")
+		game.logPlayer(actor, "Something in the floor clicks ominously%s", actor.lite > 0 and not actor:attr("blind") and ", and the crypt rearranges itself around you!" or ".")
 
 	end,
 }
@@ -164,8 +163,7 @@ defineTile('4', mod.class.Grid.new{
 		game.zone:addEntity(game.level, f, "terrain", x, y)
 		game.nicer_tiles:updateAround(game.level, x, y)
 
-		game.logPlayer(actor, "Something underfoot clicks ominously, and the crypt rearranges itself around you!")
-
+		game.logPlayer(actor, "Something in the floor clicks ominously%s", actor.lite > 0 and not actor:attr("blind") and ", and the crypt rearranges itself around you!" or ".")
 	end,
 }
 )
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 83f0f47c15a93de96c70722f791acb84019117dd..25c1455a7de71c6aa3389865b0fc50b5346f262b 100644
--- a/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua
+++ b/game/modules/tome/data/maps/vaults/paladin-vs-vampire.lua
@@ -33,10 +33,12 @@ defineTile('~', mod.class.Grid.new{
 	on_stand = function(self, x, y, who)
 		local DT = engine.DamageType
 		local dam = DT:get(DT.RETCH).projector(self, x, y, DT.RETCH, rng.range(self.mindam, self.maxdam))
-		if not who:attr("undead") then game.logPlayer(who, "Dark energies course upwards through the lava.") end
+		self.x, self.y = x, y
+		if who.player and not who:attr("undead") then self:logCombat(who, "#Source# emits dark energies at your feet.") end
 		if who.dead and not who:attr("undead") then
 			--add undead
 			local m = game.zone:makeEntityByName(game.level, "actor", "RISEN_CORPSE")
+			game.logSeen(who, "The corrupted lava reanimates %s's corpse!", who.name:capitalize())
 			game.zone:addEntity(game.level, m, "actor", x, y)
 		end
 	end,
diff --git a/game/modules/tome/data/talents/celestial/twilight.lua b/game/modules/tome/data/talents/celestial/twilight.lua
index cc14c56b2e0d92c732815342777646cecc1012b3..1612ad1b4cfd9efd5db8efc755616d6700c41b2b 100644
--- a/game/modules/tome/data/talents/celestial/twilight.lua
+++ b/game/modules/tome/data/talents/celestial/twilight.lua
@@ -222,7 +222,7 @@ newTalent{
 			target:reactionToward(self) >= 0 or -- No friends
 			target.size_category > allowed
 			then
-			game.logPlayer(self, "%s resists!", target.name:capitalize())
+			game.logSeen(target, "%s resists!", target.name:capitalize())
 			return true
 		end
 
diff --git a/game/modules/tome/data/talents/chronomancy/timeline-threading.lua b/game/modules/tome/data/talents/chronomancy/timeline-threading.lua
index 20b6d12d378751c0d9dd6139ede8fa59164f3bb2..b45ed1c68538b08908aec735ca283d88b80b806d 100644
--- a/game/modules/tome/data/talents/chronomancy/timeline-threading.lua
+++ b/game/modules/tome/data/talents/chronomancy/timeline-threading.lua
@@ -114,7 +114,7 @@ newTalent{
 			target:reactionToward(self) >= 0 or -- No friends
 			target.size_category > allowed
 			then
-			game.logPlayer(self, "%s resists!", target.name:capitalize())
+			game.logSeen(target, "%s resists!", target.name:capitalize())
 			return true
 		end
 
diff --git a/game/modules/tome/data/talents/cunning/dirty.lua b/game/modules/tome/data/talents/cunning/dirty.lua
index a14d9b0e07e706a6c1e15b1a3a908136c12f6678..45bb0f8b9dd9f90cfdef8cad619fba4c3572063e 100644
--- a/game/modules/tome/data/talents/cunning/dirty.lua
+++ b/game/modules/tome/data/talents/cunning/dirty.lua
@@ -43,7 +43,7 @@ newTalent{
 				target:setEffect(target.EFF_STUNNED, t.getDuration(self, t), {apply_power=self:combatAttack()})
 			end
 			if not target:hasEffect(target.EFF_STUNNED) then
-				game.logSeen(target, "%s resists the stun and %s quickly gets back on feet!", target.name:capitalize(), self.name:capitalize())
+				self:logCombat(target, "#Target# resists the stun and #Source# quickly regains its footing!")
 				self.energy.value = self.energy.value + game.energy_to_act * self:combatSpeed()
 			end
 		end
@@ -101,7 +101,7 @@ newTalent{
 
 		if hitted and not self.dead and tx == target.x and ty == target.y then
 			if not self:canMove(tx,ty,true) or not target:canMove(sx,sy,true) then
-				game.logSeen(self, "%s and %s cannot switch places due to terrain.",self.name:capitalize(),target.name:capitalize())
+				self:logCombat(target, "Terrain prevents #Source# from switching places with #Target#.")
 				return false
 			end						
 			self:setEffect(self.EFF_EVASION, t.getDuration(self, t), {chance=50})
diff --git a/game/modules/tome/data/talents/cunning/traps.lua b/game/modules/tome/data/talents/cunning/traps.lua
index 881f45ff9f16f0ff1ca90341f8bf1a6814b1e99a..1bbb4a794c8e85a8f9ab213236fbc0bc65fab707 100644
--- a/game/modules/tome/data/talents/cunning/traps.lua
+++ b/game/modules/tome/data/talents/cunning/traps.lua
@@ -673,7 +673,7 @@ newTalent{
 						if target:canBe("knockback") then 
 							target:pull(self.x, self.y, 1)
 							if target.x ~= ox or target.y ~= oy then
-								game.logSeen(target, "%s is pulled toward %s's gravity trap!", target.name:capitalize(), self.summoner.name:capitalize())
+								self.summoner:logCombat(target, "#Target# is pulled towards #Source#'s gravity trap!")
 							end
 						end
 					end
diff --git a/game/modules/tome/data/talents/cursed/darkness.lua b/game/modules/tome/data/talents/cursed/darkness.lua
index 514873f5ca5d169c5d455011ad65e6b33294ede3..39dbe90df8f4b6196e110f2dc6350cf5bfb9ba40 100644
--- a/game/modules/tome/data/talents/cursed/darkness.lua
+++ b/game/modules/tome/data/talents/cursed/darkness.lua
@@ -202,7 +202,7 @@ newTalent{
 	end,
 	createDark = function(summoner, x, y, damage, duration, creep, creepChance, initialCreep)
 		local e = Object.new{
-			name = "creeping dark",
+			name = summoner.name:capitalize() .. "'s creeping dark",
 			block_sight=true,
 			canAct = false,
 			canCreep = true,
@@ -224,7 +224,9 @@ newTalent{
 				local actor = game.level.map(self.x, self.y, Map.ACTOR)
 				if actor and actor ~= self.summoner and (not actor.summoner or actor.summoner ~= self.summoner) then
 					self.projecting = true -- simplest way to indicate that this damage should not be amplified by the in creeping dark bonus
+					self.summoner.__project_source = self -- intermediate projector source
 					self.summoner:project({type="hit", range=10, talent=self.summoner:getTalentFromId(self.summoner.T_CREEPING_DARKNESS)}, actor.x, actor.y, engine.DamageType.DARKNESS, self.damage)
+					self.summoner.__project_source = nil
 					self.projecting = false
 				end
 
diff --git a/game/modules/tome/data/talents/cursed/force-of-will.lua b/game/modules/tome/data/talents/cursed/force-of-will.lua
index b8d31a3f6e91a4ac24791c463c59937c2ee2ce1b..0dc4fd7324be5865386f66004cdc98d731febb9e 100644
--- a/game/modules/tome/data/talents/cursed/force-of-will.lua
+++ b/game/modules/tome/data/talents/cursed/force-of-will.lua
@@ -46,7 +46,6 @@ local function forceHit(self, t, target, sourceX, sourceY, damage, knockback, kn
 		local knockbackCount = 0
 		local blocked = false
 		while knockback > 0 do
-			blocked = true
 			local x, y, is_corner_blocked = lineFunction:step(true)
 
 			if not game.level.map:isBound(x, y) or is_corner_blocked or game.level.map:checkAllEntities(x, y, "block_move", target) then
@@ -54,14 +53,14 @@ local function forceHit(self, t, target, sourceX, sourceY, damage, knockback, kn
 				local nextTarget = game.level.map(x, y, Map.ACTOR)
 				if nextTarget then
 					if knockbackCount > 0 then
-						game.logPlayer(self, "%s was blasted %d spaces into %s!", target.name:capitalize(), knockbackCount, nextTarget.name)
+						target:logCombat(nextTarget, "#Source# was blasted %d spaces into #Target#!", knockbackCount)
 					else
-						game.logPlayer(self, "%s was blasted into %s!", target.name:capitalize(), nextTarget.name)
+						target:logCombat(nextTarget, "#Source# was blasted into #Target#!")
 					end
 				elseif knockbackCount > 0 then
-					game.logPlayer(self, "%s was smashed back %d spaces!", target.name:capitalize(), knockbackCount)
+					game.logSeen(target, "%s was smashed back %d spaces!", target.name:capitalize(), knockbackCount)
 				else
-					game.logPlayer(self, "%s was smashed!", target.name:capitalize())
+					game.logSeen(target, "%s was smashed!", target.name:capitalize())
 				end
 
 				-- take partial damage
@@ -76,6 +75,7 @@ local function forceHit(self, t, target, sourceX, sourceY, damage, knockback, kn
 				end
 
 				knockback = 0
+				blocked = true
 			else
 				-- allow move
 				finalX, finalY = x, y
@@ -85,7 +85,7 @@ local function forceHit(self, t, target, sourceX, sourceY, damage, knockback, kn
 		end
 
 		if not blocked and knockbackCount > 0 then
-			game.logPlayer(self, "%s was blasted back %d spaces!", target.name:capitalize())
+			game.logSeen(target, "%s was blasted back %d spaces!", target.name:capitalize(), knockbackCount)
 		end
 
 		if not target.dead and (finalX ~= target.x or finalY ~= target.y) then
@@ -310,7 +310,7 @@ newTalent{
 	end,
 	getSecondHitChance = function(self, t) return self:combatTalentScale(self:getTalentLevel(t)-4, 15, 35) end,
 	action = function(self, t)
-		game.logSeen(self, "An unseen force begin to swirl around %s!", self.name)
+		game.logSeen(self, "An unseen force begins to swirl around %s!", self.name)
 		local duration = t.getDuration(self, t)
 		local particles = self:addParticles(Particles.new("force_area", 1, { radius = self:getTalentRange(t) }))
 
diff --git a/game/modules/tome/data/talents/cursed/gestures.lua b/game/modules/tome/data/talents/cursed/gestures.lua
index d4665fe96278b357056358c20f1600bb2aca40b3..2382d8ba579ad63b0f49768714f6e036337110a8 100644
--- a/game/modules/tome/data/talents/cursed/gestures.lua
+++ b/game/modules/tome/data/talents/cursed/gestures.lua
@@ -231,7 +231,7 @@ newTalent{
 	-- Counterattack handled in _M:attackTargetWith function in mod.class.interface.Combat.lua (requires EFF_GESTURE_OF_GUARDING)
 	on_hit = function(self, t, who)
 		if rng.percent(t.getCounterAttackChance(self, t)) and self:isTalentActive(self.T_GESTURE_OF_PAIN) and canUseGestures(self) then
-			game.logSeen(self, "#F53CBE#%s lashes back at %s!", self.name:capitalize(), who.name)
+			self:logCombat(who, "#F53CBE##Source# lashes back at #Target#!")
 			local tGestureOfPain = self:getTalentFromId(self.T_GESTURE_OF_PAIN)
 			tGestureOfPain.attack(self, tGestureOfPain, who)
 		end
diff --git a/game/modules/tome/data/talents/cursed/predator.lua b/game/modules/tome/data/talents/cursed/predator.lua
index 71000dafd0164498ad4275cba63a386d7ec19b97..04db0f8a485f5b7199d02de06413edefb9c68eb9 100644
--- a/game/modules/tome/data/talents/cursed/predator.lua
+++ b/game/modules/tome/data/talents/cursed/predator.lua
@@ -57,7 +57,7 @@ newTalent{
 		if eff and eff.type == target.type and eff.subtype == target.subtype then
 			return false
 		end
-		if eff then self:removeEffect(self.EFF_PREDATOR) end
+		if eff then self:removeEffect(self.EFF_PREDATOR, true, true) end
 		self:setEffect(self.EFF_PREDATOR, 1, { type=target.type, subtype=target.subtype, killExperience = 0, subtypeKills = 0, typeKills = 0 })
 		
 		return true
diff --git a/game/modules/tome/data/talents/cursed/shadows.lua b/game/modules/tome/data/talents/cursed/shadows.lua
index a41883be27767e9bfea88428e78c427e67a47889..af46b974182a523bf41ef8f1795ecdac37ca49a2 100644
--- a/game/modules/tome/data/talents/cursed/shadows.lua
+++ b/game/modules/tome/data/talents/cursed/shadows.lua
@@ -596,7 +596,7 @@ newTalent{
 			end
 
 			if shadowCount > 0 then
-				game.logPlayer(self, "#PINK#The shadows converge on %s!", target.name)
+				self:logCombat(target, "#PINK#The shadows converge on #Target#!")
 				return true
 			else
 				game.logPlayer(self, "Their are no shadows to heed the call!")
@@ -617,7 +617,7 @@ newTalent{
 			end
 
 			if shadowCount > 0 then
-				game.logPlayer(self, "#PINK#The shadows form around %s!", target.name)
+				self:logCombat(target, "#PINK#The shadows form around #Target#!")
 				return true
 			else
 				game.logPlayer(self, "Their are no shadows to heed the call!")
diff --git a/game/modules/tome/data/talents/cursed/slaughter.lua b/game/modules/tome/data/talents/cursed/slaughter.lua
index 3b09aa9f4fcda5db450a0d85c45fdbce5a2125dd..d065316c845f485ce79d014563e8268b905188e3 100644
--- a/game/modules/tome/data/talents/cursed/slaughter.lua
+++ b/game/modules/tome/data/talents/cursed/slaughter.lua
@@ -186,7 +186,7 @@ newTalent{
 									and game.level.map:isBound(x, y)
 									and not game.level.map:checkAllEntities(x, y, "block_move", self) then
 								blockingTarget:move(x, y, true)
-								game.logSeen(self, "%s knocks back %s!", self.name:capitalize(), blockingTarget.name)
+								self:logCombat(blockingTarget, "#Source# knocks back #Target#!")
 								blocked = false
 								break
 							end
@@ -195,7 +195,7 @@ newTalent{
 				end
 
 				if blocked then
-					game.logSeen(self, "%s blocks %s!", blockingTarget.name:capitalize(), self.name)
+					self:logCombat(blockingTarget, "#Target# blocks #Source#!")
 				end
 			end
 
@@ -341,7 +341,7 @@ newTalent{
 				local secondTarget = game.level.map(x, y, Map.ACTOR)
 				if secondTarget and secondTarget ~= target and self:reactionToward(secondTarget) < 0 then
 					local damageMultiplier = t.getDamageMultiplier(self, t)
-					game.logSeen(self, "%s cleaves through %s!", self.name:capitalize(), secondTarget.name)
+					self:logCombat(secondTarget, "#Source# cleaves through #Target#!")
 					self:attackTarget(secondTarget, nil, damageMultiplier, true)
 					inCleave = false
 					return
diff --git a/game/modules/tome/data/talents/gifts/sand-drake.lua b/game/modules/tome/data/talents/gifts/sand-drake.lua
index e964fd32f9922d547b634623e993178dcd577a55..3367dbf0b4fde5c342045d72bf33d80fed6c1f90 100644
--- a/game/modules/tome/data/talents/gifts/sand-drake.lua
+++ b/game/modules/tome/data/talents/gifts/sand-drake.lua
@@ -39,9 +39,7 @@ newTalent{
 		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
-
-		game.logSeen(self, "%s tries to swallow %s!", self.name:capitalize(), target.name)
-
+		self:logCombat(target, "#Source# tries to swallow #Target#!")
 		local hit = self:attackTarget(target, DamageType.NATURE, self:combatTalentWeaponDamage(t, 1, 1.5), true)
 		if not hit then return true end
 
diff --git a/game/modules/tome/data/talents/gifts/storm-drake.lua b/game/modules/tome/data/talents/gifts/storm-drake.lua
index a2932da15fe748568c535f018e31abbee0b540d5..4becd302cda9de7c14c0f3dbc3f54499e4e91fb3 100644
--- a/game/modules/tome/data/talents/gifts/storm-drake.lua
+++ b/game/modules/tome/data/talents/gifts/storm-drake.lua
@@ -125,7 +125,7 @@ newTalent{
 		local movedam = self:mindCrit(self:combatTalentMindDamage(t, 10, 110))
 		local dam = self:mindCrit(self:combatTalentMindDamage(t, 15, 190))
 
-		local proj = require("engine.Projectile"):makeHoming(
+		local proj = require("mod.class.Projectile"):makeHoming(
 			self,
 			{particle="bolt_lightning", trail="lightningtrail"},
 			{speed=2, name="Tornado", dam=dam, movedam=movedam},
diff --git a/game/modules/tome/data/talents/gifts/summon-melee.lua b/game/modules/tome/data/talents/gifts/summon-melee.lua
index f9d0c06ee697184d4ca43cbd1527b1a23d75f62d..2eeedffdd51b6233f23bf6e91c676b39ee1bba2d 100644
--- a/game/modules/tome/data/talents/gifts/summon-melee.lua
+++ b/game/modules/tome/data/talents/gifts/summon-melee.lua
@@ -192,7 +192,7 @@ newTalent{
 				local p = value * 0.10
 				if self.summoner and not self.summoner.dead then
 					self.summoner:incEquilibrium(-p)
-					game.logSeen(self, "#GREEN#%s absorbs part of the blow. %s is closer to nature.", self.name:capitalize(), self.summoner.name:capitalize())
+					self:logCombat(self.summoner, "#GREEN##Source# absorbs part of the blow. #Target# is closer to nature.")
 				end
 				return value - p
 			end,
diff --git a/game/modules/tome/data/talents/misc/npcs.lua b/game/modules/tome/data/talents/misc/npcs.lua
index 19691cfb5fb8bf1b90442b6a3c70be1bd153b6dc..a20fd4f0e2f32e768cbf620b0235577af493aa75 100644
--- a/game/modules/tome/data/talents/misc/npcs.lua
+++ b/game/modules/tome/data/talents/misc/npcs.lua
@@ -389,7 +389,7 @@ newTalent{
 
 				game.zone:addEntity(game.level, m, "actor", x, y)
 
-				game.logSeen(self, "%s summons %s!", self.name:capitalize(), m.name)
+				self:logCombat(m, "#Source# summons #Target#!")
 
 				-- Apply summon destabilization
 				if self:hasEffect(self.EFF_SUMMON_DESTABILIZATION) then
@@ -1223,7 +1223,7 @@ newTalent{
 
 			game.zone:addEntity(game.level, m, "actor", x, y)
 
-			game.logSeen(self, "%s spawns one of its tentacle!", self.name:capitalize())
+			game.logSeen(self, "%s spawns one of its tentacles!", self.name:capitalize())
 		end
 
 		return true
diff --git a/game/modules/tome/data/talents/psionic/distortion.lua b/game/modules/tome/data/talents/psionic/distortion.lua
index 9e3b3cdd30cb51ec0a17aa1537585f64e0885dd5..62a1c26ac7b471bd4e95172846a847362e910c2c 100644
--- a/game/modules/tome/data/talents/psionic/distortion.lua
+++ b/game/modules/tome/data/talents/psionic/distortion.lua
@@ -196,8 +196,9 @@ newTalent{
 		local e = Object.new{
 			old_feat = oe,
 			type = oe.type, subtype = oe.subtype,
-			name = "maelstrom", image = oe.image,
+			name = self.name:capitalize().. "'s maelstrom", image = oe.image,
 			display = oe.display, color=oe.color, back_color=oe.back_color,
+			tooltip = mod.class.Grid.tooltip,
 			always_remember = true,
 			temporary = t.getDuration(self, t),
 			is_maelstrom = true,
@@ -224,11 +225,13 @@ newTalent{
 				end end
 				table.sort(tgts, "sqdist")
 				for i, target in ipairs(tgts) do
+					self.summoner.__project_source = self
 					if target.actor:canBe("knockback") then
 						target.actor:pull(self.x, self.y, 1)
-						game.logSeen(target.actor, "%s is pulled in by the %s!", target.actor.name:capitalize(), self.name)
+						target.actor.logCombat(self, target.actor, "#Source# pulls #Target# in!")
 					end
 					DamageType:get(DamageType.PHYSICAL).projector(self.summoner, target.actor.x, target.actor.y, DamageType.PHYSICAL, self.dam)
+					self.summoner.__project_source = nil
 					target.actor:setEffect(target.actor.EFF_DISTORTION, 2, {power=self.distortionPower})
 				end
 
diff --git a/game/modules/tome/data/talents/psionic/dream-forge.lua b/game/modules/tome/data/talents/psionic/dream-forge.lua
index e4a00088170e865c2e1c344627b51321b60aa814..6afd06c889a0f998bdabb49d0cc821f144a6d002 100644
--- a/game/modules/tome/data/talents/psionic/dream-forge.lua
+++ b/game/modules/tome/data/talents/psionic/dream-forge.lua
@@ -133,7 +133,8 @@ newTalent{
 			local e = Object.new{
 				old_feat = oe,
 				type = oe.type, subtype = oe.subtype,
-				name = "forge barrier", image = "terrain/lava/lava_mountain5.png",
+				name = self.name:capitalize().."'s forge barrier",
+				image = "terrain/lava/lava_mountain5.png",
 				display = '#', color=colors.RED, back_color=colors.DARK_GREY,
 				shader = "shadow_simulacrum",
 				shader_args = { color = {0.6, 0.0, 0.0}, base = 0.9, time_factor = 1500 },
@@ -152,8 +153,9 @@ newTalent{
 				radius = self:getTalentRadius(t),
 				act = function(self)
 					local tg = {type="ball", range=0, friendlyfire=false, radius = 1, talent=t, x=self.x, y=self.y,}
+					self.summoner.__project_source = self
 					self.summoner:project(tg, self.x, self.y, engine.DamageType.DREAMFORGE, self.dam)
-									
+					self.summoner.__project_source = nil
 					self:useEnergy()
 					self.temporary = self.temporary - 1
 					if self.temporary <= 0 then
diff --git a/game/modules/tome/data/talents/psionic/possession.lua b/game/modules/tome/data/talents/psionic/possession.lua
index 88a62a83526d4c7c23fd5d36d449bd068135b6fb..793c56cc873b305c97d8c15b655ebcdc030ecd03 100644
--- a/game/modules/tome/data/talents/psionic/possession.lua
+++ b/game/modules/tome/data/talents/psionic/possession.lua
@@ -50,10 +50,9 @@ newTalent{
 
 		if not t.allowedTypes(self, t, target.type) then game.logPlayer(self, "You may not possess this kind of creature.") return nil end
 --		if target.life > target.max_life * 0.25 then game.logPlayer(self, "You may not possess this creature yet its life is too high.") return nil end
-		if target.dead then game.logPlayer(self, "This creature is dead!") return nil end
-
+		if target.dead then game.logPlayer(self, "Your target is dead!") return nil end
 		if not self:checkHit(self:combatMindpower(), target:combatMentalResist(), 0, 95, 5) then -- or not target:canBe("instakill") then
-			game.logPlayer(self, "You fail to shatter %s mind, leaving you unable to possess its body.", target.name)
+			self:logCombat(target, "#Source# fails to shatter #Target#'s mind, preventing its possession.")
 			return true
 		end
 
diff --git a/game/modules/tome/data/talents/psionic/solipsism.lua b/game/modules/tome/data/talents/psionic/solipsism.lua
index 03aeba24ff7ceee94ff3fd1be7e7ab42d49f5c41..2b0883865e43d6f9a2620cf8d626c1f3f3be6af0 100644
--- a/game/modules/tome/data/talents/psionic/solipsism.lua
+++ b/game/modules/tome/data/talents/psionic/solipsism.lua
@@ -189,7 +189,8 @@ newTalent{
 		print("[Dismissal] ", self.name:capitalize(), " attempting to ignore ", value, "damage from ", src.name:capitalize(), "using", saving_throw,  "mental save.")
 		if self:checkHit(saving_throw, value) then
 			local dismissed = value * 1/self:mindCrit(2) -- Diminishing returns on high crits
-			game.logSeen(self, "%s dismisses %d damage from %s!", self.name:capitalize(), dismissed, src.name:capitalize())
+			game:delayedLogMessage(self, nil, "Dismissal", "#TAN##Source# mentally dismisses some damage!")
+			game:delayedLogDamage(src, self, 0, ("#TAN#(%d dismissed)#LAST#"):format(dismissed))
 			return value - dismissed
 		else
 			return value
diff --git a/game/modules/tome/data/talents/spells/aether.lua b/game/modules/tome/data/talents/spells/aether.lua
index a783f57458212696035e626b1a0815b5761dfcbf..0eeb8818410add0704192acc7c1743a4652c63a7 100644
--- a/game/modules/tome/data/talents/spells/aether.lua
+++ b/game/modules/tome/data/talents/spells/aether.lua
@@ -98,8 +98,10 @@ newTalent{
 				self.list.i = util.boundWrap(self.list.i + 1, 1, #self.list)
 
 				local tg = {type="beam", x=self.x, y=self.y, range=self.rad, selffire=self.summoner:spellFriendlyFire()}
+				self.summoner.__project_source = self
 				self.summoner:project(tg, x, y, engine.DamageType.ARCANE_SILENCE, {dam=self.dam, chance=25}, nil)
 				self.summoner:project(tg, self.x, self.y, engine.DamageType.ARCANE, self.dam/10, nil)
+				self.summoner.__project_source = nil
 				local _ _, x, y = self:canProject(tg, x, y)
 				game.level.map:particleEmitter(self.x, self.y, math.max(math.abs(x-self.x), math.abs(y-self.y)), "mana_beam", {tx=x-self.x, ty=y-self.y})
 			end,
@@ -152,7 +154,7 @@ newTalent{
 		local list = {}
 		self:project(tg, x, y, function(px, py) list[#list+1] = {x=px, y=py} end)
 
-		self:setEffect(self.EFF_AETHER_BREACH, t.getNb(self, t), {list=list, level=game.zone.short_name.."-"..game.level.level, dam=self:spellCrit(t.getDamage(self, t))})
+		self:setEffect(self.EFF_AETHER_BREACH, t.getNb(self, t), {src = self, list=list, level=game.zone.short_name.."-"..game.level.level, dam=self:spellCrit(t.getDamage(self, t))})
 
 		game:playSoundNear(self, "talents/arcane")
 		return true
diff --git a/game/modules/tome/data/talents/spells/fire-alchemy.lua b/game/modules/tome/data/talents/spells/fire-alchemy.lua
index 720d4c15db55fcad9d341877703e05660fad3c18..06d2ef73ee660022b676da07c396e3f60b33a837 100644
--- a/game/modules/tome/data/talents/spells/fire-alchemy.lua
+++ b/game/modules/tome/data/talents/spells/fire-alchemy.lua
@@ -131,7 +131,7 @@ newTalent{
 	getDamage = function(self, t) return self:combatTalentSpellDamage(t, 5, 120) end,
 	action = function(self, t)
 		-- Add a lasting map effect
-		game.level.map:addEffect(self,
+		local ef = game.level.map:addEffect(self,
 			self.x, self.y, t.getDuration(self, t),
 			DamageType.FIRE_FRIENDS, t.getDamage(self, t),
 			3,
@@ -144,6 +144,7 @@ newTalent{
 			end,
 			false
 		)
+		ef.name = "firestorm"
 		game:playSoundNear(self, "talents/fire")
 		return true
 	end,
@@ -195,7 +196,7 @@ newTalent{
 			local a, id = rng.table(tgts)
 			table.remove(tgts, id)
 
-			self:projectile(tg, a.x, a.y, DamageType.FIRE, self:spellCrit(t.getFireDamageInSight(self, t)), {type="flame"})
+			self:projectile(table.clone(tg), a.x, a.y, DamageType.FIRE, self:spellCrit(t.getFireDamageInSight(self, t)), {type="flame"})
 			game:playSoundNear(self, "talents/fire")
 		end
 	end,
diff --git a/game/modules/tome/data/talents/spells/golem.lua b/game/modules/tome/data/talents/spells/golem.lua
index 9369ee76ba9d22ade851a1d4f3237fb133689142..87bbc9b71a72d28fac3daaa3e4b4efb93a7ed185 100644
--- a/game/modules/tome/data/talents/spells/golem.lua
+++ b/game/modules/tome/data/talents/spells/golem.lua
@@ -124,7 +124,7 @@ newTalent{
 			if self:reactionToward(target) < 0 then
 				if self.ai_target then self.ai_target.target = target end
 				target:setTarget(self)
-				game.logSeen(self, "%s provokes %s to attack it.", self.name:capitalize(), target.name)
+				self:logCombat(target, "#Source# provokes #Target# to attack it.")
 			end
 		end)
 		return true
@@ -419,7 +419,7 @@ newTalent{
 		table.sort(tgts, "sqdist")
 		for i, target in ipairs(tgts) do
 			target.actor:pull(self.x, self.y, tg.radius)
-			game.logSeen(target.actor, "%s is pulled by %s!", target.actor.name:capitalize(), self.name)
+			self:logCombat(target.actor, "#Target# is pulled toward #Source#!")
 			DamageType:get(DamageType.ARCANE).projector(self, target.actor.x, target.actor.y, DamageType.ARCANE, t.getDamage(self, t))
 		end
 		return true
diff --git a/game/modules/tome/data/talents/spells/golemancy.lua b/game/modules/tome/data/talents/spells/golemancy.lua
index 9a6b02f9354b123c3397e756c67a73650c103442..f59c4957b85fe05c87f0c65198802644ccc4a6fc 100644
--- a/game/modules/tome/data/talents/spells/golemancy.lua
+++ b/game/modules/tome/data/talents/spells/golemancy.lua
@@ -431,7 +431,7 @@ newTalent{
 				local _, _, tgt = e:getTarget()
 				if e:reactionToward(self) < 0 and tgt == self and rng.percent(chance) then
 					e:setTarget(golem)
-					game.logSeen(e, "%s focuses on %s.", e.name:capitalize(), golem.name)
+					golem:logCombat(e, "#Target# focuses on #Source#.")
 				end
 			end
 		end
diff --git a/game/modules/tome/data/talents/techniques/dualweapon.lua b/game/modules/tome/data/talents/techniques/dualweapon.lua
index 646ffea61ac91d681b71c5bda096e18c9431d4b1..609a22b5961a151d0a6cf76ffe8346f59b0bb76d 100644
--- a/game/modules/tome/data/talents/techniques/dualweapon.lua
+++ b/game/modules/tome/data/talents/techniques/dualweapon.lua
@@ -95,7 +95,7 @@ newTalent{
 	cooldown = 10,
 	sustain_stamina = 20,
 	tactical = { BUFF = 2 },
-	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require a two weapons to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end,
 	getApr = function(self, t) return self:combatScale(self:getTalentLevel(t) * self:getDex(), 4, 0, 25, 500, 0.75) end,
 	activate = function(self, t)
 		local weapon, offweapon = self:hasDualWeapon()
@@ -164,7 +164,7 @@ newTalent{
 	require = techs_dex_req1,
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 1 }, DISABLE = { stun = 2 } },
-	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require a two weapons to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end,
 	getStunDuration = function(self, t) return math.floor(self:combatTalentScale(t, 3, 7)) end,
 	action = function(self, t)
 		local weapon, offweapon = self:hasDualWeapon()
@@ -212,7 +212,7 @@ newTalent{
 	require = techs_dex_req2,
 	requires_target = true,
 	tactical = { ATTACK = { weapon = 4 } },
-	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require a two weapons to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end,
 	action = function(self, t)
 		local weapon, offweapon = self:hasDualWeapon()
 		if not weapon then
@@ -245,7 +245,7 @@ newTalent{
 	require = techs_dex_req3,
 	requires_target = true,
 	tactical = { ATTACKAREA = { weapon = 1, cut = 1 } },
-	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require a two weapons to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end,
 	cutdur = function(self,t) return math.floor(self:combatTalentScale(t, 4, 8)) end,
 	cutPower = function(self, t)
 		local main, off = self:hasDualWeapon()
@@ -314,7 +314,7 @@ newTalent{
 	target = function(self, t)
 		return {type="ball", radius=self:getTalentRadius(t), range=self:getTalentRange(t)}
 	end,
-	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require a two weapons to use this talent.") end return false end return true end,
+	on_pre_use = function(self, t, silent) if not self:hasDualWeapon() then if not silent then game.logPlayer(self, "You require two weapons to use this talent.") end return false end return true end,
 	action = function(self, t)
 		local weapon, offweapon = self:hasDualWeapon()
 		if not weapon then
diff --git a/game/modules/tome/data/talents/techniques/excellence.lua b/game/modules/tome/data/talents/techniques/excellence.lua
index d7cc9b2e7e5f6a9717a562ee320d34ce5817f913..f76e5f51515daeabc01dc2f772c4cf72d8b76c2f 100644
--- a/game/modules/tome/data/talents/techniques/excellence.lua
+++ b/game/modules/tome/data/talents/techniques/excellence.lua
@@ -44,7 +44,7 @@ newTalent{
 				proj:terminate(x, y)
 				game.level:removeEntity(proj, true)
 				proj.dead = true
-				game.logSeen(self, "%s takes down '%s'.", self.name:capitalize(), proj.name)
+				self:logCombat(proj, "#Source# shoots down '#Target#'.")
 			end
 		end
 		
diff --git a/game/modules/tome/data/talents/techniques/unarmed-discipline.lua b/game/modules/tome/data/talents/techniques/unarmed-discipline.lua
index d7903d1e915344330a1ed477535a31ff754526ef..02c2fbf9b65f2858c7d669f8561483862c29547c 100644
--- a/game/modules/tome/data/talents/techniques/unarmed-discipline.lua
+++ b/game/modules/tome/data/talents/techniques/unarmed-discipline.lua
@@ -34,8 +34,7 @@ newTalent{
 		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
 
-		local hit = target:checkHit(self:combatAttack(), target:combatDefense(), 0, 95) -- Deprecated checkHit call
-
+		local hit = target:checkHit(self:combatAttack(), target:combatDefense(), 0, 95) and self:checkEvasion(target)
 		-- Try to knockback !
 		if hit then
 			local can = function(target)
@@ -57,7 +56,7 @@ newTalent{
 			self:buildCombo()
 
 		else
-			game.logSeen(target, "%s misses %s.", self.name:capitalize(), target.name:capitalize())
+			self:logCombat(target, "#Source# misses #Target#.")
 		end
 
 		return true
@@ -92,26 +91,27 @@ newTalent{
 	do_throw = function(self, target, t)
 		local ef = self:hasEffect(self.EFF_DEFENSIVE_GRAPPLING)
 		if not ef or not rng.percent(self.tempeffect_def.EFF_DEFENSIVE_GRAPPLING.throwchance(self, ef)) then return end
-		local hit = self:checkHit(self:combatAttack(), target:combatDefense(), 0, 95) -- Deprecated checkHit call removed
+		local grappled = target:isGrappled(self)
+		local hit = self:checkHit(self:combatAttack(), target:combatDefense(), 0, 95) and (grappled or not self:checkEvasion(target)) -- grappled target can't evade
 		ef.throws = ef.throws - 1
 		if ef.throws <= 0 then self:removeEffect(self.EFF_DEFENSIVE_GRAPPLING) end
 		
 		if hit then
 			self:project(target, target.x, target.y, DamageType.PHYSICAL, self:physicalCrit(t.getDamageTwo(self, t), nil, target, self:combatAttack(), target:combatDefense()))
 			-- if grappled stun
-			if target:isGrappled(self) and target:canBe("stun") then
+			if grappled and target:canBe("stun") then
 				target:setEffect(target.EFF_STUNNED, 2, {apply_power=self:combatAttack(), min_dur=1})
-				game.logSeen(target, "%s has been slammed into the ground!", target.name:capitalize())
+				self:logCombat(target, "#Source# slams #Target# into the ground!")
 			-- if not grappled daze
 			else
-				game.logSeen(target, "%s has been thrown to the ground!", target.name:capitalize())
+				self:logCombat(target, "#Source# throws #Target# to the ground!")
 				-- see if the throw dazes the enemy
 				if target:canBe("stun") then
 					target:setEffect(target.EFF_DAZED, 2, {apply_power=self:combatAttack(), min_dur=1})
 				end
 			end
 		else
-			game.logSeen(target, "%s misses a defensive throw against %s!", self.name:capitalize(),target.name:capitalize())
+			self:logCombat(target, "#Source# misses a defensive throw against #Target#!", self.name:capitalize(),target.name:capitalize())
 		end
 	end,
 	on_unlearn = function(self, t)
diff --git a/game/modules/tome/data/talents/uber/wil.lua b/game/modules/tome/data/talents/uber/wil.lua
index 3bc54a9020dcca1828d8265e3753c2fe2febf8db..d63f306e61adbca61cb00a5031c916c247c62067 100644
--- a/game/modules/tome/data/talents/uber/wil.lua
+++ b/game/modules/tome/data/talents/uber/wil.lua
@@ -210,7 +210,7 @@ uberTalent{
 	require = { special={desc="Antimagic", fct=function(self) return self:knowTalentType("wild-gift/antimagic") end} },
 	trigger = function(self, t, target, source_t)
 		self:startTalentCooldown(t)
-		game.logSeen(self, "#LIGHT_BLUE#%s punishes %s for casting a spell!", self.name:capitalize(), target.name)
+		self:logCombat(target, "#LIGHT_BLUE##Source# punishes #Target# for casting a spell!", self.name:capitalize(), target.name)
 		DamageType:get(DamageType.MIND).projector(self, target.x, target.y, DamageType.MIND, 20 + self:getWil() * 2)
 
 		local dur = target:getTalentCooldown(source_t)
@@ -220,9 +220,10 @@ uberTalent{
 		return true
 	end,
 	info = function(self, t)
-		return ([[Your will is a shield against the assault of crazed arcane users.
+		return ([[Your will is a shield against assaults from crazed arcane users.
 		Each time that you take damage from a spell, you punish the spellcaster with %0.2f mind damage.
-		Also, they will suffer a 35%% spell failure chance for the cooldown duration of the spell they used on you.]])
+		Also, they will suffer a 35%% spell failure chance (with duration equal to the cooldown of the spell they used on you).
+		Note: this talent has a cooldown.]])
 		:format(damDesc(self, DamageType.MIND, 20 + self:getWil() * 2))
 	end,
 }
diff --git a/game/modules/tome/data/talents/undeads/ghoul.lua b/game/modules/tome/data/talents/undeads/ghoul.lua
index 0888a1feffcca9a452d567414b7722daa9044620..75487928dd37df60f5bbd5d07a5a4ae1b42f48a4 100644
--- a/game/modules/tome/data/talents/undeads/ghoul.lua
+++ b/game/modules/tome/data/talents/undeads/ghoul.lua
@@ -155,7 +155,7 @@ newTalent{
 		game.zone:addEntity(game.level, m, "actor", target.x, target.y)
 		game.level.map:particleEmitter(target.x, target.y, 1, "slime")
 		game:playSoundNear(target, "talents/slime")
-		game.logPlayer(game.player, "A #DARK_GREEN#ghoul#LAST# rises from the corpse of %s.", target.name)
+		m:logCombat(target, "A #GREY##Source##LAST# rises from the corpse of #Target#.")
 	end,
 	action = function(self, t)
 		local tg = {type="hit", range=self:getTalentRange(t)}
diff --git a/game/modules/tome/data/timed_effects.lua b/game/modules/tome/data/timed_effects.lua
index dbd92ff2645b9864c237342163ccccabb7304219..eb3ce93f72af33206e239b19b782e9b9b046d221 100644
--- a/game/modules/tome/data/timed_effects.lua
+++ b/game/modules/tome/data/timed_effects.lua
@@ -33,6 +33,24 @@ local Chat = require "engine.Chat"
 local Map = require "engine.Map"
 local Level = require "engine.Level"
 
+local resolveSource = function(self)
+	if self.src then
+		return self.src:resolveSource()
+	else
+		return self
+	end
+end
+
+--gets the full name of the effect
+local getName = function(self)
+	local name = self.effect_id and mod.class.Actor.tempeffect_def[self.effect_id].desc or "effect"
+	if self.src and self.src.name then
+		return name .." from "..self.src.name:capitalize()
+	else
+		return name
+	end
+end
+
 local oldNewEffect = TemporaryEffects.newEffect
 TemporaryEffects.newEffect = function(self, t)
 	if not t.image then
@@ -41,7 +59,8 @@ TemporaryEffects.newEffect = function(self, t)
 	if fs.exists("/data/gfx/"..t.image) then t.display_entity = Entity.new{image=t.image, is_effect=true}
 	else t.display_entity = Entity.new{image="effects/default.png", is_effect=true} print("===", t.type, t.name)
 	end
-
+	t.getName = getName
+	t.resolveSource = resolveSource
 	return oldNewEffect(self, t)
 end
 
diff --git a/game/modules/tome/data/timed_effects/magical.lua b/game/modules/tome/data/timed_effects/magical.lua
index a56fbe7c2e287531311006b4c460053004ece8bb..10c52e287880d1144f40b13718b6caedbdc0db05 100644
--- a/game/modules/tome/data/timed_effects/magical.lua
+++ b/game/modules/tome/data/timed_effects/magical.lua
@@ -1939,7 +1939,6 @@ newEffect{
 
 		local spot = rng.table(eff.list)
 		if not spot or not spot.x then return end
-
 		self:project({type="ball", x=spot.x, y=spot.y, radius=2, selffire=self:spellFriendlyFire()}, spot.x, spot.y, DamageType.ARCANE, eff.dam)
 		game.level.map:particleEmitter(spot.x, spot.y, 2, "generic_sploom", {rm=150, rM=180, gm=20, gM=60, bm=180, bM=200, am=80, aM=150, radius=2, basenb=120})
 
diff --git a/game/modules/tome/data/timed_effects/mental.lua b/game/modules/tome/data/timed_effects/mental.lua
index 18a6a3ec9fb2d7b389b06437e5c43ea549cc5478..09481017be769480731bf283b72ad07294ba7142 100644
--- a/game/modules/tome/data/timed_effects/mental.lua
+++ b/game/modules/tome/data/timed_effects/mental.lua
@@ -359,10 +359,10 @@ newEffect{
 	status = "beneficial",
 	parameters = {},
 	activate = function(self, eff)
-		game.logSeen(self, "#F53CBE#%s is being stalked by %s!", eff.target.name:capitalize(), self.name)
+		self:logCombat(eff.target, "#F53CBE##Target# is being stalked by #Source#!")
 	end,
 	deactivate = function(self, eff)
-		game.logSeen(self, "#F53CBE#%s is no longer being stalked by %s.", eff.target.name:capitalize(), self.name)
+		self:logCombat(eff.target, "#F53CBE##Target# is no longer being stalked by #Source#.")
 	end,
 	on_timeout = function(self, eff)
 		if not eff.target or eff.target.dead or not eff.target:hasEffect(eff.target.EFF_STALKED) then
@@ -1047,7 +1047,7 @@ newEffect{
 				if (self.x ~= x or self.y ~= y) then
 					local target = game.level.map(x, y, Map.ACTOR)
 					if target then
-						game.logSeen(self, "#F53CBE#%s attacks %s in a fit of paranoia.", self.name:capitalize(), target.name)
+						self:logCombat(target, "#F53CBE##Source# attacks #Target# in a fit of paranoia.")
 						if self:attackTarget(target, nil, 1, false) and target ~= eff.source then
 							if not target:canBe("fear") then
 								game.logSeen(target, "#F53CBE#%s ignores the fear!", target.name:capitalize())
@@ -1327,7 +1327,7 @@ newEffect{
 						self:move(bestX, bestY, false)
 						game.logPlayer(self, "#F53CBE#You panic and flee from %s.", eff.source.name)
 					else
-						game.logSeen(self, "#F53CBE#%s panics and tries to flee from %s.", self.name:capitalize(), eff.source.name)
+						self:logCombat(eff.source, "#F53CBE##Source# panics but fails to flee from #Target#.")
 						self:useEnergy(game.energy_to_act * self:combatMovementSpeed(bestX, bestY))
 					end
 				end
@@ -1908,7 +1908,7 @@ newEffect{
 		if self.life <= 0 then
 			self.life = 1
 			self:setEffect(self.EFF_STUNNED, 3, {})
-			game.logSeen(self, "%s's increased life wears off and is stunned by the change.", self.name:capitalize())
+			game.logSeen(self, "%s's increased life fades, leaving it stunned by the loss.", self.name:capitalize())
 		end
 	end,
 }
@@ -2228,9 +2228,9 @@ newEffect{
 		end
 
 		if not eff.incStatsId then
-			game.logSeen(self, ("%s is mimicking %s. (no gains)."):format(self.name:capitalize(), eff.target.name))
+			self:logCombat(eff.target, "#Source# is mimicking #Target#. (no gains).")
 		else
-			local desc = ("%s is mimicking %s. ("):format(self.name:capitalize(), eff.target.name)
+			local desc = "#Source# is mimicking #Target#. ("
 			local first = true
 			for id, value in pairs(eff.incStats) do
 				if not first then desc = desc..", " end
@@ -2238,7 +2238,7 @@ newEffect{
 				desc = desc..("%+d %s"):format(value, Stats.stats_def[id].name:capitalize())
 			end
 			desc = desc..")"
-			game.logSeen(self, desc)
+			self:logCombat(eff.target, desc)
 		end
 	end,
 	deactivate = function(self, eff)
diff --git a/game/modules/tome/data/timed_effects/other.lua b/game/modules/tome/data/timed_effects/other.lua
index 66a3fe35d600b831f00c3c2c47e72a84195500e8..080d75d9710a3479941d15a34e1d4052004942fc 100644
--- a/game/modules/tome/data/timed_effects/other.lua
+++ b/game/modules/tome/data/timed_effects/other.lua
@@ -883,8 +883,8 @@ newEffect{
 	on_merge = function(self, old_eff, new_eff) return old_eff end,
 	doConspirator = function(self, eff, target)
 		if math.min(eff.unlockLevel, eff.level) >= 3 and self:attr("confused") and target:canBe("confusion") then
-			target:setEffect(target.EFF_CONFUSED, 3, {power=50}) -- Make consistent
-			game.logSeen(self, "#F53CBE#%s spreads confusion to %s.", self.name:capitalize(), target.name)
+			target:setEffect(target.EFF_CONFUSED, 3, {power=50})
+			self:logCombat(target, "#F53CBE##Source# spreads confusion to #Target#.")
 		end
 	end,
 }
@@ -1330,7 +1330,7 @@ newEffect{
 	subtype = { miscellaneous=true },
 	status = "beneficial",
 	parameters = {},
-	activate = function(self, eff) game.logPlayer(self, "#LIGHT_BLUE#You begin reloading.") end,
+	activate = function(self, eff) game.logSeen(self, "#LIGHT_BLUE#%s begins reloading.", self.name:capitalize()) end,
 	deactivate = function(self, eff)
 	end,
 	on_timeout = function(self, eff)
diff --git a/game/modules/tome/data/timed_effects/physical.lua b/game/modules/tome/data/timed_effects/physical.lua
index 04c6172ddb31951a465909778e59fdc81f6f4fe3..85d5e16f48d5c63bdbbee384fc8198a92411da07 100644
--- a/game/modules/tome/data/timed_effects/physical.lua
+++ b/game/modules/tome/data/timed_effects/physical.lua
@@ -585,6 +585,7 @@ newEffect{
 			self.add_displays = { Entity.new{image='npc/iceblock.png', display=' ', display_on_seen=true } }
 			eff.added_display = true
 		end
+		eff.ice = mod.class.Object.new{name = "Iceblock", type = "wall", image='npc/iceblock.png', display = ' '} -- use type Object to facilitate the combat log
 		self:removeAllMOs()
 		game.level.map:updateMap(self.x, self.y)
 
diff --git a/game/modules/tome/data/zones/deep-bellow/npcs.lua b/game/modules/tome/data/zones/deep-bellow/npcs.lua
index 038c1563a5933a674bba97db624aff9c1e978e5b..9934f9f478a027519c9ecce817dd02a073a1994b 100644
--- a/game/modules/tome/data/zones/deep-bellow/npcs.lua
+++ b/game/modules/tome/data/zones/deep-bellow/npcs.lua
@@ -115,13 +115,13 @@ It seems to come from the digestive system of the mouth.]],
 
 		if self.summoner.dead then
 			self:die()
-			game.logSeen(self, "#AQUAMARINE#With the Mouth death its crawler also falls lifeless on the ground!")
+			game.logSeen(self, "#AQUAMARINE#With the Mouth's death its crawler also falls lifeless on the ground!")
 		end
 	end,
 
 	on_die = function(self, who)
 		if self.summoner and not self.summoner.dead then
-			game.logSeen(self, "#AQUAMARINE#As %s falls you notice that %s seems to shudder in pain!", self.name, self.summoner.name)
+			self:logCombat(self.summoner, "#AQUAMARINE#As #Source# falls you notice that #Target# seems to shudder in pain!")
 			self.summoner.no_take_hit_achievements = true
 			self.summoner:takeHit(1000, who)
 			self.summoner.no_take_hit_achievements = nil
diff --git a/game/modules/tome/data/zones/demon-plane-spell/grids.lua b/game/modules/tome/data/zones/demon-plane-spell/grids.lua
index e3694b5a4411ddbb200bb1724b862957fb06f286..f800e740f0a6c3de3e1d69c7a38a087a969beb49 100644
--- a/game/modules/tome/data/zones/demon-plane-spell/grids.lua
+++ b/game/modules/tome/data/zones/demon-plane-spell/grids.lua
@@ -24,8 +24,9 @@ load("/data/general/grids/lava.lua", function(e) if e.define_as == "LAVA_FLOOR"
 		local DT = engine.DamageType
 		local dam = DT:get(DT.DEMONFIRE).projector(game.level.plane_owner, x, y, DT.DEMONFIRE, game.level.demonfire_dam or 1)
 		if dam then
-			if dam > 0 then game.logPlayer(who, "The lava burns you!")
-			elseif dam < 0 then game.logPlayer(who, "The lava heals you!") end
+			self.x, self.y = x, y
+			if dam > 0 then self:logCombat(who, "#Source# burns #Target#!")
+			elseif dam < 0 then self:logCombat(who, "#Source# heals #Target#!") end
 		end
 	end
 end end)
diff --git a/game/modules/tome/load.lua b/game/modules/tome/load.lua
index a80f87115b22513aad499ebb47e6c93be8e55b6e..245582d49b2880666e8e332821e038ca10de04cc 100644
--- a/game/modules/tome/load.lua
+++ b/game/modules/tome/load.lua
@@ -30,6 +30,7 @@ local KeyBind = require "engine.KeyBind"
 local DamageType = require "engine.DamageType"
 local Faction = require "engine.Faction"
 local Map = require "engine.Map"
+local MapEffects = require "mod.class.MapEffects" -- This alters Map
 local Level = require "engine.Level"
 local Tiles = require "engine.Tiles"
 local InventoryUI = require "engine.ui.Inventory"