--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]


include( "prop_tools.lua" )

-- A little hacky function to help prevent spawning props partially inside walls
-- Maybe it should use physics object bounds, not OBB, and use physics object bounds to initial position too
local function fixupProp( ply, ent, hitpos, mins, maxs )
	local entPos = ent:GetPos()
	local endposD = ent:LocalToWorld( mins )
	local tr_down = util.TraceLine( {
		start = entPos,
		endpos = endposD,
		filter = { ent, ply }
	} )

	local endposU = ent:LocalToWorld( maxs )
	local tr_up = util.TraceLine( {
		start = entPos,
		endpos = endposU,
		filter = { ent, ply }
	} )

	-- Both traces hit meaning we are probably inside a wall on both sides, do nothing
	if ( tr_up.Hit && tr_down.Hit ) then return end

	if ( tr_down.Hit ) then ent:SetPos( entPos + ( tr_down.HitPos - endposD ) ) end
	if ( tr_up.Hit ) then ent:SetPos( entPos + ( tr_up.HitPos - endposU ) ) end
end

local function TryFixPropPosition( ply, ent, hitpos )
	fixupProp( ply, ent, hitpos, Vector( ent:OBBMins().x, 0, 0 ), Vector( ent:OBBMaxs().x, 0, 0 ) )
	fixupProp( ply, ent, hitpos, Vector( 0, ent:OBBMins().y, 0 ), Vector( 0, ent:OBBMaxs().y, 0 ) )
	fixupProp( ply, ent, hitpos, Vector( 0, 0, ent:OBBMins().z ), Vector( 0, 0, ent:OBBMaxs().z ) )
end

--[[---------------------------------------------------------
	Name: CCSpawn
	Desc: Console Command for a player to spawn different items
-----------------------------------------------------------]]
function CCSpawn( ply, command, arguments )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( arguments[ 1 ] == nil ) then return end
	if ( arguments[ 1 ]:find( "%.[/\\]" ) ) then return end

	-- Clean up the path from attempted blacklist bypasses
	arguments[ 1 ] = arguments[ 1 ]:gsub( "\\\\+", "/" )
	arguments[ 1 ] = arguments[ 1 ]:gsub( "//+", "/" )
	arguments[ 1 ] = arguments[ 1 ]:gsub( "\\/+", "/" )
	arguments[ 1 ] = arguments[ 1 ]:gsub( "/\\+", "/" )

	if ( !gamemode.Call( "PlayerSpawnObject", ply, arguments[ 1 ], arguments[ 2 ] ) ) then return end
	if ( !util.IsValidModel( arguments[ 1 ] ) ) then return end

	local iSkin = tonumber( arguments[ 2 ] ) or 0
	local strBody = arguments[ 3 ] or nil

	if ( util.IsValidProp( arguments[ 1 ] ) ) then

		GMODSpawnProp( ply, arguments[ 1 ], iSkin, strBody )
		return

	end

	if ( util.IsValidRagdoll( arguments[ 1 ] ) ) then

		GMODSpawnRagdoll( ply, arguments[ 1 ], iSkin, strBody )
		return

	end

	-- Not a ragdoll or prop.. must be an 'effect' - spawn it as one
	GMODSpawnEffect( ply, arguments[ 1 ], iSkin, strBody )

end
concommand.Add( "gm_spawn", CCSpawn, nil, "Spawns props/ragdolls" )

local function MakeRagdoll( ply, _, _, model, _, Data )

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnRagdoll", ply, model ) ) then return end

	local Ent = ents.Create( "prop_ragdoll" )
	duplicator.DoGeneric( Ent, Data )
	Ent:Spawn()

	duplicator.DoGenericPhysics( Ent, ply, Data )

	Ent:Activate()

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedRagdoll", ply, model, Ent )
	end

	DoPropSpawnedEffect( Ent )

	return Ent

end

-- Register the "prop_ragdoll" class with the duplicator, (Args in brackets will be retreived for every bone)
duplicator.RegisterEntityClass( "prop_ragdoll", MakeRagdoll, "Pos", "Ang", "Model", "PhysicsObjects", "Data" )

--[[---------------------------------------------------------
	Name: GMODSpawnRagdoll - player spawns a ragdoll
-----------------------------------------------------------]]
function GMODSpawnRagdoll( ply, model, iSkin, strBody )

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnRagdoll", ply, model ) ) then return end
	local e = DoPlayerEntitySpawn( ply, "prop_ragdoll", model, iSkin, strBody )

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedRagdoll", ply, model, e )
	end

	DoPropSpawnedEffect( e )

	undo.Create( "Ragdoll" )
		undo.SetPlayer( ply )
		undo.AddEntity( e )
	undo.Finish( "Ragdoll (" .. tostring( model ) .. ")" )

	ply:AddCleanup( "ragdolls", e )

end

