using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using Godot; using Godot.Bridge; using Godot.NativeInterop; [ScriptPath("res://Scripts/Puzzles/FightingGame.cs")] public partial class FightingGame : Node2D { private enum Animations { Idle, Walk, BackWalk, Hurt, Attack1, Attack2, Block, Hadouken, Cheer, Heal, Surprise } [Signal] public delegate void OnEndEventHandler(); public partial class Buffer { public int key; public float time; } public partial class FigherData { public float hp = 100f; public float stun; public float knockback; public float hyperArmor; public float armorBuildup; public float armorTime; public float blockHold; public Entity entity; public Coroutine move; public Color color = Main.colorWhite; public bool block; } private readonly Dictionary<Animations, StringName> anims = new Dictionary<Animations, StringName> { { Animations.Idle, "Idle" }, { Animations.Walk, "Walk" }, { Animations.BackWalk, "BackWalk" }, { Animations.Hurt, "Hurt" }, { Animations.Attack1, "Attack1" }, { Animations.Attack2, "Attack2" }, { Animations.Block, "Block" }, { Animations.Hadouken, "Hadouken" }, { Animations.Cheer, "CheerBack" }, { Animations.Heal, "SpellBack" }, { Animations.Surprise, "SurpriseBack" } }; public static FightingGame instance; [Export(PropertyHint.None, "")] private Entity[] fighters; [Export(PropertyHint.None, "")] private AudioStream music; [Export(PropertyHint.None, "")] private Sprite2D soul; [Export(PropertyHint.None, "")] private PackedScene axisSphere; [Export(PropertyHint.None, "")] private PackedScene shockWave; [Export(PropertyHint.None, "")] private Node2D UI; [Export(PropertyHint.None, "")] private Node2D[] hpBars; [Export(PropertyHint.None, "")] private AnimationPlayer roundAnim; [Export(PropertyHint.None, "")] private RichTextLabel help; public int round = 1; public int tp; public int itp; private const int kanakoCost = 90; private const int hadoukenCost = 15; private const int kanakoHeal = 35; public bool active; private FigherData[] data = new FigherData[2]; private Coroutine busy; private Coroutine healing; private List<Node> proj = new List<Node>(); private float projTime; private Node2D shockwave; private readonly StringName hitbox = "Hit"; private List<Buffer> buffers = new List<Buffer>(); private const int enemyDist = 50; private float aiTime; private float blockTime; private float comboTime; private int combo; public override void _EnterTree() { instance = this; fighters[0].trigger.AreaEntered += PlayerHit; fighters[1].trigger.AreaEntered += EnemyHit; } public override void _Process(double delta) { UI.Position = new Vector2(0f, Mathf.Lerp(UI.Position.Y, (!active) ? (-70) : 0, Main.deltaTime * 0.1f)); FigherData[] array = data; if (array != null && array.Length != 0 && data[0] != null) { for (int i = 0; i < 2; i++) { hpBars[i].Scale = new Vector2(Mathf.Max(0f, data[i].hp / 100f), 1f); fighters[i].Modulate = fighters[i].Modulate.Lerp(data[i].color, Main.deltaTime * 0.1f); } } if (!active) { return; } Player.instance.canInput = false; if (busy == null || busy.done) { for (int j = 0; j < 2; j++) { fighters[j].GlobalPosition = new Vector2(fighters[j].GlobalPosition.X, fighters[j].startPos.Y); if (data[j].stun <= 0f && data[j].knockback <= 0f && (data[j].move == null || data[j].move.done)) { if (j == 0) { Inputs(); } else { EnemyAI(); } } else if (data[j].knockback > 0f) { DoKnockback(j); } else if (data[j].stun > 0f) { data[j].entity.Velocity = Vector2.Zero; } } KanakoAnim(); } DWMenu.instance.showHP = 5f; BattleDR.UpdateTP(ref itp, in tp, 0, 1); Cooldowns(); } private void KanakoAnim() { Coroutine coroutine = healing; if (coroutine == null || coroutine.done) { if (data[0].knockback > 0f) { Player.instance.followers[0].anim.Play(anims[Animations.Surprise]); } else { Player.instance.followers[0].anim.Play(anims[Animations.Cheer]); } } } private void DoKnockback(int i) { float num = Main.deltaTime * Mathf.Clamp(data[i].knockback, 0f, 10f) * 5f; if (i == 0) { fighters[i].Velocity = Vector2.Left * num; } else { fighters[i].Velocity = Vector2.Right * num; } } private IEnumerator ToastProj() { fighters[1].Velocity = Vector2.Zero; fighters[1].Shake(30f, 2f); Audio.PlaySound("snd_boost.wav"); fighters[1].anim.Play(anims[Animations.Attack2]); for (float a = 0f; a < 10f; a += Main.deltaTime) { data[1].hyperArmor = 10f; yield return null; } fighters[1].anim.Pause(); for (int i = 0; i < 3; i++) { fighters[1].Modulate = Main.colorYellow; for (float a = 0f; a < 10f; a += Main.deltaTime) { data[1].hyperArmor = 10f; yield return null; } } fighters[1].anim.Play(); CameraController.Shake(15f, 2f); Audio.PlaySound("snd_rudebuster_hit.wav"); Node node = BattleDR.NewBullet(in shockWave, fighters[1].GlobalPosition + new Vector2(-30f, 0f), this); Timer obj = new Timer { Autostart = true, WaitTime = 5.0, OneShot = true }; Timer timer = obj; node.AddChild(obj, forceReadableName: false, InternalMode.Disabled); timer.Timeout += node.QueueFree; for (float a = 0f; a < 15f; a += Main.deltaTime) { yield return null; } fighters[1].anim.Pause(); for (float a = 0f; a < 45f; a += Main.deltaTime) { yield return null; } fighters[1].anim.Play(anims[Animations.Idle]); yield return null; if (Main.RandomRange(0, 100) < 30) { aiTime = 40f; } else { aiTime = 20f; } } private IEnumerator ToastPunch() { fighters[1].Velocity = Vector2.Zero; Audio.PlaySound("motor_swing_down.wav"); fighters[1].anim.Play(anims[Animations.Attack1]); if ((round > 1 || data[1].hp < 65f) && Main.RandomRange(0, 100) < ((round == 1) ? 50 : 70)) { for (float a = 0f; a < 22f; a += Main.deltaTime) { yield return null; } fighters[1].anim.Play(anims[Animations.Attack2]); Audio.PlaySound("motor_upper_2.wav", 1f, 0.9f); yield return null; } while ((StringName)fighters[1].anim.CurrentAnimation != anims[Animations.Idle]) { yield return null; } if (Main.RandomRange(0, 100) < 30) { aiTime = 60f; } else { aiTime = 20f; } } private void EnemyAI() { float num = Mathf.Abs(fighters[1].GlobalPosition.X - fighters[0].GlobalPosition.X); data[1].block = false; switch (round) { case 1: if (!EnemyBase(num) && num < 50f) { data[1].move = Coroutine.Start(ToastPunch(), this); } break; case 2: if (projTime > 45f || proj.Count >= 2) { blockTime = 10f; } if (!EnemyBase(num) && num < 50f) { data[1].move = Coroutine.Start(ToastPunch(), this); } break; case 3: if (projTime > 30f || proj.Count >= 2) { blockTime = 10f; } if (num > 100f && Engine.GetFramesDrawn() % 3 == 0 && aiTime <= 0f) { data[1].move = Coroutine.Start(ToastProj(), this); } else if (!EnemyBase(num) && num < 50f) { data[1].move = Coroutine.Start(ToastPunch(), this); } break; } } private bool EnemyBase(float d) { if (combo > 2) { blockTime = 80f; } if (blockTime > 0f && data[1].hyperArmor <= 0f) { fighters[1].Velocity = Vector2.Zero; fighters[1].anim.Play(anims[Animations.Block]); data[1].block = true; return true; } if (aiTime > 0f) { Move(fighters[1], Animations.Walk, 0.8f); return true; } if (d > 50f) { Move(fighters[1], Animations.Walk, -1f); if (Engine.GetFramesDrawn() % 3 == 0 && Main.RandomRange(0, 100) <= 2) { aiTime = Main.RandomRange(30, 60); } return true; } return false; } private void Cooldowns() { if (blockTime > 0f) { blockTime -= Main.deltaTime; } if (aiTime > 0f) { aiTime -= Main.deltaTime; } if (comboTime > 0f) { comboTime -= Main.deltaTime; } else { combo = 0; } if (proj.Count > 0) { projTime += Main.deltaTime; } else { projTime = 0f; } for (int i = 0; i < 2; i++) { if (data[i] != null) { if (data[i].blockHold > 0f && (StringName)data[i].entity.anim.CurrentAnimation != anims[Animations.Block]) { data[i].blockHold -= Main.deltaTime * 0.05f; } else { data[i].blockHold = 0f; } if (data[i].stun > 0f) { data[i].stun -= Main.deltaTime; } if (data[i].knockback > 0f) { data[i].knockback -= Main.deltaTime; } if (data[i].hyperArmor > 0f) { data[i].armorTime += Main.deltaTime; data[i].hyperArmor -= Main.deltaTime * Mathf.Max(1f, 0.5f * Mathf.Floor(data[i].armorTime / 30f)); } if (data[i].armorBuildup > 0f) { data[i].armorBuildup -= Main.deltaTime * (float)((!(data[i].hyperArmor > 0f)) ? 1 : 2); } } } for (int num = buffers.Count - 1; num >= 0; num--) { if (buffers[num].time <= 0f) { buffers.RemoveAt(num); } else { buffers[num].time -= Main.deltaTime; } } } private IEnumerator Attack() { fighters[0].Velocity = Vector2.Zero; bool cancombo = false; if ((StringName)fighters[0].anim.CurrentAnimation != anims[Animations.Attack1]) { Audio.PlaySound("motor_swing_down.wav"); fighters[0].anim.Play(anims[Animations.Attack1]); cancombo = true; } else { Audio.PlaySound("motor_upper_2.wav"); fighters[0].anim.Play(anims[Animations.Attack2]); } for (float a = 0f; a < 18f; a += Main.deltaTime) { yield return null; } while ((StringName)fighters[0].anim.CurrentAnimation != anims[Animations.Idle]) { if (cancombo && Input.IsActionJustPressed(Main.keys[4])) { data[0].move = Coroutine.Start(Attack(), this); break; } yield return null; } } private IEnumerator HealBell() { Player.instance.followers[0].anim.Play(anims[Animations.Heal]); Audio.PlaySound("snd_spellcast_ch1.wav"); Node p = Main.Particle("HealBell", new Vector2(0f, -50f), 1, fighters[0]); for (float a = 0f; a < 70f; a += Main.deltaTime) { yield return null; } Audio.PlaySound("snd_power.wav"); data[0].hp = Mathf.Min(data[0].hp + 35f, 100f); BattleDR.ShowFloatingText(35.ToString(), fighters[0].GlobalPosition, Main.colorGreen); fighters[0].Modulate = Main.colorGreen; while (GodotObject.IsInstanceValid(p)) { yield return null; } } private void Move(Entity entity, Animations anim, float spd = 1f) { entity.Velocity = Vector2.Right * spd * entity.baseSpeed; entity.anim.Play(anims[anim]); } private IEnumerator Hadouken() { Audio.PlaySound("snd_shotmid.wav"); fighters[0].Velocity = Vector2.Zero; fighters[0].anim.Play(anims[Animations.Hadouken]); MoveAhead moveAhead = (MoveAhead)BattleDR.NewBullet(in axisSphere, fighters[0].GlobalPosition + new Vector2(26f, -23f), Room.current); moveAhead.speed = Vector2.Right * 1.3f; Area2D child = moveAhead.GetChild<Area2D>(0); child.CollisionLayer = 8u; child.SetMeta(hitbox, new int[2] { 8, 35 }); proj.Add(moveAhead); Timer obj = new Timer { WaitTime = 6.0, Autostart = true, OneShot = true }; Timer timer = obj; moveAhead.AddChild(obj, forceReadableName: false, InternalMode.Disabled); timer.Timeout += moveAhead.QueueFree; for (float a = 0f; a < 30f; a += Main.deltaTime) { yield return null; } } private void Inputs() { for (int i = 0; i < Main.keys.Length; i++) { if (Input.IsActionJustPressed(Main.keys[i])) { buffers.Add(new Buffer { key = i, time = 20f }); } } if (buffers.Count >= 2) { List<Buffer> list = buffers; if (list[list.Count - 1].key == 2) { List<Buffer> list2 = buffers; if (list2[list2.Count - 2].key == 2) { data[0].move = Coroutine.Start(Dash(), this); return; } } } if (buffers.Count >= 3) { List<Buffer> list3 = buffers; if (list3[list3.Count - 1].key == 4) { List<Buffer> list4 = buffers; if (list4[list4.Count - 2].key == 3) { List<Buffer> list5 = buffers; if (list5[list5.Count - 3].key == 1) { if (tp >= 15) { tp -= 15; data[0].move = Coroutine.Start(Hadouken(), this); return; } buffers.Clear(); } } } } if (Input.IsActionJustPressed(Main.keys[4])) { data[0].move = Coroutine.Start(Attack(), this); return; } if (Input.IsActionJustPressed(Main.keys[6]) && (healing == null || healing.done) && tp >= 90) { healing = Coroutine.Start(HealBell(), this); tp -= 90; } data[0].block = false; if (Input.IsActionPressed(Main.keys[2])) { fighters[0].Velocity = Vector2.Left * fighters[0].baseSpeed; fighters[0].anim.Play(anims[Animations.BackWalk]); } else if (Input.IsActionPressed(Main.keys[3])) { fighters[0].Velocity = Vector2.Right * fighters[0].baseSpeed; fighters[0].anim.Play(anims[Animations.Walk]); } else if (Input.IsActionPressed(Main.keys[5]) || Input.IsActionPressed(Main.keys[1])) { fighters[0].anim.Play(anims[Animations.Block]); fighters[0].Velocity = Vector2.Zero; data[0].block = true; } else { fighters[0].anim.Play(anims[Animations.Idle]); fighters[0].Velocity = Vector2.Zero; } } private IEnumerator Dash() { Audio.PlaySound("leaf_dodge.wav"); fighters[0].Modulate = Main.colorGlow; fighters[0].Velocity = Vector2.Left * fighters[0].baseSpeed * 3f; fighters[0].anim.Play(anims[Animations.BackWalk]); for (float a = 0f; a < 20f; a += Main.deltaTime) { yield return null; } } private IEnumerator IntroStuff(bool start = false) { if (start) { data = new FigherData[2] { new FigherData { entity = fighters[0] }, new FigherData { entity = fighters[1] } }; } for (int i = 0; i < 2; i++) { fighters[i].anim.Play(anims[Animations.Idle]); } roundAnim.Play("Round" + round); AudioStreamPlayer sound = Audio.PlaySound("snd_boxing_round" + round + "_bc.wav"); yield return null; while ((GodotObject.IsInstanceValid(sound) && sound.Playing) || data[1].hp < 100f) { for (int j = 0; j < 2; j++) { fighters[j].GlobalPosition = fighters[j].GlobalPosition.Lerp(fighters[j].startPos, Main.deltaTime * 0.1f); } if (data[1].hp < 100f) { data[1].hp += 1f; } yield return null; } data[1].hp = 100f; roundAnim.Play("Fight"); sound = Audio.PlaySound("snd_boxing_fight_bc.wav"); yield return null; while (sound.Playing) { yield return null; } for (int k = 0; k < 2; k++) { Audio.PlaySound("snd_bell_ch1.wav"); for (float a = 0f; a < 15f; a += Main.deltaTime) { yield return null; } } Audio.ChangeMusic(music); help.Visible = true; if (round == 1) { help.Text = Texts.common[121].Replace("[VALUE]", 90.ToString()).Replace("@", "\n"); } } public void Start(bool setPos = true) { base.ProcessMode = ProcessModeEnum.Inherit; active = true; tp = 0; round = 1; if (setPos) { for (int i = 0; i < fighters.Length; i++) { fighters[i].startPos = fighters[i].GlobalPosition; } } busy = Coroutine.Start(IntroStuff(start: true)); } private void PlayerHit(Node other) { if (other.HasMeta(hitbox)) { Damage(0, (int[])other.GetMeta(hitbox)); } } private void EnemyHit(Node other) { if (other.HasMeta(hitbox)) { bool noTP = false; if (other.GetParent() is MoveAhead moveAhead) { proj.Remove(moveAhead); moveAhead.QueueFree(); noTP = true; } Damage(1, (int[])other.GetMeta(hitbox), noTP); } } private void Damage(int id, int[] hitData, bool noTP = false) { Main.Particle("ShootHit", data[id].entity.GlobalPosition + new Vector2(Main.RandomRange(-10, 10), -25 + Main.RandomRange(-10, 10)), 1, null, localSpace: false); if (data[id].block) { data[id].hp -= data[id].blockHold + 1f; if (data[id].blockHold < 5f) { data[id].blockHold += 1f; } if (id == 1) { data[id].armorBuildup += hitData[1] * 5; } if (data[id].armorBuildup >= 100f) { data[id].hyperArmor += hitData[1]; data[id].entity.Modulate = Main.colorYellow; } else { data[id].entity.Modulate = Main.colorGlow; } if (id == 0 && !noTP) { tp = Mathf.Min(tp + 5 - (int)data[id].blockHold, 100); } Audio.PlaySound("snd_graze.wav"); } else { data[id].hp -= (float)hitData[0] * ((data[id].hyperArmor > 0f) ? 0.5f : 1f); if (id == 1) { combo++; comboTime = 60f; } else { Audio.PlaySound("snd_hurt1.wav"); } if (data[id].hyperArmor <= 0f) { if (data[id].armorBuildup >= 100f) { data[id].entity.Modulate = Main.colorYellow; Audio.PlaySound("snd_justice_effect.wav"); data[id].armorBuildup += 60f; data[id].hyperArmor += (float)hitData[1] * 1.5f; if (id == 1 && !noTP) { tp = Mathf.Min(tp + 1, 100); } } else { Audio.PlaySound("punch_ish_1.wav"); data[id].entity.Modulate = Main.colorGlow; data[id].knockback = hitData[0] * 5; Coroutine move = data[id].move; if (move != null && !move.done) { Coroutine.Stop(data[id].move, now: true); data[id].move = null; } if (id == 1) { data[id].armorBuildup += (float)hitData[1] * ((id == 0) ? 1.5f : 2f); } data[id].entity.Velocity = Vector2.Zero; data[id].entity.anim.Play(anims[Animations.Hurt]); if (id == 1 && !noTP) { tp = Mathf.Min(tp + Mathf.Min(hitData[1] / 10 * combo, 10), 100); } } } else { if (id == 1 && !noTP) { tp = Mathf.Min(tp + 1, 100); } data[id].entity.Modulate = Main.colorYellow; data[id].hyperArmor += 30f; } data[id].entity.Shake(10f, 2f); } if (data[id].hp <= 0f && (busy == null || busy.done)) { if (id == 0) { fighters[0].anim.Play(anims[Animations.Hurt]); Coroutine.Start(GameOver.DoGameOver(ChujinDWEvents.instance.nodes[8].GlobalPosition)); base.ProcessMode = ProcessModeEnum.Disabled; busy = Coroutine.Start(Reset()); } else { StopRoutines(); fighters[1].anim.Play(anims[Animations.Hurt]); busy = Coroutine.Start(EndRound()); } } } private void StopRoutines() { Coroutine.Stop(healing); for (int i = 0; i < 2; i++) { Coroutine.Stop(data[i].move); } } private IEnumerator Reset() { for (float a = 0f; a < 30f; a += Main.deltaTime) { yield return null; } for (int i = 0; i < 3; i++) { ((CpuParticles2D)fighters[1].extras[i + 1]).Emitting = false; } StopRoutines(); for (int j = 0; j < 2; j++) { fighters[j].GlobalPosition = fighters[j].startPos; fighters[j].anim.Play(anims[Animations.Idle]); } data = new FigherData[2] { new FigherData { entity = fighters[0] }, new FigherData { entity = fighters[1] } }; round = 1; tp = 0; Party.UpdateAnim(); for (float a = 0f; a < 30f; a += Main.deltaTime) { yield return null; } } private IEnumerator EndRound() { help.Visible = false; CameraController.Shake(60f, 2f); fighters[1].anim.Play(anims[Animations.Hurt]); if (round == 1) { ((CpuParticles2D)fighters[1].extras[1]).Emitting = true; data[1].color = Main.colorWhite.Lerp(Main.colorRed, 0.3f); } else if (round == 2) { ((CpuParticles2D)fighters[1].extras[2]).Emitting = true; ((CpuParticles2D)fighters[1].extras[3]).Emitting = true; data[1].color = Main.colorWhite.Lerp(Main.colorRed, 0.6f); } round++; Audio.PlaySound("snd_explosion.wav"); Engine.TimeScale = 0.5; if (round > 3) { Audio.ChangeMusic((Audio.AudioBlock)null, 5f); CameraController.Transition(Main.colorWhite, 100f); } for (int i = 0; i < 2; i++) { data[i].knockback = 60f; } while (data[0].knockback > 0f) { for (int j = 0; j < 2; j++) { DoKnockback(j); } yield return null; } Engine.TimeScale = 1.0; for (float a = 0f; a < 60f; a += Main.deltaTime) { yield return null; } if (round > 3) { active = false; while (!CameraController.transitionRoutine.done) { yield return null; } yield return null; for (int k = 0; k < 2; k++) { fighters[k].GlobalPosition = fighters[k].startPos; fighters[k].Velocity = Vector2.Zero; } fighters[0].anim.Play(anims[Animations.Idle]); UI.Visible = false; EmitSignal(SignalName.OnEnd); } else { busy = Coroutine.Start(IntroStuff()); } } }