mirror of
https://github.com/project-redbud/FunGame-Core.git
synced 2025-12-05 08:09:02 +00:00
添加了地图的移动规则
This commit is contained in:
parent
9693accdd1
commit
e1cc31110b
@ -749,10 +749,16 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
public double ExCDR { get; set; } = 0;
|
public double ExCDR { get; set; } = 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 攻击距离 [ 与技能和物品相关 ] [ 单位:格 ]
|
/// 攻击距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[InitOptional]
|
[InitOptional]
|
||||||
public double ATR { get; set; } = 1;
|
public int ATR { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 行动力/可移动距离 [ 与技能和物品相关 ] [ 单位:格(半径) ]
|
||||||
|
/// </summary>
|
||||||
|
[InitOptional]
|
||||||
|
public int MOV { get; set; } = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 暴击率(%) = [ 与敏捷相关 ] + 额外暴击率(%)
|
/// 暴击率(%) = [ 与敏捷相关 ] + 额外暴击率(%)
|
||||||
@ -2017,6 +2023,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
c.Lifesteal = Lifesteal;
|
c.Lifesteal = Lifesteal;
|
||||||
c.Shield = Shield.Copy();
|
c.Shield = Shield.Copy();
|
||||||
c.ATR = ATR;
|
c.ATR = ATR;
|
||||||
|
c.MOV = MOV;
|
||||||
c.MagicType = MagicType;
|
c.MagicType = MagicType;
|
||||||
c.ImmuneType = ImmuneType;
|
c.ImmuneType = ImmuneType;
|
||||||
}
|
}
|
||||||
@ -2126,6 +2133,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
ExAccelerationCoefficient = c.ExAccelerationCoefficient;
|
ExAccelerationCoefficient = c.ExAccelerationCoefficient;
|
||||||
ExCDR = c.ExCDR;
|
ExCDR = c.ExCDR;
|
||||||
ATR = c.ATR;
|
ATR = c.ATR;
|
||||||
|
MOV = c.MOV;
|
||||||
ExCritRate = c.ExCritRate;
|
ExCritRate = c.ExCritRate;
|
||||||
ExCritDMG = c.ExCritDMG;
|
ExCritDMG = c.ExCritDMG;
|
||||||
ExEvadeRate = c.ExEvadeRate;
|
ExEvadeRate = c.ExEvadeRate;
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using Milimoe.FunGame.Core.Api.Utility;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
using Milimoe.FunGame.Core.Interface.Base;
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
using Milimoe.FunGame.Core.Interface.Entity;
|
using Milimoe.FunGame.Core.Interface.Entity;
|
||||||
|
using Milimoe.FunGame.Core.Library.Common.Addon;
|
||||||
using Milimoe.FunGame.Core.Library.Constant;
|
using Milimoe.FunGame.Core.Library.Constant;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Entity
|
namespace Milimoe.FunGame.Core.Entity
|
||||||
@ -387,6 +388,16 @@ 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>
|
||||||
/// 在完成伤害结算后
|
/// 在完成伤害结算后
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -542,6 +553,18 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 开始选择移动目标前
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <param name="map"></param>
|
||||||
|
public virtual void BeforeSelectTargetGrid(Character character, List<Character> enemys, List<Character> teammates, GameMap map)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 开始选择目标前,修改可选择的 <paramref name="enemys"/>, <paramref name="teammates"/> 列表<para/>
|
/// 开始选择目标前,修改可选择的 <paramref name="enemys"/>, <paramref name="teammates"/> 列表<para/>
|
||||||
/// <see cref="ISkill"/> 有两种,使用时注意判断是 <see cref="Entity.Skill"/> 还是 <see cref="NormalAttack"/>
|
/// <see cref="ISkill"/> 有两种,使用时注意判断是 <see cref="Entity.Skill"/> 还是 <see cref="NormalAttack"/>
|
||||||
|
|||||||
@ -90,6 +90,11 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsMagic => SkillType == SkillType.Magic;
|
public bool IsMagic => SkillType == SkillType.Magic;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否无视施法距离(全图施法),魔法默认为 true,战技默认为 false
|
||||||
|
/// </summary>
|
||||||
|
public bool CastAnyWhere { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 施法距离 [ 单位:格 ]
|
/// 施法距离 [ 单位:格 ]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -254,6 +259,7 @@ namespace Milimoe.FunGame.Core.Entity
|
|||||||
protected Skill(SkillType type, Character? character = null)
|
protected Skill(SkillType type, Character? character = null)
|
||||||
{
|
{
|
||||||
SkillType = type;
|
SkillType = type;
|
||||||
|
CastAnyWhere = SkillType == SkillType.Magic;
|
||||||
Character = character;
|
Character = character;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -154,6 +154,16 @@ namespace Milimoe.FunGame.Core.Interface.Base
|
|||||||
/// <returns></returns>
|
/// <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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取移动目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <param name="map"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public Task<Grid> SelectTargetGridAsync(Character character, List<Character> enemys, List<Character> teammates, GameMap map);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取技能目标
|
/// 选取技能目标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -319,13 +319,36 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon.Example
|
|||||||
|
|
||||||
public override string Author => "FunGamer";
|
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 float Size => 4.0f;
|
||||||
|
|
||||||
|
public override GameMap InitGamingQueue(IGamingQueue queue)
|
||||||
|
{
|
||||||
|
// 因为模组在模组管理器中都是单例的,所以每次游戏都需要返回一个新的地图对象给队列
|
||||||
|
GameMap map = new ExampleGameMap();
|
||||||
|
|
||||||
|
// 做一些绑定,以便介入游戏队列
|
||||||
|
/// 但是,传入的 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)
|
||||||
|
{
|
||||||
|
// 介入选择,假设这里更新界面,让玩家选择目的地
|
||||||
|
await Task.CompletedTask;
|
||||||
|
return Grid.Empty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
using Milimoe.FunGame.Core.Api.Utility;
|
using Milimoe.FunGame.Core.Api.Utility;
|
||||||
|
using Milimoe.FunGame.Core.Entity;
|
||||||
using Milimoe.FunGame.Core.Interface.Addons;
|
using Milimoe.FunGame.Core.Interface.Addons;
|
||||||
|
using Milimoe.FunGame.Core.Interface.Base;
|
||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Library.Common.Addon
|
namespace Milimoe.FunGame.Core.Library.Common.Addon
|
||||||
{
|
{
|
||||||
@ -28,17 +30,17 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 长度
|
/// 长度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract float Length { get; }
|
public abstract int Length { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 宽度
|
/// 宽度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract float Width { get; }
|
public abstract int Width { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 高度
|
/// 高度
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract float Height { get; }
|
public abstract int Height { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格子大小
|
/// 格子大小
|
||||||
@ -50,6 +52,16 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<long, Grid> Grids { get; } = [];
|
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>
|
/// <summary>
|
||||||
/// 使用坐标获取格子,0号格子的坐标是(0, 0),如果你还有高度的话,则是(0, 0, 0)
|
/// 使用坐标获取格子,0号格子的坐标是(0, 0),如果你还有高度的话,则是(0, 0, 0)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,14 +69,34 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <param name="y"></param>
|
/// <param name="y"></param>
|
||||||
/// <param name="z"></param>
|
/// <param name="z"></param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
/// 使用坐标获取格子,从0号开始
|
/// 使用编号获取格子,从0号开始
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id"></param>
|
/// <param name="id"></param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
/// 加载标记
|
/// 加载标记
|
||||||
@ -88,13 +120,15 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
// 地图加载后,不允许再次加载此地图
|
// 地图加载后,不允许再次加载此地图
|
||||||
IsLoaded = true;
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,5 +152,221 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// <returns></returns>
|
||||||
|
public virtual List<Grid> GetGridsByRange(Grid grid, int range)
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
int y = grid.Y;
|
||||||
|
int z = grid.Z;
|
||||||
|
if (GridsByCoordinate.TryGetValue((x, y, z), out Grid? select) && select != null)
|
||||||
|
{
|
||||||
|
grids.Add(select);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grids.RemoveAll(g => g.Id == grid.Id);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (current.Id == target.Id)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录走到某个格子时的步数
|
||||||
|
Queue<(Grid grid, int steps)> queue = new();
|
||||||
|
// 记录已访问的格子
|
||||||
|
HashSet<long> visited = [];
|
||||||
|
|
||||||
|
// 将起始格子加入队列,步数为0,并标记为已访问
|
||||||
|
queue.Enqueue((current, 0));
|
||||||
|
visited.Add(current.Id);
|
||||||
|
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
var (currentGrid, currentSteps) = queue.Dequeue();
|
||||||
|
|
||||||
|
// 如果当前格子就是目标格子,则找到了最短路径
|
||||||
|
if (currentGrid.Id == target.Id)
|
||||||
|
{
|
||||||
|
realGrid?.Characters.Remove(character);
|
||||||
|
current.Characters.Remove(character);
|
||||||
|
target.Characters.Add(character);
|
||||||
|
Characters[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="queue"></param>
|
||||||
|
public virtual GameMap InitGamingQueue(IGamingQueue queue)
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,8 +3,13 @@ using Milimoe.FunGame.Core.Entity;
|
|||||||
|
|
||||||
namespace Milimoe.FunGame.Core.Library.Common.Addon
|
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>
|
||||||
/// 格子编号
|
/// 格子编号
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -13,17 +18,17 @@ namespace Milimoe.FunGame.Core.Library.Common.Addon
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格子在地图中的x坐标
|
/// 格子在地图中的x坐标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float X { get; } = x;
|
public int X { get; } = x;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格子在地图中的y坐标
|
/// 格子在地图中的y坐标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Y { get; } = y;
|
public int Y { get; } = y;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 格子在地图中的z坐标
|
/// 格子在地图中的z坐标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public float Z { get; } = z;
|
public int Z { get; } = z;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 是谁站在这格子上?
|
/// 是谁站在这格子上?
|
||||||
|
|||||||
@ -198,7 +198,10 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
|
|||||||
result.ExCDR = reader.GetDouble();
|
result.ExCDR = reader.GetDouble();
|
||||||
break;
|
break;
|
||||||
case nameof(Character.ATR):
|
case nameof(Character.ATR):
|
||||||
result.ATR = reader.GetDouble();
|
result.ATR = reader.GetInt32();
|
||||||
|
break;
|
||||||
|
case nameof(Character.MOV):
|
||||||
|
result.MOV = reader.GetInt32();
|
||||||
break;
|
break;
|
||||||
case nameof(Character.ExCritRate):
|
case nameof(Character.ExCritRate):
|
||||||
result.ExCritRate = reader.GetDouble();
|
result.ExCritRate = reader.GetDouble();
|
||||||
@ -309,6 +312,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
|
|||||||
writer.WriteNumber(nameof(Character.ExAccelerationCoefficient), value.ExAccelerationCoefficient);
|
writer.WriteNumber(nameof(Character.ExAccelerationCoefficient), value.ExAccelerationCoefficient);
|
||||||
writer.WriteNumber(nameof(Character.ExCDR), value.ExCDR);
|
writer.WriteNumber(nameof(Character.ExCDR), value.ExCDR);
|
||||||
writer.WriteNumber(nameof(Character.ATR), value.ATR);
|
writer.WriteNumber(nameof(Character.ATR), value.ATR);
|
||||||
|
writer.WriteNumber(nameof(Character.MOV), value.MOV);
|
||||||
writer.WriteNumber(nameof(Character.ExCritRate), value.ExCritRate);
|
writer.WriteNumber(nameof(Character.ExCritRate), value.ExCritRate);
|
||||||
writer.WriteNumber(nameof(Character.ExCritDMG), value.ExCritDMG);
|
writer.WriteNumber(nameof(Character.ExCritDMG), value.ExCritDMG);
|
||||||
writer.WriteNumber(nameof(Character.ExEvadeRate), value.ExEvadeRate);
|
writer.WriteNumber(nameof(Character.ExEvadeRate), value.ExEvadeRate);
|
||||||
|
|||||||
@ -41,6 +41,9 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
|
|||||||
case nameof(Skill.Level):
|
case nameof(Skill.Level):
|
||||||
result.Level = reader.GetInt32();
|
result.Level = reader.GetInt32();
|
||||||
break;
|
break;
|
||||||
|
case nameof(Skill.CastAnyWhere):
|
||||||
|
result.CastAnyWhere = reader.GetBoolean();
|
||||||
|
break;
|
||||||
case nameof(Skill.CastRange):
|
case nameof(Skill.CastRange):
|
||||||
result.CastRange = reader.GetInt32();
|
result.CastRange = reader.GetInt32();
|
||||||
break;
|
break;
|
||||||
@ -128,6 +131,7 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
|
|||||||
if (value.GeneralDescription.Length > 0) writer.WriteString(nameof(Skill.GeneralDescription), value.GeneralDescription);
|
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.Slogan.Length > 0) writer.WriteString(nameof(Skill.Slogan), value.Slogan);
|
||||||
if (value.Level > 0) writer.WriteNumber(nameof(Skill.Level), value.Level);
|
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);
|
writer.WriteNumber(nameof(Skill.CastRange), value.CastRange);
|
||||||
if (value.CanSelectSelf) writer.WriteBoolean(nameof(Skill.CanSelectSelf), value.CanSelectSelf);
|
if (value.CanSelectSelf) writer.WriteBoolean(nameof(Skill.CanSelectSelf), value.CanSelectSelf);
|
||||||
if (!value.CanSelectEnemy) writer.WriteBoolean(nameof(Skill.CanSelectEnemy), value.CanSelectEnemy);
|
if (!value.CanSelectEnemy) writer.WriteBoolean(nameof(Skill.CanSelectEnemy), value.CanSelectEnemy);
|
||||||
|
|||||||
@ -296,7 +296,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 战棋地图(#TODO)
|
#region 战棋地图
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 加载地图
|
/// 加载地图
|
||||||
@ -304,7 +304,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// <param name="map"></param>
|
/// <param name="map"></param>
|
||||||
public void LoadGameMap(GameMap map)
|
public void LoadGameMap(GameMap map)
|
||||||
{
|
{
|
||||||
_map = map;
|
_map = map.InitGamingQueue(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -665,6 +665,9 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理地图上的特效
|
||||||
|
_map?.OnTimeElapsed(timeToReduce);
|
||||||
|
|
||||||
// 移除到时间的特效
|
// 移除到时间的特效
|
||||||
List<Effect> effects = [.. character.Effects];
|
List<Effect> effects = [.. character.Effects];
|
||||||
foreach (Effect effect in effects)
|
foreach (Effect effect in effects)
|
||||||
@ -782,7 +785,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
List<Skill> rewards = GetRoundRewards(TotalRound, character);
|
List<Skill> rewards = GetRoundRewards(TotalRound, character);
|
||||||
|
|
||||||
// 基础硬直时间
|
// 基础硬直时间
|
||||||
double baseTime = 10;
|
double baseTime = 0;
|
||||||
bool isCheckProtected = true;
|
bool isCheckProtected = true;
|
||||||
|
|
||||||
// 队友列表
|
// 队友列表
|
||||||
@ -824,6 +827,14 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
bool decided = false;
|
bool decided = false;
|
||||||
// 最大取消次数
|
// 最大取消次数
|
||||||
int cancelTimes = 3;
|
int cancelTimes = 3;
|
||||||
|
// 此变量控制角色移动后可以继续选择其他的行动
|
||||||
|
bool moved = false;
|
||||||
|
|
||||||
|
Grid? currentGrid = null;
|
||||||
|
if (_map != null)
|
||||||
|
{
|
||||||
|
currentGrid = _map.GetCharacterCurrentGrid(character);
|
||||||
|
}
|
||||||
|
|
||||||
// 行动开始前,可以修改可选取的角色列表
|
// 行动开始前,可以修改可选取的角色列表
|
||||||
Dictionary<Character, int> continuousKillingTemp = new(_continuousKilling);
|
Dictionary<Character, int> continuousKillingTemp = new(_continuousKilling);
|
||||||
@ -847,7 +858,8 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
bool isAI = CharactersInAI.Contains(character);
|
bool isAI = CharactersInAI.Contains(character);
|
||||||
while (!decided && (!isAI || cancelTimes > 0))
|
while (!decided && (!isAI || cancelTimes > 0))
|
||||||
{
|
{
|
||||||
cancelTimes--;
|
if (moved) moved = false;
|
||||||
|
else cancelTimes--;
|
||||||
type = CharacterActionType.None;
|
type = CharacterActionType.None;
|
||||||
|
|
||||||
// 是否能使用物品和释放技能
|
// 是否能使用物品和释放技能
|
||||||
@ -1008,7 +1020,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
await OnCharacterNormalAttackAsync(character, targets);
|
await OnCharacterNormalAttackAsync(character, targets);
|
||||||
|
|
||||||
character.NormalAttack.Attack(this, character, targets);
|
character.NormalAttack.Attack(this, character, targets);
|
||||||
baseTime = character.NormalAttack.RealHardnessTime;
|
baseTime += character.NormalAttack.RealHardnessTime;
|
||||||
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
||||||
foreach (Effect effect in effects)
|
foreach (Effect effect in effects)
|
||||||
{
|
{
|
||||||
@ -1055,7 +1067,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
await OnCharacterPreCastSkillAsync(character, skillTarget);
|
await OnCharacterPreCastSkillAsync(character, skillTarget);
|
||||||
|
|
||||||
_castingSkills[character] = skillTarget;
|
_castingSkills[character] = skillTarget;
|
||||||
baseTime = skill.RealCastTime;
|
baseTime += skill.RealCastTime;
|
||||||
skill.OnSkillCasting(this, character, targets);
|
skill.OnSkillCasting(this, character, targets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1083,7 +1095,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
skill.BeforeSkillCasted();
|
skill.BeforeSkillCasted();
|
||||||
|
|
||||||
character.EP -= cost;
|
character.EP -= cost;
|
||||||
baseTime = skill.RealHardnessTime;
|
baseTime += skill.RealHardnessTime;
|
||||||
skill.CurrentCD = skill.RealCD;
|
skill.CurrentCD = skill.RealCD;
|
||||||
skill.Enable = false;
|
skill.Enable = false;
|
||||||
LastRound.SkillCost = $"{-cost:0.##} EP";
|
LastRound.SkillCost = $"{-cost:0.##} EP";
|
||||||
@ -1131,7 +1143,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
skill.BeforeSkillCasted();
|
skill.BeforeSkillCasted();
|
||||||
|
|
||||||
character.MP -= cost;
|
character.MP -= cost;
|
||||||
baseTime = skill.RealHardnessTime;
|
baseTime += skill.RealHardnessTime;
|
||||||
skill.CurrentCD = skill.RealCD;
|
skill.CurrentCD = skill.RealCD;
|
||||||
skill.Enable = false;
|
skill.Enable = false;
|
||||||
LastRound.SkillCost = $"{-cost:0.##} MP";
|
LastRound.SkillCost = $"{-cost:0.##} MP";
|
||||||
@ -1146,7 +1158,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
WriteLine($"[ {character} ] 放弃释放技能!");
|
WriteLine($"[ {character} ] 放弃释放技能!");
|
||||||
// 放弃释放技能会获得3的硬直时间
|
// 放弃释放技能会获得3的硬直时间
|
||||||
baseTime = 3;
|
if (baseTime == 0) baseTime = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
||||||
@ -1184,7 +1196,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
skill.BeforeSkillCasted();
|
skill.BeforeSkillCasted();
|
||||||
|
|
||||||
character.EP -= cost;
|
character.EP -= cost;
|
||||||
baseTime = skill.RealHardnessTime;
|
baseTime += skill.RealHardnessTime;
|
||||||
skill.CurrentCD = skill.RealCD;
|
skill.CurrentCD = skill.RealCD;
|
||||||
skill.Enable = false;
|
skill.Enable = false;
|
||||||
LastRound.SkillCost = $"{-cost:0.##} EP";
|
LastRound.SkillCost = $"{-cost:0.##} EP";
|
||||||
@ -1199,7 +1211,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
|
WriteLine($"[ {character} ] 因能量不足放弃释放爆发技!");
|
||||||
// 放弃释放技能会获得3的硬直时间
|
// 放弃释放技能会获得3的硬直时间
|
||||||
baseTime = 3;
|
if (baseTime == 0) baseTime = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
||||||
@ -1224,7 +1236,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
{
|
{
|
||||||
decided = true;
|
decided = true;
|
||||||
LastRound.Item = item;
|
LastRound.Item = item;
|
||||||
baseTime = skill.RealHardnessTime > 0 ? skill.RealHardnessTime : 5;
|
baseTime += skill.RealHardnessTime > 0 ? skill.RealHardnessTime : 5;
|
||||||
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
||||||
foreach (Effect effect in effects)
|
foreach (Effect effect in effects)
|
||||||
{
|
{
|
||||||
@ -1235,7 +1247,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else if (type == CharacterActionType.EndTurn)
|
else if (type == CharacterActionType.EndTurn)
|
||||||
{
|
{
|
||||||
baseTime = 3;
|
baseTime += 3;
|
||||||
if (character.CharacterState == CharacterState.NotActionable ||
|
if (character.CharacterState == CharacterState.NotActionable ||
|
||||||
character.CharacterState == CharacterState.ActionRestricted ||
|
character.CharacterState == CharacterState.ActionRestricted ||
|
||||||
character.CharacterState == CharacterState.BattleRestricted)
|
character.CharacterState == CharacterState.BattleRestricted)
|
||||||
@ -1249,13 +1261,23 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
}
|
}
|
||||||
else if (type == CharacterActionType.Move)
|
else if (type == CharacterActionType.Move)
|
||||||
{
|
{
|
||||||
baseTime = 3;
|
if (_map != null)
|
||||||
decided = true;
|
{
|
||||||
WriteLine($"[ {character} ] 进行了移动,并结束了回合!");
|
baseTime += 4;
|
||||||
await OnCharacterMoveAsync(character);
|
Grid target = await SelectTargetGridAsync(character, enemys, teammates, _map);
|
||||||
|
if (target.Id != -1)
|
||||||
|
{
|
||||||
|
int steps = _map.CharacterMove(character, currentGrid, target);
|
||||||
|
if (steps > 4) baseTime += 0.7 * steps;
|
||||||
|
moved = true;
|
||||||
|
WriteLine($"[ {character} ] 移动了 {steps} 步!");
|
||||||
|
await OnCharacterMoveAsync(character, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
if (baseTime == 0) baseTime += 10;
|
||||||
decided = true;
|
decided = true;
|
||||||
WriteLine($"[ {character} ] 完全行动不能!");
|
WriteLine($"[ {character} ] 完全行动不能!");
|
||||||
}
|
}
|
||||||
@ -2278,6 +2300,37 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
return CharacterActionType.EndTurn;
|
return CharacterActionType.EndTurn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 选取移动目标
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <param name="map"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<Grid> SelectTargetGridAsync(Character character, List<Character> enemys, List<Character> teammates, GameMap map)
|
||||||
|
{
|
||||||
|
List<Effect> effects = [.. character.Effects.Where(e => e.IsInEffect)];
|
||||||
|
foreach (Effect effect in effects)
|
||||||
|
{
|
||||||
|
effect.BeforeSelectTargetGrid(character, enemys, teammates, map);
|
||||||
|
}
|
||||||
|
Grid target = await OnSelectTargetGridAsync(character, enemys, teammates, map);
|
||||||
|
if (target.Id != -1)
|
||||||
|
{
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
if (map.Characters.TryGetValue(character, out Grid? current) && current != null)
|
||||||
|
{
|
||||||
|
List<Grid> grids = map.GetGridsByRange(current, character.MOV);
|
||||||
|
if (grids.Count > 0)
|
||||||
|
{
|
||||||
|
return grids[Random.Shared.Next(grids.Count)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Grid.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取技能目标
|
/// 选取技能目标
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -3269,6 +3322,24 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult<Item?>(null));
|
return await (SelectItem?.Invoke(this, character, items) ?? Task.FromResult<Item?>(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public delegate Task<Grid> SelectTargetGridEventHandler(GamingQueue queue, Character character, List<Character> enemys, List<Character> teammates, GameMap map);
|
||||||
|
/// <summary>
|
||||||
|
/// 选取移动目标事件
|
||||||
|
/// </summary>
|
||||||
|
public event SelectTargetGridEventHandler? SelectTargetGrid;
|
||||||
|
/// <summary>
|
||||||
|
/// 选取移动目标事件
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="character"></param>
|
||||||
|
/// <param name="enemys"></param>
|
||||||
|
/// <param name="teammates"></param>
|
||||||
|
/// <param name="map"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
protected async Task<Grid> OnSelectTargetGridAsync(Character character, List<Character> enemys, List<Character> teammates, GameMap map)
|
||||||
|
{
|
||||||
|
return await (SelectTargetGrid?.Invoke(this, character, enemys, teammates, map) ?? Task.FromResult(Grid.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
public delegate Task<List<Character>> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List<Character> enemys, List<Character> teammates);
|
public delegate Task<List<Character>> SelectSkillTargetsEventHandler(GamingQueue queue, Character caster, Skill skill, List<Character> enemys, List<Character> teammates);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 选取技能目标事件
|
/// 选取技能目标事件
|
||||||
@ -3543,7 +3614,7 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask);
|
await (CharacterGiveUp?.Invoke(this, actor) ?? Task.CompletedTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate Task CharacterMoveEventHandler(GamingQueue queue, Character actor);
|
public delegate Task CharacterMoveEventHandler(GamingQueue queue, Character actor, Grid grid);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 角色移动事件
|
/// 角色移动事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -3552,10 +3623,11 @@ namespace Milimoe.FunGame.Core.Model
|
|||||||
/// 角色移动事件
|
/// 角色移动事件
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="actor"></param>
|
/// <param name="actor"></param>
|
||||||
|
/// <param name="grid"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected async Task OnCharacterMoveAsync(Character actor)
|
protected async Task OnCharacterMoveAsync(Character actor, Grid grid)
|
||||||
{
|
{
|
||||||
await (CharacterMove?.Invoke(this, actor) ?? Task.CompletedTask);
|
await (CharacterMove?.Invoke(this, actor, grid) ?? Task.CompletedTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate Task<bool> GameEndEventHandler(GamingQueue queue, Character winner);
|
public delegate Task<bool> GameEndEventHandler(GamingQueue queue, Character winner);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user