Browse Source

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

master
秦汉 1 day ago
parent
commit
99a442abc2
  1. 14
      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

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

@ -698,14 +698,17 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
<el-button @click="closeProgressDialog" :icon="Close" circle /> <el-button @click="closeProgressDialog" :icon="Close" circle />
</div> </div>
<el-form-item label="目标类型"> <el-form-item label="目标类型">
<el-select v-model="progressFormData.targetType" @change="progressTargetTypeChange" :disabled="isEditingProgress"> <el-select v-model="progressFormData.targetType" @change="progressTargetTypeChange"
<el-option v-for="(value, index) in progressTargetTypes" :key="index" :value="value.id" :label="value.description"> :disabled="isEditingProgress">
<el-option v-for="(value, index) in progressTargetTypes" :key="index" :value="value.id"
:label="value.description">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="progressFormData.targetType === 1 || progressFormData.targetType === 4" label="物品"> <el-form-item v-if="progressFormData.targetType === 1 || progressFormData.targetType === 4" label="物品">
<el-select v-model="progressFormData.targetItemId" @change="onProgressItemChange"> <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-select>
</el-form-item> </el-form-item>
<el-form-item label="目标数量"> <el-form-item label="目标数量">
@ -733,14 +736,15 @@ const fetchEquipments = async (): Promise<EquipmentTemplate[]> => {
</el-form-item> </el-form-item>
<el-form-item label="分类"> <el-form-item label="分类">
<el-select v-model="formData.type"> <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 }} {{ value.description }}
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="难度"> <el-form-item label="难度">
<el-select v-model="formData.difficulty"> <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-option>
</el-select> </el-select>
</el-form-item> </el-form-item>

14
Build_God_Api/Build_God_Api/DB/Mission.cs

