Run-time support for dialogue.


§1. Placeholder.

Global latest_performed_beat = 0;
Global line_performance_count = 0;
Array DialogueTopicPool --> 20;

[ DirectorEmptyLiveSubjectList;
    DialogueTopicPool-->0 = 0;
];

[ DirectorAddLiveSubjectList obj i o2;
    for (i=0:i<20:i++) {
        o2 = DialogueTopicPool-->i;
        if (o2 == obj) return;
        if (o2 == 0) break;
    }
    for (i=18:i>0:i--) DialogueTopicPool-->i = DialogueTopicPool-->(i-1);
    DialogueTopicPool-->0 = obj;
    DialogueTopicPool-->19 = 0;
];

[ DirectorRemoveLiveSubjectList obj i j;
    for (i=0:i<20:i++) {
        if (DialogueTopicPool-->i == 0) return;
        if (DialogueTopicPool-->i == obj) {
            for (j=i:j<19:j++) DialogueTopicPool-->j = DialogueTopicPool-->(j+1);
        }
    }
    DialogueTopicPool-->19 = 0;
];

[ DirectorLiveSubjectList list len i;
    if ((list==0) || (BlkValueWeakKind(list) ~= LIST_OF_TY)) return 0;
    len = 0;
    while (DialogueTopicPool-->len) len++;
    LIST_OF_TY_SetLength(list, len);
    for (i=0: i<len: i++)
        LIST_OF_TY_PutItem(list, i+1, DialogueTopicPool-->i);
    return list;
];

[ DirectorAlterLiveSubjectList list len i;
    if ((list==0) || (BlkValueWeakKind(list) ~= LIST_OF_TY)) return 0;
    len = BlkValueRead(list, LIST_LENGTH_F);
    if (len > 19) len = 19;
    for (i=0: i<len: i++)
        DialogueTopicPool-->i = BlkValueRead(list, LIST_ITEM_BASE+i);
    DialogueTopicPool-->len = 0;
    DialogueTopicPool-->19 = 0;
];

[ DirectorBegin;
    DirectorEmptyLiveSubjectList();
    latest_performed_beat = 0;
    director_sp = 0;
    DirectorStackChoices-->0 = BlkValueCreate(LIST_OF_TY);
    BlkValueWrite(DirectorStackChoices-->0, LIST_ITEM_KOV_F, DIALOGUE_CHOICE_TY);
    rfalse;
];

Constant MAX_BEAT_PERFORMANCE_NESTING = 20;

Global director_sp = 0;
Array DirectorStackBeat --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackAgain --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackLastPC --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackPC --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackChoices --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackMin --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackStart --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackLastSpeaker --> MAX_BEAT_PERFORMANCE_NESTING;
Array DirectorStackLastInterlocutor --> MAX_BEAT_PERFORMANCE_NESTING;

[ DirectorPush db max pc;
    if (director_sp >= MAX_BEAT_PERFORMANCE_NESTING)
        "*** Director stack overflow: too many open beats ***";
    DirectorStackBeat-->director_sp = db;
    DirectorStackAgain-->director_sp = -1;
    DirectorStackLastPC-->director_sp = 0;
    DirectorStackPC-->director_sp = pc;
    DirectorStackMin-->director_sp = max;
    DirectorStackStart-->director_sp = 0;
    DirectorStackLastSpeaker-->director_sp = nothing;
    DirectorStackLastInterlocutor-->director_sp = nothing;
    if (DirectorStackChoices-->director_sp == 0) {
        DirectorStackChoices-->director_sp = BlkValueCreate(LIST_OF_TY);
        BlkValueWrite(DirectorStackChoices-->director_sp, LIST_ITEM_KOV_F, DIALOGUE_CHOICE_TY);
    } else {
        LIST_OF_TY_SetLength(DirectorStackChoices-->director_sp, 0);
    }
    director_sp++;
    if (debug_dialogue >= 2) { print "-- Push to: "; DirectorTraceStack(); }
];

[ DirectorPop;
    director_sp--;
    if (debug_dialogue >= 2) { print "-- Pop to: "; DirectorTraceStack(); }
];

