From 03826b4956af30e8171910b0f94e41bb3a4dc219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A7=A6=E6=B1=89?= <5725748+qin_and_han_dynasties@user.noreply.gitee.com> Date: Sun, 12 Apr 2026 15:14:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=80=E4=B8=AA=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E4=BF=A1=E6=81=AF=E7=9A=84=E8=AF=A6=E7=BB=86=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Build_God_Api/Dto/CharacterDto.cs | 10 +- Build_God_Api/Build_God_Api/Program.cs | 1 - .../Services/CharacterService.cs | 30 +- .../CharacterAttributeCalculateService.cs | 139 +++++++++- .../Build_God_Api/Services/ScrapService.cs | 18 +- Build_God_Game/src/api/character.ts | 8 + Build_God_Game/src/views/GameView.vue | 260 ++++++++++++++++++ 7 files changed, 446 insertions(+), 20 deletions(-) diff --git a/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs index c09d08c..12f3c6d 100644 --- a/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs +++ b/Build_God_Api/Build_God_Api/Dto/CharacterDto.cs @@ -7,10 +7,18 @@ public string LevelName { get; set; } public int LevelId { get; set; } public decimal MaxHP { get; set; } + public decimal BaseMaxHP { get; set; } + public decimal BonusMaxHP { get; set; } public decimal CurrentHP { get; set; } public decimal Attack { get; set; } + public decimal BaseAttack { get; set; } + public decimal BonusAttack { get; set; } public decimal Defend { get; set; } + public decimal BaseDefend { get; set; } + public decimal BonusDefend { get; set; } public decimal CriticalRate { get; set; } + public decimal BaseCriticalRate { get; set; } + public decimal BonusCriticalRate { get; set; } public string ProfessionName { get; set; } public decimal Money { get; set; } public decimal CurrentExp { get; set; } @@ -26,4 +34,4 @@ public DateTime LastLogin { get; set; } public DateTime CreatedOn { get; set; } } -} +} \ No newline at end of file diff --git a/Build_God_Api/Build_God_Api/Program.cs b/Build_God_Api/Build_God_Api/Program.cs index 2df4a95..d4bc4c2 100644 --- a/Build_God_Api/Build_God_Api/Program.cs +++ b/Build_God_Api/Build_God_Api/Program.cs @@ -96,7 +96,6 @@ namespace Build_God_Api sqlSugarClient.CodeFirst.InitTables(typeof(ChatMessage)); sqlSugarClient.CodeFirst.InitTables(typeof(CharacterDailyMission)); sqlSugarClient.CodeFirst.InitTables(typeof(Scrap)); - sqlSugarClient.CodeFirst.InitTables(typeof(CharacterScrap)); sqlSugarClient.CodeFirst.InitTables(typeof(Monster)); sqlSugarClient.CodeFirst.InitTables(typeof(MonsterReward)); diff --git a/Build_God_Api/Build_God_Api/Services/CharacterService.cs b/Build_God_Api/Build_God_Api/Services/CharacterService.cs index 5e8591c..d5a8a8e 100644 --- a/Build_God_Api/Build_God_Api/Services/CharacterService.cs +++ b/Build_God_Api/Build_God_Api/Services/CharacterService.cs @@ -276,10 +276,18 @@ namespace Build_God_Api.Services LevelName = level?.Name ?? "未知", LevelId = c.LevelId, MaxHP = attrs.MaxHP, + BaseMaxHP = attrs.BaseMaxHP, + BonusMaxHP = attrs.BonusMaxHP, CurrentHP = c.CurrentHP, Attack = attrs.Attack, + BaseAttack = attrs.BaseAttack, + BonusAttack = attrs.BonusAttack, Defend = attrs.Defend, + BaseDefend = attrs.BaseDefend, + BonusDefend = attrs.BonusDefend, CriticalRate = attrs.CriticalRate, + BaseCriticalRate = attrs.BaseCriticalRate, + BonusCriticalRate = attrs.BonusCriticalRate, ProfessionName = profession?.Name, Money = c.Money, CurrentExp = c.CurrentExp, @@ -340,7 +348,27 @@ namespace Build_God_Api.Services LastLogin = DateTime.Now }; - await db.Insertable(newOne).ExecuteCommandAsync(); + var characterId = await db.Insertable(newOne).ExecuteReturnIdentityAsync(); + + var bag = await db.Queryable().FirstAsync(x => x.Rarity == BagRarity.Common); + if (bag == null) + { + bag = new Bag + { + Name = "新手背包", + Rarity = BagRarity.Common, + Capacity = 50, + Description = "新手的初始背包" + }; + await db.Insertable(bag).ExecuteCommandAsync(); + } + + var characterBag = new CharacterBag + { + CharacterId = characterId, + BagId = bag.Id + }; + await db.Insertable(characterBag).ExecuteCommandAsync(); return true; } diff --git a/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs b/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs index 373b25c..89c90db 100644 --- a/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs +++ b/Build_God_Api/Build_God_Api/Services/Game/CharacterAttributeCalculateService.cs @@ -1,5 +1,6 @@ using Build_God_Api.DB; using SqlSugar; +using System.Text.Json; namespace Build_God_Api.Services.Game { @@ -16,10 +17,18 @@ namespace Build_God_Api.Services.Game public class CharacterAttributes { public decimal MaxHP { get; set; } + public decimal BaseMaxHP { get; set; } + public decimal BonusMaxHP { get; set; } public decimal CurrentHP { get; set; } public decimal Attack { get; set; } + public decimal BaseAttack { get; set; } + public decimal BonusAttack { get; set; } public decimal Defend { get; set; } + public decimal BaseDefend { get; set; } + public decimal BonusDefend { get; set; } public decimal CriticalRate { get; set; } + public decimal BaseCriticalRate { get; set; } + public decimal BonusCriticalRate { get; set; } } public class CharacterAttributeCalculateService(ISqlSugarClient context): ICharacterAttributeCalculateService @@ -44,6 +53,85 @@ namespace Build_God_Api.Services.Game return 0.5m * character.LevelId * character.LevelId * (decimal)Math.Log((double)(character.CurrentExp + 1000)) * defendRate; } + private async Task<(decimal AttackBonus, decimal DefendBonus, decimal HPBonus)> CalculateScrapBonusAsync(int characterBagId) + { + var bagItems = await _context.Queryable() + .Where(x => x.CharacterBagId == characterBagId && x.ItemType == BagItemType.Scrap) + .ToListAsync(); + + decimal attackBonus = 0; + decimal defendBonus = 0; + decimal hpBonus = 0; + + foreach (var item in bagItems) + { + var scrap = await _context.Queryable().FirstAsync(x => x.Id == item.ItemId); + if (scrap != null) + { + attackBonus += scrap.AttackBonus * item.Quantity; + defendBonus += scrap.DefenseBonus * item.Quantity; + hpBonus += scrap.HpBonus * item.Quantity; + } + } + + return (attackBonus, defendBonus, hpBonus); + } + + private async Task<(decimal AttackBonus, decimal DefendBonus, decimal HPBonus, decimal CriticalBonus)> CalculateEquipmentBonusAsync(int characterBagId) + { + var equipmentInstances = await _context.Queryable() + .Where(x => x.CharacterBagId == characterBagId) + .ToListAsync(); + + decimal attackBonus = 0; + decimal defendBonus = 0; + decimal hpBonus = 0; + decimal criticalBonus = 0; + + foreach (var instance in equipmentInstances) + { + var template = await _context.Queryable().FirstAsync(x => x.Id == instance.EquipmentTemplateId); + if (template == null || string.IsNullOrEmpty(instance.Attributes)) continue; + + try + { + var attributes = JsonSerializer.Deserialize>(instance.Attributes); + if (attributes == null) continue; + + foreach (var attr in attributes) + { + switch (attr.Type) + { + case EquipmentAttributeType.AttackFixed: + attackBonus += attr.Value; + break; + case EquipmentAttributeType.AttackPercent: + break; + case EquipmentAttributeType.DefendFixed: + defendBonus += attr.Value; + break; + case EquipmentAttributeType.DefendPercent: + break; + case EquipmentAttributeType.HealthBonusFixed: + hpBonus += attr.Value; + break; + case EquipmentAttributeType.HealthBonusPercent: + break; + case EquipmentAttributeType.CriticalPercent: + criticalBonus += attr.Value; + break; + } + } + } + catch + { + // ignore parse errors + } + } + + return (attackBonus, defendBonus, hpBonus, criticalBonus); + } + public async Task CalculateAttributesAsync(Character character) { Profession? profession = null; @@ -52,22 +140,43 @@ namespace Build_God_Api.Services.Game profession = await _context.Queryable().FirstAsync(x => x.Id == character.ProfessionId); } - decimal attackRate = profession?.AttackRate ?? 1m; - decimal defendRate = profession?.DefendRate ?? 1m; - decimal criticalRate = profession?.CriticalRate ?? 1m; + decimal baseMaxHP = CalculateMaxHP(character, profession); + decimal baseAttack = CalculateAttack(character, profession); + decimal baseDefend = CalculateDefend(character, profession); + decimal baseCritical = 0; - decimal maxHP = CalculateMaxHP(character, profession); - decimal attack = CalculateAttack(character, profession); - decimal defend = CalculateDefend(character, profession); - decimal critRate = 0; + decimal bonusAttack = 0; + decimal bonusDefend = 0; + decimal bonusHP = 0; + decimal bonusCritical = 0; + + var characterBag = await _context.Queryable().FirstAsync(x => x.CharacterId == character.Id); + if (characterBag != null) + { + var (scrapAttack, scrapDefend, scrapHP) = await CalculateScrapBonusAsync(characterBag.Id); + var (equipAttack, equipDefend, equipHP, equipCritical) = await CalculateEquipmentBonusAsync(characterBag.Id); + + bonusAttack = scrapAttack + equipAttack; + bonusDefend = scrapDefend + equipDefend; + bonusHP = scrapHP + equipHP; + bonusCritical = equipCritical; + } return new CharacterAttributes { - MaxHP = maxHP, + MaxHP = baseMaxHP + bonusHP, + BaseMaxHP = baseMaxHP, + BonusMaxHP = bonusHP, CurrentHP = character.CurrentHP, - Attack = attack, - Defend = defend, - CriticalRate = critRate + Attack = baseAttack + bonusAttack, + BaseAttack = baseAttack, + BonusAttack = bonusAttack, + Defend = baseDefend + bonusDefend, + BaseDefend = baseDefend, + BonusDefend = bonusDefend, + CriticalRate = baseCritical + bonusCritical, + BaseCriticalRate = baseCritical, + BonusCriticalRate = bonusCritical }; } @@ -85,4 +194,10 @@ namespace Build_God_Api.Services.Game await _context.Updateable(character).ExecuteCommandAsync(); } } -} + + public class EquipmentAttributeDto + { + public EquipmentAttributeType Type { get; set; } + public decimal Value { get; set; } + } +} \ No newline at end of file diff --git a/Build_God_Api/Build_God_Api/Services/ScrapService.cs b/Build_God_Api/Build_God_Api/Services/ScrapService.cs index e18a95c..1db1b5e 100644 --- a/Build_God_Api/Build_God_Api/Services/ScrapService.cs +++ b/Build_God_Api/Build_God_Api/Services/ScrapService.cs @@ -115,22 +115,30 @@ namespace Build_God_Api.Services public async Task> GetCharacterScrapHistoryAsync(int characterId) { - var history = await db.Queryable() - .Where(x => x.CharacterId == characterId) - .OrderByDescending(x => x.ObtainedAt) + var characterBag = await db.Queryable() + .FirstAsync(x => x.CharacterId == characterId); + + if (characterBag == null) + { + return new List(); + } + + var history = await db.Queryable() + .Where(x => x.CharacterBagId == characterBag.Id && x.ItemType == BagItemType.Scrap) + .OrderByDescending(x => x.CreatedOn) .ToListAsync(); var result = new List(); foreach (var item in history) { - var scrap = await db.Queryable().FirstAsync(x => x.Id == item.ScrapId); + var scrap = await db.Queryable().FirstAsync(x => x.Id == item.ItemId); if (scrap != null) { result.Add(new ScrapHistoryDto { Id = item.Id, Scrap = MapToDto(scrap), - ObtainedAt = item.ObtainedAt + ObtainedAt = item.CreatedOn }); } } diff --git a/Build_God_Game/src/api/character.ts b/Build_God_Game/src/api/character.ts index 98e0724..dd1d06a 100644 --- a/Build_God_Game/src/api/character.ts +++ b/Build_God_Game/src/api/character.ts @@ -5,10 +5,18 @@ export interface CharacterDto { levelName: string levelId: number maxHP: number + baseMaxHP: number + bonusMaxHP: number currentHP: number attack: number + baseAttack: number + bonusAttack: number defend: number + baseDefend: number + bonusDefend: number criticalRate: number + baseCriticalRate: number + bonusCriticalRate: number professionName?: string money: number currentExp: number diff --git a/Build_God_Game/src/views/GameView.vue b/Build_God_Game/src/views/GameView.vue index e0a9830..5afb82a 100644 --- a/Build_God_Game/src/views/GameView.vue +++ b/Build_God_Game/src/views/GameView.vue @@ -74,6 +74,8 @@ const navigateTo = (item: { label: string }) => { router.push('/bag') } else if (item.label === '捡垃圾') { router.push('/scrap') + } else if (item.label === '角色') { + showCharacterDetail.value = true } } @@ -85,6 +87,12 @@ const handleBreakthrough = async () => { showBreakthroughMessage.value = false }, 3000) } + +const showCharacterDetail = ref(false) + +const closeCharacterDetail = () => { + showCharacterDetail.value = false +} @@ -508,4 +597,175 @@ const handleBreakthrough = async () => { .logout-button:hover .logout-text { color: #ffffff; } + +.character-detail-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.85); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.character-detail-dialog { + background: rgba(20, 20, 25, 0.95); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 16px; + width: 90%; + max-width: 400px; + max-height: 80vh; + overflow-y: auto; + padding: 20px; +} + +.detail-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.detail-title { + color: #ffffff; + font-size: 1.2rem; + font-weight: 600; +} + +.detail-close { + color: #666666; + font-size: 1.5rem; + cursor: pointer; + line-height: 1; +} + +.detail-close:hover { + color: #ffffff; +} + +.detail-section { + margin-bottom: 20px; +} + +.section-title { + color: #22c55e; + font-size: 0.9rem; + font-weight: 500; + margin-bottom: 12px; + letter-spacing: 0.05em; +} + +.info-row { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.info-label { + color: #888888; + font-size: 0.85rem; +} + +.info-value { + color: #ffffff; + font-size: 0.85rem; +} + +.attr-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.attr-label { + color: #888888; + font-size: 0.85rem; + display: flex; + align-items: center; + gap: 6px; +} + +.attr-icon { + font-size: 0.9rem; +} + +.hp-icon { + color: #ef4444; +} + +.atk-icon { + color: #f97316; +} + +.def-icon { + color: #3b82f6; +} + +.crit-icon { + color: #eab308; +} + +.attr-value { + display: flex; + align-items: baseline; + gap: 4px; +} + +.base-value { + color: #ffffff; + font-size: 1rem; + font-weight: 500; +} + +.bonus-value { + color: #fbbf24; + font-size: 0.8rem; +} + +.exp-row { + display: flex; + justify-content: space-between; + padding: 8px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.exp-label { + color: #888888; + font-size: 0.85rem; +} + +.exp-value { + color: #cccccc; + font-size: 0.85rem; +} + +.detail-footer { + margin-top: 20px; + text-align: center; +} + +.switch-character-btn { + padding: 12px 24px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 10px; + color: #888888; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; +} + +.switch-character-btn:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.25); + color: #ffffff; +}