1.0.0 Release (#136)

This commit is contained in:
milimoe 2025-05-14 22:24:13 +08:00 committed by GitHub
parent 82538b9d93
commit aebd64e6e3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 131 additions and 64 deletions

View File

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

View File

@ -14,6 +14,7 @@ namespace Milimoe.FunGame.Core.Entity
public string SkillCost { get; set; } = ""; public string SkillCost { get; set; } = "";
public Item? Item { get; set; } = null; public Item? Item { get; set; } = null;
public bool HasKill { get; set; } = false; public bool HasKill { get; set; } = false;
public List<Character> Assists { get; set; } = [];
public Dictionary<Character, double> Damages { get; set; } = []; public Dictionary<Character, double> Damages { get; set; } = [];
public Dictionary<Character, bool> IsCritical { get; set; } = []; public Dictionary<Character, bool> IsCritical { get; set; } = [];
public Dictionary<Character, bool> IsEvaded { get; set; } = []; public Dictionary<Character, bool> IsEvaded { get; set; } = [];

View File

@ -18,15 +18,23 @@
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile> <DocumentationFile></DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild> <GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Project Redbud and Contributors</Copyright> <Copyright>©2023-Present Project Redbud and Contributors.</Copyright>
<PackageId>$(AssemblyName)</PackageId> <PackageId>$(AssemblyName)</PackageId>
<Description>FunGame.Core: A C#.NET library for turn-based games.</Description> <Description>FunGame.Core: A C#.NET library for turn-based games.</Description>
<PackageTags>game;turn-based;server;framework;dotnet;csharp;gamedev</PackageTags> <PackageTags>game;turn-based;server;framework;dotnet;csharp;gamedev</PackageTags>
<PackageReleaseNotes> <PackageReleaseNotes>
We are excited to introduce the official version 1.0.0. Here are the key changes from the last release candidate:
- Fixed an issue of incorrect recording of non-damage assists. (1.0.0)
- Fixed an issue where applying a control effect to an enemy mistakenly resulted in an assist for the character when that enemy killed a teammate. (1.0.0)
- Removed the restriction of contributing 10% damage to get an assist. (1.0.0)
- Fixed an issue where the skill always defaults to selecting enemies when there is no suitable target, which could cause the character to mistakenly apply a buff state to the enemy. (1.0.0)
- Adjusted the basic reward and assist allocation rules for killing enemies. (1.0.0)
- Added monetary compensation based on the level and economic difference between the killer/assister and the victim. (1.0.0)
Update history of all release candidate versions:
- Initial release candidate 1 (1.0.0-rc.1-0428) - 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) - 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) - 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 helper methods IsTeammate and GetIsTeammateDictionary to GamingQueue and its interface IGamingQueue for determining if someone is a teammate. 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 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 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 EffectTypes: WeakDispelling and StrongDispelling, representing DurativeWeak and DurativeStrong of DispelType. (1.0.0-rc.1-0509)
@ -40,8 +48,7 @@
<PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression> <PackageLicenseExpression>LGPL-3.0-or-later</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<VersionPrefix>1.0.0-rc.1</VersionPrefix> <VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix Condition="'$(VersionSuffix)' == ''">$([System.DateTime]::Now.ToString("MMdd"))</VersionSuffix>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

View File

@ -23,7 +23,14 @@
/// <summary> /// <summary>
/// 核心库的版本号 /// 核心库的版本号
/// </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 = "FunGame Core";
public const string FunGame_Core_Api = "FunGame Core Api"; public const string FunGame_Core_Api = "FunGame Core Api";
@ -33,7 +40,7 @@
public const int FunGame_Version_Major = 1; public const int FunGame_Version_Major = 1;
public const int FunGame_Version_Minor = 0; public const int FunGame_Version_Minor = 0;
public const string FunGame_VersionPatch = ".0-rc.1"; public const string FunGame_VersionPatch = "0";
public const string FunGame_Version_Build = ""; public const string FunGame_Version_Build = "";
public const string FunGameCoreTitle = @" _____ _ _ _ _ ____ _ __ __ _____ ____ ___ ____ _____ public const string FunGameCoreTitle = @" _____ _ _ _ _ ____ _ __ __ _____ ____ ___ ____ _____

View File