[ DirectorPerform db without_detecting tab;
    if ((db == 0) || (db > NO_DIALOGUE_BEATS)) rfalse;
    if (debug_dialogue) {
        print "-- Performing ", (PrintDialogueBeatName) db, "^";
    }
    WriteGProperty(DIALOGUE_BEAT_TY, db, performed, 1);
    latest_performed_beat = db;
    DirectorPush(db, 0, 0);
    DirectorStackStart-->(director_sp - 1) = 1;
    tab = TableOfDialogueBeats-->db;
    if ((tab-->3) && (without_detecting == false)) DetectSceneChange();
    if (tab-->1) (tab-->1)(DialogueTopicPool, true);
    DirectorRun(0);
    if ((tab-->3) && (without_detecting == false)) DetectSceneChange();
    if (debug_dialogue) {
        print "-- Performance of ", (PrintDialogueBeatName) db, " ended^";
    }
];

[ DirectorBeatBeingPerformed db x;
    if ((db >= 1) && (db <= NO_DIALOGUE_BEATS))
        for (x=0: x<director_sp: x++)
            if (DirectorStackBeat-->x == db)
                rtrue;
    rfalse;
];

[ DirectorPerformTiedBeat db;
    if (DirectorBeatBeingPerformed(db) == false) {
        DirectorPerform(db, true);
    }
];

[ PERFORM_OPENING_BEAT_R db;
    db = InitialSituation-->START_BEAT_INIS;
    if (db) DirectorPerform(db);
    rfalse;
];

[ DirectorTraceStack j structure pc instruction depth tab;
    if (director_sp > 0) {
        print "[";
        for (j=0: j<director_sp: j++) {
            if (j > 0) print " --> ";
            if (DirectorStackStart-->j) print "$";
            print (PrintDialogueBeatName) DirectorStackBeat-->j;
            pc = DirectorStackPC-->j;
            if (pc == -1) print "+return";
            else {
                tab = TableOfDialogueBeats-->(DirectorStackBeat-->j);
                structure = tab-->2;
                instruction = (structure-->pc)/100;
                depth = (structure-->pc)%100;
                if (DirectorStackAgain-->j >= 0) print "[*", DirectorStackAgain-->j, "]";
                print "+", pc, " ", "L", depth, "/", DirectorStackMin-->j, " ";
                switch (instruction) {
                    0: print "STOP";
                    1: print (PrintDialogueLineName) structure-->(pc+1);
                    2: print (PrintDialogueChoiceName) structure-->(pc+1);
                    3: switch (structure-->(pc+1)) {
                            2: print "TEXT-D";
                            3: print "ACTION-D";
                            4: print "CONTROL-D";
                            default: print "(*** unknown dtd ***)";
                        }
                    default: print "*** unknown instruction ***";
                }
            }
            if (LIST_OF_TY_GetLength(DirectorStackChoices-->j) > 0) {
                print " {";
                LIST_OF_TY_Say(DirectorStackChoices-->j);
                print "}";
            }
        }
        print "]^";
    } else {
        print "[Director stack empty]^";
    }
];

[ DirectorRun structure pc last_pc instruction depth next_instruction tab;
    while (true) {
        if (director_sp == 0) return;
        if (deadflag) { director_sp = 0; return; }
        if (LIST_OF_TY_GetLength(DirectorStackChoices-->(director_sp-1)) > 0) return;
        tab = TableOfDialogueBeats-->(DirectorStackBeat-->(director_sp-1));
        if ((tab-->3) && (scene_status-->(tab-->3 - 1) == 0)) { DirectorPop(); return; }
        structure = tab-->2;
        pc = DirectorStackPC-->(director_sp-1);
        if (pc == -1) { DirectorPop(); return; }
        last_pc = pc;
        DirectorStackLastPC-->(director_sp-1) = last_pc;
        instruction = (structure-->pc)/100;
        depth = (structure-->pc)%100;
        if (instruction == 0) { DirectorPop(); return; }
        if (depth < DirectorStackMin-->(director_sp-1)) { DirectorPop(); return; }
        pc = pc + 2;
        while ((structure-->pc)%100 > depth) pc = pc + 2;
        if (structure-->pc == 0) pc = -1;
        else if (((structure-->pc)%100) < DirectorStackMin-->(director_sp-1)) pc = -1;
        if (debug_dialogue >= 2) {
            print "-- Instruction (";
            if (pc >= 0) print "next is ", pc; else print "last";
            print "): "; DirectorTraceStack();
        }
        DirectorStackPC-->(director_sp-1) = pc;
        switch (instruction) {
            1:  Line
                if (DirectorPerformLine(structure-->(last_pc+1))) {
                    next_instruction = structure-->(last_pc+2);
                    if ((next_instruction) && (next_instruction % 100 == depth+1)) {
                        DirectorPush(DirectorStackBeat-->(director_sp-1), depth+1, last_pc+2);
                        DirectorRun();
                    }
                }
            2:  Choice
                print "*** Encountered choice node ***^";
            3:  Decision
                DirectorStackAgain-->(director_sp-1) = last_pc;
                DirectorPerformDecision(structure-->(last_pc+1));
            default: "*** Bad node ***";
        }
    }
];

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;
];

