Changed Player class to inherit from Grid

Added .gif support
This commit is contained in:
Brychan Dempsey 2021-08-30 19:42:01 +12:00
parent 70ca03a101
commit 34db81f143
5 changed files with 227 additions and 61 deletions

View File

@ -42,13 +42,28 @@
<UniformGrid x:Name="LoadedSprites" Columns="6" Margin="2,0,2,0" Rows="6" MinHeight="120" /> <UniformGrid x:Name="LoadedSprites" Columns="6" Margin="2,0,2,0" Rows="6" MinHeight="120" />
</StackPanel> </StackPanel>
<Border Grid.Column="1" BorderThickness="1,1,1,1" BorderBrush="DarkGray" Background="#19000000"> <Border Grid.Column="1" BorderThickness="1,1,1,1" BorderBrush="DarkGray" Background="#19000000">
<ListView x:Name="PlayersList" Background="{x:Null}"> <Grid>
<ListView.View> <Grid.RowDefinitions>
<GridView> <RowDefinition MinHeight="400"/>
<GridViewColumn/> <RowDefinition />
</GridView> </Grid.RowDefinitions>
</ListView.View> <ListView x:Name="PlayersList" Background="{x:Null}">
</ListView> <ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
<Image x:Name="MouseOverViewImage" Grid.Row="1"/>
<Rectangle x:Name="test" HorizontalAlignment="Left" Height="100" Margin="145,96,0,0" Grid.Row="1" Stroke="Black" VerticalAlignment="Top" Width="100">
<Rectangle.Fill>
<RadialGradientBrush>
<GradientStop Color="White"/>
<GradientStop Color="Transparent" Offset="1"/>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Border> </Border>
</Grid> </Grid>
<Grid Grid.Column="1" VerticalAlignment="Center"> <Grid Grid.Column="1" VerticalAlignment="Center">

View File

