using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Data.Excel; using EggLink.DanhengServer.Database.Avatar; using EggLink.DanhengServer.Database.Inventory; using EggLink.DanhengServer.GameServer.Game.Battle.Custom; using EggLink.DanhengServer.GameServer.Game.GridFight; using EggLink.DanhengServer.GameServer.Game.GridFight.Component; using EggLink.DanhengServer.GameServer.Game.Player; using EggLink.DanhengServer.GameServer.Game.RogueMagic; using EggLink.DanhengServer.GameServer.Game.Scene; using EggLink.DanhengServer.GameServer.Game.Scene.Entity; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Battle; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lineup; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent; namespace EggLink.DanhengServer.GameServer.Game.Battle; public class BattleManager(PlayerInstance player) : BasePlayerManager(player) { public StageConfigExcel? NextBattleStageConfig { get; set; } public List NextBattleMonsterIds { get; set; } = []; public async ValueTask StartBattle(BaseGameEntity attackEntity, List targetEntityList, bool isSkill) { if (Player.BattleInstance != null) return Player.BattleInstance; var targetList = new List(); var avatarList = new List(); var propList = new List(); Player.SceneInstance!.AvatarInfo.TryGetValue(attackEntity.EntityId, out var castAvatar); if (castAvatar != null) { foreach (var entity in targetEntityList) switch (entity) { case EntityMonster monster: targetList.Add(monster); break; case EntityProp prop: propList.Add(prop); break; } } else { var isAmbushed = targetEntityList.Any(entity => Player.SceneInstance!.AvatarInfo.ContainsKey(entity.EntityId)); if (!isAmbushed) return null; var monsterEntity = Player.SceneInstance!.Entities[attackEntity.EntityId]; if (monsterEntity is EntityMonster monster) targetList.Add(monster); } if (targetList.Count == 0 && propList.Count == 0) return null; foreach (var prop in propList) { await Player.SceneInstance!.RemoveEntity(prop); if (prop.Excel.IsMpRecover) { await Player.LineupManager!.GainMp(2, true, SyncLineupReason.SyncReasonMpAddPropHit); } else if (prop.Excel.IsHpRecover) { Player.LineupManager!.GetCurLineup()!.Heal(2000, false); await Player.SendPacket(new PacketSyncLineupNotify(Player.LineupManager!.GetCurLineup()!)); } else if (prop.PropInfo.Name == "SpeedDestruct") { var avatar = Player.SceneInstance!.AvatarInfo.Values.ToList().RandomElement(); await avatar.AddBuff(new SceneBuff(2041101, 1, -1, 15)); } else { Player.InventoryManager!.HandlePlaneEvent(prop.PropInfo.EventID); } Player.RogueManager!.GetRogueInstance()?.OnPropDestruct(prop); } if (targetList.Count > 0) { var triggerBattle = targetList.Any(target => target.IsAlive); if (!triggerBattle) return null; var inst = Player.RogueManager!.GetRogueInstance(); if (inst is RogueMagicInstance { CurLevel.CurRoom.AdventureInstance: not null } magic) { await magic.HitMonsterInAdventure(targetList); foreach (var entityMonster in targetList) await entityMonster.Kill(); return null; } BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, targetList.Where(x => x.IsAlive).ToList()) { WorldLevel = Player.Data.WorldLevel }; if (NextBattleStageConfig != null) { battleInstance = new BattleInstance(Player, Player.LineupManager!.GetCurLineup()!, [NextBattleStageConfig]) { WorldLevel = Player.Data.WorldLevel }; NextBattleStageConfig = null; } avatarList.AddRange(Player.LineupManager!.GetCurLineup()!.BaseAvatars! .Select(item => Player.SceneInstance!.AvatarInfo.Values.FirstOrDefault(x => x.AvatarInfo.AvatarId == item.BaseAvatarId)) .OfType()); MazeBuff? mazeBuff = null; if (castAvatar != null) { var index = battleInstance.Lineup.BaseAvatars!.FindIndex(x => x.BaseAvatarId == castAvatar.AvatarInfo.AvatarId); GameData.AvatarConfigData.TryGetValue(castAvatar.AvatarInfo.AvatarId, out var avatarExcel); if (avatarExcel != null) { mazeBuff = new MazeBuff((int)avatarExcel.DamageType, 1, index); mazeBuff.DynamicValues.Add("SkillIndex", isSkill ? 2 : 1); } } else { mazeBuff = new MazeBuff(GameConstants.AMBUSH_BUFF_ID, 1, -1) { WaveFlag = 1 }; } if (mazeBuff != null && mazeBuff.BuffID != 0) // avoid adding a buff with ID 0 battleInstance.Buffs.Add(mazeBuff); battleInstance.AvatarInfo = avatarList; // call battle start Player.RogueManager!.GetRogueInstance()?.OnBattleStart(battleInstance); Player.ChallengeManager!.ChallengeInstance?.OnBattleStart(battleInstance); Player.QuestManager!.OnBattleStart(battleInstance); Player.BattleInstance = battleInstance; InvokeOnPlayerEnterBattle(Player, battleInstance); return battleInstance; } return null; } public async ValueTask StartStage(int eventId) { if (Player.BattleInstance != null) { await Player.SendPacket(new PacketSceneEnterStageScRsp(Player.BattleInstance)); return; } GameData.StageConfigData.TryGetValue(eventId, out var stageConfig); if (stageConfig == null) { GameData.StageConfigData.TryGetValue(eventId * 10 + Player.Data.WorldLevel, out stageConfig); if (stageConfig == null) { await Player.SendPacket(new PacketSceneEnterStageScRsp()); return; } } if (NextBattleStageConfig != null) { stageConfig = NextBattleStageConfig; NextBattleStageConfig = null; } BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, [stageConfig]) { WorldLevel = Player.Data.WorldLevel, EventId = eventId }; var avatarList = Player.LineupManager!.GetCurLineup()!.BaseAvatars!.Select(item => Player.SceneInstance!.AvatarInfo.Values.FirstOrDefault(x => x.AvatarInfo.AvatarId == item.BaseAvatarId)) .OfType().ToList(); battleInstance.AvatarInfo = avatarList; // call battle start Player.RogueManager!.GetRogueInstance()?.OnBattleStart(battleInstance); Player.ChallengeManager!.ChallengeInstance?.OnBattleStart(battleInstance); Player.QuestManager!.OnBattleStart(battleInstance); Player.BattleInstance = battleInstance; InvokeOnPlayerEnterBattle(Player, battleInstance); await Player.SendPacket(new PacketSceneEnterStageScRsp(battleInstance)); Player.SceneInstance?.OnEnterStage(); } public async ValueTask StartCocoonStage(int cocoonId, int wave, int worldLevel) { if (Player.BattleInstance != null) return null; GameData.CocoonConfigData.TryGetValue(cocoonId * 100 + worldLevel, out var config); if (config == null) return null; wave = Math.Max(wave, 1); var cost = config.StaminaCost * wave; if (Player.Data.Stamina < cost) return null; List stageConfigExcels = []; for (var i = 0; i < wave; i++) { var stageId = config.StageIDList.RandomElement(); GameData.StageConfigData.TryGetValue(stageId, out var stageConfig); if (stageConfig == null) continue; stageConfigExcels.Add(stageConfig); } if (stageConfigExcels.Count == 0) return null; BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, stageConfigExcels) { StaminaCost = cost, WorldLevel = config.WorldLevel, CocoonWave = wave, MappingInfoId = config.MappingInfoID }; if (NextBattleStageConfig != null) { battleInstance = new BattleInstance(Player, Player.LineupManager!.GetCurLineup()!, [NextBattleStageConfig]) { WorldLevel = Player.Data.WorldLevel }; NextBattleStageConfig = null; } var avatarList = Player.LineupManager!.GetCurLineup()!.BaseAvatars!.Select(item => Player.SceneInstance!.AvatarInfo.Values.FirstOrDefault(x => x.AvatarInfo.AvatarId == item.BaseAvatarId)) .OfType().ToList(); battleInstance.AvatarInfo = avatarList; Player.BattleInstance = battleInstance; Player.QuestManager!.OnBattleStart(battleInstance); InvokeOnPlayerEnterBattle(Player, battleInstance); await ValueTask.CompletedTask; return battleInstance; } public (Retcode, BattleInstance?) StartBattleCollege(int collegeId) { if (Player.BattleInstance != null) return (Retcode.RetInBattleNow, null); GameData.BattleCollegeConfigData.TryGetValue(collegeId, out var config); if (config == null) return (Retcode.RetFail, null); var stageId = config.StageID; GameData.StageConfigData.TryGetValue(stageId, out var stageConfig); if (stageConfig == null) return (Retcode.RetStageConfigNotExist, null); BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, [stageConfig]) { WorldLevel = Player.Data.WorldLevel, CollegeConfigExcel = config, AvatarInfo = [] }; // call battle start Player.RogueManager!.GetRogueInstance()?.OnBattleStart(battleInstance); Player.ChallengeManager!.ChallengeInstance?.OnBattleStart(battleInstance); Player.QuestManager!.OnBattleStart(battleInstance); Player.BattleInstance = battleInstance; return (Retcode.RetSucc, battleInstance); } public BattleInstance? StartGridFightBattle(GridFightInstance inst) { if (Player.BattleInstance != null) return null; var levelComponent = inst.GetComponent(); var curSection = levelComponent.CurrentSection; var stageConfigId = curSection.Excel.StageID; GameData.StageConfigData.TryGetValue((int)stageConfigId, out var stageConfig); if (stageConfig == null) return null; BattleInstance battleInstance = new(Player, Player.LineupManager!.GetCurLineup()!, [stageConfig]) { WorldLevel = Player.Data.WorldLevel, AvatarInfo = [], GridFightOptions = new BattleGridFightOptions(curSection, inst, Player) }; Player.BattleInstance = battleInstance; Player.QuestManager!.OnBattleStart(battleInstance); InvokeOnPlayerEnterBattle(Player, battleInstance); return battleInstance; } public async ValueTask EndBattle(PVEBattleResultCsReq req) { InvokeOnPlayerQuitBattle(Player, req); if (Player.BattleInstance == null) { await Player.SendPacket(new PacketPVEBattleResultScRsp()); return; } Player.BattleInstance.BattleEndStatus = req.EndStatus; var battle = Player.BattleInstance; var updateStatus = true; var teleportToAnchor = false; var minimumHp = 0; var dropItems = new List(); switch (req.EndStatus) { case BattleEndStatus.BattleEndWin: // Drops foreach (var monster in battle.EntityMonsters) dropItems.AddRange(await monster.Kill(false)); // Spend stamina if (battle.StaminaCost > 0) await Player.SpendStamina(battle.StaminaCost); break; case BattleEndStatus.BattleEndLose: // Set avatar hp to 20% if the player's party is downed minimumHp = 2000; teleportToAnchor = true; break; default: teleportToAnchor = true; if (battle.CocoonWave > 0) teleportToAnchor = false; updateStatus = false; break; } if (updateStatus) { var lineup = Player.LineupManager!.GetCurLineup()!; // Update battle status foreach (var avatar in req.Stt.BattleAvatarList) { BaseAvatarInfo? avatarInstance = Player.AvatarManager!.GetFormalAvatar((int)avatar.Id); var prop = avatar.AvatarStatus; var curHp = (int)Math.Max(Math.Round(prop.LeftHp / prop.MaxHp * 10000), minimumHp); var curSp = (int)prop.LeftSp * 100; if (avatarInstance == null) { avatarInstance = Player.AvatarManager!.GetTrialAvatar((int)avatar.Id); avatarInstance?.SetCurHp(curHp, lineup.LineupType != 0); avatarInstance?.SetCurSp(curSp, lineup.LineupType != 0); } else { avatarInstance.SetCurHp(curHp, lineup.LineupType != 0); avatarInstance.SetCurSp(curSp, lineup.LineupType != 0); } } await Player.SendPacket(new PacketSyncLineupNotify(lineup)); } if (teleportToAnchor) { var anchorProp = Player.SceneInstance?.GetNearestSpring(long.MaxValue); if (anchorProp != null) { var anchor = Player.SceneInstance?.FloorInfo?.GetAnchorInfo( anchorProp.PropInfo.AnchorGroupID, anchorProp.PropInfo.AnchorID ); if (anchor != null) await Player.MoveTo(anchor.ToPositionProto()); } } // call battle end battle.MonsterDropItems = dropItems; battle.BattleResult = req; Player.BattleInstance = null; await Player.MissionManager!.OnBattleFinish(req, battle); if (Player.RogueManager?.GetRogueInstance() != null) await Player.RogueManager!.GetRogueInstance()!.OnBattleEnd(battle, req); if (Player.ChallengeManager?.ChallengeInstance != null) await Player.ChallengeManager!.ChallengeInstance.OnBattleEnd(battle, req); if (Player.ActivityManager!.TrialActivityInstance != null && req.EndStatus == BattleEndStatus.BattleEndWin) await Player.ActivityManager.TrialActivityInstance.EndActivity(TrialActivityStatus.Finish); if (Player.GridFightManager?.GridFightInstance != null && battle.GridFightOptions != null) { await Player.GridFightManager!.GridFightInstance!.EndBattle(battle); } await Player.SendPacket(new PacketPVEBattleResultScRsp(req, Player, battle)); } }