Compare commits

..

62 Commits

Author SHA1 Message Date
dcb1ffc23b
RoundRecord 优化 2025-12-03 21:09:58 +08:00
milimoe
080d546a97
词法修正和代码清理 (#143) 2025-11-23 02:18:24 +08:00
774e26240b
fix: 确保普通攻击的伤害类型正确 2025-11-16 14:46:43 +08:00
6ebeb53674
update workflow 2025-11-14 01:42:08 +08:00
c91829313d
.NET 10 2025-11-14 01:39:05 +08:00
ef3738d07e
不序列化 EquilibriumConstant 2025-10-31 19:47:49 +08:00
ad4a9a4346
插队机制优化;AI移动决策优化 2025-09-26 23:32:23 +08:00
milimoe
2c39f46dd9
支持战棋地图玩法 (#142) 2025-09-13 15:10:11 +08:00
09eea71cb6
更新地图选取范围格子的逻辑 2025-09-07 22:42:11 +08:00
030ef179e8
ISkill 添加实际可选取的目标数量;顺序表修复排序和硬直时间重复的问题 2025-09-06 19:34:20 +08:00
67f357a1cb
还原保护机制 2025-09-06 04:11:13 +08:00
607d36e23a
修复无法选择处于免疫状态队友的问题;修复顺序表排序错误的问题 2025-09-06 03:36:39 +08:00
fab341d001
修复爆发技的插队问题 2025-09-06 03:35:24 +08:00
46620aefd7
普攻添加选取目标的方法 2025-09-03 01:13:15 +08:00
13a6adb41b
移动不再计算硬直 2025-09-03 00:07:05 +08:00
ccf09b478e
错别字更改;GameMap添加新的移动方法 2025-09-02 00:59:20 +08:00
29bf7e4c3d
添加是否无视施法距离 2025-09-01 00:53:06 +08:00
e1cc31110b
添加了地图的移动规则 2025-08-29 19:37:57 +08:00
9693accdd1
在 GamingQueue 模块中添加地图相关 2025-08-29 01:34:35 +08:00
d0f6cb6c2e
地区显示优化 2025-08-12 01:10:42 +08:00
yeziuku
32f8dfa43d
fix: 列名错误 2025-08-05 10:03:44 +08:00
9c792ed781
添加社团邀请列表 2025-08-02 05:29:34 +08:00
f55fda4c86
添加了默认的房间标识 2025-07-30 23:23:41 +08:00
a181fb653c
完善市场相关类;表结构升级 2025-07-28 20:02:09 +08:00
bc2f180106
优化商店显示 2025-07-27 23:44:44 +08:00
482f281d36
商品刷新优化;文本优化 2025-07-26 02:35:55 +08:00
00429c2de0
传入 User 以便获取已购数量 2025-07-24 01:27:04 +08:00
742936e260
添加全局库存商店、商品限购数量、过期时间;区分商店开放时间和营业时间 2025-07-23 01:09:32 +08:00
b1bfcc5577
优化商店系统 2025-07-19 08:35:15 +08:00
79801ee594
优化物品状态展示;修复了角色构建器没有复制能量的问题 2025-07-18 00:01:49 +08:00
42abf38b27
添加 Web API 的 OnWebAPIStarted 事件 2025-07-16 00:02:25 +08:00
a3d3dc7df1
小数位显示优化;报价时间 BUG 修复 2025-07-11 00:57:01 +08:00
bda1124af1
加速系数和冷却缩减支持负数 2025-07-10 01:34:30 +08:00
86066ba0f0
添加遗漏的升级钩子 2025-07-08 23:51:33 +08:00
72d29cff5f
将一些非初始化的属性移出角色复制方法;OfferItems SQL 优化 2025-07-07 21:52:15 +08:00
8b3d4aa0d7
库存使用物品添加使用次数参数;添加了用户名引用和报价状态常量 2025-07-06 16:54:34 +08:00
d4aa6adf97
将锁定加入序列化器 2025-07-04 19:37:57 +08:00
160c691780
添加迟滞,区分减速 2025-07-04 01:40:14 +08:00
b59291be3f
添加易伤 2025-07-03 22:37:47 +08:00
c4ea6a7c90
优化代码;修复队友助攻另一个队友被击杀的问题 2025-07-02 00:07:09 +08:00
d163e00730
添加了任务需求指定 2025-06-30 22:46:20 +08:00
9f020247bb
调整实体类结构;探索类相关实体调整 2025-06-30 00:03:42 +08:00
425b46ac02
地图新增NPC和子区域属性 2025-06-29 18:04:38 +08:00
236c12a5f4
PluginConfig 支持 object 数组 2025-06-29 14:57:59 +08:00
2ed01700ef
在物品类中添加 AfterCopy 方法,并且为 Copy 添加了复制 Others 的参数;添加任务类的转换器 2025-06-28 02:57:02 +08:00
155b846aa5
活动、任务、活动中心优化 2025-06-28 01:27:46 +08:00
9b1a62c6bf
免疫状态重做,修改检定逻辑;优化击杀队友的反馈和一些冗余问题修复 2025-06-27 00:02:22 +08:00
b5a12ad18d
修复游戏结束偶现的崩溃问题;修复物品没有复制 Others 字典的问题;地区新增掉落物绑定 2025-06-26 01:09:48 +08:00
0a1bb0b2a5
不可选中时能给自己放技能 2025-06-25 00:04:31 +08:00
88d6d9a8c9
新增饭补队友和自杀;区分 AI 控制 2025-06-23 23:24:01 +08:00
7f2b6466e2
行动顺序表初始化优化,现在需要单独初始化队列 2025-06-22 05:12:09 +08:00
ed222e3e1b
普攻机制更改;魔抗优化 2025-06-21 05:15:52 +08:00
496f5d0675
冗余问题修复 2025-06-19 21:31:20 +08:00
milimoe
6bf3bb09a9
底层支持真实伤害 (#141) 2025-06-19 21:20:53 +08:00
milimoe
a2fcbce157
添加特效护盾、混合护盾值,优化类型护盾值的计算逻辑 (#140)
* 添加特效护盾、混合护盾值,优化类型护盾值的计算逻辑

* 附属特效不再强制隐藏
2025-06-18 21:19:52 +08:00
17914f1128
插件接口参数更改;任务计划新增错误回调;魔法类型名称优化;行动顺序表优化 2025-06-13 01:01:25 +08:00
984a7fd5a1
心跳和 log 优化 2025-06-04 01:05:53 +08:00
milimoe
5445ea141b
开启2.0-dev;Item和Skill、Effect添加局外使用物品技能的User参数 (#139) 2025-06-03 09:05:09 +08:00
milimoe
914f6c6b4a
技能、回合日志优化 (#138) 2025-05-30 01:17:34 +08:00
milimoe
aebd64e6e3
1.0.0 Release (#136) 2025-05-14 22:24:13 +08:00
82538b9d93
护盾抵消伤害与实际伤害分离 2025-05-13 07:50:31 +08:00
cb44cb5672
调整一些事件接入的位置;使用复苏道具复活不会回复满状态 2025-05-11 19:07:12 +08:00
91 changed files with 5787 additions and 1404 deletions

View File

@ -21,7 +21,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.x
dotnet-version: 10.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
@ -31,7 +31,7 @@ jobs:
- name: Prepare files for latest branch
run: |
mkdir -p latest
cp -r ./bin/Release/net9.0/FunGame.Core.dll ./bin/Release/net9.0/FunGame.Core.xml ./bin/Release/net9.0/FunGame.Core.deps.json ./latest/
cp -r ./bin/Release/net10.0/FunGame.Core.dll ./bin/Release/net10.0/FunGame.Core.xml ./bin/Release/net10.0/FunGame.Core.deps.json ./latest/
- name: Commit and push to latest
if: success()
run: |

View File

@ -59,6 +59,10 @@ namespace Milimoe.FunGame.Core.Api.Utility
if (runtime == FunGameInfo.FunGame.FunGame_Desktop)
{
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
foreach (GameMap map in loader.Maps.Values.ToList())
{
map.AfterLoad(loader, otherobjs);
}
AddonManager.LoadGameModules(loader.Modules, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
foreach (GameModule module in loader.Modules.Values.ToList())
{
@ -71,6 +75,10 @@ namespace Milimoe.FunGame.Core.Api.Utility
else if (runtime == FunGameInfo.FunGame.FunGame_Server)
{
AddonManager.LoadGameMaps(loader.Maps, otherobjs);
foreach (GameMap map in loader.Maps.Values.ToList())
{
map.AfterLoad(loader, otherobjs);
}
AddonManager.LoadGameModulesForServer(loader.ModuleServers, loader.Characters, loader.Skills, loader.Items, delegates, otherobjs);
foreach (GameModuleServer server in loader.ModuleServers.Values.ToList())
{

View File

@ -780,7 +780,9 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// <returns></returns>
public static double Round(double value, int digits)
{
return Math.Round(value, digits, MidpointRounding.AwayFromZero);
value = Math.Round(value, digits, MidpointRounding.AwayFromZero);
if (IsApproximatelyZero(value)) value = 0;
return value;
}
/// <summary>

View File

@ -0,0 +1,17 @@
namespace Milimoe.FunGame.Core.Api.Utility
{
public static class LINQExtension
{
public static IEnumerable<T> GetPage<T>(this IEnumerable<T> list, int showPage, int pageSize)
{
return [.. list.Skip((showPage - 1) * pageSize).Take(pageSize)];
}
public static int MaxPage<T>(this IEnumerable<T> list, int pageSize)
{
if (pageSize <= 0) pageSize = 1;
int page = (int)Math.Ceiling((double)list.Count() / pageSize);
return page > 0 ? page : 1;
}
}
}

View File

@ -110,7 +110,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
}
// 确保目录存在
ExistsDirectoryAndCreate(novelName);
DirectoryExistsAndCreate(novelName);
// 复制文件内容
string json = File.ReadAllText(path, General.DefaultEncoding);
@ -276,7 +276,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
/// <param name="novelName"></param>
/// <returns></returns>
public static bool ExistsDirectory(string novelName)
public static bool DirectoryExists(string novelName)
{
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
return Directory.Exists(dpath);
@ -287,7 +287,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
/// <param name="novelName"></param>
/// <returns></returns>
public static bool ExistsDirectoryAndCreate(string novelName)
public static bool DirectoryExistsAndCreate(string novelName)
{
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
bool result = Directory.Exists(dpath);
@ -304,7 +304,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// <param name="novelName"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static bool ExistsFile(string novelName, string fileName)
public static bool FileExists(string novelName, string fileName)
{
string dpath = $@"{AppDomain.CurrentDomain.BaseDirectory}{RootPath}/{novelName}";
string fpath = $@"{dpath}/{fileName}.json";

View File

@ -85,6 +85,18 @@ namespace Milimoe.FunGame.Core.Api.Utility
{
if (TryGetValue(key, out object? value) && value != null)
{
if (value is T typeValue)
{
return typeValue;
}
if (value is List<object> listValue)
{
return NetworkUtility.JsonDeserialize<T>(NetworkUtility.JsonSerialize(listValue));
}
if (value is Dictionary<object, object> dictValue)
{
return NetworkUtility.JsonDeserialize<T>(NetworkUtility.JsonSerialize(dictValue));
}
return NetworkUtility.JsonDeserialize<T>(value.ToString() ?? "");
}
return default;
@ -186,10 +198,10 @@ namespace Milimoe.FunGame.Core.Api.Utility
}
/// <summary>
/// Json数组反序列化的方法。不支持<see cref="object"/>数组
/// JSON 数组反序列化的方法,支持特定类型数组和混合类型数组(包括嵌套对象和数组)
/// </summary>
/// <param name="key"></param>
/// <param name="obj"></param>
/// <param name="key">字典的键</param>
/// <param name="obj">JSON 数组的枚举器</param>
private void AddValues(string key, JsonElement.ArrayEnumerator obj)
{
List<long> longList = [];
@ -197,34 +209,162 @@ namespace Milimoe.FunGame.Core.Api.Utility
List<decimal> decList = [];
List<string> strList = [];
List<bool> bolList = [];
foreach (JsonElement array_e in obj)
List<object> resultList = [];
// 标记是否为混合类型
bool isMixed = false;
// 记录第一个非 null 元素的类型
JsonValueKind? firstValueKind = null;
foreach (JsonElement arrayElement in obj)
{
if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetInt64(out long longValue))
switch (arrayElement.ValueKind)
{
longList.Add(longValue);
}
else if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetDouble(out double douValue))
{
douList.Add(douValue);
}
else if (array_e.ValueKind == JsonValueKind.Number && array_e.TryGetDecimal(out decimal decValue))
{
decList.Add(decValue);
}
else if (array_e.ValueKind == JsonValueKind.String)
{
strList.Add(array_e.GetString() ?? "");
}
else if (array_e.ValueKind == JsonValueKind.True || array_e.ValueKind == JsonValueKind.False)
{
bolList.Add(array_e.GetBoolean());
case JsonValueKind.Number when arrayElement.TryGetInt64(out long longValue):
longList.Add(longValue);
resultList.Add(longValue);
firstValueKind ??= JsonValueKind.Number;
if (firstValueKind != JsonValueKind.Number) isMixed = true;
break;
case JsonValueKind.Number when arrayElement.TryGetDouble(out double doubleValue):
douList.Add(doubleValue);
resultList.Add(doubleValue);
firstValueKind ??= JsonValueKind.Number;
if (firstValueKind != JsonValueKind.Number) isMixed = true;
break;
case JsonValueKind.Number when arrayElement.TryGetDecimal(out decimal decimalValue):
decList.Add(decimalValue);
resultList.Add(decimalValue);
firstValueKind ??= JsonValueKind.Number;
if (firstValueKind != JsonValueKind.Number) isMixed = true;
break;
case JsonValueKind.String:
string strValue = arrayElement.GetString() ?? "";
strList.Add(strValue);
resultList.Add(strValue);
firstValueKind ??= JsonValueKind.String;
if (firstValueKind != JsonValueKind.String) isMixed = true;
break;
case JsonValueKind.True:
case JsonValueKind.False:
bool boolValue = arrayElement.GetBoolean();
bolList.Add(boolValue);
resultList.Add(boolValue);
firstValueKind ??= arrayElement.ValueKind;
if (firstValueKind != arrayElement.ValueKind) isMixed = true;
break;
case JsonValueKind.Null:
break;
case JsonValueKind.Object:
Dictionary<string, object> objValue = ParseObject(arrayElement);
resultList.Add(objValue);
isMixed = true;
break;
case JsonValueKind.Array:
List<object> arrayValue = ParseArray(arrayElement);
resultList.Add(arrayValue);
isMixed = true;
break;
default:
isMixed = true;
break;
}
}
if (longList.Count > 0) base.Add(key, longList);
if (douList.Count > 0) base.Add(key, douList);
if (decList.Count > 0) base.Add(key, decList);
if (strList.Count > 0) base.Add(key, strList);
if (bolList.Count > 0) base.Add(key, bolList);
// 根据类型一致性选择存储的列表
if (resultList.Count > 0)
{
if (!isMixed)
{
// 所有元素类型一致,存储到对应的特定类型列表
switch (firstValueKind)
{
case JsonValueKind.Number when longList.Count == resultList.Count:
base.Add(key, longList);
break;
case JsonValueKind.Number when douList.Count == resultList.Count:
base.Add(key, douList);
break;
case JsonValueKind.Number when decList.Count == resultList.Count:
base.Add(key, decList);
break;
case JsonValueKind.String:
base.Add(key, strList);
break;
case JsonValueKind.True:
case JsonValueKind.False:
base.Add(key, bolList);
break;
default:
base.Add(key, resultList); // 包含 null 或未知情况
break;
}
}
else
{
// 混合类型或包含对象/数组,存储为 List<object>
base.Add(key, resultList);
}
}
}
/// <summary>
/// 递归解析 JSON 对象
/// </summary>
private Dictionary<string, object> ParseObject(JsonElement element)
{
Dictionary<string, object> dict = [];
foreach (JsonProperty property in element.EnumerateObject())
{
object? value = property.Value.ValueKind switch
{
JsonValueKind.Number when property.Value.TryGetInt64(out long longValue) => longValue,
JsonValueKind.Number when property.Value.TryGetDouble(out double doubleValue) => doubleValue,
JsonValueKind.Number when property.Value.TryGetDecimal(out decimal decimalValue) => decimalValue,
JsonValueKind.String => property.Value.GetString() ?? "",
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Object => ParseObject(property.Value),
JsonValueKind.Array => ParseArray(property.Value),
_ => null
};
if (value != null) dict.Add(property.Name, value);
}
return dict;
}
/// <summary>
/// 递归解析 JSON 数组
/// </summary>
private List<object> ParseArray(JsonElement element)
{
List<object> list = [];
foreach (JsonElement arrayElement in element.EnumerateArray())
{
object? value = arrayElement.ValueKind switch
{
JsonValueKind.Number when arrayElement.TryGetInt64(out long longValue) => longValue,
JsonValueKind.Number when arrayElement.TryGetDouble(out double doubleValue) => doubleValue,
JsonValueKind.Number when arrayElement.TryGetDecimal(out decimal decimalValue) => decimalValue,
JsonValueKind.String => arrayElement.GetString() ?? "",
JsonValueKind.True => true,
JsonValueKind.False => false,
JsonValueKind.Null => null,
JsonValueKind.Object => ParseObject(arrayElement),
JsonValueKind.Array => ParseArray(arrayElement),
_ => null
};
if (value != null) list.Add(value);
}
return list;
}
}
}

View File

@ -15,7 +15,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
/// <param name="single">单例对象</param>
/// <returns></returns>
public static bool IsExist(object single)
public static bool Exists(object single)
{
Type type = single.GetType();
string name = type.FullName ?? type.ToString();

View File

@ -35,17 +35,16 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// <param name="name"></param>
/// <param name="timeOfDay"></param>
/// <param name="action"></param>
public void AddTask(string name, TimeSpan timeOfDay, Action action)
/// <param name="error"></param>
public void AddTask(string name, TimeSpan timeOfDay, Action action, Action<Exception>? error = null)
{
lock (_lock)
using Lock.Scope scope = _lock.EnterScope();
ScheduledTask task = new(name, timeOfDay, action, error);
if (DateTime.Now > DateTime.Today.Add(timeOfDay))
{
ScheduledTask task = new(name, timeOfDay, action);
if (DateTime.Now > DateTime.Today.Add(timeOfDay))
{
task.LastRun = DateTime.Today.Add(timeOfDay);
}
_tasks.Add(task);
task.LastRun = DateTime.Today.Add(timeOfDay);
}
_tasks.Add(task);
}
/// <summary>
@ -55,19 +54,18 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// <param name="interval"></param>
/// <param name="action"></param>
/// <param name="startNow"></param>
public void AddRecurringTask(string name, TimeSpan interval, Action action, bool startNow = false)
/// <param name="error"></param>
public void AddRecurringTask(string name, TimeSpan interval, Action action, bool startNow = false, Action<Exception>? error = null)
{
lock (_lock)
using Lock.Scope scope = _lock.EnterScope();
DateTime now = DateTime.Now;
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0);
DateTime nextRun = startNow ? now : now.Add(interval);
RecurringTask recurringTask = new(name, interval, action, error)
{
DateTime now = DateTime.Now;
now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, 0);
DateTime nextRun = startNow ? now : now.Add(interval);
RecurringTask recurringTask = new(name, interval, action)
{
NextRun = nextRun
};
_recurringTasks.Add(recurringTask);
}
NextRun = nextRun
};
_recurringTasks.Add(recurringTask);
}
/// <summary>
@ -76,11 +74,9 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// <param name="name"></param>
public void RemoveTask(string name)
{
lock (_lock)
{
int removeTasks = _tasks.RemoveAll(t => t.Name == name);
int removeRecurringTasks = _recurringTasks.RemoveAll(t => t.Name == name);
}
using Lock.Scope scope = _lock.EnterScope();
int removeTasks = _tasks.RemoveAll(t => t.Name == name);
int removeRecurringTasks = _recurringTasks.RemoveAll(t => t.Name == name);
}
/// <summary>
@ -165,52 +161,54 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
private void CheckAndRunTasks()
{
lock (_lock)
using Lock.Scope scope = _lock.EnterScope();
DateTime now = DateTime.Now;
foreach (ScheduledTask task in _tasks)
{
DateTime now = DateTime.Now;
foreach (ScheduledTask task in _tasks)
if (!task.IsTodayRun)
{
if (!task.IsTodayRun)
if (now.TimeOfDay >= task.TimeOfDay && now.TimeOfDay < task.TimeOfDay.Add(TimeSpan.FromSeconds(10)))
{
if (now.TimeOfDay >= task.TimeOfDay && now.TimeOfDay < task.TimeOfDay.Add(TimeSpan.FromSeconds(10)))
{
task.LastRun = now;
Task.Run(() =>
{
try
{
task.Action();
}
catch (Exception ex)
{
task.Error = ex;
}
});
}
}
}
foreach (RecurringTask recurringTask in _recurringTasks)
{
if (now >= recurringTask.NextRun)
{
recurringTask.LastRun = now;
recurringTask.NextRun = recurringTask.NextRun.Add(recurringTask.Interval);
task.LastRun = now;
Task.Run(() =>
{
try
{
recurringTask.Action();
task.Action();
}
catch (Exception ex)
{
recurringTask.Error = ex;
task.Error = ex;
TXTHelper.AppendErrorLog(ex.ToString());
task.ErrorHandler?.Invoke(ex);
}
});
}
}
}
foreach (RecurringTask recurringTask in _recurringTasks)
{
if (now >= recurringTask.NextRun)
{
recurringTask.LastRun = now;
recurringTask.NextRun = recurringTask.NextRun.Add(recurringTask.Interval);
Task.Run(() =>
{
try
{
recurringTask.Action();
}
catch (Exception ex)
{
recurringTask.Error = ex;
TXTHelper.AppendErrorLog(ex.ToString());
recurringTask.ErrorHandler?.Invoke(ex);
}
});
}
}
}
}
}

View File

@ -1,5 +1,6 @@
using System.Runtime.InteropServices;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Library.Exception;
namespace Milimoe.FunGame.Core.Api.Utility
{
@ -50,7 +51,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
/// <param name="FileName">文件名缺省为FunGame.ini</param>
/// <returns>是否存在</returns>
public static bool ExistINIFile(string FileName = DefaultFileName) => File.Exists($@"{AppDomain.CurrentDomain.BaseDirectory}{FileName}");
public static bool INIFileExists(string FileName = DefaultFileName) => File.Exists($@"{AppDomain.CurrentDomain.BaseDirectory}{FileName}");
/// <summary>
/// 初始化ini模板文件
@ -180,7 +181,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
path = Path.Combine(path, filename);
}
else path = $@"{AppDomain.CurrentDomain.BaseDirectory}{filename}";
else path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, filename);
// 写入内容
StreamWriter writer = File.Exists(path) ? new(path, !overwrite, General.DefaultEncoding) : new(path, false, General.DefaultEncoding);
writer.WriteLine(content);
@ -200,5 +201,11 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// </summary>
/// <param name="msg"></param>
public static void AppendErrorLog(string msg) => WriteTXT(DateTimeUtility.GetDateTimeToString(TimeType.General) + ": " + msg + "\r\n", DateTimeUtility.GetDateTimeToString("yyyy-MM-dd") + ".log", "logs");
/// <summary>
/// 追加错误日志 默认写入logs文件夹下的当日日期.log文件
/// </summary>
/// <param name="e"></param>
public static void AppendErrorLog(Exception e) => AppendErrorLog(e.GetErrorInfo());
}
}

View File

@ -52,6 +52,14 @@ namespace Milimoe.FunGame.Core.Api.Utility
}
}
public void OnWebAPIStarted(params object[] objs)
{
Parallel.ForEach(Plugins.Values, plugin =>
{
plugin.OnWebAPIStarted(objs);
});
}
public void OnBeforeConnectEvent(object sender, ConnectEventArgs e)
{
Parallel.ForEach(Plugins.Values, plugin =>

325
Controller/AIController.cs Normal file
View File

@ -0,0 +1,325 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Controller
{
public class AIController(GamingQueue queue, GameMap map)
{
private readonly GamingQueue _queue = queue;
private readonly GameMap _map = map;
/// <summary>
/// AI的核心决策方法根据当前游戏状态为角色选择最佳行动。
/// </summary>
/// <param name="character">当前行动的AI角色。</param>
/// <param name="startGrid">角色的起始格子。</param>
/// <param name="allPossibleMoveGrids">从起始格子可达的所有移动格子(包括起始格子本身)。</param>
/// <param name="availableSkills">角色所有可用的技能已过滤CD和EP/MP。</param>
/// <param name="availableItems">角色所有可用的物品已过滤CD和EP/MP。</param>
/// <param name="allEnemysInGame">场上所有敌人。</param>
/// <param name="allTeammatesInGame">场上所有队友。</param>
/// <param name="selectableEnemys">场上能够选取的敌人。</param>
/// <param name="selectableTeammates">场上能够选取的队友。</param>
/// <returns>包含最佳行动的AIDecision对象。</returns>
public async Task<AIDecision> DecideAIActionAsync(Character character, Grid startGrid, List<Grid> allPossibleMoveGrids,
List<Skill> availableSkills, List<Item> availableItems, List<Character> allEnemysInGame, List<Character> allTeammatesInGame,
List<Character> selectableEnemys, List<Character> selectableTeammates)
{
// 初始化一个默认的“结束回合”决策作为基准
AIDecision bestDecision = new()
{
ActionType = CharacterActionType.EndTurn,
TargetMoveGrid = startGrid,
Targets = [],
Score = -1000.0
};
// 遍历所有可能的移动目标格子 (包括起始格子本身)
foreach (Grid potentialMoveGrid in allPossibleMoveGrids)
{
// 计算移动到这个格子的代价(曼哈顿距离)
int moveDistance = GameMap.CalculateManhattanDistance(startGrid, potentialMoveGrid);
double movePenalty = moveDistance * 0.5; // 每移动一步扣0.5分
if (CanCharacterNormalAttack(character))
{
// 计算普通攻击的可达格子
List<Grid> normalAttackReachableGrids = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
List<Character> normalAttackReachableEnemys = [.. allEnemysInGame
.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List<Character> normalAttackReachableTeammates = [.. allTeammatesInGame
.Where(c => normalAttackReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
if (normalAttackReachableEnemys.Count > 0)
{
// 将筛选后的目标列表传递给 SelectTargets
List<Character> targets = SelectTargets(character, character.NormalAttack, normalAttackReachableEnemys, normalAttackReachableTeammates);
if (targets.Count > 0)
{
double currentScore = EvaluateNormalAttack(character, targets) - movePenalty;
if (currentScore > bestDecision.Score)
{
bestDecision = new AIDecision
{
ActionType = CharacterActionType.NormalAttack,
TargetMoveGrid = potentialMoveGrid,
SkillToUse = character.NormalAttack,
Targets = targets,
Score = currentScore
};
}
}
}
}
foreach (Skill skill in availableSkills)
{
if (CanCharacterUseSkill(character) && _queue.CheckCanCast(character, skill, out double cost))
{
// 计算当前技能的可达格子
List<Grid> skillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true);
List<Character> skillReachableEnemys = [.. allEnemysInGame
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List<Character> skillReachableTeammates = [.. allTeammatesInGame
.Where(c => skillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标(敌人或队友,取决于技能类型)
if (skillReachableEnemys.Count > 0 || skillReachableTeammates.Count > 0)
{
// 将筛选后的目标列表传递给 SelectTargets
List<Character> targets = SelectTargets(character, skill, skillReachableEnemys, skillReachableTeammates);
if (targets.Count > 0)
{
double currentScore = EvaluateSkill(character, skill, targets, cost) - movePenalty;
if (currentScore > bestDecision.Score)
{
bestDecision = new AIDecision
{
ActionType = CharacterActionType.PreCastSkill,
TargetMoveGrid = potentialMoveGrid,
SkillToUse = skill,
Targets = targets,
Score = currentScore
};
}
}
}
}
}
foreach (Item item in availableItems)
{
if (item.Skills.Active != null && CanCharacterUseItem(character, item) && _queue.CheckCanCast(character, item.Skills.Active, out double cost))
{
Skill itemSkill = item.Skills.Active;
// 计算当前物品技能的可达格子
List<Grid> itemSkillReachableGrids = _map.GetGridsByRange(potentialMoveGrid, itemSkill.CastRange, true);
List<Character> itemSkillReachableEnemys = [.. allEnemysInGame
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
List<Character> itemSkillReachableTeammates = [.. allTeammatesInGame
.Where(c => itemSkillReachableGrids.SelectMany(g => g.Characters).Contains(c) && selectableTeammates.Contains(c))
.Distinct()];
// 检查是否有可用的目标
if (itemSkillReachableEnemys.Count > 0 || itemSkillReachableTeammates.Count > 0)
{
// 将筛选后的目标列表传递给 SelectTargets
List<Character> targetsForItem = SelectTargets(character, itemSkill, itemSkillReachableEnemys, itemSkillReachableTeammates);
if (targetsForItem.Count > 0)
{
double currentScore = EvaluateItem(character, item, targetsForItem, cost) - movePenalty;
if (currentScore > bestDecision.Score)
{
bestDecision = new AIDecision
{
ActionType = CharacterActionType.UseItem,
TargetMoveGrid = potentialMoveGrid,
ItemToUse = item,
SkillToUse = itemSkill,
Targets = targetsForItem,
Score = currentScore
};
}
}
}
}
}
// 如果从该格子没有更好的行动,但移动本身有价值
// 只有当当前最佳决策是“结束回合”或分数很低时,才考虑纯粹的移动。
if (potentialMoveGrid != startGrid && bestDecision.Score < 0) // 如果当前最佳决策是负分(即什么都不做)
{
double pureMoveScore = -movePenalty; // 移动本身有代价
// 为纯粹移动逻辑重新计算综合可达敌人列表
List<Grid> tempAttackGridsForPureMove = _map.GetGridsByRange(potentialMoveGrid, character.ATR, true);
List<Grid> tempCastGridsForPureMove = [];
foreach (Skill skill in availableSkills)
{
tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, skill.CastRange, true));
}
foreach (Item item in availableItems)
{
if (item.Skills.Active != null)
{
tempCastGridsForPureMove.AddRange(_map.GetGridsByRange(potentialMoveGrid, item.Skills.Active.CastRange, true));
}
}
List<Grid> tempAllReachableGridsForPureMove = [.. tempAttackGridsForPureMove.Union(tempCastGridsForPureMove).Distinct()];
List<Character> tempCurrentReachableEnemysForPureMove = [.. allEnemysInGame
.Where(c => tempAllReachableGridsForPureMove.SelectMany(g => g.Characters).Contains(c) && !c.IsUnselectable && selectableEnemys.Contains(c))
.Distinct()];
// 如果当前位置无法攻击任何敌人,但地图上还有敌人,尝试向最近的敌人移动
if (tempCurrentReachableEnemysForPureMove.Count == 0 && allEnemysInGame.Count > 0) // 使用新计算的列表
{
Character? target = allEnemysInGame
.OrderBy(e => GameMap.CalculateManhattanDistance(potentialMoveGrid, _map.GetCharacterCurrentGrid(e)!))
.FirstOrDefault();
if (target != null)
{
Grid? nearestEnemyGrid = _map.GetCharacterCurrentGrid(target);
if (nearestEnemyGrid != null)
{
// 奖励靠近敌人
pureMoveScore += (10 - GameMap.CalculateManhattanDistance(potentialMoveGrid, nearestEnemyGrid)) * 0.1;
}
}
}
// 如果纯粹移动比当前最佳(什么都不做)更好
if (pureMoveScore > bestDecision.Score)
{
bestDecision = new AIDecision
{
ActionType = CharacterActionType.Move,
TargetMoveGrid = potentialMoveGrid,
Targets = [],
Score = pureMoveScore,
IsPureMove = true
};
}
}
}
return await Task.FromResult(bestDecision);
}
// --- AI 决策辅助方法 ---
// 检查角色是否能进行普通攻击(基于状态)
private static bool CanCharacterNormalAttack(Character character)
{
return character.CharacterState != CharacterState.NotActionable &&
character.CharacterState != CharacterState.ActionRestricted &&
character.CharacterState != CharacterState.BattleRestricted &&
character.CharacterState != CharacterState.AttackRestricted;
}
// 检查角色是否能使用某个技能(基于状态)
private static bool CanCharacterUseSkill(Character character)
{
return character.CharacterState != CharacterState.NotActionable &&
character.CharacterState != CharacterState.ActionRestricted &&
character.CharacterState != CharacterState.BattleRestricted &&
character.CharacterState != CharacterState.SkillRestricted;
}
// 检查角色是否能使用某个物品(基于状态)
private static bool CanCharacterUseItem(Character character, Item item)
{
return character.CharacterState != CharacterState.NotActionable &&
(character.CharacterState != CharacterState.ActionRestricted || item.ItemType == ItemType.Consumable) && // 行动受限只能用消耗品
character.CharacterState != CharacterState.BattleRestricted;
}
/// <summary>
/// 选择技能的最佳目标
/// </summary>
/// <param name="character"></param>
/// <param name="skill"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
private static List<Character> SelectTargets(Character character, ISkill skill, List<Character> enemys, List<Character> teammates)
{
List<Character> targets = skill.GetSelectableTargets(character, enemys, teammates);
int count = skill.RealCanSelectTargetCount(enemys, teammates);
return [.. targets.OrderBy(o => Random.Shared.Next()).Take(count)];
}
/// <summary>
/// 评估普通攻击的价值
/// </summary>
/// <param name="character"></param>
/// <param name="targets"></param>
/// <returns></returns>
private static double EvaluateNormalAttack(Character character, List<Character> targets)
{
double score = 0;
foreach (Character target in targets)
{
double damage = character.NormalAttack.Damage * (1 - target.PDR);
score += damage;
if (target.HP <= damage) score += 100;
}
return score;
}
/// <summary>
/// 评估技能的价值
/// </summary>
/// <param name="character"></param>
/// <param name="skill"></param>
/// <param name="targets"></param>
/// <param name="cost"></param>
/// <returns></returns>
private static double EvaluateSkill(Character character, Skill skill, List<Character> targets, double cost)
{
double score = 0;
score += targets.Sum(t => CalculateTargetValue(t, skill));
//score -= cost * 5;
//score -= skill.RealCD * 2;
//score -= skill.HardnessTime * 2;
return score;
}
/// <summary>
/// 评估物品的价值
/// </summary>
/// <param name="character"></param>
/// <param name="item"></param>
/// <param name="targets"></param>
/// <param name="cost"></param>
/// <returns></returns>
private static double EvaluateItem(Character character, Item item, List<Character> targets, double cost)
{
double score = Random.Shared.Next(1000);
return score;
}
/// <summary>
/// 辅助函数:计算单个目标在某个技能下的价值
/// </summary>
/// <param name="target"></param>
/// <param name="skill"></param>
/// <returns></returns>
private static double CalculateTargetValue(Character target, ISkill skill)
{
double value = Random.Shared.Next(1000);
return value;
}
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Interface.Entity;
using System.Text.Json.Serialization;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
@ -29,6 +30,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 实体所用的游戏平衡常数
/// </summary>
[JsonIgnore]
public EquilibriumConstant GameplayEquilibriumConstant { get; set; } = General.GameplayEquilibriumConstant;
/// <summary>

View File

@ -77,28 +77,28 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取对 <paramref name="enemy"/> 造成伤害的最后时间
/// </summary>
/// <param name="enemy"></param>
/// <returns>-1 意味着没有时间</returns>
/// <returns><see cref="double.MinValue"/> 意味着没有时间</returns>
public double GetLastTime(Character enemy)
{
if (DamageLastTime.TryGetValue(enemy, out double time))
{
return time;
}
return -1;
return double.MinValue;
}
/// <summary>
/// 获取对某角色友方非伤害辅助的最后时间
/// </summary>
/// <param name="character"></param>
/// <returns>-1 意味着没有时间</returns>
/// <returns><see cref="double.MinValue"/> 意味着没有时间</returns>
public double GetNotDamageAssistLastTime(Character character)
{
if (NotDamageAssistLastTime.TryGetValue(character, out double time))
{
return time;
}
return -1;
return double.MinValue;
}
}
}

View File

@ -129,9 +129,13 @@ namespace Milimoe.FunGame.Core.Entity
}
set
{
int past = _Level;
_Level = Math.Min(Math.Max(1, value), GameplayEquilibriumConstant.MaxLevel);
OnAttributeChanged();
Recovery();
if (past != _Level)
{
OnAttributeChanged();
Recovery();
}
}
}
@ -215,7 +219,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 最大生命值 = 基础生命值 + 额外生命值 + 额外生命值2 + 额外生命值3
/// </summary>
public double MaxHP => BaseHP + ExHP + ExHP2 + ExHP3;
public double MaxHP => Math.Max(1, BaseHP + ExHP + ExHP2 + ExHP3);
/// <summary>
/// 当前生命值 [ 战斗相关 ]
@ -234,6 +238,12 @@ namespace Milimoe.FunGame.Core.Entity
}
}
/// <summary>
/// 是否有魔法值 [ 初始设定 ]
/// </summary>
[InitRequired]
public bool HasMP { get; set; } = true;
/// <summary>
/// 初始魔法值 [ 初始设定 ]
/// </summary>
@ -268,7 +278,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 最大魔法值 = 基础魔法值 + 额外魔法值 + 额外魔法值2 + 额外魔法值3
/// </summary>
public double MaxMP => BaseMP + ExMP + ExMP2 + ExMP3;
public double MaxMP => Math.Max(1, BaseMP + ExMP + ExMP2 + ExMP3);
/// <summary>
/// 当前魔法值 [ 战斗相关 ]
@ -662,16 +672,19 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 力量成长值(+BaseSTR/Lv)
/// </summary>
[InitOptional]
public double STRGrowth { get; set; } = 0;
/// <summary>
/// 敏捷成长值(+BaseAGI/Lv)
/// </summary>
[InitOptional]
public double AGIGrowth { get; set; } = 0;
/// <summary>
/// 智力成长值(+BaseINT/Lv)
/// </summary>
[InitOptional]
public double INTGrowth { get; set; } = 0;
/// <summary>
@ -683,7 +696,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 行动速度 = [ 与初始设定相关 ][ 与敏捷相关 ] + 额外行动速度
/// </summary>
public double SPD => InitialSPD + AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
public double SPD => Math.Max(0, InitialSPD + AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD);
/// <summary>
/// 额外行动速度 [ 与技能和物品相关 ]
@ -715,7 +728,7 @@ namespace Milimoe.FunGame.Core.Entity
get
{
double value = INT * GameplayEquilibriumConstant.INTtoAccelerationCoefficientMultiplier + ExAccelerationCoefficient;
return Calculation.PercentageCheck(value);
return Math.Min(1, value);
}
}
@ -732,7 +745,7 @@ namespace Milimoe.FunGame.Core.Entity
get
{
double value = INT * GameplayEquilibriumConstant.INTtoCDRMultiplier + ExCDR;
return Calculation.PercentageCheck(value);
return Math.Min(1, value);
}
}
@ -742,10 +755,68 @@ namespace Milimoe.FunGame.Core.Entity
public double ExCDR { get; set; } = 0;
/// <summary>
/// 攻击距离 [ 与技能和物品相关 ] [ 单位:格 ]
/// 攻击距离 [ 与武器相关 ] [ 单位:格(半径) ]
/// </summary>
[InitOptional]
public double ATR { get; set; } = 1;
public int ATR
{
get
{
int baseATR = 1;
if (EquipSlot.Weapon != null)
{
baseATR = EquipSlot.Weapon.WeaponType switch
{
WeaponType.OneHandedSword => GameplayEquilibriumConstant.OneHandedSwordAttackRange,
WeaponType.TwoHandedSword => GameplayEquilibriumConstant.TwoHandedSwordAttackRange,
WeaponType.Bow => GameplayEquilibriumConstant.BowAttackRange,
WeaponType.Pistol => GameplayEquilibriumConstant.PistolAttackRange,
WeaponType.Rifle => GameplayEquilibriumConstant.RifleAttackRange,
WeaponType.DualDaggers => GameplayEquilibriumConstant.DualDaggersAttackRange,
WeaponType.Talisman => GameplayEquilibriumConstant.TalismanAttackRange,
WeaponType.Staff => GameplayEquilibriumConstant.StaffAttackRange,
WeaponType.Polearm => GameplayEquilibriumConstant.PolearmAttackRange,
WeaponType.Gauntlet => GameplayEquilibriumConstant.GauntletAttackRange,
WeaponType.HiddenWeapon => GameplayEquilibriumConstant.HiddenWeaponAttackRange,
_ => baseATR
};
}
return Math.Max(1, baseATR + ExATR);
}
}
/// <summary>
/// 额外攻击距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
/// </summary>
public int ExATR { get; set; } = 0;
/// <summary>
/// 行动力/可移动距离 [ 与第一定位相关 ] [ 单位:格(半径) ]
/// </summary>
public int MOV
{
get
{
int baseMOV = 3;
if (EquipSlot.Weapon != null)
{
baseMOV = FirstRoleType switch
{
RoleType.Core => GameplayEquilibriumConstant.RoleMOV_Core,
RoleType.Vanguard => GameplayEquilibriumConstant.RoleMOV_Vanguard,
RoleType.Guardian => GameplayEquilibriumConstant.RoleMOV_Guardian,
RoleType.Support => GameplayEquilibriumConstant.RoleMOV_Support,
RoleType.Medic => GameplayEquilibriumConstant.RoleMOV_Medic,
_ => baseMOV
};
}
return Math.Max(1, baseMOV + ExMOV);
}
}
/// <summary>
/// 行动力/可移动距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
/// </summary>
public int ExMOV { get; set; } = 0;
/// <summary>
/// 暴击率(%) = [ 与敏捷相关 ] + 额外暴击率(%)
@ -900,8 +971,8 @@ namespace Milimoe.FunGame.Core.Entity
{
if (time > 0)
{
HP = Math.Min(MaxHP, HP + HR * time);
MP = Math.Min(MaxMP, MP + MR * time);
HP += HR * time;
MP += MR * time;
if (EP != -1) this.EP = EP;
}
}
@ -915,10 +986,16 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="pastMaxMP"></param>
public void Recovery(double pastHP, double pastMP, double pastMaxHP, double pastMaxMP)
{
double pHP = pastHP / pastMaxHP;
double pMP = pastMP / pastMaxMP;
HP = MaxHP * pHP;
MP = MaxMP * pMP;
if (pastHP > 0 && pastMaxHP > 0)
{
double pHP = pastHP / pastMaxHP;
HP = MaxHP * pHP;
}
if (pastMP > 0 && pastMaxMP > 0)
{
double pMP = pastMP / pastMaxMP;
MP = MaxMP * pMP;
}
}
/// <summary>
@ -1147,6 +1224,32 @@ namespace Milimoe.FunGame.Core.Entity
return result;
}
/// <summary>
/// 设置角色等级,并默认完全回复状态
/// </summary>
/// <param name="level">新的等级</param>
/// <param name="recovery">false 为按百分比回复</param>
public void SetLevel(int level, bool recovery = true)
{
if (!recovery)
{
double pastHP = HP;
double pastMP = MP;
double pastMaxHP = MaxHP;
double pastMaxMP = MaxMP;
int pastLevel = Level;
Level = level;
if (pastLevel != Level)
{
Recovery(pastHP, pastMP, pastMaxHP, pastMaxMP);
}
}
else
{
Level = level;
}
}
/// <summary>
/// 角色升级
/// </summary>
@ -1154,6 +1257,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="checkLevelBreak"></param>
public void OnLevelUp(int level = 0, bool checkLevelBreak = true)
{
bool isUp = false;
int count = 0;
while (true)
{
@ -1177,6 +1281,7 @@ namespace Milimoe.FunGame.Core.Entity
{
EXP -= need;
Level++;
isUp = true;
OnAttributeChanged();
Recovery();
}
@ -1185,6 +1290,14 @@ namespace Milimoe.FunGame.Core.Entity
break;
}
}
if (isUp)
{
Effect[] effects = [.. Effects.Where(e => e.IsInEffect)];
foreach (Effect e in effects)
{
e.OnOwnerLevelUp(this, Level);
}
}
}
/// <summary>
@ -1213,6 +1326,7 @@ namespace Milimoe.FunGame.Core.Entity
{
effect.OnAttributeChanged(this);
}
NormalAttack.SetMagicType(null, null, null);
}
/// <summary>
@ -1332,7 +1446,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的详细信息
/// </summary>
/// <returns></returns>
public string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
public string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@ -1340,12 +1454,13 @@ namespace Milimoe.FunGame.Core.Entity
if (showEXP)
{
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}(突破进度:{LevelBreak + 1} / {GameplayEquilibriumConstant.LevelBreakList.Count}");
builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
builder.AppendLine($"经验值:{EXP:0.##}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
}
double exHP = ExHP + ExHP2 + ExHP3;
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.TotalMix > 0) shield.Add($"混合:{Shield.TotalMix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
@ -1353,8 +1468,8 @@ namespace Milimoe.FunGame.Core.Entity
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exDEF >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine(GetMagicResistanceInfo().Trim());
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
@ -1377,20 +1492,13 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"魔法消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastMPReduce * 100:0.##}%");
builder.AppendLine($"能量消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastEPReduce * 100:0.##}%");
if (CharacterState != CharacterState.Actionable)
if (showMapRelated)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (IsNeutral)
{
builder.AppendLine("角色是无敌的");
}
if (IsUnselectable)
{
builder.AppendLine("角色是不可选中的");
}
GetStatusInfo(builder);
builder.AppendLine("== 普通攻击 ==");
builder.Append(NormalAttack.ToString());
@ -1406,46 +1514,12 @@ namespace Milimoe.FunGame.Core.Entity
if (EquipSlot.Any())
{
builder.AppendLine("== 装备栏 ==");
if (EquipSlot.MagicCardPack != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.MagicCardPack.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.MagicCardPack) + "" + EquipSlot.MagicCardPack.Name);
builder.AppendLine(EquipSlot.MagicCardPack.Description);
}
if (EquipSlot.Weapon != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Weapon.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Weapon) + "" + EquipSlot.Weapon.Name);
builder.AppendLine(EquipSlot.Weapon.Description);
}
if (EquipSlot.Armor != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Armor.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Armor) + "" + EquipSlot.Armor.Name);
builder.AppendLine(EquipSlot.Armor.Description);
}
if (EquipSlot.Shoes != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Shoes.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Shoes) + "" + EquipSlot.Shoes.Name);
builder.AppendLine(EquipSlot.Shoes.Description);
}
if (EquipSlot.Accessory1 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory1.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory1) + "" + EquipSlot.Accessory1.Name);
builder.AppendLine(EquipSlot.Accessory1.Description);
}
if (EquipSlot.Accessory2 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory2.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory2) + "" + EquipSlot.Accessory2.Name);
builder.AppendLine(EquipSlot.Accessory2.Description);
}
builder.AppendLine(GetEquipSlotInfo().Trim());
}
if (Items.Count > 0)
{
builder.AppendLine("== 角色背包 ==");
foreach (Item item in Items)
{
builder.Append(item.ToString());
}
builder.AppendLine(GetBackpackItemsInfo().Trim());
}
Effect[] effects = [.. Effects.Where(e => e.ShowInStatusBar)];
@ -1465,7 +1539,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的简略信息
/// </summary>
/// <returns></returns>
public string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false)
public string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@ -1473,12 +1547,13 @@ namespace Milimoe.FunGame.Core.Entity
if (showEXP)
{
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}(突破进度:{LevelBreak + 1} / {GameplayEquilibriumConstant.LevelBreakList.Count}");
builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
builder.AppendLine($"经验值:{EXP:0.##}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
}
double exHP = ExHP + ExHP2 + ExHP3;
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.TotalMix > 0) shield.Add($"混合:{Shield.TotalMix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
@ -1486,8 +1561,8 @@ namespace Milimoe.FunGame.Core.Entity
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exDEF >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine(GetMagicResistanceInfo().Trim());
if (showBasicOnly)
{
builder.AppendLine($"核心属性:{PrimaryAttributeValue:0.##}{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
@ -1507,22 +1582,15 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
if (showMapRelated)
{
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (!showBasicOnly)
{
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
}
if (IsNeutral)
{
builder.AppendLine("角色是无敌的");
}
if (IsUnselectable)
{
builder.AppendLine("角色是不可选中的");
}
GetStatusInfo(builder);
if (Skills.Count > 0)
{
@ -1585,7 +1653,8 @@ namespace Milimoe.FunGame.Core.Entity
double exHP = ExHP + ExHP2 + ExHP3;
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.TotalMix > 0) shield.Add($"混合:{Shield.TotalMix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
@ -1594,20 +1663,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
builder.AppendLine($"核心属性:{PrimaryAttributeValue:0.##}" + (ExPrimaryAttributeValue != 0 ? $" [{BasePrimaryAttributeValue:0.##} {(ExPrimaryAttributeValue >= 0 ? "+" : "-")} {Math.Abs(ExPrimaryAttributeValue):0.##}]" : ""));
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
}
if (IsNeutral)
{
builder.AppendLine("角色是中立单位,处于无敌状态");
}
if (IsUnselectable)
{
builder.AppendLine("角色是不可选中的");
}
GetStatusInfo(builder);
builder.AppendLine($"硬直时间:{hardnessTimes:0.##}");
@ -1637,7 +1693,8 @@ namespace Milimoe.FunGame.Core.Entity
double exHP = ExHP + ExHP2 + ExHP3;
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.TotalMix > 0) shield.Add($"混合:{Shield.TotalMix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
@ -1667,20 +1724,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine((HP == 0 ? "[ 死亡 ] " : "") + (showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser()));
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
}
if (IsNeutral)
{
builder.AppendLine("角色是无敌的");
}
if (IsUnselectable)
{
builder.AppendLine("角色是不可选中的");
}
GetStatusInfo(builder);
builder.AppendLine("== 普通攻击 ==");
builder.Append(NormalAttack.ToString());
@ -1711,7 +1755,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取角色的物品信息
/// </summary>
/// <returns></returns>
public string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
public string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
@ -1719,12 +1763,13 @@ namespace Milimoe.FunGame.Core.Entity
if (showEXP)
{
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}(突破进度:{LevelBreak + 1} / {GameplayEquilibriumConstant.LevelBreakList.Count}");
builder.AppendLine($"经验值:{EXP}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
builder.AppendLine($"经验值:{EXP:0.##}{(Level != GameplayEquilibriumConstant.MaxLevel && GameplayEquilibriumConstant.EXPUpperLimit.TryGetValue(Level, out double need) ? " / " + need : "")}");
}
double exHP = ExHP + ExHP2 + ExHP3;
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagicial > 0) shield.Add($"魔法:{Shield.TotalMagicial:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.TotalMix > 0) shield.Add($"混合:{Shield.TotalMix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
@ -1732,8 +1777,8 @@ namespace Milimoe.FunGame.Core.Entity
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exDEF >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
builder.AppendLine(GetMagicResistanceInfo().Trim());
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"核心属性:{CharacterSet.GetPrimaryAttributeName(PrimaryAttribute)}");
@ -1756,53 +1801,155 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"魔法消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastMPReduce * 100:0.##}%");
builder.AppendLine($"能量消耗减少:{INT * GameplayEquilibriumConstant.INTtoCastEPReduce * 100:0.##}%");
if (showMapRelated)
{
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (EquipSlot.Any())
{
builder.AppendLine("== 装备栏 ==");
if (EquipSlot.MagicCardPack != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.MagicCardPack.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.MagicCardPack) + "" + EquipSlot.MagicCardPack.Name);
builder.AppendLine(EquipSlot.MagicCardPack.Description);
}
if (EquipSlot.Weapon != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Weapon.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Weapon) + "" + EquipSlot.Weapon.Name);
builder.AppendLine(EquipSlot.Weapon.Description);
}
if (EquipSlot.Armor != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Armor.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Armor) + "" + EquipSlot.Armor.Name);
builder.AppendLine(EquipSlot.Armor.Description);
}
if (EquipSlot.Shoes != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Shoes.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Shoes) + "" + EquipSlot.Shoes.Name);
builder.AppendLine(EquipSlot.Shoes.Description);
}
if (EquipSlot.Accessory1 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory1.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory1) + "" + EquipSlot.Accessory1.Name);
builder.AppendLine(EquipSlot.Accessory1.Description);
}
if (EquipSlot.Accessory2 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory2.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory2) + "" + EquipSlot.Accessory2.Name);
builder.AppendLine(EquipSlot.Accessory2.Description);
}
builder.AppendLine(GetEquipSlotInfo().Trim());
}
if (Items.Count > 0)
{
builder.AppendLine("== 角色背包 ==");
foreach (Item item in Items)
builder.AppendLine(GetBackpackItemsInfo().Trim());
}
return builder.ToString();
}
private void GetStatusInfo(StringBuilder builder)
{
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
}
if ((ImmuneType & ImmuneType.Physical) != ImmuneType.None)
{
builder.AppendLine("角色现在物理免疫");
}
if ((ImmuneType & ImmuneType.Magical) != ImmuneType.None)
{
builder.AppendLine("角色现在魔法免疫");
}
if ((ImmuneType & ImmuneType.Skilled) != ImmuneType.None)
{
builder.AppendLine("角色现在技能免疫");
}
if ((ImmuneType & ImmuneType.All) != ImmuneType.None)
{
builder.AppendLine("角色现在完全免疫");
}
if (IsNeutral)
{
builder.AppendLine("角色是无敌的");
}
if (IsUnselectable)
{
builder.AppendLine("角色是不可选中的");
}
}
/// <summary>
/// 获取角色装备栏信息
/// </summary>
/// <returns></returns>
public string GetEquipSlotInfo()
{
StringBuilder builder = new();
builder.AppendLine("== 装备栏 ==");
if (EquipSlot.MagicCardPack != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.MagicCardPack.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.MagicCardPack) + "" + EquipSlot.MagicCardPack.Name);
builder.AppendLine(EquipSlot.MagicCardPack.Description);
}
if (EquipSlot.Weapon != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Weapon.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Weapon) + "" + EquipSlot.Weapon.Name);
builder.AppendLine(EquipSlot.Weapon.Description);
}
if (EquipSlot.Armor != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Armor.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Armor) + "" + EquipSlot.Armor.Name);
builder.AppendLine(EquipSlot.Armor.Description);
}
if (EquipSlot.Shoes != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Shoes.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Shoes) + "" + EquipSlot.Shoes.Name);
builder.AppendLine(EquipSlot.Shoes.Description);
}
if (EquipSlot.Accessory1 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory1.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory1) + "" + EquipSlot.Accessory1.Name);
builder.AppendLine(EquipSlot.Accessory1.Description);
}
if (EquipSlot.Accessory2 != null)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(EquipSlot.Accessory2.QualityType)}]" + ItemSet.GetEquipSlotTypeName(EquipSlotType.Accessory2) + "" + EquipSlot.Accessory2.Name);
builder.AppendLine(EquipSlot.Accessory2.Description);
}
return builder.ToString();
}
/// <summary>
/// 获取角色背包信息
/// </summary>
/// <returns></returns>
public string GetBackpackItemsInfo()
{
StringBuilder builder = new();
builder.AppendLine("== 角色背包 ==");
foreach (Item item in Items)
{
builder.AppendLine($"[{ItemSet.GetQualityTypeName(item.QualityType)}]" + ItemSet.GetItemTypeName(item.ItemType) + "" + item.Name);
builder.AppendLine(item.Description);
if (item.Skills.Active != null)
{
builder.Append(item.ToString());
Skill skill = item.Skills.Active;
List<string> strings = [];
if (skill.RealMPCost > 0) strings.Add($"魔法消耗:{skill.RealMPCost}");
if (skill.RealEPCost > 0) strings.Add($"能量消耗:{skill.RealEPCost}");
if (skill.RealCD > 0) strings.Add($"冷却时间:{skill.RealCD}{(skill.CurrentCD > 0 ? $" {skill.CurrentCD} {GameplayEquilibriumConstant.InGameTime}" : "")}");
if (skill.RealHardnessTime > 0) strings.Add($"硬直时间:{skill.RealHardnessTime}");
builder.AppendLine($"技能【{skill.Name}】描述:{skill.Description.Trim()}{(strings.Count > 0 ? $"{string.Join("", strings)}" : "")}");
}
}
return builder.ToString();
}
/// <summary>
/// 获取魔法抗性信息
/// </summary>
/// <returns></returns>
public string GetMagicResistanceInfo()
{
StringBuilder builder = new();
if (GameplayEquilibriumConstant.UseMagicType.Count > 0)
{
foreach (MagicType magicType in GameplayEquilibriumConstant.UseMagicType)
{
builder.Append(CharacterSet.GetMagicResistanceName(magicType));
builder.AppendLine($"{Calculation.Round4Digits(MDF[magicType] * 100):0.##}%");
}
}
else builder.AppendLine($"魔法抗性:{Calculation.Round4Digits(MDF.Avg * 100):0.##}%(平均)");
return builder.ToString();
}
/// <summary>
/// 更新角色的状态,参见 <see cref="CharacterEffectStates"/>、<see cref="CharacterEffectTypes"/> 用法
/// </summary>
@ -1822,30 +1969,27 @@ namespace Milimoe.FunGame.Core.Entity
IsUnselectable = types.Any(type => type == EffectType.Unselectable);
IEnumerable<ImmuneType> immunes = CharacterImmuneTypes.Values.SelectMany(list => list);
// 判断角色的免疫状态
// 判断角色的免疫状态,需要注意的是 All 不会覆盖任何其他类型,因为它是一种独立的类型
bool isAllImmune = immunes.Any(type => type == ImmuneType.All);
bool isPhysicalImmune = immunes.Any(type => type == ImmuneType.Physical);
bool isMagicalImmune = immunes.Any(type => type == ImmuneType.Magical);
bool isSkilledImmune = immunes.Any(type => type == ImmuneType.Skilled);
ImmuneType = ImmuneType.None;
if (isAllImmune)
{
ImmuneType = ImmuneType.All;
ImmuneType |= ImmuneType.All;
}
else if (isPhysicalImmune)
if (isPhysicalImmune)
{
ImmuneType = ImmuneType.Physical;
ImmuneType |= ImmuneType.Physical;
}
else if (isMagicalImmune)
if (isMagicalImmune)
{
ImmuneType = ImmuneType.Magical;
ImmuneType |= ImmuneType.Magical;
}
else if (isSkilledImmune)
if (isSkilledImmune)
{
ImmuneType = ImmuneType.Skilled;
}
else
{
ImmuneType = ImmuneType.None;
ImmuneType |= ImmuneType.Skilled;
}
bool isControl = isNotActionable || isActionRestricted || isBattleRestricted || isSkillRestricted || isAttackRestricted;
@ -1890,7 +2034,7 @@ namespace Milimoe.FunGame.Core.Entity
/// [ 推荐从模组中复制后使用对象 ]
/// </summary>
/// <returns></returns>
public Character Copy(bool copyEx = false)
public Character Copy(bool copyEx = false, bool copyMagic = false, bool copyItem = false)
{
Character c = new()
{
@ -1900,13 +2044,11 @@ namespace Milimoe.FunGame.Core.Entity
FirstName = FirstName,
NickName = NickName,
Profile = Profile.Copy(),
MagicType = MagicType,
FirstRoleType = FirstRoleType,
SecondRoleType = SecondRoleType,
ThirdRoleType = ThirdRoleType,
Promotion = Promotion,
PrimaryAttribute = PrimaryAttribute,
ImmuneType = ImmuneType,
Level = Level,
LevelBreak = LevelBreak,
EXP = EXP,
@ -1915,9 +2057,6 @@ namespace Milimoe.FunGame.Core.Entity
EP = EP,
InitialATK = InitialATK,
InitialDEF = InitialDEF,
MDF = MDF.Copy(),
PhysicalPenetration = PhysicalPenetration,
MagicalPenetration = MagicalPenetration,
InitialHR = InitialHR,
InitialMR = InitialMR,
ER = ER,
@ -1927,10 +2066,7 @@ namespace Milimoe.FunGame.Core.Entity
STRGrowth = STRGrowth,
AGIGrowth = AGIGrowth,
INTGrowth = INTGrowth,
InitialSPD = InitialSPD,
ATR = ATR,
Lifesteal = Lifesteal,
Shield = Shield.Copy()
InitialSPD = InitialSPD
};
if (copyEx)
{
@ -1957,18 +2093,33 @@ namespace Milimoe.FunGame.Core.Entity
c.ExCritRate = ExCritRate;
c.ExCritDMG = ExCritDMG;
c.ExEvadeRate = ExEvadeRate;
c.PhysicalPenetration = PhysicalPenetration;
c.MagicalPenetration = MagicalPenetration;
c.MDF = MDF.Copy();
c.Lifesteal = Lifesteal;
c.Shield = Shield.Copy();
c.ExATR = ExATR;
c.ExMOV = ExMOV;
c.MagicType = MagicType;
c.ImmuneType = ImmuneType;
}
foreach (Skill skill in Skills)
{
Skill newskill = skill.Copy();
newskill.Character = c;
c.Skills.Add(newskill);
if (skill.SkillType != SkillType.Magic || copyMagic)
{
Skill newskill = skill.Copy();
newskill.Character = c;
c.Skills.Add(newskill);
}
}
foreach (Item item in Items)
if (copyItem)
{
Item newitem = item.Copy();
newitem.Character = c;
c.Items.Add(newitem);
foreach (Item item in Items)
{
Item newitem = item.Copy();
newitem.Character = c;
c.Items.Add(newitem);
}
}
c.Recovery();
return c;
@ -2028,8 +2179,10 @@ namespace Milimoe.FunGame.Core.Entity
EP = c.EP;
InitialATK = c.InitialATK;
ExATK2 = c.ExATK2;
ExATKPercentage = c.ExATKPercentage;
InitialDEF = c.InitialDEF;
ExDEF2 = c.ExDEF2;
ExDEFPercentage = c.ExDEFPercentage;
MDF = c.MDF.Copy();
PhysicalPenetration = c.PhysicalPenetration;
MagicalPenetration = c.MagicalPenetration;
@ -2042,8 +2195,11 @@ namespace Milimoe.FunGame.Core.Entity
InitialAGI = c.InitialAGI;
InitialINT = c.InitialINT;
ExSTR = c.ExSTR;
ExSTRPercentage = c.ExSTRPercentage;
ExAGI = c.ExAGI;
ExAGIPercentage = c.ExAGIPercentage;
ExINT = c.ExINT;
ExINTPercentage = c.ExINTPercentage;
STRGrowth = c.STRGrowth;
AGIGrowth = c.AGIGrowth;
INTGrowth = c.INTGrowth;
@ -2052,7 +2208,8 @@ namespace Milimoe.FunGame.Core.Entity
ExActionCoefficient = c.ExActionCoefficient;
ExAccelerationCoefficient = c.ExAccelerationCoefficient;
ExCDR = c.ExCDR;
ATR = c.ATR;
ExATR = c.ExATR;
ExMOV = c.ExMOV;
ExCritRate = c.ExCritRate;
ExCritDMG = c.ExCritDMG;
ExEvadeRate = c.ExEvadeRate;

View File

@ -147,12 +147,12 @@ namespace Milimoe.FunGame.Core.Entity
Item newitem;
if (itemsDefined != null && itemsDefined.FirstOrDefault(i => i.GetIdName() == item.GetIdName()) is Item itemDefined)
{
newitem = itemDefined.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
newitem = itemDefined.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
item.SetPropertyToItemModuleNew(newitem);
}
else
{
newitem = item.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
newitem = item.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
}
newitem.Character = character;
character.Items.Add(newitem);
@ -196,32 +196,32 @@ namespace Milimoe.FunGame.Core.Entity
{
if (mcp != null)
{
mcp = mcp.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
mcp = mcp.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(mcp);
}
if (w != null)
{
w = w.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
w = w.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(w);
}
if (a != null)
{
a = a.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
a = a.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(a);
}
if (s != null)
{
s = s.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
s = s.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(s);
}
if (ac1 != null)
{
ac1 = ac1.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
ac1 = ac1.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(ac1);
}
if (ac2 != null)
{
ac2 = ac2.Copy(true, !newItemGuid, true, itemsDefined, skillsDefined);
ac2 = ac2.Copy(true, !newItemGuid, true, true, itemsDefined, skillsDefined);
character.Equip(ac2);
}
}
@ -237,12 +237,13 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="reference"></param>
/// <param name="newItemGuid"></param>
/// <param name="copyLevel"></param>
/// <param name="inventory"></param>
/// <param name="inventory">高危数据警告:传入此项会修改库存中物品的角色引用</param>
/// <param name="itemsDefined">对于动态扩展的物品而言,传入已定义的物品表,不使用被复制物品的数据</param>
/// <param name="skillsDefined">对于动态扩展的技能而言,传入已定义的技能表,不使用被复制技能的数据</param>
/// <param name="recovery"></param>
/// <param name="copyEP"></param>
/// <returns>构建的新角色</returns>
public static Character Build(Character reference, bool newItemGuid = true, bool copyLevel = true, Inventory? inventory = null, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null, bool recovery = true)
public static Character Build(Character reference, bool newItemGuid = true, bool copyLevel = true, Inventory? inventory = null, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null, bool recovery = true, bool copyEP = true)
{
Character character = new CharacterBuilder(reference).Build(reference.Skills, reference.Items, newItemGuid, reference.EquipSlot, inventory, itemsDefined, skillsDefined);
if (copyLevel)
@ -252,13 +253,17 @@ namespace Milimoe.FunGame.Core.Entity
character.EXP = reference.EXP;
}
character.NormalAttack.Level = reference.NormalAttack.Level;
character.NormalAttack.HardnessTime = reference.NormalAttack.HardnessTime;
character.NormalAttack.SetMagicType(reference.NormalAttack.IsMagic, reference.NormalAttack.MagicType);
character.NormalAttack.ExDamage = reference.NormalAttack.ExDamage;
character.NormalAttack.ExDamage2 = reference.NormalAttack.ExDamage2;
character.NormalAttack.ExHardnessTime = reference.NormalAttack.ExHardnessTime;
character.NormalAttack.ExHardnessTime2 = reference.NormalAttack.ExHardnessTime2;
character.NormalAttack.SetMagicType(null, null, null);
if (!recovery)
{
character.HP = reference.HP;
character.MP = reference.MP;
}
if (copyEP) character.EP = reference.EP;
return character;
}
}

