local meta = FindMetaTable("Player")

function meta:PlayVoiceLine(type, chance)
	net.Start("SuR.VoiceLines")
	net.WriteTable({self, type, chance})
	net.Broadcast()
end

function meta:CheckHitbox(pos)
	pos = pos or self:GetPos()
	local tr = util.TraceHull({
		start = pos,
		endpos = pos,
		filter = self,
		mins = self:OBBMins(),
		maxs = self:OBBMaxs(),
		mask = MASK_PLAYERSOLID
	})
	return !tr.Hit
end

function meta:PlaceMark()
	if not self.MarkDelay then
		self.MarkDelay = 0
	end
	if !self:Alive() or self.MarkDelay > CurTime() then return end

	local type = "point"
	local tr = self:GetEyeTrace()
	local data = tr.HitPos
	local ent = tr.Entity
	if IsValid(ent) and (ent:GetClass() == "sur_loot" or ent:GetClass() == "sur_resupply") then
		type = "item"
		data = ent
	elseif IsValid(ent) and ent.IsZombie and ent:IsNPC() then
		type = "enemy"
		data = ent
	end

	net.Start("SuR.PointMark")
	net.WriteString(type)
	net.WriteEntity(self)
	net.WriteTable({data})
	net.Send(team.GetPlayers(self:Team()))
	self.MarkDelay = CurTime()+10
end

function meta:AbilityKick()
	if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() then return end

	local anim = "sur_kick"..math.random(1,3)
	local _, ddelay = self:LookupSequence("sur_dropkick")
	local delay = 0.4
	if !self:OnGround() then
		anim = "sur_dropkick"
		delay = 0.3
	end
	local isdropkick = anim == "sur_dropkick"

	self:SetSVAnimation(anim, true, true)
	self:DropButtons()

	timer.Simple(0.1, function()
		if !IsValid(self) then return end
		self:EmitSound("bsmod/EvilDead/punch/punch_swing_2d_0"..math.random(1,4)..".wav", 70, math.random(80,120))
	end)

	if isdropkick then
		timer.Simple(ddelay, function()
			if !IsValid(self) then return end
			self:Stun(true)
		end)
	end

	timer.Simple(delay, function()
		if !IsValid(self) then return end

		local tr = util.TraceLine({
			start = self:GetShootPos(),
			endpos = self:GetShootPos() + self:GetAimVector() * 64,
			filter = self,
			mask = MASK_SHOT_HULL
		})
	
		if !IsValid(tr.Entity) then
			tr = util.TraceHull({
				start = self:GetShootPos(),
				endpos = self:GetShootPos() + self:GetAimVector() * 64,
				filter = self,
				mins = Vector( -10, -10, -8 ),
				maxs = Vector( 10, 10, 8 ),
				mask = MASK_SHOT_HULL
			})
		end

		local ent = tr.Entity

		if IsValid(ent) then
			local dmg = math.random(20,40)
			local npc_vel = isdropkick and ent:IsNPC()
			if isdropkick then
				dmg = dmg*3
				self:EmitSound("bsmod/EvilDead/punch/punch_impact_face_2d_0"..math.random(1,9)..".wav", 70, math.random(80,120))
			else
				self:EmitSound("bsmod/EvilDead/punch/punch_impact_body_2d_0"..math.random(1,6)..".wav", 70, math.random(80,120))
			end
			if self:GetPlayerClass() == "berserker" and self:GetLevelState() >= 2 then
				ent:TakeDamage(dmg*4, self, self)
			else
				ent:TakeDamage(dmg, self, self)
			end
			self:ViewPunch(Angle(10,0,0))
			local phys = ent:GetPhysicsObject()
			if IsValid(phys) then
				phys:SetVelocity(self:GetForward()*150+Vector(0,0,50))
			end
			if npc_vel then
				ent:SetPos(ent:GetPos()+Vector(0,0,8))
				ent:SetVelocity(self:GetForward()*1000)
			end
			if ent.IsVJBaseSNPC and (ent:Health() < 250 or isdropkick and ent:Health() < 1000) and not ent.LNR_Crawler then
				ent.Flinching = true
				ent:StopAttacks(true)
				ent.PlayingAttackAnimation = false
				local animTbl = ent.AnimTbl_Flinch
				if HitgroupInfo != nil then animTbl = HitgroupInfo.Animation end
				local anim = istable(animTbl) and VJ_PICK(animTbl) or animTbl
				local animDur = ent.NextMoveAfterFlinchTime
				if animDur == false and anim then
					animDur = ent:DecideAnimationLength(anim, false, ent.FlinchAnimationDecreaseLengthAmount)
				end
				animDur = tonumber(animDur) or 0.1
				if animDur <= 0 then
					animDur = 0.1
				end
				if anim then
					ent:VJ_ACT_PLAYACTIVITY(anim, true, animDur, false, 0, {SequenceDuration=animDur, PlayBackRateCalculated=true})
				end
				timer.Create("timer_act_flinching"..ent:EntIndex(), tonumber(animDur) or 0.1, 1, function() ent.Flinching = false end)
				if isfunction(ent.CustomOnFlinch_AfterFlinch) then
					ent:CustomOnFlinch_AfterFlinch(dmginfo, hitgroup)
				end
				ent.NextFlinchT = CurTime() + (tonumber(ent.NextFlinchTime) or 0)
			end
			if ent:IsPlayer() and ent:IsZombie() then
				ent:Stun()
			end
		end
	end)
