{"TotalCount":38,"Files":[{"Ident":"straightgas.graveyard_gunners","Path":"Pulse.cs","FileName":"Pulse.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\npublic sealed class Pulse : Component\r\n{\r\n\t[Doo.ArgumentHint\u003CSpriteRenderer\u003E( \u0022RedCircle\u0022 )]\r\n\t[Property] public Doo OnEnabledDoo { get; set; }\r\n\t\r\n\t[Property] public SpriteRenderer PulseRenderer;\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"RammingControl.cs","FileName":"RammingControl.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\n\r\npublic sealed class RammingControl : Component, Component.ITriggerListener\r\n{\r\n\t[Property] Rigidbody CarBody;\r\n\r\n\tpublic void OnTriggerEnter( GameObject other )\r\n\t{\r\n\t\tif ( !other.Tags.Has( \u0022enemy\u0022 ) ) return;\r\n\t\tif (CarBody.Velocity.Length \u003C 800) return;\r\n\t\tLog.Info( $\u0022Rammed {other.Name}\u0022 );\r\n\r\n\t\t// Stagger and Knock Zombie\r\n\t\tif ( other.GetComponent\u003CZombieBrain\u003E() != null )\r\n\t\t{\r\n\t\t\tother.GetComponent\u003CZombieBrain\u003E().CurrentState = ZombieState.Staggered;\r\n\t\t\tother.GetComponent\u003CZombieBrain\u003E().KnockBack = \r\n\t\t\t\tMath.Max( CarBody.Velocity.Length.Remap( 0, 4000), other.GetComponent\u003CZombieBrain\u003E().KnockBack \u002B CarBody.Velocity.Length.Remap( 0, 4000) );\r\n\t\t}\r\n\r\n\t\t// Apply Impulse and Damage to Enemy\r\n\t\tif ( other.GetComponent\u003CRigidbody\u003E() != null ) other.GetComponent\u003CRigidbody\u003E().ApplyImpulse(Vector3.Up * CarBody.Velocity.Length.Remap( 0, 4000, 0, 100000 ) );\r\n\t\t\r\n\t\t// Damage\r\n\t\tother.GetComponent\u003CHealthSystem\u003E().Damage( CarBody.Velocity.Length.Remap(0, 4000, 0, 250) );\r\n\t\t// Damage MonsterSpawner\r\n\t\tif ( other.GetComponent\u003CMonsterSpawner\u003E() != null ) other.GetComponent\u003CHealthSystem\u003E().Damage( CarBody.Velocity.Length.Remap(0, 4000, 0, 5000) );\r\n\r\n\t\tSound.Play( \u0022sounds/bullet-impact-flesh.sound\u0022, WorldPosition);\r\n\t\tif ( other.Tags.Has( \u0022cow\u0022 ) ) \r\n\t\t{ \r\n\t\t\tSoundHandle screamHandle = Sound.Play( other.GetComponent\u003CKuhBall\u003E().DamageSound, other.WorldPosition );\r\n\t\t\tscreamHandle.Parent = other;\r\n\t\t\tscreamHandle.FollowParent = true;\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"AchievementTriggerzone.cs","FileName":"AchievementTriggerzone.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\npublic sealed class AchievementTriggerzone : Component, Component.ITriggerListener\r\n{\r\n\t[Property, Description( \u0022Lowercase letters, numbers and . _ - / only. \\nNo spaces or other special characters.\u0022 )] string AchievementName { get; set; }\r\n\t[Property] bool SendTime { get; set; } = false;\r\n\tHighscoreManager highscoreManager;\r\n\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tif ( SendTime ) \r\n\t\t{ \r\n\t\t\thighscoreManager = Scene.Get\u003CHighscoreManager\u003E();\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void OnTriggerEnter( GameObject other )\r\n\t{\r\n\t\tif ( other.Tags.Has( \u0022player\u0022 ) )\r\n\t\t{\r\n\t\t\tif ( AchievementName == null ) { Log.Error( $\u0022{GameObject}: AchievementName is null\u0022 ); return; }\r\n\t\t\tSandbox.Services.Achievements.Unlock( AchievementName );\r\n\t\t\tLog.Info( $\u0022{AchievementName} unlocked!\u0022 );\r\n\r\n\t\t\tif (SendTime)\r\n\t\t\t{\r\n\t\t\t\tSandbox.Services.Stats.SetValue(AchievementName\u002B\u0022leaderboard\u0022, highscoreManager.TimeSpanSinceGameStart.Seconds);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"DestructableProp.cs","FileName":"DestructableProp.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing static HealthSystem;\r\n\r\npublic sealed class DestructableProp : Component, HealthSystem.IHealthEvent\r\n{\r\n\t[Property] GameObject DestroyedPrefab;\r\n\r\n\tpublic void OnDeath()\r\n\t{\r\n\t\tGameObject _deadClone = DestroyedPrefab.Clone(WorldPosition, WorldRotation, WorldScale);\r\n\t\tRigidbody _rb = _deadClone.GetComponent\u003CRigidbody\u003E();\r\n\t\t_rb.ApplyImpulse( Vector3.Up * 300 * _rb.Mass );\r\n\r\n\t\tGameObject.Destroy();\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"MonsterManager.cs","FileName":"MonsterManager.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Linq;\r\n\r\npublic sealed class MonsterManager : Component\r\n{\r\n    [Property] GameObject MonsterSpawnerPrefab;\r\n    [Property] GameObject ZombiePrefab;\r\n    [Property] GameObject GhostPrefab;\r\n    [Property] GameObject VampirePrefab;\r\n\r\n\t[Property] BBox SpawnArea;\r\n\r\n\t[Property] Curve ZombieCurve;\r\n    [Property] Curve GhostCurve;\r\n    [Property] Curve VampireCurve;\r\n    float TotalSpawnerLimit;\r\n\r\n    List\u003CGameObject\u003E ActiveZombieSpawner;\r\n    List\u003CGameObject\u003E ActiveGhostSpawner;\r\n    List\u003CGameObject\u003E ActiveVampireSpawner;\r\n\r\n    [Property] float CloneFrequenz;\r\n\t[Property] Curve SpawnCooldownCurve;\r\n\r\n    TimeSince SinceGameStart;\r\n    TimeUntil UntilNextClone;\r\n    Random random;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        ActiveZombieSpawner = new List\u003CGameObject\u003E();\r\n        ActiveGhostSpawner = new List\u003CGameObject\u003E();\r\n        ActiveVampireSpawner = new List\u003CGameObject\u003E();\r\n\r\n        SinceGameStart = 0;\r\n        UntilNextClone = CloneFrequenz;\r\n\r\n        random = new Random();\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n\t\t// DebugOverlay.Box( SpawnArea, Color.Green );\r\n\t\t// Listen aufr\u00E4umen bevor neue Spawner gecloned werden\r\n\r\n\t\tActiveZombieSpawner.RemoveAll( x =\u003E !x.IsValid );\r\n        ActiveGhostSpawner.RemoveAll( x =\u003E !x.IsValid );\r\n        ActiveVampireSpawner.RemoveAll( x =\u003E !x.IsValid );\r\n\r\n\r\n        int GesamtzahlAktiveSpawner = ActiveZombieSpawner.Count \r\n            \u002B ActiveGhostSpawner.Count \r\n            \u002B ActiveVampireSpawner.Count;\r\n        \r\n        TotalSpawnerLimit = ZombieCurve.Evaluate(SinceGameStart / 60) \r\n            \u002B GhostCurve.Evaluate(SinceGameStart / 60) \r\n            \u002B VampireCurve.Evaluate(SinceGameStart / 60);\r\n\r\n\t\tif (GesamtzahlAktiveSpawner \u003E= TotalSpawnerLimit) { UntilNextClone = CloneFrequenz; }\r\n\r\n        // Wenn das Limit nicht auschgesch\u00F6pft ist und UntilNextClone, dann im Verh\u00E4ltnis niedrigsten vorhandenen Spawner clonen\r\n        if ((float)GesamtzahlAktiveSpawner \u003C TotalSpawnerLimit \u0026\u0026 UntilNextClone)\r\n        {\r\n            float zombieDifferenz = ZombieCurve.Evaluate(SinceGameStart / 60) - ActiveZombieSpawner.Count;\r\n            float ghostDifferenz = GhostCurve.Evaluate(SinceGameStart / 60) - ActiveGhostSpawner.Count;\r\n            float vampireDifferenz = VampireCurve.Evaluate(SinceGameStart / 60) - ActiveVampireSpawner.Count;\r\n\r\n            zombieDifferenz = zombieDifferenz.Remap(0, ZombieCurve.Evaluate(SinceGameStart / 60));\r\n            ghostDifferenz = ghostDifferenz.Remap(0, GhostCurve.Evaluate(SinceGameStart / 60));\r\n            vampireDifferenz = vampireDifferenz.Remap(0, VampireCurve.Evaluate(SinceGameStart/ 60));\r\n\r\n            float[] differenzen = {zombieDifferenz, ghostDifferenz, vampireDifferenz};\r\n\r\n            // Index von gr\u00F6\u00DFter Differenz finden und entsprechendes Prefab clonen\r\n            int differenzenMaxIndex = Array.IndexOf(differenzen, differenzen.Max());\r\n            switch (differenzenMaxIndex)\r\n            {\r\n                default: \r\n                    Log.Error(\u0022MonsterManager: OnFixedUpdate() DEFAULT\u0022);\r\n                    break;\r\n\r\n                case 0: // Zombie\r\n                    CloneSpawner(ZombiePrefab, differenzenMaxIndex);\r\n                    break;\r\n\r\n                case 1: // Ghost\r\n                    CloneSpawner(GhostPrefab, differenzenMaxIndex);\r\n                    break;\r\n\r\n                case 2: // Vampire\r\n                    CloneSpawner(VampirePrefab, differenzenMaxIndex);\r\n                    break;\r\n            }\r\n            UntilNextClone = CloneFrequenz;\r\n        }\r\n\r\n    }\r\n\r\n    void CloneSpawner(GameObject prefab, int indexCase)\r\n    {\r\n\t\t// Scale? Spawnfreq? Spawnanzahl?\r\n\r\n\t\t// Valid Spawnpos check\r\n\t\tVector3 possibleSpawnPosition = SpawnArea.RandomPointInside;\r\n\r\n\t\tSceneTraceResult spawnPosTrace = Scene.Trace.Sphere(300, possibleSpawnPosition, possibleSpawnPosition \u002B Vector3.Down * 1000)\r\n\t\t\t.WithoutTags(\u0022world\u0022, \u0022enemy\u0022)\r\n\t\t\t.Run();\r\n\r\n\t\twhile ( spawnPosTrace.Hit ) \r\n\t\t{\r\n\t\t\tpossibleSpawnPosition = SpawnArea.RandomPointInside;\r\n\r\n\t\t\tspawnPosTrace = Scene.Trace.Sphere( 300, possibleSpawnPosition, possibleSpawnPosition \u002B Vector3.Down * 1000 )\r\n\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t.WithoutTags( \u0022World\u0022, \u0022enemy\u0022 )\r\n\t\t\t\t.Run();\r\n\t\t\t\r\n\t\t\tDebugOverlay.Trace(spawnPosTrace);\r\n\t\t\tLog.Info( \u0022Spawnpos trace hit, trying new position\u0022);\r\n\t\t}\r\n\r\n\t\tspawnPosTrace = Scene.Trace.Ray( possibleSpawnPosition, possibleSpawnPosition \u002B Vector3.Down * 1000 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithTag( \u0022World\u0022 )\r\n\t\t\t.Run();\r\n\t\tVector3 actualSpawnPosition = possibleSpawnPosition.WithZ(spawnPosTrace.EndPosition.z);\r\n\r\n\t\tGameObject newSpawner = MonsterSpawnerPrefab.Clone(actualSpawnPosition, WorldRotation.Angles().WithYaw(random.Int(0, 359)), Vector3.One);\r\n        newSpawner.GetComponent\u003CMonsterSpawner\u003E().MonsterPrefab = prefab;\r\n\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().MonsterSpawnCooldown = SpawnCooldownCurve.Evaluate(SinceGameStart / 60);\r\n\r\n\t\tLog.Info( \u0022Cloning spawner for prefab: \u0022 \u002B prefab.Name \u002B \u0022 at \u0022 \u002B newSpawner.WorldPosition );\r\n\r\n\t\tswitch (indexCase)\r\n        {\r\n            default:\r\n                Log.Error(\u0022Monster Manager: CloneSpawner() DEFAULT\u0022);\r\n                break;\r\n\r\n            case 0: // Zombie\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().LineRenderer.Color = Color.Average( new Color[] { Color.Green * 3, Color.White * .1f } ) * 8;\r\n\t\t\t\tActiveZombieSpawner.Add(newSpawner);\r\n\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().MaxMonsterSpawns = random.Int(7, 10);\r\n\r\n\t\t\t\tbreak;\r\n\r\n            case 1: // Ghost\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().LineRenderer.Color = Color.Average( new Color[] { Color.Blue * 3, Color.White * .1f } ) * 8;\r\n\t\t\t\tActiveGhostSpawner.Add(newSpawner);\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().MaxMonsterSpawns = random.Int(4, 7);\r\n\r\n\t\t\t\tbreak;\r\n\r\n            case 2: // Vampire\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().LineRenderer.Color = Color.Average( new Color[] { Color.Red * 3, Color.White * .1f } )  * 8;\r\n\t\t\t\tActiveVampireSpawner.Add(newSpawner);\r\n\t\t\t\tnewSpawner.GetComponent\u003CMonsterSpawner\u003E().MaxMonsterSpawns = random.Int(2, 4);\r\n                break;\r\n        }\r\n    }\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"MonsterSpawner.cs","FileName":"MonsterSpawner.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing static HealthSystem;\r\n\r\npublic sealed class MonsterSpawner : Component, HealthSystem.IHealthEvent\r\n{\r\n\t[Property] GameObject DeadMonsterSpawner;\r\n\t[Property] public LineRenderer LineRenderer;\r\n\t[Property] GameObject BlitzPrefab;\r\n\t[Property] public GameObject MonsterPrefab; // Wird von MonsterManager gepassed\r\n\tpublic float MonsterSpawnStartDelay = 1f;\r\n\tpublic float MonsterSpawnCooldown = 1f;\r\n\tpublic float MonsterScaleFactor = 1f;\r\n\t[Property] public int MaxMonsterSpawns = 1;\r\n\r\n\tList\u003CGameObject\u003E SpawnedMonsters;\r\n\r\n\tTimeUntil UntilNextSpawn;\r\n\tRandom random;\r\n\r\n\tpublic interface IMonsterSpawnerEvent : ISceneEvent\u003CIMonsterSpawnerEvent\u003E\r\n\t{\r\n\t\tvoid OnMonsterSpawn();\r\n\t}\r\n\r\n\tvoid IHealthEvent.OnDeath()\r\n\t{\r\n\t\tSound.Play( \u0022sounds/spawner-dies.sound\u0022, WorldPosition);\r\n\r\n\t\tSpawnedMonsters.RemoveAll( x =\u003E !x.IsValid );\r\n\r\n\t\tforeach (var monster in SpawnedMonsters)\r\n\t\t{\r\n\t\t\tif ( monster == null ) { return; }\r\n\r\n\t\t\tGameObject newBlitz = BlitzPrefab.Clone(monster.GetComponentInChildren\u003CLineRenderer\u003E().Points[0].WorldPosition, monster.WorldRotation.Angles().WithYaw(random.Float(0, 360)), new Vector3(10, 10, 10));\r\n\t\t\tnewBlitz.SetParent( monster.GetComponentInChildren\u003CLineRenderer\u003E().GameObject );\r\n\t\t\tnewBlitz.GetComponent\u003CBlitz\u003E().MonsterLineColor = LineRenderer.Color.Evaluate( 0.5f );\r\n\t\t\tnewBlitz.GetComponent\u003CBlitz\u003E().LightningStrike(0.2f);\r\n\r\n\r\n\t\t\t// HealthSystem holen, wenn nicht da aus Children holen und damagen\r\n\t\t\tHealthSystem healthSystem = monster.GetComponent\u003CHealthSystem\u003E();\r\n\t\t\tif ( healthSystem != null ) healthSystem.Damage( healthSystem.CurrentHealth * 0.8f );\r\n\t\t\telse { monster.GetComponentInChildren\u003CHealthSystem\u003E().Damage( monster.GetComponentInChildren\u003CHealthSystem\u003E().CurrentHealth * 0.8f ); }\r\n\t\t}\r\n\t\tGameObject newDeadMonsterSpawner = DeadMonsterSpawner.Clone(WorldPosition \u002B Vector3.Up, WorldRotation, WorldScale);\r\n\t\tforeach ( GameObject child in newDeadMonsterSpawner.Children ) \r\n\t\t{\r\n\t\t\tchild.GetComponent\u003CRigidbody\u003E().ApplyImpulse( (child.WorldPosition - GameObject.WorldPosition).Normal * 3 * child.GetComponent\u003CRigidbody\u003E().Mass );\r\n\t\t\tchild.GetComponent\u003CRigidbody\u003E().AngularVelocity = random.VectorInSphere( random.Float( 3, 5 ) );\r\n\t\t}\r\n\t}\r\n\r\n\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tSpawnedMonsters = new List\u003CGameObject\u003E();\r\n\r\n\t\trandom = new Random();\r\n\r\n\t\tUntilNextSpawn = MonsterSpawnStartDelay;\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\t// Solange Max Monsters gespawned sind ist bleibt der NextSpawn auf Cooldown\r\n\t\tif ( SpawnedMonsters.Count \u003E= MaxMonsterSpawns ) UntilNextSpawn = MonsterSpawnCooldown;\r\n\r\n\t\tif (true)\r\n\t\t{\r\n\t\t    SpawnedMonsters.RemoveAll( x =\u003E !x.IsValid );\t\r\n\t\t}\r\n\r\n        // Wenn NextSpawn abgelaufen ist\r\n\t\tif ( UntilNextSpawn )\r\n\t\t{\r\n\t\t\tSpawnMonster();\r\n\t\t\tUntilNextSpawn = MonsterSpawnCooldown;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid SpawnMonster()\r\n\t{\r\n\t\t// Particle Effekt, Sound?\r\n\t\tGameObject currentMonster = MonsterPrefab.Clone( WorldPosition \u002B (Vector3)random.VectorInCircle() * random.Int(200, 500), WorldRotation, Vector3.One * MonsterScaleFactor * random.Float(0.9f, 1.1f) );\r\n\t\tSpawnedMonsters.Add(currentMonster);\r\n\t\tIMonsterSpawnerEvent.PostToGameObject( this.GameObject, x =\u003E x.OnMonsterSpawn() );\r\n\t}\r\n\r\n\tprivate void BlitzMonster( GameObject monster )\r\n\t{\r\n\t\tGameObject newBlitz = BlitzPrefab.Clone( WorldPosition, WorldRotation, Vector3.One * 0.1f );\r\n\t\tLineRenderer blitzLine = newBlitz.GetComponentInChildren\u003CLineRenderer\u003E();\r\n\t\tnewBlitz.GetComponent\u003CBlitz\u003E().MonsterSpawner = GetComponent\u003CMonsterSpawner\u003E();\r\n\t\tblitzLine.Points[0].WorldPosition = WorldPosition \u002B Vector3.Up * 50;\r\n\t\tblitzLine.Points[1].WorldPosition = WorldPosition \u002B random.VectorInSphere().Normal * 16 \u002B (monster.WorldPosition - WorldPosition) * 0.33f \u002B Vector3.Up * 150;\r\n\t\tblitzLine.Points[2].WorldPosition = WorldPosition \u002B random.VectorInSphere().Normal * 16 \u002B (monster.WorldPosition - WorldPosition) * 0.66f \u002B Vector3.Up * 150;\r\n\t\tblitzLine.Points[3].WorldPosition = monster.WorldPosition \u002B Vector3.Up * 150;\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"EngineSounds.cs","FileName":"EngineSounds.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Audio;\r\nusing static WheelController;\r\n\r\npublic sealed class EngineSounds : Component\r\n{\r\n\t[Property] Rigidbody CarBody;\r\n\tWheelController WheelController;\r\n\r\n\t[Property] SoundPointComponent IdleSound { get; set; }\r\n\t[Property] SoundPointComponent GasSound { get; set; }\r\n\t[Property] SoundPointComponent VollGasSound { get; set; }\r\n\t[Property] public SoundPointComponent ReifenSound { get; set; }\r\n\r\n\tprivate SoundHandle idleHandle;\r\n\tprivate SoundHandle gasHandle;\r\n\tprivate SoundHandle vollGasHandle;\r\n\tprivate SoundHandle reifenHandle;\r\n\r\n\tfloat reifenSpeed;\r\n\r\n\tTimeSince SinceSoundRestart;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tWheelController = GameObject.GetComponent\u003CWheelController\u003E();\r\n\r\n\t\t/*\r\n\t\t// Alle Sounds geloopt starten, aber initial stumm\r\n\t\tidleHandle = Sound.Play( IdleSound, CarBody.WorldPosition );\r\n\t\tgasHandle = Sound.Play( GasSound, WorldPosition );\r\n\t\tvollGasHandle = Sound.Play( VollGasSound, WorldPosition );\r\n\t\treifenHandle = Sound.Play( ReifenSound, WorldPosition );\r\n\r\n\t\tidleHandle.Parent = CarBody.GameObject.Children.First();\r\n\t\tgasHandle.Parent = CarBody.GameObject.Children.First();\r\n\t\tvollGasHandle.Parent = CarBody.GameObject.Children.First();\r\n\t\treifenHandle.Parent = CarBody.GameObject.Children.First();\r\n\r\n\t\tidleHandle.FollowParent = true;\r\n\t\tgasHandle.FollowParent = true;\r\n\t\tvollGasHandle.FollowParent = true;\r\n\t\treifenHandle.FollowParent = true;\r\n\r\n\t\tidleHandle.Volume = 0;\r\n\t\tgasHandle.Volume = 0;\r\n\t\tvollGasHandle.Volume = 0;\r\n\t\treifenHandle.Volume = 0;\r\n\t\t*/\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tif (SinceSoundRestart \u003E 10f)\r\n\t\t{\r\n\t\t\tLog.Info( \u0022Restarting Engine Sounds\u0022 );\r\n\r\n\t\t\tIdleSound.Enabled = false;\r\n\t\t\t// GasSound.Enabled = false;\r\n\t\t\tVollGasSound.Enabled = false;\r\n\t\t\tReifenSound.Enabled = false;\r\n\r\n\t\t\tIdleSound.Enabled = true;\r\n\t\t\t// GasSound.Enabled = true;\r\n\t\t\tVollGasSound.Enabled = true;\r\n\t\t\tReifenSound.Enabled = true;\r\n\r\n\t\t\tSinceSoundRestart = 0f;\r\n\t\t}\r\n\r\n\r\n\t\t// Reifen Speed Durchschitt\r\n\t\treifenSpeed = (WheelController.RearLeft.SpinSpeed \u002B WheelController.RearRight.SpinSpeed \u002B WheelController.FrontLeft.SpinSpeed \u002B WheelController.FrontRight.SpinSpeed) * 0.25f;\r\n\r\n\t\tIdleSound.Volume = CarBody.Velocity.Length.Remap(0, 3600, 0.2f, 0.4f);\r\n\t\t//gasHandle.Volume = reifenSpeed.Remap( 100, 3000, 0.1f, 1 );\r\n\t\tVollGasSound.Volume = CarBody.Velocity.Length.Remap( 2000, 3200, 0, 0.6f );\r\n\t\tReifenSound.Volume = CarBody.Velocity.Length.Remap( 100, 3000, 0, 0.5f );\r\n\r\n\t\tIdleSound.Pitch = CarBody.Velocity.Length.Remap( 1000, 3600, 0.8f, 1.6f );\r\n\t\t// gasHandle.Pitch = reifenSpeed.Remap( 100, 3000, 0.1f, 1 );\r\n\t\tVollGasSound.Pitch = CarBody.Velocity.Length.Remap( 2000, 3200, 0.8f, 1.2f );\r\n\t\t// reifenHandle.Pitch = reifenSpeed.Remap( 100, 6000, 0, 1 );\r\n\t}\r\n\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"GhostBrain.cs","FileName":"GhostBrain.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Threading.Tasks;\r\nusing static Ballistics;\r\nusing static HealthSystem;\r\n\r\npublic enum GhostState { Culled, Idle, Moving, Attack, Staggered }\r\n\r\n\r\npublic sealed class GhostBrain : Component, HealthSystem.IHealthEvent\r\n{\r\n\tGameObject Player;\r\n\tRigidbody PlayerBody;\r\n\t[Property] GameObject GhostBall;\r\n\t[Property] GameObject DeadGhost;\r\n\t[Property] TextRenderer StateDebugText;\r\n\t[Property] bool DebugMode;\r\n\t[Property] GameObject BloodDecal;\r\n\t[Property] SoundEvent AttackSound;\r\n\t[Property] SoundEvent IdleSound;\r\n\t[Property] SoundEvent DeathSound;\r\n\r\n\tpublic GhostState CurrentState;\r\n\r\n\tSceneTraceResult GroundTrace;\r\n\r\n\tVector3 HorizontalOffset;\r\n\tpublic Vector3 TargetPosition;\r\n\tVector3 SchwebeMittelPunkt;\r\n\tfloat SchwebeDistance = 100;\r\n\tfloat SchwebeFrequenz = 2f;\r\n\r\n\tVector3 IdleKreisPunkt;\r\n\tfloat IdleKreisRadius = 1000f;\r\n\tfloat IdleKreisAngle = 0f;\r\n\r\n\tTimeSince timeSinceLastAttack;\r\n\tfloat AttackCharge = 0;\r\n\r\n\tTimeSince SinceHitAnimationStart;\r\n\tVector3 originalScale;\r\n\r\n\tTimeSince SinceGroundCheck = 0;\r\n\tpublic TimeUntil UntilKnockBack;\r\n\tTimeUntil NextOffset;\r\n\tRandom random;\r\n\r\n\tvoid IHealthEvent.OnDeath()\r\n\t{\r\n\t\tGroundTrace = Scene.Trace.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 7000 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022dead\u0022, \u0022player\u0022, \u0022playerclip\u0022 )\r\n\t\t\t.Run();\r\n\t\tGameObject newDecal = BloodDecal.Clone( GroundTrace.EndPosition, Rotation.LookAt( GroundTrace.Normal, Vector3.Up ).Angles().WithYaw( random.Int( -180, 180 ) ) );\r\n\r\n\r\n\t\tSound.Play(DeathSound, WorldPosition);\r\n\t\tDeadGhost.Clone( WorldPosition \u002B Vector3.Up * 100, GameObject.WorldRotation, GameObject.WorldScale );\r\n\t\tGameObject.Parent.Destroy();\r\n\t}\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\trandom = new Random();\r\n\r\n\t\tPlayer = Scene.FindAllWithTag( \u0022carbody\u0022 ).First\u003CGameObject\u003E();\r\n\t\tPlayerBody = Player.GetComponent\u003CRigidbody\u003E();\r\n\r\n\t\toriginalScale = WorldScale;\r\n\r\n\t\tif ( !DebugMode ) { StateDebugText.Enabled = false; }\r\n\r\n\t\tCurrentState = GhostState.Moving;\r\n\r\n\t\tGroundTrace = Scene.Trace\r\n\t\t\t.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 3000 )\r\n\t\t\t.Radius( 1 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022dead\u0022 )\r\n\t\t\t.Run();\r\n\r\n\t\tHorizontalOffset = (Vector3)random.VectorInCircle(1000);\r\n\t\tSchwebeMittelPunkt = GroundTrace.EndPosition \u002B Vector3.Up * 80;\r\n\r\n\t\tSchwebeFrequenz \u002B= random.Float( -0.1f, 0.1f );\r\n\t\tGameObject.WorldPosition = SchwebeMittelPunkt;\r\n\r\n\t\tIdleKreisPunkt = WorldPosition.WithZ( GroundTrace.EndPosition.z ) \u002B (Vector3)random.VectorInCircle( 1 ).Normal * random.Int( 300, 500 );\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tif (SinceGroundCheck \u003E 0.3f \u0026\u0026 CurrentState != GhostState.Idle ) \r\n\t\t{\r\n\t\t\tGroundTrace = Scene.Trace\r\n\t\t\t\t.Ray( TargetPosition, TargetPosition \u002B Vector3.Down * 3000 )\r\n\t\t\t\t.Radius( 1 )\r\n\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022dead\u0022 )\r\n\t\t\t\t.Run();\r\n\r\n\t\t\tSinceGroundCheck = 0;\r\n\t\t}\r\n\r\n\t\tVector3 playerPosition = Player.WorldPosition;\r\n\t\tVector3 hoverHeight = (GroundTrace.EndPosition \u002B Vector3.Up * 1000).WithY( 0 ).WithX( 0 );\r\n\t\tif ( !GroundTrace.Hit ) { hoverHeight = Vector3.Up * 1000; }\r\n\r\n\t\tswitch ( CurrentState ) \r\n\t\t{\r\n\t\t\tdefault: \r\n\t\t\t\tStateDebugText.Text = \u0022DEFAULT\u0022;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase GhostState.Idle:\r\n\t\t\t\tStateDebugText.Text = \u0022IDLE\u0022;\r\n\r\n\t\t\t\t// Wenn Ghost weiter als der Radius * 1.5f vom Punkt entfernt ist soll er einen neuen zuf\u00E4lligen finden\r\n\t\t\t\tif ((WorldPosition.WithZ(0) - IdleKreisPunkt.WithZ(0)).Length \u003E IdleKreisRadius * 1.5f)\r\n\t\t\t\t{\r\n\t\t\t\t\tIdleKreisPunkt = WorldPosition.WithZ(GroundTrace.EndPosition.z) \u002B (Vector3)random.VectorInCircle(1).Normal * random.Int(300, 500);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Winkel un damit Koordinaten vom aufm Kreis berechnen\r\n\t\t\t\tIdleKreisAngle \u002B= Time.Delta * 0.2f; // * Speed\r\n\t\t\t\tfloat x = MathF.Cos(IdleKreisAngle);\r\n\t\t\t\tfloat y = MathF.Sin(IdleKreisAngle);\r\n\r\n\t\t\t\t// TargetPosition aus IdleKreisPunkt, Koordinaten und height berechnen, SchwebeMittelPunkt zu TargetPos LERPen\r\n\t\t\t\tTargetPosition = IdleKreisPunkt \u002B new Vector3(x, y, 0).Normal * IdleKreisRadius \u002B hoverHeight;\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo(TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap(0, 5000, 0.5f, 1));\r\n\r\n\t\t\t\tif (DebugMode)\r\n\t\t\t\t{\r\n\t\t\t\t\tDebugOverlay.Sphere( new Sphere( TargetPosition, 16 ), Color.Red );\r\n\t\t\t\t\tDebugOverlay.Sphere( new Sphere( IdleKreisPunkt.WithZ( GroundTrace.EndPosition.z ), 16 ), Color.Blue );\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Zeit schiebt Sinusfunktion(Welle) voran, multipliziert mit Frequenz f\u00FCr enge oder weite Wellen, \r\n\t\t\t\t// dann mit Distanz multiplizieren und auf MittelPunkt addieren\r\n\t\t\t\tTransform transform = new Transform(\r\n\t\t\t\t\tSchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance),\r\n\t\t\t\t\tRotation.LookAt( TargetPosition - SchwebeMittelPunkt, Vector3.Up ),\r\n\t\t\t\t\tWorldScale\r\n\t\t\t\t);\r\n\r\n\t\t\t\tWorldTransform = transform;\r\n\r\n\t\t\t\tif ( (playerPosition - WorldPosition).Length \u003C 10000 ) { CurrentState = GhostState.Moving; }\t\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// MOVING\u0026SHOOTING\r\n\t\t\tcase GhostState.Moving: \r\n\t\t\t\tStateDebugText.Text = \u0022WANDER\u0022;\r\n\t\t\t\t\r\n\t\t\t\tTargetPosition = playerPosition.WithZ(0) \u002B (WorldPosition.WithZ( 0 ) - playerPosition.WithZ(0) ).Normal * 5500 \u002B hoverHeight \u002B HorizontalOffset;\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo(TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap(0, 5000, 0.2f, 0.5f));\r\n\t\t\t\t// SchwebeMittelPunkt = SchwebeMittelPunkt \u002B ( TargetPosition - SchwebeMittelPunkt ) * 0.5f \u002B Vector3.Left * (MathF.Sin( (TargetPosition - SchwebeMittelPunkt).Length * SchwebeFrequenz ) * SchwebeDistance);\r\n\t\t\t\t\r\n\t\t\t\t// Zeit schiebt Sinusfunktion(Welle) voran, multipliziert mit Frequenz f\u00FCr enge oder weite Wellen, dann mit Distanz multiplizieren und auf MittelPunkt addieren\r\n\t\t\t\tGameObject.WorldPosition = SchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance);\r\n\t\t\t\t\r\n\t\t\t\tLookAtPlayer();\r\n\t\t\t\t\r\n\t\t\t\tif ( timeSinceLastAttack \u003E 4 \u0026\u0026 playerPosition.Distance(WorldPosition) \u003C 7000 \u0026\u0026 playerPosition.Distance( WorldPosition ) \u003E 2000 ) \r\n\t\t\t\t{ timeSinceLastAttack = random.Float(0f, 0.2f); Attack(); }\r\n\t\t\t\telse {  }\r\n\t\t\t\tif ( timeSinceLastAttack \u003C 0.01f )\r\n\t\t\t\t{\r\n\t\t\t\t\t// HorizontalOffset = Player.WorldRotation.Left * random.Int( 1, 3 ) * random.Int( -1, 1 );\r\n\t\t\t\t}\r\n\t\t\t\t// if ( (TargetPosition - MittelPunkt).IsNearlyZero(500) ) CurrentState = GhostState.Attack;\r\n\t\t\t\t\r\n\t\t\t\tif ( (playerPosition - WorldPosition).Length \u003E 10000 ) { CurrentState = GhostState.Idle; }\t\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// ATTACK\r\n\t\t\tcase GhostState.Attack:\r\n\t\t\t\tStateDebugText.Text = \u0022ATTACK\u0022;\r\n\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// STAGGERED\r\n\t\t\tcase GhostState.Staggered:\r\n\t\t\t\tStateDebugText.Text = \u0022STAGGERED\u0022;\r\n\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo( TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap( 0, 5000, 1, 3 ) );\r\n\t\t\t\tGameObject.WorldPosition = SchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance);\r\n\r\n\t\t\t\tif ( UntilKnockBack ) { CurrentState = GhostState.Moving; }\r\n\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\r\n\t}\r\n\r\n\tprivate void LookAtPlayer()\r\n\t{\r\n\t\tif ( GameObject.WorldRotation.Forward.Angle( Player.WorldPosition - GameObject.WorldPosition ) \u003E 1 )\r\n\t\t{\r\n\t\t\tGameObject.WorldRotation = GameObject.WorldRotation.LerpTo( Rotation.LookAt( Player.WorldPosition - GameObject.WorldPosition, Vector3.Up ), 0.5f );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void Attack()\r\n\t{\r\n\t\tSceneTraceResult sightlineCheck = Scene.Trace\r\n\t\t\t.Sphere( 16, WorldPosition, PlayerBody.WorldPosition \u002B Vector3.Up * 100 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022world\u0022 )\r\n\t\t\t.Run();\r\n\r\n\t\tif ( sightlineCheck.Hit )\r\n\t\t{\r\n\t\t\t// Log.Info( \u0022No line of sight to player, skipping attack\u0022 );\r\n\t\t\tHorizontalOffset = (Vector3)random.VectorInCircle( 1000 );\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tGameObject newBall = GhostBall.Clone( WorldPosition );\r\n\t\tRigidbody newBody = newBall.GetComponent\u003CRigidbody\u003E();\r\n\t\t// newBody.Velocity = Vector3.Down * 100;\r\n\r\n\t\t// Sound\r\n\t\tnewBall.GetComponent\u003CGhostBallLogic\u003E().attackSoundHandle = Sound.Play( AttackSound, newBall.WorldPosition );\r\n\r\n\t\t// Unity Conversion\r\n\t\tVector3 UnityProjPos = new Vector3( WorldPosition.x, WorldPosition.z, WorldPosition.y );\r\n\t\tVector3 UnityTargetPos = new Vector3( Player.WorldPosition.x, Player.WorldPosition.z \u002B 100, Player.WorldPosition.y ) \u002B random.VectorInSphere(1);\r\n\t\tVector3 UnityTargetVel = new Vector3( PlayerBody.Velocity.x, PlayerBody.Velocity.z, PlayerBody.Velocity.y );\r\n\t\tif ( UnityTargetVel.Length \u003C 1 ) { UnityTargetVel = Vector3.Forward; }\r\n\r\n\t\tif ( Ballistics.solve_ballistic_arc( UnityProjPos, \r\n\t\t\t5000, \r\n\t\t\tUnityTargetPos, \r\n\t\t\tUnityTargetVel, \r\n\t\t\tScene.PhysicsWorld.Gravity.Length * 0.001f,\r\n\t\t\tout Vector3 s0, out Vector3 s1 ) \u003E 0 ) \r\n\t\t{ \r\n\t\t\t// Convert back to S\u0026box\r\n\t\t\tVector3 solutionVelocity = new Vector3(s0.x, s0.z, s0.y);\r\n\t\t\t\r\n\t\t\t//Apply Velocity\r\n\t\t\tnewBody.Velocity = solutionVelocity;\r\n\t\t}\r\n\t}\r\n\r\n\tpublic async Task AnimateHit()\r\n\t{\r\n\t\tfloat duration = 0.1f;\r\n\t\tSinceHitAnimationStart = 0;\r\n\r\n\t\tWorldScale = WorldScale.WithZ( originalScale.z * (1f - 0.15f) );\r\n\r\n\t\twhile ( SinceHitAnimationStart \u003C duration )\r\n\t\t{\r\n\t\t\tWorldScale = WorldScale.WithZ( WorldScale.z.LerpTo( originalScale.z, 0.1f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\r\n\t\tif ( !this.IsValid() ) return;\r\n\t\tWorldScale = originalScale;\r\n\t}\r\n\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"ui/hud.razor.scss","FileName":"hud.razor.scss","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"HUD {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    right: 0;\r\n    bottom: 0;\r\n    font-family: Poppins;\r\n    font-size: 24px;\r\n    text-stroke: 10px black;\r\n\r\n    \u003E .vitals {\r\n        position: absolute;\r\n        bottom: 10%;\r\n        right: auto;\r\n        left: 10%;\r\n        font-size: 64px;\r\n        flex-direction: column;\r\n        align-items: flex-end;\r\n        font-weight: bold;\r\n        color: grey;\r\n    }\r\n\r\n\r\n    .health-dial-almost {\r\n        position: absolute;\r\n        left: -1.5%;\r\n        bottom: -12%;\r\n        width: 36vw;\r\n        height: 25vw;\r\n        background-image: url( \u0022sprites/health-dial-almost.svg\u0022 );\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n\r\n        \u0026.low-health {\r\n            animation: shake-tacho-animation 0.4s ease-in-out;\r\n            animation-iteration-count: infinite;\r\n        }\r\n\r\n        \u0026.super-low-health {\r\n            animation: shake-tacho-animation 0.1s ease-in-out;\r\n            animation-iteration-count: infinite;\r\n        }\r\n    }\r\n\r\n    .health-needle {\r\n        position: absolute;\r\n        // Setzt die Nadel horizontal genau in die Mitte\r\n        left: 3.5%;\r\n        bottom: -15%;\r\n        // Dimensionen deiner Nadel\r\n        height: 28vw;\r\n        width: 17vw;\r\n        // Verschiebt die Nadel um die H\u00E4lfte ihrer eigenen Breite nach links, damit sie perfekt zentriert ist.\r\n        // Zudem wird sie um ihre eigene L\u00E4nge (100%) nach oben verschoben, damit ihr unteres Ende auf dem Drehpunkt liegt.\r\n        transition: transform 0.3s ease-out;\r\n        // WICHTIG: Definiert den Drehpunkt f\u00FCr das C#-Skript.\r\n        // bottom center sorgt daf\u00FCr, dass sich die Nadel am unteren Ende dreht.\r\n        background-image: url( \u0022sprites/health-needle.svg\u0022 );\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n\r\n        \u0026.damaged {\r\n            animation: shake-needle-animation 0.2s ease-in-out;\r\n        }\r\n    }\r\n\r\n    .ammocount {\r\n        position: absolute;\r\n        bottom: 2%;\r\n        left: 21.5%;\r\n        display: flex;\r\n        flex-direction: column;\r\n        align-items: flex-end;\r\n        font-weight: bold;\r\n        font-size: 3vw;\r\n        color: orange;\r\n\r\n        \u0026.used {\r\n            animation: rocket-grow-animation 0.2s;\r\n            animation-iteration-count: 1;\r\n        }\r\n    }\r\n\r\n\r\n    \u003E .score {\r\n        position: absolute;\r\n        top: 4.5%;\r\n        right: 0.5%;\r\n        display: flex;\r\n        flex-direction: column;\r\n        align-items: flex-end;\r\n        font-weight: bold;\r\n        color: white;\r\n\r\n\r\n        \u0026.center {\r\n            animation: score-center 1s forwards;\r\n            animation-iteration-count: 1;\r\n        }\r\n\r\n        .gained-score {\r\n            position: absolute;\r\n            font-family: Digit Tech 7;\r\n            font-size: 2vw;\r\n            color: #4cff00; // Optional: Gr\u00FCn f\u00FCr gewonnene Punkte\r\n            pointer-events: none; // Damit es nicht mit der Maus interagiert\r\n            z-index: 2;\r\n            // Die Animation: 0.6 Sekunden lang, wird am Ende langsamer (ease-out)\r\n            // \u0027both\u0027 sorgt daf\u00FCr, dass das Element am Ende unsichtbar bleibt und die Animation von Anfang an angewendet wird\r\n            animation: score-fall 2s ease-out forwards;\r\n        }\r\n\r\n        .current-score {\r\n            font-family: Digit Tech 7;\r\n            font-size: 4vw;\r\n        }\r\n\r\n        .multiplier {\r\n            transform-origin: right;\r\n            font-size: 70px;\r\n            font-style: italic;\r\n            transition: transform 0.1s ease-out, color 0.1s linear;\r\n            margin-top: -45px;\r\n        }\r\n    }\r\n\r\n    \u003E .timer {\r\n        position: absolute;\r\n        left: 47%;\r\n        top: 0%;\r\n        display: flex;\r\n        font-family: Digit Tech 7;\r\n        flex-direction: column;\r\n        align-items: flex-start;\r\n        font-weight: bold;\r\n        color: darkgray;\r\n        font-size: 2.5vw;\r\n\r\n        .timer-house {\r\n            position: absolute;\r\n            left: -123%;\r\n            top: -200%;\r\n            width: 26vw;\r\n            height: 19vw;\r\n            background-image: url( \u0022sprites/timer-house.svg\u0022 );\r\n            background-size: contain;\r\n            background-repeat: no-repeat;\r\n            background-position: center;\r\n        }\r\n\r\n        p {\r\n            display: flex;\r\n            align-items: center; // Richtet Zahlen und Doppelpunkt vertikal mittig aus\r\n        }\r\n\r\n        .separator {\r\n            font-family: \u0027Poppins\u0027, sans-serif; // \u00DCberschreibt die Schriftart nur f\u00FCr den Doppelpunkt\r\n            padding: 0 4px; // Optional: F\u00FCgt minimalen Abstand links/rechts hinzu, falls n\u00F6tig\r\n        }\r\n    }\r\n\r\n    .crosshair {\r\n        position: absolute;\r\n        top: 50%;\r\n        left: 50%;\r\n        transform: translate(-50%, -50%);\r\n        width: 3vw; // Breite des Fadenkreuzes\r\n        height: 3vw; // H\u00F6he des Fadenkreuzes\r\n        background-image: url( \u0022sprites/crosshair.png\u0022 );\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n\r\n        \u0026.on-target {\r\n            background-image: url( \u0022sprites/crosshair-enemy.png\u0022 );\r\n        }\r\n\r\n        \u0026.on-shot {\r\n            animation: crosshair-shot 0.1s ease-out;\r\n            animation-iteration-count: 1;\r\n        }\r\n    }\r\n\r\n    .hitmarker {\r\n        position: absolute;\r\n        top: 50%;\r\n        left: 50%;\r\n        transform: translate(-50%, -50%);\r\n        width: 2vw; // Breite des Fadenkreuzes\r\n        height: 2vw; // H\u00F6he des Fadenkreuzes\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n\r\n        \u0026.active {\r\n            background-image: url( \u0022sprites/crosshair-hitmarker.png\u0022 );\r\n        }\r\n    }\r\n\r\n    .multiplier-dial {\r\n        position: absolute;\r\n        right: -5%;\r\n        top: 1%;\r\n        width: 28vw;\r\n        height: 19vw;\r\n        background-image: url( \u0022sprites/multiplier-dial-semi-score.svg\u0022 );\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n\r\n        \u0026.on-fire {\r\n            animation: shake-tacho-animation 0.1s ease-in-out;\r\n            animation-iteration-count: infinite;\r\n        }\r\n    }\r\n\r\n    .multiplier-needle {\r\n        position: absolute;\r\n        // Setzt die Nadel horizontal genau in die Mitte\r\n        left: 22.5%;\r\n        bottom: 33.5%;\r\n        // Dimensionen deiner Nadel\r\n        height: 15vw;\r\n        width: 15vw;\r\n        // Verschiebt die Nadel um die H\u00E4lfte ihrer eigenen Breite nach links, damit sie perfekt zentriert ist.\r\n        // Zudem wird sie um ihre eigene L\u00E4nge (100%) nach oben verschoben, damit ihr unteres Ende auf dem Drehpunkt liegt.\r\n        transition: transform 0.1s ease-out;\r\n        // WICHTIG: Definiert den Drehpunkt f\u00FCr das C#-Skript.\r\n        // bottom center sorgt daf\u00FCr, dass sich die Nadel am unteren Ende dreht.\r\n        background-image: url( \u0022sprites/rechteck.svg\u0022 );\r\n        background-size: contain;\r\n        background-repeat: no-repeat;\r\n        background-position: center;\r\n    }\r\n}\r\n\r\n@keyframes hitmarker-fade {\r\n    0% {\r\n        opacity: 1;\r\n    }\r\n\r\n    100% {\r\n        opacity: 0;\r\n    }\r\n}\r\n@keyframes crosshair-shot {\r\n    0% {\r\n        transform: translate(-50%, -50%) scale(1.3f);\r\n    }\r\n\r\n    100% {\r\n        transform: translate(-50%, -50%) scale(1f);  \r\n    }\r\n}\r\n\r\n@keyframes score-fall {\r\n    0% {\r\n        opacity: 0;\r\n        transform: translateY(200px) scale(0.8f);\r\n    }\r\n\r\n    20% {\r\n        opacity: 1;\r\n        transform: scale(1.5f);\r\n    }\r\n\r\n    100% {\r\n        opacity: 0;\r\n        transform: translateY(20px) scale(1f) rotate(10deg); // F\u00E4llt nach unten\r\n    }\r\n}\r\n\r\n@keyframes score-center {\r\n    0% {\r\n        color: white;\r\n        transform: translateY(100) scale(0.8);\r\n    }\r\n\r\n    20% {\r\n        color: white;\r\n        opacity: 1;\r\n    }\r\n\r\n    100% {\r\n        color: red;\r\n        opacity: 1;\r\n        transform: translateY(25vw) translateX(-40vw) scale(3) rotate(8deg); // F\u00E4llt nach unten\r\n    }\r\n}\r\n    // Globale Keyframes f\u00FCr das Wackeln\r\n    @keyframes shake-animation {\r\n        0% {\r\n            transform: rotate(0deg);\r\n        }\r\n\r\n        25% {\r\n            transform: rotate(-5deg); // Leichte Drehung nach links\r\n        }\r\n\r\n        50% {\r\n            transform: rotate(5deg); // Leichte Drehung nach rechts\r\n        }\r\n\r\n        75% {\r\n            transform: rotate(-5deg);\r\n        }\r\n\r\n        100% {\r\n            transform: rotate(0deg);\r\n        }\r\n    }\r\n    // Globale Keyframes f\u00FCr das Wackeln\r\n    @keyframes shake-needle-animation {\r\n        0% {\r\n            transform: translateY(0);\r\n        }\r\n\r\n        25% {\r\n            transform: translateY(10);\r\n        }\r\n\r\n        50% {\r\n            transform: translateY(0);\r\n        }\r\n\r\n        75% {\r\n            transform: translateY(-10);\r\n        }\r\n\r\n        100% {\r\n            transform: translateY(0);\r\n        }\r\n    }\r\n    // Globale Keyframes f\u00FCr das Wackeln vom Tacho\r\n    @keyframes shake-tacho-animation {\r\n        0% {\r\n            transform: rotate(0deg);\r\n        }\r\n\r\n        25% {\r\n            transform: rotate(-5deg); // Leichte Drehung nach links\r\n        }\r\n\r\n        75% {\r\n            transform: rotate(5deg); // Leichte Drehung nach rechts\r\n        }\r\n\r\n        100% {\r\n            transform: rotate(0deg);\r\n        }\r\n    }\r\n    // Globale Keyframes f\u00FCr das Wackeln vom Tacho\r\n    @keyframes rocket-grow-animation {\r\n        0% {\r\n            transform: scale(1.3f);\r\n        }\r\n\r\n        25% {\r\n            transform: scale(1.3f); // Leichte Drehung nach links\r\n        }\r\n\r\n        75% {\r\n            transform: scale(1.1); // Leichte Drehung nach rechts\r\n        }\r\n\r\n        100% {\r\n            transform: scale(1);\r\n        }\r\n    }\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"Ballistics.cs","FileName":"Ballistics.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"// LICENSE\r\n//\r\n//   This software is dual-licensed to the public domain and under the following\r\n//   license: you are granted a perpetual, irrevocable license to copy, modify,\r\n//   publish, and distribute this file as you see fit.\r\n//\r\n// VERSION \r\n//   0.1.0  (2016-06-01)  Initial release\r\n//\r\n// AUTHOR\r\n//   Forrest Smith\r\n//\r\n// ADDITIONAL READING\r\n//   https://www.forrestthewoods.com/blog/solving_ballistic_trajectories/\r\n//\r\n// API\r\n//    int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector3 target, float gravity, out Vector3 low, out Vector3 high);\r\n//    int solve_ballistic_arc(Vector3 proj_pos, float proj_speed, Vector3 target, Vector3 target_velocity, float gravity, out Vector3 s0, out Vector3 s1, out Vector3 s2, out Vector3 s3);\r\n//    bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_speed, Vector3 target, float max_height, out float vertical_speed, out float gravity);\r\n//    bool solve_ballistic_arc_lateral(Vector3 proj_pos, float lateral_speed, Vector3 target, Vector3 target_velocity, float max_height_offset, out Vector3 fire_velocity, out float gravity, out Vector3 impact_point);\r\n//\r\n//    float ballistic_range(float speed, float gravity, float initial_height);\r\n//\r\n//    bool IsZero(double d);\r\n//    int SolveQuadric(double c0, double c1, double c2, out double s0, out double s1);\r\n//    int SolveCubic(double c0, double c1, double c2, double c3, out double s0, out double s1, out double s2);\r\n//    int SolveQuartic(double c0, double c1, double c2, double c3, double c4, out double s0, out double s1, out double s2, out double s3);\r\n\r\n\r\nusing System;\r\nusing System.Collections;\r\nusing System.Collections.Generic;\r\nusing Sandbox;\r\n\r\npublic class Ballistics\r\n{\r\n\r\n\t// SolveQuadric, SolveCubic, and SolveQuartic were ported from C as written for Graphics Gems I\r\n\t// Original Author: Jochen Schwarze (schwarze@isa.de)\r\n\t// https://github.com/erich666/GraphicsGems/blob/240a34f2ad3fa577ef57be74920db6c4b00605e4/gems/Roots3And4.c\r\n\r\n\t// Utility function used by SolveQuadratic, SolveCubic, and SolveQuartic\r\n\tprivate static bool IsZero( double d )\r\n\t{\r\n\t\tconst double eps = 1e-9;\r\n\t\treturn d \u003E -eps \u0026\u0026 d \u003C eps;\r\n\t}\r\n\r\n\tprivate static double GetCubicRoot( double value )\r\n\t{\r\n\t\tif ( value \u003E 0.0 )\r\n\t\t{\r\n\t\t\treturn System.Math.Pow( value, 1.0 / 3.0 );\r\n\t\t}\r\n\t\telse if ( value \u003C 0 )\r\n\t\t{\r\n\t\t\treturn -System.Math.Pow( -value, 1.0 / 3.0 );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\treturn 0.0;\r\n\t\t}\r\n\t}\r\n\r\n\t// Solve quadratic equation: c0*x^2 \u002B c1*x \u002B c2. \r\n\t// Returns number of solutions.\r\n\tpublic static int SolveQuadric( double c0, double c1, double c2, out double s0, out double s1 )\r\n\t{\r\n\t\ts0 = double.NaN;\r\n\t\ts1 = double.NaN;\r\n\r\n\t\tdouble p, q, D;\r\n\r\n\t\t/* normal form: x^2 \u002B px \u002B q = 0 */\r\n\t\tp = c1 / (2 * c0);\r\n\t\tq = c2 / c0;\r\n\r\n\t\tD = p * p - q;\r\n\r\n\t\tif ( IsZero( D ) )\r\n\t\t{\r\n\t\t\ts0 = -p;\r\n\t\t\treturn 1;\r\n\t\t}\r\n\t\telse if ( D \u003C 0 )\r\n\t\t{\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t\telse /* if (D \u003E 0) */\r\n\t\t{\r\n\t\t\tdouble sqrt_D = System.Math.Sqrt( D );\r\n\r\n\t\t\ts0 = sqrt_D - p;\r\n\t\t\ts1 = -sqrt_D - p;\r\n\t\t\treturn 2;\r\n\t\t}\r\n\t}\r\n\r\n\t// Solve cubic equation: c0*x^3 \u002B c1*x^2 \u002B c2*x \u002B c3. \r\n\t// Returns number of solutions.\r\n\tpublic static int SolveCubic( double c0, double c1, double c2, double c3, out double s0, out double s1, out double s2 )\r\n\t{\r\n\t\ts0 = double.NaN;\r\n\t\ts1 = double.NaN;\r\n\t\ts2 = double.NaN;\r\n\r\n\t\tint num;\r\n\t\tdouble sub;\r\n\t\tdouble A, B, C;\r\n\t\tdouble sq_A, p, q;\r\n\t\tdouble cb_p, D;\r\n\r\n\t\t/* normal form: x^3 \u002B Ax^2 \u002B Bx \u002B C = 0 */\r\n\t\tA = c1 / c0;\r\n\t\tB = c2 / c0;\r\n\t\tC = c3 / c0;\r\n\r\n\t\t/*  substitute x = y - A/3 to eliminate quadric term:  x^3 \u002Bpx \u002B q = 0 */\r\n\t\tsq_A = A * A;\r\n\t\tp = 1.0 / 3 * (-1.0 / 3 * sq_A \u002B B);\r\n\t\tq = 1.0 / 2 * (2.0 / 27 * A * sq_A - 1.0 / 3 * A * B \u002B C);\r\n\r\n\t\t/* use Cardano\u0027s formula */\r\n\t\tcb_p = p * p * p;\r\n\t\tD = q * q \u002B cb_p;\r\n\r\n\t\tif ( IsZero( D ) )\r\n\t\t{\r\n\t\t\tif ( IsZero( q ) ) /* one triple solution */\r\n\t\t\t{\r\n\t\t\t\ts0 = 0;\r\n\t\t\t\tnum = 1;\r\n\t\t\t}\r\n\t\t\telse /* one single and one double solution */\r\n\t\t\t{\r\n\t\t\t\tdouble u = GetCubicRoot( -q );\r\n\t\t\t\ts0 = 2 * u;\r\n\t\t\t\ts1 = -u;\r\n\t\t\t\tnum = 2;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse if ( D \u003C 0 ) /* Casus irreducibilis: three real solutions */\r\n\t\t{\r\n\t\t\tdouble phi = 1.0 / 3 * System.Math.Acos( -q / System.Math.Sqrt( -cb_p ) );\r\n\t\t\tdouble t = 2 * System.Math.Sqrt( -p );\r\n\r\n\t\t\ts0 = t * System.Math.Cos( phi );\r\n\t\t\ts1 = -t * System.Math.Cos( phi \u002B System.Math.PI / 3 );\r\n\t\t\ts2 = -t * System.Math.Cos( phi - System.Math.PI / 3 );\r\n\t\t\tnum = 3;\r\n\t\t}\r\n\t\telse /* one real solution */\r\n\t\t{\r\n\t\t\tdouble sqrt_D = System.Math.Sqrt( D );\r\n\t\t\tdouble u = GetCubicRoot( sqrt_D - q );\r\n\t\t\tdouble v = -GetCubicRoot( sqrt_D \u002B q );\r\n\r\n\t\t\ts0 = u \u002B v;\r\n\t\t\tnum = 1;\r\n\t\t}\r\n\r\n\t\t/* resubstitute */\r\n\t\tsub = 1.0 / 3 * A;\r\n\r\n\t\tif ( num \u003E 0 ) s0 -= sub;\r\n\t\tif ( num \u003E 1 ) s1 -= sub;\r\n\t\tif ( num \u003E 2 ) s2 -= sub;\r\n\r\n\t\treturn num;\r\n\t}\r\n\r\n\t// Solve quartic function: c0*x^4 \u002B c1*x^3 \u002B c2*x^2 \u002B c3*x \u002B c4. \r\n\t// Returns number of solutions.\r\n\tpublic static int SolveQuartic( double c0, double c1, double c2, double c3, double c4, out double s0, out double s1, out double s2, out double s3 )\r\n\t{\r\n\t\ts0 = double.NaN;\r\n\t\ts1 = double.NaN;\r\n\t\ts2 = double.NaN;\r\n\t\ts3 = double.NaN;\r\n\r\n\t\tdouble[] coeffs = new double[4];\r\n\t\tdouble z, u, v, sub;\r\n\t\tdouble A, B, C, D;\r\n\t\tdouble sq_A, p, q, r;\r\n\t\tint num;\r\n\r\n\t\t/* normal form: x^4 \u002B Ax^3 \u002B Bx^2 \u002B Cx \u002B D = 0 */\r\n\t\tA = c1 / c0;\r\n\t\tB = c2 / c0;\r\n\t\tC = c3 / c0;\r\n\t\tD = c4 / c0;\r\n\r\n\t\t/*  substitute x = y - A/4 to eliminate cubic term: x^4 \u002B px^2 \u002B qx \u002B r = 0 */\r\n\t\tsq_A = A * A;\r\n\t\tp = -3.0 / 8 * sq_A \u002B B;\r\n\t\tq = 1.0 / 8 * sq_A * A - 1.0 / 2 * A * B \u002B C;\r\n\t\tr = -3.0 / 256 * sq_A * sq_A \u002B 1.0 / 16 * sq_A * B - 1.0 / 4 * A * C \u002B D;\r\n\r\n\t\tif ( IsZero( r ) )\r\n\t\t{\r\n\t\t\t/* no absolute term: y(y^3 \u002B py \u002B q) = 0 */\r\n\r\n\t\t\tcoeffs[3] = q;\r\n\t\t\tcoeffs[2] = p;\r\n\t\t\tcoeffs[1] = 0;\r\n\t\t\tcoeffs[0] = 1;\r\n\r\n\t\t\tnum = Ballistics.SolveCubic( coeffs[0], coeffs[1], coeffs[2], coeffs[3], out s0, out s1, out s2 );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t/* solve the resolvent cubic ... */\r\n\t\t\tcoeffs[3] = 1.0 / 2 * r * p - 1.0 / 8 * q * q;\r\n\t\t\tcoeffs[2] = -r;\r\n\t\t\tcoeffs[1] = -1.0 / 2 * p;\r\n\t\t\tcoeffs[0] = 1;\r\n\r\n\t\t\tSolveCubic( coeffs[0], coeffs[1], coeffs[2], coeffs[3], out s0, out s1, out s2 );\r\n\r\n\t\t\t/* ... and take the one real solution ... */\r\n\t\t\tz = s0;\r\n\r\n\t\t\t/* ... to build two quadric equations */\r\n\t\t\tu = z * z - r;\r\n\t\t\tv = 2 * z - p;\r\n\r\n\t\t\tif ( IsZero( u ) )\r\n\t\t\t\tu = 0;\r\n\t\t\telse if ( u \u003E 0 )\r\n\t\t\t\tu = System.Math.Sqrt( u );\r\n\t\t\telse\r\n\t\t\t\treturn 0;\r\n\r\n\t\t\tif ( IsZero( v ) )\r\n\t\t\t\tv = 0;\r\n\t\t\telse if ( v \u003E 0 )\r\n\t\t\t\tv = System.Math.Sqrt( v );\r\n\t\t\telse\r\n\t\t\t\treturn 0;\r\n\r\n\t\t\tcoeffs[2] = z - u;\r\n\t\t\tcoeffs[1] = q \u003C 0 ? -v : v;\r\n\t\t\tcoeffs[0] = 1;\r\n\r\n\t\t\tnum = Ballistics.SolveQuadric( coeffs[0], coeffs[1], coeffs[2], out s0, out s1 );\r\n\r\n\t\t\tcoeffs[2] = z \u002B u;\r\n\t\t\tcoeffs[1] = q \u003C 0 ? v : -v;\r\n\t\t\tcoeffs[0] = 1;\r\n\r\n\t\t\tif ( num == 0 ) num \u002B= Ballistics.SolveQuadric( coeffs[0], coeffs[1], coeffs[2], out s0, out s1 );\r\n\t\t\telse if ( num == 1 ) num \u002B= Ballistics.SolveQuadric( coeffs[0], coeffs[1], coeffs[2], out s1, out s2 );\r\n\t\t\telse if ( num == 2 ) num \u002B= Ballistics.SolveQuadric( coeffs[0], coeffs[1], coeffs[2], out s2, out s3 );\r\n\t\t}\r\n\r\n\t\t/* resubstitute */\r\n\t\tsub = 1.0 / 4 * A;\r\n\r\n\t\tif ( num \u003E 0 ) s0 -= sub;\r\n\t\tif ( num \u003E 1 ) s1 -= sub;\r\n\t\tif ( num \u003E 2 ) s2 -= sub;\r\n\t\tif ( num \u003E 3 ) s3 -= sub;\r\n\r\n\t\treturn num;\r\n\t}\r\n\r\n\r\n\t// Calculate the maximum range that a ballistic projectile can be fired on given speed and gravity.\r\n\t//\r\n\t// speed (float): projectile velocity\r\n\t// gravity (float): force of gravity, positive is down\r\n\t// initial_height (float): distance above flat terrain\r\n\t//\r\n\t// return (float): maximum range\r\n\tpublic static float ballistic_range( float speed, float gravity, float initial_height )\r\n\t{\r\n\r\n\t\t// Handling these cases is up to your project\u0027s coding standards\r\n\t\t// Debug.Assert( speed \u003E 0 \u0026\u0026 gravity \u003E 0 \u0026\u0026 initial_height \u003E= 0, \u0022fts.ballistic_range called with invalid data\u0022 );\r\n\t\t// Guard: return safe default instead of Debug.Assert\r\n\t\tif ( speed \u003C= 0f || gravity \u003C= 0f || initial_height \u003C 0f )\r\n\t\t\treturn 0f;\r\n\r\n\t\t// Derivation\r\n\t\t//   (1) x = speed * time * cos O\r\n\t\t//   (2) y = initial_height \u002B (speed * time * sin O) - (.5 * gravity*time*time)\r\n\t\t//   (3) via quadratic: t = (speed*sin O)/gravity \u002B sqrt(speed*speed*sin O \u002B 2*gravity*initial_height)/gravity    [ignore smaller root]\r\n\t\t//   (4) solution: range = x = (speed*cos O)/gravity * sqrt(speed*speed*sin O \u002B 2*gravity*initial_height)    [plug t back into x=speed*time*cos O]\r\n\t\tfloat angle = 45 * ((float)Math.PI * 2) / 360; // no air resistence, so 45 degrees provides maximum range\r\n\t\tfloat cos = MathF.Cos( angle );\r\n\t\tfloat sin = MathF.Sin( angle );\r\n\r\n\t\tfloat range = (speed * cos / gravity) * (speed * sin \u002B MathF.Sqrt( speed * speed * sin * sin \u002B 2 * gravity * initial_height ));\r\n\t\treturn range;\r\n\t}\r\n\r\n\r\n\t// Solve firing angles for a ballistic projectile with speed and gravity to hit a fixed position.\r\n\t//\r\n\t// proj_pos (Vector3): point projectile will fire from\r\n\t// proj_speed (float): scalar speed of projectile\r\n\t// target (Vector3): point projectile is trying to hit\r\n\t// gravity (float): force of gravity, positive down\r\n\t//\r\n\t// s0 (out Vector3): firing solution (low angle) \r\n\t// s1 (out Vector3): firing solution (high angle)\r\n\t//\r\n\t// return (int): number of unique solutions found: 0, 1, or 2.\r\n\tpublic static int solve_ballistic_arc( Vector3 proj_pos, float proj_speed, Vector3 target, float gravity, out Vector3 s0, out Vector3 s1 )\r\n\t{\r\n\r\n\t\t// Handling these cases is up to your project\u0027s coding standards\r\n\t\t// Debug.Assert( proj_pos != target \u0026\u0026 proj_speed \u003E 0 \u0026\u0026 gravity \u003E 0, \u0022fts.solve_ballistic_arc called with invalid data\u0022 );\r\n\t\t// Guard: return no-solution instead of Debug.Assert\r\n\t\tif ( proj_pos == target || proj_speed \u003C= -1f || gravity \u003C= 0f )\r\n\t\t{\r\n\t\t\ts0 = Vector3.Zero;\r\n\t\t\ts1 = Vector3.Zero;\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\r\n\t\t// C# requires out variables be set\r\n\t\ts0 = Vector3.Zero;\r\n\t\ts1 = Vector3.Zero;\r\n\r\n\t\t// Derivation\r\n\t\t//   (1) x = v*t*cos O\r\n\t\t//   (2) y = v*t*sin O - .5*g*t^2\r\n\t\t// \r\n\t\t//   (3) t = x/(cos O*v)                                        [solve t from (1)]\r\n\t\t//   (4) y = v*x*sin O/(cos O * v) - .5*g*x^2/(cos^2 O*v^2)     [plug t into y=...]\r\n\t\t//   (5) y = x*tan O - g*x^2/(2*v^2*cos^2 O)                    [reduce; cos/sin = tan]\r\n\t\t//   (6) y = x*tan O - (g*x^2/(2*v^2))*(1\u002Btan^2 O)              [reduce; 1\u002Btan O = 1/cos^2 O]\r\n\t\t//   (7) 0 = ((-g*x^2)/(2*v^2))*tan^2 O \u002B x*tan O - (g*x^2)/(2*v^2) - y    [re-arrange]\r\n\t\t//   Quadratic! a*p^2 \u002B b*p \u002B c where p = tan O\r\n\t\t//\r\n\t\t//   (8) let gxv = -g*x*x/(2*v*v)\r\n\t\t//   (9) p = (-x \u002B- sqrt(x*x - 4gxv*(gxv - y)))/2*gxv           [quadratic formula]\r\n\t\t//   (10) p = (v^2 \u002B- sqrt(v^4 - g(g*x^2 \u002B 2*y*v^2)))/gx        [multiply top/bottom by -2*v*v/x; move 4*v^4/x^2 into root]\r\n\t\t//   (11) O = atan(p)\r\n\r\n\t\tVector3 diff = target - proj_pos;\r\n\t\tVector3 diffXZ = new Vector3( diff.x, 0f, diff.z );\r\n\t\tfloat groundDist = diffXZ.Length;\r\n\r\n\t\tfloat speed2 = proj_speed * proj_speed;\r\n\t\tfloat speed4 = proj_speed * proj_speed * proj_speed * proj_speed;\r\n\t\tfloat y = diff.y;\r\n\t\tfloat x = groundDist;\r\n\t\tfloat gx = gravity * x;\r\n\r\n\t\tfloat root = speed4 - gravity * (gravity * x * x \u002B 2 * y * speed2);\r\n\r\n\t\t// No solution\r\n\t\tif ( root \u003C 0 )\r\n\t\t\treturn 0;\r\n\r\n\t\troot = MathF.Sqrt( root );\r\n\r\n\t\tfloat lowAng = MathF.Atan2( speed2 - root, gx );\r\n\t\tfloat highAng = MathF.Atan2( speed2 \u002B root, gx );\r\n\t\tint numSolutions = lowAng != highAng ? 2 : 1;\r\n\r\n\t\tVector3 groundDir = diffXZ.Normal;\r\n\t\ts0 = groundDir * MathF.Cos( lowAng ) * proj_speed \u002B Vector3.Up * MathF.Sin( lowAng ) * proj_speed;\r\n\t\tif ( numSolutions \u003E 1 )\r\n\t\t\ts1 = groundDir * MathF.Cos( highAng ) * proj_speed \u002B Vector3.Up * MathF.Sin( highAng ) * proj_speed;\r\n\r\n\t\treturn numSolutions;\r\n\t}\r\n\r\n\t// Solve firing angles for a ballistic projectile with speed and gravity to hit a target moving with constant, linear velocity.\r\n\t//\r\n\t// proj_pos (Vector3): point projectile will fire from\r\n\t// proj_speed (float): scalar speed of projectile\r\n\t// target (Vector3): point projectile is trying to hit\r\n\t// target_velocity (Vector3): velocity of target\r\n\t// gravity (float): force of gravity, positive down\r\n\t//\r\n\t// s0 (out Vector3): firing solution (fastest time impact) \r\n\t// s1 (out Vector3): firing solution (next impact)\r\n\t// s2 (out Vector3): firing solution (next impact)\r\n\t// s3 (out Vector3): firing solution (next impact)\r\n\t//\r\n\t// return (int): number of unique solutions found: 0, 1, 2, 3, or 4.\r\n\tpublic static int solve_ballistic_arc( Vector3 proj_pos, float proj_speed, Vector3 target_pos, Vector3 target_velocity, float gravity, out Vector3 s0, out Vector3 s1 )\r\n\t{\r\n\r\n\t\t// Initialize output parameters\r\n\t\ts0 = Vector3.Zero;\r\n\t\ts1 = Vector3.Zero;\r\n\r\n\t\t// Derivation \r\n\t\t//\r\n\t\t//  For full derivation see: blog.forrestthewoods.com\r\n\t\t//  Here is an abbreviated version.\r\n\t\t//\r\n\t\t//  Four equations, four unknowns (solution.x, solution.y, solution.z, time):\r\n\t\t//\r\n\t\t//  (1) proj_pos.x \u002B solution.x*time = target_pos.x \u002B target_vel.x*time\r\n\t\t//  (2) proj_pos.y \u002B solution.y*time \u002B .5*G*t = target_pos.y \u002B target_vel.y*time\r\n\t\t//  (3) proj_pos.z \u002B solution.z*time = target_pos.z \u002B target_vel.z*time\r\n\t\t//  (4) proj_speed^2 = solution.x^2 \u002B solution.y^2 \u002B solution.z^2\r\n\t\t//\r\n\t\t//  (5) Solve for solution.x and solution.z in equations (1) and (3)\r\n\t\t//  (6) Square solution.x and solution.z from (5)\r\n\t\t//  (7) Solve solution.y^2 by plugging (6) into (4)\r\n\t\t//  (8) Solve solution.y by rearranging (2)\r\n\t\t//  (9) Square (8)\r\n\t\t//  (10) Set (8) = (7). All solution.xyz terms should be gone. Only time remains.\r\n\t\t//  (11) Rearrange 10. It will be of the form a*^4 \u002B b*t^3 \u002B c*t^2 \u002B d*t * e. This is a quartic.\r\n\t\t//  (12) Solve the quartic using SolveQuartic.\r\n\t\t//  (13) If there are no positive, real roots there is no solution.\r\n\t\t//  (14) Each positive, real root is one valid solution\r\n\t\t//  (15) Plug each time value into (1) (2) and (3) to calculate solution.xyz\r\n\t\t//  (16) The end.\r\n\r\n\t\tdouble G = gravity;\r\n\r\n\t\tdouble A = proj_pos.x;\r\n\t\tdouble B = proj_pos.y;\r\n\t\tdouble C = proj_pos.z;\r\n\t\tdouble M = target_pos.x;\r\n\t\tdouble N = target_pos.y;\r\n\t\tdouble O = target_pos.z;\r\n\t\tdouble P = target_velocity.x;\r\n\t\tdouble Q = target_velocity.y;\r\n\t\tdouble R = target_velocity.z;\r\n\t\tdouble S = proj_speed;\r\n\r\n\t\tdouble H = M - A;\r\n\t\tdouble J = O - C;\r\n\t\tdouble K = N - B;\r\n\t\tdouble L = -.5f * G;\r\n\r\n\t\t// Quartic Coeffecients\r\n\t\tdouble c0 = L * L;\r\n\t\tdouble c1 = -2 * Q * L;\r\n\t\tdouble c2 = Q * Q - 2 * K * L - S * S \u002B P * P \u002B R * R;\r\n\t\tdouble c3 = 2 * K * Q \u002B 2 * H * P \u002B 2 * J * R;\r\n\t\tdouble c4 = K * K \u002B H * H \u002B J * J;\r\n\r\n\t\t// Solve quartic\r\n\t\tdouble[] times = new double[4];\r\n\t\tint numTimes = SolveQuartic( c0, c1, c2, c3, c4, out times[0], out times[1], out times[2], out times[3] );\r\n\r\n\t\t// Sort so faster collision is found first\r\n\t\tSystem.Array.Sort( times );\r\n\r\n\t\t// Plug quartic solutions into base equations\r\n\t\t// There should never be more than 2 positive, real roots.\r\n\t\tVector3[] solutions = new Vector3[2];\r\n\t\tint numSolutions = 0;\r\n\r\n\t\tfor ( int i = 0; i \u003C times.Length \u0026\u0026 numSolutions \u003C 2; \u002B\u002Bi )\r\n\t\t{\r\n\t\t\tdouble t = times[i];\r\n\t\t\tif ( t \u003C= 0 || double.IsNaN( t ) )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\tsolutions[numSolutions].x = (float)((H \u002B P * t) / t);\r\n\t\t\tsolutions[numSolutions].y = (float)((K \u002B Q * t - L * t * t) / t);\r\n\t\t\tsolutions[numSolutions].z = (float)((J \u002B R * t) / t);\r\n\t\t\t\u002B\u002BnumSolutions;\r\n\t\t}\r\n\r\n\t\t// Write out solutions\r\n\t\tif ( numSolutions \u003E 0 ) s0 = solutions[0];\r\n\t\tif ( numSolutions \u003E 1 ) s1 = solutions[1];\r\n\r\n\t\treturn numSolutions;\r\n\t}\r\n\r\n\r\n\r\n\t// Solve the firing arc with a fixed lateral speed. Vertical speed and gravity varies. \r\n\t// This enables a visually pleasing arc.\r\n\t//\r\n\t// proj_pos (Vector3): point projectile will fire from\r\n\t// lateral_speed (float): scalar speed of projectile along XZ plane\r\n\t// target_pos (Vector3): point projectile is trying to hit\r\n\t// max_height (float): height above Max(proj_pos, impact_pos) for projectile to peak at\r\n\t//\r\n\t// fire_velocity (out Vector3): firing velocity\r\n\t// gravity (out float): gravity necessary to projectile to hit precisely max_height\r\n\t//\r\n\t// return (bool): true if a valid solution was found\r\n\tpublic static bool solve_ballistic_arc_lateral( Vector3 proj_pos, float lateral_speed, Vector3 target_pos, float max_height, out Vector3 fire_velocity, out float gravity )\r\n\t{\r\n\r\n\t\t// Handling these cases is up to your project\u0027s coding standards\r\n\t\t// Debug.Assert( proj_pos != target_pos \u0026\u0026 lateral_speed \u003E 0 \u0026\u0026 max_height \u003E proj_pos.y, \u0022fts.solve_ballistic_arc called with invalid data\u0022 );\r\n\t\t// Guard instead of Debug.Assert\r\n\t\tif ( proj_pos == target_pos || lateral_speed \u003C= 0f || max_height \u003C= proj_pos.y )\r\n\t\t{\r\n\t\t\tfire_velocity = Vector3.Zero;\r\n\t\t\tgravity = float.NaN;\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tfire_velocity = Vector3.Zero;\r\n\t\tgravity = float.NaN;\r\n\r\n\t\tVector3 diff = target_pos - proj_pos;\r\n\t\tVector3 diffXZ = new Vector3( diff.x, 0f, diff.z );\r\n\t\tfloat lateralDist = diffXZ.Length;\r\n\r\n\t\tif ( lateralDist == 0 )\r\n\t\t\treturn false;\r\n\r\n\t\tfloat time = lateralDist / lateral_speed;\r\n\r\n\t\tfire_velocity = diffXZ.Normal * lateral_speed;\r\n\r\n\t\t// System of equations. Hit max_height at t=.5*time. Hit target at t=time.\r\n\t\t//\r\n\t\t// peak = y0 \u002B vertical_speed*halfTime \u002B .5*gravity*halfTime^2\r\n\t\t// end = y0 \u002B vertical_speed*time \u002B .5*gravity*time^s\r\n\t\t// Wolfram Alpha: solve b = a \u002B .5*v*t \u002B .5*g*(.5*t)^2, c = a \u002B vt \u002B .5*g*t^2 for g, v\r\n\t\tfloat a = proj_pos.y;       // initial\r\n\t\tfloat b = max_height;       // peak\r\n\t\tfloat c = target_pos.y;     // final\r\n\r\n\t\tgravity = -4 * (a - 2 * b \u002B c) / (time * time);\r\n\t\tfire_velocity.y = -(3 * a - 4 * b \u002B c) / time;\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t// Solve the firing arc with a fixed lateral speed. Vertical speed and gravity varies. \r\n\t// This enables a visually pleasing arc.\r\n\t//\r\n\t// proj_pos (Vector3): point projectile will fire from\r\n\t// lateral_speed (float): scalar speed of projectile along XZ plane\r\n\t// target_pos (Vector3): point projectile is trying to hit\r\n\t// max_height (float): height above Max(proj_pos, impact_pos) for projectile to peak at\r\n\t//\r\n\t// fire_velocity (out Vector3): firing velocity\r\n\t// gravity (out float): gravity necessary to projectile to hit precisely max_height\r\n\t// impact_point (out Vector3): point where moving target will be hit\r\n\t//\r\n\t// return (bool): true if a valid solution was found\r\n\tpublic static bool solve_ballistic_arc_lateral( Vector3 proj_pos, float lateral_speed, Vector3 target, Vector3 target_velocity, float max_height_offset, out Vector3 fire_velocity, out float gravity, out Vector3 impact_point )\r\n\t{\r\n\r\n\t\t// Handling these cases is up to your project\u0027s coding standards\r\n\t\t// Debug.Assert( proj_pos != target \u0026\u0026 lateral_speed \u003E 0, \u0022fts.solve_ballistic_arc_lateral called with invalid data\u0022 );\r\n\t\tif ( proj_pos == target || lateral_speed \u003C= 0f )\r\n\t\t{\r\n\t\t\tfire_velocity = Vector3.Zero;\r\n\t\t\tgravity = 0f;\r\n\t\t\timpact_point = Vector3.Zero;\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Initialize output variables\r\n\t\tfire_velocity = Vector3.Zero;\r\n\t\tgravity = 0f;\r\n\t\timpact_point = Vector3.Zero;\r\n\r\n\t\t// Ground plane terms\r\n\t\tVector3 targetVelXZ = new Vector3( target_velocity.x, 0f, target_velocity.z );\r\n\t\tVector3 diffXZ = target - proj_pos;\r\n\t\tdiffXZ.y = 0;\r\n\r\n\t\t// Derivation\r\n\t\t//   (1) Base formula: |P \u002B V*t| = S*t\r\n\t\t//   (2) Substitute variables: |diffXZ \u002B targetVelXZ*t| = S*t\r\n\t\t//   (3) Square both sides: Dot(diffXZ,diffXZ) \u002B 2*Dot(diffXZ, targetVelXZ)*t \u002B Dot(targetVelXZ, targetVelXZ)*t^2 = S^2 * t^2\r\n\t\t//   (4) Quadratic: (Dot(targetVelXZ,targetVelXZ) - S^2)t^2 \u002B (2*Dot(diffXZ, targetVelXZ))*t \u002B Dot(diffXZ, diffXZ) = 0\r\n\t\tfloat c0 = Vector3.Dot( targetVelXZ, targetVelXZ ) - lateral_speed * lateral_speed;\r\n\t\tfloat c1 = 2f * Vector3.Dot( diffXZ, targetVelXZ );\r\n\t\tfloat c2 = Vector3.Dot( diffXZ, diffXZ );\r\n\t\tdouble t0, t1;\r\n\t\tint n = Ballistics.SolveQuadric( c0, c1, c2, out t0, out t1 );\r\n\r\n\t\t// pick smallest, positive time\r\n\t\tbool valid0 = n \u003E 0 \u0026\u0026 t0 \u003E 0;\r\n\t\tbool valid1 = n \u003E 1 \u0026\u0026 t1 \u003E 0;\r\n\r\n\t\tfloat t;\r\n\t\tif ( !valid0 \u0026\u0026 !valid1 )\r\n\t\t\treturn false;\r\n\t\telse if ( valid0 \u0026\u0026 valid1 )\r\n\t\t\tt = MathF.Min( (float)t0, (float)t1 );\r\n\t\telse\r\n\t\t\tt = valid0 ? (float)t0 : (float)t1;\r\n\r\n\t\t// Calculate impact point\r\n\t\timpact_point = target \u002B (target_velocity * t);\r\n\r\n\t\t// Calculate fire velocity along XZ plane\r\n\t\tVector3 dir = impact_point - proj_pos;\r\n\t\tfire_velocity = new Vector3( dir.x, 0f, dir.z ).Normal * lateral_speed;\r\n\r\n\t\t// Solve system of equations. Hit max_height at t=.5*time. Hit target at t=time.\r\n\t\t//\r\n\t\t// peak = y0 \u002B vertical_speed*halfTime \u002B .5*gravity*halfTime^2\r\n\t\t// end = y0 \u002B vertical_speed*time \u002B .5*gravity*time^s\r\n\t\t// Wolfram Alpha: solve b = a \u002B .5*v*t \u002B .5*g*(.5*t)^2, c = a \u002B vt \u002B .5*g*t^2 for g, v\r\n\t\tfloat a = proj_pos.y;       // initial\r\n\t\tfloat b = MathF.Max( proj_pos.y, impact_point.y ) \u002B max_height_offset;  // peak\r\n\t\tfloat c = impact_point.y;   // final\r\n\r\n\t\tgravity = -4 * (a - 2 * b \u002B c) / (t * t);\r\n\t\tfire_velocity.y = -(3 * a - 4 * b \u002B c) / t;\r\n\r\n\t\treturn true;\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"MyComponent.cs","FileName":"MyComponent.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"\r\npublic sealed class MyComponent : Component\r\n{\r\n\t[Property] public string StringProperty { get; set; }\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"TrailGroundFinder.cs","FileName":"TrailGroundFinder.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\npublic sealed class TrailGroundFinder : Component\r\n{\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tSceneTraceResult groundCheck = Scene.Trace.Ray( GameObject.WorldPosition \u002B Vector3.Up * 50, GameObject.WorldPosition \u002B Vector3.Down * 100 ) // 48 is radius\r\n\t\t\t.Radius( 10 )\r\n\t\t\t.WithoutTags(\u0022enemy\u0022, \u0022player\u0022)\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.Run();\r\n\r\n\t\tGameObject.WorldPosition = GameObject.Parent.WorldPosition.WithZ(groundCheck.HitPosition.z);\r\n\t\tGameObject.WorldRotation = GameObject.WorldRotation.Angles().WithRoll( 0 ).WithPitch( 75 );\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"Pointkit.cs","FileName":"Pointkit.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\n\r\npublic sealed class Pointkit : Component, Component.ITriggerListener\r\n{\r\n\t[Property] float PointAmount = 500f;\r\n\r\n\tHighscoreManager HighscoreManager;\r\n\r\n\tSceneTraceResult GroundTrace;\r\n\tint id = 0;\r\n\tVector3 MittelPunkt;\r\n\tfloat SchwebeDistance = 50;\r\n\tfloat SchwebeFrequenz = 1f;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tHighscoreManager = Scene.Get\u003CHighscoreManager\u003E();\r\n\r\n\t\t// Checkt ground und setzt Mittelpunkt\r\n\t\tGroundTrace = Scene.Trace\r\n\t\t\t.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 10000 )\r\n\t\t\t.Radius( 1 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022dead\u0022 )\r\n\t\t\t.Run();\r\n\r\n\t\tMittelPunkt = GroundTrace.EndPosition \u002B Vector3.Up * 80;\r\n\r\n\t\tGameObject.WorldPosition = MittelPunkt;\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\t// Rotiert das Kit langsam um die Y-Achse\r\n\t\t// Zeit schiebt Sinusfunktion(Welle) voran, multipliziert mit Frequenz f\u00FCr enge oder weite Wellen, dann mit Distanz multiplizieren und auf MittelPunkt addieren\r\n\r\n\t\tTransform transform = new Transform( \r\n\t\t\tMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * SchwebeFrequenz ) * SchwebeDistance),\r\n\t\t\tRotation.From( 0, GameObject.WorldRotation.Yaw() \u002B 0.2f, 0 ),\r\n\t\t\tWorldScale\t\t\r\n\t\t);\r\n\r\n\t\tWorldTransform = transform;\r\n\t}\r\n\r\n\tpublic void OnTriggerEnter( GameObject other )\r\n\t{\r\n\t\tif ( !other.Tags.Has( \u0022player\u0022 ) ) return;\r\n\t\tid\u002B\u002B;\r\n\t\tif ( id \u003E 1 ) return;\r\n\t\tLog.Info( $\u0022Collided with {other.Name}\u0022 );\r\n\r\n\t\tHighscoreManager.IncreaseScore( PointAmount );\r\n\t\tSound.Play( \u0022sounds/medikitsound.sound\u0022, WorldPosition );\r\n\r\n\t\tGameObject.Parent.Destroy();\r\n\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"GrassField.cs","FileName":"GrassField.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Collections.Generic;\r\n\r\nnamespace OpenGrass;\r\n\r\n/// \u003Csummary\u003E\r\n/// Standalone GPU-instanced grass field for s\u0026amp;box.\r\n///\r\n/// Drops grass blades by raycasting downward across a rectangular area, groups\r\n/// them into spatial chunks, and renders each chunk via DrawModelInstanced\r\n/// with distance-based LOD, frustum culling, and a simpler far mesh.\r\n///\r\n/// By default it builds an X-shaped two-quad mesh, but you can plug a list of\r\n/// \u003Csee cref=\u0022CustomModels\u0022/\u003E (and optional \u003Csee cref=\u0022CustomModelsSimple\u0022/\u003E for\r\n/// far distances) \u2014 each blade picks one deterministically from \u003Csee cref=\u0022Seed\u0022/\u003E,\r\n/// giving variety. Author custom models 1 unit tall and 1 unit wide; they get\r\n/// scaled per-blade by \u003Csee cref=\u0022Width\u0022/\u003E / \u003Csee cref=\u0022Height\u0022/\u003E.\r\n///\r\n/// Self-contained: no external dependencies. Drop into any s\u0026amp;box project.\r\n/// \u003C/summary\u003E\r\npublic sealed class GrassField : Component\r\n{\r\n\t// === Field ===\r\n\r\n\t/// \u003Csummary\u003E Field size in world units (X, Y). Centered on the GameObject. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Field\u0022 )]\r\n\tpublic Vector2 FieldSize { get; set; } = new( 45000, 45000 );\r\n\r\n\t/// \u003Csummary\u003E Blades per square unit (before slope/tag/exclusion filtering). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Field\u0022 ), Range( 0.1f, 10f )]\r\n\tpublic float Density { get; set; } = 2f;\r\n\r\n\t/// \u003Csummary\u003E Random seed \u2014 same value gives the same layout. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Field\u0022 )]\r\n\tpublic int Seed { get; set; } = 12345;\r\n\r\n\t// === Mesh ===\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Material used for the built-in mesh. Should support vertex colors and ideally\r\n\t/// alpha-test cutout (g_flAlphaTestReference). If null, a plain fallback is created\r\n\t/// so the component still renders something \u2014 assign your own for an actual grass look.\r\n\t/// \u003C/summary\u003E\r\n\t[Property, Category( \u0022Mesh\u0022 )]\r\n\tpublic Material GrassMaterial { get; set; }\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Optional custom meshes used in place of the built-in two-quad blade. If more than one\r\n\t/// is supplied, each blade picks one deterministically from \u003Csee cref=\u0022Seed\u0022/\u003E, giving you\r\n\t/// variety (different shapes / sizes / textures) at the cost of one extra DrawModelInstanced\r\n\t/// per used model per chunk. Empty list = built-in mesh for every blade.\r\n\t/// Author each model 1\u00D71\u00D71 units \u2014 they are scaled per-blade by Width/Height.\r\n\t/// \u003C/summary\u003E\r\n\t[Property, Category( \u0022Mesh\u0022 )]\r\n\tpublic List\u003CModel\u003E CustomModels { get; set; } = new();\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Optional simpler meshes for far-distance LOD, parallel to \u003Csee cref=\u0022CustomModels\u0022/\u003E.\r\n\t/// Index N here is the simple variant for index N of CustomModels. Holes (null entries\r\n\t/// or shorter list) fall back to the corresponding CustomModels entry, then the built-in.\r\n\t/// \u003C/summary\u003E\r\n\t[Property, Category( \u0022Mesh\u0022 )]\r\n\tpublic List\u003CModel\u003E CustomModelsSimple { get; set; } = new();\r\n\r\n\t// === Blade ===\r\n\r\n\t[Property, Category( \u0022Blade\u0022 ), Range( 1f, 200f )]\r\n\tpublic float Height { get; set; } = 1f;\r\n\r\n\t[Property, Category( \u0022Blade\u0022 ), Range( 1f, 100f )]\r\n\tpublic float Width { get; set; } = 1f;\r\n\r\n\t/// \u003Csummary\u003E Per-blade \u00B1variation of Height/Width (0 = uniform, 1 = up to \u00B1100%). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Blade\u0022 ), Range( 0f, 1f )]\r\n\tpublic float HeightVariation { get; set; } = 0.5f;\r\n\r\n\t/// \u003Csummary\u003E Vertex color at the base of the built-in blade. Ignored when CustomModels is non-empty. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Blade\u0022 )]\r\n\tpublic Color BottomColor { get; set; } = Color.White;\r\n\r\n\t/// \u003Csummary\u003E Vertex color at the tip of the built-in blade. Ignored when CustomModels is non-empty. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Blade\u0022 )]\r\n\tpublic Color TipColor { get; set; } = Color.White;\r\n\r\n\t/// \u003Csummary\u003E Tilt blades to match the surface normal (slopes follow the ground). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Blade\u0022 )]\r\n\tpublic bool AlignToSurface { get; set; } = true;\r\n\r\n\t// === Performance ===\r\n\r\n\t/// \u003Csummary\u003E Hard cap on blade count \u2014 guards against absurd densities. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 100, 2_000_000 )]\r\n\tpublic int MaxGrassCount { get; set; } = 150_000;\r\n\r\n\t/// \u003Csummary\u003E Spatial chunk size. Smaller = finer culling but more draw calls. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 50f, 500f )]\r\n\tpublic float ChunkSize { get; set; } = 500f;\r\n\r\n\t/// \u003Csummary\u003E Chunks past this distance from the camera are not rendered. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 100f, 5000f )]\r\n\tpublic float RenderDistance { get; set; } = 4500f;\r\n\r\n\t[Property, Category( \u0022Performance\u0022 )]\r\n\tpublic bool UseLOD { get; set; } = true;\r\n\r\n\t/// \u003Csummary\u003E Within this distance, 100% of blades render. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 100f, 2000f )]\r\n\tpublic float LOD0Distance { get; set; } = 2000f;\r\n\r\n\t/// \u003Csummary\u003E Up to this distance, ~50% of blades render. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 200f, 3000f )]\r\n\tpublic float LOD1Distance { get; set; } = 3000f;\r\n\r\n\t/// \u003Csummary\u003E Past this distance, ~25% of blades render. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 300f, 4000f )]\r\n\tpublic float LOD2Distance { get; set; } = 4000f;\r\n\r\n\t/// \u003Csummary\u003E Skip chunks behind the camera (with margin for peripheral vision). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 )]\r\n\tpublic bool UseFrustumCulling { get; set; } = true;\r\n\r\n\t/// \u003Csummary\u003E Chunks closer than this never get frustum-culled (avoids popping when turning). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 100f, 2000f )]\r\n\tpublic float FrustumCullMinDistance { get; set; } = 800f;\r\n\r\n\t/// \u003Csummary\u003E Past this distance, render the simpler (1-quad / CustomModelsSimple) variant. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 ), Range( 500f, 4000f )]\r\n\tpublic float SimpleMeshDistance { get; set; } = 3000f;\r\n\r\n\t/// \u003Csummary\u003E Cast shadows. Very expensive at high blade counts \u2014 leave off unless you need it. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Performance\u0022 )]\r\n\tpublic bool CastShadows { get; set; } = false;\r\n\r\n\t// === Filtering ===\r\n\r\n\t/// \u003Csummary\u003E Required tag on the surface object. Empty = any object. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Filtering\u0022 )]\r\n\tpublic string RequiredTag { get; set; } = \u0022\u0022;\r\n\r\n\t/// \u003Csummary\u003E Tags that block grass spawning AND are skipped during the trace. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Filtering\u0022 )]\r\n\tpublic List\u003Cstring\u003E IgnoredTags { get; set; } = new();\r\n\r\n\t/// \u003Csummary\u003E Tag for player colliders \u2014 skipped during the ground trace. Empty disables. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Filtering\u0022 )]\r\n\tpublic string PlayerTag { get; set; } = \u0022player\u0022;\r\n\r\n\t/// \u003Csummary\u003E Maximum surface slope (degrees) that still spawns grass. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Filtering\u0022 ), Range( 0f, 90f )]\r\n\tpublic float MaxSlopeAngle { get; set; } = 45f;\r\n\r\n\t/// \u003Csummary\u003E Trigger volumes with any of these tags carve out \u0022no grass\u0022 zones (paths, roads, ...). \u003C/summary\u003E\r\n\t[Property, Category( \u0022Filtering\u0022 )]\r\n\tpublic List\u003Cstring\u003E ExclusionZoneTags { get; set; } = new() { \u0022nograss\u0022 };\r\n\r\n\t// === Trace ===\r\n\r\n\t/// \u003Csummary\u003E Trace start height, relative to the GameObject\u0027s WorldPosition. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Trace\u0022 ), Range( -4000f, 4000f )]\r\n\tpublic float TraceStartHeight { get; set; } = 0f;\r\n\r\n\t/// \u003Csummary\u003E Trace end height, relative to the GameObject\u0027s WorldPosition. \u003C/summary\u003E\r\n\t[Property, Category( \u0022Trace\u0022 ), Range( -4000f, 4000f )]\r\n\tpublic float TraceEndHeight { get; set; } = -2000f;\r\n\r\n\t// === Buttons ===\r\n\r\n\t[Button( \u0022Regenerate\u0022 )]\r\n\tpublic void Regenerate()\r\n\t{\r\n\t\t_isDirty = true;\r\n\t\t_gizmoTracesDirty = true;\r\n\t}\r\n\r\n\t[Button( \u0022Refresh Gizmo Traces\u0022 ), Category( \u0022Debug\u0022 )]\r\n\tpublic void RefreshGizmoTraces() =\u003E _gizmoTracesDirty = true;\r\n\r\n\t// === Public runtime API ===\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Apply a runtime tint via the material\u0027s g_vColorTint parameter.\r\n\t/// Useful for day/night, biome blends, or ambient zone effects driven by other systems.\r\n\t/// Only affects the auto-created fallback material \u2014 when you assign your own\r\n\t/// \u003Csee cref=\u0022GrassMaterial\u0022/\u003E, drive its parameters yourself.\r\n\t/// \u003C/summary\u003E\r\n\tpublic void SetTint( Color tint )\r\n\t{\r\n\t\t_ownedMaterial?.Set( \u0022g_vColorTint\u0022, tint );\r\n\t}\r\n\r\n\t// === Internals ===\r\n\r\n\tprivate Model _builtInModel;\r\n\tprivate Model _builtInModelSimple;\r\n\tprivate Material _ownedMaterial;\r\n\tprivate SceneCustomObject _sceneObject;\r\n\tprivate Transform[] _allTransforms;\r\n\tprivate readonly List\u003CGrassChunk\u003E _chunks = new();\r\n\tprivate readonly Dictionary\u003C(int, int), int\u003E _chunkGrid = new();\r\n\tprivate readonly List\u003CCachedGrassData\u003E _cachedGrassData = new();\r\n\tprivate bool _isDirty = true;\r\n\r\n\tprivate readonly List\u003CGizmoTraceData\u003E _gizmoTraces = new();\r\n\tprivate bool _gizmoTracesDirty = true;\r\n\r\n\tprivate struct CachedGrassData\r\n\t{\r\n\t\tpublic Vector3 Position;\r\n\t\tpublic Vector3 SurfaceNormal;\r\n\t\tpublic float Height;\r\n\t\tpublic float Width;\r\n\t\tpublic float Yaw;\r\n\t\t// 0 = renders on every LOD, 1 = LOD0\u002BLOD1, 2 = LOD0 only.\r\n\t\t// Sorting blades by (ModelIndex, LodGroup) lets us draw each (model, LOD)\r\n\t\t// run as one contiguous instanced call.\r\n\t\tpublic byte LodGroup;\r\n\t\tpublic byte ModelIndex; // index into CustomModels, or 0 when using built-in\r\n\t}\r\n\r\n\tprivate struct ModelSegment\r\n\t{\r\n\t\tpublic int ModelIndex;\r\n\t\tpublic int StartIndex;\r\n\t\tpublic int Lod0Count; // all blades for this model \u2014 drawn at LOD0\r\n\t\tpublic int Lod1Count; // ~50% \u2014 drawn at LOD1\r\n\t\tpublic int Lod2Count; // ~25% \u2014 drawn at LOD2 and beyond\r\n\t}\r\n\r\n\tprivate struct GrassChunk\r\n\t{\r\n\t\tpublic Vector3 Center;\r\n\t\tpublic BBox Bounds;\r\n\t\t// One entry per ModelIndex used in this chunk. Each segment is one DrawModelInstanced.\r\n\t\tpublic ModelSegment[] Segments;\r\n\t}\r\n\r\n\tprivate struct GizmoTraceData\r\n\t{\r\n\t\tpublic Vector3 StartPos;\r\n\t\tpublic Vector3 HitPos;\r\n\t\tpublic Color Color;\r\n\t\tpublic string DebugInfo;\r\n\t}\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tEnsureMeshes();\r\n\t\tCreateSceneObject();\r\n\t\tRegenerateGrass();\r\n\t\t_gizmoTracesDirty = true;\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\t_sceneObject?.Delete();\r\n\t\t_sceneObject = null;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( _isDirty )\r\n\t\t{\r\n\t\t\tRegenerateGrass();\r\n\t\t\t_isDirty = false;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void EnsureMeshes()\r\n\t{\r\n\t\t// Always build the built-in pair \u2014 it\u0027s the universal fallback for null entries\r\n\t\t// or holes in CustomModels / CustomModelsSimple. Cheap (two tiny meshes).\r\n\t\tvar material = GrassMaterial ?? CreateFallbackMaterial();\r\n\t\t_builtInModel = BuildBuiltInModel( material, twoQuads: true );\r\n\t\t_builtInModelSimple = BuildBuiltInModel( material, twoQuads: false );\r\n\t}\r\n\r\n\tprivate Material CreateFallbackMaterial()\r\n\t{\r\n\t\t_ownedMaterial = Material.Create( $\u0022opengrass_fallback_{GetHashCode()}\u0022, \u0022shaders/complex.shader\u0022 );\r\n\t\t_ownedMaterial.Set( \u0022g_vColorTint\u0022, Color.White );\r\n\t\treturn _ownedMaterial;\r\n\t}\r\n\r\n\tprivate Model BuildBuiltInModel( Material material, bool twoQuads )\r\n\t{\r\n\t\tconst float w = 0.5f;\r\n\t\tconst float h = 1.0f;\r\n\r\n\t\tvar vb = new VertexBuffer();\r\n\t\tvb.Init( true );\r\n\r\n\t\tAddBuiltInQuad( vb, new Vector3( -w, 0, 0 ), new Vector3( w, 0, 0 ), new Vector3( w, 0, h ), new Vector3( -w, 0, h ) );\r\n\t\tif ( twoQuads )\r\n\t\t\tAddBuiltInQuad( vb, new Vector3( 0, -w, 0 ), new Vector3( 0, w, 0 ), new Vector3( 0, w, h ), new Vector3( 0, -w, h ) );\r\n\r\n\t\tvar mesh = new Mesh( material );\r\n\t\tmesh.CreateBuffers( vb );\r\n\t\treturn Model.Builder.AddMesh( mesh ).Create();\r\n\t}\r\n\r\n\tprivate void AddBuiltInQuad( VertexBuffer vb, Vector3 a, Vector3 b, Vector3 c, Vector3 d )\r\n\t{\r\n\t\tvar normal = Vector3.Cross( b - a, d - a ).Normal;\r\n\t\tvar tangent = (b - a).Normal;\r\n\r\n\t\t// Front face\r\n\t\tvb.Add( new Vertex( a, normal, tangent, new Vector2( 0, 1 ) ) { Color = BottomColor } );\r\n\t\tvb.Add( new Vertex( b, normal, tangent, new Vector2( 1, 1 ) ) { Color = BottomColor } );\r\n\t\tvb.Add( new Vertex( c, normal, tangent, new Vector2( 1, 0 ) ) { Color = TipColor } );\r\n\t\tvb.Add( new Vertex( d, normal, tangent, new Vector2( 0, 0 ) ) { Color = TipColor } );\r\n\t\tvb.AddTriangleIndex( 4, 3, 2 );\r\n\t\tvb.AddTriangleIndex( 2, 1, 4 );\r\n\r\n\t\t// Back face \u2014 blades shouldn\u0027t disappear from behind\r\n\t\tvb.Add( new Vertex( b, -normal, tangent, new Vector2( 1, 1 ) ) { Color = BottomColor } );\r\n\t\tvb.Add( new Vertex( a, -normal, tangent, new Vector2( 0, 1 ) ) { Color = BottomColor } );\r\n\t\tvb.Add( new Vertex( d, -normal, tangent, new Vector2( 0, 0 ) ) { Color = TipColor } );\r\n\t\tvb.Add( new Vertex( c, -normal, tangent, new Vector2( 1, 0 ) ) { Color = TipColor } );\r\n\t\tvb.AddTriangleIndex( 4, 3, 2 );\r\n\t\tvb.AddTriangleIndex( 2, 1, 4 );\r\n\t}\r\n\r\n\tprivate void CreateSceneObject()\r\n\t{\r\n\t\tif ( !Scene.IsValid() ) return;\r\n\r\n\t\t_sceneObject = new SceneCustomObject( Scene.SceneWorld )\r\n\t\t{\r\n\t\t\tRenderOverride = RenderGrass,\r\n\t\t\tTransform = WorldTransform,\r\n\t\t};\r\n\t\t_sceneObject.Flags.CastShadows = CastShadows;\r\n\t\t_sceneObject.Flags.IsOpaque = true;\r\n\t\t_sceneObject.Flags.IsTranslucent = false;\r\n\t}\r\n\r\n\tprivate void RegenerateGrass()\r\n\t{\r\n\t\tif ( !Scene.IsValid() ) return;\r\n\r\n\t\tvar random = new Random( Seed );\r\n\r\n\t\tint potentialCount = (int)(FieldSize.x * FieldSize.y * Density);\r\n\t\tpotentialCount = Math.Min( potentialCount, MaxGrassCount );\r\n\r\n\t\tfloat halfX = FieldSize.x * 0.5f;\r\n\t\tfloat halfY = FieldSize.y * 0.5f;\r\n\t\tfloat maxSlopeCos = MathF.Cos( MaxSlopeAngle * MathF.PI / 180f );\r\n\r\n\t\t_cachedGrassData.Clear();\r\n\r\n\t\t// 1 when no custom models \u2014 every blade uses the built-in mesh (ModelIndex 0).\r\n\t\tint modelCount = Math.Max( 1, CustomModels?.Count ?? 0 );\r\n\r\n\t\tfor ( int i = 0; i \u003C potentialCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat x = (float)(random.NextDouble() * 2 - 1) * halfX;\r\n\t\t\tfloat y = (float)(random.NextDouble() * 2 - 1) * halfY;\r\n\r\n\t\t\tvar startPos = WorldPosition \u002B new Vector3( x, y, TraceStartHeight );\r\n\t\t\tvar endPos = WorldPosition \u002B new Vector3( x, y, TraceEndHeight );\r\n\r\n\t\t\tvar trace = BuildGroundTrace( startPos, endPos ).Run();\r\n\t\t\tif ( !trace.Hit ) continue;\r\n\t\t\tif ( !IsValidSurface( trace ) ) continue;\r\n\r\n\t\t\tfloat dotUp = Vector3.Dot( trace.Normal, Vector3.Up );\r\n\t\t\tif ( dotUp \u003C maxSlopeCos ) continue;\r\n\r\n\t\t\tif ( IsInExclusionZone( trace.EndPosition ) ) continue;\r\n\r\n\t\t\tfloat scaleVar = 1f \u002B (float)(random.NextDouble() * 2 - 1) * HeightVariation;\r\n\t\t\tfloat yaw = (float)(random.NextDouble() * 360);\r\n\t\t\tbyte modelIndex = (byte)random.Next( modelCount );\r\n\r\n\t\t\t// 25% group 0 (LOD0\u002BLOD1\u002BLOD2), 25% group 1 (LOD0\u002BLOD1), 50% group 2 (LOD0 only).\r\n\t\t\tbyte lodGroup = (byte)(i % 4);\r\n\t\t\tif ( lodGroup \u003E= 2 ) lodGroup = 2;\r\n\r\n\t\t\t_cachedGrassData.Add( new CachedGrassData\r\n\t\t\t{\r\n\t\t\t\tPosition = trace.EndPosition,\r\n\t\t\t\tSurfaceNormal = trace.Normal,\r\n\t\t\t\tHeight = Height * scaleVar,\r\n\t\t\t\tWidth = Width * scaleVar,\r\n\t\t\t\tYaw = yaw,\r\n\t\t\t\tLodGroup = lodGroup,\r\n\t\t\t\tModelIndex = modelIndex,\r\n\t\t\t} );\r\n\t\t}\r\n\r\n\t\tBuildChunks();\r\n\t}\r\n\r\n\tprivate SceneTrace BuildGroundTrace( Vector3 from, Vector3 to )\r\n\t{\r\n\t\tvar t = Scene.Trace.Ray( from, to );\r\n\t\tif ( !string.IsNullOrEmpty( PlayerTag ) )\r\n\t\t\tt = t.WithoutTags( PlayerTag );\r\n\t\tif ( IgnoredTags != null )\r\n\t\t{\r\n\t\t\tforeach ( var tag in IgnoredTags )\r\n\t\t\t{\r\n\t\t\t\tif ( !string.IsNullOrEmpty( tag ) )\r\n\t\t\t\t\tt = t.WithoutTags( tag );\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn t;\r\n\t}\r\n\r\n\tprivate bool IsValidSurface( SceneTraceResult trace )\r\n\t{\r\n\t\tif ( trace.GameObject == null ) return false;\r\n\r\n\t\tvar tags = trace.GameObject.Tags;\r\n\r\n\t\tif ( IgnoredTags != null )\r\n\t\t{\r\n\t\t\tforeach ( var tag in IgnoredTags )\r\n\t\t\t{\r\n\t\t\t\tif ( !string.IsNullOrEmpty( tag ) \u0026\u0026 tags.Has( tag ) )\r\n\t\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !string.IsNullOrEmpty( RequiredTag ) )\r\n\t\t\treturn tags.Has( RequiredTag );\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\tprivate bool IsInExclusionZone( Vector3 position )\r\n\t{\r\n\t\tif ( ExclusionZoneTags == null || ExclusionZoneTags.Count == 0 )\r\n\t\t\treturn false;\r\n\r\n\t\t// Probe up and down a short distance to catch trigger volumes that cross the surface.\r\n\t\tif ( ExclusionZoneTrace( position, position \u002B Vector3.Up * 50f ) ) return true;\r\n\t\tif ( ExclusionZoneTrace( position \u002B Vector3.Up * 5f, position - Vector3.Up * 50f ) ) return true;\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate bool ExclusionZoneTrace( Vector3 from, Vector3 to )\r\n\t{\r\n\t\tvar trace = Scene.Trace.Ray( from, to ).HitTriggers();\r\n\t\tif ( !string.IsNullOrEmpty( PlayerTag ) )\r\n\t\t\ttrace = trace.WithoutTags( PlayerTag );\r\n\r\n\t\tvar hit = trace.Run();\r\n\t\tif ( !hit.Hit || hit.GameObject == null ) return false;\r\n\r\n\t\tforeach ( var tag in ExclusionZoneTags )\r\n\t\t{\r\n\t\t\tif ( !string.IsNullOrEmpty( tag ) \u0026\u0026 hit.GameObject.Tags.Has( tag ) )\r\n\t\t\t\treturn true;\r\n\t\t}\r\n\t\treturn false;\r\n\t}\r\n\r\n\tprivate void BuildChunks()\r\n\t{\r\n\t\t_chunks.Clear();\r\n\t\t_chunkGrid.Clear();\r\n\r\n\t\tif ( _cachedGrassData.Count == 0 )\r\n\t\t{\r\n\t\t\t_allTransforms = Array.Empty\u003CTransform\u003E();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Bucket blade indices by chunk grid cell.\r\n\t\tvar chunkBuckets = new Dictionary\u003C(int, int), List\u003Cint\u003E\u003E();\r\n\t\tfor ( int i = 0; i \u003C _cachedGrassData.Count; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tvar pos = _cachedGrassData[i].Position;\r\n\t\t\tint cx = (int)Math.Floor( (pos.x - WorldPosition.x) / ChunkSize );\r\n\t\t\tint cy = (int)Math.Floor( (pos.y - WorldPosition.y) / ChunkSize );\r\n\t\t\tif ( !chunkBuckets.TryGetValue( (cx, cy), out var list ) )\r\n\t\t\t\tchunkBuckets[(cx, cy)] = list = new List\u003Cint\u003E();\r\n\t\t\tlist.Add( i );\r\n\t\t}\r\n\r\n\t\t_allTransforms = new Transform[_cachedGrassData.Count];\r\n\t\tint currentIndex = 0;\r\n\r\n\t\tvar segmentScratch = new List\u003CModelSegment\u003E();\r\n\r\n\t\tforeach ( var (key, indices) in chunkBuckets )\r\n\t\t{\r\n\t\t\t// Sort blades by (ModelIndex, LodGroup) \u2014 gives us contiguous runs of\r\n\t\t\t// \u0022same model, sorted by LOD group\u0022 so each segment\u0027s first N transforms\r\n\t\t\t// are exactly what we want to draw at a given LOD.\r\n\t\t\tindices.Sort( ( a, b ) =\u003E\r\n\t\t\t{\r\n\t\t\t\tvar da = _cachedGrassData[a];\r\n\t\t\t\tvar db = _cachedGrassData[b];\r\n\t\t\t\tint byModel = da.ModelIndex.CompareTo( db.ModelIndex );\r\n\t\t\t\treturn byModel != 0 ? byModel : da.LodGroup.CompareTo( db.LodGroup );\r\n\t\t\t} );\r\n\r\n\t\t\tvar chunk = new GrassChunk();\r\n\t\t\tvar bounds = new BBox();\r\n\t\t\tfloat cxSum = 0, cySum = 0, czSum = 0;\r\n\r\n\t\t\tsegmentScratch.Clear();\r\n\t\t\tModelSegment current = default;\r\n\t\t\tint lastModelIndex = -1;\r\n\r\n\t\t\tforeach ( var idx in indices )\r\n\t\t\t{\r\n\t\t\t\tvar data = _cachedGrassData[idx];\r\n\r\n\t\t\t\tRotation rot;\r\n\t\t\t\tif ( AlignToSurface \u0026\u0026 data.SurfaceNormal != Vector3.Up )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar surfaceRot = Rotation.FromToRotation( Vector3.Up, data.SurfaceNormal );\r\n\t\t\t\t\tvar yawRot = Rotation.FromYaw( data.Yaw );\r\n\t\t\t\t\trot = surfaceRot * yawRot;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\trot = Rotation.FromYaw( data.Yaw );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tvar scale = new Vector3( data.Width, data.Width, data.Height );\r\n\t\t\t\t_allTransforms[currentIndex] = new Transform( data.Position, rot, scale );\r\n\r\n\t\t\t\tif ( data.ModelIndex != lastModelIndex )\r\n\t\t\t\t{\r\n\t\t\t\t\tif ( lastModelIndex != -1 )\r\n\t\t\t\t\t\tsegmentScratch.Add( current );\r\n\t\t\t\t\tcurrent = new ModelSegment { ModelIndex = data.ModelIndex, StartIndex = currentIndex };\r\n\t\t\t\t\tlastModelIndex = data.ModelIndex;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( data.LodGroup == 0 ) current.Lod2Count\u002B\u002B;\r\n\t\t\t\tif ( data.LodGroup \u003C= 1 ) current.Lod1Count\u002B\u002B;\r\n\t\t\t\tcurrent.Lod0Count\u002B\u002B;\r\n\r\n\t\t\t\tbounds = bounds.AddPoint( data.Position );\r\n\t\t\t\tbounds = bounds.AddPoint( data.Position \u002B new Vector3( 0, 0, Height ) );\r\n\t\t\t\tcxSum \u002B= data.Position.x;\r\n\t\t\t\tcySum \u002B= data.Position.y;\r\n\t\t\t\tczSum \u002B= data.Position.z;\r\n\t\t\t\tcurrentIndex\u002B\u002B;\r\n\t\t\t}\r\n\r\n\t\t\tif ( lastModelIndex != -1 )\r\n\t\t\t\tsegmentScratch.Add( current );\r\n\r\n\t\t\tchunk.Segments = segmentScratch.ToArray();\r\n\t\t\tchunk.Center = new Vector3( cxSum / indices.Count, cySum / indices.Count, czSum / indices.Count );\r\n\t\t\tchunk.Bounds = bounds;\r\n\r\n\t\t\t_chunkGrid[key] = _chunks.Count;\r\n\t\t\t_chunks.Add( chunk );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void RenderGrass( SceneObject obj )\r\n\t{\r\n\t\tif ( _allTransforms == null || _allTransforms.Length == 0 || _chunks.Count == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar camera = Scene.Camera;\r\n\t\tif ( !camera.IsValid() ) return;\r\n\r\n\t\tvar cameraPos = camera.WorldPosition;\r\n\t\tvar cameraForward = camera.WorldRotation.Forward;\r\n\r\n\t\tfloat renderDistSq = RenderDistance * RenderDistance;\r\n\t\tfloat lod0DistSq = LOD0Distance * LOD0Distance;\r\n\t\tfloat lod1DistSq = LOD1Distance * LOD1Distance;\r\n\t\tfloat simpleMeshDistSq = SimpleMeshDistance * SimpleMeshDistance;\r\n\t\tfloat frustumMinDistSq = FrustumCullMinDistance * FrustumCullMinDistance;\r\n\r\n\t\t// Iterate only the chunks within RenderDistance of the camera (square around camera cell).\r\n\t\tint chunksInRadius = (int)MathF.Ceiling( RenderDistance / ChunkSize ) \u002B 1;\r\n\t\tint cameraCx = (int)MathF.Floor( (cameraPos.x - WorldPosition.x) / ChunkSize );\r\n\t\tint cameraCy = (int)MathF.Floor( (cameraPos.y - WorldPosition.y) / ChunkSize );\r\n\r\n\t\tfor ( int dx = -chunksInRadius; dx \u003C= chunksInRadius; dx\u002B\u002B )\r\n\t\t{\r\n\t\t\tfor ( int dy = -chunksInRadius; dy \u003C= chunksInRadius; dy\u002B\u002B )\r\n\t\t\t{\r\n\t\t\t\tif ( !_chunkGrid.TryGetValue( (cameraCx \u002B dx, cameraCy \u002B dy), out int chunkIndex ) )\r\n\t\t\t\t\tcontinue;\r\n\r\n\t\t\t\tvar chunk = _chunks[chunkIndex];\r\n\r\n\t\t\t\tfloat distSq = (chunk.Center - cameraPos).LengthSquared;\r\n\t\t\t\tif ( distSq \u003E renderDistSq ) continue;\r\n\r\n\t\t\t\tif ( UseFrustumCulling \u0026\u0026 distSq \u003E frustumMinDistSq )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar toChunk = (chunk.Center - cameraPos).Normal;\r\n\t\t\t\t\t// dot \u003C -0.2 = clearly behind the camera (margin keeps peripheral chunks visible).\r\n\t\t\t\t\tif ( Vector3.Dot( cameraForward, toChunk ) \u003C -0.2f )\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tbool useSimple = distSq \u003E simpleMeshDistSq;\r\n\r\n\t\t\t\t// One DrawModelInstanced per (model used in this chunk).\r\n\t\t\t\tforeach ( var seg in chunk.Segments )\r\n\t\t\t\t{\r\n\t\t\t\t\tint renderCount;\r\n\t\t\t\t\tif ( !UseLOD ) renderCount = seg.Lod0Count;\r\n\t\t\t\t\telse if ( distSq \u003C= lod0DistSq ) renderCount = seg.Lod0Count;\r\n\t\t\t\t\telse if ( distSq \u003C= lod1DistSq ) renderCount = seg.Lod1Count;\r\n\t\t\t\t\telse renderCount = seg.Lod2Count;\r\n\r\n\t\t\t\t\tif ( renderCount \u003C= 0 ) continue;\r\n\r\n\t\t\t\t\tvar model = useSimple ? PickSimpleModel( seg.ModelIndex ) : PickFullModel( seg.ModelIndex );\r\n\t\t\t\t\tif ( model == null ) continue;\r\n\r\n\t\t\t\t\tvar transforms = _allTransforms.AsSpan( seg.StartIndex, renderCount );\r\n\t\t\t\t\tGraphics.DrawModelInstanced( model, transforms );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate Model PickFullModel( int modelIndex )\r\n\t{\r\n\t\tif ( CustomModels != null \u0026\u0026 modelIndex \u003C CustomModels.Count \u0026\u0026 CustomModels[modelIndex] != null )\r\n\t\t\treturn CustomModels[modelIndex];\r\n\t\treturn _builtInModel;\r\n\t}\r\n\r\n\tprivate Model PickSimpleModel( int modelIndex )\r\n\t{\r\n\t\t// Prefer the simple variant; if missing, drop down to the full custom mesh,\r\n\t\t// then to the built-in simple mesh.\r\n\t\tif ( CustomModelsSimple != null \u0026\u0026 modelIndex \u003C CustomModelsSimple.Count \u0026\u0026 CustomModelsSimple[modelIndex] != null )\r\n\t\t\treturn CustomModelsSimple[modelIndex];\r\n\t\tif ( CustomModels != null \u0026\u0026 modelIndex \u003C CustomModels.Count \u0026\u0026 CustomModels[modelIndex] != null )\r\n\t\t\treturn CustomModels[modelIndex];\r\n\t\treturn _builtInModelSimple ?? _builtInModel;\r\n\t}\r\n\r\n\t// === Gizmos: visualize trace results when this component is selected. ===\r\n\r\n\tprotected override void DrawGizmos()\r\n\t{\r\n\t\tif ( !Gizmo.IsSelected ) return;\r\n\r\n\t\tif ( _gizmoTracesDirty )\r\n\t\t{\r\n\t\t\tRebuildGizmoTraces();\r\n\t\t\t_gizmoTracesDirty = false;\r\n\t\t}\r\n\r\n\t\tvar cameraPos = Gizmo.Camera.Position;\r\n\t\tconst float drawDistSq = 1000f * 1000f;\r\n\t\tconst float textDistSq = 200f * 200f;\r\n\r\n\t\tGizmo.Draw.LineThickness = 1f;\r\n\r\n\t\tforeach ( var trace in _gizmoTraces )\r\n\t\t{\r\n\t\t\tvar worldPos = WorldPosition \u002B trace.StartPos;\r\n\t\t\tvar distSq = (worldPos - cameraPos).LengthSquared;\r\n\t\t\tif ( distSq \u003E drawDistSq ) continue;\r\n\r\n\t\t\tGizmo.Draw.Color = trace.Color;\r\n\t\t\tGizmo.Draw.Line( trace.StartPos, trace.HitPos );\r\n\r\n\t\t\t// Show why a trace was rejected (red = filtered out by IsValidSurface).\r\n\t\t\tif ( trace.Color == Color.Red \u0026\u0026 distSq \u003C textDistSq \u0026\u0026 !string.IsNullOrEmpty( trace.DebugInfo ) )\r\n\t\t\t{\r\n\t\t\t\tGizmo.Draw.Color = Color.White;\r\n\t\t\t\tGizmo.Draw.ScreenText( trace.DebugInfo, WorldPosition \u002B trace.HitPos, Vector2.Zero, \u0022Roboto\u0022, 10 );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void RebuildGizmoTraces()\r\n\t{\r\n\t\t_gizmoTraces.Clear();\r\n\r\n\t\tvar random = new Random( Seed );\r\n\t\tint potentialCount = (int)(FieldSize.x * FieldSize.y * Density);\r\n\t\tpotentialCount = Math.Min( potentialCount, MaxGrassCount );\r\n\r\n\t\tfloat halfX = FieldSize.x * 0.5f;\r\n\t\tfloat halfY = FieldSize.y * 0.5f;\r\n\t\tfloat maxSlopeCos = MathF.Cos( MaxSlopeAngle * MathF.PI / 180f );\r\n\r\n\t\tfor ( int i = 0; i \u003C potentialCount; i\u002B\u002B )\r\n\t\t{\r\n\t\t\tfloat x = (float)(random.NextDouble() * 2 - 1) * halfX;\r\n\t\t\tfloat y = (float)(random.NextDouble() * 2 - 1) * halfY;\r\n\r\n\t\t\tvar startLocal = new Vector3( x, y, TraceStartHeight );\r\n\t\t\tvar endLocal = new Vector3( x, y, TraceEndHeight );\r\n\t\t\tvar trace = BuildGroundTrace( WorldPosition \u002B startLocal, WorldPosition \u002B endLocal ).Run();\r\n\r\n\t\t\tvar hitLocal = trace.Hit ? trace.EndPosition - WorldPosition : endLocal;\r\n\t\t\tColor color;\r\n\t\t\tstring debugInfo = \u0022\u0022;\r\n\r\n\t\t\tif ( !trace.Hit )\r\n\t\t\t{\r\n\t\t\t\tcolor = Color.Gray.WithAlpha( 0.1f );\r\n\t\t\t\tdebugInfo = \u0022No hit\u0022;\r\n\t\t\t}\r\n\t\t\telse if ( !IsValidSurface( trace ) )\r\n\t\t\t{\r\n\t\t\t\tcolor = Color.Red;\r\n\t\t\t\tvar go = trace.GameObject;\r\n\t\t\t\tif ( go == null )\r\n\t\t\t\t{\r\n\t\t\t\t\tdebugInfo = \u0022GameObject is NULL\u0022;\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tvar tagList = string.Join( \u0022, \u0022, go.Tags.TryGetAll() );\r\n\t\t\t\t\tdebugInfo = $\u0022GO: {go.Name}, Tags: [{tagList}], Need: {RequiredTag}\u0022;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telse if ( Vector3.Dot( trace.Normal, Vector3.Up ) \u003C maxSlopeCos )\r\n\t\t\t{\r\n\t\t\t\tcolor = Color.Orange;\r\n\t\t\t}\r\n\t\t\telse if ( IsInExclusionZone( trace.EndPosition ) )\r\n\t\t\t{\r\n\t\t\t\tcolor = Color.Blue;\r\n\t\t\t}\r\n\t\t\telse\r\n\t\t\t{\r\n\t\t\t\tcolor = Color.Green;\r\n\t\t\t}\r\n\r\n\t\t\t_gizmoTraces.Add( new GizmoTraceData\r\n\t\t\t{\r\n\t\t\t\tStartPos = startLocal,\r\n\t\t\t\tHitPos = hitLocal,\r\n\t\t\t\tColor = color,\r\n\t\t\t\tDebugInfo = debugInfo,\r\n\t\t\t} );\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"RotationControl.cs","FileName":"RotationControl.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Threading.Tasks;\r\n\r\npublic sealed class RotationControl : Component\r\n{\r\n\t[Property] Rigidbody CarBody;\r\n\r\n\t[Property] WheelJoint FrontLeft;\r\n\t[Property] WheelJoint FrontRight;\r\n\t[Property] WheelJoint RearLeft;\r\n\t[Property] WheelJoint RearRight;\r\n\tList\u003CWheelJoint\u003E WheelJoints;\r\n\tList\u003CWheelJoint\u003E LeftWheelJoints;\r\n\tList\u003CWheelJoint\u003E RightWheelJoints;\r\n\r\n\t[Property] float Speed;\r\n\t[Property] GameObject Trails;\r\n\tGameObject currentTrail;\r\n\r\n\tbool IsTrailing;\r\n\tpublic bool IsDriving;\r\n\tpublic bool IsGrounded;\r\n\tint GroundedWheels;\r\n\tpublic SceneTraceResult groundCheck;\r\n\tSceneTraceResult groundNormal;\r\n\r\n\tTimeUntil LogSpeed;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tWheelJoints = new List\u003CWheelJoint\u003E();\r\n\t\tWheelJoints.Add(FrontLeft);\r\n\t\tWheelJoints.Add(FrontRight);\r\n\t\tWheelJoints.Add(RearLeft);\r\n\t\tWheelJoints.Add(RearRight);\r\n\r\n\t\tLeftWheelJoints = new List\u003CWheelJoint\u003E();\r\n\t\tLeftWheelJoints.Add( FrontLeft );\r\n\t\tLeftWheelJoints.Add( RearLeft );\r\n\r\n\t\tRightWheelJoints = new List\u003CWheelJoint\u003E();\r\n\t\tRightWheelJoints.Add( RearRight );\r\n\t\tRightWheelJoints.Add( FrontRight );\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tLine VelLine = new Line( CarBody.WorldPosition \u002B Vector3.Up * 100, CarBody.WorldPosition \u002B CarBody.Velocity * 0.25f \u002B Vector3.Up * 100 );\r\n\t\t// DebugOverlay.Line( VelLine );\r\n\t\tif ( LogSpeed )\r\n\t\t{\r\n\t\t\tLog.Info( \u0022Speed/4: \u0022 \u002B VelLine.Delta.Length );\r\n\t\t\tLogSpeed = \u002B1;\r\n\t\t}\r\n\r\n\t\tif ( Input.Down( \u0022Forward\u0022 ) \u0026\u0026 Input.Down( \u0022Run\u0022 ) )\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJoints )\r\n\t\t\t{\r\n\t\t\t\tgroundCheck = CheckGroundForWheel( wheel );\r\n\r\n\t\t\t\tif ( groundCheck.Hit )\r\n\t\t\t\t{\r\n\t\t\t\t\tGroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) \u002B 1;\r\n\t\t\t\t\tApplyForceToWheel( wheel, 1.3f );\r\n\t\t\t\t}\r\n\t\t\t\telse { GroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) - 1; }\r\n\t\t\t}\r\n\t\t\tIsDriving = true;\r\n\t\t}\r\n\t\telse if ( Input.Down( \u0022Forward\u0022 ) )\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJoints )\r\n\t\t\t{\r\n\t\t\t\tgroundCheck = CheckGroundForWheel( wheel );\r\n\r\n\t\t\t\tif ( groundCheck.Hit )\r\n\t\t\t\t{\r\n\t\t\t\t\tGroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) \u002B 1;\r\n\t\t\t\t\tApplyForceToWheel( wheel, 1f );\r\n\t\t\t\t}\r\n\t\t\t\telse \r\n\t\t\t\t{ \r\n\t\t\t\t\tGroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) - 1; \r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tIsDriving = true;\r\n\t\t}\r\n\t\telse if ( Input.Down( \u0022Backward\u0022 ) )\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJoints )\r\n\t\t\t{\r\n\t\t\t\tgroundCheck = CheckGroundForWheel( wheel );\r\n\r\n\r\n\t\t\t\tif ( groundCheck.Hit )\r\n\t\t\t\t{\r\n\t\t\t\t\tGroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) \u002B 1; \r\n\t\t\t\t\tApplyForceToWheel( wheel, -1f );\r\n\t\t\t\t}\r\n\t\t\t\telse { GroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) - 1; }\r\n\t\t\t}\r\n\t\t\tIsDriving = true;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJoints )\r\n\t\t\t{\r\n\t\t\t\tgroundCheck = CheckGroundForWheel( wheel );\r\n\r\n\t\t\t\tif ( groundCheck.Hit )\r\n\t\t\t\t{\r\n\t\t\t\t\tGroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) \u002B 1;\r\n\t\t\t\t}\r\n\t\t\t\telse { GroundedWheels = GroundedWheels.Clamp\u003Cint\u003E( 1, 3 ) - 1; }\r\n\t\t\t}\r\n\t\t\tIsDriving = false;\r\n\t\t}\r\n\r\n\t\tif ( GroundedWheels \u003E 2 ) { IsGrounded = true; }\r\n\t\telse { IsGrounded = false; }\r\n\t\t// Log.Info($\u0022{GroundedWheels} {IsGrounded}\u0022);\r\n\r\n\t\tif ( IsGrounded )\r\n\t\t{\r\n\t\t\tVector3 cheatSteering = Vector3.Zero;\r\n\t\t\tif ( Input.Down( \u0022Left\u0022 ) )\r\n\t\t\t{\r\n\t\t\t\tif ( CarBody.Velocity.Length \u003E 360 )\r\n\t\t\t\t{\r\n\t\t\t\t\tcheatSteering = (CarBody.WorldRotation.Right) * CarBody.Velocity.Length.Remap(0, 3000, 0, 40 );\r\n\t\t\t\t}\r\n\t\t\t\tCarBody.Velocity \u002B= cheatSteering;\r\n\t\t\t}\r\n\t\t\telse if ( Input.Down( \u0022Right\u0022 ) )\r\n\t\t\t{\r\n\t\t\t\tif ( CarBody.Velocity.Length \u003E 360 )\r\n\t\t\t\t{\r\n\t\t\t\t\tcheatSteering = (CarBody.WorldRotation.Left) * CarBody.Velocity.Length.Remap( 0, 3000, 0, 40 );\r\n\t\t\t\t}\r\n\t\t\t\tCarBody.Velocity \u002B= cheatSteering;\r\n\t\t\t}\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Air Control\r\n\t\t}\r\n\r\n\t\tif ( IsGrounded \u0026\u0026 IsDriving )\r\n\t\t{\r\n\t\t\tif ( IsTrailing ) return;\r\n\t\t\tIsTrailing = true;\r\n\t\t\tcurrentTrail = Trails.Clone(CarBody.WorldPosition, CarBody.WorldRotation);\r\n\t\t\tcurrentTrail.SetParent( CarBody.GameObject );\r\n\t\t\tMakeTrailsEmitDelayed( currentTrail );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tif ( IsTrailing )\r\n\t\t\t{\r\n\t\t\t\tIsTrailing = false;\r\n\r\n\t\t\t\tif ( currentTrail.IsValid() )\r\n\t\t\t\t{\r\n\t\t\t\t\tforeach ( TrailRenderer trail in currentTrail.GetComponentsInChildren\u003CTrailRenderer\u003E() )\r\n\t\t\t\t\t{\r\n\t\t\t\t\t\ttrail.Emitting = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcurrentTrail.SetParent( Scene );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tcurrentTrail = null;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate void ApplyForceToWheel( WheelJoint wheel, float factor )\r\n\t{\r\n\t\tVector3 forcePosition = wheel.WorldPosition;\r\n\t\tif ( wheel.Tags.Has( \u0022rear_wheel\u0022 ) \u0026\u0026 GroundedWheels \u003C 4 ) \r\n\t\t{ \r\n\t\t\tforcePosition = CarBody.WorldPosition.WithZ( wheel.WorldPosition.z) \u002B wheel.WorldRotation.Forward.Cross( groundNormal.Normal ) * -50;\r\n\t\t\t//DebugOverlay.Sphere(new Sphere(forcePosition, 20));\r\n\t\t\t//Log.Info(wheel.GameObject.Name);\r\n\t\t}\r\n\t\telse if ( wheel.Tags.Has( \u0022front_wheel\u0022 ) \u0026\u0026 GroundedWheels \u003C 4 )\r\n\t\t{\r\n\t\t\tforcePosition = CarBody.WorldPosition.WithZ( wheel.WorldPosition.z ) \u002B wheel.WorldRotation.Forward.Cross( groundNormal.Normal ) * 50;\r\n\t\t\t//DebugOverlay.Sphere( new Sphere( forcePosition, 20 ) );\r\n\t\t\t//Log.Info(wheel.GameObject.Name);\r\n\t\t}\r\n\t\tCarBody.ApplyForceAt( forcePosition, wheel.WorldRotation.Forward.Cross( groundNormal.Normal ) * Speed * factor);\r\n\t\t// DebugOverlay.Line( new Line( forcePosition, forcePosition \u002B (groundCheck.Normal ) * 500 ) );\r\n\t}\r\n\r\n\tprivate SceneTraceResult CheckGroundForWheel( WheelJoint wheel )\r\n\t{\r\n\t\t// Erstellt eine lokale Drehung um 90 Grad (hier um Pitch/X, falls n\u00F6tig auf Roll/Y \u00E4ndern)\r\n\t\tRotation localCorrection = Rotation.FromPitch( 90 );\r\n\t\tRotation traceRotation = wheel.WorldRotation * localCorrection;\r\n\r\n\t\tgroundCheck = Scene.Trace.Cylinder( 55, 55, wheel.WorldPosition, wheel.WorldPosition \u002B Vector3.Down ) // 48 is radius\r\n\t\t\t.Rotated(traceRotation)\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.Run();\r\n\r\n\t\tif (groundCheck.Hit)\r\n\t\t{\r\n\t\t\tgroundNormal = Scene.Trace.Sphere( 5, wheel.WorldPosition, wheel.WorldPosition \u002B Vector3.Down * 55 ) // 48 is radius\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.Run();\r\n\t\t\t// DebugOverlay.Line( new Line( wheel.WorldPosition, wheel.WorldPosition \u002B groundNormal.Normal * 100 ) );\r\n\t\t}\r\n\r\n\t\t// DebugOverlay.Trace( groundCheck );\r\n\t\treturn groundCheck;\r\n\t}\r\n\r\n\tasync Task MakeTrailsEmitDelayed( GameObject trails )\r\n\t{\r\n\t\tawait Task.Delay( 100 );\r\n\t\tforeach ( TrailRenderer trail in trails.GetComponentsInChildren\u003CTrailRenderer\u003E() )\r\n\t\t{\r\n\t\t\ttrail.Emitting = true;\r\n\t\t}\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"SceneLoader.cs","FileName":"SceneLoader.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Threading.Tasks;\r\n\r\npublic sealed class SceneLoader : Component, Component.ITriggerListener\r\n{\r\n\t[Property] float Timer = 3f;\r\n\t[Property] TextRenderer CountdownText;\r\n\r\n\t[Property] public SceneFile MainScene;\r\n\t[Property] public SceneFile LobbyScene;\r\n\r\n\tpublic SceneLoadOptions SceneLoadOptions;\r\n\r\n\tint triggerId = 0;\r\n\tbool UpdateCountdown = false;\r\n\r\n\tTimeUntil TimeUntilStarting;\r\n\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tSceneLoadOptions = new SceneLoadOptions();\r\n\t\tSceneLoadOptions.SetScene(MainScene);\r\n\r\n\t\tCountdownText.Text = \u0022Enter to Start!\u0022;\r\n\t}\r\n\r\n\tpublic void OnTriggerEnter( GameObject other )\r\n\t{\r\n\t\tif ( !other.Tags.Has( \u0022carbody\u0022 ) ) { return; }\r\n\r\n\t\ttriggerId\u002B\u002B;\r\n\t\tLog.Info( other.Name \u002B triggerId );\r\n\r\n\t\t// Start Countdown\r\n\t\tStartCountdown( triggerId, Timer );\r\n\t\t// Update Text\r\n\t\tUpdateCountdown = true;\r\n\t\tTimeUntilStarting = Timer;\r\n\t}\r\n\tpublic void OnTriggerExit( GameObject other )\r\n\t{\r\n\t\tif ( !other.Tags.Has( \u0022carbody\u0022 ) ) { return; }\r\n\r\n\t\ttriggerId\u002B\u002B;\r\n\t\tLog.Info( other.Name \u002B \u0022left\u0022 \u002B triggerId );\r\n\t\t//Reset Text\r\n\t\tCountdownText.Text = \u0022Enter to Start!\u0022;\r\n\t\tUpdateCountdown = false;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( !UpdateCountdown ) { return; }\r\n\t\tSetCountdownText();\r\n\t}\r\n\r\n\tpublic async Task StartCountdown(int id, float cd) \r\n\t{\r\n\t\tawait Task.DelayRealtimeSeconds( cd );\r\n\t\tif ( triggerId != id ) \r\n\t\t{\r\n\t\t\treturn; \r\n\t\t}\r\n\t\tGame.ChangeScene( SceneLoadOptions );\r\n\t}\r\n\r\n\tvoid SetCountdownText() \r\n\t{\r\n\t\tCountdownText.Text = $\u0022Starting in {Math.Round( TimeUntilStarting.Relative.Clamp(0, Timer), 0)}\u0022;\r\n\t}\r\n\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"WheelController.cs","FileName":"WheelController.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Audio;\r\nusing System;\r\nusing System.Numerics;\r\nusing System.Runtime.CompilerServices;\r\n\r\npublic sealed class WheelController : Component\r\n{\r\n\t[Property] Rigidbody CarBody;\r\n\tRotationControl RotationControl;\r\n\r\n\t[Property] public WheelJoint FrontLeft;\r\n\t[Property] public WheelJoint FrontRight;\r\n\t[Property] public WheelJoint RearLeft;\r\n\t[Property] public WheelJoint RearRight;\r\n\tList\u003CWheelJoint\u003E WheelJointList;\r\n\r\n\t[Property] float Speed = 2000;\r\n\t[Property] float FullSpeedAdd = 2000;\r\n\t[Property] float Torque = 300;\r\n\t[Property] float SteeringAngle = 50;\r\n\t[Property] float SteeringBoost = 20;\r\n\t[Property] double JumpPower = 0.5f;\r\n\t[Property] int JumpMax = 100000;\r\n\r\n\tfloat TargetSpinSpeed;\r\n\tfloat originalSteeringAngle;\r\n\r\n\tfloat originalTorque = 0;\r\n\tdouble JumpCharge;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\toriginalTorque = Torque;\r\n\t\tTargetSpinSpeed = 0;\r\n\t\t// Scene.TimeScale = 0.3f;\r\n\t\t//CarBody.InertiaTensor = 1000;\r\n\t\t// CarBody.EnhancedCcd = true;\r\n\t\tRotationControl = GameObject.GetComponent\u003CRotationControl\u003E();\r\n\r\n\t\tWheelJointList = new List\u003CWheelJoint\u003E();\r\n\t\tWheelJointList.Add( FrontLeft );\r\n\t\tWheelJointList.Add( FrontRight );\r\n\t\tWheelJointList.Add( RearLeft );\r\n\t\tWheelJointList.Add( RearRight );\r\n\r\n\t\t\r\n\t\tFrontLeft.GetComponentInParent\u003CRigidbody\u003E().EnhancedCcd = true;\r\n\t\tFrontRight.GetComponentInParent\u003CRigidbody\u003E().EnhancedCcd = true;\r\n\t\tRearLeft.GetComponentInParent\u003CRigidbody\u003E().EnhancedCcd = true;\r\n\t\tRearRight.GetComponentInParent\u003CRigidbody\u003E().EnhancedCcd = true;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\t// Log.Info( $\u0022{Math.Round( FrontLeft.SpinSpeed, 0 )}  {Math.Round( FrontRight.SpinSpeed, 0 )}  {Math.Round( RearLeft.SpinSpeed, 0 )} {Math.Round( RearRight.SpinSpeed, 0 )}\u0022 );\r\n\t\tTurnMotorOn( false );\r\n\t\tTargetSpinSpeed = 0;\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tSetSpeedAndTorque( TargetSpinSpeed );\r\n\t\tSteeringBoost = CarBody.Velocity.Length.Remap(0, 2000, 20, 0);\r\n\r\n\t\tif ( Input.Down( \u0022Jump\u0022 ) )\r\n\t\t{\r\n\t\t\tBrake();\r\n\t\t}\r\n\t\tif ( Input.Released( \u0022Jump\u0022 ) )\r\n\t\t{\r\n\t\t\tJumpCharge = 0;\r\n\t\t}\r\n\r\n\t\tif ( Input.Down( \u0022Left\u0022 ) )\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJointList )\r\n\t\t\t{\r\n\t\t\t\twheel.SteeringLimits = new Vector2 ( -SteeringAngle - 7, SteeringAngle \u002B 7);\r\n\t\t\t}\r\n\t\t\tFrontLeft.TargetSteeringAngle = SteeringAngle \u002B SteeringBoost \u002B 3; \r\n\t\t\tFrontRight.TargetSteeringAngle = SteeringAngle \u002B SteeringBoost;\r\n\r\n\t\t\tRearLeft.TargetSteeringAngle = -SteeringAngle / 5; RearRight.TargetSteeringAngle = -SteeringAngle / 5;\r\n\t\t}\r\n\t\telse if ( Input.Down( \u0022Right\u0022 ) )\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJointList )\r\n\t\t\t{\r\n\t\t\t\twheel.SteeringLimits = new Vector2( -SteeringAngle - 7, SteeringAngle \u002B 7 );\r\n\t\t\t}\r\n\t\t\tFrontLeft.TargetSteeringAngle = -SteeringAngle - SteeringBoost - 3;\r\n\t\t\tFrontRight.TargetSteeringAngle = -SteeringAngle - SteeringBoost;\r\n\r\n\t\t\tRearLeft.TargetSteeringAngle = SteeringAngle / 5; RearRight.TargetSteeringAngle = SteeringAngle / 5;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tforeach ( WheelJoint wheel in WheelJointList )\r\n\t\t\t{\r\n\t\t\t\twheel.TargetSteeringAngle = 0;\r\n\t\t\t\twheel.SteeringLimits = new Vector2( 0, 0 );\r\n\t\t\t\t//wheel.EnableSteeringLimit = false;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\r\n\t}\r\n\r\n\tprivate void SetSpeedAndTorque( float target)\r\n\t{\r\n\t\tFrontLeft.SpinMotorSpeed = FrontLeft.SpinMotorSpeed.LerpTo(target, 0.005f);\r\n\t\tFrontRight.SpinMotorSpeed = FrontRight.SpinMotorSpeed.LerpTo( target, 0.005f );\r\n\t\tRearLeft.SpinMotorSpeed = RearLeft.SpinMotorSpeed.LerpTo( target, 0.005f );\r\n\t\tRearRight.SpinMotorSpeed = RearRight.SpinMotorSpeed.LerpTo( target, 0.005f );\r\n\r\n\t\tFrontLeft.MaxSpinTorque = float.Clamp(originalTorque.LerpTo(originalTorque / (FrontLeft.SpinSpeed * 0.001f \u002B 1), 1f), originalTorque / 2, originalTorque) ;\r\n\t\tFrontRight.MaxSpinTorque = float.Clamp( originalTorque.LerpTo( originalTorque / (FrontLeft.SpinSpeed * 0.001f \u002B 1), 1f ), originalTorque / 2, originalTorque );\r\n\t\tRearLeft.MaxSpinTorque = float.Clamp( originalTorque.LerpTo(originalTorque / (FrontLeft.SpinSpeed * 0.001f \u002B 1), 1f), originalTorque / 2, originalTorque );\r\n\t\tRearRight.MaxSpinTorque = float.Clamp( originalTorque.LerpTo(originalTorque / (FrontLeft.SpinSpeed * 0.001f \u002B 1), 1f), originalTorque / 2, originalTorque );\r\n\t}\r\n\r\n\tvoid TurnMotorOn( bool status )\r\n\t{\r\n\t\tFrontRight.EnableSpinMotor = status;\r\n\t\tRearRight.EnableSpinMotor = status;\r\n\t\tFrontLeft.EnableSpinMotor = status;\r\n\t\tRearLeft.EnableSpinMotor = status;\r\n\r\n\t}\r\n\r\n\tvoid Brake()\r\n\t{\r\n\t\tTurnMotorOn( true );\r\n\t\tif ( RotationControl.IsGrounded )\r\n\t\t{\r\n\t\t\t// Log.Info( \u0022Brake\u0022 );\r\n\t\t\tRearLeft.MaxSpinTorque = Torque * 4; RearRight.MaxSpinTorque = Torque * 4;\r\n\t\t\tFrontLeft.MaxSpinTorque = Torque * 4; FrontRight.MaxSpinTorque = Torque * 4;\r\n\r\n\t\t\tRearLeft.SpinMotorSpeed = 0f; RearRight.SpinMotorSpeed = 0f;\r\n\t\t\tFrontLeft.SpinMotorSpeed = 0f; FrontRight.SpinMotorSpeed = 0f;\r\n\r\n\t\t\tCarBody.Velocity = CarBody.Velocity.WithFriction(0.01f);\r\n\t\t}\r\n\t\telse { CarBody.SmoothRotate( Rotation.From( 0, CarBody.WorldRotation.Yaw(), 0 ), 1f, Time.Delta ); }\r\n\t}\r\n\r\n\tvoid Jump() \r\n\t{\r\n\t\t// downward impulse to use suspension for jump\r\n\t\tLog.Info( \u0022Jump\u0022 );\r\n\t\tJumpCharge = JumpCharge * 1.15f \u002B JumpPower;\r\n\t\tJumpCharge = Math.Min(JumpCharge, JumpMax);\r\n\r\n\t\tCarBody.ApplyImpulse(CarBody.WorldRotation.Down * (float)JumpCharge);\r\n\t}\r\n\r\n\r\n\r\n\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"ZombieSpawner.cs","FileName":"ZombieSpawner.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\n\r\npublic sealed class ZombieSpawner : Component\r\n{\r\n\t[Property] GameObject ZombiePrefab;\r\n\t[Property] float SpawnDelay;\r\n\t[Property] float SpawnCooldown;\r\n\t[Property] float ScaleFactor;\r\n\r\n\tGameObject currentZombie;\r\n\r\n\tTimeUntil NextSpawn;\r\n\tRandom random;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\trandom = new Random();\r\n\r\n\t\tcurrentZombie = null;\r\n\r\n\t\tNextSpawn = SpawnDelay;\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\t// Solange currentKit Valid ist bleibt der NextSpawn auf Cooldown\r\n\t\tif ( currentZombie.IsValid() ) NextSpawn = SpawnCooldown;\r\n\t\t// GhostKit?\r\n\r\n\t\t// Wenn NextSpawn abgelaufen ist und es kein g\u00FCltiges Kit gibt, spawne ein neues\r\n\t\tif ( NextSpawn \u0026\u0026 (!currentZombie.IsValid() || currentZombie == null) )\r\n\t\t{\r\n\t\t\tSpawnZombie();\r\n\t\t}\r\n\t}\r\n\r\n\tvoid SpawnZombie()\r\n\t{\r\n\t\t// Particle Effekt, Sound?\r\n\t\tcurrentZombie = ZombiePrefab.Clone( WorldPosition, WorldRotation, Vector3.One * ScaleFactor * random.Float(0.9f, 1.1f) );\r\n\t}\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"ZombieBrain.cs","FileName":"ZombieBrain.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing Sandbox.Navigation;\r\nusing System;\r\nusing System.Threading.Tasks;\r\nusing static Ballistics;\r\nusing static HealthSystem;\r\n\r\npublic enum ZombieState { Wander, Approach, Leap, Slam, Staggered, Culled }\r\n\r\npublic sealed class ZombieBrain : Component, HealthSystem.IHealthEvent\r\n{\r\n\t[Property] NavMeshAgent Agent;\r\n\tGameObject Player;\r\n\t[Property] Rigidbody Body;\r\n\t[Property] ModelRenderer Model;\r\n\t[Property] ModelRenderer LeftArmModel;\r\n\t[Property] ModelRenderer RightArmModel;\r\n\t[Property] TextRenderer StateDebugText;\r\n\t[Property] bool DebugMode;\r\n\r\n\t[Property] GameObject DeadZombie;\r\n\t[Property] GameObject BloodDecal;\r\n\t[Property] ParticleRingEmitter AttackParticle;\r\n\t[Property] ParticleEffect AttackEffect;\r\n\r\n\t[Property] float MoveCooldown = 1f;\r\n\tfloat WanderCooldown = 5f;\r\n\t[Property] float LeapCooldown = 5f;\r\n\t[Property] float SlamCooldown = 3f;\r\n\t[Property] float SlamRadius = 350f;\r\n\r\n\t[Property] int ApproachRange = 7000;\r\n\t[Property] int CulledRange = 15000;\r\n\t[Property] int LeapRange = 2000;\r\n\t[Property] int SlamRange = 1000;\r\n\tint RangeOffset;\r\n\r\n\r\n\t// --- Leap-Parameter zum Tunen ---\r\n\t[Property] float LeapFlightTime = 1.5f;         // Konstante Flugzeit in Sekunden\r\n\t[Property] float LeapMaxHeightOffset = 800f;    // Wie viel h\u00F6her der Bogen gehen soll\r\n\r\n\tfloat DistanceToPlayer;\r\n\tVector3 TargetPos;\r\n\r\n\tpublic ZombieState CurrentState;\r\n\r\n\tTimeUntil NextMove;\r\n\tTimeUntil NextWander;\r\n\tTimeUntil NextLeap;\r\n\tTimeUntil NextSlam;\r\n\tTimeUntil NextChargeSlam;\r\n\tpublic TimeUntil KnockBack;\r\n\r\n\tbool IsChargingSlam;\r\n\tTimeSince SinceLeapAnimationStart;\r\n\tTimeSince SinceSlamAnimationStart;\r\n\tTimeSince SinceHitAnimationStart;\r\n\tVector3 originalScale;\r\n\tint HitFlailCounter = 0;\r\n\r\n\tRandom random = new Random();\r\n\r\n\tSceneTraceResult groundCheck;\r\n\r\n\tNavMesh NavMesh;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tCurrentState = ZombieState.Wander;\r\n\t\tAgent.MoveTo( Body.WorldPosition );\r\n\t\tPlayer = Scene.FindAllWithTag(\u0022carbody\u0022).First\u003CGameObject\u003E();\r\n\r\n\t\toriginalScale = WorldScale;\r\n\t\tRangeOffset = random.Int( -500, 500 );\r\n\t\tLeapRange \u002B= RangeOffset;\r\n\r\n\t\tif ( !DebugMode ) { StateDebugText.Enabled = false; }\r\n\t\t// Log.Info( $\u0022[START] Player found: {Player.Name}\u0022 );\r\n\t}\r\n\r\n\tvoid IHealthEvent.OnDeath()\r\n\t{\r\n\t\tgroundCheck = Scene.Trace.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 3500 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022dead\u0022, \u0022player\u0022, \u0022playerclip\u0022 )\r\n\t\t\t.Run();\r\n\t\tGameObject newDecal = BloodDecal.Clone( groundCheck.EndPosition, Rotation.LookAt( groundCheck.Normal, Vector3.Up ).Angles().WithYaw(random.Int(-180, 180)) );\t\t\r\n\t\tGameObject _deadClone = DeadZombie.Clone( WorldPosition, WorldRotation, originalScale );\r\n\t\tforeach (GameObject child in _deadClone.Children )\r\n\t\t{\r\n\t\t\tchild.GetComponent\u003CRigidbody\u003E().Velocity = Body.Velocity;\r\n\t\t\tchild.GetComponent\u003CRigidbody\u003E().ApplyImpulse( (child.WorldPosition - GameObject.WorldPosition).Normal * 1000 );\r\n\t\t\tchild.GetComponent\u003CRigidbody\u003E().AngularVelocity = random.VectorInSphere( random.Float( 3, 5) );\r\n\t\t\t// child.Enabled = random.NextDouble() \u003E= 0.5;\r\n\t\t}\r\n\r\n\t\tSound.Play( \u0022sounds/zombie-dies.sound\u0022, _deadClone.WorldPosition );\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\r\n\t\t// DebugOverlay.Sphere( new Sphere( Body.WorldPosition, SlamRadius ), Color.Orange );\r\n\t\tDistanceToPlayer = (Player.WorldPosition - WorldPosition).Length;\r\n\r\n\t\tswitch ( CurrentState ) \r\n\t\t{\r\n\t\t\tdefault:\r\n\t\t\t\tAgent.Stop();\r\n\t\t\t\tStateDebugText.Text = \u0022Default\u0022;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Culled: // STATE IS CULLED\r\n\t\t\t\tStateDebugText.Text = \u0022Culled\u0022;\r\n\t\t\t\tAgent.Stop();\r\n\t\t\t\tAgent.Enabled = false;\r\n\t\t\t\t// DebugOverlay.Sphere(new Sphere(WorldPosition, 15000));\r\n\r\n\t\t\t\tif ( DistanceToPlayer \u003C CulledRange ) \r\n\t\t\t\t{\r\n\t\t\t\t\tAgent.Enabled = true;\r\n\t\t\t\t\tAgent.MoveTo( Body.WorldPosition );\r\n\t\t\t\t\tCurrentState = ZombieState.Wander;\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Wander: // STATE IS WANDER\r\n\t\t\t\tStateDebugText.Text = \u0022Wander\u0022;\r\n\t\t\t\tAgent.MaxSpeed = 500;\r\n\t\t\t\tAgent.Acceleration = 500;\r\n\r\n\t\t\t\tif ( NextWander \u0026\u0026 Agent.TargetPosition.HasValue \u0026\u0026 ((Vector3)Agent.TargetPosition - Body.WorldPosition).IsNearlyZero(300) )\r\n\t\t\t\t{\r\n\t\t\t\t\tDoWander();\r\n\t\t\t\t\tNextWander = WanderCooldown \u002B random.Float(0f, 0.1f);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( DistanceToPlayer \u003E CulledRange ) { CurrentState = ZombieState.Culled; }\r\n\r\n\t\t\t\tif ( DistanceToPlayer \u003C ApproachRange ) { CurrentState = ZombieState.Approach; }\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Approach: // STATE IS APPROACH\r\n\t\t\t\tStateDebugText.Text = \u0022Approach\u0022;\r\n\r\n\t\t\t\tif ( !(Agent.AgentPosition - Body.WorldPosition).IsNearlyZero(50f) ) { Agent.SetAgentPosition( Body.WorldPosition ); }\r\n\t\t\t\tAgent.UpdatePosition = true; Agent.UpdateRotation = false;\r\n\t\t\t\tAgent.MaxSpeed = 800;\r\n\t\t\t\tAgent.Acceleration = 500;\r\n\r\n\t\t\t\t// Walk to Player and rotate\r\n\t\t\t\tif ( NextMove )\r\n\t\t\t\t{\r\n\t\t\t\t\tAgent.MoveTo( Player.WorldPosition \u002B (Body.WorldPosition - Player.WorldPosition).Normal * 200 );\r\n\t\t\t\t\tNextMove = MoveCooldown \u002B random.Float(0f, 0.1f);\r\n\t\t\t\t}\r\n\t\t\t\tLookAtPlayer();\r\n\t\t\t\tAnimateArms();\r\n\r\n\t\t\t\t// Go to wander if player is far\r\n\t\t\t\tif ( DistanceToPlayer \u003E ApproachRange )\r\n\t\t\t\t{\r\n\t\t\t\t\tAgent.UpdateRotation = true;\r\n\t\t\t\t\tAgent.MaxSpeed = 240; \r\n\t\t\t\t\tCurrentState = ZombieState.Wander; \r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Go to slam if near and NextSlam\r\n\t\t\t\tif ( DistanceToPlayer \u003C (SlamRadius * 2) \u0026\u0026 NextSlam ) { NextChargeSlam = 1; CurrentState = ZombieState.Slam; }\r\n\r\n\t\t\t\t// Go to Leap if NextMove, Distance und sightlineCheck \r\n\t\t\t\tif ( DistanceToPlayer \u003C LeapRange \u0026\u0026 DistanceToPlayer \u003E SlamRadius * 3 \u0026\u0026 NextLeap \u0026\u0026 NextSlam ) \r\n\t\t\t\t{\r\n\t\t\t\t\tSceneTraceResult checkSightline = Scene.Trace.Sphere( 64, Body.WorldPosition \u002B Vector3.Up * 300, Player.WorldPosition \u002B Vector3.Up * 300 )\r\n\t\t\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022world\u0022 )\r\n\t\t\t\t\t\t.Run();\r\n\t\t\t\t\t// DebugOverlay.Trace( checkSightline );\r\n\t\t\t\t\tif ( !checkSightline.Hit ) CurrentState = ZombieState.Leap; \r\n\t\t\t\t}\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Slam: // STATE IS SLAM\r\n\t\t\t\tStateDebugText.Text = \u0022Slam\u0022;\r\n\t\t\t\tAgent.Stop();\r\n\t\t\t\tLookAtPlayer();\r\n\t\t\t\tif ( !IsChargingSlam ) AnimateSlam();\r\n\t\t\t\t// Increase Charge to 100 then DoSlam()\r\n\t\t\t\tif ( NextChargeSlam ) { IsChargingSlam = false; DoSlam(); }\r\n\t\t\t\t\r\n\t\t\t\tif ( DistanceToPlayer \u003C 7000 \u0026\u0026 NextChargeSlam ) { CurrentState = ZombieState.Approach; }\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Leap: // STATE IS LEAP\r\n\r\n\t\t\t\tStateDebugText.Text = \u0022Leap\u0022;\r\n\r\n\t\t\t\tAgent.Stop();\r\n\t\t\t\tAgent.UpdatePosition = false; Agent.UpdateRotation = false;\r\n\r\n\t\t\t\tgroundCheck = Scene.Trace.Ray( WorldPosition \u002B Vector3.Up * 10, WorldPosition \u002B Vector3.Down * 5 )\r\n\t\t\t\t\t.Radius( 100 )\r\n\t\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t\t.WithoutTags( \u0022enemy\u0022 )\r\n\t\t\t\t\t.Run();\r\n\t\t\t\t// DebugOverlay.Trace( groundCheck );\r\n\r\n\t\t\t\tif (groundCheck.Hit)\r\n\t\t\t\t{\r\n\t\t\t\t\tLookAtPlayer();\r\n\t\t\t\t}\r\n\t\t\t\telse\r\n\t\t\t\t{\r\n\t\t\t\t\tBody.SmoothRotate( Rotation.LookAt( Body.WorldPosition \u002B Body.Velocity.WithZ(0) * 1000, Vector3.Up ), 0.5f, 0.01f );\r\n\t\t\t\t\tAnimateArms(0, -165);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Einmal am anfang leapen\r\n\t\t\t\tif ( NextLeap ) \r\n\t\t\t\t{ \r\n\t\t\t\t\tDoLeap();\r\n\t\t\t\t\tAnimateLeap();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// wenn er wieder aufkommt slammen und zu approach\r\n\t\t\t\tif ( NextSlam \u0026\u0026 groundCheck.Hit \u0026\u0026 !NextLeap \u0026\u0026 Body.Velocity.z \u003C 10 )\r\n\t\t\t\t{\r\n\t\t\t\t\tDoSlam();\r\n\r\n\t\t\t\t\tBody.GravityScale = 1f;\r\n\t\t\t\t\tCurrentState = ZombieState.Approach;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\tcase ZombieState.Staggered: // STATE IS STAGGERED\r\n\t\t\t\tAgent.Stop();\r\n\t\t\t\tAgent.UpdatePosition = false;\r\n\t\t\t\tAgent.SetAgentPosition( Agent.WorldPosition );\r\n\t\t\t\tBody.GravityScale = 1f;\r\n\r\n\t\t\t\tgroundCheck = Scene.Trace.Ray( WorldPosition \u002B Vector3.Up * 10, WorldPosition \u002B Vector3.Down * 35 )\r\n\t\t\t\t\t.Radius( 64 )\r\n\t\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t\t.WithoutTags( \u0022enemy\u0022 )\r\n\t\t\t\t\t.Run();\r\n\t\t\t\t// DebugOverlay.Trace( groundCheck );\r\n\r\n\t\t\t\tif (KnockBack \u0026\u0026 groundCheck.Hit) \r\n\t\t\t\t{\r\n\t\t\t\t\tAgent.UpdatePosition = true;\r\n\t\t\t\t\tCurrentState = ZombieState.Wander;\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void LookAtPlayer()\r\n\t{\r\n\t\tif ( Body.WorldRotation.Forward.Angle( Player.WorldPosition - GameObject.WorldPosition ) \u003E 1 )\r\n\t\t{\r\n\t\t\tBody.SmoothRotate( Rotation.LookAt( Player.WorldPosition - GameObject.WorldPosition, Vector3.Up ).Angles().WithPitch(0), 0.5f, 0.01f );\r\n\t\t}\r\n\t}\r\n\r\n\tvoid DoWander() \r\n\t{ \r\n\t\tVector3 possibleTargetPos = Body.WorldPosition \u002B (Vector3)random.VectorInCircle(1000);\r\n\t\tSceneTraceResult wanderTrace = Scene.Trace.Sphere( 100, possibleTargetPos, possibleTargetPos )\r\n\t\t\t.Radius( 100 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022world\u0022 )\r\n\t\t\t.Run();\r\n\r\n\t\t// Animate Arms\r\n\t\t// AnimateArms();\r\n\r\n\t\tif ( !wanderTrace.Hit )\r\n\t\t{\r\n\t\t\tAgent.MoveTo( possibleTargetPos );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tint tries = 0;\r\n\t\t\twhile ( wanderTrace.Hit \u0026\u0026 tries \u003C 10 )\r\n\t\t\t{\r\n\t\t\t\tpossibleTargetPos = Body.WorldPosition \u002B (Vector3)random.VectorInCircle( 1000 );\r\n\r\n\t\t\t\twanderTrace = Scene.Trace.Sphere( 300, possibleTargetPos, possibleTargetPos )\r\n\t\t\t\t\t.Radius( 300 )\r\n\t\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022world\u0022 )\r\n\t\t\t\t\t.Run();\r\n\t\t\t\ttries\u002B\u002B;\r\n\r\n\t\t\t\t// DebugOverlay.Trace( wanderTrace );\r\n\t\t\t}\r\n\t\t\tif ( !wanderTrace.Hit )\r\n\t\t\t{\r\n\t\t\t\tAgent.MoveTo( possibleTargetPos );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t\r\n\tvoid DoSlam()\r\n\t{\r\n\t\tAnimateHit();\r\n\t\tLog.Info( $\u0022{GameObject.Name} is slamming!\u0022 );\r\n\t\t// Attack Trace\r\n\t\t// AnimateSlam();\r\n\t\tvar attackTrace = Scene.Trace.Sphere( SlamRadius, Body.WorldPosition, Body.WorldPosition )\r\n\t\t\t\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t\t\t\t.WithAllTags( \u0022player\u0022, \u0022carbody\u0022 )\r\n\t\t\t\t\t\t.Run();\r\n\t\t// DebugOverlay.Trace(attackTrace);\r\n\r\n\t\t// Partikel, Sound\r\n\t\tAttackParticle.WorldTransform = new Transform(Body.WorldPosition, Rotation.FromPitch(0));\r\n\t\tAttackParticle.ResetEmitter();\r\n\t\tSound.Play( \u0022sounds/falling-game-character.sound\u0022, Body.WorldPosition );\r\n\r\n\t\t// Wenn er den player trifft\r\n\t\tif ( attackTrace.Hit )\r\n\t\t{\r\n\t\t\tif ( !attackTrace.GameObject.IsValid ) return;\r\n\r\n\t\t\tLog.Info( $\u0022Slam hit: {attackTrace.GameObject.Name}\u0022 );\r\n\t\t\tattackTrace.GameObject.GetComponentInParent\u003CHealthSystem\u003E().Damage( 500 );\r\n\r\n\t\t\tif ( !attackTrace.GameObject.GetComponent\u003CRigidbody\u003E().IsValid ) return;\r\n\t\t\tif (Player.GetComponent\u003CRigidbody\u003E().Velocity.z \u003C 100 ) \r\n\t\t\t{ \r\n\t\t\t\tattackTrace.GameObject.GetComponent\u003CRigidbody\u003E().ApplyImpulse( Vector3.Up * 50000 \u002B (Player.WorldPosition.WithZ(0) - Body.WorldPosition.WithZ(0)).Normal * 300000 );\r\n\t\t\t\tattackTrace.GameObject.GetComponent\u003CRigidbody\u003E().AngularVelocity \r\n\t\t\t\t\t= attackTrace.GameObject.WorldRotation.Forward * attackTrace.GameObject.GetComponent\u003CRigidbody\u003E().Mass * 5 * attackTrace.GameObject.WorldRotation.Right.Dot( ( Player.WorldPosition - Body.WorldPosition ).Normal);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tNextSlam = SlamCooldown;\r\n\r\n\t}\r\n\r\n\tvoid DoLeap()\r\n\t{\r\n\t\tSound.Play( \u0022sounds/whaa.sound\u0022, Body.WorldPosition );\r\n\t\tvar playerRb = Player.GetComponent\u003CRigidbody\u003E();\r\n\r\n\t\t// find ground below player to get accurate target position and flight time\r\n\t\tSceneTraceResult findGround = Scene.Trace.Ray( Player.WorldPosition, Player.WorldPosition \u002B Vector3.Down * 5000 )\r\n\t\t\t.Radius( 32 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022 )\r\n\t\t\t.Run();\r\n\t\t// DebugOverlay.Sphere(new Sphere(findGround.HitPosition, 32), Color.Black, 3f );\r\n\r\n\t\tVector2 randomOffset = random.VectorInCircle() * random.Int(200, 300);\r\n\t\t\r\n\t\t// === Konvertiere s\u0026box \u2192 Unity (X,Y,Z) \u2192 (X,Z,Y) ===\r\n\t\tVector3 zombieUnity = new Vector3( WorldPosition.x, WorldPosition.z, WorldPosition.y );\r\n\t\tVector3 playerUnity = new Vector3( playerRb.WorldPosition.x \u002B randomOffset.x, findGround.HitPosition.z, playerRb.WorldPosition.y \u002B randomOffset.y );\r\n\t\tVector3 playerVelUnity = new Vector3( playerRb.Velocity.x, 0, playerRb.Velocity.y );\r\n\r\n\t\t// DebugOverlay.Sphere( new Sphere( new Vector3( playerRb.WorldPosition.x, playerRb.WorldPosition.y, findGround.HitPosition.z  ), 32 ), Color.White, 3f );\r\n\r\n\t\t// === Berechne requiredLateralSpeed f\u00FCr feste Flugzeit ===\r\n\t\tVector3 directionXZ = new Vector3( playerUnity.x - zombieUnity.x, 0f, playerUnity.z - zombieUnity.z );\r\n\t\tfloat horizontalDistance = directionXZ.Length;\r\n\t\tfloat requiredLateralSpeed = horizontalDistance / LeapFlightTime;\r\n\r\n\t\t// === Nutze solve_ballistic_arc_lateral ===\r\n\t\tif ( Ballistics.solve_ballistic_arc_lateral(\r\n\t\t\tzombieUnity,\r\n\t\t\trequiredLateralSpeed,\r\n\t\t\tplayerUnity, // random offset, damit der Leap nicht immer exakt auf den Spieler zielt\r\n\t\t\tplayerVelUnity,\r\n\t\t\tLeapMaxHeightOffset,\r\n\t\t\tout Vector3 fireVelUnity,\r\n\t\t\tout float gravityNeeded,\r\n\t\t\tout Vector3 impactPointUnity ) )\r\n\t\t{\r\n\t\t\t// === Konvertiere Unity \u2192 s\u0026box (X,Z,Y) \u2192 (X,Y,Z) ===\r\n\t\t\tVector3 fireVelocity = new Vector3( fireVelUnity.x, fireVelUnity.z, fireVelUnity.y );\r\n\t\t\tVector3 impactPoint = new Vector3( impactPointUnity.x, impactPointUnity.z, impactPointUnity.y );\r\n\r\n\t\t\tfloat worldGravity = Scene.PhysicsWorld.Gravity.Length;\r\n\t\t\tfloat gravityScale = Math.Max( 0.5f, gravityNeeded / worldGravity );\r\n\r\n\t\t\tLog.Info( $\u0022[LEAP] SUCCESS  gravityScale: {gravityScale:F2}\u0022 );\r\n\r\n\t\t\t// === Wende Leap an ===\r\n\t\t\tTargetPos = impactPoint;\r\n\t\t\tBody.GravityScale = gravityScale;\r\n\t\t\tBody.Velocity = fireVelocity;\r\n\t\t\tNextLeap = LeapCooldown;\r\n\t\t}\r\n\t\telse if ( Ballistics.solve_ballistic_arc_lateral(\r\n\t\t\tzombieUnity,\r\n\t\t\trequiredLateralSpeed,\r\n\t\t\tplayerUnity,\r\n\t\t\tLeapMaxHeightOffset,\r\n\t\t\tout fireVelUnity,\r\n\t\t\tout gravityNeeded ) )\r\n\t\t{\r\n\t\t\t// === Konvertiere Unity \u2192 s\u0026box (X,Z,Y) \u2192 (X,Y,Z) ===\r\n\t\t\tVector3 fireVelocity = new Vector3( fireVelUnity.x, fireVelUnity.z, fireVelUnity.y );\r\n\t\t\tVector3 impactPoint = new Vector3( playerUnity.x, playerUnity.z, playerUnity.y );\r\n\r\n\t\t\tfloat worldGravity = Scene.PhysicsWorld.Gravity.Length;\r\n\t\t\tfloat gravityScale = Math.Max( 0.5f, gravityNeeded / worldGravity );\r\n\r\n\t\t\tLog.Info( $\u0022[LEAP] NO PREDICTION gravityScale: {gravityScale:F2}\u0022 );\r\n\r\n\t\t\t// === Wende Leap an ===\r\n\t\t\tTargetPos = impactPoint;\r\n\t\t\tBody.GravityScale = gravityScale;\r\n\t\t\tBody.Velocity = fireVelocity;\r\n\t\t\tNextLeap = LeapCooldown;\r\n\r\n\t\t}\r\n\t}\r\n\r\n\tasync Task AnimateLeap()\r\n\t{\r\n\t\tfloat duration = LeapFlightTime;\r\n\t\tSinceLeapAnimationStart = 0;\r\n\r\n\t\twhile ( SinceLeapAnimationStart \u003C duration * 0.5f )\r\n\t\t{\r\n\t\t\tif ( CurrentState != ZombieState.Leap ) { break; }\r\n\r\n\t\t\tModel.WorldScale = Model.WorldScale.WithZ( Model.WorldScale.z.LerpTo( originalScale.z \u002B 0.2f, 0.01f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\t\twhile ( !groundCheck.Hit )\r\n\t\t{\r\n\t\t\tif ( CurrentState != ZombieState.Leap ) { break; }\r\n\r\n\t\t\tModel.WorldScale = Model.WorldScale.WithZ( Model.WorldScale.z.LerpTo( originalScale.z - 0.5f, 0.01f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\r\n\t\tif ( !this.IsValid() ) return;\r\n\t\tModel.WorldScale = originalScale;\r\n\t}\r\n\r\n\tasync Task AnimateSlam()\r\n\t{\r\n\t\tfloat duration = 1f;\r\n\t\tSinceSlamAnimationStart = 0;\r\n\t\tIsChargingSlam = true;\r\n\r\n\t\twhile ( SinceSlamAnimationStart \u003C duration * 0.9f )\r\n\t\t{\r\n\t\t\tif ( CurrentState != ZombieState.Slam ) { IsChargingSlam = false; break; }\r\n\r\n\t\t\tModel.WorldScale = Model.WorldScale.WithZ( Model.WorldScale.z.LerpTo( originalScale.z \u002B 0.2f, 0.01f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\t\tSinceSlamAnimationStart = 0;\r\n\t\twhile ( SinceSlamAnimationStart \u003C duration * 0.1f )\r\n\t\t{\r\n\t\t\tif ( CurrentState != ZombieState.Slam ) { IsChargingSlam = false; break; }\r\n\r\n\t\t\tModel.WorldScale = Model.WorldScale.WithZ( Model.WorldScale.z.LerpTo( originalScale.z - 0.5f, 0.1f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\r\n\t\tif ( !this.IsValid() ) return;\r\n\t\tModel.WorldScale = originalScale;\r\n\t}\r\n\r\n\t// Nur f\u00FCr ModelRenderer muss noch gemacht werden\r\n\tpublic async Task AnimateHit() \r\n\t{\r\n\t\tfloat duration = 0.1f;\r\n\r\n\t\tSinceHitAnimationStart = 0;\r\n\t\tModel.WorldScale = Model.WorldScale.WithZ( originalScale.z * (1f - 0.15f) );\r\n\r\n\t\twhile (SinceHitAnimationStart \u003C duration) \r\n\t\t{ \r\n\t\t\tModel.WorldScale = Model.WorldScale.WithZ(Model.WorldScale.z.LerpTo(originalScale.z, 0.1f));\r\n\r\n\t\t\tif ( HitFlailCounter == 0 )\r\n\t\t\t{\r\n\t\t\t\tLeftArmModel.WorldRotation = Model.WorldRotation.Angles().WithRoll( Game.Random.Int( 60, 135 ) ).WithPitch( Game.Random.Int( -45, 45 ) );\r\n\t\t\t\tRightArmModel.WorldRotation = Model.WorldRotation.Angles().WithRoll( Game.Random.Int( -135, -65 ) ).WithPitch( Game.Random.Int( -45, 45 ) );\r\n\t\t\t}\r\n\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\r\n\t\tif ( !this.IsValid() ) return;\r\n\t\tModel.WorldScale = originalScale;\r\n\r\n\t\tHitFlailCounter\u002B\u002B;\r\n\t\tif (HitFlailCounter == 2) HitFlailCounter = 0;\r\n\t}\r\n\r\n\tvoid AnimateArms(int roll = 0, int pitch = -90)\r\n\t{\r\n\t\tLeftArmModel.WorldRotation = Model.WorldRotation.Angles().WithRoll( roll ).WithPitch( pitch );\r\n\t\tRightArmModel.WorldRotation = Model.WorldRotation.Angles().WithRoll( roll ).WithPitch( pitch );\r\n\t}\r\n\r\n}\r\n"},{"Ident":"straightgas.graveyard_gunners","Path":"VampireBrain.cs","FileName":"VampireBrain.cs","PackageType":"game","CodeKind":"Game","AssetVersionId":369061,"IsPrivate":false,"Code":"using Sandbox;\r\nusing System;\r\nusing System.Threading.Tasks;\r\nusing static Ballistics;\r\nusing static HealthSystem;\r\n\r\npublic enum VampireState { Culled, Idle, Moving, Attack, Staggered }\r\n\r\n\r\npublic sealed class VampireBrain : Component, HealthSystem.IHealthEvent\r\n{\r\n\tGameObject Player;\r\n\tRigidbody PlayerBody;\r\n\t[Property] TextRenderer StateDebugText;\r\n\t[Property] bool DebugMode;\r\n\t[Property] GameObject ParticleObject;\r\n\t[Property] GameObject BloodDecal;\r\n\tParticleEmitter BloodEmitter;\r\n\t[Property] GameObject DeathParticle;\r\n\r\n\tpublic VampireState CurrentState;\r\n\r\n\tSceneTraceResult GroundTrace;\r\n\r\n\tVector3 CircleOffset;\r\n\tpublic Vector3 TargetPosition;\r\n\tVector3 SchwebeMittelPunkt;\r\n\tfloat SchwebeDistance = 25;\r\n\tfloat SchwebeFrequenz = 4f;\r\n\r\n\tVector3 IdleKreisPunkt;\r\n\tfloat IdleKreisRadius = 1000f;\r\n\tfloat IdleKreisAngle = 0f;\r\n\r\n\tfloat AttackCharge = 0;\r\n\r\n\tTimeSince SinceHitAnimationStart;\r\n\tVector3 originalScale;\r\n\r\n\tTimeSince SinceGroundCheck = 0;\r\n\tpublic TimeUntil UntilKnockBack;\r\n\tTimeUntil NextOffset;\r\n\tTimeUntil UntilNextIdleSound;\r\n\tTimeSince SinceAttack;\r\n\tRandom random;\r\n\r\n\tvoid IHealthEvent.OnDeath()\r\n\t{\r\n\t\tGroundTrace = Scene.Trace.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 7000 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022dead\u0022, \u0022player\u0022, \u0022playerclip\u0022 )\r\n\t\t\t.Run();\r\n\t\tGameObject newDecal = BloodDecal.Clone( GroundTrace.EndPosition, Rotation.LookAt( GroundTrace.Normal, Vector3.Up ).Angles().WithYaw( random.Int( -180, 180 ) ) );\r\n\r\n\t\tSound.Play( \u0022sounds/vampire/vampire-death.sound\u0022, GameObject.WorldPosition );\r\n\t\tDeathParticle.Clone( GameObject.WorldPosition \u002B Vector3.Up * 150, WorldRotation, WorldScale * 1.5f );\r\n\t\tGameObject.Parent.Destroy();\r\n\t}\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\trandom = new Random();\r\n\r\n\t\toriginalScale = WorldScale;\r\n\r\n\t\tif ( !DebugMode ) { StateDebugText.Enabled = false; }\r\n\t\t\r\n\t\tPlayer = Scene.FindAllWithTag( \u0022carbody\u0022 ).First\u003CGameObject\u003E();\r\n\t\tPlayerBody = Player.GetComponent\u003CRigidbody\u003E();\r\n\t\tBloodEmitter = ParticleObject.GetComponent\u003CParticleEmitter\u003E();\r\n\t\t// Log.Info(Player.Name);\r\n\r\n\t\tCurrentState = VampireState.Moving;\r\n\r\n\t\tGroundTrace = Scene.Trace\r\n\t\t\t.Ray( WorldPosition, WorldPosition \u002B Vector3.Down * 3000 )\r\n\t\t\t.Radius( 1 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022dead\u0022 )\r\n\t\t\t.Run();\r\n\r\n\t\t// HorizontalOffset = Player.WorldRotation.Left * random.Int( 1000, 3000 ) * random.Int( -1, 1 );\r\n\t\tCircleOffset = new Vector3(  random.Float( -0.5f, 0.5f ), random.Float( -1, 1 ), 0 ).Normal;\r\n\t\tSchwebeMittelPunkt = GroundTrace.EndPosition \u002B Vector3.Up * 80;\r\n\r\n\t\tSchwebeFrequenz \u002B= random.Float( -0.1f, 0.1f );\r\n\t\tGameObject.WorldPosition = SchwebeMittelPunkt;\r\n\r\n\t\tIdleKreisPunkt = WorldPosition.WithZ( GroundTrace.EndPosition.z ) \u002B (Vector3)random.VectorInCircle( 1 ).Normal * random.Int( 300, 500 );\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tif ( SinceGroundCheck \u003E 0.3f \u0026\u0026 CurrentState != VampireState.Idle )\r\n\t\t{\r\n\t\t\tSinceGroundCheck = 0;\r\n\t\t\tGroundTrace = Scene.Trace\r\n\t\t\t.Ray( TargetPosition, TargetPosition \u002B Vector3.Down * 3000 )\r\n\t\t\t.Radius( 1 )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject )\r\n\t\t\t.WithoutTags( \u0022enemy\u0022, \u0022player\u0022, \u0022dead\u0022 )\r\n\t\t\t.Run();\r\n\t\t\tSinceGroundCheck = 0;\r\n\t\t}\r\n\t\tVector3 playerPosition = Player.WorldPosition;\r\n\t\tVector3 hoverHeight = (GroundTrace.EndPosition \u002B Vector3.Up * 50).WithY( 0 ).WithX( 0 );\r\n\t\tif ( !GroundTrace.Hit ) { hoverHeight = Vector3.Up * 100; }\r\n\r\n\t\tswitch ( CurrentState ) \r\n\t\t{\r\n\t\t\tdefault: \r\n\t\t\t\tStateDebugText.Text = \u0022DEFAULT\u0022;\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// IDLE\r\n\t\t\tcase VampireState.Idle:\r\n\t\t\t\tStateDebugText.Text = \u0022IDLE\u0022;\r\n\t\t\t\t\r\n\t\t\t\tif (UntilNextIdleSound) \r\n\t\t\t\t{\r\n\t\t\t\t\tSound.Play(\u0022sounds/vampire/vampire-idle.sound\u0022, WorldPosition);\r\n\t\t\t\t\tUntilNextIdleSound = random.Float(6, 10);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Wenn Vampir weiter als der Radius * 1.5f vom Punkt entfernt ist soll er einen neuen zuf\u00E4lligen finden\r\n\t\t\t\tif ((WorldPosition.WithZ( 0 ) - IdleKreisPunkt.WithZ( 0 )).Length \u003E IdleKreisRadius * 1.5f )\r\n\t\t\t\t{\r\n\t\t\t\t\tIdleKreisPunkt = WorldPosition.WithZ(GroundTrace.EndPosition.z) \u002B (Vector3)random.VectorInCircle(1).Normal * random.Int(300, 500);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Winkel un damit Koordinaten vom aufm Kreis berechnen\r\n\t\t\t\tIdleKreisAngle \u002B= Time.Delta * 0.5f; // * speed\r\n\t\t\t\tfloat x = MathF.Cos(IdleKreisAngle);\r\n\t\t\t\tfloat y = MathF.Sin(IdleKreisAngle);\r\n\r\n\t\t\t\t// TargetPosition aus IdleKreisPunkt, Koordinaten und height berechnen, SchwebeMittelPunkt zu TargetPos LERPen\r\n\t\t\t\tTargetPosition = IdleKreisPunkt \u002B new Vector3(x, y, 0).Normal * IdleKreisRadius \u002B hoverHeight;\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo(TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap(0, 5000, 1, 3));\r\n\t\t\t\t\r\n\t\t\t\tif ( DebugMode )\r\n\t\t\t\t{\r\n\t\t\t\t\tDebugOverlay.Sphere( new Sphere( TargetPosition, 16 ), Color.Red );\r\n\t\t\t\t\tDebugOverlay.Sphere( new Sphere( IdleKreisPunkt.WithZ( GroundTrace.EndPosition.z ), 16 ), Color.Blue );\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Zeit schiebt Sinusfunktion(Welle) voran, multipliziert mit Frequenz f\u00FCr enge oder weite Wellen, \r\n\t\t\t\t// dann mit Distanz multiplizieren und auf MittelPunkt addieren\r\n\t\t\t\tTransform transform = new Transform(\r\n\t\t\t\t\tSchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance),\r\n\t\t\t\t\tRotation.LookAt( TargetPosition - GameObject.WorldPosition, Vector3.Up ),\r\n\t\t\t\t\tWorldScale);\r\n\r\n\t\t\t\tif ( (playerPosition - WorldPosition).Length \u003C 8000 ) { CurrentState = VampireState.Moving; }\t\t\t\t\r\n\t\t\t\tbreak;\r\n\r\n\r\n\t\t\t// WANDER\r\n\t\t\tcase VampireState.Moving: \r\n\t\t\t\tStateDebugText.Text = \u0022MOVING\u0022;\r\n\t\t\t\t\r\n\t\t\t\tif (UntilNextIdleSound) \r\n\t\t\t\t{\r\n\t\t\t\t\tSound.Play(\u0022sounds/vampire/vampire-idle.sound\u0022, WorldPosition);\r\n\t\t\t\t\tUntilNextIdleSound = random.Float(6, 10);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// TargetPosition bestimmen und MittePunkt hinLERPen\r\n\t\t\t\tTargetPosition = playerPosition \u002B CircleOffset * 1000 \u002B hoverHeight;\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo(TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap(0, 5000, 1, 3));\r\n\t\t\t\t// Zeit schiebt Sinusfunktion(Welle) voran, multipliziert mit Frequenz f\u00FCr enge oder weite Wellen, \r\n\t\t\t\t// dann mit Distanz multiplizieren und auf MittelPunkt addieren\r\n\t\t\t\tGameObject.WorldPosition = SchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance);\r\n\r\n\t\t\t\tLookAtPlayer();\r\n\r\n\t\t\t\tif ( (playerPosition - WorldPosition).Length \u003C 1300 \u0026\u0026 SinceAttack \u003E 1f ) { Attack(); SinceAttack = 0 \u002B random.Float(0.05f, 0.1f); }\r\n\t\t\t\t// else { // BloodEmitter.Enabled = false; }\r\n\t\t\t\t\r\n\t\t\t\tif ( (playerPosition - WorldPosition).Length \u003E 8000 ) { CurrentState = VampireState.Idle; }\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// ATTACK\r\n\t\t\tcase VampireState.Attack:\r\n\t\t\t\tStateDebugText.Text = \u0022ATTACK\u0022;\r\n\r\n\r\n\t\t\t\tbreak;\r\n\r\n\t\t\t// STAGGERED\r\n\t\t\tcase VampireState.Staggered:\r\n\t\t\t\tStateDebugText.Text = \u0022STAGGERED\u0022;\r\n\t\t\t\t\r\n\t\t\t\tBloodEmitter.Enabled = false;\r\n\r\n\t\t\t\tSchwebeMittelPunkt = SchwebeMittelPunkt.LerpTo( TargetPosition, Time.Delta * (TargetPosition - SchwebeMittelPunkt).Length.Remap( 0, 5000, 1, 3 ) );\r\n\t\t\t\tGameObject.WorldPosition = SchwebeMittelPunkt \u002B Vector3.Up * (MathF.Sin( Time.Now * (SchwebeFrequenz) ) * SchwebeDistance);\r\n\r\n\t\t\t\tif (UntilKnockBack) { CurrentState = VampireState.Moving; }\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\t}\r\n\r\n\tprivate void LookAtPlayer()\r\n\t{\r\n\t\tif ( GameObject.WorldRotation.Forward.Angle( Player.WorldPosition - GameObject.WorldPosition ) \u003E 1 )\r\n\t\t{\r\n\t\t\tGameObject.WorldRotation = GameObject.WorldRotation.LerpTo( Rotation.LookAt( Player.WorldPosition - GameObject.WorldPosition, Vector3.Up ), 0.5f );\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void Attack()\r\n\t{\r\n\t\tPlayer.GetComponentInParent\u003CHealthSystem\u003E().Damage(80, false);\r\n\t\t// Particle \u0026 Sound\r\n\r\n\t\tParticleObject.WorldPosition = PlayerBody.WorldPosition \u002B Vector3.Up * 50;\r\n\t\tBloodEmitter.ResetEmitter();\r\n\t}\r\n\r\n\tpublic async Task AnimateHit()\r\n\t{\r\n\t\tfloat duration = 0.1f;\r\n\t\tSinceHitAnimationStart = 0;\r\n\r\n\t\tWorldScale = WorldScale.WithZ( originalScale.z * (1f - 0.15f) );\r\n\r\n\t\twhile ( SinceHitAnimationStart \u003C duration )\r\n\t\t{\r\n\t\t\tWorldScale = WorldScale.WithZ( WorldScale.z.LerpTo( originalScale.z, 0.1f ) );\r\n\t\t\tawait Task.FrameEnd();\r\n\t\t}\r\n\r\n\t\tif ( !this.IsValid() ) return;\r\n\t\tWorldScale = originalScale;\r\n\t}\r\n\r\n\r\n}\r\n"}]}