View File

@ -46,12 +46,12 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 紫宛魔法抗性
/// </summary>
public double Fleabane { get; set; } = 0;
public double Aster { get; set; } = 0;
/// <summary>
/// 时空魔法抗性
/// </summary>
public double Particle { get; set; } = 0;
public double SpatioTemporal { get; set; } = 0;
/// <summary>
/// 平均魔法抗性
@ -60,7 +60,7 @@ namespace Milimoe.FunGame.Core.Entity
{
get
{
double mdf = Calculation.Round4Digits((None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle) / 9) * 100;
double mdf = Calculation.Round4Digits((None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Aster + SpatioTemporal) / 9);
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
return mdf;
}
@ -83,8 +83,8 @@ namespace Milimoe.FunGame.Core.Entity
MagicType.Bright => Bright,
MagicType.Shadow => Shadow,
MagicType.Element => Element,
MagicType.Fleabane => Fleabane,
MagicType.Particle => Particle,
MagicType.Aster => Aster,
MagicType.SpatioTemporal => SpatioTemporal,
_ => None
};
}
@ -110,11 +110,11 @@ namespace Milimoe.FunGame.Core.Entity
case MagicType.Element:
Element = value;
break;
case MagicType.Fleabane:
Fleabane = value;
case MagicType.Aster:
Aster = value;
break;
case MagicType.Particle:
Particle = value;
case MagicType.SpatioTemporal:
SpatioTemporal = value;
break;
default:
None = value;
@ -123,39 +123,6 @@ namespace Milimoe.FunGame.Core.Entity
}
}
/// <summary>
/// 对所有抗性赋值
/// </summary>
/// <param name="value"></param>
/// <param name="assignment"></param>
public void SetAllValue(double value, bool assignment = true)
{
if (assignment)
{
None = value;
Particle = value;
Fleabane = value;
Element = value;
Shadow = value;
Bright = value;
PurityContemporary = value;
PurityNatural = value;
Starmark = value;
}
else
{
None += value;
Particle += value;
Fleabane += value;
Element += value;
Shadow += value;
Bright += value;
PurityContemporary += value;
PurityNatural += value;
Starmark += value;
}
}
/// <summary>
/// 增加所有抗性,传入负数来减少
/// </summary>
@ -163,14 +130,14 @@ namespace Milimoe.FunGame.Core.Entity
public void AddAllValue(double value)
{
None += value;
Particle += value;
Fleabane += value;
Element += value;
Shadow += value;
Bright += value;
PurityContemporary += value;
PurityNatural += value;
Starmark += value;
PurityNatural += value;
PurityContemporary += value;
Element += value;
Bright += value;
Shadow += value;
Aster += value;
SpatioTemporal += value;
}
/// <summary>
@ -188,8 +155,8 @@ namespace Milimoe.FunGame.Core.Entity
Bright = Bright,
Shadow = Shadow,
Element = Element,
Fleabane = Fleabane,
Particle = Particle
Aster = Aster,
SpatioTemporal = SpatioTemporal
};
}
}

