void ButtonArray::ReorderOutAnimations(int newFirstOutButtonId)
{
    // If we only have one single visible button,
    // no sense worrying about animation order.
    if (visibleButtonCount == 1)
    {
        ppVisibleButtons[0]->SetOutAnimationDelay(0);
        return;
    }

    int firstOutPosition = -1;

    for (unsigned int i = 0; i < visibleButtonCount; i++)
    {
        if (newFirstOutButtonId == ppVisibleButtons[i]->GetId())
        {
            firstOutPosition = i;
            break;
        }
    }

    if (firstOutPosition == -1)
    {
        throw MLIException("Something very bad has happened - a button not being shown has been clicked.");
    }

    for (unsigned int i = 0; i < visibleButtonCount; i++)
    {
        ppVisibleButtons[(firstOutPosition + i) % visibleButtonCount]->SetOutAnimationDelay(ButtonCascadeDelay * i);
    }
}
Staging::HeightMap * Staging::HeightMap::LoadFromXml(XmlReader *pReader)
{
    HeightMap *pHeightMap = NULL;

    if (pReader->ElementExists("ParabolicHeightMap"))
    {
        pHeightMap = new ParabolicHeightMap(pReader);
    }
    else
    {
        throw MLIException("Unexpected height map type.");
    }

    return pHeightMap;
}
AnimationSound * AnimationSound::LoadSoundFromXml(XmlReader *pReader)
{
    if (pReader->ElementExists("HoofstepSound"))
    {
        return new HoofstepSound(pReader);
    }
    else if (pReader->ElementExists("SpecifiedSound"))
    {
        return new SpecifiedSound(pReader);
    }
    else
    {
        throw MLIException("Invalid sound type.");
    }
}
Condition::ConditionCriterion * Condition::ConditionCriterion::CreateFromXml(XmlReader *pReader)
{
    if (pReader->ElementExists("FlagSetCondition"))
    {
        return new Condition::FlagSetCondition(pReader);
    }
    else if (pReader->ElementExists("EvidencePresentCondition"))
    {
        return new Condition::EvidencePresentCondition(pReader);
    }
    else if (pReader->ElementExists("PartnerPresentCondition"))
    {
        return new Condition::PartnerPresentCondition(pReader);
    }
    else if (pReader->ElementExists("ConversationLockedCondition"))
    {
        return new Condition::ConversationLockedCondition(pReader);
    }
    else if (pReader->ElementExists("TutorialsEnabledCondition"))
    {
        return new Condition::TutorialsEnabledCondition(pReader);
    }
    else if (pReader->ElementExists("AndCondition"))
    {
        return new Condition::AndCondition(pReader);
    }
    else if (pReader->ElementExists("OrCondition"))
    {
        return new Condition::OrCondition(pReader);
    }
    else if (pReader->ElementExists("NotCondition"))
    {
        return new Condition::NotCondition(pReader);
    }
    else
    {
        throw MLIException("Invalid criterion type.");
    }
}
string Dialog::ParseEvents(int lineOffset, const string &stringToParse, string *pStringToPrependOnNext)
{
    string parsedString = stringToParse;
    *pStringToPrependOnNext = "";

    while (parsedString.find('{') != string::npos && parsedString.find('}') != string::npos)
    {
        int eventStart = parsedString.find('{');
        int eventEnd = parsedString.find('}');

        deque<string> eventComponents = split(parsedString.substr(eventStart + 1, eventEnd - eventStart - 1), ':');
        string replacementText = "";
        transform(eventComponents[0].begin(), eventComponents[0].end(), eventComponents[0].begin(), ::tolower);
        string testString = eventComponents[0];

        if (testString == "speed")
        {
            double newMillisecondsPerCharacterUpdate = strtod(eventComponents[1].c_str(), NULL);
            AddSpeedChangePosition(lineOffset + eventStart, newMillisecondsPerCharacterUpdate);
        }
        else if (testString == "emotion")
        {
            string newEmotion = eventComponents[1];
            AddEmotionChangePosition(lineOffset + eventStart, newEmotion);
        }
        else if (testString == "otheremotion")
        {
            string newOtherEmotion = eventComponents[1];
            AddEmotionOtherChangePosition(lineOffset + eventStart, newOtherEmotion);
        }
        else if (testString == "pause")
        {
            double millisecondDuration = strtod(eventComponents[1].c_str(), NULL);
            AddPausePosition(lineOffset + eventStart, millisecondDuration);
        }
        else if (testString == "audiopause")
        {
            double millisecondDuration = strtod(eventComponents[1].c_str(), NULL);
            AddAudioPausePosition(lineOffset + eventStart, millisecondDuration);
         }
        else if (testString == "mouth")
        {
            transform(eventComponents[1].begin(), eventComponents[1].end(), eventComponents[1].begin(), ::tolower);
            bool mouthIsOn = eventComponents[1] == "on";
            AddMouthChangePosition(lineOffset + eventStart, mouthIsOn);
        }
        else if (testString == "fullstop")
        {
            if (eventComponents.size() > 1)
            {
                replacementText = eventComponents[1];
            }
            else
            {
                replacementText = ".";
            }

            AddMouthChangePosition(lineOffset + eventStart, false /* mouthIsOn */);
            AddPausePosition(lineOffset + eventStart + 1, FullStopMillisecondPause);

            if (eventEnd + 1 == (int)parsedString.length())
            {
                *pStringToPrependOnNext = "{Mouth:On}";
            }
            else
            {
                AddMouthChangePosition(lineOffset + eventStart + 1, true /* mouthIsOn */);
            }
        }
        else if (testString == "halfstop")
        {
            if (eventComponents.size() > 1)
            {
                replacementText = eventComponents[1];
            }
            else
            {
                replacementText = ",";
            }

            AddMouthChangePosition(lineOffset + eventStart, false /* mouthIsOn */);
            AddPausePosition(lineOffset + eventStart + 1, HalfStopMillisecondPause);

            if (eventEnd + 1 == (int)parsedString.length())
            {
                *pStringToPrependOnNext = "{Mouth:On}";
            }
            else
            {
                AddMouthChangePosition(lineOffset + eventStart + 1, true /* mouthIsOn */);
            }
        }
        else if (testString == "ellipsis")
        {
            replacementText = "...";

            AddMouthChangePosition(lineOffset + eventStart, false /* mouthIsOn */);
            AddPausePosition(lineOffset + eventStart + 1, EllipsisMillisecondPause);
            AddPausePosition(lineOffset + eventStart + 2, EllipsisMillisecondPause);
            AddPausePosition(lineOffset + eventStart + 3, EllipsisMillisecondPause);

            if (eventEnd + 1 == (int)parsedString.length())
            {
                *pStringToPrependOnNext = "{Mouth:On}";
            }
            else
            {
                AddMouthChangePosition(lineOffset + eventStart + 3, true /* mouthIsOn */);
            }
        }
        else if (testString == "aside")
        {
            int currentIndex = lineOffset + eventStart;
            Interval interval = Interval(textColorStack.back(), lastTextColorChangeIndex, currentIndex);

            textIntervalList.push_back(interval);
            AddMouthChangePosition(currentIndex, false /* mouthIsOn */);

            textColorStack.push_back(TextColorAside);
            lastTextColorChangeIndex = currentIndex;
        }
        else if (testString == "/aside")
        {
            if (textColorStack.back() == TextColorAside)
            {
                int currentIndex = lineOffset + eventStart;
                Interval interval = Interval(TextColorAside, lastTextColorChangeIndex, currentIndex);

                if (eventEnd + 1 == (int)parsedString.length())
                {
                    *pStringToPrependOnNext = "{Mouth:On}";
                }
                else
                {
                    AddMouthChangePosition(currentIndex, true /* mouthIsOn */);
                }

                textIntervalList.push_back(interval);
                textColorStack.pop_back();
                lastTextColorChangeIndex = currentIndex;
            }
        }
        else if (testString == "emphasis")
        {
            int currentIndex = lineOffset + eventStart;
            Interval interval = Interval(textColorStack.back(), lastTextColorChangeIndex, currentIndex);

            textIntervalList.push_back(interval);

            textColorStack.push_back(TextColorEmphasis);
            lastTextColorChangeIndex = currentIndex;
        }
        else if (testString == "/emphasis")
        {
            if (textColorStack.back() == TextColorEmphasis)
            {
                int currentIndex = lineOffset + eventStart;
                Interval interval = Interval(TextColorEmphasis, lastTextColorChangeIndex, currentIndex);

                textIntervalList.push_back(interval);
                textColorStack.pop_back();

                lastTextColorChangeIndex = currentIndex;
            }
        }
        else if (testString == "playsound")
        {
            string soundId = eventComponents[1];
            AddPlaySoundPosition(lineOffset + eventStart, soundId);
        }
        else if (testString == "shake")
        {
            AddShakePosition(lineOffset + eventStart);
        }
        else if (testString == "screenshake")
        {
            double shakeIntensity = min(max(strtod(eventComponents[1].c_str(), NULL), 0.0), 100.0);
            AddScreenShakePosition(lineOffset + eventStart, shakeIntensity);
        }
        else if (testString == "nextframe")
        {
            AddNextFramePosition(lineOffset + eventStart);
        }
        else if (testString == "damageplayer")
        {
            AddPlayerDamagedPosition(lineOffset + eventStart);
        }
        else if (testString == "damageopponent")
        {
            AddOpponentDamagedPosition(lineOffset + eventStart);
        }
        else if (testString == "playbgm")
        {
            string bgmId = eventComponents[1];
            AddPlayBgmPosition(lineOffset + eventStart, bgmId);
        }
        else if (testString == "playbgmpermanently")
        {
            string bgmId = eventComponents[1];
            AddPlayBgmPermanentlyPosition(lineOffset + eventStart, bgmId);
        }
        else if (testString == "stopbgm")
        {
            AddStopBgmPosition(lineOffset + eventStart);
        }
        else if (testString == "stopbgmpermanently")
        {
            AddStopBgmPermanentlyPosition(lineOffset + eventStart);
        }
        else if (testString == "stopbgminstantly")
        {
            AddStopBgmInstantlyPosition(lineOffset + eventStart);
        }
        else if (testString == "stopbgminstantlypermanently")
        {
            AddStopBgmInstantlyPermanentlyPosition(lineOffset + eventStart);
        }
        else if (testString == "zoom")
        {
            AddZoomPosition(lineOffset + eventStart);
        }
        else if (testString == "endzoom")
        {
            AddEndZoomPosition(lineOffset + eventStart);
        }
        else if (testString == "beginbreakdown")
        {
            AddBeginBreakdownPosition(lineOffset + eventStart);
        }
        else if (testString == "endbreakdown")
        {
            AddEndBreakdownPosition(lineOffset + eventStart);
        }
        else
        {
            throw MLIException("Unknown event.");
        }

        parsedString = parsedString.substr(0, eventStart) + replacementText + parsedString.substr(eventEnd + 1);
    }

    return parsedString;
}
Condition::ConditionCriterion * Condition::ConditionCriterion::LoadFromStagingCriterion(Staging::Condition::ConditionCriterion *pStagingConditionCriterion)
{
    switch (pStagingConditionCriterion->GetType())
    {
    case Staging::ConditionCriterionType_FlagSet:
        {
            return new Condition::FlagSetCondition(static_cast<Staging::Condition::FlagSetCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_EvidencePresent:
        {
            return new Condition::EvidencePresentCondition(static_cast<Staging::Condition::EvidencePresentCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_PartnerPresent:
        {
            return new Condition::PartnerPresentCondition(static_cast<Staging::Condition::PartnerPresentCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_ConversationLocked:
        {
            return new Condition::ConversationLockedCondition(static_cast<Staging::Condition::ConversationLockedCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_TutorialsEnabled:
        {
            return new Condition::TutorialsEnabledCondition(static_cast<Staging::Condition::TutorialsEnabledCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_And:
        {
            return new Condition::AndCondition(static_cast<Staging::Condition::AndCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_Or:
        {
            return new Condition::OrCondition(static_cast<Staging::Condition::OrCondition *>(pStagingConditionCriterion));
        }

    case Staging::ConditionCriterionType_Not:
        {
            // We only want negation to be occurring at the lowest level, so if we're negating an AND or an OR,
            // we'll get rid of that by propagating the negation down to the lower levels and flipping the Boolean operator.
            Condition::ConditionCriterion *pReturnCondition = NULL;
            Staging::Condition::NotCondition *pNotCondition = static_cast<Staging::Condition::NotCondition *>(pStagingConditionCriterion);

            if (pNotCondition->pCriterion->GetType() == Staging::ConditionCriterionType_And)
            {
                Staging::Condition::AndCondition *pChildAndCondition = static_cast<Staging::Condition::AndCondition *>(pNotCondition->pCriterion);

                Staging::Condition::NotCondition *pNegatedFirstCondition = new Staging::Condition::NotCondition(pChildAndCondition->pFirstCriterion);
                Staging::Condition::NotCondition *pNegatedSecondCondition = new Staging::Condition::NotCondition(pChildAndCondition->pSecondCriterion);

                Staging::Condition::OrCondition *pEquivalentOrCondition = new Staging::Condition::OrCondition(pNegatedFirstCondition, pNegatedSecondCondition);

                pChildAndCondition->pFirstCriterion = NULL;
                pChildAndCondition->pSecondCriterion = NULL;

                pReturnCondition = new Condition::OrCondition(pEquivalentOrCondition);

                delete pEquivalentOrCondition;
            }
            else if (pNotCondition->pCriterion->GetType() == Staging::ConditionCriterionType_Or)
            {
                Staging::Condition::OrCondition *pChildOrCondition = static_cast<Staging::Condition::OrCondition *>(pNotCondition->pCriterion);

                Staging::Condition::NotCondition *pNegatedFirstCondition = new Staging::Condition::NotCondition(pChildOrCondition->pFirstCriterion);
                Staging::Condition::NotCondition *pNegatedSecondCondition = new Staging::Condition::NotCondition(pChildOrCondition->pSecondCriterion);

                Staging::Condition::AndCondition *pEquivalentAndCondition = new Staging::Condition::AndCondition(pNegatedFirstCondition, pNegatedSecondCondition);

                pChildOrCondition->pFirstCriterion = NULL;
                pChildOrCondition->pSecondCriterion = NULL;

                pReturnCondition = new Condition::AndCondition(pEquivalentAndCondition);

                delete pEquivalentAndCondition;
            }
            else
            {
                pReturnCondition = new Condition::NotCondition(static_cast<Staging::Condition::NotCondition *>(pStagingConditionCriterion));
            }

            return pReturnCondition;
        }

    default:
        throw MLIException("Invalid criterion type.");
    }
}