diff --git a/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs index 12f3c6d..998e590 100644 --- a/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs +++ b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs @@ -19,6 +19,8 @@ public decimal CriticalRate { get; set; } public decimal BaseCriticalRate { get; set; } public decimal BonusCriticalRate { get; set; } + public decimal BaseCriticalDamage { get; set; } + public decimal BonusCriticalDamage { get; set; } public string ProfessionName { get; set; } public decimal Money { get; set; } public decimal CurrentExp { get; set; } diff --git a/Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs b/Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs index 9bf28ed..d6b6b45 100644 --- a/Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs +++ b/Build_God_Api/Build_God_Api/Dto/MonsterDtos.cs @@ -53,6 +53,12 @@ namespace Build_God_Api.Dto public int Defense { get; set; } public decimal CriticalRate { get; set; } + /// 基础暴击伤害加成百分比(当前为 0) + public decimal BaseCriticalDamage { get; set; } + + /// 装备等提供的暴击伤害加成百分比 + public decimal BonusCriticalDamage { get; set; } + public List? Rewards { get; set; } public List? Equipment { get; set; } } diff --git a/Build_God_Api/Build_God_Api/Services/BattleService.cs b/Build_God_Api/Build_God_Api/Services/BattleService.cs index f59d7cb..cda9981 100644 --- a/Build_God_Api/Build_God_Api/Services/BattleService.cs +++ b/Build_God_Api/Build_God_Api/Services/BattleService.cs @@ -72,12 +72,20 @@ namespace Build_God_Api.Services { round++; - var playerDamage = CalculateDamage(playerAttack, monsterDefend, playerCriticalRate); + var playerDamage = CalculateDamage( + playerAttack, + monsterDefend, + playerCriticalRate, + CombatAttributeFormula.ResolveCriticalDamageBonusPercent(attributes.BaseCriticalDamage, attributes.BonusCriticalDamage)); monsterHealth -= playerDamage; if (monsterHealth <= 0) break; - var monsterDamage = CalculateDamage(monsterAttack, playerDefend, monsterStats.CriticalRate); + var monsterDamage = CalculateDamage( + monsterAttack, + playerDefend, + monsterStats.CriticalRate, + CombatAttributeFormula.ResolveCriticalDamageBonusPercent(monsterStats.BaseCriticalDamage, monsterStats.BonusCriticalDamage)); playerHealth -= monsterDamage; } @@ -124,20 +132,22 @@ namespace Build_God_Api.Services } } - private int CalculateDamage(int attack, int defend, decimal criticalRate) + private int CalculateDamage(int attack, int defend, decimal criticalRatePercent, decimal criticalDamageBonusPercent) { - var baseDamage = Math.Max(1, attack - defend); + var baseDamage = CombatAttributeFormula.CalculateBaseDamageBeforeCritical(attack, defend); var random = Random.Shared.NextDouble(); - var isCritical = random < (double)criticalRate / 100; + var isCritical = random < (double)criticalRatePercent / 100; + var critMult = 1m + criticalDamageBonusPercent / 100m; + decimal working = baseDamage; if (isCritical) { - baseDamage = (int)(baseDamage * 1.5); + working *= critMult; } var variance = Random.Shared.Next(90, 111) / 100m; - return (int)(baseDamage * variance); + return Math.Max(1, (int)(working * variance)); } private async Task GrantReward(int characterId, MonsterReward reward) diff --git a/Build_God_Api/Build_God_Api/Services/CharacterService.cs b/Build_God_Api/Build_God_Api/Services/CharacterService.cs index d24f66c..2a2011c 100644 --- a/Build_God_Api/Build_God_Api/Services/CharacterService.cs +++ b/Build_God_Api/Build_God_Api/Services/CharacterService.cs @@ -299,6 +299,8 @@ namespace Build_God_Api.Services CriticalRate = attrs.CriticalRate, BaseCriticalRate = attrs.BaseCriticalRate, BonusCriticalRate = attrs.BonusCriticalRate, + BaseCriticalDamage = attrs.BaseCriticalDamage, + BonusCriticalDamage = attrs.BonusCriticalDamage, ProfessionName = profession?.Name, Money = c.Money, CurrentExp = c.CurrentExp, diff --git a/Build_God_Api/Build_God_Api/Services/Game/CombatAttributeFormula.cs b/Build_God_Api/Build_God_Api/Services/Game/CombatAttributeFormula.cs index 18d29f0..8fe7e7c 100644 --- a/Build_God_Api/Build_God_Api/Services/Game/CombatAttributeFormula.cs +++ b/Build_God_Api/Build_God_Api/Services/Game/CombatAttributeFormula.cs @@ -7,6 +7,34 @@ namespace Build_God_Api.Services.Game /// public static class CombatAttributeFormula { + /// + /// 装备/角色未提供「暴击伤害」加成(百分比)时,用于暴击倍率的默认加成:倍率 = 1 + 该值/100(即 50 表示 1.5 倍)。 + /// + public const decimal DefaultCriticalDamageBonusPercent = 50m; + + /// + /// 暴击伤害加成百分比:若属性总和大于 0 则用属性(通常为装备「暴击伤害-百分比」累加),否则用 。 + /// 最终暴击倍率 = 1 + 返回值/100。 + /// + public static decimal ResolveCriticalDamageBonusPercent(decimal baseBonus, decimal equipmentBonus) + { + decimal sum = baseBonus + equipmentBonus; + return sum > 0 ? sum : DefaultCriticalDamageBonusPercent; + } + + /// + /// 单次攻击基础伤害(暴击、波动前)。采用 attack²/(attack+defense),使攻击略低于防御时仍能造成可观伤害,避免长期只有 1 点保底。 + /// + public static int CalculateBaseDamageBeforeCritical(int attack, int defend) + { + long a = Math.Max(0, attack); + long d = Math.Max(0, defend); + if (a <= 0) return 1; + double raw = (double)a * a / (a + d); + int damage = (int)Math.Floor(raw); + return Math.Max(1, damage); + } + public static decimal CalculateBaseMaxHP(int levelId, decimal expForLog, Profession? profession) { decimal healthRate = profession?.HealthRate ?? 1m; diff --git a/Build_God_Api/Build_God_Api/Services/Game/MonsterAttributeCalculateService.cs b/Build_God_Api/Build_God_Api/Services/Game/MonsterAttributeCalculateService.cs index 73f6a34..a2aa051 100644 --- a/Build_God_Api/Build_God_Api/Services/Game/MonsterAttributeCalculateService.cs +++ b/Build_God_Api/Build_God_Api/Services/Game/MonsterAttributeCalculateService.cs @@ -22,6 +22,12 @@ namespace Build_God_Api.Services.Game public decimal CriticalRate { get; set; } public decimal BaseCriticalRate { get; set; } public decimal BonusCriticalRate { get; set; } + + /// 基础暴击伤害加成百分比(当前怪物恒为 0) + public decimal BaseCriticalDamage { get; set; } + + /// 装备提供的暴击伤害加成百分比 + public decimal BonusCriticalDamage { get; set; } } public class MonsterAttributeCalculateService(ISqlSugarClient db) : IMonsterAttributeCalculateService @@ -52,6 +58,8 @@ namespace Build_God_Api.Services.Game BonusAttack = bonus.AttackBonus, BonusDefend = bonus.DefendBonus, BonusCriticalRate = bonus.CriticalBonus, + BaseCriticalDamage = 0, + BonusCriticalDamage = bonus.CriticalDamageBonus, MaxHP = baseMaxHp + bonus.HPBonus, Attack = baseAttack + bonus.AttackBonus, Defend = baseDefend + bonus.DefendBonus, diff --git a/Build_God_Api/Build_God_Api/Services/MonsterService.cs b/Build_God_Api/Build_God_Api/Services/MonsterService.cs index 87219ae..fce6677 100644 --- a/Build_God_Api/Build_God_Api/Services/MonsterService.cs +++ b/Build_God_Api/Build_God_Api/Services/MonsterService.cs @@ -77,6 +77,8 @@ namespace Build_God_Api.Services Attack = (int)stats.Attack, Defense = (int)stats.Defend, CriticalRate = stats.CriticalRate, + BaseCriticalDamage = stats.BaseCriticalDamage, + BonusCriticalDamage = stats.BonusCriticalDamage, Rewards = rewards, Equipment = equipDtos }; diff --git a/Build_God_Game/src/api/character.ts b/Build_God_Game/src/api/character.ts index f41dc12..ecbd4c3 100644 --- a/Build_God_Game/src/api/character.ts +++ b/Build_God_Game/src/api/character.ts @@ -17,6 +17,9 @@ export interface CharacterDto { criticalRate: number baseCriticalRate: number bonusCriticalRate: number + /** 暴击伤害加成百分比(装备等);缺省时前端按 0 参与累加,战斗再套用默认 50 */ + baseCriticalDamage?: number + bonusCriticalDamage?: number professionName?: string money: number currentExp: number diff --git a/Build_God_Game/src/api/monster.ts b/Build_God_Game/src/api/monster.ts index a6abf8a..c4eb44f 100644 --- a/Build_God_Game/src/api/monster.ts +++ b/Build_God_Game/src/api/monster.ts @@ -22,6 +22,8 @@ export interface MonsterDto { attack: number defense: number criticalRate: number + baseCriticalDamage?: number + bonusCriticalDamage?: number rewards?: MonsterRewardDto[] equipment?: MonsterEquipmentClientDto[] } diff --git a/Build_God_Game/src/views/BattleView.vue b/Build_God_Game/src/views/BattleView.vue index 1bf2b53..a44875f 100644 --- a/Build_God_Game/src/views/BattleView.vue +++ b/Build_God_Game/src/views/BattleView.vue @@ -54,13 +54,32 @@ const getMonsterIcon = (iconName?: string) => { return `/src/assets/images/monster/${iconName}` } -const calculateDamage = (attack: number, defend: number, criticalRate: number): { damage: number; isCritical: boolean } => { - const baseDamage = Math.max(1, attack - defend) +const defaultCriticalDamageBonusPercent = 50 + +const resolveCriticalDamageBonusPercent = (baseBonus: number, equipmentBonus: number) => { + const sum = (baseBonus || 0) + (equipmentBonus || 0) + return sum > 0 ? sum : defaultCriticalDamageBonusPercent +} + +/** 与后端 CombatAttributeFormula / BattleService 一致:基础伤害 attack²/(attack+defend),暴击倍率 1+暴击伤害%/100 */ +const calculateDamage = ( + attack: number, + defend: number, + criticalRate: number, + criticalDamageBonusPercent: number +): { damage: number; isCritical: boolean } => { + const a = Math.max(0, attack) + const d = Math.max(0, defend) + const baseDamage = a <= 0 ? 1 : Math.max(1, Math.floor((a * a) / (a + d))) const random = Math.random() const isCritical = random < criticalRate / 100 - const damage = isCritical ? Math.floor(baseDamage * 1.5) : baseDamage + const critMult = 1 + criticalDamageBonusPercent / 100 + let working = baseDamage + if (isCritical) { + working = Math.floor(working * critMult) + } const variance = (90 + Math.floor(Math.random() * 21)) / 100 - return { damage: Math.floor(damage * variance), isCritical } + return { damage: Math.max(1, Math.floor(working * variance)), isCritical } } const startBattle = async () => { @@ -82,8 +101,16 @@ const startBattle = async () => { const playerAttack = currentCharacter.value.attack const playerDefend = currentCharacter.value.defend const playerCriticalRate = currentCharacter.value.criticalRate + const playerCritDamagePct = resolveCriticalDamageBonusPercent( + currentCharacter.value.baseCriticalDamage, + currentCharacter.value.bonusCriticalDamage + ) const monsterAttack = currentMonster.value.attack const monsterDefend = currentMonster.value.defense + const monsterCritDamagePct = resolveCriticalDamageBonusPercent( + currentMonster.value.baseCriticalDamage ?? 0, + currentMonster.value.bonusCriticalDamage ?? 0 + ) let round = 0 const maxRounds = 100 @@ -94,7 +121,7 @@ const startBattle = async () => { round++ // 玩家攻击 - const playerResult = calculateDamage(playerAttack, monsterDefend, playerCriticalRate) + const playerResult = calculateDamage(playerAttack, monsterDefend, playerCriticalRate, playerCritDamagePct) monsterHealth.value = Math.max(0, monsterHealth.value - playerResult.damage) battleLog.value.push({ round, @@ -109,7 +136,7 @@ const startBattle = async () => { } // 怪物攻击 - const monsterResult = calculateDamage(monsterAttack, playerDefend, currentMonster.value.criticalRate) + const monsterResult = calculateDamage(monsterAttack, playerDefend, currentMonster.value.criticalRate, monsterCritDamagePct) playerHealth.value = Math.max(0, playerHealth.value - monsterResult.damage) battleLog.value.push({ round,