using EggLink.DanhengServer.Data; using EggLink.DanhengServer.Database; using EggLink.DanhengServer.Database.Avatar; using EggLink.DanhengServer.Database.Player; using EggLink.DanhengServer.Database.Scene; using EggLink.DanhengServer.Database.Tutorial; using EggLink.DanhengServer.Enums.Mission; using EggLink.DanhengServer.Enums.Scene; using EggLink.DanhengServer.GameServer.Game.Activity; using EggLink.DanhengServer.GameServer.Game.Avatar; using EggLink.DanhengServer.GameServer.Game.Battle; using EggLink.DanhengServer.GameServer.Game.Challenge; using EggLink.DanhengServer.GameServer.Game.ChessRogue; using EggLink.DanhengServer.GameServer.Game.Drop; using EggLink.DanhengServer.GameServer.Game.Friend; using EggLink.DanhengServer.GameServer.Game.Gacha; using EggLink.DanhengServer.GameServer.Game.Inventory; using EggLink.DanhengServer.GameServer.Game.Lineup; using EggLink.DanhengServer.GameServer.Game.Mail; using EggLink.DanhengServer.GameServer.Game.Message; using EggLink.DanhengServer.GameServer.Game.Mission; using EggLink.DanhengServer.GameServer.Game.Quest; using EggLink.DanhengServer.GameServer.Game.Raid; using EggLink.DanhengServer.GameServer.Game.Rogue; using EggLink.DanhengServer.GameServer.Game.RogueMagic; using EggLink.DanhengServer.GameServer.Game.RogueTourn; using EggLink.DanhengServer.GameServer.Game.Scene; using EggLink.DanhengServer.GameServer.Game.Scene.Entity; using EggLink.DanhengServer.GameServer.Game.Shop; using EggLink.DanhengServer.GameServer.Game.Task; using EggLink.DanhengServer.GameServer.Game.TrainParty; using EggLink.DanhengServer.GameServer.Server; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lineup; using EggLink.DanhengServer.GameServer.Server.Packet.Send.MarkChest; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Player; using EggLink.DanhengServer.GameServer.Server.Packet.Send.PlayerSync; using EggLink.DanhengServer.GameServer.Server.Packet.Send.Scene; using EggLink.DanhengServer.Kcp; using EggLink.DanhengServer.Proto; using EggLink.DanhengServer.Util; using static EggLink.DanhengServer.GameServer.Plugin.Event.PluginEvent; using OfferingManager = EggLink.DanhengServer.GameServer.Game.Inventory.OfferingManager; namespace EggLink.DanhengServer.GameServer.Game.Player; public class PlayerInstance(PlayerData data) { #region Managers #region Basic Managers public AvatarManager? AvatarManager { get; private set; } public LineupManager? LineupManager { get; private set; } public InventoryManager? InventoryManager { get; private set; } public BattleManager? BattleManager { get; private set; } public BattleInstance? BattleInstance { get; set; } #endregion #region Shopping Managers public GachaManager? GachaManager { get; private set; } public ShopService? ShopService { get; private set; } public OfferingManager? OfferingManager { get; private set; } #endregion #region Quest & Mission Managers public MissionManager? MissionManager { get; private set; } public QuestManager? QuestManager { get; private set; } public RaidManager? RaidManager { get; private set; } public StoryLineManager? StoryLineManager { get; private set; } public MessageManager? MessageManager { get; private set; } public TaskManager? TaskManager { get; private set; } #endregion #region Rogue Managers public RogueManager? RogueManager { get; private set; } public ChessRogueManager? ChessRogueManager { get; private set; } public RogueTournManager? RogueTournManager { get; private set; } public RogueMagicManager? RogueMagicManager { get; private set; } #endregion #region Activity Managers public ActivityManager? ActivityManager { get; private set; } public TrainPartyManager? TrainPartyManager { get; private set; } #endregion #region Others public MailManager? MailManager { get; private set; } public FriendManager? FriendManager { get; private set; } public ChallengeManager? ChallengeManager { get; private set; } #endregion #endregion #region Datas public PlayerData Data { get; set; } = data; public PlayerUnlockData? PlayerUnlockData { get; private set; } public SceneData? SceneData { get; private set; } public HeartDialData? HeartDialData { get; private set; } public TutorialData? TutorialData { get; private set; } public TutorialGuideData? TutorialGuideData { get; private set; } public BattleCollegeData? BattleCollegeData { get; private set; } public ServerPrefsData? ServerPrefsData { get; private set; } public SceneInstance? SceneInstance { get; private set; } public int Uid { get; set; } public Connection? Connection { get; set; } public bool Initialized { get; set; } public bool IsNewPlayer { get; set; } public int NextBattleId { get; set; } = 0; public int ChargerNum { get; set; } = 0; #endregion #region Initializers public PlayerInstance(int uid) : this(new PlayerData { Uid = uid }) { // new player IsNewPlayer = true; Data.NextStaminaRecover = Extensions.GetUnixSec() + GameConstants.STAMINA_RESERVE_RECOVERY_TIME; Data.Level = ConfigManager.Config.ServerOption.StartTrailblazerLevel; Data.CurrentGender = Enum.Parse(ConfigManager.Config.ServerOption.DefaultGender); DatabaseHelper.SaveInstance(Data); var t = System.Threading.Tasks.Task.Run(async () => { await InitialPlayerManager(); await AddAvatar(8000 + (int)Data.CurrentGender); await AddAvatar(1001); if (ConfigManager.Config.ServerOption.EnableMission) { await LineupManager!.AddSpecialAvatarToCurTeam(10010050); } else { await LineupManager!.AddAvatarToCurTeam(8001); await LineupManager!.AddAvatarToCurTeam(1001); } }); t.Wait(); Initialized = true; } private async ValueTask InitialPlayerManager() { Uid = Data.Uid; ActivityManager = new ActivityManager(this); AvatarManager = new AvatarManager(this); LineupManager = new LineupManager(this); InventoryManager = new InventoryManager(this); BattleManager = new BattleManager(this); MissionManager = new MissionManager(this); GachaManager = new GachaManager(this); MessageManager = new MessageManager(this); MailManager = new MailManager(this); FriendManager = new FriendManager(this); RogueManager = new RogueManager(this); ShopService = new ShopService(this); ChessRogueManager = new ChessRogueManager(this); RogueTournManager = new RogueTournManager(this); RogueMagicManager = new RogueMagicManager(this); ChallengeManager = new ChallengeManager(this); TaskManager = new TaskManager(this); RaidManager = new RaidManager(this); StoryLineManager = new StoryLineManager(this); QuestManager = new QuestManager(this); TrainPartyManager = new TrainPartyManager(this); OfferingManager = new OfferingManager(this); PlayerUnlockData = InitializeDatabase(); SceneData = InitializeDatabase(); HeartDialData = InitializeDatabase(); TutorialData = InitializeDatabase(); TutorialGuideData = InitializeDatabase(); ServerPrefsData = InitializeDatabase(); BattleCollegeData = InitializeDatabase(); Data.LastActiveTime = Extensions.GetUnixSec(); if (LineupManager!.GetCurLineup() != null) // null -> ignore(new player) { if (LineupManager!.GetCurLineup()!.IsExtraLineup() && RaidManager!.RaidData.CurRaidId == 0 && StoryLineManager!.StoryLineData.CurStoryLineId == 0 && ChallengeManager!.ChallengeInstance == null) // do not use extra lineup when login { LineupManager!.SetExtraLineup(ExtraLineupType.LineupNone, []); if (LineupManager!.GetCurLineup()!.IsExtraLineup()) await LineupManager!.SetCurLineup(0); } foreach (var lineup in LineupManager.LineupData.Lineups) { if (lineup.Value.BaseAvatars!.Count >= 5) lineup.Value.BaseAvatars = lineup.Value.BaseAvatars.GetRange(0, 4); foreach (var avatar in lineup.Value.BaseAvatars!) if (avatar.BaseAvatarId > 10000) { GameData.SpecialAvatarData.TryGetValue(avatar.BaseAvatarId, out var special); if (special != null) { avatar.SpecialAvatarId = special.GetId(); avatar.BaseAvatarId = special.AvatarID; } else { GameData.SpecialAvatarData.TryGetValue(avatar.BaseAvatarId * 10 + Data.WorldLevel, out special); if (special != null) { avatar.SpecialAvatarId = special.GetId(); avatar.BaseAvatarId = special.AvatarID; } } } } foreach (var avatar in LineupManager.GetCurLineup()!.BaseAvatars!) { var avatarData = AvatarManager.GetAvatar(avatar.BaseAvatarId); if (avatarData is { CurrentHp: <= 0 }) // revive avatarData.CurrentHp = 2000; } } await LoadScene(Data.PlaneId, Data.FloorId, Data.EntryId, Data.Pos!, Data.Rot!, false); if (SceneInstance == null) await EnterScene(2000101, 0, false); if (ConfigManager.Config.ServerOption.EnableMission) await MissionManager!.AcceptMainMissionByCondition(); await QuestManager!.AcceptQuestByCondition(); } public T InitializeDatabase() where T : BaseDatabaseDataHelper, new() { var instance = DatabaseHelper.Instance?.GetInstanceOrCreateNew(Uid); return instance!; } #endregion #region Network public async ValueTask OnGetToken() { if (!Initialized) await InitialPlayerManager(); } public async ValueTask OnLogin() { await SendPacket(new PacketStaminaInfoScNotify(this)); ChallengeManager?.ResurrectInstance(); if (StoryLineManager != null) await StoryLineManager.OnLogin(); if (RaidManager != null) await RaidManager.OnLogin(); InvokeOnPlayerLogin(this); } public void OnLogoutAsync() { InvokeOnPlayerLogout(this); } public async ValueTask SendPacket(BasePacket packet) { if (Connection?.IsOnline == true) await Connection.SendPacket(packet); } #endregion #region Actions public async ValueTask ChangeAvatarPathType(int baseAvatarId, MultiPathAvatarType type) { var avatar = AvatarManager!.GetAvatar(baseAvatarId)!; avatar.CurAvatarId = (int)type; avatar.SetCurSp(0, LineupManager!.GetCurLineup()!.IsExtraLineup()); await SendPacket(new PacketAvatarPathChangedNotify((uint)avatar.BaseAvatarId, type)); await SendPacket(new PacketPlayerSyncScNotify(avatar)); } public async ValueTask ChangeAvatarSkin(int avatarId, int skinId) { PlayerUnlockData!.Skins.TryGetValue(avatarId, out var skins); if (skins != null && (skins.Contains(skinId) || skinId == 0)) { var avatar = AvatarManager!.GetAvatar(avatarId)!; avatar.GetPathInfo(avatarId)!.Skin = skinId; await SendPacket(new PacketPlayerSyncScNotify(avatar)); } } public async ValueTask MarkAvatar(int avatarId, bool isMarked, bool sendPacket = true) { var avatar = AvatarManager!.GetAvatar(avatarId)!; avatar.IsMarked = isMarked; if (sendPacket) await SendPacket(new PacketPlayerSyncScNotify(avatar)); return avatar; } public async ValueTask AddAvatar(int avatarId, bool sync = true, bool notify = true) { await AvatarManager!.AddAvatar(avatarId, sync, notify); } public async ValueTask SpendStamina(int staminaCost) { Data.Stamina -= staminaCost; await SendPacket(new PacketStaminaInfoScNotify(this)); } public void OnAddExp() { GameData.PlayerLevelConfigData.TryGetValue(Data.Level, out var config); GameData.PlayerLevelConfigData.TryGetValue(Data.Level + 1, out var config2); if (config == null || config2 == null) return; var nextExp = config2.PlayerExp - config.PlayerExp; while (Data.Exp >= nextExp) { Data.Exp -= nextExp; Data.Level++; GameData.PlayerLevelConfigData.TryGetValue(Data.Level, out config); GameData.PlayerLevelConfigData.TryGetValue(Data.Level + 1, out config2); if (config == null || config2 == null) break; nextExp = config2.PlayerExp - config.PlayerExp; } OnLevelChange(); } public void OnLevelChange() { if (!ConfigManager.Config.ServerOption.AutoUpgradeWorldLevel) return; var worldLevel = 0; foreach (var level in GameConstants.UpgradeWorldLevel) if (level <= Data.Level) worldLevel++; if (Data.WorldLevel != worldLevel) Data.WorldLevel = worldLevel; } public async ValueTask OnStaminaRecover() { var sendPacket = false; while (Data.NextStaminaRecover <= Extensions.GetUnixSec()) { if (Data.Stamina >= GameConstants.MAX_STAMINA) { if (Data.StaminaReserve >= GameConstants.MAX_STAMINA_RESERVE) // needn't recover break; Data.StaminaReserve = Math.Min(Data.StaminaReserve + 1, GameConstants.MAX_STAMINA_RESERVE); } else { Data.Stamina++; } Data.NextStaminaRecover = Data.NextStaminaRecover + (Data.Stamina >= GameConstants.MAX_STAMINA ? GameConstants.STAMINA_RESERVE_RECOVERY_TIME : GameConstants.STAMINA_RECOVERY_TIME); sendPacket = true; } if (sendPacket) await SendPacket(new PacketStaminaInfoScNotify(this)); } public async ValueTask OnHeartBeat() { await OnStaminaRecover(); InvokeOnPlayerHeartBeat(this); if (MissionManager != null) await MissionManager.HandleAllFinishType(); if (SceneInstance != null) await SceneInstance.OnHeartBeat(); if (OfferingManager != null) await OfferingManager.UpdateOfferingData(); DatabaseHelper.ToSaveUidList.SafeAdd(Uid); } #endregion #region Scene Actions public async ValueTask OnMove() { if (SceneInstance != null) { var prop = SceneInstance.GetNearestSpring(25_000_000); var isInRange = prop != null; if (isInRange) if (LineupManager?.GetCurLineup()?.Heal(10000, true) == true) await SendPacket(new PacketSyncLineupNotify(LineupManager.GetCurLineup()!)); } } public async ValueTask InteractProp(int propEntityId, int interactId) { if (SceneInstance == null) return null; SceneInstance.Entities.TryGetValue(propEntityId, out var entity); if (entity is not EntityProp prop) return null; GameData.InteractConfigData.TryGetValue(interactId, out var config); if (config == null || config.SrcState != prop.State) return prop; var oldState = prop.State; await prop.SetState(config.TargetState); var newState = prop.State; await SendPacket(new PacketGroupStateChangeScNotify(Data.EntryId, prop.GroupID, prop.State)); switch (prop.Excel.PropType) { case PropTypeEnum.PROP_TREASURE_CHEST: if (oldState == PropStateEnum.ChestClosed && newState == PropStateEnum.ChestUsed) { // TODO: Filter treasure chest var items = DropService.CalculateDropsFromProp(prop.PropInfo.ChestID); await InventoryManager!.AddItems(items); await SendPacket(new PacketOpenChestScNotify(prop.PropInfo.ChestID)); var notifyMark = false; foreach (var markedChest in SceneData!.MarkedChestData.Values) { var chest = markedChest.Find(x => x.FloorId == SceneInstance.FloorId && x.GroupId == prop.GroupID && x.ConfigId == prop.PropInfo.ID); if (chest == null) continue; markedChest.Remove(chest); notifyMark = true; } if (notifyMark) await SendPacket(new PacketMarkChestChangedScNotify(this)); } break; case PropTypeEnum.PROP_DESTRUCT: if (newState == PropStateEnum.Closed) await prop.SetState(PropStateEnum.Open); break; case PropTypeEnum.PROP_MAZE_JIGSAW: switch (newState) { case PropStateEnum.Closed: { foreach (var p in SceneInstance.GetEntitiesInGroup(prop.GroupID)) { if (p.Excel.PropType == PropTypeEnum.PROP_TREASURE_CHEST) { await p.SetState(PropStateEnum.ChestClosed); } else if (p.Excel.PropType == prop.Excel.PropType) { // Skip } else { await p.SetState(PropStateEnum.Open); } } break; } case PropStateEnum.Open: { foreach (var p in SceneInstance.GetEntitiesInGroup(prop.GroupID).Where(p => p.Excel.PropType is not PropTypeEnum.PROP_TREASURE_CHEST && p.Excel.PropType != prop.Excel.PropType)) { await p.SetState(PropStateEnum.Open); } break; } } break; case PropTypeEnum.PROP_MAZE_PUZZLE: if (newState is PropStateEnum.Closed or PropStateEnum.Open) foreach (var p in SceneInstance.GetEntitiesInGroup(prop.GroupID)) { if (p.Excel.PropType == PropTypeEnum.PROP_TREASURE_CHEST) { await p.SetState(PropStateEnum.ChestClosed); } else if (p.Excel.PropType == prop.Excel.PropType) { // Skip } else { await p.SetState(PropStateEnum.Open); } await MissionManager!.OnPlayerInteractWithProp(); } break; case PropTypeEnum.PROP_ORDINARY: if (prop.PropInfo.CommonConsole) // set group foreach (var p in SceneInstance.GetEntitiesInGroup(prop.GroupID)) { await p.SetState(newState); await MissionManager!.OnPlayerInteractWithProp(); } if (prop.PropInfo.Name.Contains("Piece")) { var pieceDone = SceneInstance.GetEntitiesInGroup(prop.GroupID) .Where(p => p.PropInfo.Name.Contains("Piece")).All(p => p.State == PropStateEnum.Closed); if (pieceDone) { // set JigsawSir to open foreach (var p in SceneInstance.GetEntitiesInGroup(prop.GroupID) .Where(p => p.PropInfo.Name.Contains("JigsawSir") && p.State != PropStateEnum.Closed)) { await p.SetState(PropStateEnum.TriggerEnable); } } } break; } // for door unlock if (prop.PropInfo.UnlockDoorID.Count > 0) foreach (var p in prop.PropInfo.UnlockDoorID.SelectMany(id => SceneInstance.GetEntitiesInGroup(id.Key) .Where(p => id.Value.Contains(p.PropInfo.ID)))) { await p.SetState(PropStateEnum.Open); await MissionManager!.OnPlayerInteractWithProp(); } // for mission await MissionManager!.OnPlayerInteractWithProp(); // plane event InventoryManager!.HandlePlaneEvent(prop.PropInfo.EventID); // handle plugin event InvokeOnPlayerInteract(this, prop); var floorSavedKey = prop.PropInfo.Name.Replace("Controller_", ""); var key = $"FSV_ML{floorSavedKey}{(config.TargetState == PropStateEnum.Open ? "Started" : "Complete")}"; if (prop.Group.GroupName.Contains("JigsawPuzzle") && prop.Group.GroupName.Contains("MainLine")) { var splits = prop.Group.GroupName.Split('_'); key = $"JG_ML_{splits[3]}_Puzzle{(config.TargetState == PropStateEnum.Open ? "Started" : "Complete")}"; } if (SceneInstance?.FloorInfo?.FloorSavedValue.Find(x => x.Name == key) != null) { // should save var plane = SceneInstance.PlaneId; var floor = SceneInstance.FloorId; SceneData!.FloorSavedData.TryGetValue(floor, out var value); if (value == null) { value = []; SceneData.FloorSavedData[floor] = value; } value[key] = 1; // ParamString[2] is the key await SendPacket(new PacketUpdateFloorSavedValueNotify(key, 1, this)); TaskManager?.SceneTaskTrigger.TriggerFloor(plane, floor); MissionManager?.HandleFinishType(MissionFinishTypeEnum.FloorSavedValue); } if (prop.PropInfo.IsLevelBtn) await prop.SetState(PropStateEnum.Closed); return prop; } public async ValueTask SetPropTimeline(int propEntityId, PropTimelineInfo info) { if (SceneInstance == null) return; SceneInstance.Entities.TryGetValue(propEntityId, out var entity); if (entity is not EntityProp prop) return; var data = new ScenePropTimelineData { BoolValue = info.TimelineBoolValue, ByteValue = info.TimelineByteValue.ToBase64() }; // save to db SceneData!.PropTimelineData.TryGetValue(Data.FloorId, out var floorData); if (floorData == null) { floorData = new Dictionary>(); SceneData.PropTimelineData[Data.FloorId] = floorData; } if (!floorData.ContainsKey(prop.GroupID)) floorData[prop.GroupID] = new Dictionary(); floorData[prop.GroupID][prop.PropInfo.ID] = data; prop.PropTimelineData = data; // handle mission / quest await MissionManager!.HandleFinishType(MissionFinishTypeEnum.TimeLineSetState); await MissionManager!.HandleFinishType(MissionFinishTypeEnum.TimeLineSetStateCnt); } public ScenePropTimelineData? GetScenePropTimelineData(int floorId, int groupId, int propId) { SceneData!.PropTimelineData.TryGetValue(floorId, out var floorData); if (floorData == null) return null; floorData.TryGetValue(groupId, out var groupData); if (groupData == null) return null; groupData.TryGetValue(propId, out var data); return data; } public async ValueTask EnterScene(int entryId, int teleportId, bool sendPacket, int storyLineId = 0, bool mapTp = false) { var beforeStoryLineId = StoryLineManager?.StoryLineData.CurStoryLineId; if (storyLineId != StoryLineManager?.StoryLineData.CurStoryLineId) { if (StoryLineManager != null) await StoryLineManager.EnterStoryLine(storyLineId, entryId == 0); // entryId == 0 -> teleport mapTp = false; // do not use mapTp when enter story line } GameData.MapEntranceData.TryGetValue(entryId, out var entrance); if (entrance == null) return false; GameData.GetFloorInfo(entrance.PlaneID, entrance.FloorID, out var floorInfo); var startGroup = entrance.StartGroupID; var startAnchor = entrance.StartAnchorID; if (teleportId != 0) { floorInfo.CachedTeleports.TryGetValue(teleportId, out var teleport); if (teleport != null) { startGroup = teleport.AnchorGroupID; startAnchor = teleport.AnchorID; } } else if (startAnchor == 0) { startGroup = floorInfo.StartGroupID; startAnchor = floorInfo.StartAnchorID; } var anchor = floorInfo.GetAnchorInfo(startGroup, startAnchor); await MissionManager!.HandleFinishType(MissionFinishTypeEnum.EnterMapByEntrance, entrance); var beforeEntryId = Data.EntryId; await LoadScene(entrance.PlaneID, entrance.FloorID, entryId, anchor!.ToPositionProto(), anchor.ToRotationProto(), sendPacket, mapTp); var afterEntryId = Data.EntryId; return beforeEntryId != afterEntryId || beforeStoryLineId != storyLineId; // return true if entryId changed or story line changed } public async ValueTask EnterMissionScene(int entranceId, int anchorGroupId, int anchorId, bool sendPacket) { GameData.MapEntranceData.TryGetValue(entranceId, out var entrance); if (entrance == null) return; GameData.GetFloorInfo(entrance.PlaneID, entrance.FloorID, out var floorInfo); var startGroup = anchorGroupId == 0 ? entrance.StartGroupID : anchorGroupId; var startAnchor = anchorId == 0 ? entrance.StartAnchorID : anchorId; if (startAnchor == 0) { startGroup = floorInfo.StartGroupID; startAnchor = floorInfo.StartAnchorID; } var anchor = floorInfo.GetAnchorInfo(startGroup, startAnchor); await LoadScene(entrance.PlaneID, entrance.FloorID, entranceId, anchor!.ToPositionProto(), anchor.ToRotationProto(), sendPacket); } public async ValueTask MoveTo(Position position) { Data.Pos = position; await SendPacket(new PacketSceneEntityMoveScNotify(this)); } public void MoveTo(EntityMotion motion) { Data.Pos = motion.Motion.Pos.ToPosition(); Data.Rot = motion.Motion.Rot.ToPosition(); } public async ValueTask MoveTo(Position pos, Position rot) { Data.Pos = pos; Data.Rot = rot; await SendPacket(new PacketSceneEntityMoveScNotify(this)); } public async ValueTask LoadScene(int planeId, int floorId, int entryId, Position pos, Position rot, bool sendPacket, bool mapTp = false) { GameData.MazePlaneData.TryGetValue(planeId, out var plane); if (plane == null) return; if (plane.PlaneType == PlaneTypeEnum.Rogue && RogueManager!.GetRogueInstance() == null) { await EnterScene(801120102, 0, sendPacket); return; } if (plane.PlaneType == PlaneTypeEnum.Raid && RaidManager!.RaidData.CurRaidId == 0) { await EnterScene(2000101, 0, sendPacket); return; } if (plane.PlaneType == PlaneTypeEnum.Challenge && ChallengeManager!.ChallengeInstance == null) { await EnterScene(100000103, 0, sendPacket); return; } // TODO: Sanify check Data.Pos = pos; Data.Rot = rot; var notSendMove = true; SceneInstance instance = new(this, plane, floorId, entryId); InvokeOnPlayerLoadScene(this, instance); if (planeId != Data.PlaneId || floorId != Data.FloorId || entryId != Data.EntryId) { Data.PlaneId = planeId; Data.FloorId = floorId; Data.EntryId = entryId; } else if (StoryLineManager?.StoryLineData.CurStoryLineId == 0 && mapTp) // only send move packet when not in story line and mapTp { notSendMove = false; } SceneInstance = instance; if (MissionManager != null) await MissionManager.OnPlayerChangeScene(); Connection?.SendPacket(CmdIds.SyncServerSceneChangeNotify); if (sendPacket && notSendMove) await SendPacket(new PacketEnterSceneByServerScNotify(instance)); else if (sendPacket && !notSendMove) // send move packet await SendPacket(new PacketSceneEntityMoveScNotify(this)); if (MissionManager != null) { await MissionManager.HandleFinishType(MissionFinishTypeEnum.EnterFloor); await MissionManager.HandleFinishType(MissionFinishTypeEnum.EnterPlane); await MissionManager.HandleFinishType(MissionFinishTypeEnum.NotInFloor); await MissionManager.HandleFinishType(MissionFinishTypeEnum.NotInPlane); } } public ScenePropData? GetScenePropData(int floorId, int groupId, int propId) { if (SceneData != null) if (SceneData.ScenePropData.TryGetValue(floorId, out var floorData)) if (floorData.TryGetValue(groupId, out var groupData)) { var propData = groupData.Find(x => x.PropId == propId); return propData; } return null; } public void SetScenePropData(int floorId, int groupId, int propId, PropStateEnum state) { if (SceneData != null) { if (!SceneData.ScenePropData.TryGetValue(floorId, out var floorData)) { floorData = []; SceneData.ScenePropData.Add(floorId, floorData); } if (!floorData.TryGetValue(groupId, out var groupData)) { groupData = []; floorData.Add(groupId, groupData); } var propData = groupData.Find(x => x.PropId == propId); // find prop data if (propData == null) { propData = new ScenePropData { PropId = propId, State = state }; groupData.Add(propData); } else { propData.State = state; } } } public void EnterSection(int sectionId) { if (SceneInstance != null) { SceneData!.UnlockSectionIdList.TryGetValue(SceneInstance.FloorId, out var unlockList); if (unlockList == null) { unlockList = [sectionId]; SceneData.UnlockSectionIdList.Add(SceneInstance.FloorId, unlockList); } else { SceneData.UnlockSectionIdList[SceneInstance.FloorId].Add(sectionId); } } } public void SetCustomSaveData(int entryId, int groupId, string data) { if (SceneData != null) { if (!SceneData.CustomSaveData.TryGetValue(entryId, out var entryData)) { entryData = []; SceneData.CustomSaveData.Add(entryId, entryData); } entryData[groupId] = data; } } public async ValueTask ForceQuitBattle() { if (BattleInstance != null) { BattleInstance = null; await Connection!.SendPacket(CmdIds.QuitBattleScNotify); } } #endregion #region Serialization public PlayerBasicInfo ToProto() { return Data.ToProto(); } #endregion }