├── .gitattributes ├── sounds ├── kersplat.wav ├── tg.sfx.c1.wav ├── tg.sfx.c2.wav ├── tg.sfx.c3.wav ├── kersplat.vsnd_c ├── tg.sfx.cgo.wav ├── kersplat.sound_c ├── tg.bg.pregame.mp3 ├── tg.sfx.c1.sound_c ├── tg.sfx.c1.vsnd_c ├── tg.sfx.c2.sound_c ├── tg.sfx.c2.vsnd_c ├── tg.sfx.c3.sound_c ├── tg.sfx.c3.vsnd_c ├── tg.sfx.cgo.sound_c ├── tg.sfx.cgo.vsnd_c ├── tg.sfx.manual.wav ├── tg.sfx.select.wav ├── tg.bg.pregame.vsnd_c ├── tg.sfx.automatic.wav ├── tg.sfx.finallap.wav ├── tg.sfx.getready.wav ├── tg.sfx.manual.vsnd_c ├── tg.sfx.select.vsnd_c ├── tg.sfx.selectcar.wav ├── tg.bg.pregame.sound_c ├── tg.sfx.automatic.vsnd_c ├── tg.sfx.checkpoint.wav ├── tg.sfx.finallap.sound_c ├── tg.sfx.finallap.vsnd_c ├── tg.sfx.getready.sound_c ├── tg.sfx.getready.vsnd_c ├── tg.sfx.manual.sound_c ├── tg.sfx.select.sound_c ├── tg.sfx.selectcar.vsnd_c ├── tg.sfx.automatic.sound_c ├── tg.sfx.checkpoint.sound_c ├── tg.sfx.checkpoint.vsnd_c ├── tg.sfx.selectcar.sound_c ├── kersplat.sound ├── tg.sfx.c1.sound ├── tg.sfx.c2.sound ├── tg.sfx.c3.sound ├── tg.sfx.cgo.sound ├── tg.bg.pregame.sound ├── tg.sfx.manual.sound ├── tg.sfx.select.sound ├── tg.sfx.automatic.sound ├── tg.sfx.finallap.sound ├── tg.sfx.getready.sound ├── tg.sfx.selectcar.sound └── tg.sfx.checkpoint.sound ├── ui ├── tg.bg.gnucar.jpg ├── tg.scoreboard.team.dead.png ├── tg.scoreboard.team.ingame.png ├── tg.scoreboard.team.ready.png ├── tg.scoreboard.team.finished.png ├── tg.scoreboard.team.selecting.png ├── tg.scoreboard.team.spectator.png └── tg.scoreboard.team.eliminated.png ├── .addon ├── models └── car │ ├── car.vmdl_c │ └── car.vmdl ├── surfaces ├── car.surface_c └── car.surface ├── code ├── entities │ ├── Bonus.cs │ ├── BonusSpawner.cs │ ├── Police.cs │ ├── Checkpoint.cs │ ├── Pedestrian.cs │ └── car │ │ ├── CarController.cs │ │ ├── CarAnimator.cs │ │ ├── CarWheel.cs │ │ ├── CarCamera.cs │ │ └── CarEntity.cs ├── ui │ ├── Health.cs │ ├── TerrygeddonScoreboardEntry.cs │ ├── TerrygeddonHud.scss │ ├── TerrygeddonScoreboard.cs │ └── TerrygeddonHud.cs ├── Player.Ragdoll.cs ├── Game.cs └── Player.cs ├── .gitignore ├── CREDITS.md ├── LICENSE └── config └── terrygeddon.fgd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /sounds/kersplat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/kersplat.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.c1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c1.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.c2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c2.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.c3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c3.wav -------------------------------------------------------------------------------- /ui/tg.bg.gnucar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.bg.gnucar.jpg -------------------------------------------------------------------------------- /.addon: -------------------------------------------------------------------------------- 1 | { 2 | "sharedassets": "*.*", 3 | "type": "game", 4 | "ident": "tesa.terrygeddon" 5 | } 6 | -------------------------------------------------------------------------------- /models/car/car.vmdl_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/models/car/car.vmdl_c -------------------------------------------------------------------------------- /sounds/kersplat.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/kersplat.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.cgo.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.cgo.wav -------------------------------------------------------------------------------- /surfaces/car.surface_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/surfaces/car.surface_c -------------------------------------------------------------------------------- /sounds/kersplat.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/kersplat.sound_c -------------------------------------------------------------------------------- /sounds/tg.bg.pregame.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.bg.pregame.mp3 -------------------------------------------------------------------------------- /sounds/tg.sfx.c1.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c1.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.c1.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c1.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.c2.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c2.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.c2.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c2.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.c3.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c3.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.c3.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.c3.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.cgo.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.cgo.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.cgo.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.cgo.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.manual.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.manual.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.select.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.select.wav -------------------------------------------------------------------------------- /sounds/tg.bg.pregame.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.bg.pregame.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.automatic.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.automatic.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.finallap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.finallap.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.getready.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.getready.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.manual.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.manual.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.select.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.select.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.selectcar.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.selectcar.wav -------------------------------------------------------------------------------- /sounds/tg.bg.pregame.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.bg.pregame.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.automatic.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.automatic.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.checkpoint.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.checkpoint.wav -------------------------------------------------------------------------------- /sounds/tg.sfx.finallap.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.finallap.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.finallap.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.finallap.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.getready.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.getready.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.getready.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.getready.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.manual.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.manual.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.select.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.select.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.selectcar.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.selectcar.vsnd_c -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.dead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.dead.png -------------------------------------------------------------------------------- /sounds/tg.sfx.automatic.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.automatic.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.checkpoint.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.checkpoint.sound_c -------------------------------------------------------------------------------- /sounds/tg.sfx.checkpoint.vsnd_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.checkpoint.vsnd_c -------------------------------------------------------------------------------- /sounds/tg.sfx.selectcar.sound_c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/sounds/tg.sfx.selectcar.sound_c -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.ingame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.ingame.png -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.ready.png -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.finished.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.finished.png -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.selecting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.selecting.png -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.spectator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.spectator.png -------------------------------------------------------------------------------- /ui/tg.scoreboard.team.eliminated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rndtrash/terrygeddon/master/ui/tg.scoreboard.team.eliminated.png -------------------------------------------------------------------------------- /code/entities/Bonus.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | [Library( "tg_bonus", Title = "Bonus", Spawnable = true )] 4 | public partial class Bonus : AnimEntity 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /code/entities/BonusSpawner.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | [Library( "tg_bonusspawner", Title = "Bonus spawner", Spawnable = false )] 4 | public class BonusSpawner : AnimEntity 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /sounds/kersplat.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/kersplat.vsnd", 8 | ] 9 | } 10 | } -------------------------------------------------------------------------------- /code/entities/Police.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | [Library( "tg_police", Title = "Police", Spawnable = true )] 5 | public class Police : AnimEntity 6 | { 7 | public Police() 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .codegen 2 | obj 3 | accesslist.txt 4 | *.csproj 5 | launchSettings.json 6 | tools_asset_info.bin 7 | tools_thumbnail_cache.bin 8 | *.xml 9 | *.dll 10 | *.pdb 11 | .intermediate/net5.0/sandbox.deps.json 12 | _bakeresourcecache/maps/editor/toolscene_default_baked/* -------------------------------------------------------------------------------- /code/entities/Checkpoint.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | [Library( "tg_checkpoint", Title = "Checkpoint", Spawnable = false )] 5 | public class Checkpoint : AnimEntity 6 | { 7 | public Checkpoint() 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/entities/Pedestrian.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | [Library( "tg_pedestrian", Title = "Pedestrian", Spawnable = true )] 5 | public class Pedestrian : AnimEntity 6 | { 7 | public Pedestrian() 8 | { 9 | throw new NotImplementedException(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /sounds/tg.sfx.c1.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.c1.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.c2.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.c2.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.c3.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.c3.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.cgo.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.cgo.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.bg.pregame.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.bg.pregame.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.manual.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.manual.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.select.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.select.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.automatic.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.automatic.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.finallap.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.finallap.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.getready.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.getready.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.selectcar.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.selectcar.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /sounds/tg.sfx.checkpoint.sound: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | sounds = 6 | [ 7 | "sounds/tg.sfx.checkpoint.vsnd", 8 | ] 9 | UI = true 10 | Volume = 1.0 11 | VolumeRandom = 0.0 12 | Pitch = 1.0 13 | PitchRandom = 0.0 14 | DistanceMax = 1000.0 15 | } 16 | } -------------------------------------------------------------------------------- /code/ui/Health.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | using Sandbox.UI.Construct; 4 | 5 | public class Health : Panel 6 | { 7 | public Label Label; 8 | 9 | public Health() 10 | { 11 | Label = Add.Label( "100", "value" ); 12 | } 13 | 14 | public override void Tick() 15 | { 16 | var player = Local.Pawn as TerrygeddonPlayer; 17 | if ( player == null || player.Vehicle == null ) return; 18 | 19 | Label.Text = $"{player.Vehicle.Health.CeilToInt()}"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /code/ui/TerrygeddonScoreboardEntry.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | using Sandbox.UI.Construct; 4 | 5 | public partial class TerrygeddonScoreboardEntry : Panel 6 | { 7 | public PlayerScore.Entry Entry; 8 | 9 | public Label PlayerName; 10 | public Label Ping; 11 | 12 | public TerrygeddonScoreboardEntry() 13 | { 14 | AddClass( "entry" ); 15 | 16 | PlayerName = Add.Label( "PlayerName", "name" ); 17 | Ping = Add.Label( "", "ping" ); 18 | } 19 | 20 | public void UpdateFrom( PlayerScore.Entry entry ) 21 | { 22 | Entry = entry; 23 | 24 | PlayerName.Text = entry.GetString( "name" ); 25 | Ping.Text = entry.Get( "ping", 0 ).ToString(); 26 | 27 | // FIXME: Local.Client is null 28 | SetClass( "me", Local.Client != null && entry.Get( "steamid", 0 ) == Local.Client.SteamId ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /code/entities/car/CarController.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | [Library] 4 | public class CarController : PawnController 5 | { 6 | public override void FrameSimulate() 7 | { 8 | base.FrameSimulate(); 9 | 10 | Simulate(); 11 | } 12 | 13 | public override void Simulate() 14 | { 15 | var player = Pawn as TerrygeddonPlayer; 16 | if ( !player.IsValid() ) return; 17 | 18 | var car = player.Vehicle as CarEntity; 19 | if ( !car.IsValid() ) return; 20 | 21 | car.Simulate( Client ); 22 | 23 | if ( player.Vehicle == null ) 24 | { 25 | Position = car.Position + car.Rotation.Up * (100 * car.Scale); 26 | Velocity += car.Rotation.Right * (200 * car.Scale); 27 | return; 28 | } 29 | 30 | EyeRot = Input.Rotation; 31 | EyePosLocal = Vector3.Up * (64 - 10) * car.Scale; 32 | Velocity = car.Velocity; 33 | 34 | SetTag( "noclip" ); 35 | SetTag( "sitting" ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CREDITS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## Music 4 | 5 | `sounds/tg.bg.pregame.mp3` - Daytona USA (c) SEGA **TO BE REPLACED** 6 | `sounds/tg.sfx.automatic.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 7 | `sounds/tg.sfx.select.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 8 | `sounds/tg.sfx.selectcar.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 9 | `sounds/tg.sfx.finallap.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 10 | `sounds/tg.sfx.c3.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 11 | `sounds/tg.sfx.c2.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 12 | `sounds/tg.sfx.c1.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 13 | `sounds/tg.sfx.cgo.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 14 | `sounds/tg.sfx.checkpoint.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 15 | `sounds/tg.sfx.manual.wav` - Daytona USA (c) SEGA **TO BE REPLACED** 16 | `sounds/tg.sfx.getready.wav` - Daytona USA (c) SEGA **TO BE REPLACED** -------------------------------------------------------------------------------- /code/ui/TerrygeddonHud.scss: -------------------------------------------------------------------------------- 1 | Health 2 | { 3 | position: absolute; 4 | background-color: rgba( black, 0.5 ); 5 | right: 100px; 6 | bottom: 48px; 7 | font-size: 40px; 8 | font-weight: bold; 9 | color: white; 10 | height: 80px; 11 | padding: 0 20px; 12 | align-items: center; 13 | } 14 | 15 | .sp 16 | { 17 | display: none; 18 | position: absolute; 19 | width: 100%; 20 | height: 100%; 21 | } 22 | 23 | rootpanel { 24 | display: flex; 25 | background-color: blue; 26 | 27 | &.devcamera { 28 | display: none; 29 | } 30 | 31 | &.state_wfp { 32 | background-color: red; 33 | .wfp { 34 | // 35 | } 36 | } 37 | 38 | &.state_pregame { 39 | .pregame { 40 | // 41 | } 42 | } 43 | 44 | &.state_ingame { 45 | .ingame { 46 | // 47 | } 48 | } 49 | 50 | &.state_gameover { 51 | .gameover { 52 | // 53 | } 54 | } 55 | 56 | &.state_postgame { 57 | .postgame { 58 | // 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /code/entities/car/CarAnimator.cs: -------------------------------------------------------------------------------- 1 | 2 | namespace Sandbox 3 | { 4 | public class CarAnimator : PawnAnimator 5 | { 6 | public override void Simulate() 7 | { 8 | ResetParams(); 9 | 10 | SetParam( "b_grounded", true ); 11 | SetParam( "b_sit", true ); 12 | 13 | var eyeAngles = (Pawn.Rotation.Inverse * Pawn.EyeRot).Angles(); 14 | eyeAngles.pitch = eyeAngles.pitch.Clamp( -25, 70 ); 15 | eyeAngles.yaw = eyeAngles.yaw.Clamp( -90, 90 ); 16 | 17 | var aimPos = Pawn.EyePos + (Pawn.Rotation * Rotation.From( eyeAngles )).Forward * 200; 18 | 19 | SetLookAt( "aim_eyes", aimPos ); 20 | SetLookAt( "aim_head", aimPos ); 21 | SetLookAt( "aim_body", aimPos ); 22 | 23 | if ( Pawn.ActiveChild is BaseCarriable carry ) 24 | { 25 | carry.SimulateAnimator( this ); 26 | } 27 | else 28 | { 29 | SetParam( "holdtype", 0 ); 30 | SetParam( "aim_body_weight", 0.5f ); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /surfaces/car.surface: -------------------------------------------------------------------------------- 1 | 2 | { 3 | data = 4 | { 5 | Base = "" 6 | Friction = 0.075 7 | Elasticity = 0.0 8 | Density = 2000.0 9 | Thickness = -1.0 10 | Dampening = 0.0 11 | BounceThreshold = 0.0 12 | ImpactEffects = 13 | { 14 | Bullet = 15 | [ 16 | resource:"particles/impact.metal.vpcf", 17 | ] 18 | BulletDecal = 19 | [ 20 | "decals/bullet-metal.decal", 21 | ] 22 | Regular = 23 | [ 24 | resource:"particles/impact.metal-light.vpcf", 25 | ] 26 | } 27 | Sounds = 28 | { 29 | ImpactSoft = "physics.metal.sheet.impact" 30 | ImpactHard = "physics.metal.sheet.impact" 31 | RoughScrape = "" 32 | FootLeft = "footstep-metal" 33 | FootRight = "footstep-metal" 34 | FootLaunch = "footstep-metal" 35 | FootLand = "footstep-metal" 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Facepunch, 2021 Tea Sanctuary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /code/ui/TerrygeddonScoreboard.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | using Sandbox.UI.Construct; 4 | using System.Collections.Generic; 5 | 6 | public partial class TerrygeddonScoreboard : Panel 7 | { 8 | public Panel Canvas { get; protected set; } 9 | Dictionary Entries = new(); 10 | 11 | public Panel Header { get; protected set; } 12 | 13 | public TerrygeddonScoreboard() 14 | { 15 | StyleSheet.Load( "/ui/scoreboard/Scoreboard.scss" ); 16 | AddClass( "scoreboard" ); 17 | 18 | 19 | AddHeader(); 20 | 21 | Canvas = Add.Panel( "canvas" ); 22 | 23 | PlayerScore.OnPlayerAdded += AddPlayer; 24 | PlayerScore.OnPlayerUpdated += UpdatePlayer; 25 | PlayerScore.OnPlayerRemoved += RemovePlayer; 26 | 27 | foreach ( var player in PlayerScore.All ) 28 | { 29 | AddPlayer( player ); 30 | } 31 | } 32 | 33 | public override void Tick() 34 | { 35 | base.Tick(); 36 | 37 | SetClass( "open", Input.Down( InputButton.Score ) || (Local.Pawn as TerrygeddonPlayer)?.Team == TerrygeddonPlayer.PlayerTeam.Ready ); 38 | } 39 | 40 | 41 | protected virtual void AddHeader() 42 | { 43 | Header = Add.Panel( "header" ); 44 | Header.Add.Label( "Name", "name" ); 45 | Header.Add.Label( "Ping", "ping" ); 46 | } 47 | 48 | protected virtual void AddPlayer( PlayerScore.Entry entry ) 49 | { 50 | var p = Canvas.AddChild(); 51 | p.UpdateFrom( entry ); 52 | 53 | Entries[entry.Id] = p; 54 | } 55 | 56 | protected virtual void UpdatePlayer( PlayerScore.Entry entry ) 57 | { 58 | if ( Entries.TryGetValue( entry.Id, out var panel ) ) 59 | { 60 | panel.UpdateFrom( entry ); 61 | } 62 | } 63 | 64 | protected virtual void RemovePlayer( PlayerScore.Entry entry ) 65 | { 66 | if ( Entries.TryGetValue( entry.Id, out var panel ) ) 67 | { 68 | panel.Delete(); 69 | Entries.Remove( entry.Id ); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /code/Player.Ragdoll.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | partial class TerrygeddonPlayer 4 | { 5 | [ClientRpc] 6 | private void BecomeRagdollOnClient( Vector3 velocity, DamageFlags damageFlags, Vector3 forcePos, Vector3 force, int bone ) 7 | { 8 | var ent = new ModelEntity(); 9 | ent.Position = Position; 10 | ent.Rotation = Rotation; 11 | ent.Scale = Scale; 12 | ent.MoveType = MoveType.Physics; 13 | ent.UsePhysicsCollision = true; 14 | ent.EnableAllCollisions = true; 15 | ent.CollisionGroup = CollisionGroup.Debris; 16 | ent.SetModel( GetModelName() ); 17 | ent.CopyBonesFrom( this ); 18 | ent.CopyBodyGroups( this ); 19 | ent.CopyMaterialGroup( this ); 20 | ent.TakeDecalsFrom( this ); 21 | ent.EnableHitboxes = true; 22 | ent.EnableAllCollisions = true; 23 | ent.SurroundingBoundsMode = SurroundingBoundsType.Physics; 24 | ent.RenderColor = RenderColor; 25 | ent.PhysicsGroup.Velocity = velocity; 26 | 27 | if ( Local.Pawn == this ) 28 | { 29 | //ent.EnableDrawing = false; wtf 30 | } 31 | 32 | ent.SetInteractsAs( CollisionLayer.Debris ); 33 | ent.SetInteractsWith( CollisionLayer.WORLD_GEOMETRY ); 34 | ent.SetInteractsExclude( CollisionLayer.Player | CollisionLayer.Debris ); 35 | 36 | foreach ( var child in Children ) 37 | { 38 | if ( child is ModelEntity e ) 39 | { 40 | var model = e.GetModelName(); 41 | if ( model != null && !model.Contains( "clothes" ) ) 42 | continue; 43 | 44 | var clothing = new ModelEntity(); 45 | clothing.SetModel( model ); 46 | clothing.SetParent( ent, true ); 47 | clothing.RenderColor = e.RenderColor; 48 | 49 | if ( Local.Pawn == this ) 50 | { 51 | // clothing.EnableDrawing = false; wtf 52 | } 53 | } 54 | } 55 | 56 | if ( damageFlags.HasFlag( DamageFlags.Bullet ) || 57 | damageFlags.HasFlag( DamageFlags.PhysicsImpact ) ) 58 | { 59 | PhysicsBody body = bone > 0 ? ent.GetBonePhysicsBody( bone ) : null; 60 | 61 | if ( body != null ) 62 | { 63 | body.ApplyImpulseAt( forcePos, force * body.Mass ); 64 | } 65 | else 66 | { 67 | ent.PhysicsGroup.ApplyImpulse( force ); 68 | } 69 | } 70 | 71 | if ( damageFlags.HasFlag( DamageFlags.Blast ) ) 72 | { 73 | if ( ent.PhysicsGroup != null ) 74 | { 75 | ent.PhysicsGroup.AddVelocity( (Position - (forcePos + Vector3.Down * 100.0f)).Normal * (force.Length * 0.2f) ); 76 | var angularDir = (Rotation.FromYaw( 90 ) * force.WithZ( 0 ).Normal).Normal; 77 | ent.PhysicsGroup.AddAngularVelocity( angularDir * (force.Length * 0.02f) ); 78 | } 79 | } 80 | 81 | Corpse = ent; 82 | 83 | ent.DeleteAsync( 10.0f ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /code/entities/car/CarWheel.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | struct CarWheel 5 | { 6 | private readonly CarEntity parent; 7 | 8 | private float _previousLength; 9 | private float _currentLength; 10 | 11 | public CarWheel( CarEntity parent ) 12 | { 13 | this.parent = parent; 14 | _previousLength = 0; 15 | _currentLength = 0; 16 | } 17 | 18 | public bool Raycast( float length, bool doPhysics, Vector3 offset, ref float wheel, float dt ) 19 | { 20 | var position = parent.Position; 21 | var rotation = parent.Rotation; 22 | 23 | var wheelAttachPos = position + offset; 24 | var wheelExtend = wheelAttachPos - rotation.Up * (length * parent.Scale); 25 | 26 | var tr = Trace.Ray( wheelAttachPos, wheelExtend ) 27 | .Ignore( parent ) 28 | .Ignore( parent.driver ) 29 | .Run(); 30 | 31 | wheel = length * tr.Fraction; 32 | var wheelRadius = (14 * parent.Scale); 33 | 34 | if ( !doPhysics && CarEntity.debug_car ) 35 | { 36 | var wheelPosition = tr.Hit ? tr.EndPos : wheelExtend; 37 | wheelPosition += rotation.Up * wheelRadius; 38 | 39 | if ( tr.Hit ) 40 | { 41 | DebugOverlay.Circle( wheelPosition, rotation * Rotation.FromYaw( 90 ), wheelRadius, Color.Red.WithAlpha( 0.5f ), false ); 42 | DebugOverlay.Line( tr.StartPos, tr.EndPos, Color.Red, 0, false ); 43 | } 44 | else 45 | { 46 | DebugOverlay.Circle( wheelPosition, rotation * Rotation.FromYaw( 90 ), wheelRadius, Color.Green.WithAlpha( 0.5f ), false ); 47 | DebugOverlay.Line( wheelAttachPos, wheelExtend, Color.Green, 0, false ); 48 | } 49 | } 50 | 51 | if ( !tr.Hit || !doPhysics ) 52 | { 53 | return tr.Hit; 54 | } 55 | 56 | var body = parent.PhysicsBody.SelfOrParent; 57 | 58 | _previousLength = _currentLength; 59 | _currentLength = (length * parent.Scale) - tr.Distance; 60 | 61 | var springVelocity = (_currentLength - _previousLength) / dt; 62 | var springForce = body.Mass * 50.0f * _currentLength; 63 | var damperForce = body.Mass * (1.5f + (1.0f - tr.Fraction) * 3.0f) * springVelocity; 64 | var velocity = body.GetVelocityAtPoint( wheelAttachPos ); 65 | var speed = velocity.Length; 66 | var speedDot = MathF.Abs( speed ) > 0.0f ? MathF.Abs( MathF.Min( Vector3.Dot( velocity, rotation.Up.Normal ) / speed, 0.0f ) ) : 0.0f; 67 | var speedAlongNormal = speedDot * speed; 68 | var correctionMultiplier = (1.0f - tr.Fraction) * (speedAlongNormal / 1000.0f); 69 | var correctionForce = correctionMultiplier * 50.0f * speedAlongNormal / dt; 70 | 71 | body.ApplyImpulseAt( wheelAttachPos, tr.Normal * (springForce + damperForce + correctionForce) * dt ); 72 | 73 | return true; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /code/ui/TerrygeddonHud.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using Sandbox.UI; 3 | 4 | [Library] 5 | public partial class TerrygeddonHud : HudEntity 6 | { 7 | public enum UIState 8 | { 9 | None, 10 | WaitingForPlayers, // Lobby 11 | PreGame, // Selecting a car 12 | InGame, // In game 13 | GameOver, // Player has won or lost 14 | PostGame // Game is over 15 | } 16 | 17 | public UIState State 18 | { 19 | get 20 | { 21 | return _state; 22 | } 23 | 24 | set 25 | { 26 | Log.Info( $"Changing UI state to {value}..." ); 27 | 28 | if ( _state == value ) 29 | return; 30 | 31 | _state = value; 32 | switch ( _state ) 33 | { 34 | case UIState.WaitingForPlayers: 35 | RootPanel.SetClass( "state_wfp", true ); 36 | RootPanel.SetClass( "state_prep", false ); 37 | RootPanel.SetClass( "state_ingame", false ); 38 | RootPanel.SetClass( "state_gameover", false ); 39 | RootPanel.SetClass( "state_postgame", false ); 40 | break; 41 | case UIState.PreGame: 42 | // TODO: play music and shit 43 | RootPanel.SetClass( "state_wfp", false ); 44 | RootPanel.SetClass( "state_pregame", true ); 45 | break; 46 | case UIState.InGame: 47 | RootPanel.SetClass( "state_prep", false ); 48 | RootPanel.SetClass( "state_ingame", true ); 49 | break; 50 | case UIState.GameOver: 51 | // TODO: game over yeeeaaaaah 52 | RootPanel.SetClass( "state_gameover", true ); 53 | break; 54 | case UIState.PostGame: 55 | RootPanel.SetClass( "state_ingame", false ); 56 | RootPanel.SetClass( "state_gameover", false ); 57 | RootPanel.SetClass( "state_postgame", true ); 58 | break; 59 | default: 60 | Log.Error( $"FIXME: Add {_state}" ); 61 | break; 62 | } 63 | } 64 | } 65 | 66 | private UIState _state = UIState.None; 67 | 68 | public TerrygeddonHud() 69 | { 70 | if ( !IsClient ) 71 | return; 72 | 73 | RootPanel.StyleSheet.Load( "/ui/TerrygeddonHud.scss" ); 74 | 75 | RootPanel.AddChild(); 76 | RootPanel.AddChild(); 77 | RootPanel.AddChild(); 78 | RootPanel.AddChild(); 79 | RootPanel.AddChild(); 80 | 81 | var p_wfp = RootPanel.AddChild( "sp wfp" ); 82 | 83 | var p_pregame = RootPanel.AddChild( "sp pregame" ); 84 | 85 | var p_ingame = RootPanel.AddChild( "sp ingame" ); 86 | p_ingame.AddChild(); 87 | 88 | var p_gameover = RootPanel.AddChild( "sp gameover" ); 89 | 90 | var p_postgame = RootPanel.AddChild( "sp postgame" ); 91 | } 92 | 93 | [ClientRpc] 94 | public void SetUIStateRPC(UIState newState) 95 | { 96 | State = newState; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /models/car/car.vmdl: -------------------------------------------------------------------------------- 1 | 2 | { 3 | rootNode = 4 | { 5 | _class = "RootNode" 6 | children = 7 | [ 8 | { 9 | _class = "MaterialGroupList" 10 | children = 11 | [ 12 | { 13 | _class = "DefaultMaterialGroup" 14 | remaps = 15 | [ 16 | { 17 | from = "modular_vehicle_a.vmat" 18 | to = "entities/modular_vehicle/modular_vehicle_a.vmat" 19 | }, 20 | ] 21 | use_global_default = false 22 | global_default_material = "" 23 | }, 24 | ] 25 | }, 26 | { 27 | _class = "PhysicsShapeList" 28 | children = 29 | [ 30 | { 31 | _class = "PhysicsShapeBox" 32 | parent_bone = "" 33 | surface_prop = "car" 34 | collision_prop = "default" 35 | origin = [ 0.0, 0.0, 30.0 ] 36 | angles = [ 0.0, 0.0, 0.0 ] 37 | dimensions = [ 80.0, 65.0, 20.0 ] 38 | }, 39 | { 40 | _class = "PhysicsShapeCapsule" 41 | parent_bone = "" 42 | surface_prop = "car" 43 | collision_prop = "default" 44 | radius = 10.0 45 | point0 = [ 50.0, -25.0, 25.0 ] 46 | point1 = [ 50.0, 25.0, 25.0 ] 47 | }, 48 | { 49 | _class = "PhysicsShapeCapsule" 50 | parent_bone = "" 51 | surface_prop = "car" 52 | collision_prop = "default" 53 | radius = 10.0 54 | point0 = [ -50.0, -25.0, 25.0 ] 55 | point1 = [ -50.0, 25.0, 25.0 ] 56 | }, 57 | { 58 | _class = "PhysicsShapeCapsule" 59 | parent_bone = "" 60 | surface_prop = "car" 61 | collision_prop = "default" 62 | radius = 10.0 63 | point0 = [ -50.0, -25.0, 25.0 ] 64 | point1 = [ 50.0, -25.0, 25.0 ] 65 | }, 66 | { 67 | _class = "PhysicsShapeCapsule" 68 | parent_bone = "" 69 | surface_prop = "car" 70 | collision_prop = "default" 71 | radius = 10.0 72 | point0 = [ -50.0, 25.0, 25.0 ] 73 | point1 = [ 50.0, 25.0, 25.0 ] 74 | }, 75 | ] 76 | }, 77 | { 78 | _class = "RenderMeshList" 79 | children = 80 | [ 81 | { 82 | _class = "RenderMeshFile" 83 | name = "Chassis_2_Main" 84 | filename = "entities/modular_vehicle/chassis_main.fbx" 85 | import_translation = [ 0.0, 0.0, 0.0 ] 86 | import_rotation = [ 0.0, 0.0, 0.0 ] 87 | import_scale = 0.4 88 | align_origin_x_type = "None" 89 | align_origin_y_type = "BoundsCenter" 90 | align_origin_z_type = "None" 91 | parent_bone = "" 92 | import_filter = 93 | { 94 | exclude_by_default = false 95 | exception_list = 96 | [ 97 | "LOD1", 98 | "LOD2", 99 | ] 100 | } 101 | }, 102 | { 103 | _class = "RenderMeshFile" 104 | name = "Chassis_2_Main1" 105 | filename = "entities/modular_vehicle/chassis_main.fbx" 106 | import_translation = [ 0.0, 0.0, 0.0 ] 107 | import_rotation = [ 0.0, 0.0, 0.0 ] 108 | import_scale = 0.4 109 | align_origin_x_type = "None" 110 | align_origin_y_type = "BoundsCenter" 111 | align_origin_z_type = "None" 112 | parent_bone = "" 113 | import_filter = 114 | { 115 | exclude_by_default = false 116 | exception_list = 117 | [ 118 | "LOD2", 119 | "LOD0", 120 | ] 121 | } 122 | }, 123 | { 124 | _class = "RenderMeshFile" 125 | name = "Chassis_2_Main2" 126 | filename = "entities/modular_vehicle/chassis_main.fbx" 127 | import_translation = [ 0.0, 0.0, 0.0 ] 128 | import_rotation = [ 0.0, 0.0, 0.0 ] 129 | import_scale = 0.4 130 | align_origin_x_type = "None" 131 | align_origin_y_type = "BoundsCenter" 132 | align_origin_z_type = "None" 133 | parent_bone = "" 134 | import_filter = 135 | { 136 | exclude_by_default = false 137 | exception_list = 138 | [ 139 | "LOD1", 140 | "LOD0", 141 | ] 142 | } 143 | }, 144 | ] 145 | }, 146 | ] 147 | model_archetype = "" 148 | primary_associated_entity = "" 149 | anim_graph_name = "" 150 | } 151 | } -------------------------------------------------------------------------------- /config/terrygeddon.fgd: -------------------------------------------------------------------------------- 1 | // 2 | // Bonus 3 | // 4 | @PointClass = tg_bonus : "" 5 | [ 6 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 7 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 8 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 9 | 10 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 11 | ] 12 | 13 | // 14 | // BonusSpawner 15 | // 16 | @PointClass = tg_bonusspawner : "" 17 | [ 18 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 19 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 20 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 21 | 22 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 23 | ] 24 | 25 | // 26 | // Checkpoint 27 | // 28 | @PointClass = tg_checkpoint : "" 29 | [ 30 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 31 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 32 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 33 | 34 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 35 | ] 36 | 37 | // 38 | // Pedestrian 39 | // 40 | @PointClass = tg_pedestrian : "" 41 | [ 42 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 43 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 44 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 45 | 46 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 47 | ] 48 | 49 | // 50 | // Police 51 | // 52 | @PointClass = tg_police : "" 53 | [ 54 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 55 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 56 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 57 | 58 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 59 | ] 60 | 61 | // 62 | // CarEntity 63 | // 64 | @PointClass model() = tg_car : "" 65 | [ 66 | model(studio) [report]: "World Model" : "" : "The model this entity should use." 67 | skin(materialgroup) [ group="Render Properties" ] : "Skin" : "default" : "Some models have multiple versions of their textures, called skins." 68 | bodygroups(bodygroupchoices) [ group="Render Properties" ] : "Body Groups" : "" : "Some models have multiple variations of certain items, such as characters having different hair styles, etc." 69 | rendercolor(color255) : "FX Color (R G B)" : "255 255 255" : "The FX color is used by the selected Render Mode." 70 | renderamt(integer) [ group="Render Properties" ] : "FX Amount (0 - 255)" : 255 : "The FX amount is used by the selected Render Mode." 71 | targetname(target_source) : "Name" : : "The name that other entities refer to this entity by." 72 | parentname(target_destination) [ group="Hierarchy" ] : "Parent" : : "The name of this entity's parent in the movement hierarchy. Entities with parents move with their parent." 73 | collisiongroupoverride(choices) : "Collision Group Override" : -1 : "" = 74 | [ 75 | -1 : "UNUSED" 76 | 0 : "COLLISION GROUP ALWAYS" 77 | 1 : "COLLISION GROUP NONPHYSICAL" 78 | 4 : "COLLISION GROUP DEFAULT" 79 | 5 : "COLLISION GROUP DEBRIS" 80 | 14 : "COLLISION GROUP WEAPON" 81 | ] 82 | enable_shadows(boolean) [ group="Render Properties" ] : "Shadow Casting" : 1 : "Whether this entity should cast shadows or not" 83 | 84 | input SetColor(string) : "Sets the color of this entity. Format is '255 255 255 255'." 85 | 86 | output OnDamaged(void) : "Fired when the entity gets damaged" 87 | ] 88 | 89 | -------------------------------------------------------------------------------- /code/Game.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System.Collections.Generic; 3 | 4 | public partial class TerrygeddonGame : Game 5 | { 6 | public enum GameState 7 | { 8 | None, 9 | WaitingForPlayers, 10 | PreGame, 11 | InGame, 12 | PostGame 13 | } 14 | 15 | public GameState State 16 | { 17 | get 18 | { 19 | return _state; 20 | } 21 | 22 | set 23 | { 24 | Log.Info( $"Changing game state to {value}..." ); 25 | 26 | if ( _state == value ) 27 | return; 28 | 29 | _state = value; 30 | var requiresRespawn = false; 31 | switch ( _state ) 32 | { 33 | case GameState.WaitingForPlayers: 34 | _currentSpawnAction = null; 35 | requiresRespawn = true; 36 | HUDEntity.SetUIStateRPC( TerrygeddonHud.UIState.WaitingForPlayers ); 37 | break; 38 | case GameState.PreGame: 39 | requiresRespawn = false; 40 | HUDEntity.SetUIStateRPC( TerrygeddonHud.UIState.PreGame ); 41 | break; 42 | case GameState.InGame: 43 | _currentSpawnAction = PlayerSpawnAction; 44 | requiresRespawn = true; 45 | HUDEntity.SetUIStateRPC( TerrygeddonHud.UIState.InGame ); 46 | break; 47 | case GameState.PostGame: 48 | requiresRespawn = false; 49 | HUDEntity.SetUIStateRPC( TerrygeddonHud.UIState.PostGame ); 50 | break; 51 | default: 52 | Log.Error( $"FIXME: Add {_state}" ); 53 | break; 54 | } 55 | 56 | if ( requiresRespawn ) 57 | RespawnAllPawns(); 58 | } 59 | } 60 | 61 | [ConVar.Replicated( "tg_gamestate" )] 62 | public static GameState GameStateConVar 63 | { 64 | get 65 | { 66 | return (Current as TerrygeddonGame).State; 67 | } 68 | } 69 | 70 | public TerrygeddonHud HUDEntity { get; private set; } 71 | 72 | delegate void ClientAction( Client cl ); 73 | 74 | private GameState _state = GameState.None; 75 | private Dictionary _clients = new(); 76 | private ClientAction _currentSpawnAction; 77 | 78 | public TerrygeddonGame() 79 | { 80 | if ( IsServer ) 81 | { 82 | HUDEntity = new TerrygeddonHud(); 83 | 84 | State = GameState.WaitingForPlayers; 85 | } 86 | } 87 | 88 | public override void ClientJoined( Client cl ) 89 | { 90 | base.ClientJoined( cl ); 91 | 92 | _clients.Add( cl.UserId, cl ); 93 | 94 | if ( cl.Pawn != null && _currentSpawnAction != null ) 95 | _currentSpawnAction( cl ); 96 | } 97 | 98 | protected override void OnDestroy() 99 | { 100 | base.OnDestroy(); 101 | } 102 | 103 | [ServerCmd( "spawn" )] 104 | public static void Spawn( string modelname ) 105 | { 106 | var owner = ConsoleSystem.Caller?.Pawn; 107 | 108 | if ( ConsoleSystem.Caller == null ) 109 | return; 110 | 111 | var tr = Trace.Ray( owner.EyePos, owner.EyePos + owner.EyeRot.Forward * 500 ) 112 | .UseHitboxes() 113 | .Ignore( owner ) 114 | .Size( 2 ) 115 | .Run(); 116 | 117 | var ent = new Prop(); 118 | ent.Position = tr.EndPos; 119 | ent.Rotation = Rotation.From( new Angles( 0, owner.EyeRot.Angles().yaw, 0 ) ) * Rotation.FromAxis( Vector3.Up, 180 ); 120 | ent.SetModel( modelname ); 121 | 122 | // Drop to floor 123 | if ( ent.PhysicsBody != null && ent.PhysicsGroup.BodyCount == 1 ) 124 | { 125 | var p = ent.PhysicsBody.FindClosestPoint( tr.EndPos ); 126 | 127 | var delta = p - tr.EndPos; 128 | ent.PhysicsBody.Position -= delta; 129 | //DebugOverlay.Line( p, tr.EndPos, 10, false ); 130 | } 131 | 132 | } 133 | 134 | [ServerCmd( "spawn_entity" )] 135 | public static void SpawnEntity( string entName ) 136 | { 137 | var owner = ConsoleSystem.Caller.Pawn; 138 | 139 | if ( owner == null ) 140 | return; 141 | 142 | var attribute = Library.GetAttribute( entName ); 143 | 144 | if ( attribute == null || !attribute.Spawnable ) 145 | return; 146 | 147 | var tr = Trace.Ray( owner.EyePos, owner.EyePos + owner.EyeRot.Forward * 200 ) 148 | .UseHitboxes() 149 | .Ignore( owner ) 150 | .Size( 2 ) 151 | .Run(); 152 | 153 | var ent = Library.Create( entName ); 154 | if ( ent is BaseCarriable && owner.Inventory != null ) 155 | { 156 | if ( owner.Inventory.Add( ent, true ) ) 157 | return; 158 | } 159 | 160 | ent.Position = tr.EndPos; 161 | ent.Rotation = Rotation.From( new Angles( 0, owner.EyeRot.Angles().yaw, 0 ) ); 162 | 163 | //Log.Info( $"ent: {ent}" ); 164 | } 165 | 166 | public override void DoPlayerNoclip( Client player ) 167 | { 168 | if ( player.Pawn is Player basePlayer ) 169 | { 170 | if ( basePlayer.DevController is NoclipController ) 171 | { 172 | Log.Info( "Noclip Mode Off" ); 173 | basePlayer.DevController = null; 174 | } 175 | else 176 | { 177 | Log.Info( "Noclip Mode On" ); 178 | basePlayer.DevController = new NoclipController(); 179 | } 180 | } 181 | } 182 | 183 | protected void PlayerSpawnAction( Client cl ) 184 | { 185 | var player = new TerrygeddonPlayer( cl ); 186 | player.Respawn(); 187 | 188 | cl.Pawn = player; 189 | } 190 | 191 | protected void RespawnAllPawns() 192 | { 193 | foreach ( var cl in _clients ) 194 | { 195 | var pawn = cl.Value.Pawn; 196 | 197 | if ( pawn != null ) 198 | pawn.Delete(); 199 | 200 | if ( _currentSpawnAction != null ) 201 | _currentSpawnAction( cl.Value ); 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /code/Player.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | 3 | public partial class TerrygeddonPlayer : Player 4 | { 5 | public enum PlayerTeam 6 | { 7 | Spectator, 8 | Selecting, 9 | Ready, 10 | InGame, 11 | Eliminated, 12 | Dead, 13 | Finished 14 | }; 15 | 16 | [Net] public PlayerTeam Team { get; protected set; } = PlayerTeam.Spectator; 17 | 18 | private TimeSince timeSinceJumpReleased; 19 | 20 | private DamageInfo lastDamage; 21 | 22 | [Net] public PawnController VehicleController { get; set; } 23 | [Net] public PawnAnimator VehicleAnimator { get; set; } 24 | [Net] public ICamera VehicleCamera { get; set; } 25 | [Net] public Entity Vehicle { get; set; } 26 | [Net] public ICamera MainCamera { get; set; } 27 | 28 | public ICamera LastCamera { get; set; } 29 | 30 | public Clothing.Container Clothing = new(); 31 | public TerrygeddonPlayer() 32 | { 33 | } 34 | 35 | public TerrygeddonPlayer(Client cl) : this() 36 | { 37 | Clothing.LoadFromClient( cl ); 38 | } 39 | 40 | public void ChangeTeam(PlayerTeam newTeam) 41 | { 42 | // TODO: change the hud and player controller depending on the state 43 | Team = newTeam; 44 | } 45 | 46 | public override void Spawn() 47 | { 48 | MainCamera = new ThirdPersonCamera(); 49 | LastCamera = MainCamera; 50 | 51 | base.Spawn(); 52 | } 53 | 54 | public override void Respawn() 55 | { 56 | SetModel( "models/citizen/citizen.vmdl" ); 57 | 58 | Controller = new WalkController(); 59 | Animator = new StandardPlayerAnimator(); 60 | 61 | MainCamera = LastCamera; 62 | Camera = MainCamera; 63 | 64 | if ( DevController is NoclipController ) 65 | { 66 | DevController = null; 67 | } 68 | 69 | EnableAllCollisions = true; 70 | EnableDrawing = true; 71 | EnableHideInFirstPerson = true; 72 | EnableShadowInFirstPerson = true; 73 | 74 | Clothing.DressEntity( this ); 75 | 76 | base.Respawn(); 77 | 78 | Health = 1.0f; 79 | 80 | var c = new CarEntity(); 81 | c.Position = Position; 82 | c.AddDriver( this ); 83 | 84 | PlaySound( "tg.bg.pregame" ); 85 | } 86 | 87 | public override void OnKilled() 88 | { 89 | base.OnKilled(); 90 | 91 | if ( lastDamage.Flags.HasFlag( DamageFlags.Vehicle ) ) 92 | { 93 | Particles.Create( "particles/impact.flesh.bloodpuff-big.vpcf", lastDamage.Position ); 94 | Particles.Create( "particles/impact.flesh-big.vpcf", lastDamage.Position ); 95 | PlaySound( "kersplat" ); 96 | } 97 | 98 | VehicleController = null; 99 | VehicleAnimator = null; 100 | VehicleCamera = null; 101 | Vehicle = null; 102 | 103 | BecomeRagdollOnClient( Velocity, lastDamage.Flags, lastDamage.Position, lastDamage.Force, GetHitboxBone( lastDamage.HitboxIndex ) ); 104 | LastCamera = MainCamera; 105 | MainCamera = new SpectateRagdollCamera(); 106 | Camera = MainCamera; 107 | Controller = null; 108 | 109 | EnableAllCollisions = false; 110 | EnableDrawing = false; 111 | } 112 | 113 | public override void TakeDamage( DamageInfo info ) 114 | { 115 | if ( GetHitboxGroup( info.HitboxIndex ) == 1 ) 116 | { 117 | info.Damage *= 10.0f; 118 | } 119 | 120 | lastDamage = info; 121 | 122 | TookDamage( lastDamage.Flags, lastDamage.Position, lastDamage.Force ); 123 | 124 | base.TakeDamage( info ); 125 | } 126 | 127 | [ClientRpc] 128 | public void TookDamage( DamageFlags damageFlags, Vector3 forcePos, Vector3 force ) 129 | { 130 | } 131 | 132 | public override PawnController GetActiveController() 133 | { 134 | if ( VehicleController != null ) return VehicleController; 135 | if ( DevController != null ) return DevController; 136 | 137 | return base.GetActiveController(); 138 | } 139 | 140 | public override PawnAnimator GetActiveAnimator() 141 | { 142 | if ( VehicleAnimator != null ) return VehicleAnimator; 143 | 144 | return base.GetActiveAnimator(); 145 | } 146 | 147 | public ICamera GetActiveCamera() 148 | { 149 | if ( VehicleCamera != null ) return VehicleCamera; 150 | 151 | return MainCamera; 152 | } 153 | 154 | public override void Simulate( Client cl ) 155 | { 156 | base.Simulate( cl ); 157 | 158 | if ( Input.ActiveChild != null ) 159 | { 160 | ActiveChild = Input.ActiveChild; 161 | } 162 | 163 | if ( LifeState != LifeState.Alive ) 164 | return; 165 | 166 | if ( VehicleController != null && DevController is NoclipController ) 167 | { 168 | DevController = null; 169 | } 170 | 171 | var controller = GetActiveController(); 172 | if ( controller != null ) 173 | EnableSolidCollisions = !controller.HasTag( "noclip" ); 174 | 175 | SimulateActiveChild( cl, ActiveChild ); 176 | 177 | if ( Input.Pressed( InputButton.View ) ) 178 | { 179 | if ( MainCamera is not FirstPersonCamera ) 180 | { 181 | MainCamera = new FirstPersonCamera(); 182 | } 183 | else 184 | { 185 | MainCamera = new ThirdPersonCamera(); 186 | } 187 | } 188 | 189 | Camera = GetActiveCamera(); 190 | 191 | if ( Input.Released( InputButton.Jump ) ) 192 | { 193 | if ( timeSinceJumpReleased < 0.3f ) 194 | { 195 | Game.Current?.DoPlayerNoclip( cl ); 196 | } 197 | 198 | timeSinceJumpReleased = 0; 199 | } 200 | 201 | if ( Input.Left != 0 || Input.Forward != 0 ) 202 | { 203 | timeSinceJumpReleased = 1; 204 | } 205 | } 206 | 207 | // TODO 208 | 209 | //public override bool HasPermission( string mode ) 210 | //{ 211 | // if ( mode == "noclip" ) return true; 212 | // if ( mode == "devcam" ) return true; 213 | // if ( mode == "suicide" ) return true; 214 | // 215 | // return base.HasPermission( mode ); 216 | // } 217 | } 218 | -------------------------------------------------------------------------------- /code/entities/car/CarCamera.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | 4 | public class CarCamera : Camera 5 | { 6 | protected virtual float MinFov => 80.0f; 7 | protected virtual float MaxFov => 100.0f; 8 | protected virtual float MaxFovSpeed => 1000.0f; 9 | protected virtual float FovSmoothingSpeed => 4.0f; 10 | protected virtual float OrbitCooldown => 0.6f; 11 | protected virtual float OrbitSmoothingSpeed => 25.0f; 12 | protected virtual float OrbitReturnSmoothingSpeed => 4.0f; 13 | protected virtual float MinOrbitPitch => -25.0f; 14 | protected virtual float MaxOrbitPitch => 70.0f; 15 | protected virtual float FixedOrbitPitch => 10.0f; 16 | protected virtual float OrbitHeight => 35.0f; 17 | protected virtual float OrbitDistance => 150.0f; 18 | protected virtual float MaxOrbitReturnSpeed => 100.0f; 19 | protected virtual float MinCarPitch => -60.0f; 20 | protected virtual float MaxCarPitch => 60.0f; 21 | protected virtual float FirstPersonPitch => 10.0f; 22 | protected virtual float CarPitchSmoothingSpeed => 1.0f; 23 | protected virtual float CollisionRadius => 8.0f; 24 | protected virtual float ShakeSpeed => 10.0f; 25 | protected virtual float ShakeSpeedThreshold => 1500.0f; 26 | protected virtual float ShakeMaxSpeed => 2500.0f; 27 | protected virtual float ShakeMaxLength => 1.0f; 28 | 29 | private bool orbitEnabled; 30 | private TimeSince timeSinceOrbit; 31 | private Angles orbitAngles; 32 | private Rotation orbitYawRot; 33 | private Rotation orbitPitchRot; 34 | private float currentFov; 35 | private float carPitch; 36 | private bool firstPerson; 37 | 38 | public override void Activated() 39 | { 40 | var pawn = Local.Pawn; 41 | if ( pawn == null ) return; 42 | 43 | orbitEnabled = false; 44 | timeSinceOrbit = 0.0f; 45 | orbitAngles = Angles.Zero; 46 | orbitYawRot = Rotation.Identity; 47 | orbitPitchRot = Rotation.Identity; 48 | currentFov = MinFov; 49 | carPitch = 0; 50 | firstPerson = false; 51 | } 52 | 53 | public override void Update() 54 | { 55 | var pawn = Local.Pawn; 56 | if ( pawn == null ) return; 57 | 58 | var car = (pawn as TerrygeddonPlayer)?.Vehicle as CarEntity; 59 | if ( !car.IsValid() ) return; 60 | 61 | var body = car.PhysicsBody; 62 | if ( !body.IsValid() ) 63 | return; 64 | 65 | var speed = car.MovementSpeed; 66 | var speedAbs = Math.Abs( speed ); 67 | 68 | if ( orbitEnabled && timeSinceOrbit > OrbitCooldown ) 69 | orbitEnabled = false; 70 | 71 | var carRot = car.Rotation; 72 | carPitch = carPitch.LerpTo( car.Grounded ? carRot.Pitch().Clamp( MinCarPitch, MaxCarPitch ) * (speed < 0.0f ? -1.0f : 1.0f) : 0.0f, Time.Delta * CarPitchSmoothingSpeed ); 73 | 74 | if ( orbitEnabled ) 75 | { 76 | var slerpAmount = Time.Delta * OrbitSmoothingSpeed; 77 | 78 | orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.From( 0.0f, orbitAngles.yaw, 0.0f ), slerpAmount ); 79 | orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.From( orbitAngles.pitch + carPitch, 0.0f, 0.0f ), slerpAmount ); 80 | } 81 | else 82 | { 83 | if ( firstPerson ) 84 | { 85 | var targetYaw = 0; 86 | var targetPitch = FirstPersonPitch; 87 | var slerpAmount = Time.Delta * OrbitReturnSmoothingSpeed; 88 | 89 | orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.FromYaw( targetYaw ), slerpAmount ); 90 | orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.FromPitch( targetPitch ), slerpAmount ); 91 | } 92 | else 93 | { 94 | var targetPitch = FixedOrbitPitch.Clamp( MinOrbitPitch, MaxOrbitPitch ); 95 | var targetYaw = !firstPerson && speed < 0.0f ? carRot.Yaw() + 180.0f : carRot.Yaw(); 96 | var slerpAmount = MaxOrbitReturnSpeed > 0.0f ? Time.Delta * (speedAbs / MaxOrbitReturnSpeed).Clamp( 0.0f, OrbitReturnSmoothingSpeed ) : 1.0f; 97 | 98 | orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.FromYaw( targetYaw ), slerpAmount ); 99 | orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.FromPitch( targetPitch + carPitch ), slerpAmount ); 100 | } 101 | 102 | orbitAngles.pitch = orbitPitchRot.Pitch(); 103 | orbitAngles.yaw = orbitYawRot.Yaw(); 104 | orbitAngles = orbitAngles.Normal; 105 | } 106 | 107 | if ( firstPerson ) 108 | { 109 | DoFirstPerson(); 110 | } 111 | else 112 | { 113 | DoThirdPerson( car, body ); 114 | } 115 | 116 | currentFov = MaxFovSpeed > 0.0f ? currentFov.LerpTo( MinFov.LerpTo( MaxFov, speedAbs / MaxFovSpeed ), Time.Delta * FovSmoothingSpeed ) : MaxFov; 117 | FieldOfView = currentFov; 118 | 119 | ApplyShake( speedAbs ); 120 | } 121 | 122 | private void DoFirstPerson() 123 | { 124 | var pawn = Local.Pawn; 125 | if ( pawn == null ) return; 126 | 127 | Pos = pawn.EyePos; 128 | Rot = pawn.Rotation * (orbitYawRot * orbitPitchRot); 129 | 130 | Viewer = pawn; 131 | } 132 | 133 | private void DoThirdPerson( CarEntity car, PhysicsBody body ) 134 | { 135 | Rot = orbitYawRot * orbitPitchRot; 136 | 137 | var carPos = car.Position + car.Rotation * (body.LocalMassCenter * car.Scale); 138 | var startPos = carPos; 139 | var targetPos = startPos + Rot.Backward * (OrbitDistance * car.Scale) + (Vector3.Up * (OrbitHeight * car.Scale)); 140 | 141 | var tr = Trace.Ray( startPos, targetPos ) 142 | .Ignore( car ) 143 | .Radius( Math.Clamp( CollisionRadius * car.Scale, 2.0f, 10.0f ) ) 144 | .WorldOnly() 145 | .Run(); 146 | 147 | Pos = tr.EndPos; 148 | 149 | Viewer = null; 150 | } 151 | 152 | public override void BuildInput( InputBuilder input ) 153 | { 154 | base.BuildInput( input ); 155 | 156 | var pawn = Local.Pawn; 157 | if ( pawn == null ) return; 158 | 159 | var car = (pawn as TerrygeddonPlayer)?.Vehicle as CarEntity; 160 | if ( !car.IsValid() ) return; 161 | 162 | if ( input.Pressed( InputButton.View ) ) 163 | { 164 | firstPerson = !firstPerson; 165 | orbitYawRot = firstPerson ? Rotation.Identity : Rotation.FromYaw( car.Rotation.Yaw() ); 166 | orbitPitchRot = firstPerson ? Rotation.FromPitch( FirstPersonPitch ) : Rotation.Identity; 167 | orbitAngles = (orbitYawRot * orbitPitchRot).Angles(); 168 | orbitEnabled = false; 169 | timeSinceOrbit = 0.0f; 170 | } 171 | 172 | if ( (Math.Abs( input.AnalogLook.pitch ) + Math.Abs( input.AnalogLook.yaw )) > 0.0f ) 173 | { 174 | if ( !orbitEnabled ) 175 | { 176 | orbitAngles = (orbitYawRot * orbitPitchRot).Angles(); 177 | orbitAngles = orbitAngles.Normal; 178 | 179 | orbitYawRot = Rotation.From( 0.0f, orbitAngles.yaw, 0.0f ); 180 | orbitPitchRot = Rotation.From( orbitAngles.pitch, 0.0f, 0.0f ); 181 | } 182 | 183 | orbitEnabled = true; 184 | timeSinceOrbit = 0.0f; 185 | 186 | orbitAngles.yaw += input.AnalogLook.yaw; 187 | orbitAngles.pitch += input.AnalogLook.pitch; 188 | orbitAngles = orbitAngles.Normal; 189 | orbitAngles.pitch = orbitAngles.pitch.Clamp( MinOrbitPitch, MaxOrbitPitch ); 190 | } 191 | 192 | if ( firstPerson ) 193 | { 194 | input.ViewAngles = (car.Rotation * Rotation.From( orbitAngles )).Angles(); 195 | } 196 | else 197 | { 198 | input.ViewAngles = orbitEnabled ? orbitAngles : car.Rotation.Angles(); 199 | } 200 | 201 | input.ViewAngles = input.ViewAngles.Normal; 202 | } 203 | 204 | private void ApplyShake( float speed ) 205 | { 206 | if ( speed < ShakeSpeedThreshold ) 207 | return; 208 | 209 | var pos = (Time.Now % MathF.PI) * ShakeSpeed; 210 | var length = (speed - ShakeSpeedThreshold) / (ShakeMaxSpeed - ShakeSpeedThreshold); 211 | length = length.Clamp( 0, ShakeMaxLength ); 212 | 213 | float x = Noise.Perlin( pos, 0, 0 ) * length; 214 | float y = Noise.Perlin( pos, 5.0f, 0 ) * length; 215 | 216 | Pos += Rot.Right * x + Rot.Up * y; 217 | Rot *= Rotation.FromAxis( Vector3.Up, x ); 218 | Rot *= Rotation.FromAxis( Vector3.Right, y ); 219 | } 220 | } 221 | 222 | -------------------------------------------------------------------------------- /code/entities/car/CarEntity.cs: -------------------------------------------------------------------------------- 1 | using Sandbox; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | [Library( "tg_car", Title = "Car", Spawnable = true )] 6 | public partial class CarEntity : Prop 7 | { 8 | [ConVar.Replicated( "debug_car" )] 9 | public static bool debug_car { get; set; } = false; 10 | 11 | [ConVar.Replicated( "car_accelspeed" )] 12 | public static float car_accelspeed { get; set; } = 500.0f; 13 | 14 | private CarWheel frontLeft; 15 | private CarWheel frontRight; 16 | private CarWheel backLeft; 17 | private CarWheel backRight; 18 | 19 | private float frontLeftDistance; 20 | private float frontRightDistance; 21 | private float backLeftDistance; 22 | private float backRightDistance; 23 | 24 | private bool frontWheelsOnGround; 25 | private bool backWheelsOnGround; 26 | private float accelerateDirection; 27 | private float airRoll; 28 | private float airTilt; 29 | private float grip; 30 | 31 | private RealTimeSince lastReset; // TODO: 32 | [Net] private bool goingToExplode { get; set; } = false; 33 | private float explosionHP = 5.0f; 34 | 35 | [Net] private float WheelSpeed { get; set; } 36 | [Net] private float TurnDirection { get; set; } 37 | [Net] private float AccelerationTilt { get; set; } 38 | [Net] private float TurnLean { get; set; } 39 | 40 | [Net] public float MovementSpeed { get; private set; } 41 | [Net] public bool Grounded { get; private set; } 42 | 43 | private struct InputState 44 | { 45 | public float throttle; 46 | public float turning; 47 | public float breaking; 48 | public float tilt; 49 | public float roll; 50 | 51 | public void Reset() 52 | { 53 | throttle = 0; 54 | turning = 0; 55 | breaking = 0; 56 | tilt = 0; 57 | roll = 0; 58 | } 59 | } 60 | 61 | private InputState currentInput; 62 | 63 | public CarEntity() 64 | { 65 | frontLeft = new CarWheel( this ); 66 | frontRight = new CarWheel( this ); 67 | backLeft = new CarWheel( this ); 68 | backRight = new CarWheel( this ); 69 | } 70 | 71 | [Net] public TerrygeddonPlayer driver { get; private set; } 72 | 73 | private ModelEntity chassis_axle_rear; 74 | private ModelEntity chassis_axle_front; 75 | private ModelEntity wheel0; 76 | private ModelEntity wheel1; 77 | private ModelEntity wheel2; 78 | private ModelEntity wheel3; 79 | 80 | public override void Spawn() 81 | { 82 | base.Spawn(); 83 | 84 | var modelName = "models/car/car.vmdl"; 85 | 86 | SetModel( modelName ); 87 | SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false ); 88 | SetInteractsExclude( CollisionLayer.Player ); 89 | EnableSelfCollisions = false; 90 | 91 | var trigger = new ModelEntity 92 | { 93 | Parent = this, 94 | Position = Position, 95 | Rotation = Rotation, 96 | EnableTouch = true, 97 | CollisionGroup = CollisionGroup.Trigger, 98 | Transmit = TransmitType.Never, 99 | EnableSelfCollisions = false, 100 | }; 101 | 102 | trigger.SetModel( modelName ); 103 | trigger.SetupPhysicsFromModel( PhysicsMotionType.Keyframed, false ); 104 | } 105 | 106 | public override void ClientSpawn() 107 | { 108 | base.ClientSpawn(); 109 | 110 | { 111 | var vehicle_fuel_tank = new ModelEntity(); 112 | vehicle_fuel_tank.SetModel( "entities/modular_vehicle/vehicle_fuel_tank.vmdl" ); 113 | vehicle_fuel_tank.Transform = Transform; 114 | vehicle_fuel_tank.Parent = this; 115 | vehicle_fuel_tank.LocalPosition = new Vector3( 0.75f, 0, 0 ) * 40.0f; 116 | } 117 | 118 | { 119 | chassis_axle_front = new ModelEntity(); 120 | chassis_axle_front.SetModel( "entities/modular_vehicle/chassis_axle_front.vmdl" ); 121 | chassis_axle_front.Transform = Transform; 122 | chassis_axle_front.Parent = this; 123 | chassis_axle_front.LocalPosition = new Vector3( 1.05f, 0, 0.35f ) * 40.0f; 124 | 125 | { 126 | wheel0 = new ModelEntity(); 127 | wheel0.SetModel( "entities/modular_vehicle/wheel_a.vmdl" ); 128 | wheel0.SetParent( chassis_axle_front, "Wheel_Steer_R", new Transform( Vector3.Zero, Rotation.From( 0, 180, 0 ) ) ); 129 | } 130 | 131 | { 132 | wheel1 = new ModelEntity(); 133 | wheel1.SetModel( "entities/modular_vehicle/wheel_a.vmdl" ); 134 | wheel1.SetParent( chassis_axle_front, "Wheel_Steer_L", new Transform( Vector3.Zero, Rotation.From( 0, 0, 0 ) ) ); 135 | } 136 | 137 | { 138 | var chassis_steering = new ModelEntity(); 139 | chassis_steering.SetModel( "entities/modular_vehicle/chassis_steering.vmdl" ); 140 | chassis_steering.SetParent( chassis_axle_front, "Axle_front_Center", new Transform( Vector3.Zero, Rotation.From( -90, 180, 0 ) ) ); 141 | } 142 | } 143 | 144 | { 145 | chassis_axle_rear = new ModelEntity(); 146 | chassis_axle_rear.SetModel( "entities/modular_vehicle/chassis_axle_rear.vmdl" ); 147 | chassis_axle_rear.Transform = Transform; 148 | chassis_axle_rear.Parent = this; 149 | chassis_axle_rear.LocalPosition = new Vector3( -1.05f, 0, 0.35f ) * 40.0f; 150 | 151 | { 152 | var chassis_transmission = new ModelEntity(); 153 | chassis_transmission.SetModel( "entities/modular_vehicle/chassis_transmission.vmdl" ); 154 | chassis_transmission.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Zero, Rotation.From( -90, 180, 0 ) ) ); 155 | } 156 | 157 | { 158 | wheel2 = new ModelEntity(); 159 | wheel2.SetModel( "entities/modular_vehicle/wheel_a.vmdl" ); 160 | wheel2.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Left * (0.7f * 40), Rotation.From( 0, 90, 0 ) ) ); 161 | } 162 | 163 | { 164 | wheel3 = new ModelEntity(); 165 | wheel3.SetModel( "entities/modular_vehicle/wheel_a.vmdl" ); 166 | wheel3.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Right * (0.7f * 40), Rotation.From( 0, -90, 0 ) ) ); 167 | } 168 | } 169 | } 170 | 171 | protected override void UpdatePropData( Model model ) 172 | { 173 | Host.AssertServer(); 174 | 175 | var propInfo = model.GetPropData(); 176 | Health = propInfo.Health; 177 | 178 | // 179 | // If health is unset, set it to 100 180 | // 181 | if ( Health <= 0 ) 182 | Health = 100; 183 | } 184 | protected override void OnDestroy() 185 | { 186 | base.OnDestroy(); 187 | 188 | if ( driver is TerrygeddonPlayer player ) 189 | { 190 | RemoveDriver( player ); 191 | } 192 | } 193 | 194 | public void ResetInput() 195 | { 196 | currentInput.Reset(); 197 | } 198 | 199 | [Event.Tick.Server] 200 | protected void Tick() 201 | { 202 | if ( driver is TerrygeddonPlayer player ) 203 | { 204 | if ( player.LifeState != LifeState.Alive || player.Vehicle != this ) 205 | { 206 | RemoveDriver( player ); 207 | } 208 | } 209 | } 210 | 211 | public override void Simulate( Client owner ) 212 | { 213 | DebugOverlay.ScreenText( $"{Health} {LifeState}" ); 214 | if ( owner == null || !IsServer || goingToExplode ) return; 215 | 216 | if ( Health <= explosionHP ) 217 | { 218 | RemoveDriver( driver ); 219 | _ = AcalariBomba(); 220 | return; 221 | } 222 | 223 | using ( Prediction.Off() ) 224 | { 225 | currentInput.Reset(); 226 | 227 | currentInput.throttle = (Input.Down( InputButton.Forward ) ? 1 : 0) + (Input.Down( InputButton.Back ) ? -1 : 0); 228 | currentInput.turning = (Input.Down( InputButton.Left ) ? 1 : 0) + (Input.Down( InputButton.Right ) ? -1 : 0); 229 | currentInput.breaking = (Input.Down( InputButton.Jump ) ? 1 : 0); 230 | currentInput.tilt = (Input.Down( InputButton.Run ) ? 1 : 0) + (Input.Down( InputButton.Duck ) ? -1 : 0); 231 | currentInput.roll = (Input.Down( InputButton.Left ) ? 1 : 0) + (Input.Down( InputButton.Right ) ? -1 : 0); 232 | } 233 | } 234 | 235 | [Event.Physics.PreStep] 236 | public void OnPrePhysicsStep() 237 | { 238 | if ( !IsServer ) 239 | return; 240 | 241 | var selfBody = PhysicsBody; 242 | if ( !selfBody.IsValid() ) 243 | return; 244 | 245 | var body = selfBody.SelfOrParent; 246 | if ( !body.IsValid() ) 247 | return; 248 | 249 | var dt = Time.Delta; 250 | 251 | body.DragEnabled = false; 252 | 253 | var rotation = selfBody.Rotation; 254 | 255 | accelerateDirection = currentInput.throttle.Clamp( -1, 1 ) * (1.0f - currentInput.breaking); 256 | TurnDirection = TurnDirection.LerpTo( currentInput.turning.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.001f, dt ) ); 257 | 258 | airRoll = airRoll.LerpTo( currentInput.roll.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.0001f, dt ) ); 259 | airTilt = airTilt.LerpTo( currentInput.tilt.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.0001f, dt ) ); 260 | 261 | float targetTilt = 0; 262 | float targetLean = 0; 263 | 264 | var localVelocity = rotation.Inverse * body.Velocity; 265 | 266 | if ( backWheelsOnGround || frontWheelsOnGround ) 267 | { 268 | var forwardSpeed = MathF.Abs( localVelocity.x ); 269 | var speedFraction = MathF.Min( forwardSpeed / 500.0f, 1 ); 270 | 271 | targetTilt = accelerateDirection.Clamp( -1.0f, 1.0f ); 272 | targetLean = speedFraction * TurnDirection; 273 | } 274 | 275 | AccelerationTilt = AccelerationTilt.LerpTo( targetTilt, 1.0f - MathF.Pow( 0.01f, dt ) ); 276 | TurnLean = TurnLean.LerpTo( targetLean, 1.0f - MathF.Pow( 0.01f, dt ) ); 277 | 278 | if ( backWheelsOnGround ) 279 | { 280 | var forwardSpeed = MathF.Abs( localVelocity.x ); 281 | var speedFactor = 1.0f - (forwardSpeed / 5000.0f).Clamp( 0.0f, 1.0f ); 282 | var acceleration = speedFactor * (accelerateDirection < 0.0f ? car_accelspeed * 0.5f : car_accelspeed) * accelerateDirection * dt; 283 | var impulse = rotation * new Vector3( acceleration, 0, 0 ); 284 | body.Velocity += impulse; 285 | } 286 | 287 | RaycastWheels( rotation, true, out frontWheelsOnGround, out backWheelsOnGround, dt ); 288 | var onGround = frontWheelsOnGround || backWheelsOnGround; 289 | var fullyGrounded = (frontWheelsOnGround && backWheelsOnGround); 290 | Grounded = onGround; 291 | 292 | if ( fullyGrounded ) 293 | { 294 | body.Velocity += PhysicsWorld.Gravity * dt; 295 | } 296 | 297 | body.GravityScale = fullyGrounded ? 0 : 1; 298 | 299 | bool canAirControl = false; 300 | 301 | var v = rotation * localVelocity.WithZ( 0 ); 302 | var vDelta = MathF.Pow( (v.Length / 1000.0f).Clamp( 0, 1 ), 5.0f ).Clamp( 0, 1 ); 303 | if ( vDelta < 0.01f ) vDelta = 0; 304 | 305 | if ( debug_car ) 306 | { 307 | DebugOverlay.Line( body.MassCenter, body.MassCenter + rotation.Forward.Normal * 100, Color.White, 0, false ); 308 | DebugOverlay.Line( body.MassCenter, body.MassCenter + v.Normal * 100, Color.Green, 0, false ); 309 | } 310 | 311 | var angle = (rotation.Forward.Normal * MathF.Sign( localVelocity.x )).Normal.Dot( v.Normal ).Clamp( 0.0f, 1.0f ); 312 | angle = angle.LerpTo( 1.0f, 1.0f - vDelta ); 313 | grip = grip.LerpTo( angle, 1.0f - MathF.Pow( 0.001f, dt ) ); 314 | 315 | if ( debug_car ) 316 | { 317 | DebugOverlay.ScreenText( new Vector2( 200, 200 ), $"{grip}" ); 318 | } 319 | 320 | var angularDamping = 0.0f; 321 | angularDamping = angularDamping.LerpTo( 5.0f, grip ); 322 | 323 | body.LinearDamping = 0.0f; 324 | body.AngularDamping = fullyGrounded ? angularDamping : 0.5f; 325 | 326 | if ( onGround ) 327 | { 328 | localVelocity = rotation.Inverse * body.Velocity; 329 | WheelSpeed = localVelocity.x; 330 | var turnAmount = frontWheelsOnGround ? (MathF.Sign( localVelocity.x ) * 25.0f * CalculateTurnFactor( TurnDirection, MathF.Abs( localVelocity.x ) ) * dt) : 0.0f; 331 | body.AngularVelocity += rotation * new Vector3( 0, 0, turnAmount ); 332 | 333 | airRoll = 0; 334 | airTilt = 0; 335 | 336 | var forwardGrip = 0.1f; 337 | forwardGrip = forwardGrip.LerpTo( 0.9f, currentInput.breaking ); 338 | body.Velocity = VelocityDamping( Velocity, rotation, new Vector3( forwardGrip, grip, 0 ), dt ); 339 | } 340 | else 341 | { 342 | var s = selfBody.Position + (rotation * selfBody.LocalMassCenter); 343 | var tr = Trace.Ray( s, s + rotation.Down * 50 ) 344 | .Ignore( this ) 345 | .Run(); 346 | 347 | if ( debug_car ) 348 | DebugOverlay.Line( tr.StartPos, tr.EndPos, tr.Hit ? Color.Red : Color.Green ); 349 | 350 | canAirControl = !tr.Hit; 351 | } 352 | 353 | if ( canAirControl && (airRoll != 0 || airTilt != 0) ) 354 | { 355 | var offset = 50 * Scale; 356 | var s = selfBody.Position + (rotation * selfBody.LocalMassCenter) + (rotation.Right * airRoll * offset) + (rotation.Down * (10 * Scale)); 357 | var tr = Trace.Ray( s, s + rotation.Up * (25 * Scale) ) 358 | .Ignore( this ) 359 | .Run(); 360 | 361 | if ( debug_car ) 362 | DebugOverlay.Line( tr.StartPos, tr.EndPos ); 363 | 364 | bool dampen = false; 365 | 366 | if ( currentInput.roll.Clamp( -1, 1 ) != 0 ) 367 | { 368 | var force = tr.Hit ? 400.0f : 100.0f; 369 | var roll = tr.Hit ? currentInput.roll.Clamp( -1, 1 ) : airRoll; 370 | body.ApplyForceAt( selfBody.MassCenter + rotation.Left * (offset * roll), (rotation.Down * roll) * (roll * (body.Mass * force)) ); 371 | 372 | if ( debug_car ) 373 | DebugOverlay.Sphere( selfBody.MassCenter + rotation.Left * (offset * roll), 8, Color.Red ); 374 | 375 | dampen = true; 376 | } 377 | 378 | if ( !tr.Hit && currentInput.tilt.Clamp( -1, 1 ) != 0 ) 379 | { 380 | var force = 200.0f; 381 | body.ApplyForceAt( selfBody.MassCenter + rotation.Forward * (offset * airTilt), (rotation.Down * airTilt) * (airTilt * (body.Mass * force)) ); 382 | 383 | if ( debug_car ) 384 | DebugOverlay.Sphere( selfBody.MassCenter + rotation.Forward * (offset * airTilt), 8, Color.Green ); 385 | 386 | dampen = true; 387 | } 388 | 389 | if ( dampen ) 390 | body.AngularVelocity = VelocityDamping( body.AngularVelocity, rotation, 0.95f, dt ); 391 | } 392 | 393 | localVelocity = rotation.Inverse * body.Velocity; 394 | MovementSpeed = localVelocity.x; 395 | } 396 | 397 | private static float CalculateTurnFactor( float direction, float speed ) 398 | { 399 | var turnFactor = MathF.Min( speed / 500.0f, 1 ); 400 | var yawSpeedFactor = 1.0f - (speed / 1000.0f).Clamp( 0, 0.6f ); 401 | 402 | return direction * turnFactor * yawSpeedFactor; 403 | } 404 | 405 | private static Vector3 VelocityDamping( Vector3 velocity, Rotation rotation, Vector3 damping, float dt ) 406 | { 407 | var localVelocity = rotation.Inverse * velocity; 408 | var dampingPow = new Vector3( MathF.Pow( 1.0f - damping.x, dt ), MathF.Pow( 1.0f - damping.y, dt ), MathF.Pow( 1.0f - damping.z, dt ) ); 409 | return rotation * (localVelocity * dampingPow); 410 | } 411 | 412 | private void RaycastWheels( Rotation rotation, bool doPhysics, out bool frontWheels, out bool backWheels, float dt ) 413 | { 414 | float forward = 42; 415 | float right = 32; 416 | 417 | var frontLeftPos = rotation.Forward * forward + rotation.Right * right + rotation.Up * 20; 418 | var frontRightPos = rotation.Forward * forward - rotation.Right * right + rotation.Up * 20; 419 | var backLeftPos = -rotation.Forward * forward + rotation.Right * right + rotation.Up * 20; 420 | var backRightPos = -rotation.Forward * forward - rotation.Right * right + rotation.Up * 20; 421 | 422 | var tiltAmount = AccelerationTilt * 2.5f; 423 | var leanAmount = TurnLean * 2.5f; 424 | 425 | float length = 20.0f; 426 | 427 | frontWheels = 428 | frontLeft.Raycast( length + tiltAmount - leanAmount, doPhysics, frontLeftPos * Scale, ref frontLeftDistance, dt ) | 429 | frontRight.Raycast( length + tiltAmount + leanAmount, doPhysics, frontRightPos * Scale, ref frontRightDistance, dt ); 430 | 431 | backWheels = 432 | backLeft.Raycast( length - tiltAmount - leanAmount, doPhysics, backLeftPos * Scale, ref backLeftDistance, dt ) | 433 | backRight.Raycast( length - tiltAmount + leanAmount, doPhysics, backRightPos * Scale, ref backRightDistance, dt ); 434 | } 435 | 436 | float wheelAngle = 0.0f; 437 | float wheelRevolute = 0.0f; 438 | 439 | [Event.Frame] 440 | public void OnFrame() 441 | { 442 | wheelAngle = wheelAngle.LerpTo( TurnDirection * 25, 1.0f - MathF.Pow( 0.001f, Time.Delta ) ); 443 | wheelRevolute += (WheelSpeed / (14.0f * Scale)).RadianToDegree() * Time.Delta; 444 | 445 | var wheelRotRight = Rotation.From( -wheelAngle, 180, -wheelRevolute ); 446 | var wheelRotLeft = Rotation.From( wheelAngle, 0, wheelRevolute ); 447 | var wheelRotBackRight = Rotation.From( 0, 90, -wheelRevolute ); 448 | var wheelRotBackLeft = Rotation.From( 0, -90, wheelRevolute ); 449 | 450 | RaycastWheels( Rotation, false, out _, out _, Time.Delta ); 451 | 452 | float frontOffset = 20.0f - Math.Min( frontLeftDistance, frontRightDistance ); 453 | float backOffset = 20.0f - Math.Min( backLeftDistance, backRightDistance ); 454 | 455 | chassis_axle_front.SetBoneTransform( "Axle_front_Center", new Transform( Vector3.Up * frontOffset ), false ); 456 | chassis_axle_rear.SetBoneTransform( "Axle_Rear_Center", new Transform( Vector3.Up * backOffset ), false ); 457 | 458 | wheel0.LocalRotation = wheelRotRight; 459 | wheel1.LocalRotation = wheelRotLeft; 460 | wheel2.LocalRotation = wheelRotBackRight; 461 | wheel3.LocalRotation = wheelRotBackLeft; 462 | } 463 | 464 | public void RemoveDriver( TerrygeddonPlayer player ) 465 | { 466 | driver = null; 467 | player.Vehicle = null; 468 | player.VehicleController = null; 469 | player.VehicleAnimator = null; 470 | player.VehicleCamera = null; 471 | player.Parent = null; 472 | player.PhysicsBody.Enabled = true; 473 | player.PhysicsBody.Position = player.Position; 474 | 475 | ResetInput(); 476 | } 477 | 478 | public void AddDriver( Entity user ) 479 | { 480 | if ( user is TerrygeddonPlayer player && player.Vehicle == null ) 481 | { 482 | player.Vehicle = this; 483 | player.VehicleController = new CarController(); 484 | player.VehicleAnimator = new CarAnimator(); 485 | player.VehicleCamera = new CarCamera(); 486 | player.Parent = this; 487 | player.LocalPosition = Vector3.Up * 10; 488 | player.LocalRotation = Rotation.Identity; 489 | player.LocalScale = 1; 490 | player.PhysicsBody.Enabled = false; 491 | 492 | driver = player; 493 | } 494 | } 495 | 496 | public override void StartTouch( Entity other ) 497 | { 498 | base.StartTouch( other ); 499 | 500 | if ( !IsServer ) 501 | return; 502 | 503 | var body = PhysicsBody; 504 | if ( !body.IsValid() ) 505 | return; 506 | 507 | body = body.SelfOrParent; 508 | if ( !body.IsValid() ) 509 | return; 510 | 511 | if ( other is TerrygeddonPlayer player && player.Vehicle == null ) 512 | { 513 | var speed = body.Velocity.Length; 514 | var forceOrigin = Position + Rotation.Down * Rand.Float( 20, 30 ); 515 | var velocity = (player.Position - forceOrigin).Normal * speed; 516 | var angularVelocity = body.AngularVelocity; 517 | 518 | OnPhysicsCollision( new CollisionEventData 519 | { 520 | Entity = player, 521 | Pos = player.Position + Vector3.Up * 50, 522 | Velocity = velocity, 523 | PreVelocity = velocity, 524 | PostVelocity = velocity, 525 | PreAngularVelocity = angularVelocity, 526 | Speed = speed, 527 | } ); 528 | } 529 | } 530 | 531 | protected override void OnPhysicsCollision( CollisionEventData eventData ) 532 | { 533 | if ( !IsServer ) 534 | return; 535 | 536 | if ( eventData.Entity is TerrygeddonPlayer player && player.Vehicle != null ) 537 | { 538 | return; 539 | } 540 | 541 | var propData = GetModelPropData(); 542 | 543 | var minImpactSpeed = propData.MinImpactDamageSpeed; 544 | if ( minImpactSpeed <= 0.0f ) minImpactSpeed = 500; 545 | 546 | var impactDmg = propData.ImpactDamage; 547 | if ( impactDmg <= 0.0f ) impactDmg = 10; 548 | 549 | var speed = eventData.Speed; 550 | 551 | if ( speed > minImpactSpeed ) 552 | { 553 | if ( eventData.Entity.IsValid() && eventData.Entity != this ) 554 | { 555 | var damage = speed / minImpactSpeed * impactDmg * 1.2f; 556 | eventData.Entity.TakeDamage( DamageInfo.Generic( damage ) 557 | .WithFlag( DamageFlags.PhysicsImpact ) 558 | .WithFlag( DamageFlags.Vehicle ) 559 | .WithAttacker( driver != null ? driver : this, driver != null ? this : null ) 560 | .WithPosition( eventData.Pos ) 561 | .WithForce( eventData.PreVelocity ) ); 562 | } 563 | } 564 | } 565 | 566 | public override void OnKilled() 567 | { 568 | base.OnKilled(); 569 | 570 | _ = AcalariBomba(); 571 | } 572 | 573 | private async Task AcalariBomba() 574 | { 575 | if ( goingToExplode ) 576 | return; 577 | goingToExplode = true; 578 | 579 | if (Health > 0) 580 | await Task.DelaySeconds( Health / explosionHP * 5 ); 581 | if ( LifeState == LifeState.Dead || Health > explosionHP ) 582 | { 583 | goingToExplode = false; 584 | return; 585 | } 586 | OnExplosion(); 587 | 588 | Delete(); 589 | } 590 | 591 | private void OnExplosion() 592 | { 593 | LifeState = LifeState.Dead; 594 | 595 | var model = GetModel(); 596 | if ( model == null || model.IsError ) 597 | return; 598 | 599 | if ( !PhysicsBody.IsValid() ) 600 | return; 601 | 602 | var explosionBehavior = new ModelExplosionBehavior() { Radius = 150.0f, Force = 200.0f, Damage = 50.0f }; //model.GetExplosionBehavior(); 603 | 604 | if ( !string.IsNullOrWhiteSpace( explosionBehavior.Sound ) ) 605 | { 606 | Sound.FromWorld( explosionBehavior.Sound, PhysicsBody.MassCenter ); 607 | } 608 | else 609 | { 610 | // TODO: Replace with something else 611 | Sound.FromWorld( "rust_pumpshotgun.shootdouble", PhysicsBody.MassCenter ); 612 | } 613 | 614 | if ( !string.IsNullOrWhiteSpace( explosionBehavior.Effect ) ) 615 | { 616 | Particles.Create( explosionBehavior.Effect, PhysicsBody.MassCenter ); 617 | } 618 | else 619 | { 620 | Particles.Create( "particles/explosion", PhysicsBody.MassCenter ); 621 | } 622 | 623 | if ( explosionBehavior.Radius > 0.0f ) 624 | { 625 | var sourcePos = PhysicsBody.MassCenter; 626 | var overlaps = Physics.GetEntitiesInSphere( sourcePos, explosionBehavior.Radius ); 627 | 628 | if ( debug_prop_explosion ) 629 | DebugOverlay.Sphere( sourcePos, explosionBehavior.Radius, Color.Orange, true, 5 ); 630 | 631 | foreach ( var overlap in overlaps ) 632 | { 633 | if ( overlap is not ModelEntity ent || !ent.IsValid() ) 634 | continue; 635 | 636 | if ( ent.LifeState != LifeState.Alive ) 637 | continue; 638 | 639 | if ( !ent.PhysicsBody.IsValid() ) 640 | continue; 641 | 642 | if ( ent.IsWorld ) 643 | continue; 644 | 645 | var targetPos = ent.PhysicsBody.MassCenter; 646 | 647 | var dist = Vector3.DistanceBetween( sourcePos, targetPos ); 648 | if ( dist > explosionBehavior.Radius ) 649 | continue; 650 | 651 | var tr = Trace.Ray( sourcePos, targetPos ) 652 | .Ignore( this ) 653 | .WorldOnly() 654 | .Run(); 655 | 656 | if ( tr.Fraction < 1.0f ) 657 | { 658 | if ( debug_prop_explosion ) 659 | DebugOverlay.Line( sourcePos, tr.EndPos, Color.Red, 5, true ); 660 | 661 | continue; 662 | } 663 | 664 | if ( debug_prop_explosion ) 665 | DebugOverlay.Line( sourcePos, targetPos, 5, true ); 666 | 667 | var distanceMul = 1.0f - Math.Clamp( dist / explosionBehavior.Radius, 0.0f, 1.0f ); 668 | var damage = explosionBehavior.Damage * distanceMul; 669 | var force = (explosionBehavior.Force * distanceMul) * ent.PhysicsBody.Mass; 670 | var forceDir = (targetPos - sourcePos).Normal; 671 | 672 | ent.TakeDamage( DamageInfo.Explosion( sourcePos, forceDir * force, damage ) 673 | .WithAttacker( this ) ); 674 | } 675 | } 676 | } 677 | } 678 | --------------------------------------------------------------------------------