文字游戏
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.
 
 
 
 
 

357 lines
14 KiB

using Build_God_Api.DB;
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
{
public interface ICharacterService
{
public Task<Character?> GetCharacterByAccountId(int accountId);
public Task<List<Character>> GetCharactersByAccountId(int accountId);
public Task<List<CharacterDto>> GetCharacterListWithDetails(int accountId);
public Task<List<Character>> GetAllCharacters();
public Task<Character?> GetCharacterById(int characterId);
public Task<bool> ExistsByNameAsync(string name);
public Task<bool> RegisterCharacter(CharacterRegisterDto character);
public Task<bool> DeleteCharacter(int characterId);
/// <summary>
/// 选择角色(更新最后登录时间)
/// </summary>
public Task<bool> SelectCharacter(int characterId);
/// <summary>
/// 突破
/// </summary>
/// <returns></returns>
public Task<bool> Breakthrough(int characterId);
/// <summary>
/// 开始打坐
/// </summary>
public Task<bool> StartTraining(int characterId);
/// <summary>
/// 停止打坐并结算经验
/// </summary>
/// <returns>获得的经验值</returns>
public Task<decimal> StopTraining(int characterId);
}
public class CharacterService(ISqlSugarClient db,
ICurrentUserService currentUserService,
ICharacterAttributeCalculateService calculateService,
IBagService bagService,
IDailyMissionService dailyMissionService) : ICharacterService
{
private readonly ISqlSugarClient db = db;
private readonly ICurrentUserService currentUserService = currentUserService;
private readonly ICharacterAttributeCalculateService calculateService = calculateService;
private readonly IBagService bagService = bagService;
private readonly IDailyMissionService dailyMissionService = dailyMissionService;
public async Task<bool> Breakthrough(int characterId)
{
var character = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId) ?? throw new Exception("角色不存在");
var currentLevel = await db.Queryable<Level>().FirstAsync(x => x.LevelId == character.LevelId);
var nextLevelId = currentLevel?.NextLevelId ?? 1;
var nextLevel = await db.Queryable<Level>().FirstAsync(x => x.LevelId == nextLevelId) ?? throw new Exception("你暂时无敌了,没有等级可以突破了");
if (character.CurrentExp < nextLevel.CurrentLevelMinExp)
{
throw new Exception($"距离可以突破你还差 {nextLevel.CurrentLevelMinExp - character.CurrentExp}");
}
if (currentLevel?.BaseBreakthroughRate <= 0)
{
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;
character.BreakthroughRate = nextLevel.BaseBreakthroughRate;
await calculateService.RecalculateMaxHPAsync(character);
return true;
}
decimal randomValue = GenerateSecureRandomDecimal(0, 100, 1);
bool isSuccess = randomValue < character.BreakthroughRate;
if (isSuccess)
{
character.LevelId = nextLevelId;
character.BreakthroughRate = nextLevel.BaseBreakthroughRate;
await calculateService.RecalculateMaxHPAsync(character);
return true;
}
else
{
character.BreakthroughRate += currentLevel!.FailIncrement;
if (character.BreakthroughRate > 100)
{
character.BreakthroughRate = 100;
}
}
await db.Updateable(character).ExecuteCommandAsync();
return false;
}
public async Task<bool> StartTraining(int characterId)
{
var character = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在");
if (character.TrainingOn.HasValue)
{
throw new Exception("你已经在打坐了");
}
character.TrainingOn = DateTime.Now;
await db.Updateable(character).ExecuteCommandAsync();
return true;
}
public async Task<decimal> StopTraining(int characterId)
{
var character = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId)
?? throw new Exception("角色不存在");
if (!character.TrainingOn.HasValue)
{
throw new Exception("你还没有开始打坐");
}
var duration = DateTime.Now - character.TrainingOn.Value;
var seconds = (decimal)duration.TotalSeconds;
// exp = 0.1 * seconds * level * level * (1 + buff)
var expGained = 0.01m * seconds * character.LevelId * character.LevelId * (1 + 0);
character.CurrentExp += Math.Round(expGained);
character.TrainingOn = null;
await db.Updateable(character).ExecuteCommandAsync();
return expGained;
}
private readonly RandomNumberGenerator _rng = RandomNumberGenerator.Create();
/// <summary>
/// 生成安全的高精度随机小数(生产环境用)
/// </summary>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
/// <param name="decimalPlaces">小数位数</param>
/// <returns>随机数</returns>
private decimal GenerateSecureRandomDecimal(decimal min, decimal max, int decimalPlaces)
{
byte[] randomBytes = new byte[4];
_rng.GetBytes(randomBytes);
int randomInt = BitConverter.ToInt32(randomBytes, 0);
decimal randomDecimal = (decimal)Math.Abs(randomInt) / int.MaxValue;
decimal result = min + (randomDecimal * (max - min));
return Math.Round(result, decimalPlaces);
}
public async Task<bool> DeleteCharacter(int characterId)
{
var item = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId && x.AccountId == currentUserService.UserId) ?? throw new Exception("你没有权力这么做");
item.isLocked = true;
await db.Updateable(item).ExecuteCommandAsync();
return true;
}
public async Task<bool> SelectCharacter(int characterId)
{
var character = await db.Queryable<Character>().FirstAsync(x => x.Id == characterId && x.AccountId == currentUserService.UserId && x.isLocked == false)
?? throw new Exception("角色不存在");
character.LastLogin = DateTime.Now;
await db.Updateable(character).ExecuteCommandAsync();
await dailyMissionService.AssignDailyMissionsIfNeeded(characterId);
return true;
}
public async Task<bool> ExistsByNameAsync(string name)
{
return await db.Queryable<Character>().AnyAsync(x => x.Name == name);
}
public async Task<Character?> GetCharacterByAccountId(int accountId)
{
return await db.Queryable<Character>().FirstAsync(x => x.AccountId == accountId && x.isLocked == false);
}
public async Task<List<Character>> GetCharactersByAccountId(int accountId)
{
return await db.Queryable<Character>()
.Where(x => x.AccountId == accountId && x.isLocked == false)
.OrderBy(x => x.CreatedOn)
.ToListAsync();
}
/// <summary>
/// 获取角色列表(包含境界和灵根信息)
/// </summary>
public async Task<List<CharacterDto>> GetCharacterListWithDetails(int accountId)
{
var characters = await db.Queryable<Character>()
.Where(x => x.AccountId == accountId && x.isLocked == false)
.OrderBy(x => x.CreatedOn)
.ToListAsync();
var result = new List<CharacterDto>();
foreach (var c in characters)
{
var level = await db.Queryable<Level>().FirstAsync(x => x.LevelId == c.LevelId);
Level? nextLevel = null;
if (level?.NextLevelId.HasValue == true)
{
nextLevel = await db.Queryable<Level>().FirstAsync(x => x.LevelId == level.NextLevelId);
}
Profession? profession = null;
if (c.ProfessionId.HasValue && c.ProfessionId > 0)
{
profession = await db.Queryable<Profession>().FirstAsync(x => x.Id == c.ProfessionId);
}
bool canBreakthrough = false;
if (nextLevel != null && c.CurrentExp >= nextLevel.CurrentLevelMinExp)
{
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;
}
var attrs = await calculateService.CalculateAttributesAsync(c);
result.Add(new CharacterDto
{
Id = c.Id,
Name = c.Name,
LevelName = level?.Name ?? "未知",
LevelId = c.LevelId,
MaxHP = attrs.MaxHP,
CurrentHP = c.CurrentHP,
Attack = attrs.Attack,
Defend = attrs.Defend,
CriticalRate = attrs.CriticalRate,
ProfessionName = profession?.Name,
Money = c.Money,
CurrentExp = c.CurrentExp,
TrainingOn = c.TrainingOn,
NextLevelName = nextLevel?.Name,
NextLevelMinExp = nextLevel?.CurrentLevelMinExp,
BreakthroughRate = c.BreakthroughRate,
CanBreakthrough = canBreakthrough,
NextLevelRequiredPillId = nextLevelRequiredPillId,
NextLevelRequiredPillName = nextLevelRequiredPillName,
NextLevelRequiredPillQuantity = nextLevelRequiredPillQuantity,
TrainingExpRate = 0.01m,
LastLogin = c.LastLogin,
CreatedOn = c.CreatedOn
});
}
return result;
}
public async Task<List<Character>> GetAllCharacters()
{
return await db.Queryable<Character>().Where(x => x.isLocked == false).ToListAsync();
}
public async Task<Character?> GetCharacterById(int characterId)
{
return await db.Queryable<Character>().FirstAsync(x => x.Id == characterId && x.isLocked == false);
}
public async Task<bool> RegisterCharacter(CharacterRegisterDto character)
{
var bo = await ExistsByNameAsync(character.Name);
if (bo)
{
throw new Exception("已经有道友叫这个名字了,请想另外一个吊炸天的名字吧");
}
// 限制每个账号最多3个角色
var existingCount = await db.Queryable<Character>()
.CountAsync(x => x.AccountId == currentUserService.UserId && x.isLocked == false);
if (existingCount >= 3)
{
throw new Exception("每个账号最多只能创建3个角色");
}
Character newOne = new()
{
Name = character.Name,
AccountId = currentUserService.UserId,
CurrentExp = 0,
LevelId = 1,
Money = 0,
CurrentHP = 100,
ProfessionId = character.ProfessionId,
SpiritFieldId = 0,
BreakthroughRate = 0,
LastLogin = DateTime.Now
};
await db.Insertable(newOne).ExecuteCommandAsync();
return true;
}
}
/// <summary>
/// 注册角色
/// </summary>
public class CharacterRegisterDto
{
public string Name { get; set; } = string.Empty;
public int ProfessionId { get; set; }
}
}