Runtime support for dialogue lines.
- §1. Runtime representation
- §2. Extracting line data
- §3. Determining the speakers
- §4. Scoring possible speakers
- §5. Performing a line
- §6. Running the performing activity
§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:
- ● AVAILABILITY_DLMETADATA is a function to test whether any if or unless conditions attached to the line are met; this returns true if the line is available, false if not. If the AVAILABILITY_DLMETADATA is 0, the line is always available.
- ● SPEAKER_DLMETADATA is either 0, meaning the line is narration and has no speaker, or has metaclass Object, and is therefore an specific person or object which speaks, or else has metaclass Routine, and is in that case a function which tests whether a given object meets the requirements. For example, a line attributed only to A woman in the Ballroom would result in a function here. The special value PLAYER_CONVERSATIONALIST can also be used to mean "the player, whoever that currently is".
- ● INTERLOCUTOR_DLMETADATA is similar, but for the interlocutor, and is more often 0 — many lines have no interlocutor. The special value PLAYER_CONVERSATIONALIST can also be used to mean "the player, whoever that currently is".
- ● SPEECH_DLMETADATA is a text value for the content (note: an Inform 7 text value, not an I6 String literal). This cannot be 0. The text may well include substitutions, and the compiled code for those substitutions may assume it can access the activity variables for "performing something".
- ● STYLE_DLMETADATA is the performance style for the line, and is an enumerated value of the performance style kind. For most lines, this is 1, since that's the enumerated value for the default "spoken normally". This cannot be 0.
- ● MENTIONING_DLMETADATA is either 0, meaning that the line raises no new subject, or has metaclass Object, in which case it raises only that one subject, or has metaclass Routine, in which case it is a function which contains a series of calls to DirectorAddLiveSubjectList, one for each of the at least two subjects mentioned.
- ● ACTIONS_DLMETADATA is either 0, or is a function to carry out any actions or now state-changes which accompany the line. The action function A, if given, is called three times: A(3) to perform "after ..." actions, and returning true if they succeed or false if they fail; then A(2) to perform any "now ..." business, with no meaningful return value; then A(1) to perform "before ..." actions.
- ● FLAGS_DLMETADATA is currently not very useful, and is a bitmap. The flag NARRATION_FLAG_DLMETADATA is redundant right now, since the speaker field can just as easily reveal whether or not the line is narration.
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;
[ 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.
- ● DirectorSelectConverser(val, true, nonverbal) works out the speaker meant by val, assuming the line is verbal unless nonverbal is set.
- ● DirectorSelectConverser(val, false, nonverbal) works out the interlocutor meant by val, assuming the line is verbal unless nonverbal is set.
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; ];