Browse Source

突破的时候需要消耗物品

master
hanqin 2 weeks ago
parent
commit
f5b3a16f30
  1. 2
      Build_God_Admin_Frontend/Frontend/src/api/level.ts
  2. 1
      Build_God_Admin_Frontend/Frontend/src/api/pill.ts
  3. 63
      Build_God_Admin_Frontend/Frontend/src/views/admin/LevelsView.vue
  4. 34
      Build_God_Admin_Frontend/Frontend/src/views/admin/PillsView.vue
  5. 13
      Build_God_Api/Build_God_Api/DB/Level.cs
  6. 3
      Build_God_Api/Build_God_Api/Dto/CharacterDto.cs
  7. 41
      Build_God_Api/Build_God_Api/Services/BagService.cs
  8. 46
      Build_God_Api/Build_God_Api/Services/CharacterService.cs
  9. 3
      Build_God_Game/src/api/character.ts
  10. 29
      Build_God_Game/src/views/GameView.vue

2
Build_God_Admin_Frontend/Frontend/src/api/level.ts

@ -9,6 +9,8 @@ export interface Level{
baseBreakthroughRate: number;
failIncrement: number;
description: string;
requiredPillId?: number;
requiredPillQuantity?: number;
}
//获取所有的level

1
Build_God_Admin_Frontend/Frontend/src/api/pill.ts