@ -24,6 +24,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary> /// </summary>
public Action<string> WriteLine { get; } public Action<string> WriteLine { get; }
/// <summary>
/// 参与本次游戏的所有角色列表
/// </summary>
public List<Character> AllCharacter => _allCharacter;
/// <summary> /// <summary>
/// 原始的角色字典 /// 原始的角色字典
/// </summary> /// </summary>
@ -116,6 +121,11 @@ namespace Milimoe.FunGame.Core.Model
#region #region
/// <summary>
/// 参与本次游戏的所有角色列表
/// </summary>
protected readonly List<Character> _allCharacter = [];
/// <summary> /// <summary>
/// 原始的角色字典 /// 原始的角色字典
/// </summary> /// </summary>
@ -261,6 +271,9 @@ namespace Milimoe.FunGame.Core.Model
// 保存原始的角色信息。用于复活时还原状态 // 保存原始的角色信息。用于复活时还原状态
foreach (Character character in characters) foreach (Character character in characters)
{ {
// 添加角色引用到所有角色列表
_allCharacter.Add(character);
// 复制原始角色对象
Character original = character.Copy(); Character original = character.Copy();
original.Guid = Guid.NewGuid(); original.Guid = Guid.NewGuid();
character.Guid = original.Guid; character.Guid = original.Guid;
@ -612,7 +625,7 @@ namespace Milimoe.FunGame.Core.Model
if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable) if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable)
{ {
_stats[effect.Source].ControlTime += timeToReduce; _stats[effect.Source].ControlTime += timeToReduce;
SetNotDamageAssistTime(effect.Source, character); _assistDetail[effect.Source][character, TotalTime] += 1;
} }
if (effect.Durative) if (effect.Durative)
@ -686,10 +699,12 @@ namespace Milimoe.FunGame.Core.Model
bool isCheckProtected = true; bool isCheckProtected = true;
// 队友列表 // 队友列表
List<Character> teammates = [.. GetTeammates(character).Where(_queue.Contains)]; List<Character> allTeammates = GetTeammates(character);
List<Character> teammates = [.. allTeammates.Where(_queue.Contains)];
// 敌人列表 // 敌人列表
List<Character> enemys = [.. _queue.Where(c => c != character && !c.IsUnselectable && !teammates.Contains(c))]; List<Character> allEnemys = [.. _allCharacter.Where(c => c != character && !teammates.Contains(c))];
List<Character> enemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
// 技能列表 // 技能列表
List<Skill> skills = [.. character.Skills.Where(s => s.Level > 0 && s.SkillType != SkillType.Passive && s.Enable && !s.IsInEffect && s.CurrentCD == 0 && List<Skill> skills = [.. character.Skills.Where(s => s.Level > 0 && s.SkillType != SkillType.Passive && s.Enable && !s.IsInEffect && s.CurrentCD == 0 &&
@ -848,8 +863,6 @@ namespace Milimoe.FunGame.Core.Model
{ {
type = GetActionType(pUseItem, pCastSkill, pNormalAttack); type = GetActionType(pUseItem, pCastSkill, pNormalAttack);
} }
_stats[character].ActionTurn += 1;
} }
else if (character.CharacterState == CharacterState.Casting) else if (character.CharacterState == CharacterState.Casting)
{ {
@ -889,14 +902,6 @@ namespace Milimoe.FunGame.Core.Model
{ {
// 使用普通攻击逻辑 // 使用普通攻击逻辑
List<Character> targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates); List<Character> targets = await SelectTargetsAsync(character, character.NormalAttack, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(character))
{
// 如果没有选取目标,且角色在 AI 控制下,则随机选取目标
if (enemys.Count > character.NormalAttack.CanSelectTargetCount)
targets = [.. enemys.OrderBy(o => Random.Shared.Next(enemys.Count)).Take(character.NormalAttack.CanSelectTargetCount)];
else
targets = [.. enemys];
}
if (targets.Count > 0) if (targets.Count > 0)
{ {
LastRound.Targets = [.. targets]; LastRound.Targets = [.. targets];
@ -928,11 +933,6 @@ namespace Milimoe.FunGame.Core.Model
if (skill.SkillType == SkillType.Magic) if (skill.SkillType == SkillType.Magic)
{ {
List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates); List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0)
{
// 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标
targets = [enemys[Random.Shared.Next(enemys.Count)]];
}
if (targets.Count > 0) if (targets.Count > 0)
{ {
// 免疫检定 // 免疫检定
@ -959,11 +959,6 @@ namespace Milimoe.FunGame.Core.Model
if (CheckCanCast(character, skill, out double cost)) if (CheckCanCast(character, skill, out double cost))
{ {
List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates); List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0)
{
// 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标
targets = [enemys[Random.Shared.Next(enemys.Count)]];
}
if (targets.Count > 0) if (targets.Count > 0)
{ {
// 免疫检定 // 免疫检定
@ -1150,6 +1145,7 @@ namespace Milimoe.FunGame.Core.Model
await OnCharacterGiveUpAsync(character); await OnCharacterGiveUpAsync(character);
} }
_stats[character].ActionTurn += 1;
LastRound.ActionType = type; LastRound.ActionType = type;
await AfterCharacterAction(character, type); await AfterCharacterAction(character, type);
@ -1638,24 +1634,73 @@ namespace Milimoe.FunGame.Core.Model
} }
_stats[killer].Kills += 1; _stats[killer].Kills += 1;
_stats[death].Deaths += 1; _stats[death].Deaths += 1;
int money = Random.Shared.Next(250, 350);
// 按伤害比分配金钱 只有造成 10% 伤害以上并且是在 30 秒内造成的伤害才能参与 // 基础击杀奖励
// 现在 20 秒内的非伤害类型辅助也能参与助攻了 int money = 300;
Character[] assists = [.. _assistDetail.Keys.Where(c => c != death && _assistDetail[c].GetPercentage(death) > 0.10 &&
(_assistDetail[c].GetLastTime(death) - TotalTime <= 30 || _assistDetail[c].GetNotDamageAssistLastTime(killer) - TotalTime <= 20))]; // 按伤害比分配金钱 只有在 30 时间内造成的伤害才能参与
double totalDamagePercentage = _assistDetail.Keys.Where(assists.Contains).Select(c => _assistDetail[c].GetPercentage(death)).Sum(); // 现在 20 时间内的非伤害类型辅助也能参与助攻了
int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限 Character[] assists = [.. _assistDetail.Keys.Where(c => c != death &&
((TotalTime - _assistDetail[c].GetLastTime(death) <= 30) || (TotalTime - _assistDetail[c].GetNotDamageAssistLastTime(killer) <= 20)))];
// 获取贡献百分比 以伤害为主,非伤害助攻贡献不足 10% 的按 10% 计算
double minPercentage = 0.1;
Dictionary<Character, double> assistPercentage = _assistDetail.Keys.Where(assists.Contains).ToDictionary(c => c,
c => _assistDetail[c].GetPercentage(death) < minPercentage &&
TotalTime - _assistDetail[c].GetNotDamageAssistLastTime(killer) <= 20 ? minPercentage : _assistDetail[c].GetPercentage(death));
double totalDamagePercentage = assistPercentage.Values.Sum();
if (totalDamagePercentage < 1)
{
// 归一化
foreach (Character assist in assistPercentage.Keys)
{
if (totalDamagePercentage == 0) break;
assistPercentage[assist] /= totalDamagePercentage;
}
totalDamagePercentage = assistPercentage.Values.Sum();
if (totalDamagePercentage == 0) totalDamagePercentage = 1;
}
// 如果算上助攻者总伤害贡献超过了100%,则会超过基础击杀奖励。防止刷伤害要设置金钱上限
int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425);
// 等级差和经济差补偿 d = death, koa = killer or assist
int calDiff(Character d, Character koa)
{
int moreMoney = 0;
int levelDiff = d.Level - koa.Level;
if (levelDiff > 0)
{
moreMoney += levelDiff * 10;
}
if (_earnedMoney.TryGetValue(d, out int deathMoney))
{
int moneyDiff = deathMoney;
if (_earnedMoney.TryGetValue(koa, out int killerMoney))
{
moneyDiff = deathMoney - killerMoney;
}
if (moneyDiff > 0)
{
moreMoney += (int)Math.Min(moneyDiff * 0.25, 300);
}
}
return moreMoney;
}
// 分配金钱和累计助攻 // 分配金钱和累计助攻
foreach (Character assist in assists) foreach (Character assist in assistPercentage.Keys)
{ {
int cmoney = Convert.ToInt32(_assistDetail[assist].GetPercentage(death) / totalDamagePercentage * totalMoney); int cmoney = Convert.ToInt32(assistPercentage[assist] / totalDamagePercentage * totalMoney);
if (cmoney > 320) cmoney = 320;
if (assist != killer) if (assist != killer)
{ {
// 助攻者的等级差和经济差补偿
cmoney += calDiff(death, assist);
if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney; if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney;
assist.User.Inventory.Credits += cmoney; assist.User.Inventory.Credits += cmoney;
_stats[assist].Assists += 1; _stats[assist].Assists += 1;
if (!LastRound.Assists.Contains(assist)) LastRound.Assists.Add(assist);
} }
else else
{ {
@ -1663,10 +1708,13 @@ namespace Milimoe.FunGame.Core.Model
} }
} }
// 击杀者的等级差和经济差补偿
money += calDiff(death, killer);
// 终结击杀的奖励仍然是全额的 // 终结击杀的奖励仍然是全额的
if (_continuousKilling.TryGetValue(death, out int coefficient) && coefficient > 1) if (_continuousKilling.TryGetValue(death, out int coefficient) && coefficient > 1)
{ {
money += (coefficient + 1) * Random.Shared.Next(50, 100); money += (coefficient + 1) * 60;
string termination = CharacterSet.GetContinuousKilling(coefficient); string termination = CharacterSet.GetContinuousKilling(coefficient);
string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " " + termination : "")},获得 {money} {GameplayEquilibriumConstant.InGameCurrency}"; string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " " + termination : "")},获得 {money} {GameplayEquilibriumConstant.InGameCurrency}";
LastRound.DeathContinuousKilling.Add(msg); LastRound.DeathContinuousKilling.Add(msg);
@ -1698,6 +1746,9 @@ namespace Milimoe.FunGame.Core.Model
LastRound.ActorContinuousKilling.Add(firstKill); LastRound.ActorContinuousKilling.Add(firstKill);
} }
if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money;
killer.User.Inventory.Credits += money;
int kills = _continuousKilling[killer]; int kills = _continuousKilling[killer];
string continuousKilling = CharacterSet.GetContinuousKilling(kills); string continuousKilling = CharacterSet.GetContinuousKilling(kills);
string actorContinuousKilling = ""; string actorContinuousKilling = "";
@ -1715,7 +1766,7 @@ namespace Milimoe.FunGame.Core.Model
} }
else if (kills >= 10) else if (kills >= 10)
{ {
actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "拜托谁去杀了他吧!!!"; actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "拜托谁去杀了他吧!!!";
} }
if (actorContinuousKilling != "") if (actorContinuousKilling != "")
{ {
@ -1723,9 +1774,6 @@ namespace Milimoe.FunGame.Core.Model
WriteLine(actorContinuousKilling); WriteLine(actorContinuousKilling);
} }
if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money;
killer.User.Inventory.Credits += money;
await OnDeathCalculation(death, killer); await OnDeathCalculation(death, killer);
death.EP = 0; death.EP = 0;
@ -1801,11 +1849,6 @@ namespace Milimoe.FunGame.Core.Model
if (skill != null) if (skill != null)
{ {
List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates); List<Character> targets = await SelectTargetsAsync(character, skill, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(character) && enemys.Count > 0)
{
// 如果没有选取目标,且角色在 AI 控制下,则随机选取一个目标
targets = [enemys[Random.Shared.Next(enemys.Count)]];
}
if (targets.Count > 0) if (targets.Count > 0)
{ {
// 免疫检定 // 免疫检定
@ -1939,6 +1982,14 @@ namespace Milimoe.FunGame.Core.Model
effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates); effect.AlterSelectListBeforeSelection(character, attack, enemys, teammates);
} }
List<Character> targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates); List<Character> targets = await OnSelectNormalAttackTargetsAsync(character, attack, enemys, teammates);
if (targets.Count == 0 && _charactersInAI.Contains(character))
{
targets = character.NormalAttack.GetSelectableTargets(character, enemys, teammates);
if (targets.Count > 0)
{
targets = [targets[Random.Shared.Next(targets.Count)]];
}
}
return targets; return targets;
} }
@ -2541,6 +2592,7 @@ namespace Milimoe.FunGame.Core.Model
{ {
foreach (Character target in targets) foreach (Character target in targets)
{ {
if (character == target) continue;
_assistDetail[character].NotDamageAssistLastTime[target] = TotalTime; _assistDetail[character].NotDamageAssistLastTime[target] = TotalTime;
} }
} }