From 5f0363651425e137dc8cdf8b4362f71eb5cf2278 Mon Sep 17 00:00:00 2001 From: Jake Hatton Date: Sun, 18 Jan 2026 21:42:50 -0500 Subject: [PATCH 1/4] Ported discrete pavise aiming over from my addon --- Content/Guardian/GuardianShieldAnchor.cs | 4 +++- Content/Guardian/OrchidModGuardianShield.cs | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Content/Guardian/GuardianShieldAnchor.cs b/Content/Guardian/GuardianShieldAnchor.cs index 1fd39772..354285e6 100644 --- a/Content/Guardian/GuardianShieldAnchor.cs +++ b/Content/Guardian/GuardianShieldAnchor.cs @@ -245,13 +245,15 @@ public override void AI() { aimedLocation = Main.MouseWorld - owner.Center.Floor(); aimedLocation.Normalize(); + + if (guardianItem.useDiscreteAim) aimedLocation = Vector2.UnitX.RotatedBy(OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); Projectile.velocity = aimedLocation * float.Epsilon; aimedLocation *= (guardianItem.distance + addedDistance) * -1f; Projectile.rotation = aimedLocation.ToRotation(); if (guardianItem.shouldFlip) { - if (aimedLocation.X < 0) + if (aimedLocation.X < 0 || (guardianItem.useDiscreteAim && -Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).X < 0)) { Projectile.spriteDirection = -1; } diff --git a/Content/Guardian/OrchidModGuardianShield.cs b/Content/Guardian/OrchidModGuardianShield.cs index e0ea28ad..c0e52527 100644 --- a/Content/Guardian/OrchidModGuardianShield.cs +++ b/Content/Guardian/OrchidModGuardianShield.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using OrchidMod.Common; @@ -49,6 +50,12 @@ public virtual void BlockStart(Player player, Projectile shield) { } /// Causes the shield's held sprite to flip when facing right. public bool shouldFlip = false; public bool slamAutoReuse = true; + public bool useDiscreteAim = false; + public int discreteAimIncrements; + /// + /// The angle, in pi/2 increments, that the base angle will be rotated by. + /// + public int discreteAimRotation; public sealed override void SetDefaults() { @@ -243,5 +250,17 @@ public override void ModifyTooltips(List tooltips) OverrideColor = new Color(175, 255, 175) }); } + + public static float GetSnappedAngle(OrchidModGuardianShield shield, Player player, float originalAngle) + { + if (!shield.useDiscreteAim) return originalAngle; + if (shield.discreteAimIncrements == 0) return -player.direction * MathHelper.PiOver2 * shield.discreteAimRotation + (player.direction == -1 ? MathHelper.Pi : 0f); + else + { + float angleIncrement = MathHelper.Pi / shield.discreteAimIncrements; + return (float)Math.Round((Vector2.Normalize(Main.MouseWorld - player.MountedCenter.Floor()).ToRotation() + MathHelper.PiOver2 * shield.discreteAimRotation) / angleIncrement) * angleIncrement - MathHelper.PiOver2 * shield.discreteAimRotation; + } + + } } } From 265ef275e231d8b788d72bd351c58d507ae7375a Mon Sep 17 00:00:00 2001 From: Jake Hatton Date: Sun, 18 Jan 2026 21:42:50 -0500 Subject: [PATCH 2/4] Ported discrete pavise aiming over from my addon --- Content/Guardian/GuardianShieldAnchor.cs | 4 +++- Content/Guardian/OrchidModGuardianShield.cs | 26 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/Content/Guardian/GuardianShieldAnchor.cs b/Content/Guardian/GuardianShieldAnchor.cs index 1fd39772..354285e6 100644 --- a/Content/Guardian/GuardianShieldAnchor.cs +++ b/Content/Guardian/GuardianShieldAnchor.cs @@ -245,13 +245,15 @@ public override void AI() { aimedLocation = Main.MouseWorld - owner.Center.Floor(); aimedLocation.Normalize(); + + if (guardianItem.useDiscreteAim) aimedLocation = Vector2.UnitX.RotatedBy(OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); Projectile.velocity = aimedLocation * float.Epsilon; aimedLocation *= (guardianItem.distance + addedDistance) * -1f; Projectile.rotation = aimedLocation.ToRotation(); if (guardianItem.shouldFlip) { - if (aimedLocation.X < 0) + if (aimedLocation.X < 0 || (guardianItem.useDiscreteAim && -Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).X < 0)) { Projectile.spriteDirection = -1; } diff --git a/Content/Guardian/OrchidModGuardianShield.cs b/Content/Guardian/OrchidModGuardianShield.cs index e0ea28ad..687a28a8 100644 --- a/Content/Guardian/OrchidModGuardianShield.cs +++ b/Content/Guardian/OrchidModGuardianShield.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using OrchidMod.Common; @@ -49,6 +50,19 @@ public virtual void BlockStart(Player player, Projectile shield) { } /// Causes the shield's held sprite to flip when facing right. public bool shouldFlip = false; public bool slamAutoReuse = true; + /// + /// If true, the shield will be rotated in discrete steps, instead of a single continuous circle. Defaults to false. + /// + public bool useDiscreteAim = false; + /// + /// The amount of steps per half-rotation that the shield will snap to, if useDiscreteAim is true. + ///
A value of 0 results in only a single possible angle, directly in front of the player. Otherwise, there will be twice as many snap points as this value. + ///
+ public int discreteAimIncrements; + /// + /// The angle, in pi/2 increments, that the base angle will be rotated by. + /// + public int discreteAimRotation; public sealed override void SetDefaults() { @@ -243,5 +257,17 @@ public override void ModifyTooltips(List tooltips) OverrideColor = new Color(175, 255, 175) }); } + + public static float GetSnappedAngle(OrchidModGuardianShield shield, Player player, float originalAngle) + { + if (!shield.useDiscreteAim) return originalAngle; + if (shield.discreteAimIncrements == 0) return -player.direction * MathHelper.PiOver2 * shield.discreteAimRotation + (player.direction == -1 ? MathHelper.Pi : 0f); + else + { + float angleIncrement = MathHelper.Pi / shield.discreteAimIncrements; + return (float)Math.Round((Vector2.Normalize(Main.MouseWorld - player.MountedCenter.Floor()).ToRotation() + MathHelper.PiOver2 * shield.discreteAimRotation) / angleIncrement) * angleIncrement - MathHelper.PiOver2 * shield.discreteAimRotation; + } + + } } } From ac2f5d84862c6812d12fb6e1dc826d8a1c74aa4a Mon Sep 17 00:00:00 2001 From: Jake Hatton Date: Sun, 18 Jan 2026 23:18:48 -0500 Subject: [PATCH 3/4] Added a SlamEnd method for shields similarly to the BlockEnd recently added, and attempted to add a slam rotation lock feature (there is a small glitch where the sprite flips upside down if you slam to the left and then look right while shouldFlip is true, but it's the best I can do right now) --- Content/Guardian/GuardianShieldAnchor.cs | 17 ++++++++++++++--- Content/Guardian/OrchidModGuardianShield.cs | 10 ++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Content/Guardian/GuardianShieldAnchor.cs b/Content/Guardian/GuardianShieldAnchor.cs index 354285e6..8e3fde80 100644 --- a/Content/Guardian/GuardianShieldAnchor.cs +++ b/Content/Guardian/GuardianShieldAnchor.cs @@ -30,7 +30,8 @@ public class GuardianShieldAnchor : OrchidModGuardianAnchor public float networkedRotation => Projectile.ai[2]; // ... - + private bool IsRotationLocked; + private float LockedRotation; public override void SafeSetDefaults() { Projectile.width = 20; @@ -108,6 +109,7 @@ public override void AI() } float addedDistance = 0f; + if (Projectile.ai[1] > 0f) { // Shield bash if (isSlamming == 0) @@ -142,9 +144,12 @@ public override void AI() if (Projectile.ai[1] <= 0f) { + guardianItem.SlamEnd(owner, Projectile); Projectile.ai[1] = 0f; isSlamming = 0; Projectile.friendly = false; + IsRotationLocked = false; + Projectile.netUpdate = true; } } @@ -246,14 +251,14 @@ public override void AI() aimedLocation = Main.MouseWorld - owner.Center.Floor(); aimedLocation.Normalize(); - if (guardianItem.useDiscreteAim) aimedLocation = Vector2.UnitX.RotatedBy(OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); + aimedLocation = Vector2.UnitX.RotatedBy(IsRotationLocked ? LockedRotation : OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); Projectile.velocity = aimedLocation * float.Epsilon; aimedLocation *= (guardianItem.distance + addedDistance) * -1f; Projectile.rotation = aimedLocation.ToRotation(); if (guardianItem.shouldFlip) { - if (aimedLocation.X < 0 || (guardianItem.useDiscreteAim && -Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).X < 0)) + if (aimedLocation.X < 0 || (guardianItem.useDiscreteAim && -Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).X < 0) || (IsRotationLocked && -Vector2.UnitX.RotatedBy(LockedRotation).X < 0) ) { Projectile.spriteDirection = -1; } @@ -286,6 +291,12 @@ public override void AI() if (isSlamming == 1) // Slam() is called here so the projectile has the time to reposition properly before effects such as projectile spawns are called { isSlamming = 2; + if (guardianItem.lockSlamRotation) + { + IsRotationLocked = true; + LockedRotation = OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner, Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).ToRotation()); + Projectile.netUpdate = true; + } guardianItem.Slam(owner, Projectile); guardian.GuardianCounterTime = 0; } diff --git a/Content/Guardian/OrchidModGuardianShield.cs b/Content/Guardian/OrchidModGuardianShield.cs index 687a28a8..beb40eae 100644 --- a/Content/Guardian/OrchidModGuardianShield.cs +++ b/Content/Guardian/OrchidModGuardianShield.cs @@ -29,6 +29,8 @@ public virtual void SlamHitFirst(Player player, Projectile shield, NPC npc) { } public virtual void SlamHit(Player player, Projectile shield, NPC npc) { } /// Called on the first frame of a slam. public virtual void Slam(Player player, Projectile shield) { } + /// Called on the last frame of a slam. + public virtual void SlamEnd(Player player, Projectile shield) { } /// Called when an enemy collides with the shield during a block. Will be called once per frame per enemy colliding with it. public virtual void Push(Player player, Projectile shield, NPC npc) { } /// Called once per block when the first enemy or projectile is blocked. This is called after Push or Block, but before Block destroys the projectile. @@ -63,6 +65,10 @@ public virtual void BlockStart(Player player, Projectile shield) { } /// The angle, in pi/2 increments, that the base angle will be rotated by. /// public int discreteAimRotation; + /// + /// If true, slams performed with the shield will be locked to the rotation they started with, rather than being free to rotate mid-slam. + /// + public bool lockSlamRotation; public sealed override void SetDefaults() { @@ -75,6 +81,10 @@ public sealed override void SetDefaults() Item.useStyle = ItemUseStyleID.Thrust; Item.useTime = 30; Item.knockBack = 6f; + useDiscreteAim = false; + discreteAimIncrements = 2; + discreteAimRotation = 0; + lockSlamRotation = false; OrchidGlobalItemPerEntity orchidItem = Item.GetGlobalItem(); orchidItem.guardianWeapon = true; From 2e6bca8e716f3baa82634e56fed598edd8cf3eaf Mon Sep 17 00:00:00 2001 From: Jake Hatton Date: Sun, 18 Jan 2026 23:18:48 -0500 Subject: [PATCH 4/4] Added a SlamEnd method for shields similarly to the BlockEnd recently added, and attempted to add a slam rotation lock feature (there is a small glitch where the sprite flips upside down if you slam to the left and then look right while shouldFlip is true, but it's the best I can do right now) --- Content/Guardian/GuardianShieldAnchor.cs | 17 ++++++++++++++--- Content/Guardian/OrchidModGuardianShield.cs | 10 ++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Content/Guardian/GuardianShieldAnchor.cs b/Content/Guardian/GuardianShieldAnchor.cs index 354285e6..4732f60d 100644 --- a/Content/Guardian/GuardianShieldAnchor.cs +++ b/Content/Guardian/GuardianShieldAnchor.cs @@ -30,7 +30,8 @@ public class GuardianShieldAnchor : OrchidModGuardianAnchor public float networkedRotation => Projectile.ai[2]; // ... - + public bool IsRotationLocked; + private float LockedRotation; public override void SafeSetDefaults() { Projectile.width = 20; @@ -108,6 +109,7 @@ public override void AI() } float addedDistance = 0f; + if (Projectile.ai[1] > 0f) { // Shield bash if (isSlamming == 0) @@ -142,9 +144,12 @@ public override void AI() if (Projectile.ai[1] <= 0f) { + guardianItem.SlamEnd(owner, Projectile); Projectile.ai[1] = 0f; isSlamming = 0; Projectile.friendly = false; + IsRotationLocked = false; + Projectile.netUpdate = true; } } @@ -246,14 +251,14 @@ public override void AI() aimedLocation = Main.MouseWorld - owner.Center.Floor(); aimedLocation.Normalize(); - if (guardianItem.useDiscreteAim) aimedLocation = Vector2.UnitX.RotatedBy(OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); + aimedLocation = Vector2.UnitX.RotatedBy(IsRotationLocked ? LockedRotation : OrchidModGuardianShield.GetSnappedAngle(guardianItem, owner,aimedLocation.ToRotation())); Projectile.velocity = aimedLocation * float.Epsilon; aimedLocation *= (guardianItem.distance + addedDistance) * -1f; Projectile.rotation = aimedLocation.ToRotation(); if (guardianItem.shouldFlip) { - if (aimedLocation.X < 0 || (guardianItem.useDiscreteAim && -Vector2.Normalize(Main.MouseWorld - owner.Center.Floor()).X < 0)) + if (aimedLocation.X < 0 || (isSlamming is 1 or 2 && IsRotationLocked && -Vector2.UnitX.RotatedBy(LockedRotation).X < 0)) { Projectile.spriteDirection = -1; } @@ -286,6 +291,12 @@ public override void AI() if (isSlamming == 1) // Slam() is called here so the projectile has the time to reposition properly before effects such as projectile spawns are called { isSlamming = 2; + if (guardianItem.lockSlamRotation) + { + IsRotationLocked = true; + LockedRotation = Projectile.rotation + MathHelper.Pi; + Projectile.netUpdate = true; + } guardianItem.Slam(owner, Projectile); guardian.GuardianCounterTime = 0; } diff --git a/Content/Guardian/OrchidModGuardianShield.cs b/Content/Guardian/OrchidModGuardianShield.cs index 687a28a8..beb40eae 100644 --- a/Content/Guardian/OrchidModGuardianShield.cs +++ b/Content/Guardian/OrchidModGuardianShield.cs @@ -29,6 +29,8 @@ public virtual void SlamHitFirst(Player player, Projectile shield, NPC npc) { } public virtual void SlamHit(Player player, Projectile shield, NPC npc) { } /// Called on the first frame of a slam. public virtual void Slam(Player player, Projectile shield) { } + /// Called on the last frame of a slam. + public virtual void SlamEnd(Player player, Projectile shield) { } /// Called when an enemy collides with the shield during a block. Will be called once per frame per enemy colliding with it. public virtual void Push(Player player, Projectile shield, NPC npc) { } /// Called once per block when the first enemy or projectile is blocked. This is called after Push or Block, but before Block destroys the projectile. @@ -63,6 +65,10 @@ public virtual void BlockStart(Player player, Projectile shield) { } /// The angle, in pi/2 increments, that the base angle will be rotated by. /// public int discreteAimRotation; + /// + /// If true, slams performed with the shield will be locked to the rotation they started with, rather than being free to rotate mid-slam. + /// + public bool lockSlamRotation; public sealed override void SetDefaults() { @@ -75,6 +81,10 @@ public sealed override void SetDefaults() Item.useStyle = ItemUseStyleID.Thrust; Item.useTime = 30; Item.knockBack = 6f; + useDiscreteAim = false; + discreteAimIncrements = 2; + discreteAimRotation = 0; + lockSlamRotation = false; OrchidGlobalItemPerEntity orchidItem = Item.GetGlobalItem(); orchidItem.guardianWeapon = true;