FunGame-Core/Library/Common/Addon/Example/ExampleGameModule.cs
milimoe 3db586cab2
诸多更新和问题修复 (#97)
* 添加 OpenFactory,可以动态扩展技能和物品

* 修改 Effect 的反序列化解析;增加对闪避/暴击判定的先前事件编程接口

* 补充魔法伤害的判定

* 装备系统优化;角色的复制问题修复

* 添加物品品质;更新装备饰品替换机制;添加第一滴血、团队模式

* 添加技能选取

* 添加团队死斗模式
2024-11-04 09:30:26 +08:00

351 lines
14 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Milimoe.FunGame.Core.Api.Transmittal;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
{
/// <summary>
/// 建议使用一个类来存储常量,方便重用
/// </summary>
public class ExampleGameModuleConstant
{
public static GameModuleDepend GameModuleDepend => _depends;
public const string ExampleGameModule = "fungame.example.gamemodule";
public const string ExampleMap = "fungame.example.gamemap";
public const string ExampleCharacter = "fungame.example.character";
public const string ExampleSkill = "fungame.example.skill";
public const string ExampleItem = "fungame.example.item";
private static readonly string[] Maps = [ExampleMap];
private static readonly string[] Characters = [ExampleCharacter];
private static readonly string[] Skills = [ExampleSkill];
private static readonly string[] Items = [ExampleItem];
private static readonly GameModuleDepend _depends = new(Maps, Characters, Skills, Items);
}
/// <summary>
/// 模组:必须继承基类:<see cref="GameModule"/><para/>
/// 继承事件接口并实现其方法来使模组生效。例如继承:<seealso cref="IGamingUpdateInfoEvent"/><para/>
/// </summary>
public class ExampleGameModule : GameModule, IGamingUpdateInfoEvent
{
public override string Name => ExampleGameModuleConstant.ExampleGameModule;
public override string Description => "My First GameModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override string DefaultMap => GameModuleDepend.MapsDepend.Length > 0 ? GameModuleDepend.MapsDepend[0] : "";
public override GameModuleDepend GameModuleDepend => ExampleGameModuleConstant.GameModuleDepend;
public override RoomType RoomType => RoomType.Mix;
public override int MaxUsers => 8;
public override bool HideMain => false;
public ExampleGameModule()
{
// 构造函数中可以指定模组连接到哪个模组服务器。
// 如果你使用自己的,保持默认即可:删除下面两行,并将模组服务器的名称设置为与此模组的名称相同
IsConnectToOtherServerModule = true;
AssociatedServerModuleName = ExampleGameModuleConstant.ExampleGameModule;
}
protected Gaming? Instance;
protected Room room = General.HallInstance;
protected List<User> users = [];
protected Dictionary<string, Character> characters = [];
public override void StartGame(Gaming instance, params object[] args)
{
Instance = instance;
// 取得房间玩家等信息
GamingEventArgs eventArgs = instance.EventArgs;
room = eventArgs.Room;
users = eventArgs.Users;
// 客户端做好准备后,等待服务器的消息通知,下面可以根据需求进一步处理
}
public override void StartUI(params object[] args)
{
// 如果你是一个WPF或者Winform项目可以在这里启动你的界面
// 如果没有,则不需要重写此方法
}
public void GamingUpdateInfoEvent(object sender, GamingEventArgs e, Dictionary<string, object> data)
{
// 在下方的Server示例中服务器发来的data中包含check字符串因此客户端要主动发起确认连接的请求。
if (data.ContainsKey("info_type"))
{
// 反序列化得到指定key的值
string info_type = DataRequest.GetDictionaryJsonObject<string>(data, "info_type") ?? "";
if (info_type == "check")
{
Guid token = DataRequest.GetDictionaryJsonObject<Guid>(data, "connect_token");
// 发起连接确认请求
DataRequest request = Controller.NewDataRequest(GamingType.Connect);
// 传递参数
request.AddRequestData("username", ((Gaming)sender).CurrentUser.Username);
request.AddRequestData("connect_token", token);
if (request.SendRequest() == RequestResult.Success)
{
string msg = request.GetResult<string>("msg") ?? "";
Controller.WriteLine(msg);
}
request.Dispose();
}
}
}
}
/// <summary>
/// 模组服务器:必须继承基类:<see cref="GameModuleServer"/><para/>
/// 使用switch块分类处理 <see cref="GamingType"/>。
/// </summary>
public class ExampleGameModuleServer : GameModuleServer
{
/// <summary>
/// 注意:服务器模组的名称必须和模组名称相同。除非你指定了 <see cref="GameModule.IsConnectToOtherServerModule"/> 和 <see cref="GameModule.AssociatedServerModuleName"/>
/// </summary>
public override string Name => ExampleGameModuleConstant.ExampleGameModule;
public override string Description => "My First GameModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override string DefaultMap => GameModuleDepend.MapsDepend.Length > 0 ? GameModuleDepend.MapsDepend.First() : "";
public override GameModuleDepend GameModuleDepend => ExampleGameModuleConstant.GameModuleDepend;
protected Room Room = General.HallInstance;
protected List<User> Users = [];
protected IServerModel? RoomMaster;
protected Dictionary<string, IServerModel> All = [];
public override bool StartServer(string GameModule, Room Room, List<User> Users, IServerModel RoomMasterServerModel, Dictionary<string, IServerModel> ServerModels, params object[] Args)
{
// 将参数转为本地属性
this.Room = Room;
this.Users = Users;
RoomMaster = RoomMasterServerModel;
All = ServerModels;
// 创建一个线程执行Test()
TaskUtility.NewTask(Test).OnError(Controller.Error);
return true;
}
private readonly List<User> ConnectedUser = [];
private readonly Dictionary<string, Dictionary<string, object>> UserData = [];
private async Task Test()
{
Controller.WriteLine("欢迎各位玩家进入房间 " + Room.Roomid + " 。");
// 通常,我们可以对客户端的连接状态进行确认,此方法展示如何确认客户端的连接
// 有两种确认的方式1是服务器主动确认2是客户端发起确认
// 在FunGame项目中建议永远使用客户端主动发起请求因为服务器主动发起的实现难度较高
// 下面的演示基于综合的两种情况:服务器主动发送通知,客户端收到后,发起确认
// UpdateInfo是一个灵活的类型。如果发送check字符串意味着服务器要求客户端发送确认
Dictionary<string, object> data = [];
data.Add("info_type", "check");
// 进阶示例传递一个token让客户端返回
Guid token = Guid.NewGuid();
data.Add("connect_token", token);
// 我们保存到字典UserData中这样可以方便跨方法检查变量
foreach (string username in Users.Select(u => u.Username).Distinct())
{
if (UserData.TryGetValue(username, out Dictionary<string, object>? value))
{
value.Add("connect_token", token);
}
else
{
UserData.Add(username, []);
UserData[username].Add("connect_token", token);
}
}
await SendGamingMessage(All.Values, GamingType.UpdateInfo, data);
// 新建一个线程等待所有玩家确认
while (true)
{
if (ConnectedUser.Count == Users.Count) break;
// 每200ms确认一次不需要太频繁
await Task.Delay(200);
}
Controller.WriteLine("所有玩家都已经连接。");
}
public override async Task<Dictionary<string, object>> GamingMessageHandler(string username, GamingType type, Dictionary<string, object> data)
{
Dictionary<string, object> result = [];
switch (type)
{
case GamingType.Connect:
// 编写处理“连接”命令的逻辑
// 如果需要处理客户端传递的参数获取与客户端约定好的参数key对应的值
string un = NetworkUtility.JsonDeserializeFromDictionary<string>(data, "username") ?? "";
Guid token = NetworkUtility.JsonDeserializeFromDictionary<Guid>(data, "connect_token");
if (un == username && UserData.TryGetValue(username, out Dictionary<string, object>? value) && value != null && (value["connect_token"]?.Equals(token) ?? false))
{
ConnectedUser.Add(Users.Where(u => u.Username == username).First());
Controller.WriteLine(username + " 已经连接。");
}
else Controller.WriteLine(username + " 确认连接失败!");
break;
default:
await Task.Delay(1);
break;
}
return result;
}
}
/// <summary>
/// 地图:必须继承基类:<see cref="GameMap"/><para/>
/// </summary>
public class ExampleGameMap : GameMap
{
public override string Name => ExampleGameModuleConstant.ExampleMap;
public override string Description => "My First GameMap";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override float Length => 12.0f;
public override float Width => 12.0f;
public override float Height => 6.0f;
public override float Size => 4.0f;
}
/// <summary>
/// 角色:必须继承基类:<see cref="CharacterModule"/><para/>
/// </summary>
public class ExampleCharacterModule : CharacterModule
{
public override string Name => ExampleGameModuleConstant.ExampleCharacter;
public override string Description => "My First CharacterModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary<string, Character> Characters
{
get
{
Dictionary<string, Character> dict = [];
// 构建一个你想要的角色
Character c = Factory.GetCharacter();
c.Name = "Oshima";
c.FirstName = "Shiya";
c.NickName = "OSM";
c.MagicType = MagicType.PurityNatural;
c.InitialHP = 30;
c.InitialSTR = 20;
c.InitialAGI = 10;
c.InitialINT = 5;
c.InitialATK = 100;
c.InitialDEF = 10;
dict.Add(c.Name, c);
return dict;
}
}
}
/// <summary>
/// 技能:必须继承基类:<see cref="SkillModule"/><para/>
/// </summary>
public class ExampleSkillModule : SkillModule
{
public override string Name => ExampleGameModuleConstant.ExampleSkill;
public override string Description => "My First SkillModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary<string, Skill> Skills
{
get
{
Dictionary<string, Skill> dict = [];
// 技能应该在GameModule中新建类继承Skill实现再自行构造。
return dict;
}
}
protected override Factory.EntityFactoryDelegate<Skill> SkillFactory()
{
// 注册一个工厂根据id和name返回一个你继承实现了的类对象。
return (id, name, args) =>
{
return null;
};
}
protected override Factory.EntityFactoryDelegate<Effect> EffectFactory()
{
return (id, name, args) =>
{
return null;
};
}
}
/// <summary>
/// 物品:必须继承基类:<see cref="ItemModule"/><para/>
/// </summary>
public class ExampleItemModule : ItemModule
{
public override string Name => ExampleGameModuleConstant.ExampleItem;
public override string Description => "My First ItemModule";
public override string Version => "1.0.0";
public override string Author => "FunGamer";
public override Dictionary<string, Item> Items
{
get
{
Dictionary<string, Item> dict = [];
// 物品应该在GameModule中新建类继承Item实现再自行构造。
return dict;
}
}
protected override Factory.EntityFactoryDelegate<Item> ItemFactory()
{
// 注册一个工厂根据id和name返回一个你继承实现了的类对象。
return (id, name, args) =>
{
return null;
};
}
}
}