@ -1,4 +1,5 @@
using Microsoft.Win32; using Microsoft.Win32;
using RBG.Helpers;
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
@ -72,9 +73,7 @@ namespace RBG_Server.WPF
catch (Exception e) catch (Exception e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
} }
} }
/// <summary> /// <summary>
/// Creates and starts the game server /// Creates and starts the game server
@ -154,22 +153,17 @@ namespace RBG_Server.WPF
} }
/// <summary> /// <summary>
/// Generates elements to go inside the game grid from the provided list of players /// Generates the game board overlay grid
/// The grid should be appropriately sized before using this /// The grid should be appropriately sized before using this
/// </summary> /// </summary>
private void GenerateGameBoard(List<Player> players) private void GenerateGameBoard(List<Player> players)
{ {
SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); // Create an array of BoardCells Rows*Columns in size
Grid[] cells = new Grid[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count]; BoardCell[] cells = new BoardCell[PreviewImageOverlay.RowDefinitions.Count * PreviewImageOverlay.ColumnDefinitions.Count];
List<Player> unaddedPlayers = players; List<Player> unaddedPlayers = players;
for (int i = 0; i < cells.Length; i++) for (int i = 0; i < cells.Length; i++)
{ {
cells[i] = new Grid(); cells[i] = new BoardCell();
// Each cell has several components:
// Stackpanel, which has each sprite in the cell
UniformGrid cellStack = new();
cellStack.Columns = 4;
//cellStack.ItemWidth = cellStack.Width / 4;
Queue<Player> removedPlayers = new(); Queue<Player> removedPlayers = new();
foreach (Player item in unaddedPlayers) foreach (Player item in unaddedPlayers)
{ {
@ -182,50 +176,66 @@ namespace RBG_Server.WPF
{ {
Player removed = removedPlayers.Dequeue(); Player removed = removedPlayers.Dequeue();
_ = unaddedPlayers.Remove(removed); _ = unaddedPlayers.Remove(removed);
_ = cellStack.Children.Add(removed); _ = cells[i].CellStack.Children.Add(removed);
} }
_ = cells[i].Children.Add(cellStack);
// Button, whose content is empty
Button cellButton = new()
{
Content = ""
};
cellButton.Background = buttonBrush;
cellButton.Tag = new Tuple<int, int>(i / PreviewImageOverlay.ColumnDefinitions.Count, i % PreviewImageOverlay.ColumnDefinitions.Count); // Tag is a Tuple<row, column>
cellButton.Click += CellButton_Click;
_ = cells[i].Children.Add(cellButton);
_ = PreviewImageOverlay.Children.Add(cells[i]); _ = PreviewImageOverlay.Children.Add(cells[i]);
cells[i].CellButton.Tag = new Tuple<int, int>(i % PreviewImageOverlay.ColumnDefinitions.Count, i / PreviewImageOverlay.ColumnDefinitions.Count);
Grid.SetColumn(cells[i], i % PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetColumn(cells[i], i % PreviewImageOverlay.ColumnDefinitions.Count);
Grid.SetRow(cells[i], i / PreviewImageOverlay.ColumnDefinitions.Count); Grid.SetRow(cells[i], i / PreviewImageOverlay.ColumnDefinitions.Count);
} }
} }
/// <summary> /// <summary>
/// Represents a cell on the board /// Represents a cell on the board; as a grid
/// </summary> /// </summary>
class BoardCell class BoardCell : Grid
{ {
// brush the button should have // brush the button should have
static SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255)); static SolidColorBrush buttonBrush = new(Color.FromArgb(1, 255, 255, 255));
// Each cell has a uniform grid that // Each cell has a uniform grid that
UniformGrid cellStack = new() public UniformGrid CellStack { get; } = new()
{ {
Columns = 4, Columns = 4,
HorizontalAlignment = HorizontalAlignment.Stretch, HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch VerticalAlignment = VerticalAlignment.Stretch
}; };
Button cellButton = new() public Button CellButton { get; } = new()
{ {
Content = "" Content = "",
}; };
Queue<Player> removedPlayers = new(); Queue<Player> removedPlayers = new();
Grid parentGrid; // Grid parentGrid; // Parent grid is now this itself
public BoardCell(Grid parentGrid) public BoardCell() : base()
{ {
this.parentGrid = parentGrid; CellButton.MouseEnter += CellButton_MouseEnter;
CellButton.MouseLeave += CellButton_MouseLeave;
CellButton.Click += CellButton_Click;
CellButton.Background = buttonBrush;
CellStack.Columns = 2;
CellStack.VerticalAlignment = VerticalAlignment.Top;
// Add our grid and button
_ = Children.Add(CellStack);
_ = Children.Add(CellButton);
} }
private void CellButton_Click(object sender, RoutedEventArgs e)
{
Button cellButton = sender as Button;
System.Diagnostics.Debug.WriteLine("Button at {0}, {1} was clicked", ((Tuple<int, int>)cellButton.Tag).Item2, ((Tuple<int, int>)cellButton.Tag).Item1);
}
private void CellButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
//throw new NotImplementedException();
}
private void CellButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
//throw new NotImplementedException();
}
} }
private void GenerateNewGameBoard(List<Player> players) private void GenerateNewGameBoard(List<Player> players)
@ -239,11 +249,7 @@ namespace RBG_Server.WPF
} }
} }
private void CellButton_Click(object sender, RoutedEventArgs e)
{
Button cellButton = sender as Button;
Console.WriteLine("Button at {0}, {1} was clicked", ((Tuple<int, int>)cellButton.Tag).Item2, ((Tuple<int, int>)cellButton.Tag).Item1);
}
private void Sliders_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) private void Sliders_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{ {
@ -275,26 +281,68 @@ namespace RBG_Server.WPF
// Create a new player at a random location // Create a new player at a random location
Player p = new("Player " + i, rand.Next(0, PreviewImageOverlay.RowDefinitions.Count), rand.Next(PreviewImageOverlay.ColumnDefinitions.Count)); Player p = new("Player " + i, rand.Next(0, PreviewImageOverlay.RowDefinitions.Count), rand.Next(PreviewImageOverlay.ColumnDefinitions.Count));
// Load the sprite // Load the sprite
BitmapSource pSprite = new BitmapImage(new Uri("pack://application:,,,/Sprites/" + sprites[rand.Next(0, sprites.Length)])); string[] keys = new string[loadedSprites.Count];
// Recolour: loadedSprites.Keys.CopyTo(keys, 0);
if (rand.Next(1, 4) == 1) string key = keys[rand.Next(keys.Length)];
if (key.ToLower().EndsWith(".gif"))
{ {
pSprite = RBG.Helpers.ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0); try
{
AnimatedBitmapImage animatedBitmap = new(new MemoryStream(loadedSprites[key]), p.PlayerSprite, Application.Current.Dispatcher);
}
catch
{
BitmapSource pSprite;
{
// Scope & create the base data for pSprite
BitmapImage src = new();
src.BeginInit();
src.StreamSource = new MemoryStream(loadedSprites[key]);
src.EndInit();
pSprite = src;
}
// Recolour:
if (rand.Next(1, 4) == 1)
{
pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0);
}
p.PlayerSprite.Source = pSprite;
}
} }
p.Source = pSprite; else
{
BitmapSource pSprite;
{
// Scope & create the base data for pSprite
BitmapImage src = new();
src.BeginInit();
src.StreamSource = new MemoryStream(loadedSprites[key]);
src.EndInit();
pSprite = src;
}
// Recolour:
if (rand.Next(1, 4) == 1)
{
pSprite = ImageProcessing.UpdatePixelColours(pSprite, 0.0, 0.0, 1.0);
}
p.PlayerSprite.Source = pSprite;
}
randPlayers.Add(p); randPlayers.Add(p);
} }
GenerateGameBoard(randPlayers); GenerateGameBoard(randPlayers);
} }
ConcurrentDictionary<string, MemoryStream> loadedSprites = new(); ConcurrentDictionary<string, byte[]> loadedSprites = new();
Task loadingFiles; Task loadingFiles;
private void GameSpritesBrowser_Click(object sender, RoutedEventArgs e) private void GameSpritesBrowser_Click(object sender, RoutedEventArgs e)
{ {
OpenFileDialog ofd = new(); OpenFileDialog ofd = new();
ofd.Filter = "Image Files|*.png;*.jpg"; ofd.Filter = "Image Files|*.png;*.jpg;*.gif";
ofd.Multiselect = true; ofd.Multiselect = true;
if (ofd.ShowDialog() == true) if (ofd.ShowDialog() == true)
{ {
@ -327,23 +375,57 @@ namespace RBG_Server.WPF
{ {
fs.CopyTo(ms); fs.CopyTo(ms);
} }
_ = loadedSprites.TryAdd(file, ms); _ = loadedSprites.TryAdd(file, ms.ToArray());
}); });
// Invoke the dispatcher to add all of the sprites to the loaded sprites list // Invoke the dispatcher to add all of the sprites to the loaded sprites list
_ = Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() => _ = Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() =>
{ {
LoadedSprites.Children.Clear(); LoadedSprites.Children.Clear();
foreach (KeyValuePair<string, MemoryStream> item in loadedSprites) foreach (KeyValuePair<string, byte[]> item in loadedSprites)
{ {
BitmapImage image = new(); if (item.Key.ToLower().EndsWith(".gif"))
image.BeginInit();
image.StreamSource = item.Value;
image.EndInit();
Image spriteImage = new()
{ {
Source = image try
}; {
_ = LoadedSprites.Children.Add(spriteImage); /*
// Must write-out the stream to
MediaElement gifMedia = new();
gifMedia.LoadedBehavior = MediaState.Play;
gifMedia.Source = new Uri(item.Key);
_ = LoadedSprites.Children.Add(gifMedia);
*/
Image spriteImage = new();
RBG.Helpers.AnimatedBitmapImage animatedBitmap = new(new MemoryStream(item.Value), spriteImage, Application.Current.Dispatcher);
_ = LoadedSprites.Children.Add(spriteImage);
}
catch
{
// On failed, use the cached data
BitmapImage image = new();
image.BeginInit();
image.StreamSource = new MemoryStream(item.Value);
image.EndInit();
Image spriteImage = new()
{
Source = image
};
_ = LoadedSprites.Children.Add(spriteImage);
}
}
else
{
BitmapImage image = new();
image.BeginInit();
image.StreamSource = new MemoryStream(item.Value);
image.EndInit();
Image spriteImage = new()
{
Source = image
};
_ = LoadedSprites.Children.Add(spriteImage);
}
} }
})); }));
}); });

