动态工厂可以构造主动技能了;完善技能底层选取逻辑;团队模式完善;优化回合日志 (#99)

* 修复诸多复活角色的问题;添加了更多回合记录

* 完善了底层技能选取目标方法;添加筛选条件列表,使技能能够灵活的选取角色

* 优化死亡结算的逻辑;优化回合记录日志

* 现在动态工厂可以构造主动技能了
This commit is contained in:
milimoe 2024-11-10 00:30:43 +08:00 committed by GitHub
parent 3db586cab2
commit 75d1337ce1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 682 additions and 145 deletions

View File

@ -675,7 +675,7 @@ namespace Milimoe.FunGame.Core.Api.Utility
/// 此方法检查一个 百分比(%) 数值是否存在于 [0,1] 区间 /// 此方法检查一个 百分比(%) 数值是否存在于 [0,1] 区间
/// </summary> /// </summary>
/// <param name="value"></param> /// <param name="value"></param>
/// <returns>如果超过0则返回0超过1则返回1。</returns> /// <returns>如果低于0则返回0超过1则返回1。</returns>
public static double PercentageCheck(double value) public static double PercentageCheck(double value)
{ {
return Math.Max(0, Math.Min(value, 1)); return Math.Max(0, Math.Min(value, 1));

View File

@ -1307,7 +1307,6 @@ namespace Milimoe.FunGame.Core.Entity
PrimaryAttribute = PrimaryAttribute, PrimaryAttribute = PrimaryAttribute,
Level = Level, Level = Level,
EXP = EXP, EXP = EXP,
CharacterState = CharacterState,
InitialHP = InitialHP, InitialHP = InitialHP,
ExHP2 = ExHP2, ExHP2 = ExHP2,
InitialMP = InitialMP, InitialMP = InitialMP,
@ -1363,8 +1362,9 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary> /// <summary>
/// 复活此角色,回复出厂状态 /// 复活此角色,回复出厂状态
/// </summary> /// </summary>
/// <param name="original">需要一个原始的角色用于还原状态</param>
/// <returns></returns> /// <returns></returns>
public void Respawn() public void Respawn(Character original)
{ {
Item? mcp = UnEquip(EquipSlotType.MagicCardPack); Item? mcp = UnEquip(EquipSlotType.MagicCardPack);
Item? w = UnEquip(EquipSlotType.Weapon); Item? w = UnEquip(EquipSlotType.Weapon);
@ -1374,11 +1374,17 @@ namespace Milimoe.FunGame.Core.Entity
Item? ac2 = UnEquip(EquipSlotType.Accessory2); Item? ac2 = UnEquip(EquipSlotType.Accessory2);
List<Skill> skills = new(Skills); List<Skill> skills = new(Skills);
List<Item> items = new(Items); List<Item> items = new(Items);
Character c = Copy(); Character c = original.Copy();
List<Effect> effects = [.. Effects];
foreach (Effect e in effects)
{
e.OnEffectLost(this);
}
Effects.Clear(); Effects.Clear();
Skills.Clear(); Skills.Clear();
Items.Clear(); Items.Clear();
Id = c.Id; Id = c.Id;
Guid = original.Guid;
Name = c.Name; Name = c.Name;
FirstName = c.FirstName; FirstName = c.FirstName;
NickName = c.NickName; NickName = c.NickName;
@ -1392,6 +1398,10 @@ namespace Milimoe.FunGame.Core.Entity
Level = c.Level; Level = c.Level;
EXP = c.EXP; EXP = c.EXP;
CharacterState = c.CharacterState; CharacterState = c.CharacterState;
CharacterEffectStates.Clear();
CharacterEffectTypes.Clear();
IsUnselectable = false;
UpdateCharacterState();
InitialHP = c.InitialHP; InitialHP = c.InitialHP;
ExHP2 = c.ExHP2; ExHP2 = c.ExHP2;
InitialMP = c.InitialMP; InitialMP = c.InitialMP;
@ -1432,6 +1442,7 @@ namespace Milimoe.FunGame.Core.Entity
Skill newskill = skill.Copy(); Skill newskill = skill.Copy();
newskill.Character = this; newskill.Character = this;
newskill.Level = skill.Level; newskill.Level = skill.Level;
newskill.CurrentCD = 0;
Skills.Add(newskill); Skills.Add(newskill);
} }
foreach (Item item in items) foreach (Item item in items)

View File

@ -22,21 +22,6 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary> /// </summary>
public virtual EffectType EffectType { get; set; } = EffectType.None; public virtual EffectType EffectType { get; set; } = EffectType.None;
/// <summary>
/// 作用于自身
/// </summary>
public virtual bool TargetSelf { get; set; } = false;
/// <summary>
/// 作用目标数量
/// </summary>
public virtual int TargetCount { get; set; } = 0;
/// <summary>
/// 作用范围
/// </summary>
public virtual double TargetRange { get; set; } = 0;
/// <summary> /// <summary>
/// 持续性的<para/> /// 持续性的<para/>
/// 配合 <see cref="Duration"/> 使用,而不是 <see cref="DurationTurn"/>。 /// 配合 <see cref="Duration"/> 使用,而不是 <see cref="DurationTurn"/>。
@ -232,7 +217,8 @@ namespace Milimoe.FunGame.Core.Entity
/// 技能开始吟唱时 [ 爆发技插队可触发此项 ] /// 技能开始吟唱时 [ 爆发技插队可触发此项 ]
/// </summary> /// </summary>
/// <param name="caster"></param> /// <param name="caster"></param>
public virtual void OnSkillCasting(Character caster) /// <param name="targets"></param>
public virtual void OnSkillCasting(Character caster, List<Character> targets)
{ {
} }
@ -436,6 +422,18 @@ namespace Milimoe.FunGame.Core.Entity
return result; return result;
} }
/// <summary>
/// 治疗一个目标 [ 强烈建议使用此方法而不是自行调用 <see cref="IGamingQueue.HealToTarget"/> ]
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></param>
/// <param name="canRespawn"></param>
public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false)
{
GamingQueue?.HealToTarget(actor, target, heal, canRespawn);
}
/// <summary> /// <summary>
/// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting"/>,以防止中断性变更 ] /// 打断施法 [ 尽可能的调用此方法而不是直接调用 <see cref="IGamingQueue.InterruptCasting"/>,以防止中断性变更 ]
/// </summary> /// </summary>
@ -484,9 +482,6 @@ namespace Milimoe.FunGame.Core.Entity
copy.Name = Name; copy.Name = Name;
copy.Description = Description; copy.Description = Description;
copy.EffectType = EffectType; copy.EffectType = EffectType;
copy.TargetSelf = TargetSelf;
copy.TargetCount = TargetCount;
copy.TargetRange = TargetRange;
copy.Durative = Durative; copy.Durative = Durative;
copy.Duration = Duration; copy.Duration = Duration;
copy.DurationTurn = DurationTurn; copy.DurationTurn = DurationTurn;

View File

