mirror of
https://github.com/project-redbud/FunGame-Core.git
synced 2026-03-05 22:20:26 +00:00
parent
c4e29b1f4f
commit
915de4bc36
@ -68,6 +68,43 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 移除工厂方法
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
|
/// <param name="d"></param>
|
||||||
|
public void UnRegisterFactory<T>(EntityFactoryDelegate<T> d)
|
||||||
|
{
|
||||||
|
if (typeof(T) == typeof(Character) && d is EntityFactoryDelegate<Character> character)
|
||||||
|
{
|
||||||
|
CharacterFactories.Remove(character);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Inventory) && d is EntityFactoryDelegate<Inventory> inventory)
|
||||||
|
{
|
||||||
|
InventoryFactories.Remove(inventory);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Skill) && d is EntityFactoryDelegate<Skill> skill)
|
||||||
|
{
|
||||||
|
SkillFactories.Remove(skill);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Effect) && d is EntityFactoryDelegate<Effect> effect)
|
||||||
|
{
|
||||||
|
EffectFactories.Remove(effect);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Item) && d is EntityFactoryDelegate<Item> item)
|
||||||
|
{
|
||||||
|
ItemFactories.Remove(item);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(Room) && d is EntityFactoryDelegate<Room> room)
|
||||||
|
{
|
||||||
|
RoomFactories.Remove(room);
|
||||||
|
}
|
||||||
|
if (typeof(T) == typeof(User) && d is EntityFactoryDelegate<User> user)
|
||||||
|
{
|
||||||
|
UserFactories.Remove(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个实体实例
|
/// 构造一个实体实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -39,9 +39,17 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 已加载的模组DLL名称对应的路径
|
/// 已加载的模组DLL名称对应的路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, string> ModuleFilePaths => new(AddonManager.ModuleFilePaths);
|
public Dictionary<string, string> ModuleFilePaths => IsHotLoadMode ? new(HotLoadAddonManager.ModuleFilePaths) : new(AddonManager.ModuleFilePaths);
|
||||||
|
|
||||||
private GameModuleLoader() { }
|
/// <summary>
|
||||||
|
/// 使用可热更新的加载项模式
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHotLoadMode { get; } = false;
|
||||||
|
|
||||||
|
private GameModuleLoader(bool hotMode = false)
|
||||||
|
{
|
||||||
|
IsHotLoadMode = hotMode;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器
|
/// 传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器
|
||||||
@ -61,11 +69,13 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
foreach (GameMap map in loader.Maps.Values.ToList())
|
foreach (GameMap map in loader.Maps.Values.ToList())
|
||||||
{
|
{
|
||||||
|
map.ModuleLoader = loader;
|
||||||
map.AfterLoad(loader, otherobjs);
|
map.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
AddonManager.LoadGameModules(loader.Modules, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
AddonManager.LoadGameModules(loader.Modules, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
foreach (GameModule module in loader.Modules.Values.ToList())
|
foreach (GameModule module in loader.Modules.Values.ToList())
|
||||||
{
|
{
|
||||||
|
module.ModuleLoader = loader;
|
||||||
// 读取模组的依赖集合
|
// 读取模组的依赖集合
|
||||||
module.GameModuleDepend.GetDependencies(loader);
|
module.GameModuleDepend.GetDependencies(loader);
|
||||||
// 如果模组加载后需要执行代码,请重写AfterLoad方法
|
// 如果模组加载后需要执行代码,请重写AfterLoad方法
|
||||||
@ -77,11 +87,13 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
foreach (GameMap map in loader.Maps.Values.ToList())
|
foreach (GameMap map in loader.Maps.Values.ToList())
|
||||||
{
|
{
|
||||||
|
map.ModuleLoader = loader;
|
||||||
map.AfterLoad(loader, otherobjs);
|
map.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
AddonManager.LoadGameModulesForServer(loader.ModuleServers, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
AddonManager.LoadGameModulesForServer(loader.ModuleServers, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
foreach (GameModuleServer server in loader.ModuleServers.Values.ToList())
|
foreach (GameModuleServer server in loader.ModuleServers.Values.ToList())
|
||||||
{
|
{
|
||||||
|
server.ModuleLoader = loader;
|
||||||
server.GameModuleDepend.GetDependencies(loader);
|
server.GameModuleDepend.GetDependencies(loader);
|
||||||
server.AfterLoad(loader, otherobjs);
|
server.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
@ -89,6 +101,101 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器 [ 可热更新模式 ]
|
||||||
|
/// <para>runtime = <see cref="FunGameInfo.FunGame.FunGame_Desktop"/> 时,仅读取 <seealso cref="Modules"/></para>
|
||||||
|
/// <para>runtime = <see cref="FunGameInfo.FunGame.FunGame_Server"/> 时,仅读取 <seealso cref="ModuleServers"/></para>
|
||||||
|
/// <seealso cref="Maps"/> 都会读取
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime">传入 <see cref="FunGameInfo.FunGame"/> 类型来创建指定端的模组读取器</param>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.AddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static GameModuleLoader LoadGameModulesByHotLoadMode(FunGameInfo.FunGame runtime, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
GameModuleLoader loader = new(true);
|
||||||
|
if (runtime == FunGameInfo.FunGame.FunGame_Desktop)
|
||||||
|
{
|
||||||
|
List<GameMap> updated = HotLoadAddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
|
foreach (GameMap map in updated)
|
||||||
|
{
|
||||||
|
map.ModuleLoader = loader;
|
||||||
|
map.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
List<GameModule> updatedModule = HotLoadAddonManager.LoadGameModules(loader.Modules, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
|
foreach (GameModule module in updatedModule)
|
||||||
|
{
|
||||||
|
module.ModuleLoader = loader;
|
||||||
|
// 读取模组的依赖集合
|
||||||
|
module.GameModuleDepend.GetDependencies(loader);
|
||||||
|
// 如果模组加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
module.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (runtime == FunGameInfo.FunGame.FunGame_Server)
|
||||||
|
{
|
||||||
|
List<GameMap> updated = HotLoadAddonManager.LoadGameMaps(loader.Maps, otherobjs);
|
||||||
|
foreach (GameMap map in updated)
|
||||||
|
{
|
||||||
|
map.ModuleLoader = loader;
|
||||||
|
map.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
List<GameModuleServer> updatedServer = HotLoadAddonManager.LoadGameModulesForServer(loader.ModuleServers, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
|
||||||
|
foreach (GameModuleServer server in updatedServer)
|
||||||
|
{
|
||||||
|
server.ModuleLoader = loader;
|
||||||
|
server.GameModuleDepend.GetDependencies(loader);
|
||||||
|
server.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtime"></param>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
/// <param name="otherobjs"></param>
|
||||||
|
public void HotReload(FunGameInfo.FunGame runtime, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
if (!IsHotLoadMode) return;
|
||||||
|
if (runtime == FunGameInfo.FunGame.FunGame_Desktop)
|
||||||
|
{
|
||||||
|
List<GameMap> updated = HotLoadAddonManager.LoadGameMaps(Maps, otherobjs);
|
||||||
|
foreach (GameMap map in updated)
|
||||||
|
{
|
||||||
|
map.ModuleLoader = this;
|
||||||
|
map.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
List<GameModule> updatedModule = HotLoadAddonManager.LoadGameModules(Modules, Characters, Skills, Items, delegates, otherobjs);
|
||||||
|
foreach (GameModule module in updatedModule)
|
||||||
|
{
|
||||||
|
module.ModuleLoader = this;
|
||||||
|
// 读取模组的依赖集合
|
||||||
|
module.GameModuleDepend.GetDependencies(this);
|
||||||
|
// 如果模组加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
module.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (runtime == FunGameInfo.FunGame.FunGame_Server)
|
||||||
|
{
|
||||||
|
List<GameMap> updated = HotLoadAddonManager.LoadGameMaps(Maps, otherobjs);
|
||||||
|
foreach (GameMap map in updated)
|
||||||
|
{
|
||||||
|
map.ModuleLoader = this;
|
||||||
|
map.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
List<GameModuleServer> updatedServer = HotLoadAddonManager.LoadGameModulesForServer(ModuleServers, Characters, Skills, Items, delegates, otherobjs);
|
||||||
|
foreach (GameModuleServer server in updatedServer)
|
||||||
|
{
|
||||||
|
server.ModuleLoader = this;
|
||||||
|
server.GameModuleDepend.GetDependencies(this);
|
||||||
|
server.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取对应名称的模组实例
|
/// 获取对应名称的模组实例
|
||||||
/// <para>如果需要取得服务器模组的实例,请调用 <see cref="GetServerMode"/></para>
|
/// <para>如果需要取得服务器模组的实例,请调用 <see cref="GetServerMode"/></para>
|
||||||
|
|||||||
@ -6,8 +6,10 @@ using System.Security.Cryptography;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
// 通用工具类,客户端和服务器端都可以直接调用的工具方法都可以写在这里
|
// 通用工具类,客户端和服务器端都可以直接调用的工具方法都可以写在这里
|
||||||
namespace Milimoe.FunGame.Core.Api.Utility
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
@ -850,4 +852,27 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region 加载项服务
|
||||||
|
|
||||||
|
public class HotLoadAddonUtility
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新 DLL
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">DLL 完整路径</param>
|
||||||
|
/// <returns>是否成功热更新</returns>
|
||||||
|
public static bool HotReload(string filePath) => HotLoadAddonManager.HotReload(filePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试获取当前最新的实例
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">预期类型</typeparam>
|
||||||
|
/// <param name="addonName">插件/模组名称</param>
|
||||||
|
/// <param name="instance">最新的实例</param>
|
||||||
|
/// <returns>是否找到</returns>
|
||||||
|
public static bool TryGetLiveInstance<T>(string addonName, out T? instance) where T : class, IAddon => HotLoadAddonManager.TryGetLiveInstance(addonName, out instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,11 +15,16 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 已加载的插件DLL名称对应的路径
|
/// 已加载的插件DLL名称对应的路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
public Dictionary<string, string> PluginFilePaths => IsHotLoadMode ? new(HotLoadAddonManager.PluginFilePaths) : new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
private PluginLoader()
|
/// <summary>
|
||||||
|
/// 使用可热更新的加载项模式
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHotLoadMode { get; } = false;
|
||||||
|
|
||||||
|
private PluginLoader(bool hotMode = false)
|
||||||
{
|
{
|
||||||
|
IsHotLoadMode = hotMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,12 +39,48 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
AddonManager.LoadPlugins(loader.Plugins, delegates, otherobjs);
|
AddonManager.LoadPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
foreach (Plugin plugin in loader.Plugins.Values.ToList())
|
foreach (Plugin plugin in loader.Plugins.Values.ToList())
|
||||||
{
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
plugin.AfterLoad(loader, otherobjs);
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件 [ 可热更新模式 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.AddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static PluginLoader LoadPluginsByHotLoadMode(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
PluginLoader loader = new();
|
||||||
|
List<Plugin> updated = HotLoadAddonManager.LoadPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (Plugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
/// <param name="otherobjs"></param>
|
||||||
|
public void HotReload(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
if (!IsHotLoadMode) return;
|
||||||
|
List<Plugin> updated = HotLoadAddonManager.LoadPlugins(Plugins, delegates, otherobjs);
|
||||||
|
foreach (Plugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = this;
|
||||||
|
plugin.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Plugin this[string name]
|
public Plugin this[string name]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@ -15,11 +15,16 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 已加载的插件DLL名称对应的路径
|
/// 已加载的插件DLL名称对应的路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
public Dictionary<string, string> PluginFilePaths => IsHotLoadMode ? new(HotLoadAddonManager.PluginFilePaths) : new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
private ServerPluginLoader()
|
/// <summary>
|
||||||
|
/// 使用可热更新的加载项模式
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHotLoadMode { get; } = false;
|
||||||
|
|
||||||
|
private ServerPluginLoader(bool hotMode = false)
|
||||||
{
|
{
|
||||||
|
IsHotLoadMode = hotMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,12 +39,48 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
AddonManager.LoadServerPlugins(loader.Plugins, delegates, otherobjs);
|
AddonManager.LoadServerPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
foreach (ServerPlugin plugin in loader.Plugins.Values.ToList())
|
foreach (ServerPlugin plugin in loader.Plugins.Values.ToList())
|
||||||
{
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
plugin.AfterLoad(loader, otherobjs);
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件 [ 可热更新模式 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.BaseAddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static ServerPluginLoader LoadPluginsByHotLoadMode(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
ServerPluginLoader loader = new(true);
|
||||||
|
List<ServerPlugin> updated = HotLoadAddonManager.LoadServerPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (ServerPlugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
/// <param name="otherobjs"></param>
|
||||||
|
public void HotReload(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
if (!IsHotLoadMode) return;
|
||||||
|
List<ServerPlugin> updated = HotLoadAddonManager.LoadServerPlugins(Plugins, delegates, otherobjs);
|
||||||
|
foreach (ServerPlugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = this;
|
||||||
|
plugin.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ServerPlugin this[string name]
|
public ServerPlugin this[string name]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
122
Api/Utility/SkillExtension.cs
Normal file
122
Api/Utility/SkillExtension.cs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
|
{
|
||||||
|
public static class SkillExtension
|
||||||
|
{
|
||||||
|
public static string SkillOwner(this Skill skill, Character? character = null)
|
||||||
|
{
|
||||||
|
if (character is null && skill.Character is not null)
|
||||||
|
{
|
||||||
|
character = skill.Character;
|
||||||
|
}
|
||||||
|
if (character is null)
|
||||||
|
{
|
||||||
|
return "你";
|
||||||
|
}
|
||||||
|
return character.NickName != "" ? character.NickName : character.GetName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TargetDescription(this Skill skill)
|
||||||
|
{
|
||||||
|
if (skill.IsNonDirectional && skill.GamingQueue?.Map != null)
|
||||||
|
{
|
||||||
|
return skill.RangeTargetDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
string str;
|
||||||
|
|
||||||
|
if (skill.SelectAllTeammates)
|
||||||
|
{
|
||||||
|
str = "友方全体角色";
|
||||||
|
}
|
||||||
|
else if (skill.SelectAllEnemies)
|
||||||
|
{
|
||||||
|
str = "敌方全体角色";
|
||||||
|
}
|
||||||
|
else if (skill.CanSelectTeammate && !skill.CanSelectEnemy)
|
||||||
|
{
|
||||||
|
str = $"目标{(skill.CanSelectTargetCount > 1 ? $"至多 {skill.CanSelectTargetCount} 个" : "")}友方角色{(!skill.CanSelectSelf ? "(不可选择自身)" : "")}";
|
||||||
|
}
|
||||||
|
else if (!skill.CanSelectTeammate && skill.CanSelectEnemy)
|
||||||
|
{
|
||||||
|
str = $"目标{(skill.CanSelectTargetCount > 1 ? $"至多 {skill.CanSelectTargetCount} 个" : "")}敌方角色";
|
||||||
|
}
|
||||||
|
else if (!skill.CanSelectTeammate && !skill.CanSelectEnemy && skill.CanSelectSelf)
|
||||||
|
{
|
||||||
|
str = $"自身";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str = $"{(skill.CanSelectTargetCount > 1 ? $"至多 {skill.CanSelectTargetCount} 个" : "")}目标";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skill.CanSelectTargetRange > 0 && skill.GamingQueue?.Map != null)
|
||||||
|
{
|
||||||
|
str += $"以及以{(skill.CanSelectTargetCount > 1 ? "这些" : "该")}目标为中心,半径为 {skill.CanSelectTargetRange} 格的菱形区域中的等同阵营角色";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string RangeTargetDescription(this Skill skill)
|
||||||
|
{
|
||||||
|
string str = "";
|
||||||
|
|
||||||
|
int range = skill.CanSelectTargetRange;
|
||||||
|
if (range <= 0)
|
||||||
|
{
|
||||||
|
str = "目标地点";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (skill.SkillRangeType)
|
||||||
|
{
|
||||||
|
case SkillRangeType.Diamond:
|
||||||
|
str = $"目标半径为 {skill.CanSelectTargetRange} 格的菱形区域";
|
||||||
|
break;
|
||||||
|
case SkillRangeType.Circle:
|
||||||
|
str = $"目标半径为 {skill.CanSelectTargetRange} 格的圆形区域";
|
||||||
|
break;
|
||||||
|
case SkillRangeType.Square:
|
||||||
|
str = $"目标边长为 {skill.CanSelectTargetRange * 2 + 1} 格的正方形区域";
|
||||||
|
break;
|
||||||
|
case SkillRangeType.Line:
|
||||||
|
str = $"自身与目标地点之间的、宽度为 {skill.CanSelectTargetRange} 格的直线区域";
|
||||||
|
break;
|
||||||
|
case SkillRangeType.LinePass:
|
||||||
|
str = $"自身与目标地点之间的、宽度为 {skill.CanSelectTargetRange} 格的直线区域以及贯穿该目标地点直至地图边缘的等宽直线区域";
|
||||||
|
break;
|
||||||
|
case SkillRangeType.Sector:
|
||||||
|
str = $"目标最大半径为 {skill.CanSelectTargetRange} 格的扇形区域";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skill.SelectIncludeCharacterGrid)
|
||||||
|
{
|
||||||
|
if (skill.CanSelectTeammate && !skill.CanSelectEnemy)
|
||||||
|
{
|
||||||
|
str = $"{str}中的所有友方角色{(!skill.CanSelectSelf ? "(包括自身)" : "")}";
|
||||||
|
}
|
||||||
|
else if (!skill.CanSelectTeammate && skill.CanSelectEnemy)
|
||||||
|
{
|
||||||
|
str = $"{str}中的所有敌方角色";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str = $"{str}中的所有角色";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
str = "一个未被角色占据的";
|
||||||
|
}
|
||||||
|
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
using Milimoe.FunGame.Core.Model;
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
using Milimoe.FunGame.Core.Service;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Api.Utility
|
namespace Milimoe.FunGame.Core.Api.Utility
|
||||||
{
|
{
|
||||||
@ -154,6 +155,14 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
return msg.Trim();
|
return msg.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开启循环检查是否有未清除的加载项上下文
|
||||||
|
/// </summary>
|
||||||
|
public static void StartCleanUnusedAddonContexts(Action<Exception>? error = null)
|
||||||
|
{
|
||||||
|
Shared.AddRecurringTask("CleanUnusedContexts", TimeSpan.FromMinutes(2), HotLoadAddonManager.CleanUnusedContexts, true, error);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行任务
|
/// 执行任务
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -81,6 +81,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
* Console
|
* Console
|
||||||
*/
|
*/
|
||||||
WriteINI("Console", "LogLevel", "INFO");
|
WriteINI("Console", "LogLevel", "INFO");
|
||||||
|
WriteINI("Console", "UseHotLoadAddons", "false");
|
||||||
/**
|
/**
|
||||||
* Server
|
* Server
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -15,11 +15,16 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 已加载的插件DLL名称对应的路径
|
/// 已加载的插件DLL名称对应的路径
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static Dictionary<string, string> PluginFilePaths => new(AddonManager.PluginFilePaths);
|
public Dictionary<string, string> PluginFilePaths => IsHotLoadMode ? new(HotLoadAddonManager.PluginFilePaths) : new(AddonManager.PluginFilePaths);
|
||||||
|
|
||||||
private WebAPIPluginLoader()
|
/// <summary>
|
||||||
|
/// 使用可热更新的加载项模式
|
||||||
|
/// </summary>
|
||||||
|
public bool IsHotLoadMode { get; } = false;
|
||||||
|
|
||||||
|
private WebAPIPluginLoader(bool hotMode = false)
|
||||||
{
|
{
|
||||||
|
IsHotLoadMode = hotMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -34,12 +39,48 @@ namespace Milimoe.FunGame.Core.Api.Utility
|
|||||||
AddonManager.LoadWebAPIPlugins(loader.Plugins, delegates, otherobjs);
|
AddonManager.LoadWebAPIPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
foreach (WebAPIPlugin plugin in loader.Plugins.Values.ToList())
|
foreach (WebAPIPlugin plugin in loader.Plugins.Values.ToList())
|
||||||
{
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
plugin.AfterLoad(loader, otherobjs);
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
}
|
}
|
||||||
return loader;
|
return loader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构建一个插件读取器并读取插件 [ 可热更新模式 ]
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates">用于构建 <see cref="Controller.BaseAddonController{T}"/></param>
|
||||||
|
/// <param name="otherobjs">其他需要传入给插件初始化的对象</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static WebAPIPluginLoader LoadPluginsByHotLoadMode(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
WebAPIPluginLoader loader = new(true);
|
||||||
|
List<WebAPIPlugin> updated = HotLoadAddonManager.LoadWebAPIPlugins(loader.Plugins, delegates, otherobjs);
|
||||||
|
foreach (WebAPIPlugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = loader;
|
||||||
|
// 如果插件加载后需要执行代码,请重写AfterLoad方法
|
||||||
|
plugin.AfterLoad(loader, otherobjs);
|
||||||
|
}
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="delegates"></param>
|
||||||
|
/// <param name="otherobjs"></param>
|
||||||
|
public void HotReload(Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
if (!IsHotLoadMode) return;
|
||||||
|
List<WebAPIPlugin> updated = HotLoadAddonManager.LoadWebAPIPlugins(Plugins, delegates, otherobjs);
|
||||||
|
foreach (WebAPIPlugin plugin in updated)
|
||||||
|
{
|
||||||
|
plugin.PluginLoader = this;
|
||||||
|
plugin.AfterLoad(this, otherobjs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public WebAPIPlugin this[string name]
|
public WebAPIPlugin this[string name]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@ -8,5 +8,6 @@
|
|||||||
public string Author { get; }
|
public string Author { get; }
|
||||||
|
|
||||||
public bool Load(params object[] objs);
|
public bool Load(params object[] objs);
|
||||||
|
public void UnLoad(params object[] objs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
namespace Milimoe.FunGame.Core.Interface.Addons
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
{
|
{
|
||||||
public interface IGameMap : IAddon
|
public interface IGameMap : IAddon
|
||||||
{
|
{
|
||||||
|
public GameModuleLoader? ModuleLoader { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Milimoe.FunGame.Core.Model;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Interface.Addons
|
namespace Milimoe.FunGame.Core.Interface.Addons
|
||||||
{
|
{
|
||||||
@ -6,6 +7,7 @@ namespace Milimoe.FunGame.Core.Interface.Addons
|
|||||||
IGamingRandomEventHandler, IGamingRoundEventHandler, IGamingLevelUpEventHandler, IGamingMoveEventHandler, IGamingAttackEventHandler, IGamingSkillEventHandler, IGamingItemEventHandler, IGamingMagicEventHandler,
|
IGamingRandomEventHandler, IGamingRoundEventHandler, IGamingLevelUpEventHandler, IGamingMoveEventHandler, IGamingAttackEventHandler, IGamingSkillEventHandler, IGamingItemEventHandler, IGamingMagicEventHandler,
|
||||||
IGamingBuyEventHandler, IGamingSuperSkillEventHandler, IGamingPauseEventHandler, IGamingUnpauseEventHandler, IGamingSurrenderEventHandler, IGamingUpdateInfoEventHandler, IGamingPunishEventHandler, IGameModuleDepend
|
IGamingBuyEventHandler, IGamingSuperSkillEventHandler, IGamingPauseEventHandler, IGamingUnpauseEventHandler, IGamingSurrenderEventHandler, IGamingUpdateInfoEventHandler, IGamingPunishEventHandler, IGameModuleDepend
|
||||||
{
|
{
|
||||||
|
public GameModuleLoader? ModuleLoader { get; }
|
||||||
public bool HideMain { get; }
|
public bool HideMain { get; }
|
||||||
public void StartGame(Gaming instance, params object[] args);
|
public void StartGame(Gaming instance, params object[] args);
|
||||||
public void StartUI(params object[] args);
|
public void StartUI(params object[] args);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
using Milimoe.FunGame.Core.Library.Common.Addon;
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
@ -6,6 +7,8 @@ namespace Milimoe.FunGame.Core.Interface.Addons
|
|||||||
{
|
{
|
||||||
public interface IGameModuleServer : IAddon, IAddonController<IGameModuleServer>, IGameModuleDepend
|
public interface IGameModuleServer : IAddon, IAddonController<IGameModuleServer>, IGameModuleDepend
|
||||||
{
|
{
|
||||||
|
public GameModuleLoader? ModuleLoader { get; }
|
||||||
|
|
||||||
public bool StartServer(GamingObject obj, params object[] args);
|
public bool StartServer(GamingObject obj, params object[] args);
|
||||||
|
|
||||||
public Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data);
|
public Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data);
|
||||||
|
|||||||
13
Interface/Base/Addons/IHotReloadAware.cs
Normal file
13
Interface/Base/Addons/IHotReloadAware.cs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Interface.Base.Addons
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 实现此接口的插件/模组才能被热更新模式加载
|
||||||
|
/// </summary>
|
||||||
|
public interface IHotReloadAware
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 在卸载前调用,自行做一些清理,否则卸载不安全
|
||||||
|
/// </summary>
|
||||||
|
public void OnBeforeUnload();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -51,17 +51,26 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
// 模组加载后,不允许再次加载此模组
|
// 模组加载后,不允许再次加载此模组
|
||||||
_isLoaded = true;
|
_isLoaded = true;
|
||||||
// 注册工厂
|
// 注册工厂
|
||||||
Factory.OpenFactory.RegisterFactory(EntityFactory());
|
Factory.OpenFactory.RegisterFactory(CharacterFactory());
|
||||||
// 如果加载后需要执行代码,请重写AfterLoad方法
|
// 如果加载后需要执行代码,请重写AfterLoad方法
|
||||||
AfterLoad();
|
AfterLoad();
|
||||||
}
|
}
|
||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
Factory.OpenFactory.UnRegisterFactory(CharacterFactory());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册工厂
|
/// 注册工厂
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual Factory.EntityFactoryDelegate<Character> EntityFactory()
|
protected virtual Factory.EntityFactoryDelegate<Character> CharacterFactory()
|
||||||
{
|
{
|
||||||
return (id, name, args) =>
|
return (id, name, args) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Text;
|
||||||
using Milimoe.FunGame.Core.Api.Transmittal;
|
using Milimoe.FunGame.Core.Api.Transmittal;
|
||||||
using Milimoe.FunGame.Core.Api.Utility;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
using Milimoe.FunGame.Core.Entity;
|
using Milimoe.FunGame.Core.Entity;
|
||||||
using Milimoe.FunGame.Core.Interface;
|
using Milimoe.FunGame.Core.Interface;
|
||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Architecture;
|
||||||
using Milimoe.FunGame.Core.Library.Common.Event;
|
using Milimoe.FunGame.Core.Library.Common.Event;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
using Milimoe.FunGame.Core.Model;
|
using Milimoe.FunGame.Core.Model;
|
||||||
@ -30,7 +33,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模组:必须继承基类:<see cref="GameModule"/><para/>
|
/// GameModule 是用于客户端的模组。每个模组都有一个对应的服务器模组,可以简单理解为“一种游戏模式”<para/>
|
||||||
|
/// 必须继承基类:<see cref="GameModule"/><para/>
|
||||||
/// 继承事件接口并实现其方法来使模组生效。例如继承:<seealso cref="IGamingUpdateInfoEvent"/><para/>
|
/// 继承事件接口并实现其方法来使模组生效。例如继承:<seealso cref="IGamingUpdateInfoEvent"/><para/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExampleGameModule : GameModule, IGamingUpdateInfoEvent
|
public class ExampleGameModule : GameModule, IGamingUpdateInfoEvent
|
||||||
@ -78,8 +82,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
|
|
||||||
public override void StartUI(params object[] args)
|
public override void StartUI(params object[] args)
|
||||||
{
|
{
|
||||||
// 如果你是一个WPF或者Winform项目,可以在这里启动你的界面
|
/// 如果模组不依附 <see cref="Gaming"/> 类启动,或者没有UI,则不需要重写此方法
|
||||||
// 如果没有,则不需要重写此方法
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GamingUpdateInfoEvent(object sender, GamingEventArgs e, Dictionary<string, object> data)
|
public void GamingUpdateInfoEvent(object sender, GamingEventArgs e, Dictionary<string, object> data)
|
||||||
@ -112,7 +115,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
/// 模组服务器:必须继承基类:<see cref="GameModuleServer"/><para/>
|
/// 模组服务器:必须继承基类:<see cref="GameModuleServer"/><para/>
|
||||||
/// 使用switch块分类处理 <see cref="GamingType"/>。
|
/// 使用switch块分类处理 <see cref="GamingType"/>。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExampleGameModuleServer : GameModuleServer
|
public class ExampleGameModuleServer : GameModuleServer, IHotReloadAware
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注意:服务器模组的名称必须和模组名称相同。除非你指定了 <see cref="GameModule.IsConnectToOtherServerModule"/> 和 <see cref="GameModule.AssociatedServerModuleName"/>
|
/// 注意:服务器模组的名称必须和模组名称相同。除非你指定了 <see cref="GameModule.IsConnectToOtherServerModule"/> 和 <see cref="GameModule.AssociatedServerModuleName"/>
|
||||||
@ -137,6 +140,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
{
|
{
|
||||||
public GamingObject GamingObject { get; } = obj;
|
public GamingObject GamingObject { get; } = obj;
|
||||||
public List<User> ConnectedUser { get; } = [];
|
public List<User> ConnectedUser { get; } = [];
|
||||||
|
public List<Character> CharactersForPick { get; } = [];
|
||||||
|
public Dictionary<string, Character> UserCharacters { get; } = [];
|
||||||
public Dictionary<string, Dictionary<string, object>> UserData { get; } = [];
|
public Dictionary<string, Dictionary<string, object>> UserData { get; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,48 +189,24 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
await SendGamingMessage(obj.All.Values, GamingType.UpdateInfo, data);
|
await SendGamingMessage(obj.All.Values, GamingType.UpdateInfo, data);
|
||||||
|
|
||||||
// 新建一个线程等待所有玩家确认,如果超时则取消游戏,30秒
|
// 新建一个线程等待所有玩家确认,如果超时则取消游戏,30秒
|
||||||
CancellationTokenSource cts = new();
|
// 每200ms确认一次,不需要太频繁
|
||||||
CancellationToken ct = cts.Token;
|
await WaitForUsers(30, async () =>
|
||||||
Task timeoutTask = Task.Delay(TimeSpan.FromSeconds(30), ct);
|
|
||||||
|
|
||||||
Task completionTask = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
while (!ct.IsCancellationRequested)
|
|
||||||
{
|
{
|
||||||
if (worker.ConnectedUser.Count == obj.Users.Count)
|
if (worker.ConnectedUser.Count == obj.Users.Count)
|
||||||
{
|
{
|
||||||
Controller.WriteLine("所有玩家都已经连接。");
|
Controller.WriteLine("所有玩家都已经连接。");
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
// 每200ms确认一次,不需要太频繁
|
return false;
|
||||||
await Task.Delay(200);
|
}, 200, async () =>
|
||||||
}
|
|
||||||
}, ct);
|
|
||||||
|
|
||||||
// 等待完成或超时
|
|
||||||
Task completedTask = await Task.WhenAny(completionTask, timeoutTask);
|
|
||||||
|
|
||||||
if (completedTask == timeoutTask)
|
|
||||||
{
|
{
|
||||||
Controller.WriteLine("等待玩家连接超时,放弃该局游戏!", LogLevel.Warning);
|
Controller.WriteLine("等待玩家连接超时,放弃该局游戏!", LogLevel.Warning);
|
||||||
cts.Cancel();
|
await CancelGame(obj, worker, "由于等待超时,游戏已取消!");
|
||||||
|
}, async () =>
|
||||||
// 通知已连接的玩家
|
|
||||||
Dictionary<string, object> timeoutData = new()
|
|
||||||
{
|
{
|
||||||
{ "msg", "由于等待超时,游戏已取消!" }
|
// 所有玩家都连接完毕了,可以建立一个回合制游戏了
|
||||||
};
|
await StartGame(obj, worker);
|
||||||
// 结束
|
});
|
||||||
SendEndGame(obj);
|
|
||||||
worker.ConnectedUser.Clear();
|
|
||||||
Workers.Remove(obj.Room.Roomid, out _);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cts.Cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
cts.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data)
|
public override async Task<Dictionary<string, object>> GamingMessageHandler(IServerModel model, GamingType type, Dictionary<string, object> data)
|
||||||
@ -239,6 +220,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
switch (type)
|
switch (type)
|
||||||
{
|
{
|
||||||
case GamingType.Connect:
|
case GamingType.Connect:
|
||||||
|
{
|
||||||
// 编写处理“连接”命令的逻辑
|
// 编写处理“连接”命令的逻辑
|
||||||
// 如果需要处理客户端传递的参数:获取与客户端约定好的参数key对应的值
|
// 如果需要处理客户端传递的参数:获取与客户端约定好的参数key对应的值
|
||||||
string un = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "username") ?? "";
|
string un = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "username") ?? "";
|
||||||
@ -250,6 +232,33 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
}
|
}
|
||||||
else Controller.WriteLine(username + " 确认连接失败!", LogLevel.Warning);
|
else Controller.WriteLine(username + " 确认连接失败!", LogLevel.Warning);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
|
case GamingType.PickCharacter:
|
||||||
|
{
|
||||||
|
// 客户端选完了角色这里就要处理了
|
||||||
|
long id = NetworkUtility.JsonDeserializeFromDictionary<long>(data, "id");
|
||||||
|
if (worker.CharactersForPick.FirstOrDefault(c => c.Id == id) is Character character)
|
||||||
|
{
|
||||||
|
// 如果有人选一样的,你还没有做特殊处理的话,为了防止意外,最好复制一份
|
||||||
|
worker.UserCharacters[username] = character.Copy();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GamingType.Skill:
|
||||||
|
{
|
||||||
|
string e = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "event") ?? "";
|
||||||
|
if (e.Equals("SelectSkillTargets", StringComparison.CurrentCultureIgnoreCase))
|
||||||
|
{
|
||||||
|
long caster = NetworkUtility.JsonDeserializeFromDictionary<long>(data, "caster");
|
||||||
|
long[] targets = NetworkUtility.JsonDeserializeFromDictionary<long[]>(data, "targets") ?? [];
|
||||||
|
// 接收客户端传来的目标序号并记录
|
||||||
|
if (worker.UserData.TryGetValue(username, out Dictionary<string, object>? value) && value != null)
|
||||||
|
{
|
||||||
|
value.Add("SkillTargets", targets);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
await Task.Delay(1);
|
await Task.Delay(1);
|
||||||
break;
|
break;
|
||||||
@ -258,9 +267,307 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CancelGame(GamingObject obj, ModuleServerWorker worker, string reason)
|
||||||
|
{
|
||||||
|
// 通知所有玩家
|
||||||
|
await SendAllTextMessage(obj, reason);
|
||||||
|
// 结束
|
||||||
|
SendEndGame(obj);
|
||||||
|
worker.ConnectedUser.Clear();
|
||||||
|
Workers.Remove(obj.Room.Roomid, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task WaitForUsers(int waitSeconds, Func<Task<bool>> waitSomething, int delay, Func<Task> onTimeout, Func<Task> onCompleted)
|
||||||
|
{
|
||||||
|
// 这是一个用于等待的通用辅助方法
|
||||||
|
using CancellationTokenSource cts = new(TimeSpan.FromSeconds(waitSeconds));
|
||||||
|
CancellationToken ct = cts.Token;
|
||||||
|
|
||||||
|
while (!ct.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (await waitSomething())
|
||||||
|
{
|
||||||
|
await onCompleted();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Task.Delay(delay, ct);
|
||||||
|
}
|
||||||
|
catch (System.Exception e) when (e is not OperationCanceledException)
|
||||||
|
{
|
||||||
|
Controller.Error(e);
|
||||||
|
await onTimeout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 异常和超时都走超时逻辑
|
||||||
|
await onTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task StartGame(GamingObject obj, ModuleServerWorker worker)
|
||||||
|
{
|
||||||
|
Dictionary<User, Character> characters = [];
|
||||||
|
List<Character> characterPickeds = [];
|
||||||
|
Dictionary<string, object> data = [];
|
||||||
|
|
||||||
|
// 首先,让玩家们选择角色
|
||||||
|
// 需要一个待选的角色池
|
||||||
|
// 这些角色可以从工厂中获取,比如:
|
||||||
|
Character character1 = Factory.OpenFactory.GetInstance<Character>(1, "", []);
|
||||||
|
worker.CharactersForPick.Add(character1);
|
||||||
|
// 或者在什么地方已经有个列表?则使用复制方法
|
||||||
|
if (ModuleLoader != null && ModuleLoader.Characters.Count > 0)
|
||||||
|
{
|
||||||
|
CharacterModule characterModule = ModuleLoader.Characters.Values.First();
|
||||||
|
Character character2 = characterModule.Characters.Values.FirstOrDefault()?.Copy() ?? Factory.GetCharacter();
|
||||||
|
if (character2.Id > 0)
|
||||||
|
{
|
||||||
|
worker.CharactersForPick.Add(character2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 传整个对象或者id都可以,看你心情,推荐用id,轻量,方便
|
||||||
|
data["list"] = worker.CharactersForPick.Select(c => c.Id);
|
||||||
|
await SendGamingMessage(obj.All.Values, GamingType.PickCharacter, data);
|
||||||
|
|
||||||
|
// 依然等待
|
||||||
|
await WaitForUsers(30, async () =>
|
||||||
|
{
|
||||||
|
if (worker.UserCharacters.Count == obj.Users.Count)
|
||||||
|
{
|
||||||
|
Controller.WriteLine("所有玩家都已经完成选择。");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}, 200, async () =>
|
||||||
|
{
|
||||||
|
await CancelGame(obj, worker, "由于等待超时,游戏已取消!");
|
||||||
|
}, async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// 得到一个最终列表
|
||||||
|
List<Character> finalList = [.. worker.UserCharacters.Values];
|
||||||
|
|
||||||
|
// 这里我们可以随意对角色们进行升级和赋能
|
||||||
|
int clevel = 60;
|
||||||
|
int slevel = 6;
|
||||||
|
int mlevel = 8;
|
||||||
|
|
||||||
|
foreach (Character c in finalList)
|
||||||
|
{
|
||||||
|
c.Level = clevel;
|
||||||
|
c.NormalAttack.Level = mlevel;
|
||||||
|
// 假设要给所有角色发一个编号为1的技能
|
||||||
|
Skill s = Factory.OpenFactory.GetInstance<Skill>(1, "", []);
|
||||||
|
s.Level = slevel;
|
||||||
|
c.Skills.Add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重点,创建一个战斗队列
|
||||||
|
// 注意,整个游戏中,finalList及其内部角色对象的引用始终不变,请放心使用
|
||||||
|
MixGamingQueue queue = new(finalList, (str) =>
|
||||||
|
{
|
||||||
|
// 战斗日志可以直接通过传输信息的方式输出回客户端
|
||||||
|
_ = SendAllTextMessage(obj, str);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果你需要开启战棋地图模式
|
||||||
|
GameMap? map = GameModuleDepend.Maps.FirstOrDefault();
|
||||||
|
if (map != null)
|
||||||
|
{
|
||||||
|
queue.LoadGameMap(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关键,监听任何事件
|
||||||
|
// 在客户端中,通过事件可以很方便地对UI进行操作以同步界面状态,而在服务端则需要套一层网络层
|
||||||
|
//queue.TurnStartEvent += Queue_TurnStartEvent;
|
||||||
|
//queue.DecideActionEvent += Queue_DecideActionEvent;
|
||||||
|
//queue.SelectNormalAttackTargetsEvent += Queue_SelectNormalAttackTargetsEvent;
|
||||||
|
//queue.SelectSkillEvent += Queue_SelectSkillEvent;
|
||||||
|
//queue.SelectNonDirectionalSkillTargetsEvent += Queue_SelectNonDirectionalSkillTargetsEvent;
|
||||||
|
//queue.SelectItemEvent += Queue_SelectItemEvent;
|
||||||
|
//queue.QueueUpdatedEvent += Queue_QueueUpdatedEvent;
|
||||||
|
//queue.TurnEndEvent += Queue_TurnEndEvent;
|
||||||
|
|
||||||
|
// 我们示范两个事件,一是选择技能目标,需要和客户端交互的事件
|
||||||
|
queue.SelectSkillTargetsEvent += (queue, caster, skill, enemys, teammates, castRange) =>
|
||||||
|
{
|
||||||
|
/// 如果你的逻辑都写在 <see cref="ModuleServerWorker"/> 里就不用这么麻烦每次都传 obj 和 worker 了。
|
||||||
|
return Queue_SelectSkillTargetsEvent(worker, caster, skill, enemys, teammates, castRange);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 二是角色行动完毕,需要通知客户端更新状态的事件
|
||||||
|
queue.CharacterActionTakenEvent += Queue_CharacterActionTakenEvent;
|
||||||
|
|
||||||
|
// 战棋地图模式需要额外绑定的事件(如果你在map类里没有处理的话,这里还可以处理)
|
||||||
|
if (queue.Map != null)
|
||||||
|
{
|
||||||
|
//queue.SelectTargetGridEvent += Queue_SelectTargetGridEvent;
|
||||||
|
//queue.CharacterMoveEvent += Queue_CharacterMoveEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.InitActionQueue();
|
||||||
|
// 这里我们仅演示自动化战斗,指令战斗还需要实现其他的消息处理类型/事件
|
||||||
|
// 自动化战斗时上述绑定的事件可能不会触发,参见GamingQueue的内部实现
|
||||||
|
queue.SetCharactersToAIControl(cancel: false, finalList);
|
||||||
|
|
||||||
|
// 总游戏时长
|
||||||
|
double totalTime = 0;
|
||||||
|
// 总死亡数
|
||||||
|
int deaths = 0;
|
||||||
|
|
||||||
|
// 总回合数
|
||||||
|
int max = 999;
|
||||||
|
int i = 1;
|
||||||
|
while (i < max)
|
||||||
|
{
|
||||||
|
if (i == (max - 1))
|
||||||
|
{
|
||||||
|
// 为了防止回合数超标,游戏近乎死局,可以设置一个上限,然后随便让一个人赢
|
||||||
|
await SendAllTextMessage(obj, $"=== 终局审判 ===");
|
||||||
|
Dictionary<Character, double> hp = [];
|
||||||
|
foreach (Character c in finalList)
|
||||||
|
{
|
||||||
|
hp.TryAdd(c, Calculation.Round4Digits(c.HP / c.MaxHP));
|
||||||
|
}
|
||||||
|
double maxhp = hp.Values.Max();
|
||||||
|
Character winner = hp.Keys.Where(c => hp[c] == maxhp).First();
|
||||||
|
await SendAllTextMessage(obj, "[ " + winner + " ] 成为了天选之人!!");
|
||||||
|
foreach (Character c in finalList.Where(c => c != winner && c.HP > 0))
|
||||||
|
{
|
||||||
|
await SendAllTextMessage(obj, "[ " + winner + " ] 对 [ " + c + " ] 造成了 99999999999 点真实伤害。");
|
||||||
|
queue.DeathCalculation(winner, c);
|
||||||
|
}
|
||||||
|
queue.EndGameInfo(winner);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有角色可以行动
|
||||||
|
Character? characterToAct = queue.NextCharacter();
|
||||||
|
|
||||||
|
// 处理回合
|
||||||
|
if (characterToAct != null)
|
||||||
|
{
|
||||||
|
await SendAllTextMessage(obj, $"=== Round {i++} ===");
|
||||||
|
await SendAllTextMessage(obj, "现在是 [ " + characterToAct + " ] 的回合!");
|
||||||
|
|
||||||
|
if (queue.Queue.Count == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isGameEnd = queue.ProcessTurn(characterToAct);
|
||||||
|
if (isGameEnd)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.DisplayQueue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间流逝,这样能知道下一个是谁可以行动
|
||||||
|
totalTime += queue.TimeLapse();
|
||||||
|
|
||||||
|
if (queue.Eliminated.Count > deaths)
|
||||||
|
{
|
||||||
|
deaths = queue.Eliminated.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await SendAllTextMessage(obj, "--- End ---");
|
||||||
|
await SendAllTextMessage(obj, "总游戏时长:" + Calculation.Round2Digits(totalTime));
|
||||||
|
|
||||||
|
// 赛后统计,充分利用 GamingQueue 提供的功能
|
||||||
|
await SendAllTextMessage(obj, "=== 伤害排行榜 ===");
|
||||||
|
int top = finalList.Count;
|
||||||
|
int count = 1;
|
||||||
|
foreach (Character character in queue.CharacterStatistics.OrderByDescending(d => d.Value.TotalDamage).Select(d => d.Key))
|
||||||
|
{
|
||||||
|
StringBuilder builder = new();
|
||||||
|
CharacterStatistics stats = queue.CharacterStatistics[character];
|
||||||
|
builder.AppendLine($"{count++}. [ {character.ToStringWithLevel()} ] ({stats.Kills} / {stats.Assists})");
|
||||||
|
builder.AppendLine($"存活时长:{stats.LiveTime} / 存活回合数:{stats.LiveRound} / 行动回合数:{stats.ActionTurn} / 总计决策数:{stats.TurnDecisions} / 总计决策点:{stats.UseDecisionPoints}");
|
||||||
|
builder.AppendLine($"总计伤害:{stats.TotalDamage} / 总计物理伤害:{stats.TotalPhysicalDamage} / 总计魔法伤害:{stats.TotalMagicDamage}");
|
||||||
|
builder.AppendLine($"总承受伤害:{stats.TotalTakenDamage} / 总承受物理伤害:{stats.TotalTakenPhysicalDamage} / 总承受魔法伤害:{stats.TotalTakenMagicDamage}");
|
||||||
|
builder.Append($"每秒伤害:{stats.DamagePerSecond} / 每回合伤害:{stats.DamagePerTurn}");
|
||||||
|
|
||||||
|
await SendAllTextMessage(obj, builder.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (System.Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.ToString());
|
||||||
|
Controller.Error(e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 结束
|
||||||
|
SendEndGame(obj);
|
||||||
|
worker.ConnectedUser.Clear();
|
||||||
|
Workers.Remove(obj.Room.Roomid, out _);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Character> Queue_SelectSkillTargetsEvent(ModuleServerWorker worker, Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange)
|
||||||
|
{
|
||||||
|
// 这是一个需要与客户端交互的事件,其他的选择事件与之做法相同
|
||||||
|
// SyncAwaiter是一个允许同步方法安全等待异步任务完成的工具类
|
||||||
|
return SyncAwaiter.WaitResult(RequestClientSelectSkillTargets(worker, caster, skill, enemys, teammates, castRange));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<Character>> RequestClientSelectSkillTargets(ModuleServerWorker worker, Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange)
|
||||||
|
{
|
||||||
|
List<Character> selectTargets = [];
|
||||||
|
Dictionary<string, object> data = [];
|
||||||
|
data.Add("event", "SelectSkillTargets");
|
||||||
|
data.Add("caster", caster.Id);
|
||||||
|
data.Add("skill", skill.Id);
|
||||||
|
data.Add("enemys", enemys.Select(c => c.Id));
|
||||||
|
data.Add("teammates", teammates.Select(c => c.Id));
|
||||||
|
data.Add("castRange", castRange.Select(g => g.Id));
|
||||||
|
await SendGamingMessage(_clientModels, GamingType.Skill, data);
|
||||||
|
await WaitForUsers(30, async () =>
|
||||||
|
{
|
||||||
|
string username = worker.UserCharacters.FirstOrDefault(kv => kv.Value == caster).Key;
|
||||||
|
return worker.UserData.TryGetValue(username, out Dictionary<string, object>? value) && value != null && value.ContainsKey("SkillTargets");
|
||||||
|
}, 200, async () => await Task.CompletedTask, async () =>
|
||||||
|
{
|
||||||
|
string username = worker.UserCharacters.FirstOrDefault(kv => kv.Value == caster).Key;
|
||||||
|
if (worker.UserData.TryGetValue(username, out Dictionary<string, object>? value) && value != null && value.TryGetValue("SkillTargets", out object? value2) && value2 is long[] targets)
|
||||||
|
{
|
||||||
|
selectTargets.AddRange(worker.UserCharacters.Values.Where(c => targets.Contains(c.Id)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return selectTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Queue_CharacterActionTakenEvent(GamingQueue queue, Character actor, DecisionPoints dp, CharacterActionType type, RoundRecord record)
|
||||||
|
{
|
||||||
|
Dictionary<string, object> data = [];
|
||||||
|
data.Add("event", "CharacterActionTaken");
|
||||||
|
data.Add("actor", actor.Id);
|
||||||
|
data.Add("dp", dp);
|
||||||
|
data.Add("type", type);
|
||||||
|
// 通知就行,无需等待
|
||||||
|
_ = SendGamingMessage(_clientModels, GamingType.Round, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendAllTextMessage(GamingObject obj, string str)
|
||||||
|
{
|
||||||
|
// 工具方法,向所有人推送文本消息
|
||||||
|
Dictionary<string, object> data = [];
|
||||||
|
data.Add("showmessage", true);
|
||||||
|
data.Add("msg", str);
|
||||||
|
await SendGamingMessage(obj.All.Values, GamingType.UpdateInfo, data);
|
||||||
|
}
|
||||||
|
|
||||||
protected HashSet<IServerModel> _clientModels = [];
|
protected HashSet<IServerModel> _clientModels = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// 匿名服务器允许客户端不经过FunGameServer的登录验证就能建立一个游戏模组连接<para/>
|
||||||
/// 匿名服务器示例
|
/// 匿名服务器示例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model"></param>
|
/// <param name="model"></param>
|
||||||
@ -268,7 +575,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public override bool StartAnonymousServer(IServerModel model, Dictionary<string, object> data)
|
public override bool StartAnonymousServer(IServerModel model, Dictionary<string, object> data)
|
||||||
{
|
{
|
||||||
// 可以做验证处理
|
// 可以做验证处理(这只是个演示,具体实现只需要双方约定,收发什么都无所谓)
|
||||||
string access_token = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "access_token") ?? "";
|
string access_token = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "access_token") ?? "";
|
||||||
if (access_token == "approval_access_token")
|
if (access_token == "approval_access_token")
|
||||||
{
|
{
|
||||||
@ -304,6 +611,24 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新示例:必须实现 <see cref="IHotReloadAware"/> 接口才会被热更新模式加载这个模组<para/>
|
||||||
|
/// 如果想要实现端运行的所有模组都能热更新,那么这些模组都必须实现了这个接口(包括 <see cref="GameModule"/>,<see cref="GameMap"/>,<see cref="CharacterModule"/> 等等……)
|
||||||
|
/// </summary>
|
||||||
|
public void OnBeforeUnload()
|
||||||
|
{
|
||||||
|
// 这个方法会在模组被卸载前调用,因此,这里要清理一些状态,让框架可以正确卸载模组
|
||||||
|
// 假设,这是个匿名服务器,因此它需要清理匿名连接
|
||||||
|
GamingObjects.Clear();
|
||||||
|
_ = Send(_clientModels, SocketMessageType.EndGame, Factory.GetRoom(), Factory.GetUser());
|
||||||
|
IServerModel[] models = [.. _clientModels];
|
||||||
|
foreach (IServerModel model in models)
|
||||||
|
{
|
||||||
|
model.NowGamingServer = null;
|
||||||
|
CloseAnonymousServer(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -385,6 +710,22 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Factory.EntityFactoryDelegate<Character> CharacterFactory()
|
||||||
|
{
|
||||||
|
// 上面示例用 Characters 是预定义的
|
||||||
|
// 这里的工厂模式则是根据传进来的参数定制生成角色,只要重写这个方法就能注册工厂了
|
||||||
|
return (id, name, args) =>
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Character CreateCharacter(long id, string name, Dictionary<string, object> args)
|
||||||
|
{
|
||||||
|
// 注册工厂后,后续创建角色只需要这样调用
|
||||||
|
return Factory.OpenFactory.GetInstance<Character>(id, name, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -405,14 +746,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
Dictionary<string, Skill> dict = [];
|
Dictionary<string, Skill> dict = [];
|
||||||
// 技能应该在GameModule中新建类继承Skill实现,再自行构造。
|
/// 技能应该在新建类继承Skill实现,再自行构造并加入此列表。
|
||||||
|
/// 技能的实现示例参见:<see cref="ExampleSkill"/>
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Factory.EntityFactoryDelegate<Skill> SkillFactory()
|
protected override Factory.EntityFactoryDelegate<Skill> SkillFactory()
|
||||||
{
|
{
|
||||||
// 注册一个工厂,根据id和name,返回一个你继承实现了的类对象。
|
// 注册一个工厂,根据id和name,返回一个你继承实现了的类对象。所有的工厂使用方法参考 Character,都是一样的
|
||||||
return (id, name, args) =>
|
return (id, name, args) =>
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
@ -423,6 +765,18 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
{
|
{
|
||||||
return (id, name, args) =>
|
return (id, name, args) =>
|
||||||
{
|
{
|
||||||
|
// 以下是一个示例,实际开发中 id,name,args 怎么处置,看你心情
|
||||||
|
Skill? skill = null;
|
||||||
|
if (args.TryGetValue("skill", out object? value) && value is Skill s)
|
||||||
|
{
|
||||||
|
skill = s;
|
||||||
|
}
|
||||||
|
skill ??= new OpenSkill(id, name, args);
|
||||||
|
/// 如 <see cref="ExampleOpenItemByJson"/> 中所说,特效需要在工厂中注册,方便重用
|
||||||
|
if (id == 1001)
|
||||||
|
{
|
||||||
|
return new ExampleOpenEffectExATK2(skill, args);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -446,7 +800,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
Dictionary<string, Item> dict = [];
|
Dictionary<string, Item> dict = [];
|
||||||
// 物品应该在GameModule中新建类继承Item实现,再自行构造。
|
/// 物品应该新建类继承Item实现,再自行构造并加入此列表。
|
||||||
|
/// 物品的实现示例参见:<see cref="ExampleItem"/>
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
189
Library/Common/Addon/Example/ExampleItem.cs
Normal file
189
Library/Common/Addon/Example/ExampleItem.cs
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
||||||
|
{
|
||||||
|
public class ExampleItem : Item
|
||||||
|
{
|
||||||
|
public override long Id => 1;
|
||||||
|
public override string Name => "ExampleItem";
|
||||||
|
public override string Description => $"{Skills.Passives.First().Description}{Skills.Passives.Last().Name}:{Skills.Passives.Last().Description}";
|
||||||
|
public override string BackgroundStory => "Item's Background Story";
|
||||||
|
public override QualityType QualityType => QualityType.Gold;
|
||||||
|
public override WeaponType WeaponType => WeaponType.Staff;
|
||||||
|
|
||||||
|
public ExampleItem(Character? character = null) : base(ItemType.Weapon)
|
||||||
|
{
|
||||||
|
// 如果属性不支持重写,可以在构造函数中初始化
|
||||||
|
Price = 0;
|
||||||
|
IsSellable = false;
|
||||||
|
IsTradable = false;
|
||||||
|
IsLock = true;
|
||||||
|
// 作为装备物品,添加被动技能是必要的。技能一定要设置等级大于0,否则不会生效
|
||||||
|
Skills.Passives.Add(new ExampleItemSkill(character, this));
|
||||||
|
Skills.Passives.Add(new ExamplePassiveSkill(character)
|
||||||
|
{
|
||||||
|
Level = 1
|
||||||
|
});
|
||||||
|
// 也可以添加主动技能
|
||||||
|
Skills.Active = new ExampleNonDirectionalSkill2(character)
|
||||||
|
{
|
||||||
|
Level = 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleItemSkill : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 6;
|
||||||
|
public override string Name => "ExampleItemSkill";
|
||||||
|
public override string Description => string.Join("", Effects.Select(e => e.Description));
|
||||||
|
|
||||||
|
private readonly double 攻击力加成 = 0.46;
|
||||||
|
|
||||||
|
public ExampleItemSkill(Character? character = null, Item? item = null) : base(SkillType.Passive, character)
|
||||||
|
{
|
||||||
|
Level = 1;
|
||||||
|
Item = item;
|
||||||
|
Dictionary<string, object> values = new()
|
||||||
|
{
|
||||||
|
{ "exatk", 攻击力加成 }
|
||||||
|
};
|
||||||
|
Effects.Add(new ExampleOpenEffectExATK2(this, values, character));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IEnumerable<Effect> AddPassiveEffectToCharacter()
|
||||||
|
{
|
||||||
|
return Effects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleOpenEffectExATK2 : Effect
|
||||||
|
{
|
||||||
|
public override long Id => 1001; // 赋予独特ID可以方便重用(在SkillModule的工厂方法中注册有利于动态创建)
|
||||||
|
public override string Name { get; set; } = "攻击力加成";
|
||||||
|
public override string Description => $"{(ActualBonus >= 0 ? "增加" : "减少")}角色 {Math.Abs(BonusFactor) * 100:0.##}% [ {(ActualBonus == 0 ? "基于基础攻击力" : $"{Math.Abs(ActualBonus):0.##}")} ] 点攻击力。" + (Source != null && (Skill.Character != Source || Skill is not OpenSkill) ? $"来自:[ {Source} ]" + (Skill.Item != null ? $" 的 [ {Skill.Item.Name} ]" : (Skill is OpenSkill ? "" : $" 的 [ {Skill.Name} ]")) : "");
|
||||||
|
public double Value => ActualBonus;
|
||||||
|
|
||||||
|
private readonly double BonusFactor = 0;
|
||||||
|
private double ActualBonus = 0;
|
||||||
|
|
||||||
|
public override void OnEffectGained(Character character)
|
||||||
|
{
|
||||||
|
if (Durative && RemainDuration == 0)
|
||||||
|
{
|
||||||
|
RemainDuration = Duration;
|
||||||
|
}
|
||||||
|
else if (RemainDurationTurn == 0)
|
||||||
|
{
|
||||||
|
RemainDurationTurn = DurationTurn;
|
||||||
|
}
|
||||||
|
ActualBonus = character.BaseATK * BonusFactor;
|
||||||
|
character.ExATKPercentage += BonusFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEffectLost(Character character)
|
||||||
|
{
|
||||||
|
character.ExATKPercentage -= BonusFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnAttributeChanged(Character character)
|
||||||
|
{
|
||||||
|
// 刷新加成
|
||||||
|
OnEffectLost(character);
|
||||||
|
OnEffectGained(character);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExampleOpenEffectExATK2(Skill skill, Dictionary<string, object> args, Character? source = null) : base(skill, args)
|
||||||
|
{
|
||||||
|
EffectType = EffectType.Item;
|
||||||
|
GamingQueue = skill.GamingQueue;
|
||||||
|
Source = source;
|
||||||
|
if (Values.Count > 0)
|
||||||
|
{
|
||||||
|
// 如果希望技能可以动态读取参数和创建,就这么写
|
||||||
|
string key = Values.Keys.FirstOrDefault(s => s.Equals("exatk", StringComparison.CurrentCultureIgnoreCase)) ?? "";
|
||||||
|
if (key.Length > 0 && double.TryParse(Values[key].ToString(), out double exATK))
|
||||||
|
{
|
||||||
|
BonusFactor = exATK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleOpenItemByJson
|
||||||
|
{
|
||||||
|
public static Item CreateAJsonItem()
|
||||||
|
{
|
||||||
|
// 演示使用JSON动态创建物品
|
||||||
|
string json = @"
|
||||||
|
{
|
||||||
|
""Id"": 10001,
|
||||||
|
""Name"": ""木杖"",
|
||||||
|
""Description"": ""增加角色 20 点攻击力。"",
|
||||||
|
""BackgroundStory"": ""魔法使的起点。"",
|
||||||
|
""ItemType"": 1,
|
||||||
|
""WeaponType"": 8,
|
||||||
|
""QualityType"": 0,
|
||||||
|
""Skills"": {
|
||||||
|
""Active"": null,
|
||||||
|
""Passives"": [
|
||||||
|
{
|
||||||
|
""Id"": 2001,
|
||||||
|
""Name"": ""木杖"",
|
||||||
|
""SkillType"": 3,
|
||||||
|
""Effects"": [
|
||||||
|
{
|
||||||
|
""Id"": 1001,
|
||||||
|
""exatk"": 20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
/// 如果想了解JSON结构,参见<see cref="JsonConverter.ItemConverter"/>和<see cref="JsonConverter.SkillConverter"/>
|
||||||
|
|
||||||
|
/// 属性和值解释:
|
||||||
|
/// Active 的值是一个技能对象;Passives 则是一个技能对象的数组
|
||||||
|
/// 这里的技能是是动态创建的,Id可以随便填一个没有经过编码的
|
||||||
|
/// SkillType = 3 代表枚举 <see cref="SkillType.Passive"/>
|
||||||
|
/// Effects 中传入一个特效对象的数组,其JSON结构参见<see cref="JsonConverter.EffectConverter"/>
|
||||||
|
|
||||||
|
/// 通常,框架要求所有的特效都要有一个编码的类并经过工厂注册,这样才能正常的动态构建对象
|
||||||
|
/// 没有在转换器上出现的属性,都会进入 <see cref="Effect.Values"/> 字典,特效可以自行解析,就像上面的 ExATK2 一样
|
||||||
|
|
||||||
|
/// 如果1001这个特效已经过工厂注册,那么它的工作流程如下:
|
||||||
|
Skill skill = new OpenSkill(2001, "木杖", []);
|
||||||
|
Effect effect = Factory.OpenFactory.GetInstance<Effect>(1001, "", new()
|
||||||
|
{
|
||||||
|
{ "skill", skill },
|
||||||
|
{ "exatk", 20 }
|
||||||
|
});
|
||||||
|
skill.Effects.Add(effect);
|
||||||
|
Item item = new OpenItem(10001, "木杖", [])
|
||||||
|
{
|
||||||
|
Description = "增加角色 20 点攻击力。",
|
||||||
|
BackgroundStory = "魔法使的起点。",
|
||||||
|
WeaponType = WeaponType.Staff,
|
||||||
|
QualityType = QualityType.White,
|
||||||
|
};
|
||||||
|
item.Skills.Passives.Add(skill);
|
||||||
|
|
||||||
|
/// 以下代码等效于上述代码
|
||||||
|
item = NetworkUtility.JsonDeserialize<Item>(json) ?? Factory.GetItem();
|
||||||
|
|
||||||
|
/// 如果你有一个JSON文件专门用来定义这些动态物品,可以这样加载
|
||||||
|
/// 此方法使用 <see cref="EntityModuleConfig{T}"/> 配置文件读取器
|
||||||
|
Dictionary<string, Item> exItems = Factory.GetGameModuleInstances<Item>("module_name", "file_name");
|
||||||
|
if (exItems.Count > 0)
|
||||||
|
{
|
||||||
|
item = exItems.Values.First();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 不止物品,角色和技能都支持动态创建,在工厂中注册较为关键,既能享受动态数值,也能享受编码的具体逻辑
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
386
Library/Common/Addon/Example/ExampleSkill.cs
Normal file
386
Library/Common/Addon/Example/ExampleSkill.cs
Normal file
@ -0,0 +1,386 @@
|
|||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Model;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 非指向性技能示例1:迷踪步<para/>
|
||||||
|
/// 立即将角色传送到范围内的任意一个未被角色占据的地点<para/>
|
||||||
|
/// 类型:战技
|
||||||
|
/// </summary>
|
||||||
|
public class ExampleNonDirectionalSkill1 : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 1;
|
||||||
|
public override string Name => "迷踪步";
|
||||||
|
public override string Description => string.Join("", Effects.Select(e => e.Description));
|
||||||
|
public override string DispelDescription => string.Join("", Effects.Select(e => e.DispelDescription));
|
||||||
|
public override double EPCost => 25;
|
||||||
|
public override double CD => 35 - 1.5 * Level;
|
||||||
|
public override double HardnessTime { get; set; } = 3;
|
||||||
|
public override bool IsNonDirectional => true;
|
||||||
|
public override bool CanSelectSelf => true;
|
||||||
|
public override bool CanSelectEnemy => false;
|
||||||
|
public override bool CanSelectTeammate => false;
|
||||||
|
public override int CanSelectTargetRange => 0;
|
||||||
|
public override bool SelectIncludeCharacterGrid => false;
|
||||||
|
public override bool AllowSelectNoCharacterGrid => true;
|
||||||
|
|
||||||
|
public ExampleNonDirectionalSkill1(Character? character = null) : base(SkillType.Skill, character)
|
||||||
|
{
|
||||||
|
CastRange = 9;
|
||||||
|
// 所有的技能特效,如果能直接 new,建议就直接 new,提高性能和可读性(工厂效率低且不好调试,工厂更偏向于动态创建技能,而对于编码实现的技能来说,怎么简单怎么来)
|
||||||
|
Effects.Add(new ExampleNonDirectionalSkill1Effect(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 注意:特效包含于技能之中,多个特效组合成一个技能
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="skill"></param>
|
||||||
|
public class ExampleNonDirectionalSkill1Effect(Skill skill) : Effect(skill)
|
||||||
|
{
|
||||||
|
public override long Id => Skill.Id;
|
||||||
|
public override string Name => Skill.Name;
|
||||||
|
public override string Description => $"立即将角色传送到范围内的任意{Skill.TargetDescription()}。";
|
||||||
|
public override string DispelDescription => "";
|
||||||
|
|
||||||
|
public override void OnSkillCasted(Character caster, List<Character> targets, List<Grid> grids, Dictionary<string, object> others)
|
||||||
|
{
|
||||||
|
// 只有开启了地图模式才有效
|
||||||
|
if (GamingQueue?.Map is GameMap map && grids.Count > 0)
|
||||||
|
{
|
||||||
|
map.CharacterMove(caster, map.GetCharacterCurrentGrid(caster), grids[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 主动技能特效示例:基于攻击力的伤害(带基础伤害)
|
||||||
|
/// </summary>
|
||||||
|
public class ExampleDamageBasedOnATKWithBasicDamage : Effect
|
||||||
|
{
|
||||||
|
public override long Id => Skill.Id;
|
||||||
|
public override string Name => Skill.Name;
|
||||||
|
public override string Description => $"对{Skill.TargetDescription()}造成 {BaseDamage:0.##} + {ATKCoefficient * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{CharacterSet.GetDamageTypeName(DamageType, MagicType)}。";
|
||||||
|
|
||||||
|
private double BaseDamage => Skill.Level > 0 ? BaseNumericDamage + BaseNumericDamageLevelGrowth * (Skill.Level - 1) : BaseNumericDamage;
|
||||||
|
private double ATKCoefficient => Skill.Level > 0 ? BaseATKCoefficient + BaseATKCoefficientLevelGrowth * (Skill.Level - 1) : BaseATKCoefficient;
|
||||||
|
private double Damage => BaseDamage + (ATKCoefficient * Skill.Character?.ATK ?? 0);
|
||||||
|
private double BaseNumericDamage { get; set; } = 100;
|
||||||
|
private double BaseNumericDamageLevelGrowth { get; set; } = 50;
|
||||||
|
private double BaseATKCoefficient { get; set; } = 0.2;
|
||||||
|
private double BaseATKCoefficientLevelGrowth { get; set; } = 0.2;
|
||||||
|
private DamageType DamageType { get; set; } = DamageType.Magical;
|
||||||
|
|
||||||
|
public ExampleDamageBasedOnATKWithBasicDamage(Skill skill, double baseNumericDamage, double baseNumericDamageLevelGrowth, double baseATKCoefficient, double baseATKCoefficientLevelGrowth, DamageType damageType = DamageType.Magical, MagicType magicType = MagicType.None) : base(skill)
|
||||||
|
{
|
||||||
|
GamingQueue = skill.GamingQueue;
|
||||||
|
BaseNumericDamage = baseNumericDamage;
|
||||||
|
BaseNumericDamageLevelGrowth = baseNumericDamageLevelGrowth;
|
||||||
|
BaseATKCoefficient = baseATKCoefficient;
|
||||||
|
BaseATKCoefficientLevelGrowth = baseATKCoefficientLevelGrowth;
|
||||||
|
DamageType = damageType;
|
||||||
|
MagicType = magicType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSkillCasted(Character caster, List<Character> targets, List<Grid> grids, Dictionary<string, object> others)
|
||||||
|
{
|
||||||
|
foreach (Character enemy in targets)
|
||||||
|
{
|
||||||
|
DamageToEnemy(caster, enemy, DamageType, MagicType, Damage);
|
||||||
|
}
|
||||||
|
// 或者:
|
||||||
|
//double damage = Damage;
|
||||||
|
//foreach (Character enemy in targets)
|
||||||
|
//{
|
||||||
|
// DamageToEnemy(caster, enemy, DamageType, MagicType, damage);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 非指向性技能示例2:钻石星尘<para/>
|
||||||
|
/// 对半径为 2 格的圆形区域造成魔法伤害<para/>
|
||||||
|
/// 类型:魔法
|
||||||
|
/// </summary>
|
||||||
|
public class ExampleNonDirectionalSkill2 : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 2;
|
||||||
|
public override string Name => "钻石星尘";
|
||||||
|
public override string Description => string.Join("", Effects.Select(e => e.Description));
|
||||||
|
public override double MPCost => Level > 0 ? 80 + (75 * (Level - 1)) : 80;
|
||||||
|
public override double CD => Level > 0 ? 35 + (2 * (Level - 1)) : 35;
|
||||||
|
public override double CastTime => 9;
|
||||||
|
public override double HardnessTime { get; set; } = 6;
|
||||||
|
public override int CanSelectTargetCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Level switch
|
||||||
|
{
|
||||||
|
4 or 5 or 6 => 2,
|
||||||
|
7 or 8 => 3,
|
||||||
|
_ => 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public override bool IsNonDirectional => true;
|
||||||
|
public override SkillRangeType SkillRangeType => SkillRangeType.Circle;
|
||||||
|
public override int CanSelectTargetRange => 2;
|
||||||
|
public override double MagicBottleneck => 35 + 24 * (Level - 1);
|
||||||
|
|
||||||
|
public ExampleNonDirectionalSkill2(Character? character = null) : base(SkillType.Magic, character)
|
||||||
|
{
|
||||||
|
Effects.Add(new ExampleDamageBasedOnATKWithBasicDamage(this, 20, 20, 0.03, 0.02, DamageType.Magical));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 指向性技能示例:全力一击<para/>
|
||||||
|
/// 对目标造成物理伤害并打断施法<para/>
|
||||||
|
/// 类型:战技
|
||||||
|
/// </summary>
|
||||||
|
public class ExampleSkill : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 3;
|
||||||
|
public override string Name => "全力一击";
|
||||||
|
public override string Description => string.Join("", Effects.Select(e => e.Description));
|
||||||
|
public override string DispelDescription => string.Join("", Effects.Select(e => e.Description));
|
||||||
|
public override string ExemptionDescription => Effects.Count > 0 ? Effects.First(e => e is ExampleInterruptCastingEffect).ExemptionDescription : "";
|
||||||
|
public override double EPCost => 60;
|
||||||
|
public override double CD => 20;
|
||||||
|
public override double HardnessTime { get; set; } = 8;
|
||||||
|
// 豁免检定有两种方式,通过直接设置技能的属性可自动触发豁免,但是豁免成功让整个技能都失效,包括伤害(或其他 Effects),另一种方式比较安全,但需要手动调用方法,看下面
|
||||||
|
public override Effect? EffectForExemptionCheck => Effects.FirstOrDefault(e => e is ExampleInterruptCastingEffect);
|
||||||
|
|
||||||
|
public ExampleSkill(Character? character = null) : base(SkillType.Skill, character)
|
||||||
|
{
|
||||||
|
Effects.Add(new ExampleDamageBasedOnATKWithBasicDamage(this, 65, 65, 0.09, 0.04, DamageType.Physical));
|
||||||
|
Effects.Add(new ExampleInterruptCastingEffect(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleInterruptCastingEffect : Effect
|
||||||
|
{
|
||||||
|
public override long Id => Skill.Id;
|
||||||
|
public override string Name => Skill.Name;
|
||||||
|
public override string Description => $"对{Skill.TargetDescription()}施加打断施法效果:中断其正在进行的吟唱。";
|
||||||
|
public override EffectType EffectType => EffectType.InterruptCasting;
|
||||||
|
|
||||||
|
public ExampleInterruptCastingEffect(Skill skill) : base(skill)
|
||||||
|
{
|
||||||
|
GamingQueue = skill.GamingQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSkillCasted(Character caster, List<Character> targets, List<Grid> grids, Dictionary<string, object> others)
|
||||||
|
{
|
||||||
|
foreach (Character target in targets)
|
||||||
|
{
|
||||||
|
// 这是另一种豁免检定方式,在技能实现时,自行调用 CheckExemption,只对该特效有效
|
||||||
|
if (!CheckExemption(caster, target, this))
|
||||||
|
{
|
||||||
|
InterruptCasting(target, caster);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被动技能示例:心灵之弦
|
||||||
|
/// </summary>
|
||||||
|
public class ExamplePassiveSkill : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 4;
|
||||||
|
public override string Name => "心灵之弦";
|
||||||
|
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
|
||||||
|
|
||||||
|
public ExamplePassiveSkill(Character? character = null) : base(SkillType.Passive, character)
|
||||||
|
{
|
||||||
|
Effects.Add(new ExamplePassiveSkillEffect(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 特别注意:被动技能必须重写此方法,否则它不会自动添加到角色身上
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override IEnumerable<Effect> AddPassiveEffectToCharacter()
|
||||||
|
{
|
||||||
|
return Effects;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExamplePassiveSkillEffect(Skill skill) : Effect(skill)
|
||||||
|
{
|
||||||
|
public override long Id => Skill.Id;
|
||||||
|
public override string Name => Skill.Name;
|
||||||
|
public override string Description => $"普通攻击硬直时间减少 20%。每次使用普通攻击时,额外再发动一次普通攻击,伤害特效可叠加,但伤害折减一半,冷却 {CD:0.##} {GameplayEquilibriumConstant.InGameTime}。额外普通攻击立即发动,不占用决策点配额。" +
|
||||||
|
(CurrentCD > 0 ? $"(正在冷却:剩余 {CurrentCD:0.##} {GameplayEquilibriumConstant.InGameTime})" : "");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 被动技能的冷却时间可以借用技能的冷却时间(<see cref="Skill.CD"/> 等属性)实现,也可以内部实现,看喜好
|
||||||
|
/// </summary>
|
||||||
|
public double CurrentCD { get; set; } = 0;
|
||||||
|
public double CD { get; set; } = 10;
|
||||||
|
private bool IsNested = false;
|
||||||
|
|
||||||
|
// 该钩子属于伤害计算流程的特效乘区2
|
||||||
|
public override double AlterActualDamageAfterCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult, ref bool isEvaded, Dictionary<Effect, double> totalDamageBonus)
|
||||||
|
{
|
||||||
|
if (character == Skill.Character && IsNested && isNormalAttack && damage > 0)
|
||||||
|
{
|
||||||
|
// 此方法返回的是加值
|
||||||
|
return -(damage / 2);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AfterDamageCalculation(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult)
|
||||||
|
{
|
||||||
|
if (character == Skill.Character && isNormalAttack && CurrentCD == 0 && !IsNested && GamingQueue != null && enemy.HP > 0)
|
||||||
|
{
|
||||||
|
WriteLine($"[ {character} ] 发动了{Skill.Name}!额外进行一次普通攻击!");
|
||||||
|
CurrentCD = CD;
|
||||||
|
IsNested = true;
|
||||||
|
character.NormalAttack.Attack(GamingQueue, character, null, enemy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character == Skill.Character && IsNested)
|
||||||
|
{
|
||||||
|
IsNested = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnTimeElapsed(Character character, double elapsed)
|
||||||
|
{
|
||||||
|
// 时间流逝时,手动减少CD。如果借用了技能的冷却时间属性,就不需要写了
|
||||||
|
if (CurrentCD > 0)
|
||||||
|
{
|
||||||
|
CurrentCD -= elapsed;
|
||||||
|
if (CurrentCD <= 0)
|
||||||
|
{
|
||||||
|
CurrentCD = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AlterHardnessTimeAfterNormalAttack(Character character, ref double baseHardnessTime, ref bool isCheckProtected)
|
||||||
|
{
|
||||||
|
// 普攻后调整硬直时间。ref 变量直接修改
|
||||||
|
baseHardnessTime *= 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 爆发技示例:千羽瞬华<para/>
|
||||||
|
/// 给自己加属性,并联动其他技能
|
||||||
|
/// </summary>
|
||||||
|
public class ExampleSuperSkill : Skill
|
||||||
|
{
|
||||||
|
public override long Id => 5;
|
||||||
|
public override string Name => "千羽瞬华";
|
||||||
|
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
|
||||||
|
public override string DispelDescription => Effects.Count > 0 ? Effects.First().DispelDescription : "";
|
||||||
|
public override double EPCost => 100;
|
||||||
|
public override double CD => 60;
|
||||||
|
public override double HardnessTime { get; set; } = 10;
|
||||||
|
public override bool CanSelectSelf => true;
|
||||||
|
public override bool CanSelectEnemy => false;
|
||||||
|
|
||||||
|
public ExampleSuperSkill(Character? character = null) : base(SkillType.SuperSkill, character)
|
||||||
|
{
|
||||||
|
Effects.Add(new ExampleSuperSkillEffect(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ExampleSuperSkillEffect(Skill skill) : Effect(skill)
|
||||||
|
{
|
||||||
|
public override long Id => Skill.Id;
|
||||||
|
public override string Name => Skill.Name;
|
||||||
|
public override string Description => $"{Duration:0.##} {GameplayEquilibriumConstant.InGameTime}内,增加{Skill.SkillOwner()} {ATKMultiplier * 100:0.##}% 攻击力 [ {ATKBonus:0.##} ]、{PhysicalPenetrationBonus * 100:0.##}% 物理穿透和 {EvadeRateBonus * 100:0.##}% 闪避率(不可叠加),普通攻击硬直时间额外减少 20%,基于 {Coefficient * 100:0.##}% 敏捷 [ {DamageBonus:0.##} ] 强化普通攻击的伤害。在持续时间内,[ 心灵之弦 ] 的冷却时间降低至 3 {GameplayEquilibriumConstant.InGameTime}。";
|
||||||
|
public override bool Durative => true;
|
||||||
|
public override double Duration => 30;
|
||||||
|
public override DispelledType DispelledType => DispelledType.CannotBeDispelled;
|
||||||
|
|
||||||
|
private double Coefficient => 1.2 * (1 + 0.5 * (Skill.Level - 1));
|
||||||
|
private double DamageBonus => Coefficient * Skill.Character?.AGI ?? 0;
|
||||||
|
private double ATKMultiplier => Skill.Level > 0 ? 0.15 + 0.03 * (Skill.Level - 1) : 0.15;
|
||||||
|
private double ATKBonus => ATKMultiplier * Skill.Character?.BaseATK ?? 0;
|
||||||
|
private double PhysicalPenetrationBonus => Skill.Level > 0 ? 0.1 + 0.03 * (Skill.Level - 1) : 0.1;
|
||||||
|
private double EvadeRateBonus => Skill.Level > 0 ? 0.1 + 0.02 * (Skill.Level - 1) : 0.1;
|
||||||
|
|
||||||
|
// 用于保存状态和恢复
|
||||||
|
private double ActualATKBonus = 0;
|
||||||
|
private double ActualPhysicalPenetrationBonus = 0;
|
||||||
|
private double ActualEvadeRateBonus = 0;
|
||||||
|
|
||||||
|
public override void OnEffectGained(Character character)
|
||||||
|
{
|
||||||
|
// 记录状态并修改属性
|
||||||
|
ActualATKBonus = ATKBonus;
|
||||||
|
ActualPhysicalPenetrationBonus = PhysicalPenetrationBonus;
|
||||||
|
ActualEvadeRateBonus = EvadeRateBonus;
|
||||||
|
character.ExATK2 += ActualATKBonus;
|
||||||
|
character.PhysicalPenetration += ActualPhysicalPenetrationBonus;
|
||||||
|
character.ExEvadeRate += ActualEvadeRateBonus;
|
||||||
|
if (character.Effects.FirstOrDefault(e => e is ExamplePassiveSkillEffect) is ExamplePassiveSkillEffect e)
|
||||||
|
{
|
||||||
|
e.CD = 3;
|
||||||
|
if (e.CurrentCD > e.CD) e.CurrentCD = e.CD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnEffectLost(Character character)
|
||||||
|
{
|
||||||
|
// 从记录的状态中恢复
|
||||||
|
character.ExATK2 -= ActualATKBonus;
|
||||||
|
character.PhysicalPenetration -= ActualPhysicalPenetrationBonus;
|
||||||
|
character.ExEvadeRate -= ActualEvadeRateBonus;
|
||||||
|
if (character.Effects.FirstOrDefault(e => e is ExamplePassiveSkillEffect) is ExamplePassiveSkillEffect e)
|
||||||
|
{
|
||||||
|
e.CD = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CharacterActionType AlterActionTypeBeforeAction(Character character, DecisionPoints dp, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, ref bool forceAction)
|
||||||
|
{
|
||||||
|
// 对于 AI,可以提高角色的普攻积极性,调整决策偏好,这样可以充分利用技能效果
|
||||||
|
pNormalAttack += 0.1;
|
||||||
|
return CharacterActionType.None;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double AlterExpectedDamageBeforeCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, Dictionary<Effect, double> totalDamageBonus)
|
||||||
|
{
|
||||||
|
if (character == Skill.Character && isNormalAttack)
|
||||||
|
{
|
||||||
|
return DamageBonus;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AlterHardnessTimeAfterNormalAttack(Character character, ref double baseHardnessTime, ref bool isCheckProtected)
|
||||||
|
{
|
||||||
|
// 可以和上面的心灵之弦叠加,最终硬直时间=硬直时间*0.8*0.8
|
||||||
|
baseHardnessTime *= 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnSkillCasted(Character caster, List<Character> targets, List<Grid> grids, Dictionary<string, object> others)
|
||||||
|
{
|
||||||
|
ActualATKBonus = 0;
|
||||||
|
ActualPhysicalPenetrationBonus = 0;
|
||||||
|
ActualEvadeRateBonus = 0;
|
||||||
|
// 不叠加的效果通常只刷新持续时间
|
||||||
|
RemainDuration = Duration;
|
||||||
|
// 通常,this(Effect本身)在整局战斗中都是唯一的,需要只需要判断 this 就行
|
||||||
|
if (!caster.Effects.Contains(this))
|
||||||
|
{
|
||||||
|
// 加也是加 this
|
||||||
|
caster.Effects.Add(this);
|
||||||
|
OnEffectGained(caster);
|
||||||
|
}
|
||||||
|
// 施加状态记录到回合日志中
|
||||||
|
RecordCharacterApplyEffects(caster, EffectType.DamageBoost, EffectType.PenetrationBoost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -98,6 +98,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该模组的加载器
|
||||||
|
/// </summary>
|
||||||
|
public GameModuleLoader? ModuleLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载标记
|
/// 加载标记
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -136,6 +141,22 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
foreach (Grid grid in Grids.Values)
|
||||||
|
{
|
||||||
|
grid.Characters.Clear();
|
||||||
|
grid.Effects.Clear();
|
||||||
|
}
|
||||||
|
Characters.Clear();
|
||||||
|
Grids.Clear();
|
||||||
|
GridsByCoordinate.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 地图完全加载后需要做的事
|
/// 地图完全加载后需要做的事
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -69,6 +69,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
set => _associatedServerModuleName = value;
|
set => _associatedServerModuleName = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该模组的加载器
|
||||||
|
/// </summary>
|
||||||
|
public GameModuleLoader? ModuleLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含了一些常用方法的控制器
|
/// 包含了一些常用方法的控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -138,6 +143,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
BindEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模组完全加载后需要做的事
|
/// 模组完全加载后需要做的事
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -160,8 +174,14 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void Init(params object[] objs)
|
private void Init(params object[] objs)
|
||||||
{
|
{
|
||||||
if (objs.Length > 0) _session = (Session)objs[0];
|
if (objs.Length > 0 && objs[0] is Session session)
|
||||||
if (objs.Length > 1) _config = (FunGameConfig)objs[1];
|
{
|
||||||
|
_session = session;
|
||||||
|
}
|
||||||
|
if (objs.Length > 1 && objs[1] is FunGameConfig config)
|
||||||
|
{
|
||||||
|
_config = config;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -182,126 +202,246 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BindEvent()
|
private void BindEvent(bool isAdd = true)
|
||||||
{
|
{
|
||||||
if (this is IGamingConnectEvent)
|
if (this is IGamingConnectEvent connect)
|
||||||
{
|
{
|
||||||
IGamingConnectEvent bind = (IGamingConnectEvent)this;
|
if (isAdd)
|
||||||
GamingConnect += bind.GamingConnectEvent;
|
{
|
||||||
|
GamingConnect += connect.GamingConnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingConnect -= connect.GamingConnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingDisconnectEvent)
|
if (this is IGamingDisconnectEvent disconnect)
|
||||||
{
|
{
|
||||||
IGamingDisconnectEvent bind = (IGamingDisconnectEvent)this;
|
if (isAdd)
|
||||||
GamingDisconnect += bind.GamingDisconnectEvent;
|
{
|
||||||
|
GamingDisconnect += disconnect.GamingDisconnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingDisconnect -= disconnect.GamingDisconnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingReconnectEvent)
|
if (this is IGamingReconnectEvent reconnect)
|
||||||
{
|
{
|
||||||
IGamingReconnectEvent bind = (IGamingReconnectEvent)this;
|
if (isAdd)
|
||||||
GamingReconnect += bind.GamingReconnectEvent;
|
{
|
||||||
|
GamingReconnect += reconnect.GamingReconnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingReconnect -= reconnect.GamingReconnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingBanCharacterEvent)
|
if (this is IGamingBanCharacterEvent ban)
|
||||||
{
|
{
|
||||||
IGamingBanCharacterEvent bind = (IGamingBanCharacterEvent)this;
|
if (isAdd)
|
||||||
GamingBanCharacter += bind.GamingBanCharacterEvent;
|
{
|
||||||
|
GamingBanCharacter += ban.GamingBanCharacterEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingBanCharacter -= ban.GamingBanCharacterEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingPickCharacterEvent)
|
if (this is IGamingPickCharacterEvent pick)
|
||||||
{
|
{
|
||||||
IGamingPickCharacterEvent bind = (IGamingPickCharacterEvent)this;
|
if (isAdd)
|
||||||
GamingPickCharacter += bind.GamingPickCharacterEvent;
|
{
|
||||||
|
GamingPickCharacter += pick.GamingPickCharacterEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingPickCharacter -= pick.GamingPickCharacterEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingRandomEvent)
|
if (this is IGamingRandomEvent random)
|
||||||
{
|
{
|
||||||
IGamingRandomEvent bind = (IGamingRandomEvent)this;
|
if (isAdd)
|
||||||
GamingRandom += bind.GamingRandomEvent;
|
{
|
||||||
|
GamingRandom += random.GamingRandomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingRandom -= random.GamingRandomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingRoundEvent)
|
if (this is IGamingRoundEvent round)
|
||||||
{
|
{
|
||||||
IGamingRoundEvent bind = (IGamingRoundEvent)this;
|
if (isAdd)
|
||||||
GamingRound += bind.GamingRoundEvent;
|
{
|
||||||
|
GamingRound += round.GamingRoundEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingRound -= round.GamingRoundEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingLevelUpEvent)
|
if (this is IGamingLevelUpEvent levelUp)
|
||||||
{
|
{
|
||||||
IGamingLevelUpEvent bind = (IGamingLevelUpEvent)this;
|
if (isAdd)
|
||||||
GamingLevelUp += bind.GamingLevelUpEvent;
|
{
|
||||||
|
GamingLevelUp += levelUp.GamingLevelUpEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingLevelUp -= levelUp.GamingLevelUpEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingMoveEvent)
|
if (this is IGamingMoveEvent move)
|
||||||
{
|
{
|
||||||
IGamingMoveEvent bind = (IGamingMoveEvent)this;
|
if (isAdd)
|
||||||
GamingMove += bind.GamingMoveEvent;
|
{
|
||||||
|
GamingMove += move.GamingMoveEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingMove -= move.GamingMoveEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingAttackEvent)
|
if (this is IGamingAttackEvent attack)
|
||||||
{
|
{
|
||||||
IGamingAttackEvent bind = (IGamingAttackEvent)this;
|
if (isAdd)
|
||||||
GamingAttack += bind.GamingAttackEvent;
|
{
|
||||||
|
GamingAttack += attack.GamingAttackEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingAttack -= attack.GamingAttackEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingSkillEvent)
|
if (this is IGamingSkillEvent skill)
|
||||||
{
|
{
|
||||||
IGamingSkillEvent bind = (IGamingSkillEvent)this;
|
if (isAdd)
|
||||||
GamingSkill += bind.GamingSkillEvent;
|
{
|
||||||
|
GamingSkill += skill.GamingSkillEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingSkill -= skill.GamingSkillEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingItemEvent)
|
if (this is IGamingItemEvent item)
|
||||||
{
|
{
|
||||||
IGamingItemEvent bind = (IGamingItemEvent)this;
|
if (isAdd)
|
||||||
GamingItem += bind.GamingItemEvent;
|
{
|
||||||
|
GamingItem += item.GamingItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingItem -= item.GamingItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingMagicEvent)
|
if (this is IGamingMagicEvent magic)
|
||||||
{
|
{
|
||||||
IGamingMagicEvent bind = (IGamingMagicEvent)this;
|
if (isAdd)
|
||||||
GamingMagic += bind.GamingMagicEvent;
|
{
|
||||||
|
GamingMagic += magic.GamingMagicEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingMagic -= magic.GamingMagicEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingBuyEvent)
|
if (this is IGamingBuyEvent buy)
|
||||||
{
|
{
|
||||||
IGamingBuyEvent bind = (IGamingBuyEvent)this;
|
if (isAdd)
|
||||||
GamingBuy += bind.GamingBuyEvent;
|
{
|
||||||
|
GamingBuy += buy.GamingBuyEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingBuy -= buy.GamingBuyEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingSuperSkillEvent)
|
if (this is IGamingSuperSkillEvent super)
|
||||||
{
|
{
|
||||||
IGamingSuperSkillEvent bind = (IGamingSuperSkillEvent)this;
|
if (isAdd)
|
||||||
GamingSuperSkill += bind.GamingSuperSkillEvent;
|
{
|
||||||
|
GamingSuperSkill += super.GamingSuperSkillEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingSuperSkill -= super.GamingSuperSkillEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingPauseEvent)
|
if (this is IGamingPauseEvent pause)
|
||||||
{
|
{
|
||||||
IGamingPauseEvent bind = (IGamingPauseEvent)this;
|
if (isAdd)
|
||||||
GamingPause += bind.GamingPauseEvent;
|
{
|
||||||
|
GamingPause += pause.GamingPauseEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingPause -= pause.GamingPauseEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingUnpauseEvent)
|
if (this is IGamingUnpauseEvent unpause)
|
||||||
{
|
{
|
||||||
IGamingUnpauseEvent bind = (IGamingUnpauseEvent)this;
|
if (isAdd)
|
||||||
GamingUnpause += bind.GamingUnpauseEvent;
|
{
|
||||||
|
GamingUnpause += unpause.GamingUnpauseEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingUnpause -= unpause.GamingUnpauseEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingSurrenderEvent)
|
if (this is IGamingSurrenderEvent surrender)
|
||||||
{
|
{
|
||||||
IGamingSurrenderEvent bind = (IGamingSurrenderEvent)this;
|
if (isAdd)
|
||||||
GamingSurrender += bind.GamingSurrenderEvent;
|
{
|
||||||
|
GamingSurrender += surrender.GamingSurrenderEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingSurrender -= surrender.GamingSurrenderEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingUpdateInfoEvent)
|
if (this is IGamingUpdateInfoEvent update)
|
||||||
{
|
{
|
||||||
IGamingUpdateInfoEvent bind = (IGamingUpdateInfoEvent)this;
|
if (isAdd)
|
||||||
GamingUpdateInfo += bind.GamingUpdateInfoEvent;
|
{
|
||||||
|
GamingUpdateInfo += update.GamingUpdateInfoEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingUpdateInfo -= update.GamingUpdateInfoEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IGamingPunishEvent)
|
if (this is IGamingPunishEvent punish)
|
||||||
{
|
{
|
||||||
IGamingPunishEvent bind = (IGamingPunishEvent)this;
|
if (isAdd)
|
||||||
GamingPunish += bind.GamingPunishEvent;
|
{
|
||||||
|
GamingPunish += punish.GamingPunishEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamingPunish -= punish.GamingPunishEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool IsAnonymous { get; set; } = false;
|
public virtual bool IsAnonymous { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该模组的加载器
|
||||||
|
/// </summary>
|
||||||
|
public GameModuleLoader? ModuleLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含了一些常用方法的控制器
|
/// 包含了一些常用方法的控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -147,6 +152,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 模组完全加载后需要做的事
|
/// 模组完全加载后需要做的事
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -58,6 +58,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
Factory.OpenFactory.UnRegisterFactory(ItemFactory());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册工厂
|
/// 注册工厂
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -29,6 +29,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string Author { get; }
|
public abstract string Author { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该插件的加载器
|
||||||
|
/// </summary>
|
||||||
|
public PluginLoader? PluginLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含了一些常用方法的控制器
|
/// 包含了一些常用方法的控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -79,6 +84,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
BindEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 插件完全加载后需要做的事
|
/// 插件完全加载后需要做的事
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -118,153 +132,300 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BindEvent()
|
private void BindEvent(bool isAdd = true)
|
||||||
{
|
{
|
||||||
if (this is IConnectEvent)
|
if (this is IConnectEvent connect)
|
||||||
{
|
{
|
||||||
IConnectEvent bind = (IConnectEvent)this;
|
if (isAdd)
|
||||||
BeforeConnect += bind.BeforeConnectEvent;
|
{
|
||||||
AfterConnect += bind.AfterConnectEvent;
|
BeforeConnect += connect.BeforeConnectEvent;
|
||||||
|
AfterConnect += connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeConnect -= connect.BeforeConnectEvent;
|
||||||
|
AfterConnect -= connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IDisconnectEvent)
|
if (this is IDisconnectEvent disconnect)
|
||||||
{
|
{
|
||||||
IDisconnectEvent bind = (IDisconnectEvent)this;
|
if (isAdd)
|
||||||
BeforeDisconnect += bind.BeforeDisconnectEvent;
|
{
|
||||||
AfterDisconnect += bind.AfterDisconnectEvent;
|
BeforeDisconnect += disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect += disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeDisconnect -= disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect -= disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILoginEvent)
|
if (this is ILoginEvent login)
|
||||||
{
|
{
|
||||||
ILoginEvent bind = (ILoginEvent)this;
|
if (isAdd)
|
||||||
BeforeLogin += bind.BeforeLoginEvent;
|
{
|
||||||
AfterLogin += bind.AfterLoginEvent;
|
BeforeLogin += login.BeforeLoginEvent;
|
||||||
|
AfterLogin += login.AfterLoginEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogin -= login.BeforeLoginEvent;
|
||||||
|
AfterLogin -= login.AfterLoginEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILogoutEvent)
|
if (this is ILogoutEvent logout)
|
||||||
{
|
{
|
||||||
ILogoutEvent bind = (ILogoutEvent)this;
|
if (isAdd)
|
||||||
BeforeLogout += bind.BeforeLogoutEvent;
|
{
|
||||||
AfterLogout += bind.AfterLogoutEvent;
|
BeforeLogout += logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout += logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogout -= logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout -= logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IRegEvent)
|
if (this is IRegEvent reg)
|
||||||
{
|
{
|
||||||
IRegEvent bind = (IRegEvent)this;
|
if (isAdd)
|
||||||
BeforeReg += bind.BeforeRegEvent;
|
{
|
||||||
AfterReg += bind.AfterRegEvent;
|
BeforeReg += reg.BeforeRegEvent;
|
||||||
|
AfterReg += reg.AfterRegEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeReg -= reg.BeforeRegEvent;
|
||||||
|
AfterReg -= reg.AfterRegEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IIntoRoomEvent)
|
if (this is IIntoRoomEvent intoRoom)
|
||||||
{
|
{
|
||||||
IIntoRoomEvent bind = (IIntoRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeIntoRoom += bind.BeforeIntoRoomEvent;
|
{
|
||||||
AfterIntoRoom += bind.AfterIntoRoomEvent;
|
BeforeIntoRoom += intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom += intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeIntoRoom -= intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom -= intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISendTalkEvent)
|
if (this is ISendTalkEvent sendTalk)
|
||||||
{
|
{
|
||||||
ISendTalkEvent bind = (ISendTalkEvent)this;
|
if (isAdd)
|
||||||
BeforeSendTalk += bind.BeforeSendTalkEvent;
|
{
|
||||||
AfterSendTalk += bind.AfterSendTalkEvent;
|
BeforeSendTalk += sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk += sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSendTalk -= sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk -= sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ICreateRoomEvent)
|
if (this is ICreateRoomEvent createRoom)
|
||||||
{
|
{
|
||||||
ICreateRoomEvent bind = (ICreateRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeCreateRoom += bind.BeforeCreateRoomEvent;
|
{
|
||||||
AfterCreateRoom += bind.AfterCreateRoomEvent;
|
BeforeCreateRoom += createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom += createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeCreateRoom -= createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom -= createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IQuitRoomEvent)
|
if (this is IQuitRoomEvent quitRoom)
|
||||||
{
|
{
|
||||||
IQuitRoomEvent bind = (IQuitRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeQuitRoom += bind.BeforeQuitRoomEvent;
|
{
|
||||||
AfterQuitRoom += bind.AfterQuitRoomEvent;
|
BeforeQuitRoom += quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom += quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeQuitRoom -= quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom -= quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeRoomSettingEvent)
|
if (this is IChangeRoomSettingEvent changeRoomSetting)
|
||||||
{
|
{
|
||||||
IChangeRoomSettingEvent bind = (IChangeRoomSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeRoomSetting += bind.BeforeChangeRoomSettingEvent;
|
{
|
||||||
AfterChangeRoomSetting += bind.AfterChangeRoomSettingEvent;
|
BeforeChangeRoomSetting += changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting += changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeRoomSetting -= changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting -= changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartMatchEvent)
|
if (this is IStartMatchEvent startMatch)
|
||||||
{
|
{
|
||||||
IStartMatchEvent bind = (IStartMatchEvent)this;
|
if (isAdd)
|
||||||
BeforeStartMatch += bind.BeforeStartMatchEvent;
|
{
|
||||||
AfterStartMatch += bind.AfterStartMatchEvent;
|
BeforeStartMatch += startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch += startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartMatch -= startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch -= startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartGameEvent)
|
if (this is IStartGameEvent startGame)
|
||||||
{
|
{
|
||||||
IStartGameEvent bind = (IStartGameEvent)this;
|
if (isAdd)
|
||||||
BeforeStartGame += bind.BeforeStartGameEvent;
|
{
|
||||||
AfterStartGame += bind.AfterStartGameEvent;
|
BeforeStartGame += startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame += startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartGame -= startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame -= startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeProfileEvent)
|
if (this is IChangeProfileEvent changeProfile)
|
||||||
{
|
{
|
||||||
IChangeProfileEvent bind = (IChangeProfileEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeProfile += bind.BeforeChangeProfileEvent;
|
{
|
||||||
AfterChangeProfile += bind.AfterChangeProfileEvent;
|
BeforeChangeProfile += changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile += changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeProfile -= changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile -= changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeAccountSettingEvent)
|
if (this is IChangeAccountSettingEvent changeAccountSetting)
|
||||||
{
|
{
|
||||||
IChangeAccountSettingEvent bind = (IChangeAccountSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeAccountSetting += bind.BeforeChangeAccountSettingEvent;
|
{
|
||||||
AfterChangeAccountSetting += bind.AfterChangeAccountSettingEvent;
|
BeforeChangeAccountSetting += changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting += changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeAccountSetting -= changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting -= changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenInventoryEvent)
|
if (this is IOpenInventoryEvent openInventory)
|
||||||
{
|
{
|
||||||
IOpenInventoryEvent bind = (IOpenInventoryEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenInventory += bind.BeforeOpenInventoryEvent;
|
{
|
||||||
AfterOpenInventory += bind.AfterOpenInventoryEvent;
|
BeforeOpenInventory += openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory += openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenInventory -= openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory -= openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISignInEvent)
|
if (this is ISignInEvent signIn)
|
||||||
{
|
{
|
||||||
ISignInEvent bind = (ISignInEvent)this;
|
if (isAdd)
|
||||||
BeforeSignIn += bind.BeforeSignInEvent;
|
{
|
||||||
AfterSignIn += bind.AfterSignInEvent;
|
BeforeSignIn += signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn += signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSignIn -= signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn -= signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenStoreEvent)
|
if (this is IOpenStoreEvent openStore)
|
||||||
{
|
{
|
||||||
IOpenStoreEvent bind = (IOpenStoreEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenStore += bind.BeforeOpenStoreEvent;
|
{
|
||||||
AfterOpenStore += bind.AfterOpenStoreEvent;
|
BeforeOpenStore += openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore += openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenStore -= openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore -= openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IBuyItemEvent)
|
if (this is IBuyItemEvent buyItem)
|
||||||
{
|
{
|
||||||
IBuyItemEvent bind = (IBuyItemEvent)this;
|
if (isAdd)
|
||||||
BeforeBuyItem += bind.BeforeBuyItemEvent;
|
{
|
||||||
AfterBuyItem += bind.AfterBuyItemEvent;
|
BeforeBuyItem += buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem += buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeBuyItem -= buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem -= buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IShowRankingEvent)
|
if (this is IShowRankingEvent showRanking)
|
||||||
{
|
{
|
||||||
IShowRankingEvent bind = (IShowRankingEvent)this;
|
if (isAdd)
|
||||||
BeforeShowRanking += bind.BeforeShowRankingEvent;
|
{
|
||||||
AfterShowRanking += bind.AfterShowRankingEvent;
|
BeforeShowRanking += showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking += showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeShowRanking -= showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking -= showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IUseItemEvent)
|
if (this is IUseItemEvent useItem)
|
||||||
{
|
{
|
||||||
IUseItemEvent bind = (IUseItemEvent)this;
|
if (isAdd)
|
||||||
BeforeUseItem += bind.BeforeUseItemEvent;
|
{
|
||||||
AfterUseItem += bind.AfterUseItemEvent;
|
BeforeUseItem += useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem += useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeUseItem -= useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem -= useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IEndGameEvent)
|
if (this is IEndGameEvent endGame)
|
||||||
{
|
{
|
||||||
IEndGameEvent bind = (IEndGameEvent)this;
|
if (isAdd)
|
||||||
BeforeEndGame += bind.BeforeEndGameEvent;
|
{
|
||||||
AfterEndGame += bind.AfterEndGameEvent;
|
BeforeEndGame += endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame += endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeEndGame -= endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame -= endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string Author { get; }
|
public abstract string Author { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该插件的加载器
|
||||||
|
/// </summary>
|
||||||
|
public ServerPluginLoader? PluginLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含了一些常用方法的控制器
|
/// 包含了一些常用方法的控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -76,6 +81,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
BindEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收服务器控制台的输入
|
/// 接收服务器控制台的输入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -102,153 +116,300 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BindEvent()
|
private void BindEvent(bool isAdd = true)
|
||||||
{
|
{
|
||||||
if (this is IConnectEvent)
|
if (this is IConnectEvent connect)
|
||||||
{
|
{
|
||||||
IConnectEvent bind = (IConnectEvent)this;
|
if (isAdd)
|
||||||
BeforeConnect += bind.BeforeConnectEvent;
|
{
|
||||||
AfterConnect += bind.AfterConnectEvent;
|
BeforeConnect += connect.BeforeConnectEvent;
|
||||||
|
AfterConnect += connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeConnect -= connect.BeforeConnectEvent;
|
||||||
|
AfterConnect -= connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IDisconnectEvent)
|
if (this is IDisconnectEvent disconnect)
|
||||||
{
|
{
|
||||||
IDisconnectEvent bind = (IDisconnectEvent)this;
|
if (isAdd)
|
||||||
BeforeDisconnect += bind.BeforeDisconnectEvent;
|
{
|
||||||
AfterDisconnect += bind.AfterDisconnectEvent;
|
BeforeDisconnect += disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect += disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeDisconnect -= disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect -= disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILoginEvent)
|
if (this is ILoginEvent login)
|
||||||
{
|
{
|
||||||
ILoginEvent bind = (ILoginEvent)this;
|
if (isAdd)
|
||||||
BeforeLogin += bind.BeforeLoginEvent;
|
{
|
||||||
AfterLogin += bind.AfterLoginEvent;
|
BeforeLogin += login.BeforeLoginEvent;
|
||||||
|
AfterLogin += login.AfterLoginEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogin -= login.BeforeLoginEvent;
|
||||||
|
AfterLogin -= login.AfterLoginEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILogoutEvent)
|
if (this is ILogoutEvent logout)
|
||||||
{
|
{
|
||||||
ILogoutEvent bind = (ILogoutEvent)this;
|
if (isAdd)
|
||||||
BeforeLogout += bind.BeforeLogoutEvent;
|
{
|
||||||
AfterLogout += bind.AfterLogoutEvent;
|
BeforeLogout += logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout += logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogout -= logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout -= logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IRegEvent)
|
if (this is IRegEvent reg)
|
||||||
{
|
{
|
||||||
IRegEvent bind = (IRegEvent)this;
|
if (isAdd)
|
||||||
BeforeReg += bind.BeforeRegEvent;
|
{
|
||||||
AfterReg += bind.AfterRegEvent;
|
BeforeReg += reg.BeforeRegEvent;
|
||||||
|
AfterReg += reg.AfterRegEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeReg -= reg.BeforeRegEvent;
|
||||||
|
AfterReg -= reg.AfterRegEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IIntoRoomEvent)
|
if (this is IIntoRoomEvent intoRoom)
|
||||||
{
|
{
|
||||||
IIntoRoomEvent bind = (IIntoRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeIntoRoom += bind.BeforeIntoRoomEvent;
|
{
|
||||||
AfterIntoRoom += bind.AfterIntoRoomEvent;
|
BeforeIntoRoom += intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom += intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeIntoRoom -= intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom -= intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISendTalkEvent)
|
if (this is ISendTalkEvent sendTalk)
|
||||||
{
|
{
|
||||||
ISendTalkEvent bind = (ISendTalkEvent)this;
|
if (isAdd)
|
||||||
BeforeSendTalk += bind.BeforeSendTalkEvent;
|
{
|
||||||
AfterSendTalk += bind.AfterSendTalkEvent;
|
BeforeSendTalk += sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk += sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSendTalk -= sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk -= sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ICreateRoomEvent)
|
if (this is ICreateRoomEvent createRoom)
|
||||||
{
|
{
|
||||||
ICreateRoomEvent bind = (ICreateRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeCreateRoom += bind.BeforeCreateRoomEvent;
|
{
|
||||||
AfterCreateRoom += bind.AfterCreateRoomEvent;
|
BeforeCreateRoom += createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom += createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeCreateRoom -= createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom -= createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IQuitRoomEvent)
|
if (this is IQuitRoomEvent quitRoom)
|
||||||
{
|
{
|
||||||
IQuitRoomEvent bind = (IQuitRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeQuitRoom += bind.BeforeQuitRoomEvent;
|
{
|
||||||
AfterQuitRoom += bind.AfterQuitRoomEvent;
|
BeforeQuitRoom += quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom += quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeQuitRoom -= quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom -= quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeRoomSettingEvent)
|
if (this is IChangeRoomSettingEvent changeRoomSetting)
|
||||||
{
|
{
|
||||||
IChangeRoomSettingEvent bind = (IChangeRoomSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeRoomSetting += bind.BeforeChangeRoomSettingEvent;
|
{
|
||||||
AfterChangeRoomSetting += bind.AfterChangeRoomSettingEvent;
|
BeforeChangeRoomSetting += changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting += changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeRoomSetting -= changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting -= changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartMatchEvent)
|
if (this is IStartMatchEvent startMatch)
|
||||||
{
|
{
|
||||||
IStartMatchEvent bind = (IStartMatchEvent)this;
|
if (isAdd)
|
||||||
BeforeStartMatch += bind.BeforeStartMatchEvent;
|
{
|
||||||
AfterStartMatch += bind.AfterStartMatchEvent;
|
BeforeStartMatch += startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch += startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartMatch -= startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch -= startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartGameEvent)
|
if (this is IStartGameEvent startGame)
|
||||||
{
|
{
|
||||||
IStartGameEvent bind = (IStartGameEvent)this;
|
if (isAdd)
|
||||||
BeforeStartGame += bind.BeforeStartGameEvent;
|
{
|
||||||
AfterStartGame += bind.AfterStartGameEvent;
|
BeforeStartGame += startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame += startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartGame -= startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame -= startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeProfileEvent)
|
if (this is IChangeProfileEvent changeProfile)
|
||||||
{
|
{
|
||||||
IChangeProfileEvent bind = (IChangeProfileEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeProfile += bind.BeforeChangeProfileEvent;
|
{
|
||||||
AfterChangeProfile += bind.AfterChangeProfileEvent;
|
BeforeChangeProfile += changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile += changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeProfile -= changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile -= changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeAccountSettingEvent)
|
if (this is IChangeAccountSettingEvent changeAccountSetting)
|
||||||
{
|
{
|
||||||
IChangeAccountSettingEvent bind = (IChangeAccountSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeAccountSetting += bind.BeforeChangeAccountSettingEvent;
|
{
|
||||||
AfterChangeAccountSetting += bind.AfterChangeAccountSettingEvent;
|
BeforeChangeAccountSetting += changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting += changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeAccountSetting -= changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting -= changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenInventoryEvent)
|
if (this is IOpenInventoryEvent openInventory)
|
||||||
{
|
{
|
||||||
IOpenInventoryEvent bind = (IOpenInventoryEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenInventory += bind.BeforeOpenInventoryEvent;
|
{
|
||||||
AfterOpenInventory += bind.AfterOpenInventoryEvent;
|
BeforeOpenInventory += openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory += openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenInventory -= openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory -= openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISignInEvent)
|
if (this is ISignInEvent signIn)
|
||||||
{
|
{
|
||||||
ISignInEvent bind = (ISignInEvent)this;
|
if (isAdd)
|
||||||
BeforeSignIn += bind.BeforeSignInEvent;
|
{
|
||||||
AfterSignIn += bind.AfterSignInEvent;
|
BeforeSignIn += signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn += signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSignIn -= signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn -= signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenStoreEvent)
|
if (this is IOpenStoreEvent openStore)
|
||||||
{
|
{
|
||||||
IOpenStoreEvent bind = (IOpenStoreEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenStore += bind.BeforeOpenStoreEvent;
|
{
|
||||||
AfterOpenStore += bind.AfterOpenStoreEvent;
|
BeforeOpenStore += openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore += openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenStore -= openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore -= openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IBuyItemEvent)
|
if (this is IBuyItemEvent buyItem)
|
||||||
{
|
{
|
||||||
IBuyItemEvent bind = (IBuyItemEvent)this;
|
if (isAdd)
|
||||||
BeforeBuyItem += bind.BeforeBuyItemEvent;
|
{
|
||||||
AfterBuyItem += bind.AfterBuyItemEvent;
|
BeforeBuyItem += buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem += buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeBuyItem -= buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem -= buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IShowRankingEvent)
|
if (this is IShowRankingEvent showRanking)
|
||||||
{
|
{
|
||||||
IShowRankingEvent bind = (IShowRankingEvent)this;
|
if (isAdd)
|
||||||
BeforeShowRanking += bind.BeforeShowRankingEvent;
|
{
|
||||||
AfterShowRanking += bind.AfterShowRankingEvent;
|
BeforeShowRanking += showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking += showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeShowRanking -= showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking -= showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IUseItemEvent)
|
if (this is IUseItemEvent useItem)
|
||||||
{
|
{
|
||||||
IUseItemEvent bind = (IUseItemEvent)this;
|
if (isAdd)
|
||||||
BeforeUseItem += bind.BeforeUseItemEvent;
|
{
|
||||||
AfterUseItem += bind.AfterUseItemEvent;
|
BeforeUseItem += useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem += useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeUseItem -= useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem -= useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IEndGameEvent)
|
if (this is IEndGameEvent endGame)
|
||||||
{
|
{
|
||||||
IEndGameEvent bind = (IEndGameEvent)this;
|
if (isAdd)
|
||||||
BeforeEndGame += bind.BeforeEndGameEvent;
|
{
|
||||||
AfterEndGame += bind.AfterEndGameEvent;
|
BeforeEndGame += endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame += endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeEndGame -= endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame -= endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -59,6 +59,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
Factory.OpenFactory.UnRegisterFactory(SkillFactory());
|
||||||
|
Factory.OpenFactory.UnRegisterFactory(EffectFactory());
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册工厂
|
/// 注册工厂
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using Milimoe.FunGame.Core.Library.Common.Event;
|
|||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Library.Common.Addon
|
namespace Milimoe.FunGame.Core.Library.Common.Addon
|
||||||
{
|
{
|
||||||
public abstract class WebAPIPlugin : IAddon, IAddonController<IAddon>
|
public abstract class WebAPIPlugin : IPlugin
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 插件名称
|
/// 插件名称
|
||||||
@ -28,10 +28,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract string Author { get; }
|
public abstract string Author { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录该插件的加载器
|
||||||
|
/// </summary>
|
||||||
|
public WebAPIPluginLoader? PluginLoader { get; set; } = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 包含了一些常用方法的控制器
|
/// 包含了一些常用方法的控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ServerAddonController<IAddon> Controller
|
public ServerAddonController<IPlugin> Controller
|
||||||
{
|
{
|
||||||
get => _controller ?? throw new NotImplementedException();
|
get => _controller ?? throw new NotImplementedException();
|
||||||
internal set => _controller = value;
|
internal set => _controller = value;
|
||||||
@ -40,16 +45,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// base控制器
|
/// base控制器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BaseAddonController<IAddon> IAddonController<IAddon>.Controller
|
BaseAddonController<IPlugin> IAddonController<IPlugin>.Controller
|
||||||
{
|
{
|
||||||
get => Controller;
|
get => Controller;
|
||||||
set => _controller = (ServerAddonController<IAddon>?)value;
|
set => _controller = (ServerAddonController<IPlugin>?)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 控制器内部变量
|
/// 控制器内部变量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ServerAddonController<IAddon>? _controller;
|
private ServerAddonController<IPlugin>? _controller;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载标记
|
/// 加载标记
|
||||||
@ -76,6 +81,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
return _isLoaded;
|
return _isLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 卸载模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="objs"></param>
|
||||||
|
public void UnLoad(params object[] objs)
|
||||||
|
{
|
||||||
|
BindEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 接收服务器控制台的输入
|
/// 接收服务器控制台的输入
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -111,153 +125,300 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void BindEvent()
|
private void BindEvent(bool isAdd = true)
|
||||||
{
|
{
|
||||||
if (this is IConnectEvent)
|
if (this is IConnectEvent connect)
|
||||||
{
|
{
|
||||||
IConnectEvent bind = (IConnectEvent)this;
|
if (isAdd)
|
||||||
BeforeConnect += bind.BeforeConnectEvent;
|
{
|
||||||
AfterConnect += bind.AfterConnectEvent;
|
BeforeConnect += connect.BeforeConnectEvent;
|
||||||
|
AfterConnect += connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeConnect -= connect.BeforeConnectEvent;
|
||||||
|
AfterConnect -= connect.AfterConnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IDisconnectEvent)
|
if (this is IDisconnectEvent disconnect)
|
||||||
{
|
{
|
||||||
IDisconnectEvent bind = (IDisconnectEvent)this;
|
if (isAdd)
|
||||||
BeforeDisconnect += bind.BeforeDisconnectEvent;
|
{
|
||||||
AfterDisconnect += bind.AfterDisconnectEvent;
|
BeforeDisconnect += disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect += disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeDisconnect -= disconnect.BeforeDisconnectEvent;
|
||||||
|
AfterDisconnect -= disconnect.AfterDisconnectEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILoginEvent)
|
if (this is ILoginEvent login)
|
||||||
{
|
{
|
||||||
ILoginEvent bind = (ILoginEvent)this;
|
if (isAdd)
|
||||||
BeforeLogin += bind.BeforeLoginEvent;
|
{
|
||||||
AfterLogin += bind.AfterLoginEvent;
|
BeforeLogin += login.BeforeLoginEvent;
|
||||||
|
AfterLogin += login.AfterLoginEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogin -= login.BeforeLoginEvent;
|
||||||
|
AfterLogin -= login.AfterLoginEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ILogoutEvent)
|
if (this is ILogoutEvent logout)
|
||||||
{
|
{
|
||||||
ILogoutEvent bind = (ILogoutEvent)this;
|
if (isAdd)
|
||||||
BeforeLogout += bind.BeforeLogoutEvent;
|
{
|
||||||
AfterLogout += bind.AfterLogoutEvent;
|
BeforeLogout += logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout += logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeLogout -= logout.BeforeLogoutEvent;
|
||||||
|
AfterLogout -= logout.AfterLogoutEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IRegEvent)
|
if (this is IRegEvent reg)
|
||||||
{
|
{
|
||||||
IRegEvent bind = (IRegEvent)this;
|
if (isAdd)
|
||||||
BeforeReg += bind.BeforeRegEvent;
|
{
|
||||||
AfterReg += bind.AfterRegEvent;
|
BeforeReg += reg.BeforeRegEvent;
|
||||||
|
AfterReg += reg.AfterRegEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeReg -= reg.BeforeRegEvent;
|
||||||
|
AfterReg -= reg.AfterRegEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IIntoRoomEvent)
|
if (this is IIntoRoomEvent intoRoom)
|
||||||
{
|
{
|
||||||
IIntoRoomEvent bind = (IIntoRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeIntoRoom += bind.BeforeIntoRoomEvent;
|
{
|
||||||
AfterIntoRoom += bind.AfterIntoRoomEvent;
|
BeforeIntoRoom += intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom += intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeIntoRoom -= intoRoom.BeforeIntoRoomEvent;
|
||||||
|
AfterIntoRoom -= intoRoom.AfterIntoRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISendTalkEvent)
|
if (this is ISendTalkEvent sendTalk)
|
||||||
{
|
{
|
||||||
ISendTalkEvent bind = (ISendTalkEvent)this;
|
if (isAdd)
|
||||||
BeforeSendTalk += bind.BeforeSendTalkEvent;
|
{
|
||||||
AfterSendTalk += bind.AfterSendTalkEvent;
|
BeforeSendTalk += sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk += sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSendTalk -= sendTalk.BeforeSendTalkEvent;
|
||||||
|
AfterSendTalk -= sendTalk.AfterSendTalkEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ICreateRoomEvent)
|
if (this is ICreateRoomEvent createRoom)
|
||||||
{
|
{
|
||||||
ICreateRoomEvent bind = (ICreateRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeCreateRoom += bind.BeforeCreateRoomEvent;
|
{
|
||||||
AfterCreateRoom += bind.AfterCreateRoomEvent;
|
BeforeCreateRoom += createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom += createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeCreateRoom -= createRoom.BeforeCreateRoomEvent;
|
||||||
|
AfterCreateRoom -= createRoom.AfterCreateRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IQuitRoomEvent)
|
if (this is IQuitRoomEvent quitRoom)
|
||||||
{
|
{
|
||||||
IQuitRoomEvent bind = (IQuitRoomEvent)this;
|
if (isAdd)
|
||||||
BeforeQuitRoom += bind.BeforeQuitRoomEvent;
|
{
|
||||||
AfterQuitRoom += bind.AfterQuitRoomEvent;
|
BeforeQuitRoom += quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom += quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeQuitRoom -= quitRoom.BeforeQuitRoomEvent;
|
||||||
|
AfterQuitRoom -= quitRoom.AfterQuitRoomEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeRoomSettingEvent)
|
if (this is IChangeRoomSettingEvent changeRoomSetting)
|
||||||
{
|
{
|
||||||
IChangeRoomSettingEvent bind = (IChangeRoomSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeRoomSetting += bind.BeforeChangeRoomSettingEvent;
|
{
|
||||||
AfterChangeRoomSetting += bind.AfterChangeRoomSettingEvent;
|
BeforeChangeRoomSetting += changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting += changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeRoomSetting -= changeRoomSetting.BeforeChangeRoomSettingEvent;
|
||||||
|
AfterChangeRoomSetting -= changeRoomSetting.AfterChangeRoomSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartMatchEvent)
|
if (this is IStartMatchEvent startMatch)
|
||||||
{
|
{
|
||||||
IStartMatchEvent bind = (IStartMatchEvent)this;
|
if (isAdd)
|
||||||
BeforeStartMatch += bind.BeforeStartMatchEvent;
|
{
|
||||||
AfterStartMatch += bind.AfterStartMatchEvent;
|
BeforeStartMatch += startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch += startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartMatch -= startMatch.BeforeStartMatchEvent;
|
||||||
|
AfterStartMatch -= startMatch.AfterStartMatchEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IStartGameEvent)
|
if (this is IStartGameEvent startGame)
|
||||||
{
|
{
|
||||||
IStartGameEvent bind = (IStartGameEvent)this;
|
if (isAdd)
|
||||||
BeforeStartGame += bind.BeforeStartGameEvent;
|
{
|
||||||
AfterStartGame += bind.AfterStartGameEvent;
|
BeforeStartGame += startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame += startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeStartGame -= startGame.BeforeStartGameEvent;
|
||||||
|
AfterStartGame -= startGame.AfterStartGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeProfileEvent)
|
if (this is IChangeProfileEvent changeProfile)
|
||||||
{
|
{
|
||||||
IChangeProfileEvent bind = (IChangeProfileEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeProfile += bind.BeforeChangeProfileEvent;
|
{
|
||||||
AfterChangeProfile += bind.AfterChangeProfileEvent;
|
BeforeChangeProfile += changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile += changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeProfile -= changeProfile.BeforeChangeProfileEvent;
|
||||||
|
AfterChangeProfile -= changeProfile.AfterChangeProfileEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IChangeAccountSettingEvent)
|
if (this is IChangeAccountSettingEvent changeAccountSetting)
|
||||||
{
|
{
|
||||||
IChangeAccountSettingEvent bind = (IChangeAccountSettingEvent)this;
|
if (isAdd)
|
||||||
BeforeChangeAccountSetting += bind.BeforeChangeAccountSettingEvent;
|
{
|
||||||
AfterChangeAccountSetting += bind.AfterChangeAccountSettingEvent;
|
BeforeChangeAccountSetting += changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting += changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeChangeAccountSetting -= changeAccountSetting.BeforeChangeAccountSettingEvent;
|
||||||
|
AfterChangeAccountSetting -= changeAccountSetting.AfterChangeAccountSettingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenInventoryEvent)
|
if (this is IOpenInventoryEvent openInventory)
|
||||||
{
|
{
|
||||||
IOpenInventoryEvent bind = (IOpenInventoryEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenInventory += bind.BeforeOpenInventoryEvent;
|
{
|
||||||
AfterOpenInventory += bind.AfterOpenInventoryEvent;
|
BeforeOpenInventory += openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory += openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenInventory -= openInventory.BeforeOpenInventoryEvent;
|
||||||
|
AfterOpenInventory -= openInventory.AfterOpenInventoryEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is ISignInEvent)
|
if (this is ISignInEvent signIn)
|
||||||
{
|
{
|
||||||
ISignInEvent bind = (ISignInEvent)this;
|
if (isAdd)
|
||||||
BeforeSignIn += bind.BeforeSignInEvent;
|
{
|
||||||
AfterSignIn += bind.AfterSignInEvent;
|
BeforeSignIn += signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn += signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeSignIn -= signIn.BeforeSignInEvent;
|
||||||
|
AfterSignIn -= signIn.AfterSignInEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IOpenStoreEvent)
|
if (this is IOpenStoreEvent openStore)
|
||||||
{
|
{
|
||||||
IOpenStoreEvent bind = (IOpenStoreEvent)this;
|
if (isAdd)
|
||||||
BeforeOpenStore += bind.BeforeOpenStoreEvent;
|
{
|
||||||
AfterOpenStore += bind.AfterOpenStoreEvent;
|
BeforeOpenStore += openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore += openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeOpenStore -= openStore.BeforeOpenStoreEvent;
|
||||||
|
AfterOpenStore -= openStore.AfterOpenStoreEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IBuyItemEvent)
|
if (this is IBuyItemEvent buyItem)
|
||||||
{
|
{
|
||||||
IBuyItemEvent bind = (IBuyItemEvent)this;
|
if (isAdd)
|
||||||
BeforeBuyItem += bind.BeforeBuyItemEvent;
|
{
|
||||||
AfterBuyItem += bind.AfterBuyItemEvent;
|
BeforeBuyItem += buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem += buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeBuyItem -= buyItem.BeforeBuyItemEvent;
|
||||||
|
AfterBuyItem -= buyItem.AfterBuyItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IShowRankingEvent)
|
if (this is IShowRankingEvent showRanking)
|
||||||
{
|
{
|
||||||
IShowRankingEvent bind = (IShowRankingEvent)this;
|
if (isAdd)
|
||||||
BeforeShowRanking += bind.BeforeShowRankingEvent;
|
{
|
||||||
AfterShowRanking += bind.AfterShowRankingEvent;
|
BeforeShowRanking += showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking += showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeShowRanking -= showRanking.BeforeShowRankingEvent;
|
||||||
|
AfterShowRanking -= showRanking.AfterShowRankingEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IUseItemEvent)
|
if (this is IUseItemEvent useItem)
|
||||||
{
|
{
|
||||||
IUseItemEvent bind = (IUseItemEvent)this;
|
if (isAdd)
|
||||||
BeforeUseItem += bind.BeforeUseItemEvent;
|
{
|
||||||
AfterUseItem += bind.AfterUseItemEvent;
|
BeforeUseItem += useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem += useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeUseItem -= useItem.BeforeUseItemEvent;
|
||||||
|
AfterUseItem -= useItem.AfterUseItemEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this is IEndGameEvent)
|
if (this is IEndGameEvent endGame)
|
||||||
{
|
{
|
||||||
IEndGameEvent bind = (IEndGameEvent)this;
|
if (isAdd)
|
||||||
BeforeEndGame += bind.BeforeEndGameEvent;
|
{
|
||||||
AfterEndGame += bind.AfterEndGameEvent;
|
BeforeEndGame += endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame += endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
BeforeEndGame -= endGame.BeforeEndGameEvent;
|
||||||
|
AfterEndGame -= endGame.AfterEndGameEvent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
45
Library/Common/Architecture/SyncAwaiter.cs
Normal file
45
Library/Common/Architecture/SyncAwaiter.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
namespace Milimoe.FunGame.Core.Library.Common.Architecture
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 该类的工具方法允许在同步方法中安全等待异步任务完成
|
||||||
|
/// </summary>
|
||||||
|
public class SyncAwaiter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 在同步方法中安全等待一个 Task 完成并获取结果
|
||||||
|
/// 内部使用 ManualResetEventSlim,避免死锁
|
||||||
|
/// </summary>
|
||||||
|
public static T WaitResult<T>(Task<T> task)
|
||||||
|
{
|
||||||
|
if (task.IsCompleted)
|
||||||
|
return task.Result;
|
||||||
|
|
||||||
|
ManualResetEventSlim mres = new(false);
|
||||||
|
|
||||||
|
// 当 task 完成时,设置事件信号
|
||||||
|
task.ContinueWith(_ =>
|
||||||
|
{
|
||||||
|
mres.Set();
|
||||||
|
}, TaskScheduler.Default);
|
||||||
|
|
||||||
|
// 阻塞当前线程直到 task 完成
|
||||||
|
// 注意:这会阻塞调用线程!
|
||||||
|
mres.Wait();
|
||||||
|
|
||||||
|
// 现在可以安全取 Result(不会抛死锁)
|
||||||
|
return task.Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 无返回值版本
|
||||||
|
/// </summary>
|
||||||
|
public static void Wait(Task task)
|
||||||
|
{
|
||||||
|
if (task.IsCompleted) return;
|
||||||
|
|
||||||
|
ManualResetEventSlim mres = new(false);
|
||||||
|
task.ContinueWith(_ => mres.Set(), TaskScheduler.Default);
|
||||||
|
mres.Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -184,4 +184,19 @@
|
|||||||
{
|
{
|
||||||
public override string Message => "试图构造一个不支持的类的实例 (#10037)";
|
public override string Message => "试图构造一个不支持的类的实例 (#10037)";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AddonInvalidException(string fullPath = "") : Exception
|
||||||
|
{
|
||||||
|
public override string Message => $"加载项 {(fullPath != "" ? $"'{fullPath}'" : "")} 无效 (#10038)";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddonLoadException(string fullPath = "", Exception? inner = null) : Exception("", inner)
|
||||||
|
{
|
||||||
|
public override string Message => $"加载项 {(fullPath != "" ? $"'{fullPath}'" : "")} 加载失败 (#10039)";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AddonUnloadException(string fullPath = "", Exception? inner = null) : Exception("", inner)
|
||||||
|
{
|
||||||
|
public override string Message => $"卸载加载项 {(fullPath != "" ? $"'{fullPath}' 的" : "")} 旧上下文失败 (#10040)";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2809,7 +2809,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
if (healBonus != 0)
|
if (healBonus != 0)
|
||||||
{
|
{
|
||||||
totalHealBonus[effect]= healBonus;
|
totalHealBonus[effect] = healBonus;
|
||||||
healStrings.Add($"{(healBonus > 0 ? " + " : " - ")}{Math.Abs(healBonus):0.##}({effect.Name})");
|
healStrings.Add($"{(healBonus > 0 ? " + " : " - ")}{Math.Abs(healBonus):0.##}({effect.Name})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2842,7 +2842,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (heal <= 0)
|
if (heal <= 0 || heal.ToString("0.##") == "0")
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
## 项目简介
|
## 项目简介
|
||||||
|
|
||||||
`FunGame` 是一套基于 `C#.NET` 设计的回合制游戏服务器端开发框架,旨在简化多人回合制在线游戏的开发流程。
|
`FunGame` 是一套基于 `C#.NET` 设计的回合制战斗系统解决方案,旨在打造充满策略趣味的战棋回合制游戏。
|
||||||
|
|
||||||
配套解决方案:[FunGameServer](https://github.com/project-redbud/FunGame-Server)(基于 `ASP.NET Core Web API` 的跨平台高性能服务器控制台)
|
配套服务器解决方案:[FunGameServer](https://github.com/project-redbud/FunGame-Server)(基于 `ASP.NET Core Web API` 的跨平台高性能服务器控制台)
|
||||||
|
|
||||||
本仓库 `FunGame.Core` 项目是 `FunGame` 框架的核心模块,包含了框架的基础组件。
|
本仓库 `FunGame.Core` 项目是 `FunGame` 框架的核心模块,包含了框架的基础组件。
|
||||||
本项目不局限于服务器端开发,在支持 `.NET` 编程的客户端项目中也能使用。
|
本项目不局限于服务器端开发,在支持 `.NET` 编程的客户端项目中也能使用。
|
||||||
|
|||||||
@ -28,7 +28,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
@ -81,7 +81,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
@ -134,7 +134,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return plugins;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
@ -190,7 +190,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return modules;
|
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return modules;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
@ -264,7 +264,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return servers;
|
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return servers;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
@ -334,7 +334,7 @@ namespace Milimoe.FunGame.Core.Service
|
|||||||
{
|
{
|
||||||
if (!Directory.Exists(ReflectionSet.GameMapFolderPath)) return maps;
|
if (!Directory.Exists(ReflectionSet.GameMapFolderPath)) return maps;
|
||||||
|
|
||||||
string[] dlls = Directory.GetFiles(ReflectionSet.GameMapFolderPath, "*.dll");
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameMapFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
foreach (string dll in dlls)
|
foreach (string dll in dlls)
|
||||||
{
|
{
|
||||||
|
|||||||
566
Service/HotLoadAddonManager.cs
Normal file
566
Service/HotLoadAddonManager.cs
Normal file
@ -0,0 +1,566 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.Loader;
|
||||||
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
using Milimoe.FunGame.Core.Library.Exception;
|
||||||
|
|
||||||
|
namespace Milimoe.FunGame.Core.Service
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 支持热更新、可卸载、可重载插件/模组
|
||||||
|
/// 支持插件跨 DLL 引用模组,通过全局访问最新实例
|
||||||
|
/// </summary>
|
||||||
|
internal static class HotLoadAddonManager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的插件DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
internal static Dictionary<string, string> PluginFilePaths { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的模组DLL名称对应的路径
|
||||||
|
/// </summary>
|
||||||
|
internal static Dictionary<string, string> ModuleFilePaths { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的插件
|
||||||
|
/// </summary>
|
||||||
|
internal static Dictionary<string, IAddon> Plugins { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 已加载的模组
|
||||||
|
/// </summary>
|
||||||
|
internal static Dictionary<string, IAddon> Modules { get; } = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// key = 文件路径(全小写),value = 当前加载上下文 + 程序集 + 根实例
|
||||||
|
/// </summary>
|
||||||
|
private static readonly ConcurrentDictionary<string, DLLAddonEntry> _loadedDLLs = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 即将清除的上下文
|
||||||
|
/// </summary>
|
||||||
|
private static readonly List<WeakReference<AssemblyLoadContext>> _contextsToClean = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试加载或重新加载某个 DLL,返回是否成功加载/更新
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fullPath">DLL 完整路径</param>
|
||||||
|
/// <param name="newInstances">新加载的实例列表</param>
|
||||||
|
/// <param name="isPlugin">是否为插件</param>
|
||||||
|
/// <param name="isModule">是否为模组</param>
|
||||||
|
/// <returns>是否成功(无变化不成功)</returns>
|
||||||
|
private static bool TryLoadOrReload(string fullPath, out List<IAddon> newInstances, bool isPlugin, bool isModule)
|
||||||
|
{
|
||||||
|
newInstances = [];
|
||||||
|
|
||||||
|
if (!File.Exists(fullPath)) return false;
|
||||||
|
|
||||||
|
string key = fullPath.ToLowerInvariant();
|
||||||
|
DateTime currentWriteTime = File.GetLastWriteTimeUtc(fullPath);
|
||||||
|
|
||||||
|
// 文件无变化 → 返回现有实例
|
||||||
|
if (_loadedDLLs.TryGetValue(key, out DLLAddonEntry? entry) && entry.LastWriteTimeUtc == currentWriteTime)
|
||||||
|
{
|
||||||
|
foreach (AddonSubEntry sub in entry.Addons)
|
||||||
|
{
|
||||||
|
if (sub.Weak.TryGetTarget(out IAddon? target) && target != null)
|
||||||
|
newInstances.Add(target);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 需要卸载旧 DLL
|
||||||
|
if (_loadedDLLs.TryRemove(key, out DLLAddonEntry? oldEntry))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (AddonSubEntry sub in oldEntry.Addons)
|
||||||
|
{
|
||||||
|
if (sub.Instance is IHotReloadAware aware)
|
||||||
|
{
|
||||||
|
aware.OnBeforeUnload();
|
||||||
|
}
|
||||||
|
sub.Instance.UnLoad();
|
||||||
|
Plugins.Remove(sub.Name);
|
||||||
|
Modules.Remove(sub.Name);
|
||||||
|
}
|
||||||
|
oldEntry.Context.Unload();
|
||||||
|
GC.Collect(2, GCCollectionMode.Forced, true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new AddonUnloadException(fullPath, e);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (_contextsToClean)
|
||||||
|
{
|
||||||
|
_contextsToClean.Add(new WeakReference<AssemblyLoadContext>(oldEntry.Context));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载新 DLL
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dllName = Path.GetFileNameWithoutExtension(fullPath);
|
||||||
|
AddonLoadContext ctx = new(dllName, fullPath);
|
||||||
|
|
||||||
|
using FileStream fs = new(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
Assembly assembly = ctx.LoadFromStream(fs);
|
||||||
|
string filename = assembly.GetName().Name?.Trim() ?? "";
|
||||||
|
|
||||||
|
Type[] addonTypes = [.. assembly.GetTypes().Where(t => !t.IsAbstract && typeof(IAddon).IsAssignableFrom(t) && (typeof(Plugin).IsAssignableFrom(t) || typeof(ServerPlugin).IsAssignableFrom(t) ||
|
||||||
|
typeof(WebAPIPlugin).IsAssignableFrom(t) || typeof(GameModule).IsAssignableFrom(t) || typeof(GameModuleServer).IsAssignableFrom(t) || typeof(CharacterModule).IsAssignableFrom(t) ||
|
||||||
|
typeof(SkillModule).IsAssignableFrom(t) || typeof(ItemModule).IsAssignableFrom(t) || typeof(GameMap).IsAssignableFrom(t)))];
|
||||||
|
|
||||||
|
DLLAddonEntry newEntry = new()
|
||||||
|
{
|
||||||
|
Context = ctx,
|
||||||
|
Assembly = assembly,
|
||||||
|
LastWriteTimeUtc = currentWriteTime
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (Type addonType in addonTypes)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (Activator.CreateInstance(addonType) is not IAddon instance) continue;
|
||||||
|
|
||||||
|
// 为了安全起见,未实现此接口的不允许使用热更新模式加载
|
||||||
|
if (instance is not IHotReloadAware aware) continue;
|
||||||
|
|
||||||
|
string addonName = instance.Name?.Trim() ?? addonType.Name;
|
||||||
|
AddonSubEntry sub = new()
|
||||||
|
{
|
||||||
|
Instance = instance,
|
||||||
|
Weak = new WeakReference<IAddon>(instance),
|
||||||
|
Name = addonName
|
||||||
|
};
|
||||||
|
|
||||||
|
newEntry.Addons.Add(sub);
|
||||||
|
newInstances.Add(instance);
|
||||||
|
|
||||||
|
if (isPlugin) Plugins[addonName] = instance;
|
||||||
|
if (isModule) Modules[addonName] = instance;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
TXTHelper.AppendErrorLog(e.GetErrorInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newInstances.Count > 0)
|
||||||
|
{
|
||||||
|
_loadedDLLs[key] = newEntry;
|
||||||
|
if (isPlugin) PluginFilePaths[filename] = key;
|
||||||
|
if (isModule) ModuleFilePaths[filename] = key;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw new AddonLoadException(fullPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 plugins 目录加载所有插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugins">插件字典</param>
|
||||||
|
/// <param name="delegates">委托字典</param>
|
||||||
|
/// <param name="otherobjs">其他参数</param>
|
||||||
|
/// <returns>被更新的插件列表</returns>
|
||||||
|
internal static List<Plugin> LoadPlugins(Dictionary<string, Plugin> plugins, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
List<Plugin> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, true, false);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is Plugin pluginInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
|
||||||
|
{
|
||||||
|
pluginInstance.Controller = new(pluginInstance, delegates);
|
||||||
|
updated.Add(pluginInstance);
|
||||||
|
}
|
||||||
|
plugins[name] = pluginInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 plugins 目录加载所有 Server 插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugins">插件字典</param>
|
||||||
|
/// <param name="delegates">委托字典</param>
|
||||||
|
/// <param name="otherobjs">其他参数</param>
|
||||||
|
/// <returns>被更新的插件列表</returns>
|
||||||
|
internal static List<ServerPlugin> LoadServerPlugins(Dictionary<string, ServerPlugin> plugins, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
List<ServerPlugin> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, true, false);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is ServerPlugin pluginInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
|
||||||
|
{
|
||||||
|
pluginInstance.Controller = new(pluginInstance, delegates);
|
||||||
|
updated.Add(pluginInstance);
|
||||||
|
}
|
||||||
|
plugins[name] = pluginInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 plugins 目录加载所有 WebAPI 插件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="plugins">插件字典</param>
|
||||||
|
/// <param name="delegates">委托字典</param>
|
||||||
|
/// <param name="otherobjs">其他参数</param>
|
||||||
|
/// <returns>被更新的插件列表</returns>
|
||||||
|
internal static List<WebAPIPlugin> LoadWebAPIPlugins(Dictionary<string, WebAPIPlugin> plugins, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
List<WebAPIPlugin> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.PluginFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.PluginFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, true, false);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is WebAPIPlugin pluginInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !plugins.ContainsKey(name)) && pluginInstance.Load(otherobjs))
|
||||||
|
{
|
||||||
|
pluginInstance.Controller = new(pluginInstance, delegates);
|
||||||
|
updated.Add(pluginInstance);
|
||||||
|
}
|
||||||
|
plugins[name] = pluginInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 modules 目录加载所有模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modules">模组字典</param>
|
||||||
|
/// <param name="characters">角色字典</param>
|
||||||
|
/// <param name="skills">技能字典</param>
|
||||||
|
/// <param name="items">物品字典</param>
|
||||||
|
/// <param name="delegates">委托字典</param>
|
||||||
|
/// <param name="otherobjs">其他参数</param>
|
||||||
|
/// <returns>被更新的模组列表</returns>
|
||||||
|
internal static List<GameModule> LoadGameModules(Dictionary<string, GameModule> modules, Dictionary<string, CharacterModule> characters, Dictionary<string, SkillModule> skills, Dictionary<string, ItemModule> items, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
List<GameModule> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, false, true);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is GameModule moduleInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !modules.ContainsKey(name)) && instance.Load(otherobjs))
|
||||||
|
{
|
||||||
|
moduleInstance.Controller = new(moduleInstance, delegates);
|
||||||
|
}
|
||||||
|
modules[name] = moduleInstance;
|
||||||
|
}
|
||||||
|
else if (instance is CharacterModule charInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !characters.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
characters[name] = charInstance;
|
||||||
|
}
|
||||||
|
else if (instance is SkillModule skillInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !skills.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
skills[name] = skillInstance;
|
||||||
|
}
|
||||||
|
else if (instance is ItemModule itemInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !items.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
items[name] = itemInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 modules 目录加载所有适用于服务器的模组
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="servers">服务器模组字典</param>
|
||||||
|
/// <param name="characters">角色字典</param>
|
||||||
|
/// <param name="skills">技能字典</param>
|
||||||
|
/// <param name="items">物品字典</param>
|
||||||
|
/// <param name="delegates">委托字典</param>
|
||||||
|
/// <param name="otherobjs">其他参数</param>
|
||||||
|
/// <returns>被更新的服务器模组列表</returns>
|
||||||
|
internal static List<GameModuleServer> LoadGameModulesForServer(Dictionary<string, GameModuleServer> servers, Dictionary<string, CharacterModule> characters, Dictionary<string, SkillModule> skills, Dictionary<string, ItemModule> items, Dictionary<string, object> delegates, params object[] otherobjs)
|
||||||
|
{
|
||||||
|
List<GameModuleServer> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.GameModuleFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameModuleFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, false, true);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is GameModuleServer serversInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !servers.ContainsKey(name)) && instance.Load(otherobjs))
|
||||||
|
{
|
||||||
|
serversInstance.Controller = new(serversInstance, delegates);
|
||||||
|
}
|
||||||
|
servers[name] = serversInstance;
|
||||||
|
}
|
||||||
|
else if (instance is CharacterModule charInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !characters.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
characters[name] = charInstance;
|
||||||
|
}
|
||||||
|
else if (instance is SkillModule skillInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !skills.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
skills[name] = skillInstance;
|
||||||
|
}
|
||||||
|
else if (instance is ItemModule itemInstance)
|
||||||
|
{
|
||||||
|
if (loaded || !items.ContainsKey(name))
|
||||||
|
{
|
||||||
|
instance.Load(otherobjs);
|
||||||
|
}
|
||||||
|
items[name] = itemInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 maps 目录加载所有地图
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="maps">地图字典</param>
|
||||||
|
/// <param name="objs">其他参数</param>
|
||||||
|
/// <returns>被更新的地图列表</returns>
|
||||||
|
internal static List<GameMap> LoadGameMaps(Dictionary<string, GameMap> maps, params object[] objs)
|
||||||
|
{
|
||||||
|
List<GameMap> updated = [];
|
||||||
|
if (!Directory.Exists(ReflectionSet.GameMapFolderPath)) return updated;
|
||||||
|
|
||||||
|
string[] dlls = Directory.GetFiles(ReflectionSet.GameMapFolderPath, "*.dll", SearchOption.AllDirectories);
|
||||||
|
|
||||||
|
foreach (string dll in dlls)
|
||||||
|
{
|
||||||
|
bool loaded = TryLoadOrReload(dll, out List<IAddon> instances, false, true);
|
||||||
|
foreach (IAddon instance in instances)
|
||||||
|
{
|
||||||
|
string name = instance.Name.Trim();
|
||||||
|
if (instance is GameMap mapInstance)
|
||||||
|
{
|
||||||
|
// 热更新无变化的文件时,不会再触发 Load 方法
|
||||||
|
if ((loaded || !maps.ContainsKey(name)) && mapInstance.Load(objs))
|
||||||
|
{
|
||||||
|
updated.Add(mapInstance);
|
||||||
|
}
|
||||||
|
maps[name] = mapInstance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 在任务计划中定期执行
|
||||||
|
/// </summary>
|
||||||
|
internal static void CleanUnusedContexts()
|
||||||
|
{
|
||||||
|
lock (_contextsToClean)
|
||||||
|
{
|
||||||
|
for (int i = _contextsToClean.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (!_contextsToClean[i].TryGetTarget(out AssemblyLoadContext? ctx) || ctx.IsCollectible == false)
|
||||||
|
{
|
||||||
|
_contextsToClean.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
|
||||||
|
if (!ctx.IsCollectible)
|
||||||
|
{
|
||||||
|
_contextsToClean.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 热更新 DLL
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="filePath">DLL 完整路径</param>
|
||||||
|
/// <returns>是否成功热更新</returns>
|
||||||
|
internal static bool HotReload(string filePath)
|
||||||
|
{
|
||||||
|
return TryLoadOrReload(filePath, out _, isPlugin: Directory.GetParent(filePath)?.FullName == ReflectionSet.PluginFolderPath, isModule: Directory.GetParent(filePath)?.FullName == ReflectionSet.GameModuleFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试获取当前最新的实例
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">预期类型</typeparam>
|
||||||
|
/// <param name="addonName">插件/模组名称</param>
|
||||||
|
/// <param name="instance">最新的实例</param>
|
||||||
|
/// <returns>是否找到</returns>
|
||||||
|
internal static bool TryGetLiveInstance<T>(string addonName, out T? instance) where T : class, IAddon
|
||||||
|
{
|
||||||
|
instance = null;
|
||||||
|
|
||||||
|
// 所有的 Plugin 都继承自 IPlugin -> IAddon,除此之外都是 IAddon,所以先从 Plugin 里找
|
||||||
|
if (typeof(T).IsSubclassOf(typeof(IPlugin)))
|
||||||
|
{
|
||||||
|
if (Plugins.FirstOrDefault(kv => kv.Key == addonName).Value is T plugin)
|
||||||
|
{
|
||||||
|
instance = plugin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (Modules.FirstOrDefault(kv => kv.Key == addonName).Value is T module)
|
||||||
|
{
|
||||||
|
instance = module;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance != null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 每个加载项都拥有独立的可收集 AssemblyLoadContext
|
||||||
|
/// </summary>
|
||||||
|
private class AddonLoadContext(string addonName, string filePath) : AssemblyLoadContext($"{addonName}_{Guid.NewGuid():N}", isCollectible: true)
|
||||||
|
{
|
||||||
|
public string AddonName { get; } = addonName;
|
||||||
|
public string FilePath { get; } = filePath;
|
||||||
|
public DateTime LastWriteTimeUtc { get; } = File.GetLastWriteTimeUtc(filePath);
|
||||||
|
public AssemblyDependencyResolver Resolver { get; } = new(filePath);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取加载项的依赖项
|
||||||
|
/// </summary>
|
||||||
|
protected override Assembly? Load(AssemblyName assemblyName)
|
||||||
|
{
|
||||||
|
string? assemblyPath = Resolver.ResolveAssemblyToPath(assemblyName);
|
||||||
|
|
||||||
|
if (assemblyPath != null)
|
||||||
|
{
|
||||||
|
return LoadFromAssemblyPath(assemblyPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 读取 native dll 依赖项
|
||||||
|
/// </summary>
|
||||||
|
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||||
|
{
|
||||||
|
string? nativePath = Resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||||
|
if (nativePath != null)
|
||||||
|
{
|
||||||
|
return LoadUnmanagedDllFromPath(nativePath);
|
||||||
|
}
|
||||||
|
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录 DLL 信息
|
||||||
|
/// </summary>
|
||||||
|
private class DLLAddonEntry
|
||||||
|
{
|
||||||
|
public required AddonLoadContext Context { get; set; }
|
||||||
|
public required Assembly Assembly { get; set; }
|
||||||
|
public required DateTime LastWriteTimeUtc { get; set; }
|
||||||
|
public List<AddonSubEntry> Addons { get; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 记录加载项信息
|
||||||
|
/// </summary>
|
||||||
|
private class AddonSubEntry
|
||||||
|
{
|
||||||
|
public required IAddon Instance { get; set; }
|
||||||
|
public required WeakReference<IAddon> Weak { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user