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"/> 造成伤害的最后时间
/// </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

@ -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; } = [];

View File

@ -18,30 +18,37 @@
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<DocumentationFile></DocumentationFile>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Copyright>Project Redbud and Contributors</Copyright>
<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)
</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)
- 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 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 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)
</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>
<VersionSuffix Condition="'$(VersionSuffix)' == ''">$([System.DateTime]::Now.ToString("MMdd"))</VersionSuffix>
<VersionPrefix>1.0.0</VersionPrefix>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

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";
@ -33,7 +40,7 @@
public const int FunGame_Version_Major = 1;
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 FunGameCoreTitle = @" _____ _ _ _ _ ____ _ __ __ _____ ____ ___ ____ _____

View File

@ -24,6 +24,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary>
public Action<string> WriteLine { get; }
/// <summary>
/// 参与本次游戏的所有角色列表
/// </summary>
public List<Character> AllCharacter => _allCharacter;
/// <summary>
/// 原始的角色字典
/// </summary>
@ -116,6 +121,11 @@ namespace Milimoe.FunGame.Core.Model
#region
/// <summary>
/// 参与本次游戏的所有角色列表
/// </summary>
protected readonly List<Character> _allCharacter = [];
/// <summary>
/// 原始的角色字典
/// </summary>
@ -261,6 +271,9 @@ namespace Milimoe.FunGame.Core.Model
// 保存原始的角色信息。用于复活时还原状态
foreach (Character character in characters)
{
// 添加角色引用到所有角色列表
_allCharacter.Add(character);
// 复制原始角色对象
Character original = character.Copy();
original.Guid = Guid.NewGuid();
character.Guid = original.Guid;
@ -612,7 +625,7 @@ namespace Milimoe.FunGame.Core.Model
if (effect.Source != null && SkillSet.GetCharacterStateByEffectType(effect.EffectType) != CharacterState.Actionable)
{
_stats[effect.Source].ControlTime += timeToReduce;
SetNotDamageAssistTime(effect.Source, character);
_assistDetail[effect.Source][character, TotalTime] += 1;
}
if (effect.Durative)
@ -686,10 +699,12 @@ namespace Milimoe.FunGame.Core.Model
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 &&
@ -848,8 +863,6 @@ namespace Milimoe.FunGame.Core.Model
{
type = GetActionType(pUseItem, pCastSkill, pNormalAttack);
}
_stats[character].ActionTurn += 1;
}
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);
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)
{
LastRound.Targets = [.. targets];
@ -928,11 +933,6 @@ namespace Milimoe.FunGame.Core.Model
if (skill.SkillType == SkillType.Magic)
{
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)
{
// 免疫检定
@ -959,11 +959,6 @@ namespace Milimoe.FunGame.Core.Model
if (CheckCanCast(character, skill, out double cost))
{
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)
{
// 免疫检定
@ -1150,6 +1145,7 @@ namespace Milimoe.FunGame.Core.Model
await OnCharacterGiveUpAsync(character);
}
_stats[character].ActionTurn += 1;
LastRound.ActionType = type;
await AfterCharacterAction(character, type);
@ -1638,24 +1634,73 @@ namespace Milimoe.FunGame.Core.Model
}
_stats[killer].Kills += 1;
_stats[death].Deaths += 1;
int money = Random.Shared.Next(250, 350);
// 按伤害比分配金钱 只有造成 10% 伤害以上并且是在 30 秒内造成的伤害才能参与
// 现在 20 秒内的非伤害类型辅助也能参与助攻了
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))];
double totalDamagePercentage = _assistDetail.Keys.Where(assists.Contains).Select(c => _assistDetail[c].GetPercentage(death)).Sum();
int totalMoney = Math.Min(Convert.ToInt32(money * totalDamagePercentage), 425); // 防止刷伤害设置金钱上限
// 基础击杀奖励
int money = 300;
// 按伤害比分配金钱 只有在 30 时间内造成的伤害才能参与
// 现在 20 时间内的非伤害类型辅助也能参与助攻了
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)
{
// 助攻者的等级差和经济差补偿
cmoney += calDiff(death, assist);
if (!_earnedMoney.TryAdd(assist, cmoney)) _earnedMoney[assist] += cmoney;
assist.User.Inventory.Credits += cmoney;
_stats[assist].Assists += 1;
if (!LastRound.Assists.Contains(assist)) LastRound.Assists.Add(assist);
}
else
{
@ -1663,10 +1708,13 @@ namespace Milimoe.FunGame.Core.Model
}
}
// 击杀者的等级差和经济差补偿
money += calDiff(death, killer);
// 终结击杀的奖励仍然是全额的
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 msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " " + termination : "")},获得 {money} {GameplayEquilibriumConstant.InGameCurrency}";
LastRound.DeathContinuousKilling.Add(msg);
@ -1698,6 +1746,9 @@ namespace Milimoe.FunGame.Core.Model
LastRound.ActorContinuousKilling.Add(firstKill);
}
if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money;
killer.User.Inventory.Credits += money;
int kills = _continuousKilling[killer];
string continuousKilling = CharacterSet.GetContinuousKilling(kills);
string actorContinuousKilling = "";
@ -1715,7 +1766,7 @@ namespace Milimoe.FunGame.Core.Model
}
else if (kills >= 10)
{
actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "拜托谁去杀了他吧!!!";
actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "拜托谁去杀了他吧!!!";
}
if (actorContinuousKilling != "")
{
@ -1723,9 +1774,6 @@ namespace Milimoe.FunGame.Core.Model
WriteLine(actorContinuousKilling);
}
if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money;
killer.User.Inventory.Credits += money;
await OnDeathCalculation(death, killer);
death.EP = 0;
@ -1801,11 +1849,6 @@ namespace Milimoe.FunGame.Core.Model
if (skill != null)
{
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)
{
// 免疫检定
@ -1939,6 +1982,14 @@ namespace Milimoe.FunGame.Core.Model
effect.AlterSelectListBeforeSelection(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;
}
@ -2541,6 +2592,7 @@ namespace Milimoe.FunGame.Core.Model
{
foreach (Character target in targets)
{
if (character == target) continue;
_assistDetail[character].NotDamageAssistLastTime[target] = TotalTime;
}
}