diff --git a/CTG2.cs b/CTG2.cs index 06a0f22..647e511 100644 --- a/CTG2.cs +++ b/CTG2.cs @@ -1,31 +1,29 @@ -using System; -using Terraria.ModLoader; -using System.IO; -using System.Text.Json; using ClassesNamespace; -using Terraria; using CTG2.Content; using CTG2.Content.Classes; using CTG2.Content.ClientSide; -using CTG2.Content.ServerSide; using CTG2.Content.Commands; +using CTG2.Content.ServerSide; +using DirectDashMod.Players; +using Humanizer; using Microsoft.Xna.Framework; -using Terraria.ID; -using System.Collections.Generic; -using Terraria.Chat; -using Terraria.Localization; -using Terraria.Audio; -using Terraria.GameContent; using Microsoft.Xna.Framework.Audio; using MonoMod.Cil; -using Humanizer; +using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; -using DirectDashMod.Players; - - +using System.Text.Json; +using Terraria; +using Terraria.Audio; +using Terraria.Chat; +using Terraria.GameContent; using Terraria.GameContent.Bestiary; using Terraria.GameContent.ItemDropRules; using Terraria.Graphics.CameraModifiers; +using Terraria.ID; +using Terraria.Localization; +using Terraria.ModLoader; @@ -122,7 +120,8 @@ public enum MessageType : byte SyncUuidForPlayer = 85, ClearInventory = 86, SyncAbilityAttributes = 87, - SyncClassSystemAttributes = 88 + SyncClassSystemAttributes = 88, + Dash = 89 } public class CTG2 : Mod @@ -136,6 +135,7 @@ public class CTG2 : Mod public static ModKeybind ArcherDashKeybind; public static ModKeybind AdvancedBinocularsKeybind; public static ModKeybind Ability1Keybind; + public static ModKeybind DashKeybind { get; private set; } //public static int BiomeMusicId = 0; // client side // static methods @@ -190,6 +190,7 @@ public override void Load() ArcherDashKeybind = KeybindLoader.RegisterKeybind(this, "ArcherDash", "LeftShift"); AdvancedBinocularsKeybind = KeybindLoader.RegisterKeybind(this, "AdvancedBinoculars", "MouseRight"); Ability1Keybind = KeybindLoader.RegisterKeybind(this, "Ability 1", "R"); + DashKeybind = KeybindLoader.RegisterKeybind(this, "Dash", "F"); } public override void HandlePacket(BinaryReader reader, int whoAmI) @@ -1264,7 +1265,9 @@ public override void HandlePacket(BinaryReader reader, int whoAmI) UuidByWhoAmI[idx] = uuid5; break; } - + case (byte)MessageType.Dash: + DashInputSystem.HandlePacket(reader, whoAmI); + break; default: Logger.WarnFormat("CTG2: Unknown Message type: {0}", msgType); break; diff --git a/Content/Configs/ClientConfig.cs b/Content/Configs/ClientConfig.cs index 3b50949..75791ce 100644 --- a/Content/Configs/ClientConfig.cs +++ b/Content/Configs/ClientConfig.cs @@ -19,7 +19,10 @@ public class CTG2Config : ModConfig [Label("Selected Track: 0=None, 1=Clash, 2=Mystery")] public int SelectedMusicIndex { get; set; } = 0; - + [Label("Enable vanilla double-tap dash")] + [Tooltip("When enabled, the vanilla double-tap dash works as normal. When disabled, only the Dash keybind triggers dashes.")] + [DefaultValue(true)] + public bool IsVanillaDashEnabled; } } diff --git a/Content/DashKeybind/DashInputPlayer.cs b/Content/DashKeybind/DashInputPlayer.cs new file mode 100644 index 0000000..0eea873 --- /dev/null +++ b/Content/DashKeybind/DashInputPlayer.cs @@ -0,0 +1,219 @@ +using Microsoft.Xna.Framework; +using System; +using Terraria; +using Terraria.DataStructures; +using Terraria.GameInput; +using Terraria.Graphics.Shaders; +using Terraria.ID; +using Terraria.Localization; +using Terraria.ModLoader; +using static CTG2.Content.DashInputSystem; + +namespace CTG2.Content; + +public class DashInputPlayer : ModPlayer +{ + public override void ProcessTriggers(TriggersSet triggers) + { + if (CTG2.DashKeybind == null || !CTG2.DashKeybind.JustPressed) + return; + + int inputDirection = NormalizeDirection(GetInputDirection()); + + if (PerformDash(inputDirection, force: false, out byte dashTypeUsed)) + { + if (Main.netMode == NetmodeID.MultiplayerClient) + SendDashRequest(inputDirection, dashTypeUsed); + else if (Main.netMode == NetmodeID.Server) + BroadcastDash(inputDirection, dashTypeUsed); + } + } + + private int GetInputDirection() + { + if (Player.controlRight && !Player.controlLeft) return 1; + if (Player.controlLeft && !Player.controlRight) return -1; + if (Player.velocity.X > 0f) return 1; + if (Player.velocity.X < 0f) return -1; + return Player.direction; + } + + private int NormalizeDirection(int direction) + { + if (direction > 0) return 1; + if (direction < 0) return -1; + return Player.direction == 0 ? 1 : Player.direction; + } + + private bool CanDash() + { + if (Player.dashDelay != 0) return false; + if (Player.mount.Active || Player.dead || Player.frozen || Player.webbed || Player.tongued || Player.stoned) + return false; + return true; + } + + internal bool PerformDash(int direction, bool force, out byte dashTypeUsed, int? overrideDashType = null) + { + dashTypeUsed = 0; + direction = NormalizeDirection(direction); + + if (!force && !CanDash()) + return false; + + int selectedDashType = overrideDashType.HasValue && overrideDashType.Value > 0 + ? overrideDashType.Value + : Player.dashType; + + if (selectedDashType <= 0) + { + if (Main.myPlayer == Player.whoAmI) + { + PopupText.NewText(new AdvancedPopupRequest + { + Color = Color.Crimson, + Text = "No dash item equipped!", + Velocity = new Vector2(0f, -4f), + DurationInFrames = 60 + }, Main.LocalPlayer.Top + new Vector2(0f, -40f)); + } + return false; + } + + selectedDashType = Math.Clamp(selectedDashType, 1, 5); + dashTypeUsed = (byte)selectedDashType; + + Player.dashType = selectedDashType; + Player.dash = selectedDashType; + Player.dashTime = 0; + Player.dashDelay = -1; + Player.direction = direction; + Player.timeSinceLastDashStarted = 0; + + ApplyDashEffects(selectedDashType, direction); + return true; + } + + private void ApplyDashEffects(int dashType, int direction) + { + switch (dashType) + { + case 1: ApplyTabiDash(direction); break; + case 2: + case 4: ApplyShieldDash(direction); break; + case 3: ApplySolarDash(direction); break; + case 5: ApplyCrystalDash(direction); break; + default: ApplyShieldDash(direction); break; + } + } + + private void ApplyTabiDash(int direction) + { + ApplyHorizontalVelocity(direction, 16.9f); + + if (Main.netMode == NetmodeID.Server) return; + + IEntitySource source = Player.GetSource_FromThis(); + for (int i = 0; i < 20; i++) + { + int dust = Dust.NewDust(new Vector2(Player.position.X, Player.position.Y), Player.width, Player.height, DustID.Smoke, 0f, 0f, 100, default, 2f); + Main.dust[dust].position.X += Main.rand.Next(-5, 6); + Main.dust[dust].position.Y += Main.rand.Next(-5, 6); + Main.dust[dust].velocity *= 0.2f; + Main.dust[dust].scale *= 1f + Main.rand.Next(20) * 0.01f; + } + + int gore = Gore.NewGore(source, new Vector2(Player.position.X + Player.width / 2f - 24f, Player.position.Y + Player.height / 2f - 34f), Vector2.Zero, Main.rand.Next(61, 64)); + Main.gore[gore].velocity.X = Main.rand.Next(-50, 51) * 0.01f; + Main.gore[gore].velocity.Y = Main.rand.Next(-50, 51) * 0.01f; + Main.gore[gore].velocity *= 0.4f; + + gore = Gore.NewGore(source, new Vector2(Player.position.X + Player.width / 2f - 24f, Player.position.Y + Player.height / 2f - 14f), Vector2.Zero, Main.rand.Next(61, 64)); + Main.gore[gore].velocity.X = Main.rand.Next(-50, 51) * 0.01f; + Main.gore[gore].velocity.Y = Main.rand.Next(-50, 51) * 0.01f; + Main.gore[gore].velocity *= 0.4f; + } + + private void ApplyShieldDash(int direction) + { + ApplyHorizontalVelocity(direction, 14.5f); + Player.eocDash = 15; + } + + private void ApplySolarDash(int direction) + { + ApplyHorizontalVelocity(direction, 21.9f); + Player.solarDashing = true; + Player.solarDashConsumedFlare = false; + + if (Main.netMode == NetmodeID.Server) return; + + for (int i = 0; i < 20; i++) + { + int dust = Dust.NewDust(new Vector2(Player.position.X, Player.position.Y), Player.width, Player.height, DustID.SolarFlare, 0f, 0f, 100, default, 2f); + Main.dust[dust].position.X += Main.rand.Next(-5, 6); + Main.dust[dust].position.Y += Main.rand.Next(-5, 6); + Main.dust[dust].velocity *= 0.2f; + Main.dust[dust].scale *= 1f + Main.rand.Next(20) * 0.01f; + Main.dust[dust].shader = GameShaders.Armor.GetSecondaryShader(Player.ArmorSetDye(), Player); + Main.dust[dust].noGravity = true; + Main.dust[dust].fadeIn = 0.5f; + } + } + + private void ApplyCrystalDash(int direction) + { + ApplyHorizontalVelocity(direction, 16.9f); + + if (Main.netMode == NetmodeID.Server) return; + + for (int i = 0; i < 20; i++) + { + short dustType = Main.rand.NextFromList((short)68, (short)69, (short)70); + int dust = Dust.NewDust(new Vector2(Player.position.X, Player.position.Y), Player.width, Player.height, dustType, 0f, 0f, 100, default, 1.5f); + Main.dust[dust].position.X += Main.rand.Next(-5, 6); + Main.dust[dust].position.Y += Main.rand.Next(-5, 6); + Main.dust[dust].velocity = Player.DirectionTo(Main.dust[dust].position) * 2f; + Main.dust[dust].scale *= 1f + Main.rand.Next(20) * 0.01f; + Main.dust[dust].fadeIn = 0.5f + Main.rand.Next(20) * 0.01f; + Main.dust[dust].noGravity = true; + Main.dust[dust].shader = GameShaders.Armor.GetSecondaryShader(Player.ArmorSetDye(), Player); + } + } + + private void ApplyHorizontalVelocity(int direction, float speed) + { + Player.velocity.X = speed * direction; + + Point tileAheadUpper = (Player.Center + new Vector2(direction * Player.width / 2f + 2f, Player.gravDir * (-Player.height) / 2f + Player.gravDir * 2f)).ToTileCoordinates(); + Point tileAheadMid = (Player.Center + new Vector2(direction * Player.width / 2f + 2f, 0f)).ToTileCoordinates(); + + if (WorldGen.SolidOrSlopedTile(tileAheadUpper.X, tileAheadUpper.Y) || WorldGen.SolidOrSlopedTile(tileAheadMid.X, tileAheadMid.Y)) + Player.velocity.X *= 0.5f; + } + + private void SendDashRequest(int direction, byte dashTypeUsed) + { + var mod = ModContent.GetInstance(); + ModPacket packet = mod.GetPacket(); + packet.Write((byte)MessageType.Dash); + packet.Write((byte)DashPacketType.Request); + packet.Write((sbyte)direction); + packet.Write(dashTypeUsed); + packet.Send(); + } + + internal void BroadcastDash(int direction, byte dashTypeUsed) + { + if (Main.netMode != NetmodeID.Server) return; + + var mod = ModContent.GetInstance(); + ModPacket packet = mod.GetPacket(); + packet.Write((byte)MessageType.Dash); + packet.Write((byte)DashPacketType.Perform); + packet.Write((byte)Player.whoAmI); + packet.Write((sbyte)direction); + packet.Write(dashTypeUsed); + packet.Send(toClient: -1, ignoreClient: Player.whoAmI); + } +} \ No newline at end of file diff --git a/Content/DashKeybind/DashInputSystem.cs b/Content/DashKeybind/DashInputSystem.cs new file mode 100644 index 0000000..a362030 --- /dev/null +++ b/Content/DashKeybind/DashInputSystem.cs @@ -0,0 +1,114 @@ +using CTG2.Content.Configs; +using System.IO; +using Terraria; +using Terraria.ID; +using Terraria.ModLoader; + +namespace CTG2.Content; + +public class DashInputSystem : ModSystem +{ + internal enum DashPacketType : byte + { + Request = 0, + Perform = 1 + } + + public override void Load() + { + On_Player.DoCommonDashHandle += VanillaDashDetour; + On_Player.DashMovement += CustomDashHandle; + } + + public override void Unload() + { + On_Player.DoCommonDashHandle -= VanillaDashDetour; + On_Player.DashMovement -= CustomDashHandle; + } + + // Suppress vanilla double-tap dash for the local player only. + // Other players still run vanilla logic so they animate correctly on your client. + private static void VanillaDashDetour( + On_Player.orig_DoCommonDashHandle orig, + Player self, + out int dir, + out bool dashing, + Player.DashStartAction dashStartAction) + { + if (Main.netMode == NetmodeID.Server || self.whoAmI != Main.myPlayer) + { + orig(self, out dir, out dashing, dashStartAction); + return; + } + + var config = ModContent.GetInstance(); + if (config.IsVanillaDashEnabled) + { + orig(self, out dir, out dashing, dashStartAction); + return; + } + + dir = 0; + dashing = false; + } + + private static void CustomDashHandle(On_Player.orig_DashMovement orig, Player self) + { + orig(self); + } + + internal static void HandlePacket(BinaryReader reader, int sender) + { + DashPacketType packetType = (DashPacketType)reader.ReadByte(); + + switch (packetType) + { + case DashPacketType.Request: + { + if (Main.netMode != NetmodeID.Server) break; + + sbyte direction = reader.ReadSByte(); + byte dashTypeHint = reader.ReadByte(); + + direction = ClampDirection(direction); + if (direction == 0) direction = 1; + + Player player = Main.player[sender]; + if (!player.active || player.dead) break; + + var dashPlayer = player.GetModPlayer(); + if (dashPlayer.PerformDash(direction, force: false, out byte dashTypeUsed, dashTypeHint > 0 ? dashTypeHint : (int?)null)) + dashPlayer.BroadcastDash(direction, dashTypeUsed); + + break; + } + case DashPacketType.Perform: + { + if (Main.netMode != NetmodeID.MultiplayerClient) break; + + byte playerIndex = reader.ReadByte(); + sbyte direction = reader.ReadSByte(); + byte dashTypeUsed = reader.ReadByte(); + + if (playerIndex >= Main.maxPlayers) break; + + Player remotePlayer = Main.player[playerIndex]; + if (!remotePlayer.active) break; + + direction = ClampDirection(direction); + if (direction == 0) direction = 1; + + var dashPlayer = remotePlayer.GetModPlayer(); + dashPlayer.PerformDash(direction, force: true, out _, dashTypeUsed > 0 ? dashTypeUsed : (int?)null); + break; + } + } + } + + private static sbyte ClampDirection(int direction) + { + if (direction > 1) return 1; + if (direction < -1) return -1; + return (sbyte)direction; + } +} \ No newline at end of file diff --git a/Localization/en-US_Mods.CTG2.hjson b/Localization/en-US_Mods.CTG2.hjson index 7cff0f5..8c951c1 100644 --- a/Localization/en-US_Mods.CTG2.hjson +++ b/Localization/en-US_Mods.CTG2.hjson @@ -151,6 +151,11 @@ Configs: { Label: Selected Track: 0=None, 1=Clash, 2=Mystery Tooltip: "" } + + IsVanillaDashEnabled: { + Tooltip: When enabled, the vanilla double-tap dash works as normal. When disabled, only the Dash keybind triggers dashes. + Label: Enable vanilla double-tap dash + } } }