Global director_speaker_list;
[ DirectorSelectConverser val select_speaker nonverbal speaker len i best best_score this_score x best_count;
    speaker = val;
    if (metaclass(val) == Routine) {
        if (director_speaker_list == 0) {
            director_speaker_list = BlkValueCreate(LIST_OF_TY);
            BlkValueWrite(director_speaker_list, LIST_ITEM_KOV_F, OBJECT_TY);
        } else {
            LIST_OF_TY_SetLength(director_speaker_list, 0);
        }
        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;
    }
    return speaker;
];

[ 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;
];

[ DirectorPerformLine dl tab fn action_fn speaker interlocutor manner mentioning nonverbal;
    if (dl == 0) rfalse;
    line_performance_count++;
    Must either be unperformed or recurring
    if ((GProperty(DIALOGUE_LINE_TY, dl, performed)) &&
        (GProperty(DIALOGUE_LINE_TY, dl, recurring) == 0)) rfalse;
    tab = TableOfDialogueLines-->dl;
    Must be available
    fn = tab-->0; if ((fn) && (fn() == false)) rfalse;
    if ((tab-->7) & 2) nonverbal = true;
    A speaker matching the description must be found, unless it's narration
    if (tab-->1) {
        speaker = DirectorSelectConverser(tab-->1, true, nonverbal);
        if ((speaker == nothing) || (DirectorTestAccessible(player, speaker, nonverbal) == false)) rfalse;
    }
    if (tab-->2) {
        interlocutor = DirectorSelectConverser(tab-->2, false, nonverbal);
        if ((interlocutor == nothing) || (DirectorTestAccessible(speaker, interlocutor, nonverbal) == false)) rfalse;
    }
    manner = tab-->4;
    action_fn = tab-->6;

    If the line is "after ..." some action, that action must succeed
    if (action_fn) if (action_fn(3, speaker) == false) rfalse;

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

    DivideParagraphPoint();
    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;
    DivideParagraphPoint();

    WriteGProperty(DIALOGUE_LINE_TY, dl, performed, 1);

    if (action_fn) {
        action_fn(2, speaker);
        action_fn(1, speaker);
    }

    mentioning = tab-->5;
    switch (metaclass(mentioning)) {
        Object: DirectorAddLiveSubjectList(mentioning);
        Routine: mentioning();
    }
    rtrue;
];

[ DirectorGetChoice list structure instruction depth dc tab pc fn;
    LIST_OF_TY_SetLength(list, 0);
    if (director_sp == 0) rfalse;
    tab = TableOfDialogueBeats-->(DirectorStackBeat-->(director_sp-1));
    structure = tab-->2;
    pc = DirectorStackLastPC-->(director_sp-1);
    instruction = (structure-->pc)/100;
    depth = (structure-->pc)%100;
    pc = pc + 2;
    while ((structure-->pc)%100 > depth) {
        if ((structure-->pc)%100 == depth+1) {
            dc = structure-->(pc+1);
            tab = TableOfDialogueChoices-->dc;
            if ((GProperty(DIALOGUE_CHOICE_TY, dc, performed) == 0) ||
                (GProperty(DIALOGUE_CHOICE_TY, dc, recurring))) {
                Must be available
                fn = tab-->1;
                if ((fn == 0) || (fn())) LIST_OF_TY_InsertItem(list, dc);
            }
        }
        pc = pc + 2;
    }
];

