Browse Source

规划了日常任务的经验构成以及成长曲线

master
秦汉 1 day ago
parent
commit
99a442abc2
  1. 26
      Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue
  2. 14
      Build_God_Api/Build_God_Api/DB/Mission.cs
  3. 4
      Build_God_Api/Build_God_Api/DB/MissionReward.cs
  4. 80
      Build_God_Api/Build_God_Api/Services/DailyMissionService.cs
  5. 6
      Build_God_Game/src/api/dailyMission.ts
  6. 9
      Build_God_Game/src/api/index.ts
  7. 2
      Build_God_Game/src/components/StarBorder/StarBorder.vue
  8. 45
      Build_God_Game/src/views/DailyMissionView.vue
  9. 5
      Build_God_Game/src/views/LoginView.vue
  10. BIN
      Build_God_Game/模拟导图.xls

26
Build_God_Admin_Frontend/Frontend/src/views/admin/MissionView.vue

@ -347,18 +347,18 @@ const onRewardItemChange = (id: number) => {
}
const saveReward = async (missionId?: number) => {
console.log('missionId',missionId);
console.log('isedit',isEditingReward.value)
console.log('missionId', missionId);
console.log('isedit', isEditingReward.value)
if (isEditingReward.value) {
var index = missions.value.findIndex(x => x.id == rewardFormData.value.missionId)
if (index > -1) {
var result = await UpdateReward(rewardFormData.value)
if(result){
if (result) {
ElMessage.success('任务奖励修改成功')
closeRewardDialog()
await refreshMissions()
}
else{
else {
ElMessage.error('修改失败')
}
}
@ -675,7 +675,7 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
</el-form-item>
<el-form-item v-if="rewardFormData.rewardType === 1 || rewardFormData.rewardType === 2" label="物品">
<el-select v-model="rewardFormData.itemId" @change="onRewardItemChange">
<el-option v-for="(value, index) in rewardSelectData" :key="index" :value="value.id" :label="value.name"/>
<el-option v-for="(value, index) in rewardSelectData" :key="index" :value="value.id" :label="value.name" />
</el-select>
</el-form-item>
<el-form-item v-else-if="rewardFormData.rewardType === 3 || rewardFormData.rewardType === 4" label="说明">
@ -698,14 +698,17 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
<el-button @click="closeProgressDialog" :icon="Close" circle />
</div>
<el-form-item label="目标类型">
<el-select v-model="progressFormData.targetType" @change="progressTargetTypeChange" :disabled="isEditingProgress">
<el-option v-for="(value, index) in progressTargetTypes" :key="index" :value="value.id" :label="value.description">
<el-select v-model="progressFormData.targetType" @change="progressTargetTypeChange"
:disabled="isEditingProgress">
<el-option v-for="(value, index) in progressTargetTypes" :key="index" :value="value.id"
:label="value.description">
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="progressFormData.targetType === 1 || progressFormData.targetType === 4" label="物品">
<el-select v-model="progressFormData.targetItemId" @change="onProgressItemChange">
<el-option v-for="(value, index) in progressSelectData" :key="index" :value="value.id" :label="value.name"/>
<el-option v-for="(value, index) in progressSelectData" :key="index" :value="value.id"
:label="value.name" />
</el-select>
</el-form-item>
<el-form-item label="目标数量">
@ -733,14 +736,15 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
</el-form-item>
<el-form-item label="分类">
<el-select v-model="formData.type">
<el-option v-for="(value, index) in missionTypes" :key="index" :value="value.id">
<el-option v-for="(value, index) in missionTypes" :key="index" :value="value.id" :label="value.description">
{{ value.description }}
</el-option>
</el-select>
</el-form-item>
<el-form-item label="难度">
<el-select v-model="formData.difficulty">
<el-option v-for="(value, index) in missionDifficulties" :key="index" :value="value.id" :label="value.description">
<el-option v-for="(value, index) in missionDifficulties" :key="index" :value="value.id"
:label="value.description">
</el-option>
</el-select>
</el-form-item>
@ -763,7 +767,7 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
<el-input v-model="formData.preMissionId" placeholder="前置任务" clearable />
</el-form-item>
<el-form-item label="描述" style="width: 100%;">
<el-input v-model="formData.description" clearable type="textarea"/>
<el-input v-model="formData.description" clearable type="textarea" />
</el-form-item>
<el-form-item label="可重复">
<el-checkbox v-model="formData.repeatable" size="large" />

14
Build_God_Api/Build_God_Api/DB/Mission.cs

@ -95,10 +95,7 @@ namespace Build_God_Api.DB
MainStory = 1,
[Description("日常任务")]
DailyTask = 2,
[Description("采集任务")]
BranchTask = 3
DailyTask = 2
}
/// <summary>
@ -106,16 +103,13 @@ namespace Build_God_Api.DB
/// </summary>
public enum MissionDifficulty
{
[Description("简单")]
Easy = 1,
[Description("普通")]
Normal = 2,
Normal = 1,
[Description("困难")]
Hard = 3,
Hard = 2,
[Description("炼狱")]
Purgatory = 4
Purgatory = 3
}
}

4
Build_God_Api/Build_God_Api/DB/MissionReward.cs

@ -43,9 +43,7 @@ namespace Build_God_Api.DB
Pill = 1,
[Description("装备")]
Equipment = 2,
[Description("经验")]
Exp = 3,
[Description("灵石")]
Money = 4
Money = 3
}
}

