Files
DanhengServer-OpenSource/GameServer/Game/GridFight/Component/GridFightLevelComponent.cs
2025-11-29 21:57:27 +08:00

772 lines
25 KiB
C#

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;
using System.Collections.Generic;
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<uint, List<GridFightGameSectionInfo>> Sections { get; } = [];
public GridFightGameSectionInfo CurrentSection => Sections[_curChapterId][(int)(_curSectionId - 1)];
public List<GridFightRoleDamageSttInfo> RoleDamageSttInfos { get; } = [];
public List<GridFightTraitDamageSttInfo> TraitDamageSttInfos { get; } = [];
public List<GridFightPortalBuffInfo> PortalBuffs { get; } = [];
public List<uint> Affixes { get; } = [];
#endregion
#region Constructors
public GridFightLevelComponent(GridFightInstance inst) : base(inst)
{
// TODO: randomly select a base route id
List<uint> chapterIds = [1100];
List<GridFightCampExcel> 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))];
}
if (!GameData.GridFightDivisionStageData.TryGetValue(Inst.DivisionId, out var divisionExcel)) return;
var affixIds = GameData.GridFightAffixConfigData.Keys.ToList();
foreach (var _ in divisionExcel.AffixChooseNumList)
{
var affixId = affixIds.RandomElement();
if (Affixes.Contains(affixId)) continue;
Affixes.Add(affixId);
affixIds.Remove(affixId); // avoid duplicate
}
}
#endregion
#region Stt
public async ValueTask<(Retcode, GridFightRoleDamageSttInfo?)> AddRoleDamageStt(uint roleId, double damage, bool sendPacket = true)
{
var roleComp = Inst.GetComponent<GridFightRoleComponent>();
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);
}
public async ValueTask<(Retcode, GridFightTraitDamageSttInfo?)> AddTraitDamageStt(uint traitId, double damage, bool sendPacket = true)
{
var info = TraitDamageSttInfos.FirstOrDefault(x => x.TraitId == traitId);
GridFightTraitDamageSttInfo res;
if (info == null)
{
res = info = new GridFightTraitDamageSttInfo
{
TraitId = traitId,
TotalDamage = damage
};
TraitDamageSttInfos.Add(info);
}
else
{
res = new GridFightTraitDamageSttInfo
{
TraitId = info.TraitId,
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<List<BaseGridFightSyncData>> EnterNextSection(bool sendPacket = true, GridFightSrc src = GridFightSrc.KGridFightSrcBattleEnd)
{
var shopComp = Inst.GetComponent<GridFightShopComponent>();
var basicComp = Inst.GetComponent<GridFightBasicComponent>();
// if last section of chapter
if (_curSectionId >= Sections[_curChapterId].Count)
{
if (_curChapterId >= Sections.Count)
{
// end of game
return [];
}
_curChapterId++;
_curSectionId = 1;
}
else
{
_curSectionId++;
}
List<BaseGridFightSyncData> syncs = [new GridFightLevelSyncData(src, this)];
syncs.AddRange(await Inst.CreatePendingAction<GridFightRoundBeginPendingAction>(sendPacket: false));
if (CurrentSection.Excel.IsAugment == 1)
{
// create augment action
await Inst.CreatePendingAction<GridFightAugmentPendingAction>(sendPacket: false);
}
if (CurrentSection.Excel.NodeType == GridFightNodeTypeEnum.Supply)
{
// create supply action
await Inst.CreatePendingAction<GridFightSupplyPendingAction>(sendPacket: false);
}
else if (CurrentSection.Excel.NodeType == GridFightNodeTypeEnum.EliteBranch)
{
await Inst.CreatePendingAction<GridFightEliteBranchPendingAction>(sendPacket: false);
}
if (CurrentSection.Excel.NodeType != GridFightNodeTypeEnum.Supply)
await Inst.CreatePendingAction<GridFightReturnPreparationPendingAction>(sendPacket: false);
// refresh shop
await shopComp.RefreshShop(true, false);
syncs.AddRange(new GridFightShopSyncData(src, shopComp.Data, basicComp.Data.CurLevel));
if (sendPacket)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncs));
}
return syncs;
}
public async ValueTask<List<BaseGridFightSyncData>> AddPortalBuff(uint portalBuffId, bool sendPacket = true, GridFightSrc src = GridFightSrc.KGridFightSrcSelectPortalBuff)
{
var info = new GridFightPortalBuffInfo
{
PortalBuffId = portalBuffId
};
PortalBuffs.Add(info);
var syncData = new GridFightAddPortalBuffSyncData(src, info);
if (sendPacket)
{
await Inst.Player.SendPacket(new PacketGridFightSyncUpdateResultScNotify(syncData));
}
return [syncData];
}
#endregion
#region Information
public List<GridFightMonsterInfo> GetBossMonsters()
{
// get every chapter last section camp
List<GridFightMonsterInfo> 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,
GridFightAffixList = { Affixes },
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()
},
GridFightPortalBuffList = { PortalBuffs.Select(x => x.ToProto()) }
}
};
}
public GridFightDamageSttInfo ToDamageSttInfo()
{
var traitComp = Inst.GetComponent<GridFightTraitComponent>();
return new GridFightDamageSttInfo
{
RoleDamageSttList = { RoleDamageSttInfos.Select(x => x.ToProto()) },
TraitDamageSttList = { TraitDamageSttInfos.Select(x => x.ToProto(traitComp)) }
};
}
#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 GridFightTraitDamageSttInfo
{
public uint TraitId { get; set; }
public double TotalDamage { get; set; }
public GridFightTraitDamageStt ToProto(GridFightTraitComponent trait)
{
return new GridFightTraitDamageStt
{
TraitId = TraitId,
Damage = TotalDamage,
TraitEffectLayer = trait.Data.Traits.FirstOrDefault(x => x.TraitId == TraitId)?.TraitLayer ?? 0
};
}
}
public class GridFightPortalBuffInfo
{
public uint PortalBuffId { get; set; }
public Dictionary<string, uint> SavedValue { get; set; } = [];
public GridFightGamePortalBuffInfo ToProto()
{
return new GridFightGamePortalBuffInfo
{
PortalBuffId = PortalBuffId,
GameSavedValueMap = { SavedValue }
};
}
public BattleGridFightPortalBuffInfo ToBattleInfo()
{
return new BattleGridFightPortalBuffInfo
{
PortalBuffId = PortalBuffId,
GameSavedValueMap = { SavedValue }
};
}
public GridFightPortalBuffSyncInfo ToSyncInfo()
{
return new GridFightPortalBuffSyncInfo
{
PortalBuffId = PortalBuffId,
GameSavedValueMap = { SavedValue }
};
}
}
public class GridFightGameSectionInfo
{
public GridFightStageRouteExcel Excel { get; }
public uint ChapterId { get; }
public uint SectionId { get; }
public uint BranchId { get; set; } = 1;
public GridFightCampExcel MonsterCamp { get; set; }
public List<GridFightGameEncounterInfo> 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) return;
if (Excel.NodeType is GridFightNodeTypeEnum.EliteBranch)
{
List<uint> difficulties = [1, 2, 3];
BranchId = 0;
foreach (var diff in difficulties.OrderBy(_ => Guid.NewGuid()).Take(2))
{
Encounters.Add(new GridFightGameEncounterInfo(diff, diff, this, diff));
}
}
else
{
Encounters.Add(new GridFightGameEncounterInfo(1, 1, this));
}
}
public GridFightRouteInfo ToRouteInfo()
{
return new GridFightRouteInfo
{
FightCampId = MonsterCamp.ID,
EliteBranchId = BranchId,
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, uint rewardLevel = 0)
{
EncounterIndex = index;
EncounterDifficulty = difficulty;
ParentSection = section;
var waves = GridFightEncounterGenerateHelper.GenerateMonsterWaves(section);
MonsterWaves.AddRange(waves);
switch (rewardLevel)
{
case 0:
return;
// random 5 exp or 5 gold
case 1 when Random.Shared.Next(2) == 0:
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Exp,
Num = 5
});
break;
case 1:
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Coin,
Num = 5
});
break;
case 2:
{
// random 10 exp or 10 gold or 2 golden orb
var rand = Random.Shared.Next(3);
if (rand == 0)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Exp,
Num = 10
});
}
else if (rand == 1)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Coin,
Num = 10
});
}
else
{
for (var i = 0; i < 2; i++)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Orb,
Num = 1,
DropItemId = GameData.GridFightOrbData.Values.Where(x => x.Type == GridFightOrbTypeEnum.Glod).ToList().RandomElement().OrbID
});
}
}
break;
}
case 3:
{
// random 15 exp or 15 gold or 2 colourful orb
var rand = Random.Shared.Next(3);
if (rand == 0)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Exp,
Num = 15
});
}
else if (rand == 1)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Coin,
Num = 15
});
}
else
{
for (var i = 0; i < 2; i++)
{
DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Orb,
Num = 1,
DropItemId = GameData.GridFightOrbData.Values.Where(x => x.Type == GridFightOrbTypeEnum.Colorful).ToList().RandomElement().OrbID
});
}
}
break;
}
}
}
public uint EncounterIndex { get; set; }
public uint EncounterDifficulty { get; set; }
public GridFightGameSectionInfo ParentSection { get; }
public List<GridFightGameMonsterWaveInfo> MonsterWaves { get; } = [];
public List<GridFightDropItemInfo> DropItems { get; } = [];
public async ValueTask<(List<BaseGridFightSyncData>, List<GridFightDropItemInfo>)> TakeMonsterDrop(GridFightItemsComponent itemsComp)
{
List<BaseGridFightSyncData> syncs = [];
List<GridFightDropItemInfo> items = [];
foreach (var monster in MonsterWaves.SelectMany(x => x.Monsters))
{
syncs.AddRange(await itemsComp.TakeDrop(monster.DropItems, false, GridFightSrc.KGridFightSrcBattleEnd, 0,
ParentSection.ChapterId, ParentSection.SectionId));
items.AddRange(monster.DropItems);
}
return (syncs, items);
}
public async ValueTask<List<BaseGridFightSyncData>> TakeEncounterDrop(GridFightItemsComponent itemsComp)
{
return await itemsComp.TakeDrop(DropItems, false, GridFightSrc.KGridFightSrcEliteBranchBattleBonus, 0,
ParentSection.ChapterId, ParentSection.SectionId);
}
public GridFightEncounterInfo ToProto()
{
return new GridFightEncounterInfo
{
EncounterIndex = EncounterIndex,
EncounterExtraDifficultyLevel = EncounterDifficulty,
EncounterDropInfo = new GridFightDropInfo
{
DropItemList = { DropItems }
},
MonsterWaveList = { MonsterWaves.Select(x => x.ToProto()) }
};
}
}
public class GridFightGameMonsterWaveInfo
{
public GridFightGameMonsterWaveInfo(uint wave, List<GridFightMonsterExcel> monsters, uint campId,
uint addOrbNum = 0)
{
Wave = wave;
foreach (var monsterInfo in monsters.Select(monster => new GridFightGameMonsterInfo(monster, campId,
(uint)Random.Shared.Next(1, (int)(monster.MonsterTier + 1)))))
{
if (addOrbNum > 0)
{
monsterInfo.DropItems.Add(new GridFightDropItemInfo
{
DropType = GridFightDropType.Orb,
Num = 1,
DropItemId = GameData.GridFightOrbData.Values
.Where(x => x.Type is GridFightOrbTypeEnum.White or GridFightOrbTypeEnum.Blue).ToList()
.RandomElement().OrbID
});
addOrbNum--;
}
Monsters.Add(monsterInfo);
}
}
public uint Wave { get; set; }
public List<GridFightGameMonsterInfo> Monsters { get; } = [];
public GridEncounterMonsterWave ToProto()
{
return new GridEncounterMonsterWave
{
EncounterWave = Wave,
FightMonsterList =
{
Monsters.Select(x => x.ToProto())
}
};
}
}
public class GridFightGameMonsterInfo(GridFightMonsterExcel monsters, uint campId, uint tier)
{
public uint CampId { get; set; } = campId;
public GridFightMonsterExcel Monster { get; } = monsters;
public uint Tier { get; } = tier;
public List<GridFightDropItemInfo> DropItems { get; } = [];
public GridFightMonsterInfo ToProto()
{
return new GridFightMonsterInfo
{
MonsterId = Monster.MonsterID,
MonsterCampId = CampId,
Tier = Tier
};
}
}
public static class GridFightEncounterGenerateHelper
{
private static readonly List<List<List<uint>>> RandomWaveRule =
[
[[3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]],
[[3, 2, 2, 2, 2], [3, 3, 2, 2, 2]],
[[3, 3, 3, 2, 2, 2, 2, 2]],
[[2, 2, 2, 2, 3, 3]]
];
public static List<GridFightGameMonsterWaveInfo> GenerateMonsterWaves(GridFightGameSectionInfo section)
{
switch (section.Excel.NodeType)
{
case GridFightNodeTypeEnum.Monster:
return GenerateMonsterType(section);
case GridFightNodeTypeEnum.CampMonster:
case GridFightNodeTypeEnum.EliteBranch:
return GenerateCampMonsterType(section);
case GridFightNodeTypeEnum.Boss:
return GenerateBossType(section);
default:
break;
}
return [];
}
public static List<GridFightGameMonsterWaveInfo> GenerateMonsterType(GridFightGameSectionInfo section)
{
List<GridFightGameMonsterWaveInfo> waves = [];
var monsters = section.MonsterCamp.Monsters
.Where(x => x.MonsterTier <= 2).ToList();
List<GridFightMonsterExcel> targets = [];
for (var i = 0; i < 5; i++)
{
targets.Add(monsters.RandomElement());
}
waves.Add(new GridFightGameMonsterWaveInfo(1, targets, section.MonsterCamp.ID, 5));
return waves;
}
public static List<GridFightGameMonsterWaveInfo> GenerateCampMonsterType(GridFightGameSectionInfo section)
{
List<GridFightGameMonsterWaveInfo> waves = [];
var rules = RandomWaveRule.RandomElement();
foreach (var rule in rules)
{
List<GridFightMonsterExcel> excels = [];
foreach (var tier in rule)
{
var targets = section.MonsterCamp.Monsters.Where(x => x.MonsterTier == tier).ToList();
if (targets.Count == 0)
continue;
var selected = targets.RandomElement();
excels.Add(selected);
}
// random order
excels = excels.OrderBy(_ => Guid.NewGuid()).ToList();
waves.Add(new GridFightGameMonsterWaveInfo((uint)(waves.Count + 1), excels, section.MonsterCamp.ID));
}
return waves;
}
public static List<GridFightGameMonsterWaveInfo> GenerateBossType(GridFightGameSectionInfo section)
{
List<GridFightGameMonsterWaveInfo> waves = [];
var waveNum = section.MonsterCamp.ID == 10 ? 2 : 1;
for (var i = 0; i < waveNum; i++)
{
if (i == waveNum - 1)
{
// boss wave
var bossMonsters = section.MonsterCamp.Monsters
.Where(x => x.MonsterTier == (section.ChapterId == 3 ? 6 : 5))
.ToList();
if (bossMonsters.Count == 0)
continue;
if (section.MonsterCamp.ID == 10)
{
// extra monster
bossMonsters.Add(GameData.GridFightMonsterData[300303301]);
bossMonsters.Add(GameData.GridFightMonsterData[300304301]);
bossMonsters.Add(GameData.GridFightMonsterData[300305301]);
}
waves.Add(new GridFightGameMonsterWaveInfo((uint)(waves.Count + 1), bossMonsters, section.MonsterCamp.ID));
}
else
{
// normal wave
var monsters = section.MonsterCamp.Monsters
.Where(x => x.MonsterTier <= 2).ToList();
List<GridFightMonsterExcel> targets = [];
for (var j = 0; j < 5; j++)
{
targets.Add(monsters.RandomElement());
}
waves.Add(new GridFightGameMonsterWaveInfo((uint)(waves.Count + 1), targets, section.MonsterCamp.ID));
}
}
return waves;
}
}