Combine Duplicate Attributions - Screenplay
Posted: Thu Dec 03, 2020 4:34 pm
I was just wondering if you'd be willing to add an option to collapse duplicate attributions in the screenplay exporter.
So this:
So this:
Would become this:Voodoo Detective: What time is it?
Voodoo Detective: Time for mystery I guess.
Mary Fontule: You got that right.
I wrote a crappy little implementation:Voodoo Detective: What time is it? Time for mystery I guess.
Mary Fontule: You got that right.
Code: Select all
// Copyright (c) Pixel Crushers. All rights reserved.
using UnityEngine;
using System.IO;
using System.Text;
using System.Collections.Generic;
namespace PixelCrushers.DialogueSystem
{
/// <summary>
/// This part of the Dialogue Editor window contains the screenplay export code.
/// </summary>
public static class ScreenplayExporter
{
/// <summary>
/// The main export method. Exports screenplay text files for each language.
/// </summary>
/// <param name="database">Source database.</param>
/// <param name="filename">Target filename.</param>
/// <param name="exportActors">If set to <c>true</c> export actors.</param>
public static void Export(DialogueDatabase database, string filename, EncodingType encodingType)
{
if (database == null || string.IsNullOrEmpty(filename)) return;
var otherLanguages = FindOtherLanguages(database);
ExportFile(database, string.Empty, filename, encodingType);
foreach (var language in otherLanguages)
{
ExportFile(database, language, filename, encodingType);
}
}
private static List<string> FindOtherLanguages(DialogueDatabase database)
{
var otherLanguages = new List<string>();
foreach (var conversation in database.conversations)
{
foreach (var entry in conversation.dialogueEntries)
{
foreach (var field in entry.fields)
{
if ((field.type == FieldType.Localization) &&
!otherLanguages.Contains(field.title) &&
!field.title.Contains(" "))
{
otherLanguages.Add(field.title);
}
}
}
}
return otherLanguages;
}
private static void ExportFile(DialogueDatabase database, string language, string baseFilename, EncodingType encodingType)
{
var filename = string.IsNullOrEmpty(language) ? baseFilename
: Path.GetDirectoryName(baseFilename) + "/" + Path.GetFileNameWithoutExtension(baseFilename) + "_" + language + ".csv";
using (StreamWriter file = new StreamWriter(filename, false, EncodingTypeTools.GetEncoding(encodingType)))
{
ExportConversations(database, language, file);
}
}
private static void ExportConversations(DialogueDatabase database, string language, StreamWriter file)
{
// Cache actor names:
Dictionary<int, string> actorNames = new Dictionary<int, string>();
// We need to know how many links go into each entry to know whether to show [id]:
Dictionary<int, int> numLinksToEntry = new Dictionary<int, int>();
// Track which entries we've already done:
List<DialogueEntry> visited = new List<DialogueEntry>();
// Setup Writer
ScreenplayWriter screenplayWriter = new ScreenplayWriter(file);
// Export all conversations:
foreach (var conversation in database.conversations)
{
file.WriteLine(string.Format("[{0}]\t{1}", conversation.id, conversation.Title.ToUpper()));
file.WriteLine(string.Empty);
if (!string.IsNullOrEmpty(conversation.Description))
{
file.WriteLine(string.Format("\t{0}", conversation.Description));
file.WriteLine(string.Empty);
}
// Count num links going into each entry:
foreach (var entry in conversation.dialogueEntries)
{
numLinksToEntry[entry.id] = 0;
}
foreach (var entry in conversation.dialogueEntries)
{
foreach (var link in entry.outgoingLinks)
{
if (link.destinationConversationID == entry.conversationID)
{
numLinksToEntry[link.destinationDialogueID]++;
}
}
}
// Export depth first, starting from <START> node:
ExportSubtree(database, language, actorNames, numLinksToEntry, visited, conversation.GetFirstDialogueEntry(), 0, screenplayWriter);
// Add orphan nodes at the end:
foreach (var e in conversation.dialogueEntries)
{
if (!visited.Contains(e))
{
ExportSubtree(database, language, actorNames, numLinksToEntry, visited, e, -1, screenplayWriter);
}
}
file.WriteLine(string.Empty);
file.WriteLine("---page---");
file.WriteLine(string.Empty);
}
}
private static void ExportSubtree(DialogueDatabase database, string language, Dictionary<int, string> actorNames, Dictionary<int, int> numLinksToEntry, List<DialogueEntry> visited, DialogueEntry entry, int siblingIndex, ScreenplayWriter file)
{
if (entry == null) return;
visited.Add(entry);
if (entry.id > 0)
{
// Write this entry (the root of the subtree).
// Write entry ID if necessary:
if (siblingIndex == -1)
{
file.Flush();
file.WriteLine(string.Format("\tUnconnected entry [{0}]:", entry.id));
file.WriteLine(string.Empty);
}
else if ((siblingIndex == 0 && !string.IsNullOrEmpty(entry.conditionsString)) ||
(siblingIndex > 0) ||
(numLinksToEntry.ContainsKey(entry.id) && numLinksToEntry[entry.id] > 1))
{
file.Flush();
if (string.IsNullOrEmpty(entry.conditionsString))
{
file.WriteLine(string.Format("\tEntry [{0}]:", entry.id));
}
else
{
file.WriteLine(string.Format("\tEntry [{0}]: ({1})", entry.id, entry.conditionsString));
}
file.WriteLine(string.Empty);
}
// Cache Actor Name
if (!actorNames.ContainsKey(entry.ActorID))
{
Actor actor = database.GetActor(entry.ActorID);
actorNames.Add(entry.ActorID, (actor != null) ? actor.Name.ToUpper() : "ACTOR");
}
// Write Actor Name
file.WriteActor(string.Format("\t\t\t\t{0}", actorNames[entry.ActorID]));
// var description = Field.LookupValue(entry.fields, "Description");
// if (!string.IsNullOrEmpty(description))
// {
// file.WriteLine(string.Format("\t\t\t({0})", description));
// }
// Write Line
var lineText = string.IsNullOrEmpty(language) ? entry.subtitleText : Field.LookupValue(entry.fields, language);
if (entry.isGroup)
{
// Group entries use Title:
// lineText = Field.LookupValue(entry.fields, "Title");
// lineText = !string.IsNullOrEmpty(lineText) ? ("(" + lineText + ")") : "(Group entry; no dialogue)";
lineText = "(Group entry; no dialogue)";
}
// file.WriteSubtitle(string.Format("\t\t{0}", lineText));
file.WriteSubtitle(lineText);
// file.WriteLine(string.Empty);
}
// Handle link summary:
if (entry.outgoingLinks.Count == 0)
{
file.Flush();
file.WriteLine("\t\t\t\t[END]");
file.WriteLine(string.Empty);
}
else if (entry.outgoingLinks.Count > 1)
{
var s = "\tResponses: ";
var first = true;
for (int i = 0; i < entry.outgoingLinks.Count; i++)
{
if (!first) s += ", ";
first = false;
var link = entry.outgoingLinks[i];
if (link.destinationConversationID == entry.conversationID)
{
s += "[" + link.destinationDialogueID + "]";
}
else
{
var destConversation = database.GetConversation(link.destinationConversationID);
if (destConversation != null)
{
s += "[" + destConversation.Title.ToUpper() + ":" + link.destinationDialogueID + "]";
}
else
{
s += "[Other Conversation]";
}
}
}
file.Flush();
file.WriteLine(s);
file.WriteLine(string.Empty);
}
// Follow each outgoing link as a subtree:
for (int i = 0; i < entry.outgoingLinks.Count; i++)
{
var child = database.GetDialogueEntry(entry.outgoingLinks[i]);
if (!visited.Contains(child))
{
ExportSubtree(database, language, actorNames, numLinksToEntry, visited, child, i, file);
}
}
}
public class ScreenplayWriter
{
// Properties
private StreamWriter file;
private StringBuilder run;
private string lastActor;
// Constructor
public ScreenplayWriter(StreamWriter file)
{
this.file = file;
this.run = new StringBuilder();
}
public void WriteLine(string s) => file.WriteLine(s);
public void WriteActor(string s)
{
if (lastActor != s)
{
// Flush
Flush();
// New Run
lastActor = s;
run.Append(s + "\n");
}
}
public void WriteSubtitle(string s)
{
if (run.Length == (lastActor.Length + 1)) run.Append("\t\t");
run.Append(s + " ");
}
public void Flush()
{
// Add Newline
if (run.Length > 0)
{
run.Append("\n\n");
file.Write(run.ToString());
run.Clear();
}
lastActor = string.Empty;
}
}
}
}