├── gamemode ├── util │ ├── variables.sh.lua │ ├── trail.cl.lua │ ├── stamina.sv.lua │ ├── trail.sv.lua │ ├── util.cl.lua │ ├── net.cl.lua │ ├── pred-sound.cl.lua │ ├── palette.sh.lua │ ├── net.sv.lua │ ├── savepoint.sv.lua │ ├── pred-sound.sv.lua │ ├── time-associated-map.sh.lua │ ├── cubic-bezier.sh.lua │ ├── spectate.cl.lua │ ├── util.sh.lua │ ├── net.sh.lua │ ├── sound-isolation.sh.lua │ ├── spectate.sv.lua │ ├── class.sh.lua │ └── stamina.sh.lua ├── player │ ├── methods.cl.lua │ ├── methods.sh.lua │ ├── methods.sv.lua │ └── hooks.sh.lua ├── emm.lua ├── player-class │ ├── net.sh.lua │ ├── net.sv.lua │ ├── net.cl.lua │ ├── methods.sv.lua │ ├── player-class.sh.lua │ ├── methods.sh.lua │ └── methods.cl.lua ├── minigame │ ├── minigame.sv.lua │ ├── tagging.sh.lua │ ├── hooks.sh.lua │ ├── lobby.sh.lua │ ├── util.cl.lua │ ├── states.cl.lua │ ├── settings.cl.lua │ ├── settings.sv.lua │ ├── tagging.cl.lua │ ├── states.sv.lua │ ├── states.sh.lua │ ├── net.sh.lua │ ├── util.sh.lua │ ├── net.sv.lua │ ├── lobby.sv.lua │ ├── tagging.sv.lua │ ├── util.sv.lua │ ├── net.cl.lua │ ├── minigame.sh.lua │ ├── prototype.sh.lua │ └── lobby.cl.lua ├── movement │ ├── airaccel.sv.lua │ ├── walljump.sv.lua │ ├── airaccel.cl.lua │ ├── wallslide.sv.lua │ ├── walljump.cl.lua │ ├── autojump.sh.lua │ ├── airaccel-patch.sh.lua │ ├── slope.sh.lua │ ├── gravity.sh.lua │ ├── friction.sh.lua │ ├── wallslide.cl.lua │ ├── ledge-bounce.sh.lua │ └── airaccel.sh.lua ├── core │ ├── env.sv.lua │ └── gamemode.sh.lua ├── minigame_prototypes │ ├── miscellaneous.sh.lua │ ├── deathmatch.sh.lua │ ├── hunted.sh.lua │ └── elimination.sh.lua ├── ui │ ├── elements │ │ ├── text-bar.cl.lua │ │ ├── avatar-bar.cl.lua │ │ ├── meter-bar.cl.lua │ │ ├── scroll-container.cl.lua │ │ ├── player-bar.cl.lua │ │ ├── button-bar.cl.lua │ │ ├── input-bar.cl.lua │ │ ├── checkbox.cl.lua │ │ ├── number-input.cl.lua │ │ └── text-input.cl.lua │ ├── util.cl.lua │ ├── variables.cl.lua │ ├── ui.cl.lua │ ├── vardebug.cl.lua │ └── cam.cl.lua ├── settings │ ├── factories.cl.lua │ ├── ui.cl.lua │ └── settings.cl.lua ├── cl_init.lua ├── element │ ├── setters.cl.lua │ ├── states.cl.lua │ ├── paint.cl.lua │ └── panel.cl.lua ├── hud │ ├── factories.cl.lua │ ├── elements │ │ ├── key-echo.cl.lua │ │ ├── crosshair-line.cl.lua │ │ ├── indicator.cl.lua │ │ ├── notifications.cl.lua │ │ └── hud-meter.cl.lua │ ├── nametags.cl.lua │ ├── notifications.cl.lua │ └── variables.cl.lua ├── init.lua └── lobby-ui │ └── elements │ └── lobby-bar.cl.lua ├── logo.png ├── emm.txt ├── content ├── materials │ └── emm2 │ │ ├── ui │ │ ├── x.png │ │ ├── host.png │ │ ├── join.png │ │ ├── load.png │ │ ├── lock.png │ │ ├── save.png │ │ ├── check.png │ │ ├── leave.png │ │ ├── big-arrow.png │ │ ├── hamburger.png │ │ ├── restart.png │ │ └── spectate.png │ │ ├── hud │ │ ├── speed.png │ │ ├── airaccel.png │ │ ├── autojump.png │ │ ├── disabled.png │ │ ├── health.png │ │ ├── infinite.png │ │ ├── speed-2x.png │ │ ├── walljump.png │ │ ├── health-2x.png │ │ ├── wallslide.png │ │ ├── airaccel-2x.png │ │ ├── autojump-2x.png │ │ ├── disabled-2x.png │ │ ├── infinite-2x.png │ │ ├── walljump-2x.png │ │ └── wallslide-2x.png │ │ ├── shapes │ │ ├── arrow.png │ │ ├── circle.png │ │ └── arrow-2x.png │ │ ├── trails │ │ ├── flat.vtf │ │ └── flat.vmt │ │ └── minigames │ │ ├── hunted.png │ │ ├── hunted-2x.png │ │ ├── deathmatch.png │ │ ├── elimination.png │ │ ├── deathmatch-2x.png │ │ ├── elimination-2x.png │ │ ├── miscellaneous.png │ │ └── miscellaneous-2x.png └── resource │ └── fonts │ ├── Roboto-Black.ttf │ ├── Roboto-Bold.ttf │ ├── Roboto-Light.ttf │ ├── Roboto-Thin.ttf │ ├── Roboto-Italic.ttf │ ├── Roboto-Medium.ttf │ ├── Roboto-Regular.ttf │ ├── Roboto-BoldItalic.ttf │ ├── Roboto-ThinItalic.ttf │ ├── RobotoMono-Bold.ttf │ ├── RobotoMono-Italic.ttf │ ├── RobotoMono-Light.ttf │ ├── RobotoMono-Medium.ttf │ ├── RobotoMono-Thin.ttf │ ├── Roboto-BlackItalic.ttf │ ├── Roboto-LightItalic.ttf │ ├── Roboto-MediumItalic.ttf │ ├── RobotoMono-Regular.ttf │ ├── RobotoMono-BoldItalic.ttf │ ├── RobotoMono-LightItalic.ttf │ ├── RobotoMono-ThinItalic.ttf │ └── RobotoMono-MediumItalic.ttf ├── README.md ├── entities ├── effects │ ├── emm_ripple.lua │ └── emm_spark.lua └── entities │ └── emm_trail.lua ├── classes.md ├── minigames.md └── code-style-guide.md /gamemode/util/variables.sh.lua: -------------------------------------------------------------------------------- 1 | SAFE_FRAME = 1/60 -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/logo.png -------------------------------------------------------------------------------- /gamemode/player/methods.cl.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable("Player") -------------------------------------------------------------------------------- /emm.txt: -------------------------------------------------------------------------------- 1 | "emm" 2 | { 3 | "base" "base" 4 | "title" "Parkour" 5 | "menusystem" "1" 6 | } -------------------------------------------------------------------------------- /content/materials/emm2/ui/x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/x.png -------------------------------------------------------------------------------- /gamemode/emm.lua: -------------------------------------------------------------------------------- 1 | EMM.debug = false 2 | 3 | EMM.Include { 4 | "core/env", 5 | "core/gamemode" 6 | } 7 | -------------------------------------------------------------------------------- /content/materials/emm2/ui/host.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/host.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/join.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/load.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/load.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/lock.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/save.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/speed.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/check.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/leave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/leave.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/airaccel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/airaccel.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/autojump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/autojump.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/disabled.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/health.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/infinite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/infinite.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/speed-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/speed-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/walljump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/walljump.png -------------------------------------------------------------------------------- /content/materials/emm2/shapes/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/shapes/arrow.png -------------------------------------------------------------------------------- /content/materials/emm2/trails/flat.vtf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/trails/flat.vtf -------------------------------------------------------------------------------- /content/materials/emm2/ui/big-arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/big-arrow.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/hamburger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/hamburger.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/restart.png -------------------------------------------------------------------------------- /content/materials/emm2/ui/spectate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/ui/spectate.png -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Black.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Bold.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Light.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Thin.ttf -------------------------------------------------------------------------------- /content/materials/emm2/hud/health-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/health-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/wallslide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/wallslide.png -------------------------------------------------------------------------------- /content/materials/emm2/shapes/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/shapes/circle.png -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Italic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Medium.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-Regular.ttf -------------------------------------------------------------------------------- /content/materials/emm2/hud/airaccel-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/airaccel-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/autojump-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/autojump-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/disabled-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/disabled-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/infinite-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/infinite-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/walljump-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/walljump-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/hud/wallslide-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/hud/wallslide-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/minigames/hunted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/hunted.png -------------------------------------------------------------------------------- /content/materials/emm2/shapes/arrow-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/shapes/arrow-2x.png -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-ThinItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Italic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Light.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Medium.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Thin.ttf -------------------------------------------------------------------------------- /content/materials/emm2/minigames/hunted-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/hunted-2x.png -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-BlackItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-LightItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/Roboto-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/Roboto-MediumItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /content/materials/emm2/minigames/deathmatch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/deathmatch.png -------------------------------------------------------------------------------- /content/materials/emm2/minigames/elimination.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/elimination.png -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-BoldItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-LightItalic.ttf -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-ThinItalic.ttf -------------------------------------------------------------------------------- /content/materials/emm2/minigames/deathmatch-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/deathmatch-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/minigames/elimination-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/elimination-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/minigames/miscellaneous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/miscellaneous.png -------------------------------------------------------------------------------- /content/resource/fonts/RobotoMono-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/resource/fonts/RobotoMono-MediumItalic.ttf -------------------------------------------------------------------------------- /gamemode/player-class/net.sh.lua: -------------------------------------------------------------------------------- 1 | NetService.CreateSchema("PlayerClass", {"entity", "id"}) 2 | NetService.CreateUpstreamSchema "RequestPlayerClasses" -------------------------------------------------------------------------------- /content/materials/emm2/minigames/miscellaneous-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jep-a/emm/HEAD/content/materials/emm2/minigames/miscellaneous-2x.png -------------------------------------------------------------------------------- /content/materials/emm2/trails/flat.vmt: -------------------------------------------------------------------------------- 1 | "UnlitGeneric" 2 | { 3 | "$baseTexture" "emm2/trails/flat" 4 | 5 | "$additive" 0 6 | "$vertexcolor" 1 7 | "$vertexalpha" 1 8 | } 9 | -------------------------------------------------------------------------------- /gamemode/minigame/minigame.sv.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.CallNetHook(lobby, hk_name, ...) 2 | MinigameService.CallHook(lobby, hk_name, ...) 3 | NetService.Broadcast("Minigame."..hk_name, lobby, ...) 4 | end -------------------------------------------------------------------------------- /gamemode/minigame/tagging.sh.lua: -------------------------------------------------------------------------------- 1 | TaggingService = TaggingService or {} 2 | 3 | hook.Add("CreateMinigameHookSchemas", "TaggingService", function () 4 | MinigameNetService.CreateHookSchema("Tag", {"entity", "entity"}) 5 | end) -------------------------------------------------------------------------------- /gamemode/util/trail.cl.lua: -------------------------------------------------------------------------------- 1 | TrailService = TrailService or {} 2 | 3 | function TrailService.SetupOwner(ent) 4 | if ent:GetClass() == "emm_trail" then 5 | ent:GetOwner().trail = ent 6 | end 7 | end 8 | hook.Add("OnEntityCreated", "TrailService.SetupOwner", TrailService.SetupOwner) -------------------------------------------------------------------------------- /gamemode/movement/airaccel.sv.lua: -------------------------------------------------------------------------------- 1 | AiraccelService = AiraccelService or {} 2 | 3 | 4 | -- # Prediction handling 5 | 6 | function AiraccelService.HasStamina(ply) 7 | return ply.stamina.airaccel:HasStamina() 8 | end 9 | 10 | function AiraccelService.ReduceStamina(ply, value) 11 | ply.stamina.airaccel:ReduceStamina(value) 12 | end -------------------------------------------------------------------------------- /gamemode/util/stamina.sv.lua: -------------------------------------------------------------------------------- 1 | -- # Util 2 | 3 | util.AddNetworkString "UpdateStamina" 4 | 5 | function StaminaService.SendStamina(ply, target, stamina_type) 6 | net.Start "UpdateStamina" 7 | net.WriteEntity(target) 8 | net.WriteString(stamina_type) 9 | net.WriteTable(target.stamina[stamina_type]) 10 | net.Send(ply) 11 | end -------------------------------------------------------------------------------- /gamemode/movement/walljump.sv.lua: -------------------------------------------------------------------------------- 1 | WalljumpService = WalljumpService or {} 2 | 3 | 4 | -- # Server-side prediction 5 | 6 | function WalljumpService.CooledDown(ply) 7 | return CurTime() > (ply.last_walljump_time + ply.walljump_delay) 8 | end 9 | 10 | function WalljumpService.PlayedSound(ply) 11 | return not IsFirstTimePredicted() 12 | end -------------------------------------------------------------------------------- /gamemode/core/env.sv.lua: -------------------------------------------------------------------------------- 1 | hook.Add("Initialize", "EMM.SetupEnv", function () 2 | RunConsoleCommand("sv_gravity", 300) 3 | RunConsoleCommand("sv_sticktoground", 0) 4 | RunConsoleCommand("sv_maxvelocity", 10000) 5 | RunConsoleCommand("sv_accelerate", 10) 6 | RunConsoleCommand("sv_airaccelerate", 0) 7 | RunConsoleCommand("sv_friction", 0) 8 | end) 9 | -------------------------------------------------------------------------------- /gamemode/minigame_prototypes/miscellaneous.sh.lua: -------------------------------------------------------------------------------- 1 | MINIGAME.name = "Miscellaneous" 2 | MINIGAME.color = COLOR_CYAN 3 | MINIGAME.default_state = "Playing" 4 | MINIGAME.default_player_class = "Miscellaneous" 5 | MINIGAME.required_players = 0 6 | 7 | MINIGAME:AddPlayerClass { 8 | name = "Miscellaneous", 9 | display_name = false 10 | } 11 | 12 | MINIGAME:RemoveAdjustableSetting "states.Playing.time" -------------------------------------------------------------------------------- /gamemode/minigame/hooks.sh.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.SetupMove(ply, move, cmd) 2 | if ply.lobby then 3 | MinigameService.CallHook(ply.lobby, "SetupMove", ply) 4 | 5 | local ply_class = ply.player_class 6 | 7 | if ply_class and ply_class.SetupMove then 8 | ply_class.SetupMove(ply, move, cmd) 9 | end 10 | end 11 | end 12 | hook.Add("SetupMove", "MinigameService.SetupMove", MinigameService.SetupMove) -------------------------------------------------------------------------------- /gamemode/minigame_prototypes/deathmatch.sh.lua: -------------------------------------------------------------------------------- 1 | MINIGAME.name = "Deathmatch" 2 | MINIGAME.color = COLOR_RED 3 | MINIGAME.default_state = "Playing" 4 | MINIGAME.default_player_class = "Fragger" 5 | MINIGAME.required_players = 0 6 | 7 | MINIGAME:AddPlayerClass { 8 | name = "Fragger", 9 | display_name = false, 10 | 11 | weapons = { 12 | weapon_crowbar = true, 13 | weapon_357 = true, 14 | weapon_crossbow = true 15 | } 16 | } -------------------------------------------------------------------------------- /gamemode/minigame/lobby.sh.lua: -------------------------------------------------------------------------------- 1 | MinigameLobby = MinigameLobby or Class.New() 2 | 3 | function MinigameLobby:__index(key) 4 | local proto = rawget(self, "prototype") 5 | 6 | if proto then 7 | local proto_mt_val = proto[key] 8 | 9 | if proto_mt_val ~= nil then 10 | return proto_mt_val 11 | end 12 | end 13 | 14 | local lobby_mt_val = rawget(MinigameLobby, key) 15 | 16 | if lobby_mt_val ~= nil then 17 | return lobby_mt_val 18 | end 19 | end -------------------------------------------------------------------------------- /gamemode/minigame/util.cl.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.IsLocalLobby(ent_or_lobby) 2 | local is_local 3 | local lobby 4 | 5 | if isentity(ent_or_lobby) then 6 | lobby = ent_or_lobby.lobby 7 | else 8 | lobby = lobby 9 | end 10 | 11 | local local_lobby = LocalPlayer().lobby 12 | 13 | if lobby and local_lobby and lobby == local_lobby then 14 | is_local = true 15 | else 16 | is_local = false 17 | end 18 | 19 | return is_local 20 | end 21 | -------------------------------------------------------------------------------- /gamemode/ui/elements/text-bar.cl.lua: -------------------------------------------------------------------------------- 1 | TextBar = TextBar or Class.New(Element) 2 | 3 | function TextBar:Init(text, props) 4 | TextBar.super.Init(self, { 5 | fit = true, 6 | padding_x = 8, 7 | padding_y = 4, 8 | fill_color = true, 9 | font = "TextBar", 10 | text_justification = 5, 11 | text = text 12 | }) 13 | 14 | if props then 15 | self:SetAttributes(props) 16 | end 17 | 18 | if self:GetAttribute "fill_color" then 19 | self:SetAttribute("text_color", COLOR_BACKGROUND) 20 | end 21 | end -------------------------------------------------------------------------------- /gamemode/minigame/states.cl.lua: -------------------------------------------------------------------------------- 1 | function MinigameLobby:SetState(state, last_state_start) 2 | local old_state = self.state 3 | 4 | self.state = state 5 | self.last_state_start = last_state_start or CurTime() 6 | MinigameService.CallHook(self, "StartState", old_state, state) 7 | MinigameService.CallHook(self, "StartState"..state.name, old_state, state) 8 | hook.Run("LobbySetState", lobby, old_state, state) 9 | 10 | if self:IsLocal() then 11 | hook.Run("LocalLobbySetState", self, old_state, state) 12 | end 13 | end -------------------------------------------------------------------------------- /gamemode/minigame/settings.cl.lua: -------------------------------------------------------------------------------- 1 | function MinigameSettingsService.Save(lobby, settings) 2 | MinigameSettingsService.SortChanges(lobby.original_settings, lobby.changed_settings, settings) 3 | MinigameSettingsService.Adjust(lobby, settings) 4 | hook.Run("LobbySettingsChange", lobby, settings) 5 | MinigameService.CallHook(lobby, "SettingsChange", settings) 6 | 7 | if lobby:IsLocal() then 8 | hook.Run("LocalLobbySettingsChange", lobby, settings) 9 | end 10 | end 11 | 12 | NetService.Receive("LobbySettings", MinigameSettingsService.Save) -------------------------------------------------------------------------------- /gamemode/minigame/settings.sv.lua: -------------------------------------------------------------------------------- 1 | function MinigameSettingsService.Save(ply, lobby, settings) 2 | if lobby.host == ply then 3 | MinigameSettingsService.SortChanges(lobby.original_settings, lobby.changed_settings, settings) 4 | MinigameSettingsService.Adjust(lobby, settings) 5 | NetService.Broadcast("LobbySettings", lobby, settings) 6 | hook.Run("LobbySettingsChange", lobby, settings) 7 | MinigameService.CallHook(lobby, "SettingsChange", settings) 8 | end 9 | end 10 | 11 | NetService.Receive("LobbySettings", MinigameSettingsService.Save) -------------------------------------------------------------------------------- /gamemode/player-class/net.sv.lua: -------------------------------------------------------------------------------- 1 | PlayerClassService = PlayerClassService or {} 2 | 3 | util.AddNetworkString "RequestPlayerClasses" 4 | util.AddNetworkString "PlayerClasses" 5 | 6 | function PlayerClassService.NetworkPlayerClasses(ply) 7 | net.Start "PlayerClasses" 8 | 9 | for _, _ply in pairs(player.GetAll()) do 10 | net.WriteEntity(_ply) 11 | NetService.WriteID(_ply.player_class and _ply.player_class.id) 12 | end 13 | 14 | net.Send(ply) 15 | end 16 | NetService.Receive("RequestPlayerClasses", PlayerClassService.NetworkPlayerClasses) -------------------------------------------------------------------------------- /gamemode/util/trail.sv.lua: -------------------------------------------------------------------------------- 1 | TrailService = TrailService or {} 2 | 3 | function TrailService.SetupTrail(ent) 4 | if IsValid(ent.trail) then 5 | ent.trail:StartRemove() 6 | end 7 | 8 | local trail = ents.Create("emm_trail") 9 | trail:SetOwner(ent) 10 | trail:SetPos(ent:GetPos()) 11 | trail:SetParent(ent) 12 | trail:Spawn() 13 | 14 | ent.trail = trail 15 | end 16 | hook.Add("PlayerSpawn", "TrailService.SetupTrail", TrailService.SetupTrail) 17 | 18 | function TrailService.RemoveTrail(ent) 19 | ent.trail:StartRemove() 20 | end 21 | hook.Add("DoPlayerDeath", "TrailService.RemoveTrail", TrailService.RemoveTrail) -------------------------------------------------------------------------------- /gamemode/settings/factories.cl.lua: -------------------------------------------------------------------------------- 1 | function SettingsUIService.CreateContainer() 2 | return ScrollContainer.New({ 3 | width_percent = 1, 4 | height_percent = 1, 5 | alpha = 0 6 | }, { 7 | wrap = false, 8 | width_percent = 1, 9 | fit = true, 10 | padding = MARGIN * 4, 11 | child_margin = MARGIN * 4 12 | }) 13 | end 14 | 15 | function SettingsUIService.CreateCategory() 16 | return Element.New { 17 | layout_direction = DIRECTION_COLUMN, 18 | fit_y = true, 19 | width = COLUMN_WIDTH * 1.5, 20 | padding_bottom = MARGIN * 2, 21 | background_color = COLOR_GRAY, 22 | LobbyUIService.CreateLabels {"Settings"} 23 | } 24 | end -------------------------------------------------------------------------------- /gamemode/util/util.cl.lua: -------------------------------------------------------------------------------- 1 | local local_ply 2 | 3 | function IsLocalPlayer(ply) 4 | local_ply = local_ply or LocalPlayer() 5 | 6 | return local_ply == ply 7 | end 8 | 9 | cached_png_materials = cached_png_materials or {} 10 | 11 | function PNGMaterial(mat) 12 | if not cached_png_materials[mat] then 13 | cached_png_materials[mat] = Material(mat, "noclamp smooth") 14 | end 15 | 16 | return cached_png_materials[mat] 17 | end 18 | 19 | function GetSmoothPlayerColor(ply) 20 | local color 21 | 22 | if IsValid(ply) and ply.animatable_color then 23 | color = ply.animatable_color.smooth 24 | else 25 | color = COLOR_WHITE_CLEAR 26 | end 27 | 28 | return color 29 | end 30 | -------------------------------------------------------------------------------- /gamemode/player/methods.sh.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable("Player") 2 | 3 | function player_metatable:SetupCoreProperties() 4 | if SERVER then 5 | self:SetMaxHealth(self.max_health) 6 | self:SetArmor(0) 7 | self:ShouldDropWeapon(false) 8 | self:Strip() 9 | end 10 | 11 | self:SetCollisionGroup(COLLISION_GROUP_PASSABLE_DOOR) 12 | self:SetMoveType(MOVETYPE_WALK) 13 | self:SetCanWalk(false) 14 | self:SetWalkSpeed(self.run_speed) 15 | self:SetRunSpeed(self.run_speed) 16 | self:SetCrouchedWalkSpeed(0.5) 17 | self:SetDuckSpeed(0.3) 18 | self:SetUnDuckSpeed(0.3) 19 | self:SetJumpPower(self.jump_power) 20 | self:AllowFlashlight(true) 21 | self:SetAvoidPlayers(false) 22 | end -------------------------------------------------------------------------------- /gamemode/movement/airaccel.cl.lua: -------------------------------------------------------------------------------- 1 | AiraccelService = AiraccelService or {} 2 | 3 | 4 | -- # Time maps 5 | 6 | local has_stamina = has_stamina or TimeAssociatedMapService.CreateMap(2, function() 7 | return LocalPlayer().stamina.airaccel:HasStamina() 8 | end) 9 | 10 | local last_stamina_reduced = last_stamina_reduced or TimeAssociatedMapService.CreateMap(2, function() 11 | return 0 12 | end) 13 | 14 | 15 | -- # Prediction handling 16 | 17 | function AiraccelService.HasStamina(ply) 18 | return has_stamina:Value() 19 | end 20 | 21 | function AiraccelService.ReduceStamina(ply, value) 22 | ply.stamina.airaccel:ReduceStamina(value - last_stamina_reduced:Value()) 23 | last_stamina_reduced:Set(value) 24 | end -------------------------------------------------------------------------------- /gamemode/movement/wallslide.sv.lua: -------------------------------------------------------------------------------- 1 | WallslideService = WallslideService or {} 2 | 3 | 4 | -- # Prediction handling 5 | 6 | function WallslideService.HasStamina(ply) 7 | return ply.stamina.wallslide:HasStamina() 8 | end 9 | 10 | function WallslideService.Wallsliding(ply) 11 | return ply.wallsliding 12 | end 13 | 14 | function WallslideService.StartedWallslide(ply) 15 | return false 16 | end 17 | 18 | function WallslideService.FinishedWallslide(ply) 19 | return false 20 | end 21 | 22 | function WallslideService.LastWallslideTime(ply) 23 | return ply.last_wallslide_time 24 | end 25 | 26 | function WallslideService.WallslideVelocity(ply) 27 | return ply.wallslide_velocity 28 | end -------------------------------------------------------------------------------- /gamemode/minigame_prototypes/hunted.sh.lua: -------------------------------------------------------------------------------- 1 | MINIGAME.name = "Hunted" 2 | MINIGAME.color = COLOR_SKY 3 | MINIGAME.default_player_class = "Hunter" 4 | 5 | MINIGAME.random_player_classes = { 6 | class_key = "Hunted", 7 | rejected_class_key = "Hunter" 8 | } 9 | 10 | MINIGAME:AddPlayerClass { 11 | name = "Hunted", 12 | color = COLOR_PEACH, 13 | can_tag = {Hunter = true}, 14 | tag_victim = true, 15 | swap_on_tag = true, 16 | kill_on_tag = true, 17 | swap_closest_on_death = true, 18 | swap_with_attacker = true 19 | } 20 | 21 | MINIGAME:AddPlayerClass { 22 | name = "Hunter" 23 | } 24 | 25 | MINIGAME:AddAdjustableSetting { 26 | key = "player_classes.Hunted.can_tag.Hunter", 27 | label = "Hunted can be tagged" 28 | } -------------------------------------------------------------------------------- /gamemode/util/net.cl.lua: -------------------------------------------------------------------------------- 1 | function NetService.CreateSchema(name, schema) 2 | NetService.CreateReader(name, schema) 3 | end 4 | 5 | function NetService.CreateUpstreamSchema(name, schema) 6 | NetService.CreateWriter(name, schema) 7 | end 8 | 9 | function NetService.CreateReader(name, schema) 10 | schema = schema or {} 11 | 12 | local receiver = function () 13 | local read = {} 14 | 15 | for i = 1, #schema do 16 | table.insert(read, NetService.type_readers[schema[i]]()) 17 | end 18 | 19 | NetService.hooks[name](unpack(read)) 20 | end 21 | 22 | net.Receive(name, receiver) 23 | 24 | return receiver 25 | end 26 | 27 | function NetService.SendToServer(name, ...) 28 | NetService.writers[name](...) 29 | net.SendToServer() 30 | end -------------------------------------------------------------------------------- /gamemode/minigame_prototypes/elimination.sh.lua: -------------------------------------------------------------------------------- 1 | MINIGAME.name = "Elimination" 2 | MINIGAME.color = COLOR_PEACH 3 | MINIGAME.default_player_class = "Hunter" 4 | MINIGAME.required_players = 3 5 | 6 | MINIGAME.states.Playing.time = 60 * 5 7 | 8 | MINIGAME.random_player_classes = { 9 | class_key = "Hunter", 10 | rejected_class_key = "Hunted" 11 | } 12 | 13 | MINIGAME:AddPlayerClass { 14 | name = "Hunter", 15 | can_tag = {Hunted = true}, 16 | minimum = 1, 17 | recruit_on_tag = true 18 | } 19 | 20 | MINIGAME:AddPlayerClass { 21 | name = "Hunted", 22 | color = COLOR_SKY, 23 | end_on_none = true, 24 | player_class_on_death = "Hunter" 25 | } 26 | 27 | MINIGAME:AddAdjustableSetting { 28 | key = "player_classes.Hunter.can_tag.Hunted", 29 | label = "Hunted can be tagged" 30 | } -------------------------------------------------------------------------------- /gamemode/ui/elements/avatar-bar.cl.lua: -------------------------------------------------------------------------------- 1 | AvatarBar = AvatarBar or Class.New(Element) 2 | 3 | function AvatarBar:Init(ply_or_id) 4 | AvatarBar.super.Init(self, { 5 | width = BAR_WIDTH, 6 | height = BAR_HEIGHT, 7 | background_color = COLOR_GRAY, 8 | text_justification = 5, 9 | font = "TextBar" 10 | }) 11 | 12 | self.avatar = self.panel:Add(vgui.Create "AvatarImage") 13 | self.avatar:MoveToBefore(self.panel.text) 14 | self.avatar:SetSize(BAR_WIDTH, BAR_WIDTH) 15 | self.avatar:SetPos(0, (BAR_HEIGHT/2) - (BAR_WIDTH/2)) 16 | self.avatar:SetAlpha(QUARTER_ALPHA) 17 | 18 | if isentity(ply_or_id) then 19 | self.avatar:SetPlayer(ply_or_id, 184) 20 | self:SetText(ply_or_id:GetName()) 21 | else 22 | self.avatar:SetSteamID(ply_or_id, 184) 23 | end 24 | end 25 | 26 | -------------------------------------------------------------------------------- /gamemode/movement/walljump.cl.lua: -------------------------------------------------------------------------------- 1 | WalljumpService = WalljumpService or {} 2 | 3 | SettingsService.New("clientside_walljump", { 4 | type = "boolean", 5 | default = true, 6 | help = "Client-side predicted walljump", 7 | }) 8 | 9 | 10 | -- # Time maps 11 | 12 | local last_walljump_time = last_walljump_time or TimeAssociatedMapService.CreateMap(2, function() 13 | return LocalPlayer().last_walljump_time 14 | end) 15 | 16 | local played_sound = played_sound or TimeAssociatedMapService.CreateMap(2, function() 17 | return true 18 | end) 19 | 20 | 21 | -- # Client-side prediction 22 | 23 | function WalljumpService.CooledDown(ply) 24 | return CurTime() > (last_walljump_time:Value() + ply.walljump_delay) 25 | end 26 | 27 | function WalljumpService.PlayedSound(ply) 28 | return played_sound:HasChecked() or not played_sound:Value() 29 | end -------------------------------------------------------------------------------- /gamemode/player/methods.sv.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable "Player" 2 | 3 | function player_metatable:SetupModel() 4 | local mdl = self.model or player_manager.TranslatePlayerModel(self:GetInfo "cl_playermodel") 5 | 6 | util.PrecacheModel(mdl) 7 | self:SetModel(mdl) 8 | end 9 | 10 | function player_metatable:FreezeMovement() 11 | self:SetWalkSpeed(1) 12 | self:SetRunSpeed(1) 13 | end 14 | 15 | function player_metatable:Strip() 16 | self:StripWeapons() 17 | self:StripAmmo() 18 | end 19 | 20 | local ammo_type_count = 27 21 | 22 | function player_metatable:SetupLoadout() 23 | self:Strip() 24 | 25 | if self.weapons then 26 | for i = 1, ammo_type_count do 27 | self:GiveAmmo(9999, game.GetAmmoName(i), true) 28 | end 29 | 30 | for k, v in pairs(self.weapons) do 31 | if v then 32 | self:Give(k) 33 | end 34 | end 35 | end 36 | end -------------------------------------------------------------------------------- /gamemode/movement/autojump.sh.lua: -------------------------------------------------------------------------------- 1 | AutoJumpService = AutoJumpService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function AutoJumpService.InitPlayerProperties(ply) 7 | ply.can_autojump = false 8 | ply.force_autojump = false 9 | end 10 | hook.Add( 11 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 12 | "AutoJumpService.InitPlayerProperties", 13 | AutoJumpService.InitPlayerProperties 14 | ) 15 | 16 | 17 | -- # Autojump 18 | 19 | function AutoJumpService.AutoJump(ply, move) 20 | if 21 | ply.can_autojump and 22 | (move:KeyDown(IN_JUMP) or ply.force_autojump) and 23 | ply:IsOnGround() 24 | then 25 | move:SetOldButtons(bit.band(move:GetOldButtons(), bit.bnot(IN_JUMP))) 26 | move:SetButtons(bit.bor(move:GetButtons(), IN_JUMP)) 27 | end 28 | end 29 | hook.Add("SetupMove", "AutoJumpService.AutoJump", AutoJumpService.AutoJump) -------------------------------------------------------------------------------- /gamemode/settings/ui.cl.lua: -------------------------------------------------------------------------------- 1 | SettingsUIService = SettingsUIService or {} 2 | 3 | function SettingsUIService.Init() 4 | SettingsUIService.container = SettingsUIService.CreateContainer() 5 | SettingsUIService.main_category = SettingsUIService.container:AddInner(SettingsUIService.CreateCategory()) 6 | 7 | for _, k in pairs(SettingsService.ordered_convars) do 8 | local convar = SettingsService.convars[k] 9 | 10 | SettingsUIService.main_category:Add(InputBar.New(convar.help, convar.type, SettingsService.Get(k), { 11 | on_change = function (input, v) 12 | SettingsService.Set(k, v) 13 | SettingsUIService.container.panel:MoveToFront() 14 | end 15 | })) 16 | end 17 | end 18 | hook.Add("InitUI", "SettingsUIService.Init", SettingsUIService.Init) 19 | 20 | UIService.Register("Settings", SettingsUIService, { 21 | open_hook = "OnContextMenuOpen", 22 | close_hook = "OnContextMenuClose", 23 | }) -------------------------------------------------------------------------------- /gamemode/movement/airaccel-patch.sh.lua: -------------------------------------------------------------------------------- 1 | AiraccelPatchService = AiraccelPatchService or {} 2 | 3 | function AiraccelPatchService.SetMoveType(ply, move) 4 | local z_vel = move:GetVelocity().z 5 | 6 | if 7 | z_vel > 0 and 8 | z_vel < 140 and 9 | ply:GetMoveType() == MOVETYPE_WALK 10 | then 11 | ply:SetMoveType(MOVETYPE_LADDER) 12 | end 13 | end 14 | hook.Add("SetupMove", "AiraccelPatchService.SetMoveType",AiraccelPatchService.SetMoveType) 15 | 16 | 17 | function AiraccelPatchService.RemoveLadderSound(sound) 18 | if IsValid(sound.Entity) then 19 | local z_vel = sound.Entity:GetVelocity().z 20 | 21 | if 22 | z_vel ~= 200 and 23 | z_vel ~= -200 and 24 | string.StartWith(sound.SoundName, "player/footsteps/ladder") 25 | then 26 | return false 27 | end 28 | end 29 | end 30 | hook.Add("EntityEmitSound", "AiraccelPatchService.RemoveLadderSound", AiraccelPatchService.RemoveLadderSound) -------------------------------------------------------------------------------- /gamemode/util/pred-sound.cl.lua: -------------------------------------------------------------------------------- 1 | PredictedSoundService = PredictedSoundService or {} 2 | PredictedSoundService.Cache = PredictedSoundService.Cache or {} 3 | 4 | 5 | -- # Client sounds 6 | 7 | function PredictedSoundService.PlaySound(ply, sound_file, soundLevel, pitchPercent, volume, channel) 8 | ply:EmitSound(sound_file, soundLevel, pitchPercent, volume, channel) 9 | end 10 | 11 | function PredictedSoundService.PlayWallslideSound(ply) 12 | if PredictedSoundService.Cache[ply.wallslide_sound_file] == nil then 13 | if ply.wallslide_sound and ply.wallslide_sound:IsPlaying() then 14 | ply.wallslide_sound:Stop() 15 | end 16 | 17 | PredictedSoundService.Cache[ply.wallslide_sound_file] = CreateSound(ply, ply.wallslide_sound_file) 18 | end 19 | 20 | ply.wallslide_sound = PredictedSoundService.Cache[ply.wallslide_sound_file] 21 | ply.wallslide_sound:Play() 22 | end 23 | 24 | function PredictedSoundService.StopWallslideSound(ply) 25 | ply.wallslide_sound:ChangeVolume(0, 0.25) 26 | end -------------------------------------------------------------------------------- /gamemode/player-class/net.cl.lua: -------------------------------------------------------------------------------- 1 | function PlayerClassService.ReceivePlayerClass(ply, id) 2 | if IsValid(ply) then 3 | if MinigameNetService.received_lobbies and ply.lobby then 4 | if id ~= 0 then 5 | ply:SetPlayerClass(PlayerClassService.MinigamePlayerClass(ply, id)) 6 | else 7 | ply:ClearPlayerClass() 8 | end 9 | end 10 | end 11 | end 12 | NetService.Receive("PlayerClass", PlayerClassService.ReceivePlayerClass) 13 | 14 | function PlayerClassService.ReceivePlayerClasses() 15 | for i = 1, #player.GetAll() do 16 | local ply = net.ReadEntity() 17 | local id = NetService.ReadID() 18 | 19 | if id ~= 0 then 20 | ply:SetPlayerClass(PlayerClassService.MinigamePlayerClass(ply, id)) 21 | end 22 | end 23 | end 24 | net.Receive("PlayerClasses", PlayerClassService.ReceivePlayerClasses) 25 | 26 | function PlayerClassService.RequestPlayerClasses() 27 | NetService.SendToServer "RequestPlayerClasses" 28 | end 29 | hook.Add("ReceiveLobbies", "PlayerClassService.RequestPlayerClasses", PlayerClassService.RequestPlayerClasses) -------------------------------------------------------------------------------- /gamemode/minigame/tagging.cl.lua: -------------------------------------------------------------------------------- 1 | hook.Add("CreateMinigameHooks", "TaggingService", function (proto) 2 | proto:AddHookNotification("Tag", function (self, involves_local_ply, taggable, tagger) 3 | local taggable_is_victim = taggable.player_class.tag_victim 4 | 5 | if involves_local_ply then 6 | local taggable_is_local_ply = IsLocalPlayer(taggable) 7 | local victim_text = "tagged by" 8 | local attacker_text = "tagged" 9 | 10 | local text 11 | 12 | if taggable_is_local_ply then 13 | text = taggable_is_victim and victim_text or attacker_text 14 | else 15 | text = taggable_is_victim and attacker_text or victim_text 16 | end 17 | 18 | NotificationService.PushAvatarText(taggable_is_local_ply and tagger or taggable, text) 19 | else 20 | local action = " has tagged " 21 | 22 | local text 23 | 24 | if taggable_is_victim then 25 | text = tagger:GetName()..action..taggable:GetName() 26 | else 27 | text = taggable:GetName()..action..tagger:GetName() 28 | end 29 | 30 | NotificationService.PushSideText(text) 31 | end 32 | end) 33 | end) 34 | -------------------------------------------------------------------------------- /gamemode/player-class/methods.sv.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable("Player") 2 | 3 | function player_metatable:SetPlayerClass(class) 4 | local old_class = self.player_class 5 | 6 | if old_class then 7 | self:ClearPlayerClass(false) 8 | end 9 | 10 | self.player_class = class 11 | table.insert(self.lobby[class.key], self) 12 | self:SetupPlayerClass() 13 | 14 | MinigameService.CallHook(self.lobby, "PlayerClassChange", ply, old_class, class) 15 | NetService.Broadcast("PlayerClass", self, class.id) 16 | end 17 | 18 | function player_metatable:ClearPlayerClass(net) 19 | net = Default(net, true) 20 | 21 | local old_class = self.player_class 22 | 23 | table.RemoveByValue(self.lobby[self.player_class.key], self) 24 | self.player_class = nil 25 | self:EndPlayerClass() 26 | 27 | if net then 28 | MinigameService.CallHook(self.lobby, "PlayerClassChange", ply, old_class) 29 | NetService.Broadcast("PlayerClass", self) 30 | end 31 | end 32 | hook.Add("LobbyPlayerLeave", "ClearPlayerClass", function (_, ply) 33 | if ply.player_class then 34 | ply:ClearPlayerClass() 35 | end 36 | end) -------------------------------------------------------------------------------- /gamemode/minigame/states.sv.lua: -------------------------------------------------------------------------------- 1 | function MinigameStateService.StartStateTimer(lobby) 2 | timer.Create("MinigameStateService."..lobby.id, lobby.state.time, 1, function () 3 | MinigameService.CallNetHook(lobby, "StateExpired") 4 | lobby:NextState() 5 | end) 6 | end 7 | 8 | function MinigameStateService.EndStateTimer(lobby) 9 | timer.Remove("MinigameStateService."..lobby.id) 10 | end 11 | 12 | function MinigameLobby:SetState(state) 13 | local old_state = self.state 14 | 15 | if old_state and old_state.time then 16 | MinigameStateService.EndStateTimer(self) 17 | end 18 | 19 | self.state = state 20 | self.last_state_start = CurTime() 21 | 22 | if state.time then 23 | MinigameStateService.StartStateTimer(self) 24 | end 25 | 26 | MinigameService.CallHook(self, "StartState", old_state, state) 27 | MinigameService.CallHook(self, "StartState"..state.name, old_state, state) 28 | hook.Run("LobbyStateChange", lobby, old_state, state) 29 | NetService.Broadcast("LobbyState", self, state.id, self.last_state_start) 30 | end 31 | 32 | function MinigameLobby:NextState() 33 | self:SetState(self.states[self.state.next]) 34 | end -------------------------------------------------------------------------------- /gamemode/ui/util.cl.lua: -------------------------------------------------------------------------------- 1 | function CombineAlphas(...) 2 | local alphas = {...} 3 | 4 | local prod = 1 5 | 6 | for _, alpha in pairs(alphas) do 7 | prod = prod * alpha 8 | end 9 | 10 | return prod/(255 ^ #alphas) 11 | end 12 | 13 | function GenerateSurfaceCircle(x, y, radius, arc, ang, quality) 14 | local circle = {} 15 | 16 | local offset = (ang * (quality/360)) - 1 17 | local steps = quality/(360/arc) 18 | local rounded_steps = math.Round(steps) 19 | 20 | for i = 1, rounded_steps + 1 do 21 | local rad = math.rad((arc * (i + offset))/steps) 22 | 23 | circle[i] = { 24 | x = x + (math.cos(rad) * radius), 25 | y = y + (math.sin(rad) * radius) 26 | } 27 | end 28 | 29 | circle[rounded_steps + 2] = { 30 | x = x, 31 | y = y 32 | } 33 | 34 | return circle 35 | end 36 | 37 | function ProperPlayerName(ply) 38 | local name 39 | 40 | if IsValid(ply) then 41 | name = ply:GetName() 42 | else 43 | name = "Disconnected" 44 | end 45 | 46 | return name 47 | end 48 | 49 | concommand.Add("emm_reload_ui", function () 50 | LobbyUIService.Reload() 51 | HUDService.Reload() 52 | IndicatorService.Reload(true) 53 | end) 54 | -------------------------------------------------------------------------------- /gamemode/ui/elements/meter-bar.cl.lua: -------------------------------------------------------------------------------- 1 | MeterBar = MeterBar or Class.New(Element) 2 | 3 | function MeterBar:Init(props) 4 | MeterBar.super.Init(self, { 5 | layout_justification_x = JUSTIFY_CENTER, 6 | width_percent = 1, 7 | height = LINE_THICKNESS, 8 | background_color = COLOR_BLACK, 9 | 10 | bar = Element.New { 11 | width_percent = 0, 12 | height_percent = 1, 13 | fill_color = true 14 | } 15 | }) 16 | 17 | self.animatable_percent = AnimatableValue.New(props.percent, {smooth = true}) 18 | 19 | if props then 20 | self:SetAttributes(props) 21 | end 22 | end 23 | 24 | function MeterBar:Finish() 25 | self.animatable_percent:Finish() 26 | MeterBar.super.Finish(self) 27 | end 28 | 29 | function MeterBar:SetPercent(percent) 30 | self.animatable_percent.current = math.Clamp(percent, 0, 1) 31 | end 32 | 33 | function MeterBar:Think() 34 | MeterBar.super.Think(self) 35 | 36 | local w_percent = self.bar.attributes.width_percent 37 | local old_w_percent = w_percent.current 38 | 39 | w_percent.current = self.animatable_percent.smooth 40 | 41 | if math.Round(old_w_percent, 4) ~= math.Round(w_percent.current, 4) then 42 | self.bar:Layout() 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /gamemode/ui/elements/scroll-container.cl.lua: -------------------------------------------------------------------------------- 1 | ScrollContainer = ScrollContainer or Class.New(Element) 2 | 3 | function ScrollContainer:Init(props, inner_container_props) 4 | ScrollContainer.super.Init(self, props) 5 | 6 | self.inner_container = self:Add(Element.New(inner_container_props)) 7 | 8 | self.scroll = AnimatableValue.New(0, { 9 | smooth = true, 10 | smooth_multiplier = 2, 11 | 12 | smooth_callback = function (anim_v) 13 | self.inner_container.attributes.offset_y.current = anim_v.smooth 14 | self.inner_container:SetPanelBounds() 15 | end 16 | }) 17 | end 18 | 19 | function ScrollContainer:AddInner(...) 20 | return self.inner_container:Add(...) 21 | end 22 | 23 | function ScrollContainer:OnMouseScrolled(scroll) 24 | local curr_scroll = self.scroll.current 25 | local new_scroll = curr_scroll + (scroll * 50) 26 | local min = math.min(-(self.inner_container:GetFinalHeight() - self:GetFinalHeight()), 0) 27 | 28 | -- if 0 > min and 0 > new_scroll or new_scroll > min then 29 | self.scroll.current = math.Clamp(new_scroll, min, 0) 30 | -- end 31 | end 32 | 33 | function ScrollContainer:Finish() 34 | self.scroll:Finish() 35 | ScrollContainer.super.Finish(self) 36 | end 37 | -------------------------------------------------------------------------------- /gamemode/util/palette.sh.lua: -------------------------------------------------------------------------------- 1 | COLOR_WHITE_CLEAR = Color(255, 255, 255, 0) 2 | COLOR_BLACK_CLEAR = Color(0, 0, 0, 0) 3 | COLOR_BACKGROUND = Color(0, 0, 0, 200) 4 | COLOR_BACKGROUND_LIGHT = Color(0, 0, 0, 100) 5 | COLOR_WHITE = Color(255, 255, 255) 6 | COLOR_BLACK = Color(0, 0, 0) 7 | COLOR_GRAY = Color(42, 42, 42) 8 | COLOR_GRAY_DARK = Color(32, 32, 32) 9 | COLOR_GRAY_DARKER = Color(28, 28, 28) 10 | COLOR_GRAY_LIGHT = Color(60, 60, 60) 11 | COLOR_GRAY_LIGHTER = Color(150, 150, 150) 12 | COLOR_RASPBERRY = Color(244, 71, 112) 13 | COLOR_RED = Color(250, 49, 49) 14 | COLOR_FIRE = Color(255, 62, 0) 15 | COLOR_ORANGE = Color(255, 100, 33) 16 | COLOR_PEACH = Color(255, 104, 59) 17 | COLOR_YELLOW = Color(255, 221, 0) 18 | COLOR_GOLD = Color(244, 247, 158) 19 | COLOR_GREEN = Color(18, 224, 73) 20 | COLOR_GREEN_LIGHT = Color(122, 255, 131) 21 | COLOR_TEAL = Color(31, 151, 184) 22 | COLOR_CYAN = Color(56, 242, 198) 23 | COLOR_MINT = Color(152, 245, 197) 24 | COLOR_ICE = Color(188, 236, 247) 25 | COLOR_SKY = Color(106, 169, 252) 26 | COLOR_BLUE = Color(50, 100, 255) 27 | COLOR_ROYAL = Color(80, 100, 255) 28 | COLOR_LAVENDER = Color(185, 188, 255) 29 | COLOR_PURPLE = Color(150, 126, 208) 30 | COLOR_PINK = Color(247, 161, 246) -------------------------------------------------------------------------------- /gamemode/player-class/player-class.sh.lua: -------------------------------------------------------------------------------- 1 | PlayerClassService = PlayerClassService or {} 2 | 3 | function PlayerClassService.CreatePlayerClass(props) 4 | local ply_class = table.Merge({ 5 | key = props.key or props.name, 6 | display_name = true, 7 | color = props.color, 8 | 9 | friction = FrictionService.GetDefaultFriction(), 10 | gravity = GravityService.GetDefaultGravity(), 11 | air_accelerate = AiraccelService.GetDefaultAiraccel(), 12 | 13 | can_walljump = true, 14 | can_wallslide = true, 15 | can_airaccel = true, 16 | can_autojump = false, 17 | can_regenerate_health = true, 18 | can_take_fall_damage = true, 19 | can_damage_everyone = true, 20 | 21 | has_infinite_wallslide = false, 22 | has_infinite_airaccel = false, 23 | 24 | can_damage = {}, 25 | weapons = {}, 26 | 27 | notify_player_class_on_death = true, 28 | notify_on_killed_by_player = true, 29 | notify_on_killed_by_other = false 30 | }, props) 31 | 32 | return ply_class 33 | end 34 | 35 | function PlayerClassService.MinigamePlayerClass(ply, id_or_key) 36 | for _, ply_class in pairs(ply.lobby.player_classes) do 37 | if id_or_key == ply_class.id or id_or_key == ply_class.key then 38 | return ply_class 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /gamemode/cl_init.lua: -------------------------------------------------------------------------------- 1 | EMM = EMM or {} 2 | 3 | EMM.client_includes = {} 4 | 5 | gamemode_name = "emm" 6 | gamemode_lua_directory = gamemode_name.."/gamemode/" 7 | 8 | function engine.ActiveGamemode() 9 | return gamemode_name 10 | end 11 | 12 | function IsSharedFile(file) 13 | return string.match(file, "sh.lua$") 14 | end 15 | 16 | function IsClientFile(file) 17 | return string.match(file, "cl.lua$") 18 | end 19 | 20 | function EMM.Include(inc, inc_func) 21 | inc_func = inc_func or include 22 | 23 | if istable(inc) then 24 | for _, _inc in pairs(inc) do 25 | EMM.Include(_inc) 26 | end 27 | elseif isstring(inc) then 28 | local inc_path = gamemode_lua_directory..inc 29 | local inc_file = file.Find(inc_path, "LUA")[1] 30 | local sh_inc_file = file.Find(inc_path..".sh.lua", "LUA")[1] 31 | local cl_inc_file = file.Find(inc_path..".cl.lua", "LUA")[1] 32 | 33 | if inc_file and (IsSharedFile(inc_file) or IsClientFile(inc_file)) then 34 | inc_func(inc_path) 35 | end 36 | 37 | if sh_inc_file then 38 | inc_func(inc_path..".sh.lua") 39 | end 40 | 41 | if cl_inc_file then 42 | inc_func(inc_path..".cl.lua") 43 | end 44 | 45 | EMM.client_includes[inc] = true 46 | end 47 | end 48 | 49 | include(gamemode_name..".lua") 50 | -------------------------------------------------------------------------------- /gamemode/util/net.sv.lua: -------------------------------------------------------------------------------- 1 | function NetService.CreateSchema(name, schema) 2 | util.AddNetworkString(name) 3 | NetService.CreateWriter(name, schema) 4 | end 5 | 6 | function NetService.CreateUpstreamSchema(name, schema) 7 | util.AddNetworkString(name) 8 | NetService.CreateReader(name, schema) 9 | end 10 | 11 | function NetService.CreateReader(name, schema) 12 | schema = schema or {} 13 | 14 | local receiver = function (len, ply) 15 | local read = {} 16 | 17 | for i = 1, #schema do 18 | table.insert(read, NetService.type_readers[schema[i]]()) 19 | end 20 | 21 | NetService.hooks[name](ply, unpack(read)) 22 | end 23 | 24 | net.Receive(name, receiver) 25 | 26 | return receiver 27 | end 28 | 29 | --- Broadcast net message to all clients 30 | ---@param name string | "Signal name" 31 | function NetService.Broadcast(name, ...) 32 | NetService.writers[name](...) 33 | net.Broadcast() 34 | end 35 | 36 | --- Send a net message to the specified player(s) 37 | ---@param name string | "Signal name" 38 | ---@param plys player|table | "Player or table of Players" 39 | function NetService.Send(name, plys, ...) 40 | NetService.writers[name](...) 41 | net.Send(plys) 42 | end 43 | 44 | function NetService.SendCustom(name, sender, ...) 45 | NetService.writers[name](...) 46 | sender() 47 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Extended Movement Mod Logo](https://github.com/jep-a/emm/raw/master/logo.png "Extended Movement Mod Logo") 2 | 3 | # Extended Movement Mod 4 | 5 | This is a [Garry's Mod](https://gmod.facepunch.com) gamemode that adds a new way to move around: walljumping. With this new movement capability, players can move around the map in a way much more enjoyable than walking or running. On top of the ability to walljump, this gamemode adds a minigame lobby system where players can participate with other players in minigames like tag, hunted, deathmatch, and etc. 6 | 7 | This gamemode is recommended to be played on dense city maps like [gm_bigcity](https://steamcommunity.com/sharedfiles/filedetails/?id=105982362). This gamemode will not work correctly on singleplayer mode, it should be hosted on a listen or dedicated server. 8 | 9 | This is the official second version of Extended Movement Mod that has been entirely rebuilt to improve modularity and readability. This gamemode is a reiteration of the Garry's Mod parkour gamemode concept. The notable original iterations are [Blasphemouswebs's](http://steamcommunity.com/groups/Blasphemouswebs) Parkour and [Mechanical Mind's](https://github.com/MechanicalMind) Team AWOL Parkour. 10 | 11 | When contributing, please use the [code style guide](/code-style-guide.md). 12 | -------------------------------------------------------------------------------- /gamemode/minigame/states.sh.lua: -------------------------------------------------------------------------------- 1 | MinigameStateService = MinigameStateService or {} 2 | 3 | function MinigameStateService.State(lobby, k_or_id) 4 | for _, state in pairs(lobby.states) do 5 | if k_or_id == state.key or k_or_id == state.id then 6 | return state 7 | end 8 | end 9 | end 10 | 11 | function MinigamePrototype:CanRestart() 12 | return self.prototype.key ~= "Miscellaneous" and self.state == self.states.Playing or self.state == self.states.Starting 13 | end 14 | 15 | function MinigamePrototype:AddState(state) 16 | state.key = state.key or state.name 17 | state.id = self.states[state.key] and self.states[state.key].id or (table.Count(self.states) + 1) 18 | self.states[state.key] = state 19 | end 20 | 21 | function MinigamePrototype:AddDefaultStates() 22 | self:AddState { 23 | name = "Waiting", 24 | next = "Starting" 25 | } 26 | 27 | self:AddState { 28 | name = "Starting", 29 | time = 5, 30 | next = "Playing", 31 | notify_countdown = true 32 | } 33 | 34 | self:AddState { 35 | name = "Playing", 36 | next = "Ending", 37 | notify_countdown = true, 38 | notify_countdown_text = "" 39 | } 40 | 41 | self:AddState { 42 | name = "Ending", 43 | time = 5, 44 | next = "Starting", 45 | notify_countdown = true, 46 | notify_countdown_text = "restarting in" 47 | } 48 | end -------------------------------------------------------------------------------- /gamemode/ui/elements/player-bar.cl.lua: -------------------------------------------------------------------------------- 1 | PlayerBar = PlayerBar or Class.New(Element) 2 | 3 | function PlayerBar:Init(ply) 4 | PlayerBar.super.Init(self, { 5 | fit_y = true, 6 | width_percent = 1, 7 | padding_left = MARGIN * 8, 8 | padding_top = MARGIN * 4, 9 | padding_bottom = MARGIN * 4, 10 | text_justification = 4, 11 | font = "Info" 12 | }) 13 | 14 | self.player = ply 15 | 16 | self.avatar = self.panel:Add(vgui.Create "AvatarImage") 17 | self.avatar:MoveToBefore(self.panel.text) 18 | self.avatar:SetAlpha(QUARTER_ALPHA) 19 | self.avatar:SetPlayer(ply, 184) 20 | 21 | self:SetText(ProperPlayerName(ply)) 22 | end 23 | 24 | function PlayerBar:AnimateStart() 25 | self:SetAttribute("crop_bottom", 1) 26 | self:AnimateAttribute("crop_bottom", 0) 27 | self:Add(NotificationService.CreateFlash()) 28 | end 29 | 30 | function PlayerBar:AnimateFinish() 31 | self:AnimateAttribute("crop_bottom", 1, { 32 | callback = function () 33 | PlayerBar.super.Finish(self) 34 | end 35 | }) 36 | end 37 | 38 | function PlayerBar:Finish() 39 | self.player = nil 40 | self:AnimateFinish() 41 | end 42 | 43 | function PlayerBar:Layout() 44 | PlayerBar.super.Layout(self) 45 | 46 | if self.avatar then 47 | local w = self.attributes.width.current 48 | local h = self.attributes.height.current 49 | 50 | self.avatar:SetSize(w, w) 51 | self.avatar:SetPos(0, (h/2) - (w/2)) 52 | end 53 | end -------------------------------------------------------------------------------- /gamemode/movement/slope.sh.lua: -------------------------------------------------------------------------------- 1 | SlopeService = SlopeService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function SlopeService.InitPlayerProperties(ply) 7 | ply.slope_left_ground = false 8 | end 9 | hook.Add( 10 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 11 | "SlopeService.InitPlayerProperties", 12 | SlopeService.InitPlayerProperties 13 | ) 14 | 15 | 16 | -- # Slope Boost 17 | 18 | function SlopeService.AddSpeed(ply, normal, vel) 19 | if 1 > normal.z and ply:OnGround() and ply.slope_left_ground and 0 >= ply.old_velocity.z then 20 | local old_velocity = ply.old_velocity 21 | local dot 22 | 23 | old_velocity.z = old_velocity.z - (ply.gravity * FrameTime() * 0.5) 24 | vel = old_velocity - (normal * old_velocity:Dot(normal)) 25 | dot = vel:Dot(normal) 26 | 27 | if 0 > dot then 28 | vel = vel - (normal * dot) 29 | end 30 | 31 | vel.z = 0 32 | end 33 | 34 | return vel 35 | end 36 | 37 | function SlopeService.SetupSlopeBoost(ply, move) 38 | local vel = move:GetVelocity() 39 | local slope_boost = SlideService.Trace(ply, vel, move:GetOrigin()) 40 | 41 | if slope_boost and not slope_boost.StartSolid and 1 > slope_boost.HitNormal.z then 42 | move:SetVelocity(SlopeService.AddSpeed(ply, slope_boost.HitNormal, vel)) 43 | end 44 | 45 | ply.slope_left_ground = not ply:OnGround() 46 | end 47 | hook.Add("SetupMove", "SlopeService.SetupSlopeBoost", SlopeService.SetupSlopeBoost) -------------------------------------------------------------------------------- /gamemode/element/setters.cl.lua: -------------------------------------------------------------------------------- 1 | Element.setters = { 2 | duration = function (self, static_attr, attr, v) 3 | static_attr.duration = v 4 | static_attr.start_time = CurTime() 5 | end, 6 | 7 | layout = function (self, static_attr, attr, v) 8 | static_attr.layout = v 9 | 10 | local parent = self.parent 11 | 12 | if parent then 13 | local in_layout_children = table.HasValue(parent.layout_children, self) 14 | 15 | if v then 16 | if not in_layout_children then 17 | table.insert(parent.layout_children, self) 18 | end 19 | else 20 | if in_layout_children then 21 | table.RemoveByValue(parent.layout_children, self) 22 | end 23 | end 24 | end 25 | end, 26 | 27 | cursor = function (self, static_attr, attr, v) 28 | static_attr.cursor = v 29 | 30 | self.panel:SetCursor(v or "none") 31 | 32 | for _, child in pairs(self.children) do 33 | if child.static_attributes.inherit_cursor and not child.static_attributes.cursor then 34 | child:SetAttribute("cursor", v) 35 | end 36 | end 37 | end, 38 | 39 | text_justification = function (self, static_attr, attr, v) 40 | static_attr.text_justification = v 41 | self:SetTextJustification(v) 42 | end, 43 | 44 | font = function (self, static_attr, attr, v) 45 | static_attr.font = v 46 | self:SetFont(v) 47 | end, 48 | 49 | text = function (self, static_attr, attr, v) 50 | static_attr.text = v 51 | self:SetText(v) 52 | end 53 | } 54 | -------------------------------------------------------------------------------- /gamemode/ui/variables.cl.lua: -------------------------------------------------------------------------------- 1 | HALF_ALPHA = 255/2 2 | QUARTER_ALPHA = 255/4 3 | ANIMATION_DURATION = 0.2 4 | 5 | LINE_THICKNESS = 2 6 | MARGIN = 4 7 | 8 | CAMUI_SMOOTH_MULTIPLIER = 16 9 | 10 | INDICATOR_WORLD_SIZE = 24 11 | INDICATOR_PERIPHERAL_SIZE = 40 12 | INDICATOR_COASTER_SIZE = 150 13 | 14 | COLUMN_WIDTH = 256 15 | 16 | BAR_WIDTH = 256 17 | BAR_HEIGHT = 64 18 | 19 | BUTTON_ICON_SIZE = 32 20 | LARGE_BUTTON_ICON_SIZE = 64 21 | 22 | CHECKBOX_SIZE = 24 23 | 24 | INPUT_HEIGHT = 128 + (MARGIN * 4) 25 | 26 | SettingsService.New("cam_ui_smooth_multiplier", { 27 | type = "number", 28 | default = 1, 29 | min = 0, 30 | max = 100, 31 | help = "3D UI smoothing multiplier", 32 | }) 33 | 34 | surface.CreateFont("TextBar", { 35 | font = "Roboto", 36 | size = 24, 37 | weight = 900 38 | }) 39 | 40 | surface.CreateFont("Header", { 41 | font = "Roboto", 42 | size = 26 43 | }) 44 | 45 | surface.CreateFont("ButtonBar", { 46 | font = "Roboto Mono", 47 | size = 22, 48 | italic = true 49 | }) 50 | 51 | surface.CreateFont("Info", { 52 | font = "Roboto", 53 | size = 22 54 | }) 55 | 56 | surface.CreateFont("NumberInfo", { 57 | font = "Roboto Mono", 58 | size = 18, 59 | weight = 900 60 | }) 61 | 62 | surface.CreateFont("Label", { 63 | font = "Roboto Mono", 64 | size = 16, 65 | italic = true 66 | }) 67 | 68 | surface.CreateFont("InputLabel", { 69 | font = "Roboto", 70 | size = 16 71 | }) 72 | 73 | surface.CreateFont("InputText", { 74 | font = "Roboto", 75 | size = 16 76 | }) -------------------------------------------------------------------------------- /gamemode/movement/gravity.sh.lua: -------------------------------------------------------------------------------- 1 | GravityService = GravityService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | CreateConVar("emm_gravity", 300, FCVAR_REPLICATED, "Player gravity") 7 | 8 | function GravityService.GetDefaultGravity() 9 | return GetConVar("emm_gravity"):GetFloat() 10 | end 11 | 12 | function GravityService.InitPlayerProperties(ply) 13 | ply.gravity = 300 14 | end 15 | hook.Add( 16 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 17 | "GravityService.InitPlayerProperties", 18 | GravityService.InitPlayerProperties 19 | ) 20 | 21 | 22 | -- # Gravity 23 | 24 | function GravityService.Velocity(ply, move) 25 | local gravity = Vector(0, 0, (ply.gravity * FrameTime())) 26 | 27 | return move:GetVelocity() - gravity 28 | end 29 | 30 | function GravityService.SetupGravity(ply, move) 31 | if not ply.lobby and ply.gravity ~= GravityService.GetDefaultGravity() then 32 | ply.gravity = GravityService.GetDefaultGravity() 33 | end 34 | 35 | local gravity = GetConVar("sv_stopspeed"):GetFloat() 36 | 37 | if gravity ~= 0 then 38 | local mult = gravity/(GravityService.GetDefaultGravity() * 200) 39 | 40 | ply:SetGravity(mult) 41 | move:SetVelocity(move:GetVelocity() + Vector(0, 0, GravityService.GetDefaultGravity() * mult * FrameTime())) 42 | end 43 | 44 | if not (ply:WaterLevel() > 1 or ply.gravity == 0) then 45 | move:SetVelocity(GravityService.Velocity(ply, move)) 46 | end 47 | end 48 | hook.Add("PlayerTick", "GravityService.SetupGravity", GravityService.SetupGravity) 49 | -------------------------------------------------------------------------------- /gamemode/player-class/methods.sh.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable("Player") 2 | 3 | local ent_metatable = FindMetaTable("Entity") 4 | local ent_get_table = ent_metatable.GetTable 5 | 6 | function player_metatable:__index(key) 7 | local tab = ent_get_table(self) 8 | 9 | if tab then 10 | local ply_class = tab.player_class 11 | 12 | if ply_class then 13 | local ply_class_val = ply_class[key] 14 | 15 | if ply_class_val ~= nil then 16 | return ply_class_val 17 | end 18 | end 19 | end 20 | 21 | local ply_mt_val = player_metatable[key] 22 | 23 | if ply_mt_val ~= nil then 24 | return ply_mt_val 25 | end 26 | 27 | local ent_mt_val = ent_metatable[key] 28 | 29 | if ent_mt_val ~= nil then 30 | return ent_mt_val 31 | end 32 | 33 | if tab then 34 | local tab_val = tab[key] 35 | 36 | if tab_val ~= nil then 37 | return tab_val 38 | end 39 | end 40 | 41 | return nil 42 | end 43 | 44 | function player_metatable:GetPlayerClass() 45 | return self.player_class 46 | end 47 | 48 | function player_metatable:HasPlayerClass() 49 | return self.player_class ~= nil 50 | end 51 | 52 | function player_metatable:SetupPlayerClass() 53 | self:SetupCoreProperties() 54 | 55 | if CLIENT and IsLocalPlayer(self) then 56 | hook.Run("LocalPlayerProperties", self) 57 | end 58 | 59 | hook.Run("PlayerProperties", self) 60 | 61 | if SERVER then 62 | self:SetupLoadout() 63 | end 64 | end 65 | 66 | function player_metatable:EndPlayerClass() 67 | self:SetupCoreProperties() 68 | end -------------------------------------------------------------------------------- /gamemode/util/savepoint.sv.lua: -------------------------------------------------------------------------------- 1 | SavepointService = SavepointService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function SavepointService.InitPlayerProperties(ply) 7 | ply.can_savepoint = false 8 | end 9 | hook.Add( 10 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 11 | "SavepointService.InitPlayerProperties", 12 | SavepointService.InitPlayerProperties 13 | ) 14 | 15 | 16 | -- # Saving/loading 17 | 18 | function SavepointService.CreateSavepoint(ply) 19 | local savepoint = {} 20 | savepoint.position = ply:GetPos() 21 | savepoint.velocity = ply:GetVelocity() 22 | savepoint.angle = ply:EyeAngles() 23 | 24 | return savepoint 25 | end 26 | 27 | function SavepointService.LoadSavepoint(ply, savepoint) 28 | ply:SetPos(savepoint.position) 29 | ply:SetVelocity(-ply:GetVelocity() + savepoint.velocity) 30 | ply:SetEyeAngles(savepoint.angle) 31 | end 32 | 33 | function SavepointService.RequestSavepoint(ply, cmd, args) 34 | if ply.can_savepoint then 35 | ply:ChatPrint("Savepoint created!") 36 | ply.savepoint = SavepointService.CreateSavepoint(ply) 37 | end 38 | end 39 | concommand.Add("emm_savepoint", SavepointService.RequestSavepoint) 40 | 41 | function SavepointService.RequestLoadSavepoint(ply, cmd, args) 42 | if ply.can_savepoint and ply.savepoint then 43 | ply:ChatPrint("Savepoint loaded!") 44 | SavepointService.LoadSavepoint(ply, ply.savepoint) 45 | end 46 | end 47 | concommand.Add("emm_load_savepoint", SavepointService.RequestLoadSavepoint) -------------------------------------------------------------------------------- /gamemode/minigame/net.sh.lua: -------------------------------------------------------------------------------- 1 | MinigameNetService = MinigameNetService or {} 2 | 3 | NetService.type_readers.minigame_prototype = function () 4 | return MinigameService.Prototype(NetService.ReadID()) 5 | end 6 | 7 | NetService.type_readers.minigame_lobby = function () 8 | return MinigameService.lobbies[NetService.ReadID()] 9 | end 10 | 11 | NetService.type_writers.minigame_prototype = function (proto) 12 | NetService.WriteID(proto.id) 13 | end 14 | 15 | NetService.type_writers.minigame_lobby = function (lobby) 16 | NetService.WriteID(lobby.id) 17 | end 18 | 19 | NetService.CreateSchema("Lobby", {"id", "minigame_prototype", "entity"}) 20 | NetService.CreateSchema("LobbyFinish", {"minigame_lobby"}) 21 | NetService.CreateSchema("LobbyHost", {"minigame_lobby", "entity"}) 22 | NetService.CreateSchema("LobbyState", {"minigame_lobby", "id", "float"}) 23 | NetService.CreateSchema("LobbyPlayer", {"minigame_lobby", "entity"}) 24 | NetService.CreateSchema("LobbyPlayerLeave", {"minigame_lobby", "entity"}) 25 | NetService.CreateSchema("LobbyHost", {"minigame_lobby", "entity"}) 26 | NetService.CreateUpstreamSchema "RequestLobbies" 27 | NetService.CreateUpstreamSchema("RequestLobby", {"minigame_prototype"}) 28 | NetService.CreateUpstreamSchema "RequestLobbyRestart" 29 | NetService.CreateUpstreamSchema "RequestLobbyFinish" 30 | NetService.CreateUpstreamSchema("RequestLobbyJoin", {"minigame_lobby"}) 31 | NetService.CreateUpstreamSchema "RequestLobbyLeave" 32 | 33 | hook.Add("LoadMinigamePrototypes", "CreateMinigameHookSchemas", function () 34 | hook.Run "CreateMinigameHookSchemas" 35 | end) -------------------------------------------------------------------------------- /gamemode/hud/factories.cl.lua: -------------------------------------------------------------------------------- 1 | -- # Factories 2 | 3 | function HUDService.CreateContainer() 4 | return Element.New { 5 | width_percent = 1, 6 | height_percent = 1, 7 | padding_x = SettingsService.Get "hud_padding_x", 8 | padding_y = SettingsService.Get "hud_padding_y" 9 | } 10 | end 11 | 12 | function HUDService.CreateSection(angle, dist) 13 | local element = HUDService.container:Add(Element.New { 14 | layout_direction = DIRECTION_COLUMN, 15 | width_percent = 1/3, 16 | height_percent = 1 17 | }) 18 | 19 | local rotate_origin_x 20 | 21 | if angle then 22 | if angle.y > 0 then 23 | rotate_origin_x = 1 24 | elseif 0 > angle.y then 25 | rotate_origin_x = 0 26 | end 27 | end 28 | 29 | CamUIService.AddPanel(element.panel, {distance = dist, angle = angle, rotate_origin_x = rotate_origin_x}) 30 | 31 | return element 32 | end 33 | 34 | function HUDService.CreateQuadrant(section, props) 35 | local element = section:Add(Element.New { 36 | layout_direction = DIRECTION_COLUMN, 37 | wrap = false, 38 | width_percent = 1, 39 | height_percent = 1/3, 40 | child_margin = 4 41 | }) 42 | 43 | if props then 44 | element:SetAttributes(props) 45 | end 46 | 47 | return element 48 | end 49 | 50 | function HUDService.CreateCrosshairContainer(camui) 51 | local element = Element.New { 52 | overlay = true, 53 | layout = false, 54 | origin_position = true, 55 | width_percent = 1, 56 | height_percent = 1, 57 | alpha = 0 58 | } 59 | 60 | if camui then 61 | CamUIService.AddPanel(element.panel, {smooth_divider = 4}) 62 | end 63 | 64 | return element 65 | end -------------------------------------------------------------------------------- /gamemode/util/pred-sound.sv.lua: -------------------------------------------------------------------------------- 1 | PredictedSoundService = PredictedSoundService or {} 2 | PredictedSoundService.RegisteredSounds = PredictedSoundService.RegisteredSounds or {} 3 | 4 | 5 | -- # Server sounds 6 | 7 | function PredictedSoundService.ExclusiveFilter(ply) 8 | local filter = RecipientFilter() 9 | filter:AddAllPlayers() 10 | filter:RemovePlayer(ply) 11 | 12 | return filter 13 | end 14 | 15 | function PredictedSoundService.RegisterSound(file) 16 | local sound_tab = { 17 | name = "server_predicted_sound_"..table.Count(PredictedSoundService.RegisteredSounds), 18 | channel = CHAN_STATIC, 19 | volume = 1, 20 | level = 80, 21 | pitch = {95, 110}, 22 | sound = file 23 | } 24 | 25 | sound.Add(sound_tab) 26 | PredictedSoundService.RegisteredSounds[file] = sound_tab.name 27 | end 28 | 29 | function PredictedSoundService.PlaySound(ply, file) 30 | CreateSound(ply, file, PredictedSoundService.ExclusiveFilter(ply)):Play() 31 | end 32 | 33 | function PredictedSoundService.PlayWallslideSound(ply) 34 | if not PredictedSoundService.RegisteredSounds[ply.wallslide_sound_file] then 35 | PredictedSoundService.RegisterSound(ply.wallslide_sound_file) 36 | end 37 | 38 | if ply.wallslide_sound and ply.wallslide_sound:IsPlaying() then 39 | ply.wallslide_sound:Stop() 40 | end 41 | 42 | ply.wallslide_sound = CreateSound(ply, PredictedSoundService.RegisteredSounds[ply.wallslide_sound_file], PredictedSoundService.ExclusiveFilter(ply)) 43 | ply.wallslide_sound:Play() 44 | end 45 | 46 | function PredictedSoundService.StopWallslideSound(ply) 47 | ply.wallslide_sound:FadeOut(0.25) 48 | end -------------------------------------------------------------------------------- /gamemode/hud/elements/key-echo.cl.lua: -------------------------------------------------------------------------------- 1 | -- # Key Echo 2 | 3 | KeyEcho = KeyEcho or Class.New(Element) 4 | 5 | function KeyEcho:Init(props) 6 | props = props or {} 7 | 8 | KeyEcho.super.Init(self, { 9 | layout = false, 10 | origin_position = true, 11 | width_percent = 0.325, 12 | height_percent = 0.325, 13 | background_color = COLOR_BACKGROUND, 14 | font = "KeyEcho", 15 | text_justification = 5, 16 | 17 | inner_text = props.arrow and Element.New { 18 | layout = false, 19 | width_percent = 1, 20 | height_percent = 1, 21 | y = -6, 22 | font = "KeyEcho", 23 | text_justification = 5, 24 | text = props.arrow 25 | } 26 | }) 27 | 28 | if props then 29 | if props.key then 30 | self.key = props.key 31 | end 32 | 33 | props.arrow = nil 34 | props.key = nil 35 | 36 | self:SetAttributes(props) 37 | end 38 | end 39 | 40 | function KeyEcho:Think() 41 | KeyEcho.super.Think(self) 42 | 43 | local key_down = HUDService.KeyDown(self.key) 44 | local text_color = HUDService.KeyDown(self.key) and COLOR_BACKGROUND or false 45 | 46 | self:SetAttribute("fill_color", key_down) 47 | self:SetAttribute("text_color", text_color) 48 | 49 | if self.inner_text then 50 | self.inner_text:SetAttribute("text_color", text_color) 51 | end 52 | end 53 | 54 | KeyEchos = KeyEchos or Class.New(Element) 55 | 56 | function KeyEchos:Init() 57 | KeyEchos.super.Init(self, { 58 | layout_justification_x = JUSTIFY_CENTER, 59 | layout_justification_y = JUSTIFY_START, 60 | layout_direction = DIRECTION_COLUMN, 61 | wrap = true, 62 | fit = false, 63 | width = 180, 64 | height = 180, 65 | child_margin = MARGIN * 2, 66 | }) 67 | end -------------------------------------------------------------------------------- /entities/effects/emm_ripple.lua: -------------------------------------------------------------------------------- 1 | local LIFE_SPAN = 1 2 | 3 | function EFFECT:Init(data) 4 | self.entity = data:GetEntity() 5 | self.start_time = CurTime() 6 | self.die_time = CurTime() + LIFE_SPAN 7 | self.origin = data:GetOrigin() 8 | self.normal = data:GetNormal() 9 | self.color = self.entity.color 10 | self.size = 0 11 | self.alpha = 255 12 | end 13 | 14 | local Ease = CubicBezier(0.2, 1, 0.2, 1) 15 | 16 | function EFFECT:Think() 17 | local time = math.TimeFraction(self.start_time, self.die_time, CurTime()) 18 | local alive = 1 >= time 19 | 20 | if alive then 21 | local eased_time = Ease(time) 22 | self.size = Lerp(eased_time, 0, 150) 23 | self.alpha = Lerp(eased_time, 255, 0) 24 | end 25 | 26 | return alive 27 | end 28 | 29 | local CIRCLE_MATERIAL = Material("emm2/shapes/circle.png", "noclamp smooth") 30 | 31 | function EFFECT:Render() 32 | local pos = self.origin + (self.normal/2) 33 | local norm_ang = self.normal:Angle() 34 | 35 | local ignore_z 36 | 37 | if IsValid(self.entity) then 38 | ignore_z = self.entity.indicator ~= nil 39 | else 40 | ignore_z = false 41 | end 42 | 43 | cam.IgnoreZ(ignore_z) 44 | surface.SetAlphaMultiplier(self.alpha/255) 45 | surface.SetMaterial(CIRCLE_MATERIAL) 46 | surface.SetDrawColor(self.color) 47 | 48 | cam.Start3D2D(pos, norm_ang + Angle(90, 0, 0), 0.25) 49 | surface.DrawTexturedRect(-self.size/2, -self.size/2, self.size, self.size) 50 | cam.End3D2D() 51 | 52 | cam.Start3D2D(pos, norm_ang + Angle(-90, 0, 0), 0.25) 53 | surface.DrawTexturedRect(-self.size/2, -self.size/2, self.size, self.size) 54 | cam.End3D2D() 55 | 56 | surface.SetAlphaMultiplier(1) 57 | cam.IgnoreZ(false) 58 | end 59 | -------------------------------------------------------------------------------- /gamemode/util/time-associated-map.sh.lua: -------------------------------------------------------------------------------- 1 | TimeAssociatedMapService = TimeAssociatedMapService or {} 2 | TimeAssociatedMapService.maps = TimeAssociatedMapService.maps or {} 3 | 4 | 5 | -- # Class 6 | 7 | TimeAssociatedMap = TimeAssociatedMap or {} 8 | TimeAssociatedMap.__index = TimeAssociatedMap 9 | 10 | function TimeAssociatedMapService.CreateMap(cooldown, lookup_func) 11 | local instance = setmetatable({}, TimeAssociatedMap) 12 | instance:Init(cooldown, lookup_func) 13 | 14 | table.insert(TimeAssociatedMapService.maps, instance) 15 | 16 | return instance 17 | end 18 | 19 | function TimeAssociatedMap:Init(cooldown, lookup_func) 20 | self.cooldown = cooldown 21 | self.lookup_func = lookup_func 22 | self.values = {} 23 | end 24 | 25 | function TimeAssociatedMap:Value(...) 26 | local cur_time = CurTime() 27 | 28 | if not self.values[cur_time] then 29 | self.values[cur_time] = self.lookup_func(...) 30 | end 31 | 32 | return self.values[cur_time] 33 | end 34 | 35 | function TimeAssociatedMap:Update(...) 36 | self.values[CurTime()] = self.lookup_func(...) 37 | end 38 | 39 | function TimeAssociatedMap:HasChecked() 40 | return self.values[CurTime()] ~= nil 41 | end 42 | 43 | function TimeAssociatedMap:Set(value) 44 | self.values[CurTime()] = value 45 | end 46 | 47 | 48 | -- # Cleanup 49 | 50 | function TimeAssociatedMapService.Cleanup() 51 | local cur_time = CurTime() 52 | 53 | for _, map in pairs(TimeAssociatedMapService.maps) do 54 | for time, _ in pairs(map.values) do 55 | if cur_time > (time + map.cooldown) then 56 | map.values[time] = nil 57 | end 58 | end 59 | end 60 | end 61 | hook.Add("Think", "TimeAssociatedMapService.Cleanup", TimeAssociatedMapService.Cleanup) -------------------------------------------------------------------------------- /gamemode/player-class/methods.cl.lua: -------------------------------------------------------------------------------- 1 | local player_metatable = FindMetaTable("Player") 2 | 3 | function player_metatable:SetPlayerClass(class) 4 | local old_class = self.player_class 5 | 6 | if old_class then 7 | self:ClearPlayerClass(true) 8 | end 9 | 10 | self.player_class = class 11 | table.insert(self.lobby[class.key], self) 12 | self:SetupPlayerClass() 13 | 14 | hook.Run("PlayerClassChange", self, old_class, class) 15 | 16 | if self.lobby:IsLocal() then 17 | hook.Run("LocalLobbyPlayerClassChange", self, old_class, class) 18 | end 19 | 20 | MinigameService.CallHook(self.lobby, "PlayerClassChange", self, old_class, class) 21 | 22 | if IsLocalPlayer(self) and class.display_name then 23 | NotificationService.PushMetaText(class.name, "PlayerClass", 2) 24 | end 25 | end 26 | 27 | function player_metatable:ClearPlayerClass(switching) 28 | local old_class = self.player_class 29 | 30 | table.RemoveByValue(self.lobby[self.player_class.key], self) 31 | self.player_class = nil 32 | self:EndPlayerClass() 33 | 34 | if not switching then 35 | hook.Run("PlayerClassChange", self, old_class) 36 | 37 | if self.lobby:IsLocal() then 38 | hook.Run("LocalLobbyPlayerClassChange", self, old_class) 39 | end 40 | 41 | MinigameService.CallHook(self.lobby, "PlayerClassChange", self, old_class) 42 | 43 | if IsLocalPlayer(self) then 44 | NotificationService.FinishSticky "PlayerClass" 45 | end 46 | end 47 | end 48 | 49 | function PlayerClassService.InitHUDElements() 50 | local ply_class = LocalPlayer().player_class 51 | 52 | if ply_class and ply_class.display_name then 53 | NotificationService.PushMetaText(ply_class.name, "PlayerClass", 2) 54 | end 55 | end 56 | hook.Add("InitHUDElements", "PlayerClassService.InitHUDElements", PlayerClassService.InitHUDElements) -------------------------------------------------------------------------------- /gamemode/util/cubic-bezier.sh.lua: -------------------------------------------------------------------------------- 1 | function CubicBezier(p1x, p1y, p2x, p2y) 2 | local function SolveEpsilon(duration) 3 | return 1/(200 * duration) 4 | end 5 | 6 | local cx = 3 * p1x 7 | local bx = 3 * (p2x - p1x) - cx 8 | local ax = 1 - cx - bx 9 | local cy = 3 * p1y 10 | local by = 3 * (p2y - p1y) - cy 11 | local ay = 1 - cy - by 12 | 13 | local function SampleCurveX(t) 14 | return ((((ax * t) + bx) * t) + cx) * t 15 | end 16 | 17 | local function SampleCurveY(t) 18 | return ((((ay * t) + by) * t) + cy) * t 19 | end 20 | 21 | local function SampleCurveDerivativeX(t) 22 | return (((3 * ax * t) + (2 * bx)) * t) + cx 23 | end 24 | 25 | local function SolveCurveX(x, epsilon) 26 | local t0 27 | local t1 28 | local t2 29 | local x2 30 | local d2 31 | local i 32 | 33 | for i = 1, 8 do 34 | t2 = x 35 | x2 = SampleCurveX(t2) - x 36 | 37 | if epsilon > math.abs(x2) then 38 | return t2 39 | end 40 | 41 | d2 = SampleCurveDerivativeX(t2) 42 | 43 | if math.abs(d2) < 1e-6 then 44 | break 45 | end 46 | 47 | t2 = t2 - (x2/d2) 48 | end 49 | 50 | t0 = 0 51 | t1 = 1 52 | t2 = x 53 | 54 | if t2 < t0 then 55 | return t0 56 | end 57 | 58 | if (t2 > t1) then 59 | return t1 60 | end 61 | 62 | while t0 < t1 do 63 | x2 = SampleCurveX(t2) 64 | 65 | if epsilon > math.abs(x2 - x) then 66 | return t2 67 | end 68 | 69 | if x > x2 then 70 | t0 = t2 71 | else 72 | t1 = t2 73 | end 74 | 75 | t2 = ((t1 - t0)/2) + t0 76 | end 77 | 78 | return t2 79 | end 80 | 81 | local function Solve(x, epsilon) 82 | return SampleCurveY(SolveCurveX(x, epsilon)) 83 | end 84 | 85 | return function(x, duration) 86 | duration = duration or 400 87 | return Solve(x, SolveEpsilon(duration)) 88 | end 89 | end -------------------------------------------------------------------------------- /gamemode/ui/elements/button-bar.cl.lua: -------------------------------------------------------------------------------- 1 | ButtonBar = ButtonBar or Class.New(Element) 2 | 3 | function ButtonBar:Init(props) 4 | props = props or {} 5 | 6 | ButtonBar.super.Init(self, { 7 | layout_justification_y = JUSTIFY_CENTER, 8 | width = BAR_WIDTH, 9 | height = BAR_HEIGHT, 10 | padding_x = 32, 11 | child_margin = 32, 12 | background_color = props.background and COLOR_GRAY or props.background_color, 13 | inherit_color = false, 14 | border = LINE_THICKNESS, 15 | border_color = props.color, 16 | border_alpha = 0, 17 | cursor = "hand", 18 | bubble_mouse = false, 19 | 20 | hover = { 21 | color = props.color, 22 | border_alpha = 255 23 | }, 24 | 25 | press = { 26 | background_color = COLOR_GRAY_DARK, 27 | color = COLOR_BACKGROUND, 28 | border_color = COLOR_BACKGROUND 29 | }, 30 | 31 | props.material and Element.New { 32 | width = BUTTON_ICON_SIZE, 33 | height = BUTTON_ICON_SIZE, 34 | crop_y = 0.1, 35 | material = props.material 36 | }, 37 | 38 | Element.New { 39 | fit = true, 40 | font = "ButtonBar", 41 | text_justification = 4, 42 | text = string.upper(props.text) 43 | }, 44 | 45 | props.divider and Element.New { 46 | layout = false, 47 | origin_position = true, 48 | origin_justification_x = JUSTIFY_CENTER, 49 | origin_justification_y = JUSTIFY_END, 50 | position_justification_x = JUSTIFY_CENTER, 51 | position_justification_y = JUSTIFY_END, 52 | width_percent = 1, 53 | height = 1, 54 | inherit_color = false, 55 | fill_color = true, 56 | color = COLOR_BACKGROUND_LIGHT, 57 | 58 | alpha = function () 59 | return self.last and 0 or 255 60 | end 61 | } 62 | }) 63 | 64 | self.on_click = props.on_click 65 | end 66 | 67 | function ButtonBar:OnMousePressed(mouse) 68 | ButtonBar.super.OnMousePressed(self, mouse) 69 | self.on_click(self, mouse) 70 | end -------------------------------------------------------------------------------- /entities/effects/emm_spark.lua: -------------------------------------------------------------------------------- 1 | function EFFECT:Init(data) 2 | local ent = data:GetEntity() 3 | local origin = data:GetOrigin() 4 | local ang = data:GetAngles() 5 | local norm = data:GetNormal() 6 | local emitter = ParticleEmitter(origin, false) 7 | 8 | for i = 0, 20 do 9 | local vel = Vector(math.random(0, 256), math.random(-128, 128), math.random(0, 128)) 10 | vel:Rotate(norm:Angle()) 11 | 12 | local particle = emitter:Add("effects/spark", origin) 13 | particle:SetCollide(true) 14 | particle:SetVelocity(vel) 15 | particle:SetLifeTime(0) 16 | particle:SetDieTime(1) 17 | particle:SetGravity(Vector(0, 0, -800)) 18 | particle:SetBounce(0.25) 19 | particle:SetColor(ent.color.r, ent.color.g, ent.color.b) 20 | particle:SetStartAlpha(255) 21 | particle:SetEndAlpha(0) 22 | particle:SetStartSize(math.random(3, 5)) 23 | particle:SetStartLength(math.random(3, 10)) 24 | particle:SetEndSize(0) 25 | particle:SetEndLength(0) 26 | end 27 | 28 | for i = 0, 20 do 29 | local vel = Vector(math.random(0, 256), math.random(-128, 256), math.random(-128, 256)) 30 | vel:Rotate(norm:Angle()) 31 | 32 | local particle = emitter:Add("effects/spark", origin) 33 | particle:SetCollide(true) 34 | particle:SetVelocity(vel) 35 | particle:SetLifeTime(0) 36 | particle:SetDieTime(0.5) 37 | particle:SetGravity(Vector(0, 0, -800)) 38 | particle:SetBounce(0.25) 39 | particle:SetColor(ent.color.r, ent.color.g, ent.color.b) 40 | particle:SetStartAlpha(255) 41 | particle:SetEndAlpha(0) 42 | particle:SetStartSize(math.random(2, 2)) 43 | particle:SetStartLength(math.random(1, 3)) 44 | particle:SetEndSize(0) 45 | particle:SetEndLength(0) 46 | end 47 | 48 | emitter:Finish() 49 | end 50 | 51 | function EFFECT:Think() 52 | return false 53 | end 54 | 55 | function EFFECT:Render() 56 | -- 57 | end 58 | -------------------------------------------------------------------------------- /gamemode/movement/friction.sh.lua: -------------------------------------------------------------------------------- 1 | FrictionService = FrictionService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | CreateConVar("emm_friction", 8, FCVAR_REPLICATED, "Player friction") 7 | 8 | function FrictionService.GetDefaultFriction() 9 | return GetConVar("emm_friction"):GetFloat() 10 | end 11 | 12 | function FrictionService.InitPlayerProperties(ply) 13 | ply.friction = GetConVar("emm_friction"):GetFloat() 14 | end 15 | hook.Add( 16 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 17 | "FrictionService.InitPlayerProperties", 18 | FrictionService.InitPlayerProperties 19 | ) 20 | 21 | 22 | -- # Friction 23 | 24 | function FrictionService.Velocity(friction, move) 25 | local vel = move:GetVelocity() 26 | local speed = vel:Length() 27 | local stop_speed = GetConVar("sv_stopspeed"):GetFloat() 28 | local drop = 0 29 | 30 | local new_speed 31 | local control 32 | 33 | if stop_speed > speed then 34 | control = stop_speed 35 | else 36 | control = speed 37 | end 38 | 39 | drop = drop + (control * friction * FrameTime()) 40 | new_speed = speed - drop 41 | 42 | if 0 > new_speed then 43 | new_speed = 0 44 | end 45 | 46 | if new_speed ~= speed then 47 | new_speed = new_speed/speed 48 | vel = vel * new_speed 49 | end 50 | 51 | return vel 52 | end 53 | 54 | function FrictionService.SetupFriction(ply, move) 55 | if not ply.lobby and ply.friction ~= FrictionService.GetDefaultFriction() then 56 | ply.friction = FrictionService.GetDefaultFriction() 57 | end 58 | 59 | if not ( 60 | move:GetVelocity():Length() < 0.1 or 61 | ply:GetGroundEntity() == NULL or 62 | (move:KeyPressed(IN_JUMP) and not move:KeyWasDown(IN_JUMP)) or 63 | (ply.can_autojump and move:KeyDown(IN_JUMP)) 64 | ) then 65 | move:SetVelocity(FrictionService.Velocity(ply.friction, move)) 66 | end 67 | end 68 | hook.Add("PlayerTick", "FrictionService.SetupFriction", FrictionService.SetupFriction) 69 | -------------------------------------------------------------------------------- /gamemode/util/spectate.cl.lua: -------------------------------------------------------------------------------- 1 | SpectateService = SpectateService or {} 2 | SpectateService.unspectate_keys = bit.bor(IN_JUMP, IN_MOVELEFT, IN_MOVERIGHT, IN_FORWARD, IN_BACK) 3 | SpectateService.buttons = 0 4 | 5 | 6 | -- # Utils 7 | 8 | function SpectateService.AutoComplete(cmd, args) 9 | local tbl = {} 10 | 11 | for _, v in pairs(player.GetAll()) do 12 | if string.find(string.lower(" " .. v:Nick()), args:lower()) then 13 | table.insert(tbl, "emm_spectate " .. v:Nick()) 14 | end 15 | end 16 | 17 | return tbl 18 | end 19 | 20 | 21 | -- # Spectating 22 | 23 | concommand.Add("emm_spectate", function(ply, cmd, args) 24 | RunConsoleCommand("sv_emm_spectate", unpack(args)) 25 | end, SpectateService.AutoComplete) 26 | 27 | function SpectateService.TargetKeyDown(key) 28 | return bit.band(SpectateService.buttons, key) 29 | end 30 | 31 | function SpectateService.UnSpectateCheck(ply, key) 32 | if 33 | IsFirstTimePredicted() and 34 | ply:GetObserverMode() ~= 0 and 35 | bit.band(SpectateService.unspectate_keys, key) ~= 0 36 | then 37 | SpectateService.buttons = 0 38 | ply:ConCommand("emm_unspectate") 39 | end 40 | end 41 | hook.Add("KeyPress", "SpectateService.UnSpectateCheck", SpectateService.UnSpectateCheck) 42 | 43 | function SpectateService.SpectateMode(ply, key) 44 | if 45 | IsFirstTimePredicted() and 46 | ply:GetObserverMode() ~= 0 and 47 | key == IN_ATTACK 48 | then 49 | local obs_mode = OBS_MODE_IN_EYE 50 | 51 | if ply:GetObserverMode() == obs_mode then 52 | obs_mode = OBS_MODE_CHASE 53 | end 54 | 55 | ply:SetObserverMode(obs_mode) 56 | end 57 | end 58 | hook.Add("KeyPress", "SpectateService.SpectateMode", SpectateService.SpectateMode) 59 | 60 | function SpectateService.UpdateSpectateKeys() 61 | SpectateService.buttons = net.ReadUInt(24) 62 | end 63 | net.Receive("SpectateKeys", SpectateService.UpdateSpectateKeys) 64 | -------------------------------------------------------------------------------- /gamemode/element/states.cl.lua: -------------------------------------------------------------------------------- 1 | local reserved_states = { 2 | "disabled", 3 | "hover", 4 | "press" 5 | } 6 | 7 | function Element:InitStates() 8 | self.states = {} 9 | self.current_state = "original" 10 | self.reserved_states = {} 11 | 12 | for _, k in pairs(reserved_states) do 13 | self.reserved_states[k] = true 14 | end 15 | end 16 | 17 | function Element:SaveOriginalState() 18 | self.states.original = {} 19 | 20 | for k, v in pairs(self.attributes) do 21 | self.states.original[k] = v.current 22 | end 23 | end 24 | 25 | function Element:AddState(key, props) 26 | self.states[key] = props 27 | end 28 | 29 | function Element:SetState(key, ...) 30 | if self.current_state == "original" and not self.states.original then 31 | self:SaveOriginalState() 32 | end 33 | 34 | self.current_state = key 35 | self:SetAttributes(self.states[key]) 36 | end 37 | 38 | function Element:AnimateState(state_k, ...) 39 | local original_state = self.current_state == "original" 40 | 41 | if original_state and not self.states.original then 42 | self:SaveOriginalState() 43 | end 44 | 45 | local modified_attr = {} 46 | 47 | if not original_state then 48 | for k, _ in pairs(self.states[self.current_state]) do 49 | if not self.states[state_k][k] then 50 | modified_attr[k] = true 51 | end 52 | end 53 | end 54 | 55 | self.current_state = state_k 56 | 57 | for k, _ in pairs(modified_attr) do 58 | self:AnimateAttribute(k, self.states.original[k], ...) 59 | end 60 | 61 | for k, v in pairs(self.states[state_k]) do 62 | self:AnimateAttribute(k, v, ...) 63 | end 64 | end 65 | 66 | function Element:RevertState(...) 67 | if self.states.original then 68 | for k, v in pairs(self.states[self.current_state]) do 69 | local original_v = self.states.original[k] 70 | 71 | if v ~= original_v then 72 | self:AnimateAttribute(k, original_v, ...) 73 | end 74 | end 75 | 76 | self.current_state = "original" 77 | end 78 | end -------------------------------------------------------------------------------- /gamemode/ui/elements/input-bar.cl.lua: -------------------------------------------------------------------------------- 1 | InputBar = InputBar or Class.New(Element) 2 | 3 | function InputBar.Type(type) 4 | local type = type or "boolean" 5 | 6 | type = type or "boolean" 7 | 8 | local input_element 9 | 10 | if type == "boolean" then 11 | input_element = Checkbox 12 | elseif type == "text" then 13 | input_element = TextInput 14 | elseif type == "number" then 15 | input_element = NumberInput 16 | elseif type == "time" then 17 | input_element = TimeInput 18 | elseif type == "list" then 19 | input_element = ListSelector 20 | end 21 | 22 | return input_element 23 | end 24 | 25 | function InputBar:Init(label, type, v, input_props) 26 | InputBar.super.Init(self, { 27 | layout_justification_x = JUSTIFY_END, 28 | layout_justification_y = JUSTIFY_CENTER, 29 | width_percent = 1, 30 | height = 52, 31 | padding_x = 32, 32 | padding_y = 14, 33 | font = "InputLabel", 34 | text_justification = 4, 35 | text = label, 36 | 37 | Element.New { 38 | layout = false, 39 | origin_position = true, 40 | origin_justification_x = JUSTIFY_CENTER, 41 | origin_justification_y = JUSTIFY_END, 42 | position_justification_x = JUSTIFY_CENTER, 43 | position_justification_y = JUSTIFY_END, 44 | width_percent = 1, 45 | height = 1, 46 | inherit_color = false, 47 | fill_color = true, 48 | color = COLOR_BACKGROUND_LIGHT, 49 | 50 | alpha = function () 51 | return self.last and 0 or 255 52 | end 53 | } 54 | }) 55 | 56 | self:AddState("hidden", { 57 | crop_bottom = 1, 58 | background_color = COLOR_BACKGROUND_LIGHT 59 | }) 60 | 61 | if type or v or input_props then 62 | self.input = InputBar.Type(type).New(v, input_props) 63 | 64 | self:Add(Element.New { 65 | layout_justification_x = JUSTIFY_END, 66 | width_percent = 0.25, 67 | height_percent = 1, 68 | self.input 69 | }) 70 | end 71 | end 72 | 73 | function InputBar:SetValue(v) 74 | self.input:SetValue(v) 75 | end 76 | 77 | function InputBar:GetValue() 78 | return self.input.value 79 | end -------------------------------------------------------------------------------- /gamemode/util/util.sh.lua: -------------------------------------------------------------------------------- 1 | function Nily(v) 2 | return v == "nil" or v == nil 3 | end 4 | 5 | function Falsy(v) 6 | return ( 7 | v == "" or 8 | v == 0 or 9 | v == false or 10 | Nily(v) 11 | ) 12 | end 13 | 14 | function Default(...) 15 | local final_v 16 | 17 | for _, v in pairs({...}) do 18 | if v ~= "nil" then 19 | final_v = v 20 | 21 | break 22 | end 23 | end 24 | 25 | return final_v 26 | end 27 | 28 | function Snap(n, snap) 29 | local mod = n % snap 30 | 31 | return n - mod + (math.Round(mod/snap) * snap) 32 | end 33 | 34 | function SequentialTableHasValue(tab, val) 35 | for i = 1, #tab do 36 | if val == tab[i] then 37 | return true 38 | end 39 | end 40 | 41 | return false 42 | end 43 | 44 | function Plural(string, quantity) 45 | return string..((quantity ~= 1) and "s" or "") 46 | end 47 | 48 | function NiceTime(seconds) 49 | local text 50 | 51 | if seconds == nil then 52 | text = "a few seconds" 53 | elseif seconds < 60 then 54 | local floored = math.floor(seconds) 55 | 56 | text = floored..Plural(" second", floored) 57 | elseif seconds < (60 * 60) then 58 | local floored = math.floor(seconds/60) 59 | 60 | text = floored..Plural(" minute", floored) 61 | else 62 | local floored = math.floor(seconds/(60 * 60)) 63 | 64 | text = floored..Plural(" hour", floored) 65 | end 66 | 67 | return text 68 | end 69 | 70 | function IsColor(color) 71 | return istable(color) and color.r and color.g and color.b 72 | end 73 | 74 | function IsPlayer(ply) 75 | return isentity(ply) and IsValid(ply) and ply:IsPlayer() 76 | end 77 | 78 | function GetPlayer(ply) 79 | if SERVER then 80 | if IsValid(ply) and IsValid(ply:GetObserverTarget()) then 81 | return ply:GetObserverTarget() 82 | end 83 | 84 | return ply 85 | else 86 | local local_ply = LocalPlayer() 87 | if IsValid(local_ply) then 88 | if IsValid(local_ply:GetObserverTarget()) then 89 | return local_ply:GetObserverTarget() 90 | end 91 | end 92 | 93 | return local_ply 94 | end 95 | return nil 96 | end -------------------------------------------------------------------------------- /gamemode/movement/wallslide.cl.lua: -------------------------------------------------------------------------------- 1 | WallslideService = WallslideService or {} 2 | 3 | SettingsService.New("clientside_wallslide", { 4 | type = "boolean", 5 | default = true, 6 | help = "Client-side predicted wallslide", 7 | }) 8 | 9 | 10 | -- # Time maps 11 | 12 | local has_stamina = has_stamina or TimeAssociatedMapService.CreateMap(2, function() 13 | return LocalPlayer().stamina.wallslide:HasStamina() 14 | end) 15 | 16 | local wallsliding = wallsliding or TimeAssociatedMapService.CreateMap(2, function() 17 | return LocalPlayer().wallsliding 18 | end) 19 | 20 | local last_wallslide_time = last_wallslide_time or TimeAssociatedMapService.CreateMap(2, function() 21 | return LocalPlayer().last_wallslide_time 22 | end) 23 | 24 | local wallslide_velocity = wallslide_velocity or TimeAssociatedMapService.CreateMap(2, function() return 25 | LocalPlayer().wallslide_velocity 26 | end) 27 | 28 | local started_wallsliding = started_wallsliding or TimeAssociatedMapService.CreateMap(2, function() 29 | return false 30 | end) 31 | 32 | local finished_wallsliding = finished_wallsliding or TimeAssociatedMapService.CreateMap(2, function() 33 | return false 34 | end) 35 | 36 | 37 | -- # Client Functions 38 | 39 | function WallslideService.HasStamina(ply) 40 | return has_stamina:Value() 41 | end 42 | 43 | function WallslideService.Wallsliding(ply) 44 | return wallsliding:Value() 45 | end 46 | 47 | function WallslideService.UpdateWallsliding(ply) 48 | return wallsliding:Update() 49 | end 50 | 51 | function WallslideService.StartedWallslide(ply) 52 | return started_wallsliding:HasChecked() or started_wallsliding:Value() 53 | end 54 | 55 | function WallslideService.FinishedWallslide(ply) 56 | return finished_wallsliding:HasChecked() or finished_wallsliding:Value() 57 | end 58 | 59 | function WallslideService.LastWallslideTime(ply) 60 | return last_wallslide_time:Value() 61 | end 62 | 63 | function WallslideService.WallslideVelocity(ply) 64 | return wallslide_velocity:Value() 65 | end -------------------------------------------------------------------------------- /classes.md: -------------------------------------------------------------------------------- 1 | # Classes 2 | 3 | This gamemode comes with its own object oriented class library tailored to Garry's Mod Lua. It provides inheritance and a simple way to attach class instances to Garry's Mod hooks. 4 | 5 | ## Creating a new class 6 | 7 | New classes are created with `Class.New()`. By doing `OurClass = OurClass or Class.New()` it saves existing class instances from being ruined on auto refresh if you are developing in-game. The `Init` method is where you can work with the passed parameters and assign properties. 8 | 9 | ```lua 10 | CheckpointMarkerFadeBeam = CheckpointMarkerFadeBeam or Class.New() 11 | 12 | function CheckpointMarkerFadeBeam:Init(props) 13 | self.direction = props.direction or Vector(0, 0, 1) 14 | 15 | ... 16 | end 17 | ``` 18 | 19 | In the above example, `CheckpointMarkerFadeBeam.New({direction = Vector(0, 1, 0)})` will make a new class instance with the provided direction. 20 | 21 | 22 | ```lua 23 | ButtonBar = ButtonBar or Class.New(Element) 24 | 25 | function ButtonBar:Init(props) 26 | ButtonBar.super.Init(self, props) 27 | ... 28 | end 29 | ``` 30 | 31 | Inheritance is done by providing an existing class to `Class.New(OurClass)`. `ButtonBar.super.Method(self, ...)` accesses the inherited method. 32 | 33 | ## Hooks 34 | 35 | If you need a class instance to think or render, `Class.AddHook(OurClass, "HookName", "ClassFunctionName")` lets you automatically attach new instances to these hooks. 36 | 37 | ```lua 38 | function AnimatableValue:Think() 39 | ... 40 | 41 | self:Animate() 42 | 43 | ... 44 | end 45 | Class.AddHook(AnimatableValue, "Think") 46 | 47 | function CheckpointMarkerFadeBeam:Render() 48 | render.SetColorMaterialIgnoreZ() 49 | render.StartBeam(3) 50 | ... 51 | render.EndBeam() 52 | end 53 | Class.AddHook(CheckpointMarkerFadeBeam, "PostDrawTranslucentRenderables", "Render") 54 | ``` 55 | 56 | ## Finishing a class instance 57 | 58 | The `Finish` method is the end of a class instance's lifecycle. If you attached the class to a hook, you need to run `self:DisconnectFromHooks()` which removes the instance hooks. 59 | 60 | ```lua 61 | function CheckpointMarkerFadeBeam:Finish() 62 | self.opacity:Finish() 63 | self:DisconnectFromHooks() 64 | 65 | ... 66 | end 67 | ``` 68 | -------------------------------------------------------------------------------- /gamemode/minigame/util.sh.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.IsSharingLobby(a, b) 2 | local sharing 3 | local lobby_a 4 | local lobby_b 5 | 6 | if isentity(a) then 7 | local owner = a:GetOwner() 8 | 9 | lobby_a = IsValid(owner) and owner.lobby or a.lobby 10 | else 11 | lobby_a = a 12 | end 13 | 14 | if isentity(b) then 15 | local owner = b:GetOwner() 16 | 17 | lobby_b = IsValid(owner) and owner.lobby or b.lobby 18 | else 19 | lobby_b = b 20 | end 21 | 22 | if lobby_a and lobby_b and lobby_a == lobby_b then 23 | sharing = true 24 | else 25 | sharing = false 26 | end 27 | 28 | return sharing 29 | end 30 | 31 | function MinigameService.FilterPlayers(lobby, props) 32 | props = props or {} 33 | 34 | local whitelist_class_plys = lobby[props.whitelist_class_key] 35 | local blacklist_class_plys = lobby[props.blacklist_class_key] 36 | local blacklist_plys = props.blacklist_plys or {} 37 | 38 | local filtered_plys = {} 39 | 40 | if whitelist_class_plys then 41 | filtered_plys = table.Copy(whitelist_class_plys) 42 | else 43 | filtered_plys = table.Copy(lobby.players) 44 | 45 | if blacklist_class_plys then 46 | blacklist_plys = table.Add(blacklist_plys, table.Copy(blacklist_class_plys)) 47 | end 48 | end 49 | 50 | if #blacklist_plys > 0 then 51 | local removed_plys_i = {} 52 | 53 | for i, ply in pairs(filtered_plys) do 54 | if table.HasValue(blacklist_plys, ply) then 55 | table.insert(removed_plys_i, i) 56 | end 57 | end 58 | 59 | for i, ply_i in pairs(removed_plys_i) do 60 | table.remove(filtered_plys, ply_i) 61 | end 62 | end 63 | 64 | return filtered_plys 65 | end 66 | 67 | function MinigameService.ClosestPlayer(lobby, origin_ply, filter) 68 | local plys = MinigameService.FilterPlayers(lobby, filter) 69 | local origin = origin_ply:WorldSpaceCenter() 70 | 71 | local closest_ply 72 | local closest_dist 73 | 74 | for i = 1, #plys do 75 | local ply = plys[i] 76 | 77 | if origin_ply ~= ply and ply:Alive() then 78 | local dist = origin:Distance(ply:WorldSpaceCenter()) 79 | 80 | if not closest_dist or dist < closest_dist then 81 | closest_ply = ply 82 | closest_dist = dist 83 | end 84 | end 85 | end 86 | 87 | return closest_ply 88 | end 89 | -------------------------------------------------------------------------------- /gamemode/player/hooks.sh.lua: -------------------------------------------------------------------------------- 1 | NetService.CreateSchema("PlayerInitialSpawn", {"player_index"}) 2 | NetService.CreateSchema("PlayerSpawn", {"player_index"}) 3 | NetService.CreateSchema("PlayerDisconnected", {"entity"}) 4 | NetService.CreateSchema("PrePlayerDeath", {"entity", "entity"}) 5 | NetService.CreateSchema("PlayerDeath", {"entity", "entity", "entity"}) 6 | NetService.CreateSchema("PostPlayerDeath", {"entity"}) 7 | 8 | -- # Properties 9 | 10 | hook.Add("InitPlayerProperties", "InitCorePlayerProperties", function (ply) 11 | ply.color = COLOR_WHITE 12 | 13 | if CLIENT then 14 | ply.animatable_color = AnimatableValue.New(COLOR_WHITE, { 15 | smooth = true, 16 | 17 | generate = function () 18 | return IsValid(ply) and ply.color or COLOR_WHITE 19 | end 20 | }) 21 | end 22 | 23 | ply.can_regenerate_health = true 24 | ply.max_health = 100 25 | ply.health_regenerate_step = 1 26 | 27 | ply.run_speed = 400 28 | ply.jump_power = 220 29 | 30 | ply.can_take_fall_damage = true 31 | ply.fall_damage_multiplier = 0.0563 32 | 33 | ply.death_cooldown = 2 34 | ply.last_death_time = 0 35 | ply.old_velocity = Vector() 36 | end) 37 | 38 | hook.Add("PlayerDisconnected", "FinishPlayerProperties", function (ply) 39 | if CLIENT then 40 | ply.animatable_color:Finish() 41 | end 42 | end) 43 | 44 | hook.Add("PlayerProperties", "SetCollisionCheck", function (ply) 45 | ply:SetCustomCollisionCheck(true) 46 | end) 47 | 48 | hook.Add("ShouldCollide", "EMM.ShouldCollide", function (a, b) 49 | local should_collide 50 | 51 | if MinigameService.IsSharingLobby(a, b) then 52 | should_collide = true 53 | else 54 | should_collide = false 55 | end 56 | 57 | return should_collide 58 | end) 59 | 60 | local function SetDeathTime(ply) 61 | ply.last_death_time = CurTime() 62 | end 63 | hook.Add("PlayerDeath", "DeathTime", SetDeathTime) 64 | 65 | if SERVER then 66 | hook.Add("PlayerSilentDeath", "DeathTime", SetDeathTime) 67 | end 68 | 69 | hook.Add("OnEntityCreated", "AssignLobby", function (ent) 70 | local owner = ent:GetOwner() 71 | 72 | if IsValid(owner) and owner.lobby then 73 | ent.lobby = owner.lobby 74 | end 75 | end) 76 | 77 | hook.Add("Move", "EMM.OldVelocity", function (ply, move) 78 | ply.old_velocity = move:GetVelocity() 79 | end) -------------------------------------------------------------------------------- /gamemode/util/net.sh.lua: -------------------------------------------------------------------------------- 1 | NetService = NetService or {} 2 | NetService.hooks = NetService.hooks or {} 3 | NetService.writers = NetService.writers or {} 4 | NetService.readers = NetService.readers or {} 5 | 6 | function NetService.ReadID() 7 | return net.ReadUInt(8) 8 | end 9 | 10 | function NetService.WriteID(id) 11 | net.WriteUInt(id or 0, 8) 12 | end 13 | 14 | --[[ 15 | Built-in type readers 16 | ]] 17 | NetService.type_readers = { 18 | boolean = net.ReadBool, 19 | 20 | id = function () 21 | return NetService.ReadID() 22 | end, 23 | 24 | float = net.ReadFloat, 25 | string = net.ReadString, 26 | entity = net.ReadEntity, 27 | vector = net.ReadVector, 28 | 29 | entities = function () 30 | local ents = {} 31 | local ent_count = NetService.ReadID() 32 | 33 | for i = 1, ent_count do 34 | table.insert(ents, net.ReadEntity()) 35 | end 36 | 37 | return ents 38 | end, 39 | 40 | player_index = function () 41 | return net.ReadUInt(16) 42 | end, 43 | 44 | table = function () 45 | return net.ReadTable() 46 | end 47 | } 48 | 49 | NetService.type_writers = { 50 | boolean = net.WriteBool, 51 | 52 | id = function (id) 53 | NetService.WriteID(id) 54 | end, 55 | 56 | float = net.WriteFloat, 57 | string = net.WriteString, 58 | entity = net.WriteEntity, 59 | vector = net.WriteVector, 60 | 61 | entities = function (ents) 62 | net.WriteUInt(#ents, 8) 63 | 64 | for _, ent in pairs(ents) do 65 | net.WriteEntity(ent) 66 | end 67 | end, 68 | 69 | player_index = function (ply) 70 | net.WriteUInt(ply and ply:EntIndex() or 0, 16) 71 | end, 72 | 73 | table = function (tab) 74 | net.WriteTable(tab) 75 | end, 76 | 77 | obj_id = function(obj) 78 | NetService.WriteID(obj) 79 | end 80 | } 81 | 82 | function NetService.CreateWriter(name, schema) 83 | schema = schema or {} 84 | 85 | local sender = function (...) 86 | net.Start(name) 87 | 88 | for i = 1, #schema do 89 | NetService.type_writers[schema[i]](select(i, ...)) 90 | end 91 | end 92 | 93 | NetService.writers[name] = sender 94 | 95 | return sender 96 | end 97 | 98 | 99 | function NetService.Receive(name, func) 100 | NetService.hooks[name] = func 101 | end -------------------------------------------------------------------------------- /gamemode/minigame/net.sv.lua: -------------------------------------------------------------------------------- 1 | util.AddNetworkString "RequestLobbies" 2 | util.AddNetworkString "Lobbies" 3 | 4 | function MinigameNetService.SendLobbies(ply) 5 | net.Start "Lobbies" 6 | net.WriteUInt(table.Count(MinigameService.lobbies), 8) 7 | 8 | for k, lobby in pairs(MinigameService.lobbies) do 9 | NetService.WriteID(k) 10 | NetService.WriteID(lobby.prototype.id) 11 | NetService.WriteID(lobby.state.id) 12 | net.WriteFloat(lobby.last_state_start) 13 | net.WriteEntity(lobby.host) 14 | net.WriteUInt(#lobby.players, 8) 15 | 16 | for _, ply in pairs(lobby.players) do 17 | net.WriteEntity(ply) 18 | end 19 | 20 | net.WriteTable(MinigameSettingsService.AdjustedSettings(lobby)) 21 | end 22 | 23 | net.Send(ply) 24 | 25 | ply.received_lobbies = true 26 | end 27 | NetService.Receive("RequestLobbies", MinigameNetService.SendLobbies) 28 | 29 | function MinigameNetService.RequestLobby(ply, proto) 30 | if ply.lobby then 31 | ply.lobby:RemovePlayer(ply) 32 | end 33 | 34 | MinigameService.CreateLobby { 35 | prototype = table.Copy(proto), 36 | host = ply, 37 | players = {ply} 38 | } 39 | end 40 | NetService.Receive("RequestLobby", MinigameNetService.RequestLobby) 41 | 42 | function MinigameNetService.RequestLobbyRestart(ply) 43 | local lobby = ply.lobby 44 | 45 | if lobby and lobby:CanRestart() then 46 | lobby:SetState(lobby.states.Ending) 47 | end 48 | end 49 | NetService.Receive("RequestLobbyRestart", MinigameNetService.RequestLobbyRestart) 50 | 51 | function MinigameNetService.RequestLobbyFinish(ply) 52 | MinigameService.FinishLobby(ply.lobby) 53 | end 54 | NetService.Receive("RequestLobbyFinish", MinigameNetService.RequestLobbyFinish) 55 | 56 | function MinigameNetService.RequestLobbyJoin(ply, lobby) 57 | if ply.lobby then 58 | ply.lobby:RemovePlayer(ply) 59 | end 60 | 61 | if lobby then 62 | lobby:AddPlayer(ply) 63 | end 64 | end 65 | NetService.Receive("RequestLobbyJoin", MinigameNetService.RequestLobbyJoin) 66 | 67 | function MinigameNetService.RequestLobbyLeave(ply) 68 | if ply.lobby then 69 | ply.lobby:RemovePlayer(ply) 70 | end 71 | end 72 | NetService.Receive("RequestLobbyLeave", MinigameNetService.RequestLobbyLeave) 73 | 74 | function MinigameNetService.CreateHookSchema(hk_name, schema) 75 | NetService.CreateSchema("Minigame."..hk_name, table.Add({"minigame_lobby"}, schema)) 76 | end -------------------------------------------------------------------------------- /gamemode/hud/nametags.cl.lua: -------------------------------------------------------------------------------- 1 | NametagService = NametagService or {} 2 | 3 | local hide_radius = 32 4 | local height_offset = 24 5 | local nametag_alpha_smooth_multiplier = 4 6 | 7 | local function NametagAlpha(ply) 8 | local alpha 9 | 10 | local not_near_crosshair 11 | 12 | if ply.indicator_x and ply.indicator_y then 13 | not_near_crosshair = math.sqrt(((ply.indicator_x - (ScrW()/2)) ^ 2) + ((ply.indicator_y - (ScrH()/2) - height_offset) ^ 2)) > hide_radius 14 | end 15 | 16 | if IsValid(ply) and LocalPlayer():Alive() and ply:Alive() and not_near_crosshair then 17 | if ply.indicator then 18 | alpha = 255 19 | elseif ply.visible >= 0.5 then 20 | alpha = HALF_ALPHA 21 | else 22 | alpha = 0 23 | end 24 | else 25 | alpha = 0 26 | end 27 | 28 | return alpha 29 | end 30 | 31 | function NametagService.InitPlayerProperties(ply) 32 | if not IsLocalPlayer(ply) then 33 | ply.nametag_alpha = AnimatableValue.New(0, { 34 | smooth = true, 35 | smooth_multiplier = nametag_alpha_smooth_multiplier, 36 | 37 | generate = function () 38 | return NametagAlpha(ply) 39 | end 40 | }) 41 | end 42 | end 43 | hook.Add("InitPlayerProperties", "NametagService.InitPlayerProperties", NametagService.InitPlayerProperties) 44 | 45 | function NametagService.FinishPlayerProperties(ply) 46 | if not IsLocalPlayer(ply) then 47 | ply.nametag_alpha:Finish() 48 | end 49 | end 50 | hook.Add("PlayerDisconnected", "NametagService.FinishPlayerProperties", NametagService.FinishPlayerProperties) 51 | 52 | function NametagService.Draw(ply) 53 | local plys = player.GetAll() 54 | 55 | for i = 1, #plys do 56 | local ply = plys[i] 57 | 58 | if not IsLocalPlayer(ply) and ply.nametag_alpha then 59 | local alpha = ply.nametag_alpha.smooth 60 | 61 | if alpha > 0 then 62 | surface.SetFont "Nametag" 63 | 64 | local ply_name = string.upper(ply:GetName()) 65 | local w, h = surface.GetTextSize(ply_name) 66 | local color = GetSmoothPlayerColor(ply) 67 | 68 | surface.SetTextColor(ColorAlpha(color, CombineAlphas(color.a, alpha) * 255)) 69 | surface.SetTextPos(ply.indicator_x - (w/2), ply.indicator_y - (h/2) - height_offset) 70 | surface.DrawText(ply_name) 71 | end 72 | end 73 | end 74 | end 75 | hook.Add("DrawNametags", "NametagService.Draw", NametagService.Draw) 76 | 77 | function GM:HUDDrawTargetID() 78 | -- 79 | end -------------------------------------------------------------------------------- /gamemode/movement/ledge-bounce.sh.lua: -------------------------------------------------------------------------------- 1 | LedgeBounce = LedgeBounce or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function LedgeBounce.InitPlayerProperties(ply) 7 | ply.bounce_height = 21 8 | ply.bounce_angle = 45 9 | ply.bounce_min_vel = 500^2 10 | end 11 | hook.Add( 12 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 13 | "LedgeBounce.InitPlayerProperties", 14 | LedgeBounce.InitPlayerProperties 15 | ) 16 | 17 | 18 | -- # Ledge Bounce 19 | 20 | function LedgeBounce.DoBounce(ply, vel, pos, wish_dir) 21 | if vel.z > -ply:GetJumpPower() and vel:Length2DSqr() > ply.bounce_min_vel then 22 | local pred_vel = vel * FrameTime() * 4 23 | local bounce_height = Vector(0, 0, ply.bounce_height) 24 | local ply_mins = ply:OBBMins() 25 | local ply_maxs = ply:OBBMaxs() 26 | local bottom_trace = util.TraceHull { 27 | start = pos, 28 | endpos = pos + pred_vel, 29 | mins = ply_mins, 30 | maxs = Vector(ply_maxs.x, ply_maxs.y, ply.bounce_height), 31 | mask = MASK_PLAYERSOLID_BRUSHONLY 32 | } 33 | local can_bounce = bottom_trace.HitNormal:Dot(wish_dir:GetNormalized()) 34 | 35 | if bottom_trace.HitWorld and 0.5 > can_bounce and can_bounce ~= 0 then 36 | local trace_pos = bottom_trace.HitPos + bottom_trace.HitNormal + pred_vel 37 | local top_trace = util.TraceHull { 38 | start = trace_pos + bounce_height, 39 | endpos = trace_pos, 40 | mins = ply_mins, 41 | maxs = ply_maxs - bounce_height, 42 | mask = MASK_PLAYERSOLID_BRUSHONLY 43 | } 44 | 45 | if 1 > bottom_trace.Fraction and bottom_trace.Fraction > 0 and top_trace.HitWorld and not top_trace.StartSolid and top_trace.HitNormal ~= bottom_trace.HitNormal then 46 | return bottom_trace.HitNormal 47 | end 48 | end 49 | end 50 | 51 | return false 52 | end 53 | 54 | function LedgeBounce.SetupBounce(ply, move) 55 | local vel = move:GetVelocity() 56 | local ledge_normal = LedgeBounce.DoBounce(ply, vel, move:GetOrigin(), AiraccelService.WishDir(ply, move:GetMoveAngles():Forward(), move:GetForwardSpeed(), move:GetSideSpeed())) 57 | 58 | if ledge_normal then 59 | local normal = ledge_normal:Angle() 60 | 61 | normal.x = 360 - ply.bounce_angle 62 | normal = normal:Forward() 63 | ply:SetGroundEntity(NULL) 64 | move:SetVelocity(SlideService.Clip(vel, normal)) 65 | end 66 | end 67 | hook.Add("Move", "LedgeBounce.SetupBounce", LedgeBounce.SetupBounce) -------------------------------------------------------------------------------- /gamemode/minigame/lobby.sv.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.CreateLobby(props) 2 | props = props or {} 3 | 4 | local id = #MinigameService.lobbies + 1 5 | props.id = id 6 | 7 | local lobby = MinigameLobby.New(props) 8 | MinigameService.lobbies[id] = lobby 9 | 10 | return lobby 11 | end 12 | 13 | function MinigameService.FinishLobby(lobby) 14 | MinigameService.lobbies[lobby.id] = nil 15 | lobby:Finish() 16 | end 17 | 18 | function MinigameLobby:Init(props) 19 | self.id = props.id 20 | self.prototype = props.prototype 21 | self.host = props.host 22 | self.players = props.players or {} 23 | 24 | for _, ply in pairs(self.players) do 25 | ply.lobby = self 26 | end 27 | 28 | for k, _ in pairs(self.prototype.player_classes) do 29 | self[k] = {} 30 | end 31 | 32 | self:InitSettings() 33 | 34 | NetService.Broadcast("Lobby", self.id, self.prototype, self.host) 35 | hook.Run("LobbyCreate", self) 36 | 37 | self:SetState(self.states[self.default_state]) 38 | end 39 | 40 | function MinigameLobby:Finish() 41 | hook.Run("LobbyFinish", self) 42 | 43 | for _, ply in pairs(self.players) do 44 | self:RemovePlayer(ply, false, true) 45 | end 46 | 47 | NetService.Broadcast("LobbyFinish", self) 48 | table.Empty(self) 49 | end 50 | 51 | function MinigameLobby:SetHost(ply) 52 | self.host = ply 53 | NetService.Broadcast("LobbyHost", self, ply) 54 | hook.Run("LobbyHostChange", lobby, ply) 55 | end 56 | 57 | function MinigameLobby:AddPlayer(ply) 58 | ply.lobby = self 59 | table.insert(self.players, ply) 60 | 61 | NetService.Broadcast("LobbyPlayer", self, ply) 62 | hook.Run("LobbyPlayerJoin", self, ply) 63 | MinigameService.CallHook(self, "PlayerJoin", ply) 64 | end 65 | 66 | function MinigameLobby:RemovePlayer(ply, net, force) 67 | net = Default(net, true) 68 | 69 | local has_plys = #self.players > 1 70 | 71 | if force or has_plys then 72 | MinigameService.CallHook(self, "PlayerLeave", ply) 73 | hook.Run("LobbyPlayerLeave", self, ply) 74 | 75 | ply.lobby = nil 76 | table.RemoveByValue(self.players, ply) 77 | 78 | if net then 79 | NetService.Broadcast("LobbyPlayerLeave", self, ply) 80 | end 81 | 82 | if has_plys and self.host == ply then 83 | self:SetHost(self.players[1]) 84 | end 85 | 86 | ply.leaving_lobby = nil 87 | else 88 | MinigameService.FinishLobby(self) 89 | end 90 | end 91 | hook.Add("PlayerDisconnected", "MinigameService.RemoveDisconnectedPlayer", function (ply) 92 | if ply.lobby then 93 | ply.lobby:RemovePlayer(ply) 94 | end 95 | end) -------------------------------------------------------------------------------- /gamemode/init.lua: -------------------------------------------------------------------------------- 1 | EMM = EMM or {} 2 | 3 | EMM.server_includes = {} 4 | 5 | gamemode_name = engine.ActiveGamemode() 6 | gamemode_lua_directory = gamemode_name.."/gamemode/" 7 | 8 | AddCSLuaFile "cl_init.lua" 9 | AddCSLuaFile(gamemode_name..".lua") 10 | 11 | function IsSharedFile(file) 12 | return string.match(file, "sh.lua$") 13 | end 14 | 15 | function IsServerFile(file) 16 | return string.match(file, "sv.lua$") 17 | end 18 | 19 | function IsClientFile(file) 20 | return string.match(file, "cl.lua$") 21 | end 22 | 23 | function EMM.RequireClientsideLuaDirectory(dir) 24 | dir = dir and string.TrimLeft(dir, "/") or "" 25 | 26 | local files, child_dirs = file.Find(gamemode_lua_directory..dir.."/*", "LUA") 27 | 28 | for _, file in pairs(files) do 29 | if IsSharedFile(file) or IsClientFile(file) then 30 | MsgN("requiring client-side include "..dir.."/"..file) 31 | AddCSLuaFile(gamemode_lua_directory..dir.."/"..file) 32 | end 33 | end 34 | 35 | for _, child_dir in pairs(child_dirs) do 36 | EMM.RequireClientsideLuaDirectory(dir.."/"..child_dir) 37 | end 38 | end 39 | EMM.RequireClientsideLuaDirectory() 40 | 41 | function EMM.Include(inc, inc_func) 42 | inc_func = inc_func or include 43 | 44 | if istable(inc) then 45 | for _, _inc in pairs(inc) do 46 | EMM.Include(_inc) 47 | end 48 | elseif isstring(inc) then 49 | local inc_path = gamemode_lua_directory..inc 50 | local inc_file = file.Find(inc_path, "LUA")[1] 51 | local sh_inc_file = file.Find(inc_path..".sh.lua", "LUA")[1] 52 | local sv_inc_file = file.Find(inc_path..".sv.lua", "LUA")[1] 53 | 54 | if inc_file or sh_inc_file or sv_inc_file then 55 | MsgN("including "..inc) 56 | else 57 | return 58 | end 59 | 60 | if inc_file and (IsSharedFile(inc_file) or IsServerFile(inc_file)) then 61 | inc_func(inc_path) 62 | end 63 | 64 | if sh_inc_file then 65 | inc_func(inc_path..".sh.lua") 66 | end 67 | 68 | if sv_inc_file then 69 | inc_func(inc_path..".sv.lua") 70 | end 71 | 72 | EMM.server_includes[inc] = true 73 | end 74 | end 75 | 76 | function EMM.AddResourceDirectory(dir) 77 | local files, child_dirs = file.Find(dir.."/*", "THIRDPARTY") 78 | 79 | for _, file in pairs(files) do 80 | resource.AddFile(dir.."/"..file) 81 | end 82 | 83 | for _, child_dir in pairs(child_dirs) do 84 | EMM.AddResourceDirectory(dir.."/"..child_dir) 85 | end 86 | end 87 | 88 | EMM.AddResourceDirectory "materials/emm2" 89 | EMM.AddResourceDirectory "resource/fonts" 90 | include(gamemode_name..".lua") -------------------------------------------------------------------------------- /gamemode/core/gamemode.sh.lua: -------------------------------------------------------------------------------- 1 | GM.Name = "Parkour" 2 | GM.Author = "arkade" 3 | 4 | EMM.Include { 5 | "settings/settings", 6 | 7 | "util/variables", 8 | "util/util", 9 | "util/palette", 10 | "util/class", 11 | "util/cubic-bezier", 12 | "util/anim-value", 13 | "util/net", 14 | "util/pred-sound", 15 | "util/stamina", 16 | "util/time-associated-map", 17 | "util/savepoint", 18 | "util/spectate", 19 | "util/trail", 20 | "util/sound-isolation", 21 | 22 | "player/hooks", 23 | "player/methods", 24 | "player-class/player-class", 25 | "player-class/net", 26 | "player-class/methods", 27 | 28 | "movement/walljump", 29 | "movement/wallslide", 30 | "movement/airaccel", 31 | "movement/autojump", 32 | "movement/airaccel-patch", 33 | "movement/slope", 34 | "movement/slide", 35 | "movement/ledge-bounce", 36 | "movement/friction", 37 | "movement/gravity", 38 | 39 | "minigame/minigame", 40 | "minigame/prototype", 41 | "minigame/lobby", 42 | "minigame/hooks", 43 | "minigame/net", 44 | "minigame/states", 45 | "minigame/settings", 46 | "minigame/tagging", 47 | "minigame/util", 48 | 49 | "element/element", 50 | "element/panel", 51 | "element/attributes", 52 | "element/setters", 53 | "element/layout", 54 | "element/stack", 55 | "element/paint", 56 | "element/states", 57 | 58 | "ui/variables", 59 | "ui/ui", 60 | "ui/util", 61 | "ui/cam", 62 | "ui/vardebug", 63 | "ui/elements/scroll-container", 64 | "ui/elements/text-bar", 65 | "ui/elements/button-bar", 66 | "ui/elements/meter-bar", 67 | "ui/elements/player-bar", 68 | "ui/elements/avatar-bar", 69 | "ui/elements/checkbox", 70 | "ui/elements/input-dragger", 71 | "ui/elements/text-input", 72 | "ui/elements/number-input", 73 | "ui/elements/time-input", 74 | "ui/elements/list-selector", 75 | "ui/elements/input-bar", 76 | 77 | "settings/ui", 78 | "settings/factories", 79 | 80 | "lobby-ui/lobby-ui", 81 | "lobby-ui/elements", 82 | "lobby-ui/factories", 83 | "lobby-ui/elements/lobby-bar", 84 | "lobby-ui/elements/lobby-card", 85 | "lobby-ui/elements/lobby-settings", 86 | 87 | "hud/variables", 88 | "hud/hud", 89 | "hud/elements", 90 | "hud/factories", 91 | "hud/nametags", 92 | "hud/indicators", 93 | "hud/notifications", 94 | "hud/elements/hud-meter", 95 | "hud/elements/crosshair-line", 96 | "hud/elements/crosshair-meter", 97 | "hud/elements/indicator", 98 | "hud/elements/notifications", 99 | "hud/elements/key-echo" 100 | } 101 | -------------------------------------------------------------------------------- /gamemode/minigame/tagging.sv.lua: -------------------------------------------------------------------------------- 1 | TaggingService.taggable_groups = TaggingService.taggable_groups or {} 2 | 3 | function TaggingService.InitPlayerProperties(ply) 4 | ply.taggable_radius = 80 5 | ply.taggable_cooldown = 1 6 | ply.last_tag_time = 0 7 | end 8 | hook.Add("InitPlayerProperties", "TaggingService.InitPlayerProperties", TaggingService.InitPlayerProperties) 9 | 10 | function TaggingService.Tag(lobby, taggable, tagger) 11 | taggable.last_tag_time = CurTime() 12 | 13 | MinigameService.CallNetHook(taggable.lobby, "Tag", taggable, tagger) 14 | 15 | if taggable.player_class.swap_on_tag then 16 | MinigameService.SwapPlayerClass(taggable, tagger, taggable.player_class.kill_on_tag, taggable.player_class.kill_tagger_on_tag) 17 | elseif taggable.player_class.recruit_on_tag then 18 | tagger:SetPlayerClass(taggable.player_class) 19 | end 20 | end 21 | 22 | function TaggingService.Think() 23 | for i = 1, #TaggingService.taggable_groups do 24 | for _i = 1, #TaggingService.taggable_groups[i] do 25 | local taggable = TaggingService.taggable_groups[i][_i] 26 | 27 | if IsValid(taggable) then 28 | local ents = ents.FindInSphere(taggable:WorldSpaceCenter(), taggable.taggable_radius) 29 | 30 | if 31 | taggable:Alive() and 32 | CurTime() > (taggable.last_tag_time + taggable.taggable_cooldown) 33 | then 34 | for __i = 1, #ents do 35 | local ent = ents[__i] 36 | 37 | if 38 | taggable ~= ent and 39 | ent:IsPlayer() and 40 | ent:Alive() and 41 | MinigameService.IsSharingLobby(taggable, ent) and 42 | ent.player_class and 43 | taggable.player_class.can_tag[ent.player_class.key] 44 | then 45 | TaggingService.Tag(taggable.lobby, taggable, ent) 46 | end 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | hook.Add("Think", "TaggingService.Think", TaggingService.Think) 54 | 55 | function TaggingService.InitLobby(lobby) 56 | for k, player_class in pairs(lobby.player_classes) do 57 | if player_class.can_tag then 58 | table.insert(TaggingService.taggable_groups, lobby[k]) 59 | end 60 | end 61 | end 62 | hook.Add("LobbyCreate", "TaggingService.InitLobby", TaggingService.InitLobby) 63 | 64 | function TaggingService.FinishLobby(lobby) 65 | for k, player_class in pairs(lobby.player_classes) do 66 | if player_class.can_tag then 67 | table.RemoveByValue(TaggingService.taggable_groups, lobby[k]) 68 | end 69 | end 70 | end 71 | hook.Add("LobbyFinish", "TaggingService.FinishLobby", TaggingService.FinishLobby) -------------------------------------------------------------------------------- /gamemode/minigame/util.sv.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.ClearPlayerClasses(lobby) 2 | for _, ply in pairs(lobby.players) do 3 | if ply.player_class then 4 | ply:ClearPlayerClass() 5 | end 6 | end 7 | end 8 | 9 | function MinigameService.PickRandomPlayerClasses(lobby, props) 10 | props = props or {} 11 | 12 | local ply_count = props.count or 1 13 | 14 | local ply_classes = lobby.player_classes 15 | local class = ply_classes[props.class_key] 16 | local rejected_class = ply_classes[props.rejected_class_key] 17 | 18 | local less_prob_plys = props.less_probable_players 19 | local less_prob_ply_chance = props.less_probable_player_chance or 0.33 20 | 21 | local plys = MinigameService.FilterPlayers(lobby, props) 22 | local picked_plys = {} 23 | local rejected_plys = {} 24 | 25 | while #picked_plys < ply_count do 26 | local ply = plys[math.random(1, #plys)] 27 | 28 | if not table.HasValue(picked_plys, ply) then 29 | if less_prob_plys and table.HasValue(less_prob_plys, ply) then 30 | if math.random() > less_prob_ply_chance then 31 | table.insert(picked_plys, ply) 32 | end 33 | else 34 | table.insert(picked_plys, ply) 35 | end 36 | end 37 | end 38 | 39 | for _, ply in pairs(plys) do 40 | if table.HasValue(picked_plys, ply) then 41 | ply:SetPlayerClass(class) 42 | elseif rejected_class then 43 | table.insert(rejected_plys, ply) 44 | ply:SetPlayerClass(rejected_class) 45 | end 46 | end 47 | 48 | return picked_plys, rejected_class 49 | end 50 | 51 | function MinigameService.PickClosestPlayerClass(lobby, origin_ply, props) 52 | props = props or {} 53 | 54 | local ply_classes = lobby.player_classes 55 | 56 | local origin_ply_class = ply_classes[props.origin_player_class_key] 57 | 58 | if origin_ply_class then 59 | origin_ply:SetPlayerClass(origin_ply_class) 60 | end 61 | 62 | local closest_ply = MinigameService.ClosestPlayer(lobby, origin_ply, props) 63 | 64 | if closest_ply then 65 | if ply_classes[props.class_key] then 66 | closest_ply:SetPlayerClass(ply_classes[props.class_key]) 67 | elseif props.swap_player_class then 68 | MinigameService.SwapPlayerClass(origin_ply, closest_ply) 69 | end 70 | end 71 | 72 | return closest_ply 73 | end 74 | 75 | function MinigameService.SwapPlayerClass(ply_a, ply_b, kill_ply_a, kill_ply_b) 76 | local class_a = ply_a.player_class 77 | local class_b = ply_b.player_class 78 | 79 | ply_a:SetPlayerClass(class_b) 80 | ply_b:SetPlayerClass(class_a) 81 | 82 | if kill_ply_a then 83 | ply_a:Kill() 84 | end 85 | 86 | if kill_ply_b then 87 | ply_b:Kill() 88 | end 89 | end -------------------------------------------------------------------------------- /entities/entities/emm_trail.lua: -------------------------------------------------------------------------------- 1 | AddCSLuaFile() 2 | 3 | ENT.Type = "anim" 4 | 5 | function ENT:SetupDataTables() 6 | self:NetworkVar("Entity", 0, "Sprite") 7 | end 8 | 9 | function ENT:Initialize() 10 | self:DrawShadow(false) 11 | 12 | if SERVER then 13 | local sprite = util.SpriteTrail(self, 0, COLOR_WHITE, false, 4, 4, 4, 0.125, "emm2/trails/flat.vmt") 14 | 15 | self:SetSprite(sprite) 16 | self:DeleteOnRemove(sprite) 17 | self.width = AnimatableValue.New(4) 18 | end 19 | end 20 | 21 | local REMOVE_DURATION = 4 22 | 23 | function ENT:StartRemove() 24 | local owner = self:GetOwner() 25 | local trail_pos = self:GetPos() 26 | 27 | if IsValid(owner) then 28 | owner.trail = nil 29 | end 30 | 31 | self:SetParent(nil) 32 | self:SetPos(trail_pos) 33 | self.width:AnimateTo(0, { 34 | duration = REMOVE_DURATION, 35 | finish = true, 36 | callback = function () 37 | self:Remove() 38 | end 39 | }) 40 | end 41 | 42 | function ENT:SetWidth(w) 43 | local sprite = self:GetSprite() 44 | 45 | if IsValid(sprite) then 46 | sprite:SetKeyValue("startwidth", w) 47 | sprite:SetKeyValue("endwidth", w) 48 | end 49 | end 50 | 51 | function ENT:Think() 52 | self:NextThink(CurTime()) 53 | 54 | if SERVER then 55 | self:SetWidth(self.width.current) 56 | else 57 | local owner = self:GetOwner() 58 | local parent = self:GetParent() 59 | local sprite = self:GetSprite() 60 | 61 | if IsValid(sprite) then 62 | sprite:SetColor(GetSmoothPlayerColor(owner)) 63 | end 64 | 65 | local render_pos 66 | 67 | if IsValid(owner) and owner:IsPlayer() then 68 | local parent_is_valid = IsValid(parent) 69 | 70 | local pos 71 | 72 | if parent_is_valid then 73 | pos = parent:GetPos() + Vector(0, 0, 4) 74 | else 75 | pos = self:GetPos() 76 | end 77 | 78 | if parent_is_valid and IsLocalPlayer(owner) and owner:Alive() then 79 | local eye_norm = Angle(0, owner:EyeAngles().y, 0):Forward() 80 | 81 | local trace = util.TraceLine({ 82 | start = pos, 83 | endpos = pos + (eye_norm * Vector(-70, -70, 0)), 84 | mask = MASK_NPCWORLDSTATIC 85 | }) 86 | 87 | render_pos = trace.HitPos - eye_norm * Vector(-4, -4, 0) 88 | else 89 | render_pos = pos 90 | end 91 | else 92 | render_pos = parent_is_valid and parent:GetPos() or self:GetPos() 93 | end 94 | 95 | self:SetRenderOrigin(render_pos) 96 | end 97 | 98 | return true 99 | end 100 | 101 | function ENT:UpdateTransmitState() 102 | return TRANSMIT_ALWAYS 103 | end 104 | 105 | function ENT:Draw() 106 | -- 107 | end 108 | -------------------------------------------------------------------------------- /gamemode/ui/elements/checkbox.cl.lua: -------------------------------------------------------------------------------- 1 | Checkbox = Class.New(Element) 2 | 3 | local checkbox_material = Material("emm2/ui/check.png", "noclamp smooth") 4 | 5 | function Checkbox:Init(bool, props) 6 | Checkbox.super.Init(self, { 7 | width = CHECKBOX_SIZE, 8 | height = CHECKBOX_SIZE, 9 | background_color = COLOR_GRAY_DARK, 10 | border = LINE_THICKNESS, 11 | border_alpha = 0, 12 | inherit_cursor = false, 13 | cursor = "hand", 14 | bubble_mouse = false, 15 | 16 | disabled = { 17 | background_color = COLOR_BLACK_CLEAR, 18 | border = 1, 19 | border_color = COLOR_GRAY_DARK, 20 | border_alpha = 255 21 | }, 22 | 23 | hover = { 24 | border_alpha = 255 25 | }, 26 | 27 | press = { 28 | border_alpha = 0 29 | }, 30 | 31 | check = Element.New { 32 | layout = false, 33 | origin_position = true, 34 | origin_justification_x = JUSTIFY_CENTER, 35 | origin_justification_y = JUSTIFY_CENTER, 36 | position_justification_x = JUSTIFY_CENTER, 37 | position_justification_y = JUSTIFY_CENTER, 38 | width = BUTTON_ICON_SIZE, 39 | height = BUTTON_ICON_SIZE, 40 | alpha = bool and 255 or 0, 41 | material = checkbox_material 42 | }, 43 | }) 44 | 45 | self.value = bool 46 | 47 | if props then 48 | self:SetAttributes(props) 49 | self.read_only = props.read_only 50 | self.on_change = props.on_change 51 | 52 | if props.read_only then 53 | self:Disable() 54 | end 55 | end 56 | end 57 | 58 | function Checkbox:Disable() 59 | self.disabled = true 60 | self.panel:SetMouseInputEnabled(false) 61 | self:AnimateState "disabled" 62 | end 63 | 64 | function Checkbox:Enable() 65 | self.disabled = false 66 | self.panel:SetMouseInputEnabled(true) 67 | self:RevertState() 68 | end 69 | 70 | function Checkbox:OnValueChanged(v, no_callback) 71 | self.value = v 72 | self.check:AnimateAttribute("alpha", v and 255 or 0) 73 | 74 | if not no_callback and self.on_change then 75 | self.on_change(self, v) 76 | end 77 | end 78 | 79 | function Checkbox:OnMousePressed(mouse) 80 | Checkbox.super.OnMousePressed(self, mouse) 81 | self:OnValueChanged(not self.value) 82 | end 83 | 84 | function Checkbox:OnMouseEntered() 85 | if self.disabled then 86 | self.panel:SetMouseInputEnabled(false) 87 | else 88 | Checkbox.super.OnMouseEntered(self) 89 | end 90 | end 91 | 92 | function Checkbox:OnMouseExited() 93 | if not self.disabled then 94 | Checkbox.super.OnMouseExited(self) 95 | end 96 | end 97 | 98 | function Checkbox:SetValue(v, no_callback) 99 | if v ~= self.value then 100 | self:OnValueChanged(v, no_callback) 101 | end 102 | end -------------------------------------------------------------------------------- /gamemode/minigame/net.cl.lua: -------------------------------------------------------------------------------- 1 | function MinigameNetService.ReceiveLobby(lobby_id, proto, host) 2 | MinigameService.CreateLobby { 3 | id = lobby_id, 4 | prototype = table.Copy(proto), 5 | host = host, 6 | players = {host} 7 | } 8 | end 9 | NetService.Receive("Lobby", MinigameNetService.ReceiveLobby) 10 | 11 | local function CallIfReceivedLobbies(func) 12 | return function (...) 13 | if MinigameNetService.received_lobbies then 14 | func(...) 15 | end 16 | end 17 | end 18 | 19 | function MinigameNetService.ReceiveLobbyState(lobby, state_id, last_state_start) 20 | lobby:SetState(MinigameStateService.State(lobby, state_id), last_state_start) 21 | end 22 | NetService.Receive("LobbyState", CallIfReceivedLobbies(MinigameNetService.ReceiveLobbyState)) 23 | 24 | NetService.Receive("LobbyFinish", CallIfReceivedLobbies(MinigameService.FinishLobby)) 25 | NetService.Receive("LobbyHost", CallIfReceivedLobbies(MinigameLobby.SetHost)) 26 | NetService.Receive("LobbyPlayer", CallIfReceivedLobbies(MinigameLobby.AddPlayer)) 27 | NetService.Receive("LobbyPlayerLeave", CallIfReceivedLobbies(MinigameLobby.RemovePlayer)) 28 | 29 | function MinigameNetService.RequestLobbies() 30 | NetService.SendToServer "RequestLobbies" 31 | end 32 | hook.Add("InitPostEntity", "MinigameNetService.RequestLobbies", MinigameNetService.RequestLobbies) 33 | 34 | function MinigameNetService.ReceiveLobbies(len) 35 | local lobby_count = NetService.ReadID() 36 | 37 | for i = 1, lobby_count do 38 | local lobby_id = NetService.ReadID() 39 | local proto_id = NetService.ReadID() 40 | local state_id = NetService.ReadID() 41 | local last_state_start = net.ReadFloat() 42 | local host = net.ReadEntity() 43 | local ply_count = NetService.ReadID() 44 | 45 | local plys = {} 46 | 47 | for i = 1, ply_count do 48 | plys[i] = net.ReadEntity() 49 | end 50 | 51 | local settings = net.ReadTable() 52 | local proto = table.Copy(MinigameService.Prototype(proto_id)) 53 | 54 | local lobby = MinigameService.CreateLobby({ 55 | id = lobby_id, 56 | prototype = proto, 57 | state = MinigameStateService.State(proto, state_id), 58 | last_state_start = last_state_start, 59 | host = host, 60 | players = plys 61 | }, false) 62 | 63 | MinigameSettingsService.Adjust(lobby, settings) 64 | end 65 | 66 | MinigameNetService.received_lobbies = true 67 | hook.Run "ReceiveLobbies" 68 | end 69 | net.Receive("Lobbies", MinigameNetService.ReceiveLobbies) 70 | 71 | function MinigameNetService.CreateHookSchema(hk_name, schema) 72 | NetService.CreateSchema("Minigame."..hk_name, table.Add({"minigame_lobby"}, schema)) 73 | 74 | NetService.Receive("Minigame."..hk_name, function (lobby, ...) 75 | MinigameService.CallHook(lobby, hk_name, ...) 76 | end) 77 | end -------------------------------------------------------------------------------- /gamemode/element/paint.cl.lua: -------------------------------------------------------------------------------- 1 | function Element:PaintTexture(mat, x, y, w, h, ang, color) 2 | local attr = self.attributes 3 | 4 | x = x + attr.padding_left.current - self:GetXCropOffset() 5 | y = y + attr.padding_top.current - self:GetYCropOffset() 6 | w = w or attr.width.current 7 | h = h or attr.height.current 8 | ang = ang or (attr.angle and attr.angle.current) or 0 9 | color = color or self:GetColor() 10 | 11 | draw.NoTexture() 12 | surface.SetDrawColor(color) 13 | surface.SetMaterial(mat) 14 | 15 | if ang then 16 | surface.DrawTexturedRectRotated(x + (w/2), y + (h/2), w, h, ang) 17 | else 18 | surface.DrawTexturedRect(x, y, w, h) 19 | end 20 | end 21 | 22 | function Element:PaintRect(x, y, w, h, color) 23 | local attr = self.attributes 24 | 25 | x = x or 0 26 | y = y or 0 27 | w = w or attr.width.current 28 | h = h or attr.height.current 29 | color = color or self:GetColor() 30 | 31 | surface.SetDrawColor(color) 32 | surface.DrawRect(x, y, w, h) 33 | end 34 | 35 | function Element:PaintBorder(w, h, color, thickness) 36 | local attr = self.attributes 37 | 38 | w = w or attr.width.current 39 | h = h or attr.height.current 40 | color = color or (attr.border_color and attr.border_color.current) or self:GetColor() 41 | thickness = thickness or attr.border.current 42 | 43 | local cropped_w = w - ((w * attr.crop_left.current) + (w * attr.crop_right.current)) 44 | local cropped_h = h - ((h * attr.crop_top.current) + (h * attr.crop_bottom.current)) 45 | local color_with_alpha = ColorAlpha(color, CombineAlphas(color.a, attr.border_alpha.current) * 255) 46 | local double_thickness = (thickness * 2) 47 | 48 | surface.SetDrawColor(color_with_alpha) 49 | surface.DrawRect(0, 0, cropped_w, thickness) 50 | surface.DrawRect(cropped_w - thickness, thickness, thickness, cropped_h - double_thickness) 51 | surface.DrawRect(0, cropped_h - thickness, cropped_w, thickness) 52 | surface.DrawRect(0, thickness, thickness, cropped_h - double_thickness) 53 | end 54 | 55 | function Element:Paint() 56 | local static_attr = self.static_attributes 57 | 58 | if static_attr.paint then 59 | local attr = self.attributes 60 | local x = 0 61 | local y = 0 62 | local w = attr.width.current 63 | local h = attr.height.current 64 | local color = self:GetColor() 65 | 66 | self:PaintRect(x, y, w, h, not static_attr.fill_color and attr.background_color.current or color) 67 | 68 | local mat = static_attr.material 69 | 70 | if mat then 71 | self:PaintTexture(mat, x, y, w, h, attr.angle and attr.angle.current or 0, color) 72 | end 73 | end 74 | end 75 | 76 | function Element:PaintOver() 77 | local static_attr = self.static_attributes 78 | 79 | if static_attr.paint then 80 | if self.attributes.border then 81 | self:PaintBorder() 82 | end 83 | end 84 | end -------------------------------------------------------------------------------- /gamemode/hud/notifications.cl.lua: -------------------------------------------------------------------------------- 1 | NotificationService = NotificationService or {} 2 | NotificationService.stickies = NotificationService.stickies or {} 3 | 4 | function NotificationService.CreateFlash(duration) 5 | local element = Element.New { 6 | duration = duration or (ANIMATION_DURATION * 2), 7 | overlay = true, 8 | layout = false, 9 | width_percent = 1, 10 | height_percent = 1, 11 | fill_color = true 12 | } 13 | 14 | element:AnimateAttribute("alpha", 0) 15 | 16 | return element 17 | end 18 | 19 | function NotificationService.Visible() 20 | return SettingsService.Get "show_hud" and SettingsService.Get "show_notifications" 21 | end 22 | 23 | function NotificationService.PushSideText(text) 24 | if NotificationService.Visible() then 25 | return HUDService.quadrant_c:Add(NotificationContainer.New(TextBar.New(text, { 26 | padding = 0, 27 | fill_color = false 28 | }))) 29 | end 30 | end 31 | 32 | function NotificationService.PushText(text) 33 | if NotificationService.Visible() then 34 | return HUDService.quadrant_b:Add(NotificationContainer.New(TextBar.New(text))) 35 | end 36 | end 37 | 38 | function NotificationService.PushAvatarText(ply, text) 39 | if NotificationService.Visible() then 40 | return HUDService.quadrant_b:Add(NotificationContainer.New(AvatarNotification.New(ply, text))) 41 | end 42 | end 43 | 44 | function NotificationService.PushCountdown(time, text, key) 45 | if NotificationService.Visible() then 46 | return HUDService.quadrant_b:Add(NotificationContainer.New(CountdownNotification.New(time, text), time - CurTime(), key)) 47 | end 48 | end 49 | 50 | function NotificationService.PushMetaText(text, key, i) 51 | if NotificationService.Visible() then 52 | local notification = NotificationContainer.New(TextBar.New(text), 0, key) 53 | 54 | if i then 55 | HUDService.quadrant_a:Add(i, notification) 56 | else 57 | HUDService.quadrant_a:Add(notification) 58 | end 59 | 60 | return notification 61 | end 62 | end 63 | 64 | local function FinishNotificationContainer(element) 65 | if Class.InstanceOf(element, NotificationContainer) then 66 | element:Finish() 67 | end 68 | end 69 | 70 | function NotificationService.FinishSticky(k) 71 | if NotificationService.stickies[k] then 72 | NotificationService.stickies[k]:Finish() 73 | end 74 | end 75 | 76 | function NotificationService.Clear(lobby, ply) 77 | if NotificationService.Visible() and not lobby or IsLocalPlayer(ply) then 78 | for _, element in pairs(HUDService.quadrant_b.children) do 79 | FinishNotificationContainer(element) 80 | end 81 | 82 | for _, element in pairs(HUDService.quadrant_c.children) do 83 | FinishNotificationContainer(element) 84 | end 85 | end 86 | end 87 | hook.Add("LocalLobbyPlayerLeave", "NotificationService.Clear", NotificationService.Clear) -------------------------------------------------------------------------------- /gamemode/hud/elements/crosshair-line.cl.lua: -------------------------------------------------------------------------------- 1 | CrosshairLine = CrosshairLine or Class.New(Element) 2 | 3 | function CrosshairLine:Init(props) 4 | local orientation = props.orientation or DIRECTION_ROW 5 | 6 | CrosshairLine.super.Init(self, { 7 | layout = false, 8 | origin_position = true, 9 | fill_color = true, 10 | border = 1, 11 | border_color = COLOR_BACKGROUND 12 | }) 13 | 14 | self.orientation = orientation 15 | 16 | if props then 17 | self:SetAttributes(props) 18 | end 19 | end 20 | 21 | function CrosshairLine:GenerateSize() 22 | if self.parent then 23 | local parent_attr = self.parent.attributes 24 | local attr = self.attributes 25 | local length = (parent_attr.width.current/2) - (parent_attr.child_margin.current/2) 26 | 27 | if self.orientation == DIRECTION_ROW then 28 | attr.width.current = length 29 | attr.height.current = CROSSHAIR_LINE_THICKNESS 30 | elseif self.orientation == DIRECTION_COLUMN then 31 | attr.width.current = CROSSHAIR_LINE_THICKNESS 32 | attr.height.current = length 33 | end 34 | end 35 | end 36 | 37 | function CrosshairLine:Layout() 38 | self.laying_out = true 39 | 40 | self:GenerateSize() 41 | self:PositionFromOrigin() 42 | self:SetPanelBounds() 43 | 44 | self.laying_out = false 45 | end 46 | 47 | CrosshairLines = CrosshairLines or Class.New(Element) 48 | 49 | function CrosshairLines:Init(size, gap) 50 | CrosshairLines.super.Init(self, { 51 | layout = false, 52 | origin_position = true, 53 | origin_justification_x = JUSTIFY_CENTER, 54 | origin_justification_y = JUSTIFY_CENTER, 55 | position_justification_x = JUSTIFY_CENTER, 56 | position_justification_y = JUSTIFY_CENTER, 57 | size = size or SettingsService.Get "crosshair_size", 58 | child_margin = gap or SettingsService.Get "crosshair_gap", 59 | 60 | CrosshairLine.New { 61 | orientation = DIRECTION_COLUMN, 62 | origin_justification_x = JUSTIFY_CENTER, 63 | position_justification_x = JUSTIFY_CENTER 64 | }, 65 | 66 | CrosshairLine.New { 67 | origin_justification_x = JUSTIFY_END, 68 | origin_justification_y = JUSTIFY_CENTER, 69 | position_justification_x = JUSTIFY_END, 70 | position_justification_y = JUSTIFY_CENTER 71 | }, 72 | 73 | CrosshairLine.New { 74 | orientation = DIRECTION_COLUMN, 75 | origin_justification_x = JUSTIFY_CENTER, 76 | origin_justification_y = JUSTIFY_END, 77 | position_justification_x = JUSTIFY_CENTER, 78 | position_justification_y = JUSTIFY_END, 79 | }, 80 | 81 | CrosshairLine.New { 82 | origin_justification_y = JUSTIFY_CENTER, 83 | position_justification_y = JUSTIFY_CENTER 84 | } 85 | }) 86 | 87 | if not size then 88 | self:AddConvarAnimator("crosshair_size", "size") 89 | end 90 | 91 | if not gap then 92 | self:AddConvarAnimator("crosshair_gap", "child_margin") 93 | end 94 | end -------------------------------------------------------------------------------- /gamemode/util/sound-isolation.sh.lua: -------------------------------------------------------------------------------- 1 | SoundIsolationService = SoundIsolationService or {} 2 | SoundIsolationService.Sounds = SoundIsolationService.Sounds or {} 3 | 4 | if CLIENT then 5 | SettingsService.New("sound_isolation", { 6 | default = false, 7 | userinfo = true, 8 | help = "Toggle weapon sounds" 9 | }) 10 | end 11 | 12 | function SoundIsolationService.GetRecipientFilter(shooter) 13 | local rf = RecipientFilter() 14 | 15 | for _, ply in pairs(player.GetAll()) do 16 | local concommand = ply:GetInfoNum ("emm_sound_isolation", 1) 17 | 18 | if ply != shooter and (concommand == 1 or (concommand == 0 and MinigameService.IsSharingLobby(ply, ent))) then 19 | rf:AddPlayer(ply) 20 | end 21 | end 22 | 23 | return rf 24 | end 25 | 26 | function SoundIsolationService.PlaySound(ent, name, volume, pitch, level, filter) 27 | local sound = CreateSound(ent, name, filter) 28 | 29 | if SoundIsolationService.Sounds[name] then 30 | SoundIsolationService.Sounds[name]:Stop() 31 | end 32 | 33 | sound:SetSoundLevel(level) 34 | sound:SetDSP(60) 35 | SoundIsolationService.Sounds[name] = sound 36 | sound:PlayEx(volume, pitch) 37 | end 38 | 39 | function SoundIsolationService.WeaponSound(snd) 40 | local ent = snd.Entity 41 | 42 | if IsValid(ent:GetOwner()) then 43 | ent = ent:GetOwner() 44 | end 45 | 46 | if SERVER and IsPlayer(ent) and snd.DSP ~= 60 then 47 | if IsValid(ent:GetActiveWeapon()) then 48 | if ((table.HasValue(MINIGAME_WEAPONS, ent:GetActiveWeapon():GetKeyValues().classname) or snd.Channel == CHAN_WEAPON) and snd.OriginalSoundName:Split("_")[1] == "Weapon") then 49 | if ent.lobby then 50 | SoundIsolationService.PlaySound(ent, snd.SoundName, snd.Volume, snd.Pitch, snd.SoundLevel, SoundIsolationService.GetRecipientFilter(ent)) 51 | end 52 | 53 | return false 54 | end 55 | end 56 | elseif CLIENT and snd.OriginalSoundName == "BaseExplosionEffect.Sound" then 57 | return false 58 | end 59 | end 60 | hook.Add( "EntityEmitSound", "SoundIsolationService.WeaponSound", SoundIsolationService.WeaponSound) 61 | 62 | function SoundIsolationService.FragExplosion(frag) 63 | if frag:GetClass() == "npc_grenade_frag" and IsValid(frag:GetOwner()) and SERVER then 64 | SoundIsolationService.PlaySound(frag, "weapons/explode" .. math.random(3, 5) .. ".wav", 1, 100, 140, SoundIsolationService.GetRecipientFilter(frag:GetOwner())) 65 | end 66 | end 67 | hook.Add( "EntityRemoved", "SoundIsolationService.FragExplosion", SoundIsolationService.FragExplosion) 68 | 69 | function SoundIsolationService.RPGExplosion(missile) 70 | if missile:GetClass() == "env_explosion" and IsValid(missile:GetOwner()) then 71 | SoundIsolationService.PlaySound(missile, "weapons/explode" .. math.random(3, 5) .. ".wav", 1, 100, 140, SoundIsolationService.GetRecipientFilter(missile:GetOwner())) 72 | end 73 | end 74 | hook.Add( "AcceptInput", "SoundIsolationService.RPGExplosion", SoundIsolationService.RPGExplosion) -------------------------------------------------------------------------------- /gamemode/element/panel.cl.lua: -------------------------------------------------------------------------------- 1 | local ElementPanel = {} 2 | 3 | function ElementPanel:Init() 4 | self.text = self:Add(vgui.Create "DLabel") 5 | self.text:SetMouseInputEnabled(false) 6 | self.text:SetText "" 7 | end 8 | 9 | function ElementPanel:SetAttribute(k, v) 10 | self.element:SetAttribute(k, v) 11 | end 12 | 13 | function ElementPanel:GetAttribute(k) 14 | return self.element:GetAttribute(k) 15 | end 16 | 17 | function ElementPanel:OnRemove() 18 | if not self.element.finishing then 19 | self.element:Finish() 20 | end 21 | end 22 | 23 | function ElementPanel:PerformLayout() 24 | self.element:Layout() 25 | end 26 | 27 | function ElementPanel:Think() 28 | self.element:Think() 29 | end 30 | 31 | function ElementPanel:Paint() 32 | if self:GetAlpha() > 0 then 33 | self.element:Paint() 34 | end 35 | end 36 | 37 | function ElementPanel:PaintOver() 38 | if self:GetAlpha() > 0 then 39 | self.element:PaintOver() 40 | end 41 | end 42 | 43 | function ElementPanel:CanBubble() 44 | return self.element.parent and self:GetAttribute "bubble_mouse" 45 | end 46 | 47 | function ElementPanel:IsCursorOutBoundsX() 48 | local out_bounds 49 | 50 | local x, _ = self:LocalCursorPos() 51 | local w = self:GetWide() 52 | 53 | if 0 > x or x > w then 54 | out_bounds = true 55 | else 56 | out_bounds = false 57 | end 58 | 59 | return out_bounds 60 | end 61 | 62 | function ElementPanel:IsCursorOutBoundsY() 63 | local out_bounds 64 | 65 | local _, y = self:LocalCursorPos() 66 | local h = self:GetTall() 67 | 68 | if 0 > y or y > h then 69 | out_bounds = true 70 | else 71 | out_bounds = false 72 | end 73 | 74 | return out_bounds 75 | end 76 | 77 | function ElementPanel:IsCursorOutBounds() 78 | return self:IsCursorOutBoundsX() or self:IsCursorOutBoundsY() 79 | end 80 | 81 | function ElementPanel:OnMousePressed(mouse) 82 | self.element:OnMousePressed(mouse) 83 | 84 | if self:CanBubble() then 85 | self.element.parent.panel:OnMousePressed(mouse) 86 | end 87 | end 88 | 89 | function ElementPanel:OnMouseReleased(mouse) 90 | self.element:OnMouseReleased(mouse) 91 | 92 | if self:CanBubble() then 93 | self.element.parent.panel:OnMouseReleased(mouse) 94 | end 95 | end 96 | 97 | function ElementPanel:OnMouseWheeled(scroll) 98 | self.element:OnMouseScrolled(scroll) 99 | end 100 | 101 | local hovered_panels = {} 102 | 103 | timer.Create("Element.HoveredPanels", 1/30, 0, function () 104 | for panel, _ in pairs(hovered_panels) do 105 | if IsValid(panel) and panel:IsCursorOutBounds() then 106 | panel:OnCursorExited() 107 | end 108 | end 109 | end) 110 | 111 | function ElementPanel:OnCursorEntered() 112 | self.element:OnMouseEntered() 113 | hovered_panels[self] = true 114 | 115 | if self:CanBubble() then 116 | self.element.parent.panel:OnCursorEntered() 117 | end 118 | end 119 | 120 | function ElementPanel:OnCursorExited() 121 | if not self:IsChildHovered() or self:IsCursorOutBounds() then 122 | self.element:OnMouseExited() 123 | hovered_panels[self] = nil 124 | end 125 | end 126 | 127 | vgui.Register("ElementPanel", ElementPanel, "EditablePanel") 128 | -------------------------------------------------------------------------------- /minigames.md: -------------------------------------------------------------------------------- 1 | # Minigames 2 | 3 | ## MinigamePrototype 4 | 5 | Prototypes are the actual classes that contain the logic and hooks for minigames. This is where you configure minigame properties and player classes. 6 | 7 | ```lua 8 | MinigamePrototype { 9 | player_classes = {}, 10 | 11 | states = { 12 | { 13 | name = "Waiting", 14 | next = "Starting" 15 | }, 16 | { 17 | name = "Starting", 18 | time = 3, 19 | next = "Playing" 20 | }, 21 | { 22 | name = "Playing", 23 | next = "Ending" 24 | }, 25 | { 26 | name = "Ending", 27 | time = 3, 28 | next = "Starting" 29 | } 30 | }, 31 | 32 | hooks = { 33 | PlayerLeave = { 34 | RequirePlayers = function () ... end -- Sets the state to Waiting if there's not enough players 35 | } 36 | }, 37 | 38 | state_hooks = { 39 | Waiting = { 40 | PlayerLeave = { 41 | RequirePlayers = function () ... end -- Progresses the state if there's enough players 42 | } 43 | } 44 | }, 45 | 46 | default_state = "Waiting", 47 | 48 | require_players = 2 49 | } 50 | ``` 51 | 52 | ### MinigameState 53 | 54 | Prototypes have different states like `Waiting` (not enough players), `Starting` (round start countdown), and `Playing` (actual minigame starts). You can add different states and change the default ones. Each state has a next state and an optional duration until the next state. 55 | 56 | ### PlayerClass 57 | 58 | PlayerClasses are tables of properties like `color` and `can_walljump` that will override a player's default properties. 59 | 60 | ```lua 61 | PlayerClass { 62 | name = "Hunted", 63 | color = COLOR_ORANGE, 64 | can_tag = {Hunter = true} 65 | } 66 | ``` 67 | 68 | ### Hooks 69 | 70 | Minigame hooks work like Garry's Mod hooks. Additionally you can add hooks that only work in a specific state. 71 | 72 | - `PlayerJoin(player)/Playerleave` 73 | - `StartState{state_name}(state)` 74 | - `Tag(player taggable, player tagger)` (server-side only) 75 | - `PlayerSpawn(player)` 76 | - `PlayerProperties(player)` 77 | - `PrePlayerDeath(player, entity attacker)` 78 | - `PlayerDeath(player, entity inflictor, entity attacker)` 79 | - `PostPlayerDeath(player)` (client-side only) 80 | 81 | ### Methods 82 | - `SetAdjustableSettings(table modifiable_variables)` sets what properties can be edited by lobby hosts. 83 | - `AddPlayerClass(table player_class)` 84 | - `AddHook(string hook_name, string identifier, function)/RemoveHook` 85 | - `AddStateHook(string state_name, string hook_name, string identifier, function)/RemoveStateHook` 86 | 87 | ## MinigameLobby 88 | 89 | Lobbies are instantiated prototypes. These hold properties like the current host, players, and other meta-information. 90 | 91 | ```lua 92 | MinigameLobby { 93 | prototype 94 | host = player 95 | players = {} 96 | } 97 | ``` 98 | 99 | ### Methods 100 | - `__index` first looks in the prototype and then the MinigameLobby metatable. 101 | - `Init/Finish` 102 | - `GetSanitized` returns a JSON friendly version of the lobby for the JSUI. 103 | - `IsLocal` (client-side only) 104 | - `SetHost(player)` 105 | - `AddPlayer(player)/RemovePlayer` 106 | - `SetState(table state)` -------------------------------------------------------------------------------- /gamemode/hud/elements/indicator.cl.lua: -------------------------------------------------------------------------------- 1 | Indicator = Indicator or Class.New(Element) 2 | 3 | local indicator_material = Material("emm2/shapes/arrow.png", "noclamp smooth") 4 | 5 | function Indicator:Init(ply_or_vec) 6 | Indicator.super.Init(self, { 7 | layout = false, 8 | width_percent = 1, 9 | height_percent = 1, 10 | inherit_color = false, 11 | alpha = 0 12 | }) 13 | 14 | if isentity(ply_or_vec) then 15 | ply_or_vec.indicator = self 16 | self.player = ply_or_vec 17 | self.position = ply_or_vec:WorldSpaceCenter() 18 | elseif isvector(ply_or_vec) then 19 | self.position = ply_or_vec 20 | end 21 | 22 | self.distance = LocalPlayer():EyePos():Distance(self.position) 23 | 24 | local x, y = IndicatorService.IndicatorPosition(self) 25 | 26 | self.x = x 27 | self.y = y 28 | 29 | local function OffScreen() 30 | return 0 > self.x or self.x > ScrW() or 0 > self.y or self.y > ScrH() 31 | end 32 | 33 | self.world_alpha = AnimatableValue.New(not OffScreen() and 255 or 0) 34 | 35 | self:SetAttribute("color", function () 36 | return GetSmoothPlayerColor(self.player) 37 | end) 38 | 39 | self.off_screen = AnimatableValue.New(OffScreen(), { 40 | generate = OffScreen, 41 | 42 | callback = function (anim_v) 43 | if anim_v.current then 44 | self.world_alpha:AnimateTo(0) 45 | self.peripheral:AnimateAttribute("alpha", 255) 46 | else 47 | self.world_alpha:AnimateTo(255) 48 | self.peripheral:AnimateAttribute("alpha", 0) 49 | end 50 | end 51 | }) 52 | 53 | self.peripheral = self:Add(Element.New { 54 | layout = false, 55 | width = INDICATOR_PERIPHERAL_SIZE, 56 | height = INDICATOR_PERIPHERAL_SIZE, 57 | material = indicator_material, 58 | angle = 0, 59 | alpha = 0 60 | }) 61 | end 62 | 63 | function Indicator:Think() 64 | Indicator.super.Think(self) 65 | 66 | if self.off_screen.current then 67 | local attr = self.peripheral.attributes 68 | 69 | local scr_w = ScrW() 70 | local scr_h = ScrH() 71 | local half_scr_w = scr_w/2 72 | local half_scr_h = scr_h/2 73 | local half_w = attr.width.current/2 74 | local half_h = attr.height.current/2 75 | local periph_radius = half_scr_h - half_h 76 | 77 | local x = self.x 78 | local y = self.y 79 | 80 | local rad_ang = math.atan2(y + (half_h/2) - half_scr_h, x + (half_w/2) - half_scr_w) 81 | local periph_x = (math.cos(rad_ang) * periph_radius) + half_scr_w 82 | local periph_y = (math.sin(rad_ang) * periph_radius) + half_scr_h 83 | 84 | attr.x.current = periph_x - half_w 85 | attr.y.current = periph_y - half_h 86 | attr.angle.current = -math.deg(rad_ang) + 90 87 | 88 | self.peripheral:Layout() 89 | end 90 | end 91 | 92 | function Indicator:AnimateFinish() 93 | self:AnimateAttribute("alpha", 0, { 94 | callback = function () 95 | local ent = self.player 96 | 97 | if IsValid(ent) then 98 | if self == ent.indicator then 99 | ent.indicator = nil 100 | end 101 | end 102 | 103 | self.world_alpha:Finish() 104 | self.off_screen:Finish() 105 | Indicator.super.Finish(self) 106 | end 107 | }) 108 | end 109 | 110 | function Indicator:Finish() 111 | self:AnimateFinish() 112 | end -------------------------------------------------------------------------------- /gamemode/ui/ui.cl.lua: -------------------------------------------------------------------------------- 1 | UIService = UIService or {} 2 | UIService.menus = UIService.menus or {} 3 | 4 | function UIService.Register(name, service, props) 5 | table.Merge(service, props) 6 | 7 | UIService.menus[name] = {} 8 | UIService.menus[name].service = service 9 | UIService.menus[name].properties = props 10 | 11 | local function MenuHook() 12 | local GM = GM or GAMEMODE 13 | 14 | if props.toggle_hook then 15 | hook.Add(props.toggle_hook, name, function () 16 | if service.active then 17 | service.Close() 18 | else 19 | service.Open() 20 | end 21 | end) 22 | end 23 | 24 | if props.open_hook then 25 | GM[props.open_hook] = function () 26 | UIService.Open(name) 27 | 28 | return true 29 | end 30 | end 31 | 32 | if props.close_hook then 33 | GM[props.close_hook] = function () 34 | UIService.Close(name) 35 | 36 | return true 37 | end 38 | end 39 | end 40 | 41 | hook.Add("Initialize", "UIService.Add"..name.."MenuHooks", MenuHook) 42 | hook.Add("OnReloaded", "UIService.Reload"..name.."MenuHooks", MenuHook) 43 | end 44 | 45 | function UIService.Open(name) 46 | RememberCursorPosition() 47 | RestoreCursorPosition() 48 | 49 | local menu = UIService.menus[name] 50 | 51 | if menu.focused then 52 | UIService.UnFocus(menu) 53 | end 54 | 55 | if menu.active then 56 | 57 | else 58 | menu.active = true 59 | menu.service.Init() 60 | 61 | local container = menu.service.container 62 | 63 | container.panel:SetMouseInputEnabled(true) 64 | container.panel:MoveToFront() 65 | container:AnimateAttribute("alpha", 255) 66 | 67 | gui.EnableScreenClicker(true) 68 | end 69 | end 70 | 71 | function UIService.Close(name) 72 | RememberCursorPosition() 73 | 74 | local menu = UIService.menus[name] 75 | 76 | if not menu.focused then 77 | menu.active = false 78 | 79 | local container = menu.service.container 80 | 81 | container.panel:SetMouseInputEnabled(false) 82 | container.panel:MoveToBack() 83 | 84 | container:AnimateAttribute("alpha", 0, {callback = function () 85 | container:Finish() 86 | end}) 87 | 88 | gui.EnableScreenClicker(false) 89 | end 90 | end 91 | 92 | function UIService.Active(name) 93 | return UIService.menus[name].active 94 | end 95 | 96 | function UIService.Focus(menu) 97 | menu.focused = true 98 | menu.service.container.panel:MakePopup() 99 | end 100 | 101 | function UIService.UnFocus(menu) 102 | menu.focused = false 103 | menu.service.container.panel:SetKeyboardInputEnabled(false) 104 | end 105 | 106 | function UIService.FocusTextEntry(element) 107 | for _, menu in pairs(UIService.menus) do 108 | if element:HasParent(menu.service.container) then 109 | UIService.Focus(menu) 110 | 111 | break 112 | end 113 | end 114 | end 115 | hook.Add("TextEntryFocus", "UIService.FocusTextEntry", UIService.FocusTextEntry) 116 | 117 | function UIService.UnFocusTextEntry(element) 118 | for _, menu in pairs(UIService.menus) do 119 | if element:HasParent(menu.service.container) then 120 | UIService.UnFocus(menu) 121 | 122 | break 123 | end 124 | end 125 | end 126 | hook.Add("TextEntryUnFocus", "UIService.UnFocusTextEntry", UIService.UnFocusTextEntry) -------------------------------------------------------------------------------- /gamemode/lobby-ui/elements/lobby-bar.cl.lua: -------------------------------------------------------------------------------- 1 | LobbyBar = LobbyBar or Class.New(Element) 2 | 3 | function LobbyBar:Init(lobby) 4 | LobbyBar.super.Init(self, { 5 | layout_justification_y = JUSTIFY_CENTER, 6 | width_percent = 1, 7 | height = 52, 8 | padding_x = 32, 9 | inherit_color = false, 10 | border = LINE_THICKNESS, 11 | border_color = lobby.prototype.color, 12 | border_alpha = 0, 13 | cursor = "hand", 14 | bubble_mouse = false, 15 | 16 | hover = { 17 | color = lobby.prototype.color, 18 | border_alpha = 255 19 | }, 20 | 21 | press = { 22 | background_color = COLOR_GRAY_DARK, 23 | color = COLOR_BACKGROUND, 24 | border_color = COLOR_BACKGROUND 25 | }, 26 | 27 | pulse = Element.New { 28 | overlay = true, 29 | layout = false, 30 | width_percent = 1, 31 | height_percent = 1, 32 | fill_color = true, 33 | inherit_color = false, 34 | 35 | color = function () 36 | return ColorAlpha(COLOR_GRAY_LIGHT, ((math.sin(CurTime() * 4) + 1)/2) * 255) 37 | end, 38 | 39 | alpha = lobby:IsLocal() and 255 or 0 40 | } 41 | }) 42 | 43 | self.lobby = lobby 44 | lobby.bar_element = self 45 | 46 | self.type_container = self:Add(Element.New { 47 | layout_justification_y = JUSTIFY_CENTER, 48 | width = 64, 49 | height_percent = 1 50 | }) 51 | 52 | self.type_container:Add(Element.New { 53 | width = BUTTON_ICON_SIZE, 54 | height = BUTTON_ICON_SIZE, 55 | crop = 0.1, 56 | material = PNGMaterial("emm2/minigames/"..lobby.prototype.key..".png") 57 | }) 58 | 59 | self.host = self:Add(Element.New { 60 | fit = true, 61 | font = "Info", 62 | text_justification = 4, 63 | text = ProperPlayerName(lobby.host) 64 | }) 65 | 66 | self.players = self:Add(Element.New { 67 | self_justification = JUSTIFY_END, 68 | width = 25, 69 | height = 24, 70 | crop_right = 0.01, 71 | crop_bottom = 0.01, 72 | font = "NumberInfo", 73 | text_justification = 5, 74 | text = #lobby.players, 75 | border = LINE_THICKNESS 76 | }) 77 | 78 | self:Add(Element.New { 79 | layout = false, 80 | origin_position = true, 81 | origin_justification_x = JUSTIFY_CENTER, 82 | origin_justification_y = JUSTIFY_END, 83 | position_justification_x = JUSTIFY_CENTER, 84 | position_justification_y = JUSTIFY_END, 85 | width_percent = 1, 86 | height = LINE_THICKNESS, 87 | fill_color = true, 88 | 89 | alpha = function () 90 | return self.last and 0 or 255 91 | end 92 | }) 93 | end 94 | 95 | function LobbyBar:AnimateStart() 96 | self:SetAttribute("crop_bottom", 1) 97 | self:AnimateAttribute("crop_bottom", 0) 98 | self:Add(NotificationService.CreateFlash()) 99 | end 100 | 101 | function LobbyBar:AnimateFinish() 102 | self:AnimateAttribute("crop_bottom", 1, { 103 | duration = ANIMATION_DURATION * 4, 104 | 105 | callback = function () 106 | LobbyBar.super.Finish(self) 107 | end 108 | }) 109 | 110 | self:AnimateAttribute("background_color", COLOR_BACKGROUND_LIGHT) 111 | end 112 | 113 | function LobbyBar:Finish() 114 | if not self.finishing then 115 | self.finishing = true 116 | 117 | if self == self.lobby.bar_element then 118 | self.lobby.bar_element = nil 119 | end 120 | 121 | self.lobby = nil 122 | self:AnimateFinish() 123 | end 124 | end 125 | 126 | function LobbyBar:OnMousePressed(mouse) 127 | LobbyBar.super.OnMousePressed(self, mouse) 128 | LobbyUIService.SelectLobby(self.lobby) 129 | end 130 | 131 | function LobbyBar:OnMouseExited() 132 | if self.lobby and not self.lobby.card_element then 133 | self:RevertState() 134 | end 135 | end -------------------------------------------------------------------------------- /gamemode/ui/elements/number-input.cl.lua: -------------------------------------------------------------------------------- 1 | NumberInput = NumberInput or Class.New(Element) 2 | 3 | local NumberInputPanel = {} 4 | 5 | function NumberInputPanel:Init() 6 | self:SetUpdateOnType(true) 7 | end 8 | 9 | function NumberInputPanel:Paint(w, h) 10 | local attr = self.element.attributes 11 | local color = attr.text_color and attr.text_color.current or self.element:GetColor() 12 | 13 | self:DrawTextEntryText(color, COLOR_GRAY_LIGHTER, color) 14 | end 15 | 16 | function NumberInputPanel:AllowInput(string) 17 | local allowed 18 | 19 | local is_num = string.find(string, "[%d%.-]") 20 | 21 | if is_num then 22 | allowed = true 23 | else 24 | allowed = false 25 | end 26 | 27 | return not allowed 28 | end 29 | 30 | function NumberInputPanel:OnValueChange(v) 31 | self.element:OnValueChanged(v) 32 | end 33 | 34 | function NumberInputPanel:OnCursorEntered() 35 | self.element.panel:OnCursorEntered() 36 | end 37 | 38 | function NumberInputPanel:OnCursorExited() 39 | self.element.panel:OnCursorExited() 40 | end 41 | 42 | function NumberInputPanel:OnMousePressed(mouse) 43 | self.element.panel:OnMousePressed(mouse) 44 | end 45 | 46 | function NumberInputPanel:OnLoseFocus() 47 | self.element:OnUnFocus() 48 | end 49 | 50 | vgui.Register("NumberInputPanel", NumberInputPanel, "DTextEntry") 51 | 52 | local offset = 2 53 | 54 | function NumberInput:Init(text, props) 55 | NumberInput.super.Init(self, { 56 | width_percent = 1, 57 | height_percent = 1, 58 | padding_left = 2, 59 | background_color = COLOR_GRAY_DARK, 60 | cursor = "beam", 61 | font = "NumberInfo", 62 | border = 2, 63 | border_alpha = 0, 64 | 65 | disabled = { 66 | background_color = COLOR_BLACK_CLEAR, 67 | border = 1, 68 | border_color = COLOR_GRAY_DARK, 69 | border_alpha = 255 70 | }, 71 | 72 | hover = { 73 | border_alpha = 255 74 | }, 75 | 76 | text_line = TextInput.CreateTextLine() 77 | }) 78 | 79 | self.value = tostring(text) 80 | self.panel.text = self.panel:Add(vgui.Create "NumberInputPanel") 81 | TextInput.SetupPanel(self, text) 82 | 83 | if props then 84 | self:SetAttributes(props) 85 | self.read_only = props.read_only 86 | self.on_change = props.on_change 87 | self.on_click = props.on_click 88 | 89 | if props.read_only then 90 | self:Disable() 91 | end 92 | end 93 | end 94 | 95 | function NumberInput:Disable() 96 | TextInput.Disable(self) 97 | end 98 | 99 | function NumberInput:Enable() 100 | TextInput.Enable(self) 101 | end 102 | 103 | function NumberInput:OnValueChanged(v, no_callback) 104 | self.value = v 105 | 106 | if not no_callback and self.on_change then 107 | self.on_change(self, v) 108 | end 109 | end 110 | 111 | function NumberInput:SetValue(v, no_callback) 112 | self.panel.text:SetText(v) 113 | self.panel.text:OnValueChange(v, no_callback) 114 | end 115 | 116 | function NumberInput:OnMousePressed(mouse) 117 | NumberInput.super.OnMousePressed(self, mouse) 118 | 119 | if self.on_click then 120 | self.on_click(self, mouse) 121 | end 122 | 123 | self:OnFocus(self) 124 | end 125 | 126 | function NumberInput:OnMouseEntered() 127 | TextInput.OnMouseEntered(self) 128 | end 129 | 130 | function NumberInput:OnMouseExited() 131 | TextInput.OnMouseExited(self) 132 | end 133 | 134 | function NumberInput:OnFocus() 135 | self.panel.text:RequestFocus() 136 | hook.Run("TextEntryFocus", self) 137 | self.text_line:AnimateAttribute("alpha", 255) 138 | end 139 | 140 | function NumberInput:OnUnFocus() 141 | hook.Run("TextEntryUnFocus", self) 142 | self.text_line:AnimateAttribute("alpha", 0) 143 | end -------------------------------------------------------------------------------- /gamemode/hud/elements/notifications.cl.lua: -------------------------------------------------------------------------------- 1 | NotificationContainer = NotificationContainer or Class.New(Element) 2 | 3 | function NotificationContainer:Init(children, duration, key) 4 | duration = duration or 3 5 | 6 | if duration == 0 then 7 | duration = nil 8 | end 9 | 10 | NotificationContainer.super.Init(self, { 11 | duration = duration, 12 | fit = true 13 | }) 14 | 15 | if key then 16 | self.key = key 17 | 18 | local old_notif = NotificationService.stickies[key] 19 | 20 | if old_notif then 21 | old_notif:Finish() 22 | end 23 | 24 | NotificationService.stickies[key] = self 25 | end 26 | 27 | if Class.InstanceOf(children, Element) then 28 | self:Add(children) 29 | else 30 | for _, child in pairs(children) do 31 | self:Add(child) 32 | end 33 | end 34 | 35 | self:Add(NotificationService.CreateFlash()) 36 | self:AnimateStart() 37 | end 38 | 39 | function NotificationContainer:AnimateStart() 40 | self:SetAttributes { 41 | crop_bottom = 1, 42 | layout_crop_y = 1 43 | } 44 | 45 | self:AnimateAttribute("crop_bottom", 0, SAFE_FRAME) 46 | self:AnimateAttribute("layout_crop_y", 0) 47 | end 48 | 49 | function NotificationContainer:AnimateFinish() 50 | self:AnimateAttribute("crop_bottom", 1, { 51 | duration = ANIMATION_DURATION * 10, 52 | 53 | callback = function () 54 | if self.key then 55 | local curr_notif = NotificationService.stickies[key] 56 | 57 | if curr_notif == self then 58 | NotificationService.stickies[self.key] = nil 59 | end 60 | end 61 | 62 | NotificationContainer.super.Finish(self) 63 | end 64 | }) 65 | 66 | self:AnimateAttribute("alpha", 0, ANIMATION_DURATION * 10) 67 | end 68 | 69 | function NotificationContainer:Finish() 70 | self:AnimateFinish() 71 | end 72 | 73 | AvatarNotification = AvatarNotification or Class.New(Element) 74 | 75 | function AvatarNotification:Init(ply, text) 76 | AvatarNotification.super.Init(self, { 77 | width = BAR_WIDTH, 78 | fit_y = true 79 | }) 80 | 81 | self:Add(TextBar.New(text, { 82 | width = BAR_WIDTH, 83 | fit_x = false, 84 | fit_y = true 85 | })) 86 | 87 | self:Add(AvatarBar.New(ply)) 88 | end 89 | 90 | CountdownNotification = CountdownNotification or Class.New(Element) 91 | 92 | function CountdownNotification:Init(end_time, text) 93 | CountdownNotification.super.Init(self, { 94 | width = BAR_WIDTH, 95 | fit_y = true 96 | }) 97 | 98 | self.end_time = end_time 99 | 100 | if text and #text > 0 then 101 | self:Add(TextBar.New(text, { 102 | width = BAR_WIDTH, 103 | fit_x = false, 104 | fit_y = true 105 | })) 106 | end 107 | 108 | self.time = self:Add(TextBar.New(nil, { 109 | width = BAR_WIDTH, 110 | padding_y = MARGIN * 2, 111 | fit_x = false, 112 | fit_y = true, 113 | fill_color = true, 114 | font = "HUDMeterValue" 115 | })) 116 | 117 | if text then 118 | self.time:SetAttributes { 119 | background_color = COLOR_GRAY, 120 | fill_color = false, 121 | text_color = false 122 | } 123 | end 124 | end 125 | 126 | function CountdownNotification:Think() 127 | CountdownNotification.super.Think(self) 128 | 129 | local sec = math.ceil(math.max(self.end_time - CurTime(), 0)) 130 | 131 | local text 132 | 133 | if sec >= 60 then 134 | text = string.Trim(string.FormattedTime(sec, "%2i:%02i")) 135 | elseif sec > 0 then 136 | text = sec 137 | else 138 | text = "" 139 | end 140 | 141 | self.time:SetText(text) 142 | 143 | if 5 > sec and sec ~= self.last_seconds then 144 | self.time:Add(NotificationService.CreateFlash()) 145 | end 146 | 147 | self.last_seconds = sec 148 | end 149 | -------------------------------------------------------------------------------- /gamemode/hud/variables.cl.lua: -------------------------------------------------------------------------------- 1 | HUD_LINE_THICKNESS = 2 2 | 3 | HUD_METER_SIZE = 3/4 4 | HUD_ICON_SIZE = 64 5 | 6 | HUD_MIDDLE_DISTANCE = 0 7 | 8 | HUD_SIDE_ANGLE = 35 9 | HUD_SIDE_DISTANCE = 0 10 | 11 | HUD_SPEED_METER_DIVIDER = 5000 12 | 13 | CROSSHAIR_METER_ARC_ANGLE = 90 14 | CROSSHAIR_LINE_THICKNESS = 3 15 | 16 | local function ReloadHUD() 17 | HUDService.Reload() 18 | IndicatorService.Reload(true) 19 | end 20 | 21 | SettingsService.New("show_hud", { 22 | default = true, 23 | help = "Show HUD", 24 | callback = ReloadHUD 25 | }) 26 | 27 | SettingsService.New("show_hud_meters", { 28 | default = true, 29 | help = "Show HUD meters", 30 | callback = ReloadHUD 31 | }) 32 | 33 | SettingsService.New("show_crosshair", { 34 | default = true, 35 | help = "Show crosshair", 36 | callback = ReloadHUD 37 | }) 38 | 39 | SettingsService.New("show_crosshair_meters", { 40 | help = "Show crosshair meters", 41 | callback = ReloadHUD 42 | }) 43 | 44 | SettingsService.New("show_indicators", { 45 | default = true, 46 | help = "Show indicators", 47 | 48 | callback = function () 49 | IndicatorService.Reload(true) 50 | end 51 | }) 52 | 53 | SettingsService.New("show_keys", { 54 | default = false, 55 | help = "Show key echoes", 56 | callback = ReloadHUD 57 | }) 58 | 59 | SettingsService.New("show_notifications", { 60 | default = true, 61 | help = "Show notifications", 62 | callback = ReloadHUD 63 | }) 64 | 65 | SettingsService.New("hud_padding_x", { 66 | type = "number", 67 | default = 16, 68 | round = true, 69 | snap = 8, 70 | min = 0, 71 | max = 256, 72 | help = "HUD horizontal padding (pixels)" 73 | }) 74 | 75 | SettingsService.New("hud_padding_y", { 76 | type = "number", 77 | default = 16, 78 | round = true, 79 | snap = 8, 80 | min = 0, 81 | max = 256, 82 | help = "HUD vertical padding (pixels)" 83 | }) 84 | 85 | SettingsService.New("hud_angle", { 86 | type = "number", 87 | default = 5, 88 | min = 0, 89 | max = 35, 90 | help = "HUD side angles (degrees)", 91 | callback = ReloadHUD 92 | }) 93 | 94 | SettingsService.New("crosshair_size", { 95 | type = "number", 96 | default = 24, 97 | round = true, 98 | min = 10, 99 | max = 512, 100 | help = "Crosshair size (pixels)" 101 | }) 102 | 103 | SettingsService.New("crosshair_gap", { 104 | type = "number", 105 | default = 3, 106 | round = true, 107 | min = 0, 108 | max = 64, 109 | help = "Crosshair gap (pixels)" 110 | }) 111 | 112 | SettingsService.New("crosshair_meter_radius", { 113 | type = "number", 114 | default = 40, 115 | round = true, 116 | min = 32, 117 | max = 512, 118 | help = "Crosshair meter radius (pixels)" 119 | }) 120 | 121 | SettingsService.New("crosshair_meter_arc_length", { 122 | type = "number", 123 | default = 40, 124 | round = true, 125 | min = 0, 126 | max = 90, 127 | help = "Crosshair meter arc length (degrees)" 128 | }) 129 | 130 | surface.CreateFont("HUDMeterValue", { 131 | font = "Roboto Mono", 132 | size = 34 133 | }) 134 | 135 | surface.CreateFont("HUDMeterValueSmall", { 136 | font = "Roboto Mono", 137 | size = 24 138 | }) 139 | 140 | surface.CreateFont("CrosshairMeterValue", { 141 | font = "Roboto Mono", 142 | size = 16 143 | }) 144 | 145 | surface.CreateFont("CrosshairMeterValueSmall", { 146 | font = "Roboto Mono", 147 | size = 12 148 | }) 149 | 150 | surface.CreateFont("KeyEcho", { 151 | font = "Roboto", 152 | size = 42 153 | }) 154 | 155 | surface.CreateFont("KeyEchoSmall", { 156 | font = "Roboto Mono", 157 | size = 14, 158 | weight = 900 159 | }) 160 | 161 | surface.CreateFont("Nametag", { 162 | font = "Roboto Mono Bold Italic", 163 | size = 16 164 | }) 165 | -------------------------------------------------------------------------------- /gamemode/minigame/minigame.sh.lua: -------------------------------------------------------------------------------- 1 | MinigameService = MinigameService or {} 2 | MinigameService.prototypes = MinigameService.prototypes or {} 3 | MinigameService.lobbies = MinigameService.lobbies or {} 4 | 5 | MINIGAME_WEAPONS = { 6 | "weapon_crowbar", 7 | "weapon_physcannon", 8 | "weapon_pistol", 9 | "weapon_357", 10 | "weapon_shotgun", 11 | "weapon_ar2", 12 | "weapon_crossbow", 13 | "weapon_rpg", 14 | "weapon_frag", 15 | -- "sfw_jotunn", 16 | -- "sfw_thunderbolt", 17 | -- "sfw_cryon", 18 | -- "sfw_blizzard", 19 | -- "sfw_trace", 20 | -- "sfw_hellfire", 21 | -- "sfw_neutrino", 22 | -- "sfw_aquamarine", 23 | -- "sfw_draco", 24 | -- "sfw_pandemic", 25 | -- "sfw_saphyre", 26 | -- "sfw_hwave", 27 | -- "sfw_pyre", 28 | -- "sfw_seraphim", 29 | -- "sfw_ember", 30 | -- "sfw_phoenix", 31 | -- "sfw_alchemy", 32 | -- "sfw_vectra", 33 | -- "sfw_supra", 34 | -- "sfw_prisma", 35 | -- "sfw_astra", 36 | -- "sfw_zeala", 37 | -- "sfw_storm", 38 | -- "sfw_fallingstar", 39 | -- "sfw_vapor", 40 | -- "sfw_lapis", 41 | -- "sfw_pulsar", 42 | -- "sfw_stinger", 43 | -- "sfw_hornet" 44 | } 45 | 46 | function MinigameService.Prototype(id) 47 | for _, proto in pairs(MinigameService.prototypes) do 48 | if id == proto.id then 49 | return proto 50 | end 51 | end 52 | end 53 | 54 | function MinigameService.RegisterPrototype(proto) 55 | if MinigameService.prototypes[proto.key] then 56 | proto.id = MinigameService.prototypes[proto.key].id 57 | table.Empty(MinigameService.prototypes[proto.key]) 58 | table.Merge(MinigameService.prototypes[proto.key], proto) 59 | else 60 | proto.id = table.Count(MinigameService.prototypes) + 1 61 | MinigameService.prototypes[proto.key] = proto 62 | end 63 | end 64 | 65 | function MinigameService.CallHook(lobby, hk_name, ...) 66 | if lobby[hk_name] then 67 | lobby[hk_name](lobby, ...) 68 | end 69 | 70 | MinigameService.CallHookWithoutMethod(lobby, hk_name, ...) 71 | end 72 | 73 | function MinigameService.CallHookWithoutMethod(lobby, hk_name, ...) 74 | lobby.hooks[hk_name] = lobby.hooks[hk_name] or {} 75 | 76 | for _, hk in pairs(lobby.hooks[hk_name]) do 77 | hk(lobby, ...) 78 | end 79 | 80 | if lobby.state and lobby.state_hooks[lobby.state.key] and lobby.state_hooks[lobby.state.key][hk_name] then 81 | for _, hk in pairs(lobby.state_hooks[lobby.state.key][hk_name]) do 82 | hk(lobby, ...) 83 | end 84 | end 85 | end 86 | 87 | -- # Init 88 | 89 | local MINIGAME_PROTOTYPES_DIRECTORY = "minigame_prototypes/" 90 | local minigame_prototype_files, minigame_prototype_dirs = file.Find(gamemode_lua_directory..MINIGAME_PROTOTYPES_DIRECTORY.."*", "LUA") 91 | local minigame_fenv_metatable = {__index = _G} 92 | 93 | function MinigameService.LoadPrototype(path) 94 | local proto_fenv = {} 95 | 96 | proto_fenv.MINIGAME = MinigamePrototype.New() 97 | setmetatable(proto_fenv, minigame_fenv_metatable) 98 | 99 | setfenv(0, proto_fenv) 100 | EMM.Include(path) 101 | setfenv(0, _G) 102 | 103 | MinigameService.RegisterPrototype(proto_fenv.MINIGAME) 104 | end 105 | 106 | function MinigameService.LoadPrototypes() 107 | for _, proto in pairs(minigame_prototype_files) do 108 | MinigameService.LoadPrototype(MINIGAME_PROTOTYPES_DIRECTORY..proto) 109 | end 110 | 111 | for _, proto in pairs(minigame_prototype_dirs) do 112 | MinigameService.LoadPrototype(MINIGAME_PROTOTYPES_DIRECTORY..proto.."/init") 113 | end 114 | 115 | hook.Run "LoadMinigamePrototypes" 116 | end 117 | hook.Add("Initialize", "MinigameService.LoadPrototypes", MinigameService.LoadPrototypes) 118 | hook.Add("OnReloaded", "MinigameService.ReloadPrototypes", MinigameService.LoadPrototypes) 119 | -------------------------------------------------------------------------------- /gamemode/settings/settings.cl.lua: -------------------------------------------------------------------------------- 1 | SettingsService = SettingsService or {} 2 | 3 | SettingsService.convars = SettingsService.convars or {} 4 | SettingsService.ordered_convars = {} 5 | SettingsService.values = {} 6 | SettingsService.hooks = SettingsService.hooks or {} 7 | 8 | local gamemode_prefix = engine.ActiveGamemode().."_" 9 | 10 | function SettingsService.New(name, props) 11 | if isbool(props.default) then 12 | default = props.default and 1 or 0 13 | else 14 | default = Default(props.default, 0) 15 | end 16 | 17 | if not SettingsService.convars[name] then 18 | CreateClientConVar(gamemode_prefix..name, default, true, props.userinfo, props.help) 19 | cvars.AddChangeCallback(gamemode_prefix..name, SettingsService.OnConvarChanged) 20 | end 21 | 22 | SettingsService.convars[name] = props 23 | table.insert(SettingsService.ordered_convars, name) 24 | end 25 | 26 | function SettingsService.AddHook(name, id, callback) 27 | SettingsService.hooks[name] = SettingsService.hooks[name] or {} 28 | SettingsService.hooks[name][id] = callback 29 | end 30 | 31 | function SettingsService.RemoveHook(name, id) 32 | if SettingsService.hooks[name] then 33 | SettingsService.hooks[name][id] = nil 34 | end 35 | end 36 | 37 | function SettingsService.ValidateNumber(name, v) 38 | local convar_props = SettingsService.convars[name] 39 | 40 | if convar_props.round then 41 | v = math.Round(v, isnumber(convar_props.round) and convar_props.round) 42 | end 43 | 44 | if convar_props.snap then 45 | v = Snap(v, convar_props.snap) 46 | end 47 | 48 | if convar_props.min then 49 | v = math.max(v, convar_props.min) 50 | end 51 | 52 | if convar_props.max then 53 | v = math.min(v, convar_props.max) 54 | end 55 | 56 | return v 57 | end 58 | 59 | function SettingsService.GetValidated(name) 60 | local v 61 | local convar_props = SettingsService.convars[name] 62 | local convar = GetConVar(gamemode_prefix..name) 63 | 64 | if convar_props.type == "string" then 65 | v = convar:GetString() or props.default or "" 66 | elseif convar_props.type == "number" then 67 | v = SettingsService.ValidateNumber(name, convar:GetFloat() or props.default or 0) 68 | else 69 | v = Default(convar:GetBool(), convar_props.default, false) 70 | end 71 | 72 | return v 73 | end 74 | 75 | function SettingsService.Get(name) 76 | local v 77 | 78 | if SettingsService.values[name] == nil then 79 | v = SettingsService.GetValidated(name) 80 | SettingsService.values[name] = v 81 | else 82 | v = SettingsService.values[name] 83 | end 84 | 85 | return v 86 | end 87 | 88 | function SettingsService.Set(name, v) 89 | local convar_props = SettingsService.convars[name] 90 | local convar = GetConVar(gamemode_prefix..name) 91 | 92 | if convar_props.type == "string" then 93 | local str = v or "" 94 | 95 | convar:SetString(str) 96 | elseif convar_props.type == "number" then 97 | local n = tonumber(v) 98 | 99 | if n == "" or Nily(n) then 100 | n = 0 101 | end 102 | 103 | convar:SetFloat(SettingsService.ValidateNumber(name, n)) 104 | else 105 | convar:SetBool(v) 106 | end 107 | end 108 | 109 | function SettingsService.OnConvarChanged(name) 110 | local unprefixed_name = string.sub(name, #gamemode_prefix + 1) 111 | local v = SettingsService.GetValidated(unprefixed_name) 112 | local callback = SettingsService.convars[unprefixed_name].callback 113 | 114 | SettingsService.values[unprefixed_name] = v 115 | 116 | if callback then 117 | callback(v) 118 | end 119 | 120 | if SettingsService.hooks[unprefixed_name] then 121 | for _, hk in pairs(SettingsService.hooks[unprefixed_name]) do 122 | hk(v) 123 | end 124 | end 125 | end -------------------------------------------------------------------------------- /gamemode/util/spectate.sv.lua: -------------------------------------------------------------------------------- 1 | SpectateService = SpectateService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function SpectateService.InitPlayerProperties(ply) 7 | ply.can_spectate = true 8 | ply.spectate_obs_mode = OBS_MODE_IN_EYE 9 | ply.spectate_timeout = 0 10 | ply.spectators = ply.spectators or {} 11 | end 12 | hook.Add( 13 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 14 | "SpectateService.InitPlayerProperties", 15 | SpectateService.InitPlayerProperties 16 | ) 17 | 18 | 19 | -- # Util 20 | 21 | function SpectateService.FindPlayerByName(name) 22 | for _, v in pairs(player.GetAll()) do 23 | if string.find(string.lower(" "..v:Nick()), name:lower()) then 24 | return v 25 | end 26 | end 27 | end 28 | 29 | util.AddNetworkString "SpectateKeys" 30 | 31 | function SpectateService.SendSpectateKeys(buttons, players) 32 | net.Start "SpectateKeys" 33 | net.WriteUInt(buttons, 24) 34 | net.Send(players) 35 | end 36 | 37 | 38 | -- # Hooks 39 | 40 | function SpectateService.Spectate(ply, cmd, args) 41 | local target = SpectateService.FindPlayerByName(args[1]) 42 | 43 | if ply.can_spectate and CurTime() > ply.spectate_timeout then 44 | if target then 45 | if ply:GetObserverMode() == OBS_MODE_NONE then 46 | if not ply:IsOnGround() then 47 | ply:ChatPrint("You can't spectate in the air.") 48 | return 49 | end 50 | 51 | if ply:Crouching() then 52 | ply:ChatPrint("You can't spectate while crouching.") 53 | return 54 | end 55 | 56 | if target == ply then 57 | ply:ChatPrint("You can't spectate yourself.") 58 | return 59 | end 60 | 61 | ply.spectate_savepoint = SavepointService.CreateSavepoint(ply) 62 | end 63 | 64 | table.insert(target.spectators, ply) 65 | ply:SpectateEntity(target) 66 | ply:Spectate(ply.spectate_obs_mode) 67 | ply.spectate_timeout = CurTime() + 1 68 | TrailService.RemoveTrail(ply) 69 | SpectateService.SendSpectateKeys(target.buttons, ply) 70 | StaminaService.SendStamina(ply, target, "airaccel") 71 | else 72 | ply:ChatPrint("Player not found.") 73 | end 74 | end 75 | end 76 | concommand.Add("sv_emm_spectate", SpectateService.Spectate) 77 | 78 | function SpectateService.UnSpectate(ply) 79 | if ply:GetObserverMode() ~= OBS_MODE_NONE then 80 | local health = ply:Health() 81 | 82 | table.RemoveByValue(ply:GetObserverTarget().spectators, ply) 83 | TrailService.SetupTrail(ply) 84 | ply:UnSpectate() 85 | ply:Spawn() 86 | ply:SetHealth(health) 87 | SavepointService.LoadSavepoint(ply, ply.spectate_savepoint) 88 | ply:SetVelocity(Vector(0,0,0)) 89 | end 90 | end 91 | concommand.Add("emm_unspectate", SpectateService.UnSpectate) 92 | 93 | function SpectateService.HandleDisconnect(ply) 94 | if ply:GetObserverMode() ~= OBS_MODE_NONE then 95 | SpectateService.UnSpectate(ply) 96 | end 97 | 98 | for _, spectator in pairs(ply.spectators) do 99 | SpectateService.UnSpectate(spectator) 100 | end 101 | end 102 | hook.Add("PlayerDisconnected", "SpectateService.HandleDisconnect", SpectateService.HandleDisconnect) 103 | 104 | function SpectateService.UpdateSpectateKeys(ply, move) 105 | local buttons = move:GetButtons() 106 | 107 | if buttons ~= ply.buttons then 108 | ply.buttons = buttons 109 | SpectateService.SendSpectateKeys(buttons, ply.spectators) 110 | end 111 | end 112 | hook.Add("FinishMove", "SpectateService.UpdateSpectateKeys", SpectateService.UpdateSpectateKeys) 113 | 114 | function SpectateService.CanSuicide(ply) 115 | return ply:GetObserverMode() == OBS_MODE_NONE 116 | end 117 | hook.Add("CanPlayerSuicide", "SpectateService.CanSuicide", SpectateService.CanSuicide) -------------------------------------------------------------------------------- /gamemode/util/class.sh.lua: -------------------------------------------------------------------------------- 1 | Class = {} 2 | 3 | function Class.InstanceOf(instance, class) 4 | local instance_of 5 | 6 | local current_class = getmetatable(instance) 7 | 8 | if current_class then 9 | if class == current_class then 10 | instance_of = true 11 | else 12 | while current_class.super do 13 | local super = current_class.super 14 | 15 | if class == super then 16 | instance_of = true 17 | 18 | break 19 | else 20 | current_class = super 21 | instance_of = false 22 | end 23 | end 24 | end 25 | end 26 | 27 | return instance_of 28 | end 29 | 30 | function Class.TableID(tab) 31 | return tostring(tab):gsub("table: ", "", 1) 32 | end 33 | 34 | function Class.New(super) 35 | local class = {} 36 | class.__index = class 37 | class.static = {} 38 | 39 | if super then 40 | class.super = super 41 | setmetatable(class, super) 42 | 43 | if super.static.hooks then 44 | class.static.hooks = {} 45 | class.static.instances = {} 46 | 47 | for _, hk in pairs(super.static.hooks) do 48 | Class.AddHook(class, hk.name, hk.func_key) 49 | end 50 | end 51 | end 52 | 53 | function class.New(...) 54 | local instance = Class.Instance(class, ...) 55 | 56 | if class.static.hooks then 57 | table.insert(class.static.instances, instance) 58 | end 59 | 60 | return instance 61 | end 62 | 63 | return class 64 | end 65 | 66 | function Class.Instance(class, ...) 67 | local instance = setmetatable({}, class) 68 | 69 | if class.Init then 70 | instance:Init(...) 71 | end 72 | 73 | return instance 74 | end 75 | 76 | function Class.SetupForHooks(class) 77 | class.static.hooks = {} 78 | class.static.instances = {} 79 | 80 | function class:DisconnectFromHooks() 81 | table.RemoveByValue(getmetatable(self).static.instances, self) 82 | end 83 | 84 | if not class.Finish then 85 | function class:Finish() 86 | self:DisconnectFromHooks() 87 | end 88 | end 89 | end 90 | 91 | function Class.AddHook(class, name, func_k) 92 | func_k = func_k or name 93 | 94 | if not class.static.hooks then 95 | Class.SetupForHooks(class) 96 | end 97 | 98 | local existing_hk_k 99 | 100 | for k, hk in pairs(class.static.hooks) do 101 | if hk.name == name then 102 | existing_hk_k = k 103 | 104 | break 105 | end 106 | end 107 | 108 | if existing_hk_k then 109 | class.static.hooks[existing_hk_k].func_key = func_k 110 | else 111 | table.insert(class.static.hooks, {name = name, func_key = func_k}) 112 | end 113 | 114 | local instances = class.static.instances 115 | local queued_for_finish = {} 116 | 117 | hook.Add(name, Class.TableID(class).."."..func_k, function (...) 118 | local arguments = {...} 119 | local len = #instances 120 | 121 | for i = 1, len do 122 | local instance = instances[i] 123 | 124 | if instance then 125 | local success, error = pcall(function () 126 | class[func_k](instance, unpack(arguments)) 127 | end) 128 | 129 | if not success then 130 | if instance.debug_trace then 131 | Error(error.."\n"..instance.debug_trace.."\n") 132 | else 133 | Error(error.."\n") 134 | end 135 | 136 | table.insert(queued_for_finish, instance) 137 | end 138 | end 139 | end 140 | 141 | local finish_len = #queued_for_finish 142 | 143 | for i = 1, finish_len do 144 | queued_for_finish[i]:DisconnectFromHooks() 145 | end 146 | end) 147 | end 148 | 149 | function Class.RemoveHook(class, name) 150 | local func_k 151 | 152 | for k, hk in pairs(class.static.hooks) do 153 | if name == hk.name then 154 | func_k = hk.func_key 155 | table.remove(hk, k) 156 | 157 | break 158 | end 159 | end 160 | 161 | if #class.static.hooks < 1 then 162 | class.static.hooks = false 163 | class.static.instances = {} 164 | end 165 | 166 | hook.Remove(name, Class.TableID(class).."."..func_k) 167 | end -------------------------------------------------------------------------------- /gamemode/minigame/prototype.sh.lua: -------------------------------------------------------------------------------- 1 | MinigamePrototype = MinigamePrototype or Class.New() 2 | 3 | function MinigamePrototype:__newindex(k, v) 4 | if not self.key and k == "name" then 5 | rawset(self, "key", v) 6 | end 7 | 8 | rawset(self, k, v) 9 | end 10 | 11 | function MinigamePrototype:Init() 12 | self.player_classes = {} 13 | self.states = {} 14 | self.hooks = {} 15 | self.state_hooks = {} 16 | self.event_hooks = {} 17 | self.display = true 18 | self.default_state = "Waiting" 19 | self.required_players = 2 20 | 21 | self:SetAdjustableSettings { 22 | { 23 | key = "states.Playing.time", 24 | 25 | prerequisite = { 26 | label = "Unlimited round time", 27 | opposite_value = true, 28 | override_value = 0 29 | }, 30 | 31 | label = "Round time", 32 | type = "time" 33 | }, 34 | 35 | { 36 | key = "player_classes.*", 37 | 38 | settings = { 39 | { 40 | key = "air_accelerate", 41 | label = "Air acceleration", 42 | type = "number" 43 | }, 44 | 45 | { 46 | key = "friction", 47 | label = "Friction", 48 | type = "number" 49 | }, 50 | 51 | { 52 | key = "gravity", 53 | label = "Gravity", 54 | type = "number" 55 | }, 56 | 57 | { 58 | key = "can_walljump", 59 | label = "Can walljump" 60 | }, 61 | 62 | { 63 | key = "can_wallslide", 64 | label = "Can wallslide" 65 | }, 66 | 67 | { 68 | key = "can_airaccel", 69 | label = "Can air-accelerate" 70 | }, 71 | 72 | { 73 | key = "can_autojump", 74 | label = "Can auto-jump" 75 | }, 76 | 77 | { 78 | key = "can_regenerate_health", 79 | label = "Can regenerate health" 80 | }, 81 | 82 | { 83 | key = "can_take_fall_damage", 84 | label = "Can take fall damage" 85 | }, 86 | 87 | { 88 | key = "has_infinite_wallslide", 89 | label = "Has infinite wallslide stamina" 90 | }, 91 | 92 | { 93 | key = "has_infinite_airaccel", 94 | label = "Has infinite air-accelerate stamina" 95 | }, 96 | 97 | { 98 | key = "weapons", 99 | label = "Weapons", 100 | type = "list", 101 | options = MINIGAME_WEAPONS 102 | } 103 | } 104 | } 105 | } 106 | 107 | self:AddDefaultStates() 108 | self:AddDefaultHooks() 109 | 110 | if SERVER then 111 | self:AddRequirePlayersHooks() 112 | end 113 | end 114 | 115 | function MinigamePrototype:AddPlayerClass(props) 116 | ply_class = PlayerClassService.CreatePlayerClass(props) 117 | ply_class.id = table.Count(self.player_classes) + 1 118 | ply_class.color = props.color or self.color 119 | self.player_classes[ply_class.key] = ply_class 120 | end 121 | 122 | function MinigamePrototype:AddHook(hk_name, hk_id, func) 123 | self.hooks[hk_name] = self.hooks[hk_name] or {} 124 | self.hooks[hk_name][hk_id] = func 125 | end 126 | 127 | function MinigamePrototype:RemoveHook(hk_name, hk_id) 128 | self.hooks[hk_name][hk_id] = nil 129 | end 130 | 131 | function MinigamePrototype:AddStateHook(state_key, hk_name, hk_id, func) 132 | self.state_hooks[state_key] = self.state_hooks[state_key] or {} 133 | self.state_hooks[state_key][hk_name] = self.state_hooks[state_key][hk_name] or {} 134 | self.state_hooks[state_key][hk_name][hk_id] = func 135 | end 136 | 137 | function MinigamePrototype:RemoveStateHook(state_key, hk_name, hk_id) 138 | self.state_hooks[state_key][hk_name][hk_id] = nil 139 | end 140 | 141 | hook.Add("CreateMinigameHookSchemas", "Default", function () 142 | MinigameNetService.CreateHookSchema "StateExpired" 143 | MinigameNetService.CreateHookSchema("RandomPlayerClassesPicked", {"entities"}) 144 | MinigameNetService.CreateHookSchema("PlayerClassForfeitToClosest", {"entity", "entity"}) 145 | MinigameNetService.CreateHookSchema("PlayerClassForfeitToAttacker", {"entity", "entity"}) 146 | MinigameNetService.CreateHookSchema("PlayerClassChangeFromDeath", {"entity"}) 147 | end) 148 | -------------------------------------------------------------------------------- /gamemode/hud/elements/hud-meter.cl.lua: -------------------------------------------------------------------------------- 1 | -- # Meter 2 | 3 | HUDMeter = HUDMeter or Class.New(Element) 4 | 5 | function HUDMeter:Init(quadrant, props) 6 | HUDMeter.super.Init(self, { 7 | layout_justification_x = JUSTIFY_CENTER, 8 | layout_justification_y = JUSTIFY_START, 9 | layout_direction = DIRECTION_COLUMN, 10 | wrap = false, 11 | fit_y = true, 12 | width_percent = HUD_METER_SIZE, 13 | child_margin = MARGIN * 4 14 | }) 15 | 16 | self.value_func = props.value_func 17 | self.bar_value_func = props.bar_value_func 18 | self.value_divider = props.value_divider or 100 19 | self.hide_value_on_empty = props.hide_value_on_empty 20 | self.hide_value_on_full = props.hide_value_on_full 21 | 22 | local init_v = self.value_func() 23 | 24 | self.debounced_value = AnimatableValue.New(init_v, { 25 | callback = function (v) 26 | self:OnValueChanged(v) 27 | end 28 | }) 29 | 30 | HUDService["quadrant_"..quadrant]:Add(self) 31 | 32 | if props.show_value then 33 | self.value_text_container = self:Add(Element.New { 34 | fit = true, 35 | wrap = false, 36 | child_margin = 2, 37 | alpha = not self:ShouldHideValueText() and 255 or 0 38 | }) 39 | 40 | self.value_text = self.value_text_container:Add(Element.New { 41 | fit = true, 42 | crop_top = 0.225, 43 | crop_bottom = 0.125, 44 | font = "HUDMeterValue" 45 | }) 46 | 47 | if props.sub_value then 48 | self.sub_value_text = self.value_text_container:Add(Element.New { 49 | fit = true, 50 | crop_top = 0.225, 51 | crop_bottom = 0.125, 52 | font = "HUDMeterValueSmall" 53 | }) 54 | end 55 | 56 | if props.units then 57 | self.value_text_container:Add(Element.New { 58 | self_adjacent_justification = JUSTIFY_END, 59 | fit = true, 60 | crop_top = 0.225, 61 | crop_bottom = 0.1, 62 | text = props.units, 63 | font = "HUDMeterValueSmall" 64 | }) 65 | end 66 | end 67 | 68 | local init_percent = init_v/self.value_divider 69 | 70 | self.bar = self:Add(MeterBar.New { 71 | percent = init_percent, 72 | height = props.line_thickness or HUD_LINE_THICKNESS 73 | }) 74 | 75 | self:OnValueChanged(self.debounced_value) 76 | 77 | self:Add(Element.New { 78 | width = HUD_ICON_SIZE, 79 | height = HUD_ICON_SIZE, 80 | crop_y = 0.25, 81 | material = props.icon_material 82 | }) 83 | end 84 | 85 | function HUDMeter:Finish() 86 | self.debounced_value:Finish() 87 | HUDMeter.super.Finish(self) 88 | end 89 | 90 | function HUDMeter:SetValueText(round_sub_value) 91 | local v = tostring(self.debounced_value.debounce) 92 | 93 | if self.sub_value_text then 94 | self.value_text:SetText(string.sub(v, 1, -2)) 95 | 96 | if not round_sub_value then 97 | self.sub_value_text:SetText(string.sub(v, -1)) 98 | end 99 | else 100 | self.value_text:SetText(v) 101 | end 102 | end 103 | 104 | function HUDMeter:Think() 105 | HUDMeter.super.Think(self) 106 | 107 | local value = self.value_func() 108 | 109 | local width_percent 110 | 111 | if self.bar_value_func then 112 | width_percent = self.bar_value_func() 113 | else 114 | width_percent = value/self.value_divider 115 | end 116 | 117 | self.debounced_value.current = value 118 | self.bar:SetPercent(width_percent) 119 | 120 | if self.value_text then 121 | self:SetValueText() 122 | end 123 | end 124 | 125 | function HUDMeter:ShouldHideValueText(v) 126 | v = (v or self.debounced_value).current 127 | 128 | return (self.hide_value_on_empty and v == 0) or (self.hide_value_on_full and v == self.value_divider) 129 | end 130 | 131 | function HUDMeter:OnValueChanged(v) 132 | if self.value_text then 133 | if not self.hid_value and HUDMeter.ShouldHideValueText(self, v) then 134 | self.value_text_container:AnimateAttribute("alpha", 0) 135 | self.hid_value = true 136 | elseif self.hid_value then 137 | self.value_text_container:AnimateAttribute("alpha", 255) 138 | self.hid_value = false 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /code-style-guide.md: -------------------------------------------------------------------------------- 1 | # Extended Movement Mod Code Style Guide 2 | 3 | ## Naming conventions 4 | 5 | ### Folders and Lua files 6 | 7 | All folders and files should be lowercase-dashed. All Lua files should have a secondary extension defining what realm it is meant for (server, shared, client). 8 | 9 | ``` 10 | gamemode/ 11 | util/ 12 | palette.sh.lua 13 | pred-sound.cl.lua 14 | ... 15 | ``` 16 | 17 | ### Variables 18 | 19 | All variables that are not constants should be lowercase_snake_case. All constant variables should be UPPERCASE_SNAKE_CASE. All module-like and class-like tables should be UppercaseCamelCase. All function parameters and local variables not inside the first scope should have shortened words when practical. The shortening of variable words should be consistent throughout the project. 20 | 21 | ```lua 22 | global_variable = 1 23 | CONSTANT_GLOBAL_VARIABLE = 2 24 | 25 | local variable = 3 26 | local CONSTANT_VARIABLE = 4 27 | ``` 28 | 29 | ```lua 30 | -- 'function' shortened to 'func', 'parameter ' shortened to 'param', and 'variable' shortened to 'var' inside a scope 31 | 32 | local Module = {} 33 | 34 | function Module.Function(func_param) 35 | local var_in_func_scope = func_param 36 | end 37 | 38 | if true then 39 | local var_in_statement = true 40 | end 41 | ``` 42 | 43 | ### Properties 44 | 45 | All properties should be lowercase_snake_case with no shortened words. 46 | 47 | ```lua 48 | local table_with_properties = {} 49 | table_with_properties.property = 1 50 | ``` 51 | 52 | ```lua 53 | function WalljumpService.InitPlayerProperties(ply) 54 | ply.can_walljump = true 55 | ply.can_walljump_sky = false 56 | ply.walljump_delay = 0.2 57 | ply.walljump_distance = 30 58 | ... 59 | ``` 60 | 61 | ### Functions 62 | 63 | All functions should be UppercaseCamelCase with no shortened words (with exceptions like 'Initial' to 'Init'). 64 | 65 | ```lua 66 | function WalljumpService.InitPlayerProperties(...) 67 | ... 68 | end 69 | ``` 70 | 71 | ## Structuring conventions 72 | 73 | ### Guarding 74 | - Prefer wrapping the body inside an if-then statement over returning early 75 | ```lua 76 | -- bad 77 | function SpectateService.Spectate(...) 78 | if ply.spectate_timeout > CurTime() then 79 | return 80 | end 81 | 82 | if not target then 83 | ... 84 | return 85 | end 86 | 87 | if ply:GetObserverMode() == OBS_MODE_NONE then 88 | if not ply:IsOnGround() then 89 | ... 90 | return 91 | end 92 | end 93 | 94 | -- body 95 | ... 96 | end 97 | 98 | -- good 99 | function SpectateService.Spectate(...) 100 | if CurTime() > ply.spectate_timeout then 101 | if target then 102 | if ply:GetObserverMode() == OBS_MODE_NONE then 103 | if not ply:IsOnGround() then 104 | ... 105 | return 106 | end 107 | end 108 | 109 | -- body 110 | ... 111 | else 112 | ... 113 | end 114 | end 115 | end 116 | ``` 117 | 118 | ### Function calls 119 | - If you are repeating function calls when you do not need to, save it to a variable once 120 | ```lua 121 | -- bad 122 | function TimeAssociatedMap:Value(...) 123 | if not self.values[CurTime()] then 124 | self.values[CurTime()] = self.lookup_func(...) 125 | end 126 | 127 | return self.values[CurTime()] 128 | end 129 | 130 | -- good 131 | function TimeAssociatedMap:Value(...) 132 | local cur_time = CurTime() 133 | 134 | if not self.values[cur_time] then 135 | self.values[cur_time] = self.lookup_func(...) 136 | end 137 | 138 | return self.values[cur_time] 139 | end 140 | ``` 141 | 142 | ### Equality statements 143 | - Prefer using `~=` over `not` with `==` or `!=` 144 | - Do not check if a variable is `nil` if you do not need to 145 | 146 | ```lua 147 | -- bad 148 | if foo != nil then 149 | 150 | -- good 151 | if foo then 152 | ``` 153 | 154 | ### Comparison statements 155 | - Prefer the greater than sign pointing right 156 | - Wrap one side in parentheses if it combines or references multiple variables 157 | 158 | ```lua 159 | -- bad 160 | if time + cooldown < CurTime() then 161 | 162 | -- good 163 | if CurTime() > (time + cooldown) then 164 | ``` -------------------------------------------------------------------------------- /gamemode/ui/vardebug.cl.lua: -------------------------------------------------------------------------------- 1 | VarDebugService = VarDebugService or {} 2 | 3 | 4 | -- # Panels 5 | 6 | surface.CreateFont("VarDebugFont", {font = "Roboto Mono", size = 16}) 7 | 8 | local VarDebugContainer = {} 9 | 10 | function VarDebugContainer:PerformLayout() 11 | self:SetSize(ScrW()/2 - 32, ScrH()/2 - 32) 12 | self:AlignRight(0) 13 | self:AlignTop(ScrH()/2) 14 | end 15 | 16 | vgui.Register("VarDebugContainer", VarDebugContainer, "EditablePanel") 17 | 18 | local SideVarDebugContainer = {} 19 | 20 | function SideVarDebugContainer:PerformLayout() 21 | self:SetSize(128, ScrH()/2 - 32) 22 | self:AlignRight(0) 23 | self:AlignTop(ScrH()/2) 24 | end 25 | 26 | vgui.Register("SideVarDebugContainer", SideVarDebugContainer, "EditablePanel") 27 | 28 | local VarDebug = {} 29 | 30 | function VarDebug:Init() 31 | self:Dock(TOP) 32 | self:SetTall(20) 33 | end 34 | 35 | function VarDebug:Paint(w, h) 36 | local ply = LocalPlayer() 37 | local label = string.upper(self.label or self.accessor or self.variable or "?") 38 | local val = self.func and self.func() or self.accessor and ply[self.accessor](ply) or ply:GetTable()[self.variable] or "?" 39 | local val_str = string.upper(istable(val) and table.ToString(val) or tostring(val)) 40 | 41 | surface.SetFont "VarDebugFont" 42 | local label_w, label_h = surface.GetTextSize(label) 43 | local val_w, val_h = surface.GetTextSize(val_str) 44 | 45 | surface.SetTextColor(LocalPlayer().color) 46 | surface.SetTextPos(0, h/2 - label_h/2) 47 | surface.DrawText(label) 48 | 49 | local val_padding = 3 50 | local val_x, val_y = label_w + (val_padding * 2) + 2, h/2 - val_h/2 51 | surface.SetDrawColor(COLOR_GRAY) 52 | surface.DrawRect(val_x - val_padding, val_y + 1, val_w + (val_padding * 2), val_h) 53 | surface.SetTextPos(val_x, val_y) 54 | surface.DrawText(val_str) 55 | end 56 | 57 | vgui.Register("VarDebug", VarDebug, "EditablePanel") 58 | 59 | 60 | -- # Init 61 | 62 | function VarDebugService.Init() 63 | local ply = LocalPlayer() 64 | 65 | VarDebugService.container = vgui.Create "VarDebugContainer" 66 | VarDebugService.side_container = vgui.Create "SideVarDebugContainer" 67 | CamUIService.AddPanel(VarDebugService.container) 68 | CamUIService.AddPanel(VarDebugService.side_container) 69 | 70 | VarDebugService.lobby = vgui.Create "VarDebug" 71 | VarDebugService.lobby.label = "Lobby" 72 | VarDebugService.lobby.func = function () return ply.lobby and ply.lobby.id end 73 | VarDebugService.side_container:Add(VarDebugService.lobby) 74 | 75 | VarDebugService.state = vgui.Create "VarDebug" 76 | VarDebugService.state.label = "State" 77 | VarDebugService.state.func = function () 78 | return ply.lobby and ply.lobby.state and ply.lobby.state.name 79 | end 80 | VarDebugService.side_container:Add(VarDebugService.state) 81 | 82 | VarDebugService.state_time = vgui.Create "VarDebug" 83 | VarDebugService.state_time.label = "Time" 84 | VarDebugService.state_time.func = function () 85 | return ( 86 | ply.lobby and 87 | ply.lobby.state and 88 | ply.lobby.state.time and 89 | ply.lobby.last_state_start and 90 | string.Trim(string.FormattedTime((ply.lobby.last_state_start + ply.lobby.state.time + 1) - CurTime(), "%2i:%02i")) 91 | ) 92 | end 93 | VarDebugService.side_container:Add(VarDebugService.state_time) 94 | 95 | VarDebugService.player_class = vgui.Create "VarDebug" 96 | VarDebugService.player_class.label = "Class" 97 | VarDebugService.player_class.func = function () return ply.player_class and ply.player_class.name end 98 | VarDebugService.side_container:Add(VarDebugService.player_class) 99 | end 100 | -- hook.Add("InitUI", "VarDebugService.Init", VarDebugService.Init) 101 | 102 | function VarDebugService.AddDebugger(id, func) 103 | VarDebugService[id] = vgui.Create "VarDebug" 104 | VarDebugService[id].label = id 105 | VarDebugService[id].func = func 106 | VarDebugService.container:Add(VarDebugService[id]) 107 | end 108 | 109 | function VarDebugService.Reload() 110 | VarDebugService.container:Remove() 111 | VarDebugService.side_container:Remove() 112 | VarDebugService.Init() 113 | end 114 | -- hook.Add("OnReloaded", "VarDebugService", VarDebugService.Reload) -------------------------------------------------------------------------------- /gamemode/ui/elements/text-input.cl.lua: -------------------------------------------------------------------------------- 1 | TextInput = TextInput or Class.New(Element) 2 | 3 | local TextInputPanel = {} 4 | 5 | function TextInputPanel:Init() 6 | self:SetUpdateOnType(true) 7 | end 8 | 9 | function TextInputPanel:Paint(w, h) 10 | local attr = self.element.attributes 11 | local color = attr.text_color and attr.text_color.current or self.element:GetColor() 12 | 13 | self:DrawTextEntryText(color, COLOR_GRAY_LIGHTER, color) 14 | end 15 | 16 | function TextInputPanel:OnValueChange(v) 17 | self.element:OnValueChanged(v) 18 | end 19 | 20 | function TextInputPanel:OnCursorEntered() 21 | self.element.panel:OnCursorEntered() 22 | end 23 | 24 | function TextInputPanel:OnCursorExited() 25 | self.element.panel:OnCursorExited() 26 | end 27 | 28 | function TextInputPanel:OnMousePressed(mouse) 29 | self.element.panel:OnMousePressed(mouse) 30 | end 31 | 32 | function TextInputPanel:OnLoseFocus() 33 | self.element:OnUnFocus() 34 | end 35 | 36 | vgui.Register("TextInputPanel", TextInputPanel, "DTextEntry") 37 | 38 | function TextInput:Init(text, props) 39 | TextInput.super.Init(self, { 40 | width_percent = 1, 41 | height_percent = 1, 42 | padding_left = 2, 43 | background_color = COLOR_GRAY_DARK, 44 | cursor = "beam", 45 | font = "InputText", 46 | border = 2, 47 | border_alpha = 0, 48 | 49 | disabled = { 50 | background_color = COLOR_BLACK_CLEAR, 51 | border = 1, 52 | border_color = COLOR_GRAY_DARK, 53 | border_alpha = 255 54 | }, 55 | 56 | hover = { 57 | border_alpha = 255 58 | }, 59 | 60 | text_line = self:CreateTextLine() 61 | }) 62 | 63 | self.value = tostring(text) 64 | self.panel.text = self.panel:Add(vgui.Create "TextInputPanel") 65 | self:SetupPanel(text) 66 | 67 | if props then 68 | self:SetAttributes(props) 69 | self.read_only = props.read_only 70 | self.on_change = props.on_change 71 | self.on_click = props.on_click 72 | 73 | if props.read_only then 74 | self:Disable() 75 | end 76 | end 77 | end 78 | 79 | function TextInput:Disable() 80 | self.disabled = true 81 | self.panel:SetMouseInputEnabled(false) 82 | self:OnUnFocus() 83 | self:AnimateState "disabled" 84 | end 85 | 86 | function TextInput:Enable() 87 | self.disabled = false 88 | self.panel:SetMouseInputEnabled(true) 89 | self:RevertState() 90 | end 91 | 92 | function TextInput:CreateTextLine() 93 | return Element.New { 94 | overlay = true, 95 | layout = false, 96 | origin_position = true, 97 | origin_justification_x = JUSTIFY_CENTER, 98 | origin_justification_y = JUSTIFY_END, 99 | position_justification_x = JUSTIFY_CENTER, 100 | position_justification_y = JUSTIFY_END, 101 | width_percent = 1, 102 | height = LINE_THICKNESS, 103 | fill_color = true, 104 | alpha = 0 105 | } 106 | end 107 | 108 | function TextInput:SetupPanel(text) 109 | self.panel.text.element = self 110 | self.panel.text:SetFont(self:GetAttribute "font") 111 | self.panel.text:SetText(text or self:GetAttribute "text" or "") 112 | end 113 | 114 | function TextInput:OnValueChanged(v, no_callback) 115 | self.value = v 116 | 117 | if not no_callback and self.on_change then 118 | self.on_change(self, v) 119 | end 120 | end 121 | 122 | function TextInput:SetValue(v, no_callback) 123 | self.panel.text:SetText(v) 124 | self.panel.text:OnValueChange(v, no_callback) 125 | end 126 | 127 | function TextInput:OnMousePressed(mouse) 128 | TextInput.super.OnMousePressed(self, mouse) 129 | 130 | if self.on_click then 131 | self.on_click(self, mouse) 132 | end 133 | 134 | self:OnFocus(self) 135 | end 136 | 137 | function TextInput:OnMouseEntered() 138 | if self.disabled then 139 | self.panel:SetMouseInputEnabled(false) 140 | else 141 | TextInput.super.OnMouseEntered(self) 142 | end 143 | end 144 | 145 | function TextInput:OnMouseExited() 146 | if not self.disabled then 147 | TextInput.super.OnMouseExited(self) 148 | end 149 | end 150 | 151 | function TextInput:OnFocus() 152 | self.panel.text:RequestFocus() 153 | hook.Run("TextEntryFocus", self) 154 | self.text_line:AnimateAttribute("alpha", 255) 155 | end 156 | 157 | function TextInput:OnUnFocus() 158 | hook.Run("TextEntryUnFocus", self) 159 | self.text_line:AnimateAttribute("alpha", 0) 160 | end -------------------------------------------------------------------------------- /gamemode/minigame/lobby.cl.lua: -------------------------------------------------------------------------------- 1 | function MinigameService.CreateLobby(props) 2 | local lobby = MinigameLobby.New(props) 3 | MinigameService.lobbies[lobby.id] = lobby 4 | 5 | return lobby 6 | end 7 | 8 | function MinigameService.FinishLobby(lobby) 9 | MinigameService.lobbies[lobby.id] = nil 10 | lobby:Finish() 11 | end 12 | 13 | function MinigameService.LobbyText(lobby) 14 | local host = lobby.host 15 | 16 | return (IsLocalPlayer(host) and "your" or host:GetName().."'s").." "..lobby.prototype.name.." lobby" 17 | end 18 | 19 | function MinigameService.PushLobbyMetaText(lobby) 20 | NotificationService.PushMetaText(MinigameService.LobbyText(lobby), "Lobby", 1) 21 | end 22 | 23 | function MinigameService.InitHUDElements() 24 | local lobby = LocalPlayer().lobby 25 | 26 | if lobby then 27 | MinigameService.PushLobbyMetaText(lobby) 28 | end 29 | end 30 | hook.Add("InitHUDElements", "MinigameService.InitHUDElements", MinigameService.InitHUDElements) 31 | 32 | function MinigameLobby:Init(props) 33 | self.id = props.id 34 | self.prototype = props.prototype 35 | self.state = props.state 36 | self.last_state_start = props.last_state_start 37 | self.host = props.host 38 | self.players = props.players or {} 39 | 40 | for _, ply in pairs(self.players) do 41 | ply.lobby = self 42 | end 43 | 44 | for k, _ in pairs(self.prototype.player_classes) do 45 | self[k] = self[k] or {} 46 | end 47 | 48 | self:InitSettings() 49 | 50 | hook.Run("LobbyCreate", self) 51 | 52 | if self:IsLocal() then 53 | hook.Run("LocalLobbyCreate", self) 54 | 55 | if IsLocalPlayer(self.host) then 56 | MinigameService.PushLobbyMetaText(self) 57 | end 58 | end 59 | end 60 | 61 | function MinigameLobby:Finish() 62 | hook.Run("LobbyFinish", self) 63 | 64 | if self:IsLocal() then 65 | hook.Run("LocalLobbyFinish", self) 66 | end 67 | 68 | for _, ply in pairs(self.players) do 69 | self:RemovePlayer(ply, false) 70 | end 71 | 72 | table.Empty(self) 73 | end 74 | 75 | function MinigameLobby:GetSanitized() 76 | local sanitized_lobby = {} 77 | sanitized_lobby.id = self.id 78 | sanitized_lobby.prototype = self.prototype.id 79 | sanitized_lobby.host = self.host:EntIndex() 80 | sanitized_lobby.players = {} 81 | 82 | for k, ply in pairs(self.players) do 83 | sanitized_lobby.players[k] = ply:EntIndex() 84 | end 85 | 86 | return sanitized_lobby 87 | end 88 | 89 | function MinigameLobby:IsLocal() 90 | return self == LocalPlayer().lobby 91 | end 92 | 93 | function MinigameLobby:SetHost(ply) 94 | self.host = ply 95 | hook.Run("LobbyHostChange", self, ply) 96 | 97 | if self:IsLocal() then 98 | hook.Run("LocalLobbyHostChange", self, ply) 99 | MinigameService.PushLobbyMetaText(self) 100 | end 101 | end 102 | 103 | function MinigameLobby:AddPlayer(ply) 104 | ply.lobby = self 105 | table.insert(self.players, ply) 106 | 107 | hook.Run("LobbyPlayerJoin", self, ply) 108 | 109 | if self:IsLocal() then 110 | hook.Run("LocalLobbyPlayerJoin", self, ply) 111 | 112 | if IsLocalPlayer(ply) then 113 | MinigameService.PushLobbyMetaText(self) 114 | end 115 | end 116 | 117 | MinigameService.CallHook(self, "PlayerJoin", ply) 118 | end 119 | 120 | function MinigameLobby:RemovePlayer(ply) 121 | if IsValid(ply) then 122 | hook.Run("LobbyPlayerLeave", self, ply) 123 | 124 | if self:IsLocal() then 125 | hook.Run("LocalLobbyPlayerLeave", self, ply) 126 | 127 | if IsLocalPlayer(ply) then 128 | NotificationService.FinishSticky "Lobby" 129 | end 130 | end 131 | 132 | MinigameService.CallHook(self, "PlayerLeave", ply) 133 | 134 | ply.lobby = nil 135 | table.RemoveByValue(self.players, ply) 136 | end 137 | end 138 | hook.Add("PlayerDisconnected", "MinigameService.RemoveDisconnectedPlayer", function (ply) 139 | if IsValid(ply) and ply.lobby then 140 | ply.lobby:RemovePlayer(ply) 141 | else 142 | for _, lobby in pairs(MinigameService.lobbies) do 143 | local invalid_plys = {} 144 | 145 | for _, ply in pairs(lobby.players) do 146 | if not IsValid(ply) then 147 | table.insert(invalid_plys, ply) 148 | end 149 | end 150 | 151 | for _, ply in pairs(invalid_plys) do 152 | table.RemoveByValue(lobby.players, ply) 153 | end 154 | end 155 | end 156 | end) -------------------------------------------------------------------------------- /gamemode/util/stamina.sh.lua: -------------------------------------------------------------------------------- 1 | StaminaService = StaminaService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | function StaminaService.InitPlayerProperties(ply) 7 | ply.stamina = ply.stamina or {} 8 | end 9 | hook.Add( 10 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 11 | "StaminaService.InitPlayerProperties", 12 | StaminaService.InitPlayerProperties 13 | ) 14 | 15 | 16 | -- # Utils 17 | 18 | function StaminaService.ReceiveStamina() 19 | local ply = net.ReadEntity() 20 | local stamina_type = net.ReadString() 21 | local stamina_table = net.ReadTable() 22 | 23 | ply.stamina[stamina_type].active = stamina_table.active 24 | ply.stamina[stamina_type].amount = stamina_table.amount 25 | ply.stamina[stamina_type].regen_step = stamina_table.regen_step 26 | ply.stamina[stamina_type].decay_step = stamina_table.decay_step 27 | ply.stamina[stamina_type].cooldown = stamina_table.cooldown 28 | ply.stamina[stamina_type].last_active = stamina_table.last_active 29 | end 30 | net.Receive("UpdateStamina", StaminaService.ReceiveStamina) 31 | 32 | 33 | -- # Types 34 | 35 | StaminaType = StaminaType or {} 36 | StaminaType.__index = StaminaType 37 | 38 | function StaminaService.CreateStaminaType() 39 | local instance = setmetatable({}, StaminaType) 40 | instance:Init() 41 | 42 | return instance 43 | end 44 | 45 | function StaminaType:Init() 46 | self.active = false 47 | self.amount = 100 48 | self.regen_step = 0.1 49 | self.decay_step = 0.1 50 | self.cooldown = 1 51 | self.last_active = 0 52 | end 53 | 54 | function StaminaType:GetStamina() 55 | return self.infinite and 100 or self.amount 56 | end 57 | 58 | function StaminaType:HasStamina() 59 | return self.infinite or (self.amount > 0) 60 | end 61 | 62 | function StaminaType:SetStamina(value) 63 | self.amount = math.Clamp(value, 0, 100) 64 | end 65 | 66 | function StaminaType:AddStamina(value) 67 | self.amount = math.Clamp(self.amount + value, 0, 100) 68 | end 69 | 70 | function StaminaType:ReduceStamina(value) 71 | self.amount = math.Clamp(self.amount - value, 0, 100) 72 | end 73 | 74 | function StaminaType:IsActive() 75 | return self.active 76 | end 77 | 78 | function StaminaType:SetActive(active) 79 | if not active and self.active then 80 | self.last_active = CurTime() 81 | end 82 | 83 | self.active = active 84 | end 85 | 86 | 87 | -- # Updating 88 | 89 | function StaminaService.UpdatePlayer(ply, cur_time) 90 | for _, stamina_type in pairs(ply.stamina) do 91 | if stamina_type.active then 92 | stamina_type:ReduceStamina(stamina_type.decay_step) 93 | elseif cur_time > (stamina_type.last_active + stamina_type.cooldown) then 94 | stamina_type:AddStamina(stamina_type.regen_step) 95 | end 96 | end 97 | end 98 | 99 | function StaminaService.Update() 100 | local cur_time = CurTime() 101 | 102 | if SERVER then 103 | for _, ply in pairs(player.GetAll()) do 104 | StaminaService.UpdatePlayer(ply, cur_time) 105 | end 106 | else 107 | local ply = GetPlayer() 108 | 109 | if IsValid(ply) and ply.stamina then 110 | StaminaService.UpdatePlayer(GetPlayer(), cur_time) 111 | end 112 | end 113 | end 114 | hook.Add("Tick", "StaminaService.Update", StaminaService.Update) 115 | 116 | 117 | -- # Minigame settings reloading 118 | 119 | function StaminaService.Reload(lobby, settings) 120 | local ply_classes_adjusted = {} 121 | local staminas = {} 122 | 123 | for k, v in pairs(settings) do 124 | local ply_class, stamina = string.match(k, "player_classes%.(.*)%.has_infinite_(.*)") 125 | 126 | if SERVER then 127 | if ply_class then 128 | ply_classes_adjusted[ply_class] = stamina 129 | staminas[stamina] = v 130 | end 131 | else 132 | local local_ply = LocalPlayer() 133 | 134 | if local_ply.player_class and ply_class == local_ply.player_class.key then 135 | local_ply.stamina[stamina].infinite = v 136 | end 137 | end 138 | end 139 | 140 | if SERVER and ply_classes_adjusted then 141 | for ply_class, stamina in pairs(ply_classes_adjusted) do 142 | for _, ply in pairs(lobby[ply_class]) do 143 | ply.stamina[stamina].infinite = staminas[stamina] 144 | end 145 | end 146 | end 147 | end 148 | hook.Add(SERVER and "LobbySettingsChange" or "LocalLobbySettingsChange", "StaminaService.Reload", StaminaService.Reload) -------------------------------------------------------------------------------- /gamemode/ui/cam.cl.lua: -------------------------------------------------------------------------------- 1 | CamUIService = CamUIService or {} 2 | CamUIService.panels = CamUIService.panels or {} 3 | 4 | local cam_angle_divider = 4 5 | 6 | 7 | -- # Setup 8 | 9 | CamUIService.cam_smooth_multiplier = CamUIService.cam_smooth_multiplier or AnimatableValue.NewFromSetting "cam_ui_smooth_multiplier" 10 | 11 | CamUIService.eye_angle = CamUIService.eye_angle or AnimatableValue.New(Angle(0, 0, 0), { 12 | smooth = true, 13 | smooth_multiplier = CAMUI_SMOOTH_MULTIPLIER, 14 | smooth_delta_only = true 15 | }) 16 | 17 | CamUIService.eye_angle_2 = CamUIService.eye_angle_2 or AnimatableValue.New(Angle(0, 0, 0), { 18 | smooth = true, 19 | 20 | generate = function () 21 | return CamUIService.eye_angle.smooth 22 | end 23 | }) 24 | 25 | function CamUIService.CalcView(ply, _, eye_ang) 26 | CamUIService.eye_angle.current = eye_ang 27 | end 28 | hook.Add("CalcView", "CamUIService.CalcView", CamUIService.CalcView) 29 | 30 | function CamUIService.AddPanel(pnl, props) 31 | pnl.camui_panel = true 32 | pnl.cam_smooth_divider = AnimatableValue.New(props.smooth_divider or 1) 33 | pnl.cam_distance = AnimatableValue.New(props.distance or 0) 34 | pnl.cam_angle = AnimatableValue.New(props.angle or Angle(0, 0, 0)) 35 | pnl.cam_rotate_origin_x = props.rotate_origin_x or 0.5 36 | pnl.cam_rotate_origin_y = props.rotate_origin_y or 0.5 37 | 38 | pnl:SetPaintedManually(true) 39 | table.insert(CamUIService.panels, pnl) 40 | end 41 | 42 | 43 | -- # Rendering 44 | 45 | function CamUIService.Render() 46 | for i, pnl in pairs(CamUIService.panels) do 47 | if not IsValid(pnl) then 48 | table.remove(CamUIService.panels, i) 49 | end 50 | end 51 | 52 | local scr_w = ScrW() 53 | local scr_h = ScrH() 54 | local half_scr_w = scr_w/2 55 | local half_scr_h = scr_h/2 56 | local cam_3d_vec = Vector(0, -half_scr_w, -half_scr_h) 57 | local cam_3d_ang = IsValid(LocalPlayer():GetObserverTarget()) and Angle(0, 0, 0) or CamUIService.eye_angle_2.smooth/cam_angle_divider 58 | 59 | surface.DisableClipping(false) 60 | 61 | for i, pnl in pairs(CamUIService.panels) do 62 | if IsValid(pnl) then 63 | CamUIService.RenderPanel(pnl, scr_w, scr_h, half_scr_w, half_scr_h, cam_3d_vec, cam_3d_ang) 64 | end 65 | end 66 | 67 | surface.DisableClipping(true) 68 | end 69 | hook.Add("DrawCamUI", "CamUIService.Render", CamUIService.Render) 70 | 71 | local cam_3d2d_angle = Angle(0, -90, 90) 72 | local cam_3d2d_matrix_angle = Angle(0, -90, 270) 73 | 74 | function CamUIService.RenderPanel(pnl, scr_w, scr_h, half_scr_w, half_scr_h, cam_3d_vec, cam_3d_ang) 75 | local scr_w_offset = scr_w * pnl.cam_rotate_origin_x 76 | local scr_h_offset = scr_h * pnl.cam_rotate_origin_y 77 | local offset_ang = pnl.cam_angle.current 78 | 79 | local offset_vec = Vector(scr_w_offset, scr_h_offset, 0) 80 | offset_vec:Rotate(Angle(offset_ang.y, offset_ang.r, offset_ang.p)) 81 | 82 | cam.Start3D(cam_3d_vec, (cam_3d_ang/pnl.cam_smooth_divider.current) * CamUIService.cam_smooth_multiplier.current, 90) 83 | 84 | cam.Start3D2D( 85 | Vector((half_scr_w * (pnl.cam_distance.current + 1)) - offset_vec.z, offset_vec.x - scr_w_offset, offset_vec.y - scr_h_offset), 86 | cam_3d2d_angle + Angle(offset_ang.r, -offset_ang.y, offset_ang.p), 87 | 1 88 | ) 89 | 90 | -- local mat = Matrix() 91 | -- mat:Translate(Vector(0, -half_scr_w, -half_scr_h)) 92 | -- mat:Scale(Vector(1, 1, 1)) 93 | -- mat:Translate(Vector(0, half_scr_w, half_scr_h)) 94 | -- mat:Translate(Vector((half_scr_w * (pnl.cam_distance.current + 1)) - offset_vec.z, offset_vec.x - scr_w_offset, offset_vec.y - scr_h_offset)) 95 | -- mat:Rotate(cam_3d2d_matrix_angle + Angle(offset_ang.r, -offset_ang.y, offset_ang.p)) 96 | -- cam.PushModelMatrix(mat) 97 | 98 | cam.IgnoreZ(true) 99 | 100 | local parent = pnl:GetParent() 101 | 102 | if IsValid(parent) and not parent.camui_panel then 103 | surface.SetAlphaMultiplier(parent:GetAlpha()/255) 104 | end 105 | 106 | pnl:PaintManual() 107 | surface.SetAlphaMultiplier(1) 108 | cam.IgnoreZ(false) 109 | 110 | -- cam.PopModelMatrix() 111 | 112 | cam.End3D2D() 113 | cam.End3D() 114 | end 115 | 116 | function CamUIService.ResetCamAngle() 117 | CamUIService.eye_angle_2:Freeze() 118 | end 119 | hook.Add("LocalPlayerSpawn", "CamUIService.ResetCamAngle", CamUIService.ResetCamAngle) 120 | hook.Add("OnReloaded", "CamUIService.ResetCamAngle", CamUIService.ResetCamAngle) -------------------------------------------------------------------------------- /gamemode/movement/airaccel.sh.lua: -------------------------------------------------------------------------------- 1 | AiraccelService = AiraccelService or {} 2 | 3 | 4 | -- # Properties 5 | 6 | CreateConVar("emm_airaccelerate", 10, FCVAR_REPLICATED, "Air acceleration") 7 | 8 | function AiraccelService.InitPlayerProperties(ply) 9 | ply.can_airaccel = true 10 | ply.has_infinite_airaccel = false 11 | ply.airaccel_regen_step = 0.1 12 | ply.airaccel_decay_step = 0.1 13 | ply.airaccel_cooldown = 2 14 | ply.airaccel_velocity_cost = 0.01 15 | ply.airaccel_boost_velocity = 10000 16 | ply.airaccel_sound = "player/suit_sprint.wav" 17 | ply.air_accelerate = GetConVar("emm_airaccelerate"):GetFloat() 18 | end 19 | hook.Add( 20 | SERVER and "InitPlayerProperties" or "InitLocalPlayerProperties", 21 | "AiraccelService.InitPlayerProperties", 22 | AiraccelService.InitPlayerProperties 23 | ) 24 | 25 | function AiraccelService.SetupStamina(ply) 26 | ply.stamina.airaccel = ply.stamina.airaccel or StaminaService.CreateStaminaType() 27 | ply.stamina.airaccel.active = false 28 | ply.stamina.airaccel.regen_step = ply.airaccel_regen_step 29 | ply.stamina.airaccel.decay_step = ply.airaccel_decay_step 30 | ply.stamina.airaccel.cooldown = ply.airaccel_cooldown 31 | ply.stamina.airaccel.infinite = ply.has_infinite_airaccel 32 | ply.stamina.airaccel.amount = 100 33 | end 34 | 35 | function AiraccelService.LocalPlayerProperties(ply) 36 | AiraccelService.SetupStamina(ply) 37 | end 38 | hook.Add( 39 | "LocalPlayerProperties", 40 | "AiraccelService.LocalPlayerProperties", 41 | AiraccelService.LocalPlayerProperties 42 | ) 43 | 44 | function AiraccelService.PlayerProperties(ply) 45 | if CLIENT then 46 | StaminaService.InitPlayerProperties(ply) 47 | AiraccelService.InitPlayerProperties(ply) 48 | AiraccelService.SetupStamina(ply) 49 | else 50 | AiraccelService.SetupStamina(ply) 51 | end 52 | end 53 | hook.Add( 54 | "PlayerProperties", 55 | "AiraccelService.PlayerProperties", 56 | AiraccelService.PlayerProperties 57 | ) 58 | 59 | function AiraccelService.GetDefaultAiraccel() 60 | return GetConVar("emm_airaccelerate"):GetFloat() 61 | end 62 | 63 | 64 | -- # Util 65 | 66 | function AiraccelService.KeyPress(ply, key) 67 | if IsFirstTimePredicted() and ply.can_airaccel and key == IN_SPEED and not ply.airaccel_started then 68 | ply.airaccel_started = true 69 | PredictedSoundService.PlaySound(ply, ply.airaccel_sound) 70 | end 71 | end 72 | hook.Add("KeyPress", "AiraccelService.KeyPress", AiraccelService.KeyPress) 73 | 74 | function AiraccelService.WishDir(ply, fwd, fwd_speed, side_speed) 75 | return (Vector(fwd.x, fwd.y, 0):GetNormalized() * fwd_speed) + (Vector(fwd.y, -fwd.x, 0):GetNormalized() * side_speed * 1.05) 76 | end 77 | 78 | 79 | -- # Airacceling 80 | 81 | function AiraccelService.Velocity(ply, move, amount) 82 | local strafe_vel = AiraccelService.WishDir(ply, move:GetMoveAngles():Forward(), move:GetForwardSpeed(), move:GetSideSpeed()) 83 | local strafe_vel_length = math.Clamp(strafe_vel:Length(), 0, 300) 84 | local strafe_vel_norm = strafe_vel:GetNormalized() 85 | local vel_diff = strafe_vel_length/10 - move:GetVelocity():Dot(strafe_vel_norm) 86 | 87 | return vel_diff, move:GetVelocity() + (strafe_vel_norm * math.Clamp(ply:GetMaxSpeed() * amount * FrameTime(), 0, vel_diff)) 88 | end 89 | 90 | function AiraccelService.SetupAiraccel(ply, move) 91 | if CLIENT then 92 | ply = GetPlayer() 93 | end 94 | 95 | if 96 | ply:Alive() and 97 | ply.can_airaccel and 98 | not ply:IsOnGround() and 99 | move:KeyDown(IN_SPEED) and 100 | AiraccelService.HasStamina(ply) 101 | then 102 | ply.stamina.airaccel:SetActive(true) 103 | 104 | local vel_diff, new_vel = AiraccelService.Velocity(ply, move, ply.airaccel_boost_velocity) 105 | 106 | if vel_diff > 0 then 107 | move:SetVelocity(new_vel) 108 | AiraccelService.ReduceStamina(ply, vel_diff * ply.airaccel_velocity_cost) 109 | end 110 | else 111 | if IsFirstTimePredicted() and ply.airaccel_started then 112 | if CLIENT and ply.can_airaccel then 113 | PredictedSoundService.PlaySound(ply, ply.airaccel_sound, 100, 75, 0.2) 114 | end 115 | 116 | ply.airaccel_started = false 117 | end 118 | 119 | ply.stamina.airaccel:SetActive(false) 120 | 121 | if ply:Alive() and not ply:OnGround() then 122 | local vel_diff, new_vel = AiraccelService.Velocity(ply, move, ply.air_accelerate) 123 | 124 | if vel_diff > 0 then 125 | move:SetVelocity(new_vel) 126 | end 127 | end 128 | end 129 | end 130 | hook.Add("SetupMove", "AiraccelService.SetupAiraccel", AiraccelService.SetupAiraccel) --------------------------------------------------------------------------------