添加地图自动化模拟和新技能

This commit is contained in:
milimoe 2026-01-05 01:31:05 +08:00
parent f2dad7f1b7
commit 3119b3a4c5
Signed by: milimoe
GPG Key ID: 9554D37E4B8991D0
15 changed files with 319 additions and 24 deletions

View File

@ -1,5 +1,6 @@
using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Model;
using Oshima.Core.Constant;
namespace Oshima.FunGame.OshimaMaps
@ -14,11 +15,11 @@ namespace Oshima.FunGame.OshimaMaps
public override string Author => OshimaGameModuleConstant.Author;
public override int Length => 6;
public override int Length => 9;
public override int Width => 6;
public override int Width => 9;
public override int Height => 3;
public override int Height => 1;
public override float Size => 6;
@ -26,7 +27,18 @@ namespace Oshima.FunGame.OshimaMaps
{
GameMap map = new FastAutoMap();
map.Load();
if (queue is GamingQueue gq)
{
gq.WriteLine($"地图 {map.Name} 已加载。");
}
return map;
}
protected override void AfterTimeElapsed(ref double timeToReduce)
{
}
}
}

View File

@ -71,8 +71,13 @@ namespace Oshima.FunGame.OshimaModules.Effects.SkillEffects
e = new (Skill, caster, target, _durative, duration, durationTurn);
break;
case EffectType.Delay:
WriteLine($"[ {caster} ] 对 [ {target} ] 造成了迟滞!持续时间:{持续时间}");
e = new (Skill, caster, _durative, duration, durationTurn);
double healingReductionPercent = 0.3;
if (_args.Length > 0 && _args[0] is double healingReduce)
{
healingReductionPercent = healingReduce;
}
WriteLine($"[ {caster} ] 对 [ {target} ] 造成了迟滞!普通攻击和技能的硬直时间、当前行动等待时间延长了 {healingReductionPercent * 100:0.##}%!持续时间:{持续时间}");
e = new (Skill, caster, _durative, duration, durationTurn, healingReductionPercent);
break;
case EffectType.Stun:
WriteLine($"[ {caster} ] 对 [ {target} ] 造成了眩晕!持续时间:{持续时间}");
@ -166,8 +171,13 @@ namespace Oshima.FunGame.OshimaModules.Effects.SkillEffects
_description = "愤怒:进入行动受限状态,失控并随机行动,行动回合内仅能对嘲讽者发起普通攻击。";
break;
case EffectType.Delay:
double healingReductionPercent = 0.3;
if (_args.Length > 0 && _args[0] is double healingReduce)
{
healingReductionPercent = healingReduce;
}
_dispelledType = DispelledType.Weak;
_description = "迟滞:延长普通攻击和技能的硬直时间、当前行动等待时间。";
_description = $"迟滞:普通攻击和技能的硬直时间、当前行动等待时间延长 {healingReductionPercent * 100:0.##}%。";
break;
case EffectType.Stun:
_dispelledType = DispelledType.Strong;

View File

@ -107,6 +107,9 @@ namespace Oshima.FunGame.OshimaModules
(long)SkillID. => new (),
(long)SkillID. => new (),
(long)SkillID. => new (),
(long)SkillID. => new (),
(long)SkillID. => new (),
(long)SkillID. => new (),
(long)SuperSkillID. => new (),
(long)SuperSkillID. => new (),
(long)SuperSkillID. => new (),

View File

@ -10,8 +10,8 @@ namespace Oshima.FunGame.OshimaModules.Skills
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
public override string DispelDescription => Effects.Count > 0 ? Effects.First().DispelDescription : "";
public override double EPCost => 100;
public override double CD => 45;
public override double HardnessTime { get; set; } = 5;
public override double CD => 55;
public override double HardnessTime { get; set; } = 7;
public override bool CanSelectSelf => true;
public override bool CanSelectEnemy => false;

View File