View File

@ -7,6 +7,11 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public class Shield
{
/// <summary>
/// 绑定到特效的护盾对象。键为特效,值为对应的护盾对象。
/// </summary>
public Dictionary<Effect, ShieldOfEffect> ShieldOfEffects { get; } = [];
/// <summary>
/// 物理护盾
/// </summary>
@ -50,22 +55,32 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 紫宛护盾
/// </summary>
public double Fleabane { get; set; } = 0;
public double Aster { get; set; } = 0;
/// <summary>
/// 时空护盾
/// </summary>
public double Particle { get; set; } = 0;
public double SpatioTemporal { get; set; } = 0;
/// <summary>
/// 混合护盾
/// </summary>
public double Mix { get; set; } = 0;
/// <summary>
/// 总计混合护盾
/// </summary>
public double TotalMix => Mix + ShieldOfEffects.Values.Where(soe => soe.ShieldType == ShieldType.Mix && soe.Shield > 0).Sum(soe => soe.Shield);
/// <summary>
/// 总计物理护盾
/// </summary>
public double TotalPhysical => Physical;
public double TotalPhysical => Physical + ShieldOfEffects.Values.Where(soe => soe.ShieldType == ShieldType.Physical && soe.Shield > 0).Sum(soe => soe.Shield);
/// <summary>
/// 总计魔法护盾
/// </summary>
public double TotalMagicial => None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Fleabane + Particle;
public double TotalMagical => None + Starmark + PurityNatural + PurityContemporary + Bright + Shadow + Element + Aster + SpatioTemporal + ShieldOfEffects.Values.Where(soe => soe.ShieldType == ShieldType.Magical && soe.Shield > 0).Sum(soe => soe.Shield);
/// <summary>
/// 获取或设置护盾值
@ -73,7 +88,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="isMagic"></param>
/// <param name="type"></param>
/// <returns></returns>
public double this[bool isMagic = false, MagicType type = MagicType.None]
public double this[bool isMagic, MagicType type = MagicType.None]
{
get
{
@ -87,8 +102,8 @@ namespace Milimoe.FunGame.Core.Entity
MagicType.Bright => Bright,
MagicType.Shadow => Shadow,
MagicType.Element => Element,
MagicType.Fleabane => Fleabane,
MagicType.Particle => Particle,
MagicType.Aster => Aster,
MagicType.SpatioTemporal => SpatioTemporal,
_ => None
};
}
@ -118,11 +133,11 @@ namespace Milimoe.FunGame.Core.Entity
case MagicType.Element:
Element = value;
break;
case MagicType.Fleabane:
Fleabane = value;
case MagicType.Aster:
Aster = value;
break;
case MagicType.Particle:
Particle = value;
case MagicType.SpatioTemporal:
SpatioTemporal = value;
break;
default:
None = value;
@ -137,7 +152,45 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 复制一个护盾对象
/// 添加一个绑定到特效的护盾,注意:如果特效已经存在护盾,则会覆盖原有护盾。
/// </summary>
/// <param name="soe"></param>
public void AddShieldOfEffect(ShieldOfEffect soe)
{
ShieldOfEffects[soe.Effect] = soe;
}
/// <summary>
/// 移除某个特效的护盾
/// </summary>
/// <param name="effect"></param>
public void RemoveShieldOfEffect(Effect effect)
{
ShieldOfEffects.Remove(effect);
}
/// <summary>
/// 计算并更新特效的护盾值,如果护盾值小于等于 0则移除该特效的护盾。
/// </summary>
/// <param name="effect"></param>
/// <param name="damage"></param>
/// <returns></returns>
public double CalculateShieldOfEffect(Effect effect, double damage)
{
if (ShieldOfEffects.TryGetValue(effect, out ShieldOfEffect? soe))
{
soe.Calculate(damage);
if (soe.Shield <= 0)
{
soe.Shield = 0;
ShieldOfEffects.Remove(effect);
}
}
return soe?.Shield ?? 0;
}
/// <summary>
/// 复制一个护盾对象。注意:不会复制绑定到特效的护盾对象。
/// </summary>
/// <returns></returns>
public Shield Copy()
@ -152,9 +205,31 @@ namespace Milimoe.FunGame.Core.Entity
Bright = Bright,
Shadow = Shadow,
Element = Element,
Fleabane = Fleabane,
Particle = Particle
Aster = Aster,
SpatioTemporal = SpatioTemporal,
Mix = Mix
};
}
}
/// <summary>
/// 绑定到特效的护盾对象。这个类没有 JSON 转换器支持。
/// </summary>
/// <param name="effect"></param>
/// <param name="shield"></param>
/// <param name="shieldType"></param>
/// <param name="magicType"></param>
public class ShieldOfEffect(Effect effect, double shield, ShieldType shieldType, MagicType magicType = MagicType.None)
{
public Effect Effect { get; } = effect;
public ShieldType ShieldType { get; set; } = shieldType;
public MagicType MagicType { get; set; } = magicType;
public double Shield { get; set; } = shield;
public double Calculate(double damage)
{
Shield -= damage;
return Shield;
}
}
}

View File

@ -47,24 +47,25 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的详细信息
/// </summary>
/// <returns></returns>
public new string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
public new string GetInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
double exHP = ExHP + ExHP2 + ExHP3;
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.Mix > 0) shield.Add($"混合:{Shield.Mix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
@ -77,6 +78,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
if (showMapRelated)
{
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (CharacterState != CharacterState.Actionable)
{
builder.AppendLine(CharacterSet.GetCharacterState(CharacterState));
@ -173,29 +180,36 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的简略信息
/// </summary>
/// <returns></returns>
public new string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false)
public new string GetSimpleInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showBasicOnly = false, bool showMapRelated = false)
{
StringBuilder builder = new();
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
double exHP = ExHP + ExHP2 + ExHP3;
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.Mix > 0) shield.Add($"混合:{Shield.Mix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
builder.AppendLine($"魔法回复:{MR:0.##}" + (ExMR != 0 ? $" [{InitialMR + INT * GameplayEquilibriumConstant.INTtoMRFactor:0.##} {(ExMR >= 0 ? "+" : "-")} {Math.Abs(ExMR):0.##}]" : ""));
if (showMapRelated)
{
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (!showBasicOnly)
{
if (CharacterState != CharacterState.Actionable)
@ -280,7 +294,11 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine(ToStringWithLevel());
double exHP = ExHP + ExHP2 + ExHP3;
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.Mix > 0) shield.Add($"混合:{Shield.Mix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
double exATK = ExATK + ExATK2 + ExATK3;
@ -327,7 +345,11 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine(ToStringWithLevel());
double exHP = ExHP + ExHP2 + ExHP3;
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.Mix > 0) shield.Add($"混合:{Shield.Mix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
double exATK = ExATK + ExATK2 + ExATK3;
@ -398,24 +420,25 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取单位的物品信息
/// </summary>
/// <returns></returns>
public new string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false)
public new string GetItemInfo(bool showUser = true, bool showGrowth = true, bool showEXP = false, bool showMapRelated = false)
{
StringBuilder builder = new();
builder.AppendLine(showUser ? ToStringWithLevel() : ToStringWithLevelWithOutUser());
builder.AppendLine($"等级:{Level} / {GameplayEquilibriumConstant.MaxLevel}");
double exHP = ExHP + ExHP2 + ExHP3;
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : ""));
List<string> shield = [];
if (Shield.TotalPhysical > 0) shield.Add($"物理:{Shield.TotalPhysical:0.##}");
if (Shield.TotalMagical > 0) shield.Add($"魔法:{Shield.TotalMagical:0.##}");
if (Shield.Mix > 0) shield.Add($"混合:{Shield.Mix:0.##}");
builder.AppendLine($"生命值:{HP:0.##} / {MaxHP:0.##}" + (exHP != 0 ? $" [{BaseHP:0.##} {(exHP >= 0 ? "+" : "-")} {Math.Abs(exHP):0.##}]" : "") + (shield.Count > 0 ? $"{string.Join("", shield)}" : ""));
double exMP = ExMP + ExMP2 + ExMP3;
builder.AppendLine($"魔法值:{MP:0.##} / {MaxMP:0.##}" + (exMP != 0 ? $" [{BaseMP:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exMP):0.##}]" : ""));
double exATK = ExATK + ExATK2 + ExATK3;
builder.AppendLine($"攻击力:{ATK:0.##}" + (exATK != 0 ? $" [{BaseATK:0.##} {(exATK >= 0 ? "+" : "-")} {Math.Abs(exATK):0.##}]" : ""));
double exDEF = ExDEF + ExDEF2 + ExDEF3;
builder.AppendLine($"物理护甲:{DEF:0.##}" + (exDEF != 0 ? $" [{BaseDEF:0.##} {(exMP >= 0 ? "+" : "-")} {Math.Abs(exDEF):0.##}]" : "") + $" ({PDR * 100:0.##}%)");
double mdf = Calculation.Round4Digits((MDF.None + MDF.Starmark + MDF.PurityNatural + MDF.PurityContemporary +
MDF.Bright + MDF.Shadow + MDF.Element + MDF.Fleabane + MDF.Particle) / 9) * 100;
if (Calculation.IsApproximatelyZero(mdf)) mdf = 0;
builder.AppendLine($"魔法抗性:{mdf:0.##}%(平均)");
builder.AppendLine($"魔法抗性:{MDF.Avg:0.##}%(平均)");
double exSPD = AGI * GameplayEquilibriumConstant.AGItoSPDMultiplier + ExSPD;
builder.AppendLine($"行动速度:{SPD:0.##}" + (exSPD != 0 ? $" [{InitialSPD:0.##} {(exSPD >= 0 ? "+" : "-")} {Math.Abs(exSPD):0.##}]" : "") + $" ({ActionCoefficient * 100:0.##}%)");
builder.AppendLine($"生命回复:{HR:0.##}" + (ExHR != 0 ? $" [{InitialHR + STR * GameplayEquilibriumConstant.STRtoHRFactor:0.##} {(ExHR >= 0 ? "+" : "-")} {Math.Abs(ExHR):0.##}]" : ""));
@ -428,6 +451,12 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"物理穿透:{PhysicalPenetration * 100:0.##}%");
builder.AppendLine($"魔法穿透:{MagicalPenetration * 100:0.##}%");
if (showMapRelated)
{
builder.AppendLine($"移动距离:{MOV}");
builder.AppendLine($"攻击距离:{ATR}");
}
if (EquipSlot.Any())
{
builder.AppendLine("== 装备栏 ==");

179
Entity/Explore/Activity.cs Normal file
View File

@ -0,0 +1,179 @@
using System.Text;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Activity : BaseEntity
{
public DateTime? StartTime { get; set; } = null;
public DateTime? EndTime { get; set; } = null;
public string Description { get; set; } = "";
public ActivityState Status { get; private set; } = ActivityState.Future;
public HashSet<Quest> Quests { get; set; } = [];
public long Predecessor { get; set; } = -1;
public ActivityState PredecessorStatus { get; set; } = ActivityState.Future;
public Activity(long id, string name, DateTime? startTime = null, DateTime? endTime = null)
{
Id = id;
Name = name;
StartTime = startTime;
EndTime = endTime;
}
public Activity() { }
// 事件
public event Action<ActivityEventArgs>? UserAccess;
public event Action<ActivityEventArgs>? UserGetActivityInfo;
public void UnRegisterUserAccess()
{
UserAccess = null;
}
public void UnRegisterUserGetActivityInfo()
{
UserGetActivityInfo = null;
}
public void UpdateState()
{
ActivityState newState;
DateTime now = DateTime.Now;
DateTime? upComingTime = StartTime?.AddHours(-6);
if (Predecessor != -1 && PredecessorStatus != ActivityState.Ended)
{
// 如果有前置活动且前置活动未结束,则当前活动状态为未来
newState = ActivityState.Future;
Status = newState;
return;
}
if (upComingTime != null && now < upComingTime)
{
newState = ActivityState.Future;
}
else if (upComingTime != null && now >= upComingTime && now < StartTime)
{
newState = ActivityState.Upcoming;
}
else if ((StartTime is null || now >= StartTime) && (EndTime is null || now < EndTime))
{
newState = ActivityState.InProgress;
}
else
{
newState = ActivityState.Ended;
}
if (Status != newState)
{
Status = newState;
foreach (Quest quest in Quests)
{
if (newState == ActivityState.InProgress)
{
if (quest.Status == QuestState.NotStarted && quest.QuestType == QuestType.Progressive)
{
quest.Status = QuestState.InProgress;
}
}
else if (newState == ActivityState.Ended)
{
if (quest.Status == QuestState.NotStarted || quest.Status == QuestState.InProgress)
{
quest.Status = QuestState.Missed;
}
}
}
}
}
public bool AllowUserAccess(long userId, long questId = 0)
{
UpdateState();
ActivityEventArgs args = new(userId, questId, this);
UserAccess?.Invoke(args);
return args.AllowAccess;
}
public void GetActivityInfo(long userId, long questId = 0)
{
UpdateState();
ActivityEventArgs args = new(userId, questId, this);
UserGetActivityInfo?.Invoke(args);
}
public string ToString(bool showQuests)
{
UpdateState();
StringBuilder builder = new();
builder.AppendLine($"☆--- {Name} ---☆");
builder.AppendLine($"{Description}");
builder.AppendLine($"活动状态:{CommonSet.GetActivityStatus(Status)}");
builder.AppendLine(GetTimeString());
if (showQuests && Quests.Count > 0)
{
builder.AppendLine("=== 任务列表 ===");
builder.AppendLine(string.Join("\r\n", Quests));
}
return builder.ToString().Trim();
}
public string GetTimeString(bool full = true)
{
if (Predecessor != -1 && PredecessorStatus != ActivityState.Ended)
{
return $"在前置活动结束后开启";
}
if (full)
{
if (StartTime != null && EndTime != null)
{
return $"开始时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}\r\n结束时间{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}";
}
else if (StartTime != null && EndTime is null)
{
return $"活动时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)} 起";
}
else if (StartTime is null && EndTime != null)
{
return $"活动将在 {EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)} 结束";
}
}
else
{
if (StartTime != null && EndTime != null)
{
return $"活动时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)} - {EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}";
}
else if (StartTime != null && EndTime is null)
{
return $"活动时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)} 起";
}
else if (StartTime is null && EndTime != null)
{
return $"截止于 {EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}";
}
}
return "活动时间:长期";
}
public override string ToString()
{
return ToString(true);
}
public override bool Equals(IBaseEntity? other)
{
return other is Activity && GetIdName() == other?.GetIdName();
}
}
}

View File

@ -6,9 +6,12 @@ namespace Milimoe.FunGame.Core.Entity
public class Quest : BaseEntity
{
public string Description { get; set; } = "";
public QuestState Status { get; set; } = 0;
public QuestState Status { get; set; } = QuestState.NotStarted;
public long CharacterId { get; set; } = 0;
public long RegionId { get; set; } = 0;
public string NeedyExploreCharacterName { get; set; } = "";
public string NeedyExploreItemName { get; set; } = "";
public string NeedyExploreEventName { get; set; } = "";
public double CreditsAward { get; set; } = 0;
public double MaterialsAward { get; set; } = 0;
public HashSet<Item> Awards { get; set; } = [];
@ -28,7 +31,12 @@ namespace Milimoe.FunGame.Core.Entity
}
foreach (Item item in Awards)
{
awards.Add($"[{ItemSet.GetQualityTypeName(item.QualityType)}|{ItemSet.GetItemTypeName(item.ItemType)}] {item.Name} * {AwardsCount[item.Name]}");
int count = 1;
if (AwardsCount.TryGetValue(item.Name, out int value))
{
count = value;
}
awards.Add($"[{ItemSet.GetQualityTypeName(item.QualityType)}|{ItemSet.GetItemTypeName(item.ItemType)}] {item.Name} * {count}");
}
return string.Join("", awards);
}

View File

@ -11,10 +11,13 @@ namespace Milimoe.FunGame.Core.Entity
public HashSet<Character> Characters { get; } = [];
public HashSet<Unit> Units { get; } = [];
public HashSet<Item> Crops { get; } = [];
public HashSet<Item> Items { get; } = [];
public string Weather { get; set; } = "";
public int Temperature { get; set; } = 15;
public Dictionary<string, int> Weathers { get; } = [];
public RarityType Difficulty { get; set; } = RarityType.OneStar;
public List<string> NPCs { get; set; } = [];
public List<string> Areas { get; set; } = [];
public bool ChangeWeather(string weather)
{
@ -47,25 +50,37 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"☆--- {Name} ---☆");
builder.AppendLine($"编号:{Id}");
builder.AppendLine($"天气:{Weather}");
builder.AppendLine($"温度:{Temperature} °C");
builder.AppendLine($"温度:{Temperature} ");
builder.AppendLine($"{Description}");
if (Characters.Count > 0)
{
builder.AppendLine($"== 头目 ==");
builder.AppendLine(string.Join("", Characters.Select(o => o.Name)));
builder.AppendLine(string.Join("", Characters.Select(c => c.Name)));
}
if (Units.Count > 0)
{
builder.AppendLine($"== 生物 ==");
builder.AppendLine(string.Join("", Units.Select(o => o.Name)));
builder.AppendLine(string.Join("", Units.Select(u => u.Name)));
}
if (Crops.Count > 0)
{
builder.AppendLine($"== 作物 ==");
builder.AppendLine(string.Join("", Crops.Select(c => c.Name)));
builder.AppendLine(string.Join("", Crops.Select(c => c.Name + (c.Description != "" ? $"{c.Description}" : "") + (c.BackgroundStory != "" ? $"\"{c.BackgroundStory}\"" : ""))));
}
if (Items.Count > 0)
{
builder.AppendLine($"== 掉落 ==");
builder.AppendLine(string.Join("", Items.Select(i =>
{
string itemquality = ItemSet.GetQualityTypeName(i.QualityType);
string itemtype = ItemSet.GetItemTypeName(i.ItemType) + (i.ItemType == ItemType.Weapon && i.WeaponType != WeaponType.None ? "-" + ItemSet.GetWeaponTypeName(i.WeaponType) : "");
if (itemtype != "") itemtype = $"|{itemtype}";
return $"[{itemquality + itemtype}]{i.Name}";
})));
}
builder.AppendLine($"探索难度:{CharacterSet.GetRarityTypeName(Difficulty)}");

View File

