Invisible NPCs

Hi people, I’m working on a stealth mod, and everything seems to be working fine overall. NPCs stay neutral to the player, until he’s detected and the enemies start chasing him. However, sometimes when an enemy spots a player, the npc becomes completely invisible, and if he dies, the game crash. Its weird, because my mod doesn’t manipulate NPC models in any way, it just messes with relationships. Here’s some code:

This is the main “AI” timer. Its probably too much stuff for a timer running every .2 seconds, but nobody has reported FPS drops yet.



timer.Create("NPCStealthThink",.2,0,function() --Keep checking what npcs are alert to
	if GetConVarNumber("stealth_enabled") == 0 then return end
	-- Check Player LOS
	for k = #npctable, 1, -1 do
		local v = npctable[k]
		if IsValid(v) then
			-- This only runs once, it sets initial position
			if v.initpos == nil then v.initpos = v:GetPos() end
			if v.initangles == nil then v.initangles = v:GetAngles() end
			-- checkLOS is a long function that doesn't writes anything
			-- It just does calculations and then executes alert() or investigate()
			for o,p in pairs(player.GetAll()) do
				checkLOS(p,v)
			end
			-- Check corpse LOS
			-- This makes them move to investigate corpses
			if GetConVarNumber("stealth_enabled") != 0 then
				local nearestcorpse = nil
				local nearestdistance = -1
				for o = #corpsetable, 1, -1 do
					local p = corpsetable[o]
					if IsValid(p) then
						if !table.HasValue(v.seencorpses,p) then
							local dist = checkEntityLOS(p,v)
							if dist != -1 then
								table.insert(v.seencorpses,p)
								if nearestdistance == -1 or dist < nearestdistance then
									nearestdistance = dist
									nearestcorpse = p
								end
							end
						end
					else
						table.remove(corpsetable,o)
					end
				end
				if IsValid(nearestcorpse) then investigate(v, nearestcorpse:GetPos(), true, false) end
			end
			-- Remove null enemies from npc memory
			for o = #v.MEnemies, 1, -1 do
				local p = v.MEnemies[o]
				if !IsValid(p) then table.remove(v.MEnemies,o) end
			end
		else
			table.remove(npctable,k)
			-- Send Clients the signal to remove NPC
			umsg.Start( "RemoveNPCfromTable" )
				umsg.Entity( v )
			umsg.End();	
		end
	end
	
	-- More stuff that really doesn't matter


This function is executed when an enemy is alerted:



local function alert(ply, npc, silent)
	if not npc.MEnemies or table.HasValue(npc.MEnemies, ply) then return end
	if !ply:Alive() then return end
	table.insert(npc.MEnemies,ply)
	npc:AddEntityRelationship(ply, D_HT, 0)
	npc.investigating = 0
	npc.targetpos = nil
	npc.running = false
	
		
	-- Tell player that he alerted someone
		umsg.Start( "NPCAlerted", ply )
			umsg.Entity( npc )
			umsg.Bool( silent )
		umsg.End();	
	-- Broadcast alert effect
		umsg.Start( "NPCEffect" )
			umsg.Entity( npc )
			umsg.String( "alert" )
		umsg.End();	
	-- When an enemy spots the player, he alerts nearby enemies after a few seconds.
	-- When a nearby enemy is alerted, "silent" is true, and it wont alert nearby enemies again
	if silent == false then
		timer.Simple(GetConVarNumber("stealth_backuptime"), function() 
			if IsValid(npc) and IsValid(ply) then
				for k, v in pairs(npctable) do
					if IsValid(v) and v != npc and npc:GetPos():Distance(v:GetPos()) <= GetConVarNumber("stealth_backuprange") then alert(ply, v, true) end
				end
			end
		end) 
	end
end


Finally, this is the function that tells enemies to investigate something.



local function investigate(npc, pos, run, nearest)
	-- Ignore the "nearest" parameter, is old
	if #npc.MEnemies != 0 then return end
	if npc.invdelay > CurTime() then return end
	npc.invdelay = CurTime() + 3
	if npc.investigating == 0 then
		-- Broadcast alert effect
		umsg.Start( "NPCEffect" )
			umsg.Entity( npc )
			umsg.String( "caution" )
		umsg.End();
	end
	npc.investigating = 1
	--if nearest == false or (nearest == true and (npc.targetpos == nil or npc:GetPos():Distance(npc.targetpos) >= npc:GetPos():Distance(pos))) then
	npc.targetpos = pos
	npc:SetLastPosition(pos)
	if run == false and npc.running == false then
		npc:SetSchedule( SCHED_FORCED_GO )
	else
		npc:SetSchedule( SCHED_FORCED_GO_RUN )
		npc.running = true
	end
	--end
end


Any idea of what could be wrong?

EDIT: It happens quite often. Once one enemy spots the player, any other enemy that detects the player from a certain distance, has a high chance of becoming invisible. They reappear randomly for a second and hide again. I don’t know why is this happening.

[editline]10th February 2016[/editline]

Nevermind, I solved it myself. I removed this:


npc:SetTarget(npc)
npc:SetEnemy(npc)

I have no idea why this was causing that issue, but it’s fixed now.