Runtime support for dialogue lines.


§1. Runtime representation. Values representing lines are enumerated from 1 to NO_DIALOGUE_LINES. Lines have certain properties stored by Inform just as it would for any other enumerated kind, and which this kit deals with by calls to GProperty and WriteGProperty: in particular, performed and recurring.

In addition, though, the compiler (or more accurately the linker) generates a table called TableOfDialogueLines. If dl is an enumerated value then TableOfDialogueLines-->dl is the address of the metadata table for dl. (And TableOfDialogueLines-->0 is set to NO_DIALOGUE_LINES.)

These line data arrays consist of 8 words, as follows:

All of this must of course match what is compiled in Dialogue Line Instances (in runtime).

Constant AVAILABILITY_DLMETADATA = 0;
Constant SPEAKER_DLMETADATA      = 1;
Constant INTERLOCUTOR_DLMETADATA = 2;
Constant SPEECH_DLMETADATA       = 3;
Constant STYLE_DLMETADATA        = 4;
Constant MENTIONING_DLMETADATA   = 5;
Constant ACTIONS_DLMETADATA      = 6;
Constant FLAGS_DLMETADATA        = 7;

Constant NARRATION_FLAG_DLMETADATA = 1;
Constant NONVERBAL_FLAG_DLMETADATA = 2;

Constant PLAYER_CONVERSATIONALIST = 1;

§2. Extracting line data.

[ DirectorLineContent dl text linedata;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) return text;
    linedata = TableOfDialogueLines-->dl;
    CopyPV(text, linedata-->SPEECH_DLMETADATA);
    return text;
];

[ DirectorLineNarrated dl linedata;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    linedata = TableOfDialogueLines-->dl;
    if ((linedata-->FLAGS_DLMETADATA) & NARRATION_FLAG_DLMETADATA) rtrue;
    rfalse;
];

[ DirectorLineNonverbal dl linedata;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    linedata = TableOfDialogueLines-->dl;
    if ((linedata-->FLAGS_DLMETADATA) & NONVERBAL_FLAG_DLMETADATA) rtrue;
    rfalse;
];

[ DirectorLineAvailable dl linedata fn;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    linedata = TableOfDialogueLines-->dl;
    fn = linedata-->AVAILABILITY_DLMETADATA;
    if (fn) return fn();
    rtrue;
];

§3. Determining the speakers. As noted above, the speaker and interlocutor may not be explicitly determined in the data given to us, and there are times when we have to go to some trouble to work out who they are.

The algorithm below is not very fast if there are large numbers of objects: on the other hand, it is run only when a line of dialogue is being delivered, so it cannot be needed often, and the slowness may not matter. Still, there's scope to optimise this.

Global director_speaker_list;
[ DirectorSelectConverser val select_speaker nonverbal
    speaker len i best best_score this_score x best_count;
    if (val == PLAYER_CONVERSATIONALIST) return player;
    if (metaclass(val) == Object) return val;
    if (metaclass(val) == Routine) {
        if (director_speaker_list == 0) {
            director_speaker_list = CreatePV(LIST_OF_TY);
            WritePVField(director_speaker_list, LIST_ITEM_KOV_F, OBJECT_TY);
        } else {
            LIST_OF_TY_SetLength(director_speaker_list, 0);
        }
        x = val(0);
        if (x == false) rfalse;
        if (x == true) {
            objectloop (speaker ofclass K2_thing)
                if ((speaker ~= player) && (val(speaker)))
                    LIST_OF_TY_InsertItem(director_speaker_list, speaker);
            len = LIST_OF_TY_GetLength(director_speaker_list);
            if (len == 0) return nothing;
            best = -1;
            best_score = -1;
            best_count = 0;
            for (i=1: i<=len: i++) {
                speaker = LIST_OF_TY_GetItem(director_speaker_list, i, nonverbal);
                this_score = DirectorScoreConverser(speaker, select_speaker, nonverbal);
                if (this_score > best_score) {
                    best = speaker;
                    best_score = this_score;
                    best_count = 1;
                } else if (this_score == best_score) {
                    best_count++;
                }
                print (name) speaker, " scores ", this_score, "^";
            }
            if (best_count < 1) "*** impossibly low ***";
            print best_count, " option(s)^";
            if (best_count == 1) return best;
            x = random(best_count);
            for (i=1: i<=len: i++) {
                speaker = LIST_OF_TY_GetItem(director_speaker_list, i);
                this_score = DirectorScoreConverser(speaker, select_speaker);
                if (this_score == best_score) {
                    x--;
                    if (x == 0) return speaker;
                }
            }
            return best;
        }
        if ((x ofclass K2_thing) && (x ~= player)) return x;
    }
    rfalse;
];

§4. Scoring possible speakers. Note that the scoring depends on whether we're looking for a speaker or an interlocutor, and also on whether the line is verbal or non-verbal.

