diff --git a/Entity/Character/AssistDetail.cs b/Entity/Character/AssistDetail.cs
index 28e806f..91a69d0 100644
--- a/Entity/Character/AssistDetail.cs
+++ b/Entity/Character/AssistDetail.cs
@@ -77,28 +77,28 @@ namespace Milimoe.FunGame.Core.Entity
/// 获取对 造成伤害的最后时间
///
///
- /// -1 意味着没有时间
+ /// 意味着没有时间
public double GetLastTime(Character enemy)
{
if (DamageLastTime.TryGetValue(enemy, out double time))
{
return time;
}
- return -1;
+ return double.MinValue;
}
///
/// 获取对某角色友方非伤害辅助的最后时间
///
///
- /// -1 意味着没有时间
+ /// 意味着没有时间
public double GetNotDamageAssistLastTime(Character character)
{
if (NotDamageAssistLastTime.TryGetValue(character, out double time))
{
return time;
}
- return -1;
+ return double.MinValue;
}
}
}
diff --git a/Entity/System/RoundRecord.cs b/Entity/System/RoundRecord.cs
index daf0ee0..eb11f5d 100644
--- a/Entity/System/RoundRecord.cs
+++ b/Entity/System/RoundRecord.cs
@@ -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 Assists { get; set; } = [];
public Dictionary Damages { get; set; } = [];
public Dictionary IsCritical { get; set; } = [];
public Dictionary IsEvaded { get; set; } = [];
diff --git a/FunGame.Core.csproj b/FunGame.Core.csproj
index 8f7775f..9b983df 100644
--- a/FunGame.Core.csproj
+++ b/FunGame.Core.csproj
@@ -18,30 +18,37 @@
True
True
- Project Redbud and Contributors
+ ©2023-Present Project Redbud and Contributors.
$(AssemblyName)
FunGame.Core: A C#.NET library for turn-based games.
game;turn-based;server;framework;dotnet;csharp;gamedev
- - 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)
-
+ 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)
+
https://github.com/project-redbud/FunGame-Core
https://github.com/project-redbud
LGPL-3.0-or-later
True
README.md
- 1.0.0-rc.1
- $([System.DateTime]::Now.ToString("MMdd"))
+ 1.0.0
diff --git a/Library/Constant/FunGameInfo.cs b/Library/Constant/FunGameInfo.cs
index 6da4479..6eb9cd4 100644
--- a/Library/Constant/FunGameInfo.cs
+++ b/Library/Constant/FunGameInfo.cs
@@ -23,7 +23,14 @@
///
/// 核心库的版本号
///
- 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 = @" _____ _ _ _ _ ____ _ __ __ _____ ____ ___ ____ _____
diff --git a/Model/GamingQueue.cs b/Model/GamingQueue.cs
index 00f6f3b..82fe3b7 100644
--- a/Model/GamingQueue.cs
+++ b/Model/GamingQueue.cs
@@ -24,6 +24,11 @@ namespace Milimoe.FunGame.Core.Model
///
public Action WriteLine { get; }
+ ///
+ /// 参与本次游戏的所有角色列表
+ ///
+ public List AllCharacter => _allCharacter;
+
///
/// 原始的角色字典
///
@@ -116,11 +121,16 @@ namespace Milimoe.FunGame.Core.Model
#region 保护变量
+ ///
+ /// 参与本次游戏的所有角色列表
+ ///
+ protected readonly List _allCharacter = [];
+
///
/// 原始的角色字典
///
protected readonly Dictionary _original = [];
-
+
///
/// 当前的行动顺序
///
@@ -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 teammates = [.. GetTeammates(character).Where(_queue.Contains)];
+ List allTeammates = GetTeammates(character);
+ List teammates = [.. allTeammates.Where(_queue.Contains)];
// 敌人列表
- List enemys = [.. _queue.Where(c => c != character && !c.IsUnselectable && !teammates.Contains(c))];
+ List allEnemys = [.. _allCharacter.Where(c => c != character && !teammates.Contains(c))];
+ List enemys = [.. allEnemys.Where(c => _queue.Contains(c) && !c.IsUnselectable)];
// 技能列表
List 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 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 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 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 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 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 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;
}
}