@ -1,5 +1,6 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
namespace Oshima.FunGame.OshimaModules.Skills
{
@ -24,12 +25,12 @@ namespace Oshima.FunGame.OshimaModules.Skills
{
public override long Id => Skill.Id;
public override string Name => Skill.Name;
public override string Description => $"每释放 {触发硬直次数:0.##} 次魔法才会触发硬直时间,且魔法伤害命中时基于 15% 智力 [ {获得额外能量值:0.##} ] 获得额外能量值,并减少所有技能 2 {GameplayEquilibriumConstant.InGameTime}冷却时间。";
public override string Description => $"每释放 {触发硬直次数:0.##} 次魔法才会触发硬直时间,且魔法伤害命中时基于 8% 智力 [ {获得额外能量值:0.##} ] 获得额外能量值,并减少所有技能 2 {GameplayEquilibriumConstant.InGameTime}冷却时间。";
public bool { get; set; } = false;
public int { get; set; } = 2;
public int { get; set; } = 0;
public double => 0.15 * Skill.Character?.INT ?? 0;
public double => 0.08 * Skill.Character?.INT ?? 0;
public override void AfterDamageCalculation(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult)
{
@ -81,6 +82,11 @@ namespace Oshima.FunGame.OshimaModules.Skills
baseHardnessTime = 0;
isCheckProtected = false;
WriteLine($"[ {character} ] 发动了灵能反射,消除了硬直时间!!");
if (GamingQueue != null && GamingQueue.CharacterDecisionPoints.TryGetValue(character, out DecisionPoints? dp) && dp != null)
{
dp.ActionsTaken = 1;
dp.ActionsHardnessTime.Clear();
}
}
else
{
@ -91,6 +97,11 @@ namespace Oshima.FunGame.OshimaModules.Skills
baseHardnessTime = 0;
isCheckProtected = false;
WriteLine($"[ {character} ] 发动了灵能反射,消除了硬直时间!!");
if (GamingQueue != null && GamingQueue.CharacterDecisionPoints.TryGetValue(character, out DecisionPoints? dp) && dp != null)
{
dp.ActionsTaken = 1;
dp.ActionsHardnessTime.Clear();
}
e.--;
if (e. == 0)
{

View File

@ -8,6 +8,7 @@ namespace Oshima.FunGame.OshimaModules.Skills
public override long Id => (long)PassiveID.;
public override string Name => "敏捷之刃";
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
public override double CD => 5;
public (Character? character = null) : base(SkillType.Passive, character)
{
@ -24,16 +25,18 @@ namespace Oshima.FunGame.OshimaModules.Skills
{
public override long Id => Skill.Id;
public override string Name => Skill.Name;
public override string Description => $"每次普通攻击都将附带基于 {敏捷系数 * 100:0.##}% 敏捷 [ {敏捷伤害:0.##} ] 点魔法伤害。";
public override string Description => $"普通攻击命中后,附带基于 {敏捷系数 * 100:0.##}% 敏捷 [ {敏捷伤害:0.##} ] 点魔法伤害,可叠加普攻特效。每 {Skill.CD:0.##} {GameplayEquilibriumConstant.InGameTime}仅能触发一次。";
private double => * Skill.Character?.AGI ?? 0;
private readonly double = 2.5;
private readonly double = 2;
private bool = false;
public override void AfterDamageCalculation(Character character, Character enemy, double damage, double actualDamage, bool isNormalAttack, DamageType damageType, MagicType magicType, DamageResult damageResult)
{
if (character == Skill.Character && isNormalAttack && (damageResult == DamageResult.Normal || damageResult == DamageResult.Critical) && ! && enemy.HP > 0)
{
Skill.CurrentCD = Skill.CD;
WriteLine($"[ {character} ] 发动了敏捷之刃!将造成额外伤害!");
= true;
DamageToEnemy(character, enemy, DamageType.Magical, magicType, );

View File

@ -25,7 +25,7 @@ namespace Oshima.FunGame.OshimaModules.Skills
{
public override long Id => Skill.Id;
public override string Name => Skill.Name;
public override string Description => $"进入不可选中状态,获得 100 行动速度,提高 8% 暴击率,持续 {Duration:0.##} {GameplayEquilibriumConstant.InGameTime}。破隐一击:在持续时间内,首次造成伤害会附加 {系数 * 100:0.##}% 敏捷 [ {伤害加成:0.##} ] 的强化伤害,并解除不可选中状态。";
public override string Description => $"进入不可选中状态,获得 100 行动速度,提高 8% 暴击率,持续 {Duration:0.##} {GameplayEquilibriumConstant.InGameTime}。不可选中:无法成为任何目标且免疫场地伤害。破隐一击:在持续时间内,首次造成伤害会附加 {系数 * 100:0.##}% 敏捷 [ {伤害加成:0.##} ] 的强化伤害,并解除不可选中状态。";
public override string DispelDescription => "被驱散性:不可选中状态生效期间,此技能不可驱散,否则可弱驱散";
public override EffectType EffectType => EffectType.Unselectable;
public override bool Durative => true;

View File

@ -8,15 +8,17 @@ namespace Oshima.FunGame.OshimaModules.Skills
{
public override long Id => (long)SkillID.;
public override string Name => "绝影";
public override string Description => Effects.Count > 0 ? Effects.First().Description : "";
public override string DispelDescription => Effects.Count > 0 ? Effects.First().DispelDescription : "";
public override double EPCost => 45;
public override double CD => 12;
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override string DispelDescription => Effects.Count > 0 ? Effects.First(e => e is ).DispelDescription : "";
public override double EPCost => 60;
public override double CD => 18;
public override double HardnessTime { get; set; } = 7;
public (Character? character = null) : base(SkillType.Skill, character)
{
Effects.Add(new _带基础伤害(this, 75, 70, 0.075, 0.055, DamageType.Physical));
CastRange = 6;
Effects.Add(new _带基础伤害(this, 70, 55, 0.095, 0.045, DamageType.Physical));
Effects.Add(new (this, EffectType.Delay, true, 15, 0, 0, 1, 0, 0.3));
}
}
}

View File

@ -0,0 +1,24 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Oshima.FunGame.OshimaModules.Effects.SkillEffects;
namespace Oshima.FunGame.OshimaModules.Skills
{
public class : Skill
{
public override long Id => (long)SkillID.;
public override string Name => "胧";
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override string DispelDescription => Effects.Count > 0 ? Effects.First(e => e is ).DispelDescription : "";
public override double EPCost => 60;
public override double CD => 20;
public override double HardnessTime { get; set; } = 9;
public (Character? character = null) : base(SkillType.Skill, character)
{
CastRange = 4;
Effects.Add(new _带基础伤害(this, 60, 45, 0.065, 0.035, DamageType.Physical));
Effects.Add(new (this, EffectType.Cripple, false, 0, 1, 0, 0.45, 0.05));
}
}
}

View File

@ -0,0 +1,24 @@
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Constant;
using Oshima.FunGame.OshimaModules.Effects.SkillEffects;
namespace Oshima.FunGame.OshimaModules.Skills
{
public class : Skill
{
public override long Id => (long)SkillID.;
public override string Name => "魔眼";
public override string Description => string.Join("", Effects.Select(e => e.Description));
public override string DispelDescription => "被驱散性:迟滞可弱驱散,混乱需强驱散";
public override double EPCost => 65;
public override double CD => 24;
public override double HardnessTime { get; set; } = 8;
public (Character? character = null) : base(SkillType.Skill, character)
{
CastRange = 2;
Effects.Add(new (this, EffectType.Delay, false, 0, 3, 0, 1, 0, 0.5));
Effects.Add(new (this, EffectType.Confusion, false, 0, 2, 0, 0.45, 0.05));
}
}
}

View File

@ -21,6 +21,7 @@
<ProjectReference Include="..\..\FunGame.Core\FunGame.Core.csproj" />
<ProjectReference Include="..\..\FunGame.Extension\FunGame.SQLQueryExtension\FunGame.SQLQueryExtension.csproj" />
<ProjectReference Include="..\OshimaCore\OshimaCore.csproj" />
<ProjectReference Include="..\OshimaMaps\OshimaMaps.csproj" />
<ProjectReference Include="..\OshimaModules\OshimaModules.csproj" />
</ItemGroup>

View File

@ -46,7 +46,7 @@ namespace Oshima.FunGame.OshimaServers.Service
FunGameConstant.Characters.Add(new dddovo());
FunGameConstant.Characters.Add(new Quduoduo());
FunGameConstant.Skills.AddRange([new (), new (), new (), new (), new (), new (), new (), new ()]);
FunGameConstant.Skills.AddRange([new (), new (), new (), new (), new (), new (), new (), new (), new (), new (), new ()]);
FunGameConstant.SuperSkills.AddRange([new (), new (), new (), new (), new (), new (), new (), new (), new (), new (), new (), new ()]);

View File

@ -3,8 +3,10 @@ using System.Text;
using System.Text.Json;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Library.Common.Addon;
using Milimoe.FunGame.Core.Library.Constant;
using Milimoe.FunGame.Core.Model;
using Oshima.FunGame.OshimaMaps;
using Oshima.FunGame.OshimaModules.Effects.OpenEffects;
using Oshima.FunGame.OshimaModules.Models;
using Oshima.FunGame.OshimaModules.Skills;
@ -18,6 +20,7 @@ namespace Oshima.FunGame.OshimaServers.Service
public static PluginConfig StatsConfig { get; } = new("FunGameSimulation", nameof(CharacterStatistics));
public static PluginConfig TeamStatsConfig { get; } = new("FunGameSimulation", nameof(TeamCharacterStatistics));
public static PluginConfig LastRecordConfig { get; } = new("FunGameSimulation", "LastRecord");
public static GameMap Map { get; } = new FastAutoMap();
public static bool IsRuning { get; set; } = false;
public static bool IsWeb { get; set; } = false;
public static bool PrintOut { get; set; } = false;
@ -59,7 +62,7 @@ namespace Oshima.FunGame.OshimaServers.Service
}
}
public static async Task<List<string>> StartSimulationGame(bool printout, bool isWeb = false, bool isTeam = false, bool deathMatchRoundDetail = false, int maxRespawnTimesMix = 1, bool useStore = false)
public static async Task<List<string>> StartSimulationGame(bool printout, bool isWeb = false, bool isTeam = false, bool deathMatchRoundDetail = false, int maxRespawnTimesMix = 1, bool useStore = false, bool hasMap = false)
{
PrintOut = printout;
IsWeb = isWeb;
@ -162,6 +165,8 @@ namespace Oshima.FunGame.OshimaServers.Service
};
actionQueue = mgq;
}
if (hasMap) actionQueue.LoadGameMap(Map);
actionQueue.UseQueueProtected = false;
if (PrintOut) Console.WriteLine();
// 总游戏时长
@ -267,9 +272,48 @@ namespace Oshima.FunGame.OshimaServers.Service
if (PrintOut) Console.WriteLine();
actionQueue.CharacterDeath += ActionQueue_CharacterDeath;
if (actionQueue is TeamGamingQueue teamQueue)
// 地图放置角色
if (actionQueue.Map != null)
{
//teamQueue.GameEndTeam += TeamQueue_GameEndTeam;
GameMap map = actionQueue.Map;
HashSet<Grid> allocated = [];
List<Grid> allGrids = [.. map.Grids.Values];
if (isTeam && tgq != null)
{
Team team1 = tgq.Teams.Values.First();
Team team2 = tgq.Teams.Values.Last();
// 团队模式放置角色
List<Character> team1Members = [.. team1.Members];
List<Character> team2Members = [.. team2.Members];
// 获取地图尺寸
int mapLength = map.Length;
int mapWidth = map.Width;
// 放置队伍一在左下角区域
PlaceTeamInCorner(actionQueue, map, team1Members, allocated, startX: 0, endX: mapLength / 4, startY: mapWidth * 3 / 4, endY: mapWidth - 1);
// 放置队伍二在右上角区域
PlaceTeamInCorner(actionQueue, map, team2Members, allocated, startX: mapLength * 3 / 4, endX: mapLength - 1, startY: 0, endY: mapWidth / 4);
}
else
{
// 非团队模式,随机放置角色
foreach (Character character in characters)
{
character.NormalAttack.GamingQueue = actionQueue;
Grid grid = Grid.Empty;
do
{
grid = allGrids[Random.Shared.Next(allGrids.Count)];
}
while (allocated.Contains(grid));
allocated.Add(grid);
map.SetCharacterCurrentGrid(character, grid);
}
}
}
// 总回合数
@ -1157,5 +1201,120 @@ namespace Oshima.FunGame.OshimaServers.Service
if (totalStats.ActionTurn != 0) totalStats.DamagePerTurn = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.ActionTurn);
if (totalStats.LiveTime != 0) totalStats.DamagePerSecond = Calculation.Round2Digits(totalStats.TotalDamage / totalStats.LiveTime);
}
private static void PlaceTeamInCorner(GamingQueue queue, GameMap map, List<Character> teamMembers, HashSet<Grid> allocated, int startX, int endX, int startY, int endY)
{
// 确保坐标有效
startX = Math.Clamp(startX, 0, map.Length - 1);
endX = Math.Clamp(endX, 0, map.Length - 1);
startY = Math.Clamp(startY, 0, map.Width - 1);
endY = Math.Clamp(endY, 0, map.Width - 1);
// 计算可用空间
int availableWidth = endX - startX + 1;
int availableHeight = endY - startY + 1;
// 计算每行最多角色数保持至少2格间距
int maxPerRow = Math.Min(availableWidth, Math.Max(1, availableWidth / 3));
// 计算起始位置
int currentX = startX;
int currentY = endY; // 从底部开始放置
foreach (Character character in teamMembers)
{
character.NormalAttack.GamingQueue = queue;
Grid? grid = null;
int attempts = 0;
// 尝试在指定区域找到合适位置
do
{
// 获取网格
grid = map[currentX, currentY, 0];
// 如果网格无效或已被占用,尝试下一个位置
if (grid == null || allocated.Contains(grid))
{
// 移动到下一个位置
currentX++;
// 换行处理
if (currentX > endX || currentX - startX >= maxPerRow)
{
currentX = startX;
currentY--; // 向上移动一行
// 如果超出区域,回到底部
if (currentY < startY)
{
currentY = endY;
}
}
}
attempts++;
// 如果尝试次数过多,使用随机位置
if (attempts > teamMembers.Count * 2)
{
grid = GetRandomGridInArea(map, allocated, startX, endX, startY, endY);
break;
}
}
while (grid == null || allocated.Contains(grid));
// 放置角色
if (grid != null)
{
allocated.Add(grid);
map.SetCharacterCurrentGrid(character, grid);
// 移动到下一个位置(保持间距)
currentX += 2; // 水平间距2格
// 换行处理
if (currentX > endX || currentX - startX >= maxPerRow)
{
currentX = startX;
currentY -= 2; // 垂直间距2格
// 如果超出区域,回到底部
if (currentY < startY)
{
currentY = endY;
}
}
}
else
{
// 回退到随机放置
Grid randomGrid = map.Grids.Values.FirstOrDefault(g => !allocated.Contains(g)) ?? Grid.Empty;
allocated.Add(randomGrid);
map.SetCharacterCurrentGrid(character, randomGrid);
}
}
}
private static Grid? GetRandomGridInArea(GameMap map, HashSet<Grid> allocated, int startX, int endX, int startY, int endY)
{
List<Grid> availableGrids = [];
for (int x = startX; x <= endX; x++)
{
for (int y = startY; y <= endY; y++)
{
Grid? grid = map[x, y, 0];
if (grid != null && !allocated.Contains(grid))
{
availableGrids.Add(grid);
}
}
}
return availableGrids.Count > 0 ?
availableGrids[Random.Shared.Next(availableGrids.Count)] :
null;
}
}
}

View File

@ -33,9 +33,9 @@ namespace Oshima.FunGame.WebAPI.Controllers
[AllowAnonymous]
[HttpGet("test")]
public async Task<List<string>> GetTest([FromQuery] bool? isweb = null, [FromQuery] bool? isteam = null, [FromQuery] bool? showall = null, [FromQuery] int? maxRespawnTimesMix = null)
public async Task<List<string>> GetTest([FromQuery] bool? isweb = null, [FromQuery] bool? isteam = null, [FromQuery] bool? showall = null, [FromQuery] int? maxRespawnTimesMix = null, [FromQuery] bool? hasMap = null)
{
return await FunGameSimulation.StartSimulationGame(false, isweb ?? true, isteam ?? false, showall ?? false, maxRespawnTimesMix ?? 1);
return await FunGameSimulation.StartSimulationGame(false, isweb ?? true, isteam ?? false, showall ?? false, maxRespawnTimesMix ?? 1, false, hasMap ?? false);
}
[AllowAnonymous]

View File

@ -397,6 +397,29 @@ namespace Oshima.FunGame.WebAPI.Services
return result;
}
if (e.Detail.StartsWith("个人地图模拟", StringComparison.CurrentCultureIgnoreCase))
{
e.UseNotice = false;
if (!FunGameSimulation)
{
FunGameSimulation = true;
List<string> msgs = await Controller.GetTest(false, maxRespawnTimesMix: 0, hasMap: true);
List<string> real = MergeMessages(msgs);
int count = 1;
foreach (string msg in real)
{
await SendAsync(e, "筽祀牻", msg.Trim(), msgSeq: count++);
if (count != real.Count) await Task.Delay(5500);
}
FunGameSimulation = false;
}
else
{
await SendAsync(e, "筽祀牻", "游戏正在模拟中,请勿重复请求!");
}
return result;
}
if (e.Detail.StartsWith("混战模拟"))
{
e.UseNotice = false;
@ -456,6 +479,29 @@ namespace Oshima.FunGame.WebAPI.Services
return result;
}
if (e.Detail.StartsWith("团队地图模拟", StringComparison.CurrentCultureIgnoreCase))
{
e.UseNotice = false;
if (!FunGameSimulation)
{
FunGameSimulation = true;
List<string> msgs = await Controller.GetTest(false, true, hasMap: true);
List<string> real = MergeMessages(msgs);
int count = 1;
foreach (string msg in real)
{
await SendAsync(e, "筽祀牻", msg.Trim(), msgSeq: count++);
if (count != real.Count) await Task.Delay(5500);
}
FunGameSimulation = false;
}
else
{
await SendAsync(e, "筽祀牻", "游戏正在模拟中,请勿重复请求!");
}
return result;
}
if (e.Detail.StartsWith("查数据", StringComparison.CurrentCultureIgnoreCase))
{
e.UseNotice = false;