public static int GetPlotNumber(int mapId, CoordB coord) { CoordS coordS = coord.ToShort(); List <MapBlock> blocks = new List <MapBlock>(); MapMetadata mapD = GetMetadata(mapId); for (int i = 0; i < 20; i++) // checking 20 blocks in the same Z axis { MapBlock block = mapD.Blocks.FirstOrDefault(x => x.Coord == coordS); if (block == null) { coordS.Z -= Block.BLOCK_SIZE; continue; } if (block.SaleableGroup > 0) { return(block.SaleableGroup); } coordS.Z -= Block.BLOCK_SIZE; } return(0); }
public static Packet Send(IFieldObject <Player> player, CoordS effectCoord, SkillCast skill) { PacketWriter pWriter = PacketWriter.Of(SendOp.REGION_SKILL); byte tileCount = 1; // TODO: add amount of tiles affected to SkillCast? pWriter.WriteEnum(RegionSkillMode.Add); pWriter.WriteInt(player.ObjectId); pWriter.WriteInt(player.ObjectId); pWriter.WriteInt(); if (tileCount != 0) { pWriter.WriteByte(tileCount); for (int i = 0; i < tileCount; i++) { pWriter.Write(effectCoord.ToFloat()); } pWriter.WriteInt(skill.SkillId); pWriter.WriteShort(skill.SkillLevel); pWriter.WriteLong(); } return(pWriter); }
private Task StartHealingSpot(GameSession session, IFieldObject <Player> player) { int healAmount = 30; Status status = new Status(new SkillCast(70000018, 1, 0, 1), player.ObjectId, player.ObjectId, 1, healAmount); return(Task.Run(async() => { while (!State.Players.IsEmpty) { CoordS healingCoord = MapEntityStorage.GetHealingSpot(MapId); if ((healingCoord - player.Coord.ToShort()).Length() < Block.BLOCK_SIZE * 2 && healingCoord.Z == player.Coord.ToShort().Z - 1) // 3x3x1 area { session.Send(BuffPacket.SendBuff(0, status)); session.Send(SkillDamagePacket.ApplyHeal(player, status)); session.Player.Stats.Increase(PlayerStatId.Hp, healAmount); session.Send(StatPacket.UpdateStats(player, PlayerStatId.Hp)); } await Task.Delay(1000); } })); }
public MapPortal(int id, string name, MapPortalFlag flags, int target, CoordS coord, CoordS rotation, int targetPortalId) { Id = id; Name = name; Flags = flags; Target = target; Coord = coord; Rotation = rotation; TargetPortalId = targetPortalId; }
public static MapBlock GetMapBlock(int mapId, CoordS coord) { MapMetadata mapD = GetMetadata(mapId); return(mapD.Blocks.FirstOrDefault(x => x.Coord == coord)); }
/// <summary> /// Get the coordinates of the skill's effect, if needed change the offset to match the direction of the player. /// For skills that paint the ground, match the correct height. /// </summary> private static List <CoordF> GetEffectCoords(SkillCast skillCast, int mapId, int attackIndex) { SkillAttack skillAttack = skillCast.SkillAttack; List <MagicPathMove> cubeMagicPathMoves = new(); List <MagicPathMove> magicPathMoves = new(); if (skillAttack.CubeMagicPathId != 0) { cubeMagicPathMoves.AddRange(MagicPathMetadataStorage.GetMagicPath(skillAttack.CubeMagicPathId)?.MagicPathMoves ?? new()); } if (skillAttack.MagicPathId != 0) { magicPathMoves.AddRange(MagicPathMetadataStorage.GetMagicPath(skillAttack.MagicPathId)?.MagicPathMoves ?? new()); } int skillMovesCount = cubeMagicPathMoves.Count + magicPathMoves.Count; List <CoordF> effectCoords = new(); if (skillMovesCount <= 0) { effectCoords.Add(skillCast.Position); return(effectCoords); } // TODO: Handle case where magicPathMoves and cubeMagicPathMoves counts are > 0 // Basically do the next if, with the later for loop if (magicPathMoves.Count > 0) { MagicPathMove magicPathMove = magicPathMoves[attackIndex]; IFieldActor <NpcMetadata> parentSkillTarget = skillCast.ParentSkill.Target; if (parentSkillTarget is not null) { effectCoords.Add(parentSkillTarget.Coord); return(effectCoords); } // Rotate the offset coord and distance based on the look direction CoordF rotatedOffset = CoordF.From(magicPathMove.FireOffsetPosition.Length(), skillCast.LookDirection); CoordF distance = CoordF.From(magicPathMove.Distance, skillCast.LookDirection); // Create new effect coord based on offset rotation and distance effectCoords.Add(rotatedOffset + distance + skillCast.Position); return(effectCoords); } // Adjust the effect on the destination/cube foreach (MagicPathMove cubeMagicPathMove in cubeMagicPathMoves) { CoordF offSetCoord = cubeMagicPathMove.FireOffsetPosition; // If false, rotate the offset based on the look direction. Example: Wizard's Tornado if (!cubeMagicPathMove.IgnoreAdjust) { // Rotate the offset coord based on the look direction CoordF rotatedOffset = CoordF.From(offSetCoord.Length(), skillCast.LookDirection); // Create new effect coord based on offset rotation and source coord effectCoords.Add(rotatedOffset + skillCast.Position); continue; } offSetCoord += Block.ClosestBlock(skillCast.Position); CoordS tempBlockCoord = offSetCoord.ToShort(); // Set the height to the max allowed, which is one block above the cast coord. tempBlockCoord.Z += Block.BLOCK_SIZE * 2; // Find the first block below the effect coord int distanceToNextBlockBelow = MapMetadataStorage.GetDistanceToNextBlockBelow(mapId, offSetCoord.ToShort(), out MapBlock blockBelow); // If the block is null or the distance from the cast effect Z height is greater than two blocks, continue if (blockBelow is null || distanceToNextBlockBelow > Block.BLOCK_SIZE * 2) { continue; } // If there is a block above, continue if (MapMetadataStorage.BlockAboveExists(mapId, blockBelow.Coord)) { continue; } // If block is liquid, continue if (MapMetadataStorage.IsLiquidBlock(blockBelow)) { continue; } // Since this is the block below, add 150 units to the Z coord so the effect is above the block offSetCoord = blockBelow.Coord.ToFloat(); offSetCoord.Z += Block.BLOCK_SIZE; effectCoords.Add(offSetCoord); } return(effectCoords); }
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); }
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 bool Equals(CoordS other) { return(X == other.X && Y == other.Y && Z == other.Z); }
protected override List <MapEntityMetadata> Parse() { // Iterate over preset objects to later reference while iterating over exported maps Dictionary <string, string> mapObjects = new Dictionary <string, string>(); foreach (PackFileEntry entry in Resources.ExportedFiles) { 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 = Resources.ExportedMemFile.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 Resources.ExportedFiles) { 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 = Resources.ExportedMemFile.GetDocument(entry.FileHeader); XmlNodeList mapEntities = document.SelectNodes("/game/entitySet/entity"); foreach (XmlNode node in mapEntities) { string modelName = node.Attributes["modelName"].Value.ToLower(); XmlNode blockCoord = node.SelectSingleNode("property[@name='Position']"); CoordS boundingBox = CoordS.Parse(blockCoord?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"); if (node.Attributes["name"].Value.Contains("MS2Bounding0")) { if (metadata.BoundingBox0.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox0 = boundingBox; } } else if (node.Attributes["name"].Value.Contains("MS2Bounding1")) { if (metadata.BoundingBox1.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox1 = boundingBox; } } else 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(CoordS.Parse(playerPositionValue), CoordS.Parse(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 = CoordS.Parse(positionValue); CoordS rotation = CoordS.Parse(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 = CoordS.Parse(positionValue); CoordS rotation = CoordS.Parse(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 MapPlayerSpawn(CoordS coord, CoordS rotation) { Coord = coord; Rotation = rotation; }
public MapNpc(int id, CoordS coord, CoordS rotation) { Id = id; Coord = coord; Rotation = rotation; }
public static PacketWriter Sync(IFieldObject <GuideObject> guide, byte unk2, byte unk3, byte unk4, byte unk5, CoordS unkCoord, short unk6, int unk7) { PacketWriter pWriter = PacketWriter.Of(SendOp.GuideObject); pWriter.Write(GuideObjectPacketMode.Sync); pWriter.WriteInt(guide.ObjectId); pWriter.WriteByte(unk2); pWriter.WriteByte(unk3); pWriter.WriteByte(unk4); pWriter.WriteByte(unk5); pWriter.Write(guide.Coord.ToShort()); pWriter.Write(unkCoord); pWriter.Write(guide.Rotation.ToShort()); pWriter.WriteShort(unk6); pWriter.WriteInt(unk7); return(pWriter); }
public MapPortal(int id, string name, bool enable, bool isVisible, bool minimapVisible, int target, CoordS coord, CoordS rotation, int targetPortalId, PortalTypes portalType, int triggerId = 0) { Id = id; Name = name; Enable = enable; IsVisible = isVisible; MinimapVisible = minimapVisible; Target = target; Coord = coord; Rotation = rotation; TargetPortalId = targetPortalId; PortalType = portalType; TriggerId = triggerId; }
private void BuildMetadata(string xblock, IEnumerable <IMapEntity> mapEntities) { if (xblock.EndsWith("_cn") || xblock.EndsWith("_jp") || xblock.EndsWith("_kr")) { return; } Match isParsableField = Regex.Match(xblock, @"^(\d{8})"); if (!isParsableField.Success) { // TODO: Handle these later, if we need them. They're xblock files with some other names like // character_test.xblock, login.xblock, return; } string mapId = isParsableField.Groups[1].Value; if (Maps.ContainsKey(mapId)) { return; } Maps.Add(mapId, xblock); // Only used to check if we've visited this node before. // TODO: metadata should be keyed on xblock name, not mapId MapEntityMetadata metadata = new(int.Parse(mapId)); foreach (IMapEntity entity in mapEntities) { switch (entity) { case IMS2Bounding bounding: if (bounding.EntityName.EndsWith("0") && metadata.BoundingBox0.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox0 = CoordS.FromVector3(bounding.Position); } else if (bounding.EntityName.EndsWith("1") && metadata.BoundingBox1.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox1 = CoordS.FromVector3(bounding.Position); } break; case IMS2PatrolData patrolData: string patrolDataName = patrolData.EntityName.Replace("-", string.Empty); List <string> wayPointIds = new(); foreach (KeyValuePair <string, string> entry in patrolData.WayPoints) { string wayPointId = entry.Value.Replace("-", string.Empty); wayPointIds.Add(wayPointId); } List <string> arriveAnimations = new(); foreach (KeyValuePair <string, string> entry in patrolData.ArriveAnims) { arriveAnimations.Add(entry.Value); } List <string> approachAnimations = new(); foreach (KeyValuePair <string, string> entry in patrolData.ApproachAnims) { approachAnimations.Add(entry.Value); } List <int> arriveAnimationTimes = new(); foreach (KeyValuePair <string, uint> entry in patrolData.ArriveAnimsTime) { arriveAnimationTimes.Add((int)entry.Value); } metadata.PatrolDatas.Add(new(patrolDataName, wayPointIds, (int)patrolData.PatrolSpeed, patrolData.IsLoop, patrolData.IsAirWayPoint, arriveAnimations, approachAnimations, arriveAnimationTimes)); break; case IMS2WayPoint wayPoint: metadata.WayPoints.Add(new(wayPoint.EntityId, wayPoint.IsVisible, CoordS.FromVector3(wayPoint.Position), CoordS.FromVector3(wayPoint.Rotation))); break; // TODO: This can probably be more generally handled as IMS2RegionSkill case IMS2HealingRegionSkillSound healingRegion: if (healingRegion.Position == default) { continue; } metadata.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: metadata.InteractObjects.Add(new(uiObject.EntityId, uiObject.interactID, uiObject.Enabled, type)); break; case IMS2InteractMesh interactMesh: metadata.InteractObjects.Add(new(interactMesh.EntityId, interactMesh.interactID, interactMesh.IsVisible, type)); break; case IMS2Telescope telescope: metadata.InteractObjects.Add(new(telescope.EntityId, telescope.interactID, telescope.Enabled, type)); break; case IMS2InteractActor interactActor: metadata.InteractObjects.Add(new(interactActor.EntityId, interactActor.interactID, interactActor.IsVisible, type)); break; case IMS2InteractDisplay interactDisplay: metadata.InteractObjects.Add(new(interactDisplay.EntityId, interactDisplay.interactID, interactDisplay.IsVisible, type)); 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); metadata.EventNpcSpawnPoints.Add(new(eventSpawnNpc.SpawnPointID, eventSpawnNpc.NpcCount, npcIds, eventSpawnNpc.SpawnAnimation, eventSpawnNpc.SpawnRadius, CoordF.FromVector3(eventSpawnNpc.Position), CoordF.FromVector3(eventSpawnNpc.Rotation))); break; case ISpawnPointPC pcSpawn: metadata.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); // Parse some additional flat supplemented data about this NPC. metadata.Npcs.Add(npc); break; } break; case IMS2RegionSpawnBase spawnBase: switch (spawnBase) { case IMS2RegionBoxSpawn boxSpawn: SpawnMetadata mobSpawnDataBox = SpawnTagMap.ContainsKey(mapId) && SpawnTagMap[mapId].ContainsKey(boxSpawn.SpawnPointID) ? SpawnTagMap[mapId][boxSpawn.SpawnPointID] : null; if (boxSpawn.ModelName.Contains("chest", StringComparison.CurrentCultureIgnoreCase)) { metadata.MapChests.Add(new() { IsGolden = boxSpawn.ModelName.Contains("rare", StringComparison.CurrentCultureIgnoreCase), Position = CoordF.FromVector3(boxSpawn.Position), Rotation = CoordF.FromVector3(boxSpawn.Rotation), }); continue; } AddMobSpawn(mobSpawnDataBox, metadata, boxSpawn); break; case IMS2RegionSpawn regionSpawn: SpawnMetadata mobSpawnData = SpawnTagMap.ContainsKey(mapId) && SpawnTagMap[mapId].ContainsKey(regionSpawn.SpawnPointID) ? SpawnTagMap[mapId][regionSpawn.SpawnPointID] : null; int mobNpcCount = mobSpawnData?.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 }; metadata.MobSpawns.Add(new(regionSpawn.SpawnPointID, CoordS.FromVector3(regionSpawn.Position), mobNpcCount, mobNpcList, (int)regionSpawn.SpawnRadius, mobSpawnData)); break; } break; case IPortal portal: metadata.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: metadata.BreakableActors.Add(new(actor.EntityId, actor.Enabled, actor.hideTimer, actor.resetTimer)); break; case IMS2BreakableNIF nif: metadata.BreakableNifs.Add(new(nif.EntityId, nif.Enabled, (int)nif.TriggerBreakableID, nif.hideTimer, nif.resetTimer)); break; } break; case IMS2TriggerObject triggerObject: switch (triggerObject) { case IMS2TriggerMesh triggerMesh: metadata.TriggerMeshes.Add(new(triggerMesh.TriggerObjectID, triggerMesh.IsVisible)); break; case IMS2TriggerEffect triggerEffect: metadata.TriggerEffects.Add(new(triggerEffect.TriggerObjectID, triggerEffect.IsVisible)); break; case IMS2TriggerCamera triggerCamera: metadata.TriggerCameras.Add(new(triggerCamera.TriggerObjectID, triggerCamera.Enabled)); break; case IMS2TriggerBox triggerBox: metadata.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?) metadata.TriggerLadders.Add(new(triggerLadder.TriggerObjectID, triggerLadder.IsVisible)); break; case IMS2TriggerRope triggerRope: // TODO: Find which parameters correspond to animationeffect (bool) and animation delay (int?) metadata.TriggerRopes.Add(new(triggerRope.TriggerObjectID, triggerRope.IsVisible)); break; case IMS2TriggerPortal triggerPortal: metadata.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: metadata.TriggerActors.Add(new(triggerActor.TriggerObjectID, triggerActor.IsVisible, triggerActor.InitialSequence)); break; case IMS2TriggerCube triggerCube: metadata.TriggerCubes.Add(new(triggerCube.TriggerObjectID, triggerCube.IsVisible)); break; case IMS2TriggerSound triggerSound: metadata.TriggerSounds.Add(new(triggerSound.TriggerObjectID, triggerSound.Enabled)); break; case IMS2TriggerSkill triggerSkill: metadata.TriggerSkills.Add(new(triggerSkill.TriggerObjectID, triggerSkill.skillID, (short)triggerSkill.skillLevel, (byte)triggerSkill.count, CoordF.FromVector3(triggerSkill.Position))); break; } break; case IMS2Liftable liftable: metadata.LiftableObjects.Add(new(liftable.EntityId, (int)liftable.ItemID, liftable.EffectQuestID, liftable.EffectQuestState, liftable.ItemLifeTime, liftable.LiftableRegenCheckTime)); break; case IMS2LiftableTargetBox liftableTargetBox: metadata.LiftableTargets.Add(new(liftableTargetBox.liftableTarget, CoordF.FromVector3(liftableTargetBox.Position), CoordF.FromVector3(liftableTargetBox.ShapeDimensions))); break; case IMS2CubeProp prop: if (prop.IsObjectWeapon) { List <int> weaponIds = prop.ObjectWeaponItemCode.Split(",").Select(int.Parse).ToList(); metadata.WeaponObjects.Add(new(CoordB.FromVector3(prop.Position), weaponIds)); } break; case IMS2Vibrate vibrate: metadata.VibrateObjects.Add(new(vibrate.EntityId)); break; } /* NPC Objects have a modelName of 8 digits followed by an underscore and a name that's the same thing, * but with a number (for each instance on that map) after it * * IM_ Prefixed items are Interactable Meshes supplemented by data in "xml/table/interactobject.xml" * IA_ prefixed items are Interactable Actors (Doors, etc). Have an interactID, which is an event on interact. * "mixinMS2MapProperties" is generic field items * "mixinMS2SalePost" - is for sale signs. Does a packet need to respond to this item? */ /* * * // Unhandled Items: * // "mixinEventSpawnPointNPC", * // "mixinMS2Actor" as in "fa_fi_funct_irondoor_A01_" * // MS2RegionSkill as in "SkillObj_co_Crepper_C03_" (Only on 8xxxx and 9xxxxx maps) * // "mixinMS2FunctionCubeKFM" as in "ry_functobj_lamp_B01_", "ke_functobj_bath_B01_" * // "mixinMS2FunctionCubeNIF" * // "MS2MapProperties"->"MS2PhysXProp" that's not a weapon. Standard * * /* * if (Regex.Match(modelName, @"^\d{8}_").Success && Regex.Match(name, @"\d{1,3}").Success) * { * // Parse non-permanent NPCs. These have no .flat files to supplement them. * string npcListIndex = node.SelectSingleNode("property[@name='NpcList']")?.FirstChild.Attributes["index"].Value ?? "-1"; * if (npcListIndex == "-1") * { * continue; * } * string npcPositionValue = node.SelectSingleNode("property[@name='Position']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * string npcRotationValue = node.SelectSingleNode("property[@name='Rotation']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * // metadata.Npcs.Add(new MapNpc(int.Parse(npcListIndex), modelName, name, ParseCoord(npcPositionValue), ParseCoord(npcRotationValue))); * } */ } // No data on this map if (metadata.Npcs.Count == 0 && metadata.Portals.Count == 0 && metadata.PlayerSpawns.Count == 0) { return; } Entities.Add(metadata); }
private void BuildMetadata(string xblock, IEnumerable <IMapEntity> mapEntities) { if (xblock.EndsWith("_cn") || xblock.EndsWith("_jp") || xblock.EndsWith("_kr")) { return; } Match isParsableField = Regex.Match(xblock, @"^(\d{8})"); if (!isParsableField.Success) { // TODO: Handle these later, if we need them. They're xblock files with some other names like // character_test.xblock, login.xblock, return; } string mapId = isParsableField.Groups[1].Value; if (Maps.ContainsKey(mapId)) { return; } Maps.Add(mapId, xblock); // Only used to check if we've visited this node before. // TODO: metadata should be keyed on xblock name, not mapId MapEntityMetadata metadata = new MapEntityMetadata(int.Parse(mapId)); foreach (IMapEntity entity in mapEntities) { switch (entity) { case IMS2Bounding bounding: if (bounding.EntityName.EndsWith("0") && metadata.BoundingBox0.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox0 = CoordS.FromVector3(bounding.Position); } else if (bounding.EntityName.EndsWith("1") && metadata.BoundingBox1.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox1 = CoordS.FromVector3(bounding.Position); } break; case IMS2PatrolData patrolData: string patrolDataName = patrolData.EntityName.Replace("-", string.Empty); List <string> wayPointIds = new List <string>(); foreach (KeyValuePair <string, string> entry in patrolData.WayPoints) { string wayPointId = entry.Value.Replace("-", string.Empty); wayPointIds.Add(wayPointId); } List <string> arriveAnimations = new List <string>(); foreach (KeyValuePair <string, string> entry in patrolData.ArriveAnims) { arriveAnimations.Add(entry.Value); } List <string> approachAnimations = new List <string>(); foreach (KeyValuePair <string, string> entry in patrolData.ApproachAnims) { approachAnimations.Add(entry.Value); } List <int> arriveAnimationTimes = new List <int>(); foreach (KeyValuePair <string, uint> entry in patrolData.ArriveAnimsTime) { arriveAnimationTimes.Add((int)entry.Value); } metadata.PatrolDatas.Add(new PatrolData(patrolDataName, wayPointIds, (int)patrolData.PatrolSpeed, patrolData.IsLoop, patrolData.IsAirWayPoint, arriveAnimations, approachAnimations, arriveAnimationTimes)); break; case IMS2WayPoint wayPoint: metadata.WayPoints.Add(new WayPoint(wayPoint.EntityId, wayPoint.IsVisible, CoordS.FromVector3(wayPoint.Position), CoordS.FromVector3(wayPoint.Rotation))); break; // TODO: This can probably be more generally handled as IMS2RegionSkill case IMS2HealingRegionSkillSound healingRegion: if (healingRegion.Position == default) { continue; } metadata.HealingSpot.Add(CoordS.FromVector3(healingRegion.Position)); break; case IMS2InteractObject interact: // TODO: this one is kinda f****d if (interact.interactID == 0) { continue; } if (interact.ModelName.Contains("funct_extract_")) { metadata.InteractObjects.Add(new MapInteractObject(interact.EntityId, interact.EntityName, InteractObjectType.Extractor, interact.interactID)); continue; } if (entity is IMS2Telescope) { metadata.InteractObjects.Add(new MapInteractObject(interact.EntityId, interact.EntityName, InteractObjectType.Binoculars, interact.interactID)); continue; } if (interact.ModelName.EndsWith("hub") || interact.ModelName.EndsWith("vein")) { if (InteractRecipeMap.TryGetValue(interact.interactID, out int recipeId)) { metadata.InteractObjects.Add(new MapInteractObject(interact.EntityId, interact.EntityName, InteractObjectType.Gathering, recipeId)); } continue; } if (entity is IMS2InteractDisplay) { // TODO: Implement Interactive Displays like 02000183, Dark Wind Wanted Board (7bb334fe41f94182a9569ab884004c32) // "mixinMS2InteractDisplay" ("ID_19100003_") } if (entity is IMS2InteractMesh) { // TODO: InteractMesh IS InteractObject, maybe shouldn't separate them... metadata.InteractMeshes.Add(new MapInteractMesh(entity.EntityId, entity.EntityName)); } metadata.InteractObjects.Add(new MapInteractObject(interact.EntityId, interact.EntityName, InteractObjectType.Unknown, interact.interactID)); break; case ISpawnPoint spawn: switch (spawn) { // TODO: Parse "value" from NPCList. case IEventSpawnPointNPC eventSpawnNpc: // trigger mob/npc spawns List <string> npcIds = new List <string>(); npcIds.AddRange(eventSpawnNpc.NpcList.Keys); metadata.EventNpcSpawnPoints.Add(new MapEventNpcSpawnPoint(eventSpawnNpc.SpawnPointID, eventSpawnNpc.NpcCount, npcIds, eventSpawnNpc.SpawnAnimation, eventSpawnNpc.SpawnRadius, CoordF.FromVector3(eventSpawnNpc.Position), CoordF.FromVector3(eventSpawnNpc.Rotation))); break; case ISpawnPointPC pcSpawn: metadata.PlayerSpawns.Add( new MapPlayerSpawn(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 MapNpc(npcId, npcSpawn.ModelName, npcSpawn.EntityName, CoordS.FromVector3(npcSpawn.Position), CoordS.FromVector3(npcSpawn.Rotation), npcSpawn.IsSpawnOnFieldCreate, npcSpawn.dayDie, npcSpawn.nightDie); // Parse some additional flat supplemented data about this NPC. metadata.Npcs.Add(npc); break; } break; case IMS2RegionSpawnBase spawnBase: switch (spawnBase) { case IMS2RegionBoxSpawn boxSpawn: SpawnMetadata mobSpawnDataBox = (SpawnTagMap.ContainsKey(mapId) && SpawnTagMap[mapId].ContainsKey(boxSpawn.SpawnPointID)) ? SpawnTagMap[mapId][boxSpawn.SpawnPointID] : null; 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 List <int> { 21000025 // Placeholder }; metadata.MobSpawns.Add(new MapMobSpawn(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. break; case IMS2RegionSpawn regionSpawn: SpawnMetadata mobSpawnData = (SpawnTagMap.ContainsKey(mapId) && SpawnTagMap[mapId].ContainsKey(regionSpawn.SpawnPointID)) ? SpawnTagMap[mapId][regionSpawn.SpawnPointID] : null; int mobNpcCount = mobSpawnData?.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 List <int> { 21000025 // Placeholder }; metadata.MobSpawns.Add(new MapMobSpawn(regionSpawn.SpawnPointID, CoordS.FromVector3(regionSpawn.Position), mobNpcCount, mobNpcList, (int)regionSpawn.SpawnRadius, mobSpawnData)); break; } break; case IPortal portal: metadata.Portals.Add(new MapPortal(portal.PortalID, portal.ModelName, portal.PortalEnable, portal.IsVisible, portal.MinimapIconVisible, portal.TargetFieldSN, CoordS.FromVector3(portal.Position), CoordS.FromVector3(portal.Rotation), portal.TargetPortalID, (byte)portal.PortalType)); break; case IMS2Breakable breakable: switch (breakable) { case IMS2BreakableActor actor: metadata.BreakableActors.Add(new MapBreakableActorObject(actor.EntityId, actor.Enabled, actor.hideTimer, actor.resetTimer)); break; case IMS2BreakableNIF nif: metadata.BreakableNifs.Add(new MapBreakableNifObject(nif.EntityId, nif.Enabled, (int)nif.TriggerBreakableID, nif.hideTimer, nif.resetTimer)); break; } break; case IMS2TriggerObject triggerObject: switch (triggerObject) { case IMS2TriggerMesh triggerMesh: metadata.TriggerMeshes.Add(new MapTriggerMesh(triggerMesh.TriggerObjectID, triggerMesh.IsVisible)); break; case IMS2TriggerEffect triggerEffect: metadata.TriggerEffects.Add(new MapTriggerEffect(triggerEffect.TriggerObjectID, triggerEffect.IsVisible)); break; case IMS2TriggerCamera triggerCamera: metadata.TriggerCameras.Add(new MapTriggerCamera(triggerCamera.TriggerObjectID, triggerCamera.Enabled)); break; case IMS2TriggerBox triggerBox: metadata.TriggerBoxes.Add(new MapTriggerBox(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?) metadata.TriggerLadders.Add(new MapTriggerLadder(triggerLadder.TriggerObjectID, triggerLadder.IsVisible)); break; case IMS2TriggerRope triggerRope: // TODO: Find which parameters correspond to animationeffect (bool) and animation delay (int?) metadata.TriggerRopes.Add(new MapTriggerRope(triggerRope.TriggerObjectID, triggerRope.IsVisible)); break; case IMS2TriggerPortal triggerPortal: metadata.Portals.Add(new MapPortal(triggerPortal.PortalID, triggerPortal.ModelName, triggerPortal.PortalEnable, triggerPortal.IsVisible, triggerPortal.MinimapIconVisible, triggerPortal.TargetFieldSN, CoordS.FromVector3(triggerPortal.Position), CoordS.FromVector3(triggerPortal.Rotation), triggerPortal.TargetPortalID, (byte)triggerPortal.PortalType, triggerPortal.TriggerObjectID)); break; case IMS2TriggerActor triggerActor: metadata.TriggerActors.Add(new MapTriggerActor(triggerActor.TriggerObjectID, triggerActor.IsVisible, triggerActor.InitialSequence)); break; case IMS2TriggerCube triggerCube: metadata.TriggerCubes.Add(new MapTriggerCube(triggerCube.TriggerObjectID, triggerCube.IsVisible)); break; case IMS2TriggerSound triggerSound: metadata.TriggerSounds.Add(new MapTriggerSound(triggerSound.TriggerObjectID, triggerSound.Enabled)); break; } break; case IMS2Vibrate vibrate: metadata.VibrateObjects.Add(new MapVibrateObject(vibrate.EntityId)); break; case IPlaceable placeable: // TODO: placeable might be too generic // These are objects which you can place in the world string nameCoord = placeable.EntityName.ToLower(); Match coordMatch = Regex.Match(nameCoord, @"-?\d+, -?\d+, -?\d+"); if (!coordMatch.Success) { continue; } // Only MS2MapProperties has ObjectWeaponItemCode if (entity is not IMS2MapProperties mapProperties) { continue; } try //TODO: The parser will output errors here, which are non-critical, yet need resolving later. { CoordB coord = CoordB.Parse(coordMatch.Value, ", "); metadata.Objects.Add(new MapObject(coord, int.Parse(mapProperties.ObjectWeaponItemCode))); } catch (FormatException) { // ignored //byte[] bytes = System.Text.Encoding.UTF8.GetBytes(mapProperties.ObjectWeaponItemCode); //Console.WriteLine($"String in bytes: {Convert.ToHexString(bytes)}"); } catch (OverflowException) { //byte[] bytes = System.Text.Encoding.UTF8.GetBytes(mapProperties.ObjectWeaponItemCode); //Console.WriteLine($"String in bytes: {Convert.ToHexString(bytes)}"); } break; } /* NPC Objects have a modelName of 8 digits followed by an underscore and a name that's the same thing, * but with a number (for each instance on that map) after it * * IM_ Prefixed items are Interactable Meshes supplemented by data in "xml/table/interactobject.xml" * IA_ prefixed items are Interactable Actors (Doors, etc). Have an interactID, which is an event on interact. * "mixinMS2MapProperties" is generic field items * "mixinMS2SalePost" - is for sale signs. Does a packet need to respond to this item? */ /* * * // Unhandled Items: * // "mixinEventSpawnPointNPC", * // "mixinMS2Actor" as in "fa_fi_funct_irondoor_A01_" * // MS2RegionSkill as in "SkillObj_co_Crepper_C03_" (Only on 8xxxx and 9xxxxx maps) * // "mixinMS2FunctionCubeKFM" as in "ry_functobj_lamp_B01_", "ke_functobj_bath_B01_" * // "mixinMS2FunctionCubeNIF" * // "MS2MapProperties"->"MS2PhysXProp" that's not a weapon. Standard * * /* * if (Regex.Match(modelName, @"^\d{8}_").Success && Regex.Match(name, @"\d{1,3}").Success) * { * // Parse non-permanent NPCs. These have no .flat files to supplement them. * string npcListIndex = node.SelectSingleNode("property[@name='NpcList']")?.FirstChild.Attributes["index"].Value ?? "-1"; * if (npcListIndex == "-1") * { * continue; * } * string npcPositionValue = node.SelectSingleNode("property[@name='Position']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * string npcRotationValue = node.SelectSingleNode("property[@name='Rotation']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * // metadata.Npcs.Add(new MapNpc(int.Parse(npcListIndex), modelName, name, ParseCoord(npcPositionValue), ParseCoord(npcRotationValue))); * } */ } // No data on this map if (metadata.Npcs.Count == 0 && metadata.Portals.Count == 0 && metadata.PlayerSpawns.Count == 0) { return; } Entities.Add(metadata); }
protected override List <MapMetadata> Parse() { // Iterate over preset Cubes to later reference while iterating over exported maps List <string> mapCubes = new List <string>(); foreach (PackFileEntry entry in Resources.ExportedFiles) { if (!entry.Name.StartsWith("flat/presets/presets cube/")) { continue; } // Check if file is valid string objStr = entry.Name.ToLower(); if (string.IsNullOrEmpty(objStr)) { continue; } if (mapCubes.Contains(objStr)) { continue; } // Parse XML XmlDocument document = Resources.ExportedMemFile.GetDocument(entry.FileHeader); XmlElement root = document.DocumentElement; string cubeName = root.Attributes["name"].Value.ToLower(); mapCubes.Add(cubeName); } // Parse map names Dictionary <int, string> mapNames = new Dictionary <int, string>(); foreach (PackFileEntry entry in Resources.XmlFiles.Where(x => x.Name.StartsWith("string/en/mapname.xml"))) { XmlDocument document = Resources.XmlMemFile.GetDocument(entry.FileHeader); foreach (XmlNode node in document.DocumentElement.ChildNodes) { int id = int.Parse(node.Attributes["id"].Value); string name = node.Attributes["name"].Value; mapNames[id] = name; } } // Parse every block for each map List <MapMetadata> mapsList = new List <MapMetadata>(); foreach (PackFileEntry entry in Resources.ExportedFiles.Where(x => x.Name.StartsWith("xblock/"))) { if (entry.Name.Contains("_cn.xblock") || entry.Name.Contains("_jp.xblock") || entry.Name.Contains("_kr.xblock")) { continue; } string mapIdStr = Regex.Match(entry.Name, @"\d{8}").Value; if (string.IsNullOrEmpty(mapIdStr)) { continue; } MapMetadata metadata = new MapMetadata(); metadata.Id = int.Parse(mapIdStr); XmlDocument document = Resources.ExportedMemFile.GetDocument(entry.FileHeader); XmlNodeList mapEntities = document.SelectNodes("/game/entitySet/entity"); List <CoordS> blockList = new List <CoordS>(); foreach (XmlNode node in mapEntities) { string modelName = node.Attributes["modelName"].Value.ToLower(); if (!mapCubes.Contains(modelName)) { continue; } XmlNode fallReturn = node.SelectSingleNode("property[@name='IsFallReturn']"); bool isFallReturn = (fallReturn?.FirstChild.Attributes["value"].Value) != "False"; if (!isFallReturn) { continue; } string id = node.Attributes["id"].Value.ToLower(); XmlNode blockCoord = node.SelectSingleNode("property[@name='Position']"); CoordS coordS = CoordS.Parse(blockCoord?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"); blockList.Add(coordS); } if (blockList.Count == 0) { continue; } if (mapNames.ContainsKey(metadata.Id)) { metadata.Name = mapNames[metadata.Id]; } metadata.Blocks.AddRange(blockList); mapsList.Add(metadata); } return(mapsList); }
public HealingSpot(int objectId, CoordS coord) { ObjectId = objectId; Coord = coord; }
public static PacketWriter RegionDamage(long skillSN, int userObjectId, int skillObjectId, byte count, byte count2, IFieldObject <Player> player, CoordF direction, CoordS blockPosition, long damage) { PacketWriter pWriter = PacketWriter.Of(SendOp.SKILL_DAMAGE); SkillCast skillCast = SkillUsePacket.SkillCastMap[skillSN]; pWriter.Write(SkillDamageMode.RegionDamage); pWriter.WriteLong(skillCast.SkillSN); pWriter.WriteInt(userObjectId); pWriter.WriteInt(skillObjectId); pWriter.WriteByte(); pWriter.WriteByte(count); for (int i = 0; i < count; i++) { pWriter.WriteInt(player.ObjectId); pWriter.WriteByte(count2); pWriter.Write(blockPosition); pWriter.Write(direction); for (int j = 0; j < count2; j++) { pWriter.Write(skillCast.GetSkillDamageType()); pWriter.WriteLong(damage); } } return(pWriter); }
public static bool BlockExists(int mapId, CoordS coord) { return(!map[mapId].Blocks.Find(x => x == coord).Equals(CoordS.From(0, 0, 0))); }
public MapBlock(CoordS coord, string attribute, string type) { Coord = coord; Attribute = attribute; Type = type; }
public MapNpc(int id, CoordS coord, CoordS rotation) { this.Id = id; this.Coord = coord; this.Rotation = rotation; }
public static short Distance(CoordS left, CoordS right) { return((left - right).Length()); }
public MapPlayerSpawn(CoordS coord, CoordS rotation) { this.Coord = coord; this.Rotation = rotation; }
public readonly CoordS ToShort() { return(CoordS.From((short)X, (short)Y, (short)Z)); }
protected override List <MapEntityMetadata> Parse() { List <MapEntityMetadata> entities = new List <MapEntityMetadata>(); /* Iterate over preset objects to later reference while iterating over exported maps * Key is modelName, value is parsed key/value from property * in json, this would look like: * { * "11000003_": {"isSpawnPointNPC": "true", "SpawnPointID": "0", "SpawnRadius", "0"} * } */ Dictionary <string, Dictionary <string, string> > mapObjects = new Dictionary <string, Dictionary <string, string> >(); foreach (PackFileEntry entry in Resources.ExportedFiles .Where(entry => Regex.Match(entry.Name, @"^flat/presets/presets (object|npc)/").Success) .OrderBy(entry => entry.Name)) { // Parse XML XmlDocument document = Resources.ExportedMemFile.GetDocument(entry.FileHeader); string modelName = document.DocumentElement.Attributes["name"].Value; // A local in-mem storage for all flat file supplementary data. Dictionary <string, string> thisNode = new Dictionary <string, string> { }; foreach (XmlNode node in document.SelectNodes("model/mixin")) { // These define the superclass this model inherits from. We store these as a property for later processing // TODO: Should we only parse type="Active"? if (node.Attributes["type"].Value.Equals("Active")) { string name = node.Attributes["name"].Value; thisNode[$"mixin{name}"] = "true"; // isMS2InteractActor = "true"; } } foreach (XmlNode node in document.SelectNodes("model/property")) { if (node.ChildNodes.Count > 0) // hasChildren { thisNode[node.Attributes["name"].Value] = node.FirstChild?.Attributes["value"].Value; } } mapObjects.Add(modelName, thisNode); } // Iterate over map xblocks Dictionary <string, string> maps = new Dictionary <string, string>(); // Have we already parsed this map? foreach (PackFileEntry entry in Resources.ExportedFiles .Where(entry => entry.Name.StartsWith("xblock/")) .OrderByDescending(entry => entry.Name)) { Match isParsableField = Regex.Match(Path.GetFileNameWithoutExtension(entry.Name), @"^(\d{8})"); if (!isParsableField.Success) { // TODO: Handle these later, if we need them. They're xblock files with some other names like // character_test.xblock, login.xblock, continue; } string mapId = isParsableField.Groups[1].Value; if (maps.ContainsKey(mapId)) { continue; } maps.Add(mapId, entry.Name); // Only used to check if we've visited this node before. MapEntityMetadata metadata = new MapEntityMetadata(int.Parse(mapId)); XmlDocument document = Resources.ExportedMemFile.GetDocument(entry.FileHeader); foreach (XmlNode node in document.SelectNodes("/game/entitySet/entity")) { string modelName = node.Attributes["modelName"].Value; // Always maps to a .flat fileName for additional supplemented data string name = node.Attributes["name"].Value; if (modelName == "MS2Bounding") { XmlNode blockCoord = node.SelectSingleNode("property[@name='Position']"); CoordS boundingBox = CoordS.Parse(blockCoord?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"); if (name.EndsWith("0") && metadata.BoundingBox0.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox0 = boundingBox; } else if (name.EndsWith("1") && metadata.BoundingBox1.Equals(CoordS.From(0, 0, 0))) { metadata.BoundingBox1 = boundingBox; } } else if (modelName == "MS2Telescope") { string uuid = node.Attributes["id"].Value; int interactId = int.Parse(node.SelectSingleNode("property[@name='interactID']")?.FirstChild.Attributes["value"]?.Value); metadata.InteractActors.Add(new MapInteractActor(uuid, name, InteractActorType.Binoculars, interactId)); } else if (modelName == "SpawnPointPC") // Player Spawn point on map { 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(CoordS.Parse(playerPositionValue), CoordS.Parse(playerRotationValue))); } else if (modelName == "Portal_entrance" || modelName == "Portal_cube") { XmlNode portalIdNode = node.SelectSingleNode("property[@name='PortalID']"); XmlNode targetNode = node.SelectSingleNode("property[@name='TargetFieldSN']"); if (targetNode == null || portalIdNode == null) { continue; } XmlNode visibleNode = node.SelectSingleNode("property[@name='IsVisible']"); XmlNode enabledNode = node.SelectSingleNode("property[@name='PortalEnable']"); XmlNode minimapVisibleNode = node.SelectSingleNode("property[@name='MinimapIconVisible']"); XmlNode coordNode = node.SelectSingleNode("property[@name='Position']"); XmlNode rotationNode = node.SelectSingleNode("property[@name='Rotation']"); 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(portalIdNode?.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 = CoordS.Parse(positionValue); CoordS rotation = CoordS.Parse(rotationValue); metadata.Portals.Add(new MapPortal(portalId, flags, target, position, rotation)); } else if (modelName == "SpawnPointNPC" && !name.StartsWith("SpawnPointNPC")) { // These tend to be vendors, shops, etc. // If the name tag begins with SpawnPointNPC, I think these are mob spawn locations. Skipping these. string npcId = node.SelectSingleNode("property[@name='NpcList']")?.FirstChild.Attributes["index"].Value ?? "0"; if (npcId == "0") { continue; } string npcPositionValue = node.SelectSingleNode("property[@name='Position']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string npcRotationValue = node.SelectSingleNode("property[@name='Rotation']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; MapNpc thisNpc = new MapNpc(int.Parse(npcId), modelName, name, CoordS.Parse(npcPositionValue), CoordS.Parse(npcRotationValue)); thisNpc.IsSpawnOnFieldCreate = true; metadata.Npcs.Add(thisNpc); } // Parse the rest of the objects in the xblock if they have a flat component. else if (mapObjects.ContainsKey(modelName)) // There was .flat file data about this item { /* NPC Objects have a modelName of 8 digits followed by an underscore and a name that's the same thing, * but with a number (for each instance on that map) after it * * IM_ Prefixed items are Interactable Meshes supplemented by data in "xml/table/interactobject.xml" * IA_ prefixed items are Interactable Actors (Doors, etc). Have an interactID, which is an event on interact. * "mixinMS2MapProperties" is generic field items * "mixinMS2SalePost" - is for sale signs. Does a packet need to respond to this item? */ Dictionary <string, string> modelData = mapObjects[modelName]; if (modelData.ContainsKey("mixinMS2TimeShowSetting") || modelData.ContainsKey("mixinMS2Sound") || modelData.ContainsKey("mixinMS2SalePost") || modelData.ContainsKey("mixinMS2Actor") || // "fa_fi_funct_irondoor_A01_" modelData.ContainsKey("mixinMS2RegionSkill") || modelData.ContainsKey("mixinMS2FunctionCubeKFM") || modelData.ContainsKey("mixinMS2FunctionCubeNIF") ) { continue; // Skip these for now. } else if (modelData.ContainsKey("mixinMS2InteractObject")) { if (modelData.ContainsKey("mixinMS2InteractMesh")) { // TODO: Implement mesh packet string uuid = node.Attributes["id"].Value.ToLower(); metadata.InteractMeshes.Add(new MapInteractMesh(uuid, name)); } else if (modelData.ContainsKey("mixinMS2InteractActor")) { string uuid = node.Attributes["id"].Value.ToLower(); metadata.InteractActors.Add(new MapInteractActor(uuid, name, InteractActorType.Unknown, 0)); } else if (modelData.ContainsKey("mixinMS2InteractDisplay")) { // TODO: Implement Interactive Displays like 02000183, Dark Wind Wanted Board (7bb334fe41f94182a9569ab884004c32) // "mixinMS2InteractDisplay" ("ID_19100003_") } else { // TODO: Any others? if (name.Contains("funct_extract_")) { string uuid = node.Attributes["id"].Value.ToLower(); metadata.InteractActors.Add(new MapInteractActor(uuid, name, InteractActorType.Extractor, 0)); } else if (name.Contains("funct_telescope_")) { string uuid = node.Attributes["id"].Value; int interactId = int.Parse(node.SelectSingleNode("property[@name='interactID']")?.FirstChild.Attributes["value"]?.Value); metadata.InteractActors.Add(new MapInteractActor(uuid, name, InteractActorType.Binoculars, interactId)); } } } else if (modelData.ContainsKey("mixinSpawnPointNPC")) { // These can be full natural spawns, or only spawnable as a reaction to a quest, or something else as well. string npcId = Regex.Match(modelName, @"^(\d{8})_").Groups[1].Value; string npcPositionValue = node.SelectSingleNode("property[@name='Position']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; string npcRotationValue = node.SelectSingleNode("property[@name='Rotation']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; MapNpc thisNpc = new MapNpc(int.Parse(npcId), modelName, name, CoordS.Parse(npcPositionValue), CoordS.Parse(npcRotationValue)); // Parse some additional flat supplemented data about this NPC. if (mapObjects.ContainsKey(modelName)) { Dictionary <string, string> thisFlatData = mapObjects[modelName]; // Parse IsSpawnOnFieldCreate thisNpc.IsSpawnOnFieldCreate = thisFlatData["IsSpawnOnFieldCreate"] == "True"; thisNpc.IsDayDie = thisFlatData["dayDie"] == "True"; thisNpc.IsNightDie = thisFlatData["nightDie"] == "True"; } metadata.Npcs.Add(thisNpc); } else if (modelData.ContainsKey("mixinMS2BreakableActor")) { // TODO: Do we need to parse these as some special NPC object? } else if (modelData.ContainsKey("mixinMS2Placeable")) { // These are objects which you can place in the world 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(modelData["ObjectWeaponItemCode"]))); } else if (modelData.ContainsKey("mixinMS2CubeProp")) { if (!modelData.ContainsKey("ObjectWeaponItemCode")) { continue; } string weaponId = modelData["ObjectWeaponItemCode"] ?? "0"; if (!weaponId.Equals("0")) { // Extract the coordinate from the name. rhy tried just grabbing position, but it wasn't reliable. Match coordMatch = Regex.Match(name, @"[\-]?\d+[,]\s[\-]?\d+[,]\s[\-]?\d+"); if (coordMatch.Success) { metadata.Objects.Add(new MapObject(CoordB.Parse(coordMatch.Value, ", "), int.Parse(weaponId))); } } } else if (modelData.ContainsKey("mixinMS2Breakable")) { // "mixinMS2Breakable" But not "mixinMS2BreakableActor", as in ke_fi_prop_buoy_A01_ or el_move_woodbox_B04_ } else if (modelData.ContainsKey("mixinMS2RegionBoxSpawn")) { // "QR_10000264_" is Quest Reward Chest? This is tied to a MS2TriggerAgent making this object appear. } // Unhandled Items: // "mixinEventSpawnPointNPC", // "mixinMS2Actor" as in "fa_fi_funct_irondoor_A01_" // MS2RegionSkill as in "SkillObj_co_Crepper_C03_" (Only on 8xxxx and 9xxxxx maps) // "mixinMS2FunctionCubeKFM" as in "ry_functobj_lamp_B01_", "ke_functobj_bath_B01_" // "mixinMS2FunctionCubeNIF" // "MS2MapProperties"->"MS2PhysXProp" that's not a weapon. Standard } /* * if (Regex.Match(modelName, @"^\d{8}_").Success && Regex.Match(name, @"\d{1,3}").Success) * { * // Parse non-permanent NPCs. These have no .flat files to supplement them. * string npcListIndex = node.SelectSingleNode("property[@name='NpcList']")?.FirstChild.Attributes["index"].Value ?? "-1"; * if (npcListIndex == "-1") * { * continue; * } * string npcPositionValue = node.SelectSingleNode("property[@name='Position']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * string npcRotationValue = node.SelectSingleNode("property[@name='Rotation']")?.FirstChild.Attributes["value"].Value ?? "0, 0, 0"; * // metadata.Npcs.Add(new MapNpc(int.Parse(npcListIndex), modelName, name, ParseCoord(npcPositionValue), ParseCoord(npcRotationValue))); * } */ } // No data on this map if (metadata.Npcs.Count == 0 && metadata.Portals.Count == 0 && metadata.PlayerSpawns.Count == 0) { continue; } entities.Add(metadata); } Console.Out.WriteLine($"Parsed {entities.Count} entities"); return(entities); }
public static bool HasHealingSpot(int mapId) { return(!healthSpot.GetValueOrDefault(mapId).Equals(CoordS.From(0, 0, 0))); }
public HealingSpot(CoordS coord) { Coord = coord; }