function MakeProp( ply, Pos, Ang, model, _, Data )

	-- Uck.
	Data.Pos = Pos
	Data.Angle = Ang
	Data.Model = model

	-- Make sure this is allowed
	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnProp", ply, model ) ) then return end

	local Prop = ents.Create( "prop_physics" )
	duplicator.DoGeneric( Prop, Data )
	Prop:Spawn()

	duplicator.DoGenericPhysics( Prop, ply, Data )

	-- Tell the gamemode we just spawned something
	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedProp", ply, model, Prop )
	end

	FixInvalidPhysicsObject( Prop )

	DoPropSpawnedEffect( Prop )

	return Prop

end

duplicator.RegisterEntityClass( "prop_physics", MakeProp, "Pos", "Ang", "Model", "PhysicsObjects", "Data" )
duplicator.RegisterEntityClass( "prop_physics_multiplayer", MakeProp, "Pos", "Ang", "Model", "PhysicsObjects", "Data" )

function MakeEffect( ply, model, Data )

	Data.Model = model

	-- Make sure this is allowed
	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnEffect", ply, model ) ) then return end

	local Prop = ents.Create( "prop_effect" )
	duplicator.DoGeneric( Prop, Data )
	if ( Data.AttachedEntityInfo ) then
		Prop.AttachedEntityInfo = table.Copy( Data.AttachedEntityInfo ) -- This shouldn't be neccesary
	end
	Prop:Spawn()

	-- duplicator.DoGenericPhysics( Prop, ply, Data )

	-- Tell the gamemode we just spawned something
	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedEffect", ply, model, Prop )
	end

	if ( IsValid( Prop.AttachedEntity ) ) then
		DoPropSpawnedEffect( Prop.AttachedEntity )
	end

	return Prop

end

duplicator.RegisterEntityClass( "prop_effect", MakeEffect, "Model", "Data" )

--[[---------------------------------------------------------
	Name: FixInvalidPhysicsObject
	Desc: Attempts to detect and correct the physics object
	on models such as the TF2 Turrets
-----------------------------------------------------------]]
function FixInvalidPhysicsObject( prop )

	local PhysObj = prop:GetPhysicsObject()
	if ( !IsValid( PhysObj ) ) then return end

	local min, max = PhysObj:GetAABB()
	if ( !min or !max ) then return end

	local PhysSize = ( min - max ):Length()
	if ( PhysSize > 5 ) then return end

	local mins = prop:OBBMins()
	local maxs = prop:OBBMaxs()
	if ( !mins or !maxs ) then return end

	local ModelSize = ( mins - maxs ):Length()
	local Difference = math.abs( ModelSize - PhysSize )
	if ( Difference < 10 ) then return end

	-- This physics object is definitiely weird.
	-- Make a new one.
	prop:PhysicsInitBox( mins, maxs )
	prop:SetCollisionGroup( COLLISION_GROUP_DEBRIS )

	-- Check for success
	PhysObj = prop:GetPhysicsObject()
	if ( !IsValid( PhysObj ) ) then return end

	PhysObj:SetMass( 100 )
	PhysObj:Wake()

end

--[[---------------------------------------------------------
	Name: GMODSpawnProp - player spawns a prop
-----------------------------------------------------------]]
function GMODSpawnProp( ply, model, iSkin, strBody )

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnProp", ply, model ) ) then return end

	local e = DoPlayerEntitySpawn( ply, "prop_physics", model, iSkin, strBody )
	if ( !IsValid( e ) ) then return end

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedProp", ply, model, e )
	end

	-- This didn't work out - todo: Find a better way.
	--timer.Simple( 0.01, CheckPropSolid, e, COLLISION_GROUP_NONE, COLLISION_GROUP_WORLD )

	FixInvalidPhysicsObject( e )

	DoPropSpawnedEffect( e )

	undo.Create( "Prop" )
		undo.SetPlayer( ply )
		undo.AddEntity( e )
	undo.Finish( "Prop (" .. tostring( model ) .. ")" )

	ply:AddCleanup( "props", e )

end

--[[---------------------------------------------------------
	Name: GMODSpawnEffect
-----------------------------------------------------------]]
function GMODSpawnEffect( ply, model, iSkin, strBody )

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnEffect", ply, model ) ) then return end

	local e = DoPlayerEntitySpawn( ply, "prop_effect", model, iSkin, strBody )
	if ( !IsValid( e ) ) then return end

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedEffect", ply, model, e )
	end

	if ( IsValid( e.AttachedEntity ) ) then
		DoPropSpawnedEffect( e.AttachedEntity )
	end

	undo.Create( "Effect" )
		undo.SetPlayer( ply )
		undo.AddEntity( e )
	undo.Finish( "Effect (" .. tostring( model ) .. ")" )

	ply:AddCleanup( "effects", e )

end

