diff options
author | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-07 12:56:48 -0500 |
---|---|---|
committer | Jesse Plamondon-Willard <Pathoschild@users.noreply.github.com> | 2020-03-07 12:56:48 -0500 |
commit | db4254513ef9fca49dfe4da00448a057813cf842 (patch) | |
tree | 0f6637936ebe370b5ba4f711bc133d14afa14762 /src/SMAPI/Framework | |
parent | c6947682b050f4ed8c070a3ca021c7f10ec50009 (diff) | |
download | SMAPI-db4254513ef9fca49dfe4da00448a057813cf842.tar.gz SMAPI-db4254513ef9fca49dfe4da00448a057813cf842.tar.bz2 SMAPI-db4254513ef9fca49dfe4da00448a057813cf842.zip |
add support for flipped and rotated map tiles
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r-- | src/SMAPI/Framework/Rendering/SDisplayDevice.cs | 89 | ||||
-rw-r--r-- | src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs | 121 | ||||
-rw-r--r-- | src/SMAPI/Framework/SGame.cs | 21 |
3 files changed, 229 insertions, 2 deletions
diff --git a/src/SMAPI/Framework/Rendering/SDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs new file mode 100644 index 00000000..382949bf --- /dev/null +++ b/src/SMAPI/Framework/Rendering/SDisplayDevice.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using StardewValley; +using xTile.Dimensions; +using xTile.Layers; +using xTile.ObjectModel; +using xTile.Tiles; + +namespace StardewModdingAPI.Framework.Rendering +{ + /// <summary>A map display device which overrides the draw logic to support tile rotation.</summary> + internal class SDisplayDevice : SXnaDisplayDevice + { + /********* + ** Fields + *********/ + /// <summary>The origin to use when rotating tiles.</summary> + private readonly Vector2 RotationOrigin; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="contentManager">The content manager through which to load tiles.</param> + /// <param name="graphicsDevice">The graphics device with which to render tiles.</param> + public SDisplayDevice(ContentManager contentManager, GraphicsDevice graphicsDevice) + : base(contentManager, graphicsDevice) + { + this.RotationOrigin = new Vector2((Game1.tileSize * Game1.pixelZoom) / 2f); + } + + /// <summary>Draw a tile to the screen.</summary> + /// <param name="tile">The tile to draw.</param> + /// <param name="location">The tile position to draw.</param> + /// <param name="layerDepth">The layer depth at which to draw.</param> + public override void DrawTile(Tile tile, Location location, float layerDepth) + { + // identical to XnaDisplayDevice + if (tile == null) + return; + xTile.Dimensions.Rectangle tileImageBounds = tile.TileSheet.GetTileImageBounds(tile.TileIndex); + Texture2D tileSheetTexture = this.m_tileSheetTextures[tile.TileSheet]; + if (tileSheetTexture.IsDisposed) + return; + this.m_tilePosition.X = location.X; + this.m_tilePosition.Y = location.Y; + this.m_sourceRectangle.X = tileImageBounds.X; + this.m_sourceRectangle.Y = tileImageBounds.Y; + this.m_sourceRectangle.Width = tileImageBounds.Width; + this.m_sourceRectangle.Height = tileImageBounds.Height; + + // get rotation and effects + float rotation = this.GetRotation(tile); + SpriteEffects effects = this.GetSpriteEffects(tile); + var origin = new Vector2(tileImageBounds.Width / 2f, tileImageBounds.Height / 2f); + this.m_tilePosition.X += origin.X * Layer.zoom; + this.m_tilePosition.Y += origin.X * Layer.zoom; + + // apply + this.m_spriteBatchAlpha.Draw(tileSheetTexture, this.m_tilePosition, this.m_sourceRectangle, this.m_modulationColour, rotation, origin, Layer.zoom, effects, layerDepth); + } + + /// <summary>Get the sprite effects to apply for a tile.</summary> + /// <param name="tile">The tile being drawn.</param> + private SpriteEffects GetSpriteEffects(Tile tile) + { + return tile.Properties.TryGetValue("@Flip", out PropertyValue propertyValue) && int.TryParse(propertyValue, out int value) + ? (SpriteEffects)value + : SpriteEffects.None; + } + + /// <summary>Get the draw rotation to apply for a tile.</summary> + /// <param name="tile">The tile being drawn.</param> + private float GetRotation(Tile tile) + { + if (!tile.Properties.TryGetValue("@Rotation", out PropertyValue propertyValue) || !int.TryParse(propertyValue, out int value)) + return 0; + + value %= 360; + if (value == 0) + return 0; + + return (float)(Math.PI / (180.0 / value)); + } + } +} diff --git a/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs new file mode 100644 index 00000000..d4f62b4f --- /dev/null +++ b/src/SMAPI/Framework/Rendering/SXnaDisplayDevice.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using Microsoft.Xna.Framework.Graphics; +using xTile.Dimensions; +using xTile.Display; +using xTile.Layers; +using xTile.Tiles; +using Rectangle = xTile.Dimensions.Rectangle; + +namespace StardewModdingAPI.Framework +{ + /// <summary>A map display device which reimplements the default logic.</summary> + /// <remarks>This is an exact copy of <see cref="XnaDisplayDevice"/>, except that private fields are protected and all methods are virtual.</remarks> + [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Field naming deliberately matches " + nameof(XnaDisplayDevice) + " to minimize differences.")] + internal class SXnaDisplayDevice : IDisplayDevice + { + /********* + ** Fields + *********/ + protected readonly ContentManager m_contentManager; + protected readonly GraphicsDevice m_graphicsDevice; + protected SpriteBatch m_spriteBatchAlpha; + protected SpriteBatch m_spriteBatchAdditive; + protected readonly Dictionary<TileSheet, Texture2D> m_tileSheetTextures; + protected Vector2 m_tilePosition; + protected Microsoft.Xna.Framework.Rectangle m_sourceRectangle; + protected readonly Color m_modulationColour; + + + /********* + ** Public methods + *********/ + /// <summary>Construct an instance.</summary> + /// <param name="contentManager">The content manager through which to load tiles.</param> + /// <param name="graphicsDevice">The graphics device with which to render tiles.</param> + public SXnaDisplayDevice(ContentManager contentManager, GraphicsDevice graphicsDevice) + { + this.m_contentManager = contentManager; + this.m_graphicsDevice = graphicsDevice; + this.m_spriteBatchAlpha = new SpriteBatch(graphicsDevice); + this.m_spriteBatchAdditive = new SpriteBatch(graphicsDevice); + this.m_tileSheetTextures = new Dictionary<TileSheet, Texture2D>(); + this.m_tilePosition = new Vector2(); + this.m_sourceRectangle = new Microsoft.Xna.Framework.Rectangle(); + this.m_modulationColour = Color.White; + } + + /// <summary>Load a tilesheet texture.</summary> + /// <param name="tileSheet">The tilesheet instance.</param> + public virtual void LoadTileSheet(TileSheet tileSheet) + { + Texture2D texture2D = this.m_contentManager.Load<Texture2D>(tileSheet.ImageSource); + this.m_tileSheetTextures[tileSheet] = texture2D; + } + + /// <summary>Unload a tilesheet texture.</summary> + /// <param name="tileSheet">The tilesheet instance.</param> + public virtual void DisposeTileSheet(TileSheet tileSheet) + { + this.m_tileSheetTextures.Remove(tileSheet); + } + + /// <summary>Prepare to render to the screen.</summary> + /// <param name="b">The sprite batch being rendered.</param> + public virtual void BeginScene(SpriteBatch b) + { + this.m_spriteBatchAlpha = b; + } + + /// <summary>Set the clipping region.</summary> + /// <param name="clippingRegion">The clipping region.</param> + public virtual void SetClippingRegion(Rectangle clippingRegion) + { + int backBufferWidth = this.m_graphicsDevice.PresentationParameters.BackBufferWidth; + int backBufferHeight = this.m_graphicsDevice.PresentationParameters.BackBufferHeight; + int x = this.Clamp(clippingRegion.X, 0, backBufferWidth); + int y = this.Clamp(clippingRegion.Y, 0, backBufferHeight); + int num1 = this.Clamp(clippingRegion.X + clippingRegion.Width, 0, backBufferWidth); + int num2 = this.Clamp(clippingRegion.Y + clippingRegion.Height, 0, backBufferHeight); + int width = num1 - x; + int height = num2 - y; + this.m_graphicsDevice.Viewport = new Viewport(x, y, width, height); + } + + /// <summary>Draw a tile to the screen.</summary> + /// <param name="tile">The tile to draw.</param> + /// <param name="location">The tile position to draw.</param> + /// <param name="layerDepth">The layer depth at which to draw.</param> + public virtual void DrawTile(Tile tile, Location location, float layerDepth) + { + if (tile == null) + return; + xTile.Dimensions.Rectangle tileImageBounds = tile.TileSheet.GetTileImageBounds(tile.TileIndex); + Texture2D tileSheetTexture = this.m_tileSheetTextures[tile.TileSheet]; + if (tileSheetTexture.IsDisposed) + return; + this.m_tilePosition.X = (float)location.X; + this.m_tilePosition.Y = (float)location.Y; + this.m_sourceRectangle.X = tileImageBounds.X; + this.m_sourceRectangle.Y = tileImageBounds.Y; + this.m_sourceRectangle.Width = tileImageBounds.Width; + this.m_sourceRectangle.Height = tileImageBounds.Height; + this.m_spriteBatchAlpha.Draw(tileSheetTexture, this.m_tilePosition, new Microsoft.Xna.Framework.Rectangle?(this.m_sourceRectangle), this.m_modulationColour, 0.0f, Vector2.Zero, (float)Layer.zoom, SpriteEffects.None, layerDepth); + } + + /// <summary>Finish drawing to the screen.</summary> + public virtual void EndScene() { } + + /// <summary>Snap a value to the given range.</summary> + /// <param name="nValue">The value to normalize.</param> + /// <param name="nMin">The minimum value.</param> + /// <param name="nMax">The maximum value.</param> + protected int Clamp(int nValue, int nMin, int nMax) + { + return Math.Min(Math.Max(nValue, nMin), nMax); + } + } +} diff --git a/src/SMAPI/Framework/SGame.cs b/src/SMAPI/Framework/SGame.cs index b2d92ce8..d09e1e93 100644 --- a/src/SMAPI/Framework/SGame.cs +++ b/src/SMAPI/Framework/SGame.cs @@ -19,6 +19,7 @@ using StardewModdingAPI.Framework.Input; using StardewModdingAPI.Framework.Networking; using StardewModdingAPI.Framework.PerformanceMonitoring; using StardewModdingAPI.Framework.Reflection; +using StardewModdingAPI.Framework.Rendering; using StardewModdingAPI.Framework.StateTracking.Comparers; using StardewModdingAPI.Framework.StateTracking.Snapshots; using StardewModdingAPI.Framework.Utilities; @@ -193,6 +194,13 @@ namespace StardewModdingAPI.Framework Game1.locations = new ObservableCollection<GameLocation>(); } + /// <summary>Load content when the game is launched.</summary> + protected override void LoadContent() + { + base.LoadContent(); + Game1.mapDisplayDevice = new SDisplayDevice(Game1.content, this.GraphicsDevice); + } + /// <summary>Initialize just before the game's first update tick.</summary> private void InitializeAfterGameStarted() { @@ -252,12 +260,12 @@ namespace StardewModdingAPI.Framework // update data LoadStage oldStage = Context.LoadStage; Context.LoadStage = newStage; + this.Monitor.VerboseLog($"Context: load stage changed to {newStage}"); if (newStage == LoadStage.None) { this.Monitor.Log("Context: returned to title", LogLevel.Trace); - this.Multiplayer.CleanupOnMultiplayerExit(); + this.OnReturnedToTitle(); } - this.Monitor.VerboseLog($"Context: load stage changed to {newStage}"); // raise events this.Events.LoadStageChanged.Raise(new LoadStageChangedEventArgs(oldStage, newStage)); @@ -283,6 +291,15 @@ namespace StardewModdingAPI.Framework } } + /// <summary>Perform cleanup when the game returns to the title screen.</summary> + private void OnReturnedToTitle() + { + this.Multiplayer.CleanupOnMultiplayerExit(); + + if (!(Game1.mapDisplayDevice is SDisplayDevice)) + Game1.mapDisplayDevice = new SDisplayDevice(Game1.content, this.GraphicsDevice); + } + /// <summary>Constructor a content manager to read XNB files.</summary> /// <param name="serviceProvider">The service provider to use to locate services.</param> /// <param name="rootDirectory">The root directory to search for content.</param> |