[ DirectorPerformDecision decision dc tab count n structure spc i list pc;
    list = DirectorStackChoices-->(director_sp-1);
    DirectorGetChoice(list);
    count = LIST_OF_TY_GetLength(list);
    if (debug_dialogue >= 2) {
        if (count == 0) {
            print "-- no available options^";
        } else if (decision == 2 or 3) {
            print "-- available options: "; LIST_OF_TY_Say(list); print "^";
        }
    }
    if (count == 0) return;
    switch (decision) {
        1: "*** blank dtd ***";
        2:  DivideParagraphPoint();
            if (OFFERING_A_DIALOGUE_CHOICE == 0) "*** no activity ***";
            else {
                BeginActivity(OFFERING_A_DIALOGUE_CHOICE, list);
                if ((ForActivity(OFFERING_A_DIALOGUE_CHOICE, list)) &&
                    (RulebookFailed())) rfalse;
                EndActivity(OFFERING_A_DIALOGUE_CHOICE, list);
            }
            DivideParagraphPoint();
            n = DirectorPickANumber(count);
            dc = LIST_OF_TY_GetItem(list, n);
            LIST_OF_TY_SetLength(list, 0);
            tab = TableOfDialogueChoices-->dc;
            style bold;
            TEXT_TY_Say(tab-->2);
            style roman;
            print "^";
            say__p = 1;
            DivideParagraphPoint();
            DirectorExerciseChoice(dc);
        3:  rfalse;
        4:  dc = LIST_OF_TY_GetItem(list, 1);
            LIST_OF_TY_SetLength(list, 0);
            if (dc) DirectorPerformChoice(dc);
            else "*** c = 0 ***";
        default: "*** unknown dtd ***";
    }
    rfalse;
];

[ DirectorBeforeAction N list was dc i fn chose tab;
    return DirectorScreenActionChoices(BEFORE_DSEL);
];

[ DirectorInsteadAction N list was dc i fn chose tab;
    return DirectorScreenActionChoices(INSTEAD_OF_DSEL);
];

[ DirectorAfterAction N list was dc i fn chose tab;
    return DirectorScreenActionChoices(AFTER_DSEL);
];

[ DirectorScreenActionChoices wanted N list was dc i fn chose tab suppress_otherwise;
    if (director_sp > 0) {
        if (debug_dialogue >= 2) { print "-- found: "; DirectorTraceStack(); }
        list = DirectorStackChoices-->(director_sp-1);
        N = LIST_OF_TY_GetLength(list);
        if (N > 0) {
            for (i=1: i<=N: i++) {
                dc = LIST_OF_TY_GetItem(list, i);
                tab = TableOfDialogueChoices-->dc;
                print "-- type: ", tab-->0, "^";
                if ((tab-->0 == OTHERWISE_DSEL) && (suppress_otherwise == false)) {
                    chose = dc; break;
                } else {
                    fn = tab-->2;
                    if ((fn) && (fn())) {
                        if (tab-->0 == wanted) {
                            chose = dc; break;
                        } else {
                            suppress_otherwise = true;
                        }
                    }
                }
            }
            if (debug_dialogue >= 2) {
                if (chose) {
                    print "-- selected ", (PrintDialogueChoiceName) chose, " at stage ", wanted, "^";
                } else {
                    print "-- no selection at stage ", wanted, "^";
                }
            }
            if ((chose) || (wanted == -1)) {
                LIST_OF_TY_SetLength(list, 0);
                if (chose) DirectorExerciseChoice(chose);
                while (director_sp > 0) {
                    was = director_sp;
                    if (debug_dialogue) { print "-- Resuming ", (PrintDialogueBeatName) DirectorStackBeat-->(director_sp-1), "^"; }
                    DirectorRun();
                    if (was == director_sp) break;
                }
                if (director_sp > 0) {
                    if (debug_dialogue) { print "-- Stack not empty: "; DirectorTraceStack(); }
                }
                rtrue;
            }
            if (debug_dialogue >= 2) { print "-- gave up: "; DirectorTraceStack(); }
        }
    }
    rfalse;
];

[ DirectorCurrentChoiceList i L;
    if (director_sp == 0) return DirectorStackChoices-->0;
    return DirectorStackChoices-->(director_sp-1);
];

[ DirectorExerciseChoice dc structure pc spc tab;
    WriteGProperty(DIALOGUE_CHOICE_TY, dc, performed, 1);
    tab = TableOfDialogueBeats-->(DirectorStackBeat-->(director_sp-1));
    structure = tab-->2;
    pc = DirectorStackLastPC-->(director_sp-1);
    spc = pc + 2;
    while (((structure-->spc)/100 ~= 2) || (structure-->(spc+1) ~= dc)) spc = spc + 2;
    spc = spc + 2;
    DirectorPush(DirectorStackBeat-->(director_sp-1), (structure-->pc) % 100 + 2, spc);
    DirectorRun();
];

