Skip to content

Commit

Permalink
feat: [コア拡張機能]車掌動作を上書きするパッチを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
automatic9045 committed Jun 28, 2023
1 parent 1fc2aee commit 6c29ec5
Show file tree
Hide file tree
Showing 9 changed files with 437 additions and 0 deletions.
8 changes: 8 additions & 0 deletions Extensions/AtsEx.CoreExtensions/AtsEx.CoreExtensions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,19 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ConductorPatch\ConductorPatch.cs" />
<Compile Include="ConductorPatch\ConductorPatchFactory.cs" />
<Compile Include="ConductorPatch\ConductorPatchInvoker.cs" />
<Compile Include="ConductorPatch\DeclarationPriority.cs" />
<Compile Include="ConductorPatch\ConductorBase.cs" />
<Compile Include="ConductorPatch\HarmonyPatchSet.cs" />
<Compile Include="ConductorPatch\IConductorPatchFactory.cs" />
<Compile Include="ContextMenuHacker\ContextMenuHacker.cs" />
<Compile Include="ContextMenuHacker\ContextMenuItemType.cs" />
<Compile Include="ContextMenuHacker\IContextMenuHacker.cs" />
<Compile Include="DiagramUpdater\IDiagramUpdater.cs" />
<Compile Include="DiagramUpdater\DiagramUpdater.cs" />
<Compile Include="MethodOverrideMode.cs" />
<Compile Include="PreTrainPatch\IPreTrainLocationConverter.cs" />
<Compile Include="PreTrainPatch\IPreTrainPatchFactory.cs" />
<Compile Include="PreTrainPatch\PreTrainLocation.cs" />
Expand Down
100 changes: 100 additions & 0 deletions Extensions/AtsEx.CoreExtensions/ConductorPatch/ConductorBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using BveTypes.ClassWrappers;