80
Build_God_Api/Build_God_Api/Services/DailyMissionService.cs

@ -46,6 +46,9 @@ namespace Build_God_Api.Services
public DateTime? EndTime { get; set; }
public bool IsFromYesterday { get; set; }
public DateTime AssignedDate { get; set; }
public int ExpReward { get; set; }
public int TodayClaimedCount { get; set; }
public int TodayTotalCount { get; set; }
public List<MissionRewardDto> Rewards { get; set; } = new();
}
@ -69,9 +72,23 @@ namespace Build_God_Api.Services
private readonly ILogger<DailyMissionService> _logger = logger;
private const int DailyMissionCount = 5;
private const int HardMissionCount = 2;
private const int PurgatoryMissionCount = 1;
private static readonly TimeZoneInfo BeijingTimeZone = TimeZoneInfo.FindSystemTimeZoneById("China Standard Time");
private int CalculateExpReward(MissionDifficulty difficulty, int levelId)
{
var coefficient = difficulty switch
{
MissionDifficulty.Normal => 2,
MissionDifficulty.Hard => 4,
MissionDifficulty.Purgatory => 6,
_ => 2
};
return coefficient * levelId * levelId * levelId;
}
private DateTime GetBeijingTime()
{
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, BeijingTimeZone);
@ -88,12 +105,20 @@ namespace Build_God_Api.Services
var yesterday = today.AddDays(-1);
var dailyMissions = await _db.Queryable<CharacterDailyMission>()
.Where(x => x.CharacterId == characterId && x.Status != DailyMissionStatus.Claimed)
.Where(x => x.CharacterId == characterId)
.Where(x => x.AssignedDate == today || x.AssignedDate == yesterday)
.OrderByDescending(x => x.AssignedDate)
.OrderByDescending(x => x.IsFromYesterday)
.ToListAsync();
var character = await _db.Queryable<Character>()
.FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在");
var todayMissions = dailyMissions.Where(x => x.AssignedDate == today).ToList();
var todayClaimedCount = todayMissions.Count(x => x.Status == DailyMissionStatus.Claimed);
var todayTotalCount = todayMissions.Count;
var result = new List<DailyMissionDto>();
foreach (var dm in dailyMissions)
@ -117,7 +142,10 @@ namespace Build_God_Api.Services
StartTime = dm.StartTime,
EndTime = dm.EndTime,
IsFromYesterday = dm.IsFromYesterday,
AssignedDate = dm.AssignedDate
AssignedDate = dm.AssignedDate,
ExpReward = CalculateExpReward(mission.Difficulty, character.LevelId),
TodayClaimedCount = todayClaimedCount,
TodayTotalCount = todayTotalCount
};
if (mission.Rewards != null)
@ -143,6 +171,13 @@ namespace Build_God_Api.Services
public async Task<bool> AcceptMission(int characterId, int dailyMissionId)
{
var existingInProgress = await _db.Queryable<CharacterDailyMission>()
.Where(x => x.CharacterId == characterId && x.Status == DailyMissionStatus.InProgress)
.AnyAsync();
if (existingInProgress)
throw new Exception("已有任务在执行中,请先完成当前任务");
var dailyMission = await _db.Queryable<CharacterDailyMission>()
.FirstAsync(x => x.Id == dailyMissionId && x.CharacterId == characterId)
?? throw new Exception("任务不存在");
@ -184,17 +219,16 @@ namespace Build_God_Api.Services
.FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在");
var expReward = CalculateExpReward(mission.Difficulty, character.LevelId);
character.CurrentExp += expReward;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:经验 +{ExpReward}", characterId, expReward);
if (mission.Rewards != null)
{
foreach (var reward in mission.Rewards)
{
switch (reward.RewardType)
{
case RewardType.Exp:
character.CurrentExp += reward.Count;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:经验 +{Count}", characterId, reward.Count);
break;
case RewardType.Money:
character.Money += reward.Count;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:灵石 +{Count}", characterId, reward.Count);
@ -212,9 +246,9 @@ namespace Build_God_Api.Services
break;
}
}
}
await _db.Updateable(character).ExecuteCommandAsync();
}
dailyMission.Status = DailyMissionStatus.Claimed;
await _db.Updateable(dailyMission).ExecuteCommandAsync();
@ -275,16 +309,31 @@ namespace Build_God_Api.Services
}
var random = new Random();
var filteredMissions = availableMissions
.Where(x => random.Next(100) < (int)x.ObtainPercentage)
.ToList();
var selectedMissions = filteredMissions
.OrderBy(x => random.Next())
var normalMissions = availableMissions
.Where(x => x.Difficulty == MissionDifficulty.Normal)
//.Where(x => random.Next(100) < (int)x.ObtainPercentage)
//.OrderBy(x => random.Next())
.Take(DailyMissionCount)
.ToList();
foreach (var mission in selectedMissions)
var hardMissions = availableMissions
.Where(x => x.Difficulty == MissionDifficulty.Hard)
//.Where(x => random.Next(100) < (int)x.ObtainPercentage)
//.OrderBy(x => random.Next())
.Take(HardMissionCount)
.ToList();
var purgatoryMissions = availableMissions
.Where(x => x.Difficulty == MissionDifficulty.Purgatory)
//.Where(x => random.Next(100) < (int)x.ObtainPercentage)
//.OrderBy(x => random.Next())
.Take(PurgatoryMissionCount)
.ToList();
var allSelectedMissions = normalMissions.Concat(hardMissions).Concat(purgatoryMissions).ToList();
foreach (var mission in allSelectedMissions)
{
var dailyMission = new CharacterDailyMission
{
@ -299,7 +348,8 @@ namespace Build_God_Api.Services
await _db.Insertable(dailyMission).ExecuteCommandAsync();
}
_logger.LogInformation("为角色 {CharacterId} 分配了 {Count} 个每日任务", characterId, selectedMissions.Count);
_logger.LogInformation("为角色 {CharacterId} 分配了 {Count} 个每日任务(普通:{NormalCount}, 困难:{HardCount}, 炼狱:{PurgatoryCount})",
characterId, allSelectedMissions.Count, normalMissions.Count, hardMissions.Count, purgatoryMissions.Count);
}
}
}

