BVEのオリジナルの補助表示を作る

　先日の第2回大糸線研究会総会にて、音声をオフにしてビューワーモードでプレイしている時にEB装置のブザーが鳴っていても気付けないというお話があり、BveEX経由で補助表示として表示する方法を取ることにしました。

#### おまじないの作成
　おーとま様の[都市型ワンマンプラグイン](https://www.okaoka-depot.com/AtsEX.Docs/plugins/city-oneman/)の左上に表示されているのがBveEXで追加した補助表示になります。そのソースを参考に、おまじないを作ってみます。
~~~C#
using BveTypes.ClassWrappers;

namespace AtsPlugin
{
    [Plugin(PluginType.VehiclePlugin)]
    internal class AtsMain : AssemblyPluginBase
    {
        // オリジナル補助表示
        private readonly AssistantText AssistantAtsText;

        // コンストラクタ
        public AtsMain(PluginBuilder services) : base(services)
        {
            AssistantAtsText = new AssistantText(new Mackoy.Bvets.AssistantSettings()
            {
                Name = "",
                AutoLocation = true,
                Anchor = AnchorStyles.Top | AnchorStyles.Left,
                Location = Point.Empty,
                Visible = true,
                Scale = 18
            });
            AssistantAtsText.Text = "補助表示だよ";
            AssistantAtsText.Color = System.Drawing.Color.White;
            AssistantAtsText.BackgroundColor = System.Drawing.Color.Red;
            // BVEの補助表示リストに追加
            BveHacker.MainForm.Assistants.Items.Add(AssistantAtsText);
        }

        public override void Dispose()
        {
            if (!(AssistantAtsText is null))
            {
                // お片付け
                BveHacker.MainForm.Assistants.Items.Remove(AssistantAtsText);
            }
        }
    }
}
~~~
これで、左上に赤い背景に白い文字で「補助表示だよ」と表示がされます。

#### 設定
~~~C#
AssistantAtsText = new AssistantText(new Mackoy.Bvets.AssistantSettings()
{
    Name = "",
    AutoLocation = true,
    Anchor = AnchorStyles.Top | AnchorStyles.Left,
    Location = Point.Empty,
    Visible = true,
    Scale = 18
});
~~~
NameとAutoLocationはBVE標準の補助表示用の設定と思われます。(設定したところで何も起きませんでした）
Anchorは、補助表示のマージンの基準位置(AnchorStyles.Top | AnchorStyles.Left なら画面の左上が基準）、Locationは基準位置と補助表示のマージン。補助表示をマウスでドラッグすると、AnchorとLocationの値も変化します。
Visibleは補助表示の表示状態。
Scaleは補助表示のフォントサイズ。BVE標準の補助設定のScaleは、BVEの設定の補助表示タブの「大きさ」の値。

AssistantText変数にAssistantSettingsを持っていて、たとえば
~~~C#
AssistantAtsText.AssistantSettings.Visible = false;
~~~
とすると、補助表示が消えます。

Tickなどで変数の状態に合わせて表示を変えることができます。

~~~C#
public override void Tick(TimeSpan elapsed)
{
    if (!(AssistantAtsText is null))
    {
        if (EB == 0)
        {
            // EB装置は何も起きていない
            AssistantAtsText.AssistantSettings.Visible = false;
        }
        else if(EB == 1)
        {
            // EB装置ブザー鳴動
            AssistantEBText.Text = "EB装置が鳴動しています。リセットしてください。";
            AssistantEBText.Color = System.Drawing.Color.White;
            AssistantEBText.BackgroundColor = System.Drawing.Color.FromArgb(128, 0, 0, 0);
            AssistantEBText.AssistantSettings.Visible = true;
        }
        else
        {
            // EB装置ブザー動作
            AssistantEBText.Text = "EB装置が作動しました。ブレーキハンドルを非常位置に入れてください。";
            AssistantEBText.Color = System.Drawing.Color.White;
            AssistantEBText.BackgroundColor = System.Drawing.Color.Red;
            AssistantEBText.AssistantSettings.Visible = true;
        }
    }
}
~~~
実際に実装したものが[こんな感じ](https://x.com/hamatetsu_train/status/1861068604332196035)です。

#### 設定値の保存
BVE本体の補助表示の設定は、ドキュメントフォルダのBveTs\Settings\Preferences.xmlに保存されています。
Preferences.xmlに手動で追記すればいいかと思いましたが、実際試すとダメでした。
というわけで、オリジナルの補助表示の設定を保存するには、自前で設定ファイルを作る必要があります。

設定値を格納するクラス
~~~C#
/// <summary>
/// 補助表示設定クラス
/// </summary>
public class ConfigAssistants
{
    /// <summary>
    /// 補助表示設定リスト
    /// </summary>
    public List<Assistant> Settings { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public ConfigAssistants()
    {
        this.Settings = new List<Assistant>();
    }

    /// <summary>
    /// 補助表示設定スキーマ
    /// </summary>
    public class Assistant
    {
        /// <summary>
        /// 項目名
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// フォントサイズ
        /// </summary>
        public int Size { get; set; }

        /// <summary>
        /// 座標基準
        /// </summary>
        public int Anchor { get; set; }

        /// <summary>
        /// 座標基準からのX座標
        /// </summary>
        public int X { get; set; }

        /// <summary>
        /// 座標基準からのY座標
        /// </summary>
        public int Y { get; set; }
    }
}
~~~

設定の読み書きを処理するクラス
~~~C#
/// <summary>
/// 設定処理クラス
/// </summary>
public class Config
{
    /// <summary>
    /// 補助表示設定
    /// </summary>
    public ConfigAssistants AssistantSettings { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    public Config()
    {
        this.AssistantSettings = new ConfigAssistants();
    }

    /// <summary>
    /// 補助表示設定の読み込み
    /// </summary>
    /// <returns></returns>
    public bool LoadAssistant()
    {
        try
        {
            string path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) +      "\\hamatetsu\\AssistantSettings.json"; // C:\Users\(  )\AppData\Local\hamatetsu\AssistantSettings.json
            if (System.IO.File.Exists(path) == true)
            {
                string jsonText = File.ReadAllText(path);
                AssistantSettings = JsonSerializer.Deserialize<ConfigAssistants>(jsonText);
                if (AssistantSettings == null) AssistantSettings= new ConfigAssistants();
                return true;
            }
            else
            {
                AssistantSettings = new ConfigAssistants();
                return false;
            }
        }
        catch (Exception ex)
        {
            AssistantSettings = new ConfigAssistants();
            MessageBox.Show(path + "\n" + ex.Message, "補助表示設定読込(LoadAssistant)");
            return false;
        }
    }

    /// <summary>
    /// 補助表示設定の保存
    /// </summary>
    /// <returns></returns>
    public bool SaveAssistant()
    {
        try
        {
            string path = System.Environment.GetFolderPath(System.Environment.SpecialFolder.LocalApplicationData) + "\\hamatetsu\\";
            if (System.IO.Directory.Exists(path) == false)
            {
                System.IO.Directory.CreateDirectory(path);
            }

            var options = new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.Create(UnicodeRanges.All),
                WriteIndented = true
            };
            string jsonString = JsonSerializer.Serialize(AssistantSettings, options);
            File.WriteAllText(path, jsonString);
        }
        catch (Exception ex)
        {
            MessageBox.Show(path + "\n" + ex.Message, "補助表示設定保存(SaveAssistant)");
            return false;
        }
    }

    /// <summary>
    /// 補助表示設定リストから指定名称の設定を取得(見つからなかった場合は新規追加)
    /// </summary>
    /// <param name="name">名称</param>
    /// <param name="size">フォントサイズ</param>
    /// <returns></returns>
    public ConfigAssistants.Assistant GetAssistant(string name, int size = 18)
    {
        ConfigAssistants.Assistant retval = null;
        try
        {
            foreach (ConfigAssistants.Assistant AS in this.AssistantSettings.Settings)
            {
                if (AS.Name == name)
                {
                    retval = AS;
                    break;
                }
            }
            if(retval == null)
            {
                // 新規追加
                retval = new ConfigAssistants.Assistant();
                retval.Name = name;
                retval.Size = size;
                retval.Anchor = (int)(AnchorStyles.Top | AnchorStyles.Left);
                retval.X = 0;
                retval.Y = 0;
                this.AssistantSettings.Settings.Add(retval);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(name + "\n" + ex.Message, "GetAssistant");
        }
        return retval;
    }
}
~~~

おまじないを改造
~~~C#
using BveTypes.ClassWrappers;

namespace AtsPlugin
{
    [Plugin(PluginType.VehiclePlugin)]
    internal class AtsMain : AssemblyPluginBase
    {
        // 設定
        private Config CFG;

        // 変数
        private readonly AssistantText AssistantAtsText;

        // コンストラクタ
        public AtsMain(PluginBuilder services) : base(services)
        {
            try
            {
                // 設定初期化
                this.CFG = new Config();
                // 設定読込
                if(this.CFG.LoadAssistant() == false) MessageBox.Show("設定読込失敗");
    
                // BVE標準の補助表示設定のフォントサイズ
                int sz = 18;
                if (BveHacker.MainForm.AssistantDrawer.Items.Count > 0)
                {
                    sz = BveHacker.MainForm.Assistants.Items[0].AssistantSettings.Scale;
                }
                // 補助表示設定取得
                ConfigAssistants.Assistant AS1 = this.CFG.GetAssistant("hojo", sz);
                // 補助表示初期化
                AssistantAtsText = new AssistantText(new Mackoy.Bvets.AssistantSettings()
                {
                    Name = AS1.Name,
                    AutoLocation = true,
                    Anchor = (AnchorStyles)AS1.Anchor,
                    Location = new System.Drawing.Point(AS1.X, AS1.Y),
                    Visible = true,
                    Scale = AS1.Size
                });
                AssistantAtsText.Text = "補助表示だよ";
                AssistantAtsText.Color = System.Drawing.Color.White;
                AssistantAtsText.BackgroundColor = System.Drawing.Color.Red;
                // 補助表示リストに追加
                BveHacker.MainForm.AssistantDrawer.Items.Add(AssistantAtsText);
            }
            catch(Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        public override void Dispose()
        {
            if (!(AssistantAtsText is null))
            {
                // 補助表示設定保存
                ConfigAssistants.Assistant AS1 = this.CFG.GetAssistant("hojo");
                AS1.Anchor = (int)AssistantAtsText.AssistantSettings.Anchor;
                AS1.X = AssistantAtsText.AssistantSettings.Location.X;
                AS1.Y = AssistantAtsText.AssistantSettings.Location.Y;
                this.CFG.SaveAssistant();
                // お片付け
                BveHacker.MainForm.Assistants.Items.Remove(AssistantAtsText);
            }
        }
    }
}
~~~
※フォントサイズの初期値はBVEの設定の補助表示のフォントサイズになります。設定ファイルに保存されたScaleの値を書き換えてやれば、以降のフォントサイズが変わります。

※2024.12.22 BveEXリリースに伴いコードを修正しました。
BveHacker.MainForm.AssistantDrawer → BveHacker.MainForm.Assistants