[ DirectorPickANumber max i j wa wl sign base digit_count n digit;
    for (::) {
        print ">";
        #Ifdef TARGET_ZCODE;
        if (location == nothing || parent(player) == nothing) read buffer2 parse2;
        else read buffer2 parse2 DrawStatusLine;
        j = parse2->1;
        wa = buffer2 + parse2->5;
        wl = parse2->4;
        #Ifnot; TARGET_GLULX;
        if (location ~= nothing && parent(player) ~= nothing) DrawStatusLine();
        KeyboardPrimitive(buffer2, parse2);
        j = parse2-->0;
        wa = buffer2 + parse2-->3;
        wl = parse2-->2;
        #Endif; TARGET_
        if (j) { at least one word entered
            sign = 1; base = 10; digit_count = 0;
            if (wa->0 ~= '-' or '$' or '0' or '1' or '2' or '3' or '4'
                or '5' or '6' or '7' or '8' or '9')
                jump Retry;
            if (wa->0 == '-') { sign = -1; wl--; wa++; }
            if (wl == 0) jump Retry;
            n = 0;
            while (wl > 0) {
                if (wa->0 >= 'a') digit = wa->0 - 'a' + 10;
                else digit = wa->0 - '0';
                digit_count++;
                switch (base) {
                    2:  if (digit_count == 17) jump Retry;
                    10:
                        #Iftrue (WORDSIZE == 2);
                        if (digit_count == 6) jump Retry;
                        if (digit_count == 5) {
                            if (n > 3276) jump Retry;
                            if (n == 3276) {
                                if (sign == 1 && digit > 7) jump Retry;
                                if (sign == -1 && digit > 8) jump Retry;
                            }
                        }
                        #Ifnot; i.e., if (WORDSIZE == 4)
                        if (digit_count == 11) jump Retry;
                        if (digit_count == 10) {
                            if (n > 214748364) jump Retry;
                            if (n == 214748364) {
                                if (sign == 1 && digit > 7) jump Retry;
                                if (sign == -1 && digit > 8) jump Retry;
                            }
                        }
                        #Endif;
                    16: if (digit_count == 5) jump Retry;
                }
                if (digit >= 0 && digit < base) n = base*n + digit;
                else jump Retry;
                wl--; wa++;
            }
            n = n*sign;
            if ((n < 1) || (n > max)) jump Retry;
            return n;
        }
        .Retry;
        print "(Please type an option in the range 1 to ", max, " and press return.)^^";
    }
];

Constant NEW_CHOICE_DSEL = 1;
Constant TEXTUAL_DSEL = 2;
Constant AGAIN_DSEL = 3;
Constant STOP_DSEL = 4;
Constant ENDING_DSEL = 5;
Constant ENDING_SAYING_DSEL = 6;
Constant ENDING_FINALLY_DSEL = 7;
Constant ENDING_FINALLY_SAYING_DSEL = 8;
Constant OTHERWISE_DSEL = 9;
Constant INSTEAD_OF_DSEL = 10;
Constant AFTER_DSEL = 11;
Constant BEFORE_DSEL = 12;
Constant PERFORM_DSEL = 13;

[ DirectorPerformChoice dc tab fn pc enough;
    if (dc == 0) rfalse;
    WriteGProperty(DIALOGUE_CHOICE_TY, dc, performed, 1);
    tab = TableOfDialogueChoices-->dc;
    switch (tab-->0) {
        AGAIN_DSEL:
            if (debug_dialogue >= 2) { print "-- again^"; }
            while (director_sp > 0) {
                if (debug_dialogue >= 2) { print "-- again at: "; DirectorTraceStack(); }
                DirectorPop();
                if (DirectorStackAgain-->(director_sp-1) >= 0) {
                    DirectorStackPC-->(director_sp-1) = DirectorStackAgain-->(director_sp-1);
                    break;
                }
            }
            rtrue;
        STOP_DSEL:
            if (debug_dialogue >= 2) { print "-- stop at: "; DirectorTraceStack(); }
            while (director_sp > 0) {
                enough = false;
                if (DirectorStackStart-->(director_sp-1)) enough = true;
                DirectorPop();
                if (enough) break;
            }
            if (debug_dialogue >= 2) { print "-- after stop: "; DirectorTraceStack(); }
            rtrue;
        ENDING_DSEL, ENDING_SAYING_DSEL, ENDING_FINALLY_DSEL, ENDING_FINALLY_SAYING_DSEL:
            if (debug_dialogue >= 2) { print "-- ending at: "; DirectorTraceStack(); }
            deadflag = tab-->2;
            if (tab-->0 == ENDING_FINALLY_DSEL or ENDING_FINALLY_SAYING_DSEL)
                story_complete = true;
            rtrue;
        PERFORM_DSEL: DirectorPerform(tab-->2); rtrue;
        default: print "*** Unimplemented choice ***^"; rfalse;
    }
    rtrue;
];

