using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Data.Excel; using EggLink.DanhengServer.Enums.GridFight; using EggLink.DanhengServer.GameServer.Game.GridFight.PendingAction; using EggLink.DanhengServer.GameServer.Game.GridFight.Sync; using EggLink.DanhengServer.GameServer.Server.Packet.Send.GridFight; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; namespace EggLink.DanhengServer.GameServer.Game.GridFight.Component; public class GridFightLevelComponent : BaseGridFightComponent { #region Properties & Fields // TODO : to proto field private uint _curChapterId = 1; private uint _curSectionId = 1; public Dictionary> Sections { get; } = []; public GridFightGameSectionInfo CurrentSection => Sections[_curChapterId][(int)(_curSectionId - 1)]; public List RoleDamageSttInfos { get; } = []; #endregion #region Constructors public GridFightLevelComponent(GridFightInstance inst) : base(inst) { // TODO: randomly select a base route id List chapterIds = [1100]; List campPool = GameData.GridFightCampData.Values.Where(x => x.BossBattleArea != 0).ToList(); foreach (var chapterId in Enumerable.Range(1, 3)) { var chapters = chapterIds.Count >= chapterId ? [GameData.GridFightStageRouteData[chapterIds[chapterId - 1]]] : GameData.GridFightStageRouteData.Values.Where(x => x.Any(j => j.Value.ChapterID == chapterId)) .ToList(); if (chapters.Count == 0) continue; var select = chapters.RandomElement(); var camp = campPool.RandomElement(); // cannot the same campPool.Remove(camp); // create section infos Sections[(uint)chapterId] = [.. select.Values.Select(x => new GridFightGameSectionInfo(x, camp))]; } } #endregion #region Stt public async ValueTask<(Retcode, GridFightRoleDamageSttInfo?)> AddRoleDamageStt(uint roleId, double damage, bool sendPacket = true) { var roleComp = Inst.GetComponent(); var role = roleComp.Data.Roles.OrderBy(x => x.Pos).FirstOrDefault(x => x.RoleId == roleId); if (role == null) return (Retcode.RetGridFightRoleNotExist, null); var info = RoleDamageSttInfos.FirstOrDefault(x => x.RoleId == roleId && x.Tier == role.Tier); GridFightRoleDamageSttInfo res; if (info == null) { res = info = new GridFightRoleDamageSttInfo { RoleId = roleId, Tier = role.Tier, TotalDamage = damage, IsTrialAvatar = false, IsUpgrade = false }; RoleDamageSttInfos.Add(info); } else { res = new GridFightRoleDamageSttInfo { RoleId = info.RoleId, IsTrialAvatar = info.IsTrialAvatar, IsUpgrade = info.IsUpgrade, Tier = info.Tier, TotalDamage = damage }; info.TotalDamage += damage; } if (sendPacket) { await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(new GridFightRoleDamageSttSyncData(GridFightSrc.KGridFightSrcBattleEnd, this))); } return (Retcode.RetSucc, res); } #endregion #region Actions public async ValueTask> EnterNextSection(bool sendPacket = true, GridFightSrc src = GridFightSrc.KGridFightSrcBattleEnd) { // if last section of chapter if (_curSectionId >= Sections[_curChapterId].Count) { if (_curChapterId >= Sections.Count) { // end of game return []; } _curChapterId++; _curSectionId = 1; } else { _curSectionId++; } List syncs = [new GridFightLevelSyncData(src, this)]; //await Inst.RollPortalBuff(); if (CurrentSection.Excel.IsAugment == 1) { // create augment action syncs.Add(await Inst.CreatePendingAction(sendPacket: false)); } if (CurrentSection.Excel.NodeType == GridFightNodeTypeEnum.Supply) { // create supply action syncs.Add(await Inst.CreatePendingAction(sendPacket: false)); } if (sendPacket) { await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs)); } return syncs; } #endregion #region Information public List GetBossMonsters() { // get every chapter last section camp List bosses = []; foreach (var chapter in Sections.Values) { var lastSection = chapter.Last(); var bossMonsters = lastSection.MonsterCamp.Monsters.Where(x => x.MonsterTier == 5).ToList(); if (bossMonsters.Count == 0) continue; var boss = bossMonsters.RandomElement(); bosses.Add(new GridFightMonsterInfo { MonsterId = boss.MonsterID, Tier = boss.MonsterTier, MonsterCampId = lastSection.MonsterCamp.ID }); } return bosses; } #endregion #region Serialization public override GridFightGameInfo ToProto() { return new GridFightGameInfo { GridLevelInfo = new GridFightLevelInfo { ChapterId = CurrentSection.ChapterId, SectionId = CurrentSection.SectionId, RouteId = CurrentSection.Excel.ID, GridFightLayerInfo = new GridFightLayerInfo { RouteInfo = CurrentSection.ToRouteInfo(), RouteIsPending = CurrentSection.Excel.IsAugment == 1 }, BossInfo = new GridFightBossInfo { BossMonsters = { GetBossMonsters() } }, GridFightCampList = { Sections.Values.SelectMany(x => x).Select(h => h.MonsterCamp.ID).ToHashSet().Select(s => new GridFightGameCampInfo { MonsterCampId = s, }) }, GridChapterInfo = new GridFightChapterInfo { SectionInfo = { Sections.Values.SelectMany(x => x).Select(s => s.ToProto()) } }, LevelSttInfo = new GridFightLevelSttInfo { GridFightDamageSttInfo = ToDamageSttInfo() } } }; } public GridFightDamageSttInfo ToDamageSttInfo() { return new GridFightDamageSttInfo { RoleDamageSttList = { RoleDamageSttInfos.Select(x => x.ToProto()) } }; } #endregion } public class GridFightRoleDamageSttInfo { public uint RoleId { get; set; } public uint Tier { get; set; } public double TotalDamage { get; set; } public bool IsTrialAvatar { get; set; } public bool IsUpgrade { get; set; } public GridFightRoleDamageStt ToProto() { return new GridFightRoleDamageStt { RoleBasicId = RoleId, Tier = Tier, IsTrialAvatar = IsTrialAvatar, IsUpgrade = IsUpgrade, TotalDamage = TotalDamage }; } } public class GridFightGameSectionInfo { public GridFightStageRouteExcel Excel { get; } public uint ChapterId { get; } public uint SectionId { get; } public GridFightCampExcel MonsterCamp { get; set; } public List Encounters { get; } = []; public GridFightGameSectionInfo(GridFightStageRouteExcel excel, GridFightCampExcel camp) { Excel = excel; ChapterId = excel.ChapterID; SectionId = excel.SectionID; MonsterCamp = camp; if (Excel.NodeType is not GridFightNodeTypeEnum.Monster and not GridFightNodeTypeEnum.CampMonster and not GridFightNodeTypeEnum.Boss and not GridFightNodeTypeEnum.EliteBranch || Excel.IsAugment == 1) return; Encounters.Add(new GridFightGameEncounterInfo(1, 1, this)); } public GridFightRouteInfo ToRouteInfo() { return new GridFightRouteInfo { FightCampId = MonsterCamp.ID, EliteBranchId = 0, RouteEncounterList = { Encounters.Select(x => x.ToProto()) } }; } public GridFightSectionInfo ToProto() { return new GridFightSectionInfo { ChapterId = ChapterId, SectionId = SectionId }; } } public class GridFightGameEncounterInfo { public GridFightGameEncounterInfo(uint index, uint difficulty, GridFightGameSectionInfo section) { EncounterIndex = index; EncounterDifficulty = difficulty; ParentSection = section; var waveNum = ParentSection.Excel.NodeType switch { //GridFightNodeTypeEnum.Boss => 2, //GridFightNodeTypeEnum.EliteBranch => 2, _ => 1 }; List monsterNum = ParentSection.Excel.NodeType switch { GridFightNodeTypeEnum.Boss => [1], GridFightNodeTypeEnum.EliteBranch => [3], GridFightNodeTypeEnum.CampMonster => [3], _ => [Random.Shared.Next(3, 5)] }; var monsterPool = ParentSection.MonsterCamp.Monsters.Where(x => x.MonsterTier <= 2).OrderBy(_ => Guid.NewGuid()).ToList(); var monster4Pool = ParentSection.MonsterCamp.Monsters.Where(x => x.MonsterTier is 4 or 3).OrderBy(_ => Guid.NewGuid()).ToList(); var monster5Pool = ParentSection.MonsterCamp.Monsters.Where(x => x.MonsterTier == 5).OrderBy(_ => Guid.NewGuid()).ToList(); for (var i = 0; i < waveNum; i++) { if (i < waveNum - 1 || ParentSection.Excel.NodeType is GridFightNodeTypeEnum.Monster) { // no elite var res = monsterPool.OrderBy(_ => Guid.NewGuid()).Take(monsterNum[i]).ToList(); MonsterWaves.Add(new GridFightGameMonsterWaveInfo((uint)(i + 1), res, ParentSection.MonsterCamp.ID)); } else { var elite = ParentSection.Excel.NodeType switch { GridFightNodeTypeEnum.Boss => monster5Pool.RandomElement(), _ => monster4Pool.RandomElement(), }; List monsters = [elite]; var remain = monsterNum[i] - 1; if (remain > 0) { monsters.AddRange(monsterPool.OrderBy(_ => Guid.NewGuid()).Take(remain).ToList()); } MonsterWaves.Add(new GridFightGameMonsterWaveInfo((uint)(i + 1), monsters, ParentSection.MonsterCamp.ID)); } } } public uint EncounterIndex { get; set; } public uint EncounterDifficulty { get; set; } public GridFightGameSectionInfo ParentSection { get; } public List MonsterWaves { get; } = []; public GridFightEncounterInfo ToProto() { return new GridFightEncounterInfo { EncounterIndex = EncounterIndex, EncounterExtraDifficultyLevel = EncounterDifficulty, EncounterDropInfo = new GridFightDropInfo(), MonsterWaveList = { MonsterWaves.Select(x => x.ToProto()) } }; } } public class GridFightGameMonsterWaveInfo(uint wave, List monsters, uint campId) { public uint Wave { get; set; } = wave; public uint CampId { get; set; } = campId; public List Monsters { get; } = monsters; public GridEncounterMonsterWave ToProto() { return new GridEncounterMonsterWave { EncounterWave = Wave, FightMonsterList = { Monsters.Select(x => new GridFightMonsterInfo { MonsterId = x.MonsterID, MonsterCampId = CampId, Tier = x.MonsterTier }) } }; } }