using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using Godot; using Godot.Bridge; using Godot.NativeInterop; [ScriptPath("res://Scripts/Envirioment/Fish.cs")] public partial class Fish : Node2D { private enum State { None, Casting, Main, Reel } public enum FishTypes { Bass, Bluegill, Koi, Snakehead, Gar, Catfish, Eel, Trout, Froggit, Carp, Salmon } public partial class FishEntity { public FishTypes type; public float size; public float distance; public float lifeTime; public float idle; public float maxLife; public int hp; public int state; public Sprite2D sprite; public Vector2 dir; public Vector2 target; public void Flee() { state = 3; idle = 0f; } public float GetSize() { return Mathf.Lerp(difficulty[type][0], difficulty[type][1], size); } } [Export(PropertyHint.None, "")] private Node2D[] partyPos; [Export(PropertyHint.None, "")] private Sprite2D cursor; [Export(PropertyHint.None, "")] private Sprite2D cast; [Export(PropertyHint.None, "")] private AudioStream reelMusic; [Export(PropertyHint.None, "")] private AudioStream castMusic; private CpuParticles2D part; private Node2D prompt; private static readonly Dictionary difficulty = new Dictionary { { FishTypes.Bass, new int[2] { 3, 6 } }, { FishTypes.Bluegill, new int[2] { 2, 4 } }, { FishTypes.Koi, new int[2] { 3, 8 } }, { FishTypes.Snakehead, new int[2] { 8, 12 } }, { FishTypes.Gar, new int[2] { 8, 15 } }, { FishTypes.Catfish, new int[2] { 7, 15 } }, { FishTypes.Eel, new int[2] { 3, 7 } }, { FishTypes.Trout, new int[2] { 3, 6 } }, { FishTypes.Froggit, new int[2] { 2, 8 } }, { FishTypes.Carp, new int[2] { 4, 6 } }, { FishTypes.Salmon, new int[2] { 5, 7 } } }; private Texture2D[] texs = new Texture2D[5] { GD.Load("res://Sprites/Props/Fish/FishBobber2.tres"), GD.Load("res://Sprites/Props/Fish/FishBobber.tres"), GD.Load("res://Sprites/Props/Fish/FishBobber3.tres"), GD.Load("res://Sprites/Props/Fish/FishBobber4.tres"), GD.Load("res://Sprites/Props/Fish/FishShadow.tres") }; private Texture2D[] fishTex = Array.Empty(); private const float radius = 100f; private const int max = 5; private Coroutine action; private State state; private int combo; private int promptDir; private float time; private float spawn; private float promptTime; private FishEntity fishing; private string[] fishNames; public List fishes = new List(); private readonly float[] promptAngle = new float[4] { 180f, 0f, 90f, 270f }; private void StartFish() { if (SaveFile.current.followers.Count > 0) { TextSystem.GetText("FishCant"); } else if (!SaveFile.HasFlag(SaveFile.Flags.AfterDinnerDay1)) { TextSystem.GetText("KanakoFishD1"); } else { Main.inEvent = Coroutine.Start(Routine()); } } public override void _EnterTree() { fishNames = FileAccess.Open(Texts.textFolder + "FishNames.txt", FileAccess.ModeFlags.Read).GetAsText(skipCr: true).Split('\n'); part = cast.GetChild(0); prompt = cast.GetChild(1); } public override void _Process(double delta) { Coroutine coroutine = action; if (coroutine != null && coroutine.done) { action = null; } FishScale(); } private IEnumerator Routine() { state = State.None; combo = 0; time = 0f; spawn = 0f; if (SaveFile.HasFlag(SaveFile.Flags.KanakoFollowing) && !SaveFile.HasFlag(SaveFile.Flags.KanakoFishFirstTalk)) { SaveFile.AddFlag(SaveFile.Flags.KanakoFishFirstTalk); TextSystem.GetText("KanakoFishD2"); while (TextSystem.instance.Visible) { yield return null; } } TextSystem.GetText("FishPrompt"); while (TextSystem.instance.Visible) { yield return null; } if (TextSystem.lastPrompt != 0) { yield break; } Party.ChangeCollider(state: false); IList list = partyPos; Party.MoveTo(Common.PosFromNode(in list), 0.5f, align: true, Entity.Direction.West); while (!Party.IsStopped()) { yield return null; } Coroutine c = Coroutine.Start(CameraController.Pan(base.GlobalPosition + 50f * Vector2.Left, 0.5f)); Audio.ChangeMusic(castMusic, 1f); while (true) { if (c.done) { Coroutine routine = Audio.routine; if (routine == null || routine.done) { break; } } yield return null; } SetAnims(); state = State.Casting; for (int i = 0; i < fishes.Count; i++) { if (GodotObject.IsInstanceValid(fishes[i].sprite)) { fishes[i].sprite.QueueFree(); } } fishes.Clear(); while (GameLoop()) { yield return null; } state = State.None; cursor.Visible = false; Party.UpdateAnim(); c = Coroutine.Start(CameraController.PanToObj(Player.instance, 0.5f)); while (!c.done) { yield return null; } Party.ChangeCollider(state: true); Room.current.DoMusic(0); Player.instance.ResetFollowStep(); } private Vector2 GetRandomPoint(float mult = 1f) { return (Vector2.Up * (float)GD.RandRange(0.0, 100f * mult)).Rotated(Mathf.DegToRad(GD.RandRange(0, 360))); } private bool GameLoop() { time += Main.deltaTime; Coroutine coroutine = action; if (coroutine != null && !coroutine.done) { return true; } switch (state) { case State.Casting: FishLoop(); if (!cursor.Visible) { cursor.GlobalPosition = base.GlobalPosition; cursor.Visible = true; break; } cursor.RotationDegrees += Main.deltaTime * -0.75f; cursor.GlobalPosition += Main.GetDirection().Normalized() * 2f; cursor.Position = cursor.Position.LimitLength(85f); if (Input.IsActionJustPressed(Main.keys[4])) { action = Coroutine.Start(Cast()); state = State.Main; return true; } if (!Input.IsActionJustPressed(Main.keys[5]) && !Input.IsActionJustPressed(Main.keys[6])) { break; } Audio.PlaySound(Audio.commonSounds[0]); return false; case State.Main: FishLoop(); CastTexture(); if (Input.IsActionJustPressed(Main.keys[4])) { if (fishing != null) { for (int j = 0; j < fishes.Count; j++) { if (fishes[j] != fishing) { fishes[j].state = 3; } } Audio.PlaySound("snd_grab.wav"); Player.instance.anim.Play("FishReel"); if (SaveFile.HasFlag(SaveFile.Flags.KanakoFollowing)) { Party.party[1].oEntity.anim.Play("SitLook2"); } Player.instance.shakeIntensity = 1f; part.Emitting = true; state = State.Reel; BattleDR.ShowFloatingText(Texts.common[123], cast.GlobalPosition); } else { CancelReel(); } } else if ((Input.IsActionJustPressed(Main.keys[5]) || Input.IsActionJustPressed(Main.keys[6])) && fishing == null) { CancelReel(); } break; case State.Reel: Player.instance.shake = 5f; FishLoop(); if (!prompt.Visible) { promptTime = 80f; promptDir = GD.RandRange(0, 3); prompt.RotationDegrees = promptAngle[promptDir]; prompt.Visible = true; } if (promptTime > 0f) { prompt.Modulate = ((Mathf.Sin(time * 0.2f) > 0f) ? Main.colorWhite : Main.colorDark); promptTime -= Main.deltaTime; for (int i = 0; i <= 3; i++) { if (Input.IsActionJustPressed(Main.keys[i])) { if (i == promptDir) { fishing.hp--; action = Coroutine.Start(PromptSuccess()); } else { Fail(); } } } } else { Fail(); } break; } return true; } private void Fail() { Audio.PlaySound("snd_break1.wav"); BattleDR.ShowFloatingText(Texts.common[122], cast.GlobalPosition); combo = 0; state = State.Casting; RemoveFish(0); fishing = null; CancelReel(); if (SaveFile.HasFlag(SaveFile.Flags.KanakoFollowing)) { Party.party[1].oEntity.anim.Play("SitDraw"); } } private IEnumerator PromptSuccess() { Audio.PlaySound("snd_shineselect.wav"); prompt.Modulate = Main.colorYellow; for (float a = 0f; a < 20f; a += Main.deltaTime) { yield return null; } prompt.Modulate = Main.colorWhite; prompt.Visible = false; if (fishing.hp <= 0) { combo++; action = Coroutine.Start(Cast(back: true)); } } private void CancelReel() { part.Emitting = false; prompt.Visible = false; action = Coroutine.Start(Cast(back: true)); } private void CastTexture() { cast.Texture = texs[(fishing != null) ? 3 : ((Mathf.Sin(time * 0.2f) > 0f) ? 1 : 2)]; } private void FishLoop() { for (int num = fishes.Count - 1; num >= 0; num--) { if (fishing == null && (fishes[num].state == 1 || fishes[num].state == 2) && fishes[num].distance < 16f) { fishes[num].idle = 0f; fishes[num].state = 4; } switch (fishes[num].state) { case 0: if (fishes[num].lifeTime > 60f) { fishes[num].state = 1; fishes[num].idle = GD.RandRange(60, 200); } break; case 1: if (fishes[num].lifeTime > 1600f) { fishes[num].Flee(); } else if (fishes[num].idle <= 0f) { fishes[num].state = 2; fishes[num].target = base.GlobalPosition + GetRandomPoint(0.8f); fishes[num].dir = fishes[num].sprite.GlobalPosition.DirectionTo(fishes[num].target); } else { fishes[num].idle -= Main.deltaTime; } break; case 2: if (fishes[num].sprite.GlobalPosition.DistanceTo(fishes[num].target) > 5f) { fishes[num].sprite.GlobalPosition += fishes[num].dir * 0.25f * Main.deltaTime; } else { fishes[num].state = 0; } break; case 3: if (fishes[num].idle < 60f) { fishes[num].idle += Main.deltaTime; } else { RemoveFish(num); } break; case 4: if (fishes[num].idle < 30f) { fishes[num].idle += Main.deltaTime; break; } fishes[num].idle = 0f; fishes[num].state = 5; break; case 5: if (fishes[num].distance > 1f) { fishes[num].sprite.GlobalPosition += fishes[num].sprite.GlobalPosition.DirectionTo(cast.GlobalPosition) * Mathf.Clamp(fishes[num].distance / 2f, 0f, 1f) * 0.2f * Main.deltaTime; break; } fishes[num].idle = 0f; if (fishing != null) { fishes[num].state = 3; break; } Audio.PlaySound("snd_swallow_ch1.wav"); Main.Particle("WaterBurst", fishes[num].sprite.GlobalPosition); fishing = fishes[num]; fishes[num].state = 6; break; case 6: if (state == State.Reel) { break; } if (fishes[num].idle < 60f) { fishes[num].idle += Main.deltaTime; break; } if (fishing == fishes[num]) { fishing = null; } fishes[num].Flee(); BattleDR.ShowFloatingText(Texts.common[122], cast.GlobalPosition); break; } } if (spawn > 0f) { spawn -= Main.deltaTime; } else if ((state == State.Main || state == State.Casting) && fishes.Count < 5) { spawn = GD.RandRange(120, 300); SpawnFish(); } } private void RemoveFish(int i) { fishes[i].sprite.QueueFree(); fishes.RemoveAt(i); } private void SpawnFish() { fishes.Add(new FishEntity { type = (FishTypes)GD.RandRange(0, 10), size = (float)GD.RandRange(0.5, 1.2999999523162842), distance = 99999f, maxLife = GD.RandRange(1600, 2800) }); List list = fishes; FishEntity fishEntity = list[list.Count - 1]; Dictionary dictionary = difficulty; List list2 = fishes; float num = dictionary[list2[list2.Count - 1].type][0]; Dictionary dictionary2 = difficulty; List list3 = fishes; float to = dictionary2[list3[list3.Count - 1].type][1]; List list4 = fishes; fishEntity.hp = Mathf.Clamp(Mathf.RoundToInt(Mathf.Lerp(num, to, list4[list4.Count - 1].size)), 3, 8); List list5 = fishes; FishEntity fishEntity2 = list5[list5.Count - 1]; Sprite2D obj = new Sprite2D { Texture = texs[4], Scale = Vector2.Zero, Position = GetRandomPoint(0.8f) }; Sprite2D node = obj; fishEntity2.sprite = obj; AddChild(node, forceReadableName: false, InternalMode.Disabled); } private IEnumerator Cast(bool back = false) { cursor.Visible = false; if (!back) { Player.instance.anim.Play("FishCast"); } else { Audio.PlaySound("snd_splash.wav"); part.Emitting = false; Player.instance.anim.PlayBackwards("FishCast"); FishEntity fishEntity = fishing; if (fishEntity != null && fishEntity.hp <= 0) { fishing.sprite.Visible = false; } } Audio.PlaySound("snd_swing.wav"); float a; for (a = 0f; a < 5f; a += Main.deltaTime) { yield return null; } cast.Visible = true; cast.Texture = texs[0]; Vector2 sp = Player.instance.GlobalPosition + Vector2.Up * 32f; Vector2 m = sp.Lerp(cursor.GlobalPosition, 0.5f) + Vector2.Up * 50f; a = 0f; for (float b = 60f; a < b; a += Main.deltaTime) { cast.GlobalPosition = Common.Beizier(sp, m, cursor.GlobalPosition, back ? (1f - a / b) : (a / b)); yield return null; } if (back) { cast.Visible = false; state = State.Casting; FishEntity fishEntity2 = fishing; if (fishEntity2 != null && fishEntity2.hp <= 0) { BattleDR.ShowFloatingText(Texts.common[124], Player.instance.GlobalPosition); Texts.TextBlock[] diagClone = Texts.GetDiagClone("FishGet"); Audio.PlaySound("snd_item_ch1.wav"); Player.instance.anim.Play("SideIdleHat"); float b = fishing.GetSize(); diagClone[0].text = diagClone[0].text.Replace("[NAME]", fishNames[(int)fishing.type]).Replace("[SIZE]", b.ToString("0.00")); diagClone[0].portrait = "Fish" + fishing.type; RemoveFish(0); if (SaveFile.HasFlag(SaveFile.Flags.KanakoFollowing)) { Party.party[1].oEntity.anim.Play("SitHappy"); } TextSystem.SingleText(diagClone); while (TextSystem.instance.Visible) { yield return null; } if (!SaveFile.current.fishes.ContainsKey((int)fishing.type)) { SaveFile.current.fishes.Add((int)fishing.type, new float[2]); } SaveFile.current.fishes[(int)fishing.type][0] += 1f; if (b > SaveFile.current.fishes[(int)fishing.type][1]) { SaveFile.current.fishes[(int)fishing.type][1] = b; } fishing = null; if (SaveFile.HasFlag(SaveFile.Flags.KanakoFollowing)) { Party.party[1].oEntity.anim.Play("SitDraw"); } Player.instance.anim.Play("FishOut"); } yield break; } Audio.PlaySound("snd_splash.wav"); Main.Particle("WaterBurst", cursor.GlobalPosition); for (int i = 0; i < fishes.Count; i++) { if (fishes[i].sprite.GlobalPosition.DistanceTo(cast.GlobalPosition) < 20f) { fishes[i].Flee(); } } } private void SetAnims() { Party.UpdateFollowerEntities(); for (int i = 0; i < SaveFile.current.activeParty.Count; i++) { switch (Party.party[SaveFile.current.activeParty[i]].oEntity.id) { case Entity.IDs.Clover: Party.party[SaveFile.current.activeParty[i]].oEntity.sprite.FlipH = true; Party.party[SaveFile.current.activeParty[i]].oEntity.anim.Play("FishOut"); break; case Entity.IDs.Kanako: Party.party[SaveFile.current.activeParty[i]].oEntity.sprite.FlipH = false; Party.party[SaveFile.current.activeParty[i]].oEntity.anim.Play("SitDraw"); break; } } } private void FishScale() { float weight = Main.deltaTime * 0.05f; for (int i = 0; i < fishes.Count; i++) { if (state == State.None || fishes[i].state == 3) { fishes[i].sprite.Scale = fishes[i].sprite.Scale.Lerp(Vector2.Zero, weight); } else { fishes[i].sprite.Scale = fishes[i].sprite.Scale.Lerp((Mathf.Clamp(Mathf.InverseLerp(4f, 10f, fishes[i].GetSize()), 0.5f, 1.5f) + Mathf.Sin(fishes[i].lifeTime * 0.1f) * 0.1f) * Vector2.One, weight); fishes[i].lifeTime += Main.deltaTime; } fishes[i].sprite.Modulate = Main.colorClear.Lerp(Main.colorWhite, fishes[i].sprite.Scale.Length()); if (state == State.Main) { fishes[i].distance = fishes[i].sprite.GlobalPosition.DistanceTo(cast.GlobalPosition); } else { fishes[i].distance = 99999f; } } } private void FishFanEventD2() { if (SaveFile.HasFlag(SaveFile.Flags.FishGuyTaskDay2)) { TextSystem.GetText("FishFanD2After"); } else if (!SaveFile.HasFlag(SaveFile.Flags.FishGuyFirstTalk)) { TextSystem.GetText("FishFanFirstDiag"); SaveFile.AddFlag(SaveFile.Flags.FishGuyFirstTalk); } else if (SaveFile.current.fishes.Count >= 5) { TextSystem.GetText("FishFanD2Got"); SaveFile.current.gold += 5; SaveFile.AddFlag(SaveFile.Flags.FishGuyTaskDay2); } else { TextSystem.GetText("FishFanD2"); } } private void FishSign() { Main.inEvent = Coroutine.Start(FishSignRoutine()); } private IEnumerator FishSignRoutine() { TextSystem.GetText("FishSign"); while (TextSystem.instance.Visible) { yield return null; } if (TextSystem.lastPrompt == 0) { TextSystem.GetText("FishInstructions"); } else { if (TextSystem.lastPrompt != 1) { yield break; } if (SaveFile.current.fishes.Count == 0) { TextSystem.GetText("FishNone"); yield break; } foreach (int key in SaveFile.current.fishes.Keys) { Texts.TextBlock[] diagClone = Texts.GetDiagClone("FishStat"); diagClone[0].text = diagClone[0].text.Replace("[NAME]", fishNames[key]).Replace("[SIZE]", SaveFile.current.fishes[key][1].ToString("0.00")).Replace("[AMT]", SaveFile.current.fishes[key][0].ToString()); Texts.TextBlock obj = diagClone[0]; FishTypes fishTypes = (FishTypes)key; obj.portrait = "Fish" + fishTypes; TextSystem.SingleText(diagClone); while (TextSystem.instance.Visible) { yield return null; } } } } }