6
Build_God_Game/src/api/dailyMission.ts

@ -9,8 +9,7 @@ export type DailyMissionStatus = typeof DailyMissionStatus[keyof typeof DailyMis
export const RewardType = {
Pill: 1,
Equipment: 2,
Exp: 3,
Money: 4
Money: 3
} as const
export type RewardType = typeof RewardType[keyof typeof RewardType]
export interface MissionReward {
@ -33,6 +32,9 @@ export interface DailyMission {
endTime?: string
isFromYesterday: boolean
assignedDate: string
expReward: number
todayClaimedCount: number
todayTotalCount: number
rewards: MissionReward[]
}
export const dailyMissionApi = {

9
Build_God_Game/src/api/index.ts

@ -30,7 +30,14 @@ instance.interceptors.response.use(
if (error.code === 'ERR_NETWORK' || error.code === 'ECONNABORTED' || !error.response) {
window.location.href = '/404'
}
return Promise.reject(error.response?.data || error.message)
const responseData = error.response?.data
if (typeof responseData === 'string') {
return Promise.reject({ data: responseData, message: responseData })
}
if (responseData?.message) {
return Promise.reject({ data: responseData.message, message: responseData.message })
}
return Promise.reject(error.response?.data || { data: error.message, message: error.message })
}
)

2
Build_God_Game/src/components/StarBorder/StarBorder.vue

@ -30,7 +30,7 @@
</template>
<script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue';
import { computed, useAttrs } from 'vue';
interface StarBorderProps {
as?: string;

45
Build_God_Game/src/views/DailyMissionView.vue

@ -7,6 +7,7 @@ import Particles from '@/components/Particles/Particles.vue'
const router = useRouter()
const missions = ref<DailyMission[]>([])
const todayStats = ref({ claimed: 0, total: 0 })
const loading = ref(false)
const message = ref('')
const showMessage = ref(false)
@ -51,8 +52,6 @@ const getStatusClass = (status: DailyMissionStatus) => {
const getRewardIcon = (type: RewardType) => {
switch (type) {
case RewardType.Exp:
return '✨'
case RewardType.Money:
return '💰'
case RewardType.Pill:
@ -100,6 +99,12 @@ const loadMissions = async () => {
try {
const data = await dailyMissionApi.getList()
missions.value = data
if (data.length > 0) {
todayStats.value = {
claimed: data[0].todayClaimedCount,
total: data[0].todayTotalCount
}
}
updateRemainingTimes()
} catch (error: any) {
showMsg(error?.message || '加载任务失败', 'error')
@ -114,7 +119,8 @@ const handleAccept = async (mission: DailyMission) => {
showMsg('接取任务成功')
await loadMissions()
} catch (error: any) {
showMsg(error?.message || '接取任务失败', 'error')
const errorMsg = error?.data?.message || error?.data || error?.message || '接取任务失败'
showMsg(errorMsg, 'error')
}
}
@ -124,7 +130,8 @@ const handleClaim = async (mission: DailyMission) => {
showMsg('领取奖励成功!')
await loadMissions()
} catch (error: any) {
showMsg(error?.message || '领取奖励失败', 'error')
const errorMsg = error?.data?.message || error?.data || error?.message || '领取奖励失败'
showMsg(errorMsg, 'error')
}
}
@ -138,8 +145,8 @@ onMounted(() => {
})
const activeMissions = computed(() => missions.value.filter(m => m.status !== DailyMissionStatus.Claimed))
const yesterdayMissions = computed(() => activeMissions.value.filter(m => m.isFromYesterday))
const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromYesterday))
const yesterdayMissions = computed(() => missions.value.filter(m => m.isFromYesterday))
const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterday))
</script>
<template>
@ -191,6 +198,7 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
</div>
<div class="mission-rewards">
<span class="rewards-label">奖励:</span>
<span class="reward-item exp-reward"> 经验 ×{{ mission.expReward }}</span>
<span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item">
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count
}}
@ -215,11 +223,10 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
<div v-if="todayMissions.length > 0" class="mission-section">
<div class="section-header">
<span class="section-title">今日任务</span>
<span class="section-badge">{{todayMissions.filter(m => m.status === DailyMissionStatus.Claimed).length}}/{{
todayMissions.length }}</span>
<span class="section-badge">{{ todayStats.claimed }}/{{ todayStats.total }}</span>
</div>
<div class="mission-list">
<div v-for="mission in todayMissions" :key="mission.id" class="mission-card">
<div v-for="mission in todayMissions" :key="mission.id" class="mission-card" :class="{ claimed: mission.status === DailyMissionStatus.Claimed }">
<div class="mission-header">
<span class="mission-name">{{ mission.missionTitle }}</span>
<span class="mission-status" :class="getStatusClass(mission.status)">
@ -240,6 +247,7 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
</div>
<div class="mission-rewards">
<span class="rewards-label">奖励:</span>
<span class="reward-item exp-reward"> 经验 ×{{ mission.expReward }}</span>
<span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item">
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count
}}
@ -421,6 +429,19 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
box-shadow: 0 0 20px rgba(255, 68, 68, 0.1);
}
.mission-card.claimed {
opacity: 0.5;
}
.mission-card.claimed .mission-name {
text-decoration: line-through;
color: #666666;
}
.mission-card.claimed .mission-desc {
text-decoration: line-through;
}
.mission-header {
display: flex;
justify-content: space-between;
@ -493,6 +514,12 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
font-size: 0.75rem;
}
.exp-reward {
background: rgba(255, 215, 0, 0.1);
border-color: rgba(255, 215, 0, 0.3);
color: #ffd700;
}
.mission-actions {
display: flex;
gap: 10px;

5
Build_God_Game/src/views/LoginView.vue

@ -62,13 +62,14 @@ const handleLogin = async () => {
<form class="login-form" @submit.prevent="handleLogin">
<div class="form-group">
<label class="form-label">用户名</label>
<input v-model="username" type="text" class="form-input" placeholder="请输入用户名" autocomplete="username" />
<input v-model="username" type="text" class="form-input" placeholder="请输入用户名" autocomplete="username"
@keyup.enter="handleLogin" />
</div>
<div class="form-group">
<label class="form-label">密码</label>
<input v-model="password" type="password" class="form-input" placeholder="请输入密码"
autocomplete="current-password" />
autocomplete="current-password" @keyup.enter="handleLogin" />
</div>
<div v-if="errorMsg" class="error-message">

BIN
Build_God_Game/模拟导图.xls

Binary file not shown.
Loading…
Cancel
Save