[ DirectorScoreConverser speaker select_speaker nonverbal this_score;
    if (select_speaker) {
        if (OnStage(speaker, -1)) this_score = this_score + 16;
        if (DirectorTestAccessible(player, speaker, nonverbal)) this_score = this_score + 8;
        if (speaker == DirectorStackLastInterlocutor-->(director_sp-1)) this_score = this_score + 4;
        if (speaker ofclass K8_person) this_score = this_score + 2;
        if (speaker ~= DirectorStackLastSpeaker-->(director_sp-1)) this_score = this_score + 1;
    } else {
        if (OnStage(speaker, -1)) this_score = this_score + 16;
        if (DirectorTestAccessible(player, speaker, nonverbal)) this_score = this_score + 8;
        if (speaker == DirectorStackLastSpeaker-->(director_sp-1)) this_score = this_score + 4;
        if (speaker ofclass K8_person) this_score = this_score + 2;
        if (speaker ~= DirectorStackLastInterlocutor-->(director_sp-1)) this_score = this_score + 1;
    }
    return this_score;
];

[ DirectorTestAccessible from to nonverbal;
    if (nonverbal) return TestVisibility(from, to);
    return TestAudibility(from, to);
];

§5. Performing a line. The following attempts to perform a line, though this may by stymied in a variety of ways. The return value is true if a line is actually performed, false otherwise.

[ DirectorPerformLine dl linedata fn action_fn speaker interlocutor mentioning nonverbal ta rv;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;

    Must either be unperformed or recurring
    if ((GProperty(DIALOGUE_LINE_TY, dl, performed)) &&
        (GProperty(DIALOGUE_LINE_TY, dl, recurring) == 0)) rfalse;

    linedata = TableOfDialogueLines-->dl;
    Must be available
    fn = linedata-->AVAILABILITY_DLMETADATA; if ((fn) && (fn() == false)) rfalse;

    if ((linedata-->FLAGS_DLMETADATA) & NONVERBAL_FLAG_DLMETADATA) nonverbal = true;

    An accessible speaker matching the description must be found, if a description is given
    if (linedata-->SPEAKER_DLMETADATA) {
        speaker = DirectorSelectConverser(linedata-->SPEAKER_DLMETADATA, true, nonverbal);
        if ((speaker == nothing) ||
            (DirectorTestAccessible(player, speaker, nonverbal) == false)) rfalse;
    }

    An accessible interlocutor matching the description must be found, if a description is given
    if (linedata-->INTERLOCUTOR_DLMETADATA) {
        interlocutor = DirectorSelectConverser(linedata-->INTERLOCUTOR_DLMETADATA, false, nonverbal);
        if ((interlocutor == nothing) ||
            (DirectorTestAccessible(speaker, interlocutor, nonverbal) == false)) rfalse;
    }

    action_fn = linedata-->ACTIONS_DLMETADATA;

    If the line is "after ..." some action, that action must succeed
    if (action_fn) {
        ta = trace_actions;
        if (debug_dialogue) trace_actions = 1;
        rv = action_fn(3, speaker);
        trace_actions = ta;
        if (rv == false) rfalse;
    }

    DirectorStackLastSpeaker-->(director_sp-1) = speaker;
    DirectorStackLastInterlocutor-->(director_sp-1) = interlocutor;

    DivideParagraphPoint();
    if (DirectorPerformingActivity(dl, speaker, interlocutor, linedata-->STYLE_DLMETADATA) == false)
        rfalse;
    DivideParagraphPoint();

    WriteGProperty(DIALOGUE_LINE_TY, dl, performed, true);

    if (action_fn) {
        ta = trace_actions;
        if (debug_dialogue) trace_actions = 1;
        Carry out any "now" state changes
        action_fn(2, speaker);
        Run any "before ..." actions
        action_fn(1, speaker);
        trace_actions = ta;
    }

    Make any newly mentioned subjects live
    mentioning = linedata-->MENTIONING_DLMETADATA;
    switch (metaclass(mentioning)) {
        Object: DirectorAddLiveSubjectList(mentioning);
        Routine: mentioning();
    }
    rtrue;
];

§6. Running the performing activity. The only tricky thing here is that we need to expose our data here so that the activity variables for performing something can be initialised. We do that in a fairly clumsy way, but it's good enough.

Global director_current_speaker;
Global director_current_interlocutor;
Global director_current_style = 1;
[ DirectorCurrentLineSpeaker;
    return director_current_speaker;
];
[ DirectorCurrentLineInterlocutor;
    return director_current_interlocutor;
];
[ DirectorCurrentLineStyle;
    return director_current_style;
];

[ DirectorPerformingActivity dl speaker interlocutor manner;
    director_current_speaker = speaker;
    director_current_interlocutor = interlocutor;
    director_current_style = manner;
    if (PERFORMING_DIALOGUE == 0) "*** no activity ***";
    else {
        BeginActivity(PERFORMING_DIALOGUE, dl);
        if ((ForActivity(PERFORMING_DIALOGUE, dl)) &&
            (RulebookFailed())) rfalse;
        EndActivity(PERFORMING_DIALOGUE, dl);
    }
    director_current_speaker = nothing;
    director_current_interlocutor = nothing;
    director_current_style = 1;
    rtrue;
];