--[[---------------------------------------------------------
	Name: DoPlayerEntitySpawn
	Desc: Utility function for player entity spawning functions
-----------------------------------------------------------]]
function DoPlayerEntitySpawn( ply, entity_name, model, iSkin, strBody )

	local vStart = ply:GetShootPos()
	local vForward = ply:GetAimVector()

	local trace = {}
	trace.start = vStart
	trace.endpos = vStart + ( vForward * 2048 )
	trace.filter = ply

	local tr = util.TraceLine( trace )

	-- Prevent spawning too close
	--[[if ( !tr.Hit or tr.Fraction < 0.05 ) then
		return
	end]]

	local ent = ents.Create( entity_name )
	if ( !IsValid( ent ) ) then return end

	local ang = ply:EyeAngles()
	ang.yaw = ang.yaw + 180 -- Rotate it 180 degrees in my favour
	ang.roll = 0
	ang.pitch = 0

	if ( entity_name == "prop_ragdoll" ) then
		ang.pitch = -90
		tr.HitPos = tr.HitPos
	end

	ent:SetModel( model )
	ent:SetSkin( iSkin )
	ent:SetAngles( ang )
	if ( strBody ) then ent:SetBodyGroups( strBody ) end
	ent:SetPos( tr.HitPos )
	ent:Spawn()
	ent:Activate()

	-- Special case for effects
	if ( strBody && entity_name == "prop_effect" && IsValid( ent.AttachedEntity ) ) then
		ent.AttachedEntity:SetBodyGroups( strBody )
	end

	-- Attempt to move the object so it sits flush
	-- We could do a TraceEntity instead of doing all
	-- of this - but it feels off after the old way
	local vFlushPoint = tr.HitPos - ( tr.HitNormal * 512 )	-- Find a point that is definitely out of the object in the direction of the floor
	vFlushPoint = ent:NearestPoint( vFlushPoint )			-- Find the nearest point inside the object to that point
	vFlushPoint = ent:GetPos() - vFlushPoint				-- Get the difference
	vFlushPoint = tr.HitPos + vFlushPoint					-- Add it to our target pos

	if ( entity_name != "prop_ragdoll" ) then

		-- Set new position
		ent:SetPos( vFlushPoint )
		ply:SendLua( "achievements.SpawnedProp()" )

	else

		-- With ragdolls we need to move each physobject
		local VecOffset = vFlushPoint - ent:GetPos()
		for i = 0, ent:GetPhysicsObjectCount() - 1 do
			local phys = ent:GetPhysicsObjectNum( i )
			phys:SetPos( phys:GetPos() + VecOffset )
		end

		ply:SendLua( "achievements.SpawnedRagdoll()" )

	end

	TryFixPropPosition( ply, ent, tr.HitPos )

	return ent

end