@ -2,6 +2,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
@ -176,18 +177,10 @@ namespace Milimoe.FunGame.Core.Entity
foreach (Skill skill in Skills.Passives)
{
skill.Character = _character;
foreach (Effect e in skill.Effects)
{
e.Source = _character;
}
}
foreach (Skill skill in Skills.Magics)
{
skill.Character = _character;
foreach (Effect e in skill.Effects)
{
e.Source = _character;
}
}
}
}
@ -318,7 +311,13 @@ namespace Milimoe.FunGame.Core.Entity
}
if (result && Skills.Active != null)
{
used = await queue.UseItemAsync(this, character, enemys, teammates);
List<Grid> castRange = [];
if (Skills.Active.GamingQueue != null && Skills.Active.GamingQueue.Map != null)
{
Grid? grid = Skills.Active.GamingQueue.Map.GetCharacterCurrentGrid(character);
castRange = grid is null ? [] : Skills.Active.GamingQueue.Map.GetGridsByRange(grid, Skills.Active.CastRange, true);
}
used = await queue.UseItemAsync(this, character, enemys, teammates, castRange);
}
if (used)
{
@ -332,11 +331,11 @@ namespace Milimoe.FunGame.Core.Entity
/// 局外(库存)使用物品触发
/// </summary>
/// <returns></returns>
public bool UseItem(Dictionary<string, object> args)
public bool UseItem(User user, int times, Dictionary<string, object> args)
{
if (User != null)
{
bool result = OnItemUsed(args);
bool result = OnItemUsed(user, times, args);
if (result)
{
EntityState = EntityState.Modified;
@ -347,19 +346,19 @@ namespace Milimoe.FunGame.Core.Entity
return false;
}
public void ReduceTimesAndRemove()
/// <summary>
/// 使用后减少使用次数或删除物品
/// </summary>
public void ReduceTimesAndRemove(int times = 1)
{
if (User != null)
if (IsReduceTimesAfterUse)
{
if (IsReduceTimesAfterUse)
{
RemainUseTimes--;
}
if (RemainUseTimes < 0) RemainUseTimes = 0;
if (IsRemoveAfterUse && RemainUseTimes == 0)
{
EntityState = EntityState.Deleted;
}
RemainUseTimes -= times;
}
if (RemainUseTimes < 0) RemainUseTimes = 0;
if (IsRemoveAfterUse && RemainUseTimes == 0)
{
EntityState = EntityState.Deleted;
}
}
@ -378,9 +377,11 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 当物品被玩家使用时
/// </summary>
/// <param name="user"></param>
/// <param name="times"></param>
/// <param name="args"></param>
/// <returns></returns>
protected virtual bool OnItemUsed(Dictionary<string, object> args)
protected virtual bool OnItemUsed(User user, int times, Dictionary<string, object> args)
{
return false;
}
@ -407,6 +408,14 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 物品完成复制后触发
/// </summary>
protected virtual void AfterCopy()
{
}
protected Item(ItemType type, bool isInGame = true)
{
ItemType = type;
@ -434,7 +443,7 @@ namespace Milimoe.FunGame.Core.Entity
{
StringBuilder builder = new();
builder.AppendLine($"【{Name}】");
builder.AppendLine($"【{Name}】{(IsLock ? " []" : "")}");
string itemquality = ItemSet.GetQualityTypeName(QualityType);
string itemtype = ItemSet.GetItemTypeName(ItemType) + (ItemType == ItemType.Weapon && WeaponType != WeaponType.None ? "-" + ItemSet.GetWeaponTypeName(WeaponType) : "");
@ -444,11 +453,11 @@ namespace Milimoe.FunGame.Core.Entity
if (isShowInStore && Price > 0)
{
builder.AppendLine($"售价:{Price} {GameplayEquilibriumConstant.InGameCurrency}");
builder.AppendLine($"售价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
}
else if (Price > 0)
{
builder.AppendLine($"回收价:{Price} {GameplayEquilibriumConstant.InGameCurrency}");
builder.AppendLine($"回收价:{Price:0.##} {GameplayEquilibriumConstant.InGameCurrency}");
}
if (RemainUseTimes > 0)
@ -471,36 +480,47 @@ namespace Milimoe.FunGame.Core.Entity
else
{
List<string> sellandtrade = [];
bool useRN = false;
if (IsSellable)
{
sellandtrade.Add("可出售");
}
if (!IsSellable && NextSellableTime != DateTime.MinValue)
{
sellandtrade.Add($"此物品将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
}
else if (!IsSellable)
if (IsLock)
{
sellandtrade.Add("不可出售");
}
if (IsTradable)
{
sellandtrade.Add("可交易");
}
if (!IsTradable && NextTradableTime != DateTime.MinValue)
{
sellandtrade.Add($"此物品将在 {NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易");
}
else if (!IsTradable)
{
sellandtrade.Add("不可交易");
}
else
{
if (IsSellable)
{
sellandtrade.Add("可出售");
}
if (sellandtrade.Count > 0) builder.AppendLine(string.Join(" ", sellandtrade).Trim());
if (!IsSellable && NextSellableTime != DateTime.MinValue)
{
useRN = true;
sellandtrade.Add($"此物品将在 {NextSellableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可出售");
}
else if (!IsSellable)
{
sellandtrade.Add("不可出售");
}
if (IsTradable)
{
sellandtrade.Add("可交易");
}
if (!IsTradable && NextTradableTime != DateTime.MinValue)
{
useRN = true;
sellandtrade.Add($"此物品将在 {NextTradableTime.ToString(General.GeneralDateTimeFormatChinese)} 后可交易");
}
else if (!IsTradable)
{
sellandtrade.Add("不可交易");
}
}
if (sellandtrade.Count > 0) builder.AppendLine(string.Join(useRN ? "\r\n" : " ", sellandtrade).Trim());
}
if (isShowGeneralDescription && GeneralDescription != "")
@ -567,6 +587,11 @@ namespace Milimoe.FunGame.Core.Entity
sellandtrade.Add("不可交易");
}
if (IsLock)
{
builder.AppendLine("此物品已锁定");
}
if (sellandtrade.Count > 0) builder.AppendLine(string.Join(" ", sellandtrade).Trim());
if (Description != "") builder.AppendLine($"{Description}");
if (IsEquipment && Character != null) builder.AppendLine($"装备于:{Character.ToStringWithLevelWithOutUser()}");
@ -609,7 +634,7 @@ namespace Milimoe.FunGame.Core.Entity
/// 复制一个物品
/// </summary>
/// <returns></returns>
public Item Copy(bool copyLevel = false, bool copyGuid = false, bool copyProperty = true, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null)
public Item Copy(bool copyLevel = false, bool copyGuid = false, bool copyProperty = true, bool copyOthers = true, IEnumerable<Item>? itemsDefined = null, IEnumerable<Skill>? skillsDefined = null)
{
Item item = Factory.OpenFactory.GetInstance<Item>(Id, Name, []);
Item? itemDefined = null;
@ -657,6 +682,18 @@ namespace Milimoe.FunGame.Core.Entity
item.Skills.Magics.Add(newskill);
}
}
foreach (string key in itemDefined.Others.Keys)
{
item.Others[key] = itemDefined.Others[key];
}
if (copyOthers)
{
foreach (string key in Others.Keys)
{
item.Others[key] = Others[key];
}
}
item.AfterCopy();
return item;
}

View File

@ -2,6 +2,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
@ -73,7 +74,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 是否显示在状态栏
/// </summary>
public bool ShowInStatusBar => !ForceHideInStatusBar && !IsSubsidiary && (Skill.Item is null || (Durative && Duration > 0) || DurationTurn > 0 || DurativeWithoutDuration);
public bool ShowInStatusBar => !ForceHideInStatusBar && (Skill.Item is null || (Durative && Duration > 0) || DurationTurn > 0 || DurativeWithoutDuration);
/// <summary>
/// 特效是否生效
@ -218,9 +219,9 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="character"></param>
/// <param name="enemy"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
public virtual void AlterDamageTypeBeforeCalculation(Character character, Character enemy, ref bool isNormalAttack, ref bool isMagicDamage, ref MagicType magicType)
public virtual void AlterDamageTypeBeforeCalculation(Character character, Character enemy, ref bool isNormalAttack, ref DamageType damageType, ref MagicType magicType)
{
}
@ -232,11 +233,11 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="totalDamageBonus"></param>
/// <returns>返回伤害增减值</returns>
public virtual double AlterExpectedDamageBeforeCalculation(Character character, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, Dictionary<Effect, double> totalDamageBonus)
public virtual double AlterExpectedDamageBeforeCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, Dictionary<Effect, double> totalDamageBonus)
{
return 0;
}
@ -248,17 +249,49 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
/// <param name="isEvaded"></param>
/// <param name="totalDamageBonus"></param>
/// <returns>返回伤害增减值</returns>
public virtual double AlterActualDamageAfterCalculation(Character character, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult, ref bool isEvaded, Dictionary<Effect, double> totalDamageBonus)
public virtual double AlterActualDamageAfterCalculation(Character character, Character enemy, double damage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult, ref bool isEvaded, Dictionary<Effect, double> totalDamageBonus)
{
return 0;
}
/// <summary>
/// 在应用真实伤害前修改伤害 [ 允许取消伤害 ]
/// </summary>
/// <param name="character"></param>
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="damageResult"></param>
/// <returns>返回 true 取消伤害</returns>
public virtual bool BeforeApplyTrueDamage(Character character, Character enemy, double damage, bool isNormalAttack, DamageResult damageResult)
{
return false;
}
/// <summary>
/// 伤害应用时触发
/// </summary>
/// <param name="character"></param>
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="actualDamage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
/// <param name="shieldMessage"></param>
/// <param name="originalMessage"></param>
public virtual void OnApplyDamage(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult, string shieldMessage, ref string originalMessage)
{
}
/// <summary>
/// 在完成普通攻击动作之后修改硬直时间
/// </summary>
@ -335,11 +368,12 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 对目标触发技能效果
/// 对目标触发技能效果(局外)
/// </summary>
/// <param name="user"></param>
/// <param name="targets"></param>
/// <param name="others"></param>
public virtual void OnSkillCasted(List<Character> targets, Dictionary<string, object> others)
public virtual void OnSkillCasted(User user, List<Character> targets, Dictionary<string, object> others)
{
}
@ -354,17 +388,28 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 时间流逝时 [ 地图用 ]
/// </summary>
/// <param name="grid"></param>
/// <param name="elapsed"></param>
public virtual void OnTimeElapsed(Grid grid, double elapsed)
{
}
/// <summary>
/// 在完成伤害结算后
/// </summary>
/// <param name="character"></param>
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="actualDamage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
public virtual void AfterDamageCalculation(Character character, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage, MagicType magicType, DamageResult damageResult)
public virtual void AfterDamageCalculation(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult)
{
}
@ -508,6 +553,19 @@ namespace Milimoe.FunGame.Core.Entity
}
/// <summary>
/// 开始选择移动目标前
/// </summary>
/// <param name="character"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="map"></param>
/// <param name="moveRange"></param>
public virtual void BeforeSelectTargetGrid(Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> moveRange)
{
}
/// <summary>
/// 开始选择目标前,修改可选择的 <paramref name="enemys"/>, <paramref name="teammates"/> 列表<para/>
/// <see cref="ISkill"/> 有两种,使用时注意判断是 <see cref="Entity.Skill"/> 还是 <see cref="NormalAttack"/>
@ -531,8 +589,9 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="pUseItem"></param>
/// <param name="pCastSkill"></param>
/// <param name="pNormalAttack"></param>
/// <param name="forceAction"></param>
/// <returns></returns>
public virtual CharacterActionType AlterActionTypeBeforeAction(Character character, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack)
public virtual CharacterActionType AlterActionTypeBeforeAction(Character character, CharacterState state, ref bool canUseItem, ref bool canCastSkill, ref double pUseItem, ref double pCastSkill, ref double pNormalAttack, ref bool forceAction)
{
return CharacterActionType.None;
}
@ -574,19 +633,22 @@ namespace Milimoe.FunGame.Core.Entity
if (effect.DurativeWithoutDuration || (effect.Durative && effect.Duration > 0) || effect.DurationTurn > 0)
{
// 先从角色身上移除特效类型
if (target.CharacterEffectTypes.TryGetValue(effect, out List<EffectType>? types) && types != null)
if (isEnemy != effect.IsDebuff)
{
RemoveEffectTypesByDispel(types, isEnemy);
if (types.Count == 0)
if (target.CharacterEffectTypes.TryGetValue(effect, out List<EffectType>? types) && types != null)
{
RemoveEffectTypesByDispel(types, isEnemy);
if (types.Count == 0)
{
target.CharacterEffectTypes.Remove(effect);
removeEffectTypes = true;
}
}
else
{
target.CharacterEffectTypes.Remove(effect);
removeEffectTypes = true;
}
}
else
{
removeEffectTypes = true;
}
// 友方移除控制状态
if (!isEnemy && effect.IsDebuff)
{
@ -654,29 +716,53 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
/// <param name="character"></param>
/// <param name="attacker"></param>
/// <param name="isMagic"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damage"></param>
/// <param name="shield"></param>
/// <param name="damageReduce"></param>
/// <param name="message"></param>
/// <returns>返回 false 可以阻止后续扣除角色护盾</returns>
public virtual bool BeforeShieldCalculation(Character character, Character attacker, bool isMagic, MagicType magicType, double damage, double shield, ref string message)
/// <returns>返回 false 可以跳过护盾结算</returns>
public virtual bool BeforeShieldCalculation(Character character, Character attacker, DamageType damageType, MagicType magicType, double damage, ref double damageReduce, ref string message)
{
return true;
}
/// <summary>
/// 当角色护盾破碎时
/// 在角色护盾有效防御时 [ 破碎本身不会触发此钩子,但破碎后化解可触发 ]
/// </summary>
/// <param name="character"></param>
/// <param name="attacker"></param>
/// <param name="isMagic"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damage"></param>
/// <param name="shield"></param>
/// <param name="shieldType"></param>
public virtual void OnShieldNeutralizeDamage(Character character, Character attacker, DamageType damageType, MagicType magicType, double damage, ShieldType shieldType)
{
}
/// <summary>
/// 当角色护盾破碎时 [ 非绑定特效,只有同种类型的总护盾值小于等于 0 时触发 ]
/// </summary>
/// <param name="character"></param>
/// <param name="attacker"></param>
/// <param name="type"></param>
/// <param name="overFlowing"></param>
/// <returns>返回 false 可以阻止后续扣除角色生命值</returns>
public virtual bool OnShieldBroken(Character character, Character attacker, bool isMagic, MagicType magicType, double damage, double shield, double overFlowing)
public virtual bool OnShieldBroken(Character character, Character attacker, ShieldType type, double overFlowing)
{
return true;
}
/// <summary>
/// 当角色护盾破碎时 [ 绑定特效的护盾值小于等于 0 时便会触发 ]
/// </summary>
/// <param name="character"></param>
/// <param name="attacker"></param>
/// <param name="effect"></param>
/// <param name="overFlowing"></param>
/// <returns>返回 false 可以阻止后续扣除角色生命值</returns>
public virtual bool OnShieldBroken(Character character, Character attacker, Effect effect, double overFlowing)
{
return true;
}
@ -684,12 +770,12 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 在免疫检定时
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="character"></param>
/// <param name="target"></param>
/// <param name="skill"></param>
/// <param name="item"></param>
/// <returns>false免疫检定不通过</returns>
public virtual bool OnImmuneCheck(Character actor, Character enemy, ISkill skill, Item? item = null)
public virtual bool OnImmuneCheck(Character character, Character target, ISkill skill, Item? item = null)
{
return true;
}
@ -700,11 +786,11 @@ namespace Milimoe.FunGame.Core.Entity
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagic"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damage"></param>
/// <returns>false免疫检定不通过</returns>
public virtual bool OnDamageImmuneCheck(Character actor, Character enemy, bool isNormalAttack, bool isMagic, MagicType magicType, double damage)
public virtual bool OnDamageImmuneCheck(Character actor, Character enemy, bool isNormalAttack, DamageType damageType, MagicType magicType, double damage)
{
return true;
}
@ -714,16 +800,22 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
/// <param name="actor"></param>
/// <param name="enemy"></param>
/// <param name="isMagic"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="expectedDamage"></param>
/// <returns></returns>
public DamageResult DamageToEnemy(Character actor, Character enemy, bool isMagic, MagicType magicType, double expectedDamage)
public DamageResult DamageToEnemy(Character actor, Character enemy, DamageType damageType, MagicType magicType, double expectedDamage)
{
if (GamingQueue is null) return DamageResult.Evaded;
int changeCount = 0;
DamageResult result = !isMagic ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out double damage, ref changeCount) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount);
GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, isMagic, magicType, result);
DamageResult result = DamageResult.Normal;
double damage = expectedDamage;
if (damageType != DamageType.True)
{
result = damageType == DamageType.Physical ? GamingQueue.CalculatePhysicalDamage(actor, enemy, false, expectedDamage, out damage, ref changeCount) : GamingQueue.CalculateMagicalDamage(actor, enemy, false, MagicType, expectedDamage, out damage, ref changeCount);
}
// 注意此方法在后台线程运行
GamingQueue.DamageToEnemyAsync(actor, enemy, damage, false, damageType, magicType, result);
return result;
}
@ -955,6 +1047,17 @@ namespace Milimoe.FunGame.Core.Entity
GamingQueue?.ChangeCharacterHardnessTime(character, addValue, isPercentage, isCheckProtected);
}
/// <summary>
/// 设置角色为 AI 控制 [ 系统控制 ]
/// </summary>
/// <param name="cancel"></param>
/// <param name="characters"></param>
/// <returns></returns>
public void SetCharactersToAIControl(bool cancel = false, params IEnumerable<Character> characters)
{
GamingQueue?.SetCharactersToAIControl(true, cancel, characters);
}
/// <summary>
/// 检查角色是否在 AI 控制状态
/// </summary>
@ -965,6 +1068,19 @@ namespace Milimoe.FunGame.Core.Entity
return GamingQueue?.IsCharacterInAIControlling(character) ?? false;
}
/// <summary>
/// 添加角色应用的特效类型到回合记录中
/// </summary>
/// <param name="character"></param>
/// <param name="types"></param>
public void RecordCharacterApplyEffects(Character character, params List<EffectType> types)
{
if (GamingQueue?.LastRound.ApplyEffects.TryAdd(character, types) ?? false)
{
GamingQueue?.LastRound.ApplyEffects[character].AddRange(types);
}
}
/// <summary>
/// 返回特效详情
/// </summary>
@ -991,6 +1107,11 @@ namespace Milimoe.FunGame.Core.Entity
builder.Append($"{dispels}");
}
if (IsBeingTemporaryDispelled)
{
builder.Append("(已被临时驱散)");
}
return builder.ToString();
}

View File

@ -16,22 +16,77 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 普通攻击说明
/// </summary>
public string Description => $"对目标敌人造成 {(1.0 + 0.05 * (Level - 1)) * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。";
public string Description => $"对目标敌人造成 {BaseDamageMultiplier * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。";
/// <summary>
/// 普通攻击的通用说明
/// </summary>
public string GeneralDescription => $"对目标敌人造成基于 100+5/Lv% 攻击力的{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。";
public string GeneralDescription => $"对目标敌人造成基于攻击力的{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。";
/// <summary>
/// 所属的角色
/// </summary>
public Character Character { get; } = character;
/// <summary>
/// 基础普通攻击伤害倍率 [ 武器类型相关 ]
/// </summary>
private double BaseDamageMultiplier
{
get
{
double baseMultiplier = 1.0 + 0.05 * (Level - 1);
if (Character.EquipSlot.Weapon != null)
{
baseMultiplier = Character.EquipSlot.Weapon.WeaponType switch
{
WeaponType.OneHandedSword => GameplayEquilibriumConstant.OneHandedSwordBaseMultiplier,
WeaponType.TwoHandedSword => GameplayEquilibriumConstant.TwoHandedSwordBaseMultiplier,
WeaponType.Bow => GameplayEquilibriumConstant.BowBaseMultiplier,
WeaponType.Pistol => GameplayEquilibriumConstant.PistolBaseMultiplier,
WeaponType.Rifle => GameplayEquilibriumConstant.RifleBaseMultiplier,
WeaponType.DualDaggers => GameplayEquilibriumConstant.DualDaggersBaseMultiplier,
WeaponType.Talisman => GameplayEquilibriumConstant.TalismanBaseMultiplier,
WeaponType.Staff => GameplayEquilibriumConstant.StaffBaseMultiplier,
WeaponType.Polearm => GameplayEquilibriumConstant.PolearmBaseMultiplier,
WeaponType.Gauntlet => GameplayEquilibriumConstant.GauntletBaseMultiplier,
WeaponType.HiddenWeapon => GameplayEquilibriumConstant.HiddenWeaponBaseMultiplier,
_ => 1.0
};
double levelBonus = Character.EquipSlot.Weapon.WeaponType switch
{
WeaponType.OneHandedSword => GameplayEquilibriumConstant.OneHandedSwordLevelBonus,
WeaponType.TwoHandedSword => GameplayEquilibriumConstant.TwoHandedSwordLevelBonus,
WeaponType.Bow => GameplayEquilibriumConstant.BowLevelBonus,
WeaponType.Pistol => GameplayEquilibriumConstant.PistolLevelBonus,
WeaponType.Rifle => GameplayEquilibriumConstant.RifleLevelBonus,
WeaponType.DualDaggers => GameplayEquilibriumConstant.DualDaggersLevelBonus,
WeaponType.Staff => GameplayEquilibriumConstant.StaffLevelBonus,
WeaponType.Polearm => GameplayEquilibriumConstant.PolearmLevelBonus,
WeaponType.Gauntlet => GameplayEquilibriumConstant.GauntletLevelBonus,
WeaponType.HiddenWeapon => GameplayEquilibriumConstant.HiddenWeaponLevelBonus,
_ => 0.05
};
baseMultiplier += levelBonus * (Level - 1);
}
return baseMultiplier;
}
}
/// <summary>
/// 普通攻击的伤害
/// </summary>
public double Damage => Character.ATK * (1.0 + 0.05 * (Level - 1));
public double Damage => Character.ATK * BaseDamageMultiplier * (1 + ExDamage2) + ExDamage;
/// <summary>
/// 额外普通攻击伤害 [ 技能和物品相关 ]
/// </summary>
public double ExDamage { get; set; } = 0;
/// <summary>
/// 额外普通攻击伤害% [ 技能和物品相关 ]
/// </summary>
public double ExDamage2 { get; set; } = 0;
/// <summary>
/// 普通攻击等级
@ -74,9 +129,44 @@ namespace Milimoe.FunGame.Core.Entity
public ImmuneType IgnoreImmune { get; set; } = ImmuneType.None;
/// <summary>
/// 硬直时间
/// 硬直时间 [ 武器类型相关 ]
/// </summary>
public double HardnessTime { get; set; } = 10;
public double HardnessTime
{
get
{
double ht = 10;
if (Character.EquipSlot.Weapon != null)
{
ht = Character.EquipSlot.Weapon.WeaponType switch
{
WeaponType.OneHandedSword => GameplayEquilibriumConstant.OneHandedSwordHardness,
WeaponType.TwoHandedSword => GameplayEquilibriumConstant.TwoHandedSwordHardness,
WeaponType.Bow => GameplayEquilibriumConstant.BowHardness,
WeaponType.Pistol => GameplayEquilibriumConstant.PistolHardness,
WeaponType.Rifle => GameplayEquilibriumConstant.RifleHardness,
WeaponType.DualDaggers => GameplayEquilibriumConstant.DualDaggersHardness,
WeaponType.Talisman => GameplayEquilibriumConstant.TalismanHardness,
WeaponType.Staff => GameplayEquilibriumConstant.StaffHardness,
WeaponType.Polearm => GameplayEquilibriumConstant.PolearmHardness,
WeaponType.Gauntlet => GameplayEquilibriumConstant.GauntletHardness,
WeaponType.HiddenWeapon => GameplayEquilibriumConstant.HiddenWeaponHardness,
_ => 10,
};
}
return ht * (1 + ExHardnessTime2) + ExHardnessTime;
}
}
/// <summary>
/// 额外硬直时间 [ 技能和物品相关 ]
/// </summary>
public double ExHardnessTime { get; set; } = 0;
/// <summary>
/// 额外硬直时间% [ 技能和物品相关 ]
/// </summary>
public double ExHardnessTime2 { get; set; } = 0;
/// <summary>
/// 实际硬直时间
@ -98,15 +188,25 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public bool CanSelectTeammate { get; set; } = false;
/// <summary>
/// 选取所有敌对角色,优先级大于 <see cref="CanSelectTargetCount"/>
/// </summary>
public bool SelectAllEnemies { get; set; } = false;
/// <summary>
/// 选取所有友方角色,优先级大于 <see cref="CanSelectTargetCount"/>,默认包含自身
/// </summary>
public bool SelectAllTeammates { get; set; } = false;
/// <summary>
/// 可选取的作用目标数量
/// </summary>
public int CanSelectTargetCount { get; set; } = 1;
/// <summary>
/// 可选取的作用范围
/// 可选取的作用范围 [ 单位:格 ]
/// </summary>
public double CanSelectTargetRange { get; set; } = 0;
public int CanSelectTargetRange { get; set; } = 0;
/// <summary>
/// 普通攻击没有魔法消耗
@ -133,34 +233,104 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public double CurrentCD => 0;
/// <summary>
/// 游戏中的行动顺序表实例,使用时需要判断其是否存在
/// </summary>
public IGamingQueue? GamingQueue { get; set; } = null;
/// <summary>
/// 绑定到特效的普通攻击扩展。键为特效,值为对应的普攻扩展对象。
/// </summary>
public Dictionary<Effect, NormalAttackOfEffect> NormalAttackOfEffects { get; } = [];
/// <summary>
/// 获取可选择的目标列表
/// </summary>
/// <param name="caster"></param>
/// <param name="attacker"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public List<Character> GetSelectableTargets(Character caster, List<Character> enemys, List<Character> teammates)
public List<Character> GetSelectableTargets(Character attacker, List<Character> enemys, List<Character> teammates)
{
List<Character> selectable = [];
if (CanSelectSelf)
{
selectable.Add(caster);
selectable.Add(attacker);
}
if (CanSelectEnemy)
foreach (Character character in enemys)
{
selectable.AddRange(enemys);
if (CanSelectEnemy && ((character.ImmuneType & ImmuneType.All) != ImmuneType.All || IgnoreImmune == ImmuneType.All))
{
selectable.Add(character);
}
}
if (CanSelectTeammate)
foreach (Character character in teammates)
{
selectable.AddRange(teammates);
if (CanSelectTeammate)
{
selectable.Add(character);
}
}
return selectable;
}
/// <summary>
/// 实际可选取的目标数量
/// </summary>
public int RealCanSelectTargetCount(List<Character> enemys, List<Character> teammates)
{
int count = CanSelectTargetCount;
if (SelectAllTeammates)
{
return teammates.Count + 1;
}
if (SelectAllEnemies)
{
return enemys.Count;
}
return count;
}
/// <summary>
/// 选取普攻目标
/// </summary>
/// <param name="attacker"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public List<Character> SelectTargets(Character attacker, List<Character> enemys, List<Character> teammates)
{
List<Character> tobeSelected = GetSelectableTargets(attacker, enemys, teammates);
List<Character> targets = [];
if (SelectAllTeammates || SelectAllEnemies)
{
if (SelectAllTeammates)
{
targets.AddRange(tobeSelected.Where(c => c == attacker || teammates.Contains(c)));
}
if (SelectAllEnemies)
{
targets.AddRange(tobeSelected.Where(enemys.Contains));
}
}
else if (tobeSelected.Count <= CanSelectTargetCount)
{
targets.AddRange(tobeSelected);
}
else
{
targets.AddRange(tobeSelected.OrderBy(x => Random.Shared.Next()).Take(CanSelectTargetCount));
}
return [.. targets.Distinct()];
}
/// <summary>
/// 对目标(或多个目标)发起普通攻击
/// </summary>
@ -177,24 +347,91 @@ namespace Milimoe.FunGame.Core.Entity
{
if (enemy.HP > 0)
{
queue.WriteLine("[ " + Character + $" ] 对 [ {enemy} ] 发起了普通攻击!");
queue.WriteLine($"[ {Character} ] 对 [ {enemy} ] 发起了普通攻击!");
double expected = Damage;
int changeCount = 0;
DamageResult result = IsMagic ? queue.CalculateMagicalDamage(attacker, enemy, true, MagicType, expected, out double damage, ref changeCount) : queue.CalculatePhysicalDamage(attacker, enemy, true, expected, out damage, ref changeCount);
queue.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic, MagicType, result);
queue.DamageToEnemyAsync(attacker, enemy, damage, true, IsMagic ? DamageType.Magical : DamageType.Physical, MagicType, result);
}
}
}
/// <summary>
/// 修改伤害类型
/// 修改基础伤害类型。不一定转换成功,要看是否有特效覆盖
/// </summary>
/// <param name="isMagic"></param>
/// <param name="magicType"></param>
public void SetMagicType(bool isMagic, MagicType magicType)
/// <param name="queue"></param>
public void SetMagicType(bool? isMagic, MagicType? magicType = null, IGamingQueue? queue = null)
{
_IsMagic = isMagic;
_MagicType = magicType;
_ExIsMagic = isMagic;
if (isMagic.HasValue && isMagic.Value)
{
magicType ??= MagicType.None;
}
_ExMagicType = magicType;
ResolveMagicType(queue);
}
/// <summary>
/// 修改伤害类型。不一定转换成功,要看是否有其他特效覆盖
/// </summary>
/// <param name="naoe"></param>
/// <param name="queue"></param>
public void SetMagicType(NormalAttackOfEffect naoe, IGamingQueue? queue = null)
{
NormalAttackOfEffects[naoe.Effect] = naoe;
ResolveMagicType(queue);
}
/// <summary>
/// 移除特效对伤害类型的更改
/// </summary>
/// <param name="effect"></param>
/// <param name="queue"></param>
public void UnsetMagicType(Effect effect, IGamingQueue? queue = null)
{
NormalAttackOfEffects.Remove(effect);
ResolveMagicType(queue);
}
/// <summary>
/// 计算是否是魔法伤害和当前的魔法类型
/// </summary>
internal void ResolveMagicType(IGamingQueue? queue = null)
{
bool past = _IsMagic;
MagicType pastType = _MagicType;
if (NormalAttackOfEffects.Count > 0)
{
if (NormalAttackOfEffects.Values.OrderByDescending(n => n.Priority).FirstOrDefault() is NormalAttackOfEffect naoe)
{
_IsMagic = naoe.IsMagic;
_MagicType = naoe.MagicType;
}
}
else if (_ExIsMagic.HasValue && _ExMagicType.HasValue)
{
_IsMagic = _ExIsMagic.Value;
_MagicType = _ExMagicType.Value;
}
else
{
_IsMagic = false;
_MagicType = MagicType.None;
if (Character.EquipSlot.Weapon != null)
{
WeaponType type = Character.EquipSlot.Weapon.WeaponType;
if (type == WeaponType.Talisman || type == WeaponType.Staff)
{
_IsMagic = true;
}
}
}
if (queue != null && (past != _IsMagic || pastType != _MagicType))
{
queue.WriteLine($"[ {Character} ] 的普通攻击类型已转变为:{(_IsMagic ? CharacterSet.GetMagicDamageName(_MagicType) : "")}");
}
}
/// <summary>
@ -218,6 +455,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine($"{Name} - 等级 {Level}");
builder.AppendLine($"描述:{Description}");
if (GamingQueue?.Map != null) builder.AppendLine($"攻击距离:{Character.ATR}");
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"{HardnessTime}" : "")}");
return builder.ToString();
@ -235,13 +473,34 @@ namespace Milimoe.FunGame.Core.Entity
private int _Level = 0;
/// <summary>
/// 是否是魔法伤害
/// 是否是魔法伤害 [ 生效型 ]
/// </summary>
private bool _IsMagic = isMagic;
/// <summary>
/// 魔法类型
/// 魔法类型 [ 生效型 ]
/// </summary>
private MagicType _MagicType = magicType;
/// <summary>
/// 是否是魔法伤害 [ 修改型 ]
/// </summary>
private bool? _ExIsMagic = null;
/// <summary>
/// 魔法类型 [ 修改型 ]
/// </summary>
private MagicType? _ExMagicType = null;
}
/// <summary>
/// 绑定到特效的普通攻击扩展。这个类没有 JSON 转换器支持。
/// </summary>
public class NormalAttackOfEffect(Effect effect, bool isMagic, MagicType type, int priority)
{
public Effect Effect { get; set; } = effect;
public bool IsMagic { get; set; } = isMagic;
public MagicType MagicType { get; set; } = type;
public int Priority { get; set; } = priority;
}
}

View File

