/// <summary> /// Initializes a new instance of the <see cref="GrhData"/> class. /// </summary> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> protected GrhData(SpriteCategorization cat) { // This is the only way we allow GrhDatas with an invalid GrhIndex. It should only ever be called for // GrhDatas that will NOT persist, such as the AutomaticAnimatedGrhData's frames. _categorization = cat; _grhIndex = GrhIndex.Invalid; }
/// <summary> /// Sets the GrhToPlace from the hotkey. /// </summary> /// <param name="hotkeyIndex">The hotkey index.</param> public void SetGrhFromHotkey(int hotkeyIndex) { if (hotkeyIndex >= HotkeyedGrhs.Length || hotkeyIndex < 0) { return; } string categorization = HotkeyedGrhs[hotkeyIndex]; if (string.IsNullOrEmpty(categorization)) { return; } GrhData grhData = null; try { SpriteCategorization cat = new SpriteCategorization(categorization); grhData = GrhInfo.GetData(cat); } catch (Exception ex) { Debug.Fail(ex.ToString()); } if (grhData == null) { // No grh for categorization, so unset it HotkeyedGrhs[hotkeyIndex] = null; return; } Map.SetGrhToPlace(grhData.GrhIndex); }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copy = new StationaryGrhData(ContentManager, newGrhIndex, newCategorization, TextureName, AutomaticSize ? (Rectangle?)null : SourceRect); return(copy); }
public static StationaryGrhData CreateGrhData(IContentManager contentManager, SpriteCategory category) { var index = NextFreeIndex(); var title = GetUniqueTitle(category, "tmp" + index); var categorization = new SpriteCategorization(category, title); return(CreateGrhData(index, contentManager, categorization, string.Empty, Vector2.Zero, Vector2.Zero)); }
/// <summary> /// Gets the <see cref="IAddGrhDataTask"/>s for stationary <see cref="GrhData"/>s. /// </summary> /// <param name="rootGrhDir">The root grh dir.</param> /// <returns>The <see cref="IAddGrhDataTask"/>s for stationary <see cref="GrhData"/>s.</returns> static IEnumerable <IAddGrhDataTask> GetStationaryTasks(string rootGrhDir) { var dirSepChars = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; if (log.IsInfoEnabled) { log.InfoFormat("Searching for automatic stationary GrhDatas from root `{0}`.", rootGrhDir); } // Get a List of all of the textures from the root directory var textures = FindTextures(rootGrhDir); // Get a List of all of the used textures var usedTextures = FindUsedTextures(); // Grab the relative path instead of the complete file path since this // is how they are stored in the GrhData, then if it is in the usedTextures, remove it var trimLen = GetRelativeTrimLength(rootGrhDir); textures.RemoveAll(x => usedTextures.ContainsKey(TextureAbsoluteToRelativePath(trimLen, x))); // Check if there are any unused textures if (textures.Count == 0) { return(Enumerable.Empty <IAddGrhDataTask>()); } // Create the GrhDatas var ret = new List <IAddGrhDataTask>(); foreach (var texture in textures) { // Go back to the relative path, and use it to figure out the categorization var relative = TextureAbsoluteToRelativePath(trimLen, texture); if (relative.LastIndexOfAny(dirSepChars) < 0) { if (log.IsWarnEnabled) { log.WarnFormat("Stationary GrhData found at `{0}`, but could not be created because it has no category.", texture); } continue; } var categorization = SpriteCategorization.SplitCategoryAndTitle(relative); // Ensure the GrhData doesn't already exist if (GrhInfo.GetData(categorization) != null) { continue; } // Create the task ret.Add(new AddStationaryGrhDataTask(categorization, relative)); } return(ret); }
/// <summary> /// Gets the <see cref="IAddGrhDataTask"/>s for animated <see cref="GrhData"/>s. /// </summary> /// <param name="rootGrhDir">The root grh dir.</param> /// <returns>The <see cref="IAddGrhDataTask"/>s for animated <see cref="GrhData"/>s.</returns> static IEnumerable <IAddGrhDataTask> GetAnimatedTasks(string rootGrhDir) { if (log.IsInfoEnabled) { log.InfoFormat("Searching for automatic animated GrhDatas from root `{0}`.", rootGrhDir); } var ret = new List <IAddGrhDataTask>(); // Find all directories that match the needed pattern var dirs = GetAnimatedDirectories(rootGrhDir); foreach (var dir in dirs) { // Grab the animation info from the directory var animInfo = AutomaticAnimatedGrhData.GetAutomaticAnimationInfo(dir); if (animInfo == null) { continue; } // Get the virtual directory (remove the root) var partialDir = dir.Substring(rootGrhDir.Length); if (partialDir.StartsWith(Path.DirectorySeparatorChar.ToString()) || partialDir.StartsWith(Path.AltDirectorySeparatorChar.ToString())) { partialDir = partialDir.Substring(1); } // Get the categorization var lastDirSep = partialDir.LastIndexOf(Path.DirectorySeparatorChar); if (lastDirSep < 0) { if (log.IsWarnEnabled) { log.WarnFormat("Animated GrhData found at `{0}`, but could not be created because it has no category.", dir); } continue; } var categoryStr = partialDir.Substring(0, lastDirSep); var categorization = new SpriteCategorization(new SpriteCategory(categoryStr), new SpriteTitle(animInfo.Title)); // Ensure the GrhData doesn't already exist if (GrhInfo.GetData(categorization) != null) { continue; } // Create the task ret.Add(new AddAnimatedGrhDataTask(categorization)); } return(ret); }
/// <summary> /// Initializes a new instance of the <see cref="AnimatedGrhData"/> class. /// </summary> /// <param name="r">The <see cref="IValueReader"/>.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> AnimatedGrhData(IValueReader r, GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { var speed = r.ReadInt(_speedValueKey); var frames = r.ReadMany(_framesNodeName, (xreader, xname) => xreader.ReadGrhIndex(xname)); _speed = 1f / speed; _frames = CreateFrames(frames); _size = GetMaxSize(_frames); }
/// <summary> /// Initializes a new instance of the <see cref="StationaryGrhData"/> class. /// </summary> /// <param name="cm">The <see cref="IContentManager"/>.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <param name="textureName">Name of the texture.</param> /// <param name="textureSource">The area of the texture to use, or null for the whole texture.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> public StationaryGrhData(IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat, TextureAssetName textureName, Rectangle? textureSource) : base(grhIndex, cat) { _cm = cm; _textureName = textureName; if (textureSource == null) AutomaticSize = true; else SetSourceRect(textureSource.Value); }
/// <summary> /// Initializes a new instance of the <see cref="StationaryGrhData"/> class. /// </summary> /// <param name="r">The <see cref="IValueReader"/>.</param> /// <param name="cm">The <see cref="IContentManager"/>.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> StationaryGrhData(IValueReader r, IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { _cm = cm; var automaticSize = r.ReadBool(_automaticSizeValueKey); var textureReader = r.ReadNode(_textureNodeName); var textureName = textureReader.ReadTextureAssetName(_textureNameValueKey); var textureSource = textureReader.ReadRectangle(_textureSourceValueKey); _textureName = textureName; SetSourceRect(textureSource); _automaticSize = automaticSize; }
/// <summary> /// Initializes a new instance of the <see cref="AutomaticAnimatedGrhData"/> class. /// </summary> /// <param name="cm">The <see cref="IContentManager"/> used for creating the frames.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> internal AutomaticAnimatedGrhData(IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { var framesDir = GetFramesDirectory(); if (framesDir == null) return; var animInfo = GetAutomaticAnimationInfo(framesDir); _speed = 1f / animInfo.Speed; Debug.Assert(animInfo.Title == cat.Title); _cm = cm; _frames = CreateFrames(framesDir); }
/// <summary> /// Creates a new <see cref="AutomaticAnimatedGrhData"/>. This should only be called from the /// AutomaticGrhDataUpdater. /// </summary> /// <param name="contentManager">The content manager.</param> /// <param name="categorization">The categorization for the <see cref="AutomaticAnimatedGrhData"/>.</param> /// <returns>The new <see cref="AutomaticAnimatedGrhData"/>, or null if none created.</returns> public static AutomaticAnimatedGrhData CreateAutomaticAnimatedGrhData(IContentManager contentManager, SpriteCategorization categorization) { // Check if the GrhData already exists if (GetData(categorization) != null) { return(null); } var index = NextFreeIndex(); var gd = new AutomaticAnimatedGrhData(contentManager, index, categorization); AddGrhData(gd); return(gd); }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copyArray = new StationaryGrhData[_frames.Length]; Array.Copy(_frames, copyArray, _frames.Length); var copy = new AnimatedGrhData(newGrhIndex, newCategorization) { _frames = copyArray }; copy.SetSpeed(Speed); return(copy); }
/// <summary> /// Initializes a new instance of the <see cref="StationaryGrhData"/> class. /// </summary> /// <param name="cm">The <see cref="IContentManager"/>.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <param name="textureName">Name of the texture.</param> /// <param name="textureSource">The area of the texture to use, or null for the whole texture.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> public StationaryGrhData(IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat, TextureAssetName textureName, Rectangle?textureSource) : base(grhIndex, cat) { _cm = cm; _textureName = textureName; if (textureSource == null) { AutomaticSize = true; } else { SetSourceRect(textureSource.Value); } }
/// <summary> /// Initializes a new instance of the <see cref="AutomaticAnimatedGrhData"/> class. /// </summary> /// <param name="cm">The <see cref="IContentManager"/> used for creating the frames.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> internal AutomaticAnimatedGrhData(IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { var framesDir = GetFramesDirectory(); if (framesDir == null) { return; } var animInfo = GetAutomaticAnimationInfo(framesDir); _speed = 1f / animInfo.Speed; Debug.Assert(animInfo.Title == cat.Title); _cm = cm; _frames = CreateFrames(framesDir); }
/// <summary> /// Creates a deep copy of the <see cref="GrhData"/>. /// </summary> /// <param name="newCategorization">Categorization for the duplicated GrhData. Must be unique.</param> /// <returns>Deep copy of the <see cref="GrhData"/> with the new categorization and its own /// unique <see cref="GrhIndex"/>.</returns> /// <exception cref="ArgumentException"><paramref name="newCategorization"/> is already in use.</exception> public GrhData Duplicate(SpriteCategorization newCategorization) { if (GrhInfo.GetData(newCategorization) != null) { throw new ArgumentException("Category already in use.", "newCategorization"); } var index = GrhInfo.NextFreeIndex(); Debug.Assert(GrhInfo.GetData(index) == null, "Slot to use is already in use! How the hell did this happen!? GrhInfo.NextFreeIndex() must be broken."); var dc = DeepCopy(newCategorization, index); GrhInfo.AddGrhData(dc); return(dc); }
/// <summary> /// Initializes a new instance of the <see cref="GrhData"/> class. /// </summary> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> protected GrhData(GrhIndex grhIndex, SpriteCategorization cat) { if (cat == null) throw new ArgumentNullException("cat"); if (grhIndex.IsInvalid) { const string errmsg = "Failed to create GrhData with category `{0}`." + " No GrhData may be created with a GrhIndex equal to GrhIndex.Invalid"; var err = string.Format(errmsg, cat); log.Error(err); throw new ArgumentOutOfRangeException("grhIndex", err); } _categorization = cat; _grhIndex = grhIndex; }
/// <summary> /// Initializes a new instance of the <see cref="GrhData"/> class. /// </summary> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> protected GrhData(GrhIndex grhIndex, SpriteCategorization cat) { if (cat == null) { throw new ArgumentNullException("cat"); } if (grhIndex.IsInvalid) { const string errmsg = "Failed to create GrhData with category `{0}`." + " No GrhData may be created with a GrhIndex equal to GrhIndex.Invalid"; var err = string.Format(errmsg, cat); log.Error(err); throw new ArgumentOutOfRangeException("grhIndex", err); } _categorization = cat; _grhIndex = grhIndex; }
static StationaryGrhData CreateGrhData(GrhIndex grhIndex, IContentManager contentManager, SpriteCategorization categorization, string texture, Vector2?pos, Vector2?size) { Debug.Assert(!grhIndex.IsInvalid); Rectangle?source; if (pos == null || size == null) { source = null; } else { source = new Rectangle(pos.Value.X, pos.Value.Y, size.Value.X, size.Value.Y); } var gd = new StationaryGrhData(contentManager, grhIndex, categorization, texture, source); AddGrhData(gd); return(gd); }
/// <summary> /// Initializes a new instance of the <see cref="AutomaticAnimatedGrhData"/> class. /// </summary> /// <param name="cm">The <see cref="IContentManager"/> used for creating the frames.</param> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> internal AutomaticAnimatedGrhData(IContentManager cm, GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { _cm = cm; try { var framesDir = GetFramesDirectory(); var framesDirName = Path.GetFileName(framesDir).Substring(1); // Get dir name only, and skip the _ at the start var fileTags = FileTags.Create(framesDirName); _speed = 1f / fileTags.AnimationSpeed.Value; Debug.Assert(fileTags.Title == cat.Title); _frames = CreateFrames(framesDir); } catch { _speed = 1f; _frames = new StationaryGrhData[0]; } }
/// <summary> /// Sets the categorization for the <see cref="GrhData"/>. /// </summary> /// <param name="categorization">The new categorization.</param> /// <exception cref="ArgumentNullException"><paramref name="categorization" /> is <c>null</c>.</exception> public void SetCategorization(SpriteCategorization categorization) { if (categorization == null) { throw new ArgumentNullException("categorization"); } // Check that either of the values are different if (_categorization == categorization) { return; } var oldCategorization = _categorization; _categorization = categorization; if (CategorizationChanged != null) { CategorizationChanged.Raise(this, EventArgsHelper.Create(oldCategorization)); } }
/// <summary> /// Sets the categorization for the <see cref="GrhData"/>. /// </summary> /// <param name="categorization">The new categorization.</param> /// <exception cref="ArgumentNullException"><paramref name="categorization" /> is <c>null</c>.</exception> public void SetCategorization(SpriteCategorization categorization) { if (categorization == null) throw new ArgumentNullException("categorization"); // Check that either of the values are different if (_categorization == categorization) return; var oldCategorization = _categorization; _categorization = categorization; if (CategorizationChanged != null) CategorizationChanged.Raise(this, EventArgsHelper.Create(oldCategorization)); }
/// <summary> /// Initializes a new instance of the <see cref="AnimatedGrhData"/> class. /// </summary> /// <param name="grhIndex">The <see cref="GrhIndex"/>.</param> /// <param name="cat">The <see cref="SpriteCategorization"/>.</param> /// <exception cref="ArgumentNullException"><paramref name="cat"/> is null.</exception> /// <exception cref="ArgumentOutOfRangeException"><paramref name="grhIndex"/> is equal to GrhIndex.Invalid.</exception> public AnimatedGrhData(GrhIndex grhIndex, SpriteCategorization cat) : base(grhIndex, cat) { _frames = new StationaryGrhData[0]; _speed = 1f / 300f; }
public static StationaryGrhData CreateGrhData(IContentManager contentManager, SpriteCategorization categorization, string texture, GrhIndex?index = null) { return(CreateGrhData(index ?? NextFreeIndex(), contentManager, categorization, texture, null, null)); }
public static AnimatedGrhData CreateGrhData(IEnumerable <GrhIndex> frames, float speed, SpriteCategorization categorization) { var grhIndex = NextFreeIndex(); var gd = new AnimatedGrhData(grhIndex, categorization); gd.SetSpeed(speed); gd.SetFrames(frames); AddGrhData(gd); return(gd); }
protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { throw new NotSupportedException("Cannot make a copy of an AutomaticAnimatedGrhData."); }
/// <summary> /// Updates all of the automaticly added GrhDatas. /// </summary> /// <param name="cm"><see cref="IContentManager"/> to use for new GrhDatas.</param> /// <param name="rootGrhDir">Root Grh texture directory.</param> /// <param name="added">The GrhDatas that were added (empty if none were added).</param> /// <param name="deleted">The GrhDatas that were deleted (empty if none were added).</param> /// <param name="grhDataFileTags">The file tags for the corresponding GrhDatas.</param> /// <returns> /// IEnumerable of all of the new GrhDatas created. /// </returns> public static void Update(IContentManager cm, string rootGrhDir, out GrhData[] added, out GrhData[] deleted, out Dictionary <GrhData, GrhData.FileTags> grhDataFileTags) { if (!rootGrhDir.EndsWith("\\") && !rootGrhDir.EndsWith("/")) { rootGrhDir += "/"; } // Clear the temporary content to make sure we have plenty of working memory cm.Unload(ContentLevel.Temporary, true); // Get the relative file path for all files up-front (only do this once since it doesn't scale well) var relFilePaths = Directory.GetFiles(rootGrhDir, "*", SearchOption.AllDirectories) .Select(x => x.Replace('\\', '/').Substring(rootGrhDir.Length)) .ToArray(); // Also grab the existing GrhDatas var existingGrhDatas = GrhInfo.GrhDatas.ToDictionary(x => x.Categorization.ToString(), x => x); // Go through each file and do the adds grhDataFileTags = new Dictionary <GrhData, GrhData.FileTags>(); HashSet <GrhData> addedGrhDatas = new HashSet <GrhData>(); HashSet <GrhData> deletedGrhDatas = new HashSet <GrhData>(); HashSet <GrhData> grhDatasToDelete = new HashSet <GrhData>(existingGrhDatas.Values); HashSet <string> checkedAnimationRelDirs = new HashSet <string>(StringComparer.OrdinalIgnoreCase); foreach (var relFilePath in relFilePaths) { // Before doing anything else, ensure it is a valid file type to handle string fileExtension = Path.GetExtension(relFilePath); if (!_graphicFileSuffixes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase)) { continue; } string absFilePath = rootGrhDir + relFilePath; // Grab some stuff based on the file path string absDir = Path.GetDirectoryName(absFilePath); if (rootGrhDir.Length >= absDir.Length) { continue; } string relDir = absDir.Substring(rootGrhDir.Length); string parentDirName = absDir.Substring(Path.GetDirectoryName(absDir).Length + 1); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(relFilePath); bool isAnimationFrame = parentDirName.StartsWith("_"); if (!isAnimationFrame) { // ** Stationary ** var fileTags = GrhData.FileTags.Create(fileNameWithoutExtension); // Build the categorization info string category = relDir.Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter); SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title); // Get existing GrhIndex?grhIndex = null; GrhData grhData; if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData)) { grhDatasToDelete.Remove(grhData); } // If already exists as animated, delete first if (grhData != null && (grhData is AnimatedGrhData || grhData is AutomaticAnimatedGrhData)) { grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex GrhInfo.Delete(grhData); deletedGrhDatas.Add(grhData); grhData = null; } // Add new string texturePath = "/" + relFilePath.Substring(0, relFilePath.Length - Path.GetExtension(absFilePath).Length); if (grhData == null) { grhData = GrhInfo.CreateGrhData(cm, cat, texturePath, grhIndex); addedGrhDatas.Add(grhData); } else { // Make sure the texture is correct string currTextureName = "/" + ((StationaryGrhData)grhData).TextureName.ToString().TrimStart('/', '\\'); if (currTextureName != texturePath) { ((StationaryGrhData)grhData).ChangeTexture(texturePath); } } // Ensure set to auto-size StationaryGrhData stationaryGrhData = (StationaryGrhData)grhData; if (!stationaryGrhData.AutomaticSize) { stationaryGrhData.AutomaticSize = true; } grhDataFileTags.Add(grhData, fileTags); } else { // ** Animated ** // Make sure we only handle each animation once (since this will get called for each frame since we're looping over the files) if (!checkedAnimationRelDirs.Add(relDir)) { continue; } var fileTags = GrhData.FileTags.Create(parentDirName.Substring(1)); // Remove the _ prefix from directory name // Build the categorization string category = Path.GetDirectoryName(absDir).Substring(rootGrhDir.Length) .Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter); SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title); // Get existing GrhIndex?grhIndex = null; GrhData grhData; if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData)) { grhDatasToDelete.Remove(grhData); // If already exists as stationary, delete first if (grhData is StationaryGrhData) { grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex GrhInfo.Delete(grhData); deletedGrhDatas.Add(grhData); grhData = null; } } // Add new if (grhData == null) { grhData = GrhInfo.CreateAutomaticAnimatedGrhData(cm, cat, grhIndex); addedGrhDatas.Add(grhData); } grhDataFileTags.Add(grhData, fileTags); } } // Now check if there are any GrhDatas to be deleted by taking existing GrhDatas, getting their relative path, and // see if that exists in our relative path list we built earlier against the file system foreach (var toDelete in grhDatasToDelete) { GrhInfo.Delete(toDelete); deletedGrhDatas.Add(toDelete); } if (log.IsInfoEnabled) { log.WarnFormat("Automatic GrhData creation update resulted in `{0}` new GrhData(s) and `{1}` deleted GrhData(s).", addedGrhDatas.Count, deletedGrhDatas.Count); } added = addedGrhDatas.ToArray(); deleted = deletedGrhDatas.ToArray(); }
/// <summary> /// Sets the GrhToPlace from the hotkey. /// </summary> /// <param name="hotkeyIndex">The hotkey index.</param> public void SetGrhFromHotkey(int hotkeyIndex) { if (hotkeyIndex >= HotkeyedGrhs.Length || hotkeyIndex < 0) return; string categorization = HotkeyedGrhs[hotkeyIndex]; if (string.IsNullOrEmpty(categorization)) return; GrhData grhData = null; try { SpriteCategorization cat = new SpriteCategorization(categorization); grhData = GrhInfo.GetData(cat); } catch (Exception ex) { Debug.Fail(ex.ToString()); } if (grhData == null) { // No grh for categorization, so unset it HotkeyedGrhs[hotkeyIndex] = null; return; } Map.SetGrhToPlace(grhData.GrhIndex); }
protected internal static void ReadHeader(IValueReader r, out GrhIndex grhIndex, out SpriteCategorization cat) { grhIndex = r.ReadGrhIndex(_indexValueKey); cat = r.ReadSpriteCategorization(_categorizationValueKey); }
/// <summary> /// Updates all of the automaticly added GrhDatas. /// </summary> /// <param name="cm"><see cref="IContentManager"/> to use for new GrhDatas.</param> /// <param name="rootGrhDir">Root Grh texture directory.</param> /// <param name="added">The GrhDatas that were added (empty if none were added).</param> /// <param name="deleted">The GrhDatas that were deleted (empty if none were added).</param> /// <param name="grhDataFileTags">The file tags for the corresponding GrhDatas.</param> /// <returns> /// IEnumerable of all of the new GrhDatas created. /// </returns> public static void Update(IContentManager cm, string rootGrhDir, out GrhData[] added, out GrhData[] deleted, out Dictionary<GrhData, GrhData.FileTags> grhDataFileTags) { if (!rootGrhDir.EndsWith("\\") && !rootGrhDir.EndsWith("/")) rootGrhDir += "/"; // Clear the temporary content to make sure we have plenty of working memory cm.Unload(ContentLevel.Temporary, true); // Get the relative file path for all files up-front (only do this once since it doesn't scale well) var relFilePaths = Directory.GetFiles(rootGrhDir, "*", SearchOption.AllDirectories) .Select(x => x.Replace('\\', '/').Substring(rootGrhDir.Length)) .ToArray(); // Also grab the existing GrhDatas var existingGrhDatas = GrhInfo.GrhDatas.ToDictionary(x => x.Categorization.ToString(), x => x); // Go through each file and do the adds grhDataFileTags = new Dictionary<GrhData, GrhData.FileTags>(); HashSet<GrhData> addedGrhDatas = new HashSet<GrhData>(); HashSet<GrhData> deletedGrhDatas = new HashSet<GrhData>(); HashSet<GrhData> grhDatasToDelete = new HashSet<GrhData>(existingGrhDatas.Values); HashSet<string> checkedAnimationRelDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (var relFilePath in relFilePaths) { // Before doing anything else, ensure it is a valid file type to handle string fileExtension = Path.GetExtension(relFilePath); if (!_graphicFileSuffixes.Contains(fileExtension, StringComparer.OrdinalIgnoreCase)) continue; string absFilePath = rootGrhDir + relFilePath; // Grab some stuff based on the file path string absDir = Path.GetDirectoryName(absFilePath); if (rootGrhDir.Length >= absDir.Length) continue; string relDir = absDir.Substring(rootGrhDir.Length); string parentDirName = absDir.Substring(Path.GetDirectoryName(absDir).Length + 1); string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(relFilePath); bool isAnimationFrame = parentDirName.StartsWith("_"); if (!isAnimationFrame) { // ** Stationary ** var fileTags = GrhData.FileTags.Create(fileNameWithoutExtension); // Build the categorization info string category = relDir.Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter); SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title); // Get existing GrhIndex? grhIndex = null; GrhData grhData; if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData)) { grhDatasToDelete.Remove(grhData); } // If already exists as animated, delete first if (grhData != null && (grhData is AnimatedGrhData || grhData is AutomaticAnimatedGrhData)) { grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex GrhInfo.Delete(grhData); deletedGrhDatas.Add(grhData); grhData = null; } // Add new string texturePath = "/" + relFilePath.Substring(0, relFilePath.Length - Path.GetExtension(absFilePath).Length); if (grhData == null) { grhData = GrhInfo.CreateGrhData(cm, cat, texturePath, grhIndex); addedGrhDatas.Add(grhData); } else { // Make sure the texture is correct string currTextureName = "/" + ((StationaryGrhData)grhData).TextureName.ToString().TrimStart('/', '\\'); if (currTextureName != texturePath) { ((StationaryGrhData)grhData).ChangeTexture(texturePath); } } // Ensure set to auto-size StationaryGrhData stationaryGrhData = (StationaryGrhData)grhData; if (!stationaryGrhData.AutomaticSize) { stationaryGrhData.AutomaticSize = true; } // Add to GrhDataFileTags if (grhDataFileTags.ContainsKey(grhData)) { throw new GrhDataException(grhData, string.Format("Found more than one stationary GrhData with the categorization: `{0}`." + " Make sure that you do not have multiple image files in /DevContent/ in the same folder with the same name but different extensions (e.g. Sprite.png and Sprite.jpg).", grhData.Categorization)); } grhDataFileTags.Add(grhData, fileTags); } else { // ** Animated ** // Make sure we only handle each animation once (since this will get called for each frame since we're looping over the files) if (!checkedAnimationRelDirs.Add(relDir)) continue; var fileTags = GrhData.FileTags.Create(parentDirName.Substring(1)); // Remove the _ prefix from directory name // Build the categorization string category = Path.GetDirectoryName(absDir).Substring(rootGrhDir.Length) .Replace("/", SpriteCategorization.Delimiter).Replace("\\", SpriteCategorization.Delimiter); SpriteCategorization cat = new SpriteCategorization(category, fileTags.Title); // Get existing GrhIndex? grhIndex = null; GrhData grhData; if (existingGrhDatas.TryGetValue(cat.ToString(), out grhData)) { grhDatasToDelete.Remove(grhData); // If already exists as stationary, delete first if (grhData is StationaryGrhData) { grhIndex = grhData.GrhIndex; // We will re-use this GrhIndex GrhInfo.Delete(grhData); deletedGrhDatas.Add(grhData); grhData = null; } } // Add new if (grhData == null) { grhData = GrhInfo.CreateAutomaticAnimatedGrhData(cm, cat, grhIndex); addedGrhDatas.Add(grhData); } // Add to GrhDataFileTags if (grhDataFileTags.ContainsKey(grhData)) { throw new GrhDataException(grhData, string.Format("Found more than one animated GrhData with the categorization: `{0}`." + " Make sure that you do not have multiple sub-folders in /DevContent/ in the same folder with the same name but different folder tags (e.g. /_Sprite[s100]/ and /_Sprite[s200]/).", grhData.Categorization)); } grhDataFileTags.Add(grhData, fileTags); } } // Now check if there are any GrhDatas to be deleted by taking existing GrhDatas, getting their relative path, and // see if that exists in our relative path list we built earlier against the file system foreach (var toDelete in grhDatasToDelete) { GrhInfo.Delete(toDelete); deletedGrhDatas.Add(toDelete); } if (log.IsInfoEnabled) log.WarnFormat("Automatic GrhData creation update resulted in `{0}` new GrhData(s) and `{1}` deleted GrhData(s).", addedGrhDatas.Count, deletedGrhDatas.Count); added = addedGrhDatas.ToArray(); deleted = deletedGrhDatas.ToArray(); }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns>A deep copy of this <see cref="GrhData"/>.</returns> protected abstract GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex);
void btnAccept_Click(object sender, EventArgs e) { var gdStationary = _gd as StationaryGrhData; var gdAnimated = _gd as AnimatedGrhData; // Validate the category and title, making sure its unique if (!ValidateCategorization(true)) { return; } if (radioAnimated.Checked) { // Generate the frames var framesText = txtFrames.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var frames = new GrhIndex[framesText.Length]; for (var i = 0; i < framesText.Length; i++) { // First check if it was entered as by the index if (!Parser.Current.TryParse(framesText[i], out frames[i])) { // Support it being entered by category var lastPeriod = framesText[i].LastIndexOf('.'); var category = framesText[i].Substring(0, lastPeriod); var title = framesText[i].Substring(lastPeriod + 1); var tempGD = GrhInfo.GetData(category, title); if (tempGD != null) { frames[i] = tempGD.GrhIndex; } } } // Check that all the frames are valid foreach (var frame in frames) { if (GrhInfo.GetData(frame) == null) { MessageBox.Show("GrhIndex [" + frame + "] does not exist! Aborting save..."); return; } } } // Validate the strings GrhIndex newIndex; if (Parser.Current.TryParse(txtIndex.Text, out newIndex)) { if (newIndex != _gd.GrhIndex) { if ( MessageBox.Show("Are you sure you wish to change the index? Changes will not be reflected on maps!", "Change GrhIndex", MessageBoxButtons.YesNo) == DialogResult.No) { return; } if (GrhInfo.GetData(newIndex) != null) { MessageBox.Show("Index already in use"); return; } } } else { MessageBox.Show("Invalid index specified"); return; } // Get the categorization var categorization = new SpriteCategorization(txtCategory.GetSanitizedText(), txtTitle.Text); // Set the information if (radioStationary.Checked) { if (gdStationary == null) { MessageBox.Show("For some reason, could not cast the GrhData to StationaryGrhData..."); return; } // Stationary var cm = gdStationary.ContentManager; var x = Parser.Current.ParseInt(txtX.Text); var y = Parser.Current.ParseInt(txtY.Text); var w = Parser.Current.ParseInt(txtW.Text); var h = Parser.Current.ParseInt(txtH.Text); var textureName = txtTexture.GetSanitizedText(); var autoSize = chkAutoSize.Checked; // Validate the texture try { cm.LoadImage("Grh" + DirSep + textureName, ContentLevel.Map); } catch (Exception ex) { MessageBox.Show("Unable to load texture [" + textureName + "]! Aborting save..." + Environment.NewLine + ex); return; } gdStationary.ChangeTexture(textureName, new Rectangle(x, y, w, h)); _gd.SetCategorization(categorization); gdStationary.AutomaticSize = autoSize; } else { // Animated if (gdAnimated == null) { MessageBox.Show("For some reason, could not cast the GrhData to AnimatedGrhData..."); return; } var speed = Parser.Current.ParseFloat(txtSpeed.Text); gdAnimated.SetSpeed(speed); } // Set the MapGrhWalls _mapGrhWalls[_gd] = BoundWalls.ToList(); // Write Enabled = false; GrhInfo.Save(ContentPaths.Dev); Enabled = true; WasCanceled = false; DialogResult = DialogResult.OK; }
/// <summary> /// Initializes a new instance of the <see cref="AddAnimatedGrhDataTask"/> class. /// </summary> /// <param name="categorization">The <see cref="SpriteCategorization"/>.</param> public AddAnimatedGrhDataTask(SpriteCategorization categorization) { _categorization = categorization; }
void btnAccept_Click(object sender, EventArgs e) { var gdStationary = _gd as StationaryGrhData; var gdAnimated = _gd as AnimatedGrhData; // Validate the category and title, making sure its unique if (!ValidateCategorization(true)) return; if (radioAnimated.Checked) { // Generate the frames var framesText = txtFrames.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var frames = new GrhIndex[framesText.Length]; for (var i = 0; i < framesText.Length; i++) { // First check if it was entered as by the index if (!Parser.Current.TryParse(framesText[i], out frames[i])) { // Support it being entered by category var lastPeriod = framesText[i].LastIndexOf('.'); var category = framesText[i].Substring(0, lastPeriod); var title = framesText[i].Substring(lastPeriod + 1); var tempGD = GrhInfo.GetData(category, title); if (tempGD != null) frames[i] = tempGD.GrhIndex; } } // Check that all the frames are valid foreach (var frame in frames) { if (GrhInfo.GetData(frame) == null) { MessageBox.Show("GrhIndex [" + frame + "] does not exist! Aborting save..."); return; } } } // Validate the strings GrhIndex newIndex; if (Parser.Current.TryParse(txtIndex.Text, out newIndex)) { if (newIndex != _gd.GrhIndex) { if ( MessageBox.Show("Are you sure you wish to change the index? Changes will not be reflected on maps!", "Change GrhIndex", MessageBoxButtons.YesNo) == DialogResult.No) return; if (GrhInfo.GetData(newIndex) != null) { MessageBox.Show("Index already in use"); return; } } } else { MessageBox.Show("Invalid index specified"); return; } // Get the categorization var categorization = new SpriteCategorization(txtCategory.GetSanitizedText(), txtTitle.Text); // Set the information if (radioStationary.Checked) { if (gdStationary == null) { MessageBox.Show("For some reason, could not cast the GrhData to StationaryGrhData..."); return; } // Stationary var cm = gdStationary.ContentManager; var x = Parser.Current.ParseInt(txtX.Text); var y = Parser.Current.ParseInt(txtY.Text); var w = Parser.Current.ParseInt(txtW.Text); var h = Parser.Current.ParseInt(txtH.Text); var textureName = txtTexture.GetSanitizedText(); var autoSize = chkAutoSize.Checked; // Validate the texture try { cm.LoadImage("Grh" + DirSep + textureName, ContentLevel.Map); } catch (Exception ex) { MessageBox.Show("Unable to load texture [" + textureName + "]! Aborting save..." + Environment.NewLine + ex); return; } gdStationary.ChangeTexture(textureName, new Rectangle(x, y, w, h)); _gd.SetCategorization(categorization); gdStationary.AutomaticSize = autoSize; } else { // Animated if (gdAnimated == null) { MessageBox.Show("For some reason, could not cast the GrhData to AnimatedGrhData..."); return; } var speed = Parser.Current.ParseFloat(txtSpeed.Text); gdAnimated.SetSpeed(speed); } // Set the MapGrhWalls _mapGrhWalls[_gd] = BoundWalls.ToList(); // Write Enabled = false; GrhInfo.Save(ContentPaths.Dev); Enabled = true; WasCanceled = false; DialogResult = DialogResult.OK; }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copyArray = new StationaryGrhData[_frames.Length]; Array.Copy(_frames, copyArray, _frames.Length); var copy = new AnimatedGrhData(newGrhIndex, newCategorization) { _frames = copyArray }; copy.SetSpeed(Speed); return copy; }
/// <summary> /// Handles the Click event of the btnAdd control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> void btnAdd_Click(object sender, EventArgs e) { try { if (cmbSkeletonBodies.SelectedItem == null || cmbSkeletonBodyNodes.SelectedItem == null) return; var skelBodies = cmbSkeletonBodies.SelectedItem.ToString(); var skelBodyNodes = cmbSkeletonBodyNodes.SelectedItem.ToString(); Array.Resize(ref SkeletonBody.BodyItems, SkeletonBody.BodyItems.Length + 1); var spriteCategorization = new SpriteCategorization("Character.Skeletons." + skelBodies, skelBodyNodes); var grhData = GrhInfo.GetData(spriteCategorization); if (grhData == null) return; var bodyItemInfo = new SkeletonBodyItemInfo(grhData.GrhIndex, _skeleton.RootNode.Name, string.Empty, Vector2.Zero, Vector2.Zero); var bodyItem = new SkeletonBodyItem(bodyItemInfo); SkeletonBody.BodyItems[SkeletonBody.BodyItems.Length - 1] = bodyItem; UpdateBodyList(); } catch { } }
public static void Write(this IValueWriter writer, string name, SpriteCategorization value) { writer.Write(name, value.ToString()); }
/// <summary> /// Creates a deep copy of the <see cref="GrhData"/>. /// </summary> /// <param name="newCategorization">Categorization for the duplicated GrhData. Must be unique.</param> /// <returns>Deep copy of the <see cref="GrhData"/> with the new categorization and its own /// unique <see cref="GrhIndex"/>.</returns> /// <exception cref="ArgumentException"><paramref name="newCategorization"/> is already in use.</exception> public GrhData Duplicate(SpriteCategorization newCategorization) { if (GrhInfo.GetData(newCategorization) != null) throw new ArgumentException("Category already in use.", "newCategorization"); var index = GrhInfo.NextFreeIndex(); Debug.Assert(GrhInfo.GetData(index) == null, "Slot to use is already in use! How the hell did this happen!? GrhInfo.NextFreeIndex() must be broken."); var dc = DeepCopy(newCategorization, index); GrhInfo.AddGrhData(dc); return dc; }
public static StationaryGrhData CreateGrhData(IContentManager contentManager, SpriteCategorization categorization, string texture, Vector2 pos, Vector2 size) { return(CreateGrhData(NextFreeIndex(), contentManager, categorization, texture, pos, size)); }
/// <summary> /// Initializes a new instance of the <see cref="AddStationaryGrhDataTask"/> class. /// </summary> /// <param name="categorization">The categorization.</param> /// <param name="relative">The relative path.</param> public AddStationaryGrhDataTask(SpriteCategorization categorization, string relative) { _categorization = categorization; _relative = relative; }
/// <summary> /// Handles the Click event of the btnAdd control. /// </summary> /// <param name="sender">The source of the event.</param> /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param> void btnAdd_Click(object sender, EventArgs e) { Array.Resize(ref SkeletonBody.BodyItems, SkeletonBody.BodyItems.Length + 1); var spriteCategorization = new SpriteCategorization("Character.Naked", "Body"); var grhData = GrhInfo.GetData(spriteCategorization); var bodyItemInfo = new SkeletonBodyItemInfo(grhData.GrhIndex, _skeleton.RootNode.Name, string.Empty, Vector2.Zero, Vector2.Zero); var bodyItem = new SkeletonBodyItem(bodyItemInfo); SkeletonBody.BodyItems[SkeletonBody.BodyItems.Length - 1] = bodyItem; UpdateBodyList(); }
/// <summary> /// Gets the <see cref="GrhData"/> by the given categorization information. /// </summary> /// <param name="categorization">Categorization of the <see cref="GrhData"/>.</param> /// <returns><see cref="GrhData"/> matching the given information if found, or null if no matches.</returns> public static GrhData GetData(SpriteCategorization categorization) { return(GetData(categorization.Category, categorization.Title)); }
/// <summary> /// Gets the <see cref="IAddGrhDataTask"/>s for animated <see cref="GrhData"/>s. /// </summary> /// <param name="rootGrhDir">The root grh dir.</param> /// <returns>The <see cref="IAddGrhDataTask"/>s for animated <see cref="GrhData"/>s.</returns> static IEnumerable<IAddGrhDataTask> GetAnimatedTasks(string rootGrhDir) { if (log.IsInfoEnabled) log.InfoFormat("Searching for automatic animated GrhDatas from root `{0}`.", rootGrhDir); var ret = new List<IAddGrhDataTask>(); // Find all directories that match the needed pattern var dirs = GetAnimatedDirectories(rootGrhDir); foreach (var dir in dirs) { // Grab the animation info from the directory var animInfo = AutomaticAnimatedGrhData.GetAutomaticAnimationInfo(dir); if (animInfo == null) continue; // Get the virtual directory (remove the root) var partialDir = dir.Substring(rootGrhDir.Length); if (partialDir.StartsWith(Path.DirectorySeparatorChar.ToString()) || partialDir.StartsWith(Path.AltDirectorySeparatorChar.ToString())) partialDir = partialDir.Substring(1); // Get the categorization var lastDirSep = partialDir.LastIndexOf(Path.DirectorySeparatorChar); if (lastDirSep < 0) { if (log.IsWarnEnabled) log.WarnFormat("Animated GrhData found at `{0}`, but could not be created because it has no category.", dir); continue; } var categoryStr = partialDir.Substring(0, lastDirSep); var categorization = new SpriteCategorization(new SpriteCategory(categoryStr), new SpriteTitle(animInfo.Title)); // Ensure the GrhData doesn't already exist if (GrhInfo.GetData(categorization) != null) continue; // Create the task ret.Add(new AddAnimatedGrhDataTask(categorization)); } return ret; }
/// <summary> /// When overridden in the derived class, creates a new <see cref="GrhData"/> equal to this <see cref="GrhData"/> /// except for the specified parameters. /// </summary> /// <param name="newCategorization">The <see cref="SpriteCategorization"/> to give to the new /// <see cref="GrhData"/>.</param> /// <param name="newGrhIndex">The <see cref="GrhIndex"/> to give to the new /// <see cref="GrhData"/>.</param> /// <returns> /// A deep copy of this <see cref="GrhData"/>. /// </returns> protected override GrhData DeepCopy(SpriteCategorization newCategorization, GrhIndex newGrhIndex) { var copy = new StationaryGrhData(ContentManager, newGrhIndex, newCategorization, TextureName, AutomaticSize ? (Rectangle?)null : SourceRect); return copy; }