local function InternalSpawnNPC( NPCData, ply, Position, Normal, Class, Equipment, SpawnFlagsSaved, NoDropToFloor )

	-- Don't let them spawn this entity if it isn't in our NPC Spawn list.
	-- We don't want them spawning any entity they like!
	if ( !NPCData ) then return end

	local isAdmin = ( IsValid( ply ) && ply:IsAdmin() ) or game.SinglePlayer()
	if ( NPCData.AdminOnly && !isAdmin ) then return end

	local bDropToFloor = false
	local wasSpawnedOnCeiling = false
	local wasSpawnedOnFloor = false

	--
	-- This NPC has to be spawned on a ceiling (Barnacle) or a floor (Turrets)
	--
	if ( NPCData.OnCeiling or NPCData.OnFloor ) then
		local isOnCeiling	= Vector( 0, 0, -1 ):Dot( Normal ) >= 0.95
		local isOnFloor		= Vector( 0, 0,  1 ):Dot( Normal ) >= 0.95

		-- Not on ceiling, and we can't be on floor
		if ( !isOnCeiling && !NPCData.OnFloor ) then return end

		-- Not on floor, and we can't be on ceiling
		if ( !isOnFloor && !NPCData.OnCeiling ) then return end

		-- We can be on either, and we are on neither
		if ( !isOnFloor && !isOnCeiling ) then return end

		wasSpawnedOnCeiling = isOnCeiling
		wasSpawnedOnFloor = isOnFloor
	else
		bDropToFloor = true
	end

	if ( NPCData.NoDrop or NoDropToFloor ) then bDropToFloor = false end

	-- Create NPC
	local NPC = ents.Create( NPCData.Class )
	if ( !IsValid( NPC ) ) then return end

	--
	-- Offset the position
	--
	local Offset = NPCData.Offset or 32
	NPC:SetPos( Position + Normal * Offset )

	-- Rotate to face player (expected behaviour)
	local Angles = Angle( 0, 0, 0 )

	if ( IsValid( ply ) ) then
		Angles = ply:GetAngles()
	end

	Angles.pitch = 0
	Angles.roll = 0
	Angles.yaw = Angles.yaw + 180

	if ( NPCData.Rotate ) then Angles = Angles + NPCData.Rotate end

	NPC:SetAngles( Angles )

	if ( NPCData.SnapToNormal ) then
		NPC:SetAngles( Normal:Angle() )
	end

	--
	-- Does this NPC have a specified model? If so, use it.
	--
	if ( NPCData.Model ) then
		NPC:SetModel( NPCData.Model )
	end

	--
	-- Does this NPC have a specified material? If so, use it.
	--
	if ( NPCData.Material ) then
		NPC:SetMaterial( NPCData.Material )
	end

	--
	-- Spawn Flags
	--
	local SpawnFlags = bit.bor( SF_NPC_FADE_CORPSE, SF_NPC_ALWAYSTHINK )
	if ( NPCData.SpawnFlags ) then SpawnFlags = bit.bor( SpawnFlags, NPCData.SpawnFlags ) end
	if ( NPCData.TotalSpawnFlags ) then SpawnFlags = NPCData.TotalSpawnFlags end
	if ( SpawnFlagsSaved ) then SpawnFlags = SpawnFlagsSaved end
	NPC:SetKeyValue( "spawnflags", SpawnFlags )
	NPC.SpawnFlags = SpawnFlags

	--
	-- Optional Key Values
	--
	if ( NPCData.KeyValues ) then
		for k, v in pairs( NPCData.KeyValues ) do
			NPC:SetKeyValue( k, v )
		end
	end

	--
	-- Does this NPC have a specified skin? If so, use it.
	--
	if ( NPCData.Skin ) then
		NPC:SetSkin( NPCData.Skin )
	end

	--
	-- What weapon this NPC should be carrying
	--

	-- Check if this is a valid weapon from the list, or the user is trying to fool us.
	local valid = false
	for _, v in pairs( list.Get( "NPCUsableWeapons" ) ) do
		if ( v.class == Equipment ) then valid = true break end
	end
	for _, v in pairs( NPCData.Weapons or {} ) do
		if ( v == Equipment ) then valid = true break end
	end

	if ( Equipment && Equipment != "none" && valid ) then
		NPC:SetKeyValue( "additionalequipment", Equipment )
		NPC.Equipment = Equipment
	end

	if ( wasSpawnedOnCeiling && isfunction( NPCData.OnCeiling ) ) then
		NPCData.OnCeiling( NPC )
	elseif ( wasSpawnedOnFloor && isfunction( NPCData.OnFloor ) ) then
		NPCData.OnFloor( NPC )
	end

	-- Allow special case for duplicator stuff
	if ( isfunction( NPCData.OnDuplicated ) ) then
		NPC.OnDuplicated = NPCData.OnDuplicated
	end

	DoPropSpawnedEffect( NPC )

	NPC:Spawn()
	NPC:Activate()

	-- Store spawnmenu data for addons and stuff
	NPC.NPCName = Class
	NPC.NPCTable = NPCData
	NPC._wasSpawnedOnCeiling = wasSpawnedOnCeiling

	-- For those NPCs that set their model/skin in Spawn function
	-- We have to keep the call above for NPCs that want a model set by Spawn() time
	-- BAD: They may adversly affect entity collision bounds
	if ( NPCData.Model && NPC:GetModel():lower() != NPCData.Model:lower() ) then
		NPC:SetModel( NPCData.Model )
	end
	if ( NPCData.Skin ) then
		NPC:SetSkin( NPCData.Skin )
	end

	if ( bDropToFloor ) then
		NPC:DropToFloor()
	end

	if ( NPCData.Health ) then
		NPC:SetHealth( NPCData.Health )
		NPC:SetMaxHealth( NPCData.Health )
	end

	-- Body groups
	if ( NPCData.BodyGroups ) then
		for k, v in pairs( NPCData.BodyGroups ) do
			NPC:SetBodygroup( k, v )
		end
	end

	return NPC

end

function Spawn_NPC( ply, NPCClassName, WeaponName, tr )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( !NPCClassName ) then return end

	-- Give the gamemode an opportunity to deny spawning
	if ( !gamemode.Call( "PlayerSpawnNPC", ply, NPCClassName, WeaponName ) ) then return end

	if ( !tr ) then

		local vStart = ply:GetShootPos()
		local vForward = ply:GetAimVector()

		tr = util.TraceLine( {
			start = vStart,
			endpos = vStart + ( vForward * 2048 ),
			filter = ply
		} )

	end

	local NPCData = list.Get( "NPC" )[ NPCClassName ]

	-- Create the NPC if you can.
	local SpawnedNPC = InternalSpawnNPC( NPCData, ply, tr.HitPos, tr.HitNormal, NPCClassName, WeaponName )
	if ( !IsValid( SpawnedNPC ) ) then return end

	TryFixPropPosition( ply, SpawnedNPC, tr.HitPos )

	-- Give the gamemode an opportunity to do whatever
	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedNPC", ply, SpawnedNPC )
	end

	-- See if we can find a nice name for this NPC..
	local NiceName = nil
	if ( NPCData ) then
		NiceName = NPCData.Name
	end

	-- Add to undo list
	undo.Create( "NPC" )
		undo.SetPlayer( ply )
		undo.AddEntity( SpawnedNPC )
		if ( NiceName ) then
			undo.SetCustomUndoText( "Undone " .. NiceName )
		end
	undo.Finish( "NPC (" .. tostring( NPCClassName ) .. ")" )

	-- And cleanup
	ply:AddCleanup( "npcs", SpawnedNPC )

	ply:SendLua( "achievements.SpawnedNPC()" )