@ -15,7 +15,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary> /// <summary>
/// 普通攻击说明 /// 普通攻击说明
/// </summary> /// </summary>
public string Description => $"对目标敌人造成 {(1.0 + 0.05 * (Level - 1)) * 100:0.##}% [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。"; public string Description => $"对目标敌人造成 {(1.0 + 0.05 * (Level - 1)) * 100:0.##}% 攻击力 [ {Damage:0.##} ] 点{(IsMagic ? CharacterSet.GetMagicDamageName(MagicType) : "")}。";
/// <summary> /// <summary>
/// 所属的角色 /// 所属的角色
@ -66,6 +66,8 @@ namespace Milimoe.FunGame.Core.Entity
public void Attack(IGamingQueue queue, Character attacker, params Character[] enemys) public void Attack(IGamingQueue queue, Character attacker, params Character[] enemys)
{ {
foreach (Character enemy in enemys) foreach (Character enemy in enemys)
{
if (enemy.HP > 0)
{ {
queue.WriteLine("[ " + Character + $" ] 对 [ {enemy} ] 发起了普通攻击!"); queue.WriteLine("[ " + Character + $" ] 对 [ {enemy} ] 发起了普通攻击!");
double expected = Damage; double expected = Damage;
@ -73,6 +75,7 @@ namespace Milimoe.FunGame.Core.Entity
queue.DamageToEnemy(attacker, enemy, damage, true, IsMagic, MagicType, result); queue.DamageToEnemy(attacker, enemy, damage, true, IsMagic, MagicType, result);
} }
} }
}
/// <summary> /// <summary>
/// 修改伤害类型 /// 修改伤害类型
@ -95,7 +98,7 @@ namespace Milimoe.FunGame.Core.Entity
StringBuilder builder = new(); StringBuilder builder = new();
builder.AppendLine(Name + " - 等级 " + Level); builder.AppendLine(Name + " - 等级 " + Level);
builder.AppendLine("技能描述:" + Description); builder.AppendLine("描述:" + Description);
builder.AppendLine("硬直时间:" + HardnessTime); builder.AppendLine("硬直时间:" + HardnessTime);
return builder.ToString(); return builder.ToString();

View File

@ -18,8 +18,92 @@ namespace Milimoe.FunGame.Core.Entity
foreach (string str in args.Keys) foreach (string str in args.Keys)
{ {
Values[str] = args[str]; Values[str] = args[str];
switch (str) switch (str.ToLower())
{ {
case "active":
if (bool.TryParse(args[str].ToString(), out bool isActive) && isActive)
{
SkillType = SkillType.Item;
}
break;
case "debuff":
if (bool.TryParse(args[str].ToString(), out bool isDebuff) && isDebuff)
{
IsDebuff = isDebuff;
}
break;
case "self":
if (bool.TryParse(args[str].ToString(), out bool self))
{
CanSelectSelf = self;
}
break;
case "enemy":
if (bool.TryParse(args[str].ToString(), out bool enemy))
{
CanSelectEnemy = enemy;
}
break;
case "teammate":
if (bool.TryParse(args[str].ToString(), out bool teammate))
{
CanSelectTeammate = teammate;
}
break;
case "count":
if (int.TryParse(args[str].ToString(), out int count) && count > 0)
{
CanSelectTargetCount = count;
}
break;
case "range":
if (int.TryParse(args[str].ToString(), out int range) && range > 0)
{
CanSelectTargetRange = range;
}
break;
case "mpcost":
if (double.TryParse(args[str].ToString(), out double mpcost) && mpcost > 0)
{
MPCost = mpcost;
}
break;
case "epcost":
if (double.TryParse(args[str].ToString(), out double epcost) && epcost > 0)
{
EPCost = epcost;
}
break;
case "costall":
if (bool.TryParse(args[str].ToString(), out bool costall) && costall)
{
CostAllEP = costall;
}
break;
case "mincost":
if (double.TryParse(args[str].ToString(), out double mincost) && mincost > 0)
{
MinCostEP = mincost;
}
break;
case "cd":
if (double.TryParse(args[str].ToString(), out double cd) && cd > 0)
{
CD = cd;
}
break;
case "cast":
if (double.TryParse(args[str].ToString(), out double cast) && cast > 0)
{
CastTime = cast;
}
break;
case "ht":
if (double.TryParse(args[str].ToString(), out double ht) && ht > 0)
{
HardnessTime = ht;
}
break;
default: default:
break; break;
} }

View File

@ -82,11 +82,26 @@ namespace Milimoe.FunGame.Core.Entity
[InitRequired] [InitRequired]
public bool IsMagic => SkillType == SkillType.Magic; public bool IsMagic => SkillType == SkillType.Magic;
/// <summary>
/// 是否属于 Debuff
/// </summary>
public bool IsDebuff { get; set; } = false;
/// <summary> /// <summary>
/// 可选取自身 /// 可选取自身
/// </summary> /// </summary>
public virtual bool CanSelectSelf { get; set; } = false; public virtual bool CanSelectSelf { get; set; } = false;
/// <summary>
/// 可选取敌对角色
/// </summary>
public virtual bool CanSelectEnemy { get; set; } = true;
/// <summary>
/// 可选取友方角色
/// </summary>
public virtual bool CanSelectTeammate { get; set; } = false;
/// <summary> /// <summary>
/// 可选取的作用目标数量 /// 可选取的作用目标数量
/// </summary> /// </summary>
@ -97,10 +112,15 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary> /// </summary>
public virtual double CanSelectTargetRange { get; set; } = 0; public virtual double CanSelectTargetRange { get; set; } = 0;
/// <summary>
/// 选取角色的条件
/// </summary>
public List<Func<Character, bool>> SelectTargetPredicates { get; } = [];
/// <summary> /// <summary>
/// 实际魔法消耗 [ 魔法 ] /// 实际魔法消耗 [ 魔法 ]
/// </summary> /// </summary>
public double RealMPCost => Math.Max(0, MPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * 0.00125))); public double RealMPCost => Math.Max(0, MPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * General.GameplayEquilibriumConstant.INTtoCastMPReduce)));
/// <summary> /// <summary>
/// 魔法消耗 [ 魔法 ] /// 魔法消耗 [ 魔法 ]
@ -122,7 +142,7 @@ namespace Milimoe.FunGame.Core.Entity
/// <summary> /// <summary>
/// 实际能量消耗 [ 战技 ] /// 实际能量消耗 [ 战技 ]
/// </summary> /// </summary>
public double RealEPCost => CostAllEP ? Math.Max(MinCostEP, Character?.EP ?? MinCostEP) : (IsSuperSkill ? EPCost : Math.Max(0, EPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * 0.00075)))); public double RealEPCost => CostAllEP ? Math.Max(MinCostEP, Character?.EP ?? MinCostEP) : (IsSuperSkill ? EPCost : Math.Max(0, EPCost * (1 - Calculation.PercentageCheck((Character?.INT ?? 0) * General.GameplayEquilibriumConstant.INTtoCastEPReduce))));
/// <summary> /// <summary>
/// 能量消耗 [ 战技 ] /// 能量消耗 [ 战技 ]
@ -140,10 +160,20 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary> /// </summary>
public virtual double MinCostEP { get; set; } = 100; public virtual double MinCostEP { get; set; } = 100;
/// <summary>
/// 上一次释放此技能消耗的魔法 [ 魔法 ]
/// </summary>
public double LastCostMP { get; set; } = 0;
/// <summary>
/// 上一次释放此技能消耗的能量 [ 战技 ]
/// </summary>
public double LastCostEP { get; set; } = 0;
/// <summary> /// <summary>
/// 实际冷却时间 /// 实际冷却时间
/// </summary> /// </summary>
public double RealCD => Math.Max(0, CD * (1 - Character?.CDR ?? 0)); public double RealCD => Math.Max(0, CD * (1 - (Character?.CDR ?? 0)));
/// <summary> /// <summary>
/// 冷却时间 /// 冷却时间
@ -247,21 +277,37 @@ namespace Milimoe.FunGame.Core.Entity
/// <returns></returns> /// <returns></returns>
public virtual List<Character> SelectTargets(Character caster, List<Character> enemys, List<Character> teammates) public virtual List<Character> SelectTargets(Character caster, List<Character> enemys, List<Character> teammates)
{ {
List<Character> tobeSelected = [];
if (CanSelectSelf) if (CanSelectSelf)
{ {
return [caster]; tobeSelected.Add(caster);
}
if (CanSelectEnemy)
{
tobeSelected.AddRange(enemys);
}
if (CanSelectTeammate)
{
tobeSelected.AddRange(teammates);
}
// 筛选出符合条件的角色
tobeSelected = [.. tobeSelected.Where(c => SelectTargetPredicates.All(f => f(c)))];
List<Character> targets = [];
if (tobeSelected.Count <= CanSelectTargetCount)
{
targets.AddRange(tobeSelected);
} }
else else
{ {
List<Character> targets = []; targets.AddRange(tobeSelected.OrderBy(x => Random.Shared.Next()).Take(CanSelectTargetCount));
if (enemys.Count <= CanSelectTargetCount)
{
return [.. enemys];
} }
return enemys.OrderBy(x => Random.Shared.Next()).Take(CanSelectTargetCount).ToList(); return [.. targets.Distinct()];
}
} }
/// <summary> /// <summary>
@ -269,16 +315,26 @@ namespace Milimoe.FunGame.Core.Entity
/// </summary> /// </summary>
/// <param name="queue"></param> /// <param name="queue"></param>
/// <param name="caster"></param> /// <param name="caster"></param>
public void OnSkillCasting(IGamingQueue queue, Character caster) /// <param name="targets"></param>
public void OnSkillCasting(IGamingQueue queue, Character caster, List<Character> targets)
{ {
GamingQueue = queue; GamingQueue = queue;
foreach (Effect e in Effects) foreach (Effect e in Effects)
{ {
e.GamingQueue = GamingQueue; e.GamingQueue = GamingQueue;
e.OnSkillCasting(caster); e.OnSkillCasting(caster, targets);
} }
} }
/// <summary>
/// 技能效果触发前
/// </summary>
public void BeforeSkillCasted()
{
LastCostMP = RealMPCost;
LastCostEP = RealEPCost;
}
/// <summary> /// <summary>
/// 触发技能效果 /// 触发技能效果
/// </summary> /// </summary>