@ -44,6 +44,21 @@ namespace Milimoe.FunGame.Core.Entity
CanSelectTeammate = teammate;
}
break;
case "allenemy":
case "allenemys":
case "allenemies":
if (bool.TryParse(args[str].ToString(), out bool allenemy))
{
SelectAllEnemies = allenemy;
}
break;
case "allteammate":
case "allteammates":
if (bool.TryParse(args[str].ToString(), out bool allteammate))
{
SelectAllTeammates = allteammate;
}
break;
case "count":
if (int.TryParse(args[str].ToString(), out int count) && count > 0)
{
@ -56,6 +71,19 @@ namespace Milimoe.FunGame.Core.Entity
CanSelectTargetRange = range;
}
break;
case "nd":
case "nondirectional":
if (bool.TryParse(args[str].ToString(), out bool nondirectional))
{
IsNonDirectional = nondirectional;
}
break;
case "rangetype":
if (int.TryParse(args[str].ToString(), out int rangetype) && rangetype > 0)
{
SkillRangeType = (SkillRangeType)rangetype;
}
break;
case "mpcost":
if (double.TryParse(args[str].ToString(), out double mpcost) && mpcost > 0)
{
@ -69,12 +97,14 @@ namespace Milimoe.FunGame.Core.Entity
}
break;
case "costall":
case "costallep":
if (bool.TryParse(args[str].ToString(), out bool costall) && costall)
{
CostAllEP = costall;
}
break;
case "mincost":
case "mincostep":
if (double.TryParse(args[str].ToString(), out double mincost) && mincost > 0)
{
MinCostEP = mincost;
@ -87,12 +117,28 @@ namespace Milimoe.FunGame.Core.Entity
}
break;
case "cast":
case "casttime":
if (double.TryParse(args[str].ToString(), out double cast) && cast > 0)
{
CastTime = cast;
}
break;
case "cr":
case "castrange":
if (int.TryParse(args[str].ToString(), out int castrange) && castrange > 0)
{
CastRange = castrange;
}
break;
case "caw":
case "castanywhere":
if (bool.TryParse(args[str].ToString(), out bool castanywhere))
{
CastAnywhere = castanywhere;
}
break;
case "ht":
case "hardnesstime":
if (double.TryParse(args[str].ToString(), out double ht) && ht > 0)
{
HardnessTime = ht;

View File

@ -68,7 +68,6 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 是否是主动技能 [ 此项为高优先级 ]
/// </summary>
[InitRequired]
public bool IsActive => SkillType != SkillType.Passive;
/// <summary>
@ -84,15 +83,28 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 是否是爆发技 [ 此项为高优先级 ]
/// </summary>
[InitRequired]
public bool IsSuperSkill => SkillType == SkillType.SuperSkill;
/// <summary>
/// 是否属于魔法 [ <see cref="IsActive"/> 必须为 true ],反之为战技
/// </summary>
[InitRequired]
public bool IsMagic => SkillType == SkillType.Magic;
/// <summary>
/// 是否无视施法距离(全图施法),魔法默认为 true战技默认为 false
/// </summary>
public virtual bool CastAnywhere { get; set; } = false;
/// <summary>
/// 施法距离 [ 单位:格 ]
/// </summary>
[InitOptional]
public int CastRange
{
get => Math.Max(1, CastAnywhere ? (GamingQueue?.Map != null ? GamingQueue.Map.Grids.Count : 999) : _CastRange);
set => _CastRange = Math.Max(1, value);
}
/// <summary>
/// 可选取自身
/// </summary>
@ -108,15 +120,43 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary>
public virtual bool CanSelectTeammate { get; set; } = false;
/// <summary>
/// 选取所有敌对角色,优先级大于 <see cref="CanSelectTargetCount"/>
/// </summary>
public virtual bool SelectAllEnemies { get; set; } = false;
/// <summary>
/// 选取所有友方角色,优先级大于 <see cref="CanSelectTargetCount"/>,默认包含自身
/// </summary>
public virtual bool SelectAllTeammates { get; set; } = false;
/// <summary>
/// 可选取的作用目标数量
/// </summary>
public virtual int CanSelectTargetCount { get; set; } = 1;
/// <summary>
/// 可选取的作用范围
/// 可选取的作用范围 [ 单位:格 ]
/// </summary>
public virtual double CanSelectTargetRange { get; set; } = 0;
public virtual int CanSelectTargetRange { get; set; } = 0;
/// <summary>
/// 如果为 true表示非指向性技能可以任意选取一个范围<see cref="CanSelectTargetRange"/> = 0 时为单个格子)。<para/>
/// 如果为 false表示必须选取一个角色作为目标当 <see cref="CanSelectTargetRange"/> > 0 时,技能作用范围将根据目标位置覆盖 <see cref="SkillRangeType"/> 形状的区域;= 0 时正常选取目标。
/// </summary>
public virtual bool IsNonDirectional { get; set; } = false;
/// <summary>
/// 作用范围形状<para/>
/// <see cref="SkillRangeType.Diamond"/> - 菱形。默认的曼哈顿距离正方形<para/>
/// <see cref="SkillRangeType.Circle"/> - 圆形。基于欧几里得距离的圆形<para/>
/// <see cref="SkillRangeType.Square"/> - 正方形<para/>
/// <see cref="SkillRangeType.Line"/> - 施法者与目标之前的直线<para/>
/// <see cref="SkillRangeType.LinePass"/> - 施法者与目标所在的直线,贯穿至地图边缘<para/>
/// <see cref="SkillRangeType.Sector"/> - 扇形<para/>
/// 注意,该属性不影响选取目标的范围。选取目标的范围由 <see cref="Library.Common.Addon.GameMap"/> 决定。
/// </summary>
public virtual SkillRangeType SkillRangeType { get; set; } = SkillRangeType.Diamond;
/// <summary>
/// 选取角色的条件
@ -198,10 +238,20 @@ namespace Milimoe.FunGame.Core.Entity
[InitRequired]
public virtual double HardnessTime { get; set; } = 0;
/// <summary>
/// 额外硬直时间 [ 技能和物品相关 ]
/// </summary>
public double ExHardnessTime { get; set; } = 0;
/// <summary>
/// 额外硬直时间% [ 技能和物品相关 ]
/// </summary>
public double ExHardnessTime2 { get; set; } = 0;
/// <summary>
/// 实际硬直时间
/// </summary>
public double RealHardnessTime => Math.Max(0, HardnessTime * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0)));
public double RealHardnessTime => Math.Max(0, (HardnessTime + ExHardnessTime) * (1 + ExHardnessTime2) * (1 - Calculation.PercentageCheck(Character?.ActionCoefficient ?? 0)));
/// <summary>
/// 效果列表
@ -231,6 +281,7 @@ namespace Milimoe.FunGame.Core.Entity
protected Skill(SkillType type, Character? character = null)
{
SkillType = type;
CastAnywhere = SkillType == SkillType.Magic;
Character = character;
}
@ -268,6 +319,14 @@ namespace Milimoe.FunGame.Core.Entity
}
}
}
if (Level > 0 && Character != null)
{
Effect[] effects = [.. Character.Effects.Where(e => e.IsInEffect)];
foreach (Effect e in effects)
{
e.OnSkillLevelUp(Character, Level);
}
}
}
/// <summary>
@ -309,19 +368,54 @@ namespace Milimoe.FunGame.Core.Entity
selectable.Add(caster);
}
if (CanSelectEnemy)
ImmuneType checkType = ImmuneType.Skilled | ImmuneType.All;
if (IsMagic)
{
selectable.AddRange(enemys);
checkType |= ImmuneType.Magical;
}
if (CanSelectTeammate)
foreach (Character character in enemys)
{
selectable.AddRange(teammates);
IEnumerable<Effect> effects = character.Effects.Where(e => e.IsInEffect);
if (CanSelectEnemy && ((character.ImmuneType & checkType) == ImmuneType.None ||
effects.Any(e => e.IgnoreImmune == ImmuneType.All || e.IgnoreImmune == ImmuneType.Skilled || (IsMagic && e.IgnoreImmune == ImmuneType.Magical))))
{
selectable.Add(character);
}
}
foreach (Character character in teammates)
{
IEnumerable<Effect> effects = character.Effects.Where(e => e.IsInEffect);
if (CanSelectTeammate)
{
selectable.Add(character);
}
}
// 其他条件
selectable = [.. selectable.Where(c => SelectTargetPredicates.All(f => f(c)))];
return selectable;
}
/// <summary>
/// 实际可选取的目标数量
/// </summary>
public int RealCanSelectTargetCount(List<Character> enemys, List<Character> teammates)
{
int count = CanSelectTargetCount;
if (SelectAllTeammates)
{
return teammates.Count + 1;
}
if (SelectAllEnemies)
{
return enemys.Count;
}
return count;
}
/// <summary>
/// 选取技能目标
/// </summary>
@ -333,12 +427,20 @@ namespace Milimoe.FunGame.Core.Entity
{
List<Character> tobeSelected = GetSelectableTargets(caster, enemys, teammates);
// 筛选出符合条件的角色
tobeSelected = [.. tobeSelected.Where(c => SelectTargetPredicates.All(f => f(c)))];
List<Character> targets = [];
if (tobeSelected.Count <= CanSelectTargetCount)
if (SelectAllTeammates || SelectAllEnemies)
{
if (SelectAllTeammates)
{
targets.AddRange(tobeSelected.Where(c => c == caster || teammates.Contains(c)));
}
if (SelectAllEnemies)
{
targets.AddRange(tobeSelected.Where(enemys.Contains));
}
}
else if (tobeSelected.Count <= CanSelectTargetCount)
{
targets.AddRange(tobeSelected);
}
@ -394,12 +496,13 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary>
/// 对目标触发技能效果
/// </summary>
/// <param name="user"></param>
/// <param name="targets"></param>
public void OnSkillCasted(List<Character> targets)
public void OnSkillCasted(User user, List<Character> targets)
{
foreach (Effect e in Effects)
{
e.OnSkillCasted(targets, Values);
e.OnSkillCasted(user, targets, Values);
}
}
@ -426,8 +529,10 @@ namespace Milimoe.FunGame.Core.Entity
/// 返回技能的详细说明
/// </summary>
/// <param name="showOriginal"></param>
/// <param name="showCD"></param>
/// <param name="showHardness"></param>
/// <returns></returns>
public string GetInfo(bool showOriginal = false)
public string GetInfo(bool showOriginal = false, bool showCD = true, bool showHardness = true)
{
StringBuilder builder = new();
@ -451,40 +556,50 @@ namespace Milimoe.FunGame.Core.Entity
{
builder.AppendLine($"{DispelDescription}");
}
if (GamingQueue?.Map != null && SkillType != SkillType.Passive)
{
builder.AppendLine($"施法距离:{(CastAnywhere ? "" : CastRange)}");
}
if (IsActive && (Item?.IsInGameItem ?? true))
{
if (SkillType == SkillType.Item)
{
if (RealMPCost > 0)
{
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"{MPCost}" : "")}");
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"{MPCost:0.##}" : "")}");
}
if (RealEPCost > 0)
{
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost}" : "")}");
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost:0.##}" : "")}");
}
}
else
{
if (IsSuperSkill)
{
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost}" : "")}");
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost:0.##}" : "")}");
}
else
{
if (IsMagic)
{
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"{MPCost}" : "")}");
builder.AppendLine($"吟唱时间:{RealCastTime:0.##}{(showOriginal && RealCastTime != CastTime ? $"{CastTime}" : "")}");
builder.AppendLine($"魔法消耗:{RealMPCost:0.##}{(showOriginal && RealMPCost != MPCost ? $"{MPCost:0.##}" : "")}");
builder.AppendLine($"吟唱时间:{RealCastTime:0.##}{(showOriginal && RealCastTime != CastTime ? $"{CastTime:0.##}" : "")}");
}
else
{
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost}" : "")}");
builder.AppendLine($"能量消耗:{RealEPCost:0.##}{(showOriginal && RealEPCost != EPCost ? $"{EPCost:0.##}" : "")}");
}
}
}
builder.AppendLine($"冷却时间:{RealCD:0.##}{(showOriginal && RealCD != CD ? $"{CD}" : "")}");
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"{HardnessTime}" : "")}");
if (showCD && CD > 0)
{
builder.AppendLine($"冷却时间:{RealCD:0.##}{(showOriginal && RealCD != CD ? $"{CD:0.##}" : "")}");
}
if (showHardness && HardnessTime > 0)
{
builder.AppendLine($"硬直时间:{RealHardnessTime:0.##}{(showOriginal && RealHardnessTime != HardnessTime ? $"{HardnessTime:0.##}" : "")}");
}
}
return builder.ToString();
@ -496,6 +611,18 @@ namespace Milimoe.FunGame.Core.Entity
/// <returns></returns>
public override string ToString() => GetInfo(true);
/// <summary>
/// 返回技能的详细说明,有选项
/// </summary>
/// <param name="showOriginal"></param>
/// <param name="showCD"></param>
/// <param name="showHardness"></param>
/// <returns></returns>
public string ToString(bool showOriginal, bool showCD, bool showHardness)
{
return GetInfo(showOriginal, showCD, showHardness);
}
/// <summary>
/// 判断两个技能是否相同 检查Id.Name
/// </summary>
@ -560,5 +687,10 @@ namespace Milimoe.FunGame.Core.Entity
/// 等级
/// </summary>
private int _Level = 0;
/// <summary>
/// 施法距离
/// </summary>
private int _CastRange = 3;
}
}

View File

@ -5,21 +5,23 @@
public double TotalDamage { get; set; } = 0;
public double TotalPhysicalDamage { get; set; } = 0;
public double TotalMagicDamage { get; set; } = 0;
public double TotalRealDamage { get; set; } = 0;
public double TotalTrueDamage { get; set; } = 0;
public double TotalTakenDamage { get; set; } = 0;
public double TotalTakenPhysicalDamage { get; set; } = 0;
public double TotalTakenMagicDamage { get; set; } = 0;
public double TotalTakenRealDamage { get; set; } = 0;
public double TotalTakenTrueDamage { get; set; } = 0;
public double AvgDamage { get; set; } = 0;
public double AvgPhysicalDamage { get; set; } = 0;
public double AvgMagicDamage { get; set; } = 0;
public double AvgRealDamage { get; set; } = 0;
public double AvgTrueDamage { get; set; } = 0;
public double AvgTakenDamage { get; set; } = 0;
public double AvgTakenPhysicalDamage { get; set; } = 0;
public double AvgTakenMagicDamage { get; set; } = 0;
public double AvgTakenRealDamage { get; set; } = 0;
public double AvgTakenTrueDamage { get; set; } = 0;
public double TotalHeal { get; set; } = 0;
public double AvgHeal { get; set; } = 0;
public double TotalShield { get; set; } = 0;
public double AvgShield { get; set; } = 0;
public int LiveRound { get; set; } = 0;
public int AvgLiveRound { get; set; } = 0;
public int ActionTurn { get; set; } = 0;

View File

@ -1,15 +1,15 @@
namespace Milimoe.FunGame.Core.Entity
{
public class GameStatistics
public class GameStatistics(Room Room)
{
public long Id => Room.Id;
public Room Room { get; }
public Room Room { get; } = Room;
public DateTime RecordTime { get; set; } = DateTime.Now;
public string Record { get; set; } = "";
public Dictionary<User, double> DamageStats { get; set; } = new();
public Dictionary<User, double> PhysicalDamageStats { get; } = new();
public Dictionary<User, double> MagicDamageStats { get; } = new();
public Dictionary<User, double> RealDamageStats { get; } = new();
public Dictionary<User, double> DamageStats { get; set; } = [];
public Dictionary<User, double> PhysicalDamageStats { get; } = [];
public Dictionary<User, double> MagicDamageStats { get; } = [];
public Dictionary<User, double> TrueDamageStats { get; } = [];
public double AvgDamageStats
{
get
@ -46,30 +46,25 @@
return Math.Round(total / MagicDamageStats.Count, 2);
}
}
public double AvgRealDamageStats
public double AvgTrueDamageStats
{
get
{
double total = 0;
foreach (User user in RealDamageStats.Keys)
foreach (User user in TrueDamageStats.Keys)
{
total += RealDamageStats[user];
total += TrueDamageStats[user];
}
return Math.Round(total / RealDamageStats.Count, 2);
return Math.Round(total / TrueDamageStats.Count, 2);
}
}
public Dictionary<User, double> KillStats { get; } = new();
public Dictionary<User, Dictionary<User, int>> KillDetailStats { get; } = new(); // 子字典记录的是被击杀者以及被击杀次数
public Dictionary<User, double> DeathStats { get; } = new();
public Dictionary<User, Dictionary<User, int>> DeathDetailStats { get; } = new(); // 子字典记录的是击杀者以及击杀次数
public Dictionary<User, long> AssistStats { get; } = new();
public Dictionary<User, double> RatingStats { get; } = new(); // 结算后的Rating
public Dictionary<User, double> EloStats { get; } = new(); // Elo分数变化(+/-)
public Dictionary<User, string> RankStats { get; } = new(); // 结算后的Rank非比赛前
public GameStatistics(Room Room)
{
this.Room = Room;
}
public Dictionary<User, double> KillStats { get; } = [];
public Dictionary<User, Dictionary<User, int>> KillDetailStats { get; } = []; // 子字典记录的是被击杀者以及被击杀次数
public Dictionary<User, double> DeathStats { get; } = [];
public Dictionary<User, Dictionary<User, int>> DeathDetailStats { get; } = []; // 子字典记录的是击杀者以及击杀次数
public Dictionary<User, long> AssistStats { get; } = [];
public Dictionary<User, double> RatingStats { get; } = []; // 结算后的Rating
public Dictionary<User, double> EloStats { get; } = []; // Elo分数变化(+/-)
public Dictionary<User, string> RankStats { get; } = []; // 结算后的Rank非比赛前
}
}

View File