end
concommand.Add( "gmod_spawnnpc", function( ply, cmd, args ) Spawn_NPC( ply, args[ 1 ], args[ 2 ] ) end )

-- This should be in base_npcs.lua really
local function GenericNPCDuplicator( ply, mdl, class, equipment, spawnflags, data )

	-- Match the behavior of Spawn_NPC above - class should be the one in the list, NOT the entity class!
	if ( data.NPCName ) then class = data.NPCName end

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnNPC", ply, class, equipment ) ) then return end

	local NPCData = list.Get( "NPC" )[ class ]
	-- I don't think we are ready for this
	-- if ( !NPCData ) then NPCData = data.NPCTable end

	local normal = Vector( 0, 0, 1 )
	if ( NPCData && NPCData.OnCeiling && ( NPCData.OnFloor && data._wasSpawnedOnCeiling or !NPCData.OnFloor ) ) then
		normal = Vector( 0, 0, -1 )
	end

	local ent = InternalSpawnNPC( NPCData, ply, data.Pos, normal, class, equipment, spawnflags, true )
	if ( IsValid( ent ) ) then

		local pos = ent:GetPos() -- Hack! Prevents the NPCs from falling through the floor

		duplicator.DoGeneric( ent, data )

		if ( NPCData && !NPCData.OnCeiling && !NPCData.NoDrop ) then
			ent:SetPos( pos )
		end

		if ( IsValid( ply ) ) then
			gamemode.Call( "PlayerSpawnedNPC", ply, ent )
			ply:AddCleanup( "npcs", ent )
		end

		if ( data.CurHealth ) then ent:SetHealth( data.CurHealth ) end
		if ( data.MaxHealth ) then ent:SetMaxHealth( data.MaxHealth ) end

		table.Merge( ent:GetTable(), data )

	end

	return ent

end

-- Huuuuuuuuhhhh
local function AddNPCToDuplicator( class ) duplicator.RegisterEntityClass( class, GenericNPCDuplicator, "Model", "Class", "Equipment", "SpawnFlags", "Data" ) end

-- HL2
AddNPCToDuplicator( "npc_alyx" )
AddNPCToDuplicator( "npc_breen" )
AddNPCToDuplicator( "npc_kleiner" )
AddNPCToDuplicator( "npc_antlion" )
AddNPCToDuplicator( "npc_antlionguard" )
AddNPCToDuplicator( "npc_barnacle" )
AddNPCToDuplicator( "npc_barney" )
AddNPCToDuplicator( "npc_combine_s" )
AddNPCToDuplicator( "npc_crow" )
AddNPCToDuplicator( "npc_cscanner" )
AddNPCToDuplicator( "npc_clawscanner" )
AddNPCToDuplicator( "npc_dog" )
AddNPCToDuplicator( "npc_eli" )
AddNPCToDuplicator( "npc_gman" )
AddNPCToDuplicator( "npc_headcrab" )
AddNPCToDuplicator( "npc_headcrab_black" )
AddNPCToDuplicator( "npc_headcrab_poison" )
AddNPCToDuplicator( "npc_headcrab_fast" )
AddNPCToDuplicator( "npc_manhack" )
AddNPCToDuplicator( "npc_metropolice" )
AddNPCToDuplicator( "npc_monk" )
AddNPCToDuplicator( "npc_mossman" )
AddNPCToDuplicator( "npc_pigeon" )
AddNPCToDuplicator( "npc_rollermine" )
AddNPCToDuplicator( "npc_strider" )
AddNPCToDuplicator( "npc_helicopter" )
AddNPCToDuplicator( "npc_combinegunship" )
AddNPCToDuplicator( "npc_combinedropship" )
AddNPCToDuplicator( "npc_turret_ceiling" )
AddNPCToDuplicator( "npc_combine_camera" )
AddNPCToDuplicator( "npc_turret_floor" )
AddNPCToDuplicator( "npc_vortigaunt" )
AddNPCToDuplicator( "npc_sniper" )
AddNPCToDuplicator( "npc_seagull" )
AddNPCToDuplicator( "npc_citizen" )
AddNPCToDuplicator( "npc_stalker" )
AddNPCToDuplicator( "npc_zombie" )
AddNPCToDuplicator( "npc_zombie_torso" )
AddNPCToDuplicator( "npc_zombine" )
AddNPCToDuplicator( "npc_poisonzombie" )
AddNPCToDuplicator( "npc_fastzombie" )
AddNPCToDuplicator( "npc_fastzombie_torso" )
-- EP2
AddNPCToDuplicator( "npc_hunter" )
AddNPCToDuplicator( "npc_antlion_worker" )
AddNPCToDuplicator( "npc_antlion_grub" )
AddNPCToDuplicator( "npc_magnusson" )

