From 3bd3c2498bac349c3d6cc9041338fb166b412575 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Sat, 27 Apr 2019 21:58:48 +1000 Subject: [PATCH 01/54] Bomb Tag: Fix weapon-related bugs --- fluffy_bombtag/entities/weapons/bt_punch.lua | 5 +++-- fluffy_bombtag/gamemode/init.lua | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) 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..40bc37f 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 From 9c6428091f005e95f1ef1d060fe9e6554fd5b1c8 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 30 Apr 2019 00:54:21 +1000 Subject: [PATCH 02/54] Base: Improve stats naming --- fluffy_mg_base/gamemode/init.lua | 4 ++-- fluffy_mg_base/gamemode/sv_levels.lua | 9 +++++---- fluffy_mg_base/gamemode/sv_round.lua | 8 ++++---- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/fluffy_mg_base/gamemode/init.lua b/fluffy_mg_base/gamemode/init.lua index 7093b40..925aebf 100644 --- a/fluffy_mg_base/gamemode/init.lua +++ b/fluffy_mg_base/gamemode/init.lua @@ -170,7 +170,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 +331,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 diff --git a/fluffy_mg_base/gamemode/sv_levels.lua b/fluffy_mg_base/gamemode/sv_levels.lua index 823a656..25ee8ed 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,10 @@ 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) end) -- Convert a stat name & score to a table with XP 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 From ff277cd922e3cb7fb2c7ca85f6e644c5180fd4af Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 30 Apr 2019 00:56:51 +1000 Subject: [PATCH 03/54] Base: Stats database functionality --- fluffy_mg_base/gamemode/sv_stats.lua | 83 ++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/fluffy_mg_base/gamemode/sv_stats.lua b/fluffy_mg_base/gamemode/sv_stats.lua index bc81183..ac118a2 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 @@ -68,4 +77,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 From e2bc4a7f47424fce5767df133489b03ea3402a3e Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 30 Apr 2019 03:18:50 +1000 Subject: [PATCH 04/54] Reworked stats for various gamemodes --- fluffy_balls/gamemode/init.lua | 9 +++++++-- fluffy_bombtag/gamemode/init.lua | 4 ++-- fluffy_climb/gamemode/init.lua | 11 ++++++++--- fluffy_cratewars/gamemode/init.lua | 11 +++++++++-- fluffy_incoming/gamemode/init.lua | 2 +- fluffy_kingmaker/gamemode/init.lua | 13 +++++++------ fluffy_mg_base/gamemode/sv_levels.lua | 2 ++ fluffy_microgames/gamemode/init.lua | 2 +- .../entities/entities/pb_weapon_spawner.lua | 1 + fluffy_paintball/gamemode/init.lua | 5 +++++ fluffy_pitfall/gamemode/init.lua | 4 ++-- fluffy_suicidebarrels/gamemode/init.lua | 14 ++++++++++---- 12 files changed, 55 insertions(+), 23 deletions(-) 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/gamemode/init.lua b/fluffy_bombtag/gamemode/init.lua index 40bc37f..0c76c5b 100644 --- a/fluffy_bombtag/gamemode/init.lua +++ b/fluffy_bombtag/gamemode/init.lua @@ -82,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 @@ -100,6 +100,6 @@ 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, 'bombtag_score', 1) end 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_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_kingmaker/gamemode/init.lua b/fluffy_kingmaker/gamemode/init.lua index 5859ecd..170b847 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() @@ -151,4 +146,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_mg_base/gamemode/sv_levels.lua b/fluffy_mg_base/gamemode/sv_levels.lua index 25ee8ed..a231f72 100644 --- a/fluffy_mg_base/gamemode/sv_levels.lua +++ b/fluffy_mg_base/gamemode/sv_levels.lua @@ -119,6 +119,8 @@ hook.Add('RegisterStatsConversions', 'AddBaseStatConversions', function() 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_microgames/gamemode/init.lua b/fluffy_microgames/gamemode/init.lua index 773d95e..87c137e 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 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..2f9d7dc 100644 --- a/fluffy_paintball/gamemode/init.lua +++ b/fluffy_paintball/gamemode/init.lua @@ -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_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 From 75d9479b748c72e5640a6472f9230a39e4b614b4 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 30 Apr 2019 03:46:06 +1000 Subject: [PATCH 05/54] Kingmaker: Statistics fixes --- fluffy_kingmaker/gamemode/init.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/fluffy_kingmaker/gamemode/init.lua b/fluffy_kingmaker/gamemode/init.lua index 170b847..294ddfa 100644 --- a/fluffy_kingmaker/gamemode/init.lua +++ b/fluffy_kingmaker/gamemode/init.lua @@ -59,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) @@ -73,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) @@ -84,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) @@ -108,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 From 7f1950855880b7c2d0adc61dd63b44834c3e8ef2 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 30 Apr 2019 03:46:14 +1000 Subject: [PATCH 06/54] Save stats at the end of the game --- fluffy_mg_base/gamemode/sv_voting.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/fluffy_mg_base/gamemode/sv_voting.lua b/fluffy_mg_base/gamemode/sv_voting.lua index f42a171..7264732 100644 --- a/fluffy_mg_base/gamemode/sv_voting.lua +++ b/fluffy_mg_base/gamemode/sv_voting.lua @@ -111,6 +111,7 @@ function GM:StartVoting() net.WriteTable(tbl) net.Send(v) v:ProcessLevels() + v:UpdateStatsToDB() end -- 30 seconds of voting time From 5d487a07f56dcb03f9f185d9fbfa234958bccdbb Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 2 May 2019 23:12:27 +1000 Subject: [PATCH 07/54] Infection: V1 of the NPC zombies --- .../entities/entities/ai_zo_base.lua | 154 +++++++ .../entities/entities/npc_zo_base.lua | 390 ++++++++++++++++++ 2 files changed, 544 insertions(+) create mode 100644 fluffy_infection/entities/entities/ai_zo_base.lua create mode 100644 fluffy_infection/entities/entities/npc_zo_base.lua diff --git a/fluffy_infection/entities/entities/ai_zo_base.lua b/fluffy_infection/entities/entities/ai_zo_base.lua new file mode 100644 index 0000000..40fa082 --- /dev/null +++ b/fluffy_infection/entities/entities/ai_zo_base.lua @@ -0,0 +1,154 @@ +AddCSLuaFile() +ENT.Base = "base_ai" +ENT.Type = "ai" + +ENT.ClawHit = { + "npc/zombie/claw_strike1.wav", + "npc/zombie/claw_strike2.wav", + "npc/zombie/claw_strike3.wav" +} + +ENT.ClawMiss = { + "npc/zombie/claw_miss1.wav", + "npc/zombie/claw_miss2.wav" +} + +ENT.DoorHit = Sound("npc/zombie/zombie_hit.wav") + +ENT.IdleTalk = 0 +ENT.DoorTime = 0 +ENT.VoiceTime = 0 +ENT.RemoveTime = 0 +ENT.RemovePos = Vector(0,0,0) +ENT.SearchRadius = 4000 + +function ENT:Initialize() + if CLIENT then return end + self:SetModel('models/zombie/classic.mdl') + self:SetHullSizeNormal() + self:SetHullType(HULL_HUMAN) + + self:SetSolid(SOLID_BBOX) + self:SetMoveType(MOVETYPE_STEP) + self:CapabilitiesAdd(CAP_MOVE_GROUND) + self:CapabilitiesAdd(CAP_INNATE_MELEE_ATTACK1) + + self:SetMaxYawSpeed(1000) + self:SetHealth(100) + self:DropToFloor() + self:UpdateEnemy(self:FindEnemy()) +end + +function ENT:VoiceSound(tbl) + if self.VoiceTime > CurTime() then return end + self.VoiceTime = CurTime() + 1 + self:EmitSound(Sound(table.Random(tbl)), 100, math.random(85, 105)) +end + +function ENT:FindEnemy() + local ents = ents.FindInSphere(self:GetPos(), self.SearchRadius) + for k, v in pairs(ents) do + if v:IsPlayer() then + return v + end + end + return nil +end + +function ENT:UpdateEnemy(ent) + if ent and ent:IsValid() and ent:Alive() then + self:SetEnemy(ent, true) + self:UpdateEnemyMemory(ent, ent:GetPos()) + else + self:SetEnemy(NULL) + end +end + +function ENT:Think() + if self.IdleTalk < CurTime() then + --self:VoiceSound(self.VoiceSounds.Taunt) + self.IdleTalk = CurTime() + math.random(10, 20) + end + + if self.DoorTime < CurTime() then + local door = self:NearDoor() + if IsValid(door) then + + else + local breakable = self:NearBreakable() + if IsValid(breakable) then + if breakable:GetClass() == 'func_breakable_surf' then + breakable:Fire('shatter', '1 1 1', 0) + else + self:SetSchedule(SCHED_MELEE_ATTACK1) + breakable:TakeDamage(self.Damage, self) + self:EmitSound(self.DoorHit, 100, math.random(85, 115)) + end + end + end + end + + if self.AttackTime and self.AttackTime < CurTime() then + self.AttackTime = nil + local enemy = self:GetEnemy() + if enemy and enemy:IsValid() and enemy:GetPos():Distance(self:GetPos()) < 64 then + enemy:TakeDamage(self.Damage, self) + self:VoiceSound(self.ClawHit) + else + self:VoiceSound(self.ClawMiss) + end + end +end + +function ENT:NearDoor() + local doors = ents.FindInSphere(self:GetPos(), 50) + for k,v in pairs(doors) do + if string.find(v:GetClass(), "prop_door_") then + return v + end + end + + return nil +end + +local breakables = {} +breakables['func_breakable'] = true +breakables['func_physbox'] = true +breakables['prop_physics'] = true +breakables['prop_physics_multiplayer'] = true +breakables['func_breakable_surf'] = true + +function ENT:NearBreakable() + local doors = ents.FindInSphere(self:GetPos(), 50) + for k,v in pairs(doors) do + if breakables[v:GetClass()] then + return v + end + end + + return nil +end + +function ENT:GetRelationship(ent) + if ent:IsPlayer() then return D_HT end +end + +function ENT:SelectSchedule() + local enemy = self:GetEnemy() + local sched = SCHED_IDLE_WANDER + + if enemy and enemy:IsValid() then + if self:HasCondition(23) then + sched = SCHED_MELEE_ATTACK1 + self.AttackTime = CurTime() + 1 + --self:VoiceSound(self.VoiceSounds.Attack) + else + sched = SCHED_CHASE_ENEMY + end + else + self:UpdateEnemy(self:FindEnemy()) + end + + self:SetPlaybackRate(5) + self:SetSchedule(sched) +end \ 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..1f703aa --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -0,0 +1,390 @@ +AddCSLuaFile() +ENT.Base = 'base_nextbot' + +-- Speed +ENT.Speed = 100 +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/skeleton.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") +} + +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 CLIENT then return end + self.loco:SetDesiredSpeed(self.Speed) + self.loco:SetAcceleration(self.Acceleration) + self.loco:SetDeceleration(self.Acceleration * 8) + self:SetMaxHealth(self.BaseHealth) -- idk why this isn't clientside but hey, not my fault +end + +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 + +-- 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 ents = ents.FindInSphere(self:GetPos(), self.SearchRadius) + for k, v in pairs(ents) do + if v:IsPlayer() then + self:SetEnemy(v) + return true + end + end + + -- No players found + self:SetEnemy(nil) + return false +end + +function ENT:DistanceToEnemy(enemy) + local enemy = enemy or self:GetEnemy() + return self:GetPos():Distance(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, ACT_HL2MP_WALK_ZOMBIE_01, 0, 0) + 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) + + -- 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 + path:Update(self) + --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 + + self:CheckTrace() + coroutine.yield() + end + + print('done chase') + return true +end + +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 + +function ENT:CheckTrace() + if (self.NextAttackTime or 0) > CurTime() then return end + + local mins = self:OBBMins() + local maxs = self:OBBMaxs() + + local tr = util.TraceHull({ + mins = mins, + maxs = maxs, + start = self:GetPos(), + endpos = self:GetPos() + self:GetForward()*24, + filter = self + }) + + 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 + +function ENT:AttackPlayer(ent) + if not IsValid(ent) then return end + if ent == self:GetEnemy() then + self:AttackSound() + self.IsAttacking = true + self:RestartGesture(self.AttackAnim) + self:AttackEffect(0.9, self.Enemy, self.Damage, 0) + else + self:AttackSound() + self:AttackEffect(0.9, ent, self.Damage, 0) + end + self.NextAttackTime = CurTime() + self.NextAttack +end + +function ENT:AttackDoor(v) + if not IsValid(v) then return end + if v.DoorHealth == nil then + v.DoorHealth = math.random(50, 100) + end + + if v.DoorHealth > 0 then + 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 + 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) + door.FakeProp = true + + local phys = door:GetPhysicsObject() + if phys:IsValid() then phys:ApplyForceCenter(self:GetForward():GetNormalized()*20000 + Vector(0, 0, 2)) end + SafeRemoveEntity(v) + end + + self.NextAttackTime = CurTime() + self.NextAttack +end + +function ENT:AttackObject(v) + if not IsValid(v) then return end + if v.FakeProp then return end + + 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 + v.Damaged = true + v:SetHealth( v:Health() - self.Damage ) + else + if !v.Damaged then + v.Damaged = true + v:SetHealth(50) + else + v:GibBreakClient( Vector(0, 0, 0) ) + v:Remove() + end + end +end + +function ENT:AttackWindow(ent) + ent:Fire('shatter', '1 1 1', 0) +end + +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 + ent:TakeDamage(self.Damage, self) + if ent:IsPlayer() or ent:IsNPC() then + self:EmitSound(self.HitSound) + end + + 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 + self:EmitSound(self.Miss) + end + end) + + 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 From 81fcb4bb88ca2175105c7af6c6821e74a36f3edb Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 3 May 2019 00:20:32 +1000 Subject: [PATCH 08/54] Infection: Add two testing skeleton types --- .../entities/entities/npc_skeleton.lua | 21 +++++++++++++++++ .../entities/entities/npc_skeleton_gold.lua | 23 +++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 fluffy_infection/entities/entities/npc_skeleton.lua create mode 100644 fluffy_infection/entities/entities/npc_skeleton_gold.lua 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..c12f91f --- /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 = 240 +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.Color = Color(255, 255, 0) + +ENT.CollisionSide = 10 \ No newline at end of file From 627d3e8e01e55408a0d3e5a834ea0ffbccfd41d0 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 3 May 2019 00:20:40 +1000 Subject: [PATCH 09/54] Infection: Zombie AI changes --- .../entities/entities/npc_zo_base.lua | 56 +++++++++++++++---- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index 1f703aa..85ad50f 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -1,6 +1,10 @@ AddCSLuaFile() ENT.Base = 'base_nextbot' +if CLIENT then + language.Add('npc_skeleton_gold', 'Zombie' ) +end + -- Speed ENT.Speed = 100 ENT.WalkSpeedAnimation = 2 @@ -25,7 +29,7 @@ ENT.AttackRange = 60 ENT.NextAttack = 1.3 -- Model & Animations -ENT.Model = "models/player/skeleton.mdl" +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) @@ -89,6 +93,12 @@ function ENT:CollisionSetup(side, height, group) self.NEXTBOT = true end +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 @@ -133,17 +143,24 @@ end -- Search for a new enemy for this NPC function ENT:FindEnemy() - local ents = ents.FindInSphere(self:GetPos(), self.SearchRadius) - for k, v in pairs(ents) do - if v:IsPlayer() then - self:SetEnemy(v) - return true - end + local players = team.GetPlayers(TEAM_BLUE) + local distances = {} + + for k,v in pairs(players) do + if v.Spectating then continue end + table.insert(distances, {v, self:GetPos():DistToSqr(v:GetPos())}) end - -- No players found - self:SetEnemy(nil) - return false + -- 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 + + self:SetEnemy(distances[1][1]) + return true end function ENT:DistanceToEnemy(enemy) @@ -175,7 +192,7 @@ end -- hi my name is zombie i'm lazy function ENT:Idle() - self:MovementFunctions(1, ACT_HL2MP_WALK_ZOMBIE_01, 0, 0) + 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 @@ -214,6 +231,11 @@ function ENT:ChaseEnemy() end end + -- Check the enemy every so often + if math.random() > 0.5 then + self:FindEnemy() + end + self:CheckTrace() coroutine.yield() end @@ -232,6 +254,18 @@ function ENT:CheckRange(enemy) end end +function ENT:OnInjured(info) + if not self:HaveEnemy() then return end + if info:GetAttacker() == self:GetEnemy() then return end + + 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 + function ENT:CheckTrace() if (self.NextAttackTime or 0) > CurTime() then return end From 75759ef897c3481af052ba7e4867965da24accc7 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 3 May 2019 00:20:58 +1000 Subject: [PATCH 10/54] Infection: Rework gamemode to include NPC zombies --- fluffy_infection/gamemode/init.lua | 89 +++++++++++++++++++----- fluffy_infection/gamemode/shared.lua | 12 ++-- fluffy_infection/gamemode/sv_zombies.lua | 44 ++++++++++++ 3 files changed, 119 insertions(+), 26 deletions(-) create mode 100644 fluffy_infection/gamemode/sv_zombies.lua diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 5a5e510..f08e38a 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 ) @@ -24,24 +25,18 @@ function GM:PlayerLoadout( ply ) -- 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(200) + ply:SetHealth(200) + ply:SetRunSpeed(400) + ply:SetWalkSpeed(250) 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_soldier.mdl") else GAMEMODE.BaseClass:PlayerSetModel(ply) end @@ -54,18 +49,74 @@ function GM:CanPlayerSuicide(ply) return true 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) +-- Make new players join the Hunter team on connection +function GM:PlayerInitialSpawn(ply) + ply:SetTeam(TEAM_RED) +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 +end) + +-- Stop Zombies from switching back to the other team +hook.Add('PlayerCanJoinTeam', 'StopZombieSwap', function(ply, team) + local current_team = ply:Team() + if current_team == TEAM_RED then + ply:ChatPrint('You cannot change teams currently') + return false + end +end) + +-- Assign dead survivors to the hunter team +hook.Add('PlayerDeath', 'InfectionDeath', function(ply) + if ply:Team() == TEAM_BLUE then + ply:SetTeam(TEAM_RED) + end +end) + +-- 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 + +-- Check if there enough players to start a round +function GM:CanRoundStart() + if GAMEMODE:NumNonSpectators() >= 2 then + return true + else + return false + end end -hook.Add('EntityTakeDamage', 'ShotgunGlobalBuff', function(target, dmg) +-- Last Survivor gets a message and a stat bonus +hook.Add('DoPlayerDeath', 'AwardLastSurvivorInfection', function(ply) + if ply:Team() != TEAM_BLUE then return end + + local last_player = GAMEMODE:GetLastPlayer(ply) + if IsValid(last_player) and last_player != false then + -- Award the last survivor bonus + local name = string.sub(last_player:Nick(), 1, 10) + GAMEMODE:PulseAnnouncement(4, name .. ' is the lone survivor!', 0.8) + last_player:AddStatPoints('Last Survivor', 1) + end +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 diff --git a/fluffy_infection/gamemode/shared.lua b/fluffy_infection/gamemode/shared.lua index 6612505..8e38834 100644 --- a/fluffy_infection/gamemode/shared.lua +++ b/fluffy_infection/gamemode/shared.lua @@ -19,12 +19,10 @@ TEAM_BLUE = 2 -- Configure teams for Hunter vs Hunted GM.TeamBased = true -GM.TeamSurvival = true -GM.SurvivorTeam = TEAM_BLUE -GM.HunterTeam = TEAM_RED +GM.TeamSurvival = false -GM.RoundNumber = 10 -- How many rounds? -GM.RoundTime = 90 -- How long should each round go for? +GM.RoundNumber = 20 -- 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 +31,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..764ba31 --- /dev/null +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -0,0 +1,44 @@ +function GM:GenerateSpawns() + GAMEMODE.ZombieSpawns = ents.FindByClass('info_player_zombie') + GAMEMODE.ZombieSpawns = table.Add(GAMEMODE.ZombieSpawns, ents.FindByClass('info_player_terrorist')) +end + +function GM:GetZombieSpawns() + if not GAMEMODE.ZombieSpawns then + GAMEMODE:GenerateSpawns() + end + return GAMEMODE.ZombieSpawns +end + +function GM:CreateZombie(ztype) + if #ents.FindByClass('npc_*') >= 50 then return end + + local spawn = table.Random(GAMEMODE:GetZombieSpawns()) + if not IsValid(spawn) then + GAMEMODE:GenerateSpawns() + return + end + + local pos = spawn:GetPos() + local zombie = ents.Create(ztype) + zombie:SetPos(pos) + zombie:Spawn() + print('made a zombie!') +end + +hook.Add('Think', 'ZombieTimer', function() + if GetGlobalString( 'RoundState' ) != 'InRound' then return end + + if not GAMEMODE.WaveTimer then GAMEMODE.WaveTimer = 0 end + if GAMEMODE.WaveTimer < CurTime() then + print('Spawning wave..') + GAMEMODE.WaveTimer = CurTime() + math.random(15, 30) + + for i=1,5 do + local type = 'npc_zo_base' + local r = math.random() + if r > 0.8 then type = 'npc_skeleton_gold' elseif r > 0.5 then type = 'npc_skeleton' end + GAMEMODE:CreateZombie(type) + end + end +end) \ No newline at end of file From 6a34a5271d5689025013d54bfc7fc8b71042d084 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Sun, 5 May 2019 01:41:42 +1000 Subject: [PATCH 11/54] Base: Completely rewrite the end-game screen! This is one of the largest changes made for quite a while but it's definitely long overdue! --- fluffy_mg_base/gamemode/cl_endgame.lua | 468 ++++++++++++------ fluffy_mg_base/gamemode/cl_init.lua | 6 + fluffy_mg_base/gamemode/init.lua | 5 +- fluffy_mg_base/gamemode/sv_stats.lua | 30 ++ fluffy_mg_base/gamemode/sv_voting.lua | 2 + .../gamemode/vgui/Screen_Experience.lua | 115 ----- fluffy_mg_base/gamemode/vgui/Screen_Maps.lua | 47 -- .../gamemode/vgui/Screen_Scoreboard.lua | 13 - 8 files changed, 371 insertions(+), 315 deletions(-) delete mode 100644 fluffy_mg_base/gamemode/vgui/Screen_Experience.lua delete mode 100644 fluffy_mg_base/gamemode/vgui/Screen_Maps.lua delete mode 100644 fluffy_mg_base/gamemode/vgui/Screen_Scoreboard.lua 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..ee3ea12 100644 --- a/fluffy_mg_base/gamemode/cl_init.lua +++ b/fluffy_mg_base/gamemode/cl_init.lua @@ -42,6 +42,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/init.lua b/fluffy_mg_base/gamemode/init.lua index 925aebf..8897542 100644 --- a/fluffy_mg_base/gamemode/init.lua +++ b/fluffy_mg_base/gamemode/init.lua @@ -28,8 +28,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') diff --git a/fluffy_mg_base/gamemode/sv_stats.lua b/fluffy_mg_base/gamemode/sv_stats.lua index ac118a2..e856b7e 100644 --- a/fluffy_mg_base/gamemode/sv_stats.lua +++ b/fluffy_mg_base/gamemode/sv_stats.lua @@ -64,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") diff --git a/fluffy_mg_base/gamemode/sv_voting.lua b/fluffy_mg_base/gamemode/sv_voting.lua index 7264732..a12c645 100644 --- a/fluffy_mg_base/gamemode/sv_voting.lua +++ b/fluffy_mg_base/gamemode/sv_voting.lua @@ -107,8 +107,10 @@ 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() 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 From 5bdb7d87d2ed18fd66f515e1dd37068a94119d19 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Sun, 5 May 2019 01:45:37 +1000 Subject: [PATCH 12/54] Infection: Minor zombies AI changes --- .../entities/entities/npc_skeleton_gold.lua | 4 ++-- .../entities/entities/npc_skeleton_mini.lua | 23 +++++++++++++++++++ .../entities/entities/npc_zo_base.lua | 4 +++- fluffy_infection/gamemode/init.lua | 2 +- 4 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 fluffy_infection/entities/entities/npc_skeleton_mini.lua diff --git a/fluffy_infection/entities/entities/npc_skeleton_gold.lua b/fluffy_infection/entities/entities/npc_skeleton_gold.lua index c12f91f..e88f112 100644 --- a/fluffy_infection/entities/entities/npc_skeleton_gold.lua +++ b/fluffy_infection/entities/entities/npc_skeleton_gold.lua @@ -8,7 +8,7 @@ if CLIENT then end -- Speed -ENT.Speed = 240 +ENT.Speed = 225 ENT.WalkSpeedAnimation = 0.6 ENT.Acceleration = 200 ENT.MoveType = 1 @@ -18,6 +18,6 @@ ENT.Model = "models/player/skeleton.mdl" ENT.BaseHealth = 200 ENT.Damage = 25 ENT.ModelScale = 1.25 -ENT.Color = Color(255, 255, 0) +ENT.BoldColor = Color(255, 255, 0) 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 index 85ad50f..0414d37 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -6,7 +6,7 @@ if CLIENT then end -- Speed -ENT.Speed = 100 +ENT.Speed = 200 ENT.WalkSpeedAnimation = 2 ENT.Acceleration = 25 ENT.MoveType = 1 @@ -79,6 +79,8 @@ function ENT:Initialize() 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) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index f08e38a..ad95b7e 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -18,7 +18,7 @@ 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 From dfb4015dc29e7a3f05a4a3cd9d2b8f044ff94e15 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Sun, 5 May 2019 01:46:37 +1000 Subject: [PATCH 13/54] Infection: Make golden skeleton more golden --- fluffy_infection/entities/entities/npc_skeleton_gold.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fluffy_infection/entities/entities/npc_skeleton_gold.lua b/fluffy_infection/entities/entities/npc_skeleton_gold.lua index e88f112..c70f63e 100644 --- a/fluffy_infection/entities/entities/npc_skeleton_gold.lua +++ b/fluffy_infection/entities/entities/npc_skeleton_gold.lua @@ -18,6 +18,6 @@ ENT.Model = "models/player/skeleton.mdl" ENT.BaseHealth = 200 ENT.Damage = 25 ENT.ModelScale = 1.25 -ENT.BoldColor = Color(255, 255, 0) +ENT.BoldColor = Color(212, 175, 55) ENT.CollisionSide = 10 \ No newline at end of file From 9714c3e60e5a6b68b84ead1f4726c5b2af3fd7ae Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Sun, 5 May 2019 02:41:43 +1000 Subject: [PATCH 14/54] Stalker: Gamemode development Fixes for team structure & making the stalker invisible are the key fixes in this commit --- fluffy_stalker/gamemode/init.lua | 63 +++++++++++++++++++++++++++--- fluffy_stalker/gamemode/shared.lua | 3 ++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 69eab92..4b5da5f 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -7,15 +7,27 @@ include('shared.lua') -- Gives weapons to the human teams -- Makes the Stalker invisible and sets HP function GM:PlayerLoadout(ply) + ply:StripWeapons() + if ply:Team() == TEAM_RED then -- Stalker loadout here - ply:SetColor(Color(255, 255, 255, 15)) + ply:SetColor(Color(255, 255, 255, 10)) + ply:SetRenderMode(RENDERMODE_TRANSALPHA) + ply:SetWalkSpeed(500) + ply:SetRunSpeed(500) + + local hp = 150 + math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 16) * 10 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) -- Pick from one of three random loadouts if math.random() > 0.8 then @@ -33,17 +45,47 @@ 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/combine_soldier_prisonguard.mdl') GAMEMODE.BaseClass:PlayerSetModel(ply) end 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 + return false + end +end + +-- Make new players join the Hunter team on connection +function GM:PlayerInitialSpawn(ply) + ply:SetTeam(TEAM_BLUE) +end + +function GM:GetFallDamage() + return 0 +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 +96,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 +106,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 +114,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..951eeff 100644 --- a/fluffy_stalker/gamemode/shared.lua +++ b/fluffy_stalker/gamemode/shared.lua @@ -7,6 +7,9 @@ GM.HelpText = [[ ]] GM.TeamBased = true -- Is the gamemode FFA or Teams? +GM.Elimination = true +GM.WinBySurvival = true + GM.RoundTime = 90 GM.RoundNumber = 10 From ff2e2d57b19b271e110b8a7bcca3d029b36cd7db Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Wed, 8 May 2019 10:35:04 +1000 Subject: [PATCH 15/54] Infection: Remove old AI based zombies --- .../entities/entities/ai_zo_base.lua | 154 ------------------ 1 file changed, 154 deletions(-) delete mode 100644 fluffy_infection/entities/entities/ai_zo_base.lua diff --git a/fluffy_infection/entities/entities/ai_zo_base.lua b/fluffy_infection/entities/entities/ai_zo_base.lua deleted file mode 100644 index 40fa082..0000000 --- a/fluffy_infection/entities/entities/ai_zo_base.lua +++ /dev/null @@ -1,154 +0,0 @@ -AddCSLuaFile() -ENT.Base = "base_ai" -ENT.Type = "ai" - -ENT.ClawHit = { - "npc/zombie/claw_strike1.wav", - "npc/zombie/claw_strike2.wav", - "npc/zombie/claw_strike3.wav" -} - -ENT.ClawMiss = { - "npc/zombie/claw_miss1.wav", - "npc/zombie/claw_miss2.wav" -} - -ENT.DoorHit = Sound("npc/zombie/zombie_hit.wav") - -ENT.IdleTalk = 0 -ENT.DoorTime = 0 -ENT.VoiceTime = 0 -ENT.RemoveTime = 0 -ENT.RemovePos = Vector(0,0,0) -ENT.SearchRadius = 4000 - -function ENT:Initialize() - if CLIENT then return end - self:SetModel('models/zombie/classic.mdl') - self:SetHullSizeNormal() - self:SetHullType(HULL_HUMAN) - - self:SetSolid(SOLID_BBOX) - self:SetMoveType(MOVETYPE_STEP) - self:CapabilitiesAdd(CAP_MOVE_GROUND) - self:CapabilitiesAdd(CAP_INNATE_MELEE_ATTACK1) - - self:SetMaxYawSpeed(1000) - self:SetHealth(100) - self:DropToFloor() - self:UpdateEnemy(self:FindEnemy()) -end - -function ENT:VoiceSound(tbl) - if self.VoiceTime > CurTime() then return end - self.VoiceTime = CurTime() + 1 - self:EmitSound(Sound(table.Random(tbl)), 100, math.random(85, 105)) -end - -function ENT:FindEnemy() - local ents = ents.FindInSphere(self:GetPos(), self.SearchRadius) - for k, v in pairs(ents) do - if v:IsPlayer() then - return v - end - end - return nil -end - -function ENT:UpdateEnemy(ent) - if ent and ent:IsValid() and ent:Alive() then - self:SetEnemy(ent, true) - self:UpdateEnemyMemory(ent, ent:GetPos()) - else - self:SetEnemy(NULL) - end -end - -function ENT:Think() - if self.IdleTalk < CurTime() then - --self:VoiceSound(self.VoiceSounds.Taunt) - self.IdleTalk = CurTime() + math.random(10, 20) - end - - if self.DoorTime < CurTime() then - local door = self:NearDoor() - if IsValid(door) then - - else - local breakable = self:NearBreakable() - if IsValid(breakable) then - if breakable:GetClass() == 'func_breakable_surf' then - breakable:Fire('shatter', '1 1 1', 0) - else - self:SetSchedule(SCHED_MELEE_ATTACK1) - breakable:TakeDamage(self.Damage, self) - self:EmitSound(self.DoorHit, 100, math.random(85, 115)) - end - end - end - end - - if self.AttackTime and self.AttackTime < CurTime() then - self.AttackTime = nil - local enemy = self:GetEnemy() - if enemy and enemy:IsValid() and enemy:GetPos():Distance(self:GetPos()) < 64 then - enemy:TakeDamage(self.Damage, self) - self:VoiceSound(self.ClawHit) - else - self:VoiceSound(self.ClawMiss) - end - end -end - -function ENT:NearDoor() - local doors = ents.FindInSphere(self:GetPos(), 50) - for k,v in pairs(doors) do - if string.find(v:GetClass(), "prop_door_") then - return v - end - end - - return nil -end - -local breakables = {} -breakables['func_breakable'] = true -breakables['func_physbox'] = true -breakables['prop_physics'] = true -breakables['prop_physics_multiplayer'] = true -breakables['func_breakable_surf'] = true - -function ENT:NearBreakable() - local doors = ents.FindInSphere(self:GetPos(), 50) - for k,v in pairs(doors) do - if breakables[v:GetClass()] then - return v - end - end - - return nil -end - -function ENT:GetRelationship(ent) - if ent:IsPlayer() then return D_HT end -end - -function ENT:SelectSchedule() - local enemy = self:GetEnemy() - local sched = SCHED_IDLE_WANDER - - if enemy and enemy:IsValid() then - if self:HasCondition(23) then - sched = SCHED_MELEE_ATTACK1 - self.AttackTime = CurTime() + 1 - --self:VoiceSound(self.VoiceSounds.Attack) - else - sched = SCHED_CHASE_ENEMY - end - else - self:UpdateEnemy(self:FindEnemy()) - end - - self:SetPlaybackRate(5) - self:SetSchedule(sched) -end \ No newline at end of file From 53b92ea56e0605c261b6df00b3619e52ac8efb56 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Wed, 8 May 2019 10:50:01 +1000 Subject: [PATCH 16/54] Infection: Zombie code cleanup --- .../entities/entities/npc_zo_base.lua | 66 ++++++++++++++++--- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index 0414d37..8dd94a0 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -69,6 +69,7 @@ ENT.PainSounds = { 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") @@ -88,6 +89,7 @@ function ENT:Initialize() 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)) @@ -95,6 +97,7 @@ function ENT:CollisionSetup(side, height, group) self.NEXTBOT = true end +-- Color the playermodel if set function ENT:GetPlayerColor() if not self.Color then return end local c = self.Color @@ -148,6 +151,7 @@ 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())}) @@ -161,15 +165,24 @@ function ENT:FindEnemy() 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 @@ -242,10 +255,10 @@ function ENT:ChaseEnemy() coroutine.yield() end - print('done chase') return true end +-- Check if the zombie is close enough to attack the enemy function ENT:CheckRange(enemy) local distance = self:DistanceToEnemy(enemy) @@ -260,6 +273,9 @@ 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 @@ -268,12 +284,16 @@ function ENT:OnInjured(info) 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, @@ -282,6 +302,8 @@ function ENT:CheckTrace() 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 @@ -297,33 +319,45 @@ function ENT:CheckTrace() 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()) @@ -333,21 +367,25 @@ function ENT:AttackDoor(v) door:SetColor(v:GetColor()) door:Spawn() door:EmitSound("Wood_Plank.Break") - door:SetCollisionGroup(COLLISION_GROUP_DEBRIS) + 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) @@ -355,23 +393,29 @@ function ENT:AttackObject(v) 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 ) + v:SetHealth(v:Health() - self.Damage) else if !v.Damaged then + -- ? v.Damaged = true v:SetHealth(50) else - v:GibBreakClient( Vector(0, 0, 0) ) + -- 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 @@ -379,19 +423,25 @@ function ENT:AttackEffect(time, ent, dmg, type) 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 @@ -407,20 +457,20 @@ end function ENT:AttackSound() -- Play an attack sound - self:PlaySound( table.Random(self.AttackSounds) ) + self:PlaySound(table.Random(self.AttackSounds)) end function ENT:IdleSound() -- Play an idle sound - self:PlaySound( table.Random(self.IdleSounds) ) + self:PlaySound(table.Random(self.IdleSounds)) end function ENT:DamageSound() -- Play a damage sound - self:PlaySound( table.Random(self.PainSounds) ) + self:PlaySound(table.Random(self.PainSounds)) end function ENT:AlertSound() -- Play alert sound - self:PlaySound( table.Random(self.AlertSounds) ) + self:PlaySound(table.Random(self.AlertSounds)) end \ No newline at end of file From 62d9601838f062909016b437662e38161564da56 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Wed, 8 May 2019 10:56:14 +1000 Subject: [PATCH 17/54] Infection: Zombie ragdolls removed after a brief timeframe --- fluffy_infection/entities/entities/npc_zo_base.lua | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index 8dd94a0..aaf41d5 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -269,6 +269,7 @@ function ENT:CheckRange(enemy) 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 @@ -284,6 +285,19 @@ function ENT:OnInjured(info) 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 From 3bf2fa56a51790e37377ba1b8798f7d14430f87b Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Wed, 8 May 2019 10:59:27 +1000 Subject: [PATCH 18/54] Infection: Stats tracking for Zombie deaths --- fluffy_infection/gamemode/init.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index ad95b7e..383f0b5 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -116,6 +116,19 @@ hook.Add('DoPlayerDeath', 'AwardLastSurvivorInfection', function(ply) 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() then + attacker:AddStatPoints('Zombies Killed', 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 From cefb94b5adb5abb0996d90aa15930961956b7947 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 07:52:28 +1000 Subject: [PATCH 19/54] Infection: 4 new zombie types --- .../entities/entities/npc_zo_base.lua | 2 +- .../entities/entities/npc_zombie_boom.lua | 30 +++++++++++++++++++ .../entities/entities/npc_zombie_corpse.lua | 19 ++++++++++++ .../entities/entities/npc_zombie_fast.lua | 19 ++++++++++++ .../entities/entities/npc_zombie_shadow.lua | 19 ++++++++++++ 5 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 fluffy_infection/entities/entities/npc_zombie_boom.lua create mode 100644 fluffy_infection/entities/entities/npc_zombie_corpse.lua create mode 100644 fluffy_infection/entities/entities/npc_zombie_fast.lua create mode 100644 fluffy_infection/entities/entities/npc_zombie_shadow.lua diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index aaf41d5..e27548d 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -2,7 +2,7 @@ AddCSLuaFile() ENT.Base = 'base_nextbot' if CLIENT then - language.Add('npc_skeleton_gold', 'Zombie' ) + language.Add('npc_zo_base', 'Basic Zombie' ) end -- Speed 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..95c016d --- /dev/null +++ b/fluffy_infection/entities/entities/npc_zombie_boom.lua @@ -0,0 +1,30 @@ +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) + 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.Owner or self, self:GetPos(), 175, 125) +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 From 3ef77b57d00038fe2b13bd6f09165683244031d5 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:12:48 +1000 Subject: [PATCH 20/54] Infection: Nerfed player zombies --- fluffy_infection/gamemode/init.lua | 11 +++++++---- fluffy_infection/gamemode/shared.lua | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 383f0b5..21dd806 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -26,9 +26,9 @@ function GM:PlayerLoadout( ply ) -- Initial infected are stronger but slower ply:SetBloodColor(BLOOD_COLOR_GREEN) ply:Give('weapon_fists') - ply:SetMaxHealth(200) - ply:SetHealth(200) - ply:SetRunSpeed(400) + ply:SetMaxHealth(125) + ply:SetHealth(125) + ply:SetRunSpeed(300) ply:SetWalkSpeed(250) end end @@ -36,7 +36,7 @@ end -- Pick player models function GM:PlayerSetModel( ply ) if ply:Team() == TEAM_RED then - ply:SetModel("models/player/zombie_soldier.mdl") + ply:SetModel("models/player/zombie_classic.mdl") else GAMEMODE.BaseClass:PlayerSetModel(ply) end @@ -60,6 +60,9 @@ hook.Add('PreRoundStart', 'InfectionResetPlayers', function() if v:Team() == TEAM_SPECTATOR then continue end v:SetTeam(TEAM_BLUE) end + + GAMEMODE.WaveNumber = 1 + GAMEMODE.WaveTimer = 0 end) -- Stop Zombies from switching back to the other team diff --git a/fluffy_infection/gamemode/shared.lua b/fluffy_infection/gamemode/shared.lua index 8e38834..79e8deb 100644 --- a/fluffy_infection/gamemode/shared.lua +++ b/fluffy_infection/gamemode/shared.lua @@ -21,7 +21,7 @@ TEAM_BLUE = 2 GM.TeamBased = true GM.TeamSurvival = false -GM.RoundNumber = 20 -- How many rounds? +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? :( From f70a0f5e0708def2d709cf5d5d183200331bca8a Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:19:37 +1000 Subject: [PATCH 21/54] Infection: Stop Infected players getting points for killing zombies --- fluffy_infection/gamemode/init.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 21dd806..d9dfbdd 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -122,8 +122,9 @@ end) -- Stat Tracking for Zombie kills hook.Add('OnNPCKilled', 'ZombieKilledStats', function(npc, attacker, inflictor) local class = npc:GetClass() -- not yet relevant - if attacker:IsPlayer() then + if attacker:IsPlayer() and attacker:Team() == TEAM_BLUE then attacker:AddStatPoints('Zombies Killed', 1) + attacker:AddFrags(1) end end) From d67577f954f5ef167ff0aab91e4af166e7c2b284 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:21:50 +1000 Subject: [PATCH 22/54] Infection: Wave based system --- fluffy_infection/gamemode/sv_zombies.lua | 46 ++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/fluffy_infection/gamemode/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua index 764ba31..6aeaf16 100644 --- a/fluffy_infection/gamemode/sv_zombies.lua +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -26,19 +26,51 @@ function GM:CreateZombie(ztype) print('made a zombie!') end +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'}, +} + +function GM:WaveScaler(wave) + if wave <= 3 then + return 3 + elseif wave <= 6 then + return 4 + else + return 6 + end +end + hook.Add('Think', 'ZombieTimer', function() if GetGlobalString( 'RoundState' ) != 'InRound' then return end if not GAMEMODE.WaveTimer then GAMEMODE.WaveTimer = 0 end + if not GAMEMODE.WaveNumber then GAMEMODE.WaveNumber = 1 end + if GAMEMODE.WaveTimer < CurTime() then - print('Spawning wave..') - GAMEMODE.WaveTimer = CurTime() + math.random(15, 30) + GAMEMODE:PulseAnnouncement(2, 'Wave ' .. GAMEMODE.WaveNumber, 1) + + GAMEMODE.WaveTimer = CurTime() + 15 + GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 + local wave = GAMEMODE.Waves[GAMEMODE.WaveNumber] + local wavescale = GAMEMODE:WaveScaler(GAMEMODE.WaveNumber) + local playercount = math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 5) - for i=1,5 do - local type = 'npc_zo_base' - local r = math.random() - if r > 0.8 then type = 'npc_skeleton_gold' elseif r > 0.5 then type = 'npc_skeleton' end - GAMEMODE:CreateZombie(type) + for wi=1, wavescale do + timer.Simple(wavescale/10, function() + for i=1, playercount do + local type = table.Random(wave) + GAMEMODE:CreateZombie(type) + end + end) end end end) \ No newline at end of file From 62940b767aff1138b6e6a6b9f4aba207179436e3 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:39:21 +1000 Subject: [PATCH 23/54] Infection: Stop boom zombie from crashing the game --- fluffy_infection/entities/entities/npc_zombie_boom.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fluffy_infection/entities/entities/npc_zombie_boom.lua b/fluffy_infection/entities/entities/npc_zombie_boom.lua index 95c016d..2caa885 100644 --- a/fluffy_infection/entities/entities/npc_zombie_boom.lua +++ b/fluffy_infection/entities/entities/npc_zombie_boom.lua @@ -21,10 +21,14 @@ 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.Owner or self, self:GetPos(), 175, 125) + util.BlastDamage(self, self, self:GetPos(), 175, 125) + + self:Remove() end \ No newline at end of file From 12ba76a2d1d680d1ac5ece62fa08819b56178da5 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:39:43 +1000 Subject: [PATCH 24/54] Infection: Fix wave number bugs --- fluffy_infection/gamemode/init.lua | 2 +- fluffy_infection/gamemode/sv_zombies.lua | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index d9dfbdd..6c30fbb 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -61,7 +61,7 @@ hook.Add('PreRoundStart', 'InfectionResetPlayers', function() v:SetTeam(TEAM_BLUE) end - GAMEMODE.WaveNumber = 1 + GAMEMODE.WaveNumber = 0 GAMEMODE.WaveTimer = 0 end) diff --git a/fluffy_infection/gamemode/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua index 6aeaf16..7ba801d 100644 --- a/fluffy_infection/gamemode/sv_zombies.lua +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -55,11 +55,13 @@ hook.Add('Think', 'ZombieTimer', function() if not GAMEMODE.WaveTimer then GAMEMODE.WaveTimer = 0 end if not GAMEMODE.WaveNumber then GAMEMODE.WaveNumber = 1 end + if GAMEMODE.WaveNumber > #GAMEMODE.Waves then return end + GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 + if GAMEMODE.WaveTimer < CurTime() then GAMEMODE:PulseAnnouncement(2, 'Wave ' .. GAMEMODE.WaveNumber, 1) GAMEMODE.WaveTimer = CurTime() + 15 - GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 local wave = GAMEMODE.Waves[GAMEMODE.WaveNumber] local wavescale = GAMEMODE:WaveScaler(GAMEMODE.WaveNumber) local playercount = math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 5) From 87e6bcb90b04a5e89c312ece4035873f4a89dc5a Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:40:16 +1000 Subject: [PATCH 25/54] Infection: Attempt new 'Chase' pathing routine --- fluffy_infection/entities/entities/npc_zo_base.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index e27548d..cf0bd46 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -84,7 +84,7 @@ function ENT:Initialize() if CLIENT then return end self.loco:SetDesiredSpeed(self.Speed) - self.loco:SetAcceleration(self.Acceleration) + self.loco:SetAcceleration(self.Acceleration * 2) self.loco:SetDeceleration(self.Acceleration * 8) self:SetMaxHealth(self.BaseHealth) -- idk why this isn't clientside but hey, not my fault end @@ -151,6 +151,8 @@ function ENT:FindEnemy() local players = team.GetPlayers(TEAM_BLUE) local distances = {} + --if self:HaveEnemy() and self:DistSqrToEnemy() < 500000 then return true end + -- Get how far away every living player is for k,v in pairs(players) do if v.Spectating then continue end @@ -207,6 +209,7 @@ end -- hi my name is zombie i'm lazy function ENT:Idle() + self.loco:SetDesiredSpeed(self.Speed) 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 @@ -215,10 +218,11 @@ end function ENT:ChaseEnemy() local enemy = self:GetEnemy() local pos = enemy:GetPos() + self.loco:SetDesiredSpeed(self.Speed) self:MovementFunctions(1, self.WalkAnim, 0, 1) -- Pathing stuff? - local path = Path('Follow') + local path = Path('Chase') path:SetMinLookAheadDistance(300) path:SetGoalTolerance(20) local result = path:Compute(self, pos) @@ -228,7 +232,7 @@ function ENT:ChaseEnemy() if path:GetAge() > 1 then path:Compute(self, self:GetEnemy():GetPos()) end - path:Update(self) + path:Chase(self, self:GetEnemy()) --path:Draw() -- Ensure we are not stuck @@ -247,7 +251,7 @@ function ENT:ChaseEnemy() end -- Check the enemy every so often - if math.random() > 0.5 then + if math.random() > 0.95 then self:FindEnemy() end From ff90217545d9be65511b0d8737c9adceccf25bab Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 9 May 2019 08:43:53 +1000 Subject: [PATCH 26/54] Revert "Infection: Attempt new 'Chase' pathing routine" This reverts commit 87e6bcb90b04a5e89c312ece4035873f4a89dc5a. --- fluffy_infection/entities/entities/npc_zo_base.lua | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index cf0bd46..e27548d 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -84,7 +84,7 @@ function ENT:Initialize() if CLIENT then return end self.loco:SetDesiredSpeed(self.Speed) - self.loco:SetAcceleration(self.Acceleration * 2) + self.loco:SetAcceleration(self.Acceleration) self.loco:SetDeceleration(self.Acceleration * 8) self:SetMaxHealth(self.BaseHealth) -- idk why this isn't clientside but hey, not my fault end @@ -151,8 +151,6 @@ function ENT:FindEnemy() local players = team.GetPlayers(TEAM_BLUE) local distances = {} - --if self:HaveEnemy() and self:DistSqrToEnemy() < 500000 then return true end - -- Get how far away every living player is for k,v in pairs(players) do if v.Spectating then continue end @@ -209,7 +207,6 @@ end -- hi my name is zombie i'm lazy function ENT:Idle() - self.loco:SetDesiredSpeed(self.Speed) 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 @@ -218,11 +215,10 @@ end function ENT:ChaseEnemy() local enemy = self:GetEnemy() local pos = enemy:GetPos() - self.loco:SetDesiredSpeed(self.Speed) self:MovementFunctions(1, self.WalkAnim, 0, 1) -- Pathing stuff? - local path = Path('Chase') + local path = Path('Follow') path:SetMinLookAheadDistance(300) path:SetGoalTolerance(20) local result = path:Compute(self, pos) @@ -232,7 +228,7 @@ function ENT:ChaseEnemy() if path:GetAge() > 1 then path:Compute(self, self:GetEnemy():GetPos()) end - path:Chase(self, self:GetEnemy()) + path:Update(self) --path:Draw() -- Ensure we are not stuck @@ -251,7 +247,7 @@ function ENT:ChaseEnemy() end -- Check the enemy every so often - if math.random() > 0.95 then + if math.random() > 0.5 then self:FindEnemy() end From 9c464b299e3094aa73e9c6a7dc1a986d58e4ccf7 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 11:48:18 +1000 Subject: [PATCH 27/54] Update .gitignore so base materials are now synced --- .gitignore | 1 - 1 file changed, 1 deletion(-) 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/ From d7282e545aa109868b9c524843f0b2642737dad3 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 11:50:00 +1000 Subject: [PATCH 28/54] Paintball: Fixed 10 second time penalty Will adjust if needed and introduce a proper lives system as required! --- fluffy_paintball/gamemode/init.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fluffy_paintball/gamemode/init.lua b/fluffy_paintball/gamemode/init.lua index 2f9d7dc..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() From 9d3c7969bcb144e5aab818da5cd93af589ed3508 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 12:05:47 +1000 Subject: [PATCH 29/54] Duck Hunt: Code cleanup --- .../entities/entities/svr_wintrigger/init.lua | 45 ++----------------- fluffy_duckhunt/gamemode/init.lua | 8 ++-- 2 files changed, 8 insertions(+), 45 deletions(-) diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua b/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua index 4cadd62..a34dd31 100644 --- a/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua +++ b/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua @@ -1,47 +1,10 @@ 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 +-- Award players that touch this entity a round win +function ENT:StartTouch(ent) + if IsValid(self) and ent:IsPlayer() then self:Remove() - GAMEMODE:EndRound( entity ) + GAMEMODE:EndRound(ent) 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..51c5879 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(325) + ply:SetRunSpeed(375) elseif ply:Team() == TEAM_RED then -- Snipers ply:Give('sniper_normal') - ply:SetWalkSpeed( 425 ) - ply:SetRunSpeed( 500 ) + ply:SetWalkSpeed(425) + ply:SetRunSpeed(500) end end From 39931709c8d453b4931b121b433538b3be32845c Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 12:06:23 +1000 Subject: [PATCH 30/54] Duck Hunt: Stats improvements --- fluffy_duckhunt/gamemode/init.lua | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fluffy_duckhunt/gamemode/init.lua b/fluffy_duckhunt/gamemode/init.lua index 51c5879..651654c 100644 --- a/fluffy_duckhunt/gamemode/init.lua +++ b/fluffy_duckhunt/gamemode/init.lua @@ -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 From ebeb657c3fe89f8dbcdba3a17d7e3b2c7caa0db9 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 12:42:31 +1000 Subject: [PATCH 31/54] Bombtag: Statistic updates --- fluffy_bombtag/entities/weapons/bt_bomb.lua | 1 + fluffy_bombtag/gamemode/init.lua | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) 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/gamemode/init.lua b/fluffy_bombtag/gamemode/init.lua index 0c76c5b..6d18298 100644 --- a/fluffy_bombtag/gamemode/init.lua +++ b/fluffy_bombtag/gamemode/init.lua @@ -100,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 From b0c738375226f06e1910b89fd34c4b91ecd0be3d Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 12:52:44 +1000 Subject: [PATCH 32/54] Duck Hunt: Win entity restructure Can now use "duckhunt_win" as an alternative to "svr_wintrigger" --- .../{svr_wintrigger/init.lua => duckhunt_win.lua} | 0 fluffy_duckhunt/entities/entities/svr_wintrigger.lua | 10 ++++++++++ 2 files changed, 10 insertions(+) rename fluffy_duckhunt/entities/entities/{svr_wintrigger/init.lua => duckhunt_win.lua} (100%) create mode 100644 fluffy_duckhunt/entities/entities/svr_wintrigger.lua diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua b/fluffy_duckhunt/entities/entities/duckhunt_win.lua similarity index 100% rename from fluffy_duckhunt/entities/entities/svr_wintrigger/init.lua rename to fluffy_duckhunt/entities/entities/duckhunt_win.lua diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger.lua b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua new file mode 100644 index 0000000..a34dd31 --- /dev/null +++ b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua @@ -0,0 +1,10 @@ +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 + self:Remove() + GAMEMODE:EndRound(ent) + end +end \ No newline at end of file From fc0cb62a98bf3e2786cb516c01c1625f997224c2 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 13:20:43 +1000 Subject: [PATCH 33/54] Stalker: Enable flashlight for humans --- fluffy_stalker/gamemode/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 4b5da5f..62defc4 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -78,10 +78,16 @@ 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) +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) From 45b0678568753e3ad25d65eec466b7776d308ab2 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 13:24:43 +1000 Subject: [PATCH 34/54] Stalker: Ammo changes --- fluffy_stalker/gamemode/init.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 62defc4..2899c4c 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -8,6 +8,7 @@ include('shared.lua') -- Makes the Stalker invisible and sets HP function GM:PlayerLoadout(ply) ply:StripWeapons() + ply:StripAmmo() if ply:Team() == TEAM_RED then -- Stalker loadout here @@ -39,6 +40,11 @@ function GM:PlayerLoadout(ply) else ply:Give('weapon_pistol') end + + -- Some (but not a whole heap) of ammo for all the guns + ply:GiveAmmo(24, 'Pistol', true) + ply:GiveAmmo(60, 'SMG1', true) + ply:GiveAmmo(18, 'Buckshot', true) end end From fd4cd1447e537db4bf9dd483664b38f6ba1d2ea9 Mon Sep 17 00:00:00 2001 From: Robert Fraser Date: Sun, 12 May 2019 13:29:08 +1000 Subject: [PATCH 35/54] Various: Enable spawn protection Dodgeball, Duck Hunt, and Sniper Wars all now have spawn protection enabled - default value of 5 seconds --- fluffy_dodgeball/gamemode/shared.lua | 2 ++ fluffy_duckhunt/gamemode/shared.lua | 1 + fluffy_sniperwars/gamemode/shared.lua | 2 ++ 3 files changed, 5 insertions(+) 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/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_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 From 437e19835175d58b64421549f2f4129a79a39592 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Mon, 13 May 2019 08:09:24 +1000 Subject: [PATCH 36/54] Base: Sync patterns to github repo --- .../content/materials/fluffy/pattern1.png | Bin 0 -> 35971 bytes .../content/materials/fluffy/pattern2.png | Bin 0 -> 32133 bytes .../content/materials/fluffy/pattern3.png | Bin 0 -> 36324 bytes .../content/materials/fluffy/pattern5.png | Bin 0 -> 17732 bytes .../content/materials/fluffy/pattern6.png | Bin 0 -> 15611 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 fluffy_mg_base/content/materials/fluffy/pattern1.png create mode 100644 fluffy_mg_base/content/materials/fluffy/pattern2.png create mode 100644 fluffy_mg_base/content/materials/fluffy/pattern3.png create mode 100644 fluffy_mg_base/content/materials/fluffy/pattern5.png create mode 100644 fluffy_mg_base/content/materials/fluffy/pattern6.png 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 0000000000000000000000000000000000000000..3a65c2a543a209033f810a1eb24e05ab97ce2cce GIT binary patch literal 35971 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..fbb5620970f4221b1ddced34f1c45d8fa94d9bcc GIT binary patch literal 36324 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..613fba781e079de992e282a828e572839740700e GIT binary patch literal 17732 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 e?#R>b2%`|->z~DPa=^EvfRyDm#WLjDhy=r(Eq literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..0bed9bd000829a1bad9da331c1ec8a55faee2525 GIT binary patch literal 15611 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 (vT|4>(K~8OA(z4bR#6Erq1F9V<_*EOZ`Y~ zhcLQW$nA zjg)$u*0a@5Mo!6n=EMV@(QB<^1z`gDKW!Z>pfVYTjJc%}1a_U(j`#(M&*uX=USqBY z>$&L6X!q&oC|3%yO$gp|>#hu_YqK!78#H_P%2}38MHR-eM_GU*f^szW!P*&ubT6XCUCeK_gVcFFB83%pXo&k#= zZs0YbcK;aZy2-g zaWq~HmCi_37jY#&^E^&T4oSV=qoEJmx@;Gq2?@wR7>tZ3G${STDFET?2#aGbgg=?` z5!MoUeK7kzPi+Z}C`e7oh=@i+BqE|nXrpHbl$Kzg!7g6mN2M6QCUf;Z7)ub3lDWb`mVhL(;zZmVWb&~Lk z$_~;bq7SwVEys*9q?B$-&FuoVN(8pD1M74VvgG_w2~(uj$u%n<2@ zgqrxgoB~y@3nW4v&MjH!VrEMi#j_TUa)6^KeCL84qZftwfR~yE#k{|_kA B9033T literal 0 HcmV?d00001 From 3836d103ef5b24609722c1d91d483870328e2417 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Tue, 14 May 2019 12:49:27 +1000 Subject: [PATCH 37/54] Duck Hunt: Fix bug where Snipers and Spectators could win --- fluffy_duckhunt/entities/entities/duckhunt_win.lua | 2 ++ fluffy_duckhunt/entities/entities/svr_wintrigger.lua | 2 ++ 2 files changed, 4 insertions(+) diff --git a/fluffy_duckhunt/entities/entities/duckhunt_win.lua b/fluffy_duckhunt/entities/entities/duckhunt_win.lua index a34dd31..4e72c78 100644 --- a/fluffy_duckhunt/entities/entities/duckhunt_win.lua +++ b/fluffy_duckhunt/entities/entities/duckhunt_win.lua @@ -4,6 +4,8 @@ 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 diff --git a/fluffy_duckhunt/entities/entities/svr_wintrigger.lua b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua index a34dd31..4e72c78 100644 --- a/fluffy_duckhunt/entities/entities/svr_wintrigger.lua +++ b/fluffy_duckhunt/entities/entities/svr_wintrigger.lua @@ -4,6 +4,8 @@ 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 From 1aa9d853b10bdbd4b7c1e08bff3d79734a36c252 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 19:03:09 +1000 Subject: [PATCH 38/54] Duck Hunt: Mild speed buffs --- fluffy_duckhunt/gamemode/init.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fluffy_duckhunt/gamemode/init.lua b/fluffy_duckhunt/gamemode/init.lua index 651654c..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 From 1fda7d2fc85e0385fbe26961e950a3ff0a10197a Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:37:11 +1000 Subject: [PATCH 39/54] Base: Fix bug with unusual damage types --- fluffy_mg_base/gamemode/init.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fluffy_mg_base/gamemode/init.lua b/fluffy_mg_base/gamemode/init.lua index 8897542..2ad9f58 100644 --- a/fluffy_mg_base/gamemode/init.lua +++ b/fluffy_mg_base/gamemode/init.lua @@ -348,8 +348,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 From 851232dee42cfc40ac40c27250998c4853e2eca3 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:37:23 +1000 Subject: [PATCH 40/54] Stalker: Revert to simpler nametag format --- fluffy_stalker/gamemode/cl_init.lua | 56 ++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) 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 From 28116aa9e1355a8b2e8e905228f87de72f18d3a7 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:37:51 +1000 Subject: [PATCH 41/54] Stalker: Various balancing --- fluffy_stalker/gamemode/init.lua | 14 ++++++++------ fluffy_stalker/gamemode/shared.lua | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 2899c4c..c6e1095 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -16,9 +16,9 @@ function GM:PlayerLoadout(ply) ply:SetRenderMode(RENDERMODE_TRANSALPHA) ply:SetWalkSpeed(500) ply:SetRunSpeed(500) + ply:SetJumpPower(500) - - local hp = 150 + math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 16) * 10 + local hp = 150 + math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 16) * 35 ply:SetHealth(hp) ply:SetMaxHealth(hp) ply:Give('weapon_fists') @@ -29,6 +29,7 @@ function GM:PlayerLoadout(ply) ply:SetWalkSpeed(200) ply:SetRunSpeed(300) + ply:SetJumpPower(200) -- Pick from one of three random loadouts if math.random() > 0.8 then @@ -42,9 +43,10 @@ function GM:PlayerLoadout(ply) end -- Some (but not a whole heap) of ammo for all the guns - ply:GiveAmmo(24, 'Pistol', true) - ply:GiveAmmo(60, 'SMG1', true) - ply:GiveAmmo(18, 'Buckshot', true) + ply:Give('weapon_stunstick') + ply:GiveAmmo(200, 'Pistol', true) + ply:GiveAmmo(120, 'SMG1', true) + ply:GiveAmmo(32, 'Buckshot', true) end end @@ -54,7 +56,7 @@ function GM:PlayerSetModel( ply ) ply:SetModel('models/player/soldier_stripped.mdl') else ply:SetModel('models/player/combine_soldier_prisonguard.mdl') - GAMEMODE.BaseClass:PlayerSetModel(ply) + --GAMEMODE.BaseClass:PlayerSetModel(ply) end end diff --git a/fluffy_stalker/gamemode/shared.lua b/fluffy_stalker/gamemode/shared.lua index 951eeff..e62ff57 100644 --- a/fluffy_stalker/gamemode/shared.lua +++ b/fluffy_stalker/gamemode/shared.lua @@ -10,7 +10,7 @@ GM.TeamBased = true -- Is the gamemode FFA or Teams? GM.Elimination = true GM.WinBySurvival = true -GM.RoundTime = 90 +GM.RoundTime = 120 GM.RoundNumber = 10 function GM:Initialize() From fd930ce43a9da2d852ea3caa0e9880475e2a9b77 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:38:02 +1000 Subject: [PATCH 42/54] Stalker: Spectators can no longer use flashlight --- fluffy_stalker/gamemode/init.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index c6e1095..9ce878f 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -93,7 +93,7 @@ end -- Flashlight enabled for humans only function GM:PlayerSwitchFlashlight(ply, state) - return (ply:Team() == TEAM_BLUE) + return (ply:Team() == TEAM_BLUE and not ply.Spectating) end -- Stop any form of team swapping in this gamemode From aa983a4d1dbdfff57d5e33b89f78890699f08539 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:52:25 +1000 Subject: [PATCH 43/54] Infection: Zombie wave code rework --- fluffy_infection/gamemode/sv_zombies.lua | 64 ++++++++++++++++-------- 1 file changed, 44 insertions(+), 20 deletions(-) diff --git a/fluffy_infection/gamemode/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua index 7ba801d..108ef16 100644 --- a/fluffy_infection/gamemode/sv_zombies.lua +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -1,8 +1,12 @@ +-- 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() @@ -10,22 +14,26 @@ function GM:GetZombieSpawns() 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() - print('made a zombie!') 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'}, @@ -38,7 +46,10 @@ GM.Waves = { {'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 @@ -49,30 +60,43 @@ function GM:WaveScaler(wave) 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 = 1 end - - if GAMEMODE.WaveNumber > #GAMEMODE.Waves then return end - GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 - - if GAMEMODE.WaveTimer < CurTime() then - GAMEMODE:PulseAnnouncement(2, 'Wave ' .. GAMEMODE.WaveNumber, 1) + if not GAMEMODE.WaveNumber then GAMEMODE.WaveNumber = 0 end + if GAMEMODE.WaveNumber > GM.WaveMax then return end + -- Spawn waves if the timer has hit + if CurTime() > GAMEMODE.WaveTimer then GAMEMODE.WaveTimer = CurTime() + 15 - local wave = GAMEMODE.Waves[GAMEMODE.WaveNumber] - local wavescale = GAMEMODE:WaveScaler(GAMEMODE.WaveNumber) - local playercount = math.Clamp(team.NumPlayers(TEAM_BLUE), 1, 5) - - for wi=1, wavescale do - timer.Simple(wavescale/10, function() - for i=1, playercount do - local type = table.Random(wave) - GAMEMODE:CreateZombie(type) - end - end) - end + GAMEMODE.WaveNumber = GAMEMODE.WaveNumber + 1 + GAMEMODE:SpawnWave(GAMEMODE.WaveNumber) end end) \ No newline at end of file From 3d1e789d62043642831766080c1f8137bd459c34 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 20:58:21 +1000 Subject: [PATCH 44/54] Microgames: Added support for map restrictions --- fluffy_microgames/gamemode/init.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/fluffy_microgames/gamemode/init.lua b/fluffy_microgames/gamemode/init.lua index 87c137e..ff53bf8 100644 --- a/fluffy_microgames/gamemode/init.lua +++ b/fluffy_microgames/gamemode/init.lua @@ -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) From 90430fcc9b8a61157c8d4666cc9576e092f33480 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 21:41:06 +1000 Subject: [PATCH 45/54] Base: Made FFA Color less redundant --- fluffy_mg_base/gamemode/sv_player.lua | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 From ebad8a524de0f59026d53d397c8df3d00151435a Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 21:42:00 +1000 Subject: [PATCH 46/54] Stalker: Playermodel and colour changes for humans --- fluffy_stalker/gamemode/init.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/fluffy_stalker/gamemode/init.lua b/fluffy_stalker/gamemode/init.lua index 9ce878f..2a2adaf 100644 --- a/fluffy_stalker/gamemode/init.lua +++ b/fluffy_stalker/gamemode/init.lua @@ -55,9 +55,16 @@ function GM:PlayerSetModel( ply ) if ply:Team() == TEAM_RED then ply:SetModel('models/player/soldier_stripped.mdl') else - ply:SetModel('models/player/combine_soldier_prisonguard.mdl') + 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 From d0a8b68e9e1a77e2f53bac7fcf59e1968df18301 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Thu, 16 May 2019 21:42:56 +1000 Subject: [PATCH 47/54] Stalker: Fix team names --- fluffy_stalker/gamemode/shared.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/fluffy_stalker/gamemode/shared.lua b/fluffy_stalker/gamemode/shared.lua index e62ff57..59458bc 100644 --- a/fluffy_stalker/gamemode/shared.lua +++ b/fluffy_stalker/gamemode/shared.lua @@ -15,4 +15,17 @@ GM.RoundNumber = 10 function GM:Initialize() +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 \ No newline at end of file From 8745bebd33cad18041e457a372596835b28f506c Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:28:44 +1000 Subject: [PATCH 48/54] Base: Remove unneeded VGUI includes --- fluffy_mg_base/gamemode/cl_init.lua | 3 --- fluffy_mg_base/gamemode/init.lua | 3 --- 2 files changed, 6 deletions(-) diff --git a/fluffy_mg_base/gamemode/cl_init.lua b/fluffy_mg_base/gamemode/cl_init.lua index ee3ea12..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 diff --git a/fluffy_mg_base/gamemode/init.lua b/fluffy_mg_base/gamemode/init.lua index 2ad9f58..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') From c2ff8b71c61bef6cde9cf700f3fc1d437143ced2 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:29:04 +1000 Subject: [PATCH 49/54] Infection: Rework to use team survival codebase --- fluffy_infection/gamemode/init.lua | 46 ---------------------------- fluffy_infection/gamemode/shared.lua | 4 ++- 2 files changed, 3 insertions(+), 47 deletions(-) diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 6c30fbb..96a7639 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -42,45 +42,12 @@ function GM: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 new players join the Hunter team on connection -function GM:PlayerInitialSpawn(ply) - ply:SetTeam(TEAM_RED) -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 - GAMEMODE.WaveNumber = 0 GAMEMODE.WaveTimer = 0 end) --- Stop Zombies from switching back to the other team -hook.Add('PlayerCanJoinTeam', 'StopZombieSwap', function(ply, team) - local current_team = ply:Team() - if current_team == TEAM_RED then - ply:ChatPrint('You cannot change teams currently') - return false - end -end) - --- Assign dead survivors to the hunter team -hook.Add('PlayerDeath', 'InfectionDeath', function(ply) - if ply:Team() == TEAM_BLUE then - ply:SetTeam(TEAM_RED) - end -end) - -- Get the last player on the human team function GM:GetLastPlayer(exclude_player) local last_alive = nil @@ -106,19 +73,6 @@ function GM:CanRoundStart() end end --- Last Survivor gets a message and a stat bonus -hook.Add('DoPlayerDeath', 'AwardLastSurvivorInfection', function(ply) - if ply:Team() != TEAM_BLUE then return end - - local last_player = GAMEMODE:GetLastPlayer(ply) - if IsValid(last_player) and last_player != false then - -- Award the last survivor bonus - local name = string.sub(last_player:Nick(), 1, 10) - GAMEMODE:PulseAnnouncement(4, name .. ' is the lone survivor!', 0.8) - last_player:AddStatPoints('Last Survivor', 1) - end -end) - -- Stat Tracking for Zombie kills hook.Add('OnNPCKilled', 'ZombieKilledStats', function(npc, attacker, inflictor) local class = npc:GetClass() -- not yet relevant diff --git a/fluffy_infection/gamemode/shared.lua b/fluffy_infection/gamemode/shared.lua index 79e8deb..aad38e3 100644 --- a/fluffy_infection/gamemode/shared.lua +++ b/fluffy_infection/gamemode/shared.lua @@ -19,7 +19,9 @@ TEAM_BLUE = 2 -- Configure teams for Hunter vs Hunted GM.TeamBased = true -GM.TeamSurvival = false +GM.TeamSurvival = true +GM.SurvivorTeam = TEAM_BLUE +GM.HunterTeam = TEAM_RED GM.RoundNumber = 5 -- How many rounds? GM.RoundTime = 180 -- How long should each round go for? From 30c2710647631a32b7437a79fe5846410faa22b1 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:29:17 +1000 Subject: [PATCH 50/54] Infection: Fix typo in wave code --- fluffy_infection/gamemode/sv_zombies.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fluffy_infection/gamemode/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua index 108ef16..90374d5 100644 --- a/fluffy_infection/gamemode/sv_zombies.lua +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -91,7 +91,7 @@ hook.Add('Think', 'ZombieTimer', function() -- 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 > GM.WaveMax then return end + if GAMEMODE.WaveNumber > GAMEMODE.WaveMax then return end -- Spawn waves if the timer has hit if CurTime() > GAMEMODE.WaveTimer then From 9add4bc63663c60cf197ac48baae86f05592f881 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:36:03 +1000 Subject: [PATCH 51/54] Infection: BVarious balancing changes - Fix zombie speed - Nerf player zombies - Mild typo fixes --- fluffy_infection/entities/entities/npc_zo_base.lua | 7 +++++-- fluffy_infection/gamemode/init.lua | 10 +++++----- fluffy_infection/gamemode/sv_zombies.lua | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/fluffy_infection/entities/entities/npc_zo_base.lua b/fluffy_infection/entities/entities/npc_zo_base.lua index e27548d..66a906a 100644 --- a/fluffy_infection/entities/entities/npc_zo_base.lua +++ b/fluffy_infection/entities/entities/npc_zo_base.lua @@ -85,7 +85,7 @@ function ENT:Initialize() if CLIENT then return end self.loco:SetDesiredSpeed(self.Speed) self.loco:SetAcceleration(self.Acceleration) - self.loco:SetDeceleration(self.Acceleration * 8) + self.loco:SetDeceleration(self.Acceleration * 0.2) self:SetMaxHealth(self.BaseHealth) -- idk why this isn't clientside but hey, not my fault end @@ -216,6 +216,7 @@ 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') @@ -228,7 +229,9 @@ function ENT:ChaseEnemy() 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 @@ -247,7 +250,7 @@ function ENT:ChaseEnemy() end -- Check the enemy every so often - if math.random() > 0.5 then + if math.random() > 0.99 then self:FindEnemy() end diff --git a/fluffy_infection/gamemode/init.lua b/fluffy_infection/gamemode/init.lua index 96a7639..6603912 100644 --- a/fluffy_infection/gamemode/init.lua +++ b/fluffy_infection/gamemode/init.lua @@ -26,10 +26,10 @@ function GM:PlayerLoadout( ply ) -- Initial infected are stronger but slower ply:SetBloodColor(BLOOD_COLOR_GREEN) ply:Give('weapon_fists') - ply:SetMaxHealth(125) - ply:SetHealth(125) - ply:SetRunSpeed(300) - ply:SetWalkSpeed(250) + ply:SetMaxHealth(100) + ply:SetHealth(100) + ply:SetRunSpeed(250) + ply:SetWalkSpeed(225) end end @@ -91,6 +91,6 @@ 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/sv_zombies.lua b/fluffy_infection/gamemode/sv_zombies.lua index 90374d5..dbcf6e7 100644 --- a/fluffy_infection/gamemode/sv_zombies.lua +++ b/fluffy_infection/gamemode/sv_zombies.lua @@ -91,7 +91,7 @@ hook.Add('Think', 'ZombieTimer', function() -- 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 + if GAMEMODE.WaveNumber >= GAMEMODE.WaveMax then return end -- Spawn waves if the timer has hit if CurTime() > GAMEMODE.WaveTimer then From 48579d91e860f951b098de49002cd6e32b249395 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:48:44 +1000 Subject: [PATCH 52/54] Item: Reworked ShouldDrawCosmetics hook --- fluffy_mg_base/gamemode/gametype_hunter.lua | 13 ------------- fluffy_mg_base/gamemode/shared.lua | 6 ------ fluffy_mg_base/gamemode/shop/sh_init.lua | 7 +++++++ fluffy_mg_base/gamemode/shop/sv_equip.lua | 5 ++++- 4 files changed, 11 insertions(+), 20 deletions(-) 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/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/sh_init.lua b/fluffy_mg_base/gamemode/shop/sh_init.lua index 7febbca..2120367 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:ShouldDrawCosmetics(ply, ITEM) + return hook.Run('DrawCosmeticsCheck', 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..438feb0 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:ShouldDrawCosmetics(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:ShouldDrawCosmetics(ent, {Type='Tracer'}) then return end data.Tracer = 1 data.TracerName = effect From 045c5e0bbf351c11f8eed4b84cbd9411ecea9e68 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 11:56:59 +1000 Subject: [PATCH 53/54] Various: Add ShouldDrawCosmetics hooks Appropiate hook additions to the following gamemodes: - Staker - Poltergeist - Suicide Barrels - Laser Dance --- fluffy_laserdance/gamemode/shared.lua | 8 +++++++- fluffy_mg_base/gamemode/shop/cl_render.lua | 2 +- fluffy_mg_base/gamemode/shop/sh_init.lua | 4 ++-- fluffy_mg_base/gamemode/shop/sv_equip.lua | 4 ++-- fluffy_poltergeist/gamemode/shared.lua | 7 ++++++- fluffy_stalker/gamemode/shared.lua | 8 +++++++- fluffy_suicidebarrels/gamemode/shared.lua | 7 ++++++- 7 files changed, 31 insertions(+), 9 deletions(-) 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/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 2120367..3fea86e 100644 --- a/fluffy_mg_base/gamemode/shop/sh_init.lua +++ b/fluffy_mg_base/gamemode/shop/sh_init.lua @@ -94,8 +94,8 @@ 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:ShouldDrawCosmetics(ply, ITEM) - return hook.Run('DrawCosmeticsCheck', ply, ITEM) or true +function GM:DoCosmeticsCheck(ply, ITEM) + return hook.Run('ShouldDrawCosmetics', ply, ITEM) or true end SHOP:LoadResources() diff --git a/fluffy_mg_base/gamemode/shop/sv_equip.lua b/fluffy_mg_base/gamemode/shop/sv_equip.lua index 438feb0..24655a9 100644 --- a/fluffy_mg_base/gamemode/shop/sv_equip.lua +++ b/fluffy_mg_base/gamemode/shop/sv_equip.lua @@ -41,7 +41,7 @@ end function SHOP:WearTrail(ply, force) if ply.EquippedTrail then local ITEM = ply.EquippedTrail - if not GAMEMODE:ShouldDrawCosmetics(ply, ITEM) and not force then return end + 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 @@ -75,7 +75,7 @@ hook.Add('EntityFireBullets', 'ShopTracerEffects', function(ent, data) local effect = ent:GetNWString('ShopTracerEffect') if not effect then return end - if not GAMEMODE:ShouldDrawCosmetics(ent, {Type='Tracer'}) then return end + if not GAMEMODE:DoCosmeticsCheck(ent, {Type='Tracer'}) then return end data.Tracer = 1 data.TracerName = effect 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_stalker/gamemode/shared.lua b/fluffy_stalker/gamemode/shared.lua index 59458bc..dad0175 100644 --- a/fluffy_stalker/gamemode/shared.lua +++ b/fluffy_stalker/gamemode/shared.lua @@ -28,4 +28,10 @@ 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 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/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 From 00cccdcdd24f17880dc4473fd093ccc5702c6820 Mon Sep 17 00:00:00 2001 From: FluffyXVI Date: Fri, 17 May 2019 12:10:38 +1000 Subject: [PATCH 54/54] Item: Inventory access for superadmins --- fluffy_mg_base/gamemode/shop/cl_inventory.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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()