@ -3,7 +3,6 @@ import http, { type EnumInfoDto } from "../api/index";
export interface Pill {
id: number;
name: string;
grade?: number;
type?: number;
rarity?: number;
money?: number;

63
Build_God_Admin_Frontend/Frontend/src/views/admin/LevelsView.vue

@ -2,9 +2,11 @@
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { AddLevel, DeleteLevel, GetLevelList, UpdateLevel, type Level } from '@/api/level'
import { GetPillList, type Pill } from '@/api/pill'
import { Plus,Edit,Delete,Close } from '@element-plus/icons-vue'
const levels = ref<Level[]>([])
const pills = ref<Pill[]>([])
const showDialog = ref(false)
const isEditing = ref(false)
@ -18,7 +20,9 @@ const formData = ref<Partial<Level>>({
nextLevelId: null,
baseBreakthroughRate: 0,
failIncrement: 0,
description: ''
description: '',
requiredPillId: undefined,
requiredPillQuantity: 0
})
const filteredLevels = computed(() => {
@ -33,7 +37,11 @@ const filteredLevels = computed(() => {
const openDialog = (level?: Level) => {
if (level!=undefined) {
isEditing.value = true
formData.value = { ...level }
formData.value = {
...level,
requiredPillId: level.requiredPillId || undefined,
requiredPillQuantity: level.requiredPillQuantity || 0
}
} else {
isEditing.value = false
formData.value = {
@ -43,7 +51,9 @@ const openDialog = (level?: Level) => {
nextLevelId: null,
baseBreakthroughRate: 0,
failIncrement: 0,
description: ''
description: '',
requiredPillId: undefined,
requiredPillQuantity: 0
}
}
showDialog.value = true
@ -118,8 +128,24 @@ const translateLevelName = (nextLevelId:number)=> {
return nextLevel ? nextLevel.name : '未知';
}
const translateLevelExp = (exp:number)=>{
return exp.toLocaleString()
}
const translatePillName = (pillId?: number) => {
if (!pillId) return '-'
const pill = pills.value.find(p => p.id === pillId)
return pill ? pill.name : '未知'
}
const loadPills = async () => {
const result = await GetPillList()
pills.value = result
}
onMounted(async () => {
RefreshLevels();
loadPills();
})
const RefreshLevels = async () => {
@ -145,7 +171,13 @@ const RefreshLevels = async () => {
<el-table :data="filteredLevels" style="width: 100%;" stripe>
<el-table-column label="境界ID" prop="levelId"/>
<el-table-column label="境界名称" prop="name"/>
<el-table-column label="当前境界最低经验" prop="currentLevelMinExp"/>
<el-table-column label="当前境界最低经验" prop="currentLevelMinExp">
<template #default="scoped">
<span>
{{ translateLevelExp(scoped.row.currentLevelMinExp) }}
</span>
</template>
</el-table-column>
<el-table-column label="下一境界ID">
<template #default="scoped">
<span>
@ -163,6 +195,16 @@ const RefreshLevels = async () => {
<span>{{ scoped.row.failIncrement }}%</span>
</template>
</el-table-column>
<el-table-column label="突破消耗丹药">
<template #default="scoped">
<span>{{ translatePillName(scoped.row.requiredPillId) }}</span>
</template>
</el-table-column>
<el-table-column label="突破消耗数量" prop="requiredPillQuantity">
<template #default="scoped">
<span>{{ scoped.row.requiredPillQuantity || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="描述" prop="description" show-overflow-tooltip/>
<el-table-column table="操作">
<template #default="scoped">
@ -198,6 +240,19 @@ const RefreshLevels = async () => {
<el-form-item label="失败增量[0-100]">
<el-input v-model="formData.failIncrement" placeholder="失败后增加的概率" clearable></el-input>
</el-form-item>
<el-form-item label="突破消耗丹药">
<el-select v-model="formData.requiredPillId" placeholder="选择突破消耗的丹药(不选择则不消耗)" clearable>
<el-option
v-for="pill in pills"
:key="pill.id"
:label="pill.name"
:value="pill.id"
/>
</el-select>
</el-form-item>
<el-form-item label="突破消耗数量">
<el-input-number v-model="formData.requiredPillQuantity" :min="0" placeholder="消耗数量" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="formData.description" placeholder="描述" type="textarea" clearable></el-input>
</el-form-item>

34
Build_God_Admin_Frontend/Frontend/src/views/admin/PillsView.vue

@ -1,14 +1,12 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { AddPill, DeletePill, getPillGrades, GetPillList, getPillRarities, getPillTypes, UpdatePill, type Pill } from '@/api/pill'
import { AddPill, DeletePill, GetPillList, getPillRarities, getPillTypes, UpdatePill, type Pill } from '@/api/pill'
import type { EnumInfoDto } from '@/api'
import { GetLevelList, type Level } from '@/api/level'
import { Plus, Edit, Delete, Close } from '@element-plus/icons-vue'
const pills = ref<Pill[]>([])
//
const grades = ref<EnumInfoDto[]>([])
//
const types = ref<EnumInfoDto[]>([])
//
@ -23,7 +21,6 @@ const searchQuery = ref('')
const formData = ref<Pill>({
id: 0,
name: '',
grade: undefined,
type: undefined,
rarity: undefined,
money: undefined,
@ -43,14 +40,6 @@ const filteredPills = computed(() => {
})
})
const translateGrade = (id: number) => {
var item = grades.value.find(x => x.id == id);
if (item) {
return item.description
}
return '未知'
}
const translateType = (id: number) => {
var item = types.value.find(x => x.id == id);
if (item) {
@ -84,7 +73,6 @@ const openDialog = (pill?: Pill) => {
formData.value = {
id: 0,
name: '',
grade: undefined,
type: undefined,
rarity: undefined,
money: undefined,
@ -104,7 +92,6 @@ const closeDialog = () => {
const savePill = async () => {
console.log('formdata', formData.value)
if (!formData.value.name
|| !formData.value.grade
|| !formData.value.type
|| !formData.value.rarity
|| !formData.value.money
@ -135,7 +122,6 @@ const savePill = async () => {
const newOne: Pill = {
id: 1,
name: formData.value.name,
grade: formData.value.grade,
type: formData.value.type,
rarity: formData.value.rarity,
money: formData.value.money,
@ -179,7 +165,6 @@ const deletePill = async (pill: Pill) => {
onMounted(async () => {
await fetchPills()
await fetchPillGrades()
await fetchPillTypes()
await fetchPillRarities()
await fetchLevels()
@ -189,10 +174,6 @@ const fetchPills = async () => {
pills.value = await GetPillList()
}
const fetchPillGrades = async () => {
grades.value = await getPillGrades()
}
const fetchPillTypes = async () => {
types.value = await getPillTypes()
}
@ -224,13 +205,6 @@ const fetchLevels = async () => {
<el-table :data="filteredPills" style="width: 100%;" stripe>
<el-table-column label="名称" prop="name" />
<el-table-column label="等级">
<template #default="scoped">
<span>
{{ translateGrade(scoped.row.grade) }}
</span>
</template>
</el-table-column>
<el-table-column label="类型">
<template #default="scoped">
<span>
@ -274,12 +248,6 @@ const fetchLevels = async () => {
<el-form-item label="丹药名称">
<el-input v-model="formData.name" clearable></el-input>
</el-form-item>
<el-form-item label="等级">
<el-select v-model="formData.grade">
<el-option v-for="(value, index) in grades" :key="index" :value="value.id" :label="value.description">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="类型">
<el-select v-model="formData.type">
<el-option v-for="(value, index) in types" :key="index" :value="value.id" :label="value.description">

13
Build_God_Api/Build_God_Api/DB/Level.cs

@ -15,7 +15,7 @@
/// <summary>
/// 当前等级最小经验值
/// </summary>
public int CurrentLevelMinExp { get; set; }
public decimal CurrentLevelMinExp { get; set; }
/// <summary>
/// 下一境界ID
@ -36,5 +36,16 @@
/// 描述
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// 突破所需丹药ID(如果为null或0则不需要消耗丹药)
/// </summary>
[SqlSugar.SugarColumn(IsNullable = true)]
public int? RequiredPillId { get; set; }
/// <summary>
/// 突破所需丹药数量
/// </summary>
public int RequiredPillQuantity { get; set; }
}
}

3
Build_God_Api/Build_God_Api/Dto/CharacterDto.cs

@ -16,6 +16,9 @@
public decimal? NextLevelMinExp { get; set; }
public decimal BreakthroughRate { get; set; }
public bool CanBreakthrough { get; set; }
public int? NextLevelRequiredPillId { get; set; }
public string? NextLevelRequiredPillName { get; set; }
public int NextLevelRequiredPillQuantity { get; set; }
public DateTime LastLogin { get; set; }
public DateTime CreatedOn { get; set; }
}

41
Build_God_Api/Build_God_Api/Services/BagService.cs

@ -20,6 +20,10 @@ namespace Build_God_Api.Services
Task<List<BagItemDto>> GetBagItems(int characterBagId);
Task<bool> AddItemToBag(int characterBagId, int itemType, int itemId, int quantity);
Task<bool> RemoveItemFromBag(int characterBagId, int bagItemId);
// 物品数量管理
Task<int> GetItemQuantity(int characterBagId, int itemType, int itemId);
Task<bool> ReduceItemQuantity(int characterBagId, int itemType, int itemId, int quantity);
}
public class BagItemDto
@ -252,5 +256,42 @@ namespace Build_God_Api.Services
await db.Deleteable(item).ExecuteCommandAsync();
return true;
}
public async Task<int> GetItemQuantity(int characterBagId, int itemType, int itemId)
{
var item = await db.Queryable<BagItem>()
.FirstAsync(x => x.CharacterBagId == characterBagId
&& x.ItemType == (BagItemType)itemType
&& x.ItemId == itemId);
return item?.Quantity ?? 0;
}
public async Task<bool> ReduceItemQuantity(int characterBagId, int itemType, int itemId, int quantity)
{
var item = await db.Queryable<BagItem>()
.FirstAsync(x => x.CharacterBagId == characterBagId
&& x.ItemType == (BagItemType)itemType
&& x.ItemId == itemId);
if (item == null)
throw new Exception("物品不存在");
if (item.Quantity < quantity)
throw new Exception($"物品数量不足,需要{quantity}个,当前只有{item.Quantity}个");
item.Quantity -= quantity;
if (item.Quantity <= 0)
{
await db.Deleteable(item).ExecuteCommandAsync();
}
else
{
await db.Updateable(item).ExecuteCommandAsync();
}
return true;
}
}
}

46
Build_God_Api/Build_God_Api/Services/CharacterService.cs

@ -3,6 +3,7 @@ using Build_God_Api.Dto;
using Build_God_Api.Services.Game;
using SqlSugar;
using System.Security.Cryptography;
using static Build_God_Api.DB.BagItem;
namespace Build_God_Api.Services
{
@ -45,11 +46,13 @@ public interface ICharacterService
public class CharacterService(ISqlSugarClient db,
ICurrentUserService currentUserService,
ICharacterAttributeCalculateService calculateService) : ICharacterService
ICharacterAttributeCalculateService calculateService,
IBagService bagService) : ICharacterService
{
private readonly ISqlSugarClient db = db;
private readonly ICurrentUserService currentUserService = currentUserService;
private readonly ICharacterAttributeCalculateService calculateService = calculateService;
private readonly IBagService bagService = bagService;
public async Task<bool> Breakthrough(int characterId)
{
@ -68,6 +71,29 @@ public interface ICharacterService
throw new Exception("当前境界无法突破");
}
// 检查突破是否需要消耗丹药
int? requiredPillId = currentLevel?.RequiredPillId;
int requiredPillQuantity = currentLevel?.RequiredPillQuantity ?? 0;
// 如果有配置突破消耗的丹药
if (requiredPillId.HasValue && requiredPillId.Value > 0 && requiredPillQuantity > 0)
{
var characterBag = await db.Queryable<CharacterBag>().FirstAsync(x => x.CharacterId == characterId);
if (characterBag == null)
throw new Exception("角色没有背包");
// 检查背包是否有足够的丹药
int pillQuantity = await bagService.GetItemQuantity(characterBag.Id, (int)BagItemType.Pill, requiredPillId.Value);
if (pillQuantity < requiredPillQuantity)
{
var pill = await db.Queryable<Pill>().FirstAsync(x => x.Id == requiredPillId);
throw new Exception($"突破所需{pill?.Name ?? ""}数量不足,需要{requiredPillQuantity}个,当前只有{pillQuantity}个");
}
// 扣除丹药(无论成功或失败都扣除)
await bagService.ReduceItemQuantity(characterBag.Id, (int)BagItemType.Pill, requiredPillId.Value, requiredPillQuantity);
}
if (character.BreakthroughRate >= 100)
{
character.LevelId = nextLevelId;
@ -84,6 +110,7 @@ public interface ICharacterService
character.LevelId = nextLevelId;
character.BreakthroughRate = nextLevel.BaseBreakthroughRate;
await calculateService.CalculateAndUpdateAttributesAsync(character);
return true;
}
else
{
@ -95,7 +122,7 @@ public interface ICharacterService
}
await db.Updateable(character).ExecuteCommandAsync();
return isSuccess;
return false;
}
public async Task<bool> StartTraining(int characterId)
@ -223,6 +250,18 @@ public async Task<Character?> GetCharacterByAccountId(int accountId)
canBreakthrough = true;
}
// 获取突破所需丹药信息
int? nextLevelRequiredPillId = null;
string? nextLevelRequiredPillName = null;
int nextLevelRequiredPillQuantity = 0;
if (level?.RequiredPillId > 0 && level.RequiredPillQuantity > 0)
{
nextLevelRequiredPillId = level.RequiredPillId;
nextLevelRequiredPillQuantity = level.RequiredPillQuantity;
var pill = await db.Queryable<Pill>().FirstAsync(x => x.Id == level.RequiredPillId);
nextLevelRequiredPillName = pill?.Name;
}
result.Add(new CharacterDto
{
Id = c.Id,
@ -239,6 +278,9 @@ public async Task<Character?> GetCharacterByAccountId(int accountId)
NextLevelMinExp = nextLevel?.CurrentLevelMinExp,
BreakthroughRate = c.BreakthroughRate,
CanBreakthrough = canBreakthrough,
NextLevelRequiredPillId = nextLevelRequiredPillId,
NextLevelRequiredPillName = nextLevelRequiredPillName,
NextLevelRequiredPillQuantity = nextLevelRequiredPillQuantity,
LastLogin = c.LastLogin,
CreatedOn = c.CreatedOn
});

3
Build_God_Game/src/api/character.ts

@ -49,6 +49,9 @@ export interface CharacterDto {
nextLevelMinExp?: number
breakthroughRate: number
canBreakthrough: boolean
nextLevelRequiredPillId?: number
nextLevelRequiredPillName?: string
nextLevelRequiredPillQuantity: number
lastLogin: string
createdOn: string
}

29
Build_God_Game/src/views/GameView.vue

@ -19,6 +19,9 @@ const currentExp = computed(() => characterStore.currentCharacter?.currentExp ??
const nextLevelName = computed(() => characterStore.currentCharacter?.nextLevelName ?? '')
const nextLevelMinExp = computed(() => characterStore.currentCharacter?.nextLevelMinExp ?? 0)
const breakthroughRate = computed(() => characterStore.currentCharacter?.breakthroughRate ?? 0)
const requiredPillName = computed(() => characterStore.currentCharacter?.nextLevelRequiredPillName ?? '')
const requiredPillQuantity = computed(() => characterStore.currentCharacter?.nextLevelRequiredPillQuantity ?? 0)
const hasPillCost = computed(() => requiredPillQuantity.value > 0 && !!requiredPillName.value)
const expProgress = computed(() => {
if (!nextLevelMinExp.value) return 0
@ -124,6 +127,10 @@ const handleBreakthrough = async () => {
<span class="rate-label">突破成功率</span>
<span class="rate-value">{{ breakthroughRate }}%</span>
</div>
<div v-if="hasPillCost" class="pill-cost-info">
<span class="pill-cost-label">突破消耗</span>
<span class="pill-cost-value">{{ requiredPillName }} ×{{ requiredPillQuantity }}</span>
</div>
<button
class="breakthrough-btn"
:class="{ 'can-breakthrough': canBreakthrough }"
@ -369,6 +376,28 @@ const handleBreakthrough = async () => {
font-weight: 500;
}
.pill-cost-info {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 14px;
background: rgba(255, 68, 68, 0.1);
border: 1px solid rgba(255, 68, 68, 0.2);
border-radius: 8px;
margin-top: 10px;
}
.pill-cost-label {
color: #888888;
font-size: 0.85rem;
}
.pill-cost-value {
color: #ff4444;
font-size: 0.9rem;
font-weight: 500;
}
.breakthrough-btn {
width: 100%;
padding: 14px;

Loading…
Cancel
Save