end

function meta:AbilityGrenade()
	if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or self:GetNWInt('Grenades') <= 0 then return end

	self:SetSVAnimation("sur_throw_grenade", true, true, true)
	self:DropButtons()
	self:SetNWInt('Grenades', self:GetNWInt('Grenades')-1)
		
	timer.Simple(1.6, function()
		if !IsValid(self) then return end
			
		local grenade = ents.Create("obj_vj_grenade_sur")
		grenade:SetPos(self:GetBonePosition(self:LookupBone("ValveBiped.Bip01_L_Hand")))
		grenade:SetAngles(self:EyeAngles())
		grenade:Spawn()
		grenade:Activate()
		grenade:GetPhysicsObject():ApplyForceCenter(self:GetAimVector() * 512 + Vector(0,0,128))
	end)
end


function meta:AbilityVault()
	if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or !self:GetNWBool('CanVault') then return end

	self:SetSVAnimation("sur_vault1", true, true, true)
	self:Freeze(true)
	self:DropButtons()
		
	local i = 0
	timer.Create("Vaulting"..self:EntIndex(), 0.033, 60, function()
		if !IsValid(self) then return end

		i = i + 1
		if i == 20 then
			self:EmitSound("npc/footsteps/hardboot_generic"..math.random(1,6)..".wav", 70, 80)
		end
		if i == 60 then
			self:SetPos(self:GetPos()+self:GetForward()*60)
			self:Freeze(false)
		end
	end)
end