View File

@ -43,5 +43,6 @@
public int LastRank { get; set; } = 0; public int LastRank { get; set; } = 0;
public double AvgRank { get; set; } = 0; public double AvgRank { get; set; } = 0;
public double Rating { get; set; } = 0; public double Rating { get; set; } = 0;
public int MVPs { get; set; } = 0;
} }
} }

View File

@ -1,14 +1,129 @@
using Milimoe.FunGame.Core.Library.Constant; using System.Text;
using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Library.Constant;
namespace Milimoe.FunGame.Core.Entity.System namespace Milimoe.FunGame.Core.Entity
{ {
public class RoundRecord(int round, Character actor) public class RoundRecord(int round)
{ {
public int Round { get; set; } = round; public int Round { get; set; } = round;
public Character Actor { get; set; } = actor; public Character Actor { get; set; } = Factory.GetCharacter();
public CharacterActionType ActionType { get; set; } = CharacterActionType.None; public CharacterActionType ActionType { get; set; } = CharacterActionType.None;
public List<Character> Targets { get; set; } = []; public List<Character> Targets { get; set; } = [];
public Skill? Skill { get; set; } = null; public Skill? Skill { get; set; } = null;
public string SkillCost { get; set; } = "";
public Item? Item { get; set; } = null; public Item? Item { get; set; } = null;
public bool HasKill { get; set; } = false;
public Dictionary<Character, double> Damages { get; set; } = [];
public Dictionary<Character, bool> IsCritical { get; set; } = [];
public Dictionary<Character, double> Heals { get; set; } = [];
public Dictionary<Character, EffectType> Effects { get; set; } = [];
public List<string> ActorContinuousKilling { get; set; } = [];
public List<string> DeathContinuousKilling { get; set; } = [];
public double CastTime { get; set; } = 0;
public double HardnessTime { get; set; } = 0;
public Dictionary<Character, double> RespawnCountdowns { get; set; } = [];
public List<Character> Respawns { get; set; } = [];
public List<Skill> RoundRewards { get; set; } = [];
public override string ToString()
{
StringBuilder builder = new();
builder.AppendLine($"=== Round {Round} ===");
if (RoundRewards.Count > 0)
{
builder.AppendLine($"[ {Actor} ] 回合奖励 -> {string.Join(" / ", RoundRewards.Select(s => s.Description)).Trim()}");
}
if (ActionType == CharacterActionType.NormalAttack || ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
{
if (ActionType == CharacterActionType.NormalAttack)
{
builder.Append($"[ {Actor} ] {Actor.NormalAttack.Name} -> ");
}
else if (ActionType == CharacterActionType.CastSkill || ActionType == CharacterActionType.CastSuperSkill)
{
if (Skill != null)
{
builder.Append($"[ {Actor} ] {Skill.Name}{SkillCost}-> ");
}
else
{
builder.Append($"释放魔法 -> ");
}
}
builder.AppendLine(string.Join(" / ", GetTargetsState()));
if (DeathContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", DeathContinuousKilling)}");
if (ActorContinuousKilling.Count > 0) builder.AppendLine($"{string.Join("\r\n", ActorContinuousKilling)}");
}
if (ActionType == CharacterActionType.PreCastSkill && Skill != null)
{
if (Skill.IsMagic)
{
builder.AppendLine($"[ {Actor} ] 吟唱 [ {Skill.Name} ],持续时间:{CastTime:0.##}");
}
else
{
builder.AppendLine($"[ {Actor} ]{Skill.Name}{SkillCost}-> ");
builder.AppendLine(string.Join(" / ", GetTargetsState()));
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
}
}
else
{
builder.AppendLine($"[ {Actor} ] 回合结束,硬直时间:{HardnessTime:0.##}");
}
foreach (Character character in RespawnCountdowns.Keys)
{
builder.AppendLine($"[ {character} ] 进入复活倒计时:{RespawnCountdowns[character]:0.##}");
}
foreach (Character character in Respawns)
{
builder.AppendLine($"[ {character} ] 复活了");
}
return builder.ToString();
}
private List<string> GetTargetsState()
{
List<string> strings = [];
foreach (Character target in Targets.Distinct())
{
string hasDamage = "";
string hasHeal = "";
string hasEffect = "";
if (Damages.TryGetValue(target, out double damage))
{
hasDamage = $"伤害:{damage:0.##}";
if (IsCritical.TryGetValue(target, out bool isCritical) && isCritical)
{
hasDamage = "暴击," + hasDamage;
}
}
if (Heals.TryGetValue(target, out double heals))
{
hasHeal = $"治疗:{heals:0.##}";
}
if (Effects.TryGetValue(target, out EffectType effectType))
{
hasEffect = $"施加:{SkillSet.GetEffectTypeName(effectType)}";
}
if (ActionType == CharacterActionType.NormalAttack && hasDamage == "")
{
hasDamage = "完美闪避";
}
if ((ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.PreCastSkill || ActionType == CharacterActionType.CastSkill) && hasDamage == "" && target != Actor)
{
hasDamage = "免疫";
}
string[] strs = [hasDamage, hasHeal, hasEffect];
strings.Add($"[ {target}{string.Join(" / ", strs.Where(s => s != ""))}]");
}
return strings;
}
} }
} }

View File

@ -1,4 +1,4 @@
namespace Milimoe.FunGame.Core.Entity.System namespace Milimoe.FunGame.Core.Entity
{ {
public class Season(long id, string name, string description) public class Season(long id, string name, string description)
{ {

View File

@ -13,11 +13,26 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// </summary> /// </summary>
public Action<string> WriteLine { get; } public Action<string> WriteLine { get; }
/// <summary>
/// 原始的角色字典
/// </summary>
public Dictionary<Guid, Character> Original { get; }
/// <summary> /// <summary>
/// 当前的行动顺序 /// 当前的行动顺序
/// </summary> /// </summary>
public List<Character> Queue { get; } public List<Character> Queue { get; }
/// <summary>
/// 上回合记录
/// </summary>
public RoundRecord LastRound { get; set; }
/// <summary>
/// 所有回合的记录
/// </summary>
public List<RoundRecord> Rounds { get; }
/// <summary> /// <summary>
/// 当前已死亡的角色顺序(第一个是最早死的) /// 当前已死亡的角色顺序(第一个是最早死的)
/// </summary> /// </summary>
@ -63,6 +78,15 @@ namespace Milimoe.FunGame.Core.Interface.Base
/// <param name="damageResult"></param> /// <param name="damageResult"></param>
public void DamageToEnemy(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal); public void DamageToEnemy(Character actor, Character enemy, double damage, bool isNormalAttack, bool isMagicDamage = false, MagicType magicType = MagicType.None, DamageResult damageResult = DamageResult.Normal);
/// <summary>
/// 治疗一个目标
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></param>
/// <param name="canRespawn"></param>
public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false);
/// <summary> /// <summary>
/// 计算物理伤害 /// 计算物理伤害
/// </summary> /// </summary>

View File

@ -41,6 +41,12 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Skill.CanSelectSelf): case nameof(Skill.CanSelectSelf):
result.CanSelectSelf = reader.GetBoolean(); result.CanSelectSelf = reader.GetBoolean();
break; break;
case nameof(Skill.CanSelectEnemy):
result.CanSelectEnemy = reader.GetBoolean();
break;
case nameof(Skill.CanSelectTeammate):
result.CanSelectTeammate = reader.GetBoolean();
break;
case nameof(Skill.CanSelectTargetCount): case nameof(Skill.CanSelectTargetCount):
result.CanSelectTargetCount = reader.GetInt32(); result.CanSelectTargetCount = reader.GetInt32();
break; break;
@ -59,6 +65,12 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
case nameof(Skill.EPCost): case nameof(Skill.EPCost):
result.EPCost = reader.GetDouble(); result.EPCost = reader.GetDouble();
break; break;
case nameof(Skill.CostAllEP):
result.CostAllEP = reader.GetBoolean();
break;
case nameof(Skill.MinCostEP):
result.MinCostEP = reader.GetDouble();
break;
case nameof(Skill.CastTime): case nameof(Skill.CastTime):
result.CastTime = reader.GetDouble(); result.CastTime = reader.GetDouble();
break; break;
@ -100,12 +112,16 @@ namespace Milimoe.FunGame.Core.Library.Common.JsonConverter
if (value.Level > 0) writer.WriteNumber(nameof(Skill.Level), value.Level); if (value.Level > 0) writer.WriteNumber(nameof(Skill.Level), value.Level);
writer.WriteNumber(nameof(Skill.SkillType), (int)value.SkillType); writer.WriteNumber(nameof(Skill.SkillType), (int)value.SkillType);
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.CanSelectTeammate) writer.WriteBoolean(nameof(Skill.CanSelectTeammate), value.CanSelectTeammate);
if (value.CanSelectTargetCount != 0) writer.WriteNumber(nameof(Skill.CanSelectTargetCount), value.CanSelectTargetCount); if (value.CanSelectTargetCount != 0) writer.WriteNumber(nameof(Skill.CanSelectTargetCount), value.CanSelectTargetCount);
if (value.CanSelectTargetRange != 0) writer.WriteNumber(nameof(Skill.CanSelectTargetRange), value.CanSelectTargetRange); if (value.CanSelectTargetRange != 0) writer.WriteNumber(nameof(Skill.CanSelectTargetRange), value.CanSelectTargetRange);
if (!value.Enable) writer.WriteBoolean(nameof(Skill.Enable), value.Enable); if (!value.Enable) writer.WriteBoolean(nameof(Skill.Enable), value.Enable);
if (value.IsInEffect) writer.WriteBoolean(nameof(Skill.IsInEffect), value.IsInEffect); if (value.IsInEffect) writer.WriteBoolean(nameof(Skill.IsInEffect), value.IsInEffect);
if (value.MPCost > 0) writer.WriteNumber(nameof(Skill.MPCost), value.MPCost); if (value.MPCost > 0) writer.WriteNumber(nameof(Skill.MPCost), value.MPCost);
if (value.EPCost > 0) writer.WriteNumber(nameof(Skill.EPCost), value.EPCost); if (value.EPCost > 0) writer.WriteNumber(nameof(Skill.EPCost), value.EPCost);
if (value.CostAllEP) writer.WriteBoolean(nameof(Skill.CostAllEP), value.CostAllEP);
if (value.MinCostEP > 0) writer.WriteNumber(nameof(Skill.MinCostEP), value.MinCostEP);
if (value.CastTime > 0) writer.WriteNumber(nameof(Skill.CastTime), value.CastTime); if (value.CastTime > 0) writer.WriteNumber(nameof(Skill.CastTime), value.CastTime);
if (value.CD > 0) writer.WriteNumber(nameof(Skill.CD), value.CD); if (value.CD > 0) writer.WriteNumber(nameof(Skill.CD), value.CD);
if (value.CurrentCD > 0) writer.WriteNumber(nameof(Skill.CurrentCD), value.CurrentCD); if (value.CurrentCD > 0) writer.WriteNumber(nameof(Skill.CurrentCD), value.CurrentCD);

View File

@ -521,5 +521,53 @@ namespace Milimoe.FunGame.Core.Library.Constant
_ => General.GameplayEquilibriumConstant.MaxPassiveSkillLevel _ => General.GameplayEquilibriumConstant.MaxPassiveSkillLevel
}; };
} }
public static string GetEffectTypeName(EffectType type)
{
return type switch
{
EffectType.None => "无特殊效果",
EffectType.Item => "装备特效",
EffectType.Mark => "标记",
EffectType.Stun => "眩晕",
EffectType.Freeze => "冰冻",
EffectType.Silence => "沉默",
EffectType.Root => "定身",
EffectType.Fear => "恐惧",
EffectType.Sleep => "睡眠",
EffectType.Knockback => "击退",
EffectType.Knockdown => "击倒",
EffectType.Taunt => "嘲讽",
EffectType.Slow => "减速",
EffectType.Weaken => "衰弱",
EffectType.Poison => "中毒",
EffectType.Burn => "燃烧",
EffectType.Bleed => "流血",
EffectType.Blind => "致盲",
EffectType.Cripple => "致残",
EffectType.Shield => "护盾",
EffectType.HealOverTime => "持续治疗",
EffectType.Haste => "加速",
EffectType.Invulnerable => "无敌",
EffectType.Unselectable => "不可选中",
EffectType.DamageBoost => "伤害提升",
EffectType.DefenseBoost => "防御提升",
EffectType.CritBoost => "暴击提升",
EffectType.ManaRegen => "魔法恢复",
EffectType.ArmorBreak => "破甲",
EffectType.MagicResistBreak => "降低魔抗",
EffectType.Curse => "诅咒",
EffectType.Exhaustion => "疲劳",
EffectType.ManaBurn => "魔力燃烧",
EffectType.Charm => "魅惑",
EffectType.Disarm => "缴械",
EffectType.Confusion => "混乱",
EffectType.Petrify => "石化",
EffectType.SilenceMagic => "法术沉默",
EffectType.Banish => "放逐",
EffectType.Doom => "毁灭",
_ => "未知效果"
};
}
} }
} }

