From 6c7a24102e40a0fb3eab38b969cf4c0b4c56c3b1 Mon Sep 17 00:00:00 2001 From: Vladisvell Date: Thu, 2 Jun 2022 16:54:49 +0500 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B2=D1=83=D0=BA=D0=B8!=20=D0=9F=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=B5=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавлены звуки при поддержке NAudio https://github.com/naudio/NAudio Добавлена помощь. Добавлен счетчик очков. Скрыты отладочные данные. (можно включить, нажав на ~) --- Astroshooter.csproj | 77 +++++++++- Controller.cs | 70 +++++---- Objects/Asteroid.cs | 6 +- Objects/Booster.cs | 12 -- Objects/Bullet.cs | 5 - Objects/Ship.cs | 5 - ...Interface1.cs => SpaceObject_Interface.cs} | 0 Rendering.Designer.cs | 9 +- Rendering.cs | 138 ++++++++++++------ Rendering.resx | 120 +++++++++++++++ SoundEngine.cs | 129 ++++++++++++++++ Vec2.cs | 5 +- packages.config | 13 ++ 13 files changed, 478 insertions(+), 111 deletions(-) delete mode 100644 Objects/Booster.cs rename Objects/{Interface1.cs => SpaceObject_Interface.cs} (100%) create mode 100644 Rendering.resx create mode 100644 SoundEngine.cs create mode 100644 packages.config diff --git a/Astroshooter.csproj b/Astroshooter.csproj index c4371fe..bcc2512 100644 --- a/Astroshooter.csproj +++ b/Astroshooter.csproj @@ -12,6 +12,21 @@ 512 true true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + false + true AnyCPU @@ -33,8 +48,38 @@ 4 + + packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll + + + packages\NAudio.2.1.0\lib\net472\NAudio.dll + + + packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll + + + packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll + + + packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll + + + packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll + + + packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll + + + packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll + + + packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll + + + packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll + @@ -46,11 +91,11 @@ + - - + Form @@ -71,6 +116,10 @@ Resources.resx True + + Rendering.cs + + SettingsSingleFileGenerator Settings.Designer.cs @@ -84,6 +133,28 @@ - + + + {6BF52A50-394A-11D3-B153-00C04F79FAA6} + 1 + 0 + 0 + tlbimp + False + True + + + + + False + Microsoft .NET Framework 4.7.2 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + \ No newline at end of file diff --git a/Controller.cs b/Controller.cs index a04ceba..7bdb056 100644 --- a/Controller.cs +++ b/Controller.cs @@ -1,9 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; using System.Drawing; namespace Astroshooter @@ -16,8 +12,9 @@ class Controller private Random random = new Random((int)DateTime.Now.Ticks); private List asteroidTextureList; private Queue toSpawn = new Queue(100); - private bool isPaused; private double circularParameter = 0; + private readonly Dictionary soundLibrary = new Dictionary(); + private int score = 0; public Controller(Ship ship, SpaceField spacefield, List spaceObjects, List asteroidImages) @@ -26,6 +23,26 @@ public Controller(Ship ship, SpaceField spacefield, List spaceObjec this.spacefield = spacefield; this.spaceObjects = spaceObjects; asteroidTextureList = asteroidImages; + InitializeSoundLibrary(); + } + + private void InitializeSoundLibrary() + { + soundLibrary.Add("AsteroidExplode", new CachedSound(@".\audio\explode.wav")); + soundLibrary.Add("ShipShootSound", new CachedSound(@".\audio\impulse.wav")); + } + + public void CreateBullet(float angle) + { + if (ship.cooldown <= 0) + { + var pelleteVelocity = new Vec2(); + pelleteVelocity.X = -Math.Cos(angle / 180 * Math.PI); + pelleteVelocity.Y = -Math.Sin(angle / 180 * Math.PI); + spaceObjects.Add(new Bullet(ship.GetCurrentCoordinates(), pelleteVelocity)); + AudioPlaybackEngine.Instance.PlaySound(soundLibrary["ShipShootSound"]); + ship.SetShootCooldown(300); + } } private void BoundaryCollisionHandle(SpaceObject obj) @@ -73,31 +90,22 @@ void SimulateSpaceObjectsTimeFrame(double dt) for(int i = 0; i < spaceObjects.Count; i++) { - //if(spaceObjects[i] != null) - //{ spaceObjects[i].SimulateTimeFrame(dt); BoundaryCollisionHandle(spaceObjects[i]); Collision(spaceObjects[i]); if (spaceObjects[i].IsDead()) toDelete.Add(i); - //} } - //while(toDeleteQ.Count != 0) - //{ - // var idToDelete = toDeleteQ.Dequeue(); - // if (idToDelete == spaceObjects.Count - 1) - // spaceObjects.RemoveAt(spaceObjects.Count - 1); - // else if(idToDelete < spaceObjects.Count - 1) - // { - // spaceObjects[idToDelete] = spaceObjects[spaceObjects.Count - 1]; - // spaceObjects.RemoveAt(spaceObjects.Count - 1); - // } - //} - for(int i = 0; i < toDelete.Count; i++) { - + if(spaceObjects[toDelete[i] - i] is Asteroid) + { + if (spacefield.isSoundEnabled) + AudioPlaybackEngine.Instance.PlaySound(soundLibrary["AsteroidExplode"]); + score += 100; + spacefield.UpdateScore(score); + } spaceObjects[toDelete[i] - i] = spaceObjects[spaceObjects.Count - 1]; spaceObjects.RemoveAt(spaceObjects.Count - 1); } @@ -129,31 +137,19 @@ private Vec2 GetRandomVelocity() return new Vec2(xvel, yvel); } - public void CreateRandomAsteroid() - { - //if(spaceObjects.Count <= 32 && toSpawn.Count <= 16) - //{ - // Vec2 pos = GetRandomPositionOutsidePlayzone(); - // Vec2 vel = GetRandomVelocity(); - // var asteroid = new Asteroid(pos, vel * 3, asteroidTextureList[random.Next(0, asteroidTextureList.Count - 1)]); - // asteroid.SetCooldown(2000); - // toSpawn.Enqueue(asteroid); - //} - CreateAsteroidInCircle(2000); - //return new Asteroid(pos, vel, asteroidTextureList[random.Next(0, asteroidTextureList.Count-1)]); - } + public void CreateRandomAsteroid() => CreateAsteroidInRadius(2000); - public void CreateAsteroidInCircle(double spawnRadius) + public void CreateAsteroidInRadius(double spawnRadius) { circularParameter %= Math.PI * 2; if(spaceObjects.Count <= 32 && toSpawn.Count <= 16) { + circularParameter += Math.PI / 6; Vec2 pos = new Vec2() { X = spawnRadius * Math.Cos(circularParameter) + spacefield.Width / 2, Y = spawnRadius * Math.Sin(circularParameter) + spacefield.Height / 2 }; - circularParameter += Math.PI / 6; Vec2 vel = GetRandomVelocity(); var asteroid = new Asteroid(pos, vel * 3, asteroidTextureList[random.Next(0, asteroidTextureList.Count - 1)]); asteroid.SetCooldown(300); @@ -166,6 +162,8 @@ public void Respawn() if (ship.IsDead()) { ship.SetDeadState(false); + score = 0; + spacefield.UpdateScore(score); ship.SetCurrentCoordinates(spacefield.Width / 2, spacefield.Height / 2); spaceObjects.Add(ship); } diff --git a/Objects/Asteroid.cs b/Objects/Asteroid.cs index cf2f3cb..2bb16b4 100644 --- a/Objects/Asteroid.cs +++ b/Objects/Asteroid.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + namespace Astroshooter { @@ -115,5 +112,6 @@ public void Collide(SpaceObject spaceObject) public Image GetImage() => texture; public bool IsDead() => isDead; + } } diff --git a/Objects/Booster.cs b/Objects/Booster.cs deleted file mode 100644 index 0952c49..0000000 --- a/Objects/Booster.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Astroshooter.Objects -{ - class Booster - { - } -} diff --git a/Objects/Bullet.cs b/Objects/Bullet.cs index 9f5090b..cb2c48a 100644 --- a/Objects/Bullet.cs +++ b/Objects/Bullet.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Astroshooter { @@ -101,7 +97,6 @@ public Bullet(Vec2 spawnloc, Vec2 velocity, Image image = null) { location = spawnloc * 1; this.velocity = velocity * 1; - //acceleration = this.velocity * 0.05 + new Vec2(0.01, 0.01); if (image != null) texture = image; timeToLive = 2000; diff --git a/Objects/Ship.cs b/Objects/Ship.cs index 464f065..240c622 100644 --- a/Objects/Ship.cs +++ b/Objects/Ship.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Drawing.Imaging; using System.Drawing; namespace Astroshooter diff --git a/Objects/Interface1.cs b/Objects/SpaceObject_Interface.cs similarity index 100% rename from Objects/Interface1.cs rename to Objects/SpaceObject_Interface.cs diff --git a/Rendering.Designer.cs b/Rendering.Designer.cs index e99c1da..7c99d00 100644 --- a/Rendering.Designer.cs +++ b/Rendering.Designer.cs @@ -29,10 +29,17 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); + this.SuspendLayout(); + // + // SpaceField + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(800, 450); + this.Name = "SpaceField"; this.Text = "Form1"; + this.ResumeLayout(false); + } #endregion diff --git a/Rendering.cs b/Rendering.cs index 8c8fd4c..3f5f83b 100644 --- a/Rendering.cs +++ b/Rendering.cs @@ -1,19 +1,14 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Forms; -using System.Windows; -using System.Timers; using System.Drawing.Drawing2D; -using System.Windows.Input; using System.IO; + namespace Astroshooter { public partial class SpaceField : Form @@ -33,11 +28,16 @@ public partial class SpaceField : Form private bool isLPressed = false; private bool debugCollisions = false; private bool debugModeEnabled = false; + public bool isSoundEnabled { get; private set; } = true; + + private bool isHelpEnabled = false; private Label velocity; private Label acceleration; private Label thrustforce; private Label direction; + private Label help; + private Label score; List asteroidTextures; @@ -48,10 +48,10 @@ public partial class SpaceField : Form public SpaceField(string[] args) { - - InitializeComponent(); + this.WindowState = FormWindowState.Maximized; + - //if (args.Contains("-debug")) + if (args.Contains("-debug")) debugModeEnabled = true; @@ -64,39 +64,100 @@ public SpaceField(string[] args) spaceObjects = new List(512); spaceObjects.Add(ship); - controller = new Controller(ship, this, spaceObjects, asteroidTextures); - - + InitializeComponent(); + InitializeControlHelp(); + InitializeScore(); ShipTexture = ship.ShipTexture; } + public SpaceField() + { + + } + protected override void OnLoad(EventArgs e) { base.OnLoad(e); + Text = "Astroshooter"; DoubleBuffered = true; BackColor = Color.Black; InitializeTimer(); if(debugModeEnabled) - InitializeLabels(); + InitializeDebugLabels(); //Cursor.Hide(); //this.TopMost = true; //this.FormBorderStyle = FormBorderStyle.None; - this.WindowState = FormWindowState.Maximized; } - public void InitializeLabels() + void InitializeControlHelp() + { + int X = 0; + int Y = this.Bottom / 2; + Font font = new Font("Arial", 12.0f, + FontStyle.Bold); + this.help = new Label + { + Location = new Point(X, Y), + Size = new Size(180, 400), + Text = "Нажмите H чтобы показать помощь.", + ForeColor = Color.White, + BackColor = Color.Transparent, + Font = font, + }; + Controls.Add(this.help); + } + + void ToggleHelp() + { + isHelpEnabled = !isHelpEnabled; + if(isHelpEnabled) + help.Text = + "Esc - выйти из игры. \n" + + "Пробел - стрелять. \n" + + "Мышь - наведение. \n" + + "W - двигаться вперед. \n" + + "J - переключить звук. \n" + + "Pause - переключить паузу. \n" + + "E - воскреснуть, и потерять очки. \n"; + if (!isHelpEnabled) + help.Text = "Нажмите H чтобы показать помощь."; + } + + void InitializeScore() + { + Font font = new Font("Arial", 21.0f, + FontStyle.Bold); + score = new Label + { + Location = new Point(ClientSize.Width / 2, 30), + Size = new Size(300, 70), + ForeColor = Color.Gainsboro, + BackColor = Color.Transparent, + Font = font, + AutoSize = true, + }; + Controls.Add(score); + } + + public void UpdateScore(int newScore) + { + score.Text = newScore.ToString(); + } + + void InitializeDebugLabels() { velocity = new Label { Location = new Point(0, 10), Size = new Size(100, 15), Text = "Velocity = ", - ForeColor = Color.White, + ForeColor = Color.White, + BackColor = Color.Transparent }; acceleration = new Label { @@ -130,17 +191,16 @@ public void InitializeLabels() } - public List InitializeAsteroidImages() + List InitializeAsteroidImages() { string asteroidDirectoryPath = @".\textures\asteroid"; string directory = Directory.GetCurrentDirectory(); var files = Directory.EnumerateFiles(asteroidDirectoryPath, "*.png", SearchOption.AllDirectories); - //var ech = files.ToList(); var imageList = files.Select(x => Image.FromFile(x)).ToList(); return imageList; } - public void InitializeTimer() + void InitializeTimer() { timer.Interval = 10; timer.Tick += (sender, args) => ship.SimulateTimeFrame(timer.Interval); @@ -154,7 +214,7 @@ public void InitializeTimer() timer.Tick += (sender, args) => controller.CreateRandomAsteroid(); } - public void MovementDataTick() + void MovementDataTick() { var vel = ship.GetVelocity(); var thrust = ship.GetThrustForce(); @@ -169,18 +229,11 @@ public void MovementDataTick() } - public void InputTick() + void InputTick() { if (isSpacePressed && !ship.IsDead()) { - if (ship.cooldown <= 0) - { - var pelleteVelocity = new Vec2(); - pelleteVelocity.X = -Math.Cos(angle / 180 * Math.PI); - pelleteVelocity.Y = -Math.Sin(angle / 180 * Math.PI); - spaceObjects.Add(new Bullet(ship.GetCurrentCoordinates(), pelleteVelocity)); //пока это торпеда - ship.SetShootCooldown(300); - } + controller.CreateBullet(angle); } if (isWPressed && !ship.IsDead()) ship.ApplyForce(); @@ -188,7 +241,6 @@ public void InputTick() BackColor = Color.Black; if (isLPressed) controller.CreateRandomAsteroid(); - } protected override void OnKeyDown(KeyEventArgs e) @@ -200,12 +252,17 @@ protected override void OnKeyDown(KeyEventArgs e) isWPressed = true; if (e.KeyCode == Keys.R) isRPressed = true; - if (e.KeyCode == Keys.T) - debugCollisions = !debugCollisions; - if (e.KeyCode == Keys.H) + if (e.KeyCode == Keys.Oemtilde) debugModeEnabled = !debugModeEnabled; - if (e.KeyCode == Keys.L) - controller.CreateRandomAsteroid(); + if (e.KeyCode == Keys.H) + ToggleHelp(); + if (debugModeEnabled) + { + if (e.KeyCode == Keys.T) + debugCollisions = !debugCollisions; + if (e.KeyCode == Keys.L) + controller.CreateRandomAsteroid(); + } if (e.KeyCode == Keys.O) Cursor.Hide(); if (e.KeyCode == Keys.P) @@ -216,6 +273,8 @@ protected override void OnKeyDown(KeyEventArgs e) this.Dispose(); if (e.KeyCode == Keys.Pause) timer.Enabled = !timer.Enabled; + if (e.KeyCode == Keys.J) + isSoundEnabled = !isSoundEnabled; } protected override void OnKeyUp(KeyEventArgs e) @@ -249,9 +308,7 @@ protected override void OnMouseUp(MouseEventArgs e) protected override void OnPaint(PaintEventArgs e) { shipCords = ship.GetCurrentCoordinates(); - Graphics g = null; - - g = e.Graphics; + Graphics g = e.Graphics; g.SmoothingMode = SmoothingMode.AntiAlias; e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; //рендер корабля @@ -263,7 +320,6 @@ protected override void OnPaint(PaintEventArgs e) ship.ChangeDirection(angle); g.DrawImage(ShipTexture, new Point(-ShipTexture.Width / 2, -ShipTexture.Height / 2)); e.Graphics.ResetTransform(); - //e.Graphics.DrawImage(ShipTexture, (float)shipCords.X, (float)shipCords.Y); if(debugModeEnabled) e.Graphics.DrawLine(Pens.Red, new PointF((float)shipCords.X, (float)shipCords.Y), mousecords); } @@ -277,7 +333,7 @@ protected override void OnMouseMove(MouseEventArgs e) RotateImageAngle(e); } - public float RotateImageAngle(MouseEventArgs e) + float RotateImageAngle(MouseEventArgs e) { var y2 = e.Y; var y1 = shipCords.Y + ShipTexture.Height / 2; @@ -307,6 +363,6 @@ void RenderAsteroids(PaintEventArgs e) } } } - } + } } } diff --git a/Rendering.resx b/Rendering.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Rendering.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/SoundEngine.cs b/SoundEngine.cs new file mode 100644 index 0000000..23e328b --- /dev/null +++ b/SoundEngine.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NAudio.Wave; +using NAudio.Wave.SampleProviders; + +namespace Astroshooter +{ + class AudioPlaybackEngine : IDisposable + { + private readonly IWavePlayer outputDevice; + private readonly MixingSampleProvider mixer; + + public AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2) + { + outputDevice = new WaveOutEvent(); + mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount)); + mixer.ReadFully = true; + outputDevice.Init(mixer); + outputDevice.Play(); + } + + public void PlaySound(string fileName) + { + var input = new AudioFileReader(fileName); + AddMixerInput(new AutoDisposeFileReader(input)); + } + + private ISampleProvider ConvertToRightChannelCount(ISampleProvider input) + { + if (input.WaveFormat.Channels == mixer.WaveFormat.Channels) + { + return input; + } + if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2) + { + return new MonoToStereoSampleProvider(input); + } + throw new NotImplementedException("Not yet implemented this channel count conversion"); + } + + public void PlaySound(CachedSound sound) + { + AddMixerInput(new CachedSoundSampleProvider(sound)); + } + + private void AddMixerInput(ISampleProvider input) + { + mixer.AddMixerInput(ConvertToRightChannelCount(input)); + } + + public void Dispose() + { + outputDevice.Dispose(); + } + + public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(44100, 2); + } + + class AutoDisposeFileReader : ISampleProvider + { + private readonly AudioFileReader reader; + private bool isDisposed; + public AutoDisposeFileReader(AudioFileReader reader) + { + this.reader = reader; + this.WaveFormat = reader.WaveFormat; + } + + public int Read(float[] buffer, int offset, int count) + { + if (isDisposed) + return 0; + int read = reader.Read(buffer, offset, count); + if (read == 0) + { + reader.Dispose(); + isDisposed = true; + } + return read; + } + + public WaveFormat WaveFormat { get; private set; } + } + + class CachedSound + { + public float[] AudioData { get; private set; } + public WaveFormat WaveFormat { get; private set; } + public CachedSound(string audioFileName) + { + using (var audioFileReader = new AudioFileReader(audioFileName)) + { + // TODO: could add resampling in here if required + WaveFormat = audioFileReader.WaveFormat; + var wholeFile = new List((int)(audioFileReader.Length / 4)); + var readBuffer = new float[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels]; + int samplesRead; + while ((samplesRead = audioFileReader.Read(readBuffer, 0, readBuffer.Length)) > 0) + { + wholeFile.AddRange(readBuffer.Take(samplesRead)); + } + AudioData = wholeFile.ToArray(); + } + } + } + + class CachedSoundSampleProvider : ISampleProvider + { + private readonly CachedSound cachedSound; + private long position; + + public CachedSoundSampleProvider(CachedSound cachedSound) + { + this.cachedSound = cachedSound; + } + + public int Read(float[] buffer, int offset, int count) + { + var availableSamples = cachedSound.AudioData.Length - position; + var samplesToCopy = Math.Min(availableSamples, count); + Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy); + position += samplesToCopy; + return (int)samplesToCopy; + } + + public WaveFormat WaveFormat { get { return cachedSound.WaveFormat; } } + } +} diff --git a/Vec2.cs b/Vec2.cs index 0648436..018b3e1 100644 --- a/Vec2.cs +++ b/Vec2.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Drawing; + namespace Astroshooter { public class Vec2 diff --git a/packages.config b/packages.config new file mode 100644 index 0000000..41b1b16 --- /dev/null +++ b/packages.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file