forked from PGR/ascnet
315 lines
12 KiB
C#
315 lines
12 KiB
C#
using MongoDB.Bson;
|
|
using MongoDB.Bson.Serialization.Attributes;
|
|
using MongoDB.Driver;
|
|
using AscNet.Table.V2.share.character;
|
|
using AscNet.Table.V2.share.character.skill;
|
|
using AscNet.Common.MsgPack;
|
|
using AscNet.Common.Util;
|
|
using Newtonsoft.Json;
|
|
using AscNet.Table.V2.share.equip;
|
|
using AscNet.Table.V2.share.character.quality;
|
|
|
|
namespace AscNet.Common.Database
|
|
{
|
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
public class Character
|
|
{
|
|
public static readonly List<CharacterLevelUpTemplate> characterLevelUpTemplates;
|
|
public static readonly List<EquipLevelUpTemplate> equipLevelUpTemplates;
|
|
public static readonly IMongoCollection<Character> collection = Common.db.GetCollection<Character>("characters");
|
|
|
|
static Character()
|
|
{
|
|
if (File.Exists("Data/CharacterLevelUpTemplate.json"))
|
|
characterLevelUpTemplates = JsonConvert.DeserializeObject<List<CharacterLevelUpTemplate>>(File.ReadAllText("Data/CharacterLevelUpTemplate.json")) ?? new();
|
|
else
|
|
characterLevelUpTemplates = new();
|
|
if (File.Exists("Data/EquipLevelUpTemplate.json"))
|
|
equipLevelUpTemplates = JsonConvert.DeserializeObject<List<EquipLevelUpTemplate>>(File.ReadAllText("Data/EquipLevelUpTemplate.json")) ?? new();
|
|
else
|
|
equipLevelUpTemplates = new();
|
|
}
|
|
|
|
private uint NextEquipId => Equips.MaxBy(x => x.Id)?.Id + 1 ?? 1;
|
|
|
|
public static Character FromUid(long uid)
|
|
{
|
|
return collection.AsQueryable().FirstOrDefault(x => x.Uid == uid) ?? Create(uid);
|
|
}
|
|
|
|
private static Character Create(long uid)
|
|
{
|
|
Character character = new()
|
|
{
|
|
Uid = uid,
|
|
Characters = new(),
|
|
Equips = new(),
|
|
Fashions = new()
|
|
};
|
|
// Lucia havers by default
|
|
character.AddCharacter(1021001);
|
|
|
|
collection.InsertOne(character);
|
|
|
|
return character;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Don't forget to send Equip, Fashion, and the Character notify after using this!
|
|
/// </summary>
|
|
/// <param name="id"></param>
|
|
/// <exception cref="ServerCodeException"></exception>
|
|
public AddCharacterRet AddCharacter(uint id)
|
|
{
|
|
AddCharacterRet ret = new();
|
|
CharacterTable? character = TableReaderV2.Parse<CharacterTable>().Find(x => x.Id == id);
|
|
CharacterSkillTable? characterSkill = TableReaderV2.Parse<CharacterSkillTable>().Find(x => x.CharacterId == id);
|
|
CharacterQualityTable? characterQuality = TableReaderV2.Parse<CharacterQualityTable>().OrderBy(x => x.Quality).FirstOrDefault(x => x.CharacterId == id);
|
|
|
|
if (character is null || characterSkill is null || characterQuality is null)
|
|
{
|
|
// CharacterManagerGetCharacterDataNotFound
|
|
throw new ServerCodeException("Invalid character id!", 20009021);
|
|
}
|
|
if (Characters.FirstOrDefault(x => x.Id == character.Id) is not null)
|
|
{
|
|
// CharacterManagerCreateCharacterAlreadyExist
|
|
throw new ServerCodeException("Character already obtained!", 20009022);
|
|
}
|
|
|
|
CharacterData characterData = new()
|
|
{
|
|
Id = (uint)character.Id,
|
|
Level = 1,
|
|
Exp = 0,
|
|
Quality = characterQuality.Quality,
|
|
InitQuality = characterQuality.Quality,
|
|
Star = 0,
|
|
Grade = 1,
|
|
FashionId = (uint)character.DefaultNpcFashtionId,
|
|
CreateTime = DateTimeOffset.Now.ToUnixTimeSeconds(),
|
|
TrustLv = 1,
|
|
TrustExp = 0,
|
|
Ability = 0,
|
|
LiberateLv = 1,
|
|
CharacterHeadInfo = new()
|
|
{
|
|
HeadFashionId = (uint)character.DefaultNpcFashtionId,
|
|
HeadFashionType = 0
|
|
}
|
|
};
|
|
|
|
// TODO: Don't do the ToString, query skill properly pls.
|
|
characterData.SkillList.AddRange(characterSkill.SkillGroupId.Take(8).Select(x => new CharacterSkill()
|
|
{
|
|
Id = uint.Parse(x.ToString().Take(6).ToArray()),
|
|
Level = 1
|
|
}));
|
|
|
|
FashionList fashion = new()
|
|
{
|
|
Id = character.DefaultNpcFashtionId,
|
|
IsLock = false
|
|
};
|
|
Fashions.Add(fashion);
|
|
ret.Fashion = fashion;
|
|
if (character.EquipId > 0)
|
|
ret.Equip = AddEquip((uint)character.EquipId, character.Id);
|
|
|
|
Characters.Add(characterData);
|
|
ret.Character = characterData;
|
|
return ret;
|
|
}
|
|
|
|
public CharacterData? AddCharacterExp(int characterId, int exp, int maxLvl = 0)
|
|
{
|
|
var characterData = TableReaderV2.Parse<CharacterTable>().FirstOrDefault(x => x.Id == characterId);
|
|
var character = Characters.FirstOrDefault(x => x.Id == characterId);
|
|
|
|
if (character is not null && characterData is not null)
|
|
{
|
|
levelCheck:
|
|
CharacterLevelUpTemplate? levelUpTemplate = characterLevelUpTemplates.FirstOrDefault(x => x.Level == character.Level && x.Type == characterData.Type);
|
|
if (levelUpTemplate is not null)
|
|
{
|
|
if (levelUpTemplate.Exp > exp)
|
|
{
|
|
character.Exp += (uint)Math.Max(0, exp);
|
|
}
|
|
else if (maxLvl > 0 && character.Level == maxLvl)
|
|
{
|
|
character.Exp = (uint)Math.Max(0, levelUpTemplate.Exp);
|
|
}
|
|
else
|
|
{
|
|
character.Level++;
|
|
exp -= (int)(levelUpTemplate.Exp - character.Exp);
|
|
character.Exp = 0;
|
|
goto levelCheck;
|
|
}
|
|
}
|
|
}
|
|
|
|
return character;
|
|
}
|
|
|
|
public UpgradeCharacterSkillResult UpgradeCharacterSkillGroup(int skillGroupId, int count)
|
|
{
|
|
List<uint> affectedCharacters = new();
|
|
int totalCoinCost = 0;
|
|
int totalSkillPointCost = 0;
|
|
IEnumerable<int> affectedSkills = TableReaderV2.Parse<CharacterSkillGroupTable>().Where(x => x.Id == skillGroupId).SelectMany(x => x.SkillId);
|
|
|
|
foreach (var skillId in affectedSkills)
|
|
{
|
|
foreach (var character in Characters.Where(x => x.SkillList.Any(x => x.Id == skillId)))
|
|
{
|
|
var characterSkill = character.SkillList.First(x => x.Id == skillId);
|
|
int targetLevel = characterSkill.Level + count;
|
|
|
|
while (characterSkill.Level < targetLevel)
|
|
{
|
|
var skillUpgrade = TableReaderV2.Parse<CharacterSkillUpgradeTable>().Find(x => x.SkillId == skillId && x.Level == characterSkill.Level);
|
|
|
|
totalCoinCost += skillUpgrade?.UseCoin ?? 0;
|
|
totalSkillPointCost += skillUpgrade?.UseSkillPoint ?? 0;
|
|
|
|
characterSkill.Level++;
|
|
}
|
|
affectedCharacters.Add(character.Id);
|
|
}
|
|
}
|
|
|
|
return new UpgradeCharacterSkillResult()
|
|
{
|
|
AffectedCharacters = affectedCharacters,
|
|
CoinCost = totalCoinCost,
|
|
SkillPointCost = totalSkillPointCost
|
|
};
|
|
}
|
|
|
|
public EquipData AddEquip(uint equipId, int characterId = 0)
|
|
{
|
|
EquipData equipData = new()
|
|
{
|
|
Id = NextEquipId,
|
|
TemplateId = equipId,
|
|
CharacterId = characterId,
|
|
Level = 1,
|
|
Exp = 0,
|
|
Breakthrough = 0,
|
|
ResonanceInfo = new(),
|
|
UnconfirmedResonanceInfo = new(),
|
|
AwakeSlotList = new(),
|
|
IsLock = false,
|
|
CreateTime = (uint)DateTimeOffset.Now.ToUnixTimeSeconds(),
|
|
IsRecycle = false
|
|
};
|
|
|
|
Equips.Add(equipData);
|
|
return equipData;
|
|
}
|
|
|
|
public EquipData? AddEquipExp(int equipId, int exp)
|
|
{
|
|
var equip = Equips.FirstOrDefault(x => x.Id == equipId);
|
|
EquipTable? equipData = TableReaderV2.Parse<EquipTable>().FirstOrDefault(x => x.Id == equip?.TemplateId);
|
|
EquipBreakThroughTable? equipBreakThroughTable = TableReaderV2.Parse<EquipBreakThroughTable>().FirstOrDefault(x => x.EquipId == equip?.TemplateId && x.Times == equip?.Breakthrough);
|
|
|
|
if (equip is not null && equipData is not null && equipBreakThroughTable is not null)
|
|
{
|
|
EquipLevelUpTemplate? levelUpTemplate = equipLevelUpTemplates.FirstOrDefault(x => x.TemplateId == equipBreakThroughTable.LevelUpTemplateId && x.Level == equip.Level);
|
|
|
|
if (levelUpTemplate is not null)
|
|
{
|
|
if (exp + equip.Exp < levelUpTemplate.Exp)
|
|
{
|
|
equip.Exp += Math.Max(0, exp);
|
|
}
|
|
else if (equip.Level < equipBreakThroughTable.LevelLimit)
|
|
{
|
|
equip.Level++;
|
|
exp -= levelUpTemplate.Exp - equip.Exp;
|
|
equip.Exp = 0;
|
|
return AddEquipExp(equipId, exp);
|
|
}
|
|
else
|
|
{
|
|
equip.Exp = levelUpTemplate.Exp;
|
|
}
|
|
}
|
|
}
|
|
|
|
return equip;
|
|
}
|
|
|
|
public void Save()
|
|
{
|
|
collection.ReplaceOne(Builders<Character>.Filter.Eq(x => x.Id, Id), this);
|
|
}
|
|
|
|
[BsonId]
|
|
public ObjectId Id { get; set; }
|
|
|
|
[BsonElement("uid")]
|
|
[BsonRequired]
|
|
public long Uid { get; set; }
|
|
|
|
[BsonElement("characters")]
|
|
[BsonRequired]
|
|
public List<CharacterData> Characters { get; set; }
|
|
|
|
[BsonElement("equips")]
|
|
[BsonRequired]
|
|
public List<EquipData> Equips { get; set; }
|
|
|
|
[BsonElement("fashions")]
|
|
[BsonRequired]
|
|
public List<FashionList> Fashions { get; set; }
|
|
}
|
|
|
|
public struct UpgradeCharacterSkillResult
|
|
{
|
|
public int CoinCost { get; init; }
|
|
public int SkillPointCost { get; init; }
|
|
public List<uint> AffectedCharacters { get; init; }
|
|
}
|
|
|
|
public partial class CharacterLevelUpTemplate
|
|
{
|
|
[JsonProperty("Level")]
|
|
public int Level { get; set; }
|
|
|
|
[JsonProperty("Exp")]
|
|
public int Exp { get; set; }
|
|
|
|
[JsonProperty("AllExp")]
|
|
public int AllExp { get; set; }
|
|
|
|
[JsonProperty("Type")]
|
|
public int Type { get; set; }
|
|
}
|
|
|
|
public partial class EquipLevelUpTemplate
|
|
{
|
|
[JsonProperty("Level")]
|
|
public int Level { get; set; }
|
|
|
|
[JsonProperty("Exp")]
|
|
public int Exp { get; set; }
|
|
|
|
[JsonProperty("AllExp")]
|
|
public int AllExp { get; set; }
|
|
|
|
[JsonProperty("TemplateId")]
|
|
public int TemplateId { get; set; }
|
|
}
|
|
|
|
public struct AddCharacterRet
|
|
{
|
|
public CharacterData Character { get; set; }
|
|
public EquipData Equip { get; set; }
|
|
public FashionList Fashion { get; set; }
|
|
}
|
|
}
|