From 1ec210ea26f2656d8d89090c9a8273f3c1741241 Mon Sep 17 00:00:00 2001 From: psyGamer Date: Wed, 7 Feb 2024 19:37:52 +0100 Subject: [PATCH] Fix formatting --- Source/Actors/Player.cs | 597 ++++++++++++++++++++++++++-------------- Source/Data/Save.cs | 415 ++++++++++++++-------------- Source/Helpers/Menu.cs | 234 +++++++++------- Source/Scenes/World.cs | 275 +++++++++++------- Source/TAS/Manager.cs | 36 ++- 5 files changed, 933 insertions(+), 624 deletions(-) diff --git a/Source/Actors/Player.cs b/Source/Actors/Player.cs index a52ab8c..517d9c4 100644 --- a/Source/Actors/Player.cs +++ b/Source/Actors/Player.cs @@ -7,7 +7,8 @@ namespace Celeste64; /// /// Welcome to the monolithic player class! This time only 2300 lines ;) /// -public class Player : Actor, IHaveModels, IHaveSprites, IRidePlatforms, ICastPointShadow, IHaveRenderCollider { +public class Player : Actor, IHaveModels, IHaveSprites, IRidePlatforms, ICastPointShadow, IHaveRenderCollider +{ #region Constants private const float Acceleration = 500; @@ -82,24 +83,28 @@ public class Player : Actor, IHaveModels, IHaveSprites, IRidePlatforms, ICastPoi #region SubClasses - private class Trail { + private class Trail + { public readonly Hair Hair; public readonly SkinnedModel Model; public Matrix Transform; public float Percent; public Color Color; - public Trail() { + public Trail() + { Model = new(Assets.Models["player"]); Model.Flags = ModelFlags.Transparent; Model.MakeMaterialsUnique(); - foreach (var mat in Model.Materials) { + foreach (var mat in Model.Materials) + { mat.Texture = Assets.Textures["white"]; mat.Effects = 0; } Hair = new(); - foreach (var mat in Hair.Materials) { + foreach (var mat in Hair.Materials) + { mat.Texture = Assets.Textures["white"]; mat.Effects = 0; } @@ -115,6 +120,7 @@ public Trail() { private static float storedCameraDistance; public enum States { Normal, Dashing, Skidding, Climbing, StrawbGet, FeatherStart, Feather, Respawn, Dead, StrawbReveal, Cutscene, Bubble, Cassette }; // TAS: publicized + public enum Events { Land }; // TAS: publicized public bool Dead = false; @@ -144,6 +150,7 @@ public enum Events { Land }; // TAS: publicized public readonly StateMachine stateMachine; // TAS: publicized private record struct CameraOverride(Vec3 Position, Vec3 LookAt); + private CameraOverride? cameraOverride = null; private Vec3 cameraOriginPos; @@ -165,12 +172,13 @@ private record struct CameraOverride(Vec3 Position, Vec3 LookAt); public Vec3 SolidWaistTestPos // TAS: publicized => Position + Vec3.UnitZ * 3; + private Vec3 SolidHeadTestPos => Position + Vec3.UnitZ * 10; private bool InFeatherState => stateMachine.State == States.FeatherStart - || stateMachine.State == States.Feather; + || stateMachine.State == States.Feather; private bool InBubble => stateMachine.State == States.Bubble; @@ -180,19 +188,20 @@ public bool IsStrawberryCounterVisible public bool IsAbleToPickup => stateMachine.State != States.StrawbGet - && stateMachine.State != States.Bubble - && stateMachine.State != States.Cassette - && stateMachine.State != States.StrawbReveal - && stateMachine.State != States.Respawn - && stateMachine.State != States.Dead; + && stateMachine.State != States.Bubble + && stateMachine.State != States.Cassette + && stateMachine.State != States.StrawbReveal + && stateMachine.State != States.Respawn + && stateMachine.State != States.Dead; public bool IsAbleToPause => stateMachine.State != States.StrawbReveal - && stateMachine.State != States.StrawbGet - && stateMachine.State != States.Cassette - && stateMachine.State != States.Dead; + && stateMachine.State != States.StrawbGet + && stateMachine.State != States.Cassette + && stateMachine.State != States.Dead; - public Player() { + public Player() + { PointShadowAlpha = 1.0f; LocalBounds = new BoundingBox(new Vec3(0, 0, 10), 10); UpdateOffScreen = true; @@ -226,7 +235,8 @@ public Player() { stateMachine.InitState(States.Bubble, null, null, StBubbleExit, StBubbleRoutine); stateMachine.InitState(States.Cassette, null, null, StCassetteExit, StCassetteRoutine); - spikeBlockCheck = (spike) => { + spikeBlockCheck = (spike) => + { return Vec3.Dot(velocity.Normalized(), spike.Direction) < 0.5f; }; @@ -235,16 +245,20 @@ public Player() { #region Added / Update - public override void Added() { - if (World.Entry.Reason == World.EntryReasons.Respawned) { + public override void Added() + { + if (World.Entry.Reason == World.EntryReasons.Respawned) + { cameraTargetForward = storedCameraForward; cameraTargetDistance = storedCameraDistance; stateMachine.State = States.Respawn; } - else if (World.Entry.Submap && World.Entry.Reason == World.EntryReasons.Entered) { + else if (World.Entry.Submap && World.Entry.Reason == World.EntryReasons.Entered) + { stateMachine.State = States.StrawbReveal; } - else { + else + { stateMachine.State = States.Normal; } @@ -258,16 +272,19 @@ public override void Added() { World.Camera.Position = orig; World.Camera.FarPlane = Save.Instance.SimplifiedGraphics ? 8000 : 800; - if (Save.Instance.Freecam != Save.FreecamMode.Free) { + if (Save.Instance.Freecam != Save.FreecamMode.Free) + { Manager.FreeCamPosition = Position; } } - public override void Update() { - - if (Save.Instance.Freecam is Save.FreecamMode.Orbit or Save.FreecamMode.Free) { + public override void Update() + { + if (Save.Instance.Freecam is Save.FreecamMode.Orbit or Save.FreecamMode.Free) + { // Freecam rotation - if (Input.Mouse.Down(MouseButtons.Left)) { + if (Input.Mouse.Down(MouseButtons.Left)) + { //if the mouse moves too much, it means, the Game reset the mouse somewhere //the camera would behave weirdly, if this was not there //TODO: find a way to limit Mouse Movement on Load? @@ -282,7 +299,8 @@ public override void Update() { Manager.FreeCamDistance -= Input.Mouse.Wheel.Y * FreecamZoomSpeed; } - if (Save.Instance.Freecam == Save.FreecamMode.Free) { + if (Save.Instance.Freecam == Save.FreecamMode.Free) + { // Freecam movement var cameraForward = new Vec2( MathF.Sin(Manager.FreeCamRotation.X), @@ -292,7 +310,8 @@ public override void Update() { MathF.Cos(Manager.FreeCamRotation.X - Calc.HalfPI)); // if not currently in TAS, proceed with normal movement - if (!Manager.Running) { + if (!Manager.Running) + { Manager.FreeCamPosition.X -= Controls.Move.Value.Y * cameraForward.X * FreecamMoveSpeed; Manager.FreeCamPosition.Y -= Controls.Move.Value.Y * cameraForward.Y * FreecamMoveSpeed; Manager.FreeCamPosition.X -= Controls.Move.Value.X * cameraRight.X * FreecamMoveSpeed; @@ -323,7 +342,8 @@ public override void Update() { // only update camera if not dead and not in freecam if (stateMachine.State != States.Respawn && stateMachine.State != States.Dead && - stateMachine.State != States.StrawbReveal && stateMachine.State != States.Cassette) { + stateMachine.State != States.StrawbReveal && stateMachine.State != States.Cassette) + { // Rotate Camera { var invertX = Save.Instance.InvertCamera == Save.InvertCameraOptions.X || Save.Instance.InvertCamera == Save.InvertCameraOptions.Both; @@ -335,12 +355,14 @@ public override void Update() { } // Move Camera in / out - if (Controls.Camera.Value.Y != 0) { + if (Controls.Camera.Value.Y != 0) + { var invertY = Save.Instance.InvertCamera == Save.InvertCameraOptions.Y || Save.Instance.InvertCamera == Save.InvertCameraOptions.Both; cameraTargetDistance += Controls.Camera.Value.Y * Time.Delta * (invertY ? -1 : 1); cameraTargetDistance = Calc.Clamp(cameraTargetDistance, 0, 1); } - else { + else + { const float interval = 1f / 3; const float threshold = .1f; if (cameraTargetDistance % interval < threshold || cameraTargetDistance % interval > interval - threshold) @@ -355,16 +377,19 @@ public override void Update() { if (Manager.IsPaused()) return; // don't do anything if dead - if (stateMachine.State is States.Respawn or States.Dead or States.Cutscene) { + if (stateMachine.State is States.Respawn or States.Dead or States.Cutscene) + { stateMachine.Update(); return; } // death plane - if (!InBubble) { + if (!InBubble) + { if (Position.Z < World.DeathPlane || World.Overlaps(SolidWaistTestPos) || - World.Overlaps(SolidWaistTestPos, spikeBlockCheck)) { + World.Overlaps(SolidWaistTestPos, spikeBlockCheck)) + { Kill(); return; } @@ -400,9 +425,11 @@ public override void Update() { stateMachine.Update(); // move and pop out - if (!InBubble) { + if (!InBubble) + { // push out of NPCs - foreach (var actor in World.All()) { + foreach (var actor in World.All()) + { var it = (actor as IHavePushout)!; if (it.PushoutRadius <= 0 || it.PushoutHeight <= 0) continue; @@ -432,9 +459,12 @@ public override void Update() { } // pickups - if (IsAbleToPickup) { - foreach (var actor in World.All()) { - if (actor is IPickup pickup) { + if (IsAbleToPickup) + { + foreach (var actor in World.All()) + { + if (actor is IPickup pickup) + { if ((SolidWaistTestPos - actor.Position).LengthSquared() < pickup.PickupRadius * pickup.PickupRadius) pickup.Pickup(this); } @@ -442,8 +472,10 @@ public override void Update() { } } - public override void LateUpdate() { - if (!Manager.IsPaused()) { + public override void LateUpdate() + { + if (!Manager.IsPaused()) + { // ground checks { bool prevOnGround = onGround; @@ -451,15 +483,18 @@ public override void LateUpdate() { if (onGround) Position += pushout; - if (tGroundSnapCooldown <= 0 && prevOnGround && !onGround) { + if (tGroundSnapCooldown <= 0 && prevOnGround && !onGround) + { // try to ground snap? - if (World.SolidRayCast(Position, -Vec3.UnitZ, 5, out var hit) && FloorNormalCheck(hit.Normal)) { + if (World.SolidRayCast(Position, -Vec3.UnitZ, 5, out var hit) && FloorNormalCheck(hit.Normal)) + { Position = hit.Point; onGround = GroundCheck(out _, out normal, out _); } } - if (onGround) { + if (onGround) + { autoJump = false; groundNormal = normal; tCoyote = CoyoteTime; @@ -470,15 +505,18 @@ public override void LateUpdate() { else groundNormal = Vec3.UnitZ; - if (!prevOnGround && onGround) { + if (!prevOnGround && onGround) + { float t = Calc.ClampedMap(previousVelocity.Z, 0, MaxFall); ModelScale = Vec3.Lerp(Vec3.One, new(1.4f, 1.4f, .6f), t); stateMachine.CallEvent(Events.Land); - if (!Game.Instance.IsMidTransition && !InBubble) { + if (!Game.Instance.IsMidTransition && !InBubble) + { Audio.Play(Sfx.sfx_land, Position); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < 16; i++) + { var angle = Calc.AngleToVector((i / 16.0f) * MathF.Tau); var at = Position + new Vec3(angle, 0) * 4; var vel = (tPlatformVelocityStorage > 0 ? platformVelocity : Vec3.Zero) + new Vec3(angle, 0) * 50; @@ -490,7 +528,8 @@ public override void LateUpdate() { } - if (Save.Instance.Freecam == Save.FreecamMode.Free) { + if (Save.Instance.Freecam == Save.FreecamMode.Free) + { var forward = new Vec3( MathF.Sin(Manager.FreeCamRotation.X) * MathF.Cos(Manager.FreeCamRotation.Y), MathF.Cos(Manager.FreeCamRotation.X) * MathF.Cos(Manager.FreeCamRotation.Y), @@ -498,7 +537,8 @@ public override void LateUpdate() { World.Camera.Position = Manager.FreeCamPosition; World.Camera.LookAt = Manager.FreeCamPosition + forward; } - else if (Save.Instance.Freecam == Save.FreecamMode.Orbit) { + else if (Save.Instance.Freecam == Save.FreecamMode.Orbit) + { var forward = new Vec3( MathF.Sin(Manager.FreeCamRotation.X) * MathF.Cos(Manager.FreeCamRotation.Y), MathF.Cos(Manager.FreeCamRotation.X) * MathF.Cos(Manager.FreeCamRotation.Y), @@ -506,7 +546,8 @@ public override void LateUpdate() { World.Camera.LookAt = Position; World.Camera.Position = Position - forward * Math.Max(10.0f, Manager.FreeCamDistance); } - else { + else + { // update camera origin position { float ZPad = stateMachine.State == States.Climbing ? 0 : 8; @@ -531,11 +572,13 @@ public override void LateUpdate() { { Vec3 lookAt, cameraPos; - if (cameraOverride.HasValue) { + if (cameraOverride.HasValue) + { lookAt = cameraOverride.Value.LookAt; cameraPos = cameraOverride.Value.Position; } - else { + else + { GetCameraTarget(out lookAt, out cameraPos, out _); } @@ -559,7 +602,8 @@ public override void LateUpdate() { Model.Update(); Model.Transform = Matrix.CreateScale(ModelScale * 3); - if (stateMachine.State != States.Feather && stateMachine.State != States.FeatherStart) { + if (stateMachine.State != States.Feather && stateMachine.State != States.FeatherStart) + { Color color; if (tDashResetFlash > 0) color = CRefillFlash; @@ -578,8 +622,10 @@ public override void LateUpdate() { { var hairMatrix = Matrix.Identity; - foreach (var it in Model.Instance.Armature.LogicalNodes) { - if (it.Name == "Head") { + foreach (var it in Model.Instance.Armature.LogicalNodes) + { + if (it.Name == "Head") + { hairMatrix = it.ModelMatrix * SkinnedModel.BaseTranslation * Model.Transform * Matrix; } } @@ -593,7 +639,8 @@ public override void LateUpdate() { } // trails - for (int i = trails.Count - 1; i >= 0; i--) { + for (int i = trails.Count - 1; i >= 0; i--) + { if (trails[i].Percent < 1) trails[i].Percent += Time.Delta / 0.5f; } @@ -603,25 +650,28 @@ public override void LateUpdate() { #region Camera Calculation - public void GetCameraTarget(out Vec3 cameraLookAt, out Vec3 cameraPosition, out bool snapRequested) { + public void GetCameraTarget(out Vec3 cameraLookAt, out Vec3 cameraPosition, out bool snapRequested) + { snapRequested = false; // get default values cameraLookAt = cameraOriginPos; cameraPosition = cameraLookAt - - cameraTargetForward * Utils.Lerp3(30, 60, 110, 110, cameraTargetDistance) - + Vec3.UnitZ * Utils.Lerp3(1, 30, 80, 180, cameraTargetDistance); + - cameraTargetForward * Utils.Lerp3(30, 60, 110, 110, cameraTargetDistance) + + Vec3.UnitZ * Utils.Lerp3(1, 30, 80, 180, cameraTargetDistance); cameraLookAt += Vec3.UnitZ * 12; // inside a fixed camera zone if (World.OverlapsFirst(SolidWaistTestPos) is { } fixedCamera - && (cameraLookAt - fixedCamera.Position).Length() > 5) { + && (cameraLookAt - fixedCamera.Position).Length() > 5) + { cameraPosition = fixedCamera.Point; cameraTargetForward = new Vec3((cameraLookAt.XY() - cameraPosition.XY()).Normalized(), 0); snapRequested = true; } // try to push out of solids if we're in them - else { + else + { var from = cameraLookAt; // - Vec3.UnitZ * (onGround ? 0 : 6); var to = cameraPosition; var normal = (to - from).Normalized(); @@ -632,15 +682,18 @@ public void GetCameraTarget(out Vec3 cameraLookAt, out Vec3 cameraPosition, out distance -= World.Camera.NearPlane; // inside a wall, push out - if (World.SolidRayCast(from, normal, distance, out var hit, false, true)) { - if ((hit.Intersections % 2) == 1) { + if (World.SolidRayCast(from, normal, distance, out var hit, false, true)) + { + if ((hit.Intersections % 2) == 1) + { snapRequested = true; cameraPosition = hit.Point; } } // push down from ceilings a bit - if (World.SolidRayCast(cameraPosition, Vec3.UnitZ, 5, out hit, true, true)) { + if (World.SolidRayCast(cameraPosition, Vec3.UnitZ, 5, out hit, true, true)) + { cameraPosition = hit.Point - Vec3.UnitZ * 5; } } @@ -650,9 +703,12 @@ public void GetCameraTarget(out Vec3 cameraLookAt, out Vec3 cameraPosition, out #region Various Methods - private Vec2 RelativeMoveInput { - get { - if (Manager.Running && CameraModeCommand.Mode == CameraModeCommand.CameraMode.Independent) { + private Vec2 RelativeMoveInput + { + get + { + if (Manager.Running && CameraModeCommand.Mode == CameraModeCommand.CameraMode.Independent) + { return Controls.Move.Value.Normalized(); } @@ -675,13 +731,17 @@ private Vec2 RelativeMoveInput { } } - public void SetTargetFacing(Vec2 facing) { + public void SetTargetFacing(Vec2 facing) + { targetFacing = facing; } - public void SetHairColor(Color color) { - foreach (var mat in Model.Materials) { - if (mat.Name == "Hair") { + public void SetHairColor(Color color) + { + foreach (var mat in Model.Materials) + { + if (mat.Name == "Hair") + { mat.Color = color; mat.Effects = 0; } @@ -693,7 +753,8 @@ public void SetHairColor(Color color) { Hair.Nodes = (InFeatherState ? 18 : (dashes >= 2 ? 16 : 10)); } - public void SweepTestMove(Vec3 delta, bool resolveImpact) { + public void SweepTestMove(Vec3 delta, bool resolveImpact) + { if (delta.LengthSquared() <= 0) return; @@ -701,13 +762,15 @@ public void SweepTestMove(Vec3 delta, bool resolveImpact) { var stepSize = 2.0f; var stepNormal = delta / remaining; - while (remaining > 0) { + while (remaining > 0) + { // perform step var step = MathF.Min(remaining, stepSize); remaining -= step; Position += stepNormal * step; - if (Popout(resolveImpact)) { + if (Popout(resolveImpact)) + { // don't repeatedly resolve wall impacts resolveImpact = false; } @@ -717,16 +780,19 @@ public void SweepTestMove(Vec3 delta, bool resolveImpact) { /// /// Pops out of Solid Geometry. Returns true if popped out of a wall /// - public bool Popout(bool resolveImpact) { + public bool Popout(bool resolveImpact) + { // ground test - if (GroundCheck(out var pushout, out _, out _)) { + if (GroundCheck(out var pushout, out _, out _)) + { Position += pushout; if (resolveImpact) velocity.Z = MathF.Max(velocity.Z, 0); } // ceiling test - else if (CeilingCheck(out pushout)) { + else if (CeilingCheck(out pushout)) + { Position += pushout; if (resolveImpact) velocity.Z = MathF.Min(velocity.Z, 0); @@ -734,23 +800,28 @@ public bool Popout(bool resolveImpact) { // wall test if (World.SolidWallCheckNearest(SolidWaistTestPos, WallPushoutDist, out var hit) || - World.SolidWallCheckNearest(SolidHeadTestPos, WallPushoutDist, out hit)) { + World.SolidWallCheckNearest(SolidHeadTestPos, WallPushoutDist, out hit)) + { // feather state handling - if (resolveImpact && stateMachine.State == States.Feather && tFeatherWallBumpCooldown <= 0 && !(Controls.Climb.Down && TryClimb())) { + if (resolveImpact && stateMachine.State == States.Feather && tFeatherWallBumpCooldown <= 0 && !(Controls.Climb.Down && TryClimb())) + { Position += hit.Pushout; velocity = velocity.WithXY(Vec2.Reflect(velocity.XY(), hit.Normal.XY().Normalized())); tFeatherWallBumpCooldown = 0.50f; Audio.Play(Sfx.sfx_feather_state_bump_wall, Position); } // is it a breakable thing? - else if (resolveImpact && hit.Actor is BreakBlock breakable && !breakable.Destroying && velocity.XY().Length() > 90) { + else if (resolveImpact && hit.Actor is BreakBlock breakable && !breakable.Destroying && velocity.XY().Length() > 90) + { BreakBlock(breakable, velocity.Normalized()); } // normal wall - else { + else + { Position += hit.Pushout; - if (resolveImpact) { + if (resolveImpact) + { var dot = MathF.Min(0.0f, Vec3.Dot(velocity.Normalized(), hit.Normal)); velocity -= hit.Normal * velocity.Length() * dot; } @@ -765,7 +836,8 @@ public bool Popout(bool resolveImpact) { public void CancelGroundSnap() => tGroundSnapCooldown = 0.1f; - private void Jump() { + private void Jump() + { Position = Position with { Z = coyoteZ }; holdJumpSpeed = velocity.Z = JumpSpeed; tHoldJump = JumpHoldTime; @@ -773,7 +845,8 @@ private void Jump() { autoJump = false; var input = RelativeMoveInput; - if (input != Vec2.Zero) { + if (input != Vec2.Zero) + { input = input.Normalized(); targetFacing = input; velocity += new Vec3(input * JumpXYBoost, 0); @@ -786,7 +859,8 @@ private void Jump() { Audio.Play(Sfx.sfx_jump, Position); } - private void WallJump() { + private void WallJump() + { holdJumpSpeed = velocity.Z = JumpSpeed; tHoldJump = JumpHoldTime; autoJump = false; @@ -801,7 +875,8 @@ private void WallJump() { Audio.Play(Sfx.sfx_jump_wall, Position); } - private void SkidJump() { + private void SkidJump() + { Position = Position with { Z = coyoteZ }; holdJumpSpeed = velocity.Z = SkidJumpSpeed; tHoldJump = SkidJumpHoldTime; @@ -813,7 +888,8 @@ private void SkidJump() { AddPlatformVelocity(false); CancelGroundSnap(); - for (int i = 0; i < 16; i++) { + for (int i = 0; i < 16; i++) + { var dir = new Vec3(Calc.AngleToVector((i / 16f) * MathF.Tau), 0); World.Request().Init(Position + dir * 8, new Vec3(velocity.XY() * 0.5f, 10) - dir * 50, 0x666666); } @@ -823,7 +899,8 @@ private void SkidJump() { Audio.Play(Sfx.sfx_jump_skid, Position); } - private void DashJump() { + private void DashJump() + { Position = Position with { Z = coyoteZ }; velocity.Z = DashJumpSpeed; holdJumpSpeed = DashJumpHoldSpeed; @@ -832,9 +909,11 @@ private void DashJump() { autoJump = false; dashes = 1; - if (DashJumpXYBoost != 0) { + if (DashJumpXYBoost != 0) + { var input = RelativeMoveInput; - if (input != Vec2.Zero) { + if (input != Vec2.Zero) + { input = input.Normalized(); targetFacing = input; velocity += new Vec3(input * DashJumpXYBoost, 0); @@ -849,8 +928,10 @@ private void DashJump() { Audio.Play(Sfx.sfx_jump_superslide, Position); } - private void AddPlatformVelocity(bool playSound) { - if (tPlatformVelocityStorage > 0) { + private void AddPlatformVelocity(bool playSound) + { + if (tPlatformVelocityStorage > 0) + { Vec3 add = platformVelocity; add.Z = Calc.Clamp(add.Z, 0, 180); @@ -866,7 +947,8 @@ private void AddPlatformVelocity(bool playSound) { } } - public void Kill() { + public void Kill() + { stateMachine.State = States.Dead; storedCameraForward = cameraTargetForward; storedCameraDistance = cameraTargetDistance; @@ -877,13 +959,14 @@ public void Kill() { public bool ClimbCheckAt(Vec3 offset, out WallHit hit) // TAS: publicized { if (World.SolidWallCheckClosestToNormal(SolidWaistTestPos + offset, ClimbCheckDist, -new Vec3(targetFacing, 0), out hit) - && (RelativeMoveInput == Vec2.Zero || Vec2.Dot(hit.Normal.XY().Normalized(), RelativeMoveInput) <= -0.5f) - && ClimbNormalCheck(hit.Normal)) + && (RelativeMoveInput == Vec2.Zero || Vec2.Dot(hit.Normal.XY().Normalized(), RelativeMoveInput) <= -0.5f) + && ClimbNormalCheck(hit.Normal)) return true; return false; } - private bool TryClimb() { + private bool TryClimb() + { var result = ClimbCheckAt(Vec3.Zero, out var wall); // let us snap up to walls if we're jumping for them @@ -892,7 +975,8 @@ private bool TryClimb() { if (!result && Velocity.Z > 0 && !onGround && stateMachine.State != States.Climbing) result = ClimbCheckAt(Vec3.UnitZ * 4, out wall); - if (result) { + if (result) + { climbingWallNormal = wall.Normal; climbingWallActor = wall.Actor; var moveTo = wall.Point + (Position - SolidWaistTestPos) + wall.Normal * WallPushoutDist; @@ -900,23 +984,27 @@ private bool TryClimb() { targetFacing = -climbingWallNormal.XY().Normalized(); return true; } - else { + else + { climbingWallActor = default; climbingWallNormal = default; return false; } } - private bool ClimbNormalCheck(in Vec3 normal) { + private bool ClimbNormalCheck(in Vec3 normal) + { return MathF.Abs(normal.Z) < 0.35f; } private bool FloorNormalCheck(in Vec3 normal) => !ClimbNormalCheck(normal) && normal.Z > 0; - private bool WallJumpCheck() { + private bool WallJumpCheck() + { if (Controls.Jump.Pressed - && World.SolidWallCheckClosestToNormal(SolidWaistTestPos, ClimbCheckDist, -new Vec3(targetFacing, 0), out var hit)) { + && World.SolidWallCheckClosestToNormal(SolidWaistTestPos, ClimbCheckDist, -new Vec3(targetFacing, 0), out var hit)) + { Controls.Jump.ConsumePress(); Position += (hit.Pushout * (WallPushoutDist / ClimbCheckDist)); targetFacing = hit.Normal.XY().Normalized(); @@ -926,11 +1014,13 @@ private bool WallJumpCheck() { return false; } - private void BreakBlock(BreakBlock block, Vec3 direction) { + private void BreakBlock(BreakBlock block, Vec3 direction) + { World.HitStun = 0.1f; block.Break(direction); - if (block.BouncesPlayer) { + if (block.BouncesPlayer) + { velocity.X = -velocity.X * 0.80f; velocity.Y = -velocity.Y * 0.80f; velocity.Z = 100; @@ -940,7 +1030,8 @@ private void BreakBlock(BreakBlock block, Vec3 direction) { } } - internal void Spring(Spring spring) { + internal void Spring(Spring spring) + { stateMachine.State = States.Normal; Position = Position with { Z = Calc.Approach(Position.Z, spring.Position.Z + 3, 2) }; @@ -974,29 +1065,36 @@ internal void Spring(Spring spring) { private float tNoMove; private float tFootstep; - private void StNormalEnter() { + private void StNormalEnter() + { tHoldJump = 0; tFootstep = FootstepInterval; } - private void StNormalExit() { + private void StNormalExit() + { tHoldJump = 0; tNoMove = 0; autoJump = false; Model.Rate = 1; } - private void StNormalUpdate() { + private void StNormalUpdate() + { // Check for NPC interaction - if (onGround) { + if (onGround) + { foreach (var actor in World.All()) - if (actor is NPC npc && npc.InteractEnabled) { + if (actor is NPC npc && npc.InteractEnabled) + { if ((Position - npc.Position).LengthSquared() < npc.InteractRadius * npc.InteractRadius && Vec2.Dot((npc.Position - Position).XY(), targetFacing) > 0 && - MathF.Abs(npc.Position.Z - Position.Z) < 2) { + MathF.Abs(npc.Position.Z - Position.Z) < 2) + { npc.IsPlayerOver = true; - if (Controls.Dash.ConsumePress()) { + if (Controls.Dash.ConsumePress()) + { npc.Interact(this); return; } @@ -1010,7 +1108,8 @@ private void StNormalUpdate() { { var velXY = velocity.XY(); - if (Controls.Move.Value == Vec2.Zero || tNoMove > 0) { + if (Controls.Move.Value == Vec2.Zero || tNoMove > 0) + { // if not moving, simply apply friction float fric = Friction; @@ -1020,11 +1119,13 @@ private void StNormalUpdate() { // friction Calc.Approach(ref velXY, Vec2.Zero, fric * Time.Delta); } - else if (onGround) { + else if (onGround) + { float max = MaxSpeed; // change max speed based on ground slope angle - if (groundNormal != Vec3.UnitZ) { + if (groundNormal != Vec3.UnitZ) + { float slopeDot = 1 - Calc.Clamp(Vec3.Dot(groundNormal, Vec3.UnitZ), 0, 1); slopeDot *= Vec2.Dot(groundNormal.XY().Normalized(), targetFacing) * 2; max += max * slopeDot; @@ -1050,17 +1151,20 @@ private void StNormalUpdate() { if (input != Vec2.Zero && !World.SolidRayCast(Position + new Vec3(input, 1) * d, -Vec3.UnitZ, 8, out var hit) && - !World.SolidRayCast(Position + new Vec3(0, 0, d), new Vec3(input, 0), d, out hit)) { + !World.SolidRayCast(Position + new Vec3(0, 0, d), new Vec3(input, 0), d, out hit)) + { var left = Calc.AngleToVector(Calc.Angle(input) + 0.3f); var right = Calc.AngleToVector(Calc.Angle(input) - 0.3f); var count = 0; - if (World.SolidRayCast(Position + new Vec3(left, 1) * d, -Vec3.UnitZ, 8, out hit)) { + if (World.SolidRayCast(Position + new Vec3(left, 1) * d, -Vec3.UnitZ, 8, out hit)) + { while (World.SolidRayCast(Position + new Vec3(left, 1) * d, -Vec3.UnitZ, 8, out hit) && count++ < 10) left = Calc.AngleToVector(Calc.Angle(left) - 0.1f); input = Calc.AngleToVector(Calc.Angle(left) + 0.1f); } - else if (World.SolidRayCast(Position + new Vec3(right, 1) * d, -Vec3.UnitZ, 8, out hit)) { + else if (World.SolidRayCast(Position + new Vec3(right, 1) * d, -Vec3.UnitZ, 8, out hit)) + { while (World.SolidRayCast(Position + new Vec3(right, 1) * d, -Vec3.UnitZ, 8, out hit) && count++ < 10) right = Calc.AngleToVector(Calc.Angle(right) + 0.1f); input = Calc.AngleToVector(Calc.Angle(right) - 0.1f); @@ -1078,13 +1182,16 @@ private void StNormalUpdate() { // if our XY velocity is above the Rotate Threshold, then our XY velocity begins rotating // instead of using a simple approach to accelerate - if (velXY.LengthSquared() >= RotateThreshold * RotateThreshold) { - if (Vec2.Dot(input, velXY.Normalized()) <= SkidDotThreshold) { + if (velXY.LengthSquared() >= RotateThreshold * RotateThreshold) + { + if (Vec2.Dot(input, velXY.Normalized()) <= SkidDotThreshold) + { Facing = targetFacing = input; stateMachine.State = States.Skidding; return; } - else { + else + { // Rotate speed is less when travelling above our "true max" speed // this gives high speeds less fine control float rotate; @@ -1097,22 +1204,26 @@ private void StNormalUpdate() { velXY = targetFacing * Calc.Approach(velXY.Length(), max, accel * Time.Delta); } } - else { + else + { // if we're below the RotateThreshold, acceleration is very simple Calc.Approach(ref velXY, input * max, accel * Time.Delta); targetFacing = input.Normalized(); } } - else { + else + { float accel; - if (velXY.LengthSquared() >= MaxSpeed * MaxSpeed && Vec2.Dot(RelativeMoveInput.Normalized(), velXY.Normalized()) >= .7f) { + if (velXY.LengthSquared() >= MaxSpeed * MaxSpeed && Vec2.Dot(RelativeMoveInput.Normalized(), velXY.Normalized()) >= .7f) + { accel = PastMaxDeccel; var dot = Vec2.Dot(RelativeMoveInput.Normalized(), targetFacing); accel *= Calc.ClampedMap(dot, -1, 1, AirAccelMultMax, AirAccelMultMin); } - else { + else + { accel = Acceleration; var dot = Vec2.Dot(RelativeMoveInput.Normalized(), targetFacing); @@ -1126,14 +1237,17 @@ private void StNormalUpdate() { } // Footstep sounds - if (onGround && velocity.XY().Length() > 10) { + if (onGround && velocity.XY().Length() > 10) + { tFootstep -= Time.Delta * Model.Rate; - if (tFootstep <= 0) { + if (tFootstep <= 0) + { tFootstep = FootstepInterval; Audio.Play(Sfx.sfx_footstep_general, Position); } - if (Time.OnInterval(0.05f)) { + if (Time.OnInterval(0.05f)) + { var at = Position + new Vec3(World.Rng.Float(-3, 3), World.Rng.Float(-3, 3), 0); var vel = tPlatformVelocityStorage > 0 ? platformVelocity : Vec3.Zero; World.Request().Init(at, vel); @@ -1143,7 +1257,8 @@ private void StNormalUpdate() { tFootstep = FootstepInterval; // start climbing - if (Controls.Climb.Down && tClimbCooldown <= 0 && TryClimb()) { + if (Controls.Climb.Down && tClimbCooldown <= 0 && TryClimb()) + { stateMachine.State = States.Climbing; return; } @@ -1157,16 +1272,20 @@ private void StNormalUpdate() { Jump(); else if (WallJumpCheck()) WallJump(); - else { - if (tHoldJump > 0 && (autoJump || Controls.Jump.Down)) { + else + { + if (tHoldJump > 0 && (autoJump || Controls.Jump.Down)) + { if (velocity.Z < holdJumpSpeed) velocity.Z = holdJumpSpeed; } - else { + else + { float mult; if ((Controls.Jump.Down || autoJump) && MathF.Abs(velocity.Z) < HalfGravThreshold) mult = .5f; - else { + else + { mult = 1; autoJump = false; } @@ -1177,9 +1296,11 @@ private void StNormalUpdate() { } // Update Model Animations - if (onGround) { + if (onGround) + { var velXY = velocity.XY(); - if (velXY.LengthSquared() > 1) { + if (velXY.LengthSquared() > 1) + { // TODO: this was jittery, turning off for now Model.Play("Run"); // if (turning == 0) @@ -1191,12 +1312,14 @@ private void StNormalUpdate() { Model.Rate = Calc.ClampedMap(velXY.Length(), 0, MaxSpeed * 2, .1f, 3); } - else { + else + { Model.Play("Idle"); Model.Rate = 1; } } - else { + else + { // basically resets everything to the first frame of Run over and over Model.Clear(); Model.Play("Run"); @@ -1217,8 +1340,10 @@ private void StNormalUpdate() { private bool dashedOnGround; private int dashTrailsCreated; - private bool TryDash() { - if (dashes > 0 && tDashCooldown <= 0 && Controls.Dash.ConsumePress()) { + private bool TryDash() + { + if (dashes > 0 && tDashCooldown <= 0 && Controls.Dash.ConsumePress()) + { dashes--; stateMachine.State = States.Dashing; return true; @@ -1226,7 +1351,8 @@ private bool TryDash() { else return false; } - private void StDashingEnter() { + private void StDashingEnter() + { if (RelativeMoveInput != Vec2.Zero) targetFacing = RelativeMoveInput; Facing = targetFacing; @@ -1251,28 +1377,33 @@ private void StDashingEnter() { //CancelGroundSnap(); } - private void StDashingExit() { + private void StDashingExit() + { tDashCooldown = DashCooldown; CreateDashtTrail(); } - private void StDashingUpdate() { + private void StDashingUpdate() + { Model.Play("Dash"); tDash -= Time.Delta; - if (tDash <= 0) { + if (tDash <= 0) + { if (!onGround) velocity *= DashEndSpeedMult; stateMachine.State = States.Normal; return; } - if (dashTrailsCreated <= 0 || (dashTrailsCreated == 1 && tDash <= DashTime * .5f)) { + if (dashTrailsCreated <= 0 || (dashTrailsCreated == 1 && tDash <= DashTime * .5f)) + { dashTrailsCreated++; CreateDashtTrail(); } - if (Controls.Move.Value != Vec2.Zero && Vec2.Dot(Controls.Move.Value, targetFacing) >= -.2f) { + if (Controls.Move.Value != Vec2.Zero && Vec2.Dot(Controls.Move.Value, targetFacing) >= -.2f) + { targetFacing = Calc.RotateToward(targetFacing, RelativeMoveInput, DashRotateSpeed * Time.Delta, 0); SetDashSpeed(targetFacing); } @@ -1281,17 +1412,20 @@ private void StDashingUpdate() { tNoDashJump -= Time.Delta; // dash jump - if (dashedOnGround && tCoyote > 0 && tNoDashJump <= 0 && Controls.Jump.ConsumePress()) { + if (dashedOnGround && tCoyote > 0 && tNoDashJump <= 0 && Controls.Jump.ConsumePress()) + { stateMachine.State = States.Normal; DashJump(); return; } } - private void CreateDashtTrail() { + private void CreateDashtTrail() + { Trail? trail = null; foreach (var it in trails) - if (it.Percent >= 1) { + if (it.Percent >= 1) + { trail = it; break; } @@ -1306,8 +1440,10 @@ private void CreateDashtTrail() { trail.Color = lastDashHairColor; } - public bool RefillDash(int amount = 1) { - if (dashes < amount) { + public bool RefillDash(int amount = 1) + { + if (dashes < amount) + { dashes = amount; tDashResetFlash = .05f; return true; @@ -1316,7 +1452,8 @@ public bool RefillDash(int amount = 1) { return false; } - private void SetDashSpeed(in Vec2 dir) { + private void SetDashSpeed(in Vec2 dir) + { if (dashedOnGround) velocity = new Vec3(dir, 0) * DashSpeed; else @@ -1329,7 +1466,8 @@ private void SetDashSpeed(in Vec2 dir) { public float tNoSkidJump; // TAS: publicized - private void StSkiddingEnter() { + private void StSkiddingEnter() + { tNoSkidJump = .1f; Model.Play("Skid", true); Audio.Play(Sfx.sfx_skid, Position); @@ -1338,27 +1476,32 @@ private void StSkiddingEnter() { World.Request().Init(Position + new Vec3(targetFacing, 0) * i, new Vec3(-targetFacing, 0.0f).Normalized() * 50, 0x666666); } - private void StSkiddingExit() { + private void StSkiddingExit() + { Model.Play("Idle", true); } - private void StSkiddingUpdate() { + private void StSkiddingUpdate() + { if (tNoSkidJump > 0) tNoSkidJump -= Time.Delta; if (TryDash()) return; - if (RelativeMoveInput.LengthSquared() < .2f * .2f || Vec2.Dot(RelativeMoveInput, targetFacing) < .7f || !onGround) { + if (RelativeMoveInput.LengthSquared() < .2f * .2f || Vec2.Dot(RelativeMoveInput, targetFacing) < .7f || !onGround) + { //cancelling stateMachine.State = States.Normal; return; } - else { + else + { var velXY = velocity.XY(); // skid jump - if (tNoSkidJump <= 0 && Controls.Jump.ConsumePress()) { + if (tNoSkidJump <= 0 && Controls.Jump.ConsumePress()) + { stateMachine.State = States.Normal; SkidJump(); return; @@ -1376,7 +1519,8 @@ private void StSkiddingUpdate() { velocity = velocity.WithXY(velXY); // reached target - if (dotMatches && velXY.LengthSquared() >= EndSkidSpeed * EndSkidSpeed) { + if (dotMatches && velXY.LengthSquared() >= EndSkidSpeed * EndSkidSpeed) + { stateMachine.State = States.Normal; return; } @@ -1397,7 +1541,8 @@ private void StSkiddingUpdate() { private int climbInputSign = 1; public float tClimbCooldown = 0; // TAS: publicized - private void StClimbingEnter() { + private void StClimbingEnter() + { Model.Play("Climb.Idle", true); Model.Rate = 1.8f; velocity = Vec3.Zero; @@ -1406,21 +1551,25 @@ private void StClimbingEnter() { Audio.Play(Sfx.sfx_grab, Position); } - private void StClimbingExit() { + private void StClimbingExit() + { Model.Play("Idle"); Model.Rate = 1.0f; climbingWallActor = default; sfxWallSlide?.Stop(); } - private void StClimbingUpdate() { - if (!Controls.Climb.Down) { + private void StClimbingUpdate() + { + if (!Controls.Climb.Down) + { Audio.Play(Sfx.sfx_let_go, Position); stateMachine.State = States.Normal; return; } - if (Controls.Jump.ConsumePress()) { + if (Controls.Jump.ConsumePress()) + { stateMachine.State = States.Normal; targetFacing = -targetFacing; WallJump(); @@ -1482,6 +1631,7 @@ private void StClimbingUpdate() { var vel = tPlatformVelocityStorage > 0 ? platformVelocity : Vec3.Zero; World.Request().Init(at, vel); } + wallSlideSoundEnabled = true; } @@ -1892,12 +2042,14 @@ private CoEnumerator StStrawbRevealRoutine() yield return 1f; - for (float p = 0; p < 1.0f; p += Time.Delta / 3) { + for (float p = 0; p < 1.0f; p += Time.Delta / 3) + { cameraOverride = new(Utils.Bezier(fromPos, control, toPos, Ease.Sine.In(p)), lookAt); yield return Co.SingleFrame; } - for (float p = 0; p < 1.0f; p += Time.Delta / 1f) { + for (float p = 0; p < 1.0f; p += Time.Delta / 1f) + { GetCameraTarget(out var lookAtTo, out var posTo, out _); var t = Ease.Sine.Out(p); @@ -1929,17 +2081,15 @@ private void StDeadEnter() Audio.Play(Sfx.sfx_death, Position); } - private void StDeadUpdate() { + private void StDeadUpdate() + { if (drawOrbsEase < 1.0f) drawOrbsEase += Time.Delta * 2.0f; - if (!Game.Instance.IsMidTransition && drawOrbsEase > 0.30f) { + if (!Game.Instance.IsMidTransition && drawOrbsEase > 0.30f) + { var entry = World.Entry with { Reason = World.EntryReasons.Respawned }; - Game.Instance.Goto(new Transition() { - Mode = Transition.Modes.Replace, - Scene = () => new World(entry), - ToBlack = new AngledWipe() - }); + Game.Instance.Goto(new Transition() { Mode = Transition.Modes.Replace, Scene = () => new World(entry), ToBlack = new AngledWipe() }); } } @@ -1964,7 +2114,8 @@ private void StCutsceneUpdate() private Vec3 bubbleTo; - public void BubbleTo(Vec3 target) { + public void BubbleTo(Vec3 target) + { bubbleTo = target; Model.Play("StrawberryGrab"); stateMachine.State = States.Bubble; @@ -2030,17 +2181,13 @@ private CoEnumerator StCassetteRoutine() { if (World.Entry.Submap) { - Game.Instance.Goto(new Transition() - { - Mode = Transition.Modes.Pop, - ToPause = true, - ToBlack = new SpotlightWipe(), - StopMusic = true - }); + Game.Instance.Goto(new Transition() { Mode = Transition.Modes.Pop, ToPause = true, ToBlack = new SpotlightWipe(), StopMusic = true }); } //Saves and quits game if you collect a cassette with an empty map property when you're not in a submap - else if (!Assets.Maps.ContainsKey(cassette.Map)) { - Game.Instance.Goto(new Transition() { + else if (!Assets.Maps.ContainsKey(cassette.Map)) + { + Game.Instance.Goto(new Transition() + { Mode = Transition.Modes.Replace, Scene = () => new Overworld(true), ToPause = true, @@ -2050,8 +2197,10 @@ private CoEnumerator StCassetteRoutine() Saving = true }); } - else { - Game.Instance.Goto(new Transition() { + else + { + Game.Instance.Goto(new Transition() + { Mode = Transition.Modes.Push, Scene = () => new World(new(cassette.Map, string.Empty, true, World.EntryReasons.Entered)), ToPause = true, @@ -2073,7 +2222,8 @@ private CoEnumerator StCassetteRoutine() autoJump = true; } - private void StCassetteExit() { + private void StCassetteExit() + { cassette?.SetCooldown(); cassette = null; drawModel = drawHair = true; @@ -2085,36 +2235,45 @@ private void StCassetteExit() { #region Graphics - public void CollectSprites(List populate) { + public void CollectSprites(List populate) + { // debug: draw camera origin pos - if (World.DebugDraw) { + if (World.DebugDraw) + { populate.Add(Sprite.CreateBillboard(World, cameraOriginPos, "circle", 1, Color.Red)); } // debug: draw wall up-normal - if (World.DebugDraw) { - if (stateMachine.State == States.Climbing) { + if (World.DebugDraw) + { + if (stateMachine.State == States.Climbing) + { var up = climbingWallNormal.UpwardPerpendicularNormal(); - for (int i = 0; i < 12; i++) { + for (int i = 0; i < 12; i++) + { populate.Add(Sprite.CreateBillboard(World, SolidWaistTestPos + up * i * 1.5f, "circle", 1, Color.Red)); } } } - if (InBubble) { + if (InBubble) + { populate.Add(Sprite.CreateBillboard(World, Position + Vec3.UnitZ * 8, "bubble", 10, Color.White) with { Post = true }); } - if (InFeatherState) { + if (InFeatherState) + { populate.Add(Sprite.CreateBillboard(World, Position + Forward * 4 + Vec3.UnitZ * 8, "gradient", 12, CFeather * 0.50f)); } - if (drawOrbs && drawOrbsEase > 0) { + if (drawOrbs && drawOrbsEase > 0) + { var ease = drawOrbsEase; var col = Math.Floor(ease * 10) % 2 == 0 ? Hair.Color : Color.White; var s = (ease < 0.5f) ? (0.5f + ease) : (Ease.Cube.Out(1 - (ease - 0.5f) * 2)); - for (int i = 0; i < 8; i++) { + for (int i = 0; i < 8; i++) + { var rot = (i / 8f + ease * 0.25f) * MathF.Tau; var rad = Ease.Cube.Out(ease) * 16; var pos = SolidWaistTestPos + World.Camera.Left * MathF.Cos(rot) * rad + World.Camera.Up * MathF.Sin(rot) * rad; @@ -2125,19 +2284,21 @@ public void CollectSprites(List populate) { } if (!onGround && !Dead && PointShadowAlpha > 0 && !InBubble && Save.Instance.ZGuide) - { - var distance = 1000.0f; - if (World.SolidRayCast(Position, -Vec3.UnitZ, distance, out var hit)) - distance = hit.Distance; + { + var distance = 1000.0f; + if (World.SolidRayCast(Position, -Vec3.UnitZ, distance, out var hit)) + distance = hit.Distance; - for (int i = 3; i < distance; i += 5) - populate.Add(Sprite.CreateBillboard(World, Position - Vec3.UnitZ * i, "circle", 0.5f, Color.Gray * 0.50f)); - } + for (int i = 3; i < distance; i += 5) + populate.Add(Sprite.CreateBillboard(World, Position - Vec3.UnitZ * i, "circle", 0.5f, Color.Gray * 0.50f)); + } } - public void CollectModels(List<(Actor Actor, Model Model)> populate) { + public void CollectModels(List<(Actor Actor, Model Model)> populate) + { if (Save.Instance.InvisiblePlayer) return; - if ((World.Camera.Position - (Position + Vec3.UnitZ * 8)).LengthSquared() > World.Camera.NearPlane * World.Camera.NearPlane) { + if ((World.Camera.Position - (Position + Vec3.UnitZ * 8)).LengthSquared() > World.Camera.NearPlane * World.Camera.NearPlane) + { if (drawHair) populate.Add((this, Hair)); @@ -2145,7 +2306,8 @@ public void CollectModels(List<(Actor Actor, Model Model)> populate) { populate.Add((this, Model)); } - foreach (var trail in trails) { + foreach (var trail in trails) + { if (trail.Percent >= 1) continue; @@ -2164,7 +2326,8 @@ public void CollectModels(List<(Actor Actor, Model Model)> populate) { } } - public void RenderCollider(Batcher3D batch) { + public void RenderCollider(Batcher3D batch) + { var transform = Matrix.CreateTranslation(-Position) * Matrix.CreateRotationZ(targetFacing.Angle()) * Matrix.CreateTranslation(Position); // Ground ray cast @@ -2180,9 +2343,11 @@ public void RenderCollider(Batcher3D batch) { batch.Cube(SolidWaistTestPos, Color.Red, thickness: 0.2f); // Pickup colliders - foreach (var actor in World.All()) { + foreach (var actor in World.All()) + { batch.Sphere(actor.Position, (actor as IPickup)!.PickupRadius, 12, Color.Green * 0.5f); } + batch.Cube(Position, Color.Green, thickness: 0.2f); } @@ -2190,19 +2355,22 @@ public void RenderCollider(Batcher3D batch) { #region Platform Riding / Solid Checks - public void RidingPlatformSetVelocity(in Vec3 value) { + public void RidingPlatformSetVelocity(in Vec3 value) + { if (value == Vec3.Zero) return; if (tPlatformVelocityStorage < 0 || value.Z >= velocity.Z || value.XY().LengthSquared() + .1f >= velocity.XY().LengthSquared() - || (value.XY() != Vec2.Zero && Vec2.Dot(value.XY().Normalized(), velocity.XY().Normalized()) < .5f)) { + || (value.XY() != Vec2.Zero && Vec2.Dot(value.XY().Normalized(), velocity.XY().Normalized()) < .5f)) + { platformVelocity = value; tPlatformVelocityStorage = .1f; } } - public bool RidingPlatformCheck(Actor platform) { + public bool RidingPlatformCheck(Actor platform) + { // check if we're climbing this thing if (platform == climbingWallActor) return true; @@ -2215,7 +2383,8 @@ public bool RidingPlatformCheck(Actor platform) { return GroundCheck(out _, out _, out var floor) && floor == platform; } - public void RidingPlatformMoved(in Vec3 delta) { + public void RidingPlatformMoved(in Vec3 delta) + { var was = Position; SweepTestMove(delta, false); var newDelta = (Position - was); @@ -2223,12 +2392,14 @@ public void RidingPlatformMoved(in Vec3 delta) { climbCornerTo += newDelta; } - public bool GroundCheck(out Vec3 pushout, out Vec3 normal, out Actor? floor) { + public bool GroundCheck(out Vec3 pushout, out Vec3 normal, out Actor? floor) + { pushout = default; floor = null; normal = Vec3.UnitZ; - if (World.SolidRayCast(Position + Vec3.UnitZ * 5, -Vec3.UnitZ, 5.01f, out var hit)) { + if (World.SolidRayCast(Position + Vec3.UnitZ * 5, -Vec3.UnitZ, 5.01f, out var hit)) + { pushout = hit.Point - Position; floor = hit.Actor; normal = hit.Normal; @@ -2238,12 +2409,14 @@ public bool GroundCheck(out Vec3 pushout, out Vec3 normal, out Actor? floor) { return false; } - public bool CeilingCheck(out Vec3 pushout) { + public bool CeilingCheck(out Vec3 pushout) + { const float Height = 12; pushout = default; - if (World.SolidRayCast(Position + Vec3.UnitZ, Vec3.UnitZ, Height - 1, out var hit)) { + if (World.SolidRayCast(Position + Vec3.UnitZ, Vec3.UnitZ, Height - 1, out var hit)) + { pushout = hit.Point - Vec3.UnitZ * Height - Position; return true; } diff --git a/Source/Data/Save.cs b/Source/Data/Save.cs index cf7ca01..4ec8d53 100644 --- a/Source/Data/Save.cs +++ b/Source/Data/Save.cs @@ -1,4 +1,3 @@ - using Celeste64.TAS; using System.Text.Json; using System.Text.Json.Serialization; @@ -7,101 +6,103 @@ namespace Celeste64; public class Save { - public const string FileName = "save.json"; - - public enum InvertCameraOptions - { - None, - X, - Y, - Both - } - - /// - /// Stored data associated with a single level - /// - public class LevelRecord - { - public string ID { get; set; } = string.Empty; - public string Checkpoint { get; set; } = string.Empty; - public HashSet Strawberries { get; set; } = []; - public HashSet CompletedSubMaps { get; set; } = []; - public Dictionary Flags { get; set; } = []; - public int Deaths { get; set; } = 0; - public TimeSpan Time { get; set; } = new(); - - public int GetFlag(string name, int defaultValue = 0) - => Flags.TryGetValue(name, out int value) ? value : defaultValue; - - public int SetFlag(string name, int value = 1) - => Flags[name] = value; - - public int IncFlag(string name) - => Flags[name] = GetFlag(name) + 1; - } + public const string FileName = "save.json"; + + public enum InvertCameraOptions + { + None, + X, + Y, + Both + } + + /// + /// Stored data associated with a single level + /// + public class LevelRecord + { + public string ID { get; set; } = string.Empty; + public string Checkpoint { get; set; } = string.Empty; + public HashSet Strawberries { get; set; } = []; + public HashSet CompletedSubMaps { get; set; } = []; + public Dictionary Flags { get; set; } = []; + public int Deaths { get; set; } = 0; + public TimeSpan Time { get; set; } = new(); + + public int GetFlag(string name, int defaultValue = 0) + => Flags.TryGetValue(name, out int value) ? value : defaultValue; + + public int SetFlag(string name, int value = 1) + => Flags[name] = value; + + public int IncFlag(string name) + => Flags[name] = GetFlag(name) + 1; + } public enum FreecamMode { Disabled, Orbit, Free, } - public static Save Instance = new(); - - /// - /// Gets the Record for the current Level. - /// - public static LevelRecord CurrentRecord => Manager.Running ? Manager.TASLevelRecord : Instance.GetOrMakeRecord(Instance.LevelID); - - /// - /// The last level that was entered - /// - public string LevelID { get; set; } = "NONE"; - - /// - /// If Fullscreen should be enabled - /// - public bool Fullscreen { get; set; } = true; - - /// - /// If the Vertical Z Guide should be drawn below the Player - /// - public bool ZGuide { get; set; } = true; - - /// - /// If the Speedrun Timer should be visible while playing - /// - public bool SpeedrunTimer { get; set; } = false; - - /// - /// 0-10 Music volume level - /// - public int MusicVolume { get; set; } = 10; - - /// - /// 0-10 Sfx Volume level - /// - public int SfxVolume { get; set; } = 10; - - /// - /// Invert the camera in given directions - /// - public InvertCameraOptions InvertCamera { get; set; } = InvertCameraOptions.None; - - /// - /// Current Language ID - /// - public string Language = "english"; - - /// - /// Records for each level - /// - public List Records { get; set; } = []; + public static Save Instance = new(); + + /// + /// Gets the Record for the current Level. + /// + public static LevelRecord CurrentRecord => Manager.Running ? Manager.TASLevelRecord : Instance.GetOrMakeRecord(Instance.LevelID); + + /// + /// The last level that was entered + /// + public string LevelID { get; set; } = "NONE"; + + /// + /// If Fullscreen should be enabled + /// + public bool Fullscreen { get; set; } = true; + + /// + /// If the Vertical Z Guide should be drawn below the Player + /// + public bool ZGuide { get; set; } = true; + + /// + /// If the Speedrun Timer should be visible while playing + /// + public bool SpeedrunTimer { get; set; } = false; + + /// + /// 0-10 Music volume level + /// + public int MusicVolume { get; set; } = 10; + + /// + /// 0-10 Sfx Volume level + /// + public int SfxVolume { get; set; } = 10; + + /// + /// Invert the camera in given directions + /// + public InvertCameraOptions InvertCamera { get; set; } = InvertCameraOptions.None; + + /// + /// Current Language ID + /// + public string Language = "english"; + + /// + /// Records for each level + /// + public List Records { get; set; } = []; // TAS Settings public FreecamMode Freecam { get; set; } = FreecamMode.Disabled; public bool SimplifiedGraphics { get; set; } = false; public bool Hitboxes { get; set; } = false; + public bool InvisiblePlayer { get; set; } = false; + // Info HUD public bool InfoHudShowInputs { get; set; } = true; public bool InfoHudShowWorld { get; set; } = true; @@ -110,66 +111,70 @@ public enum FreecamMode public int InfoHudDecimals { get; set; } = 3; public float InfoHudFontSize { get; set; } = 1.0f; - /// - /// Finds the record associated with a specific level, or adds it if not found - /// - public LevelRecord GetOrMakeRecord(string levelID) - { - if (TryGetRecord(levelID) is { } record) - return record; - - record = new LevelRecord() { ID = levelID }; - Records.Add(record); - return record; - } - - /// - /// Tries to get a Level Record, returns null if not found - /// - public LevelRecord? TryGetRecord(string levelID) - { - foreach (var record in Records) - if (record.ID == levelID) - return record; - return null; - } - - /// - /// Erases a Level Record - /// - public void EraseRecord(string levelID) - { - for (int i = 0; i < Records.Count; i ++) - { - if (Records[i].ID == levelID) - { - Records.RemoveAt(i); - break; - } - } - } - - public void ToggleFullscreen() - { - Fullscreen = !Fullscreen; - SyncSettings(); - } + /// + /// Finds the record associated with a specific level, or adds it if not found + /// + public LevelRecord GetOrMakeRecord(string levelID) + { + if (TryGetRecord(levelID) is { } record) + return record; + + record = new LevelRecord() { ID = levelID }; + Records.Add(record); + return record; + } + + /// + /// Tries to get a Level Record, returns null if not found + /// + public LevelRecord? TryGetRecord(string levelID) + { + foreach (var record in Records) + if (record.ID == levelID) + return record; + return null; + } + + /// + /// Erases a Level Record + /// + public void EraseRecord(string levelID) + { + for (int i = 0; i < Records.Count; i++) + { + if (Records[i].ID == levelID) + { + Records.RemoveAt(i); + break; + } + } + } + + public void ToggleFullscreen() + { + Fullscreen = !Fullscreen; + SyncSettings(); + } public void ToggleSimplifiedGraphics() { SimplifiedGraphics = !SimplifiedGraphics; Save.Instance.SyncSettings(); - if (Game.Scene is World world) { + if (Game.Scene is World world) + { world.Camera.FarPlane = Save.Instance.SimplifiedGraphics ? 8000 : 800; - foreach (var actor in world.Actors) { + foreach (var actor in world.Actors) + { var fields = actor.GetType().GetFields() .Where(f => f.FieldType.IsAssignableTo(typeof(Model))); - foreach (var field in fields) { + foreach (var field in fields) + { var model = (Model)field.GetValue(actor)!; - foreach (var material in model.Materials) { + foreach (var material in model.Materials) + { if (material.Shader == null || !Assets.Shaders.ContainsKey(material.Shader.Name)) continue; material.Simplified = Save.Instance.SimplifiedGraphics; } @@ -178,79 +183,81 @@ public void ToggleSimplifiedGraphics() } } - public void ToggleZGuide() - { - ZGuide = !ZGuide; - } - - public void SetCameraInverted(InvertCameraOptions value) - { - InvertCamera = value; - } - - public void ToggleTimer() - { - SpeedrunTimer = !SpeedrunTimer; - } - - public void SetMusicVolume(int value) - { - MusicVolume = Calc.Clamp(value, 0, 10); - SyncSettings(); - } - - public void SetSfxVolume(int value) - { - SfxVolume = Calc.Clamp(value, 0, 10); - SyncSettings(); - } - - public void SyncSettings() - { - App.Fullscreen = Fullscreen; - Audio.SetVCAVolume("vca:/music", Calc.Clamp(MusicVolume / 10.0f, 0, 1)); - Audio.SetVCAVolume("vca:/sfx", Calc.Clamp(SfxVolume / 10.0f, 0, 1)); - } - - public void SaveToFile() - { - var savePath = Path.Join(App.UserPath, FileName); - var tempPath = Path.Join(App.UserPath, FileName + ".backup"); - - // first save to a temporary file - { - using var stream = File.Create(tempPath); - Serialize(stream, this); - stream.Flush(); - } - - // validate that the temp path worked, and overwride existing if it did. - if (File.Exists(tempPath) && - Deserialize(File.ReadAllText(tempPath)) != null) - { - File.Copy(tempPath, savePath, true); - } - } - - public static void Serialize(Stream stream, Save instance) - { - JsonSerializer.Serialize(stream, instance, SaveContext.Default.Save); - } - - public static Save? Deserialize(string data) - { - try - { - return JsonSerializer.Deserialize(data, SaveContext.Default.Save); - } - catch (Exception e) - { - Log.Error(e.ToString()); - return null; - } - } + public void ToggleZGuide() + { + ZGuide = !ZGuide; + } + + public void SetCameraInverted(InvertCameraOptions value) + { + InvertCamera = value; + } + + public void ToggleTimer() + { + SpeedrunTimer = !SpeedrunTimer; + } + + public void SetMusicVolume(int value) + { + MusicVolume = Calc.Clamp(value, 0, 10); + SyncSettings(); + } + + public void SetSfxVolume(int value) + { + SfxVolume = Calc.Clamp(value, 0, 10); + SyncSettings(); + } + + public void SyncSettings() + { + App.Fullscreen = Fullscreen; + Audio.SetVCAVolume("vca:/music", Calc.Clamp(MusicVolume / 10.0f, 0, 1)); + Audio.SetVCAVolume("vca:/sfx", Calc.Clamp(SfxVolume / 10.0f, 0, 1)); + } + + public void SaveToFile() + { + var savePath = Path.Join(App.UserPath, FileName); + var tempPath = Path.Join(App.UserPath, FileName + ".backup"); + + // first save to a temporary file + { + using var stream = File.Create(tempPath); + Serialize(stream, this); + stream.Flush(); + } + + // validate that the temp path worked, and overwride existing if it did. + if (File.Exists(tempPath) && + Deserialize(File.ReadAllText(tempPath)) != null) + { + File.Copy(tempPath, savePath, true); + } + } + + public static void Serialize(Stream stream, Save instance) + { + JsonSerializer.Serialize(stream, instance, SaveContext.Default.Save); + } + + public static Save? Deserialize(string data) + { + try + { + return JsonSerializer.Deserialize(data, SaveContext.Default.Save); + } + catch (Exception e) + { + Log.Error(e.ToString()); + return null; + } + } } [JsonSourceGenerationOptions(WriteIndented = true, AllowTrailingCommas = true, UseStringEnumConverter = true)] [JsonSerializable(typeof(Save))] -internal partial class SaveContext : JsonSerializerContext {} +internal partial class SaveContext : JsonSerializerContext +{ +} diff --git a/Source/Helpers/Menu.cs b/Source/Helpers/Menu.cs index c9eaaba..245e01f 100644 --- a/Source/Helpers/Menu.cs +++ b/Source/Helpers/Menu.cs @@ -1,47 +1,53 @@ - namespace Celeste64; -public class Menu { +public class Menu +{ public static float Spacing => 4 * Game.RelativeScale; public static float SpacerHeight => 12 * Game.RelativeScale; public const float TitleScale = 0.75f; - public abstract class Item { + public abstract class Item + { public virtual string Label { get; } = string.Empty; public virtual bool Selectable { get; } = true; public virtual bool Pressed() => false; public virtual void Slide(int dir) { } } - public class Submenu(string label, Menu? rootMenu, Menu? submenu = null) : Item - { - private readonly string label = label; - public override string Label => label; - public override bool Pressed() - { - if (submenu != null) - { - Audio.Play(Sfx.ui_select); - submenu.Index = 0; - rootMenu?.PushSubMenu(submenu); - return true; - } - return false; - } - } - - public class Spacer : Item { + public class Submenu(string label, Menu? rootMenu, Menu? submenu = null) : Item + { + private readonly string label = label; + public override string Label => label; + + public override bool Pressed() + { + if (submenu != null) + { + Audio.Play(Sfx.ui_select); + submenu.Index = 0; + rootMenu?.PushSubMenu(submenu); + return true; + } + + return false; + } + } + + public class Spacer : Item + { public override bool Selectable => false; } - public class Slider : Item { + public class Slider : Item + { private readonly List labels = []; private readonly int min; private readonly int max; private readonly Func get; private readonly Action set; - public Slider(string label, int min, int max, Func get, Action set) { + public Slider(string label, int min, int max, Func get, Action set) + { for (int i = 0, n = (max - min); i <= n; i++) labels.Add($"{label} [{new string('|', i)}{new string('.', n - i)}]"); this.min = min; @@ -54,26 +60,34 @@ public Slider(string label, int min, int max, Func get, Action set) { public override void Slide(int dir) => set(Calc.Clamp(get() + dir, min, max)); } - public class Option(string label, Action? action = null) : Item { + public class Option(string label, Action? action = null) : Item + { private readonly string label = label; private readonly Action? action = action; public override string Label => label; - public override bool Pressed() { - if (action != null) { + + public override bool Pressed() + { + if (action != null) + { Audio.Play(Sfx.ui_select); action(); return true; } + return false; } } - public class Toggle(string label, Action action, Func get) : Item { + public class Toggle(string label, Action action, Func get) : Item + { private readonly string labelOff = $"{label} : OFF"; private readonly string labelOn = $"{label} : ON"; private readonly Action action = action; public override string Label => get() ? labelOn : labelOff; - public override bool Pressed() { + + public override bool Pressed() + { action(); if (get()) Audio.Play(Sfx.main_menu_toggle_on); @@ -83,48 +97,47 @@ public override bool Pressed() { } } - public class MultiSelect(string label, List options, Func get, Action set) : Item - { - private readonly List options = options; - private readonly Action set = set; - public override string Label => $"{label} : {options[get()]}"; - - public override void Slide(int dir) - { - Audio.Play(Sfx.ui_select); - - int index = get(); - if (index < options.Count() - 1 && dir == 1) - index++; - if (index > 0 && dir == -1) - index--; - set(index); - } - } - - public class MultiSelect : MultiSelect where T : struct, Enum - { - private static List GetEnumOptions() - { - var list = new List(); - foreach (var it in Enum.GetNames()) - list.Add(it); - return list; - } - - public MultiSelect(string label, Action set, Func get) - : base(label, GetEnumOptions(), () => (int)(object)get(), (i) => set((T)(object)i)) - { - - } - } - - public int Index; - public string Title = string.Empty; - public bool Focused = true; - - private readonly List items = []; - private readonly Stack submenus = []; + public class MultiSelect(string label, List options, Func get, Action set) : Item + { + private readonly List options = options; + private readonly Action set = set; + public override string Label => $"{label} : {options[get()]}"; + + public override void Slide(int dir) + { + Audio.Play(Sfx.ui_select); + + int index = get(); + if (index < options.Count() - 1 && dir == 1) + index++; + if (index > 0 && dir == -1) + index--; + set(index); + } + } + + public class MultiSelect : MultiSelect where T : struct, Enum + { + private static List GetEnumOptions() + { + var list = new List(); + foreach (var it in Enum.GetNames()) + list.Add(it); + return list; + } + + public MultiSelect(string label, Action set, Func get) + : base(label, GetEnumOptions(), () => (int)(object)get(), (i) => set((T)(object)i)) + { + } + } + + public int Index; + public string Title = string.Empty; + public bool Focused = true; + + private readonly List items = []; + private readonly Stack submenus = []; public string UpSound = Sfx.ui_move; public string DownSound = Sfx.ui_move; @@ -132,27 +145,32 @@ public MultiSelect(string label, Action set, Func get) public bool IsInMainMenu => submenus.Count <= 0; private Menu CurrentMenu => submenus.Count > 0 ? submenus.Peek() : this; - public Vec2 Size - { - get - { - var size = Vec2.Zero; - var font = Language.Current.SpriteFont; + public Vec2 Size + { + get + { + var size = Vec2.Zero; + var font = Language.Current.SpriteFont; - if (!string.IsNullOrEmpty(Title)) { + if (!string.IsNullOrEmpty(Title)) + { size.X = font.WidthOf(Title) * TitleScale; size.Y += font.LineHeight * TitleScale; size.Y += SpacerHeight + Spacing; } - foreach (var item in items) { - if (string.IsNullOrEmpty(item.Label)) { + foreach (var item in items) + { + if (string.IsNullOrEmpty(item.Label)) + { size.Y += SpacerHeight; } - else { + else + { size.X = MathF.Max(size.X, font.WidthOf(item.Label)); size.Y += font.LineHeight; } + size.Y += Spacing; } @@ -163,22 +181,26 @@ public Vec2 Size } } - public Menu Add(Item item) - { - items.Add(item); - return this; - } + public Menu Add(Item item) + { + items.Add(item); + return this; + } - protected void PushSubMenu(Menu menu) { + protected void PushSubMenu(Menu menu) + { submenus.Push(menu); } - public void CloseSubMenus() { + public void CloseSubMenus() + { submenus.Clear(); } - private void HandleInput() { - if (items.Count > 0) { + private void HandleInput() + { + if (items.Count > 0) + { var was = Index; var step = 0; @@ -205,25 +227,29 @@ private void HandleInput() { } } - public void Update() { - if (Focused) { + public void Update() + { + if (Focused) + { CurrentMenu.HandleInput(); - if (Controls.Cancel.Pressed && !IsInMainMenu) { + if (Controls.Cancel.Pressed && !IsInMainMenu) + { Audio.Play(Sfx.main_menu_toggle_off); submenus.Pop(); } } } - private void RenderItems(Batcher batch) - { - var font = Language.Current.SpriteFont; - var size = Size; - var position = Vec2.Zero; - batch.PushMatrix(-size / 2); + private void RenderItems(Batcher batch) + { + var font = Language.Current.SpriteFont; + var size = Size; + var position = Vec2.Zero; + batch.PushMatrix(-size / 2); - if (!string.IsNullOrEmpty(Title)) { + if (!string.IsNullOrEmpty(Title)) + { var at = position + new Vec2(size.X / 2, 0); var text = Title; var justify = new Vec2(0.5f, 0); @@ -239,8 +265,10 @@ private void RenderItems(Batcher batch) position.Y += SpacerHeight + Spacing; } - for (int i = 0; i < items.Count; i++) { - if (string.IsNullOrEmpty(items[i].Label)) { + for (int i = 0; i < items.Count; i++) + { + if (string.IsNullOrEmpty(items[i].Label)) + { position.Y += SpacerHeight; continue; } @@ -255,10 +283,12 @@ private void RenderItems(Batcher batch) position.Y += font.LineHeight; position.Y += Spacing; } + batch.PopMatrix(); } - public void Render(Batcher batch, Vec2 position) { + public void Render(Batcher batch, Vec2 position) + { batch.PushMatrix(position); CurrentMenu.RenderItems(batch); batch.PopMatrix(); diff --git a/Source/Scenes/World.cs b/Source/Scenes/World.cs index 61b604b..1892ccc 100644 --- a/Source/Scenes/World.cs +++ b/Source/Scenes/World.cs @@ -6,8 +6,10 @@ namespace Celeste64; -public class World : Scene { +public class World : Scene +{ public enum EntryReasons { Entered, Returned, Respawned } + public readonly record struct EntryInfo(string Map, string CheckPoint, bool Submap, EntryReasons Reason); public Camera Camera = new(); @@ -50,8 +52,10 @@ public enum EntryReasons { Entered, Returned, Respawned } private bool IsInEndingArea => Get() is { } player && Overlaps(player.Position); - private bool IsPauseEnabled { - get { + private bool IsPauseEnabled + { + get + { if (Game.Instance.IsMidTransition) return false; if (Get() is not Player player) return true; return player.IsAbleToPause; @@ -65,10 +69,12 @@ private bool IsPauseEnabled { private int debugUpdateCount; public static bool DebugDraw { get; private set; } = false; - public World(EntryInfo entry) { + public World(EntryInfo entry) + { Entry = entry; - if (tassettingsmenu == null) { + if (tassettingsmenu == null) + { CustomSubMenu.pauseMenu = pauseMenu; tassettingsmenu = new TASSettingsMenu(); } @@ -90,18 +96,21 @@ public World(EntryInfo entry) { pauseMenu.Title = Loc.Str("PauseTitle"); pauseMenu.Add(new Menu.Option(Loc.Str("PauseResume"), () => SetPaused(false))); - pauseMenu.Add(new Menu.Option(Loc.Str("PauseRetry"), () => { + pauseMenu.Add(new Menu.Option(Loc.Str("PauseRetry"), () => + { SetPaused(false); Audio.StopBus(Sfx.bus_dialog, false); Get()?.Kill(); })); pauseMenu.Add(new Menu.Submenu(Loc.Str("PauseOptions"), pauseMenu, optionsMenu)); - - foreach (CustomSubMenu menu in CustomSubMenu.TopLevelMenus) { + + foreach (CustomSubMenu menu in CustomSubMenu.TopLevelMenus) + { pauseMenu.Add(new Menu.Submenu(menu.Name, pauseMenu, menu)); } - pauseMenu.Add(new Menu.Option(Loc.Str("PauseSaveQuit"), () => Game.Instance.Goto(new Transition() { + pauseMenu.Add(new Menu.Option(Loc.Str("PauseSaveQuit"), () => Game.Instance.Goto(new Transition() + { Mode = Transition.Modes.Replace, Scene = () => new Overworld(true), FromPause = true, @@ -109,8 +118,6 @@ public World(EntryInfo entry) { ToBlack = new SlideWipe(), Saving = true }))); - - } // environment @@ -118,13 +125,16 @@ public World(EntryInfo entry) { if (map.SnowAmount > 0) Add(new Snow(map.SnowAmount, map.SnowWind)); - if (!string.IsNullOrEmpty(map.Skybox)) { + if (!string.IsNullOrEmpty(map.Skybox)) + { // single skybox - if (Assets.Textures.TryGetValue($"skyboxes/{map.Skybox}", out var skybox)) { + if (Assets.Textures.TryGetValue($"skyboxes/{map.Skybox}", out var skybox)) + { skyboxes.Add(new(skybox)); } // group - else { + else + { while (Assets.Textures.TryGetValue($"skyboxes/{map.Skybox}_{skyboxes.Count}", out var nextSkybox)) skyboxes.Add(new(nextSkybox)); } @@ -138,13 +148,14 @@ public World(EntryInfo entry) { map.Load(this); Log.Info($"Loaded Map '{Entry.Map}' in {stopwatch.ElapsedMilliseconds}ms"); - } - public override void Disposed() { + public override void Disposed() + { SetPaused(false); - while (Actors.Count > 0) { + while (Actors.Count > 0) + { foreach (var it in Actors) Destroy(it); ResolveChanges(); @@ -154,16 +165,20 @@ public override void Disposed() { postTarget = null; } - public T Request() where T : Actor, IRecycle, new() { - if (recycled.TryGetValue(typeof(T), out var list) && list.Count > 0) { + public T Request() where T : Actor, IRecycle, new() + { + if (recycled.TryGetValue(typeof(T), out var list) && list.Count > 0) + { return Add((list.Dequeue() as T)!); } - else { + else + { return Add(new T()); } } - public T Add(T instance) where T : Actor { + public T Add(T instance) where T : Actor + { adding.Add(instance); instance.Destroying = false; instance.SetWorld(this); @@ -171,14 +186,16 @@ public T Add(T instance) where T : Actor { return instance; } - public T? Get() where T : class { + public T? Get() where T : class + { var list = GetTypesOf(); if (list.Count > 0) return (list[0] as T)!; return null; } - public T? Get(Func predicate) where T : class { + public T? Get(Func predicate) where T : class + { var list = GetTypesOf(); foreach (var it in list) if (predicate((it as T)!)) @@ -186,19 +203,23 @@ public T Add(T instance) where T : Actor { return null; } - public List All() { + public List All() + { return GetTypesOf(); } - public void Destroy(Actor actor) { + public void Destroy(Actor actor) + { Debug.Assert(actor.World == this); actor.Destroying = true; destroying.Add(actor); } - private List GetTypesOf() { + private List GetTypesOf() + { var type = typeof(T); - if (!tracked.TryGetValue(type, out var list)) { + if (!tracked.TryGetValue(type, out var list)) + { tracked[type] = list = new(); foreach (var actor in Actors) if (actor is T) @@ -209,12 +230,15 @@ private List GetTypesOf() { return list; } - private void ResolveChanges() { + private void ResolveChanges() + { // resolve adding/removing actors - while (adding.Count > 0 || destroying.Count > 0) { + while (adding.Count > 0 || destroying.Count > 0) + { // first add group to world int addCount = adding.Count; - for (int i = 0; i < addCount; i++) { + for (int i = 0; i < addCount; i++) + { // sort into buckets var type = adding[i].GetType(); @@ -232,7 +256,8 @@ private void ResolveChanges() { adding[i].Added(); adding.RemoveRange(0, addCount); - for (int i = 0; i < destroying.Count; i++) { + for (int i = 0; i < destroying.Count; i++) + { var it = destroying[i]; it.Destroyed(); @@ -247,7 +272,8 @@ private void ResolveChanges() { it.SetWorld(null); // recycled type - if (it is IRecycle) { + if (it is IRecycle) + { if (!recycled.TryGetValue(type, out var list)) recycled[type] = list = new(); list.Enqueue(it); @@ -258,26 +284,31 @@ private void ResolveChanges() { } } - public override void Update() { + public override void Update() + { debugUpdTimer.Restart(); // Reload shaders - if (Input.Keyboard.Pressed(Keys.F5)) { + if (Input.Keyboard.Pressed(Keys.F5)) + { Assets.ReloadShaders(); // Can't use material.SetShader, since that causes issues var m_set_Shader = typeof(Material).GetProperty("Shader")?.GetSetMethod(true) - ?? throw new Exception("Material is missing Shader property"); + ?? throw new Exception("Material is missing Shader property"); int x = 0, a = 0; - foreach (var actor in Actors) { + foreach (var actor in Actors) + { var fields = actor.GetType().GetFields() .Where(f => f.FieldType.IsAssignableTo(typeof(Model))); a++; - foreach (var field in fields) { + foreach (var field in fields) + { var model = (Model)field.GetValue(actor)!; - foreach (var material in model.Materials) { + foreach (var material in model.Materials) + { if (material.Shader == null || !Assets.Shaders.ContainsKey(material.Shader.Name)) continue; m_set_Shader.Invoke(material, [Assets.Shaders[material.Shader.Name]]); x++; @@ -295,18 +326,21 @@ public override void Update() { Audio.SetListener(Camera); // increment playtime (if not in the ending area) - if (!IsInEndingArea) { + if (!IsInEndingArea) + { Save.CurrentRecord.Time += TimeSpan.FromSeconds(Time.Delta); Game.Instance.Music.Set("at_baddy", 0); } - else { + else + { Game.Instance.Music.Set("at_baddy", 1); } // handle strawb counter { // wiggle when gained - if (strawbCounterWas != Save.CurrentRecord.Strawberries.Count) { + if (strawbCounterWas != Save.CurrentRecord.Strawberries.Count) + { strawbCounterCooldown = 4.0f; strawbCounterWiggle = 1.0f; strawbCounterWas = Save.CurrentRecord.Strawberries.Count; @@ -332,15 +366,18 @@ public override void Update() { DebugDraw = !DebugDraw; // normal game loop - if (!Paused) { + if (!Paused) + { // start pause menu - if (Controls.Pause.Pressed && IsPauseEnabled) { + if (Controls.Pause.Pressed && IsPauseEnabled) + { SetPaused(true); return; } // ONLY update the player when dead - if (Get() is Player player && player.Dead) { + if (Get() is Player player && player.Dead) + { player.Update(); player.LateUpdate(); ResolveChanges(); @@ -348,7 +385,8 @@ public override void Update() { } // ONLY update single cutscene object - if (Get((it) => it.FreezeGame) is Cutscene cs) { + if (Get((it) => it.FreezeGame) is Cutscene cs) + { cs.Update(); cs.LateUpdate(); ResolveChanges(); @@ -356,7 +394,8 @@ public override void Update() { } // pause from hitstun - if (HitStun > 0) { + if (HitStun > 0) + { HitStun -= Time.Delta; return; } @@ -370,7 +409,8 @@ public override void Update() { var view = Camera.Frustum.GetBoundingBox().Inflate(10); debugUpdateCount = 0; foreach (var actor in Actors) - if (actor.UpdateOffScreen || actor.WorldBounds.Intersects(view)) { + if (actor.UpdateOffScreen || actor.WorldBounds.Intersects(view)) + { debugUpdateCount++; actor.Update(); } @@ -380,8 +420,10 @@ public override void Update() { actor.LateUpdate(); } // unpause - else { - if ((Controls.Pause.Pressed || Controls.Cancel.Pressed) && pauseMenu.IsInMainMenu) { + else + { + if ((Controls.Pause.Pressed || Controls.Cancel.Pressed) && pauseMenu.IsInMainMenu) + { SetPaused(false); Audio.Play(Sfx.ui_unpause); pauseMenu.CloseSubMenus(); @@ -393,16 +435,20 @@ public override void Update() { debugUpdTimer.Stop(); } - public void SetPaused(bool paused) { - if (paused != Paused) { + public void SetPaused(bool paused) + { + if (paused != Paused) + { Audio.SetBusPaused(Sfx.bus_gameplay, paused); Audio.SetBusPaused(Sfx.bus_bside_music, paused); - if (paused) { + if (paused) + { Audio.Play(Sfx.ui_pause); pauseSnapshot = Audio.Play(Sfx.snapshot_pause); } - else { + else + { pauseMenu.Index = 0; pauseSnapshot.Stop(); } @@ -412,7 +458,8 @@ public void SetPaused(bool paused) { } } - public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out RayHit hit, bool ignoreBackfaces = true, bool ignoreTransparent = false) { + public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out RayHit hit, bool ignoreBackfaces = true, bool ignoreTransparent = false) + { hit = default; float? closest = null; @@ -423,7 +470,8 @@ public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out R var solids = Pool.Get>(); SolidGrid.Query(solids, new Rect(box.Min.XY(), box.Max.XY())); - foreach (var solid in solids) { + foreach (var solid in solids) + { if (!solid.Collidable || solid.Destroying) continue; @@ -436,7 +484,8 @@ public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out R var verts = solid.WorldVertices; var faces = solid.WorldFaces; - foreach (var face in faces) { + foreach (var face in faces) + { // only do planes that are facing against us if (ignoreBackfaces && Vec3.Dot(face.Plane.Normal, direction) >= 0) continue; @@ -446,11 +495,13 @@ public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out R continue; // check against each triangle in the face - for (int i = 0; i < face.Indices.Count - 2; i++) { + for (int i = 0; i < face.Indices.Count - 2; i++) + { if (Utils.RayIntersectsTriangle(point, direction, verts[face.Indices[0]], verts[face.Indices[i + 1]], - verts[face.Indices[i + 2]], out float dist)) { + verts[face.Indices[i + 2]], out float dist)) + { // too far away if (dist > distance) continue; @@ -477,7 +528,8 @@ public bool SolidRayCast(in Vec3 point, in Vec3 direction, float distance, out R return closest.HasValue; } - public StackList8 SolidWallCheck(in Vec3 point, float radius) { + public StackList8 SolidWallCheck(in Vec3 point, float radius) + { var radiusSquared = radius * radius; var flatPlane = new Plane(Vec3.UnitZ, point.Z); var flatPoint = point.XY(); @@ -485,7 +537,8 @@ public StackList8 SolidWallCheck(in Vec3 point, float radius) { var solids = Pool.Get>(); SolidGrid.Query(solids, new Rect(point.X - radius, point.Y - radius, radius * 2, radius * 2)); - foreach (var solid in solids) { + foreach (var solid in solids) + { if (!solid.Collidable || solid.Destroying) continue; @@ -495,7 +548,8 @@ public StackList8 SolidWallCheck(in Vec3 point, float radius) { var verts = solid.WorldVertices; var faces = solid.WorldFaces; - foreach (var face in faces) { + foreach (var face in faces) + { // TODO: ignore planes that are less flat than this but still fairly floor-like? // ignore flat planes if (face.Plane.Normal.Z <= -1 || face.Plane.Normal.Z >= 1) @@ -508,10 +562,12 @@ public StackList8 SolidWallCheck(in Vec3 point, float radius) { WallHit? closestTriangleOnPlane = null; - for (int i = 0; i < face.Indices.Count - 2; i++) { + for (int i = 0; i < face.Indices.Count - 2; i++) + { if (Utils.PlaneTriangleIntersection(flatPlane, verts[face.Indices[0]], verts[face.Indices[i + 1]], verts[face.Indices[i + 2]], - out var line0, out var line1)) { + out var line0, out var line1)) + { var next = new Vec3(new Line(line0.XY(), line1.XY()).ClosestPoint(flatPoint), point.Z); var diff = (point - next); if (diff.LengthSquared() > radiusSquared) @@ -526,7 +582,8 @@ public StackList8 SolidWallCheck(in Vec3 point, float radius) { } } - if (closestTriangleOnPlane.HasValue) { + if (closestTriangleOnPlane.HasValue) + { hits.Add(closestTriangleOnPlane.Value); if (hits.Count >= hits.Capacity) goto RESULT; @@ -539,11 +596,14 @@ public StackList8 SolidWallCheck(in Vec3 point, float radius) { return hits; } - public bool SolidWallCheckNearest(in Vec3 point, float radius, out WallHit hit) { + public bool SolidWallCheckNearest(in Vec3 point, float radius, out WallHit hit) + { var hits = SolidWallCheck(point, radius); - if (hits.Count > 0) { + if (hits.Count > 0) + { var closest = hits[0]; - for (int i = 1; i < hits.Count; i++) { + for (int i = 1; i < hits.Count; i++) + { if (hits[i].Pushout.LengthSquared() > closest.Pushout.LengthSquared()) // note reversed because we want the most pushout closest = hits[i]; } @@ -551,17 +611,21 @@ public bool SolidWallCheckNearest(in Vec3 point, float radius, out WallHit hit) hit = closest; return true; } - else { + else + { hit = default; return false; } } - public bool SolidWallCheckClosestToNormal(in Vec3 point, float radius, Vec3 normal, out WallHit hit) { + public bool SolidWallCheckClosestToNormal(in Vec3 point, float radius, Vec3 normal, out WallHit hit) + { var hits = SolidWallCheck(point, radius); - if (hits.Count > 0) { + if (hits.Count > 0) + { hit = hits[0]; - for (int i = 1; i < hits.Count; i++) { + for (int i = 1; i < hits.Count; i++) + { var d0 = Vec3.Dot(hit.Normal, normal); var d1 = Vec3.Dot(hits[i].Normal, normal); if (d1 > d0) @@ -570,24 +634,30 @@ public bool SolidWallCheckClosestToNormal(in Vec3 point, float radius, Vec3 norm return true; } - else { + else + { hit = default; return false; } } - public bool Overlaps(Vec3 point, Func? predicate = null) where T : Actor { + public bool Overlaps(Vec3 point, Func? predicate = null) where T : Actor + { return OverlapsFirst(point, predicate) != null; } - public T? OverlapsFirst(Vec3 point, Func? predicate = null) where T : Actor { - if (typeof(T).IsAssignableTo(typeof(Solid))) { + public T? OverlapsFirst(Vec3 point, Func? predicate = null) where T : Actor + { + if (typeof(T).IsAssignableTo(typeof(Solid))) + { var solids = Pool.Get>(); SolidGrid.Query(solids, new Rect(point.X - 1, point.Y - 1, 2, 2)); - foreach (var solid in solids) { + foreach (var solid in solids) + { if (solid is T instance) - if (instance.WorldBounds.Contains(point) && (predicate == null || predicate(instance))) { + if (instance.WorldBounds.Contains(point) && (predicate == null || predicate(instance))) + { Pool.Return(solids); return instance; } @@ -595,7 +665,8 @@ public bool Overlaps(Vec3 point, Func? predicate = null) where T : A Pool.Return(solids); } - else { + else + { foreach (var actor in All()) if (actor.WorldBounds.Contains(point) && (predicate == null || predicate((actor as T)!))) return (actor as T)!; @@ -604,7 +675,8 @@ public bool Overlaps(Vec3 point, Func? predicate = null) where T : A return null; } - public override void Render(Target target) { + public override void Render(Target target) + { debugRndTimer.Restart(); Camera.Target = target; target.Clear(0x444c83, 1, 0, ClearMask.All); @@ -627,7 +699,8 @@ public override void Render(Target target) { models.Clear(); // collect point shadows - foreach (var actor in All()) { + foreach (var actor in All()) + { var alpha = (actor as ICastPointShadow)!.PointShadowAlpha; if (alpha > 0 && Camera.Frustum.Contains(actor.WorldBounds.Conflate(actor.WorldBounds - Vec3.UnitZ * 1000))) @@ -635,7 +708,8 @@ public override void Render(Target target) { } // collect models & sprites - foreach (var actor in Actors) { + foreach (var actor in Actors) + { if (!Camera.Frustum.Contains(actor.WorldBounds.Inflate(1))) continue; @@ -656,7 +730,8 @@ public override void Render(Target target) { // draw the skybox first { var shift = new Vec3(Camera.Position.X, Camera.Position.Y, Camera.Position.Z); - for (int i = 0; i < skyboxes.Count; i++) { + for (int i = 0; i < skyboxes.Count; i++) + { skyboxes[i].Render(Camera, Matrix.CreateRotationZ(i * GeneralTimer * 0.01f) * Matrix.CreateScale(1, 1, 0.5f) * @@ -705,7 +780,8 @@ public override void Render(Target target) { } // strawberry collect effect - if (Camera.Target != null && models.Any((it) => it.Model.Flags.Has(ModelFlags.StrawberryGetEffect))) { + if (Camera.Target != null && models.Any((it) => it.Model.Flags.Has(ModelFlags.StrawberryGetEffect))) + { var img = Assets.Subtextures["splash"]; var orig = new Vec2(img.Width, img.Height) / 2; @@ -722,7 +798,8 @@ public override void Render(Target target) { } // render colliders - if (Save.Instance.Hitboxes) { + if (Save.Instance.Hitboxes) + { foreach (var actor in All()) (actor as IHaveRenderCollider).RenderCollider(batch3d); batch3d.Render(ref state); @@ -739,13 +816,15 @@ public override void Render(Target target) { (actor as IHaveUI)!.RenderUI(batch, bounds); // pause menu - if (Paused) { + if (Paused) + { batch.Rect(bounds, Color.Black * 0.70f); pauseMenu.Render(batch, bounds.Center); } // debug - if (DebugDraw) { + if (DebugDraw) + { var updateMs = debugUpdTimer.Elapsed.TotalMilliseconds; var renderMs = lastDebugRndTime.TotalMilliseconds; var frameMs = debugFpsTimer.Elapsed.TotalMilliseconds; @@ -760,12 +839,14 @@ public override void Render(Target target) { // stats { var at = bounds.TopLeft + new Vec2(4, 8); - if (IsInEndingArea || Save.Instance.SpeedrunTimer) { + if (IsInEndingArea || Save.Instance.SpeedrunTimer) + { UI.Timer(batch, Save.CurrentRecord.Time, at, 0.0f, IsInEndingArea ? Color.CornflowerBlue : Color.White); at.Y += UI.IconSize + 4; } - if (strawbCounterEase > 0) { + if (strawbCounterEase > 0) + { var wiggle = 1 + MathF.Sin(strawbCounterWiggle * MathF.Tau * 2) * strawbCounterWiggle * .3f; batch.PushMatrix( @@ -777,7 +858,8 @@ public override void Render(Target target) { } // show version number when paused / in ending area - if (IsInEndingArea || Paused) { + if (IsInEndingArea || Paused) + { UI.Text(batch, Game.VersionString, bounds.BottomLeft + new Vec2(4, -4) * Game.RelativeScale, new Vec2(0, 1), Color.White * 0.25f); } } @@ -802,10 +884,13 @@ public override void Render(Target target) { debugRndTimer.Stop(); } - private void ApplyPostEffects() { + private void ApplyPostEffects() + { // perform post processing effects - if (Camera.Target != null) { - if (postTarget == null || postTarget.Width != Camera.Target.Width || postTarget.Height != Camera.Target.Height) { + if (Camera.Target != null) + { + if (postTarget == null || postTarget.Width != Camera.Target.Width || postTarget.Height != Camera.Target.Height) + { postTarget?.Dispose(); postTarget = new(Camera.Target.Width, Camera.Target.Height); } @@ -834,8 +919,10 @@ private void ApplyPostEffects() { } } - private void RenderModels(ref RenderState state, List models, ModelFlags flags) { - foreach (var it in models) { + private void RenderModels(ref RenderState state, List models, ModelFlags flags) + { + foreach (var it in models) + { if (!it.Model.Flags.Has(flags)) continue; diff --git a/Source/TAS/Manager.cs b/Source/TAS/Manager.cs index 060ed99..4c67374 100644 --- a/Source/TAS/Manager.cs +++ b/Source/TAS/Manager.cs @@ -9,8 +9,10 @@ internal class EnableRunAttribute : Attribute; [AttributeUsage(AttributeTargets.Method)] internal class DisableRunAttribute : Attribute; -public static class Manager { - public enum State { +public static class Manager +{ + public enum State + { Disabled, Running, Paused, FrameAdvance, FastForward, @@ -33,12 +35,14 @@ public enum State { public static Vec2 prevMousePosition; public static Vec2 MouseDelta => Foster.Framework.Input.Mouse.Position - prevMousePosition; - static Manager() { + static Manager() + { AttributeUtils.CollectMethods(); AttributeUtils.CollectMethods(); } - public static void EnableRun() { + public static void EnableRun() + { Log.Info($"Starting TAS: {InputController.TasFilePath}"); CurrState = State.Running; @@ -51,7 +55,8 @@ public static void EnableRun() { TASLevelRecord.ID = string.Empty; } - public static void DisableRun() { + public static void DisableRun() + { Log.Info("Stopping TAS"); CurrState = State.Disabled; @@ -60,22 +65,26 @@ public static void DisableRun() { Controller.Stop(); } - public static void Update() { + public static void Update() + { CurrState = NextState; if (!Running) return; if (Save.Instance.LevelID != TASLevelRecord.ID) TASLevelRecord = new Save.LevelRecord { ID = Save.Instance.LevelID }; - if (!IsPaused()) { + if (!IsPaused()) + { Controller.AdvanceFrame(out bool canPlayback); - if (!canPlayback) { + if (!canPlayback) + { DisableRun(); } } - switch (CurrState) { + switch (CurrState) + { case State.Running: if (TASControls.PauseResume.ConsumePress()) NextState = State.Paused; @@ -101,17 +110,20 @@ public static void Update() { } } - public static void AbortTas(string message) { + public static void AbortTas(string message) + { Log.Error(message); DisableRun(); } - public static bool IsLoading() { + public static bool IsLoading() + { return !(Game.Instance.transitionStep == Game.TransitionStep.FadeIn || Game.Instance.transitionStep == Game.TransitionStep.None); } - public static bool IsPaused() { + public static bool IsPaused() + { if (IsLoading()) return false; return CurrState == State.Paused; }