Lines 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_DLMETADATAis a function to test whether anyiforunlessconditions attached to the line are met; this returnstrueif the line is available,falseif not. If theAVAILABILITY_DLMETADATAis 0, the line is always available. -
SPEAKER_DLMETADATAis either 0, meaning the line is narration and has no speaker, or has metaclassObject, and is therefore an specific person or object which speaks, or else has metaclassRoutine, and is in that case a function which tests whether a given object meets the requirements. For example, a line attributed only toA woman in the Ballroomwould result in a function here. The special valuePLAYER_CONVERSATIONALISTcan also be used to mean "the player, whoever that currently is". -
INTERLOCUTOR_DLMETADATAis similar, but for the interlocutor, and is more often 0 — many lines have no interlocutor. The special valuePLAYER_CONVERSATIONALISTcan also be used to mean "the player, whoever that currently is". -
SPEECH_DLMETADATAis atextvalue for the content (note: an Inform 7 text value, not an I6Stringliteral). 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_DLMETADATAis the performance style for the line, and is an enumerated value of theperformance stylekind. For most lines, this is 1, since that's the enumerated value for the default "spoken normally". This cannot be 0. -
MENTIONING_DLMETADATAis either 0, meaning that the line raises no new subject, or has metaclassObject, in which case it raises only that one subject, or has metaclassRoutine, in which case it is a function which contains a series of calls toDirectorAddLiveSubjectList, one for each of the at least two subjects mentioned. -
ACTIONS_DLMETADATAis either 0, or is a function to carry out any actions ornowstate-changes which accompany the line. The action functionA, if given, is called three times:A(3)to perform "after ..." actions, and returningtrueif they succeed orfalseif they fail; thenA(2)to perform any "now ..." business, with no meaningful return value; thenA(1)to perform "before ..." actions. -
FLAGS_DLMETADATAis currently not very useful, and is a bitmap. The flagNARRATION_FLAG_DLMETADATAis 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 byval, assuming the line is verbal unlessnonverbalis set. -
DirectorSelectConverser(val, false, nonverbal)works out the interlocutor meant byval, assuming the line is verbal unlessnonverbalis 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; ];