function meta:AbilityHealing(teammate)
	if IsValid(teammate) then
		if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or self:GetNWFloat("Bandages") <= 0 then return end

		if !IsValid(self.LastPickupWeapon) then
			self.LastPickupWeapon = self:GetActiveWeapon()
		end
		self:SetActiveWeapon(nil)

		local _, dur = self:LookupSequence("sur_heal_friend")
		self:SetSVAnimation("sur_heal_friend", true, true, true)
		self:EmitSound("surrounded/player/healing.wav", 70)
		self:PlayVoiceLine("cover", 10)
		self:SetNWFloat("Bandages", self:GetNWFloat("Bandages")-1)
		self:Freeze(true)

		teammate:SetSVAnimation("idle_all_scared", false, true, false)
		teammate:DropButtons()
			
		timer.Simple(dur, function()
			if !IsValid(self) then return end
			self:Freeze(false)
			if IsValid(self.LastPickupWeapon) then		
				local class = self.LastPickupWeapon:GetClass()
				self:SelectWeapon(class)
				self.LastPickupWeapon = nil
			end
			SuR:MessageOnClient(17, teammate:Nick(), self)

			if !IsValid(teammate) then return end

			SuR:MessageOnClient(16, self:Nick(), teammate)
			if self:GetPlayerClass() == "medic" and self:GetLevelState() >= 2 then
				teammate:SetHealth(teammate:GetMaxHealth())
			else
				teammate:SetHealth(math.Clamp(teammate:Health()+50, 1, 100))
			end
			teammate:PlayVoiceLine("thanks")
			teammate:SetSVAnimation("")
		end)
	else
		if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or !self:GetNWBool('CanHeal') or self:GetNWFloat("Bandages") <= 0 then return end

		if !IsValid(self.LastPickupWeapon) then
			self.LastPickupWeapon = self:GetActiveWeapon()
		end
		self:SetActiveWeapon(nil)

		local _, dur = self:LookupSequence("sur_healing")
		self:SetSVAnimation("sur_healing", true, true, true)
		self:EmitSound("surrounded/player/healing.wav", 70)
		self:PlayVoiceLine("cover", 10)
		self:SetNWFloat("Bandages", self:GetNWFloat("Bandages")-1)
		self:DropButtons()
			
		timer.Simple(dur, function()
			if !IsValid(self) then return end

			if self:GetPlayerClass() == "medic" and self:GetLevelState() >= 2 then
				self:SetHealth(self:GetMaxHealth())
			else
				self:SetHealth(math.Clamp(self:Health()+50, 1, 100))
			end
			if IsValid(self.LastPickupWeapon) then		
				local class = self.LastPickupWeapon:GetClass()
				self:SelectWeapon(class)
				self.LastPickupWeapon = nil
			end
		end)
	end
end

function meta:AbilitySpray(teammate)
	if IsValid(teammate) then
		if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or !teammate:GetNWBool("IsInfected") or self:GetNWFloat("Sprays") <= 0 then return end

		if !IsValid(self.LastPickupWeapon) then
			self.LastPickupWeapon = self:GetActiveWeapon()
		end
		self:SetActiveWeapon(nil)

		local _, dur = self:LookupSequence("sur_heal_friend")
		self:SetSVAnimation("sur_heal_friend", true, true, true)
		self:EmitSound("surrounded/player/spray.mp3", 70)
		self:PlayVoiceLine("cover", 10)
		self:SetNWFloat("Sprays", self:GetNWFloat("Sprays")-1)
		self:Freeze(true)

		teammate:SetSVAnimation("idle_all_scared", false, true, false)
		teammate:DropButtons()
			
		timer.Simple(dur, function()
			if !IsValid(self) then return end
			self:Freeze(false)
			if IsValid(self.LastPickupWeapon) then		
				local class = self.LastPickupWeapon:GetClass()
				self:SelectWeapon(class)
				self.LastPickupWeapon = nil
			end
			SuR:MessageOnClient(17, teammate:Nick(), self)

			if !IsValid(teammate) then return end
	
			teammate:SetSVAnimation("")
			teammate:PlayVoiceLine("thanks")
			SuR:MessageOnClient(16, self:Nick(), teammate)
			SuR:StopInfection(teammate)
		end)
	else
		if !self:Alive() or self:GetNWString('SVAnim') != "" or self:IsLockedAbilities() or !self:GetNWBool("IsInfected") or self:GetNWFloat("Sprays") <= 0 then return end

		if !IsValid(self.LastPickupWeapon) then
			self.LastPickupWeapon = self:GetActiveWeapon()
		end
		self:SetActiveWeapon(nil)

		local _, dur = self:LookupSequence("sur_healing")
		self:SetSVAnimation("sur_healing", true, true, true)
		self:EmitSound("surrounded/player/spray.mp3", 70)
		self:PlayVoiceLine("cover", 10)
		self:SetNWFloat("Sprays", self:GetNWFloat("Sprays")-1)
		self:DropButtons()
			
		timer.Simple(dur, function()
			if !IsValid(self) then return end
	
			SuR:StopInfection(self)
			if IsValid(self.LastPickupWeapon) then		
				local class = self.LastPickupWeapon:GetClass()
				self:SelectWeapon(class)
				self.LastPickupWeapon = nil
			end
		end)
	end
end

function meta:Stun(nofall, count)
	if !self:Alive() or self:IsDowned() or self:IsStunned() or self:Health() > 250 or self.ExecutingTarget or self:GetPlayerClass() == "demoman" and self:GetLevelState() >= 1 then return end

	if self:IsZombie() then
		local anim = "shove_backwards_10"
		local _, dur = self:LookupSequence(anim)
		self:SetSVAnimation(anim, false, true, true)
		self:Freeze(true)
		self:EmitSound("physics/body/body_medium_impact_hard"..math.random(1,6)..".wav", 70, 90)
			
		timer.Create("StunAnim"..self:EntIndex(), dur, 1, function()
			if !IsValid(self) then return end

			if anim == self:GetNWString('SVAnim') then
				self:SetSVAnimation(sanim, true, true)
			end
			local _, dur = self:LookupSequence(sanim)
			timer.Create("StunAnim"..self:EntIndex(), dur, 1, function()
				if !IsValid(self) then return end
		
				self:Freeze(false)
			end)
		end)
	else
		if not count then
			count = 0
		end
		self.FallPressCount = 0
		self.FallPressMax = count
		if nofall then
			if count > 0 then
				self:SetSVAnimation("sur_fall_idle", false, true)
			else
				self:SetSVAnimation("sur_fall_standup", true, true)
			end
		else
			local anim = "sur_fall_down"
			local _, dur = self:LookupSequence(anim)
			self:SetSVAnimation(anim, false, true)
			self:EmitSound("physics/body/body_medium_impact_hard"..math.random(1,6)..".wav", 70, 90)
			timer.Create("StunAnim"..self:EntIndex(), dur, 1, function()
				if !IsValid(self) then return end
				if count > 0 then
					self:SetSVAnimation("sur_fall_idle", false, true)
				else
					self:SetSVAnimation("sur_fall_standup", true, true)
				end
			end)
		end
	end
end

hook.Add("PlayerButtonDown", "SuR_ButtonHookQTEStun", function(ply, but)
    if ply:IsStunned(true) and but == KEY_SPACE and ply:OnGround() then
        if ply.FallPressCount < ply.FallPressMax then
            if ply:GetPlayerClass() == "survivor" and ply:GetLevelState() >= 2 then
                ply.FallPressCount = ply.FallPressCount + 2
            else
                ply.FallPressCount = ply.FallPressCount + 1
            end
        else
            ply:SetSVAnimation("sur_fall_standup", true, true)
        end
    end
end)

hook.Add("OnPlayerJump", "SuR.BlockJump", function(ply, speed)
	if ply:IsDowned() or ply:IsStunned() then
		ply:SetVelocity(-Vector(0,0,speed))
	end
end)

net.Receive("SuR.SelectPlayerModel", function(len, ply)
	local model = net.ReadString()
	if #SuR.Config.Allowed_PlayerModels > 0 then
		if file.Exists(model, "GAME") and table.HasValue(player_manager.AllValidModels(), model) and table.HasValue(SuR.Config.Allowed_PlayerModels, model) and (not SuR.Wave_Started or !ply:Alive()) then
			ply:SetNWString('SuR_PlayerModel', model)
			if not ply:Alive() then
				ply:SetModel(model)
			end
		end
	else
		if file.Exists(model, "GAME") and table.HasValue(player_manager.AllValidModels(), model) and not table.HasValue(SuR.Config.Blocked_PlayerModels, model) and (not SuR.Wave_Started or !ply:Alive()) then
			ply:SetNWString('SuR_PlayerModel', model)
			if not ply:Alive() then
				ply:SetModel(model)
			end
		end
	end
end)