View File

@ -1,6 +1,5 @@
using Milimoe.FunGame.Core.Api.Utility; using Milimoe.FunGame.Core.Api.Utility;
using Milimoe.FunGame.Core.Entity; using Milimoe.FunGame.Core.Entity;
using Milimoe.FunGame.Core.Entity.System;
using Milimoe.FunGame.Core.Interface.Base; using Milimoe.FunGame.Core.Interface.Base;
using Milimoe.FunGame.Core.Library.Constant; using Milimoe.FunGame.Core.Library.Constant;
@ -16,6 +15,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary> /// </summary>
public Action<string> WriteLine { get; } public Action<string> WriteLine { get; }
/// <summary>
/// 原始的角色字典
/// </summary>
public Dictionary<Guid, Character> Original => _original;
/// <summary> /// <summary>
/// 当前的行动顺序 /// 当前的行动顺序
/// </summary> /// </summary>
@ -82,7 +86,17 @@ namespace Milimoe.FunGame.Core.Model
/// <summary> /// <summary>
/// 上回合记录 /// 上回合记录
/// </summary> /// </summary>
public RoundRecord LastRound { get; set; } = new(0, Factory.GetCharacter()); public RoundRecord LastRound { get; set; } = new(0);
/// <summary>
/// 所有回合的记录
/// </summary>
public List<RoundRecord> Rounds { get; } = [];
/// <summary>
/// 原始的角色字典
/// </summary>
protected readonly Dictionary<Guid, Character> _original = [];
/// <summary> /// <summary>
/// 当前的行动顺序 /// 当前的行动顺序
@ -119,6 +133,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary> /// </summary>
protected readonly Dictionary<Character, int> _earnedMoney = []; protected readonly Dictionary<Character, int> _earnedMoney = [];
/// <summary>
/// 角色最高连杀数
/// </summary>
protected readonly Dictionary<Character, int> _maxContinuousKilling = [];
/// <summary> /// <summary>
/// 角色目前的连杀数 /// 角色目前的连杀数
/// </summary> /// </summary>
@ -154,6 +173,11 @@ namespace Milimoe.FunGame.Core.Model
/// </summary> /// </summary>
protected readonly Dictionary<Character, double> _respawnCountdown = []; protected readonly Dictionary<Character, double> _respawnCountdown = [];
/// <summary>
/// 当前回合死亡角色
/// </summary>
protected readonly List<Character> _roundDeaths = [];
/// <summary> /// <summary>
/// 是否是团队模式 /// 是否是团队模式
/// </summary> /// </summary>
@ -202,6 +226,15 @@ namespace Milimoe.FunGame.Core.Model
/// <param name="characters"></param> /// <param name="characters"></param>
public void InitCharacterQueue(List<Character> characters) public void InitCharacterQueue(List<Character> characters)
{ {
// 保存原始的角色信息。用于复活时还原状态
foreach (Character character in characters)
{
Character original = character.Copy();
original.Guid = Guid.NewGuid();
character.Guid = original.Guid;
_original.Add(original.Guid, original);
}
// 初始排序:按速度排序 // 初始排序:按速度排序
List<IGrouping<double, Character>> groupedBySpeed = [.. characters List<IGrouping<double, Character>> groupedBySpeed = [.. characters
.GroupBy(c => c.SPD) .GroupBy(c => c.SPD)
@ -291,6 +324,7 @@ namespace Milimoe.FunGame.Core.Model
public void ClearQueue() public void ClearQueue()
{ {
FirstKiller = null; FirstKiller = null;
_original.Clear();
_queue.Clear(); _queue.Clear();
_hardnessTimes.Clear(); _hardnessTimes.Clear();
_assistDamage.Clear(); _assistDamage.Clear();
@ -298,6 +332,7 @@ namespace Milimoe.FunGame.Core.Model
_cutCount.Clear(); _cutCount.Clear();
_castingSkills.Clear(); _castingSkills.Clear();
_castingSuperSkills.Clear(); _castingSuperSkills.Clear();
_maxContinuousKilling.Clear();
_continuousKilling.Clear(); _continuousKilling.Clear();
_earnedMoney.Clear(); _earnedMoney.Clear();
_eliminated.Clear(); _eliminated.Clear();
@ -491,8 +526,8 @@ namespace Milimoe.FunGame.Core.Model
/// <returns>是否结束游戏</returns> /// <returns>是否结束游戏</returns>
public bool ProcessTurn(Character character) public bool ProcessTurn(Character character)
{ {
TotalRound++; LastRound.Actor = character;
LastRound = new(TotalRound, character); _roundDeaths.Clear();
if (!BeforeTurn(character)) if (!BeforeTurn(character))
{ {
@ -696,7 +731,7 @@ namespace Milimoe.FunGame.Core.Model
character.CharacterState = CharacterState.Casting; character.CharacterState = CharacterState.Casting;
_castingSkills.Add(character, new(skill, targets)); _castingSkills.Add(character, new(skill, targets));
baseTime = skill.CastTime; baseTime = skill.CastTime;
skill.OnSkillCasting(this, character); skill.OnSkillCasting(this, character, targets);
} }
} }
else else
@ -708,12 +743,14 @@ namespace Milimoe.FunGame.Core.Model
if (!cancel) if (!cancel)
{ {
decided = true; decided = true;
skill.OnSkillCasting(this, character); skill.OnSkillCasting(this, character, targets);
skill.BeforeSkillCasted();
character.EP -= cost; character.EP -= cost;
baseTime = skill.HardnessTime; baseTime = skill.HardnessTime;
skill.CurrentCD = skill.RealCD; skill.CurrentCD = skill.RealCD;
skill.Enable = false; skill.Enable = false;
LastRound.SkillCost = $"{-cost:0.##} EP";
WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "" : "")} [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}"); WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点能量,释放了{(skill.IsSuperSkill ? "" : "")} [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}");
skill.OnSkillCasted(this, character, targets); skill.OnSkillCasted(this, character, targets);
@ -742,10 +779,13 @@ namespace Milimoe.FunGame.Core.Model
// 判断是否能够释放技能 // 判断是否能够释放技能
if (CheckCanCast(character, skill, out double cost)) if (CheckCanCast(character, skill, out double cost))
{ {
skill.BeforeSkillCasted();
character.MP -= cost; character.MP -= cost;
baseTime = skill.HardnessTime; baseTime = skill.HardnessTime;
skill.CurrentCD = skill.RealCD; skill.CurrentCD = skill.RealCD;
skill.Enable = false; skill.Enable = false;
LastRound.SkillCost = $"{-cost:0.##} MP";
WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}"); WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点魔法值,释放了魔法 [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}");
skill.OnSkillCasted(this, character, targets); skill.OnSkillCasted(this, character, targets);
@ -779,10 +819,13 @@ namespace Milimoe.FunGame.Core.Model
List<Character> targets = SelectTargets(character, skill, enemys, teammates, out _); List<Character> targets = SelectTargets(character, skill, enemys, teammates, out _);
LastRound.Targets = [.. targets]; LastRound.Targets = [.. targets];
skill.BeforeSkillCasted();
character.EP -= cost; character.EP -= cost;
baseTime = skill.HardnessTime; baseTime = skill.HardnessTime;
skill.CurrentCD = skill.RealCD; skill.CurrentCD = skill.RealCD;
skill.Enable = false; skill.Enable = false;
LastRound.SkillCost = $"{-cost:0.##} EP";
WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}"); WriteLine("[ " + character + $" ] 消耗了 {cost:0.##} 点能量值,释放了爆发技 [ {skill.Name} ]{(skill.Slogan != "" ? skill.Slogan : "")}");
skill.OnSkillCasted(this, character, targets); skill.OnSkillCasted(this, character, targets);
@ -811,13 +854,16 @@ namespace Milimoe.FunGame.Core.Model
WriteLine("[ " + character + $" ] 放弃了行动!"); WriteLine("[ " + character + $" ] 放弃了行动!");
} }
LastRound.ActionType = type;
// 统一在回合结束时处理角色的死亡
ProcessCharacterDeath(character);
if (_isGameEnd) if (_isGameEnd)
{ {
return _isGameEnd; return _isGameEnd;
} }
LastRound.ActionType = type;
// 减少硬直时间 // 减少硬直时间
double newHardnessTime = baseTime; double newHardnessTime = baseTime;
if (character.CharacterState != CharacterState.Casting) if (character.CharacterState != CharacterState.Casting)
@ -829,8 +875,10 @@ namespace Milimoe.FunGame.Core.Model
{ {
newHardnessTime = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.AccelerationCoefficient))); newHardnessTime = Math.Max(0, Calculation.Round2Digits(baseTime * (1 - character.AccelerationCoefficient)));
WriteLine("[ " + character + " ] 进行吟唱,持续时间: " + newHardnessTime); WriteLine("[ " + character + " ] 进行吟唱,持续时间: " + newHardnessTime);
LastRound.CastTime = newHardnessTime;
} }
AddCharacter(character, newHardnessTime, isCheckProtected); AddCharacter(character, newHardnessTime, isCheckProtected);
LastRound.HardnessTime = newHardnessTime;
// 有人想要插队吗? // 有人想要插队吗?
WillPreCastSuperSkill(character); WillPreCastSuperSkill(character);
@ -885,10 +933,38 @@ namespace Milimoe.FunGame.Core.Model
// 获取第一个角色的硬直时间 // 获取第一个角色的硬直时间
double timeToReduce = _hardnessTimes[_queue[0]]; double timeToReduce = _hardnessTimes[_queue[0]];
TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce); // 如果复活时间更快,应该先流逝复活时间
if (_respawnCountdown.Count != 0)
{
double timeToRespawn = _respawnCountdown.Values.Min();
if (timeToRespawn < timeToReduce)
{
timeToReduce = Calculation.Round2Digits(timeToRespawn);
}
}
TotalTime = Calculation.Round2Digits(TotalTime + timeToReduce);
WriteLine("时间流逝:" + timeToReduce); WriteLine("时间流逝:" + timeToReduce);
// 减少复活倒计时
foreach (Character character in _respawnCountdown.Keys)
{
_respawnCountdown[character] = Calculation.Round2Digits(_respawnCountdown[character] - timeToReduce);
if (_respawnCountdown[character] <= 0)
{
double hardnessTime = 5;
character.Respawn(_original[character.Guid]);
WriteLine($"[ {character} ] 已复活!获得 {hardnessTime} 硬直时间。");
AddCharacter(character, hardnessTime, false);
LastRound.Respawns.Add(character);
_respawnCountdown.Remove(character);
if (!_respawnTimes.TryAdd(character, 1))
{
_respawnTimes[character] += 1;
}
}
}
foreach (Character character in _queue) foreach (Character character in _queue)
{ {
// 减少所有角色的硬直时间 // 减少所有角色的硬直时间
@ -970,25 +1046,13 @@ namespace Milimoe.FunGame.Core.Model
} }
} }
// 减少复活倒计时
foreach (Character character in _respawnCountdown.Keys)
{
_respawnCountdown[character] -= timeToReduce;
if (_respawnCountdown[character] <= 0)
{
double hardnessTime = 5;
character.Respawn();
WriteLine($"[ {character} ] 已复活!获得 {hardnessTime} 硬直时间。");
AddCharacter(character, hardnessTime, false);
_respawnCountdown.Remove(character);
if (!_respawnTimes.TryAdd(character, 1))
{
_respawnTimes[character] += 1;
}
}
}
WriteLine("\r\n"); WriteLine("\r\n");
// 在时间流逝后,进入下一回合
TotalRound++;
LastRound = new(TotalRound);
Rounds.Add(LastRound);
return timeToReduce; return timeToReduce;
} }
@ -1010,6 +1074,10 @@ namespace Milimoe.FunGame.Core.Model
return; return;
} }
if (!LastRound.IsCritical.TryAdd(enemy, damageResult == DamageResult.Critical) && damageResult == DamageResult.Critical)
{
LastRound.IsCritical[enemy] = true;
}
bool isEvaded = damageResult == DamageResult.Evaded; bool isEvaded = damageResult == DamageResult.Evaded;
List<Effect> effects = actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0).ToList(); List<Effect> effects = actor.Effects.Union(enemy.Effects).Where(e => e.Level > 0).ToList();
foreach (Effect effect in effects) foreach (Effect effect in effects)
@ -1062,71 +1130,54 @@ namespace Milimoe.FunGame.Core.Model
effect.AfterDamageCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult); effect.AfterDamageCalculation(actor, enemy, damage, isNormalAttack, isMagicDamage, magicType, damageResult);
} }
if (enemy.HP <= 0 && !_eliminated.Contains(enemy)) if (enemy.HP <= 0 && !_eliminated.Contains(enemy) && !_respawnCountdown.ContainsKey(enemy))
{ {
LastRound.HasKill = true;
_roundDeaths.Add(enemy);
DeathCalculation(actor, enemy); DeathCalculation(actor, enemy);
// 给所有角色的特效广播角色死亡结算
effects = _queue.SelectMany(c => c.Effects.Where(e => e.Level > 0)).ToList();
foreach (Effect effect in effects)
{
effect.AfterDeathCalculation(enemy, actor, _continuousKilling, _earnedMoney);
}
// 将死者移出队列
_queue.Remove(enemy);
if (_isTeamMode)
{
Team? killTeam = GetTeam(actor);
Team? deathTeam = GetTeam(enemy);
if (deathTeam != null)
{
if (deathTeam.GetActiveCharacters(this).Count == 0)
{
// 团灭了
_eliminatedTeams.Add(deathTeam);
_teams.Remove(deathTeam.Name);
}
else
{
List<Character> remain = deathTeam.GetActiveTeammates(this, enemy);
int remainCount = remain.Count;
if (remainCount > 0) WriteLine($"[ {deathTeam} ] 剩余成员:[ {string.Join(" ] / [ ", remain)} ]{remainCount} 人)");
} }
} }
if (killTeam != null) /// <summary>
/// 治疗一个目标
/// </summary>
/// <param name="actor"></param>
/// <param name="target"></param>
/// <param name="heal"></param>
/// <param name="canRespawn"></param>
public void HealToTarget(Character actor, Character target, double heal, bool canRespawn = false)
{ {
if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin) if (target.HP == target.MaxHP)
{ {
List<Team> combinedTeams = [.. _eliminatedTeams, .. _teams.Values];
combinedTeams.Remove(killTeam);
_eliminatedTeams.Clear();
_eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score));
EndGameInfo(killTeam);
return; return;
} }
if (!_teams.Keys.Where(str => str != killTeam.Name).Any())
bool isDead = target.HP <= 0;
if (heal < 0) heal = 0;
if (target.HP > 0 || (isDead && canRespawn))
{ {
// 没有其他的团队了,游戏结束 target.HP += heal;
EndGameInfo(killTeam); if (!LastRound.Heals.TryAdd(target, heal))
return;
}
else if (killTeam != null)
{ {
List<Character> actives = killTeam.GetActiveCharacters(this); LastRound.Heals[target] += heal;
actives.Add(actor);
int remainCount = actives.Count;
if (remainCount > 0) WriteLine($"[ {killTeam} ] 剩余成员:[ {string.Join(" ] / [ ", actives)} ]{remainCount} 人)");
} }
} }
if (isDead && canRespawn)
{
if (target != actor)
{
WriteLine($"[ {target} ] 被 [ {actor} ] 复苏了,并回复了 {heal:0.##} 点生命值!!");
}
else
{
WriteLine($"[ {target} ] 复苏了,并回复了 {heal:0.##} 点生命值!!");
}
} }
else else
{ {
if (!_queue.Where(c => c != actor).Any()) WriteLine($"[ {target} ] 回复了 {heal:0.##} 点生命值!");
{
// 没有其他的角色了,游戏结束
EndGameInfo(actor);
}
}
} }
} }
@ -1211,10 +1262,10 @@ namespace Milimoe.FunGame.Core.Model
double penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF; double penetratedDEF = (1 - actor.PhysicalPenetration) * enemy.DEF;
// 物理伤害减免 // 物理伤害减免
double physicalDamageReduction = penetratedDEF / (penetratedDEF + 120); double physicalDamageReduction = penetratedDEF / (penetratedDEF + General.GameplayEquilibriumConstant.DEFReductionFactor);
// 最终的物理伤害 // 最终的物理伤害
finalDamage = expectedDamage * (1 - physicalDamageReduction); finalDamage = expectedDamage * (1 - Calculation.PercentageCheck(physicalDamageReduction + enemy.ExPDR));
// 暴击判定 // 暴击判定
effects = actor.Effects.Where(e => e.Level > 0).ToList(); effects = actor.Effects.Where(e => e.Level > 0).ToList();
@ -1355,6 +1406,79 @@ namespace Milimoe.FunGame.Core.Model
return DamageResult.Normal; return DamageResult.Normal;
} }
public void ProcessCharacterDeath(Character character)
{
foreach (Character enemy in _roundDeaths)
{
// 给所有角色的特效广播角色死亡结算
List<Effect> effects = _queue.SelectMany(c => c.Effects.Where(e => e.Level > 0)).ToList();
foreach (Effect effect in effects)
{
effect.AfterDeathCalculation(enemy, character, _continuousKilling, _earnedMoney);
}
// 将死者移出队列
_queue.Remove(enemy);
if (_isTeamMode)
{
Team? killTeam = GetTeam(character);
Team? deathTeam = GetTeam(enemy);
if (MaxRespawnTimes != 0)
{
WriteLine($"\r\n=== 当前死亡竞赛比分 ===\r\n{string.Join("\r\n", Teams.OrderByDescending(kv => kv.Value.Score).Select(kv => kv.Key + "" + kv.Value.Score + "" + kv.Value.GetActiveCharacters(this).Count + ""))}");
}
if (deathTeam != null)
{
List<Character> remain = deathTeam.GetActiveCharacters(this);
int remainCount = remain.Count;
if (remainCount == 0)
{
// 团灭了
_eliminatedTeams.Add(deathTeam);
_teams.Remove(deathTeam.Name);
}
else if (MaxRespawnTimes == 0)
{
WriteLine($"[ {deathTeam} ] 剩余成员:[ {string.Join(" ] / [ ", remain)} ]{remainCount} 人)");
}
}
if (killTeam != null)
{
List<Character> actives = killTeam.GetActiveCharacters(this);
actives.Add(character);
int remainCount = actives.Count;
if (remainCount > 0 && MaxRespawnTimes == 0)
{
WriteLine($"[ {killTeam} ] 剩余成员:[ {string.Join(" ] / [ ", actives)} ]{remainCount} 人)");
}
if (!_teams.Keys.Where(str => str != killTeam.Name).Any())
{
// 没有其他的团队了,游戏结束
EndGameInfo(killTeam);
}
if (MaxScoreToWin > 0 && killTeam.Score >= MaxScoreToWin)
{
List<Team> combinedTeams = [.. _eliminatedTeams, .. _teams.Values];
combinedTeams.Remove(killTeam);
_eliminatedTeams.Clear();
_eliminatedTeams.AddRange(combinedTeams.OrderByDescending(t => t.Score));
EndGameInfo(killTeam);
}
}
}
else
{
if (!_queue.Where(c => c != character).Any())
{
// 没有其他的角色了,游戏结束
EndGameInfo(character);
}
}
}
}
/// <summary> /// <summary>
/// 死亡结算 /// 死亡结算
/// </summary> /// </summary>
@ -1363,6 +1487,10 @@ namespace Milimoe.FunGame.Core.Model
public void DeathCalculation(Character killer, Character death) public void DeathCalculation(Character killer, Character death)
{ {
if (!_continuousKilling.TryAdd(killer, 1)) _continuousKilling[killer] += 1; if (!_continuousKilling.TryAdd(killer, 1)) _continuousKilling[killer] += 1;
if (!_maxContinuousKilling.TryAdd(killer, 1) && _continuousKilling[killer] > _maxContinuousKilling[killer])
{
_maxContinuousKilling[killer] = _continuousKilling[killer];
}
_stats[killer].Kills += 1; _stats[killer].Kills += 1;
_stats[death].Deaths += 1; _stats[death].Deaths += 1;
int money = Random.Shared.Next(250, 350); int money = Random.Shared.Next(250, 350);
@ -1392,6 +1520,7 @@ namespace Milimoe.FunGame.Core.Model
money += (coefficient + 1) * Random.Shared.Next(50, 100); money += (coefficient + 1) * Random.Shared.Next(50, 100);
string termination = CharacterSet.GetContinuousKilling(coefficient); string termination = CharacterSet.GetContinuousKilling(coefficient);
string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " " + termination : "")},获得 {money} {General.GameplayEquilibriumConstant.InGameCurrency}"; string msg = $"[ {killer} ] 终结了 [ {death} ]{(termination != "" ? " " + termination : "")},获得 {money} {General.GameplayEquilibriumConstant.InGameCurrency}";
LastRound.DeathContinuousKilling.Add(msg);
if (assists.Length > 1) if (assists.Length > 1)
{ {
msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]";
@ -1401,6 +1530,7 @@ namespace Milimoe.FunGame.Core.Model
else else
{ {
string msg = $"[ {killer} ] 杀死了 [ {death} ],获得 {money} {General.GameplayEquilibriumConstant.InGameCurrency}"; string msg = $"[ {killer} ] 杀死了 [ {death} ],获得 {money} {General.GameplayEquilibriumConstant.InGameCurrency}";
LastRound.DeathContinuousKilling.Add(msg);
if (assists.Length > 1) if (assists.Length > 1)
{ {
msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]"; msg += "助攻:[ " + string.Join(" ] / [ ", assists.Where(c => c != killer)) + " ]";
@ -1419,21 +1549,27 @@ namespace Milimoe.FunGame.Core.Model
int kills = _continuousKilling[killer]; int kills = _continuousKilling[killer];
string continuousKilling = CharacterSet.GetContinuousKilling(kills); string continuousKilling = CharacterSet.GetContinuousKilling(kills);
string actorContinuousKilling = "";
if (kills == 2 || kills == 3) if (kills == 2 || kills == 3)
{ {
WriteLine("[ " + killer + " ] 完成了" + continuousKilling + ""); actorContinuousKilling = "[ " + killer + " ] 完成了一次" + continuousKilling + "";
} }
else if (kills == 4) else if (kills == 4)
{ {
WriteLine("[ " + killer + " ] 正在" + continuousKilling + ""); actorContinuousKilling = "[ " + killer + " ] 正在" + continuousKilling + "";
} }
else if (kills > 4 && kills < 10) else if (kills > 4 && kills < 10)
{ {
WriteLine("[ " + killer + " ] 已经" + continuousKilling + ""); actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "";
} }
else if (kills >= 10) else if (kills >= 10)
{ {
WriteLine("[ " + killer + " ] 已经" + continuousKilling + "!拜托谁去杀了他吧!!!"); actorContinuousKilling = "[ " + killer + " ] 已经" + continuousKilling + "!拜托谁去杀了他吧!!!";
}
if (actorContinuousKilling != "")
{
LastRound.ActorContinuousKilling.Add(actorContinuousKilling);
WriteLine(actorContinuousKilling);
} }
if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money; if (!_earnedMoney.TryAdd(killer, money)) _earnedMoney[killer] += money;
@ -1467,8 +1603,34 @@ namespace Milimoe.FunGame.Core.Model
else else
{ {
// 进入复活倒计时 // 进入复活倒计时
double respawnTime = Math.Min(90, death.Level * 0.36 + times * 2.77); double respawnTime = Calculation.Round2Digits(Math.Min(90, death.Level * 0.36 + times * 2.77 + kills * Random.Shared.Next(0, 3)));
_respawnCountdown.TryAdd(death, respawnTime); _respawnCountdown.TryAdd(death, respawnTime);
LastRound.RespawnCountdowns.TryAdd(death, respawnTime);
WriteLine($"[ {death} ] 进入复活倒计时:{respawnTime:0.##} 时间!");
}
// 移除死者的施法
_castingSkills.Remove(death);
_castingSuperSkills.Remove(death);
// 因丢失目标而中断施法
List<Character> castingSkills = [.. _castingSkills.Keys];
foreach (Character caster in castingSkills)
{
SkillTarget st = _castingSkills[caster];
if (st.Targets.Remove(death) && st.Targets.Count == 0)
{
_castingSkills.Remove(caster);
if (caster.CharacterState == CharacterState.Casting)
{
caster.CharacterState = CharacterState.Actionable;
}
WriteLine($"[ {caster} ] 终止了 [ {st.Skill.Name} ] 的施法" + (_hardnessTimes[caster] > 3 ? ",并获得了 3 硬直时间的补偿。" : "。"));
if (_hardnessTimes[caster] > 3)
{
_hardnessTimes[caster] = 3;
}
}
} }
} }
@ -1489,7 +1651,7 @@ namespace Milimoe.FunGame.Core.Model
CharacterStatistics statistics = CharacterStatistics[ec]; CharacterStatistics statistics = CharacterStatistics[ec];
string topCharacter = ec.ToString() + string topCharacter = ec.ToString() +
(statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") +
(_continuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") +
(_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {General.GameplayEquilibriumConstant.InGameCurrency} ]" : ""); (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {General.GameplayEquilibriumConstant.InGameCurrency} ]" : "");
if (top == 1) if (top == 1)
{ {
@ -1571,7 +1733,7 @@ namespace Milimoe.FunGame.Core.Model
string topCharacter = respawning + ec.ToString() + string topCharacter = respawning + ec.ToString() +
(statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") + (statistics.FirstKills > 0 ? " [ 第一滴血 ]" : "") +
(_continuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") + (_maxContinuousKilling.TryGetValue(ec, out int kills) && kills > 1 ? $" [ {CharacterSet.GetContinuousKilling(kills)} ]" : "") +
(_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {General.GameplayEquilibriumConstant.InGameCurrency} ]" : "") + (_earnedMoney.TryGetValue(ec, out int earned) ? $" [ 已赚取 {earned} {General.GameplayEquilibriumConstant.InGameCurrency} ]" : "") +
$"{statistics.Kills} / {statistics.Assists}{(MaxRespawnTimes != 0 ? " / " + statistics.Deaths : "")}"; $"{statistics.Kills} / {statistics.Assists}{(MaxRespawnTimes != 0 ? " / " + statistics.Deaths : "")}";
topTeam += topCharacter + "\r\n"; topTeam += topCharacter + "\r\n";
@ -1671,7 +1833,7 @@ namespace Milimoe.FunGame.Core.Model
_hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01); _hardnessTimes[c] = Calculation.Round2Digits(_hardnessTimes[c] + 0.01);
} }
} }
skill.OnSkillCasting(this, other); skill.OnSkillCasting(this, other, []);
} }
} }
} }
@ -1779,6 +1941,14 @@ namespace Milimoe.FunGame.Core.Model
} }
statsTaken.TotalTakenDamage = Calculation.Round2Digits(statsTaken.TotalTakenDamage + damage); statsTaken.TotalTakenDamage = Calculation.Round2Digits(statsTaken.TotalTakenDamage + damage);
} }
if (LastRound.Damages.TryGetValue(characterTaken, out double damageTotal))
{
LastRound.Damages[characterTaken] = damageTotal + damage;
}
else
{
LastRound.Damages[characterTaken] = damage;
}
} }
/// <summary> /// <summary>
@ -1837,6 +2007,10 @@ namespace Milimoe.FunGame.Core.Model
cancel = false; cancel = false;
if (skill.SkillType == SkillType.SuperSkill) cancel = false; if (skill.SkillType == SkillType.SuperSkill) cancel = false;
List<Character> targets = skill.SelectTargets(caster, enemys, teammates); List<Character> targets = skill.SelectTargets(caster, enemys, teammates);
if (targets.Count == 0)
{
cancel = true;
}
return targets; return targets;
} }

View File

@ -225,6 +225,16 @@
/// </summary> /// </summary>
public double INTtoMRFactor { get; set; } = 0.1; public double INTtoMRFactor { get; set; } = 0.1;
/// <summary>
/// 每 1 点智力减少魔法消耗
/// </summary>
public double INTtoCastMPReduce { get; set; } = 0.00125;
/// <summary>
/// 每 1 点智力减少能量消耗
/// </summary>
public double INTtoCastEPReduce { get; set; } = 0.00075;
/// <summary> /// <summary>
/// 每 1 点敏捷增加行动速度 /// 每 1 点敏捷增加行动速度
/// </summary> /// </summary>