[ DirectorListLiveTopics i t;
    for (i=0: i<20: i++) {
        t = DialogueTopicPool-->i;
        if (t == 0) return;
        print (name) t, "^";
    }
];

[ DirectorBeatAvailable db fn tab;
    if ((db == 0) || (db > NO_DIALOGUE_BEATS)) rfalse;
    tab = TableOfDialogueBeats-->db;
    fn = tab-->0;
    if (fn) {
        return (fn)(latest_performed_beat);
    }
    rtrue;
];

[ DirectorBeatRelevant db fn tab;
    if ((db == 0) || (db > NO_DIALOGUE_BEATS)) rfalse;
    tab = TableOfDialogueBeats-->db;
    fn = tab-->1;
    if (fn) {
        return (fn)(DialogueTopicPool);
    }
    rfalse;
];

[ DirectorBeatPrintStructure db tab pc which depth i;
    if ((db == 0) || (db > NO_DIALOGUE_BEATS)) return;
    print (PrintDialogueBeatName) db;
    print " (";
    if (DirectorBeatAvailable(db)) print "available"; else print "unavailable";
    print ", ";
    if (DirectorBeatRelevant(db)) print "relevant"; else print "irrelevant";
    print "):^";
    tab = TableOfDialogueBeats-->db;
    tab = tab-->2;
    if (tab) {
        pc = 0;
        while (tab-->pc) {
            which = (tab-->pc)/100;
            depth = (tab-->pc)%100;
            for (i=0: i<depth: i++) print "  ";
            switch (which) {
                1: print "Line ", (PrintDialogueLineName) tab-->(pc+1), "^";
                2: print "Choice ", (PrintDialogueChoiceName) tab-->(pc+1), "^";
                3: print "Decision of type ", tab-->(pc+1), "^";
                default: print "*** Unimplemented ***^";
            }
            pc = pc + 2;
        }
    }
];

[ DirectorLineAvailable dl tab i;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    tab = TableOfDialogueLines-->dl;
    if (tab-->0) {
        return (tab-->0)();
    }
    rtrue;
];

Global director_is_active = false;

[ DirectorActivate;
    director_is_active = true;
    line_performance_count = 0;
];
[ DirectorDeactivate;
    director_is_active = false;
];

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

[ DirectorBeatAvailableToPlayer db tab i speaker;
    if ((GProperty(DIALOGUE_BEAT_TY, db, performed) == 0) ||
        (GProperty(DIALOGUE_BEAT_TY, db, recurring))) {
        tab = TableOfDialogueBeats-->db;
        i = 4;
        while (true) {
            speaker = tab-->i;
            if (speaker == nothing) rtrue;
            if (TestAudibility(player, speaker) == false) rfalse;
            i++;
        }
    }
    rfalse;
];

[ DirectorLiveRequiredList list db len i tab speaker;
    if ((list==0) || (BlkValueWeakKind(list) ~= LIST_OF_TY)) return 0;
    len = 0;
    tab = TableOfDialogueBeats-->db;
    i = 4;
    while (true) {
        speaker = tab-->i;
        if (speaker == nothing) break;
        len++;
        i++;
    }
    LIST_OF_TY_SetLength(list, len);
    for (i=0: i<len: i++)
        LIST_OF_TY_PutItem(list, i+1, tab-->(i+4));
    return list;
];

[ DIALOGUE_DIRECTION_R db i topic fn tab;
    if (director_is_active == false) rfalse;
    If nothing much has happened this turn...
    if ((director_sp == 0) && (line_performance_count == 0)) {
        for (db=1: db<=NO_DIALOGUE_BEATS: db++) {
            if (DirectorBeatAvailableToPlayer(db)) {
                tab = TableOfDialogueBeats-->db;
                fn = tab-->0;
                if ((fn == 0) || ((fn)(latest_performed_beat))) {
                    fn = tab-->1;
                    if ((fn) && (fn(DialogueTopicPool))) {
                        DirectorPerform(db);
                        line_performance_count = 0;
                        rfalse;
                    }
                }
            }
        }
        for (db=1: db<=NO_DIALOGUE_BEATS: db++) {
            if (GProperty(DIALOGUE_BEAT_TY, db, spontaneous)) {
                if (DirectorBeatAvailableToPlayer(db)) {
                    DirectorEmptyLiveSubjectList();
                    DirectorPerform(db);
                    line_performance_count = 0;
                    rfalse;
                }
            }
        }
    }
    line_performance_count = 0;
    rfalse;
];