View File

@ -1,6 +1,12 @@
using System; using System;
using System.Windows; using System.Windows;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Windows.Controls;
using System.Windows.Threading;
using System.Collections.Generic;
namespace RBG.Helpers namespace RBG.Helpers
{ {
@ -83,4 +89,47 @@ namespace RBG.Helpers
} }
public class AnimatedBitmapImage
{
public BitmapSource BaseImage { get; set; }
BitmapFrame[] frames;
int framerate;
int currentFrame;
Timer frameChange;
public AnimatedBitmapImage(Stream source, Image target, Dispatcher dispatcher)
{
GifBitmapDecoder decoder = new(source, BitmapCreateOptions.None, BitmapCacheOption.Default);
frames = new BitmapFrame[decoder.Frames.Count];
decoder.Frames.CopyTo(frames, 0);
byte[] frameratebytes = new byte[2];
source.Position = 0x324;
frameratebytes[0] = (byte)source.ReadByte();
frameratebytes[1] = (byte)source.ReadByte();
framerate = 33;
BaseImage = frames[0];
// cycle through frames
frameChange = new Timer((t) =>
{
currentFrame++;
currentFrame %= frames.Length;
BaseImage = frames[currentFrame];
_ = dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Render, new Action(() =>
{
try
{
target.Source = BaseImage;
}
catch
{
}
}));
},null, framerate, framerate);
}
}
} }

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net5.0-windows10.0.19041.0</TargetFramework>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -4,6 +4,8 @@ using System.Linq;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
namespace RBG_Server namespace RBG_Server
{ {
@ -11,8 +13,18 @@ namespace RBG_Server
/// Data class containing information about a player. Drawn directly to the screen, hence the inheritance /// Data class containing information about a player. Drawn directly to the screen, hence the inheritance
/// of Image, which allows this entire object to be /// of Image, which allows this entire object to be
/// </summary> /// </summary>
public class Player : Image public class Player : Grid
{ {
static GradientStopCollection gradientStops = new(5)
{
new GradientStop(Color.FromArgb(255, 0, 255, 255), 0),
new GradientStop(Color.FromArgb(192, 0, 255, 255), 0.75),
new GradientStop(Color.FromArgb(128, 0, 255, 255), 0.85),
new GradientStop(Color.FromArgb(32, 0, 255, 255), 0.95),
new GradientStop(Color.FromArgb(0, 0, 255, 255), 1)
};
static RadialGradientBrush shadowBrush = new(gradientStops);
// C# uses implicit field definitions; i.e. declaring a property creates an implicit private field // C# uses implicit field definitions; i.e. declaring a property creates an implicit private field
// Note that it is also possible to assign values, and specify access modifiers for the get & set independantly // Note that it is also possible to assign values, and specify access modifiers for the get & set independantly
public string PlayerName { get; set; } public string PlayerName { get; set; }
@ -21,6 +33,10 @@ namespace RBG_Server
public long LastTime { get; set; } = DateTime.Now.Ticks; public long LastTime { get; set; } = DateTime.Now.Ticks;
public bool Connected { get; private set; } public bool Connected { get; private set; }
public byte[] UnhandledBuffer { get; set; } public byte[] UnhandledBuffer { get; set; }
public Image PlayerSprite { get; set; } = new();
private Rectangle spriteShadow = new();
private int processing; private int processing;
public int ObtainLock() public int ObtainLock()
@ -43,6 +59,10 @@ namespace RBG_Server
LastTime = DateTime.Now.Ticks; LastTime = DateTime.Now.Ticks;
Connected = true; Connected = true;
UnhandledBuffer = Array.Empty<byte>(); UnhandledBuffer = Array.Empty<byte>();
spriteShadow.Fill = shadowBrush;
Children.Add(spriteShadow);
Children.Add(PlayerSprite);
} }
public new bool Equals(object obj) public new bool Equals(object obj)