style: move scene actions in PlayerInstance to another file

This commit is contained in:
Somebody
2025-07-31 17:46:54 +08:00
parent 1cf339ecc7
commit 19cd76c502
2 changed files with 506 additions and 494 deletions

View File

@@ -0,0 +1,505 @@
using EggLink.DanhengServer.Data;
using EggLink.DanhengServer.Database.Scene;
using EggLink.DanhengServer.Enums.Mission;
using EggLink.DanhengServer.Enums.Scene;
using EggLink.DanhengServer.GameServer.Game.Drop;
using EggLink.DanhengServer.GameServer.Game.Scene;
using EggLink.DanhengServer.GameServer.Game.Scene.Entity;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.Lineup;
using EggLink.DanhengServer.GameServer.Server.Packet.Send.MarkChest;
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;
namespace EggLink.DanhengServer.GameServer.Game.Player;
public partial class PlayerInstance
{
#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<EntityProp?> 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<EntityProp>(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<EntityProp>(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<EntityProp>(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<EntityProp>(prop.GroupId))
{
await p.SetState(newState);
await MissionManager!.OnPlayerInteractWithProp();
}
if (prop.Excel.ID == 104039)
{
foreach (var p in SceneInstance.GetEntitiesInGroup<EntityProp>(prop.GroupId))
{
await p.SetState(newState);
}
await MissionManager!.OnPlayerInteractWithProp();
}
if (prop.PropInfo.Name.Contains("Piece"))
{
var pieceDone = SceneInstance.GetEntitiesInGroup<EntityProp>(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<EntityProp>(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<EntityProp>(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<int, Dictionary<int, ScenePropTimelineData>>();
SceneData.PropTimelineData[Data.FloorId] = floorData;
}
if (!floorData.ContainsKey(prop.GroupId))
floorData[prop.GroupId] = new Dictionary<int, ScenePropTimelineData>();
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<bool> 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 EnterSceneByEntranceId(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;
if (planeId != Data.PlaneId || floorId != Data.FloorId || entryId != Data.EntryId || SceneInstance == null || !mapTp)
{
if (SceneInstance != null)
await SceneInstance.OnDestroy();
SceneInstance instance = new(this, plane, floorId, entryId);
InvokeOnPlayerLoadScene(this, instance);
SceneInstance = instance;
await instance.SyncLineup(true);
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;
}
if (MissionManager != null)
await MissionManager.OnPlayerChangeScene();
Connection?.SendPacket(CmdIds.SyncServerSceneChangeNotify);
if (sendPacket && notSendMove)
await SendPacket(new PacketEnterSceneByServerScNotify(SceneInstance!));
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
}

View File

@@ -5,14 +5,11 @@ using EggLink.DanhengServer.Database.Player;
using EggLink.DanhengServer.Database.Scene;
using EggLink.DanhengServer.Database.Tutorial;
using EggLink.DanhengServer.Enums.Avatar;
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;
@@ -27,16 +24,12 @@ 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;
@@ -45,7 +38,7 @@ using OfferingManager = EggLink.DanhengServer.GameServer.Game.Inventory.Offering
namespace EggLink.DanhengServer.GameServer.Game.Player;
public class PlayerInstance(PlayerData data)
public partial class PlayerInstance(PlayerData data)
{
#region Managers
@@ -482,492 +475,6 @@ public class PlayerInstance(PlayerData data)
#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<EntityProp?> 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<EntityProp>(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<EntityProp>(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<EntityProp>(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<EntityProp>(prop.GroupId))
{
await p.SetState(newState);
await MissionManager!.OnPlayerInteractWithProp();
}
if (prop.Excel.ID == 104039)
{
foreach (var p in SceneInstance.GetEntitiesInGroup<EntityProp>(prop.GroupId))
{
await p.SetState(newState);
}
await MissionManager!.OnPlayerInteractWithProp();
}
if (prop.PropInfo.Name.Contains("Piece"))
{
var pieceDone = SceneInstance.GetEntitiesInGroup<EntityProp>(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<EntityProp>(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<EntityProp>(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<int, Dictionary<int, ScenePropTimelineData>>();
SceneData.PropTimelineData[Data.FloorId] = floorData;
}
if (!floorData.ContainsKey(prop.GroupId))
floorData[prop.GroupId] = new Dictionary<int, ScenePropTimelineData>();
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<bool> 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 EnterSceneByEntranceId(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;
if (planeId != Data.PlaneId || floorId != Data.FloorId || entryId != Data.EntryId || SceneInstance == null || !mapTp)
{
if (SceneInstance != null)
await SceneInstance.OnDestroy();
SceneInstance instance = new(this, plane, floorId, entryId);
InvokeOnPlayerLoadScene(this, instance);
SceneInstance = instance;
await instance.SyncLineup(true);
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;
}
if (MissionManager != null)
await MissionManager.OnPlayerChangeScene();
Connection?.SendPacket(CmdIds.SyncServerSceneChangeNotify);
if (sendPacket && notSendMove)
await SendPacket(new PacketEnterSceneByServerScNotify(SceneInstance!));
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()