private static void AddMobSpawn(SpawnMetadata mobSpawnDataBox, MapEntityMetadata metadata, IMS2RegionBoxSpawn boxSpawn) { int mobNpcCountBox = mobSpawnDataBox?.Population ?? 6; int mobSpawnRadiusBox = 150; // TODO: This previously relied in "NpcList" to be set. NpcList is impossible to be set on // MS2RegionSpawn, it's only set for SpawnPointNPC. List <int> mobNpcListBox = new() { 21000025 // Placeholder }; metadata.MobSpawns.Add(new(boxSpawn.SpawnPointID, CoordS.FromVector3(boxSpawn.Position), mobNpcCountBox, mobNpcListBox, mobSpawnRadiusBox, mobSpawnDataBox)); // "QR_10000264_" is Quest Reward Chest? This is tied to a MS2TriggerAgent making this object appear. }
public static List <MapEntityMetadata> Parse(MemoryMappedFile m2dFile, IEnumerable <PackFileEntry> entries) { List <MapEntityMetadata> entities = new List <MapEntityMetadata>(); Dictionary <string, string> maps = new Dictionary <string, string>(); foreach (PackFileEntry entry in entries) { if (!entry.Name.StartsWith("xblock/")) { continue; } string mapIdStr = Regex.Match(entry.Name, @"\d{8}").Value; if (string.IsNullOrEmpty(mapIdStr)) { continue; } int mapId = int.Parse(mapIdStr); if (maps.ContainsKey(mapIdStr)) { Console.WriteLine($"Duplicate {entry.Name} was already added as {maps[mapIdStr]}"); continue; } maps.Add(mapIdStr, entry.Name); MapEntityMetadata metadata = new MapEntityMetadata(mapId); XmlDocument document = m2dFile.GetDocument(entry.FileHeader); XmlNodeList mapEntities = document.SelectNodes("/game/entitySet/entity"); foreach (XmlNode node in mapEntities) { if (node.Attributes["name"].Value.Contains("SpawnPointPC")) { XmlNode playerCoord = node.SelectSingleNode("property[@name='Position']"); XmlNode playerRotation = node.SelectSingleNode("property[@name='Rotation']"); string playerPositionValue = playerCoord?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string playerRotationValue = playerRotation?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; metadata.PlayerSpawns.Add(new MapPlayerSpawn(ParseCoord(playerPositionValue), ParseCoord(playerRotationValue))); } } //XmlNodeList nodes = document.SelectNodes("/game/entitySet/entity/property[@name='NpcList']"); XmlNodeList nodes = document.SelectNodes("/game/entitySet/entity/property"); foreach (XmlNode node in nodes) { if (node.Attributes["name"].Value == "NpcList") { if (node.FirstChild != null) { XmlNode parent = node.ParentNode; try { XmlNode coordNode = parent.SelectSingleNode("property[@name='Position']"); XmlNode rotationNode = parent.SelectSingleNode("property[@name='Rotation']"); int npcId = int.Parse(node.FirstChild.Attributes["index"].Value); string positionValue = coordNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string rotationValue = rotationNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; CoordS position = ParseCoord(positionValue); CoordS rotation = ParseCoord(rotationValue); metadata.Npcs.Add(new MapNpc(npcId, position, rotation)); } catch (Exception ex) { Console.WriteLine(ex); Console.WriteLine(mapId); Console.WriteLine("Failed NPC " + parent.InnerXml); } } } else if (node.Attributes["name"].Value == "PortalID") { XmlNode parent = node.ParentNode; try { XmlNode visibleNode = parent.SelectSingleNode("property[@name='IsVisible']"); XmlNode enabledNode = parent.SelectSingleNode("property[@name='PortalEnable']"); XmlNode minimapVisibleNode = parent.SelectSingleNode("property[@name='MinimapIconVisible']"); XmlNode targetNode = parent.SelectSingleNode("property[@name='TargetFieldSN']"); XmlNode coordNode = parent.SelectSingleNode("property[@name='Position']"); XmlNode rotationNode = parent.SelectSingleNode("property[@name='Rotation']"); if (targetNode == null) { continue; } if (!bool.TryParse(visibleNode?.FirstChild.Attributes["value"].Value, out bool visibleValue)) { visibleValue = true; } if (!bool.TryParse(enabledNode?.FirstChild.Attributes["value"].Value, out bool enabledValue)) { enabledValue = true; } if (!bool.TryParse(minimapVisibleNode?.FirstChild.Attributes["value"].Value, out bool minimapVisibleValue)) { minimapVisibleValue = true; } int target = int.Parse(targetNode.FirstChild.Attributes["value"].Value); int portalId = int.Parse(node.FirstChild.Attributes["value"].Value); string positionValue = coordNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string rotationValue = rotationNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; MapPortalFlag flags = visibleValue ? MapPortalFlag.Visible : MapPortalFlag.None; flags |= enabledValue ? MapPortalFlag.Enabled : MapPortalFlag.None; flags |= minimapVisibleValue ? MapPortalFlag.MinimapVisible : MapPortalFlag.None; CoordS position = ParseCoord(positionValue); CoordS rotation = ParseCoord(rotationValue); metadata.Portals.Add(new MapPortal(portalId, flags, target, position, rotation)); } catch (Exception ex) { Console.WriteLine(ex); Console.WriteLine(mapId); Console.WriteLine("Failed NPC " + parent.InnerXml); } } } // No data on this map if (metadata.Npcs.Count == 0 && metadata.Portals.Count == 0 && metadata.PlayerSpawns.Count == 0) { continue; } entities.Add(metadata); } return(entities); }
public static List <MapEntityMetadata> Parse(MemoryMappedFile m2dFile, IEnumerable <PackFileEntry> entries) { // Iterate over preset objects to later reference while iterating over exported maps Dictionary <string, string> mapObjects = new Dictionary <string, string>(); foreach (PackFileEntry entry in entries) { if (!entry.Name.StartsWith("flat/presets/presets object/")) { continue; } // Check if file is valid string objStr = entry.Name.ToLower(); if (string.IsNullOrEmpty(objStr)) { continue; } if (mapObjects.ContainsKey(objStr)) { Console.WriteLine($"Duplicate {entry.Name} was already added as {mapObjects[objStr]}"); continue; } // Parse XML XmlDocument document = m2dFile.GetDocument(entry.FileHeader); XmlElement root = document.DocumentElement; XmlNodeList objProperties = document.SelectNodes("/model/property"); string objectName = root.Attributes["name"].Value.ToLower(); foreach (XmlNode node in objProperties) { // Storing only weapon item code for now, but there are other uses if (node.Attributes["name"].Value.Contains("ObjectWeaponItemCode")) { string weaponId = node?.FirstChild.Attributes["value"].Value ?? "0"; if (!weaponId.Equals("0")) { mapObjects.Add(objectName, weaponId); } } } } // Iterate over map xblocks List <MapEntityMetadata> entities = new List <MapEntityMetadata>(); Dictionary <string, string> maps = new Dictionary <string, string>(); foreach (PackFileEntry entry in entries) { if (!entry.Name.StartsWith("xblock/")) { continue; } string mapIdStr = Regex.Match(entry.Name, @"\d{8}").Value; if (string.IsNullOrEmpty(mapIdStr)) { continue; } int mapId = int.Parse(mapIdStr); if (maps.ContainsKey(mapIdStr)) { Console.WriteLine($"Duplicate {entry.Name} was already added as {maps[mapIdStr]}"); continue; } maps.Add(mapIdStr, entry.Name); MapEntityMetadata metadata = new MapEntityMetadata(mapId); XmlDocument document = m2dFile.GetDocument(entry.FileHeader); XmlNodeList mapEntities = document.SelectNodes("/game/entitySet/entity"); foreach (XmlNode node in mapEntities) { string modelName = node.Attributes["modelName"].Value.ToLower(); if (node.Attributes["name"].Value.Contains("SpawnPointPC")) { XmlNode playerCoord = node.SelectSingleNode("property[@name='Position']"); XmlNode playerRotation = node.SelectSingleNode("property[@name='Rotation']"); string playerPositionValue = playerCoord?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string playerRotationValue = playerRotation?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; metadata.PlayerSpawns.Add(new MapPlayerSpawn(ParseCoord(playerPositionValue), ParseCoord(playerRotationValue))); } else if (mapObjects.ContainsKey(modelName)) { string nameCoord = node.Attributes["name"].Value.ToLower(); Match coordMatch = Regex.Match(nameCoord, @"[\-]?\d+[,]\s[\-]?\d+[,]\s[\-]?\d+"); if (!coordMatch.Success) { continue; } CoordB coord = CoordB.Parse(coordMatch.Value, ", "); metadata.Objects.Add(new MapObject(coord, int.Parse(mapObjects[modelName]))); } } //XmlNodeList nodes = document.SelectNodes("/game/entitySet/entity/property[@name='NpcList']"); XmlNodeList nodes = document.SelectNodes("/game/entitySet/entity/property"); foreach (XmlNode node in nodes) { if (node.Attributes["name"].Value == "NpcList") { if (node.FirstChild != null) { XmlNode parent = node.ParentNode; try { XmlNode coordNode = parent.SelectSingleNode("property[@name='Position']"); XmlNode rotationNode = parent.SelectSingleNode("property[@name='Rotation']"); int npcId = int.Parse(node.FirstChild.Attributes["index"].Value); string positionValue = coordNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string rotationValue = rotationNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; CoordS position = ParseCoord(positionValue); CoordS rotation = ParseCoord(rotationValue); metadata.Npcs.Add(new MapNpc(npcId, position, rotation)); } catch (Exception ex) { Console.WriteLine(ex); Console.WriteLine(mapId); Console.WriteLine("Failed NPC " + parent.InnerXml); } } } else if (node.Attributes["name"].Value == "PortalID") { XmlNode parent = node.ParentNode; try { XmlNode visibleNode = parent.SelectSingleNode("property[@name='IsVisible']"); XmlNode enabledNode = parent.SelectSingleNode("property[@name='PortalEnable']"); XmlNode minimapVisibleNode = parent.SelectSingleNode("property[@name='MinimapIconVisible']"); XmlNode targetNode = parent.SelectSingleNode("property[@name='TargetFieldSN']"); XmlNode coordNode = parent.SelectSingleNode("property[@name='Position']"); XmlNode rotationNode = parent.SelectSingleNode("property[@name='Rotation']"); if (targetNode == null) { continue; } if (!bool.TryParse(visibleNode?.FirstChild.Attributes["value"].Value, out bool visibleValue)) { visibleValue = true; } if (!bool.TryParse(enabledNode?.FirstChild.Attributes["value"].Value, out bool enabledValue)) { enabledValue = true; } if (!bool.TryParse(minimapVisibleNode?.FirstChild.Attributes["value"].Value, out bool minimapVisibleValue)) { minimapVisibleValue = true; } int target = int.Parse(targetNode.FirstChild.Attributes["value"].Value); int portalId = int.Parse(node.FirstChild.Attributes["value"].Value); string positionValue = coordNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string rotationValue = rotationNode?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; MapPortalFlag flags = visibleValue ? MapPortalFlag.Visible : MapPortalFlag.None; flags |= enabledValue ? MapPortalFlag.Enabled : MapPortalFlag.None; flags |= minimapVisibleValue ? MapPortalFlag.MinimapVisible : MapPortalFlag.None; CoordS position = ParseCoord(positionValue); CoordS rotation = ParseCoord(rotationValue); metadata.Portals.Add(new MapPortal(portalId, flags, target, position, rotation)); } catch (Exception ex) { Console.WriteLine(ex); Console.WriteLine(mapId); Console.WriteLine("Failed NPC " + parent.InnerXml); } } } // No data on this map if (metadata.Npcs.Count == 0 && metadata.Portals.Count == 0 && metadata.PlayerSpawns.Count == 0) { continue; } entities.Add(metadata); } return(entities); }
private void BuildMetadata(string xblock, IEnumerable <IMapEntity> mapEntities) { if (xblock.EndsWith("_cn") || xblock.EndsWith("_jp") || xblock.EndsWith("_kr")) { return; } MapMetadata mapMetadata = MapMetadatas.FirstOrDefault(x => x.XBlockName == xblock); if (mapMetadata is null) { return; } MapEntityMetadata mapEntity = mapMetadata.Entities; List <IMS2WayPoint> tempWaypoints = new(); List <IMS2PatrolData> tempPatrolData = new(); foreach (IMapEntity entity in mapEntities) { if (entity is IMS2CubeProp cube) { MapBlock mapBlock = new() { Coord = CoordS.FromVector3(cube.Position), Type = cube.CubeType, SaleableGroup = cube.CubeSalableGroup, Attribute = cube.MapAttribute }; mapMetadata.Blocks.TryAdd(mapBlock.Coord, mapBlock); } switch (entity) { case IMS2Bounding bounding: if (bounding.EntityName.EndsWith("0") && mapEntity.BoundingBox0.Equals(CoordS.From(0, 0, 0))) { mapEntity.BoundingBox0 = CoordS.FromVector3(bounding.Position); } else if (bounding.EntityName.EndsWith("1") && mapEntity.BoundingBox1.Equals(CoordS.From(0, 0, 0))) { mapEntity.BoundingBox1 = CoordS.FromVector3(bounding.Position); } break; case IMS2PatrolData patrolData: tempPatrolData.Add(patrolData); break; case IMS2WayPoint wayPoint: tempWaypoints.Add(wayPoint); break; // TODO: This can probably be more generally handled as IMS2RegionSkill case IMS2HealingRegionSkillSound healingRegion: if (healingRegion.Position == default) { continue; } mapEntity.HealingSpot.Add(CoordS.FromVector3(healingRegion.Position)); break; case IMS2InteractObject interact: InteractObjectType type = GetInteractObjectType(interact.interactID); if (type == InteractObjectType.None) { continue; } switch (interact) { case IMS2SimpleUiObject uiObject: mapEntity.InteractObjects.Add(new(uiObject.EntityId, uiObject.interactID, uiObject.Enabled, type, CoordF.FromVector3(uiObject.Position), CoordF.FromVector3(uiObject.Rotation))); break; case IMS2InteractMesh interactMesh: mapEntity.InteractObjects.Add(new(interactMesh.EntityId, interactMesh.interactID, interactMesh.IsVisible, type, CoordF.FromVector3(interactMesh.Position), CoordF.FromVector3(interactMesh.Rotation))); break; case IMS2Telescope telescope: mapEntity.InteractObjects.Add(new(telescope.EntityId, telescope.interactID, telescope.Enabled, type, CoordF.FromVector3(telescope.Position), CoordF.FromVector3(telescope.Rotation))); break; case IMS2InteractActor interactActor: mapEntity.InteractObjects.Add(new(interactActor.EntityId, interactActor.interactID, interactActor.IsVisible, type , CoordF.FromVector3(interactActor.Position), CoordF.FromVector3(interactActor.Rotation))); break; case IMS2InteractDisplay interactDisplay: mapEntity.InteractObjects.Add(new(interactDisplay.EntityId, interactDisplay.interactID, interactDisplay.IsVisible, type, CoordF.FromVector3(interactDisplay.Position), CoordF.FromVector3(interactDisplay.Rotation))); break; } break; case ISpawnPoint spawn: switch (spawn) { // TODO: Parse "value" from NPCList. case IEventSpawnPointNPC eventSpawnNpc: // trigger mob/npc spawns List <string> npcIds = new(); npcIds.AddRange(eventSpawnNpc.NpcList.Keys); mapEntity.EventNpcSpawnPoints.Add(new(eventSpawnNpc.SpawnPointID, eventSpawnNpc.NpcCount, npcIds, eventSpawnNpc.SpawnAnimation, eventSpawnNpc.SpawnRadius, CoordF.FromVector3(eventSpawnNpc.Position), CoordF.FromVector3(eventSpawnNpc.Rotation))); break; case ISpawnPointPC pcSpawn: mapEntity.PlayerSpawns.Add(new(CoordS.FromVector3(pcSpawn.Position), CoordS.FromVector3(pcSpawn.Rotation))); break; case ISpawnPointNPC npcSpawn: // These tend to be vendors, shops, etc. // If the name tag begins with SpawnPointNPC, I think these are mob spawn locations. Skipping these. string npcIdStr = npcSpawn.NpcList.FirstOrDefault().Key ?? "0"; if (npcIdStr == "0" || !int.TryParse(npcIdStr, out int npcId)) { continue; } MapNpc npc = new(npcId, npcSpawn.ModelName, npcSpawn.EntityName, CoordS.FromVector3(npcSpawn.Position), CoordS.FromVector3(npcSpawn.Rotation), npcSpawn.IsSpawnOnFieldCreate, npcSpawn.dayDie, npcSpawn.nightDie, npcSpawn.PatrolData); // Parse some additional flat supplemented data about this NPC. mapEntity.Npcs.Add(npc); break; } break; case IMS2RegionSpawnBase spawnBase: SpawnMetadata spawnData = SpawnTagMap.ContainsKey(mapMetadata.Id) && SpawnTagMap[mapMetadata.Id].ContainsKey(spawnBase.SpawnPointID) ? SpawnTagMap[mapMetadata.Id][spawnBase.SpawnPointID] : null; switch (spawnBase) { case IMS2RegionBoxSpawn boxSpawn: if (boxSpawn.ModelName.Contains("chest", StringComparison.CurrentCultureIgnoreCase)) { mapEntity.MapChests.Add(new() { IsGolden = boxSpawn.ModelName.Contains("rare", StringComparison.CurrentCultureIgnoreCase), Position = CoordF.FromVector3(boxSpawn.Position), Rotation = CoordF.FromVector3(boxSpawn.Rotation) }); continue; } AddMobSpawn(spawnData, mapEntity, boxSpawn); break; case IMS2RegionSpawn regionSpawn: int mobNpcCount = spawnData?.Population ?? 6; // TODO: This previously relied in "NpcList" to be set. NpcList is impossible to be set on // MS2RegionSpawn, it's only set for SpawnPointNPC. List <int> mobNpcList = new() { 21000025 // Placeholder }; mapEntity.MobSpawns.Add(new(regionSpawn.SpawnPointID, CoordS.FromVector3(regionSpawn.Position), mobNpcCount, mobNpcList, (int)regionSpawn.SpawnRadius, spawnData)); break; } break; case IPortal portal: mapEntity.Portals.Add(new(portal.PortalID, portal.ModelName, portal.PortalEnable, portal.IsVisible, portal.MinimapIconVisible, portal.TargetFieldSN, CoordS.FromVector3(portal.Position), CoordS.FromVector3(portal.Rotation), portal.TargetPortalID, (PortalTypes)portal.PortalType)); break; case IMS2Breakable breakable: switch (breakable) { case IMS2BreakableActor actor: mapEntity.BreakableActors.Add(new(actor.EntityId, actor.Enabled, actor.hideTimer, actor.resetTimer)); break; case IMS2BreakableNIF nif: mapEntity.BreakableNifs.Add(new(nif.EntityId, nif.Enabled, (int)nif.TriggerBreakableID, nif.hideTimer, nif.resetTimer)); break; } break; case IMS2TriggerObject triggerObject: switch (triggerObject) { case IMS2TriggerMesh triggerMesh: mapEntity.TriggerMeshes.Add(new(triggerMesh.TriggerObjectID, triggerMesh.IsVisible)); break; case IMS2TriggerEffect triggerEffect: mapEntity.TriggerEffects.Add(new(triggerEffect.TriggerObjectID, triggerEffect.IsVisible)); break; case IMS2TriggerCamera triggerCamera: mapEntity.TriggerCameras.Add(new(triggerCamera.TriggerObjectID, triggerCamera.Enabled)); break; case IMS2TriggerBox triggerBox: mapEntity.TriggerBoxes.Add(new(triggerBox.TriggerObjectID, CoordF.FromVector3(triggerBox.Position), CoordF.FromVector3(triggerBox.ShapeDimensions))); break; case IMS2TriggerLadder triggerLadder: // TODO: Find which parameters correspond to animationeffect (bool) and animation delay (int?) mapEntity.TriggerLadders.Add(new(triggerLadder.TriggerObjectID, triggerLadder.IsVisible)); break; case IMS2TriggerRope triggerRope: // TODO: Find which parameters correspond to animationeffect (bool) and animation delay (int?) mapEntity.TriggerRopes.Add(new(triggerRope.TriggerObjectID, triggerRope.IsVisible)); break; case IMS2TriggerPortal triggerPortal: mapEntity.Portals.Add(new(triggerPortal.PortalID, triggerPortal.ModelName, triggerPortal.PortalEnable, triggerPortal.IsVisible, triggerPortal.MinimapIconVisible, triggerPortal.TargetFieldSN, CoordS.FromVector3(triggerPortal.Position), CoordS.FromVector3(triggerPortal.Rotation), triggerPortal.TargetPortalID, (PortalTypes)triggerPortal.PortalType, triggerPortal.TriggerObjectID)); break; case IMS2TriggerActor triggerActor: mapEntity.TriggerActors.Add(new(triggerActor.TriggerObjectID, triggerActor.IsVisible, triggerActor.InitialSequence)); break; case IMS2TriggerCube triggerCube: mapEntity.TriggerCubes.Add(new(triggerCube.TriggerObjectID, triggerCube.IsVisible)); break; case IMS2TriggerSound triggerSound: mapEntity.TriggerSounds.Add(new(triggerSound.TriggerObjectID, triggerSound.Enabled)); break; case IMS2TriggerSkill triggerSkill: mapEntity.TriggerSkills.Add(new(triggerSkill.TriggerObjectID, triggerSkill.skillID, (short)triggerSkill.skillLevel, (byte)triggerSkill.count, CoordF.FromVector3(triggerSkill.Position))); break; } break; case IMS2LiftableTargetBox liftableTargetBox: mapEntity.LiftableTargets.Add(new(liftableTargetBox.liftableTarget, CoordF.FromVector3(liftableTargetBox.Position), CoordF.FromVector3(liftableTargetBox.ShapeDimensions))); break; case IMS2PhysXProp physXProp: switch (physXProp) { case IMS2CubeProp prop: if (!prop.IsObjectWeapon) { break; } List <int> weaponIds = prop.ObjectWeaponItemCode.SplitAndParseToInt(',').ToList(); mapEntity.WeaponObjects.Add(new(CoordB.FromVector3(prop.Position), weaponIds)); break; case IMS2Liftable liftable: mapEntity.LiftableObjects.Add(new(liftable.EntityId, (int)liftable.ItemID, liftable.ItemStackCount, liftable.MaskQuestID, liftable.MaskQuestState, liftable.EffectQuestID, liftable.EffectQuestState, liftable.ItemLifeTime, liftable.LiftableRegenCheckTime, liftable.LiftableFinishTime, CoordF.FromVector3(liftable.Position), CoordF.FromVector3(liftable.Rotation))); break; case IMS2Vibrate vibrate: mapEntity.VibrateObjects.Add(new(vibrate.EntityId, CoordF.FromVector3(physXProp.Position))); break; } break; } } BuildWaypoints(tempPatrolData, tempWaypoints, mapEntity); }
private static void BuildWaypoints(List <IMS2PatrolData> tempPatrolData, List <IMS2WayPoint> tempWaypoints, MapEntityMetadata mapEntity) { foreach (IMS2PatrolData patrolData in tempPatrolData) { List <string> wayPointIds = patrolData.WayPoints.Select(entry => entry.Value.Replace("-", string.Empty)).ToList(); List <string> approachAnimations = patrolData.ApproachAnims.Select(entry => entry.Value).ToList(); List <string> arriveAnimations = patrolData.ArriveAnims.Select(entry => entry.Value).ToList(); List <int> arriveAnimationTimes = patrolData.ArriveAnimsTime.Select(entry => (int)entry.Value).ToList(); List <WayPoint> wayPoints = new(); for (int i = 0; i < wayPointIds.Count; i++) { string wayPointId = wayPointIds.ElementAtOrDefault(i); string approachAnimation = approachAnimations.ElementAtOrDefault(i); string arriveAnimation = arriveAnimations.ElementAtOrDefault(i); int arriveAnimationTime = arriveAnimationTimes.ElementAtOrDefault(i); IMS2WayPoint waypoint = tempWaypoints.FirstOrDefault(x => x.EntityId == wayPointId); if (waypoint is null) { continue; } wayPoints.Add(new(wayPointId, waypoint.IsVisible, CoordS.FromVector3(waypoint.Position), CoordS.FromVector3(waypoint.Rotation), approachAnimation, arriveAnimation, arriveAnimationTime)); } mapEntity.PatrolDatas.Add(new(patrolData.EntityId, patrolData.EntityName, patrolData.IsAirWayPoint, (int)patrolData.PatrolSpeed, patrolData.IsLoop, wayPoints)); } }