AddNPCToDuplicator( "npc_fisherman" )

-- HL1
AddNPCToDuplicator( "monster_alien_grunt" )
AddNPCToDuplicator( "monster_alien_slave" )
AddNPCToDuplicator( "monster_alien_controller" )
AddNPCToDuplicator( "monster_barney" )
AddNPCToDuplicator( "monster_bigmomma" )
AddNPCToDuplicator( "monster_bullchicken" )
AddNPCToDuplicator( "monster_babycrab" )
AddNPCToDuplicator( "monster_cockroach" )
AddNPCToDuplicator( "monster_houndeye" )
AddNPCToDuplicator( "monster_headcrab" )
AddNPCToDuplicator( "monster_gargantua" )
AddNPCToDuplicator( "monster_human_assassin" )
AddNPCToDuplicator( "monster_human_grunt" )
AddNPCToDuplicator( "monster_scientist" )
AddNPCToDuplicator( "monster_snark" )
AddNPCToDuplicator( "monster_nihilanth" )
AddNPCToDuplicator( "monster_tentacle" )
AddNPCToDuplicator( "monster_zombie" )
AddNPCToDuplicator( "monster_turret" )
AddNPCToDuplicator( "monster_miniturret" )
AddNPCToDuplicator( "monster_sentry" )

-- Portal
AddNPCToDuplicator( "npc_portal_turret_floor" )
AddNPCToDuplicator( "npc_rocket_turret" )
AddNPCToDuplicator( "npc_security_camera" )

--[[---------------------------------------------------------
	Name: CanPlayerSpawnSENT
-----------------------------------------------------------]]
local function CanPlayerSpawnSENT( ply, EntityName )

	local isAdmin = ( IsValid( ply ) && ply:IsAdmin() ) or game.SinglePlayer()

	-- Make sure that given EntityName is actually a SENT
	local sent = scripted_ents.GetStored( EntityName )
	if ( sent == nil ) then

		-- Is the entity spawnable?
		local SpawnableEntities = list.Get( "SpawnableEntities" )
		if ( !SpawnableEntities ) then return false end
		local EntTable = SpawnableEntities[ EntityName ]
		if ( !EntTable ) then return false end
		if ( EntTable.AdminOnly && !isAdmin ) then return false end
		return true

	end

	-- We need a spawn function. The SENT can then spawn itself properly
	local SpawnFunction = scripted_ents.GetMember( EntityName, "SpawnFunction" )
	if ( !isfunction( SpawnFunction ) ) then return false end

	-- You're not allowed to spawn this unless you're an admin!
	if ( !scripted_ents.GetMember( EntityName, "Spawnable" ) && !isAdmin ) then return false end
	if ( scripted_ents.GetMember( EntityName, "AdminOnly" ) && !isAdmin ) then return false end

	return true

end

--[[---------------------------------------------------------
	Name: Spawn_SENT
	Desc: Console Command for a player to spawn different items
-----------------------------------------------------------]]
function Spawn_SENT( ply, EntityName, tr )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( EntityName == nil ) then return end

	if ( !CanPlayerSpawnSENT( ply, EntityName ) ) then return end

	-- Ask the gamemode if it's OK to spawn this
	if ( !gamemode.Call( "PlayerSpawnSENT", ply, EntityName ) ) then return end

	if ( !tr ) then

		local vStart = ply:EyePos()
		local vForward = ply:GetAimVector()

		tr = util.TraceLine( {
			start = vStart,
			endpos = vStart + ( vForward * 4096 ),
			filter = ply
		} )

	end

	local entity = nil
	local PrintName = nil
	local sent = scripted_ents.GetStored( EntityName )

	if ( sent ) then

		local sentTable = sent.t

		ClassName = EntityName

			local SpawnFunction = scripted_ents.GetMember( EntityName, "SpawnFunction" )
			if ( !SpawnFunction ) then return end -- Fallback to default behavior below?

			entity = SpawnFunction( sentTable, ply, tr, EntityName )

			if ( IsValid( entity ) ) then
				entity:SetCreator( ply )
			end

		ClassName = nil

		PrintName = sentTable.PrintName

	else

		-- Spawn from list table
		local SpawnableEntities = list.Get( "SpawnableEntities" )
		if ( !SpawnableEntities ) then return end

		local EntTable = SpawnableEntities[ EntityName ]
		if ( !EntTable ) then return end

		PrintName = EntTable.PrintName

		local SpawnPos = tr.HitPos + tr.HitNormal * 16
		if ( EntTable.NormalOffset ) then SpawnPos = SpawnPos + tr.HitNormal * EntTable.NormalOffset end

		-- Make sure the spawn position is not out of bounds
		local oobTr = util.TraceLine( {
			start = tr.HitPos,
			endpos = SpawnPos,
			mask = MASK_SOLID_BRUSHONLY
		} )

		if ( oobTr.Hit ) then
			SpawnPos = oobTr.HitPos + oobTr.HitNormal * ( tr.HitPos:Distance( oobTr.HitPos ) / 2 )
		end

		entity = ents.Create( EntTable.ClassName )
		entity:SetPos( SpawnPos )

		if ( EntTable.KeyValues ) then
			for k, v in pairs( EntTable.KeyValues ) do
				entity:SetKeyValue( k, v )
			end
		end

		if ( EntTable.Material ) then
			entity:SetMaterial( EntTable.Material )
		end

		entity:Spawn()
		entity:Activate()

		DoPropSpawnedEffect( entity )

		if ( EntTable.DropToFloor ) then
			entity:DropToFloor()
		end

	end

	if ( !IsValid( entity ) ) then return end

	TryFixPropPosition( ply, entity, tr.HitPos )

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedSENT", ply, entity )
	end

	undo.Create( "SENT" )
		undo.SetPlayer( ply )
		undo.AddEntity( entity )
		if ( PrintName ) then
			undo.SetCustomUndoText( "Undone " .. PrintName )
		end
	undo.Finish( "Scripted Entity (" .. tostring( EntityName ) .. ")" )

	ply:AddCleanup( "sents", entity )
	entity:SetVar( "Player", ply )

