You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
14 KiB
355 lines
14 KiB
using Build_God_Api.DB;
|
|
using Build_God_Api.Dto;
|
|
using SqlSugar;
|
|
|
|
namespace Build_God_Api.Services
|
|
{
|
|
public interface IDailyMissionService
|
|
{
|
|
/// <summary>
|
|
/// 获取角色所有未领取的任务(包括昨日的)
|
|
/// </summary>
|
|
Task<List<DailyMissionDto>> GetCharacterDailyMissions(int characterId);
|
|
|
|
/// <summary>
|
|
/// 接取任务
|
|
/// </summary>
|
|
Task<bool> AcceptMission(int characterId, int dailyMissionId);
|
|
|
|
/// <summary>
|
|
/// 领取奖励
|
|
/// </summary>
|
|
Task<bool> ClaimReward(int characterId, int dailyMissionId);
|
|
|
|
/// <summary>
|
|
/// 检查并完成所有到期任务
|
|
/// </summary>
|
|
Task CheckAndCompleteExpiredMissions();
|
|
|
|
/// <summary>
|
|
/// 为角色分配每日任务(登录时调用)
|
|
/// </summary>
|
|
Task AssignDailyMissionsIfNeeded(int characterId);
|
|
}
|
|
|
|
public class DailyMissionDto
|
|
{
|
|
public int Id { get; set; }
|
|
public int CharacterId { get; set; }
|
|
public int MissionId { get; set; }
|
|
public string MissionName { get; set; } = string.Empty;
|
|
public string MissionTitle { get; set; } = string.Empty;
|
|
public string MissionDescription { get; set; } = string.Empty;
|
|
public int SpendTimeMinutes { get; set; }
|
|
public DailyMissionStatus Status { get; set; }
|
|
public DateTime? StartTime { get; set; }
|
|
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();
|
|
}
|
|
|
|
public class MissionRewardDto
|
|
{
|
|
public RewardType RewardType { get; set; }
|
|
public string RewardTypeName { get; set; } = string.Empty;
|
|
public int ItemId { get; set; }
|
|
public string ItemName { get; set; } = string.Empty;
|
|
public int Count { get; set; }
|
|
}
|
|
|
|
public class DailyMissionService(
|
|
ISqlSugarClient db,
|
|
IBagService bagService,
|
|
ILogger<DailyMissionService> logger
|
|
) : IDailyMissionService
|
|
{
|
|
private readonly ISqlSugarClient _db = db;
|
|
private readonly IBagService _bagService = bagService;
|
|
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);
|
|
}
|
|
|
|
private DateTime GetBeijingDate()
|
|
{
|
|
return GetBeijingTime().Date;
|
|
}
|
|
|
|
public async Task<List<DailyMissionDto>> GetCharacterDailyMissions(int characterId)
|
|
{
|
|
var today = GetBeijingDate();
|
|
var yesterday = today.AddDays(-1);
|
|
|
|
var dailyMissions = await _db.Queryable<CharacterDailyMission>()
|
|
.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)
|
|
{
|
|
var mission = await _db.Queryable<Mission>()
|
|
.Includes(x => x.Rewards)
|
|
.FirstAsync(x => x.Id == dm.MissionId);
|
|
|
|
if (mission == null) continue;
|
|
|
|
var dto = new DailyMissionDto
|
|
{
|
|
Id = dm.Id,
|
|
CharacterId = dm.CharacterId,
|
|
MissionId = dm.MissionId,
|
|
MissionName = mission.Name,
|
|
MissionTitle = mission.Title,
|
|
MissionDescription = mission.Description,
|
|
SpendTimeMinutes = mission.SpendTimeMinutes,
|
|
Status = dm.Status,
|
|
StartTime = dm.StartTime,
|
|
EndTime = dm.EndTime,
|
|
IsFromYesterday = dm.IsFromYesterday,
|
|
AssignedDate = dm.AssignedDate,
|
|
ExpReward = CalculateExpReward(mission.Difficulty, character.LevelId),
|
|
TodayClaimedCount = todayClaimedCount,
|
|
TodayTotalCount = todayTotalCount
|
|
};
|
|
|
|
if (mission.Rewards != null)
|
|
{
|
|
foreach (var reward in mission.Rewards)
|
|
{
|
|
dto.Rewards.Add(new MissionRewardDto
|
|
{
|
|
RewardType = reward.RewardType,
|
|
RewardTypeName = reward.RewardType.ToString(),
|
|
ItemId = reward.ItemId,
|
|
ItemName = reward.ItemName ?? string.Empty,
|
|
Count = reward.Count
|
|
});
|
|
}
|
|
}
|
|
|
|
result.Add(dto);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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("任务不存在");
|
|
|
|
if (dailyMission.Status != DailyMissionStatus.Pending)
|
|
throw new Exception("任务不是待接取状态");
|
|
|
|
var mission = await _db.Queryable<Mission>()
|
|
.FirstAsync(x => x.Id == dailyMission.MissionId)
|
|
?? throw new Exception("任务不存在");
|
|
|
|
var now = GetBeijingTime();
|
|
dailyMission.Status = DailyMissionStatus.InProgress;
|
|
dailyMission.StartTime = now;
|
|
dailyMission.EndTime = now.AddMinutes(mission.SpendTimeMinutes);
|
|
|
|
await _db.Updateable(dailyMission).ExecuteCommandAsync();
|
|
|
|
_logger.LogInformation("角色 {CharacterId} 接取了任务 {MissionId}", characterId, dailyMission.MissionId);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task<bool> ClaimReward(int characterId, int dailyMissionId)
|
|
{
|
|
var dailyMission = await _db.Queryable<CharacterDailyMission>()
|
|
.FirstAsync(x => x.Id == dailyMissionId && x.CharacterId == characterId)
|
|
?? throw new Exception("任务不存在");
|
|
|
|
if (dailyMission.Status != DailyMissionStatus.Completed)
|
|
throw new Exception("任务未完成,无法领取奖励");
|
|
|
|
var mission = await _db.Queryable<Mission>()
|
|
.Includes(x => x.Rewards)
|
|
.FirstAsync(x => x.Id == dailyMission.MissionId)
|
|
?? throw new Exception("任务不存在");
|
|
|
|
var character = await _db.Queryable<Character>()
|
|
.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.Money:
|
|
character.Money += reward.Count;
|
|
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:灵石 +{Count}", characterId, reward.Count);
|
|
break;
|
|
|
|
case RewardType.Pill:
|
|
case RewardType.Equipment:
|
|
var characterBag = await _db.Queryable<CharacterBag>()
|
|
.FirstAsync(x => x.CharacterId == characterId);
|
|
if (characterBag != null)
|
|
{
|
|
await _bagService.AddItemToBag(characterBag.Id, (int)reward.RewardType, reward.ItemId, reward.Count);
|
|
_logger.LogInformation("角色 {CharacterId} 领取任务奖励:{ItemName} x{Count}", characterId, reward.ItemName, reward.Count);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
await _db.Updateable(character).ExecuteCommandAsync();
|
|
|
|
dailyMission.Status = DailyMissionStatus.Claimed;
|
|
await _db.Updateable(dailyMission).ExecuteCommandAsync();
|
|
|
|
_logger.LogInformation("角色 {CharacterId} 领取了任务 {MissionId} 的奖励", characterId, mission.Id);
|
|
|
|
return true;
|
|
}
|
|
|
|
public async Task CheckAndCompleteExpiredMissions()
|
|
{
|
|
var now = GetBeijingTime();
|
|
|
|
var expiredMissions = await _db.Queryable<CharacterDailyMission>()
|
|
.Where(x => x.Status == DailyMissionStatus.InProgress && x.EndTime <= now)
|
|
.ToListAsync();
|
|
|
|
foreach (var mission in expiredMissions)
|
|
{
|
|
mission.Status = DailyMissionStatus.Completed;
|
|
await _db.Updateable(mission).ExecuteCommandAsync();
|
|
|
|
_logger.LogInformation("任务 {MissionId} 已自动完成,等待角色 {CharacterId} 领取", mission.MissionId, mission.CharacterId);
|
|
}
|
|
}
|
|
|
|
public async Task AssignDailyMissionsIfNeeded(int characterId)
|
|
{
|
|
var today = GetBeijingDate();
|
|
|
|
var character = await _db.Queryable<Character>()
|
|
.FirstAsync(x => x.Id == characterId)
|
|
?? throw new Exception("角色不存在");
|
|
|
|
var existingMissions = await _db.Queryable<CharacterDailyMission>()
|
|
.Where(x => x.CharacterId == characterId && x.AssignedDate == today)
|
|
.ToListAsync();
|
|
|
|
var hasInProgressOrPending = existingMissions.Any(x =>
|
|
x.Status == DailyMissionStatus.InProgress || x.Status == DailyMissionStatus.Pending);
|
|
|
|
if (hasInProgressOrPending)
|
|
{
|
|
_logger.LogInformation("角色 {CharacterId} 今日已有任务,无需分配", characterId);
|
|
return;
|
|
}
|
|
|
|
var availableMissions = await _db.Queryable<Mission>()
|
|
.Where(x => x.Type == MissionType.DailyTask)
|
|
.Where(x => x.IsAvailable == true)
|
|
.Where(x => x.RequiredLevelId <= character.LevelId)
|
|
.ToListAsync();
|
|
|
|
if (!availableMissions.Any())
|
|
{
|
|
_logger.LogWarning("没有可用的日常任务配置");
|
|
return;
|
|
}
|
|
|
|
var random = new Random();
|
|
|
|
var normalMissions = availableMissions
|
|
.Where(x => x.Difficulty == MissionDifficulty.Normal)
|
|
//.Where(x => random.Next(100) < (int)x.ObtainPercentage)
|
|
//.OrderBy(x => random.Next())
|
|
.Take(DailyMissionCount)
|
|
.ToList();
|
|
|
|
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
|
|
{
|
|
CharacterId = characterId,
|
|
MissionId = mission.Id,
|
|
AssignedDate = today,
|
|
Status = DailyMissionStatus.Pending,
|
|
IsFromYesterday = false,
|
|
CreatedOn = DateTime.UtcNow
|
|
};
|
|
|
|
await _db.Insertable(dailyMission).ExecuteCommandAsync();
|
|
}
|
|
|
|
_logger.LogInformation("为角色 {CharacterId} 分配了 {Count} 个每日任务(普通:{NormalCount}, 困难:{HardCount}, 炼狱:{PurgatoryCount})",
|
|
characterId, allSelectedMissions.Count, normalMissions.Count, hardMissions.Count, purgatoryMissions.Count);
|
|
}
|
|
}
|
|
}
|
|
|