Start adding plugin infrastructure

This commit is contained in:
ficedula 2023-03-13 21:59:39 +00:00
parent a41149c50b
commit 8f7210b85f
14 changed files with 247 additions and 83 deletions

View File

@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: EPL-2.0
using Ficedula.FF7;
using System;
using System.Collections.Generic;
using System.Linq;
@ -18,74 +19,80 @@ namespace Braver {
public abstract IEnumerable<string> Scan();
}
public class PackDataSource : DataSource {
private Pack _pack;
private string _path;
private HashSet<string> _files;
public PackDataSource(Pack pack, string path) {
_pack = pack;
_path = path;
_files = new HashSet<string>(
pack.Filenames
.Where(s => s.StartsWith(path + "\\", StringComparison.InvariantCultureIgnoreCase)),
StringComparer.InvariantCultureIgnoreCase
);
}
public override IEnumerable<string> Scan() {
return _files.Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = _path + "\\" + file;
if (_files.Contains(fn)) {
return _pack.Read(fn);
} else
return null;
}
}
public class LGPDataSource : DataSource {
private Ficedula.FF7.LGPFile _lgp;
public LGPDataSource(Ficedula.FF7.LGPFile lgp) {
_lgp = lgp;
}
public override IEnumerable<string> Scan() => _lgp.Filenames;
public override Stream TryOpen(string file) => _lgp.TryOpen(file);
}
public class FileDataSource : DataSource {
private string _root;
public FileDataSource(string root) {
_root = root;
}
public override IEnumerable<string> Scan() {
//TODO subdirectories
return Directory.GetFiles(_root).Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = Path.Combine(_root, file);
if (File.Exists(fn))
return new FileStream(fn, FileMode.Open, FileAccess.Read);
return null;
}
}
public abstract class BGame {
protected class PackDataSource : DataSource {
private Pack _pack;
private string _path;
private HashSet<string> _files;
public PackDataSource(Pack pack, string path) {
_pack = pack;
_path = path;
_files = new HashSet<string>(
pack.Filenames
.Where(s => s.StartsWith(path + "\\", StringComparison.InvariantCultureIgnoreCase)),
StringComparer.InvariantCultureIgnoreCase
);
}
public override IEnumerable<string> Scan() {
return _files.Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = _path + "\\" + file;
if (_files.Contains(fn)) {
return _pack.Read(fn);
} else
return null;
}
}
protected class LGPDataSource : DataSource {
private Ficedula.FF7.LGPFile _lgp;
public LGPDataSource(Ficedula.FF7.LGPFile lgp) {
_lgp = lgp;
}
public override IEnumerable<string> Scan() => _lgp.Filenames;
public override Stream TryOpen(string file) => _lgp.TryOpen(file);
}
protected class FileDataSource : DataSource {
private string _root;
public FileDataSource(string root) {
_root = root;
}
public override IEnumerable<string> Scan() {
//TODO subdirectories
return Directory.GetFiles(_root).Select(s => Path.GetFileName(s));
}
public override Stream TryOpen(string file) {
string fn = Path.Combine(_root, file);
if (File.Exists(fn))
return new FileStream(fn, FileMode.Open, FileAccess.Read);
return null;
}
}
public VMM Memory { get; } = new();
public SaveMap SaveMap { get; }
public IAudio Audio { get; protected set; }
public SaveData SaveData { get; protected set; }
protected Dictionary<string, List<DataSource>> _data = new Dictionary<string, List<DataSource>>(StringComparer.InvariantCultureIgnoreCase);
private Dictionary<Type, object> _singletons = new();
protected Dictionary<string, string> _paths = new(StringComparer.InvariantCultureIgnoreCase);
public BGame() {
SaveMap = new SaveMap(Memory);
@ -114,6 +121,13 @@ namespace Braver {
}
}
public void AddDataSource(string folder, DataSource source) {
if (!_data.TryGetValue(folder, out var list))
list = _data[folder] = new List<DataSource>();
list.Add(source);
}
public string GetPath(string name) => _paths[name];
protected void FrameIncrement() {
if (++SaveMap.GameTimeFrames >= 30) {
SaveMap.GameTimeFrames = 0;
@ -195,13 +209,13 @@ namespace Braver {
public IEnumerable<string> ScanData(string category) {
if (_data.TryGetValue(category, out var sources))
return sources.SelectMany(s => s.Scan());
return sources.SelectMany(s => s.Scan()).Distinct();
else
return Enumerable.Empty<string>();
}
public Stream TryOpen(string category, string file) {
foreach (var source in _data[category]) {
foreach (var source in _data[category].Reverse<DataSource>()) {
var s = source.TryOpen(file);
if (s != null)
return s;

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Braver.Core\Braver.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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 ChoiceMade(int window, int choice);
}
}

67
Braver.Plugins/Plugin.cs Normal file
View File

@ -0,0 +1,67 @@
// This program and the accompanying materials are made available under the terms of the
// Eclipse Public License v2.0 which accompanies this distribution, and is available at
// https://www.eclipse.org/legal/epl-v20.html
//
// SPDX-License-Identifier: EPL-2.0
namespace Braver.Plugins {
public abstract class Plugin {
public abstract string Name { get; }
public abstract Version Version { get; }
public abstract IEnumerable<Type> GetPluginInstances();
public abstract IPluginInstance Get(string context, Type t);
public abstract void Init(BGame game);
}
public interface IPluginInstance {
}
public class PluginManager {
private Dictionary<Type, List<Plugin>> _types = new();
public PluginManager() {
var pluginTypes = System.Reflection.Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => t.IsAssignableTo(typeof(IPluginInstance)));
foreach (var type in pluginTypes)
_types[type] = new List<Plugin>();
}
public void Init(BGame game, IEnumerable<Plugin> plugins) {
foreach(var plugin in plugins) {
System.Diagnostics.Trace.WriteLine($"Loading plugin {plugin.Name} v{plugin.Version}");
plugin.Init(game);
foreach(var type in plugin.GetPluginInstances()) {
_types[type].Add(plugin);
}
}
}
public PluginInstances GetInstances(string context, params Type[] types) {
var instances = types
.SelectMany(t => _types[t].Select(plugin => plugin.Get(context, t)));
return new PluginInstances(instances);
}
}
public class PluginInstances : IDisposable {
private List<IPluginInstance> _instances;
internal PluginInstances(IEnumerable<IPluginInstance> instances) {
_instances = instances.ToList();
}
public void Call<T>(Action<T> action) where T : IPluginInstance {
foreach (var instance in _instances.OfType<T>())
action(instance);
}
public void Dispose() {
foreach (var instance in _instances.OfType<IDisposable>())
instance.Dispose();
}
}
}

View File

@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Braver.Plugins", "Braver.Plugins\Braver.Plugins.csproj", "{4C8A556C-F596-4F35-8E09-C79E5BEAFCEB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -62,6 +64,10 @@ Global
{BC7A0739-2DEE-4396-BF05-9242D4833A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BC7A0739-2DEE-4396-BF05-9242D4833A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BC7A0739-2DEE-4396-BF05-9242D4833A5F}.Release|Any CPU.Build.0 = Release|Any CPU
{4C8A556C-F596-4F35-8E09-C79E5BEAFCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4C8A556C-F596-4F35-8E09-C79E5BEAFCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4C8A556C-F596-4F35-8E09-C79E5BEAFCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4C8A556C-F596-4F35-8E09-C79E5BEAFCEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -8,12 +8,15 @@ using Microsoft.Xna.Framework.Audio;
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Channels;
using System.Threading.Tasks;
namespace Braver {
public class Audio {
public class Audio : IAudio {
private string _musicFolder;
private Channel<MusicCommand> _channel;
@ -336,16 +339,22 @@ namespace Braver {
public void Quit() {
_channel.Writer.TryWrite(null);
}
private NAudio.Wave.WaveOut _streamOut;
public void PlaySfxStream(Stream s, float volume, float pan) {
//TODO very basic implementation
//TODO pan
//TODO non-ogg!
_streamOut?.Stop();
_streamOut?.Dispose();
_streamOut = new NAudio.Wave.WaveOut();
var vorbis = new NAudio.Vorbis.VorbisWaveReader(s, true);
_streamOut.Init(vorbis);
_streamOut.Volume = volume;
_streamOut.Play();
}
}
public enum Sfx {
Cursor = 0,
SaveReady = 1,
Invalid = 2,
Cancel = 3,
EnemyDeath = 21,
BattleSwirl = 42,
BuyItem = 261,
DeEquip = 446,
}
}

View File

@ -39,6 +39,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Braver.Core\Braver.Core.csproj" />
<ProjectReference Include="..\Braver.Plugins\Braver.Plugins.csproj" />
<ProjectReference Include="..\Ficedula.FF7\Ficedula.FF7.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: EPL-2.0
using Braver.Plugins;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@ -43,11 +44,10 @@ namespace Braver {
public Net.Net Net { get; set; }
public Audio Audio { get; }
public Screen Screen => _screens.Peek();
public DebugOptions DebugOptions { get; }
public PluginManager PluginManager { get; }
private Dictionary<string, string> _paths = new(StringComparer.InvariantCultureIgnoreCase);
public FGame(GraphicsDevice graphics) {
_graphics = graphics;
@ -139,6 +139,16 @@ namespace Braver {
Audio.Precache(Sfx.Cancel, true);
Audio.Precache(Sfx.Invalid, true);
PluginManager = new PluginManager();
if (_paths.ContainsKey("PLUGINS")) {
var plugins = Directory.GetFiles(_paths["PLUGINS"], "*.dll")
.Select(fn => System.Reflection.Assembly.LoadFrom(fn))
.SelectMany(asm => asm.GetTypes())
.Where(t => t.IsAssignableTo(typeof(Plugin)))
.Select(t => Activator.CreateInstance(t))
.OfType<Plugin>();
PluginManager.Init(this, plugins);
}
}
private class TraceFile : TraceListener {
@ -171,8 +181,6 @@ namespace Braver {
}
}
public string GetPath(string name) => _paths[name];
public void AutoSave() {
string path = GetPath("save");
foreach (string file1 in Directory.GetFiles(path, "auto1.*"))

View File

@ -4,6 +4,7 @@
//
// SPDX-License-Identifier: EPL-2.0
using Braver.Plugins;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@ -59,12 +60,14 @@ namespace Braver.Field {
private List<Window> _windows = Enumerable.Range(0, 10).Select(_ => new Window()).ToList();
private FGame _game;
private PluginInstances _plugins;
private UI.UIBatch _ui;
public bool IsActive => _windows.Any(w => (w.State != WindowState.Hidden) && !w.Options.HasFlag(DialogOptions.IsPermanent));
public Dialog(FGame g, GraphicsDevice graphics) {
public Dialog(FGame g, PluginInstances plugins, GraphicsDevice graphics) {
_game = g;
_plugins = plugins;
_ui = new UI.UIBatch(graphics, g);
}
@ -127,18 +130,20 @@ namespace Braver.Field {
}
}
public void Show(int window, string text, Action onClosed) {
public void Show(int window, int tag, string text, Action onClosed) {
PrepareWindow(window, text);
_windows[window].OnClosed = onClosed;
_windows[window].OnChoice = null;
_windows[window].ChoiceLines = null;
_plugins.Call<Plugins.Field.IDialog>(dlg => dlg.Showing(window, tag, _windows[window].Text));
}
public void Ask(int window, string text, IEnumerable<int> choices, Action<int?> onChoice) {
public void Ask(int window, int tag, string text, IEnumerable<int> choices, Action<int?> onChoice) {
PrepareWindow(window, text);
_windows[window].ChoiceLines = choices.ToArray();
_windows[window].OnClosed = null;
_windows[window].OnChoice = onChoice;
_windows[window].Choice = 0;
_plugins.Call<Plugins.Field.IDialog>(dlg => dlg.Asking(window, tag, _windows[window].Text, _windows[window].ChoiceLines));
}
public void ProcessInput(InputState input) {

View File

@ -181,6 +181,7 @@ namespace Braver.Field {
field = new FieldFile(s);
}
_plugins = g.PluginManager.GetInstances(_file, typeof(Plugins.Field.IDialog));
Background = new Background(g, graphics, field.GetBackground());
Movie = new Movie(g, graphics);
FieldDialog = field.GetDialogEvent();
@ -344,7 +345,7 @@ namespace Braver.Field {
_bgZTo = Background.AutoDetectZTo;
}
Dialog = new Dialog(g, graphics);
Dialog = new Dialog(g, _plugins, graphics);
FieldUI = new FieldUI(g, graphics);
g.Memory.ResetScratch();

View File

@ -1574,7 +1574,7 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
addr = f.ReadU8();
f.Pause($"Waiting for ASK on window {win}");
s.Dialog.Ask(win, s.FieldDialog.Dialogs[msg], Enumerable.Range(firstChoice, lastChoice - firstChoice + 1), ch => {
s.Dialog.Ask(win, msg, s.FieldDialog.Dialogs[msg], Enumerable.Range(firstChoice, lastChoice - firstChoice + 1), ch => {
if (ch != null)
s.Game.Memory.Write(bank, addr, (ushort)ch);
f.Resume();
@ -1586,7 +1586,7 @@ if (y + h + MIN_WINDOW_DISTANCE > GAME_HEIGHT) { y = GAME_HEIGHT - h - MIN_WINDO
public static OpResult MESSAGE(Fiber f, Entity e, FieldScreen s) {
byte id = f.ReadU8(), dlg = f.ReadU8();
f.Pause($"Waiting for MESSAGE in window {id}");
s.Dialog.Show(id, s.FieldDialog.Dialogs[dlg], () => f.Resume());
s.Dialog.Show(id, dlg, s.FieldDialog.Dialogs[dlg], () => f.Resume());
return OpResult.Continue;
}

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\net6.0-windows\publish\win-x86\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net6.0-windows</TargetFramework>
<RuntimeIdentifier>win-x86</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
</PropertyGroup>
</Project>

View File

@ -77,6 +77,7 @@ namespace Braver {
private Action _transitionAction;
protected bool _readyToRender;
protected Plugins.PluginInstances _plugins;
public virtual void Init(FGame g, GraphicsDevice graphics) {
Game = g;

View File

@ -38,4 +38,6 @@ PATH MOVIES %movies%
PATH DEBUG %braver%
PATH SAVE %save%
PATH PLUGINS %plugins%
LOG %braver%\log.txt