@ -11,7 +11,7 @@ namespace Milimoe.FunGame.Core.Entity
public Dictionary<long, double> DamageStats { get; } = [];
public Dictionary<long, double> PhysicalDamageStats { get; } = [];
public Dictionary<long, double> MagicDamageStats { get; } = [];
public Dictionary<long, double> RealDamageStats { get; } = [];
public Dictionary<long, double> TrueDamageStats { get; } = [];
public Dictionary<long, double> AvgDamageStats
{
get
@ -66,7 +66,7 @@ namespace Milimoe.FunGame.Core.Entity
return avgdamage;
}
}
public Dictionary<long, double> AvgRealDamageStats
public Dictionary<long, double> AvgTrueDamageStats
{
get
{
@ -75,9 +75,9 @@ namespace Milimoe.FunGame.Core.Entity
{
long plays = Plays[key];
double total = 0;
if (RealDamageStats.ContainsKey(key))
if (TrueDamageStats.ContainsKey(key))
{
total = RealDamageStats.Values.Sum();
total = TrueDamageStats.Values.Sum();
}
avgdamage.Add(key, Math.Round(total / plays, 2));
}

View File

@ -1,122 +0,0 @@
using System.Text;
using Milimoe.FunGame.Core.Library.Common.Event;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Activity(long id, string name, DateTime startTime, DateTime endTime)
{
public long Id { get; set; } = id;
public string Name { get; set; } = name;
public DateTime StartTime { get; set; } = startTime;
public DateTime EndTime { get; set; } = endTime;
public ActivityState Status { get; private set; } = ActivityState.Future;
public HashSet<Quest> Quests { get; set; } = [];
// 事件
public event Action<ActivityEventArgs>? UserAccess;
public event Action<ActivityEventArgs>? UserGetActivityInfo;
public void UnRegisterUserAccess()
{
UserAccess = null;
}
public void UnRegisterUserGetActivityInfo()
{
UserGetActivityInfo = null;
}
public void UpdateState()
{
ActivityState newState;
DateTime now = DateTime.Now;
DateTime upComingTime = StartTime.AddHours(-6);
if (now < upComingTime)
{
newState = ActivityState.Future;
}
else if (now >= upComingTime && now < StartTime)
{
newState = ActivityState.Upcoming;
}
else if (now >= StartTime && now < EndTime)
{
newState = ActivityState.InProgress;
}
else
{
newState = ActivityState.Ended;
}
if (Status != newState)
{
Status = newState;
foreach (Quest quest in Quests)
{
if (newState == ActivityState.InProgress)
{
if (quest.Status == QuestState.NotStarted && quest.QuestType == QuestType.Progressive)
{
quest.Status = QuestState.InProgress;
}
}
else if (newState == ActivityState.Ended)
{
if (quest.Status == QuestState.NotStarted || quest.Status == QuestState.InProgress)
{
quest.Status = QuestState.Missed;
}
}
}
}
}
public bool AllowUserAccess(long userId, long questId = 0)
{
UpdateState();
ActivityEventArgs args = new(userId, questId, this);
UserAccess?.Invoke(args);
return args.AllowAccess;
}
public void GetActivityInfo(long userId, long questId = 0)
{
UpdateState();
ActivityEventArgs args = new(userId, questId, this);
UserGetActivityInfo?.Invoke(args);
}
public string ToString(bool showQuests)
{
UpdateState();
StringBuilder builder = new();
builder.AppendLine($"☆--- [{Name}] ---☆");
string status = Status switch
{
ActivityState.Future => "预告中",
ActivityState.Upcoming => "即将开始",
ActivityState.InProgress => "进行中",
_ => "已结束"
};
builder.AppendLine($"活动状态:{status}");
builder.AppendLine($"开始时间:{StartTime.ToString(General.GeneralDateTimeFormatChinese)}");
builder.AppendLine($"结束时间:{EndTime.ToString(General.GeneralDateTimeFormatChinese)}");
if (showQuests && Quests.Count > 0)
{
builder.AppendLine("=== 任务列表 ===");
builder.AppendLine(string.Join("\r\n", Quests));
}
return builder.ToString().Trim();
}
public override string ToString()
{
return ToString(true);
}
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
@ -10,12 +11,19 @@ namespace Milimoe.FunGame.Core.Entity
public bool IsNeedApproval { get; set; } = false;
public bool IsPublic { get; set; } = false;
public double ClubPoins { get; set; } = 0;
public User? Master { get; set; }
public User Master { get; set; } = General.UnknownUserInstance;
public Dictionary<long, User> Admins { get; set; } = [];
public Dictionary<long, User> Members { get; set; } = [];
public Dictionary<long, User> Applicants { get; set; } = [];
public Dictionary<long, User> Invitees { get; set; } = [];
public Dictionary<long, DateTime> MemberJoinTime { get; set; } = [];
public Dictionary<long, DateTime> ApplicationTime { get; set; } = [];
public Dictionary<long, DateTime> InvitedTime { get; set; } = [];
public override string ToString()
{
return $"{Name} [{Prefix}]";
}
public override bool Equals(IBaseEntity? other)
{

View File

@ -42,6 +42,11 @@ namespace Milimoe.FunGame.Core.Entity
Statistics = new(this);
}
public override string ToString()
{
return $"[ {Roomid} ] {Name}";
}
public override bool Equals(IBaseEntity? other)
{
return other is Room r && r.Roomid == Roomid;

View File

@ -1,110 +0,0 @@
using System.Text;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Store : BaseEntity
{
public User User { get; set; } = General.UnknownUserInstance;
public DateTime? StartTime { get; set; } = null;
public DateTime? EndTime { get; set; } = null;
public Dictionary<long, Goods> Goods { get; } = [];
public Store(string name, User? user = null)
{
Name = name;
if (user != null)
{
User = user;
}
}
public override string ToString()
{
StringBuilder builder = new();
builder.AppendLine($"☆★☆ {Name} ☆★☆");
if (StartTime.HasValue && EndTime.HasValue)
{
builder.AppendLine($"营业时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}至{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else if (StartTime.HasValue && !EndTime.HasValue)
{
builder.AppendLine($"开始营业时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else if (!StartTime.HasValue && EndTime.HasValue)
{
builder.AppendLine($"停止营业时间:{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else
{
builder.AppendLine($"[ 24H ] 全年无休,永久开放");
}
builder.AppendLine($"☆--- 商品列表 ---☆");
foreach (Goods goods in Goods.Values)
{
builder.AppendLine(goods.ToString());
}
return builder.ToString().Trim();
}
public void AddItem(Item item, int stock, string name = "", string description = "")
{
long id = Goods.Count > 0 ? Goods.Keys.Max() + 1 : 1;
if (name.Trim() == "")
{
name = item.Name;
}
if (description.Trim() == "")
{
description = item.Description;
}
Goods goods = new(id, item, stock, name, description);
goods.SetPrice(GameplayEquilibriumConstant.InGameCurrency, item.Price);
Goods.Add(id, goods);
}
public void AddItems(IEnumerable<Item> items, int stock)
{
foreach (Item item in items)
{
AddItem(item, stock);
}
}
public void SetPrice(long id, string needy, double price)
{
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
goods.SetPrice(needy, price);
}
}
public bool GetPrice(long id, string needy, out double price)
{
price = 0;
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
return goods.GetPrice(needy, out price);
}
return false;
}
public double GetPrice(long id)
{
double price = 0;
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
goods.GetPrice(GameplayEquilibriumConstant.InGameCurrency, out price);
}
return price;
}
public override bool Equals(IBaseEntity? other)
{
return other is Store && other.GetIdName() == GetIdName();
}
}
}

View File

@ -1,19 +1,17 @@
using Milimoe.FunGame.Core.Interface.Base;
namespace Milimoe.FunGame.Core.Entity
namespace Milimoe.FunGame.Core.Entity
{
public class Team(string name, IEnumerable<Character> charaters)
{
public Guid Id { get; set; } = Guid.Empty;
public string Name { get; set; } = name;
public List<Character> Members { get; } = new(charaters);
public List<Character> Members { get; } = [.. charaters];
public int Score { get; set; } = 0;
public bool IsWinner { get; set; } = false;
public int Count => Members.Count;
public List<Character> GetActiveCharacters(IGamingQueue queue)
public List<Character> GetActiveCharacters()
{
return [.. Members.Where(queue.Queue.Contains)];
return [.. Members.Where(c => c.HP > 0)];
}
public List<Character> GetTeammates(Character character)
@ -21,9 +19,9 @@ namespace Milimoe.FunGame.Core.Entity
return [.. Members.Where(c => c != character)];
}
public List<Character> GetActiveTeammates(IGamingQueue queue, Character character)
public List<Character> GetActiveTeammates(Character character)
{
return [.. Members.Where(c => queue.Queue.Contains(c) && c != character)];
return [.. Members.Where(c => c.HP > 0 && c != character)];
}
public bool IsOnThisTeam(Character character)

View File

@ -11,37 +11,57 @@ namespace Milimoe.FunGame.Core.Entity
public string Description { get; set; } = "";
public Dictionary<string, double> Prices { get; } = [];
public int Stock { get; set; }
public int Quota { get; set; }
public Dictionary<long, int> UsersBuyCount { get; } = [];
public DateTime? ExpireTime { get; set; } = null;
public Goods() { }
public Goods(long id, Item item, int stock, string name, string description, Dictionary<string, double>? prices = null)
public Goods(long id, Item item, int stock, string name, string description, Dictionary<string, double>? prices = null, int quota = 0)
{
Id = id;
Items.Add(item);
Stock = stock;
Quota = quota;
Name = name;
Description = description;
if (prices != null) Prices = prices;
}
public Goods(long id, List<Item> items, int stock, string name, string description, Dictionary<string, double>? prices = null)
public Goods(long id, List<Item> items, int stock, string name, string description, Dictionary<string, double>? prices = null, int quota = 0)
{
Id = id;
Items = items;
Stock = stock;
Quota = quota;
Name = name;
Description = description;
if (prices != null) Prices = prices;
}
public override string ToString()
{
return ToString(null);
}
public string ToString(User? user = null)
{
StringBuilder builder = new();
builder.AppendLine($"{Id}. {Name}");
if (ExpireTime.HasValue) builder.AppendLine($"限时购买:{ExpireTime.Value.ToString(General.GeneralDateTimeFormatChinese)} 截止");
builder.AppendLine($"商品描述:{Description}");
builder.AppendLine($"商品售价:{(Prices.Count > 0 ? string.Join("", Prices.Select(kv => $"{kv.Value} {kv.Key}")) : "")}");
builder.AppendLine($"包含物品:{string.Join("", Items.Select(i => $"[{ItemSet.GetQualityTypeName(i.QualityType)}|{ItemSet.GetItemTypeName(i.ItemType)}] {i.Name}"))}");
builder.AppendLine($"剩余库存:{Stock}");
int buyCount = 0;
if (user != null)
{
UsersBuyCount.TryGetValue(user.Id, out buyCount);
}
builder.AppendLine($"剩余库存:{(Stock == -1 ? "" : Stock)}(已购:{buyCount}");
if (Quota > 0)
{
builder.AppendLine($"限购数量:{Quota}");
}
return builder.ToString().Trim();
}

67
Entity/Trade/Market.cs Normal file
View File

@ -0,0 +1,67 @@
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Market : BaseEntity
{
public string Description { get; set; } = "";
public DateTime? StartTime { get; set; } = null;
public DateTime? EndTime { get; set; } = null;
public DateTime? StartTimeOfDay { get; set; } = null;
public DateTime? EndTimeOfDay { get; set; } = null;
public Dictionary<long, MarketItem> MarketItems { get; } = [];
public Market(string name)
{
Name = name;
}
public override string ToString()
{
return Name;
}
public void AddItem(User user, Item item, double price, int stock, string name = "")
{
if (MarketItems.Values.FirstOrDefault(m => m.Item.Id == item.Id && m.Item.Name == item.Name && m.Price == price && m.User == user.Id && m.Status == MarketItemState.Listed) is MarketItem marketItem)
{
marketItem.Stock += stock;
marketItem.Item.Price = (marketItem.Item.Price + item.Price) / marketItem.Stock;
}
else
{
long id = MarketItems.Count > 0 ? MarketItems.Keys.Max() + 1 : 1;
if (name.Trim() == "")
{
name = item.Name;
}
marketItem = new()
{
Id = id,
User = user.Id,
Username = user.Username,
Item = item,
Price = price,
Stock = stock,
Name = name
};
MarketItems.Add(id, marketItem);
}
}
public void AddItems(User user, Item[] items, double price)
{
for (int index = 0; index < items.Length; index++)
{
Item item = items[index];
AddItem(user, item, price, 1);
}
}
public override bool Equals(IBaseEntity? other)
{
return other is Market && other.GetIdName() == GetIdName();
}
}
}

View File

@ -6,13 +6,20 @@ namespace Milimoe.FunGame.Core.Entity
{
public class MarketItem : BaseEntity
{
public User User { get; set; }
public long User { get; set; } = 0;
public string Username { get; set; } = "";
public Item Item { get; set; }
public double Price { get; set; } = 0;
public int Stock { get; set; } = 0;
public DateTime CreateTime { get; set; } = DateTime.Now;
public DateTime? FinishTime { get; set; } = null;
public MarketItemState Status { get; set; } = MarketItemState.Listed;
public User? Buyer { get; set; } = null;
public HashSet<long> Buyers { get; set; } = [];
public override string ToString()
{
return Item.Name;
}
public override bool Equals(IBaseEntity? other)
{
@ -21,7 +28,6 @@ namespace Milimoe.FunGame.Core.Entity
public MarketItem()
{
User = Factory.GetUser();
Item = Factory.GetItem();
}
}

200
Entity/Trade/Store.cs Normal file
View File

@ -0,0 +1,200 @@
using System.Text;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity
{
public class Store : BaseEntity
{
public User User { get; set; } = General.UnknownUserInstance;
public string Description { get; set; } = "";
public DateTime? StartTime { get; set; } = null;
public DateTime? EndTime { get; set; } = null;
public DateTime? StartTimeOfDay { get; set; } = null;
public DateTime? EndTimeOfDay { get; set; } = null;
public Dictionary<long, Goods> Goods { get; } = [];
public bool AutoRefresh { get; set; } = false;
public DateTime NextRefreshDate { get; set; } = DateTime.MinValue;
public Dictionary<long, Goods> NextRefreshGoods { get; } = [];
public int RefreshInterval { get; set; } = 1; // Days
public bool GetNewerGoodsOnVisiting { get; set; } = false;
public bool GlobalStock { get; set; } = false;
public DateTime? ExpireTime { get; set; } = null;
public Store(string name, User? user = null)
{
Name = name;
if (user != null)
{
User = user;
}
}
public override string ToString()
{
return ToString(null);
}
public string ToString(User? user = null)
{
StringBuilder builder = new();
builder.AppendLine($"☆★☆ {Name} ☆★☆");
if (Description != "") builder.AppendLine($"{Description}");
if (StartTime.HasValue && EndTime.HasValue)
{
builder.AppendLine($"开放时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)} 至 {EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else if (StartTime.HasValue && !EndTime.HasValue)
{
builder.AppendLine($"开始开放时间:{StartTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else if (!StartTime.HasValue && EndTime.HasValue)
{
builder.AppendLine($"停止开放时间:{EndTime.Value.ToString(General.GeneralDateTimeFormatChinese)}");
}
else
{
builder.AppendLine($"开放时间:全年无休,永久开放");
}
if (StartTimeOfDay.HasValue && EndTimeOfDay.HasValue)
{
builder.AppendLine($"每日营业时间:{StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)} 至 {EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormatTimeOnly)}");
}
else
{
builder.AppendLine($"[ 24H ] 全天营业");
}
DateTime now = DateTime.Now;
TimeSpan nowTimeOfDay = now.TimeOfDay;
bool isStoreOpen = true;
bool isStoreOpenInDate = true;
if (StartTime.HasValue && StartTime.Value > now || EndTime.HasValue && EndTime.Value < now)
{
isStoreOpen = false;
isStoreOpenInDate = false;
}
if (isStoreOpen && StartTimeOfDay.HasValue && EndTimeOfDay.HasValue)
{
TimeSpan startTimeSpan = StartTimeOfDay.Value.TimeOfDay;
TimeSpan endTimeSpan = EndTimeOfDay.Value.TimeOfDay;
if (startTimeSpan <= endTimeSpan)
{
isStoreOpen = nowTimeOfDay >= startTimeSpan && nowTimeOfDay <= endTimeSpan;
}
else
{
isStoreOpen = nowTimeOfDay >= startTimeSpan || nowTimeOfDay <= endTimeSpan;
}
}
if (!isStoreOpen)
{
builder.AppendLine($"商店现在不在营业时间内。");
}
builder.AppendLine($"☆--- 商品列表 ---☆");
Goods[] goodsValid = [.. Goods.Values.Where(g => !g.ExpireTime.HasValue || g.ExpireTime.Value > DateTime.Now)];
if (!isStoreOpen || goodsValid.Length == 0)
{
builder.AppendLine("当前没有商品可供购买,过一段时间再来吧。");
}
else
{
foreach (Goods goods in goodsValid)
{
builder.AppendLine(goods.ToString(user));
}
builder.AppendLine("提示:使用【商店查看+序号】查看商品详细信息,使用【商店购买+序号】购买商品(指令在 2 分钟内可用)。");
}
if (isStoreOpenInDate && AutoRefresh)
{
builder.AppendLine($"商品将在 {NextRefreshDate.ToString(General.GeneralDateTimeFormatChinese)} 刷新。");
}
return builder.ToString().Trim();
}
public void AddItem(Item item, int stock, string name = "", string description = "")
{
long id = Goods.Count > 0 ? Goods.Keys.Max() + 1 : 1;
if (name.Trim() == "")
{
name = item.Name;
}
if (description.Trim() == "")
{
description = item.Description;
}
Goods goods = new(id, item, stock, name, description);
goods.SetPrice(GameplayEquilibriumConstant.InGameCurrency, item.Price);
Goods.Add(id, goods);
}
public void AddItems(IEnumerable<Item> items, int stock)
{
foreach (Item item in items)
{
AddItem(item, stock);
}
}
public void SetPrice(long id, string needy, double price)
{
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
goods.SetPrice(needy, price);
}
}
public bool GetPrice(long id, string needy, out double price)
{
price = 0;
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
return goods.GetPrice(needy, out price);
}
return false;
}
public double GetPrice(long id)
{
double price = 0;
if (Goods.TryGetValue(id, out Goods? goods) && goods != null)
{
goods.GetPrice(GameplayEquilibriumConstant.InGameCurrency, out price);
}
return price;
}
public void UpdateRefreshTime(DateTime? time = null)
{
if (AutoRefresh)
{
DateTime now = DateTime.Now;
time ??= NextRefreshDate;
if (now > time)
{
NextRefreshDate = time.Value.AddDays(RefreshInterval);
}
}
else
{
NextRefreshDate = DateTime.MinValue;
}
}
public void CopyGoodsToNextRefreshGoods(Dictionary<long, Goods>? goods = null)
{
goods ??= Goods;
NextRefreshGoods.Clear();
foreach (long goodsId in goods.Keys)
{
NextRefreshGoods.Add(goodsId, goods[goodsId]);
}
}
public override bool Equals(IBaseEntity? other)
{
return other is Store && other.GetIdName() == GetIdName();
}
}
}

View File

@ -7,6 +7,7 @@ namespace Milimoe.FunGame.Core.Entity
public class User : BaseEntity
{
public static readonly User Empty = new();
public override string Name => Username;
public string Username { get; set; } = "";
public DateTime RegTime { get; set; }
public DateTime LastTime { get; set; }

View File

@ -1,64 +1,54 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>bin\</BaseOutputPath>
<Company>$(Author)</Company>
<Authors>Project Redbud</Authors>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<PackageOutputPath>bin</PackageOutputPath>
<Title>FunGame Core</Title>
<RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Project Redbud and Contributors</Copyright>
<PackageId>$(AssemblyName)</PackageId>
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>bin\</BaseOutputPath>
<Company>$(Author)</Company>
<Authors>Project Redbud</Authors>
<AssemblyVersion>2.0.0</AssemblyVersion>
<FileVersion>2.0.0</FileVersion>
<PackageOutputPath>bin</PackageOutputPath>
<Title>FunGame Core</Title>
<RootNamespace>Milimoe.$(MSBuildProjectName.Replace(" ", "_"))</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyName>$(MSBuildProjectName)</AssemblyName>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>©2023-Present Project Redbud and Contributors.</Copyright>
<PackageId>$(AssemblyName)</PackageId>
<Description>FunGame.Core: A C#.NET library for turn-based games.</Description>
<PackageTags>game;turn-based;server;framework;dotnet;csharp;gamedev</PackageTags>
<PackageReleaseNotes>
- Initial release candidate 1 (1.0.0-rc.1-0428)
- Abstract ActionQueue as GamingQueue, and separate the original Mix / Team modes into two queue types: MixGamingQueue and TeamGamingQueue. (1.0.0-rc.1-0502)
- In the Effect class, added ParentEffect and ForceHideInStatusBar properties for more precise control of the status bar display. (1.0.0-rc.1-0509)
- Added helper methods IsTeammate and GetIsTeammateDictionary to GamingQueue for determining if someone is a teammate, IGamingQueue also. This facilitates the skill effects to determine whether the target is a teammate. (1.0.0-rc.1-0509)
- Added more properties (such as Name, RealCD) to the ISkill interface. Both NormalAttack and Skill inherit from ISkill, thus implementing these properties, although some properties are not meaningful for NormalAttack. (1.0.0-rc.1-0509)
- Added corresponding text for EffectTypes Lifesteal and GrievousWound. (1.0.0-rc.1-0509)
- Added EffectTypes: WeakDispelling and StrongDispelling, representing DurativeWeak and DurativeStrong of DispelType. (1.0.0-rc.1-0509)
- Added underlying processing support for continuous dispelling in the TimeLapse method. (1.0.0-rc.1-0509)
- Fixed an issue where the effect's shield hook provided incorrect parameters. (1.0.0-rc.1-0509)
- Fixed an issue where the result of pre-hooks for evade and critical hit checks always deferred to the result of the last effect. It should be that if any effect prevents the check, it is skipped. (1.0.0-rc.1-0509)
- Added comments for some code. (1.0.0-rc.1-0509)
See github releases for details on the latest changes.
</PackageReleaseNotes>
<RepositoryUrl>https://github.com/project-redbud/FunGame-Core</RepositoryUrl>
<PackageProjectUrl>https://github.com/project-redbud</PackageProjectUrl>
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageReadmeFile>README.md</PackageReadmeFile>
<VersionPrefix>1.0.0-rc.1</VersionPrefix>
<PackageProjectUrl>https://github.com/project-redbud</PackageProjectUrl>
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageReadmeFile>README.md</PackageReadmeFile>
<VersionPrefix>2.0.0-dev</VersionPrefix>
<VersionSuffix Condition="'$(VersionSuffix)' == ''">$([System.DateTime]::Now.ToString("MMdd"))</VersionSuffix>
</PropertyGroup>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DebugType>embedded</DebugType>
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
<NoWarn>1701;1702;CS1591;CS1587;IDE0130</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
@ -24,6 +25,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// </summary>
public Dictionary<Guid, Character> Original { get; }
/// <summary>
/// 参与本次游戏的所有角色列表
/// </summary>
public List<Character> AllCharacters { get; }
/// <summary>
/// 当前的行动顺序
/// </summary>
@ -60,6 +66,11 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// </summary>
public int TotalRound { get; }
/// <summary>
/// 使用的地图
/// </summary>
public GameMap? Map { get; }
/// <summary>
/// 显示队列信息
/// </summary>
@ -79,10 +90,10 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="enemy"></param>
/// <param name="damage"></param>
/// <param name="isNormalAttack"></param>
/// <param name="isMagicDamage"></param>
/// <param name="damageType"></param>
/// <param name="magicType"></param>
/// <param name="damageResult"></param>
public Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal);
public Task DamageToEnemyAsync(Character actor, Character enemy, double damage, bool isNormalAttack, DamageType damageType = DamageType.Physical, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal);
/// <summary>
/// 治疗一个目标
@ -145,8 +156,30 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="caster"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="castRange"></param>
/// <param name="desiredTargets"></param>
/// <returns></returns>
public Task<bool> UseItemAsync(Item item, Character caster, List<Character> enemys, List<Character> teammates);
public Task<bool> UseItemAsync(Item item, Character caster, List<Character> enemys, List<Character> teammates, List<Grid> castRange, List<Character>? desiredTargets = null);
/// <summary>
/// 角色移动
/// </summary>
/// <param name="character"></param>
/// <param name="target"></param>
/// <param name="startGrid"></param>
/// <returns></returns>
public Task<bool> CharacterMoveAsync(Character character, Grid target, Grid? startGrid);
/// <summary>
/// 选取移动目标
/// </summary>
/// <param name="character"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="map"></param>
/// <param name="moveRange"></param>
/// <returns></returns>
public Task<Grid> SelectTargetGridAsync(Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> moveRange);
/// <summary>
/// 选取技能目标
@ -155,8 +188,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="skill"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="castRange"></param>
/// <returns></returns>
public Task<List<Character>> SelectTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates);
public Task<List<Character>> SelectTargetsAsync(Character caster, Skill skill, List<Character> enemys, List<Character> teammates, List<Grid> castRange);
/// <summary>
/// 选取普通攻击目标
@ -165,8 +199,9 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="attack"></param>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <param name="attackRange"></param>
/// <returns></returns>
public Task<List<Character>> SelectTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates);
public Task<List<Character>> SelectTargetsAsync(Character character, NormalAttack attack, List<Character> enemys, List<Character> teammates, List<Grid> attackRange);
/// <summary>
/// 判断目标对于某个角色是否是队友
@ -184,6 +219,12 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <returns></returns>
public Dictionary<Character, bool> GetIsTeammateDictionary(Character character, IEnumerable<Character> targets);
/// <summary>
/// 设置角色为 AI 控制
/// </summary>
/// <returns></returns>
public void SetCharactersToAIControl(bool bySystem = true, bool cancel = false, params IEnumerable<Character> characters);
/// <summary>
/// 检查角色是否在 AI 控制状态
/// </summary>
@ -198,5 +239,15 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="isPercentage">是否是百分比</param>
/// <param name="isCheckProtected">是否使用插队保护机制</param>
public void ChangeCharacterHardnessTime(Character character, double addValue, bool isPercentage, bool isCheckProtected);
/// <summary>
/// 计算角色的数据
/// </summary>
/// <param name="character"></param>
/// <param name="characterTaken"></param>
/// <param name="damage"></param>
/// <param name="damageType"></param>
/// <param name="takenDamage"></param>
public void CalculateCharacterDamageStatistics(Character character, Character characterTaken, double damage, DamageType damageType, double takenDamage = -1);
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Base;
namespace Milimoe.FunGame.Core.Interface.Entity
{
@ -7,6 +8,11 @@ namespace Milimoe.FunGame.Core.Interface.Entity
/// </summary>
public interface ISkill : IBaseEntity
{
/// <summary>
/// 所属的行动顺序表实例
/// </summary>
public IGamingQueue? GamingQueue { get; }
/// <summary>
/// 此技能所属的角色
/// </summary>
@ -51,15 +57,26 @@ namespace Milimoe.FunGame.Core.Interface.Entity
/// 可选取友方角色
/// </summary>
public bool CanSelectTeammate { get; }
/// <summary>
/// 选取所有敌对角色,优先级大于 <see cref="CanSelectTargetCount"/>
/// </summary>
public bool SelectAllEnemies { get; }
/// <summary>
/// 选取所有友方角色,优先级大于 <see cref="CanSelectTargetCount"/>,默认包含自身
/// </summary>
public bool SelectAllTeammates { get; }
/// <summary>
/// 可选取的作用目标数量
/// </summary>
public int CanSelectTargetCount { get; }
/// <summary>
/// 可选取的作用范围
/// 可选取的作用范围 [ 单位:格 ]
/// </summary>
public double CanSelectTargetRange { get; }
public int CanSelectTargetRange { get; }
/// <summary>
/// 实际魔法消耗 [ 魔法 ]
@ -99,5 +116,13 @@ namespace Milimoe.FunGame.Core.Interface.Entity
/// <param name="teammates"></param>
/// <returns></returns>
public List<Character> GetSelectableTargets(Character caster, List<Character> enemys, List<Character> teammates);
/// <summary>
/// 实际可选取的目标数量
/// </summary>
/// <param name="enemys"></param>
/// <param name="teammates"></param>
/// <returns></returns>
public int RealCanSelectTargetCount(List<Character> enemys, List<Character> teammates);
}
}

View File

@ -319,13 +319,37 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
public override string Author => "FunGamer";
public override float Length => 12.0f;
public override int Length => 12;
public override float Width => 12.0f;
public override int Width => 12;
public override float Height => 6.0f;
public override int Height => 6;
public override float Size => 4.0f;
public override GameMap InitGamingQueue(IGamingQueue queue)
{
// 因为模组在模组管理器中都是单例的,所以每次游戏都需要返回一个新的地图对象给队列
GameMap map = new ExampleGameMap();
map.Load();
// 做一些绑定,以便介入游戏队列
/// 但是,传入的 queue 可能不是 <see cref="GamingQueue"/>,要做类型检查
// 不使用框架的实现时,需要地图作者与游戏队列的作者做好适配
if (queue is GamingQueue gq)
{
gq.SelectTargetGrid += Gq_SelectTargetGrid;
}
return map;
}
private async Task<Grid> Gq_SelectTargetGrid(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, GameMap map, List<Grid> canMoveGrids)
{
// 介入选择,假设这里更新界面,让玩家选择目的地
await Task.CompletedTask;
return Grid.Empty;
}
}
/// <summary>

View File

@ -1,4 +1,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Addons;
using Milimoe.FunGame.Core.Interface.Base;
namespace Milimoe.FunGame.Core.Library.Common.Addon
{
@ -27,17 +30,17 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <summary>
/// 长度
/// </summary>
public abstract float Length { get; }
public abstract int Length { get; }
/// <summary>
/// 宽度
/// </summary>
public abstract float Width { get; }
public abstract int Width { get; }
/// <summary>
/// 高度
/// </summary>
public abstract float Height { get; }
public abstract int Height { get; }
/// <summary>
/// 格子大小
@ -49,6 +52,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// </summary>
public Dictionary<long, Grid> Grids { get; } = [];
/// <summary>
/// 格子集(基于坐标)
/// </summary>
public Dictionary<(int x, int y, int z), Grid> GridsByCoordinate { get; } = [];
/// <summary>
/// 角色集
/// </summary>
public Dictionary<Character, Grid> Characters { get; } = [];
/// <summary>
/// 使用坐标获取格子0号格子的坐标是(0, 0),如果你还有高度的话,则是(0, 0, 0)
/// </summary>
@ -56,14 +69,34 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <param name="y"></param>
/// <param name="z"></param>
/// <returns></returns>
public Grid this[float x, float y, float z = 0] => Grids.Values.Where(g => g.X == x && g.Y == y && g.Z == z).FirstOrDefault();
public Grid? this[int x, int y, int z = 0]
{
get
{
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? grid))
{
return grid;
}
return null;
}
}
/// <summary>
/// 使用坐标获取格子从0号开始
/// 使用编号获取格子从0号开始
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public Grid this[int id] => Grids[id];
public Grid? this[long id]
{
get
{
if (Grids.TryGetValue(id, out Grid? grid))
{
return grid;
}
return null;
}
}
/// <summary>
/// 加载标记
@ -87,26 +120,26 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
// 地图加载后,不允许再次加载此地图
IsLoaded = true;
// 生成格子
for (float x = 0; x < Length; x++)
for (int x = 0; x < Length; x++)
{
for (float y = 0; y < Width; y++)
for (int y = 0; y < Width; y++)
{
for (float z = 0; z < Height; z++)
for (int z = 0; z < Height; z++)
{
Grids.Add(Grids.Count, new(Grids.Count, x, y, z));
Grid grid = new(Grids.Count, x, y, z);
Grids.Add(Grids.Count, grid);
GridsByCoordinate.Add((x, y, z), grid);
}
}
}
// 如果加载后需要执行代码请重写AfterLoad方法
AfterLoad();
}
return IsLoaded;
}
/// <summary>
/// 加载后需要做的事
/// 地图完全加载后需要做的事
/// </summary>
protected virtual void AfterLoad()
public virtual void AfterLoad(GameModuleLoader loader, params object[] args)
{
// override
}
@ -119,5 +152,460 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
{
return true;
}
/// <summary>
/// 初始化游戏队列,要求返回一个新的地图实例,而不是 this
/// </summary>
/// <param name="queue"></param>
public abstract GameMap InitGamingQueue(IGamingQueue queue);
/// <summary>
/// 获取角色当前所在的格子
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
public Grid? GetCharacterCurrentGrid(Character character)
{
if (Characters.TryGetValue(character, out Grid? current))
{
return current;
}
return null;
}
/// <summary>
/// 强制设置角色当前所在的格子
/// </summary>
/// <param name="character"></param>
/// <param name="target"></param>
/// <returns></returns>
public bool SetCharacterCurrentGrid(Character character, Grid target)
{
Grid? current = GetCharacterCurrentGrid(character);
current?.Characters.Remove(character);
if (Grids.ContainsValue(target))
{
target.Characters.Add(character);
Characters[character] = target;
return true;
}
return false;
}
/// <summary>
/// 将角色从地图中移除
/// </summary>
/// <param name="character"></param>
/// <returns></returns>
public void RemoveCharacter(Character character)
{
Grid? current = GetCharacterCurrentGrid(character);
current?.Characters.Remove(character);
Characters[character] = Grid.Empty;
}
/// <summary>
/// 获取以某个格子为中心,一定范围内的格子(曼哈顿距离),只考虑同一平面的格子。
/// </summary>
/// <param name="grid"></param>
/// <param name="range"></param>
/// <param name="includeCharacter"></param>
/// <returns></returns>
public virtual List<Grid> GetGridsByRange(Grid grid, int range, bool includeCharacter = false)
{
List<Grid> grids = [];
for (int dx = -range; dx <= range; ++dx)
{
for (int dy = -range; dy <= range; ++dy)
{
//限制在中心点周围范围内
if (Math.Abs(dx) + Math.Abs(dy) <= range)
{
//检查是否在棋盘范围内
int x = grid.X + dx;
int y = grid.Y + dy;
int z = grid.Z;
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null)
{
if (includeCharacter || select.Characters.Count == 0)
{
grids.Add(select);
}
}
}
}
}
return grids;
}
/// <summary>
/// 获取以某个格子为中心,最远距离的格子(曼哈顿距离),只考虑同一平面的格子。
/// </summary>
/// <param name="grid"></param>
/// <param name="range"></param>
/// <param name="includeCharacter"></param>
/// <returns></returns>
public virtual List<Grid> GetOuterGridsByRange(Grid grid, int range, bool includeCharacter = false)
{
List<Grid> grids = [];
if (range < 0)
{
return grids;
}
// 遍历以中心格子为中心的方形区域
// dx和dy的范围从 -range 到 +range
for (int dx = -range; dx <= range; ++dx)
{
for (int dy = -range; dy <= range; ++dy)
{
// 只有当曼哈顿距离恰好等于 range 时,才认为是最远距离的格子
if (Math.Abs(dx) + Math.Abs(dy) == range)
{
int x = grid.X + dx;
int y = grid.Y + dy;
int z = grid.Z; // 只考虑同一平面
// 检查格子是否存在于地图中
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null)
{
if (includeCharacter || select.Characters.Count == 0)
{
grids.Add(select);
}
}
}
}
}
return grids;
}
/// <summary>
/// 获取以某个格子为中心,一定半径内的格子(圆形范围,欧几里得距离),只考虑同一平面的格子。
/// </summary>
/// <param name="grid"></param>
/// <param name="range"></param>
/// <param name="includeCharacter"></param>
/// <returns></returns>
public virtual List<Grid> GetGridsByCircleRange(Grid grid, int range, bool includeCharacter = false)
{
List<Grid> grids = [];
// 预计算半径的平方
int rangeSquared = range * range;
// 遍历以中心格子为中心的区域
// 范围从 -range 到 +range覆盖所有可能的圆形区域内的格子
for (int dx = -range; dx <= range; ++dx)
{
for (int dy = -range; dy <= range; ++dy)
{
// 计算当前格子与中心格子的欧几里得距离的平方
if ((dx * dx) + (dy * dy) <= rangeSquared)
{
int x = grid.X + dx;
int y = grid.Y + dy;
int z = grid.Z;
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null)
{
if (includeCharacter || select.Characters.Count == 0)
{
grids.Add(select);
}
}
}
}
}
return grids;
}
/// <summary>
/// 获取以某个格子为中心,最远距离的格子(圆形范围,欧几里得距离),只考虑同一平面的格子。
/// </summary>
/// <param name="grid"></param>
/// <param name="range"></param>
/// <param name="includeCharacter"></param>
/// <returns></returns>
public virtual List<Grid> GetOuterGridsByCircleRange(Grid grid, int range, bool includeCharacter = false)
{
List<Grid> grids = [];
// 预计算半径的平方
int rangeSquared = range * range;
// 遍历以中心格子为中心的区域
// 范围从 -range 到 +range覆盖所有可能的圆形区域内的格子
for (int dx = -range; dx <= range; ++dx)
{
for (int dy = -range; dy <= range; ++dy)
{
// 计算当前格子与中心格子的欧几里得距离的平方
if ((dx * dx) + (dy * dy) == rangeSquared)
{
int x = grid.X + dx;
int y = grid.Y + dy;
int z = grid.Z;
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null)
{
if (includeCharacter || select.Characters.Count == 0)
{
grids.Add(select);
}
}
}
}
}
return grids;
}
/// <summary>
/// 设置角色移动
/// </summary>
/// <param name="character"></param>
/// <param name="current"></param>
/// <param name="target"></param>
/// <returns>移动的步数,只算平面移动步数</returns>
public virtual int CharacterMove(Character character, Grid? current, Grid target)
{
if (current is null || current.Id < 0 || target.Id < 0 || !Grids.ContainsValue(target))
{
return -1;
}
Grid? realGrid = GetCharacterCurrentGrid(character);
Grid startGrid = current;
if (realGrid != null && current.Id != realGrid.Id)
{
startGrid = realGrid;
}
if (startGrid.Id == target.Id)
{
SetCharacterCurrentGrid(character, startGrid);
return 0;
}
// 记录走到某个格子时的步数
Queue<(Grid grid, int steps)> queue = new();
// 记录已访问的格子
HashSet<long> visited = [];
// 将起始格子加入队列步数为0并标记为已访问
queue.Enqueue((startGrid, 0));
visited.Add(startGrid.Id);
while (queue.Count > 0)
{
var (currentGrid, currentSteps) = queue.Dequeue();
// 如果当前格子就是目标格子,则找到了最短路径
if (currentGrid.Id == target.Id)
{
realGrid?.Characters.Remove(character);
SetCharacterCurrentGrid(character, target);
return currentSteps;
}
// 定义平面移动的四个方向
(int dx, int dy)[] directions = [
(0, 1), // 上
(0, -1), // 下
(1, 0), // 右
(-1, 0) // 左
];
foreach (var (dx, dy) in directions)
{
int nextX = currentGrid.X + dx;
int nextY = currentGrid.Y + dy;
int nextZ = currentGrid.Z;
// 尝试获取相邻格子
Grid? neighborGrid = this[nextX, nextY, nextZ];
// 如果相邻格子存在且未被访问过
if (neighborGrid != null && !visited.Contains(neighborGrid.Id))
{
visited.Add(neighborGrid.Id);
queue.Enqueue((neighborGrid, currentSteps + 1));
}
}
}
return -1;
}
/// <summary>
/// 设置角色移动。如果不能达到目标格子,则移动到离目标格子最近的一个可达格子上。
/// </summary>
/// <param name="character"></param>
/// <param name="current"></param>
/// <param name="target"></param>
/// <returns>移动的步数,只算平面移动步数</returns>
public virtual int CharacterMoveToClosestReachable(Character character, Grid? current, Grid target)
{
if (current is null || current.Id < 0 || target.Id < 0 || !Grids.ContainsValue(target))
{
return -1;
}
Grid? realGrid = GetCharacterCurrentGrid(character);
Grid startGrid = current;
if (realGrid != null && current.Id != realGrid.Id)
{
startGrid = realGrid;
}
if (startGrid.Id == target.Id)
{
SetCharacterCurrentGrid(character, startGrid);
return 0;
}
// 使用 BFS 算法探索所有可达格子,并记录它们到起点的步数
Queue<(Grid grid, int steps)> queue = new();
// 记录已访问的格子ID
HashSet<long> visited = [];
// 初始化 BFS 队列将起始格子加入步数为0
queue.Enqueue((startGrid, 0));
visited.Add(startGrid.Id);
Grid? bestReachableGrid = current;
int minDistanceToTarget = CalculateManhattanDistance(startGrid, target);
int stepsToBestReachable = 0;
// 定义平面移动的四个方向
(int dx, int dy)[] directions = [
(0, 1), (0, -1), (1, 0), (-1, 0)
];
while (queue.Count > 0)
{
var (currentGrid, currentSteps) = queue.Dequeue();
// 计算当前可达格子到目标格子的曼哈顿距离
int distToTarget = CalculateManhattanDistance(currentGrid, target);
// 如果当前格子比之前找到的 bestReachableGrid 更接近目标
if (distToTarget < minDistanceToTarget)
{
minDistanceToTarget = distToTarget;
bestReachableGrid = currentGrid;
stepsToBestReachable = currentSteps;
}
// 如果距离相同,优先选择到达步数更少的格子(作为一种 tie-breaking 规则)
else if (distToTarget == minDistanceToTarget && currentSteps < stepsToBestReachable)
{
bestReachableGrid = currentGrid;
stepsToBestReachable = currentSteps;
}
// 探索相邻格子
foreach (var (dx, dy) in directions)
{
int nextX = currentGrid.X + dx;
int nextY = currentGrid.Y + dy;
int nextZ = currentGrid.Z;
Grid? neighborGrid = this[nextX, nextY, nextZ];
// 如果相邻格子存在且未被访问过
if (neighborGrid != null && !visited.Contains(neighborGrid.Id))
{
visited.Add(neighborGrid.Id);
queue.Enqueue((neighborGrid, currentSteps + 1));
}
}
}
// 理论上 bestReachableGrid 不会是 null因为 current 至少是可达的
if (bestReachableGrid == null)
{
return -1;
}
// 更新角色的实际位置
realGrid?.Characters.Remove(character);
SetCharacterCurrentGrid(character, bestReachableGrid);
return stepsToBestReachable;
}
/// <summary>
/// 计算两个格子之间的曼哈顿距离
/// </summary>
/// <param name="g1"></param>
/// <param name="g2"></param>
/// <returns>两个格子之间的曼哈顿距离</returns>
public static int CalculateManhattanDistance(Grid g1, Grid g2)
{
return Math.Abs(g1.X - g2.X) + Math.Abs(g1.Y - g2.Y) + Math.Abs(g1.Z - g2.Z);
}
/// <summary>
/// 在事件流逝前处理
/// </summary>
/// <param name="timeToReduce"></param>
protected virtual void BeforeTimeElapsed(ref double timeToReduce)
{
}
/// <summary>
/// 在事件流逝后处理
/// </summary>
/// <param name="timeToReduce"></param>
protected virtual void AfterTimeElapsed(ref double timeToReduce)
{
}
/// <summary>
/// 时间流逝时,处理格子上的特效
/// </summary>
/// <param name="timeToReduce"></param>
public void OnTimeElapsed(double timeToReduce)
{
BeforeTimeElapsed(ref timeToReduce);
foreach (Grid grid in Grids.Values)
{
List<Effect> effects = [.. grid.Effects];
foreach (Effect effect in effects)
{
if (effect.Durative)
{
if (effect.RemainDuration < timeToReduce)
{
// 移除特效前也完成剩余时间内的效果
effect.OnTimeElapsed(grid, effect.RemainDuration);
effect.RemainDuration = 0;
grid.Effects.Remove(effect);
}
else
{
effect.RemainDuration -= timeToReduce;
effect.OnTimeElapsed(grid, timeToReduce);
}
}
else
{
effect.OnTimeElapsed(grid, timeToReduce);
}
}
}
AfterTimeElapsed(ref timeToReduce);
}
}
}

View File

@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Controller;
using Milimoe.FunGame.Core.Interface.Addons;
using Milimoe.FunGame.Core.Interface.Base;
@ -149,7 +150,7 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <summary>
/// 模组完全加载后需要做的事
/// </summary>
public virtual void AfterLoad(params object[] args)
public virtual void AfterLoad(GameModuleLoader loader, params object[] args)
{
// override
}

View File

@ -3,8 +3,13 @@ using Milimoe.FunGame.Core.Entity;
namespace Milimoe.FunGame.Core.Library.Common.Addon
{
public struct Grid(int id, float x, float y, float z)
public class Grid(int id, int x, int y, int z)
{
/// <summary>
/// 空格子
/// </summary>
public static Grid Empty { get; } = new Grid(-1, 0, 0, 0);
/// <summary>
/// 格子编号
/// </summary>
@ -13,31 +18,40 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
/// <summary>
/// 格子在地图中的x坐标
/// </summary>
public float X { get; } = x;
public int X { get; } = x;
/// <summary>
/// 格子在地图中的y坐标
/// </summary>
public float Y { get; } = y;
public int Y { get; } = y;
/// <summary>
/// 格子在地图中的z坐标
/// </summary>
public float Z { get; } = z;
public int Z { get; } = z;
/// <summary>
/// 是谁站在这格子上?
/// </summary>
public Dictionary<string, Character> Characters { get; set; } = [];
public HashSet<Character> Characters { get; set; } = [];
/// <summary>
/// 此格子目前受到了什么影响?或者它有什么技能…
/// 此格子目前受到了什么影响?
/// </summary>
public Dictionary<string, Skill> Skills { get; set; } = [];
public HashSet<Effect> Effects { get; set; } = [];
/// <summary>
/// 此格子呈现的颜色(默认为 <see cref="Color.Gray"/>
/// </summary>
public Color Color { get; set; } = Color.Gray;
/// <summary>
/// 默认的字符串表示形式
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Grid: {Id} ({X}, {Y}, {Z})";
}
}
}

View File

@ -99,6 +99,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
return true;
}
/// <summary>
/// 当 Web API 服务启动完成后触发
/// </summary>
/// <returns></returns>
public virtual void OnWebAPIStarted(params object[] objs)
{
}
/// <summary>
/// 绑定事件。在<see cref="BeforeLoad"/>后触发
/// </summary>

View File

@ -77,10 +77,11 @@ namespace Milimoe.FunGame.Core.Library.Common.Architecture
{
if (!SendingHeartBeat) _SendingHeartBeat = true;
// 发送心跳包
_LastHeartbeatReceived = false;
if (await _HTTPClient.Send(SocketMessageType.HeartBeat) == SocketResult.Success)
{
await Task.Delay(4 * 1000);
AddHeartBeatFaileds();
if (!_LastHeartbeatReceived) AddHeartBeatFaileds();
}
else AddHeartBeatFaileds();
await Task.Delay(20 * 1000);

View File

@ -9,8 +9,8 @@ namespace Milimoe.FunGame.Core.Library.Common.Event
public long QuestId { get; } = questId;
public Activity Activity { get; } = activity;
public ActivityState ActivityState { get; } = activity.Status;
public DateTime StartTime { get; } = activity.StartTime;
public DateTime EndTime { get; } = activity.EndTime;
public DateTime? StartTime { get; } = activity.StartTime;
public DateTime? EndTime { get; } = activity.EndTime;
public bool AllowAccess { get; set; } = false;
}
}

View File

@ -0,0 +1,88 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class ActivityConverter : BaseEntityConverter<Activity>
{
public override Activity NewInstance()
{
return new Activity();
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Activity result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(Activity.Id):
result.Id = reader.GetInt64();
break;
case nameof(Activity.Guid):
result.Guid = NetworkUtility.JsonDeserialize<Guid>(ref reader, options);
break;
case nameof(Activity.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(Activity.Description):
result.Description = reader.GetString() ?? "";
break;
case nameof(Activity.StartTime):
string startTimeStr = reader.GetString() ?? "";
if (DateTime.TryParseExact(startTimeStr, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime startTime))
{
result.StartTime = startTime;
}
result.UpdateState();
break;
case nameof(Activity.EndTime):
string endTimeStr = reader.GetString() ?? "";
if (DateTime.TryParseExact(endTimeStr, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime endTime))
{
result.EndTime = endTime;
}
result.UpdateState();
break;
case nameof(Activity.Quests):
List<Quest> quests = NetworkUtility.JsonDeserialize<List<Quest>>(ref reader, options) ?? [];
foreach (Quest quest in quests)
{
result.Quests.Add(quest);
}
break;
case nameof(Activity.Predecessor):
result.Predecessor = reader.GetInt64();
result.UpdateState();
break;
case nameof(Activity.PredecessorStatus):
result.PredecessorStatus = (ActivityState)reader.GetInt32();
result.UpdateState();
break;
}
}
public override void Write(Utf8JsonWriter writer, Activity value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber(nameof(Activity.Id), value.Id);
if (value.Guid != Guid.Empty)
{
writer.WritePropertyName(nameof(Activity.Guid));
JsonSerializer.Serialize(writer, value.Guid, options);
}
writer.WriteString(nameof(Activity.Name), value.Name);
writer.WriteString(nameof(Activity.Description), value.Description);
if (value.StartTime != null) writer.WriteString(nameof(Activity.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTime != null) writer.WriteString(nameof(Activity.EndTime), value.EndTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WritePropertyName(nameof(Activity.Quests));
JsonSerializer.Serialize(writer, value.Quests, options);
writer.WriteNumber(nameof(Activity.Predecessor), value.Predecessor);
writer.WriteNumber(nameof(Activity.PredecessorStatus), (int)value.PredecessorStatus);
writer.WriteEndObject();
}
}
}

View File

@ -83,6 +83,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Character.ExHPPercentage):
result.ExHPPercentage = reader.GetDouble();
break;
case nameof(Character.HasMP):
result.HasMP = reader.GetBoolean();
break;
case nameof(Character.InitialMP):
result.InitialMP = reader.GetDouble();
break;
@ -197,8 +200,11 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Character.ExCDR):
result.ExCDR = reader.GetDouble();
break;
case nameof(Character.ATR):
result.ATR = reader.GetDouble();
case nameof(Character.ExATR):
result.ExATR = reader.GetInt32();
break;
case nameof(Character.ExMOV):
result.ExMOV = reader.GetInt32();
break;
case nameof(Character.ExCritRate):
result.ExCritRate = reader.GetDouble();
@ -218,7 +224,10 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Character.NormalAttack):
NormalAttack normalAttack = NetworkUtility.JsonDeserialize<NormalAttack>(ref reader, options) ?? new NormalAttack(result);
result.NormalAttack.Level = normalAttack.Level;
result.NormalAttack.HardnessTime = normalAttack.HardnessTime;
result.NormalAttack.ExDamage = normalAttack.ExDamage;
result.NormalAttack.ExDamage2 = normalAttack.ExDamage2;
result.NormalAttack.ExHardnessTime = normalAttack.ExHardnessTime;
result.NormalAttack.ExHardnessTime2 = normalAttack.ExHardnessTime2;
result.NormalAttack.SetMagicType(normalAttack.IsMagic, normalAttack.MagicType);
break;
case nameof(Character.Skills):
@ -267,6 +276,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Character.InitialHP), value.InitialHP);
writer.WriteNumber(nameof(Character.ExHP2), value.ExHP2);
writer.WriteNumber(nameof(Character.ExHPPercentage), value.ExHPPercentage);
writer.WriteBoolean(nameof(Character.HasMP), value.HasMP);
writer.WriteNumber(nameof(Character.InitialMP), value.InitialMP);
writer.WriteNumber(nameof(Character.ExMP2), value.ExMP2);
writer.WriteNumber(nameof(Character.ExMPPercentage), value.ExMPPercentage);
@ -305,7 +315,8 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Character.ExActionCoefficient), value.ExActionCoefficient);
writer.WriteNumber(nameof(Character.ExAccelerationCoefficient), value.ExAccelerationCoefficient);
writer.WriteNumber(nameof(Character.ExCDR), value.ExCDR);
writer.WriteNumber(nameof(Character.ATR), value.ATR);
writer.WriteNumber(nameof(Character.ExATR), value.ExATR);
writer.WriteNumber(nameof(Character.ExMOV), value.ExMOV);
writer.WriteNumber(nameof(Character.ExCritRate), value.ExCritRate);
writer.WriteNumber(nameof(Character.ExCritDMG), value.ExCritDMG);
writer.WriteNumber(nameof(Character.ExEvadeRate), value.ExEvadeRate);

View File

