Expand dialog plugins to provide more info on each individual line

Support voiced dialog in FFNx compat plugin
Support TryLoad on audio streams
Fix MENU opcode returning too quickly
Implement SPECIAL 0xFD sub-op
Make unrecognised text characters a warning in renderer rather than an error
Add a few more characters to font
Recognise avatar tag characters in text decoder (don't do anything with them yet)
More 7H support - map folders properly
This commit is contained in:
ficedula 2023-07-28 13:12:44 +01:00
parent e1a43931e8
commit 449c1b837e
11 changed files with 147 additions and 23 deletions

View File

@ -46,6 +46,7 @@ namespace Braver {
void PlaySfx(Sfx which, float volume, float pan, int? channel = null);
IAudioItem LoadStream(string path, string file);
IAudioItem TryLoadStream(string path, string file);
void DecodeStream(Stream s, out byte[] rawData, out int channels, out int frequency);
}

View File

@ -14,7 +14,7 @@ namespace Braver.Plugins.Field {
public interface IDialog : IPluginInstance {
void Showing(int window, int tag, IEnumerable<string> text);
void Asking(int window, int tag, IEnumerable<string> text, IEnumerable<int> choiceLines);
void Dialog(string dialog);
void Dialog(int tag, int index, string dialog);
void ChoiceSelected(IEnumerable<string> choices, int selected);
void ChoiceMade(int window, int choice);
}

View File

@ -15,7 +15,6 @@ using System.IO;
using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.TrackBar;
namespace Braver {
@ -444,8 +443,17 @@ namespace Braver {
}
public IAudioItem LoadStream(string path, string file) {
var item = TryLoadStream(path, file);
if (item == null) throw new InvalidDataException($"Could not find audio stream {path}/{file}");
return item;
}
public IAudioItem TryLoadStream(string path, string file) {
//TODO networking
return new LoadedAudioItem(_game.Open(path, file));
var s = _game.TryOpen(path, file);
if (s != null)
return new LoadedAudioItem(s);
else
return null;
}
public void DecodeStream(Stream s, out byte[] rawData, out int channels, out int frequency) {

View File

@ -47,7 +47,7 @@ namespace Braver.Field {
public int X, Y, Width, Height;
public string[] Text;
public WindowState State = WindowState.Hidden;
public int FrameProgress, ScreenProgress;
public int FrameProgress, ScreenProgress, Tag;
public Action OnClosed;
public Action<int?> OnChoice;
public int Choice;
@ -61,7 +61,7 @@ namespace Braver.Field {
public bool ReadyForChoice => (ChoiceLines != null) && (ScreenProgress == (Text.Length - 1));
public void StateChanged(PluginInstances<IDialog> plugins) {
plugins.Call(ui => ui.Dialog(Text[ScreenProgress]));
plugins.Call(ui => ui.Dialog(Tag, ScreenProgress, Text[ScreenProgress]));
if (ReadyForChoice)
ChoiceChanged(plugins);
}
@ -118,7 +118,7 @@ namespace Braver.Field {
_windows[window].VariableY = y;
}
private void PrepareWindow(int window, string text) {
private void PrepareWindow(int window, string text, int tag) {
var chars = _game.SaveData.Characters.Select(c => c?.Name).ToArray();
var party = _game.SaveData.Party.Select(c => c?.Name).ToArray();
var win = _windows[window];
@ -126,6 +126,7 @@ namespace Braver.Field {
.Select(line => Ficedula.FF7.Text.Expand(line, chars, party))
.ToArray();
win.FrameProgress = win.ScreenProgress = win.LineScroll = 0;
win.Tag = tag;
win.StateChanged(_plugins);
@ -149,14 +150,14 @@ namespace Braver.Field {
}
public void Show(int window, int tag, string text, Action onClosed) {
PrepareWindow(window, text);
PrepareWindow(window, text, tag);
_windows[window].OnClosed = onClosed;
_windows[window].OnChoice = null;
_windows[window].ChoiceLines = null;
_plugins.Call(dlg => dlg.Showing(window, tag, _windows[window].Text));
}
public void Ask(int window, int tag, string text, IEnumerable<int> choices, Action<int?> onChoice) {
PrepareWindow(window, text);
PrepareWindow(window, text, tag);
_windows[window].ChoiceLines = choices.ToArray();
_windows[window].OnClosed = null;
_windows[window].OnChoice = onChoice;

View File

@ -1802,18 +1802,18 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
s.Game.PushScreen(new UI.Layout.LayoutScreen("Name", parm: parmValue));
else
throw new NotImplementedException();
break;
return OpResult.ContinueNextFrame;
case 0x8:
s.Game.PushScreen(new UI.Layout.LayoutScreen("Shop", parm: parmValue));
break;
return OpResult.ContinueNextFrame;
case 0x9:
s.Game.PushScreen(new UI.Layout.LayoutScreen("MainMenu"));
break;
return OpResult.ContinueNextFrame;
case 0xE:
s.Game.PushScreen(new UI.Layout.LayoutScreen("SaveMenu"));
break;
return OpResult.ContinueNextFrame;
case 0x12: //Save materia - saving main stock & party member 0, since that seems like the intended use...
var saved = s.Game.Singleton(() => new SavedMateria());
@ -2473,6 +2473,11 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
s.Options |= FieldOptions.ShowPlayerHand;
break;
case 0xFD:
byte chr = f.ReadU8(), text = f.ReadU8();
s.Game.SaveData.Characters[chr].Name = s.FieldDialog.Dialogs[text];
break;
default:
throw new NotImplementedException($"SPECIAL op {subOp:x2} not implemented");
}

View File

@ -9,6 +9,7 @@ using Microsoft.Xna.Framework.Graphics;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
@ -404,6 +405,10 @@ namespace Braver.UI {
Color colour = item.colour;
foreach (char c in item.text) {
switch (c) {
case '¬':
Trace.TraceWarning($"Unrecognised char in string '{item.text}'");
break;
case '\xE012':
//Colour change opcode ... ignore and just pay attention to the actual colour code
break;
@ -422,6 +427,18 @@ namespace Braver.UI {
case '\xE030': //pause opcode - just skip
break;
case '\xE040': //avatar opcodes - just skip
case '\xE041':
case '\xE042':
case '\xE043':
case '\xE044':
case '\xE045':
case '\xE046':
case '\xE047':
case '\xE048':
case '\xE049':
break;
case ' ':
dx += (int)(f.GlyphsDict['i'].W * 1 * item.Size);
break;

View File

@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Ficedula.FF7 {
@ -17,20 +18,33 @@ namespace Ficedula.FF7 {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?',
'@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
'`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', //0x40
'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', ' ', '}', '~', ' ',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', ', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', ', '¬', '¬', '¬', '…', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '“', '”', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '\xE040', '\xE041', '¬', '\xE042', '\xE043', '¬', '\xE044', //0x80
'¬', '\xE045', '\xE046', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '\xE047', '\xE048', '\xE049', '¬', '¬', '¬', '…', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '', '“', '”', '¬', '', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬',
'¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', //0xC0
' ', '¬', '\xE020', '\xE021', '\xE022', '\xE023', '\xE024', '\xE025', '\xE026', '\xE027', '\xE028', '\xE029', '\xE030', '¬', '¬', '¬',
'\xE000', '\t', '\xE003', '\xE001', '\xE002', '¬', '¬', '\r', '\xC', '\xE005', '\xE006', '\xE007', '\xE008', '\xE009', '\xE00A', '\xE00B',
'\xE00C', '\xE00D', '\xE00E', '\xE00F', '\xE010', '\xE011', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '¬', '\xE012', '\xE013',
};
/*
8D 141 Cloud E043
89 137 Barret E040
A4 164 Tifa E048
8F 143 Aeris E044
A3 163 Red? E047
92 146 Sephiroth? E046
91 145 Cait Sith E045
A5 165 Yuffie E049
8C 140 Cid E042
8A 138 Vincent E041
*/
public static string Convert(byte[] input, int offset, int? length = null) {
char[] c = Enumerable.Range(offset, length ?? input.Length - offset)
.Select(i => _translate[input[i]])

View File

@ -217,7 +217,14 @@ namespace Braver._7HShim {
return _iro.AllFolderNames()
.Where(s => s.StartsWith(prefix))
.Select(s => s.Substring(prefix.Length))
.Where(s => !s.Contains('\\'));
.Select(s => {
int separator = s.IndexOf('\\');
if (separator >= 0)
return s.Substring(0, separator);
else
return s;
})
.Distinct();
}
public void Dispose() {
@ -366,6 +373,19 @@ namespace Braver._7HShim {
yield break;
}
private static Dictionary<string, string> _folderRemap = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase) {
["char.lgp"] = "field",
["flevel.lgp"] = "field",
};
private string RemapSourceName(string name) {
name = Path.GetFileName(name);
if (_folderRemap.TryGetValue(name, out string remap))
return remap;
else
return name;
}
public override void Init(BGame game) {
foreach(var mod in _mods) {
HashSet<string> remainingFolders = new HashSet<string>(mod.GetFolders(), StringComparer.InvariantCultureIgnoreCase);
@ -381,11 +401,11 @@ namespace Braver._7HShim {
isActive = true;
if (isActive) {
foreach (string datafolder in mod.GetSubFolders(folder))
game.AddDataSource(Path.GetFileName(datafolder), mod.GetDataSource(folder, datafolder));
game.AddDataSource(RemapSourceName(datafolder), mod.GetDataSource(folder, datafolder));
}
}
foreach (string folder in remainingFolders)
game.AddDataSource(Path.GetFileName(folder), mod.GetDataSource(folder, ""));
game.AddDataSource(RemapSourceName(folder), mod.GetDataSource(folder, ""));
}
}
}

View File

@ -18,6 +18,8 @@ namespace Braver.FFNxCompatibility {
public bool FieldAmbientSounds { get; set; } = true;
[ConfigProperty("Allow replacing sound effects")]
public bool ReplaceSfx { get; set; } = true;
[ConfigProperty("Allow voicing field dialog")]
public bool FieldDialogVoices { get; set; } = true;
}
public class FFNxPlugin : Plugin {
@ -45,6 +47,9 @@ namespace Braver.FFNxCompatibility {
_sfx ??= GetToml("Sfx");
yield return new FFNxSfx(_game, _sfx);
}
if (t == typeof(IDialog)) {
yield return new FFNxFieldVoices(_game, context);
}
}
public override IEnumerable<Type> GetPluginInstances() {
@ -52,6 +57,8 @@ namespace Braver.FFNxCompatibility {
yield return typeof(IFieldLocation);
if (_config.ReplaceSfx)
yield return typeof(ISfxSource);
if (_config.FieldDialogVoices)
yield return typeof(IDialog);
}
public override void Init(BGame game) {
@ -117,6 +124,49 @@ namespace Braver.FFNxCompatibility {
}
}
public class FFNxFieldVoices : IDialog, IDisposable {
private IAudioItem _playing;
private BGame _game;
private string _field;
public FFNxFieldVoices(BGame game, string field) {
_game = game;
_field = field;
}
public void Asking(int window, int tag, IEnumerable<string> text, IEnumerable<int> choiceLines) {
//
}
public void ChoiceMade(int window, int choice) {
//
}
public void ChoiceSelected(IEnumerable<string> choices, int selected) {
//
}
public void Dialog(int tag, int index, string dialog) {
if (_playing != null) {
_playing.Stop();
_playing.Dispose();
_playing = null;
}
_playing = _game.Audio.TryLoadStream("Voice", $"{_field}\\{tag}.ogg")
?? _game.Audio.TryLoadStream("Voice", $"{_field}\\{tag}{(char)('a' + index)}.ogg");
_playing?.Play(1f, 0f, false, 1f);
}
public void Dispose() {
_playing?.Dispose();
}
public void Showing(int window, int tag, IEnumerable<string> text) {
//
}
}
public class FFNxFieldAmbience : FFNxAudioPlugin, IFieldLocation, IDisposable {
private List<TomlTable> _ambients;
private IAudioItem _audio;

View File

@ -22,6 +22,8 @@ namespace Braver.Tolk {
public bool EnableFootsteps { get; set; } = true;
[ConfigProperty("Enable Focus sounds")]
public bool EnableFocusTracking { get; set; } = true;
[ConfigProperty("Voice dialogue")]
public bool VoiceDialogue { get; set; } = true;
}
public class TolkPlugin : Plugin {
@ -59,9 +61,12 @@ namespace Braver.Tolk {
public class TolkInstance : ISystem, IDialog, IUI, IBattleUI {
private bool _dialog;
public TolkInstance(TolkConfig config) {
DavyKager.Tolk.TrySAPI(config.EnableSAPI);
DavyKager.Tolk.Load();
_dialog = config.VoiceDialogue;
}
public void ActiveScreenChanged(IScreen screen) {
@ -75,8 +80,9 @@ namespace Braver.Tolk {
);
}
public void Dialog(string dialog) {
DavyKager.Tolk.Speak(dialog, false);
public void Dialog(int tag, int index, string dialog) {
if (_dialog)
DavyKager.Tolk.Speak(dialog, false);
}
private object _lastMenuContainer = null;

View File

@ -66,6 +66,7 @@
<Glyph X="224" Y="120" W="10" H="24" Layer="0">249</Glyph>
<Glyph X="8" Y="144" W="13" H="24" Layer="0">169</Glyph>
<Glyph X="8" Y="192" W="8" H="24" Layer="0">8221</Glyph>
<Glyph X="56" Y="144" W="5" H="24" Layer="0">8217</Glyph>
<Glyph X="24" Y="0" W="3" H="24" Layer="1">33</Glyph>
<Glyph X="48" Y="0" W="7" H="24" Layer="1">34</Glyph>
<Glyph X="72" Y="0" W="16" H="24" Layer="1">35</Glyph>
@ -122,6 +123,7 @@
<Glyph X="240" Y="96" W="12" H="24" Layer="1">126</Glyph>
<Glyph X="240" Y="192" W="8" H="24" Layer="1">8220</Glyph>
<Glyph X="24" Y="192" W="24" H="24" Layer="1">8230</Glyph>
<Glyph X="216" Y="192" W="18" H="24" Layer="1">8212</Glyph>
</Glyphs>
</FontSet>