net.Receive("SuR.ChooseVoicePack", function(len, ply)
	local voice = net.ReadString()
	if istable(SuR.Config.VoicePacks[voice]) and (not SuR.Wave_Started or !ply:Alive()) then
		ply:SetNWString('VoicePack', voice)
	end
end)

net.Receive("SuR.RadioSupport", function(len, ply)
	local fl = net.ReadFloat()
	if ply:GetNWFloat('CanCallSupport') > 0 then
		ply:SetNWFloat('CanCallSupport', ply:GetNWFloat('CanCallSupport')-1)
		SuR:CallSupport(ply, fl)
	end
end)

net.Receive("SuR.ChooseStartWeapon", function(len, ply)
	local str = net.ReadString()
	if (table.HasValue(SuR.Config.TierWeapons["Tier1"], str) or table.HasValue(SuR.Config.TierWeapons["Tier2"], str) and ply:GetNWBool("ExtendedStartWeapons")) and (not SuR.Wave_Started or !ply:Alive()) then
		ply:SetNWString('StartWeapon', str)
	end
end)

local function create_item(self, id)
	local tr = util.TraceLine( {
		start = self:EyePos(),
		endpos = self:EyePos() + self:GetAimVector() * 50,
		filter = self,
		mask = MASK_SOLID,
	})

	local ent = ents.Create("sur_loot")
	ent.LootType = id
	ent:SetPos(tr.HitPos)
	ent:Spawn()
	ent.IsLoot = true
end

concommand.Add("sur_dropweapon", function(ply, cmd, args)
	if not args[1] or !ply:Alive() or ply:IsLockedAbilities() or ply:IsZombie() then return end

	local arg = args[1]
	BroadcastLua([[Entity(]]..ply:EntIndex()..[[):AnimRestartGesture(6, ACT_GMOD_GESTURE_ITEM_DROP, true)]])

	local type = "weapon"
	if arg == "bandage" or arg == "antidote" or arg == "selfrevive" or arg == "radio" then
		type = arg
	end
	
	timer.Simple(0.2, function()
		if !IsValid(ply) then return end

		if type == "weapon" then
			ply:CreateDroppedWeapon(arg)
			if arg == ply:GetNWString('MeleeWeapon') then
				ply:Give(SuR.Config.EmptyWeapon)
				ply:SelectWeapon(SuR.Config.EmptyWeapon)
				ply:SetNWString('MeleeWeapon', SuR.Config.EmptyWeapon)
			end
		elseif type == "bandage" and ply:GetNWFloat('Bandages') > 2 then
			create_item(ply, 8)
			ply:SetNWFloat('Bandages', ply:GetNWFloat('Bandages')-3)
		elseif type == "antidote" and ply:GetNWFloat('Sprays') > 0 then
			create_item(ply, 7)
			ply:SetNWFloat('Sprays', ply:GetNWFloat('Sprays')-1)
		elseif type == "selfrevive" and ply:GetNWFloat('SelfRevive') > 0 then
			create_item(ply, 12)
			ply:SetNWFloat('SelfRevive', ply:GetNWFloat('SelfRevive')-1)
		elseif type == "radio" and ply:GetNWFloat('CanCallSupport') > 0 then
			create_item(ply, 6)
			ply:SetNWFloat('CanCallSupport', ply:GetNWFloat('CanCallSupport')-1)
		end

		ply:EmitSound("npc/vort/claw_swing"..math.random(1,2)..".wav", 60, math.random(80,100))
	end)
end)
