Objectives: UI

This is the manager that displays all active objectives for the client. It is written in the exp language created by John Haggerty.

Exp Cheatsheet:

  • aslongas - keeps the current scope alive "as long as" the expression remains true
  • whenever - Runs the following statement repeatedly, once each time the expression is true, ending the statement when it becomes false, and waiting for it to become true again when the statement ends
  • meantime - Whenever may be paired with "meantime" to provide a statement that will be run when the expression is false
  • thread - Exp "threads" are asynchronous function calls. They are akin to coroutines in some other languages: they take turns executing and so are not truly asynchronous.
  • localthread - A localthread is a thread that is guaranteed to end when the scope it is defined in is exited. You can customize the scope it is attached to with the @ symbol.
  • cleanup - After a cleanup block is passed during execution, the statement (or block of statements) after cleanup is guaranteed to run when the scope is exited.
// Currently only supports one mission at a time
shared maywait fn APlayer::UIMissionThread()
{
    // waits for a mission instance to be active
    watchobj< AMissionBase? > missionInstanceWatch = watchexpr GetMissionForUI()
    scope MissionLoop // defines a scope used by aslongas, endwhen, etc
    loop
    {
        const AMissionBase? mission = missionInstanceWatch.val
        watchobj< bool > shouldDisplayThisMission = watchexpr missionInstanceWatch.val == mission
        if ( !mission )
        {
            waitwhile shouldDisplayThisMission
            continue
        }

        localthread @MissionLoop mission.WhileMissionActiveForPlayerHud( this )
        
        // Play the intro animation for a mission
        UIMissionSequence_Intro( mission )

        bool isFirstObjective = true

        watchobj< AObjectiveBase? > activeObjectiveWatch = watchexpr mission.GetActiveObjective()

        while ( shouldDisplayThisMission )
        scope ObjectiveLoop
        {
            // gets the active objective, and can use exp magic to detect if it changes prematurely
            const AObjectiveBase? activeObjective = activeObjectiveWatch.val

            watchobj< bool > shouldDisplayThisObjective = watchexpr shouldDisplayThisMission && activeObjectiveWatch.val == activeObjective

            if ( !activeObjective )
            {
                waitwhile shouldDisplayThisObjective
                continue
            }

            localthread @ObjectiveLoop activeObjective.WhileObjectiveActiveForPlayerHud( this )

            // play the animations for introducing a new objective
            if ( isFirstObjective )
                ObjectiveSequence_Intro( activeObjective )
            else
                ObjectiveSequence_IntroAdditional( activeObjective )

            isFirstObjective = false

            localthread
            {
                // update objective info while its active.
                ObjectiveSequence_Update( activeObjective )

                loop
                {
                    waitfor activeObjective.objectiveUpdated

                    ObjectiveSequence_Update( activeObjective )
                }
            }

            waitfor !shouldDisplayThisObjective

            // Update and Completed OnRep's come in on the same frame, out of order. So wait one frame to catch an update if its there.
            WaitFrame()

            if ( activeObjective.IsObjectiveCompleted() )
            {
                waitfor !activeObjective.GetIsUpdatingInHud()
                ObjectiveSequence_Completed( activeObjective )
            }
        }

        // outro for the mission
        if ( mission.IsMissionCompleted() )
            UIMissionSequence_Completed( mission.IsMissionCompletedSuccessfully() )
    }
}