@ -95,10 +95,7 @@ namespace Build_God_Api.DB
MainStory = 1, MainStory = 1,
[Description("日常任务")] [Description("日常任务")]
DailyTask = 2, DailyTask = 2
[Description("采集任务")]
BranchTask = 3
} }
/// <summary> /// <summary>
@ -106,16 +103,13 @@ namespace Build_God_Api.DB
/// </summary> /// </summary>
public enum MissionDifficulty public enum MissionDifficulty
{ {
[Description("简单")]
Easy = 1,
[Description("普通")] [Description("普通")]
Normal = 2, Normal = 1,
[Description("困难")] [Description("困难")]
Hard = 3, Hard = 2,
[Description("炼狱")] [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, Pill = 1,
[Description("装备")] [Description("装备")]
Equipment = 2, Equipment = 2,
[Description("经验")]
Exp = 3,
[Description("灵石")] [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 DateTime? EndTime { get; set; }
public bool IsFromYesterday { get; set; } public bool IsFromYesterday { get; set; }
public DateTime AssignedDate { 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(); public List<MissionRewardDto> Rewards { get; set; } = new();
} }
@ -69,9 +72,23 @@ namespace Build_God_Api.Services
private readonly ILogger<DailyMissionService> _logger = logger; private readonly ILogger<DailyMissionService> _logger = logger;
private const int DailyMissionCount = 5; 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 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() private DateTime GetBeijingTime()
{ {
return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, BeijingTimeZone); return TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, BeijingTimeZone);
@ -88,12 +105,20 @@ namespace Build_God_Api.Services
var yesterday = today.AddDays(-1); var yesterday = today.AddDays(-1);
var dailyMissions = await _db.Queryable<CharacterDailyMission>() 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) .Where(x => x.AssignedDate == today || x.AssignedDate == yesterday)
.OrderByDescending(x => x.AssignedDate) .OrderByDescending(x => x.AssignedDate)
.OrderByDescending(x => x.IsFromYesterday) .OrderByDescending(x => x.IsFromYesterday)
.ToListAsync(); .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>(); var result = new List<DailyMissionDto>();
foreach (var dm in dailyMissions) foreach (var dm in dailyMissions)
@ -117,7 +142,10 @@ namespace Build_God_Api.Services
StartTime = dm.StartTime, StartTime = dm.StartTime,
EndTime = dm.EndTime, EndTime = dm.EndTime,
IsFromYesterday = dm.IsFromYesterday, IsFromYesterday = dm.IsFromYesterday,
AssignedDate = dm.AssignedDate AssignedDate = dm.AssignedDate,
ExpReward = CalculateExpReward(mission.Difficulty, character.LevelId),
TodayClaimedCount = todayClaimedCount,
TodayTotalCount = todayTotalCount
}; };
if (mission.Rewards != null) if (mission.Rewards != null)
@ -143,6 +171,13 @@ namespace Build_God_Api.Services
public async Task<bool> AcceptMission(int characterId, int dailyMissionId) 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>() var dailyMission = await _db.Queryable<CharacterDailyMission>()
.FirstAsync(x => x.Id == dailyMissionId && x.CharacterId == characterId) .FirstAsync(x => x.Id == dailyMissionId && x.CharacterId == characterId)
?? throw new Exception("任务不存在"); ?? throw new Exception("任务不存在");
@ -184,17 +219,16 @@ namespace Build_God_Api.Services
.FirstAsync(x => x.Id == characterId) .FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在"); ?? throw new Exception("角色不存在");
var expReward = CalculateExpReward(mission.Difficulty, character.LevelId);
character.CurrentExp += expReward;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:经验 +{ExpReward}", characterId, expReward);
if (mission.Rewards != null) if (mission.Rewards != null)
{ {
foreach (var reward in mission.Rewards) foreach (var reward in mission.Rewards)
{ {
switch (reward.RewardType) switch (reward.RewardType)
{ {
case RewardType.Exp:
character.CurrentExp += reward.Count;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:经验 +{Count}", characterId, reward.Count);
break;
case RewardType.Money: case RewardType.Money:
character.Money += reward.Count; character.Money += reward.Count;
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:灵石 +{Count}", characterId, reward.Count); _logger.LogInformation("角色 {CharacterId} 领取任务奖励:灵石 +{Count}", characterId, reward.Count);
@ -212,9 +246,9 @@ namespace Build_God_Api.Services
break; break;
} }
} }
}
await _db.Updateable(character).ExecuteCommandAsync(); await _db.Updateable(character).ExecuteCommandAsync();
}
dailyMission.Status = DailyMissionStatus.Claimed; dailyMission.Status = DailyMissionStatus.Claimed;
await _db.Updateable(dailyMission).ExecuteCommandAsync(); await _db.Updateable(dailyMission).ExecuteCommandAsync();
@ -275,16 +309,31 @@ namespace Build_God_Api.Services
} }
var random = new Random(); var random = new Random();
var filteredMissions = availableMissions
.Where(x => random.Next(100) < (int)x.ObtainPercentage)
.ToList();
var selectedMissions = filteredMissions var normalMissions = availableMissions
.OrderBy(x => random.Next()) .Where(x => x.Difficulty == MissionDifficulty.Normal)
//.Where(x => random.Next(100) < (int)x.ObtainPercentage)
//.OrderBy(x => random.Next())
.Take(DailyMissionCount) .Take(DailyMissionCount)
.ToList(); .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 var dailyMission = new CharacterDailyMission
{ {
@ -299,7 +348,8 @@ namespace Build_God_Api.Services
await _db.Insertable(dailyMission).ExecuteCommandAsync(); 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 = { export const RewardType = {
Pill: 1, Pill: 1,
Equipment: 2, Equipment: 2,
Exp: 3, Money: 3
Money: 4
} as const } as const
export type RewardType = typeof RewardType[keyof typeof RewardType] export type RewardType = typeof RewardType[keyof typeof RewardType]
export interface MissionReward { export interface MissionReward {
@ -33,6 +32,9 @@ export interface DailyMission {
endTime?: string endTime?: string
isFromYesterday: boolean isFromYesterday: boolean
assignedDate: string assignedDate: string
expReward: number
todayClaimedCount: number
todayTotalCount: number
rewards: MissionReward[] rewards: MissionReward[]
} }
export const dailyMissionApi = { 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) { if (error.code === 'ERR_NETWORK' || error.code === 'ECONNABORTED' || !error.response) {
window.location.href = '/404' 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> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, defineProps, useAttrs } from 'vue'; import { computed, useAttrs } from 'vue';
interface StarBorderProps { interface StarBorderProps {
as?: string; 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 router = useRouter()
const missions = ref<DailyMission[]>([]) const missions = ref<DailyMission[]>([])
const todayStats = ref({ claimed: 0, total: 0 })
const loading = ref(false) const loading = ref(false)
const message = ref('') const message = ref('')
const showMessage = ref(false) const showMessage = ref(false)
@ -51,8 +52,6 @@ const getStatusClass = (status: DailyMissionStatus) => {
const getRewardIcon = (type: RewardType) => { const getRewardIcon = (type: RewardType) => {
switch (type) { switch (type) {
case RewardType.Exp:
return '✨'
case RewardType.Money: case RewardType.Money:
return '💰' return '💰'
case RewardType.Pill: case RewardType.Pill:
@ -100,6 +99,12 @@ const loadMissions = async () => {
try { try {
const data = await dailyMissionApi.getList() const data = await dailyMissionApi.getList()
missions.value = data missions.value = data
if (data.length > 0) {
todayStats.value = {
claimed: data[0].todayClaimedCount,
total: data[0].todayTotalCount
}
}
updateRemainingTimes() updateRemainingTimes()
} catch (error: any) { } catch (error: any) {
showMsg(error?.message || '加载任务失败', 'error') showMsg(error?.message || '加载任务失败', 'error')
@ -114,7 +119,8 @@ const handleAccept = async (mission: DailyMission) => {
showMsg('接取任务成功') showMsg('接取任务成功')
await loadMissions() await loadMissions()
} catch (error: any) { } 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('领取奖励成功!') showMsg('领取奖励成功!')
await loadMissions() await loadMissions()
} catch (error: any) { } 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 activeMissions = computed(() => missions.value.filter(m => m.status !== DailyMissionStatus.Claimed))
const yesterdayMissions = computed(() => activeMissions.value.filter(m => m.isFromYesterday)) const yesterdayMissions = computed(() => missions.value.filter(m => m.isFromYesterday))
const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromYesterday)) const todayMissions = computed(() => missions.value.filter(m => !m.isFromYesterday))
</script> </script>
<template> <template>
@ -191,6 +198,7 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
</div> </div>
<div class="mission-rewards"> <div class="mission-rewards">
<span class="rewards-label">奖励:</span> <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"> <span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item">
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count {{ 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 v-if="todayMissions.length > 0" class="mission-section">
<div class="section-header"> <div class="section-header">
<span class="section-title">今日任务</span> <span class="section-title">今日任务</span>
<span class="section-badge">{{todayMissions.filter(m => m.status === DailyMissionStatus.Claimed).length}}/{{ <span class="section-badge">{{ todayStats.claimed }}/{{ todayStats.total }}</span>
todayMissions.length }}</span>
</div> </div>
<div class="mission-list"> <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"> <div class="mission-header">
<span class="mission-name">{{ mission.missionTitle }}</span> <span class="mission-name">{{ mission.missionTitle }}</span>
<span class="mission-status" :class="getStatusClass(mission.status)"> <span class="mission-status" :class="getStatusClass(mission.status)">
@ -240,6 +247,7 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
</div> </div>
<div class="mission-rewards"> <div class="mission-rewards">
<span class="rewards-label">奖励:</span> <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"> <span v-for="(reward, idx) in mission.rewards" :key="idx" class="reward-item">
{{ getRewardIcon(reward.rewardType) }}{{ reward.itemName || reward.rewardTypeName }}×{{ reward.count {{ 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); 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 { .mission-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -493,6 +514,12 @@ const todayMissions = computed(() => activeMissions.value.filter(m => !m.isFromY
font-size: 0.75rem; 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 { .mission-actions {
display: flex; display: flex;
gap: 10px; gap: 10px;

5
Build_God_Game/src/views/LoginView.vue

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

BIN
Build_God_Game/模拟导图.xls

Binary file not shown.
Loading…
Cancel
Save