private static string GetImageSourcePath(string tmxPath, Tileset tileset, TilesetImage image) { // The image may be absolute, so only prepend the tmx path if the image is relative string sourcepath; if (FileManager.IsRelative(image.Source)) { sourcepath = tmxPath + image.Source; } else { sourcepath = image.Source; } sourcepath = FileManager.RemoveDotDotSlash(sourcepath); if (tileset.Source != null) { if (tileset.SourceDirectory != "." && !tileset.SourceDirectory.Contains(":")) { sourcepath = tmxPath + tileset.SourceDirectory.Replace("\\", "/") + "/" + image.sourceFileName; sourcepath = FileManager.GetDirectory(sourcepath) + image.sourceFileName; } else if (tileset.SourceDirectory.Contains(":")) { sourcepath = tileset.SourceDirectory + "/" + image.sourceFileName; } else { sourcepath = tmxPath + image.sourceFileName; } } return sourcepath; }
private static void AddTileShapeCollections(LayeredTileMap layeredTileMap, TiledMapSave tms, bool separateOnTileType) { var allTilesets = tms.Tilesets; Dictionary <string, TileCollisions.TileShapeCollection> nameCollisionPairs = new Dictionary <string, TileCollisions.TileShapeCollection>(); for (int i = 0; i < tms.Layers.Count; i++) { var layer = tms.Layers[i]; // Currently we only support 1 tileset per layer, so we'll find the tileset for this layer var firstNonZero = layer.data[0].tiles.FirstOrDefault(item => item != 0); TMXGlueLib.Tileset tileset = null; if (firstNonZero != 0) { tileset = tms.GetTilesetForGid(firstNonZero); } AddTileShapeCollectionForLayer(layer, nameCollisionPairs, tileset, tms.tilewidth, i, separateOnTileType); } foreach (var item in nameCollisionPairs.Values) { if (item.Rectangles.Count > 0 || item.Polygons.Count > 0) { var sortOnY = layeredTileMap.Height > layeredTileMap.Width; if (sortOnY) { item.SortAxis = Math.Axis.Y; } else { item.SortAxis = Math.Axis.X; } item.RefreshAllRepositionDirections(); layeredTileMap.Collisions.Add(item); } } }
private static void AddTileShapeCollectionForLayer(TMXGlueLib.MapLayer layer, Dictionary <string, TileCollisions.TileShapeCollection> collisionDictionary, TMXGlueLib.Tileset tileset, float tileDimension, float z, bool separateOnTileType) { Math.Geometry.AxisAlignedRectangle rectangle = null; Math.Geometry.Polygon polygon = null; Circle circle; var tiles = layer.data[0].tiles; bool sortOnY = layer.height > layer.width; if (tileset != null) { foreach (var tilesetTile in tileset.Tiles.Where(item => item.Objects?.@object?.Length > 0)) { var tilesetTileGid = tilesetTile.id + tileset.Firstgid; foreach (var tilesetObject in tilesetTile.Objects.@object) { var name = layer.Name; TiledMapToShapeCollectionConverter.ConvertTiledObjectToFrbShape(tilesetObject, out polygon, out rectangle, out circle); if (rectangle != null) { var collectionName = layer.Name; if (tilesetObject.Type != null && separateOnTileType) { collectionName += "_" + tilesetObject.Type; } var collection = GetOrAddTileShapeCollection(collectionName, collisionDictionary); collection.GridSize = tileDimension; rectangle.Z = z; if (sortOnY) { for (int y = 0; y < layer.height; y++) { for (int x = 0; x < layer.width; x++) { AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collection); } } } else { for (int x = 0; x < layer.width; x++) { for (int y = 0; y < layer.height; y++) { AddRectangleCloneAtXY(layer, tileDimension, rectangle, tiles, tilesetTileGid, x, y, collection); } } } } else if (polygon != null) { var collectionName = layer.Name; if (tilesetObject.Type != null && separateOnTileType) { collectionName += "_" + tilesetObject.Type; } var collection = GetOrAddTileShapeCollection(collectionName, collisionDictionary); collection.GridSize = tileDimension; // For tile polygons we want them to be centered on the tile. // To do this, we shift all points by its position: for (int i = 0; i < polygon.Points.Count; i++) { var point = polygon.Points[i]; point.X += polygon.Position.X - tileDimension / 2.0f; point.Y += polygon.Position.Y + tileDimension / 2.0f; polygon.SetPoint(i, point); } polygon.Z = z; if (sortOnY) { for (int y = 0; y < layer.height; y++) { for (int x = 0; x < layer.width; x++) { var i = y * layer.width + x; if ((tiles[i] & 0x0fffffff) == tilesetTileGid) { var cloned = AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, i, collection); ApplyFlip(tiles[i], cloned); } } } } else { for (int x = 0; x < layer.width; x++) { for (int y = 0; y < layer.height; y++) { var i = y * layer.width + x; if ((tiles[i] & 0x0fffffff) == tilesetTileGid) { var cloned = AddPolygonCloneAtXY(layer, tileDimension, polygon, tiles, tilesetTileGid, i, collection); ApplyFlip(tiles[i], cloned); } } } } } else if (circle != null) { throw new NotImplementedException("Need to handle circles..."); } } } } }
public static LayeredTileMap FromTiledMapSave(string tiledMapSaveFile, string contentManager, TiledMapSave tms) { // Ultimately properties are tied to tiles by the tile name. // If a tile has no name but it has properties, those properties // will be lost in the conversion. Therefore, we have to add name properties. tms.MoveTypeToProperties(); #if DEBUG CheckForDuplicateTilesets(tms); #endif tms.NameUnnamedTilesetTiles(); tms.NameUnnamedObjects(); string directory = FlatRedBall.IO.FileManager.GetDirectory(tiledMapSaveFile); var rtmi = ReducedTileMapInfo.FromTiledMapSave( tms, 1, 0, directory, FileReferenceType.Absolute); var toReturn = FromReducedTileMapInfo(rtmi, contentManager, tiledMapSaveFile); AddShapeCollections(toReturn, tms); foreach (var layer in tms.MapLayers) { var matchingLayer = toReturn.MapLayers.FirstOrDefault(item => item.Name == layer.Name); if (matchingLayer != null) { if (layer is MapLayer) { var mapLayer = layer as MapLayer; foreach (var propertyValues in mapLayer.properties) { matchingLayer.Properties.Add(new NamedValue { Name = propertyValues.StrippedName, Value = propertyValues.value, Type = propertyValues.Type }); } matchingLayer.Visible = mapLayer.visible == 1; matchingLayer.Alpha = mapLayer.Opacity; } else if (layer is mapObjectgroup objectLayer) { matchingLayer.Visible = objectLayer.IsVisible; } } } foreach (var tileset in tms.Tilesets) { foreach (var tile in tileset.TileDictionary.Values) { int propertyCountFromTileset = 0; if (tile.properties.Count != 0) { // this needs a name: string name = tile.properties.FirstOrDefault(item => item.StrippedName.ToLowerInvariant() == "name")?.value; // todo - eventually need to copy default values from the Tileset to the tile here AddPropertiesToMap(tms, toReturn.TileProperties, tile.properties, null, name); } } } foreach (var objectLayer in tms.objectgroup) { if (objectLayer.@object != null) { foreach (var objectInstance in objectLayer.@object) { TMXGlueLib.Tileset tileset = null; int propertyCountFromTileset = 0; var objectProperties = objectInstance.properties; List <property> tilesetProperties = null; if (objectInstance.gid != null) { var gidNoFlip = objectInstance.GidNoFlip; tileset = tms.GetTilesetForGid(gidNoFlip.Value); if (tileset.TileDictionary.ContainsKey(gidNoFlip.Value - tileset.Firstgid)) { tilesetProperties = tileset.TileDictionary[gidNoFlip.Value - tileset.Firstgid].properties; propertyCountFromTileset = tilesetProperties.Count; } } if (objectProperties.Count + propertyCountFromTileset != 0) { string name = objectInstance.Name; // if name is null, check the properties: if (string.IsNullOrEmpty(name)) { name = objectProperties.FirstOrDefault(item => item.StrippedNameLower == "name")?.value; } var objectInstanceIsTile = objectInstance.gid != null; if (objectInstanceIsTile) { AddPropertiesToMap(tms, toReturn.TileProperties, objectProperties, tilesetProperties, name); } else { AddPropertiesToMap(tms, toReturn.ShapeProperties, objectProperties, tilesetProperties, name); } } } } } var tmxDirectory = FileManager.GetDirectory(tiledMapSaveFile); // add image layers foreach (var imageLayer in tms.ImageLayers) { var imageLayerFile = tmxDirectory + imageLayer.ImageObject.Source; var texture = FlatRedBallServices.Load <Microsoft.Xna.Framework.Graphics.Texture2D>(imageLayerFile); var newSprite = new Sprite { Texture = texture, Width = imageLayer.ImageObject.Width, Height = imageLayer.ImageObject.Height, X = imageLayer.ImageObject.Width / 2 + imageLayer.OffsetX, Y = -imageLayer.ImageObject.Height / 2 + imageLayer.OffsetY }; var mdb = new MapDrawableBatch(1, texture); mdb.Alpha = imageLayer.Opacity; mdb.AttachTo(toReturn, false); mdb.Paste(newSprite); mdb.Visible = imageLayer.IsVisible; toReturn.mMapLists.Add(mdb); } var animationDictionary = new Dictionary <string, AnimationChain>(); // add animations foreach (var tileset in tms.Tilesets) { string tilesetImageFile = tmxDirectory + tileset.Images[0].Source; if (tileset.SourceDirectory != ".") { tilesetImageFile = tmxDirectory + tileset.SourceDirectory + tileset.Images[0].Source; } var texture = FlatRedBallServices.Load <Microsoft.Xna.Framework.Graphics.Texture2D>(tilesetImageFile, contentManager); foreach (var tile in tileset.Tiles.Where(item => item.Animation != null && item.Animation.Frames.Count != 0)) { var animation = tile.Animation; var animationChain = new AnimationChain(); foreach (var frame in animation.Frames) { var animationFrame = new AnimationFrame(); animationFrame.FrameLength = frame.Duration / 1000.0f; animationFrame.Texture = texture; int tileIdRelative = frame.TileId; int globalTileId = (int)(tileIdRelative + tileset.Firstgid); int leftPixel; int rightPixel; int topPixel; int bottomPixel; TiledMapSave.GetPixelCoordinatesFromGid((uint)globalTileId, tileset, out leftPixel, out topPixel, out rightPixel, out bottomPixel); animationFrame.LeftCoordinate = MapDrawableBatch.CoordinateAdjustment + leftPixel / (float)texture.Width; animationFrame.RightCoordinate = -MapDrawableBatch.CoordinateAdjustment + rightPixel / (float)texture.Width; animationFrame.TopCoordinate = MapDrawableBatch.CoordinateAdjustment + topPixel / (float)texture.Height; animationFrame.BottomCoordinate = -MapDrawableBatch.CoordinateAdjustment + bottomPixel / (float)texture.Height; animationChain.Add(animationFrame); } var property = tile.properties.FirstOrDefault(item => item.StrippedNameLower == "name"); if (property == null) { throw new InvalidOperationException( $"The tile with ID {tile.id} has an animation, but it doesn't have a Name property, which is required for animation."); } else { animationDictionary.Add(property.value, animationChain); } } } toReturn.Animation = new LayeredTileMapAnimation(animationDictionary); AddTileShapeCollections(toReturn, tms, separateOnTileType: true); toReturn.MapProperties = tms.properties .Select(propertySave => new NamedValue { Name = propertySave.name, Value = propertySave.value, Type = propertySave.Type }) .ToList(); return(toReturn); }
private static bool TryCopyTilesetImage(string tmxPath, string destinationPath, Tileset tileset, TilesetImage image) { string sourcepath = GetImageSourcePath(tmxPath, tileset, image); string whyCantCopy = null; if(!System.IO.File.Exists(sourcepath)) { whyCantCopy = "Could not find the file\n" + sourcepath + "\nwhich is referenced by the tileset " + tileset.Name + " in the tmx\n" + tmxPath; } if (!string.IsNullOrEmpty(whyCantCopy)) { System.Console.Error.WriteLine(whyCantCopy); } else { string destinationFullPath = destinationPath + image.sourceFileName; bool shouldCopy = !sourcepath.Equals(destinationFullPath, StringComparison.InvariantCultureIgnoreCase) && !FileManager.GetDirectory(destinationFullPath).Equals(FileManager.GetDirectory(sourcepath)); if(shouldCopy) { if (File.Exists(destinationFullPath)) { // Only copy if source is newer than destination var sourceDate = File.GetLastWriteTimeUtc(sourcepath); var destinationDate = File.GetLastWriteTimeUtc(destinationFullPath); shouldCopy = sourceDate > destinationDate; } } if (shouldCopy) { System.Console.WriteLine("Copying \"{0}\" to \"{1}\".", sourcepath, destinationFullPath); string fileWithoutDotDotSlash = FileManager.RemoveDotDotSlash(sourcepath); const int maxFailures = 5; int currentFailures = 0; while (true) { try { // try it 3 times, in case someone else is copying it at the same time File.Copy(fileWithoutDotDotSlash, destinationFullPath, true); break; } catch (Exception e) { if (currentFailures < maxFailures) { System.Threading.Thread.Sleep(300); // try again: currentFailures++; } else { System.Console.Error.WriteLine("Could not copy \"{0}\" to \"{1}\" \n{2}.", sourcepath, destinationFullPath, e.ToString()); break; } } } } } bool succeeded = string.IsNullOrEmpty(whyCantCopy); return succeeded; }
private void HandleAddNewTilesetClick(object sender, EventArgs e) { string fileName; bool succeeded; GetTilesetFromFileOrOptions(canBeImageFile:true, fileName: out fileName, succeeded: out succeeded); if (fileName != null) { Tileset newTileset = new Tileset(); // We're going to add this to the end, so we need to see what the last tileset is, // get its ID and count, and start this tileset at that number var lastTileset = AppState.Self.CurrentTiledMapSave.Tilesets.LastOrDefault(); uint startingIndex = 1; if (lastTileset != null) { startingIndex = lastTileset.Firstgid + (uint)lastTileset.GetTileCount(); } newTileset.Firstgid = startingIndex; string oldRelative = FileManager.RelativeDirectory; FileManager.RelativeDirectory = AppState.Self.TmxFolder; bool isFileTsx = FileManager.GetExtension(fileName) == "tsx"; bool shouldContinue = true; if (isFileTsx) { newTileset.Source = FileManager.MakeRelative(fileName, AppState.Self.TmxFolder); } else { TilesetWindow window = new TilesetWindow(); TilesetViewModel viewModel = new TilesetViewModel(); viewModel.Name = FileManager.RemovePath(FileManager.RemoveExtension(fileName)); window.DataContext = viewModel; var result = window.ShowDialog(); if(result.HasValue && result.Value) { shouldContinue = true; string sourceToSet = fileName; if (viewModel.CopyFile && !FileManager.IsRelativeTo(fileName, AppState.Self.TmxFolder)) { try { sourceToSet = AppState.Self.TmxFolder + FileManager.RemovePath(fileName); System.IO.File.Copy(fileName, sourceToSet, overwrite: true); } catch (Exception copyException) { MessageBox.Show("Error copying file:\n" + copyException.Message); shouldContinue = false; } } if (shouldContinue) { sourceToSet = FileManager.MakeRelative(sourceToSet, AppState.Self.TmxFolder); newTileset.Images = new TilesetImage[1]; newTileset.Name = viewModel.Name; newTileset.Tilewidth = viewModel.TileWidth; newTileset.Tileheight = viewModel.TileHeight; newTileset.Margin = viewModel.Margin; newTileset.Spacing = viewModel.Spacing; var tilesetImage = new TilesetImage(); tilesetImage.Source = sourceToSet; // We could use either the original file or the copied file. // I'll use the original since it's in scope and shouldn't matter var dimensions = ImageHeader.GetDimensions(fileName); tilesetImage.width = dimensions.Width; tilesetImage.height = dimensions.Height; newTileset.Images[0] = tilesetImage; } } else { shouldContinue = false; } } FileManager.RelativeDirectory = oldRelative; if (shouldContinue) { AppState.Self.CurrentTiledMapSave.Tilesets.Add(newTileset); // refresh everything: RefreshListBox(newTileset); mTilesetsListBox.SelectedItem = newTileset; UpdateXnaDisplayToTileset(); // and make sure the apps are notified about the change if (AnyTileMapChange != null) { AnyTileMapChange(this, null); } } } }
private void RefreshListBox(Tileset tileset) { // Does this tileset already exist? bool alreadyExists = mTilesetsListBox.Items.Contains(tileset); if (alreadyExists) { var index = mTilesetsListBox.Items.IndexOf(tileset); // This should refresh the text, but keep things selected mTilesetsListBox.Items[index] = tileset; } else { mTilesetsListBox.Items.Add(tileset); } }
private ExternalTileSet CloneAndCast(Tileset tileset) { string container; FileManager.XmlSerialize(tileset, out container); container = container.Replace("<mapTileset", "<tileset"); container = container.Replace(@"</mapTileset", "</tileset"); return FileManager.XmlDeserializeFromString<ExternalTileSet>(container); }
private static TileCollisions.TileShapeCollection GetTileShapeCollectionForLayer(TMXGlueLib.MapLayer layer, TMXGlueLib.Tileset tileset, float tileDimension, float z) { TileCollisions.TileShapeCollection toReturn = new TileCollisions.TileShapeCollection(); toReturn.Name = layer.Name; Math.Geometry.AxisAlignedRectangle rectangle = null; Math.Geometry.Polygon polygon = null; Circle circle; var tiles = layer.data[0].tiles; bool sortOnY = layer.height > layer.width; foreach (var tilesetTile in tileset.Tiles.Where(item => item.Objects?.@object?.Length > 0)) { var tilesetTileGid = tilesetTile.id + tileset.Firstgid; foreach (var tilesetObject in tilesetTile.Objects.@object) { TiledMapToShapeCollectionConverter.ConvertTiledObjectToFrbShape(tilesetObject, out polygon, out rectangle, out circle); if (rectangle != null) { rectangle.Z = z; if (sortOnY) { for (int y = 0; y < layer.height; y++) { for (int x = 0; x < layer.width; x++) { AddRectangleCloneAtXY(layer, tileDimension, toReturn, rectangle, tiles, tilesetTileGid, x, y); } } } else { for (int x = 0; x < layer.width; x++) { for (int y = 0; y < layer.height; y++) { AddRectangleCloneAtXY(layer, tileDimension, toReturn, rectangle, tiles, tilesetTileGid, x, y); } } } } else if (polygon != null) { // For tile polygons we want them to be centered on the tile. // To do this, we shift all points by its position: for (int i = 0; i < polygon.Points.Count; i++) { var point = polygon.Points[i]; point.X += polygon.Position.X - tileDimension / 2.0f; point.Y += polygon.Position.Y + tileDimension / 2.0f; polygon.SetPoint(i, point); } polygon.Z = z; if (sortOnY) { for (int y = 0; y < layer.height; y++) { for (int x = 0; x < layer.width; x++) { AddPolygonCloneAtXY(layer, tileDimension, toReturn, polygon, tiles, tilesetTileGid, x, y); } } } else { for (int x = 0; x < layer.width; x++) { for (int y = 0; y < layer.height; y++) { AddPolygonCloneAtXY(layer, tileDimension, toReturn, polygon, tiles, tilesetTileGid, x, y); } } } } else if (circle != null) { throw new NotImplementedException("Need to handle circles..."); } } } // The values are inserted sorted above for speed, now we set the XAxis - this will also sort but will be fast: if (sortOnY) { toReturn.SortAxis = Math.Axis.Y; } else { toReturn.SortAxis = Math.Axis.X; } toReturn.RefreshAllRepositionDirections(); return(toReturn); }