using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Data.Config.Scene; using EggLink.DanhengServer.Data.Excel; using EggLink.DanhengServer.Database.Avatar; using EggLink.DanhengServer.Enums.Scene; using EggLink.DanhengServer.GameServer.Game.Activity.Loaders; using EggLink.DanhengServer.GameServer.Game.Battle; using EggLink.DanhengServer.GameServer.Game.Challenge; using EggLink.DanhengServer.GameServer.Game.ChessRogue.Cell; using EggLink.DanhengServer.GameServer.Game.Mission; using EggLink.DanhengServer.GameServer.Game.Player; using EggLink.DanhengServer.GameServer.Game.Rogue.Scene; using EggLink.DanhengServer.GameServer.Game.RogueMagic.Scene; using EggLink.DanhengServer.GameServer.Game.RogueTourn.Scene; using EggLink.DanhengServer.GameServer.Game.Scene.Entity; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; namespace EggLink.DanhengServer.GameServer.Game.Scene; public class SceneInstance { #region Scene Details public EntityProp? GetNearestSpring(long minDistSq) { EntityProp? spring = null; long springDist = 0; foreach (var prop in HealingSprings) { var dist = Player.Data?.Pos?.GetFast2dDist(prop.Position) ?? 1000000; if (dist > minDistSq) continue; if (spring == null || dist < springDist) { spring = prop; springDist = dist; } } return spring; } #endregion #region Serialization public SceneInfo ToProto() { SceneInfo sceneInfo = new() { WorldId = (uint)(Excel.WorldID == 100 ? GameConstants.LAST_TRAIN_WORLD_ID : Excel.WorldID), GameModeType = (uint)GameModeType, PlaneId = (uint)PlaneId, FloorId = (uint)FloorId, EntryId = (uint)EntryId, SceneMissionInfo = new MissionStatusBySceneInfo(), DimensionId = (uint)(EntityLoader is StoryLineEntityLoader loader ? loader.DimensionId : 0), GameStoryLineId = (uint)(Player.StoryLineManager?.StoryLineData.CurStoryLineId ?? 0) }; var playerGroupInfo = new SceneEntityGroupInfo(); // avatar group foreach (var avatar in AvatarInfo) playerGroupInfo.EntityList.Add(avatar.Value.AvatarInfo .ToSceneEntityInfo(Player.Data.Pos, Player.Data.Rot, avatar.Value.AvatarType)); if (playerGroupInfo.EntityList.Count > 0) { if (LeaderEntityId == 0) { LeaderEntityId = AvatarInfo.Values.First().AvatarInfo.InternalEntityId; sceneInfo.LeaderEntityId = (uint)LeaderEntityId; } else { sceneInfo.LeaderEntityId = (uint)LeaderEntityId; } } sceneInfo.EntityGroupList.Add(playerGroupInfo); List groups = []; // other groups // add entities to groups foreach (var entity in Entities) { if (entity.Value.GroupID == 0) continue; if (groups.FindIndex(x => x.GroupId == entity.Value.GroupID) == -1) groups.Add(new SceneEntityGroupInfo { GroupId = (uint)entity.Value.GroupID }); groups[groups.FindIndex(x => x.GroupId == entity.Value.GroupID)].EntityList.Add(entity.Value.ToProto()); } foreach (var groupId in Groups) // Add for empty group if (groups.FindIndex(x => x.GroupId == groupId) == -1) groups.Add(new SceneEntityGroupInfo { GroupId = (uint)groupId }); foreach (var group in groups) sceneInfo.EntityGroupList.Add(group); // custom save data and floor saved data Player.SceneData!.CustomSaveData.TryGetValue(EntryId, out var data); if (data != null) foreach (var customData in data) sceneInfo.CustomDataList.Add(new CustomSaveData { GroupId = (uint)customData.Key, SaveData = customData.Value }); Player.SceneData!.FloorSavedData.TryGetValue(FloorId, out var floorData); foreach (var value in FloorInfo?.FloorSavedValue ?? []) if (floorData != null && floorData.TryGetValue(value.Name, out var v)) sceneInfo.FloorSavedData[value.Name] = v; else sceneInfo.FloorSavedData[value.Name] = value.DefaultValue; foreach (var value in floorData ?? []) sceneInfo.FloorSavedData[value.Key] = value.Value; // mission Player.MissionManager!.OnLoadScene(sceneInfo); // unlock section if (!ConfigManager.Config.ServerOption.AutoLightSection) { Player.SceneData!.UnlockSectionIdList.TryGetValue(FloorId, out var unlockSectionList); if (unlockSectionList != null) foreach (var sectionId in unlockSectionList) sceneInfo.LightenSectionList.Add((uint)sectionId); } else { for (uint i = 1; i <= 100; i++) sceneInfo.LightenSectionList.Add(i); } return sceneInfo; } #endregion #region Data public PlayerInstance Player; public MazePlaneExcel Excel; public FloorInfo? FloorInfo; public int FloorId; public int PlaneId; public int EntryId; public int LeaveEntryId; public int LastEntityId; public bool IsLoaded = false; public Dictionary AvatarInfo = []; public int LeaderEntityId; public Dictionary Entities = []; public List Groups = []; public List HealingSprings = []; public SceneEntityLoader? EntityLoader; public GameModeTypeEnum GameModeType; public EntitySummonUnit? SummonUnit; public SceneInstance(PlayerInstance player, MazePlaneExcel excel, int floorId, int entryId) { Player = player; Excel = excel; PlaneId = excel.PlaneID; FloorId = floorId; EntryId = entryId; LeaveEntryId = 0; System.Threading.Tasks.Task.Run(async () => { await SyncLineup(true, true); }).Wait(); GameData.GetFloorInfo(PlaneId, FloorId, out FloorInfo); if (FloorInfo == null) return; GameModeType = (GameModeTypeEnum)excel.PlaneType; switch (Excel.PlaneType) { case PlaneTypeEnum.Rogue: if (Player.ChessRogueManager!.RogueInstance != null) { EntityLoader = new ChessRogueEntityLoader(this); GameModeType = GameModeTypeEnum.ChessRogue; // ChessRogue } else if (Player.RogueTournManager!.RogueTournInstance != null) { EntityLoader = new RogueTournEntityLoader(this, Player); GameModeType = GameModeTypeEnum.TournRogue; // TournRogue } else if (Player.RogueMagicManager!.RogueMagicInstance != null) { EntityLoader = new RogueMagicEntityLoader(this, Player); GameModeType = GameModeTypeEnum.MagicRogue; // MagicRogue } else { EntityLoader = new RogueEntityLoader(this, Player); } break; case PlaneTypeEnum.Challenge: EntityLoader = new ChallengeEntityLoader(this, Player); break; case PlaneTypeEnum.TrialActivity: EntityLoader = new TrialActivityEntityLoader(this, Player); break; default: if (Player.StoryLineManager?.StoryLineData.CurStoryLineId != 0) EntityLoader = new StoryLineEntityLoader(this); else EntityLoader = new SceneEntityLoader(this); break; } System.Threading.Tasks.Task.Run(async () => { await EntityLoader.LoadEntity(); }).Wait(); Player.TaskManager?.SceneTaskTrigger.TriggerFloor(PlaneId, FloorId); } #endregion #region Scene Actions public async ValueTask SyncLineup(bool notSendPacket = false, bool forceSetEntityId = false) { var oldAvatarInfo = AvatarInfo.Values.ToList(); AvatarInfo.Clear(); var sendPacket = false; var addAvatar = new List(); var removeAvatar = new List(); foreach (var avatar in Player.LineupManager?.GetAvatarsFromCurTeam() ?? []) { if (forceSetEntityId && avatar.AvatarInfo.InternalEntityId != 0) { removeAvatar.Add(new AvatarSceneInfo(new AvatarInfo(), AvatarType.AvatarFormalType, Player)); avatar.AvatarInfo.SetEntityId(Player.Uid, 0); sendPacket = true; } var avatarInstance = oldAvatarInfo.Find(x => x.AvatarInfo.BaseAvatarId == avatar.AvatarInfo.BaseAvatarId); if (avatarInstance == null) { if (avatar.AvatarInfo.InternalEntityId == 0) avatar.AvatarInfo.SetEntityId(Player.Uid, ++LastEntityId); addAvatar.Add(avatar); AvatarInfo.Add(avatar.AvatarInfo.InternalEntityId, avatar); sendPacket = true; } else { AvatarInfo.Add(avatarInstance.AvatarInfo.InternalEntityId, avatarInstance); } } foreach (var avatar in oldAvatarInfo.Where(avatar => AvatarInfo.Values.ToList().FindIndex(x => x.AvatarInfo.BaseAvatarId == avatar.AvatarInfo.BaseAvatarId) == -1)) { removeAvatar.Add(new AvatarSceneInfo(new(), AvatarType.AvatarFormalType, Player)); avatar.AvatarInfo.SetEntityId(Player.Uid, 0); sendPacket = true; } var leaderAvatarId = Player.LineupManager?.GetCurLineup()?.LeaderAvatarId; var leaderAvatarSlot = Player.LineupManager?.GetCurLineup()?.BaseAvatars ?.FindIndex(x => x.BaseAvatarId == leaderAvatarId); if (leaderAvatarSlot == -1) leaderAvatarSlot = 0; if (AvatarInfo.Count == 0) return; var info = AvatarInfo.Values.ToList()[leaderAvatarSlot ?? 0]; LeaderEntityId = info.AvatarInfo.InternalEntityId; if (sendPacket && !notSendPacket) await Player.SendPacket(new PacketSceneGroupRefreshScNotify(Player, addAvatar, removeAvatar)); } public void SyncGroupInfo() { EntityLoader?.SyncEntity(); } #endregion #region Entity Management public async ValueTask AddEntity(IGameEntity entity) { await AddEntity(entity, IsLoaded); } public async ValueTask AddEntity(IGameEntity entity, bool sendPacket) { if (entity.EntityID != 0) return; entity.EntityID = ++LastEntityId; Entities.Add(entity.EntityID, entity); if (sendPacket) await Player.SendPacket(new PacketSceneGroupRefreshScNotify(Player, entity)); } public async ValueTask AddSummonUnitEntity(EntitySummonUnit entity) { if (entity.EntityID != 0) return; entity.EntityID = ++LastEntityId; // old foreach (var e in Entities.Values.Where(x => x is EntityMonster)) { var monster = e as EntityMonster; monster!.IsInSummonUnit = false; List buffList = [.. monster.BuffList]; foreach (var sceneBuff in buffList) if (sceneBuff.SummonUnitEntityId > 0) // clear old buff await monster.RemoveBuff(sceneBuff.BuffId); } await Player.SendPacket(new PacketSceneGroupRefreshScNotify(Player, entity, SummonUnit)); SummonUnit = entity; } public async ValueTask RemoveEntity(IGameEntity monster) { await RemoveEntity(monster, IsLoaded); } public async ValueTask RemoveEntity(IGameEntity monster, bool sendPacket) { Entities.Remove(monster.EntityID); if (sendPacket) await Player.SendPacket(new PacketSceneGroupRefreshScNotify(Player, null, monster)); } public List GetEntitiesInGroup(int groupID) { List entities = []; foreach (var entity in Entities) if (entity.Value.GroupID == groupID && entity.Value is T t) entities.Add(t); return entities; } #endregion #region SummonUnit public async ValueTask TriggerSummonUnit(string triggerName, List targetIds) { if (SummonUnit == null) return Retcode.RetSceneEntityNotExist; // check trigger var trigger = SummonUnit.TriggerList.Find(x => x.TriggerName == triggerName); if (trigger == null) return Retcode.RetSceneUseSkillFail; await Player.SendPacket( new PacketRefreshTriggerByClientScNotify(triggerName, (uint)SummonUnit.EntityID, targetIds)); // check target List targetEnter = []; List targetExit = []; foreach (var targetId in targetIds) { if (!Entities.TryGetValue((int)targetId, out var entity)) continue; EntityMonster? monster = null; EntityProp? prop = null; switch (entity) { case EntityMonster m: monster = m; break; case EntityProp p: prop = p; break; } if (monster != null) { if (!monster.IsAlive) continue; monster.IsInSummonUnit = true; targetEnter.Add(monster); } if (prop != null) targetEnter.Add(prop); } foreach (var gameEntity in Entities.Values) { if (gameEntity is not EntityMonster monster) continue; if (monster.IsInSummonUnit && !targetEnter.Contains(monster)) { monster.IsInSummonUnit = false; targetExit.Add(monster); } } if (targetEnter.Count > 0) { // enter var config = trigger.OnTriggerEnter; Player.TaskManager!.AvatarLevelTask.TriggerTasks(config, targetEnter, SummonUnit); } if (targetExit.Count <= 0) return Retcode.RetSucc; { // enter var config = trigger.OnTriggerExit; Player.TaskManager!.AvatarLevelTask.TriggerTasks(config, targetExit, SummonUnit); } return Retcode.RetSucc; } public async ValueTask ClearSummonUnit() { if (SummonUnit == null) return; await Player.SendPacket(new PacketSceneGroupRefreshScNotify(Player, null, SummonUnit)); SummonUnit = null; foreach (var entity in Entities.Values.Where(x => x is EntityMonster)) { var monster = entity as EntityMonster; monster!.IsInSummonUnit = false; List buffList = [.. monster.BuffList]; foreach (var sceneBuff in buffList) if (sceneBuff.SummonUnitEntityId > 0) // clear old buff await monster.RemoveBuff(sceneBuff.BuffId); } } public async ValueTask OnHeartBeat() { foreach (var gameEntity in Entities.Values.Clone().Where(x => x is EntityMonster).OfType()) foreach (var sceneBuff in gameEntity.BuffList.Clone().Where(sceneBuff => sceneBuff.IsExpired())) await gameEntity.RemoveBuff(sceneBuff.BuffId); foreach (var gameEntity in AvatarInfo.Values.Clone()) foreach (var sceneBuff in gameEntity.BuffList.Clone().Where(sceneBuff => sceneBuff.IsExpired())) await gameEntity.RemoveBuff(sceneBuff.BuffId); if (SummonUnit == null) return; var endTime = SummonUnit.CreateTimeMs + SummonUnit.LifeTimeMs; if (endTime < Extensions.GetUnixMs()) await ClearSummonUnit(); } #endregion } public class AvatarSceneInfo(AvatarInfo avatarInfo, AvatarType avatarType, PlayerInstance player) : IGameEntity { public AvatarInfo AvatarInfo = avatarInfo; public AvatarType AvatarType = avatarType; public List BuffList = []; public int EntityID { get => AvatarInfo.InternalEntityId; set => AvatarInfo.SetEntityId(player.Uid, player.Data.WorldLevel, value); } public int GroupID { get; set; } = 0; public async ValueTask AddBuff(SceneBuff buff) { var oldBuff = BuffList.Find(x => x.BuffId == buff.BuffId); if (oldBuff != null) { if (oldBuff.IsExpired()) { BuffList.Remove(oldBuff); BuffList.Add(buff); } else { oldBuff.CreatedTime = Extensions.GetUnixMs(); oldBuff.Duration = buff.Duration; await player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, oldBuff)); return; } } BuffList.Add(buff); await player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, buff)); } public async ValueTask ApplyBuff(BattleInstance instance) { if (BuffList.Count == 0) return; foreach (var buff in BuffList.Where(buff => !buff.IsExpired())) instance.Buffs.Add(new MazeBuff(buff)); await player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, BuffList)); BuffList.Clear(); } public SceneEntityInfo ToProto() => AvatarInfo.ToSceneEntityInfo(player.Data.Pos, player.Data.Rot, AvatarType); public async ValueTask RemoveBuff(int buffId) { var buff = BuffList.Find(x => x.BuffId == buffId); if (buff == null) return; BuffList.Remove(buff); await player.SendPacket(new PacketSyncEntityBuffChangeListScNotify(this, [buff])); } }