namespace AtsEx.Extensions.ConductorPatch
{
/// <summary>
/// 車掌動作を上書きするための基本クラスを表します。
/// </summary>
public abstract class ConductorBase
{
/// <summary>
/// オリジナルの車掌動作が定義されている <see cref="Conductor"/> です。
/// </summary>
protected readonly Conductor Original;

/// <summary>
/// 停止位置の修正を要求したことを通知します。
/// </summary>
protected abstract event EventHandler FixStopPositionRequested;

/// <summary>
/// 停止位置が良いことを確認したことを通知します。
/// </summary>
protected abstract event EventHandler StopPositionChecked;

/// <summary>
/// ドアスイッチを開方向に扱ったことを通知します。
/// </summary>
protected abstract event EventHandler DoorOpening;

/// <summary>
/// 発車ベルスイッチを扱ったこと、つまり停車場リストで定義した departureSound の再生を開始することを通知します。
/// </summary>
protected abstract event EventHandler DepartureSoundPlaying;

/// <summary>
/// ドアスイッチを閉方向に扱ったことを通知します。
/// </summary>
protected abstract event EventHandler DoorClosing;

/// <summary>
/// 車側灯の滅灯を確認したこと、つまり全てのドアが閉まりきったことを通知します。
/// </summary>
protected abstract event EventHandler DoorClosed;

/// <summary>
/// <see cref="ConductorBase"/> クラスの新しいインスタンスを生成します。
/// </summary>
/// <param name="original">オリジナルの車掌動作が定義されている <see cref="Conductor"/>。</param>
protected ConductorBase(Conductor original)
{
Original = original;

FixStopPositionRequested += (sender, e) => Original.FixStopPositionRequested_Invoke();
StopPositionChecked += (sender, e) => Console.WriteLine("車掌: 停止位置よし");
DoorOpening += (sender, e) => Console.WriteLine("車掌スイッチ: 開");
DepartureSoundPlaying += (sender, e) => Console.WriteLine("発車ベル: ON");
DoorClosing += (sender, e) => Console.WriteLine("車掌スイッチ: 閉");
DoorClosed += (sender, e) =>
{
Console.WriteLine("側灯滅");
Original.DepartureRequested_Invoke();
};
}

/// <summary>
/// 自列車がテレポートしたときに呼び出されます。
/// </summary>
/// <param name="nextStationIndex">ジャンプ先の距離程における次駅のインデックス。</param>
/// <param name="isDoorClosed">ドアが閉まっているかどうか。</param>
/// <returns>オリジナルの処理をオーバーライドする方法。</returns>
internal protected virtual MethodOverrideMode OnJumped(int nextStationIndex, bool isDoorClosed)
{
return MethodOverrideMode.RunOriginal;
}

/// <summary>
/// ドアの状態が変更されたときに呼び出されます。
/// </summary>
/// <returns>オリジナルの処理をオーバーライドする方法。</returns>
internal protected virtual MethodOverrideMode OnDoorStateChanged()
{
return MethodOverrideMode.RunOriginal;
}

/// <summary>
/// 毎フレーム呼び出されます。
/// </summary>
/// <returns>オリジナルの処理をオーバーライドする方法。</returns>
internal protected virtual MethodOverrideMode OnTick()
{
return MethodOverrideMode.RunOriginal;
}
}
}
31 changes: 31 additions & 0 deletions Extensions/AtsEx.CoreExtensions/ConductorPatch/ConductorPatch.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AtsEx.Extensions.ConductorPatch
{
/// <summary>
/// 車掌動作を上書きするパッチを表します。
/// </summary>
public class ConductorPatch
{
/// <summary>
/// 新しい車掌動作を定義した <see cref="ConductorBase"/> を取得します。
/// </summary>
public ConductorBase Conductor { get; }

/// <summary>
/// 車掌動作の上書き宣言の優先度を取得します。
/// </summary>
public DeclarationPriority Priority { get; }


internal ConductorPatch(ConductorBase conductor, DeclarationPriority priority)
{
Conductor = conductor;
Priority = priority;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using BveTypes.ClassWrappers;
using ObjectiveHarmonyPatch;

using AtsEx.PluginHost.Plugins;
using AtsEx.PluginHost.Plugins.Extensions;

namespace AtsEx.Extensions.ConductorPatch
{
[PluginType(PluginType.Extension)]
[ExtensionMainDisplayType(typeof(IConductorPatchFactory))]
internal class ConductorPatchFactory : AssemblyPluginBase, IConductorPatchFactory
{
private readonly HarmonyPatchSet HarmonyPatches;
private readonly ConductorPatchInvoker PatchInvoker;

private readonly List<ConductorPatch> Patches = new List<ConductorPatch>();

public override string Title { get; } = nameof(ConductorPatch);
public override string Description { get; } = "車掌の動作を自由に変更できるようにするパッチを提供します。";

public ConductorPatchFactory(PluginBuilder builder) : base(builder)
{
HarmonyPatches = new HarmonyPatchSet(BveHacker.BveTypes);
PatchInvoker = new ConductorPatchInvoker(HarmonyPatches);

BveHacker.ScenarioClosed += OnScenarioClosed;
}

public override void Dispose()
{
HarmonyPatches.Dispose();
}

private void OnScenarioClosed(object sender, EventArgs e)
{
PatchInvoker.ActivePatch = null;
}

public override TickResult Tick(TimeSpan elapsed) => new ExtensionTickResult();

public void BeginPatch(Func<Conductor, ConductorBase> conductorFactory, DeclarationPriority priority, Action<ConductorPatch> patchedCallback)
{
HarmonyPatches.Constructor.Invoked += OnConductorConstructed;


PatchInvokationResult OnConductorConstructed(object sender, PatchInvokedEventArgs e)
{
HarmonyPatches.Constructor.Invoked -= OnConductorConstructed;

Conductor original = Conductor.FromSource(e.Instance);
ConductorBase conductor = conductorFactory(original);
ConductorPatch patch = Patch(conductor, priority);

patchedCallback(patch);
return PatchInvokationResult.DoNothing(e);
}
}

public ConductorPatch Patch(ConductorBase conductor, DeclarationPriority priority)
{
if (PatchInvoker.ActivePatch?.Priority == DeclarationPriority.TopMost)
{
throw new InvalidOperationException($"優先度 {DeclarationPriority.TopMost} のパッチが既に適用されています。");
}

ConductorPatch patch = new ConductorPatch(conductor, priority);
Patches.Add(patch);

PatchInvoker.ActivePatch = GetStrongest();
return patch;
}

public void Unpatch(ConductorPatch patch)
{
Patches.Remove(patch);
PatchInvoker.ActivePatch = GetStrongest();
}

private ConductorPatch GetStrongest()
{
ConductorPatch topMost = Patches.FirstOrDefault(patch => patch.Priority == DeclarationPriority.TopMost);
if (!(topMost is null)) return topMost;

ConductorPatch sequentially = Patches.LastOrDefault(patch => patch.Priority == DeclarationPriority.Sequentially);
if (!(sequentially is null)) return sequentially;

ConductorPatch asDefaultValue = Patches.LastOrDefault();
return asDefaultValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using ObjectiveHarmonyPatch;

namespace AtsEx.Extensions.ConductorPatch
{
internal class ConductorPatchInvoker
{
private readonly HarmonyPatchSet HarmonyPatches;

public ConductorPatch ActivePatch { get; set; } = null;

public ConductorPatchInvoker(HarmonyPatchSet harmonyPatches)
{
HarmonyPatches = harmonyPatches;

HarmonyPatches.OnJumped.Invoked += OnJumped;
HarmonyPatches.OnDoorStateChanged.Invoked += OnDoorStateChanged;
HarmonyPatches.OnTick.Invoked += OnTick;
}

private PatchInvokationResult OnJumped(object sender, PatchInvokedEventArgs e)
{
if (ActivePatch is null) return PatchInvokationResult.DoNothing(e);

MethodOverrideMode overrideMode = ActivePatch.Conductor.OnJumped((int)e.Args[0], (bool)e.Args[1]);
return CreateInvokationResult(overrideMode);
}

private PatchInvokationResult OnDoorStateChanged(object sender, PatchInvokedEventArgs e)
{
if (ActivePatch is null) return PatchInvokationResult.DoNothing(e);

MethodOverrideMode overrideMode = ActivePatch.Conductor.OnDoorStateChanged();
return CreateInvokationResult(overrideMode);
}

private PatchInvokationResult OnTick(object sender, PatchInvokedEventArgs e)
{
if (ActivePatch is null) return PatchInvokationResult.DoNothing(e);

MethodOverrideMode overrideMode = ActivePatch.Conductor.OnTick();
return CreateInvokationResult(overrideMode);
}

private PatchInvokationResult CreateInvokationResult(MethodOverrideMode overrideMode)
{
switch (overrideMode)
{
case MethodOverrideMode.SkipOriginal:
return new PatchInvokationResult(SkipModes.SkipOriginal);

case MethodOverrideMode.RunOriginal:
return new PatchInvokationResult(SkipModes.Continue);

default:
throw new NotImplementedException();
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AtsEx.Extensions.ConductorPatch
{
/// <summary>
/// 車掌動作の上書き宣言の優先度を指定します。
/// </summary>
public enum DeclarationPriority
{
/// <summary>
/// 既定値として宣言することを指定します。他に宣言が存在する場合は無条件に上書きされます。
/// </summary>
AsDefaultValue = 0,

/// <summary>
/// 通常の優先度で宣言することを指定します。既になされた優先度 <see cref="Sequentially"/> の宣言を上書きしますが、後に他の優先度 <see cref="Sequentially"/> の宣言がなされた場合は上書きされます。
/// </summary>
Sequentially,

/// <summary>
/// 最上級の優先度で宣言することを指定します。全ての他の宣言を無条件に上書きします。
/// </summary>
TopMost,
}
}
39 changes: 39 additions & 0 deletions Extensions/AtsEx.CoreExtensions/ConductorPatch/HarmonyPatchSet.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using BveTypes;
using BveTypes.ClassWrappers;
using ObjectiveHarmonyPatch;
using TypeWrapping;

namespace AtsEx.Extensions.ConductorPatch
{
internal class HarmonyPatchSet : IDisposable
{
public HarmonyPatch Constructor { get; }
public HarmonyPatch OnJumped { get; }
public HarmonyPatch OnDoorStateChanged { get; }
public HarmonyPatch OnTick { get; }

public HarmonyPatchSet(BveTypeSet bveTypes)
{
ClassMemberSet members = bveTypes.GetClassInfoOf<Conductor>();

Constructor = HarmonyPatch.Patch(nameof(ConductorPatchFactory), members.GetSourceConstructor().Source, PatchType.Postfix);
OnJumped = HarmonyPatch.Patch(nameof(ConductorPatchFactory), members.GetSourceMethodOf(nameof(Conductor.OnJumped)).Source, PatchType.Prefix);
OnDoorStateChanged = HarmonyPatch.Patch(nameof(ConductorPatchFactory), members.GetSourceMethodOf(nameof(Conductor.OnDoorStateChanged)).Source, PatchType.Prefix);
OnTick = HarmonyPatch.Patch(nameof(ConductorPatchFactory), members.GetSourceMethodOf(nameof(Conductor.OnTick)).Source, PatchType.Prefix);
}

public void Dispose()
{
Constructor.Dispose();
OnJumped.Dispose();
OnDoorStateChanged.Dispose();
OnTick.Dispose();
}
}
}
Loading

0 comments on commit 6c29ec5

Please sign in to comment.