/// <summary>
 /// Constructor
 /// </summary>
 /// <param name="nextState">the state after this state</param>
 /// <param name="startSkyColor">the starting sky color for this state</param>
 /// <param name="endSkyColor">the ending sky color for this state</param>
 /// <param name="timeInState">the time to spend in this state</param>
 /// <param name="startDarkness">the starting darkness color for this state</param>
 /// <param name="endDarkness">the ending darkness color for this state</param>
 /// <param name="startVolume">the starting background volume for this state (defaults to maximum)</param>
 /// <param name="endVolume">the ending background volume for this state (defaults to maximum)</param>
 /// <param name="endAudio">the background sound to switch to at the end of this state (default to null, no change)</param>
 public SkyState(SkyStateType nextState, Color startSkyColor, Color endSkyColor, float timeInState, Color startDarkness, Color endDarkness,
     float startVolume = Constants.BGM_MAX_VOLUME, float endVolume = Constants.BGM_MAX_VOLUME, AudioClip endAudio = null)
 {
     this.nextState = nextState;
     this.startSkyColor = startSkyColor;
     this.endSkyColor = endSkyColor;
     this.timeInState = timeInState;
     this.startDarkness = startDarkness;
     this.endDarkness = endDarkness;
     this.startVolume = startVolume;
     this.endVolume = endVolume;
     this.endAudio = endAudio;
 }
    /// <summary>
    /// Start is called once on object creation
    /// </summary>
    private void Start()
    {
        // SEED IS HARDCODED IN DEBUG MODE
        if (debugMode)
        { Random.seed = 71; }

        instance = this;

        // Sets up the sky state dictionary
        currSkyState = SkyStateType.Day;
        skyStates = new Dictionary<SkyStateType, SkyState>();
        skyStates.Add(SkyStateType.Dawn, new SkyState(SkyStateType.DawnToDay, Constants.ORANGE_SKY_COLOR, Constants.ORANGE_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.ORANGE_TIME_PCT, Constants.ORANGE_DARKNESS_COLOR, Constants.ORANGE_DARKNESS_COLOR, Constants.BGM_MAX_VOLUME, 0, daySound));
        skyStates.Add(SkyStateType.DawnToDay, new SkyState(SkyStateType.Day, Constants.ORANGE_SKY_COLOR, Constants.DAY_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.FADE_TIME_PCT, Constants.ORANGE_DARKNESS_COLOR, Constants.DAY_DARKNESS_COLOR, 0, Constants.BGM_MAX_VOLUME));
        skyStates.Add(SkyStateType.Day, new SkyState(SkyStateType.DayToDusk, Constants.DAY_SKY_COLOR, Constants.DAY_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.DAY_TIME_PCT, Constants.DAY_DARKNESS_COLOR, Constants.DAY_DARKNESS_COLOR));
        skyStates.Add(SkyStateType.DayToDusk, new SkyState(SkyStateType.Dusk, Constants.DAY_SKY_COLOR, Constants.ORANGE_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.FADE_TIME_PCT, Constants.DAY_DARKNESS_COLOR, Constants.ORANGE_DARKNESS_COLOR));
        skyStates.Add(SkyStateType.Dusk, new SkyState(SkyStateType.DuskToNight, Constants.ORANGE_SKY_COLOR, Constants.ORANGE_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.ORANGE_TIME_PCT, Constants.ORANGE_DARKNESS_COLOR, Constants.ORANGE_DARKNESS_COLOR, Constants.BGM_MAX_VOLUME, 0, nightSound));
        skyStates.Add(SkyStateType.DuskToNight, new SkyState(SkyStateType.Night, Constants.ORANGE_SKY_COLOR, Constants.NIGHT_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.ORANGE_TIME_PCT, Constants.ORANGE_DARKNESS_COLOR, Constants.NIGHT_DARKNESS_COLOR, 0, Constants.BGM_MAX_VOLUME));
        skyStates.Add(SkyStateType.Night, new SkyState(SkyStateType.NightToDawn, Constants.NIGHT_SKY_COLOR, Constants.NIGHT_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.NIGHT_TIME_PCT, Constants.NIGHT_DARKNESS_COLOR, Constants.NIGHT_DARKNESS_COLOR));
        skyStates.Add(SkyStateType.NightToDawn, new SkyState(SkyStateType.Dawn, Constants.NIGHT_SKY_COLOR, Constants.ORANGE_SKY_COLOR, Constants.CYCLE_TIME *
            Constants.FADE_TIME_PCT, Constants.NIGHT_DARKNESS_COLOR, Constants.ORANGE_DARKNESS_COLOR));

        // Starts the sky timer
        skyTimer = new Timer(skyStates[currSkyState].TimeInState);
        skyTimer.Register(SkyTimerFinished);
        skyTimer.Start();

        parallaxBackgrounds = GameObject.FindGameObjectsWithTag(Constants.PARALLAX_BACKGROUND_TAG);
        starrySky.transform.rotation = Quaternion.Euler(0, 0, Constants.SKY_START_ROT);

        BGM = GetComponent<AudioSource>();
        BGM.volume = Constants.BGM_MAX_VOLUME;

        elevationWeight = Constants.ELEVATION_CHANGE_WEIGHT + Random.Range(-Constants.ELEVATION_CHANGE_OFFSET, Constants.ELEVATION_CHANGE_OFFSET); ;
        heightDifferenceWeight = Constants.HEIGHT_DIFFERENCE_WEIGHT + Random.Range(-Constants.HEIGHT_DIFFERENCE_OFFSET, Constants.HEIGHT_DIFFERENCE_OFFSET);

        if (debugMode)
        {
            Debug.Log("Elevation weight: " + elevationWeight);
            Debug.Log("Height Difference Weight: " + heightDifferenceWeight);
        }

        // Generates the map
        GenerateTerrain();
        GenerateParallaxObjects();

        // Spawns castles
        int enemyCastlePos = levels.Length - (Constants.PLATFORM_LENGTH - 4);
        Instantiate(GameManager.Instance.CastlePrefabs[GameManager.Instance.CurrentLoadedKingdom]).GetComponent<CastleScript>().Initialize(enemyCastleHealth,
            Constants.ENEMY_TAG, new Vector2(enemyCastlePos, levels[enemyCastlePos] + 1));
        Instantiate(GameManager.Instance.CastlePrefabs[GameManager.Instance.GetPrevKingdom()]).GetComponent<CastleScript>().Initialize(playerCastleHealth,
            Constants.PLAYER_TAG, playerCastleLocation);

        // Sets up the defeat darkness
        defeatDarknessTimer = new Timer(Constants.DARKNESS_TIMER);
        defeatDarknessTimer.Register(DefeatDarknessTimerFinished);

        // Creates the player and HUD
        player = (GameObject)Instantiate(GameManager.Instance.PlayerPrefabs[GameManager.Instance.CurrentSave.PlayerType],
            playerCastleLocation, transform.rotation);
        GameObject hud;
        switch (GameManager.Instance.CurrentSave.PlayerType)
        {
            case CharacterType.Ranger:
                hud = Instantiate<GameObject>(rangerHUD);
                break;
            case CharacterType.Mage:
                hud = Instantiate<GameObject>(mageHUD);
                break;
            case CharacterType.Warrior:
                hud = Instantiate<GameObject>(warriorHUD);
                break;
            default:
                hud = Instantiate<GameObject>(rangerHUD);
                break;
        }
        hud.transform.SetParent(hudParent, false);
        HUDScript hudScript = hud.GetComponent<HUDScript>();
        player.GetComponent<PlayerScript>().Initialize(healthBar, energyBar, hudScript.GcdBars, hudScript.TimerBars, hudScript.SecondaryCDBar,
            hudScript.PowerCDBar, hudScript.SpecialCDBar);
        energyBar.color = player.GetComponent<CharacterScript>().EnergyColor;
        playerLocation = player.transform.position;
    }
    /// <summary>
    /// Handles the sky timer finishing
    /// </summary>
    private void SkyTimerFinished()
    {
        // Changes background sound if needed
        if (skyStates[currSkyState].EndAudio != null)
        {
            BGM.Stop();
            BGM.clip = skyStates[currSkyState].EndAudio;
            BGM.Play();
        }

        // Switches state and restarts timer
        currSkyState = skyStates[currSkyState].NextState;
        skyTimer.TotalSeconds = skyStates[currSkyState].TimeInState;
        skyTimer.Start();
    }