Tips for taking turns based on a speed value

So in short, what I’m trying to accomplish is I have 6 monsters, 3 player mons, 3 enemy mons.
I want to put them in a list based on their individual Speed stats, and have them each take their turns 1 by 1. Picture it like Pokemon, all 6 monsters already know what they’re going to do beforehand, I just need them each to take only 1 turn.
The problem I keep running into is either it will use the incorrect mon’s speed value, or it will try to reference an index that doesn’t exist.
The way I have it setup is there’s a list of 7 monster stat blocks. 0 is an empty, uninitialized one that is just meant as a placeholder, that way indexes 1-3 are the players’ and indexes 4-6 are the enemies. I’ve tried setting up the turn order a bunch of different ways and just haven’t been able to get it right, so I’m coming here as a last-ditch effort before just going through and changing all the indexes and how I have the whole thing setup.

Here’s the parts of the script that are giving me problems.

    public void CheckIfPlayerDone()
    {
        if (Mon1 != SlotAction.NULL)
        {
            Monsters[1].selectedAction = Mon1;
            if (Mon2 != SlotAction.NULL)
            {
                Monsters[2].selectedAction = Mon2;
                if (Mon3 != SlotAction.NULL)
                {
                    Monsters[3].selectedAction = Mon3;
                    Monsters[4].selectedAction = Mon4;
                    Monsters[5].selectedAction = Mon5;
                    Monsters[6].selectedAction = Mon6;
                    state = BattleState.ACTION;
                    TurnOrder = new List<int>();
                    TurnOrder.Clear();
                    TurnOrder.Add(MM1.Speed);
                    TurnOrder.Add(MM2.Speed);
                    TurnOrder.Add(MM3.Speed);
                    TurnOrder.Add(MM4.Speed);
                    TurnOrder.Add(MM5.Speed);
                    TurnOrder.Add(MM6.Speed);
                    TurnOrder.Sort();
                    //TurnsTaken = 0;
                    ActionState();
                }
            }
        }
    }

    public void ActionState()
    {
        foreach (int i in TurnOrder)
        {
            TakeAction(TurnOrder.IndexOf(i));
            StartCoroutine(ActionSequence());
        }

        CheckForWin();
    }

    private IEnumerator ActionSequence()
    {
        // Create a copy of the TurnOrder list and sort it in descending order
        List<int> sortedTurnOrder = new List<int>(TurnOrder);
        sortedTurnOrder.Sort((a, b) => Monsters[b].Speed.CompareTo(Monsters[a].Speed));

        foreach (int spotID in sortedTurnOrder)
        {
            // Take action for the current spotID
            TakeAction(spotID);

            // Wait for 3 seconds before executing the next action
            yield return new WaitForSeconds(3);
        }

        CheckForWin();
    }

    public void TakeAction(int spotID)
    {
        // Adjust spotID to ensure it's within the range of valid indices
        Debug.Log(spotID + " = Unmodified SpotID");
        spotID = Mathf.Clamp(spotID, 1, 6);
        Debug.Log(spotID + " = Modified SpotID");

        // Retrieve the monster based on spotID and check if it's not null
        MiniMonster monster = Monsters[spotID];
        if (monster != null)
        {
            Debug.Log(monster.MonName + " - " + spotID + " takes action");

            // Check which action to take based on the monster's SlotAction
            switch (monster.selectedAction)
            {
                case SlotAction.AB1:
                    ABHandler.AB1(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                case SlotAction.AB2:
                    ABHandler.AB2(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                case SlotAction.ITEM:
                    ABHandler.ItemAB(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                default:
                    Debug.LogWarning("Invalid SlotAction for monster: " + monster.MonName);
                    break;
            }
        }
    }

And here’s the entire script in case that isn’t enough.

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public enum BattleState { START, PLAYERTURN, ACTION, WON, LOST }
public enum SlotAction { NULL, AB1, AB2, ITEM }

public class MiniMonManager : MonoBehaviour
{

    public Player player;
    public StatBlockMon[] MonIDList;
    public ItemStatBlock[] itemIDList;
    public GameObject MonGO0;
    public GameObject MonGO1;
    public GameObject MonGO2;
    public GameObject MonGO3;
    public GameObject MonGO4;
    public GameObject MonGO5;
    public GameObject MonGO6;
    public TMP_Text Coinvaltext;
    MiniMonster MM0;
    MiniMonster MM1;
    MiniMonster MM2;
    MiniMonster MM3;
    MiniMonster MM4;
    MiniMonster MM5;
    MiniMonster MM6;
    ItemStatBlock M1I;
    ItemStatBlock M2I;
    ItemStatBlock M3I;
    ItemStatBlock M4I;
    ItemStatBlock M5I;
    ItemStatBlock M6I;
    public BattleState state;
    public Image StatusBG;
    public TMP_Text StatusTextObj;
    public SlotAction Mon1 = SlotAction.NULL;
    public SlotAction Mon2 = SlotAction.NULL;
    public SlotAction Mon3 = SlotAction.NULL;
    public SlotAction Mon4 = SlotAction.NULL;
    public SlotAction Mon5 = SlotAction.NULL;
    public SlotAction Mon6 = SlotAction.NULL;
    public List<int> TurnOrder;
    int TurnsTaken;
    public int EnemyTarget;
    public int AllyTarget;
    public GameObject PlayerABUIGO;
    public List<MiniMonster> Monsters;
    public MiniMonAbilityHandler ABHandler;
    public TMP_Text S1AB2CD;
    public TMP_Text S2AB2CD;
    public TMP_Text S3AB2CD;
    public TMP_Text S1IABCD;
    public TMP_Text S2IABCD;
    public TMP_Text S3IABCD;
    public int EnemyAITarget;

    // Start is called before the first frame update
    void Start()
    {
        Time.timeScale = 1f;
        Monsters = new List<MiniMonster>();
        state = BattleState.START;
        player.LoadPlayer();
        Coinvaltext.text = player.coins.ToString();
        MonsterInit();
        ItemInit();
        Monsters.Add(MM0);
        Monsters.Add(MM1); Monsters.Add(MM2); Monsters.Add(MM3); Monsters.Add(MM4); Monsters.Add(MM5); Monsters.Add(MM6);
        state = BattleState.PLAYERTURN;
        S1AB2CD.text = MM1.AB2CD.ToString(); S2AB2CD.text = MM2.AB2CD.ToString(); S3AB2CD.text = MM3.AB2CD.ToString();
        S1IABCD.text = MM1.ItemCD.ToString(); S2IABCD.text = MM1.ItemCD.ToString(); S3IABCD.text = MM1.ItemCD.ToString();
        if (MM1.ItemCD < 1) { S1IABCD.text = ""; } if (MM2.ItemCD < 1) { S2IABCD.text = ""; } if (MM3.ItemCD < 1) { S3IABCD.text = ""; }
        SetTarget(4);
        SetTarget(1);
        PlayerTurn();
    }

    private void Update()
    {
        if (state == BattleState.PLAYERTURN)
        {
            PlayerABUIGO.SetActive(true);
        } else PlayerABUIGO.SetActive(false);

        if (EnemyTarget == 4) { MM4.TargetimgGO.SetActive(true);} else MM4.TargetimgGO.SetActive(false);
        if (EnemyTarget == 5) { MM5.TargetimgGO.SetActive(true); } else MM5.TargetimgGO.SetActive(false);
        if (EnemyTarget == 6) { MM6.TargetimgGO.SetActive(true); } else MM6.TargetimgGO.SetActive(false);
        if (AllyTarget == 1) { MM1.TargetimgGO.SetActive(true); } else MM1.TargetimgGO.SetActive(false);
        if (AllyTarget == 2) { MM2.TargetimgGO.SetActive(true); } else MM2.TargetimgGO.SetActive(false);
        if (AllyTarget == 3) { MM3.TargetimgGO.SetActive(true); } else MM3.TargetimgGO.SetActive(false);
    }

    public void PlayerTurn()
    {
        if (!MM4.Targettable) { EnemyTarget = 4; }
        if (!MM5.Targettable) { EnemyTarget = 5; }
        if (!MM6.Targettable) { EnemyTarget = 6;}
        if (!MM1.Targettable) { AllyTarget = 1;}
        if (!MM2.Targettable) { AllyTarget = 2; }
        if (!MM3.Targettable) { AllyTarget = 3; }
        state = BattleState.PLAYERTURN;
        Mon1 = SlotAction.NULL;
        Mon2 = SlotAction.NULL;
        Mon3 = SlotAction.NULL;
        Mon4 = SlotAction.NULL;
        Mon5 = SlotAction.NULL;
        Mon6 = SlotAction.NULL;
        EnemyTurn();
        StatusUpdate("Select your Actions");
    }

    public void AB1(int Slot)
    {
        if (Slot == 1)
        {
            Mon1 = SlotAction.AB1;
        }
        if (Slot == 2)
        {
            Mon2 = SlotAction.AB1;
        }
        if (Slot == 3)
        {
            Mon3 = SlotAction.AB1;
        }
        CheckIfPlayerDone();
    }

    public void AB2(int Slot)
    {
        if (Slot == 1)
        {
            Mon1 = SlotAction.AB2;
        }
        if (Slot == 2)
        {
            Mon2 = SlotAction.AB2;
        }
        if (Slot == 3)
        {
            Mon3 = SlotAction.AB2;
        }
        CheckIfPlayerDone();
    }

    public void ItemAB(int Slot)
    {
        if (Slot == 1)
        {
            Mon1 = SlotAction.ITEM;
        }
        if (Slot == 2)
        {
            Mon2 = SlotAction.ITEM;
        }
        if (Slot == 3)
        {
            Mon3 = SlotAction.ITEM;
        }
        CheckIfPlayerDone();
    }

    public void CheckIfPlayerDone()
    {
        if (Mon1 != SlotAction.NULL)
        {
            Monsters[1].selectedAction = Mon1;
            if (Mon2 != SlotAction.NULL)
            {
                Monsters[2].selectedAction = Mon2;
                if (Mon3 != SlotAction.NULL)
                {
                    Monsters[3].selectedAction = Mon3;
                    Monsters[4].selectedAction = Mon4;
                    Monsters[5].selectedAction = Mon5;
                    Monsters[6].selectedAction = Mon6;
                    state = BattleState.ACTION;
                    TurnOrder = new List<int>();
                    TurnOrder.Clear();
                    TurnOrder.Add(MM1.Speed);
                    TurnOrder.Add(MM2.Speed);
                    TurnOrder.Add(MM3.Speed);
                    TurnOrder.Add(MM4.Speed);
                    TurnOrder.Add(MM5.Speed);
                    TurnOrder.Add(MM6.Speed);
                    TurnOrder.Sort();
                    //TurnsTaken = 0;
                    ActionState();
                }
            }
        }
    }

    public void ActionState()
    {
        foreach (int i in TurnOrder)
        {
            TakeAction(TurnOrder.IndexOf(i));
            StartCoroutine(ActionSequence());
        }

        CheckForWin();
    }

    private IEnumerator ActionSequence()
    {
        // Create a copy of the TurnOrder list and sort it in descending order
        List<int> sortedTurnOrder = new List<int>(TurnOrder);
        sortedTurnOrder.Sort((a, b) => Monsters[b].Speed.CompareTo(Monsters[a].Speed));

        foreach (int spotID in sortedTurnOrder)
        {
            // Take action for the current spotID
            TakeAction(spotID);

            // Wait for 3 seconds before executing the next action
            yield return new WaitForSeconds(3);
        }

        CheckForWin();
    }

    public void TakeAction(int spotID)
    {
        // Adjust spotID to ensure it's within the range of valid indices
        Debug.Log(spotID + " = Unmodified SpotID");
        spotID = Mathf.Clamp(spotID, 1, 6);
        Debug.Log(spotID + " = Modified SpotID");

        // Retrieve the monster based on spotID and check if it's not null
        MiniMonster monster = Monsters[spotID];
        if (monster != null)
        {
            Debug.Log(monster.MonName + " - " + spotID + " takes action");

            // Check which action to take based on the monster's SlotAction
            switch (monster.selectedAction)
            {
                case SlotAction.AB1:
                    ABHandler.AB1(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                case SlotAction.AB2:
                    ABHandler.AB2(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                case SlotAction.ITEM:
                    ABHandler.ItemAB(spotID + 1, AllyTarget, EnemyTarget);
                    break;
                default:
                    Debug.LogWarning("Invalid SlotAction for monster: " + monster.MonName);
                    break;
            }
        }
    }


    public void CheckForWin()
    {
        if (MM1.Dead)
        {
            if (MM2.Dead)
            {
                if (MM3.Dead)
                {
                    Debug.Log("Loser!"); //YOU LOSE
                    return;
                }
            }
        }
        if (MM4.Dead)
        {
            if(MM5.Dead)
            {
                if (MM6.Dead)
                {
                    Debug.Log("Winner!"); //YOU WIN
                    return;
                }
            }
        }
        RoundCount(); // DO ALL THE TURN TIMERS/CD reductions etc
        S1AB2CD.text = MM1.AB2CD.ToString(); S2AB2CD.text = MM2.AB2CD.ToString(); S3AB2CD.text = MM3.AB2CD.ToString();
        S1IABCD.text = MM1.ItemCD.ToString(); S2IABCD.text = MM1.ItemCD.ToString(); S3IABCD.text = MM1.ItemCD.ToString();
        if (MM1.ItemCD < 1) { S1IABCD.text = " "; } if (MM2.ItemCD < 1) { S2IABCD.text = " "; } if (MM3.ItemCD < 1) { S3IABCD.text = " "; }
        if (MM1.ItemCD < 1) { S1AB2CD.text = " "; } if (MM2.ItemCD < 1) { S2AB2CD.text = " "; } if (MM3.ItemCD < 1) { S3AB2CD.text = " "; }
        PlayerTurn();
    }

    public void RoundCount()
    {
        MM1.ItemCD--; MM1.AB2CD--; MM1.AB2Disabled--; MM1.ItemDisabled--; MM1.Invulnerable--;
        MM2.ItemCD--; MM2.AB2CD--; MM2.AB2Disabled--; MM2.ItemDisabled--; MM2.Invulnerable--;
        MM3.ItemCD--; MM3.AB2CD--; MM3.AB2Disabled--; MM3.ItemDisabled--; MM3.Invulnerable--;
        MM4.ItemCD--; MM4.AB2CD--; MM4.AB2Disabled--; MM4.ItemDisabled--; MM4.Invulnerable--;
        MM5.ItemCD--; MM5.AB2CD--; MM5.AB2Disabled--; MM5.ItemDisabled--; MM5.Invulnerable--;
        MM6.ItemCD--; MM6.AB2CD--; MM6.AB2Disabled--; MM6.ItemDisabled--; MM6.Invulnerable--;
    }

    public void SetTarget(int ID)
    {
        if (ID > 3)
        {
            if (Monsters[ID].Targettable) { EnemyTarget = ID; UpdateMonUI(); }
        }
        if (ID <= 3)
        {
            if (Monsters[ID].Targettable) { AllyTarget = ID; UpdateMonUI(); }
        }
    }

    public int SetEnemyAITarget()
    {
        int targ = Random.Range(1, 3);
        if (Monsters[targ].Targettable) { return targ; }
        if (Monsters[1].Targettable) { return 1; }
        if (Monsters[2].Targettable) { return 2; }
        if (Monsters[3].Targettable) { return 3; }

        Debug.Log("EnemyAITarget Failure, targetting ID Spot 0");
        return 0;
    }

    public void UpdateMonUI()
    {
        MM1.UpdateUI(); MM2.UpdateUI(); MM3.UpdateUI(); MM4.UpdateUI(); MM5.UpdateUI(); MM6.UpdateUI();
    }

    public SlotAction AISlotAction(int ID)
    {
        if (ID == 4)
        {
            if (!MM4.Dead)
            {
                if (!MM4.isItemPassive)
                {
                    if (MM4.ItemCD < 1)
                    {
                        return SlotAction.ITEM;
                    }
                }
                if (MM4.AB2CD < 1)
                {
                    return SlotAction.AB2;
                }
                return SlotAction.AB1;
            }
            return SlotAction.NULL;
        }
        if (ID == 5)
        {
            if (!MM5.Dead)
            {
                if (!MM5.isItemPassive)
                {
                    if (MM5.ItemCD < 1)
                    {
                        return SlotAction.ITEM;
                    }
                }
                if (MM5.AB2CD < 1)
                {
                    return SlotAction.AB2;
                }
                return SlotAction.AB1;
            }
            return SlotAction.NULL;
        }
        if (ID == 6)
        {
            if (!MM6.Dead)
            {
                if (!MM6.isItemPassive)
                {
                    if (MM6.ItemCD < 1)
                    {
                        return SlotAction.ITEM;
                    }
                }
                if (MM6.AB2CD < 1)
                {
                    return SlotAction.AB2;
                }
                return SlotAction.AB1;
            }
            return SlotAction.NULL;
        }

        Debug.Log("AISlotAction function failed");
        return SlotAction.NULL;
    }

    public void EnemyTurn()
    {
        EnemyAITarget = SetEnemyAITarget();
        // If they can use their item, they do, then they see if they can use AB2, if not they use AB1
        //Monster 4
        Mon4 = AISlotAction(4);
        //Monster 5
        Mon5 = AISlotAction(5);
        //Monster 6
        Mon6 = AISlotAction(6);
    }

    public void StatusUpdate(string StatusText)
    {
        StatusBG.GetComponent<Animator>().Play("FadeInStatusBG");
        StatusTextObj.text = StatusText;
    }


    void MonsterInit()
    {
            MM0 = MonGO0.GetComponentInChildren<MiniMonster>();
            MM1 = MonGO1.GetComponentInChildren<MiniMonster>();
            MM1.setupMon(GrabMonStat(player.MiniMonS1ID), 1);
            MM2 = MonGO2.GetComponentInChildren<MiniMonster>();
            MM2.setupMon(GrabMonStat(player.MiniMonS2ID), 2);
            MM3 = MonGO3.GetComponentInChildren<MiniMonster>();
            MM3.setupMon(GrabMonStat(player.MiniMonS3ID), 3);
            MM4 = MonGO4.GetComponentInChildren<MiniMonster>();
            MM4.setupMon(GrabMonStat((int)Random.Range(1, MonIDList.Length)), 4);
            MM5 = MonGO5.GetComponentInChildren<MiniMonster>();
            MM5.setupMon(GrabMonStat((int)Random.Range(1, MonIDList.Length)), 5);
            MM6 = MonGO6.GetComponentInChildren<MiniMonster>();
            MM6.setupMon(GrabMonStat((int)Random.Range(1, MonIDList.Length)), 6);
    }
    void ItemInit()
    {
        M1I = itemIDList[player.MMS1ItemID];
        MM1.InitializeItem(itemIDList[M1I.ID]);
        M2I = itemIDList[player.MMS2ItemID];
        MM2.InitializeItem(itemIDList[M2I.ID]);
        M3I = itemIDList[player.MMS3ItemID];
        MM3.InitializeItem(itemIDList[M3I.ID]);
        M4I = itemIDList[(int)Random.Range(1, itemIDList.Length)];
        MM4.InitializeItem(itemIDList[M4I.ID]);
        M5I = itemIDList[(int)Random.Range(1, itemIDList.Length)];
        MM5.InitializeItem(itemIDList[M5I.ID]);
        M6I = itemIDList[(int)Random.Range(1, itemIDList.Length)];
        MM6.InitializeItem(itemIDList[M6I.ID]);
    }

    public StatBlockMon GrabMonStat(int monID)
    {
        if (monID <= MonIDList.Length)
        {
            return MonIDList[monID];
        }
        else return null;
    }

    public ItemStatBlock GrabItemStat(int itemID)
    {
        if (itemID <= itemIDList.Length)
        {
            return itemIDList[itemID];
        }
        else return null;
    }

}

I will take any feedback on the script as a whole. I am still learning, this is a personal project that I’m trying to learn from. I’m sure there’s a lot that could be done better or more efficiently. The problem parts I’ve tried understanding with help from ChatGPT, but I’m getting different errors no matter how I try to do it.

After taking a small break and looking over it again I saw some glaring issues in the ActionState / ActionSequence, so I know that they are providing a host of problems like running the Coroutine multiple times and CheckingForWin too often, I’m sorting that out but still as I’m working on it I keep getting Index exception errors with my foreach in ActionSequence and Im not sure why

Here are some notes on IndexOutOfRangeException and ArgumentOutOfRangeException:

http://plbm.com/?p=236

Steps to success:

  • find which collection it is and what line of code accesses it <— (critical first step!)
  • find out why it has fewer items than you expect
  • fix whatever logic is making the indexing value exceed the collection size
  • remember that a collection with ZERO elements cannot be indexed at all: it is empty
  • remember you might have more than one instance of this script in your scene/prefab
  • remember the collection may be used in more than one location in the code
  • remember that indices start at ZERO (0) and go to the count / length minus 1.

This means with three (3) elements in your collection, they are numbered 0, 1, and 2 only.

o Faking an array starting at 1 instead of 0 is almost never worth it. For example, sort assumes it starts at 0 (which is fixable, but why make it more complicated). Just use slots 0-5 and you’ll get used to it.

o Put the monsters in an array. Instead of MM1 to MM6 and MI1 to MI6, just make size-6 arrays MM and MI where MM[0] is the old MM1 and MM[5] is the old MM6. In other words, you made an array to sort them – just do that from the start with all of your lists. That lets you use loops for lots of things, like finding the fastest, or copying them into another array, sorting by speed, or setting slotAction to NULL for everyone. That tends to reduce mistakes since you can’t accidentally mess-up just MM3 – broken loops tend to make them all be right or wrong.

1 Like