Array DirectorMiniPool --> 0 0;

[ DirectorIntervenes obj tab fn db saved;
    saved = DirectorMiniPool-->0;
    DirectorMiniPool-->0 = obj;
    for (db=1: db<=NO_DIALOGUE_BEATS: db++) {
        if (DirectorBeatAvailableToPlayer(db)) {
            tab = TableOfDialogueBeats-->db;
            fn = tab-->0;
            if ((fn == 0) || ((fn)(latest_performed_beat))) {
                fn = tab-->1;
                if ((fn) && (fn(DirectorMiniPool))) {
                    DirectorMiniPool-->0 = saved; DirectorPerform(db); rtrue;
                }
            }
        }
    }
    DirectorMiniPool-->0 = saved;
    rfalse;
];

[ DirectorTestLineContainment X db tX tb tab structure pc instruction wanted;
print "I.e. whether ", (PrintDialogueLineName) X, " is in ", (PrintDialogueBeatName) db, "^";
    if (tb ~= DIALOGUE_BEAT_TY) { print "*** Not a db ***"; rfalse; }
    if (tX ~= DIALOGUE_LINE_TY or DIALOGUE_CHOICE_TY) { print "*** Not a dl/dc ***"; rfalse; }
    if ((db < 1) || (db > NO_DIALOGUE_BEATS)) rfalse;
    if (tX == DIALOGUE_LINE_TY) {
        if ((X < 1) || (X > NO_DIALOGUE_LINES)) rfalse;
        wanted = 1;
    } else {
        if ((X < 1) || (X > NO_DIALOGUE_CHOICES)) rfalse;
        wanted = 2;
    }
    tab = TableOfDialogueBeats-->db;
    structure = tab-->2;
    pc = 0;
    instruction = (structure-->pc)/100;
    while (instruction) {
        if ((instruction == wanted) && (structure-->(pc+1) == X)) rtrue;
        pc = pc + 2;
        instruction = (structure-->pc)/100;
    }
    rfalse;
];

[ DirectorLineContent dl text tab;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) return text;
    tab = TableOfDialogueLines-->dl;
    BlkValueCopy(text, tab-->3);
    return text;
];

[ DirectorLineNarrated dl tab;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    tab = TableOfDialogueLines-->dl;
    if (tab-->1 == 0) rtrue;
    rfalse;
];

[ DirectorLineNonverbal dl tab;
    if ((dl == 0) || (dl > NO_DIALOGUE_LINES)) rfalse;
    tab = TableOfDialogueLines-->dl;
    if ((tab-->7) & 2) rtrue;
    rfalse;
];

[ DirectorChoiceStoryEnding dc tab;
    if ((dc == 0) || (dc > NO_DIALOGUE_CHOICES)) rfalse;
    tab = TableOfDialogueChoices-->dc;
    if (tab-->0 == ENDING_DSEL or ENDING_SAYING_DSEL or ENDING_FINALLY_DSEL or ENDING_FINALLY_SAYING_DSEL)
        rtrue;
    rfalse;
];

[ DirectorChoiceContent dc text tab;
    if ((dc == 0) || (dc > NO_DIALOGUE_CHOICES)) return text;
    tab = TableOfDialogueChoices-->dc;
    BlkValueCopy(text, tab-->2);
    return text;
];

[ DirectorChoiceFlowing dc tab type;
    if ((dc == 0) || (dc > NO_DIALOGUE_CHOICES)) rfalse;
    tab = TableOfDialogueChoices-->dc;
    type = tab-->0;
    if (type == NEW_CHOICE_DSEL or AGAIN_DSEL or STOP_DSEL or PERFORM_DSEL
         or ENDING_DSEL or ENDING_SAYING_DSEL or ENDING_FINALLY_DSEL or ENDING_FINALLY_SAYING_DSEL)
        rtrue;
    rfalse;
];