diff --git a/.gitignore b/.gitignore index 4dfe23b..ba3c21e 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ murder/ sandbox/ svr/ terrortown/ -fluffy_mg_base/content/materials/ fluffy_mg_base/gamemode/db_config.lua fluffy_incoming_standalone/ diff --git a/fluffy_balls/gamemode/init.lua b/fluffy_balls/gamemode/init.lua index 9067843..c600a6c 100644 --- a/fluffy_balls/gamemode/init.lua +++ b/fluffy_balls/gamemode/init.lua @@ -16,7 +16,7 @@ end function GM:CollectBall(ply) local balls = ply:GetNWInt("Balls", 0) ply:SetNWInt("Balls", balls+1) - GAMEMODE:AddStatPoints(ply, 'total_balls', 1) + GAMEMODE:AddStatPoints(ply, 'Balls Collected', 1) ply:EmitSound('buttons/blip1.wav', 50, math.Clamp(100 + balls*5, 100, 255)) @@ -90,4 +90,9 @@ function GM:GetWinningPlayer() -- Return the winner! Yay! return bestplayer -end \ No newline at end of file +end + +-- Register XP for Balls +hook.Add('RegisterStatsConversions', 'AddBallsStatConversions', function() + GAMEMODE:AddStatConversion('Balls Collected', 'Total Balls', 0.05) +end) \ No newline at end of file diff --git a/fluffy_bombtag/entities/weapons/bt_bomb.lua b/fluffy_bombtag/entities/weapons/bt_bomb.lua index 31aa017..076bca9 100644 --- a/fluffy_bombtag/entities/weapons/bt_bomb.lua +++ b/fluffy_bombtag/entities/weapons/bt_bomb.lua @@ -97,6 +97,7 @@ function SWEP:Trace() ent:SetTime(self.Owner:GetTime()) ent:StripWeapons() self.Owner:StripWeapon("bt_bomb") + self.Owner:AddStatPoints('Bomb Passes', 1) timer.Simple(0.1, function() ent:Give("bt_bomb") end) end end diff --git a/fluffy_bombtag/entities/weapons/bt_punch.lua b/fluffy_bombtag/entities/weapons/bt_punch.lua index 0ca0307..dbffd04 100644 --- a/fluffy_bombtag/entities/weapons/bt_punch.lua +++ b/fluffy_bombtag/entities/weapons/bt_punch.lua @@ -9,6 +9,7 @@ SWEP.ViewModel = "models/weapons/c_357.mdl" SWEP.WorldModel = "models/weapons/w_357.mdl" SWEP.UseHands = true SWEP.Primary.Sound = Sound( "Weapon_AR2.Single" ) +SWEP.Primary.Delay = 0.4 function SWEP:Initialize() self:SetWeaponHoldType(self.HoldType) @@ -33,9 +34,9 @@ end function SWEP:PrimaryAttack() self.Weapon:SendWeaponAnim(ACT_VM_PRIMARYATTACK) - self.Weapon:SetNextPrimaryFire( CurTime() + self.Primary.Delay ) + self.Weapon:SetNextPrimaryFire(CurTime() + self.Primary.Delay) - self.Weapon:EmitSound(self.Primary.Sound, 100, math.random(110,130) ) + self.Weapon:EmitSound(self.Primary.Sound, 100, math.random(110,130)) self.Weapon:ShootBullets(self.Primary.Damage, self.Primary.NumShots, self.Primary.Cone) end diff --git a/fluffy_bombtag/gamemode/init.lua b/fluffy_bombtag/gamemode/init.lua index 6ea816f..6d18298 100644 --- a/fluffy_bombtag/gamemode/init.lua +++ b/fluffy_bombtag/gamemode/init.lua @@ -6,6 +6,7 @@ include('ply_extension.lua') -- Players get the punch weapon by default function GM:PlayerLoadout(ply) + ply:StripWeapons() ply:SetCarrier(false) ply:Give('bt_punch') end @@ -81,7 +82,7 @@ end) function GM:StatsRoundWin(winners) for k,v in pairs(player.GetAll()) do if v:Alive() and !v.Spectating then - GAMEMODE:AddStatPoints(v, 'survived_rounds', 1) + GAMEMODE:AddStatPoints(v, 'Survived Rounds', 1) end end end @@ -99,6 +100,12 @@ function GM:DoPlayerDeath( ply, attacker, dmginfo ) for k,v in pairs(player.GetAll()) do if !v:Alive() or v == ply or v.Spectating then continue end v:AddFrags(1) - GAMEMODE:AddStatPoints(v, 'bombtag_score', 1) + GAMEMODE:AddStatPoints(v, 'Explosions Survived', 1) end -end \ No newline at end of file +end + +-- Register XP for Bomb Tag +hook.Add('RegisterStatsConversions', 'AddBombTagStatConversions', function() + GAMEMODE:AddStatConversion('Bomb Passes', 'Bomb Tagged', 0.5) + GAMEMODE:AddStatConversion('Explosions Survived', 'Explosions Survived', 0.25) +end) \ No newline at end of file diff --git a/fluffy_climb/gamemode/init.lua b/fluffy_climb/gamemode/init.lua index 6baf004..ee74fea 100644 --- a/fluffy_climb/gamemode/init.lua +++ b/fluffy_climb/gamemode/init.lua @@ -60,13 +60,13 @@ hook.Add('RoundEnd', 'ClimbHeightPoints', function() if v.BestHeight then local ratio = v.BestHeight/GAMEMODE.CurrentHeight local p = math.Clamp(math.floor(ratio * 100), 0, 100) - v:AddStatPoints('ClimbHeight', p) + v:AddStatPoints('Distance', p) v:AddFrags(math.floor(p/10)) end if v:Alive() and not v.Spectating then v:AddFrags(1) - v:AddStatPoints('SurvivedRounds', 1) + v:AddStatPoints('Survived Rounds', 1) end end end) @@ -81,4 +81,9 @@ function GM:ClimbVictory(ply) GAMEMODE:EndRound(ply) GAMEMODE:EntityCameraAnnouncement(ply, GAMEMODE.RoundCooldown or 5, Vector(0, 0, 72)) -end \ No newline at end of file +end + +-- Register XP for Paintball +hook.Add('RegisterStatsConversions', 'AddClimbStatConversions', function() + GAMEMODE:AddStatConversion('Distance', 'Distance Climbed', 0.01) +end) \ No newline at end of file diff --git a/fluffy_cratewars/gamemode/init.lua b/fluffy_cratewars/gamemode/init.lua index c7c71d5..4fcd0c3 100644 --- a/fluffy_cratewars/gamemode/init.lua +++ b/fluffy_cratewars/gamemode/init.lua @@ -108,7 +108,7 @@ hook.Add('PropBreak', 'TrackBrokenCrates', function(ply, prop) if !ply.SmashedCrates then return end ply.SmashedCrates = ply.SmashedCrates + 1 ply:SetNWInt("Crates", ply.SmashedCrates) - ply:AddStatPoints('Crates', 1) + ply:AddStatPoints('Crates Smashed', 1) -- Award bonuses to the player if lucky if prop.BonusWeapon then @@ -121,6 +121,7 @@ hook.Add('PropBreak', 'TrackBrokenCrates', function(ply, prop) local text = GAMEMODE.WeaponOptions[prop.BonusWeapon][1] GAMEMODE:PlayerOnlyAnnouncement(ply, 1, text or 'Bonus!', 1) + ply:AddStatPoints('Bonuses Earned', 1) end end ) @@ -164,4 +165,10 @@ function GM:EntityTakeDamage(target, dmginfo) else return end -end \ No newline at end of file +end + +-- Register XP for Crate Wars +hook.Add('RegisterStatsConversions', 'AddCrateWarsStatConversions', function() + GAMEMODE:AddStatConversion('Crates Smashed', 'Destroyed Crates', 0.1) + GAMEMODE:AddStatConversion('Bonuses Earned', 'Bonuses Earned', 0) +end) \ No newline at end of file diff --git a/fluffy_dodgeball/gamemode/shared.lua b/fluffy_dodgeball/gamemode/shared.lua index 4231ce1..881c30f 100644 --- a/fluffy_dodgeball/gamemode/shared.lua +++ b/fluffy_dodgeball/gamemode/shared.lua @@ -15,6 +15,8 @@ GM.WinBySurvival = true GM.DeathSounds = true +GM.SpawnProtection = true -- Spawn protection enabled + function GM:Initialize() end \ No newline at end of file diff --git a/fluffy_duckhunt/entities/entities/duckhunt_win.lua b/fluffy_duckhunt/entities/entities/duckhunt_win.lua new file mode 100644 index 0000000..4e72c78 --- /dev/null +++ b/fluffy_duckhunt/entities/entities/duckhunt_win.lua @@ -0,0 +1,12 @@ +ENT.Base = "base_entity" +ENT.Type = "brush" + +-- Award players that touch this entity a round win +function ENT:StartTouch(ent) + if IsValid(self) and ent:IsPlayer() then + if not ent:Alive() or ent.Spectating then return end + if ent:Team() != TEAM_BLUE then return end + self:Remove() + GAMEMODE:EndRound(ent) + end +end \ No newline at end of file diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger.lua b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua new file mode 100644 index 0000000..4e72c78 --- /dev/null +++ b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua @@ -0,0 +1,12 @@ +ENT.Base = "base_entity" +ENT.Type = "brush" + +-- Award players that touch this entity a round win +function ENT:StartTouch(ent) + if IsValid(self) and ent:IsPlayer() then + if not ent:Alive() or ent.Spectating then return end + if ent:Team() != TEAM_BLUE then return end + self:Remove() + GAMEMODE:EndRound(ent) + end +end \ No newline at end of file diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua b/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua deleted file mode 100644 index 4cadd62..0000000 --- a/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua +++ /dev/null @@ -1,47 +0,0 @@ -ENT.Base = "base_entity" -ENT.Type = "brush" - -/*--------------------------------------------------------- - Name: Initialize ----------------------------------------------------------*/ -function ENT:Initialize() -end - -/*--------------------------------------------------------- - Name: Touch ----------------------------------------------------------*/ -function ENT:StartTouch( entity ) - if IsValid( self ) && entity:IsPlayer() then - self:Remove() - GAMEMODE:EndRound( entity ) - end -end - -/*--------------------------------------------------------- - Name: PassesTriggerFilters - Desc: Return true if this object should trigger us ----------------------------------------------------------*/ -function ENT:PassesTriggerFilters( entity ) - return true -end - -/*--------------------------------------------------------- - Name: KeyValue - Desc: Called when a keyvalue is added to us ----------------------------------------------------------*/ -function ENT:KeyValue( key, value ) -end - -/*--------------------------------------------------------- - Name: Think - Desc: Entity's think function. ----------------------------------------------------------*/ -function ENT:Think() -end - -/*--------------------------------------------------------- - Name: OnRemove - Desc: Called just before entity is deleted ----------------------------------------------------------*/ -function ENT:OnRemove() -end \ No newline at end of file diff --git a/fluffy_duckhunt/gamemode/init.lua b/fluffy_duckhunt/gamemode/init.lua index 1780274..d178c24 100644 --- a/fluffy_duckhunt/gamemode/init.lua +++ b/fluffy_duckhunt/gamemode/init.lua @@ -8,13 +8,13 @@ function GM:PlayerLoadout( ply ) if ply:Team() == TEAM_BLUE then -- Runners ply:StripWeapons() - ply:SetWalkSpeed( 325 ) - ply:SetRunSpeed( 375 ) + ply:SetWalkSpeed(350) + ply:SetRunSpeed(400) elseif ply:Team() == TEAM_RED then -- Snipers ply:Give('sniper_normal') - ply:SetWalkSpeed( 425 ) - ply:SetRunSpeed( 500 ) + ply:SetWalkSpeed(475) + ply:SetRunSpeed(525) end end @@ -32,13 +32,13 @@ function GM:HandleEndRound(reason) elseif type(reason) == 'Player' then -- Winning player gets 3 points reason:AddFrags(3) - GAMEMODE:AddStatPoints(reason, 'RoundWins', 1) + GAMEMODE:AddStatPoints(reason, 'Finished First', 1) -- Other survivors get 1 point if reason:Team() == TEAM_BLUE then team.AddScore(TEAM_BLUE, 1) for k,v in pairs( team.GetPlayers(TEAM_BLUE) ) do - GAMEMODE:AddStatPoints(v, 'survived_rounds', 1) + GAMEMODE:AddStatPoints(v, 'Survived Rounds', 1) if v != reason then v:AddFrags(1) end end end @@ -51,4 +51,10 @@ end function GM:StatsRoundWin() -- Handled above -end \ No newline at end of file +end + +-- Register XP for Duck Hunt +hook.Add('RegisterStatsConversions', 'AddDuckHuntStatConversions', function() + GAMEMODE:AddStatConversion('Finished First', 'Finished First', 5) + GAMEMODE:AddStatConversion('Runners Sniped', 'Runners Sniped', 0.5) +end) \ No newline at end of file diff --git a/fluffy_duckhunt/gamemode/shared.lua b/fluffy_duckhunt/gamemode/shared.lua index 2c74f00..eafe55f 100644 --- a/fluffy_duckhunt/gamemode/shared.lua +++ b/fluffy_duckhunt/gamemode/shared.lua @@ -36,6 +36,7 @@ GM.RoundCooldown = 5 -- How long between each round? GM.CanSuicide = false -- Should players be able to die at will? :( GM.ThirdPersonEnabled = false -- This gamemode overrides some functions to do with this +GM.SpawnProtection = true -- Spawn protection enabled function GM:CreateTeams() if ( !GAMEMODE.TeamBased ) then return end diff --git a/fluffy_incoming/gamemode/init.lua b/fluffy_incoming/gamemode/init.lua index 9e9d582..77d0eb1 100644 --- a/fluffy_incoming/gamemode/init.lua +++ b/fluffy_incoming/gamemode/init.lua @@ -162,7 +162,7 @@ end -- Equivalent of 1XP for every 100% of distance travelled hook.Add('RegisterStatsConversions', 'AddIncomingStatConversions', function() - GAMEMODE:AddStatConversion('Distance', 'Slope Distance', 0.01) + GAMEMODE:AddStatConversion('Distance', 'Distance Travelled', 0.01) end) IncludeResFolder( "materials/models/clannv/incoming/" ) diff --git a/fluffy_infection/entities/entities/npc_skeleton.lua b/fluffy_infection/entities/entities/npc_skeleton.lua new file mode 100644 index 0000000..fd3970f --- /dev/null +++ b/fluffy_infection/entities/entities/npc_skeleton.lua @@ -0,0 +1,21 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Skeleton' + +if CLIENT then + language.Add('npc_skeleton', 'Skeleton' ) +end + +-- Speed +ENT.Speed = 160 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/skeleton.mdl" +ENT.BaseHealth = 200 +ENT.Damage = 25 +ENT.ModelScale = 1 +ENT.Color = Color(0, 0, 255) \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_skeleton_gold.lua b/fluffy_infection/entities/entities/npc_skeleton_gold.lua new file mode 100644 index 0000000..c70f63e --- /dev/null +++ b/fluffy_infection/entities/entities/npc_skeleton_gold.lua @@ -0,0 +1,23 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Gold Skeleton' + +if CLIENT then + language.Add('npc_skeleton_gold', 'Gold Skeleton' ) +end + +-- Speed +ENT.Speed = 225 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/skeleton.mdl" +ENT.BaseHealth = 200 +ENT.Damage = 25 +ENT.ModelScale = 1.25 +ENT.BoldColor = Color(212, 175, 55) + +ENT.CollisionSide = 10 \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_skeleton_mini.lua b/fluffy_infection/entities/entities/npc_skeleton_mini.lua new file mode 100644 index 0000000..a4dc9e5 --- /dev/null +++ b/fluffy_infection/entities/entities/npc_skeleton_mini.lua @@ -0,0 +1,23 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Mini Skeleton' + +if CLIENT then + language.Add('npc_skeleton_mini', 'Mini Skeleton' ) +end + +-- Speed +ENT.Speed = 300 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/skeleton.mdl" +ENT.BaseHealth = 10 +ENT.Damage = 15 +ENT.ModelScale = 0.75 +ENT.BoldColor = Color(255, 159, 243) + +ENT.CollisionSide = 4 \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua new file mode 100644 index 0000000..66a906a --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -0,0 +1,493 @@ +AddCSLuaFile() +ENT.Base = 'base_nextbot' + +if CLIENT then + language.Add('npc_zo_base', 'Basic Zombie' ) +end + +-- Speed +ENT.Speed = 200 +ENT.WalkSpeedAnimation = 2 +ENT.Acceleration = 25 +ENT.MoveType = 1 + +-- Health & Other +ENT.BaseHealth = 100 +ENT.Damage = 15 +ENT.ModelScale = 1 + +-- Other Behaviour +ENT.SearchRadius = 2000 +ENT.LoseTargetDistance = 4000 + +-- Collisons +ENT.CollisionHeight = 64 +ENT.CollisionSide = 7 + +-- Attack +ENT.AttackRange = 60 +ENT.NextAttack = 1.3 + +-- Model & Animations +ENT.Model = "models/player/zombie_classic.mdl" +ENT.AttackAnim = (ACT_GMOD_GESTURE_RANGE_ZOMBIE) +ENT.WalkAnim = (ACT_HL2MP_WALK_ZOMBIE_01) -- { (ACT_HL2MP_RUN_ZOMBIE), (ACT_HL2MP_WALK_ZOMBIE_03) } +ENT.FlinchAnim = (ACT_HL2MP_ZOMBIE_SLUMP_RISE) +ENT.AttackDoorAnim = (ACT_GMOD_GESTURE_RANGE_ZOMBIE) + +-- Sound +ENT.AttackSounds = { + Sound("npc/zombie/zo_attack1.wav"), + Sound("npc/zombie/zo_attack2.wav") +} + +ENT.AlertSounds = { + Sound("npc/zombie/zombie_alert1.wav"), + Sound("npc/zombie/zombie_alert2.wav"), + Sound("npc/zombie/zombie_alert3.wav") +} + +ENT.DeathSounds = { + Sound("npc/zombie/zombie_die1.wav"), + Sound("npc/zombie/zombie_die2.wav"), + Sound("npc/zombie/zombie_die3.wav") +} + +ENT.IdleSounds = { + Sound("npc/zombie/zombie_voice_idle1.wav"), + Sound("npc/zombie/zombie_voice_idle2.wav"), + Sound("npc/zombie/zombie_voice_idle3.wav"), + Sound("npc/zombie/zombie_voice_idle4.wav"), + Sound("npc/zombie/zombie_voice_idle5.wav") +} + +ENT.PainSounds = { + Sound("npc/zombie/zombie_pain1.wav"), + Sound("npc/zombie/zombie_pain2.wav"), + Sound("npc/zombie/zombie_pain3.wav"), + Sound("npc/zombie/zombie_pain4.wav"), + Sound("npc/zombie/zombie_pain5.wav") +} + +-- More sound +ENT.HitSound = Sound("npc/zombie/claw_strike1.wav") +ENT.Miss = Sound("npc/zombie/claw_miss1.wav") +ENT.DoorBreak = Sound("npc/zombie/zombie_pound_door.wav") + +function ENT:Initialize() + self:SetModel(self.Model) + self:SetHealth(self.BaseHealth) + self:CollisionSetup(self.CollisionSide, self.CollisionHeight, COLLISION_GROUP_PLAYER) + self:SetModelScale(self.ModelScale or 1) + + if self.BoldColor then self:SetColor(self.BoldColor) end + + if CLIENT then return end + self.loco:SetDesiredSpeed(self.Speed) + self.loco:SetAcceleration(self.Acceleration) + self.loco:SetDeceleration(self.Acceleration * 0.2) + self:SetMaxHealth(self.BaseHealth) -- idk why this isn't clientside but hey, not my fault +end + +-- Create the collisions for this entity +function ENT:CollisionSetup(side, height, group) + self:SetCollisionGroup(group) + self:SetCollisionBounds(Vector(-side, -side, 0), Vector(side, side, height)) + self:PhysicsInitShadow(true, false) + self.NEXTBOT = true +end + +-- Color the playermodel if set +function ENT:GetPlayerColor() + if not self.Color then return end + local c = self.Color + return Vector(c.r/255, c.g/255, c.b/255) +end + +-- Useful function to set up movement animations +function ENT:MovementFunctions(type, act, speed, rate) + if type == 1 then + self:StartActivity(act) + else + self:ResetSequence(act) + self:SetPlaybackRate(rate) + end + + self:SetPoseParameter('move_x', rate) +end + +function ENT:DefaultMovement() + self:MovementFunctions(self.MoveType, self.WalkAnim, self.Speed, self.WalkSpeedAnimation) +end + +-- Useful functions to track the current enemy +function ENT:SetEnemy(ent) + self.Enemy = ent +end + +function ENT:GetEnemy(ent) + return self.Enemy +end + +-- Check if we currently have an enemy, if not, find one +function ENT:HaveEnemy() + if self:GetEnemy() and IsValid(self:GetEnemy()) then + -- Find new enemy if target is dead or out of range + if self:GetRangeTo(self:GetEnemy():GetPos()) > self.LoseTargetDistance then + return self:FindEnemy() + elseif self:GetEnemy():IsPlayer() and not self:GetEnemy():Alive() then + return self:FindEnemy() + end + + return true + else + -- Search for a new enemy + return self:FindEnemy() + end +end + +-- Search for a new enemy for this NPC +function ENT:FindEnemy() + local players = team.GetPlayers(TEAM_BLUE) + local distances = {} + + -- Get how far away every living player is + for k,v in pairs(players) do + if v.Spectating then continue end + table.insert(distances, {v, self:GetPos():DistToSqr(v:GetPos())}) + end + + -- Sort players based on distance + if distances and #distances >= 1 then + table.sort(distances, function(a, b) return a[2] < b[2] end) + else + self:SetEnemy(nil) + return false + end + + -- Target the closest player + self:SetEnemy(distances[1][1]) + return true +end + +function ENT:DistanceToEnemy(enemy) + -- Return the distance to a given enemy + -- This is inefficent! See the below functon + local enemy = enemy or self:GetEnemy() + return self:GetPos():Distance(enemy:GetPos()) +end + +function ENT:DistSqrToEnemy(enemy) + -- More efficient distance check + local enemy = enemy or self:GetEnemy() + return self:GetPos():DistToSqr(enemy:GetPos()) +end + +-- Determine what activities to do +function ENT:RunBehaviour() + while true do + if self:HaveEnemy() then + local enemy = self:GetEnemy() + local pos = enemy:GetPos() + if pos then + if enemy:IsValid() and enemy:Health() > 0 then + self:ChaseEnemy() + else + self:SetEnemy(nil) + self:FindEnemy() + end + end + else + self:Idle() + self:FindEnemy() + end + coroutine.yield() + end +end + +-- hi my name is zombie i'm lazy +function ENT:Idle() + self:MovementFunctions(1, self.WalkAnim, 0, 1) + self:MoveToPos(self:GetPos() + Vector(math.random(-512, 512), math.random(-512, 512), 0), {repath=3, maxage=5}) +end + +-- Chase down the enemy! +function ENT:ChaseEnemy() + local enemy = self:GetEnemy() + local pos = enemy:GetPos() + self:MovementFunctions(1, self.WalkAnim, 0, 1) + self.loco:SetDesiredSpeed(self.Speed) + + -- Pathing stuff? + local path = Path('Follow') + path:SetMinLookAheadDistance(300) + path:SetGoalTolerance(20) + local result = path:Compute(self, pos) + + while path:IsValid() and self:HaveEnemy() do + -- Keep the path updated + if path:GetAge() > 1 then + path:Compute(self, self:GetEnemy():GetPos()) + end + self.loco:SetDesiredSpeed(self.Speed) + path:Update(self) + self.loco:SetDesiredSpeed(self.Speed) + --path:Draw() + + -- Ensure we are not stuck + if self.loco:IsStuck() then + self:CheckTrace() + return false + end + + -- Idle sounds if approaching an enemy + if enemy and enemy:IsValid() and enemy:Health() > 0 then + if math.random() > 0.995 and not self.IsAttacking then + if self:DistanceToEnemy() < 600 then + self:IdleSound() + end + end + end + + -- Check the enemy every so often + if math.random() > 0.99 then + self:FindEnemy() + end + + self:CheckTrace() + coroutine.yield() + end + + return true +end + +-- Check if the zombie is close enough to attack the enemy +function ENT:CheckRange(enemy) + local distance = self:DistanceToEnemy(enemy) + + if self:HaveEnemy() and self:GetEnemy():Alive() then + if distance < self.AttackRange then + self:Attack() + end + end +end + +-- Handles when the zombie is injured +function ENT:OnInjured(info) + if not self:HaveEnemy() then return end + if info:GetAttacker() == self:GetEnemy() then return end + + -- If the person that hurt the zombie is closer than our current enemy + -- then target the person that attacked us instead + -- This could probably be abused in some way so might need some reworking but it works quite well + if info:GetAttacker():IsPlayer() then + local attacker = info:GetAttacker() + if self:GetPos():DistToSqr(attacker:GetPos()) < self:GetPos():DistToSqr(self:GetEnemy():GetPos()) then + self:SetEnemy(attacker) + end + end +end + +-- Called when the zombie is killed +function ENT:OnKilled(info) + hook.Run('OnNPCKilled', self, info:GetAttacker(), info:GetInflictor()) -- Run the hook + + -- Create the ragdoll then remove after 5 seconds + local ragdoll = self:BecomeRagdoll(info) + timer.Simple(5, function() + if IsValid(ragdoll) then + ragdoll:Remove() + end + end) +end + +-- Perform a trace to do attacking stuff +function ENT:CheckTrace() + if (self.NextAttackTime or 0) > CurTime() then return end + + local mins = self:OBBMins() + local maxs = self:OBBMaxs() + + -- Perform a hull trace about the size of ourselves + -- This should reasonably accurately check for obstructions + -- allowing the zombie to deal with them quite easily + local tr = util.TraceHull({ + mins = mins, + maxs = maxs, + start = self:GetPos(), + endpos = self:GetPos() + self:GetForward()*24, + filter = self + }) + + -- Attack anything that stands in our way + -- Seperates this into seperate functions based on entity data + if not tr.Hit or tr.HitWorld then return false end + local ent = tr.Entity + if ent:IsPlayer() then + self:AttackPlayer(ent) + elseif string.find(ent:GetClass(), 'prop_door_') then + self:AttackDoor(ent) + elseif ent:GetClass() == 'func_breakable' then + self:AttackObject(ent) + elseif ent:GetClass() == 'prop_physics' then + self:AttackObject(ent) + elseif ent:GetClass() == 'func_breakable_surf' then + self:AttackWindow(ent) + end +end + +-- Attack function for player entities +function ENT:AttackPlayer(ent) + if not IsValid(ent) then return end + + -- Slightly different code for our enemy vs. players that get in our way + if ent == self:GetEnemy() then + self:AttackSound() + self.IsAttacking = true + self:RestartGesture(self.AttackAnim) + self:AttackEffect(0.9, self.Enemy, self.Damage, 0) + else + -- Basic attack sound and effect + self:AttackSound() + self:AttackEffect(0.9, ent, self.Damage, 0) + end + + -- Attack cooldown + self.NextAttackTime = CurTime() + self.NextAttack +end + +-- Attack function for prop_door_rotating entities +function ENT:AttackDoor(v) + if not IsValid(v) then return end + + -- Assign health to doors + if v.DoorHealth == nil then + v.DoorHealth = math.random(50, 100) + end + + if v.DoorHealth > 0 then + -- Deal damage to the door with a basic attack + self:AttackSound() + self:RestartGesture(self.AttackAnim) + self:AttackEffect(0.9, v, self.Damage, 2) + v:EmitSound(self.DoorBreak) + v.DoorHealth = v.DoorHealth - self.Damage + else + -- Break the door off the hinges + -- This creates a prop_physics that imitates the door + local door = ents.Create("prop_physics") + if not door then return end + door:SetModel(v:GetModel()) + door:SetPos(v:GetPos()) + door:SetAngles(v:GetAngles()) + door:SetSkin(v:GetSkin()) + door:SetColor(v:GetColor()) + door:Spawn() + door:EmitSound("Wood_Plank.Break") + door:SetCollisionGroup(COLLISION_GROUP_DEBRIS) -- stop collisions + door.FakeProp = true + + -- Send the door flying + local phys = door:GetPhysicsObject() + if phys:IsValid() then phys:ApplyForceCenter(self:GetForward():GetNormalized()*20000 + Vector(0, 0, 2)) end + SafeRemoveEntity(v) + end + + -- Attack cooldown + self.NextAttackTime = CurTime() + self.NextAttack +end + +-- Generic attack function +function ENT:AttackObject(v) + if not IsValid(v) then return end + if v.FakeProp then return end + + -- Basic attack stuff + self:AttackSound() + self:RestartGesture(self.AttackAnim) + self:AttackEffect(0.9, v, self.Damage, 1) + v:EmitSound(self.DoorBreak) + self.NextAttackTime = CurTime() + self.NextAttack + + if v:Health() > 0 then + -- Deal health damage to the entity + v.Damaged = true + v:SetHealth(v:Health() - self.Damage) + else + if !v.Damaged then + -- ? + v.Damaged = true + v:SetHealth(50) + else + -- Break the entity into gibs + v:GibBreakClient(Vector(0, 0, 0)) + v:Remove() + end + end +end + +-- Attack function for breakable windows +function ENT:AttackWindow(ent) + -- func_breakable_surf should be shattered + ent:Fire('shatter', '1 1 1', 0) +end + +-- Basic attack effects - shared between the above functions +function ENT:AttackEffect(time, ent, dmg, type) + timer.Simple(time, function() + if not self:IsValid() then return end + if self:Health() < 0 then return end + + if not ent:IsValid() then return end + if self:DistanceToEnemy(ent) < self.AttackRange then + -- Apply damage + ent:TakeDamage(self.Damage, self) + + -- Emit the player damage sound for living things + if ent:IsPlayer() or ent:IsNPC() then + self:EmitSound(self.HitSound) + end + + -- View punch the player + if ent:IsPlayer() then + ent:ViewPunch(Angle(math.random(-1, 1)*self.Damage*0.1, math.random(-1, 1)*self.Damage*0.1, math.random(-1, 1)*self.Damage*0.1)) + end + else + -- Missed! Play a miss sound effect + self:EmitSound(self.Miss) + end + end) + + -- Reset the attacking after half a second delay + timer.Simple(time + 0.5, function() + if not self:IsValid() then return end + self.IsAttacking = false + end) +end + +-- Sound functions +function ENT:PlaySound(sound) + -- Emit a given sound with variance to pitch + if type(sound) == 'table' then sound = sound[1] end + self:EmitSound(sound, 100, math.Rand(80, 150) ) +end + +function ENT:AttackSound() + -- Play an attack sound + self:PlaySound(table.Random(self.AttackSounds)) +end + +function ENT:IdleSound() + -- Play an idle sound + self:PlaySound(table.Random(self.IdleSounds)) +end + +function ENT:DamageSound() + -- Play a damage sound + self:PlaySound(table.Random(self.PainSounds)) +end + +function ENT:AlertSound() + -- Play alert sound + self:PlaySound(table.Random(self.AlertSounds)) +end \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_zombie_boom.lua b/fluffy_infection/entities/entities/npc_zombie_boom.lua new file mode 100644 index 0000000..2caa885 --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zombie_boom.lua @@ -0,0 +1,34 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Boom Zombie' + +if CLIENT then + language.Add('npc_zombie_boom', 'Boom Zombie' ) +end + +-- Speed +ENT.Speed = 150 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/zombie_soldier.mdl" +ENT.BaseHealth = 30 +ENT.Damage = 25 + +-- Override default death function +-- This zombie blows up on death instead of creating a ragdoll +function ENT:OnKilled(info) + if self.Exploded then return end + self.Exploded = true + hook.Run('OnNPCKilled', self, info:GetAttacker(), info:GetInflictor()) -- Run the hook + + local ed = EffectData() + ed:SetOrigin(self:GetPos()) + util.Effect("Explosion", ed, true, true) + util.BlastDamage(self, self, self:GetPos(), 175, 125) + + self:Remove() +end \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_zombie_corpse.lua b/fluffy_infection/entities/entities/npc_zombie_corpse.lua new file mode 100644 index 0000000..feff1a9 --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zombie_corpse.lua @@ -0,0 +1,19 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Corpse Walker' + +if CLIENT then + language.Add('npc_zombie_corpse', 'Corpse Walker' ) +end + +-- Speed +ENT.Speed = 150 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/corpse1.mdl" +ENT.BaseHealth = 150 +ENT.Damage = 25 \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_zombie_fast.lua b/fluffy_infection/entities/entities/npc_zombie_fast.lua new file mode 100644 index 0000000..99cdf88 --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zombie_fast.lua @@ -0,0 +1,19 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Fast Zombie' + +if CLIENT then + language.Add('npc_zombie_fast', 'Fast Zombie' ) +end + +-- Speed +ENT.Speed = 300 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/zombie_fast.mdl" +ENT.BaseHealth = 60 +ENT.Damage = 10 \ No newline at end of file diff --git a/fluffy_infection/entities/entities/npc_zombie_shadow.lua b/fluffy_infection/entities/entities/npc_zombie_shadow.lua new file mode 100644 index 0000000..28082fe --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zombie_shadow.lua @@ -0,0 +1,19 @@ +AddCSLuaFile() + +ENT.Base = 'npc_zo_base' +ENT.PrintName = 'Shadow' + +if CLIENT then + language.Add('npc_zombie_shadow', 'Shadow' ) +end + +-- Speed +ENT.Speed = 240 +ENT.WalkSpeedAnimation = 0.6 +ENT.Acceleration = 200 +ENT.MoveType = 1 + +-- Health & Other +ENT.Model = "models/player/charple.mdl" +ENT.BaseHealth = 200 +ENT.Damage = 40 \ No newline at end of file diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 5a5e510..6603912 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -2,6 +2,7 @@ AddCSLuaFile('cl_init.lua') AddCSLuaFile('shared.lua') include('shared.lua') +include('sv_zombies.lua') -- Appropiate weapon stuff function GM:PlayerLoadout( ply ) @@ -17,58 +18,79 @@ function GM:PlayerLoadout( ply ) ply:GiveAmmo(512, 'Buckshot', true) ply:GiveAmmo(1024, 'SMG1', true) - ply:SetRunSpeed(300) + ply:SetRunSpeed(225) ply:SetWalkSpeed(200) ply:SetBloodColor(BLOOD_COLOR_RED) elseif ply:Team() == TEAM_RED then -- Infected -- Initial infected are stronger but slower ply:SetBloodColor(BLOOD_COLOR_GREEN) - if ply.InitialHunter then - ply:SetMaxHealth(200) - ply:SetHealth(200) - ply:Give('weapon_fists') - ply:SetRunSpeed(400) - ply:SetWalkSpeed(200) - else - ply:Give('weapon_fists') - ply:SetRunSpeed(500) - ply:SetWalkSpeed(300) - end + ply:Give('weapon_fists') + ply:SetMaxHealth(100) + ply:SetHealth(100) + ply:SetRunSpeed(250) + ply:SetWalkSpeed(225) end end -- Pick player models function GM:PlayerSetModel( ply ) if ply:Team() == TEAM_RED then - ply:SetModel( "models/player/zombie_classic.mdl" ) + ply:SetModel("models/player/zombie_classic.mdl") else GAMEMODE.BaseClass:PlayerSetModel(ply) end end --- Humans can stil commit suicide -function GM:CanPlayerSuicide(ply) - if ply:Team() == TEAM_RED then return false end - - return true -end +-- Make everyone start as a human +hook.Add('PreRoundStart', 'InfectionResetPlayers', function() + GAMEMODE.WaveNumber = 0 + GAMEMODE.WaveTimer = 0 +end) --- Track survived rounds -function GM:StatsRoundWin(winners) - if winners == TEAM_BLUE then - for k,v in pairs(team.GetPlayers(TEAM_BLUE)) do - if v:Alive() then - GAMEMODE:AddStatPoints(v, 'survived_rounds', 1) +-- Get the last player on the human team +function GM:GetLastPlayer(exclude_player) + local last_alive = nil + for k,v in pairs( player.GetAll() ) do + if v:Alive() and v:Team() == TEAM_BLUE and !v.Spectating then + if exclude_player and v == exclude_player then continue end + if IsValid(last_alive) then + return false + else + last_alive = v end end end + return last_alive end -hook.Add('EntityTakeDamage', 'ShotgunGlobalBuff', function(target, dmg) +-- Check if there enough players to start a round +function GM:CanRoundStart() + if GAMEMODE:NumNonSpectators() >= 2 then + return true + else + return false + end +end + +-- Stat Tracking for Zombie kills +hook.Add('OnNPCKilled', 'ZombieKilledStats', function(npc, attacker, inflictor) + local class = npc:GetClass() -- not yet relevant + if attacker:IsPlayer() and attacker:Team() == TEAM_BLUE then + attacker:AddStatPoints('Zombies Killed', 1) + attacker:AddFrags(1) + end +end) + +-- 1XP for every 5 zombies defeated +hook.Add('RegisterStatsConversions', 'AddInfectionStatConversions', function() + GAMEMODE:AddStatConversion('Zombies Killed', 'Zombies Killed', 0.2) +end) + +hook.Add('EntityTakeDamage', 'FistsBuff', function(target, dmg) local wep = dmg:GetInflictor() if wep:GetClass() == 'player' then wep = wep:GetActiveWeapon() end if wep:GetClass() == "weapon_fists" then - dmg:ScaleDamage(3) + dmg:ScaleDamage(2) end end) \ No newline at end of file diff --git a/fluffy_infection/gamemode/shared.lua b/fluffy_infection/gamemode/shared.lua index 6612505..aad38e3 100644 --- a/fluffy_infection/gamemode/shared.lua +++ b/fluffy_infection/gamemode/shared.lua @@ -23,8 +23,8 @@ GM.TeamSurvival = true GM.SurvivorTeam = TEAM_BLUE GM.HunterTeam = TEAM_RED -GM.RoundNumber = 10 -- How many rounds? -GM.RoundTime = 90 -- How long should each round go for? +GM.RoundNumber = 5 -- How many rounds? +GM.RoundTime = 180 -- How long should each round go for? GM.CanSuicide = false -- Should players be able to die at will? :( GM.ThirdPersonEnabled = false -- This gamemode overrides some functions to do with this @@ -33,10 +33,10 @@ function GM:CreateTeams() if ( !GAMEMODE.TeamBased ) then return end team.SetUp( TEAM_RED, "Infected", Color( 16, 172, 82 ), true ) - team.SetSpawnPoint( TEAM_RED, {"info_player_start"} ) + team.SetSpawnPoint( TEAM_RED, {"info_player_zombie", "info_player_terrorist", "info_player_combine"} ) team.SetUp( TEAM_BLUE, "Survivors", Color( 80, 80, 255 ), true ) - team.SetSpawnPoint( TEAM_BLUE, {"info_player_start"} ) + team.SetSpawnPoint( TEAM_BLUE, {"info_player_counterterrorist", "info_player_rebel", "info_player_survivor"} ) team.SetUp( TEAM_SPECTATOR, "Spectators", Color( 255, 255, 80 ), true ) team.SetSpawnPoint( TEAM_SPECTATOR, { "info_player_start", "info_player_terrorist", "info_player_combine" } ) diff --git a/fluffy_infection/gamemode/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua new file mode 100644 index 0000000..dbcf6e7 --- /dev/null +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -0,0 +1,102 @@ +-- Generate the spawns list +-- By default merges 'info_player_zombie' and 'info_player_terrorist' +function GM:GenerateSpawns() + GAMEMODE.ZombieSpawns = ents.FindByClass('info_player_zombie') + GAMEMODE.ZombieSpawns = table.Add(GAMEMODE.ZombieSpawns, ents.FindByClass('info_player_terrorist')) +end + +-- Return the list of zombie spawns +-- Will generate if it does not exist +function GM:GetZombieSpawns() + if not GAMEMODE.ZombieSpawns then + GAMEMODE:GenerateSpawns() + end + return GAMEMODE.ZombieSpawns +end + +-- Create a given zombie entity at a random spawn +function GM:CreateZombie(ztype) + -- Limit of 50 zombies at one time + if #ents.FindByClass('npc_*') >= 50 then return end + + -- Pick a random spawn from the table (or generate it) + local spawn = table.Random(GAMEMODE:GetZombieSpawns()) + if not IsValid(spawn) then + GAMEMODE:GenerateSpawns() + return + end + + -- Create a zombie at the position + local pos = spawn:GetPos() + local zombie = ents.Create(ztype) + zombie:SetPos(pos) + zombie:Spawn() +end + +-- Definition of the different zombie types that spawn in each wave +GM.Waves = { + {'npc_zo_base', 'npc_zo_base', 'npc_zo_base'}, + {'npc_zo_base', 'npc_zo_base', 'npc_zombie_fast', 'npc_skeleton'}, + {'npc_zo_base', 'npc_zombie_fast', 'npc_zombie_fast', 'npc_skeleton'}, + {'npc_zombie_fast', 'npc_zombie_boom', 'npc_skeleton', 'npc_skeleton'}, + {'npc_zombie_boom', 'npc_zombie_corpse', 'npc_skeleton', 'npc_skeleton_mini'}, + {'npc_zombie_boom', 'npc_zombie_corpse', 'npc_skeleton_mini', 'npc_skeleton_mini'}, + {'npc_skeleton', 'npc_skeleton', 'npc_skeleton_mini', 'npc_skeleton_gold'}, + {'npc_zombie_corpse', 'npc_zombie_corpse', 'npc_zombie_boom', 'npc_zombie_shadow'}, + {'npc_skeleton_gold', 'npc_skeleton_gold', 'npc_zombie_fast', 'npc_zombie_fast'}, + {'npc_zombie_shadow', 'npc_zombie_shadow', 'npc_skeleton_gold', 'npc_skeleton_gold'}, +} +GM.WaveMax = 10 + +-- Wave scaling +-- Higher waves have more zombies - this function helps determine that slightly +function GM:WaveScaler(wave) + if wave <= 3 then + return 3 + elseif wave <= 6 then + return 4 + else + return 6 + end +end + +-- Spawn a wave of zombies +function GM:SpawnWave(number) + -- Announce the wave to all players + GAMEMODE:PulseAnnouncement(2, 'Wave ' .. number, 1) + + -- Scaling and other stuff for wave data + local wavescale = GAMEMODE:WaveScaler(number) + local playercount = math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 5) + local wave = GAMEMODE.Waves[number] + + -- Setup the timers + -- Messy I know but hey + for wi=1, wavescale do + timer.Simple(wavescale/10, function() + -- Create a random zombie for each player (max of 5) + for i=1, playercount do + local type = table.Random(wave) + GAMEMODE:CreateZombie(type) + end + end) + end +end + +-- Handle the zombie wave spawning timer +hook.Add('Think', 'ZombieTimer', function() + -- Only spawn zombies during a wave + if GetGlobalString( 'RoundState' ) != 'InRound' then return end + + -- Default values are 0 + if not GAMEMODE.WaveTimer then GAMEMODE.WaveTimer = 0 end + if not GAMEMODE.WaveNumber then GAMEMODE.WaveNumber = 0 end + if GAMEMODE.WaveNumber >= GAMEMODE.WaveMax then return end + + -- Spawn waves if the timer has hit + if CurTime() > GAMEMODE.WaveTimer then + GAMEMODE.WaveTimer = CurTime() + 15 + GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 + GAMEMODE:SpawnWave(GAMEMODE.WaveNumber) + end +end) \ No newline at end of file diff --git a/fluffy_kingmaker/gamemode/init.lua b/fluffy_kingmaker/gamemode/init.lua index 5859ecd..294ddfa 100644 --- a/fluffy_kingmaker/gamemode/init.lua +++ b/fluffy_kingmaker/gamemode/init.lua @@ -2,11 +2,6 @@ AddCSLuaFile('cl_init.lua') AddCSLuaFile('shared.lua') include('shared.lua') -hook.Add('Initialize', 'AddKingmakerStatConversions', function() - GAMEMODE:AddStatConversion('KingFrags', 'Kills as King', 0.25) - GAMEMODE:AddStatConversion('KingEliminations', 'Regicide', 1) -end) - function GM:PlayerLoadout(ply) ply:StripAmmo() ply:StripWeapons() @@ -64,8 +59,8 @@ function GM:DoPlayerDeath(ply, attacker, dmginfo) attacker:SetNWBool('IsKing', true) attacker:SetNWInt('KingPoints', attacker:GetNWInt('KingPoints', 0) + 1) attacker:AddFrags(1) - attacker:AddStatPoints('KingPoints', 1) - attacker:AddStatPoints('KingEliminations', 1) + attacker:AddStatPoints('Points', 1) + attacker:AddStatPoints('Regicide', 1) GAMEMODE:MakeKing(attacker) GAMEMODE.CurrentKing = attacker local name = string.sub(attacker:Nick(), 1, 10) @@ -78,7 +73,7 @@ function GM:DoPlayerDeath(ply, attacker, dmginfo) attacker:SetNWBool('IsKing', true) attacker:SetNWInt('KingPoints', attacker:GetNWInt('KingPoints', 0) + 1) attacker:AddFrags(1) - attacker:AddStatPoints('KingPoints', 1) + attacker:AddStatPoints('Points', 1) GAMEMODE:MakeKing(attacker) GAMEMODE.CurrentKing = attacker local name = string.sub(attacker:Nick(), 1, 10) @@ -89,7 +84,7 @@ function GM:DoPlayerDeath(ply, attacker, dmginfo) -- Do not count deaths unless in round if GetGlobalString( 'RoundState' ) != 'InRound' then return end ply:AddDeaths(1) - GAMEMODE:AddStatPoints(ply, 'deaths', 1) + ply:AddStatPoints('Death', 1) -- Delegate this to each gamemode (defaults are provided lower down for reference) GAMEMODE:HandlePlayerDeath(ply, attacker, dmginfo) @@ -113,6 +108,7 @@ hook.Add('Think', 'KingTimer', function() if IsValid(GAMEMODE.CurrentKing) then GAMEMODE.CurrentKing:AddFrags(1) GAMEMODE.CurrentKing:SetNWInt('KingPoints', GAMEMODE.CurrentKing:GetNWInt('KingPoints', 0) + 1) + GAMEMODE.CurrentKing:AddStatPoints('Points', 1) GAMEMODE.CurrentKing:EmitSound("npc/roller/code2.wav") GAMEMODE.LastKingThink = CurTime() end @@ -151,4 +147,10 @@ hook.Add('SetupPlayerVisibility', 'KingVisible', function(ply) if IsValid(GAMEMODE.CurrentKing) then AddOriginToPVS(GAMEMODE.CurrentKing:GetPos()) end -end ) \ No newline at end of file +end ) + +-- Register XP for Kingmaker +hook.Add('RegisterStatsConversions', 'AddKingmakerStatConversions', function() + GAMEMODE:AddStatConversion('Points', 'King Score', 0.05) + GAMEMODE:AddStatConversion('Regicide', 'Regicide', 2) +end) \ No newline at end of file diff --git a/fluffy_laserdance/gamemode/shared.lua b/fluffy_laserdance/gamemode/shared.lua index 46e25ca..031d4fe 100644 --- a/fluffy_laserdance/gamemode/shared.lua +++ b/fluffy_laserdance/gamemode/shared.lua @@ -24,4 +24,10 @@ GM.SpawnProtection = true function GM:Initialize() -end \ No newline at end of file +end + +-- Hide all Tracer and Trail cosmetics +-- Part of the Laser Dance mechanics is the bright lasers everywhere +hook.Add('ShouldDrawCosmetics', 'HideLaserDanceCosmetics', function(ply, ITEM) + if ITEM.Type == 'Tracer' or ITEM.Type == 'Trail' then return false end +end) \ No newline at end of file diff --git a/fluffy_mg_base/content/materials/fluffy/pattern1.png b/fluffy_mg_base/content/materials/fluffy/pattern1.png new file mode 100644 index 0000000..3a65c2a Binary files /dev/null and b/fluffy_mg_base/content/materials/fluffy/pattern1.png differ diff --git a/fluffy_mg_base/content/materials/fluffy/pattern2.png b/fluffy_mg_base/content/materials/fluffy/pattern2.png new file mode 100644 index 0000000..413d1ac Binary files /dev/null and b/fluffy_mg_base/content/materials/fluffy/pattern2.png differ diff --git a/fluffy_mg_base/content/materials/fluffy/pattern3.png b/fluffy_mg_base/content/materials/fluffy/pattern3.png new file mode 100644 index 0000000..fbb5620 Binary files /dev/null and b/fluffy_mg_base/content/materials/fluffy/pattern3.png differ diff --git a/fluffy_mg_base/content/materials/fluffy/pattern5.png b/fluffy_mg_base/content/materials/fluffy/pattern5.png new file mode 100644 index 0000000..613fba7 Binary files /dev/null and b/fluffy_mg_base/content/materials/fluffy/pattern5.png differ diff --git a/fluffy_mg_base/content/materials/fluffy/pattern6.png b/fluffy_mg_base/content/materials/fluffy/pattern6.png new file mode 100644 index 0000000..0bed9bd Binary files /dev/null and b/fluffy_mg_base/content/materials/fluffy/pattern6.png differ diff --git a/fluffy_mg_base/gamemode/cl_endgame.lua b/fluffy_mg_base/gamemode/cl_endgame.lua index 53f4fc3..1c3aacf 100644 --- a/fluffy_mg_base/gamemode/cl_endgame.lua +++ b/fluffy_mg_base/gamemode/cl_endgame.lua @@ -7,97 +7,15 @@ - Stats / leaderboard overview --]] --- Open up the end of game panel --- This is a pretty complicated thing -> maybe split into more functions one day? --- I blame derma personally -function GM:CreateMapVotePanel() - local frame = GAMEMODE.EndGamePanel - if not IsValid(frame) then return end - frame.CurrentScreen = "Map Vote" - frame.ep:Clear() - local change_button = frame.change_button - - -- This section creates the 4 cards for the mapvote - -- Do the calculations to make them sized nicely - local w = frame:GetWide() - local h = frame:GetTall() - local n = 4 - local margin = 32 - local padding = 24 - local panel_w = ( w - margin*2 - (n-1)*padding ) / n - local image_w = panel_w - 32 - local panel_h = (h-32-48) - local vote_panels = {} - - -- The big loop - for i =1,n do - -- Create a panel - local map_panel = vgui.Create('DPanel', frame.ep) - map_panel:SetSize(panel_w, panel_h) - map_panel:SetPos(margin + (panel_w+padding)*(i-1), 16) - map_panel.index = i - function map_panel:Paint(w, h) - DisableClipping(true) - draw.RoundedBox(16, -1, -1, w+4, h+5, GAMEMODE.FCol3) -- Draw the outline/shadow effect - DisableClipping(false) - - draw.RoundedBox(16, 0, 0, w, h, GAMEMODE.FCol1) - draw.RoundedBox(0, 16, 16, image_w, image_w, GAMEMODE.FCol2) - end - - if GAMEMODE.CurrentVote and GAMEMODE.CurrentVote == i then - map_panel.Selected = true - end - - table.insert(vote_panels, map_panel) - end - - change_button:SetText("Scoreboard") - function change_button:DoClick() - GAMEMODE:CreateScoreboardPanel() - end -end - -function GM:CreateItemDropPanel() - -end - -function GM:CreateScoreboardPanel() - local frame = GAMEMODE.EndGamePanel - if not IsValid(frame) then return end - frame.CurrentScreen = "Scoreboard" - frame.ep:Clear() - local change_button = frame.change_button - - change_button:SetText("Map Vote") - change_button:SetEnabled(true) - function change_button:DoClick() - GAMEMODE:CreateMapVotePanel() - end - - local w = frame:GetWide() - local h = frame.ep:GetTall() - local stats_panel = vgui.Create('DPanel', frame.ep) - stats_panel:SetSize(w/2, h) - - local scoreboard_panel = vgui.Create('DPanel', frame.ep) - scoreboard_panel:SetSize(w/2, h) - scoreboard_panel:SetPos(w/2, 0) - - local my_stats = GAMEMODE.StatsReport[LocalPlayer()] or {} - for k,v in pairs(my_stats) do - local lbl = vgui.Create('DLabel', stats_panel) - lbl:Dock(TOP) - lbl:DockMargin(4, 0, 0, 0) - lbl:SetColor(color_black) - lbl:SetText(k .. ": " .. v) - end -end - -function GM:CreateLevelUpPanel() - local frame = GAMEMODE.EndGamePanel - frame.CurrentScreen = "Level Up" -end +local sounds = { + "vo/coast/odessa/male01/nlo_cheer01.wav", + "vo/coast/odessa/male01/nlo_cheer02.wav", + "vo/coast/odessa/male01/nlo_cheer03.wav", + "vo/coast/odessa/male01/nlo_cheer04.wav", + "vo/coast/odessa/female01/nlo_cheer01.wav", + "vo/coast/odessa/female01/nlo_cheer02.wav", + "vo/coast/odessa/female01/nlo_cheer03.wav", +} function GM:OpenEndGamePanel() local frame = vgui.Create('DFrame') @@ -112,73 +30,343 @@ function GM:OpenEndGamePanel() frame:SetPopupStayAtBack(true) frame:SetKeyboardInputEnabled(false) frame.CurrentScreen = "End of Game" + frame.offx = 0 + frame.offy = 0 + frame.bar_h = 64 + frame.Background = Material('gamemodes/fluffy_mg_base/content/materials/fluffy/pattern1.png', 'noclamp') + + -- Prepare variables to do with level information + frame.CurrentXP = LocalPlayer():GetExperience() + frame.MaxXP = LocalPlayer():GetMaxExperience() + frame.Level = LocalPlayer():GetLevel() + frame.TargetXP = frame.CurrentXP + frame.XPMessage = "" + frame.XPMessageTime = nil + + local c1 = Color(0, 168, 255) + local c2 = Color(0, 151, 230) function frame:Paint(w, h) - --Derma_DrawBackgroundBlur( self, self.m_fCreateTime ) -- cool blur + local psize = 512 + self.offx = (self.offx - FrameTime()*0.1) % 1 + self.offy = (self.offy - FrameTime()*0.15) % 1 - -- Draw simple white box with bottom bar - local bar_h = 0 - surface.SetDrawColor(GAMEMODE.FCol1) - surface.DrawRect(0, 0, w, h-bar_h) - surface.SetDrawColor(GAMEMODE.FCol2) - surface.DrawRect(0, h-bar_h, w, bar_h) + -- UV stuff + -- DrawTexturedRectUV has a lot of quirks + local uw = ScrW()/psize + local vh = ScrH()/psize + uw = uw + uw/psize + vh = vh + vh/psize + + surface.SetDrawColor(color_white) + surface.SetMaterial(self.Background) + surface.DrawTexturedRectUV(0, 0, w, h, self.offx, self.offy, uw+self.offx, vh+self.offy) + + -- Draw the bar at the bottom of the panel + local bar_h = self.bar_h + self:PaintXPMessage(w, h, bar_h) + surface.SetDrawColor(c1) + draw.NoTexture() + surface.DrawTexturedRect(0, h-bar_h, w, bar_h) + + -- Draw the basic level information + local tw = draw.SimpleText('Level', 'FS_L48', 8, h - bar_h/2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + tw = tw + draw.SimpleText(self.Level, 'FS_L64', 116, h - bar_h/2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + draw.SimpleText(math.floor(self.CurrentXP) .. '/' .. self.MaxXP .. 'XP', 'FS_L32', 224, h - bar_h/2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + -- Draw the bar + local bar_empty = Color(220, 221, 225) + local bar_filled = Color(0, 151, 230) + + -- XP animation + self.CurrentXP = math.Approach(self.CurrentXP, self.TargetXP, FrameTime()*25) + if self.CurrentXP >= self.MaxXP then + self:LevelUp() + end + + local percentage = math.Clamp(self.CurrentXP/self.MaxXP, 0, 1) + local bar_fill_width = (w-480-48) + draw.RoundedBox(16, 416, h - 3*(bar_h/4), w - 480, bar_h/2, bar_empty) + draw.RoundedBox(16, 414, h - 3*(bar_h/4), 48 + (bar_fill_width*percentage), bar_h/2, bar_filled) + draw.SimpleText(math.floor(percentage*100) .. '%', 'FS_L24', 424, h - bar_h/2, color_white, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) end - GAMEMODE.EndGamePanel = frame - local sw = frame:GetWide() + function frame:PaintXPMessage(w, h, bar_h) + if not self.XPMessage or self.XPMessage == '' then return end + + -- Draw the XP message (if applicable) + local xph = math.Clamp((CurTime() - self.XPMessageTime), 0, 1) * (bar_h/2) + local ca = Color(c2.r, c2.g, c2.b, 255 - xph*2) + draw.SimpleText(self.XPMessage or '', 'FS_L32', w/2, h - bar_h - 2 + xph, ca, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) + end - local scroll_panel = vgui.Create('DPanel', frame) - scroll_panel:SetPos(0, 0) - scroll_panel:SetSize(sw*3, frame:GetTall()) - function scroll_panel:Paint() + -- Confetti effect on level up + function frame:PaintOver(w, h) + if self.LevelledUp then + if not self.Confetti then + self.Confetti = {} + for i = 1, w/3 do + local piece = {} + piece.x = i * 3 + math.random(-8, 8) + piece.y = h + 32 + piece.vx = 0 + piece.vy = math.random(-800, -100) + piece.ax = math.random(-20, 20) + piece.ay = 300 + piece.ang = math.random(0, 6) + piece.angv = math.random(-1, 1) + piece.c = HSVToColor(math.random(360), 1, 1) + table.insert(self.Confetti, piece) + end + else + for k,p in pairs(self.Confetti) do + p.vx = p.vx + p.ax * FrameTime() + p.vy = p.vy + p.ay * FrameTime() + p.x = p.x + p.vx * FrameTime() + p.y = p.y + p.vy * FrameTime() + p.ang = p.ang + p.angv + draw.NoTexture() + surface.SetDrawColor(p.c) + surface.DrawTexturedRectRotated(p.x, p.y, 12, 20, p.ang) + end + end + end + end + function frame:AddXP(amount, reason) + if (self.TargetXP or self.CurrentXP) > self.CurrentXP then + self.CurrentXP = self.TargetXP + end + self.TargetXP = self.CurrentXP + amount + self.XPMessage = "+" .. amount .. "XP: " .. reason + self.XPMessageTime = (CurTime() + amount/25) + + local percentage = math.Clamp(self.TargetXP/self.MaxXP, 0, 1) + local pitch = 150 + (percentage*100) + LocalPlayer():EmitSound('ambient/alarms/warningbell1.wav', 75, pitch) end - frame.ep = scroll_panel - local test = vgui.Create("Screen_Experience", scroll_panel) - test:SetPos(0, 0) - GAMEMODE.ExperienceScreen = test + function frame:LevelUp() + self.Level = self.Level + 1 + self.CurrentXP = 0 + self.TargetXP = (self.TargetXP or self.MaxXP) - self.MaxXP + self.LevelledUp = true - local test2 = vgui.Create("Screen_Maps", scroll_panel) - test2:SetPos(sw, 0) - GAMEMODE.MapVoteScreen = test2 + local sound = table.Random(sounds) + LocalPlayer():EmitSound(sound, 75, math.random(95, 115)) + end + + function frame:ProcessXP(tbl) + local ttime = 1 + for k,v in pairs(tbl) do + local amount = v[3] + local reason = v[1] + if amount == 0 then continue end + timer.Simple(ttime, function() self:AddXP(amount, reason) end) + ttime = ttime + (amount/25) + 1.25 + end - local test3 = vgui.Create("Screen_Scoreboard", scroll_panel) - test3:SetPos(sw*2, 0) - GAMEMODE.ScoreboardScreen = test3 + --timer.Simple(ttime + 2.5, function() self:GetParent():TriggerMapVote() end) + end - local function inQuad(fraction, beginning, change) - if fraction < 0.5 then - return change * (2*fraction^2) + beginning - else - return change * (-1 + (4-2*fraction)*fraction) + beginning + function frame:ProcessStatisticsTable(tbl) + local duration = 20 + local stat_duration = duration/(table.Count(tbl)+1) + local i = 1 + for k,v in pairs(tbl) do + local statistic = k + local data = v + timer.Simple(i * stat_duration, function() self:UpdateScoreboardStat(statistic, data) end) + i = i + 1 end + + timer.Simple(duration, function() self:DisplayMapVote() end) end - local anim = Derma_Anim("EaseInQuad", scroll_panel, function(p, a, delta, data) - p:SetPos( inQuad(delta, 0, -sw), 0 ) - end) + function frame:UpdateScoreboardStat(category, data) + self.scoreboard.ScoreboardMessage = category + self.scoreboard:Clear() + self.scoreboard.ShowTeams = false + self.scoreboard.yy = 56 + + for k,v in pairs(data) do + self.scoreboard:CreatePlayerRow(k, v[1], v[2], false) + end + end - local anim2 = Derma_Anim("EaseInQuad", scroll_panel, function(p, a2, delta, data) - p:SetPos( inQuad(delta, -sw, -sw), 0 ) - end) + function frame:SetMapVoteOptions(tbl) + self.MapVoteOptions = tbl + end + + function frame:DisplayMapVote() + self.scoreboard:Remove() + if not self.MapVoteOptions then return end + + self.VotePanels = {} + local margin = 32 + local box = ((self:GetTall() - self.bar_h) - (3*margin))/2 + local gap = self:GetWide() - (3*box) - (2*margin) + gap = gap/2 + + for j = 1,2 do + local yy = margin + if j == 2 then yy = (self:GetTall()-self.bar_h) - box - margin end + + for i = 1,3 do + local map = vgui.Create('MapVotePanel', self) + map:SetSize(box, box) + map:SetPos(margin + (box+gap)*(i-1), yy) + map:AddChildren() + map:SetIndex(i + (j-1)*3) + self.VotePanels[i + (j-1)*3] = map + + if self.MapVoteOptions then + map:SetOptions(self.MapVoteOptions[i + (j-1)*3]) + end + end + end + end + + GAMEMODE.EndGamePanel = frame + + local scoreboard = vgui.Create('DPanel', frame) + local h = ScrH() - 64 - (40*2) + local w = h * 0.75 + scoreboard:SetSize(w, h) + scoreboard:SetPos(ScrW()/2 - w/2, 40) + scoreboard.ScoreboardMessage = 'Scoreboard' + scoreboard.ShowTeams = true + scoreboard.yy = 56 + frame.scoreboard = scoreboard + function scoreboard:Paint(w, h) + DisableClipping(true) + draw.RoundedBox(16, -4, -4, w+8, h+8, Color(0, 168, 255)) + DisableClipping(false) + draw.RoundedBox(16, 0, 0, w, h, color_white) + + -- Draw the header of the scoreboard + draw.SimpleText(self.ScoreboardMessage, 'FS_L48', w/2, 4, c1, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) + + -- Paint Team Info + self:PaintTeamInfo() + end - function scroll_panel:TriggerMapVote() - anim:Start(1) - frame.CurrentScreen = 'MapVote' + if GAMEMODE.TeamBased and scoreboard.ShowTeams then + scoreboard.yy = 112 end - function scroll_panel:TriggerScoreboard() - anim2:Start(1) - frame.CurrentScreen = 'Scoreboard' + function scoreboard:PaintTeamInfo() + if GAMEMODE.TeamBased and scoreboard.ShowTeams then + local tab_width = (w-40)/2 + draw.RoundedBox(8, 16, 48, tab_width, 52, team.GetColor(1)) + draw.SimpleText(team.GetName(1), 'FS_32', 24, 48, color_white) + draw.SimpleText('Kills: ' .. team.TotalFrags(1), 'FS_16', 24, 72, color_white) + draw.SimpleText(team.GetScore(1), 'FS_60', 16 + tab_width - 4, 72, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + + local t2 = w - tab_width - 16 + draw.RoundedBox(8, t2, 48, tab_width, 52, team.GetColor(2)) + draw.SimpleText(team.GetName(2), 'FS_32', t2+8, 48, color_white, TEXT_ALIGN_LEFT ) + draw.SimpleText('Kills: ' .. team.TotalFrags(2), 'FS_16', t2+8, 72, color_white, TEXT_ALIGN_LEFT ) + draw.SimpleText(team.GetScore(2), 'FS_60', t2 + tab_width - 4, 72, color_white, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER ) + end end - scroll_panel.Think = function(self) - if anim:Active() then anim:Run() end - if anim2:Active() then anim2:Run() end + local medal_icons = {Material('icon16/medal_gold_2.png'), Material('icon16/medal_silver_2.png'), Material('icon16/medal_bronze_2.png')} + + function scoreboard:CreatePlayerRow(k, ply, num, detailed) + local row = vgui.Create('DPanel', self) + --row:SetSize(self:GetWide() - 32, 52) + row:SetPos(16, self.yy) + row.Value = num + row.Player = ply + row.Position = k + + if detailed then + function row:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(236, 240, 241)) + draw.SimpleText(self.Player:Nick() or 'Player?', 'FS_32', 72, h/2 + 2, c1, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + local ty = h/2 + + -- Draw team information + if GAMEMODE.TeamBased then + local pt = self.Player:Team() + local name = 'None' + -- WHY DO GOOD PEOPLE DO BAD THINGS WTF + if pt == 1 then name = 'Red' end + if pt == 2 then name = 'Blue' end + if pt == 1002 then name = 'Spec' end + draw.SimpleText(name, 'FS_24', 400, ty - 20, team.GetColor(pt), TEXT_ALIGN_CENTER) + draw.SimpleText('Team', 'FS_16', 400, ty + 4, c1, TEXT_ALIGN_CENTER) + end + + -- Draw the score + draw.SimpleText(self.Player:Frags(), 'FS_32', 475, ty - 24, c1, TEXT_ALIGN_CENTER) + draw.SimpleText('Score', 'FS_16', 475, ty + 4, c1, TEXT_ALIGN_CENTER) + + -- Draw the deaths + draw.SimpleText(self.Player:Deaths(), 'FS_32', 550, ty - 24, c1, TEXT_ALIGN_CENTER) + draw.SimpleText('Deaths', 'FS_16', 550, ty + 4, c1, TEXT_ALIGN_CENTER) + end + else + function row:Paint(w, h) + draw.RoundedBox(8, 0, 0, w, h, Color(236, 240, 241)) + draw.SimpleText(self.Player:Nick() or 'Player?', 'FS_32', 72, h/2 + 2, c1, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) + + if k == 1 then + draw.SimpleText(num or 0, 'FS_56', w - 36, h/2 + 1, c1, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + else + draw.SimpleText(num or 0, 'FS_60', w - 32, h/2 + 1, c1, TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER) + end + end + end + + function row:PaintOver(w, h) + if self.Position <= 3 then + surface.SetDrawColor(color_white) + surface.SetMaterial(medal_icons[self.Position]) + surface.DrawTexturedRect(2, 0, 16, 16) + end + end + + -- Add the avatar + row.Avatar = row:Add('AvatarCircle') + if IsValid(row.Avatar) then + row.Avatar.Avatar:SetPlayer(ply, 64) -- Don't ask + row.Avatar:SetPos(2, 2) + --row.Avatar:SetSize(48, 48) + end + + if k == 1 then + row:SetSize(self:GetWide() - 32, 64) + row.Avatar:SetSize(60, 60) + self.yy = self.yy + 72 + else + row:SetSize(self:GetWide() - 32, 52) + row.Avatar:SetSize(48, 48) + self.yy = self.yy + 60 + end end - frame.CurrentScreen = 'Experience' + -- Start off with the default scoreboard display + local players = player.GetAll() + table.sort(players, function(a, b) return a:Frags() > b:Frags() end) + for k,v in pairs(players) do + scoreboard:CreatePlayerRow(k, v, v:Frags(), true) + end +end + +function TestEndGameScreen() + GAMEMODE:OpenEndGamePanel() + timer.Simple(1, function() + local stats = { + {'Thanks for playing!', nil, 25}, + {'Rounds Won', nil, 10}, + {'Crates Broken', nil, 45}, + {'lol filler text', nil, 30}, + } + GAMEMODE.EndGamePanel:ProcessXP(stats) + end) end -- Open up the end game panel when the server says the game has ended @@ -189,13 +377,17 @@ end ) -- Get the map vote options information from the server net.Receive("SendMapVoteTable", function() GAMEMODE.VotingOptions = net.ReadTable() - GAMEMODE.MapVoteScreen:SetVotes(GAMEMODE.VotingOptions) + GAMEMODE.EndGamePanel:SetMapVoteOptions(GAMEMODE.VotingOptions) + --GAMEMODE.MapVoteScreen:SetVotes(GAMEMODE.VotingOptions) end) -- Get the stats report information from the server net.Receive("SendExperienceTable", function() + GAMEMODE.XPReport = net.ReadTable() GAMEMODE.StatsReport = net.ReadTable() - if GAMEMODE.ExperienceScreen then - GAMEMODE.ExperienceScreen:ProcessXP(GAMEMODE.StatsReport) + + if GAMEMODE.EndGamePanel then + GAMEMODE.EndGamePanel:ProcessXP(GAMEMODE.XPReport) + GAMEMODE.EndGamePanel:ProcessStatisticsTable(GAMEMODE.StatsReport) end end) \ No newline at end of file diff --git a/fluffy_mg_base/gamemode/cl_init.lua b/fluffy_mg_base/gamemode/cl_init.lua index 935d246..8b3a14e 100644 --- a/fluffy_mg_base/gamemode/cl_init.lua +++ b/fluffy_mg_base/gamemode/cl_init.lua @@ -16,9 +16,6 @@ include('cl_announcements.lua') include('vgui/avatar_circle.lua') include('vgui/MapVotePanel.lua') -include('vgui/Screen_Experience.lua') -include('vgui/Screen_Maps.lua') -include('vgui/Screen_Scoreboard.lua') -- Register universal fonts -- Coolvetica @@ -42,6 +39,12 @@ surface.CreateFont("FS_60", { font = "Coolvetica", size = 48, -- hmmm }) + +surface.CreateFont("FS_56", { + font = "Coolvetica", + size = 56, +}) + surface.CreateFont("FS_64", { font = "Coolvetica", size = 64, diff --git a/fluffy_mg_base/gamemode/gametype_hunter.lua b/fluffy_mg_base/gamemode/gametype_hunter.lua index ae1de01..4b3f083 100644 --- a/fluffy_mg_base/gamemode/gametype_hunter.lua +++ b/fluffy_mg_base/gamemode/gametype_hunter.lua @@ -79,17 +79,4 @@ hook.Add('DoPlayerDeath', 'AwardLastSurvivor', function(ply) GAMEMODE:PulseAnnouncement(4, name .. ' is the lone survivor!', 0.8) last_player:AddStatPoints('LastSurvivor', 1) end -end) - --- Disable cosmetics -hook.Add('ShouldDrawCosmetics', 'HideHunterCosmetics', function(ply, ITEM) - if GAMEMODE.TeamSurvival then - -- Cosmetics shouldn't show for the Hunter Team (in most cases) - -- Override in some cases - if ply:Team() == GAMEMODE.HunterTeam then - return false - else - return true - end - end end) \ No newline at end of file diff --git a/fluffy_mg_base/gamemode/init.lua b/fluffy_mg_base/gamemode/init.lua index 7093b40..ca96857 100644 --- a/fluffy_mg_base/gamemode/init.lua +++ b/fluffy_mg_base/gamemode/init.lua @@ -18,9 +18,6 @@ AddCSLuaFile('cl_announcements.lua') AddCSLuaFile('vgui/avatar_circle.lua') AddCSLuaFile('vgui/MapVotePanel.lua') -AddCSLuaFile('vgui/Screen_Experience.lua') -AddCSLuaFile('vgui/Screen_Maps.lua') -AddCSLuaFile('vgui/Screen_Scoreboard.lua') AddCSLuaFile('shared.lua') AddCSLuaFile('sound_tables.lua') @@ -28,8 +25,9 @@ AddCSLuaFile('sh_levels.lua') -- Add workshop content resource.AddWorkshop('1518438705') -resource.AddFile('resource/fonts/BebasKai.ttf') -resource.AddFile('resource/fonts/LemonMilk.ttf') +resource.AddFile('gamemodes/fluffy_mg_base/content/resource/fonts/BebasKai.ttf') +resource.AddFile('gamemodes/fluffy_mg_base/content/resource/fonts/LemonMilk.ttf') +resource.AddFile('gamemodes/fluffy_mg_base/content/materials/fluffy/pattern1.png') -- Include useful server files include('shared.lua') @@ -170,7 +168,7 @@ function GM:DoPlayerDeath(ply, attacker, dmginfo) -- Do not count deaths unless in round if GetGlobalString( 'RoundState' ) != 'InRound' then return end ply:AddDeaths(1) - GAMEMODE:AddStatPoints(ply, 'deaths', 1) + GAMEMODE:AddStatPoints(ply, 'Deaths', 1) -- Delegate this to each gamemode (defaults are provided lower down for reference) GAMEMODE:HandlePlayerDeath(ply, attacker, dmginfo) @@ -331,7 +329,7 @@ function GM:HandlePlayerDeath(ply, attacker, dmginfo) -- Add the frag to scoreboard attacker:AddFrags(GAMEMODE.KillValue) - GAMEMODE:AddStatPoints(attacker, 'kills', 1) + GAMEMODE:AddStatPoints(attacker, 'Kills', 1) if GAMEMODE.TeamBased then -- Add the kill to the team @@ -347,8 +345,11 @@ end hook.Add('EntityTakeDamage', 'ShotgunGlobalBuff', function(target, dmg) local wep = dmg:GetInflictor() + + if not IsValid(wep) then return end if wep:GetClass() == 'player' then wep = wep:GetActiveWeapon() end if not IsValid(wep) then return end + if wep:GetClass() == "weapon_shotgun" then dmg:ScaleDamage(2) end diff --git a/fluffy_mg_base/gamemode/shared.lua b/fluffy_mg_base/gamemode/shared.lua index f78f96f..581f8bb 100644 --- a/fluffy_mg_base/gamemode/shared.lua +++ b/fluffy_mg_base/gamemode/shared.lua @@ -86,12 +86,6 @@ function GM:CreateTeams() team.SetSpawnPoint(TEAM_SPECTATOR, {"info_player_terrorist", "info_player_combine", "info_player_counterterrorist", "info_player_rebel"}) end --- Function to toggle displaying cosmetics --- Obviously, cosmetic items shouldn't be displayed on barrels etc. -function GM:ShouldDrawCosmetics(ply, ITEM) - return hook.Run('DrawCosmeticsCheck', ply, ITEM) or true -end - -- Valid playermodels GM.ValidModels = { male01 = "models/player/Group01/male_01.mdl", diff --git a/fluffy_mg_base/gamemode/shop/cl_inventory.lua b/fluffy_mg_base/gamemode/shop/cl_inventory.lua index cd44bfd..20fe1ea 100644 --- a/fluffy_mg_base/gamemode/shop/cl_inventory.lua +++ b/fluffy_mg_base/gamemode/shop/cl_inventory.lua @@ -66,7 +66,7 @@ function SHOP:PopulateSettings() end function SHOP:OpenInventory() - if true then return end -- disable this function for now + if not LocalPlayer():IsSuperAdmin() then return end -- disable this function for now if IsValid(SHOP.InventoryPanel) then return end SHOP:VerifyInventory() diff --git a/fluffy_mg_base/gamemode/shop/cl_render.lua b/fluffy_mg_base/gamemode/shop/cl_render.lua index b28afad..df59472 100644 --- a/fluffy_mg_base/gamemode/shop/cl_render.lua +++ b/fluffy_mg_base/gamemode/shop/cl_render.lua @@ -6,7 +6,7 @@ function SHOP:RenderCosmetics(ent, ply, force) if not SHOP.ClientModels[ply] then return end for _, ITEM in pairs(SHOP.ClientModels[ply]) do - if not GAMEMODE:ShouldDrawCosmetics(ply, ITEM) and not force then continue end + if not GAMEMODE:DoCosmeticsCheck(ply, ITEM) and not force then continue end if not ITEM.ent then continue end -- Search for the attachment and calculate the position & angles diff --git a/fluffy_mg_base/gamemode/shop/sh_init.lua b/fluffy_mg_base/gamemode/shop/sh_init.lua index 7febbca..3fea86e 100644 --- a/fluffy_mg_base/gamemode/shop/sh_init.lua +++ b/fluffy_mg_base/gamemode/shop/sh_init.lua @@ -91,6 +91,13 @@ function SHOP:LoadResources() include('fluffy_mg_base/gamemode/shop/item/paint_master.lua') end +-- Add the cosmetics check hook function +-- This function is hooked into various gamemodes to stop cosmetics from being drawn when they shouldn't be +-- Key examples of this is to override tracers, hide hats on certain playermodels, etc. +function GM:DoCosmeticsCheck(ply, ITEM) + return hook.Run('ShouldDrawCosmetics', ply, ITEM) or true +end + SHOP:LoadResources() if SERVER then diff --git a/fluffy_mg_base/gamemode/shop/sv_equip.lua b/fluffy_mg_base/gamemode/shop/sv_equip.lua index 485c376..24655a9 100644 --- a/fluffy_mg_base/gamemode/shop/sv_equip.lua +++ b/fluffy_mg_base/gamemode/shop/sv_equip.lua @@ -38,9 +38,10 @@ function SHOP:UnequipTrail(ply) end -- Wear a trail -function SHOP:WearTrail(ply) +function SHOP:WearTrail(ply, force) if ply.EquippedTrail then local ITEM = ply.EquippedTrail + if not GAMEMODE:DoCosmeticsCheck(ply, ITEM) and not force then return end ply.TrailEntity = util.SpriteTrail(ply, 0, ITEM.Color or color_white, false, 20, 2, 2.5, 0.1, ITEM.Material) end end @@ -73,6 +74,8 @@ hook.Add('EntityFireBullets', 'ShopTracerEffects', function(ent, data) if !ent:IsPlayer() then return end local effect = ent:GetNWString('ShopTracerEffect') if not effect then return end + + if not GAMEMODE:DoCosmeticsCheck(ent, {Type='Tracer'}) then return end data.Tracer = 1 data.TracerName = effect diff --git a/fluffy_mg_base/gamemode/sv_levels.lua b/fluffy_mg_base/gamemode/sv_levels.lua index 823a656..a231f72 100644 --- a/fluffy_mg_base/gamemode/sv_levels.lua +++ b/fluffy_mg_base/gamemode/sv_levels.lua @@ -96,6 +96,7 @@ end -- Load the player level from database on join hook.Add('PlayerInitialSpawn', 'LoadMinigamesLevelData', function(ply) ply:LoadLevelFromDB() + ply:LoadStatsFromDB() end ) -- Complicated methods of converting the various tracked stats to XP below @@ -114,10 +115,12 @@ hook.Add('Initialize', 'AddBaseStatConversions', function() end) hook.Add('RegisterStatsConversions', 'AddBaseStatConversions', function() - GAMEMODE:AddStatConversion('RoundWins', 'Rounds Won', 0) - GAMEMODE:AddStatConversion('RoundsPlayed', 'Thanks for playing!', 1.5) - GAMEMODE:AddStatConversion('kills', 'Kills', 1) - GAMEMODE:AddStatConversion('LastSurvivor', 'Last Survivor Bonus', 5) + GAMEMODE:AddStatConversion('Rounds Won', 'Rounds Won', 0) + GAMEMODE:AddStatConversion('Rounds Played', 'Thanks for playing!', 1.5) + GAMEMODE:AddStatConversion('Kills', 'Kills', 1) + GAMEMODE:AddStatConversion('Last Survivor', 'Last Survivor Bonus', 5) + GAMEMODE:AddStatConversion('Deaths', 'Deaths', 0) + GAMEMODE:AddStatConversion('Survived Rounds', 'Rounds Survived', 1) end) -- Convert a stat name & score to a table with XP diff --git a/fluffy_mg_base/gamemode/sv_player.lua b/fluffy_mg_base/gamemode/sv_player.lua index b4c2df1..ae08369 100644 --- a/fluffy_mg_base/gamemode/sv_player.lua +++ b/fluffy_mg_base/gamemode/sv_player.lua @@ -57,14 +57,12 @@ function GM:PlayerSetModel(ply) return end else - if ply.FFAColor then - local c = Vector(ply.FFAColor.r/255, ply.FFAColor.g/255, ply.FFAColor.b/255) - ply:SetPlayerColor(c) - else + if not ply.FFAColor then ply.FFAColor = HSVToColor(math.random(360), 1, 1) - local c = Vector(ply.FFAColor.r/255, ply.FFAColor.g/255, ply.FFAColor.b/255) - ply:SetPlayerColor(c) end + + local c = Vector(ply.FFAColor.r/255, ply.FFAColor.g/255, ply.FFAColor.b/255) + ply:SetPlayerColor(c) end end diff --git a/fluffy_mg_base/gamemode/sv_round.lua b/fluffy_mg_base/gamemode/sv_round.lua index 89b029b..ccf83b6 100644 --- a/fluffy_mg_base/gamemode/sv_round.lua +++ b/fluffy_mg_base/gamemode/sv_round.lua @@ -101,7 +101,7 @@ function GM:PreStartRound() v.FFAKills = 0 if (not GAMEMODE.TeamBased) or (GAMEMODE.TeamBased and v:Team() != TEAM_UNASSIGNED and v:Team() != TEAM_SPECTATOR) then - v:AddStatPoints('RoundsPlayed', 1) + v:AddStatPoints('Rounds Played', 1) end end @@ -203,19 +203,19 @@ end) function GM:StatsRoundWin(winners) if IsEntity(winners) then if winners:IsPlayer() then - winners:AddStatPoints('RoundWins', 1) + winners:AddStatPoints('Rounds Won', 1) end elseif type(winners) == 'number' then if winners > 0 then for k,v in pairs(team.GetPlayers(winners)) do - v:AddStatPoints('RoundWins', 1) + v:AddStatPoints('Rounds Won', 1) end end elseif type(winners) == 'table' then for k,v in pairs(winners) do if not IsEntity(v) then continue end if not v:IsPlayer() then continue end - v:AddStatPoints('RoundWins', 1) + v:AddStatPoints('Rounds Won', 1) end end end diff --git a/fluffy_mg_base/gamemode/sv_stats.lua b/fluffy_mg_base/gamemode/sv_stats.lua index bc81183..e856b7e 100644 --- a/fluffy_mg_base/gamemode/sv_stats.lua +++ b/fluffy_mg_base/gamemode/sv_stats.lua @@ -6,6 +6,15 @@ ]]-- GM.StatsTracking = {} +-- Prepare some prepared queries to make database stuff faster and more secure +hook.Add('InitPostEntity', 'PrepareStatsStuff', function() + local db = GAMEMODE:CheckDBConnection() + if not db then return end + GAMEMODE.MinigamesPQueries['getstats'] = db:prepare("SELECT stats FROM stats_minigames WHERE `steamid64` = ? AND `gamemode` = ?;") + GAMEMODE.MinigamesPQueries['addnewstats'] = db:prepare('INSERT INTO stats_minigames VALUES(?, ?, "{}");') + GAMEMODE.MinigamesPQueries['updatestats'] = db:prepare("UPDATE stats_minigames SET `stats` = ? WHERE `steamid64` = ? AND `gamemode` = ?;") +end) + -- Add some points to a given statistic function GM:AddStatPoints(ply, stat, amount) -- Create player index if not there @@ -55,6 +64,36 @@ function GM:GetStatWinner(stat) return {winning_player, highest_score} end +-- Generate a table of scores for each stat +function GM:GenerateStatisticsTable() + if not GAMEMODE.StatConversions then return end + + -- Iterate over all the stat types to generate the table + local tbl = {} + for stat,_ in pairs(GAMEMODE.StatConversions) do + if stat == 'Deaths' or stat == 'Rounds Played' then continue end + local passed = false + local stat_tbl = {} + + -- Get the results for each player + for k,v in pairs(player.GetAll()) do + local value = v:GetStat(stat) or 0 + table.insert(stat_tbl, {v, value}) + + if value > 0 then + passed = true + end + end + + -- Sort & store if there is at least one non-zero value + if passed then + table.sort(stat_tbl, function(a, b) return a[2] > b[2] end) + tbl[stat] = stat_tbl + end + end + return tbl +end + -- Meta functions for player stats -- Call the corresponding functions above - see those for more information local meta = FindMetaTable("Player") @@ -68,4 +107,78 @@ end function meta:AddStatPoints(stat, amount) return GAMEMODE:AddStatPoints(self, stat, amount) +end + +function meta:LoadStatsFromDB() + if not self:SteamID64() or self:IsBot() then return end + local ply = self + + -- Prepare the query + local q = GAMEMODE.MinigamesPQueries['getstats'] + if not q then return end + q:setString(1, self:SteamID64()) + q:setString(2, string.Replace(GAMEMODE_NAME, 'fluffy_', '')) + + -- Success function + function q:onSuccess(data) + if type(data) == 'table' and #data > 0 then + -- Load information from DB + ply.GamemodeDBStatsTable = util.JSONToTable(data[1]['stats']) + else + -- Add new blank row into the table + local q = GAMEMODE.MinigamesPQueries['addnewstats'] + q:setString(1, ply:SteamID64()) + q:setString(2, string.Replace(GAMEMODE_NAME, 'fluffy_', '')) + function q:onError(err) + print(err) + end + q:start() + + ply.GamemodeDBStatsTable = {} + end + end + + -- Print error if any occur (they shouldn't) + function q:onError(err) + print(err) + end + q:start() +end + +function meta:UpdateStatsToDB() + if not self:SteamID64() or self:IsBot() then return end + local ply = self + + -- Copy the current gamemode stats table + -- Then add any new data to the table + if not ply.GamemodeDBStatsTable then return end + local new_table = table.Copy(ply.GamemodeDBStatsTable) + for k,v in pairs(self:GetStatTable()) do + if not new_table[k] then + new_table[k] = v + else + new_table[k] = new_table[k] + v + end + end + + -- Convert table to json form + local json = util.TableToJSON(new_table, false) + + -- Prepare the query + local q = GAMEMODE.MinigamesPQueries['updatestats'] + if not q then return end + q:setString(1, json) + q:setString(2, self:SteamID64()) + q:setString(3, string.Replace(GAMEMODE_NAME, 'fluffy_', '')) + + -- Success function + function q:onSuccess(data) + -- done + end + + -- Print error if any occur (they shouldn't) + function q:onError(err) + print(err) + end + q:start() end \ No newline at end of file diff --git a/fluffy_mg_base/gamemode/sv_voting.lua b/fluffy_mg_base/gamemode/sv_voting.lua index f42a171..a12c645 100644 --- a/fluffy_mg_base/gamemode/sv_voting.lua +++ b/fluffy_mg_base/gamemode/sv_voting.lua @@ -107,10 +107,13 @@ function GM:StartVoting() -- Handle the stats queue for k,v in pairs(player.GetAll()) do local tbl = v:ConvertStatsToExperience() + local tbl2 = GAMEMODE:GenerateStatisticsTable() net.Start('SendExperienceTable') net.WriteTable(tbl) + net.WriteTable(tbl2) net.Send(v) v:ProcessLevels() + v:UpdateStatsToDB() end -- 30 seconds of voting time diff --git a/fluffy_mg_base/gamemode/vgui/Screen_Experience.lua b/fluffy_mg_base/gamemode/vgui/Screen_Experience.lua deleted file mode 100644 index 5f2c0e0..0000000 --- a/fluffy_mg_base/gamemode/vgui/Screen_Experience.lua +++ /dev/null @@ -1,115 +0,0 @@ -local PANEL = {} - -local sounds = { - "vo/coast/odessa/male01/nlo_cheer01.wav", - "vo/coast/odessa/male01/nlo_cheer02.wav", - "vo/coast/odessa/male01/nlo_cheer03.wav", - "vo/coast/odessa/male01/nlo_cheer04.wav", - "vo/coast/odessa/female01/nlo_cheer01.wav", - "vo/coast/odessa/female01/nlo_cheer02.wav", - "vo/coast/odessa/female01/nlo_cheer03.wav", -} - -function PANEL:Init() - local w = self:GetParent():GetWide() - local h = self:GetParent():GetTall() - self:SetSize(w/3, h) - - self.CurrentXP = LocalPlayer():GetExperience() - self.MaxXP = LocalPlayer():GetMaxExperience() - self.Level = LocalPlayer():GetLevel() - - self.TargetXP = self.CurrentXP - self.XPMessage = "" -end - -function PANEL:Paint(w, h) - local tw = draw.SimpleText(self.Level, "FS_128", w/2, h/3, GAMEMODE.FCol2, TEXT_ALIGN_CENTER, TEXT_ALIGN_BOTTOM) - draw.SimpleText("Level", "FS_40", w/2, h/3 - 24, GAMEMODE.FCol2, TEXT_ALIGN_CENTER, TEXT_ALIGN_TOP) - - self.CurrentXP = math.Approach(self.CurrentXP, self.TargetXP, FrameTime()*25) - if self.CurrentXP >= self.MaxXP then - self:LevelUp() - end - local percentage = math.Clamp(self.CurrentXP/self.MaxXP, 0, 1) - - local bar_h = 32 - draw.RoundedBox(16, w/4, 2*h/3, w/2, bar_h, GAMEMODE.FCol3) - draw.RoundedBox(16, w/4, 2*h/3, math.Clamp(w/2 * percentage, 24, w/2), bar_h, GAMEMODE.FCol2) - - draw.SimpleText(self.XPMessage, "FS_32", w/4 + 16, 2*h/3, GAMEMODE.FCol2, TEXT_ALIGN_LEFT, TEXT_ALIGN_BOTTOM) - draw.SimpleText(math.floor(percentage*100) .. "%", "FS_32", w/4 + 16, 2*h/3 + bar_h/2 + 2, GAMEMODE.FCol1, TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER) -end - -function PANEL:PaintOver(w, h) - if self.LevelledUp then - if not self.Confetti then - self.Confetti = {} - for i = 1, w/3 do - local piece = {} - piece.x = i * 3 + math.random(-8, 8) - piece.y = h + 32 - piece.vx = 0 - piece.vy = math.random(-800, -100) - piece.ax = math.random(-20, 20) - piece.ay = 300 - piece.ang = math.random(0, 6) - piece.angv = math.random(-1, 1) - piece.c = HSVToColor(math.random(360), 1, 1) - table.insert(self.Confetti, piece) - end - else - for k,p in pairs(self.Confetti) do - p.vx = p.vx + p.ax * FrameTime() - p.vy = p.vy + p.ay * FrameTime() - p.x = p.x + p.vx * FrameTime() - p.y = p.y + p.vy * FrameTime() - p.ang = p.ang + p.angv - draw.NoTexture() - surface.SetDrawColor(p.c) - surface.DrawTexturedRectRotated(p.x, p.y, 12, 20, p.ang) - end - end - end -end - -function PANEL:AddXP(amount, reason) - if self.TargetXP > self.CurrentXP then - self.CurrentXP = self.TargetXP - end - self.TargetXP = self.CurrentXP + amount - self.XPMessage = "+" .. amount .. "XP: " .. reason - - local percentage = math.Clamp(self.TargetXP/self.MaxXP, 0, 1) - local pitch = 150 + (percentage*100) - LocalPlayer():EmitSound('ambient/alarms/warningbell1.wav', 75, pitch) -end - -function PANEL:LevelUp() - self.Level = self.Level + 1 - self.CurrentXP = 0 - self.TargetXP = self.TargetXP - self.MaxXP - self.LevelledUp = true - - local sound = table.Random(sounds) - LocalPlayer():EmitSound(sound, 75, math.random(100, 125)) -end - -function PANEL:ProcessXP(tbl) - local ttime = 1 - for k,v in pairs(tbl) do - local amount = v[3] - local reason = v[1] - if amount == 0 then continue end - timer.Simple(ttime, function() self:AddXP(amount, reason) end) - ttime = ttime + (amount/25) + 1.25 - end - - timer.Simple(ttime + 2.5, function() self:GetParent():TriggerMapVote() end) -end - --- ambient/alarms/warningbell1.wav --- ambient/levels/canals/windchine1.wav --- buttons/blip1.wav - -vgui.Register("Screen_Experience", PANEL, "Panel") \ No newline at end of file diff --git a/fluffy_mg_base/gamemode/vgui/Screen_Maps.lua b/fluffy_mg_base/gamemode/vgui/Screen_Maps.lua deleted file mode 100644 index 5c755f3..0000000 --- a/fluffy_mg_base/gamemode/vgui/Screen_Maps.lua +++ /dev/null @@ -1,47 +0,0 @@ -local PANEL = {} - -function PANEL:Init() - local w = self:GetParent():GetWide() - local h = self:GetParent():GetTall() - - self:SetSize(w/3, h) - - w = w/3 - - local n = 3 - local margin = 32 - local padding = 24 - - local panel_h = (h/2) - 32 - 48 - - local panel_w = (w - margin*2 - (n-1)*padding ) / n - - self.VotePanels = {} - - for j = 1,2 do - local yy = 16 - if j == 2 then yy = h - panel_h - 16 end - - for i = 1,3 do - local map = vgui.Create("MapVotePanel", self) - map:SetSize(panel_h, panel_h) - map:SetPos(margin + (panel_w+padding)*(i-1), yy) - map:AddChildren() - map:SetIndex(i + (j-1)*3) - self.VotePanels[i + (j-1)*3] = map - end - end -end - -function PANEL:Paint(w, h) - -end - -function PANEL:SetVotes(options) - for i,o in pairs(options) do - if not self.VotePanels[i] then continue end - self.VotePanels[i]:SetOptions(o) - end -end - -vgui.Register("Screen_Maps", PANEL, "Panel") \ No newline at end of file diff --git a/fluffy_mg_base/gamemode/vgui/Screen_Scoreboard.lua b/fluffy_mg_base/gamemode/vgui/Screen_Scoreboard.lua deleted file mode 100644 index 28cc4ca..0000000 --- a/fluffy_mg_base/gamemode/vgui/Screen_Scoreboard.lua +++ /dev/null @@ -1,13 +0,0 @@ -local PANEL = {} - -function PANEL:Init() - local w = self:GetParent():GetWide() - local h = self:GetParent():GetTall() - - self:SetSize(w/3, h) -end - -function PANEL:Paint(w, h) - -end -vgui.Register("Screen_Scoreboard", PANEL, "Panel") \ No newline at end of file diff --git a/fluffy_microgames/gamemode/init.lua b/fluffy_microgames/gamemode/init.lua index 773d95e..ff53bf8 100644 --- a/fluffy_microgames/gamemode/init.lua +++ b/fluffy_microgames/gamemode/init.lua @@ -47,7 +47,7 @@ function GM:PreStartRound() if v:Team() == TEAM_SPECTATOR then return end if not v:Alive() then v:Spawn() end - v:AddStatPoints('RoundsPlayed', 1) + v:AddStatPoints('Rounds Played', 1) end -- Start the round after a short cooldown @@ -154,21 +154,32 @@ function GM:NewModifier() -- Make sure the same modifier doesn't come up twice if not GAMEMODE.CurrentModifier then GAMEMODE.CurrentModifier = table.Random(GAMEMODE.Modifiers) end local modifier = GAMEMODE.CurrentModifier + while modifier == GAMEMODE.CurrentModifier do modifier = table.Random(GAMEMODE.Modifiers) + + -- If the modifier is restricted to certain maps, ensure that the map is valid + if modifier.maps then + if not modifier.maps[game.GetMap()] then + modifier = GAMEMODE.CurrentModifier + end + end end GAMEMODE.CurrentModifier = modifier + -- Call the initialize function for the modifier if modifier.func_init then modifier.func_init() end + -- Call the player function for the modifier if modifier.func_player then for k,v in pairs(player.GetAll()) do modifier.func_player(v) end end + -- Register any hooks related to this modifier if modifier.hooks then for k,v in pairs(modifier.hooks) do hook.Add(k, modifier.name, v) diff --git a/fluffy_paintball/entities/entities/pb_weapon_spawner.lua b/fluffy_paintball/entities/entities/pb_weapon_spawner.lua index 26fe1e4..4ec328e 100644 --- a/fluffy_paintball/entities/entities/pb_weapon_spawner.lua +++ b/fluffy_paintball/entities/entities/pb_weapon_spawner.lua @@ -73,6 +73,7 @@ if SERVER then else ent:Give(wep) end + ent:AddStatPoints('Weapons Collected', 1) -- Shuffle the type (if applicable) if self.RandomTable then diff --git a/fluffy_paintball/gamemode/init.lua b/fluffy_paintball/gamemode/init.lua index 5a698a4..c2ceb0e 100644 --- a/fluffy_paintball/gamemode/init.lua +++ b/fluffy_paintball/gamemode/init.lua @@ -120,8 +120,8 @@ function GM:SetPlayerUnGhost(ply) -- Reduce the timer as a penalty local starttime = ply:GetNWFloat('GhostStart') - local timetaken = CurTime() - starttime - ply.DeathTimer = (ply.DeathTimer or GAMEMODE.LifeTimer) - 2 - timetaken + --local timetaken = CurTime() - starttime + ply.DeathTimer = (ply.DeathTimer or GAMEMODE.LifeTimer) - 10--2 - timetaken -- Ungodmode after given time timer.Simple(3, function() @@ -189,4 +189,9 @@ hook.Add('PostPlayerDeath', 'PaintballSpawnGhost', function(ply) ply:SetPos(ply.DeathPos) ply:SetEyeAngles(ply.DeathAng) GAMEMODE:SetPlayerGhost(ply) +end) + +-- Register XP for Paintball +hook.Add('RegisterStatsConversions', 'AddPaintballStatConversions', function() + GAMEMODE:AddStatConversion('Weapons Collected', 'Weapons Collected', 0.25) end) \ No newline at end of file diff --git a/fluffy_pitfall/gamemode/init.lua b/fluffy_pitfall/gamemode/init.lua index eb911fc..e326360 100644 --- a/fluffy_pitfall/gamemode/init.lua +++ b/fluffy_pitfall/gamemode/init.lua @@ -68,13 +68,13 @@ function GM:DoPlayerDeath( ply, attacker, dmginfo ) -- Do not count deaths unless in round if GetGlobalString( 'RoundState' ) != 'InRound' then return end ply:AddDeaths(1) - GAMEMODE:AddStatPoints(ply, 'deaths', 1) + GAMEMODE:AddStatPoints(ply, 'Deaths', 1) -- Every living players earns a point for k,v in pairs(player.GetAll()) do if !v:Alive() or v == ply then continue end v:AddFrags(1) - GAMEMODE:AddStatPoints(v, 'pitfall_score', 1) + --GAMEMODE:AddStatPoints(v, 'pitfall_score', 1) end end diff --git a/fluffy_poltergeist/gamemode/shared.lua b/fluffy_poltergeist/gamemode/shared.lua index 8f64a5b..4cef8d3 100644 --- a/fluffy_poltergeist/gamemode/shared.lua +++ b/fluffy_poltergeist/gamemode/shared.lua @@ -46,4 +46,9 @@ function GM:CreateTeams() team.SetUp( TEAM_SPECTATOR, "Spectators", Color( 255, 255, 80 ), true ) team.SetSpawnPoint( TEAM_SPECTATOR, { "info_player_start", "info_player_terrorist", "info_player_combine" } ) -end \ No newline at end of file +end + +-- Hide all cosmetics on Poltergeists +hook.Add('ShouldDrawCosmetics', 'HideHunterCosmetics', function(ply, ITEM) + if ply:Team() == TEAM_RED then return false end +end) \ No newline at end of file diff --git a/fluffy_sniperwars/gamemode/shared.lua b/fluffy_sniperwars/gamemode/shared.lua index 71260c2..c059c13 100644 --- a/fluffy_sniperwars/gamemode/shared.lua +++ b/fluffy_sniperwars/gamemode/shared.lua @@ -16,6 +16,8 @@ GM.RoundType = 'timed_endless' GM.GameTime = 500 GM.HUDStyle = 4 +GM.SpawnProtection = true -- Spawn protection enabled + function GM:Initialize() end \ No newline at end of file diff --git a/fluffy_stalker/gamemode/cl_init.lua b/fluffy_stalker/gamemode/cl_init.lua index 0580b40..411c247 100644 --- a/fluffy_stalker/gamemode/cl_init.lua +++ b/fluffy_stalker/gamemode/cl_init.lua @@ -1 +1,55 @@ -include('shared.lua') \ No newline at end of file +include('shared.lua') + +-- Stop the plyayer information showing up on mouseover +function GM:HUDDrawTargetID() + local tr = util.GetPlayerTrace( LocalPlayer() ) + local trace = util.TraceLine( tr ) + if ( !trace.Hit ) then return end + if ( !trace.HitNonWorld ) then return end + + local text = "ERROR" + local font = "TargetID" + + if ( trace.Entity:IsPlayer() and trace.Entity:Team() == TEAM_BLUE ) then + text = trace.Entity:Nick() + else + return + --text = trace.Entity:GetClass() + end + + surface.SetFont( font ) + local w, h = surface.GetTextSize( text ) + + local MouseX, MouseY = gui.MousePos() + + if ( MouseX == 0 && MouseY == 0 ) then + + MouseX = ScrW() / 2 + MouseY = ScrH() / 2 + + end + + local x = MouseX + local y = MouseY + + x = x - w / 2 + y = y + 30 + + -- The fonts internal drop shadow looks lousy with AA on + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ) ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ) ) + draw.SimpleText( text, font, x, y, self:GetTeamColor( trace.Entity ) ) + + y = y + h + 5 + + local text = trace.Entity:Health() .. "%" + local font = "TargetIDSmall" + + surface.SetFont( font ) + local w, h = surface.GetTextSize( text ) + local x = MouseX - w / 2 + + draw.SimpleText( text, font, x + 1, y + 1, Color( 0, 0, 0, 120 ) ) + draw.SimpleText( text, font, x + 2, y + 2, Color( 0, 0, 0, 50 ) ) + draw.SimpleText( text, font, x, y, self:GetTeamColor( trace.Entity ) ) +end \ No newline at end of file diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 69eab92..2a2adaf 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -7,15 +7,29 @@ include('shared.lua') -- Gives weapons to the human teams -- Makes the Stalker invisible and sets HP function GM:PlayerLoadout(ply) + ply:StripWeapons() + ply:StripAmmo() + if ply:Team() == TEAM_RED then -- Stalker loadout here - ply:SetColor(Color(255, 255, 255, 15)) - local hp = 150 + math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 16) * 10 + ply:SetColor(Color(255, 255, 255, 10)) + ply:SetRenderMode(RENDERMODE_TRANSALPHA) + ply:SetWalkSpeed(500) + ply:SetRunSpeed(500) + ply:SetJumpPower(500) + + local hp = 150 + math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 16) * 35 ply:SetHealth(hp) ply:SetMaxHealth(hp) + ply:Give('weapon_fists') elseif ply:Team() == TEAM_BLUE then -- Reset colour ply:SetColor(color_white) + ply:SetRenderMode(RENDERMODE_NORMAL) + + ply:SetWalkSpeed(200) + ply:SetRunSpeed(300) + ply:SetJumpPower(200) -- Pick from one of three random loadouts if math.random() > 0.8 then @@ -27,23 +41,72 @@ function GM:PlayerLoadout(ply) else ply:Give('weapon_pistol') end + + -- Some (but not a whole heap) of ammo for all the guns + ply:Give('weapon_stunstick') + ply:GiveAmmo(200, 'Pistol', true) + ply:GiveAmmo(120, 'SMG1', true) + ply:GiveAmmo(32, 'Buckshot', true) end end -- Set player models for the Stalker function GM:PlayerSetModel( ply ) if ply:Team() == TEAM_RED then - --ply:SetModel('models/player/soldier_stripped.mdl') + ply:SetModel('models/player/soldier_stripped.mdl') + else + ply:SetModel('models/player/police.mdl') + --GAMEMODE.BaseClass:PlayerSetModel(ply) + end + + if not ply.FFAColor then + ply.FFAColor = HSVToColor(math.random(360), 1, 1) + end + + local c = Vector(ply.FFAColor.r/255, ply.FFAColor.g/255, ply.FFAColor.b/255) + ply:SetPlayerColor(c) +end + +-- Make everyone start as a human +hook.Add('PreRoundStart', 'InfectionResetPlayers', function() + for k,v in pairs(player.GetAll()) do + if v:Team() == TEAM_SPECTATOR then continue end + v:SetTeam(TEAM_BLUE) + end + + local stalker = table.Random(team.GetPlayers(TEAM_BLUE)) + stalker:SetTeam(TEAM_RED) + stalker:Spawn() +end) + +-- Check if there enough players to start a round +function GM:CanRoundStart() + if GAMEMODE:NumNonSpectators() >= 2 then + return true else - ply:SetModel('models/player/combine_soldier_prisonguard.mdl') - GAMEMODE.BaseClass:PlayerSetModel(ply) + return false end end +-- Make new players join the Hunter team on connection +function GM:PlayerInitialSpawn(ply) + ply:SetTeam(TEAM_BLUE) +end + +-- No fall damage +function GM:GetFallDamage() + return 0 +end + +-- Flashlight enabled for humans only +function GM:PlayerSwitchFlashlight(ply, state) + return (ply:Team() == TEAM_BLUE and not ply.Spectating) +end + -- Stop any form of team swapping in this gamemode -- Teams are rigorously chosen before the round starts hook.Add('PlayerCanJoinTeam', 'StopTeamSwap', function(ply, team) - if team != TEAM_SPECTATOR then return false end + if ply:Team() == TEAM_RED or team == TEAM_RED then return false end end) -- Movement tricks for the Stalker @@ -54,7 +117,7 @@ hook.Add('KeyPress', 'StalkerMovementTricks', function(ply, key) if key == IN_SPEED then if ply:OnGround() and (ply.JumpTime or 0) < CurTime() then -- Jump into the air - local jump = ply:GetAimVector() * 200 + Vector(0, 0, 300) + local jump = ply:GetAimVector() * 500 + Vector(0, 0, 200) ply:SetVelocity(jump) ply.JumpTime = CurTime() + 1 else @@ -64,7 +127,7 @@ hook.Add('KeyPress', 'StalkerMovementTricks', function(ply, key) ply:SetMoveType(MOVETYPE_NONE) elseif ply:GetMoveType() == MOVETYPE_NONE then ply:SetMoveType(MOVETYPE_WALK) - ply:SetLocalVelocity(ply:GetAimVector() * 200) + ply:SetLocalVelocity(ply:GetAimVector() * 400) end end elseif key == IN_JUMP and ply:GetMoveType() == MOVETYPE_NONE then @@ -72,4 +135,13 @@ hook.Add('KeyPress', 'StalkerMovementTricks', function(ply, key) ply:SetMoveType(MOVETYPE_WALK) ply:SetLocalVelocity(Vector(0, 0, 50)) end -end \ No newline at end of file +end) + +-- Buff fists +hook.Add('EntityTakeDamage', 'FistsBuff', function(target, dmg) + local wep = dmg:GetInflictor() + if wep:GetClass() == 'player' then wep = wep:GetActiveWeapon() end + if wep:GetClass() == "weapon_fists" then + dmg:ScaleDamage(4) + end +end) \ No newline at end of file diff --git a/fluffy_stalker/gamemode/shared.lua b/fluffy_stalker/gamemode/shared.lua index bdff10d..dad0175 100644 --- a/fluffy_stalker/gamemode/shared.lua +++ b/fluffy_stalker/gamemode/shared.lua @@ -7,9 +7,31 @@ GM.HelpText = [[ ]] GM.TeamBased = true -- Is the gamemode FFA or Teams? -GM.RoundTime = 90 +GM.Elimination = true +GM.WinBySurvival = true + +GM.RoundTime = 120 GM.RoundNumber = 10 function GM:Initialize() -end \ No newline at end of file +end + +function GM:CreateTeams() + if not GAMEMODE.TeamBased then return end + + team.SetUp(TEAM_RED, "Stalker", Color( 255, 80, 80 ), true) + team.SetSpawnPoint(TEAM_RED, {"info_player_counterterrorist", "info_player_rebel"}) + + team.SetUp(TEAM_BLUE, "Survivors", Color( 80, 80, 255 ), true) + team.SetSpawnPoint(TEAM_BLUE, {"info_player_terrorist", "info_player_combine"}) + + team.SetUp(TEAM_SPECTATOR, "Spectators", Color( 255, 255, 80 ), true) + team.SetSpawnPoint(TEAM_SPECTATOR, { "info_player_start", "info_player_terrorist", "info_player_combine" }) +end + +-- Hide all cosmetics for the Stalker +-- It'd be a bit sucky for the invisible man to have a very obvious top hat equipped +hook.Add('ShouldDrawCosmetics', 'HideHunterCosmetics', function(ply, ITEM) + if ply:Team() == TEAM_RED then return false end +end) \ No newline at end of file diff --git a/fluffy_suicidebarrels/gamemode/init.lua b/fluffy_suicidebarrels/gamemode/init.lua index 29bda82..40341ce 100644 --- a/fluffy_suicidebarrels/gamemode/init.lua +++ b/fluffy_suicidebarrels/gamemode/init.lua @@ -51,11 +51,11 @@ function GM:HandlePlayerDeath(ply, attacker, dmginfo) if attacker:Team() == TEAM_RED then -- Barrel killed human attacker:AddFrags(5) - GAMEMODE:AddStatPoints(attacker, 'humans_killed', 1) + GAMEMODE:AddStatPoints(attacker, 'Humans Killed', 1) elseif attacker:Team() == TEAM_BLUE then -- Human killed barrel attacker:AddFrags(1) - GAMEMODE:AddStatPoints(attacker, 'barrels_killed', 1) + GAMEMODE:AddStatPoints(attacker, 'Barrels Killed', 1) end end @@ -64,8 +64,14 @@ function GM:StatsRoundWin(winners) if winners == TEAM_BLUE then for k,v in pairs(team.GetPlayers(TEAM_BLUE)) do if v:Alive() then - GAMEMODE:AddStatPoints(v, 'survived_rounds', 1) + GAMEMODE:AddStatPoints(v, 'Survived Rounds', 1) end end end -end \ No newline at end of file +end + +-- Register XP for Suicide Barrels +hook.Add('RegisterStatsConversions', 'AddSuicideBarrelsStatConversions', function() + GAMEMODE:AddStatConversion('Humans Killed', 'Humans Killed', 3) + GAMEMODE:AddStatConversion('Barrels Killed', 'Barrels Killed', 0.5) +end) \ No newline at end of file diff --git a/fluffy_suicidebarrels/gamemode/shared.lua b/fluffy_suicidebarrels/gamemode/shared.lua index 6106fc6..d2230d7 100644 --- a/fluffy_suicidebarrels/gamemode/shared.lua +++ b/fluffy_suicidebarrels/gamemode/shared.lua @@ -54,4 +54,9 @@ end function GM:PlayerFootstep( ply, pos, foot, sound, volume, rf ) if ply:Team() == TEAM_RED then return true end -end \ No newline at end of file +end + +-- Hide all cosmetics for barrels +hook.Add('ShouldDrawCosmetics', 'HideHunterCosmetics', function(ply, ITEM) + if ply:Team() == TEAM_RED then return false end +end) \ No newline at end of file