end
concommand.Add( "gm_spawnsent", function( ply, cmd, args ) Spawn_SENT( ply, args[ 1 ] ) end )

--[[---------------------------------------------------------
	-- Give a swep.
-----------------------------------------------------------]]
function CCGiveSWEP( ply, command, arguments )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( arguments[1] == nil ) then return end
	if ( !ply:Alive() ) then return end

	-- Make sure this is a SWEP
	local swep = list.Get( "Weapon" )[ arguments[1] ]
	if ( swep == nil ) then return end

	-- You're not allowed to spawn this!
	local isAdmin = ply:IsAdmin() or game.SinglePlayer()
	if ( ( !swep.Spawnable && !isAdmin ) or ( swep.AdminOnly && !isAdmin ) ) then
		return
	end

	if ( !gamemode.Call( "PlayerGiveSWEP", ply, arguments[1], swep ) ) then return end

	if ( !ply:HasWeapon( swep.ClassName ) ) then
		MsgAll( "Giving " .. ply:Nick() .. " a " .. swep.ClassName .. "\n" )
		ply:Give( swep.ClassName )
	end

	-- And switch to it
	ply:SelectWeapon( swep.ClassName )

end
concommand.Add( "gm_giveswep", CCGiveSWEP )

--[[---------------------------------------------------------
	-- Spawn a SWEP on the ground
-----------------------------------------------------------]]
function Spawn_Weapon( ply, wepname, tr )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( wepname == nil ) then return end

	local swep = list.Get( "Weapon" )[ wepname ]

	-- Make sure this is a SWEP
	if ( swep == nil ) then return end

	-- You're not allowed to spawn this!
	local isAdmin = ply:IsAdmin() or game.SinglePlayer()
	if ( ( !swep.Spawnable && !isAdmin ) or ( swep.AdminOnly && !isAdmin ) ) then
		return
	end

	if ( !gamemode.Call( "PlayerSpawnSWEP", ply, wepname, swep ) ) then return end

	if ( !tr ) then
		tr = ply:GetEyeTraceNoCursor()
	end

	if ( !tr.Hit ) then return end

	local entity = ents.Create( swep.ClassName )

	if ( !IsValid( entity ) ) then return end

	DoPropSpawnedEffect( entity )

	local SpawnPos = tr.HitPos + tr.HitNormal * 32

	-- Make sure the spawn position is not out of bounds
	local oobTr = util.TraceLine( {
		start = tr.HitPos,
		endpos = SpawnPos,
		mask = MASK_SOLID_BRUSHONLY
	} )

	if ( oobTr.Hit ) then
		SpawnPos = oobTr.HitPos + oobTr.HitNormal * ( tr.HitPos:Distance( oobTr.HitPos ) / 2 )
	end

	entity:SetPos( SpawnPos )
	entity:Spawn()

	undo.Create( "SWEP" )
		undo.SetPlayer( ply )
		undo.AddEntity( entity )
		undo.SetCustomUndoText( "Undone " .. tostring( swep.PrintName ) )
	undo.Finish( "Scripted Weapon (" .. tostring( swep.ClassName ) .. ")" )

	-- Throw it into SENTs category
	ply:AddCleanup( "sents", entity )

	TryFixPropPosition( ply, entity, tr.HitPos )

	gamemode.Call( "PlayerSpawnedSWEP", ply, entity )

end
concommand.Add( "gm_spawnswep", function( ply, cmd, args ) Spawn_Weapon( ply, args[1] ) end )

-- Do not allow people to undo weapons from player's hands
hook.Add( "WeaponEquip", "SpawnWeaponUndoRemoval", function( wep, ply )
	undo.ReplaceEntity( wep, nil )
	cleanup.ReplaceEntity( wep, nil )
end )