@ -73,6 +73,13 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
result.Applicants[id] = new(id);
}
break;
case nameof(Club.Invitees):
List<long> invitees = NetworkUtility.JsonDeserialize<List<long>>(ref reader, options) ?? [];
foreach (long id in invitees)
{
result.Invitees[id] = new(id);
}
break;
case nameof(Club.MemberJoinTime):
Dictionary<long, DateTime> memberJoinTime = NetworkUtility.JsonDeserialize<Dictionary<long, DateTime>>(ref reader, options) ?? [];
foreach (long id in memberJoinTime.Keys)
@ -87,6 +94,13 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
result.ApplicationTime[id] = applicationTime[id];
}
break;
case nameof(Club.InvitedTime):
Dictionary<long, DateTime> invitedTime = NetworkUtility.JsonDeserialize<Dictionary<long, DateTime>>(ref reader, options) ?? [];
foreach (long id in invitedTime.Keys)
{
result.InvitedTime[id] = invitedTime[id];
}
break;
}
}
@ -111,10 +125,14 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
JsonSerializer.Serialize(writer, value.Members.Keys, options);
writer.WritePropertyName(nameof(Club.Applicants));
JsonSerializer.Serialize(writer, value.Applicants.Keys, options);
writer.WritePropertyName(nameof(Club.Invitees));
JsonSerializer.Serialize(writer, value.Invitees.Keys, options);
writer.WritePropertyName(nameof(Club.MemberJoinTime));
JsonSerializer.Serialize(writer, value.MemberJoinTime, options);
writer.WritePropertyName(nameof(Club.ApplicationTime));
JsonSerializer.Serialize(writer, value.ApplicationTime, options);
writer.WritePropertyName(nameof(Club.InvitedTime));
JsonSerializer.Serialize(writer, value.InvitedTime, options);
writer.WriteEndObject();
}

View File

@ -2,6 +2,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
@ -29,6 +30,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Goods.Stock):
result.Stock = reader.GetInt32();
break;
case nameof(Goods.Quota):
result.Quota = reader.GetInt32();
break;
case nameof(Goods.Name):
result.Name = reader.GetString() ?? "";
break;
@ -42,6 +46,20 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
result.Prices[needy] = prices[needy];
}
break;
case nameof(Goods.UsersBuyCount):
Dictionary<long, int> buyCount = NetworkUtility.JsonDeserialize<Dictionary<long, int>>(ref reader, options) ?? [];
foreach (long uid in buyCount.Keys)
{
result.UsersBuyCount[uid] = buyCount[uid];
}
break;
case nameof(Store.ExpireTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.ExpireTime = time;
}
break;
}
}
@ -53,10 +71,14 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WritePropertyName(nameof(Goods.Items));
JsonSerializer.Serialize(writer, value.Items, options);
writer.WriteNumber(nameof(Goods.Stock), value.Stock);
writer.WriteNumber(nameof(Goods.Quota), value.Quota);
writer.WriteString(nameof(Goods.Name), value.Name);
writer.WriteString(nameof(Goods.Description), value.Description);
writer.WritePropertyName(nameof(Goods.Prices));
JsonSerializer.Serialize(writer, value.Prices, options);
writer.WritePropertyName(nameof(Goods.UsersBuyCount));
JsonSerializer.Serialize(writer, value.UsersBuyCount, options);
if (value.ExpireTime.HasValue) writer.WriteString(nameof(Goods.ExpireTime), value.ExpireTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WriteEndObject();
}

View File

@ -56,6 +56,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Item.IsInGameItem):
result.IsInGameItem = reader.GetBoolean();
break;
case nameof(Item.IsLock):
result.IsLock = reader.GetBoolean();
break;
case nameof(Item.Equipable):
result.Equipable = reader.GetBoolean();
break;
@ -133,6 +136,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Item.RarityType), (int)value.RarityType);
writer.WriteNumber(nameof(Item.RankType), (int)value.RankType);
writer.WriteBoolean(nameof(Item.IsInGameItem), value.IsInGameItem);
writer.WriteBoolean(nameof(Item.IsLock), value.IsLock);
writer.WriteBoolean(nameof(Item.Equipable), value.Equipable);
writer.WriteBoolean(nameof(Item.Unequipable), value.Unequipable);
writer.WriteNumber(nameof(Item.RemainUseTimes), value.RemainUseTimes);

View File

@ -36,11 +36,11 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(MagicResistance.Element):
result.Element = reader.GetDouble();
break;
case nameof(MagicResistance.Fleabane):
result.Fleabane = reader.GetDouble();
case nameof(MagicResistance.Aster):
result.Aster = reader.GetDouble();
break;
case nameof(MagicResistance.Particle):
result.Particle = reader.GetDouble();
case nameof(MagicResistance.SpatioTemporal):
result.SpatioTemporal = reader.GetDouble();
break;
}
}
@ -56,8 +56,8 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(MagicResistance.Bright), value.Bright);
writer.WriteNumber(nameof(MagicResistance.Shadow), value.Shadow);
writer.WriteNumber(nameof(MagicResistance.Element), value.Element);
writer.WriteNumber(nameof(MagicResistance.Fleabane), value.Fleabane);
writer.WriteNumber(nameof(MagicResistance.Particle), value.Particle);
writer.WriteNumber(nameof(MagicResistance.Aster), value.Aster);
writer.WriteNumber(nameof(MagicResistance.SpatioTemporal), value.SpatioTemporal);
writer.WriteEndObject();
}

View File

@ -0,0 +1,96 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class MarketConverter : BaseEntityConverter<Market>
{
public override Market NewInstance()
{
return new Market("市场");
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Market result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(Market.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(Market.Description):
result.Description = reader.GetString() ?? "";
break;
case nameof(Market.StartTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.StartTime = time;
}
else
{
result.StartTime = DateTime.MinValue;
}
break;
case nameof(Market.EndTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTime = time;
}
else
{
result.EndTime = DateTime.MinValue;
}
break;
case nameof(Market.StartTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.StartTimeOfDay = time;
}
else
{
result.StartTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Market.EndTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTimeOfDay = time;
}
else
{
result.EndTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Market.MarketItems):
Dictionary<long, MarketItem> marketItems = NetworkUtility.JsonDeserialize<Dictionary<long, MarketItem>>(ref reader, options) ?? [];
foreach (long id in marketItems.Keys)
{
result.MarketItems[id] = marketItems[id];
}
break;
}
}
public override void Write(Utf8JsonWriter writer, Market value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteString(nameof(Market.Name), value.Name);
writer.WriteString(nameof(Market.Description), value.Description);
if (value.StartTime.HasValue) writer.WriteString(nameof(Market.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTime.HasValue) writer.WriteString(nameof(Market.EndTime), value.EndTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.StartTimeOfDay.HasValue) writer.WriteString(nameof(Market.StartTimeOfDay), value.StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTimeOfDay.HasValue) writer.WriteString(nameof(Market.EndTimeOfDay), value.EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
writer.WritePropertyName(nameof(Market.MarketItems));
JsonSerializer.Serialize(writer, value.MarketItems, options);
writer.WriteEndObject();
}
}
}

View File

@ -0,0 +1,93 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class MarketItemConverter : BaseEntityConverter<MarketItem>
{
public override MarketItem NewInstance()
{
return new MarketItem();
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref MarketItem result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(MarketItem.Id):
result.Id = reader.GetInt64();
break;
case nameof(MarketItem.User):
result.User = reader.GetInt64();
break;
case nameof(MarketItem.Username):
result.Username = reader.GetString() ?? "";
break;
case nameof(MarketItem.Item):
result.Item = NetworkUtility.JsonDeserialize<Item>(ref reader, options) ?? Factory.GetItem();
break;
case nameof(MarketItem.Stock):
result.Stock = Convert.ToInt32(reader.GetInt64());
break;
case nameof(MarketItem.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(MarketItem.Price):
result.Price = reader.GetDouble();
break;
case nameof(MarketItem.CreateTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.CreateTime = time;
}
else
{
result.CreateTime = DateTime.MinValue;
}
break;
case nameof(MarketItem.FinishTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.FinishTime = time;
}
else
{
result.FinishTime = DateTime.MinValue;
}
break;
case nameof(MarketItem.Status):
result.Status = (MarketItemState)reader.GetInt32();
break;
case nameof(MarketItem.Buyers):
result.Buyers = NetworkUtility.JsonDeserialize<HashSet<long>>(ref reader, options) ?? [];
break;
}
}
public override void Write(Utf8JsonWriter writer, MarketItem value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber(nameof(MarketItem.Id), value.Id);
writer.WriteNumber(nameof(MarketItem.User), value.User);
writer.WriteString(nameof(MarketItem.Username), value.Username);
writer.WritePropertyName(nameof(MarketItem.Item));
JsonSerializer.Serialize(writer, value.Item, options);
writer.WriteNumber(nameof(MarketItem.Stock), value.Stock);
writer.WriteString(nameof(MarketItem.Name), value.Name);
writer.WriteNumber(nameof(MarketItem.Price), value.Price);
writer.WriteString(nameof(MarketItem.CreateTime), value.CreateTime.ToString(General.GeneralDateTimeFormat));
if (value.FinishTime.HasValue) writer.WriteString(nameof(MarketItem.FinishTime), value.FinishTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WriteNumber(nameof(MarketItem.Status), (int)value.Status);
writer.WritePropertyName(nameof(MarketItem.Buyers));
JsonSerializer.Serialize(writer, value.Buyers, options);
writer.WriteEndObject();
}
}
}

View File

@ -20,8 +20,17 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(NormalAttack.Level):
result.Level = reader.GetInt32();
break;
case nameof(NormalAttack.HardnessTime):
result.HardnessTime = reader.GetDouble();
case nameof(NormalAttack.ExDamage):
result.ExDamage = reader.GetDouble();
break;
case nameof(NormalAttack.ExDamage2):
result.ExDamage2 = reader.GetDouble();
break;
case nameof(NormalAttack.ExHardnessTime):
result.ExHardnessTime = reader.GetDouble();
break;
case nameof(NormalAttack.ExHardnessTime2):
result.ExHardnessTime2 = reader.GetDouble();
break;
case nameof(NormalAttack.IsMagic):
result.SetMagicType(reader.GetBoolean(), result.MagicType);
@ -40,7 +49,10 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteStartObject();
writer.WriteNumber(nameof(NormalAttack.Level), value.Level);
writer.WriteNumber(nameof(NormalAttack.HardnessTime), value.HardnessTime);
writer.WriteNumber(nameof(NormalAttack.ExDamage), value.ExDamage);
writer.WriteNumber(nameof(NormalAttack.ExDamage2), value.ExDamage2);
writer.WriteNumber(nameof(NormalAttack.ExHardnessTime), value.ExHardnessTime);
writer.WriteNumber(nameof(NormalAttack.ExHardnessTime2), value.ExHardnessTime2);
writer.WriteBoolean(nameof(NormalAttack.IsMagic), value.IsMagic);
writer.WriteNumber(nameof(NormalAttack.MagicType), (int)value.MagicType);
writer.WriteNumber(nameof(NormalAttack.IgnoreImmune), (int)value.IgnoreImmune);

View File

@ -0,0 +1,134 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class QuestConverter : BaseEntityConverter<Quest>
{
public override Quest NewInstance()
{
return new Quest();
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref Quest result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(Quest.Id):
result.Id = reader.GetInt64();
break;
case nameof(Quest.Guid):
result.Guid = NetworkUtility.JsonDeserialize<Guid>(ref reader, options);
break;
case nameof(Quest.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(Quest.Description):
result.Description = reader.GetString() ?? "";
break;
case nameof(Quest.Status):
result.Status = (QuestState)reader.GetInt32();
break;
case nameof(Quest.CharacterId):
result.CharacterId = reader.GetInt64();
break;
case nameof(Quest.RegionId):
result.RegionId = reader.GetInt64();
break;
case nameof(Quest.NeedyExploreCharacterName):
result.NeedyExploreCharacterName = reader.GetString() ?? "";
break;
case nameof(Quest.NeedyExploreItemName):
result.NeedyExploreItemName = reader.GetString() ?? "";
break;
case nameof(Quest.NeedyExploreEventName):
result.NeedyExploreEventName = reader.GetString() ?? "";
break;
case nameof(Quest.CreditsAward):
result.CreditsAward = reader.GetDouble();
break;
case nameof(Quest.MaterialsAward):
result.MaterialsAward = reader.GetDouble();
break;
case nameof(Quest.Awards):
List<Item> awards = NetworkUtility.JsonDeserialize<List<Item>>(ref reader, options) ?? [];
foreach (Item item in awards)
{
result.Awards.Add(item);
}
break;
case nameof(Quest.AwardsCount):
Dictionary<string, int> dict = NetworkUtility.JsonDeserialize<Dictionary<string, int>>(ref reader, options) ?? [];
foreach (string key in dict.Keys)
{
result.AwardsCount[key] = dict[key];
}
break;
case nameof(Quest.StartTime):
string startTimeStr = reader.GetString() ?? "";
if (DateTime.TryParseExact(startTimeStr, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime startTime))
{
result.StartTime = startTime;
}
break;
case nameof(Quest.SettleTime):
string settleTimeStr = reader.GetString() ?? "";
if (DateTime.TryParseExact(settleTimeStr, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime settleTime))
{
result.SettleTime = settleTime;
}
break;
case nameof(Quest.QuestType):
result.QuestType = (QuestType)reader.GetInt32();
break;
case nameof(Quest.EstimatedMinutes):
result.EstimatedMinutes = reader.GetInt32();
break;
case nameof(Quest.Progress):
result.Progress = reader.GetInt32();
break;
case nameof(Quest.MaxProgress):
result.MaxProgress = reader.GetInt32();
break;
}
}
public override void Write(Utf8JsonWriter writer, Quest value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber(nameof(Quest.Id), value.Id);
if (value.Guid != Guid.Empty)
{
writer.WritePropertyName(nameof(Quest.Guid));
JsonSerializer.Serialize(writer, value.Guid, options);
}
writer.WriteString(nameof(Quest.Name), value.Name);
writer.WriteString(nameof(Quest.Description), value.Description);
writer.WriteNumber(nameof(Quest.Status), (int)value.Status);
writer.WriteNumber(nameof(Quest.CharacterId), value.CharacterId);
writer.WriteNumber(nameof(Quest.RegionId), value.RegionId);
writer.WriteString(nameof(Quest.NeedyExploreCharacterName), value.NeedyExploreCharacterName);
writer.WriteString(nameof(Quest.NeedyExploreItemName), value.NeedyExploreItemName);
writer.WriteString(nameof(Quest.NeedyExploreEventName), value.NeedyExploreEventName);
writer.WriteNumber(nameof(Quest.CreditsAward), value.CreditsAward);
writer.WriteNumber(nameof(Quest.MaterialsAward), value.MaterialsAward);
writer.WritePropertyName(nameof(Quest.Awards));
JsonSerializer.Serialize(writer, value.Awards, options);
writer.WritePropertyName(nameof(Quest.AwardsCount));
JsonSerializer.Serialize(writer, value.AwardsCount, options);
writer.WriteString(nameof(Quest.AwardsString), value.AwardsString);
if (value.StartTime != null) writer.WriteString(nameof(Quest.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.SettleTime != null) writer.WriteString(nameof(Quest.SettleTime), value.SettleTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WriteNumber(nameof(Quest.QuestType), (int)value.QuestType);
writer.WriteNumber(nameof(Quest.EstimatedMinutes), value.EstimatedMinutes);
writer.WriteNumber(nameof(Quest.Progress), value.Progress);
writer.WriteNumber(nameof(Quest.MaxProgress), value.MaxProgress);
writer.WriteEndObject();
}
}
}

View File

@ -0,0 +1,234 @@
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
public class RoundRecordConverter : BaseEntityConverter<RoundRecord>
{
public override RoundRecord NewInstance()
{
return new RoundRecord(0);
}
public override void ReadPropertyName(ref Utf8JsonReader reader, string propertyName, JsonSerializerOptions options, ref RoundRecord result, Dictionary<string, object> convertingContext)
{
switch (propertyName)
{
case nameof(RoundRecord.Round):
result.Round = reader.GetInt32();
break;
case nameof(RoundRecord.Actor):
result.Actor = NetworkUtility.JsonDeserialize<Character>(ref reader, options) ?? Factory.GetCharacter();
break;
case nameof(RoundRecord.Targets):
List<Character> targets = NetworkUtility.JsonDeserialize<List<Character>>(ref reader, options) ?? [];
result.Targets.AddRange(targets);
break;
case nameof(RoundRecord.Damages):
Dictionary<Guid, double> damagesGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, double>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, double> kvp in damagesGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.Damages[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.ActionType):
result.ActionType = (CharacterActionType)reader.GetInt32();
break;
case nameof(RoundRecord.Skill):
result.Skill = NetworkUtility.JsonDeserialize<Skill>(ref reader, options);
break;
case nameof(RoundRecord.SkillCost):
result.SkillCost = reader.GetString() ?? "";
break;
case nameof(RoundRecord.Item):
result.Item = NetworkUtility.JsonDeserialize<Item>(ref reader, options);
break;
case nameof(RoundRecord.HasKill):
result.HasKill = reader.GetBoolean();
break;
case nameof(RoundRecord.Assists):
List<Character> assists = NetworkUtility.JsonDeserialize<List<Character>>(ref reader, options) ?? [];
result.Assists.AddRange(assists);
break;
case nameof(RoundRecord.IsCritical):
Dictionary<Guid, bool> isCriticalGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, bool>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, bool> kvp in isCriticalGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.IsCritical[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.IsEvaded):
Dictionary<Guid, bool> isEvadedGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, bool>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, bool> kvp in isEvadedGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.IsEvaded[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.IsImmune):
Dictionary<Guid, bool> isImmuneGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, bool>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, bool> kvp in isImmuneGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.IsImmune[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.Heals):
Dictionary<Guid, double> healsGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, double>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, double> kvp in healsGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.Heals[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.Effects):
Dictionary<Guid, Effect> effectsGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, Effect>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, Effect> kvp in effectsGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.Effects[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.ApplyEffects):
Dictionary<Guid, List<EffectType>> applyEffectsGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, List<EffectType>>>(ref reader, options) ?? [];
result.ApplyEffects.Clear();
foreach (KeyValuePair<Guid, List<EffectType>> kvp in applyEffectsGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.ApplyEffects[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.ActorContinuousKilling):
List<string> actorCK = NetworkUtility.JsonDeserialize<List<string>>(ref reader, options) ?? [];
result.ActorContinuousKilling.AddRange(actorCK);
break;
case nameof(RoundRecord.DeathContinuousKilling):
List<string> deathCK = NetworkUtility.JsonDeserialize<List<string>>(ref reader, options) ?? [];
result.DeathContinuousKilling.AddRange(deathCK);
break;
case nameof(RoundRecord.CastTime):
result.CastTime = reader.GetDouble();
break;
case nameof(RoundRecord.HardnessTime):
result.HardnessTime = reader.GetDouble();
break;
case nameof(RoundRecord.RespawnCountdowns):
Dictionary<Guid, double> respawnCountdownGuid = NetworkUtility.JsonDeserialize<Dictionary<Guid, double>>(ref reader, options) ?? [];
foreach (KeyValuePair<Guid, double> kvp in respawnCountdownGuid)
{
Character? character = FindCharacterByGuid(kvp.Key, result);
if (character != null)
{
result.RespawnCountdowns[character] = kvp.Value;
}
}
break;
case nameof(RoundRecord.Respawns):
List<Character> respawns = NetworkUtility.JsonDeserialize<List<Character>>(ref reader, options) ?? [];
result.Respawns.AddRange(respawns);
break;
case nameof(RoundRecord.RoundRewards):
List<Skill> rewards = NetworkUtility.JsonDeserialize<List<Skill>>(ref reader, options) ?? [];
result.RoundRewards.AddRange(rewards);
break;
case nameof(RoundRecord.OtherMessages):
List<string> messages = NetworkUtility.JsonDeserialize<List<string>>(ref reader, options) ?? [];
result.OtherMessages.AddRange(messages);
break;
default:
reader.Skip();
break;
}
}
public override void Write(Utf8JsonWriter writer, RoundRecord value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteNumber(nameof(RoundRecord.Round), value.Round);
writer.WritePropertyName(nameof(RoundRecord.Actor));
JsonSerializer.Serialize(writer, value.Actor, options);
writer.WritePropertyName(nameof(RoundRecord.Targets));
JsonSerializer.Serialize(writer, value.Targets, options);
writer.WritePropertyName(nameof(RoundRecord.Damages));
JsonSerializer.Serialize(writer, value.Damages.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WriteNumber(nameof(RoundRecord.ActionType), (int)value.ActionType);
writer.WritePropertyName(nameof(RoundRecord.Skill));
JsonSerializer.Serialize(writer, value.Skill, options);
writer.WriteString(nameof(RoundRecord.SkillCost), value.SkillCost);
writer.WritePropertyName(nameof(RoundRecord.Item));
JsonSerializer.Serialize(writer, value.Item, options);
writer.WriteBoolean(nameof(RoundRecord.HasKill), value.HasKill);
writer.WritePropertyName(nameof(RoundRecord.Assists));
JsonSerializer.Serialize(writer, value.Assists, options);
writer.WritePropertyName(nameof(RoundRecord.IsCritical));
JsonSerializer.Serialize(writer, value.IsCritical.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.IsEvaded));
JsonSerializer.Serialize(writer, value.IsEvaded.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.IsImmune));
JsonSerializer.Serialize(writer, value.IsImmune.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.Heals));
JsonSerializer.Serialize(writer, value.Heals.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.Effects));
JsonSerializer.Serialize(writer, value.Effects.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.ApplyEffects));
JsonSerializer.Serialize(writer, value.ApplyEffects.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.ActorContinuousKilling));
JsonSerializer.Serialize(writer, value.ActorContinuousKilling, options);
writer.WritePropertyName(nameof(RoundRecord.DeathContinuousKilling));
JsonSerializer.Serialize(writer, value.DeathContinuousKilling, options);
writer.WriteNumber(nameof(RoundRecord.CastTime), value.CastTime);
writer.WriteNumber(nameof(RoundRecord.HardnessTime), value.HardnessTime);
writer.WritePropertyName(nameof(RoundRecord.RespawnCountdowns));
JsonSerializer.Serialize(writer, value.RespawnCountdowns.ToDictionary(kv => kv.Key.Guid, kv => kv.Value), options);
writer.WritePropertyName(nameof(RoundRecord.Respawns));
JsonSerializer.Serialize(writer, value.Respawns, options);
writer.WritePropertyName(nameof(RoundRecord.RoundRewards));
JsonSerializer.Serialize(writer, value.RoundRewards, options);
writer.WritePropertyName(nameof(RoundRecord.OtherMessages));
JsonSerializer.Serialize(writer, value.OtherMessages, options);
writer.WriteEndObject();
}
private static Character? FindCharacterByGuid(Guid guid, RoundRecord record)
{
Character? character = record.Targets.FirstOrDefault(c => c.Guid == guid);
if (character != null) return character;
if (record.Actor != null && record.Actor.Guid == guid) return record.Actor;
character = record.Assists.FirstOrDefault(c => c.Guid == guid);
if (character != null) return character;
character = record.Respawns.FirstOrDefault(c => c.Guid == guid);
if (character != null) return character;
return null;
}
}
}

View File

@ -39,11 +39,14 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Shield.Element):
result.Element = reader.GetDouble();
break;
case nameof(Shield.Fleabane):
result.Fleabane = reader.GetDouble();
case nameof(Shield.Aster):
result.Aster = reader.GetDouble();
break;
case nameof(Shield.Particle):
result.Particle = reader.GetDouble();
case nameof(Shield.SpatioTemporal):
result.SpatioTemporal = reader.GetDouble();
break;
case nameof(Shield.Mix):
result.Mix = reader.GetDouble();
break;
}
}
@ -60,8 +63,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteNumber(nameof(Shield.Bright), value.Bright);
writer.WriteNumber(nameof(Shield.Shadow), value.Shadow);
writer.WriteNumber(nameof(Shield.Element), value.Element);
writer.WriteNumber(nameof(Shield.Fleabane), value.Fleabane);
writer.WriteNumber(nameof(Shield.Particle), value.Particle);
writer.WriteNumber(nameof(Shield.Aster), value.Aster);
writer.WriteNumber(nameof(Shield.SpatioTemporal), value.SpatioTemporal);
writer.WriteNumber(nameof(Shield.Mix), value.Mix);
writer.WriteEndObject();
}

View File

@ -41,6 +41,12 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Skill.Level):
result.Level = reader.GetInt32();
break;
case nameof(Skill.CastAnywhere):
result.CastAnywhere = reader.GetBoolean();
break;
case nameof(Skill.CastRange):
result.CastRange = reader.GetInt32();
break;
case nameof(Skill.CanSelectSelf):
result.CanSelectSelf = reader.GetBoolean();
break;
@ -86,6 +92,12 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Skill.HardnessTime):
result.HardnessTime = reader.GetDouble();
break;
case nameof(Skill.ExHardnessTime):
result.ExHardnessTime = reader.GetDouble();
break;
case nameof(Skill.ExHardnessTime2):
result.ExHardnessTime2 = reader.GetDouble();
break;
case nameof(Skill.Effects):
HashSet<Effect> effects = NetworkUtility.JsonDeserialize<HashSet<Effect>>(ref reader, options) ?? [];
foreach (Effect effect in effects)
@ -119,6 +131,8 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
if (value.GeneralDescription.Length > 0) writer.WriteString(nameof(Skill.GeneralDescription), value.GeneralDescription);
if (value.Slogan.Length > 0) writer.WriteString(nameof(Skill.Slogan), value.Slogan);
if (value.Level > 0) writer.WriteNumber(nameof(Skill.Level), value.Level);
writer.WriteBoolean(nameof(Skill.CastAnywhere), value.CastAnywhere);
writer.WriteNumber(nameof(Skill.CastRange), value.CastRange);
if (value.CanSelectSelf) writer.WriteBoolean(nameof(Skill.CanSelectSelf), value.CanSelectSelf);
if (!value.CanSelectEnemy) writer.WriteBoolean(nameof(Skill.CanSelectEnemy), value.CanSelectEnemy);
if (value.CanSelectTeammate) writer.WriteBoolean(nameof(Skill.CanSelectTeammate), value.CanSelectTeammate);
@ -134,6 +148,8 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
if (value.CD > 0) writer.WriteNumber(nameof(Skill.CD), value.CD);
if (value.CurrentCD > 0) writer.WriteNumber(nameof(Skill.CurrentCD), value.CurrentCD);
if (value.HardnessTime > 0) writer.WriteNumber(nameof(Skill.HardnessTime), value.HardnessTime);
if (value.ExHardnessTime != 0) writer.WriteNumber(nameof(Skill.ExHardnessTime), value.ExHardnessTime);
if (value.ExHardnessTime2 != 0) writer.WriteNumber(nameof(Skill.ExHardnessTime2), value.ExHardnessTime2);
writer.WritePropertyName(nameof(Skill.Effects));
JsonSerializer.Serialize(writer, value.Effects, options);
writer.WritePropertyName(nameof(Skill.Values));

View File

@ -2,6 +2,7 @@
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Architecture;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
{
@ -19,6 +20,53 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Store.Name):
result.Name = reader.GetString() ?? "";
break;
case nameof(Store.Description):
result.Description = reader.GetString() ?? "";
break;
case nameof(Store.StartTime):
string dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out DateTime time))
{
result.StartTime = time;
}
else
{
result.StartTime = DateTime.MinValue;
}
break;
case nameof(Store.EndTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTime = time;
}
else
{
result.EndTime = DateTime.MinValue;
}
break;
case nameof(Store.StartTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.StartTimeOfDay = time;
}
else
{
result.StartTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Store.EndTimeOfDay):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.EndTimeOfDay = time;
}
else
{
result.EndTimeOfDay = DateTime.MinValue;
}
break;
case nameof(Store.Goods):
Dictionary<long, Goods> goods = NetworkUtility.JsonDeserialize<Dictionary<long, Goods>>(ref reader, options) ?? [];
foreach (long id in goods.Keys)
@ -26,6 +74,43 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
result.Goods[id] = goods[id];
}
break;
case nameof(Store.AutoRefresh):
result.AutoRefresh = reader.GetBoolean();
break;
case nameof(Store.NextRefreshDate):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.NextRefreshDate = time;
}
else
{
result.NextRefreshDate = DateTime.MinValue;
}
break;
case nameof(Store.NextRefreshGoods):
Dictionary<long, Goods> goods2 = NetworkUtility.JsonDeserialize<Dictionary<long, Goods>>(ref reader, options) ?? [];
foreach (long id in goods2.Keys)
{
result.NextRefreshGoods[id] = goods2[id];
}
break;
case nameof(Store.RefreshInterval):
result.RefreshInterval = Convert.ToInt32(reader.GetInt64());
break;
case nameof(Store.GetNewerGoodsOnVisiting):
result.GetNewerGoodsOnVisiting = reader.GetBoolean();
break;
case nameof(Store.GlobalStock):
result.GlobalStock = reader.GetBoolean();
break;
case nameof(Store.ExpireTime):
dateString = reader.GetString() ?? "";
if (DateTime.TryParseExact(dateString, General.GeneralDateTimeFormat, null, System.Globalization.DateTimeStyles.None, out time))
{
result.ExpireTime = time;
}
break;
}
}
@ -34,8 +119,21 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
writer.WriteStartObject();
writer.WriteString(nameof(Store.Name), value.Name);
writer.WriteString(nameof(Store.Description), value.Description);
if (value.StartTime.HasValue) writer.WriteString(nameof(Store.StartTime), value.StartTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTime.HasValue) writer.WriteString(nameof(Store.EndTime), value.EndTime.Value.ToString(General.GeneralDateTimeFormat));
if (value.StartTimeOfDay.HasValue) writer.WriteString(nameof(Store.StartTimeOfDay), value.StartTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
if (value.EndTimeOfDay.HasValue) writer.WriteString(nameof(Store.EndTimeOfDay), value.EndTimeOfDay.Value.ToString(General.GeneralDateTimeFormat));
writer.WritePropertyName(nameof(Store.Goods));
JsonSerializer.Serialize(writer, value.Goods, options);
writer.WriteBoolean(nameof(Store.AutoRefresh), value.AutoRefresh);
writer.WriteString(nameof(Store.NextRefreshDate), value.NextRefreshDate.ToString(General.GeneralDateTimeFormat));
writer.WritePropertyName(nameof(Store.NextRefreshGoods));
JsonSerializer.Serialize(writer, value.NextRefreshGoods, options);
writer.WriteNumber(nameof(Store.RefreshInterval), value.RefreshInterval);
writer.WriteBoolean(nameof(Store.GetNewerGoodsOnVisiting), value.GetNewerGoodsOnVisiting);
writer.WriteBoolean(nameof(Store.GlobalStock), value.GlobalStock);
if (value.ExpireTime.HasValue) writer.WriteString(nameof(Store.ExpireTime), value.ExpireTime.Value.ToString(General.GeneralDateTimeFormat));
writer.WriteEndObject();
}

View File

@ -1,5 +1,4 @@

