Hiding particle effects of invisible/stealthed creatures
Posted: Mon Feb 28, 2011 5:40 pm
The problem: particle effects give away the position of things that the player cannot see.
The setting: boss level of the necromancer pride. I enter the level in LOS of the boss, fight him for a few turns and then teleport away. I am in the process of clearing out the corner I landed into when I see some bone shield particle effects turn the corner at my back. I was expecting the boss to find me soon, but I will admit the warning the particle effects gave me severly undercut the tension.
So I started digging to understand how all of this work. It looks like the NPC's tile goes invisible in the engine.Map:updateMap call:
Alright, easy enough... the mo or map object of the actor is either added or not added to that tile's list of map objects to display. Particles are separately displayed in engine.Map:displayParticles:
My understanding of particles is fairly low, but my first read through says there is a concept of a particle effect being alive or dead, and that the e.ps:toScreen call is what adds the particle effect to the map. Not sure if it move the effect or if a toScreen call is required even if a particle's source didn't move... I think the latter.
How then are particles attached to actors handled? Well they are added to the actor's __particles table, which is then used by engine.Actor:move:
So when the actor moves the particle's position is updated. I think the fix would be to have the "e" particle object carry around a source_actor variable, and then in displayParticles check if the player can see the source_actor and if not do not display the particle effect. I don't have a lot of time to code and test it now, though, so getting it down for others to look at.
The setting: boss level of the necromancer pride. I enter the level in LOS of the boss, fight him for a few turns and then teleport away. I am in the process of clearing out the corner I landed into when I see some bone shield particle effects turn the corner at my back. I was expecting the boss to find me soon, but I will admit the warning the particle effects gave me severly undercut the tension.
So I started digging to understand how all of this work. It looks like the NPC's tile goes invisible in the engine.Map:updateMap call:
Code: Select all
if a then
-- Handles invisibility and telepathy and other such things
if not self.actor_player or self.actor_player:canSee(a) then
a:getMapObjects(self.tiles, mos, 10)
a:setupMinimapInfo(a._mo, self)
end
end
...
-- Cache the map objects in the C map
self._map:setGrid(x, y, mos)
Code: Select all
function _M:displayParticles(nb_keyframes)
...
if nb_keyframes == 0 and e.x and e.y then
-- Just display it, not updating, no emitting
if e.x + e.radius >= self.mx and e.x - e.radius < self.mx + self.viewport.mwidth and e.y + e.radius >= self.my and e.y - e.radius < self.my + self.viewport.mheight then
e.ps:toScreen(self.display_x + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, self.display_y + (ady + e.y - self.my + 0.5) * self.tile_h * self.zoom, self.seens(e.x, e.y), e.zoom * self.zoom)
end
elseif e.x and e.y then
alive = e.ps:isAlive()
-- Update more, if needed
if alive and e.x + e.radius >= self.mx and e.x - e.radius < self.mx + self.viewport.mwidth and e.y + e.radius >= self.my and e.y - e.radius < self.my + self.viewport.mheight then
e.ps:toScreen(self.display_x + (adx + e.x - self.mx + 0.5) * self.tile_w * self.zoom, self.display_y + (ady + e.y - self.my + 0.5) * self.tile_h * self.zoom, self.seens(e.x, e.y))
end
if not alive then
del[#del+1] = e
e.dead = true
end
else
del[#del+1] = e
e.dead = true
end
...
How then are particles attached to actors handled? Well they are added to the actor's __particles table, which is then used by engine.Actor:move:
Code: Select all
-- Update particle emitters attached to that actor
local del = {}
for e, _ in pairs(self.__particles) do
if e.dead then del[#del+1] = e
else
e.x = x
e.y = y
map.particles[e] = true
-- Give it our main _mo for display coords
e._mo = self._mo
end
end
for i = 1, #del do self.__particles[del[i]] = nil end