local function MakeVehicle( ply, Pos, Ang, model, Class, VName, VTable, data )

	if ( IsValid( ply ) && !gamemode.Call( "PlayerSpawnVehicle", ply, model, VName, VTable ) ) then return end

	local Ent = ents.Create( Class )
	if ( !IsValid( Ent ) ) then return NULL end

	duplicator.DoGeneric( Ent, data )

	Ent:SetModel( model )

	-- Fallback vehiclescripts for HL2 maps ( dupe support )
	if ( model == "models/buggy.mdl" ) then Ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jeep_test.txt" ) end
	if ( model == "models/vehicle.mdl" ) then Ent:SetKeyValue( "vehiclescript", "scripts/vehicles/jalopy.txt" ) end

	-- Fill in the keyvalues if we have them
	if ( VTable && VTable.KeyValues ) then
		for k, v in pairs( VTable.KeyValues ) do

			local kLower = string.lower( k )

			if ( kLower == "vehiclescript" or
				 kLower == "limitview"     or
				 kLower == "vehiclelocked" or
				 kLower == "cargovisible"  or
				 kLower == "enablegun" )
			then
				Ent:SetKeyValue( k, v )
			end

		end
	end

	Ent:SetAngles( Ang )
	Ent:SetPos( Pos )

	DoPropSpawnedEffect( Ent )

	Ent:Spawn()
	Ent:Activate()

	-- Some vehicles reset this in Spawn()
	if ( data && data.ColGroup ) then Ent:SetCollisionGroup( data.ColGroup ) end

	-- Store spawnmenu data for addons and stuff
	if ( Ent.SetVehicleClass && VName ) then Ent:SetVehicleClass( VName ) end
	Ent.VehicleName = VName
	Ent.VehicleTable = VTable

	-- We need to override the class in the case of the Jeep, because it
	-- actually uses a different class than is reported by GetClass
	Ent.ClassOverride = Class

	if ( IsValid( ply ) ) then
		gamemode.Call( "PlayerSpawnedVehicle", ply, Ent )
	end

	return Ent

end

duplicator.RegisterEntityClass( "prop_vehicle_jeep_old", MakeVehicle, "Pos", "Ang", "Model", "Class", "VehicleName", "VehicleTable", "Data" )
duplicator.RegisterEntityClass( "prop_vehicle_jeep", MakeVehicle, "Pos", "Ang", "Model", "Class", "VehicleName", "VehicleTable", "Data" )
duplicator.RegisterEntityClass( "prop_vehicle_airboat", MakeVehicle, "Pos", "Ang", "Model", "Class", "VehicleName", "VehicleTable", "Data" )
duplicator.RegisterEntityClass( "prop_vehicle_prisoner_pod", MakeVehicle, "Pos", "Ang", "Model", "Class", "VehicleName", "VehicleTable", "Data" )

--[[---------------------------------------------------------
	Name: Spawn_Vehicle
	Desc: Player attempts to spawn vehicle
-----------------------------------------------------------]]
function Spawn_Vehicle( ply, vname, tr )

	-- We don't support this command from dedicated server console
	if ( !IsValid( ply ) ) then return end

	if ( !vname ) then return end

	local VehicleList = list.Get( "Vehicles" )
	local vehicle = VehicleList[ vname ]

	-- Not a valid vehicle to be spawning..
	if ( !vehicle ) then return end

	if ( !tr ) then
		tr = ply:GetEyeTraceNoCursor()
	end

	local Angles = ply:GetAngles()
	Angles.pitch = 0
	Angles.roll = 0
	Angles.yaw = Angles.yaw + 180

	local pos = tr.HitPos
	if ( vehicle.Offset ) then
		pos = pos + tr.HitNormal * vehicle.Offset
	end

	local Ent = MakeVehicle( ply, pos, Angles, vehicle.Model, vehicle.Class, vname, vehicle )
	if ( !IsValid( Ent ) ) then return end

	-- Unstable for Jeeps
	-- TryFixPropPosition( ply, Ent, tr.HitPos )

	if ( vehicle.Members ) then
		table.Merge( Ent, vehicle.Members )
		duplicator.StoreEntityModifier( Ent, "VehicleMemDupe", vehicle.Members )
	end

	undo.Create( "Vehicle" )
		undo.SetPlayer( ply )
		undo.AddEntity( Ent )
		undo.SetCustomUndoText( "Undone " .. vehicle.Name )
	undo.Finish( "Vehicle (" .. tostring( vehicle.Name ) .. ")" )

	ply:AddCleanup( "vehicles", Ent )

end
concommand.Add( "gm_spawnvehicle", function( ply, cmd, args ) Spawn_Vehicle( ply, args[1] ) end )

local function VehicleMemDupe( ply, ent, Data )

	table.Merge( ent, Data )

end
duplicator.RegisterEntityModifier( "VehicleMemDupe", VehicleMemDupe )
