From 6310a34ff80d2cfd0ddff000b56312c0b1939fc1 Mon Sep 17 00:00:00 2001 From: Interkarma Date: Tue, 29 Nov 2022 10:08:26 +1000 Subject: [PATCH] Initial LocalizedBook class New class in progress to abstract all book data (classic or translated) into a unified text file format. File format designed to be simple as possible to edit in any text editor supporting UTF-8 (e.g. Notepad). Supports import of classic book data from ARENA2 or mods. Book reader and other areas touching on books will be refactored to use LocalizedBook as their input data. --- Assets/Scripts/Game/LocalizedBook.cs | 195 ++++++++++++++++++++++ Assets/Scripts/Game/LocalizedBook.cs.meta | 11 ++ 2 files changed, 206 insertions(+) create mode 100644 Assets/Scripts/Game/LocalizedBook.cs create mode 100644 Assets/Scripts/Game/LocalizedBook.cs.meta diff --git a/Assets/Scripts/Game/LocalizedBook.cs b/Assets/Scripts/Game/LocalizedBook.cs new file mode 100644 index 0000000000..2b1732f2e1 --- /dev/null +++ b/Assets/Scripts/Game/LocalizedBook.cs @@ -0,0 +1,195 @@ +// Project: Daggerfall Unity +// Copyright: Copyright (C) 2009-2022 Daggerfall Workshop +// Web Site: http://www.dfworkshop.net +// License: MIT License (http://www.opensource.org/licenses/mit-license.php) +// Source Code: https://github.com/Interkarma/daggerfall-unity +// Original Author: Gavin Clayton (interkarma@dfworkshop.net) +// Contributors: +// +// Notes: +// + +using UnityEngine; +using System.IO; +using DaggerfallConnect.Arena2; +using DaggerfallWorkshop; +using DaggerfallWorkshop.Utility.AssetInjection; +using System.Text; + +/// +/// Represents data for localized book format. +/// Can import classic books and export localized file. +/// +public class LocalizedBook +{ + const string localizedFilenameSuffix = "-LOC"; + const string fileExtension = ".txt"; + const string textFolderName = "Text"; + const string booksFolderName = "Books"; + + // Formatting markup + const string markupJustifyLeft = "[/left]"; + const string markupJustifyCenter = "[/center]"; + const string markupFont = "[/font={0}]"; + + // Keywords for save/load localized book format + const string titleKeyword = "Title"; + const string authorKeyword = "Author"; + const string isNaughtyKeyword = "IsNaughty"; + const string priceKeyword = "Price"; + const string isUniqueKeyword = "IsUnique"; + const string whenVarSetKeyword = "WhenVarSet"; + const string contentKeyword = "Content"; + + // Book data fields + public string Title; // Title of book + public string Author; // Author's name + public bool IsNaughty; // Flagged for adult content + public int Price; // Base price for merchants + public bool IsUnique; // Book only available to mods and will not appear in loot tables or shelves + public string WhenVarSet; // Book only available in loot tables once this GlobalVar is set + public string Content; // Text content of book + + /// + /// Opens either localized or classic book file. + /// Seeks localized books first then classic books. + /// + /// Filename of classic book file, e.g. "BOK00042.txt". + /// True if successful. + public bool OpenBookFile(string filename) + { + // Seek localized book file + if (!OpenLocalizedBookFile(filename)) + { + // Fallback to classic book file + if (!OpenClassicBookFile(filename)) + return false; + } + + return true; + } + + /// + /// Opens a classic book file. + /// Seeks classic book files from ARENA2 and mods. + /// Formatting of classic books is unreliable and often broken in classic data. + /// Opening a localized book cleaned up by a human is always preferred. + /// + /// Filename of classic book file, e.g. "BOK00042.txt". + /// True if successful. + public bool OpenClassicBookFile(string filename) + { + // Try to open book + BookFile bookFile = new BookFile(); + if (!BookReplacement.TryImportBook(filename, bookFile) && + !bookFile.OpenBook(DaggerfallUnity.Instance.Arena2Path, filename)) + return false; + + // Prepare initial data + Title = bookFile.Title; + Author = bookFile.Author; + IsNaughty = bookFile.IsNaughty; + Price = bookFile.Price; + IsUnique = false; + WhenVarSet = string.Empty; + Content = string.Empty; + + // Convert page tokens to markup and set book content + string content = string.Empty; + for (int page = 0; page < bookFile.PageCount; page++) + { + TextFile.Token[] tokens = bookFile.GetPageTokens(page); // Get all tokens on current page + content += ConvertTokensToString(tokens); // Convert contents of page to book markup + } + Content = content; + + return true; + } + + /// + /// Opens a localized book file from StreamingAssets/Text/Books. + /// As classic books are also .txt files (despite not being true plain text) the localized files append "-LOC" to book filename. + /// This is to ensure load process can seek correct version of book later when loading localized book files from mods. + /// + /// Filename of classic or localized book file, e.g. "BOK00042-LOC.txt" or "BOK00042.txt". -LOC is added to filename automatically if missing. + /// True if successfull. + public bool OpenLocalizedBookFile(string filename) + { + // Append -LOC if missing from filename + string fileNoExt = Path.GetFileNameWithoutExtension(filename); + if (!fileNoExt.EndsWith(localizedFilenameSuffix)) + filename = fileNoExt + localizedFilenameSuffix + fileExtension; + + // Attempt to load file from StreamingAssets/Text/Books + // TODO: Also seek localized book file from mods + string path = Path.Combine(Application.streamingAssetsPath, textFolderName, booksFolderName, filename); + string[] lines = File.ReadAllLines(path); + if (lines == null || lines.Length == 0) + return false; + + return false; + } + + /// + /// Saves localized book file. + /// + /// Filename of localized book file. + /// True if successfull. + public void SaveLocalizedBook(string filename) + { + StringBuilder builder = new StringBuilder(); + builder.AppendLine(string.Format("{0}: {1}", titleKeyword, Title)); + builder.AppendLine(string.Format("{0}: {1}", authorKeyword, Author)); + builder.AppendLine(string.Format("{0}: {1}", isNaughtyKeyword, IsNaughty)); + builder.AppendLine(string.Format("{0}: {1}", priceKeyword, Price)); + builder.AppendLine(string.Format("{0}: {1}", isUniqueKeyword, IsUnique)); + builder.AppendLine(string.Format("{0}: {1}", whenVarSetKeyword, WhenVarSet)); + builder.AppendLine(string.Format("{0}:\n{1}", contentKeyword, Content)); + File.WriteAllText(filename, builder.ToString()); + } + + /// + /// Convert book tokens to markup. + /// This is a simplified version of RSC markup converter. + /// + /// Input tokens. + /// String result. + string ConvertTokensToString(TextFile.Token[] tokens) + { + string text = string.Empty; + + // Classic books use only the following tokens + for (int i = 0; i < tokens.Length; i++) + { + switch (tokens[i].formatting) + { + case TextFile.Formatting.Text: + text += tokens[i].text; + break; + case TextFile.Formatting.NewLine: + text += "\n"; + break; + case TextFile.Formatting.JustifyLeft: + text += markupJustifyLeft; + break; + case TextFile.Formatting.JustifyCenter: + text += markupJustifyCenter; + break; + case TextFile.Formatting.FontPrefix: + text += string.Format(markupFont, tokens[i].x); + break; + case TextFile.Formatting.PositionPrefix: + // Unused + break; + case TextFile.Formatting.SameLineOffset: + // Unused + break; + default: + Debug.LogFormat("Unknown book formatting token '{0}'", tokens[i].formatting); + break; + } + } + + return text; + } +} \ No newline at end of file diff --git a/Assets/Scripts/Game/LocalizedBook.cs.meta b/Assets/Scripts/Game/LocalizedBook.cs.meta new file mode 100644 index 0000000000..f1b68a243a --- /dev/null +++ b/Assets/Scripts/Game/LocalizedBook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00f6dfe6a118d9346bc34b23dc59ed6f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: