do -- seagulls
	local function AccessorFunc(tbl, name, dt)
		tbl["Set" .. name] = function(self, var)
			if dt and self.dt then
				self.dt[name] = var
			else
				self[name] = var
			end
		end

		tbl["Get" .. name] = function(self, var)
			if dt and self.dt then
				return self.dt[name]
			else
				return self[name]
			end
		end
	end

	local ENT = {}

	ENT.ClassName = "fishing_seagull"
	ENT.Type = "anim"
	ENT.Base = "base_anim"

	ENT.IsFishingEntity = true
	ENT.IsFishingSeagull = true

	ENT.Model = "models/seagull.mdl"

	ENT.Target = NULL
	ENT.Size = 1

	ENT.Animations = {
		Fly = "Fly",
		Run = "run",
		Walk = "walk",
		Idle = "idle01",
	}

	ENT.Sounds = {
		Pain= {
			"ambient/creatures/seagull_pain1.wav",
			"ambient/creatures/seagull_pain2.wav",
			"ambient/creatures/seagull_pain3.wav",
		},
		Idle= {
			"ambient/creatures/seagull_idle1.wav",
			"ambient/creatures/seagull_idle2.wav",
			"ambient/creatures/seagull_idle3.wav",
		},
		AmbientIdle= {
			"ambient/levels/coast/seagulls_ambient1.wav",
			"ambient/levels/coast/seagulls_ambient2.wav",
			"ambient/levels/coast/seagulls_ambient3.wav",
			"ambient/levels/coast/seagulls_ambient4.wav",
			"ambient/levels/coast/seagulls_ambient5.wav",
		},
		Impact= {
			"physics/body/body_medium_impact_soft1.wav",
			"physics/body/body_medium_impact_soft2.wav",
			"physics/body/body_medium_impact_soft3.wav",
			"physics/body/body_medium_impact_soft4.wav",
			"physics/body/body_medium_impact_soft5.wav",
			"physics/body/body_medium_impact_soft6.wav",
			"physics/body/body_medium_impact_soft7.wav",
		},
	}

	AccessorFunc(ENT, "Size", true)
	AccessorFunc(ENT, "SetWingCycle", true)

	function ENT:SetupDataTables()
		self:DTVar("Float", 0, "Size")
	end

	do -- util
		function ENT:PlaySound(type)
			self:EmitSound(
				table.Random(self.Sounds[type]),
				math.Clamp(math.Rand(40, 60) * self:GetSize(), 1, 160),
				math.Clamp(math.random(90,110) / self:GetSize(), 25, 255)
			)
		end

		function ENT:GetBottom()
			return self:GetPos() + Vector(0,0,-5 * self:GetSize())
		end

		function ENT:GetTop()
			return self:GetPos() + Vector(0,0,5 * self:GetSize())
		end

		function ENT:GetOnGround()
			if not self.LastOnGround or self.LastOnGround < CurTime() then
				self.OnGroundCache = util.QuickTrace(self:GetBottom(), vector_up * -10, {ents.FindInSphere(self:GetPos(), 20)}).Hit
				self.LastOnGround = CurTime() + 0.05
			end

			return self.OnGroundCache
		end

		function ENT:GetPlayerPosition()
			return self:GetPos() + Vector(0,0,self:GetSize() * 7)
		end
	end

	local function setup_player(ply, ent)
		if CLIENT then
			ent:InvalidateBoneCache()
			ply:InvalidateBoneCache()
		end
		local pos, ang = ent:GetTop(), ent:GetAngles()

		pos, ang = LocalToWorld(Vector(5,3,0) * ent:GetSize(), Angle(0,10,-90), pos, ang)

		ply:SetPos(pos)
		ply:SetAngles(ang)
		ply:SetRenderOrigin(pos)
		ply:SetRenderAngles(ang)

		ply:SetupBones()

		local wep = ply:GetActiveWeapon()
		if wep:IsValid() then
			wep:SetPos(pos)
			wep:SetAngles(ang)
			wep:SetRenderOrigin(pos)
			wep:SetRenderAngles(ang)

			wep:SetupBones()
		end
	end

	if CLIENT then
		language.Add("fishing_seagull", "Seagull")

		do -- util
			function ENT:SetAnim(anim)
				self:SetSequence(self:LookupSequence(self.Animations[anim]))
			end
		end

		do -- calc
			ENT.Cycle = 0
			ENT.Noise = 0

			function ENT:AnimationThink(vel, len, ang)
				local siz = self:GetSize()
				len = len / siz

				if self:GetOnGround() then
					if len < 3 / siz then
						self:SetAnim("Idle")
						len = 15 / self:GetSize() * (self.Noise * 2)
					else
						self:StepSoundThink()

						if len > 50 / siz then
							self:SetAnim("Run")
						else
							self:SetAnim("Walk")
						end
					end

					self.Noise = (self.Noise + (math.Rand(-1,1) - self.Noise) * FrameTime())
				else
					self:SetAnim("Fly")

					if vel.z > 0 then
						len = len / 20
					else
						len = 0
						self.Cycle = 0.2
					end
				end

				self.Cycle = (self.Cycle + (len / (15 / siz)) * FrameTime()) % 1
				self:SetCycle(self.Cycle)
			end

			function ENT:StepSoundThink()
				local stepped = self.Cycle%0.5
				if stepped  < 0.3 then
					if not self.stepped then
						sound.Play(
							"npc/fast_zombie/foot2.wav",
							self:GetPos(),
							math.Clamp(10 * self:GetSize(), 70, 160) + math.Rand(-5,5),
							math.Clamp(100 / (self:GetSize()/3), 40, 200) + math.Rand(-10,10)
						)
						self.stepped = true
					end
				else
					self.stepped = false
				end
			end
		end

		do -- standard
			function ENT:Draw()
				self.vel = self:GetVelocity() / self:GetSize()
				self.len = self.vel:Length()
				self.ang = self.vel:Angle()

				self:AnimationThink(self.vel, self.len, self.ang)

				self:SetModelScale(self:GetSize(), 0)

				if self.len > 5 or not self.lastang then
					self:SetAngles(self.ang)
					self.lastang = self.ang
				else
					self:SetAngles(self.lastang)
				end

				if self:GetOnGround() then
					self.ang = Angle(0, self.ang.y, 0)
					self.lastang = Angle(0, self.lastang and self.lastang.y or self.ang.y, 0)
				end

				self:SetRenderOrigin(self:GetBottom())

				--local min, max = self:GetRenderBounds()
				--local size = self:GetSize() * 2
				--self:SetRenderBounds(min * size, max * size)
				self:SetupBones()
				self:DrawShadow(false)
				self:DrawModel()
				self:SetRenderOrigin(nil)
				self:InvalidateBoneCache()
			end

			function ENT:Think()
				if math.random() > 0.9999 then
					self:EmitSound(table.Random(self.Sounds.AmbientIdle), 120, math.Clamp(100 / (self:GetSize()/2), 30, 200) + math.Rand(-10,10))
				end
			end
			
			local emitter = ParticleEmitter(Vector())
			
			function ENT:OnRemove()
				for i=1, 100 do
					local size = math.Rand(1, 5) * self:GetSize() 
					local particle = emitter:Add("effects/blood2", self:GetPos() + (VectorRand():Angle():Forward() * self:GetSize() * 10))
					
					particle:SetDieTime( math.Rand( 2, 2.5 ) * 4 )
					particle:SetVelocity( VectorRand() * math.Rand( 15, 30 ) * 5 )
					particle:SetGravity( Vector( math.Rand( -25, 25 ), math.Rand( -25, 25 ), -300 ) )
					particle:SetCollide( true )
					particle:SetBounce( 0.4 )
					particle:SetAngles(Angle(math.Rand(-180,180),math.Rand(-180,180),math.Rand(-180,180)))
					particle:SetStartLength( size * 4 )
					particle:SetStartAlpha( 255 )
					particle:SetStartSize( size )
					particle:SetEndSize( size )
					particle:SetColor( 220, 20, 30 )
					particle:SetAirResistance( 0 )
					
				end
			end
		end
	end

	if SERVER then
		function ENT:SetSize(siz)
			self.dt.Size = siz
			self:PhysicsInitSphere(5 * self:GetSize())
			self:GetPhysicsObject():SetMass(20 * self:GetSize())
			self:StartMotionController()
		end

		function ENT:Initialize()
			self:SetSize(math.Rand(0.75, 4))

			self:SetModel(self.Model)
			self:PhysicsInitSphere(5 * self:GetSize())
			self:StartMotionController()
			self:SetCollisionGroup(COLLISION_GROUP_DEBRIS)

			self:SetHealth(100)
			self:SetMaxHealth(100)

			local phys = self:GetPhysicsObject()
				phys:SetMass(20 * self:GetSize())
				phys:SetMaterial("default_silent")
			self.phys = phys
			
			self.weld = NULL
			self.holding_ent = NULL
			self.target_ent = NULL
		end

		function ENT:Think()

			self:PhysWake()
			self:NextThink(CurTime() + 0.25)

			local flock_pos

			do
				local pos = Vector()
				local count = 0

				for key, ent in pairs(ents.GetAll()) do
					if ent.IsFishingSeagull and not ent.holding_ent:IsValid() and not ent.target_ent:IsValid() then
						pos = pos + ent:GetPos()
						count = count + 1
					end
				end

				pos = pos / count

				if count > 0 then
					flock_pos = pos
				end

				if count > 0 and self:GetPos():Distance(pos) > 300 then

					self.current_dir = (pos - self:GetPos()):GetNormalized()
				else
					if (self.last_idle or 0) < os.clock() then
						self.current_dir = Vector(math.Rand(-1,1), math.Rand(-1,1), 0) * 0.5
						self.last_idle = os.clock() + (math.random()*3)
					end
				end
			end


			do
				local pos = Vector()
				local count = 0

				for key, ent in pairs(ents.FindInSphere(self:GetPos(), 200)) do
					if ent:IsPlayer() and ent:Alive() then
						pos = pos + ent:GetPos()
						count = count + 1

						if self.dt.Size > 3 and self:GetPos():Distance(ent:GetPos()) < 30 then
							local params = DamageInfo()
								params:SetDamage(math.random(8,13))
								params:SetAttacker(self)
								params:SetInflictor(self)
								params:SetDamageType(DMG_CLUB)
								params:SetDamageForce((ent:GetPos() - self:GetPos()):GetNormalized()*50)
							ent:TakeDamageInfo(params)
							self:PlaySound("Pain")
							self:PlaySound("Impact")
						end
					end
				end

				if count > 0 then
					pos = pos / count

					self.current_dir = (self:GetPos() - pos):GetNormalized()

					if self.dt.Size > 3 then
						self.current_dir = -self.current_dir
						return
					end
				end
			end

			if self.holding_ent:IsValid() then
				if flock_pos then
					local t = os.clock() + self:EntIndex()
					local sin = Vector(math.sin(t), math.cos(t), self:EntIndex()%10 < 5 and math.sin(t) or math.cos(t)) * 10

					if self:EntIndex()%1 == 0 then
						sin = -sin
					end

					self.current_dir = ((flock_pos - self:GetPos()):GetNormalized() + sin):GetNormalized() * 10
					return
				end
			end

			local ent = self.target_ent

			if ent:IsValid() and self.target_ent.fishing_seagull ~= self then
				self.target_ent = NULL
				ent = NULL
			end

			if not ent:IsValid() then

				for key, _ent in RandomPairs(ents.FindInSphere(self:GetPos(), 5000)) do
					if _ent.IsFishingBait then
						local seag = _ent.fishing_seagull or NULL
						if seag == self or not seag:IsValid() then
							ent = _ent
							break
						end
					end
				end

				if not ent then
					for key, _ent in RandomPairs(ents.FindInSphere(self:GetPos(), 5000)) do
						if _ent.IsFishingFish then
							local seag = _ent.fishing_seagull or NULL
							if seag == self or not seag:IsValid() then
								ent = _ent
								break
							end
						end
					end
				end
			end

			if ent:IsValid() then
				self.current_dir = (ent:GetPos() - self:GetPos()):GetNormalized()

				self.target_ent = ent

				if not self.holding_ent:IsValid() and self:GetPos():Distance(ent:GetPos()) < 50 then
					
					if ent.Detach and ent.hook and ent.hook:IsValid() then
						ent:Detach()
					end
				
					SafeRemoveEntity(self.weld)

					ent:SetPos(self:GetPos())
					local weld = constraint.Weld(self, ent)
					if weld then
						self.phys:EnableMotion(true)

						for key, ent in pairs(constraint.GetAllConstrainedEntities(ent)) do
							ent:GetPhysicsObject():EnableMotion(true)
						end

						self.weld = weld
						ent.fishing_seagull = self

						if ent.IsFishingFish and ent.Kill then
							ent:Kill()
						end
					else
						ent:SetPos(self:GetPos())
						ent:SetParent(self)
					end

					self.holding_ent = ent
					self.target_ent = NULL
				end
			end
		end

		ENT.current_dir = Vector()

		function ENT:PhysicsSimulate(phys)
			self.vel = phys:GetVelocity()
			self.len2d = self.vel:Length2D()

			local vel = self.current_dir * phys:GetMass()

			local damp = 0

			if self.target_ent:IsValid() then
				vel.z = vel.z + 30
			end

			if self:GetOnGround() then

				if self.len2d < 5 * self:GetSize() then
					damp = 0.7
				else
					damp = 0.2
				end

				phys:AddVelocity(vel)
			else
				vel = vel + VectorRand() * 20

				damp = 0.01

				phys:AddVelocity(vel / 3)
			end


			phys:AddVelocity(phys:GetVelocity() * -damp)
			phys:AddAngleVelocity(phys:GetAngleVelocity() * -0.2)
			
			if self:WaterLevel() > 0 then
				phys:AddVelocity(Vector(0,0,25))
			end
		end

		function ENT:OnTakeDamage(dmg)
			self:PlaySound("Pain")

			self:SetHealth(self:Health() - (dmg:GetDamage() / self:GetSize()))

			if self:Health() <= 0 then

				for i = 1, 10 do
					self:PlaySound("Pain")
					self:PlaySound("Impact")
				end

				self:Kill()
			end
		end

		function ENT:Kill()
			self:Remove()
			self:PlaySound("Impact")
		end

		function ENT:OnRemove()

		end
	end

	scripted_ents.Register(ENT, ENT.ClassName, true)
	
	if SERVER then
		timer.Create(ENT.ClassName .. "_spawner", 1, 0, function()
			if fishing.IsSomeoneFishing() then
				
				local count =  0
				
				for key, ent in pairs(ents.GetAll()) do
					if ent.IsFishingBait then
						count = count + 1
					end
				end
				
				if count > 10 then
					local seagulls = ents.FindByClass(ENT.ClassName)
					
					if #seagulls == 0 then
						local ent = ents.Create(ENT.ClassName)
						ent:SetPos(table.Random(player.GetAll()):EyePos() + Vector(0,0,50))
						ent:Spawn()
					elseif #seagulls >= 1 and #seagulls < 30 then
						local ent = ents.Create(ENT.ClassName)
						ent:SetPos(table.Random(seagulls):EyePos())
						ent:Spawn()
					end
				end
			end
		end)
	end
end