using Milimoe.FunGame.Core.Model;
using Milimoe.FunGame.Core.Model;
/**
* String Set
@ -33,6 +32,46 @@ namespace Milimoe.FunGame.Core.Library.Constant
_ => "未开始"
};
}
public static string GetActivityStatus(ActivityState status)
{
return status switch
{
ActivityState.Future => "预告中",
ActivityState.Upcoming => "即将开始",
ActivityState.InProgress => "进行中",
_ => "已结束"
};
}
public static string GetOfferStatus(OfferState status)
{
return status switch
{
OfferState.Created => "已创建",
OfferState.Cancelled => "已取消",
OfferState.PendingOfferorConfirmation => "等待发起方确认",
OfferState.PendingOffereeConfirmation => "等待接收方确认",
OfferState.OfferorConfirmed => "发起方已确认",
OfferState.OffereeConfirmed => "接收方已确认",
OfferState.Sent => "已发送",
OfferState.Negotiating => "协商中",
OfferState.NegotiationAccepted => "协商已接受",
OfferState.Rejected => "已拒绝",
OfferState.Completed => "已完成",
_ => "已过期"
};
}
public static string GetMarketItemStatus(MarketItemState status)
{
return status switch
{
MarketItemState.Delisted => "已下架",
MarketItemState.Purchased => "已售罄",
_ => "已上架"
};
}
}
/// <summary>
@ -387,8 +426,8 @@ namespace Milimoe.FunGame.Core.Library.Constant
MagicType.Bright => "光魔法伤害",
MagicType.Shadow => "影魔法伤害",
MagicType.Element => "元素魔法伤害",
MagicType.Fleabane => "紫宛魔法伤害",
MagicType.Particle => "时空魔法伤害",
MagicType.Aster => "紫宛魔法伤害",
MagicType.SpatioTemporal => "时空魔法伤害",
_ => "魔法伤害",
};
}
@ -397,14 +436,14 @@ namespace Milimoe.FunGame.Core.Library.Constant
{
return type switch
{
MagicType.Starmark => "星痕抗性",
MagicType.PurityNatural => "现代结晶抗性",
MagicType.PurityContemporary => "纯粹结晶抗性",
MagicType.Bright => "光抗性",
MagicType.Shadow => "影抗性",
MagicType.Element => "元素抗性",
MagicType.Fleabane => "紫宛抗性",
MagicType.Particle => "时空抗性",
MagicType.Starmark => "星痕魔法抗性",
MagicType.PurityNatural => "现代结晶魔法抗性",
MagicType.PurityContemporary => "纯粹结晶魔法抗性",
MagicType.Bright => "光魔法抗性",
MagicType.Shadow => "影魔法抗性",
MagicType.Element => "元素魔法抗性",
MagicType.Aster => "紫宛魔法抗性",
MagicType.SpatioTemporal => "时空魔法抗性",
_ => "魔法抗性",
};
}
@ -439,6 +478,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
CharacterState.ActionRestricted => "角色现在行动受限",
CharacterState.BattleRestricted => "角色现在战斗不能",
CharacterState.SkillRestricted => "角色现在技能受限",
CharacterState.AttackRestricted => "角色现在攻击受限",
_ => "角色现在完全行动不能"
};
}
@ -454,6 +494,28 @@ namespace Milimoe.FunGame.Core.Library.Constant
_ => "★"
};
}
public static string GetDamageTypeName(DamageType damageType, MagicType magicType = MagicType.None)
{
return damageType switch
{
DamageType.Magical => GetMagicDamageName(magicType),
DamageType.True => "真实伤害",
_ => "物理伤害"
};
}
public static string GetImmuneTypeName(ImmuneType type)
{
return type switch
{
ImmuneType.Physical => "物理免疫",
ImmuneType.Magical => "魔法免疫",
ImmuneType.Skilled => "技能免疫",
ImmuneType.All => "完全免疫",
_ => ""
};
}
}
public class ItemSet
@ -686,6 +748,9 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.GrievousWound => "重伤",
EffectType.WeakDispelling => "持续性弱驱散",
EffectType.StrongDispelling => "持续性强驱散",
EffectType.Recovery => "恢复",
EffectType.Vulnerable => "易伤",
EffectType.Delay => "迟滞",
_ => "未知效果"
};
}
@ -743,6 +808,9 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.Lifesteal => DispelledType.Weak,
EffectType.GrievousWound => DispelledType.Weak,
EffectType.WeakDispelling => DispelledType.Weak,
EffectType.Recovery => DispelledType.Weak,
EffectType.Vulnerable => DispelledType.Weak,
EffectType.Delay => DispelledType.Weak,
_ => DispelledType.Weak
};
}
@ -797,7 +865,12 @@ namespace Milimoe.FunGame.Core.Library.Constant
EffectType.AllImmune => false,
EffectType.EvadeBoost => false,
EffectType.Lifesteal => false,
EffectType.GrievousWound => false,
EffectType.GrievousWound => true,
EffectType.WeakDispelling => false,
EffectType.StrongDispelling => false,
EffectType.Recovery => false,
EffectType.Vulnerable => true,
EffectType.Delay => true,
_ => false
};
}

View File

@ -23,7 +23,14 @@
/// <summary>
/// 核心库的版本号
/// </summary>
public static string FunGame_Version { get; } = $"{FunGame_Version_Major}.{FunGame_Version_Minor}{FunGame_VersionPatch}";
public static string FunGame_Version
{
get
{
string patch = FunGame_VersionPatch.StartsWith('.') ? FunGame_VersionPatch : $".{FunGame_VersionPatch}";
return $"{FunGame_Version_Major}.{FunGame_Version_Minor}{patch}";
}
}
public const string FunGame_Core = "FunGame Core";
public const string FunGame_Core_Api = "FunGame Core Api";
@ -31,9 +38,9 @@
public const string FunGame_Desktop = "FunGame Desktop";
public const string FunGame_Server = "FunGame Server Console";
public const int FunGame_Version_Major = 1;
public const int FunGame_Version_Major = 2;
public const int FunGame_Version_Minor = 0;
public const string FunGame_VersionPatch = ".0-rc.1";
public const string FunGame_VersionPatch = "0-dev";
public const string FunGame_Version_Build = "";
public const string FunGameCoreTitle = @" _____ _ _ _ _ ____ _ __ __ _____ ____ ___ ____ _____

View File

@ -57,6 +57,11 @@ namespace Milimoe.FunGame.Core.Library.Constant
/// </summary>
public static string GeneralDateTimeFormatChinese => "yyyy年MM月dd日 HH:mm:ss";
/// <summary>
/// HH:mm:ss
/// </summary>
public static string GeneralDateTimeFormatTimeOnly => "HH:mm:ss";
/// <summary>
/// 默认的时间值1970年8月1日8点0分0秒
/// </summary>

View File

@ -48,7 +48,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
Fail,
NotFound,
SQLError,
IsExist
Exists
}
public enum MailSendResult
@ -62,7 +62,9 @@ namespace Milimoe.FunGame.Core.Library.Constant
{
Normal,
Critical,
Evaded
Evaded,
Shield,
Immune
}
public enum RedeemResult

View File

@ -297,7 +297,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
Taunt,
/// <summary>
/// 减速,目标行动速度和攻击频率降低
/// 减速,目标行动速度或加速系数降低
/// </summary>
Slow,
@ -312,12 +312,12 @@ namespace Milimoe.FunGame.Core.Library.Constant
Poison,
/// <summary>
/// 燃烧,目标受到火焰伤害,持续一段时间
/// 燃烧,目标受到伤害,持续一段时间
/// </summary>
Burn,
/// <summary>
/// 流血,目标持续受到物理伤害
/// 流血,目标持续受到伤害
/// </summary>
Bleed,
@ -417,7 +417,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
Confusion,
/// <summary>
/// 石化,目标无法行动,并大幅增加受到的伤害
/// 石化,目标无法行动
/// </summary>
Petrify,
@ -479,7 +479,22 @@ namespace Milimoe.FunGame.Core.Library.Constant
/// <summary>
/// 持续性强驱散
/// </summary>
StrongDispelling
StrongDispelling,
/// <summary>
/// 恢复
/// </summary>
Recovery,
/// <summary>
/// 易伤
/// </summary>
Vulnerable,
/// <summary>
/// 迟滞,硬直时间延长
/// </summary>
Delay
}
public enum ItemType
@ -627,8 +642,8 @@ namespace Milimoe.FunGame.Core.Library.Constant
Bright,
Shadow,
Element,
Fleabane,
Particle
Aster,
SpatioTemporal
}
public enum PrimaryAttribute
@ -725,6 +740,7 @@ namespace Milimoe.FunGame.Core.Library.Constant
public enum CharacterActionType
{
None,
Move,
NormalAttack,
PreCastSkill,
CastSkill,
@ -1021,13 +1037,40 @@ namespace Milimoe.FunGame.Core.Library.Constant
/// <summary>
/// 标准实现的免疫类型
/// </summary>
[Flags]
public enum ImmuneType
{
None = 0,
Physical = 1 << 0,
Magical = 1 << 1,
Skilled = 1 << 2,
All = 1 << 3,
Special = 1 << 4
}
public enum ShieldType
{
None,
Physical,
Magical,
Skilled,
All,
Special
Mix,
Effect
}
public enum DamageType
{
Physical,
Magical,
True
}
public enum SkillRangeType
{
Diamond,
Circle,
Square,
Line,
LinePass,
Sector
}
}

View File

@ -10,10 +10,11 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
public const string Column_ItemGuid = "ItemGuid";
public const string Column_UserId = "UserId";
public const string Column_Price = "Price";
public const string Column_Stock = "Stock";
public const string Column_CreateTime = "CreateTime";
public const string Column_FinishTime = "FinishTime";
public const string Column_Status = "Status";
public const string Column_Buyer = "Buyer";
public const string Column_Buyers = "Buyers";
public const string Select_MarketItems = $"{Command_Select} {Command_All} {Command_From} {TableName}";
@ -35,14 +36,15 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Select_MarketItems} {Command_Where} {Column_Status} = @Status";
}
public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, MarketItemState state = MarketItemState.Listed)
public static string Insert_MarketItem(SQLHelper SQLHelper, Guid ItemGuid, long UserId, double Price, double Stock, MarketItemState state = MarketItemState.Listed)
{
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@UserId"] = UserId;
SQLHelper.Parameters["@Price"] = Price;
SQLHelper.Parameters["@Stock"] = Stock;
SQLHelper.Parameters["@Status"] = (int)state;
return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Status)";
return $"{Command_Insert} {Command_Into} {TableName} ({Column_ItemGuid}, {Column_UserId}, {Column_Price}, {Column_Stock}, {Column_Status}) {Command_Values} (@ItemId, @UserId, @Price, @Stock, @Status)";
}
public static string Update_MarketItemPrice(SQLHelper SQLHelper, Guid ItemGuid, double Price)
@ -52,6 +54,13 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Command_Update} {TableName} {Command_Set} {Column_Price} = @Price {Command_Where} {Column_ItemGuid} = @ItemGuid";
}
public static string Update_MarketItemStock(SQLHelper SQLHelper, Guid ItemGuid, double Stock)
{
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@Stock"] = Stock;
return $"{Command_Update} {TableName} {Command_Set} {Column_Stock} = @Stock {Command_Where} {Column_ItemGuid} = @ItemGuid";
}
public static string Update_MarketItemState(SQLHelper SQLHelper, Guid ItemGuid, MarketItemState state)
{
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
@ -59,12 +68,12 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Command_Update} {TableName} {Command_Set} {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid";
}
public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, long Buyer)
public static string Update_Buy(SQLHelper SQLHelper, Guid ItemGuid, string Buyers)
{
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
SQLHelper.Parameters["@Buyer"] = Buyer;
SQLHelper.Parameters["@Buyers"] = Buyers;
SQLHelper.Parameters["@Status"] = (int)MarketItemState.Purchased;
return $"{Command_Update} {TableName} {Command_Set} {Column_Buyer} = @Buyer, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid";
return $"{Command_Update} {TableName} {Command_Set} {Column_Buyers} = @Buyers, {Column_Status} = @Status {Command_Where} {Column_ItemGuid} = @ItemGuid";
}
public static string Update_MarketItemFinishTime(SQLHelper SQLHelper, Guid ItemGuid, DateTime FinishTime)

View File

@ -65,5 +65,19 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
SQLHelper.Parameters["@OfferId"] = OfferId;
return $"{Command_Delete} {Command_From} {TableName_Backup} {Command_Where} {Column_OfferId} = @OfferId";
}
public static string Delete_OfferItemsByOfferIdAndItemGuid(SQLHelper SQLHelper, long OfferId, Guid ItemGuid)
{
SQLHelper.Parameters["@OfferId"] = OfferId;
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
return $"{Command_Delete} {Command_From} {TableName} {Command_Where} {Column_OfferId} = @OfferId {Command_And} {Column_ItemGuid} = @ItemGuid";
}
public static string Delete_OfferItemsBackupByOfferIdAndItemGuid(SQLHelper SQLHelper, long OfferId, Guid ItemGuid)
{
SQLHelper.Parameters["@OfferId"] = OfferId;
SQLHelper.Parameters["@ItemGuid"] = ItemGuid.ToString();
return $"{Command_Delete} {Command_From} {TableName_Backup} {Command_Where} {Column_OfferId} = @OfferId {Command_And} {Column_ItemGuid} = @ItemGuid";
}
}
}

View File

@ -39,10 +39,11 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
SQLHelper.Parameters["@Offeror"] = Offeror;
SQLHelper.Parameters["@Offeree"] = Offeree;
SQLHelper.Parameters["@Status"] = (int)Status;
SQLHelper.Parameters["@CreateTime"] = DateTime.Now;
SQLHelper.Parameters["@NegotiatedTimes"] = NegotiatedTimes;
return $"{Command_Insert} {Command_Into} {TableName} ({Column_Offeror}, {Column_Offeree}, {Column_Status}, {Column_NegotiatedTimes}) " +
$"{Command_Values} (@Offeror, @Offeree, @Status, @NegotiatedTimes)";
return $"{Command_Insert} {Command_Into} {TableName} ({Column_Offeror}, {Column_Offeree}, {Column_Status}, {Column_CreateTime}), {Column_NegotiatedTimes}) " +
$"{Command_Values} (@Offeror, @Offeree, @Status, @CreateTime, @NegotiatedTimes)";
}
public static string Update_OfferStatus(SQLHelper SQLHelper, long Id, OfferState Status)

View File

@ -23,7 +23,7 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
public const string Select_Rooms = $"{Command_Select} {TableName}.{Command_All}, {UserQuery.TableName}.{UserQuery.Column_Username} {Command_As} {Column_RoomMasterName} " +
$"{Command_From} {TableName} {Command_LeftJoin} {UserQuery.TableName} {Command_On} {UserQuery.TableName}.{UserQuery.Column_Id} = {TableName}.{Column_RoomMaster}";
public static string Select_IsExistRoom(SQLHelper SQLHelper, string Roomid)
public static string Select_RoomByRoomId(SQLHelper SQLHelper, string Roomid)
{
SQLHelper.Parameters["@Roomid"] = Roomid;
return $"{Command_Select} {Command_All} {Command_From} {TableName} {Command_Where} {Column_Roomid} = @Roomid";

View File

@ -33,13 +33,13 @@ namespace Milimoe.FunGame.Core.Library.SQLScript.Entity
return $"{Select_Users} {Command_Where} {Column_Id} = @Id";
}
public static string Select_IsExistEmail(SQLHelper SQLHelper, string Email)
public static string Select_UserByEmail(SQLHelper SQLHelper, string Email)
{
SQLHelper.Parameters["@Email"] = Email;
return $"{Select_Users} {Command_Where} {Column_Email} = @Email";
}
public static string Select_IsExistUsername(SQLHelper SQLHelper, string Username)
public static string Select_UserByUsername(SQLHelper SQLHelper, string Username)
{
SQLHelper.Parameters["@Username"] = Username;
return $"{Select_Users} {Command_Where} {Column_Username} = @Username";

View File

@ -0,0 +1 @@
ALTER TABLE MarketItems CHANGE COLUMN Buyer Buyers VARCHAR(255) NOT NULL DEFAULT '';

View File

@ -0,0 +1,43 @@
PRAGMA foreign_keys = OFF;
BEGIN TRANSACTION;
ALTER TABLE MarketItems RENAME TO _MarketItems_old;
CREATE TABLE MarketItems (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ItemGuid TEXT NOT NULL DEFAULT '',
UserId INTEGER NOT NULL DEFAULT 0,
Price REAL NOT NULL DEFAULT 0,
CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')),
FinishTime DATETIME DEFAULT NULL,
Status INTEGER NOT NULL DEFAULT 0,
Buyers TEXT NOT NULL DEFAULT ''
);
INSERT INTO MarketItems (
Id,
ItemGuid,
UserId,
Price,
CreateTime,
FinishTime,
Status,
Buyers
)
SELECT
Id,
ItemGuid,
UserId,
Price,
CreateTime,
FinishTime,
Status,
CAST(Buyer AS TEXT)
FROM _MarketItems_old;
DROP TABLE _MarketItems_old;
COMMIT;
PRAGMA foreign_keys = ON;

View File

@ -95,7 +95,7 @@ CREATE TABLE `MarketItems` (
`CreateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`FinishTime` datetime DEFAULT NULL,
`Status` int(10) NOT NULL DEFAULT '0',
`Buyer` bigint(20) NOT NULL DEFAULT '0',
`Buyers` varchar(255) NOT NULL DEFAULT '',
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

View File

@ -92,7 +92,7 @@ CREATE TABLE MarketItems (
CreateTime DATETIME NOT NULL DEFAULT (DATETIME('now')),
FinishTime DATETIME DEFAULT NULL,
Status INTEGER NOT NULL DEFAULT 0,
Buyer INTEGER NOT NULL DEFAULT 0
Buyers TEXT NOT NULL DEFAULT ''
);
-- ----------------------------

18
Model/AIDecision.cs Normal file
View File

@ -0,0 +1,18 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Interface.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Model
{
public class AIDecision
{
public CharacterActionType ActionType { get; set; } = CharacterActionType.EndTurn;
public Grid? TargetMoveGrid { get; set; } = null;
public ISkill? SkillToUse { get; set; } = null;
public Item? ItemToUse { get; set; } = null;
public List<Character> Targets { get; set; } = [];
public double Score { get; set; } = 0;
public bool IsPureMove { get; set; } = false;
}
}

View File

@ -22,8 +22,8 @@ namespace Milimoe.FunGame.Core.Model
characters ??= [];
return type switch
{
RoomType.Team => new TeamGamingQueue(writer),
_ => new MixGamingQueue(writer)
RoomType.Team => new TeamGamingQueue(characters, writer),
_ => new MixGamingQueue(characters, writer)
};
}
}

View File

@ -1,4 +1,5 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Model
{
@ -37,6 +38,14 @@ namespace Milimoe.FunGame.Core.Model
{ "E", 200 },
};
/// <summary>
/// 使用的魔法类型
/// </summary>
public HashSet<MagicType> UseMagicType { get; set; } = [
MagicType.None, MagicType.Starmark, MagicType.PurityNatural, MagicType.PurityContemporary,
MagicType.Element, MagicType.Bright, MagicType.Shadow, MagicType.Aster, MagicType.SpatioTemporal
];
/// <summary>
/// 初始生命值
/// </summary>
@ -286,6 +295,251 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
public double TakenDamageGetEPMax { get; set; } = 15;
/// <summary>
/// 单手剑的基础伤害倍率
/// </summary>
public double OneHandedSwordBaseMultiplier { get; set; } = 1.0;
/// <summary>
/// 单手剑的基础伤害倍率成长
/// </summary>
public double OneHandedSwordLevelBonus { get; set; } = 0.05;
/// <summary>
/// 双手剑的基础伤害倍率
/// </summary>
public double TwoHandedSwordBaseMultiplier { get; set; } = 1.2;
/// <summary>
/// 双手剑的基础伤害倍率成长
/// </summary>
public double TwoHandedSwordLevelBonus { get; set; } = 0.06;
/// <summary>
/// 弓的基础伤害倍率
/// </summary>
public double BowBaseMultiplier { get; set; } = 0.9;
/// <summary>
/// 弓的基础伤害倍率成长
/// </summary>
public double BowLevelBonus { get; set; } = 0.04;
/// <summary>
/// 手枪的基础伤害倍率
/// </summary>
public double PistolBaseMultiplier { get; set; } = 0.9;
/// <summary>
/// 手枪的基础伤害倍率成长
/// </summary>
public double PistolLevelBonus { get; set; } = 0.03;
/// <summary>
/// 步枪的基础伤害倍率
/// </summary>
public double RifleBaseMultiplier { get; set; } = 1.1;
/// <summary>
/// 步枪的基础伤害倍率成长
/// </summary>
public double RifleLevelBonus { get; set; } = 0.05;
/// <summary>
/// 双持短刀的基础伤害倍率
/// </summary>
public double DualDaggersBaseMultiplier { get; set; } = 0.85;
/// <summary>
/// 双持短刀的基础伤害倍率成长
/// </summary>
public double DualDaggersLevelBonus { get; set; } = 0.04;
/// <summary>
/// 法器的基础伤害倍率
/// </summary>
public double TalismanBaseMultiplier { get; set; } = 1.0;
/// <summary>
/// 法器的基础伤害倍率成长
/// </summary>
public double TalismanLevelBonus { get; set; } = 0.05;
/// <summary>
/// 法杖的基础伤害倍率
/// </summary>
public double StaffBaseMultiplier { get; set; } = 1.15;
/// <summary>
/// 法杖的基础伤害倍率成长
/// </summary>
public double StaffLevelBonus { get; set; } = 0.04;
/// <summary>
/// 长柄武器的基础伤害倍率
/// </summary>
public double PolearmBaseMultiplier { get; set; } = 0.95;
/// <summary>
/// 长柄武器的基础伤害倍率成长
/// </summary>
public double PolearmLevelBonus { get; set; } = 0.05;
/// <summary>
/// 拳套的基础伤害倍率
/// </summary>
public double GauntletBaseMultiplier { get; set; } = 1.05;
/// <summary>
/// 拳套的基础伤害倍率成长
/// </summary>
public double GauntletLevelBonus { get; set; } = 0.05;
/// <summary>
/// 暗器的基础伤害倍率
/// </summary>
public double HiddenWeaponBaseMultiplier { get; set; } = 0.9;
/// <summary>
/// 暗器的基础伤害倍率成长
/// </summary>
public double HiddenWeaponLevelBonus { get; set; } = 0.05;
/// <summary>
/// 单手剑的硬直时间
/// </summary>
public double OneHandedSwordHardness { get; set; } = 8;
/// <summary>
/// 双手剑的硬直时间
/// </summary>
public double TwoHandedSwordHardness { get; set; } = 12;
/// <summary>
/// 弓的硬直时间
/// </summary>
public double BowHardness { get; set; } = 9;
/// <summary>
/// 手枪的硬直时间
/// </summary>
public double PistolHardness { get; set; } = 6;
/// <summary>
/// 步枪的硬直时间
/// </summary>
public double RifleHardness { get; set; } = 11;
/// <summary>
/// 双持短刀的硬直时间
/// </summary>
public double DualDaggersHardness { get; set; } = 7;
/// <summary>
/// 法器的硬直时间
/// </summary>
public double TalismanHardness { get; set; } = 10;
/// <summary>
/// 法杖的硬直时间
/// </summary>
public double StaffHardness { get; set; } = 12;
/// <summary>
/// 长柄武器的硬直时间
/// </summary>
public double PolearmHardness { get; set; } = 10;
/// <summary>
/// 拳套的硬直时间
/// </summary>
public double GauntletHardness { get; set; } = 8;
/// <summary>
/// 暗器的硬直时间
/// </summary>
public double HiddenWeaponHardness { get; set; } = 7;
/// <summary>
/// 单手剑的攻击距离
/// </summary>
public int OneHandedSwordAttackRange { get; set; } = 1;
/// <summary>
/// 双手剑的攻击距离
/// </summary>
public int TwoHandedSwordAttackRange { get; set; } = 2;
/// <summary>
/// 弓的攻击距离
/// </summary>
public int BowAttackRange { get; set; } = 4;
/// <summary>
/// 手枪的攻击距离
/// </summary>
public int PistolAttackRange { get; set; } = 3;
/// <summary>
/// 步枪的攻击距离
/// </summary>
public int RifleAttackRange { get; set; } = 5;
/// <summary>
/// 双持短刀的攻击距离
/// </summary>
public int DualDaggersAttackRange { get; set; } = 1;
/// <summary>
/// 法器的攻击距离
/// </summary>
public int TalismanAttackRange { get; set; } = 5;
/// <summary>
/// 法杖的攻击距离
/// </summary>
public int StaffAttackRange { get; set; } = 3;
/// <summary>
/// 长柄武器的攻击距离
/// </summary>
public int PolearmAttackRange { get; set; } = 2;
/// <summary>
/// 拳套的攻击距离
/// </summary>
public int GauntletAttackRange { get; set; } = 1;
/// <summary>
/// 暗器的攻击距离
/// </summary>
public int HiddenWeaponAttackRange { get; set; } = 4;
/// <summary>
/// 核心角色的移动距离
/// </summary>
public int RoleMOV_Core { get; set; } = 3;
/// <summary>
/// 先锋角色的移动距离
/// </summary>
public int RoleMOV_Vanguard { get; set; } = 6;
/// <summary>
/// 近卫角色的移动距离
/// </summary>
public int RoleMOV_Guardian { get; set; } = 5;
/// <summary>
/// 支援角色的移动距离
/// </summary>
public int RoleMOV_Support { get; set; } = 4;
/// <summary>
/// 治疗角色的移动距离
/// </summary>
public int RoleMOV_Medic { get; set; } = 3;
/// <summary>
/// 应用此游戏平衡常数给实体
/// </summary>

View File

@ -38,7 +38,7 @@ namespace Milimoe.FunGame.Core.Model
public void AddOrUpdateEvent(string name, IEnumerable<Activity> activities)
{
Events[name] = new(activities);
Events[name] = [.. activities];
}
public bool RemoveEvent(string name)

File diff suppressed because it is too large Load Diff

View File

@ -111,7 +111,7 @@ namespace Milimoe.FunGame.Core.Model
}
/// <summary>
/// 创建一个混战游戏队列并初始化行动顺序表
/// 创建一个混战游戏队列并初始化角色
/// </summary>
/// <param name="characters"></param>
/// <param name="writer"></param>

View File

@ -1,6 +1,6 @@
namespace Milimoe.FunGame.Core.Model
{
public class RecurringTask(string name, TimeSpan interval, Action action)
public class RecurringTask(string name, TimeSpan interval, Action action, Action<Exception>? error = null)
{
/// <summary>
/// 任务名称
@ -31,5 +31,10 @@
/// 最后一次运行时发生的错误
/// </summary>
public Exception? Error { get; set; }
/// <summary>
/// 捕获异常后,触发的回调函数
/// </summary>
public Action<Exception>? ErrorHandler { get; set; } = error;
}
}

View File

@ -98,7 +98,7 @@ namespace Milimoe.FunGame.Core.Model
public Room GetRoom(string roomid) => _List.TryGetValue(roomid, out Room? room) ? room : General.HallInstance;
public bool IsExist(string roomid) => _List.ContainsKey(roomid);
public bool Exists(string roomid) => _List.ContainsKey(roomid);
public void SetRoomMaster(string roomid, User user)
{

View File

@ -14,6 +14,7 @@ namespace Milimoe.FunGame.Core.Entity
public string SkillCost { get; set; } = "";
public Item? Item { get; set; } = null;
public bool HasKill { get; set; } = false;
public List<Character> Assists { get; set; } = [];
public Dictionary<Character, double> Damages { get; set; } = [];
public Dictionary<Character, bool> IsCritical { get; set; } = [];
public Dictionary<Character, bool> IsEvaded { get; set; } = [];
@ -65,6 +66,7 @@ namespace Milimoe.FunGame.Core.Entity
builder.AppendLine(string.Join(" / ", GetTargetsState()));
if (DeathContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", DeathContinuousKilling)}");
if (ActorContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", ActorContinuousKilling)}");
if (Assists.Count > 0) builder.AppendLine($"本回合助攻:[ {string.Join(" ] / [ ", Assists)} ]");
}
if (ActionType == CharacterActionType.PreCastSkill && Skill != null)
@ -75,11 +77,17 @@ namespace Milimoe.FunGame.Core.Entity
}
else
{
builder.AppendLine($"[ {Actor} ]{Skill.Name}{SkillCost}-> ");
builder.Append($"[ {Actor} ] {Skill.Name}{SkillCost}-> ");
builder.AppendLine(string.Join(" / ", GetTargetsState()));
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
}
}
else if (ActionType == CharacterActionType.UseItem && Item != null)
{
builder.Append($"[ {Actor} ] {Item.Name}{(SkillCost != "" ? $"{SkillCost}" : " ")}-> ");
builder.AppendLine(string.Join(" / ", GetTargetsState()));
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
}
else
{
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
@ -106,6 +114,7 @@ namespace Milimoe.FunGame.Core.Entity
string hasDamage = "";
string hasHeal = "";
string hasEffect = "";
string hasEvaded = "";
if (Damages.TryGetValue(target, out double damage))
{
hasDamage = $"伤害:{damage:0.##}";
@ -126,19 +135,21 @@ namespace Milimoe.FunGame.Core.Entity
{
if (ActionType == CharacterActionType.NormalAttack)
{
hasDamage = "完美闪避";
hasEvaded = hasDamage == "" ? "完美闪避" : "闪避";
}
else if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill))
{
hasDamage = "技能免疫";
hasEvaded = "技能免疫";
}
}
if (IsImmune.ContainsKey(target) && hasDamage != "" && target != Actor)
{
hasDamage = "免疫";
}
string[] strs = [hasDamage, hasHeal, hasEffect];
strings.Add($"[ {target}{string.Join(" / ", strs.Where(s => s != ""))}]");
string[] strs = [hasDamage, hasHeal, hasEffect, hasEvaded];
strs = [.. strs.Where(s => s != "")];
if (strs.Length == 0) strings.Add($"[ {target} ]");
else strings.Add($"[ {target}{string.Join(" / ", strs)}]");
}
return strings;
}

View File

@ -1,6 +1,6 @@
namespace Milimoe.FunGame.Core.Model
{
public class ScheduledTask(string name, TimeSpan timeSpan, Action action)
public class ScheduledTask(string name, TimeSpan timeSpan, Action action, Action<Exception>? error = null)
{
/// <summary>
/// 任务名称
@ -31,5 +31,10 @@
/// 最后一次运行时发生的错误
/// </summary>
public Exception? Error { get; set; }
/// <summary>
/// 捕获异常后,触发的回调函数
/// </summary>
public Action<Exception>? ErrorHandler { get; set; } = error;
}
}

View File

@ -35,7 +35,7 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="characters"></param>
public void AddTeam(string teamName, IEnumerable<Character> characters)
{
if (teamName != "" && characters.Any())
if (teamName != "" && characters.Any(c => c.HP > 0))
{
_teams.Add(teamName, new(teamName, characters));
}
@ -114,10 +114,20 @@ namespace Milimoe.FunGame.Core.Model
/// <returns></returns>
protected override async Task OnDeathCalculation(Character death, Character killer)
{
if (killer == death)
{
return;
}
Team? team = GetTeam(killer);
if (team != null)
{
team.Score++;
Team? team2 = GetTeam(death);
if (team == team2)
{
WriteLine($"[ {team} ] 受到了击杀队友惩罚!减少死亡竞赛得分!!");
team.Score--;
}
else team.Score++;
}
else await Task.CompletedTask;
}
@ -137,7 +147,7 @@ namespace Milimoe.FunGame.Core.Model
{
string[] teamActive = [.. Teams.OrderByDescending(kv => kv.Value.Score).Select(kv =>
{
int activeCount = kv.Value.GetActiveCharacters(this).Count;
int activeCount = kv.Value.GetActiveCharacters().Count;
if (kv.Value == killTeam)
{
activeCount += 1;
@ -149,7 +159,7 @@ namespace Milimoe.FunGame.Core.Model
if (deathTeam != null)
{
List<Character> remain = deathTeam.GetActiveCharacters(this);
List<Character> remain = deathTeam.GetActiveCharacters();
int remainCount = remain.Count;
if (remainCount == 0)
{
@ -165,8 +175,7 @@ namespace Milimoe.FunGame.Core.Model
if (killTeam != null)
{
List<Character> actives = killTeam.GetActiveCharacters(this);
actives.Add(killer);
List<Character> actives = killTeam.GetActiveCharacters();
int remainCount = actives.Count;
if (remainCount > 0 && MaxRespawnTimes == 0)
{
@ -288,7 +297,7 @@ namespace Milimoe.FunGame.Core.Model
}
/// <summary>
/// 创建一个团队游戏队列并初始化行动顺序表
/// 创建一个团队游戏队列并初始化角色
/// </summary>
/// <param name="characters"></param>
/// <param name="writer"></param>

View File

@ -22,7 +22,8 @@ namespace Milimoe.FunGame.Core.Service
Converters = { new DateTimeConverter(), new DataTableConverter(), new DataSetConverter(), new UserConverter(), new RoomConverter(),
new CharacterConverter(), new MagicResistanceConverter(), new EquipSlotConverter(), new SkillConverter(), new EffectConverter(), new ItemConverter(),
new InventoryConverter(), new NormalAttackConverter(), new ClubConverter(), new GoodsConverter(), new StoreConverter(),
new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter()
new NovelOptionConverter(), new NovelNodeConverter(), new ShieldConverter(), new RoundRecordConverter(), new ActivityConverter(), new QuestConverter(),
new MarketConverter(), new MarketItemConverter()
}
};