diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa527e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,349 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/Rex2.sln b/Rex2.sln new file mode 100644 index 0000000..031bd14 --- /dev/null +++ b/Rex2.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32112.339 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rex2", "Rex2\Rex2.csproj", "{98787DE9-BD8B-4992-B183-4688B7FCDB2C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {98787DE9-BD8B-4992-B183-4688B7FCDB2C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98787DE9-BD8B-4992-B183-4688B7FCDB2C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98787DE9-BD8B-4992-B183-4688B7FCDB2C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98787DE9-BD8B-4992-B183-4688B7FCDB2C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {03930F4B-CD7F-44C1-A732-D740BFC2F570} + EndGlobalSection +EndGlobal diff --git a/Rex2/LevelBase.cs b/Rex2/LevelBase.cs new file mode 100644 index 0000000..64094d8 --- /dev/null +++ b/Rex2/LevelBase.cs @@ -0,0 +1,106 @@ +using Raylib_cs; +using System.Numerics; + +namespace Rex2 +{ + internal struct Player + { + public Vector2 position; + public float speed; + public bool canJump; + } + + internal struct Platform + { + public Rectangle rect; + public int blocking; + public Color color; + + public Platform(Rectangle rect, int blocking, Color color) + { + this.rect = rect; + this.blocking = blocking; + this.color = color; + } + } + + internal abstract class LevelBase + { + protected int screenHeight; + protected int screenWidth; + protected float scale = 2.0f; + + protected RenderTexture2D screenPlayer1; + protected RenderTexture2D screenPlayer2; + protected Font font; + protected int framesCounter; + + public LevelBase(int screenHeight, int screenWidth, ref RenderTexture2D screenPlayer1, ref RenderTexture2D screenPlayer2) + { + this.screenWidth = screenWidth; + this.screenHeight = screenHeight; + + this.screenPlayer1 = screenPlayer1; + this.screenPlayer2 = screenPlayer2; + + font = Raylib.LoadFontEx("assets/opensans.ttf", 48, null, 5000); + } + + public virtual void Update(float deltaTime) + { + framesCounter++; + } + + public virtual void Unload() + { + Raylib.UnloadFont(font); + } + + protected Vector2 GetVirtualMouse() + { + Vector2 mouse = Raylib.GetMousePosition(); + Vector2 virtualMouse = Vector2.Zero; + + virtualMouse.X = mouse.X / scale - screenWidth; + virtualMouse.Y = (mouse.Y - (screenHeight * 2 - (screenHeight * scale)) * 0.5f) / scale; + + Vector2 max = new Vector2((float)screenWidth, (float)screenHeight); + virtualMouse = Vector2.Clamp(virtualMouse, Vector2.Zero, max); + return virtualMouse; + } + + protected void DrawRot13AnimatedTextEx(string text, int timing, int x, int y, int size, Color color) + { + Raylib.DrawTextEx(font, text.Rot13TextAnimation(0, framesCounter / timing), new Vector2(x, y), size, 1, color); + } + + protected void DrawRot13AnimatedText(string text, int timing, int x, int y, int size, Color color) + { + Raylib.DrawText(text.Rot13TextAnimation(0, framesCounter / timing), x, y, size, color); + } + + protected void DrawCenteredTextEx(string m, int y, int fontSize, Color color) + { + var width = Raylib.MeasureTextEx(font, m, fontSize, 1); + Raylib.DrawTextEx(font, m, new Vector2((screenWidth - width.X) / 2, y), fontSize, 1, color); + } + + protected void DrawCenteredText(string m, int y, int fontSize, Color color) + { + var width = Raylib.MeasureText(m, fontSize); + Raylib.DrawText(m, (screenWidth - width) / 2, y, fontSize, color); + } + + protected void DrawRectangledTextEx(Rectangle container, string text, int fontSize, Color borderColor, Color textColor) + { + Raylib.DrawRectangleLinesEx(container, 3, borderColor); // Draw container border + + // Draw text in container (add some padding) + Raylib.DrawTextRec(font, text, + new Rectangle(container.x + 4, container.y + 4, container.width - 4, container.height - 4), + fontSize, 2.0f, true, textColor); + + Raylib.DrawRectangleRec(new Rectangle(container.x + container.width - 6, container.y + container.height - 6, 8, 8), borderColor); + } + } +} \ No newline at end of file diff --git a/Rex2/LevelManager.cs b/Rex2/LevelManager.cs new file mode 100644 index 0000000..ec39605 --- /dev/null +++ b/Rex2/LevelManager.cs @@ -0,0 +1,50 @@ +namespace Rex2 +{ + internal class LevelManager + { + private List Levels { get; set; } + + private LevelBase welcome; + private LevelBase menu; + private LevelBase lose; + private LevelBase win; + + public LevelManager(LevelBase welcome, LevelBase menu, LevelBase lose, LevelBase win) + { + this.welcome = welcome; + this.lose = lose; + this.win = win; + this.menu = menu; + + Levels = new List(); + Current = welcome; + } + + public LevelBase Current { get; private set; } + + public void Lose() + { + Current = lose; + } + + public void Win() + { + Current = win; + } + + public void Menu() + { + Current = menu; + } + + public void Welcome() + { + Current = welcome; + } + + public void Add(LevelBase level) + { + Levels.Add(level); + } + } +} \ No newline at end of file diff --git a/Rex2/Program.cs b/Rex2/Program.cs new file mode 100644 index 0000000..622d5df --- /dev/null +++ b/Rex2/Program.cs @@ -0,0 +1,51 @@ +using Raylib_cs; +using System.Numerics; +using static Raylib_cs.Color; +using static Raylib_cs.Raylib; + +namespace Rex2 +{ + internal static class Rex2 + { + public static void Main() + { + const int screenWidth = 320; + const int screenHeight = 320; + + const int gameWidth = screenWidth * 4; + const int gameHeight = screenHeight * 2; + + InitWindow(gameWidth, gameHeight, "Rex2"); + SetTargetFPS(60); + RenderTexture2D screenPlayer1 = LoadRenderTexture(screenWidth, screenHeight); + RenderTexture2D screenPlayer2 = LoadRenderTexture(screenWidth, screenHeight); + float scale = gameHeight / screenHeight; + Rectangle sourceRec = new Rectangle(0.0f, 0.0f, screenPlayer1.texture.width, -screenPlayer1.texture.height); + Rectangle destRec = new Rectangle(0, 0, screenWidth * scale, screenHeight * scale); + Rectangle destRec2 = new Rectangle(screenWidth * scale, 0, screenWidth * scale, screenHeight * scale); + + TestLevel test = new TestLevel(screenHeight, screenWidth, ref screenPlayer1, ref screenPlayer2); + LevelManager levelManager = new LevelManager(test, null!, null!, null!); + levelManager.Welcome(); + + while (!WindowShouldClose()) + { + float deltaTime = GetFrameTime(); + + levelManager.Current.Update(deltaTime); + + BeginDrawing(); + ClearBackground(BLACK); + DrawTexturePro(screenPlayer1.texture, sourceRec, destRec, new Vector2(0, 0), 0.0f, WHITE); + DrawTexturePro(screenPlayer2.texture, sourceRec, destRec2, new Vector2(0, 0), 0.0f, WHITE); + EndDrawing(); + } + + UnloadRenderTexture(screenPlayer1); + UnloadRenderTexture(screenPlayer2); + levelManager.Current.Unload(); + + CloseWindow(); + } + } +} \ No newline at end of file diff --git a/Rex2/Rex2.csproj b/Rex2/Rex2.csproj new file mode 100644 index 0000000..4cb0af0 --- /dev/null +++ b/Rex2/Rex2.csproj @@ -0,0 +1,23 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/Rex2/Utils.cs b/Rex2/Utils.cs new file mode 100644 index 0000000..b38f7dd --- /dev/null +++ b/Rex2/Utils.cs @@ -0,0 +1,26 @@ +namespace Rex2 +{ + internal static class Utils + { + public static string SubTextAnimation(this string input, int position, int length) + { + return input.Substring(position, Math.Min(length, input.Length)); + } + + public static string Rot13TextAnimation(this string input, int position, int length) + { + if (length < input.Length) + { + var l = Math.Min(length, input.Length); + return input.Substring(position, l) + input.Rot13().Substring(l); + } + else + return input; + } + + public static string Rot13(this string input) + { + return !string.IsNullOrEmpty(input) ? new string(input.ToCharArray().Select(s => { return (char)((s >= 97 && s <= 122) ? ((s + 13 > 122) ? s - 13 : s + 13) : (s >= 65 && s <= 90 ? (s + 13 > 90 ? s - 13 : s + 13) : s)); }).ToArray()) : input; + } + } +} \ No newline at end of file diff --git a/Rex2/assets/opensans.ttf b/Rex2/assets/opensans.ttf new file mode 100644 index 0000000..51dd3c3 Binary files /dev/null and b/Rex2/assets/opensans.ttf differ diff --git a/Rex2/levels/TestLevel.cs b/Rex2/levels/TestLevel.cs new file mode 100644 index 0000000..8062bdb --- /dev/null +++ b/Rex2/levels/TestLevel.cs @@ -0,0 +1,185 @@ +using Raylib_cs; +using System.Numerics; +using static Raylib_cs.Color; +using static Raylib_cs.KeyboardKey; +using static Raylib_cs.Raylib; + +namespace Rex2 +{ + internal class TestLevel : LevelBase + { + private void UpdateCameraCenter(ref Camera2D camera, ref Player player, Platform[] envItems, float delta, int width, int height) + { + camera.offset = new Vector2(width / 2, height / 2); + camera.target = player.position; + } + + private void UpdatePlayer(ref Player player, Platform[] envItems, float delta) + { + if (IsKeyDown(KEY_LEFT)) + player.position.X -= PLAYER_HOR_SPD * delta; + + if (IsKeyDown(KEY_RIGHT)) + player.position.X += PLAYER_HOR_SPD * delta; + + if (IsKeyDown(KEY_SPACE) && player.canJump) + { + player.speed = -PLAYER_JUMP_SPD; + player.canJump = false; + } + + int hitObstacle = 0; + for (int i = 0; i < envItems.Length; i++) + { + Platform ei = envItems[i]; + Vector2 p = player.position; + if (ei.blocking != 0 && + ei.rect.x <= p.X && + ei.rect.x + ei.rect.width >= p.X && + ei.rect.y >= p.Y && + ei.rect.y < p.Y + player.speed * delta) + { + hitObstacle = 1; + player.speed = 0.0f; + player.position.Y = ei.rect.y; + } + } + + if (hitObstacle == 0) + { + player.position.Y += player.speed * delta; + player.speed += G * delta; + player.canJump = false; + } + else + player.canJump = true; + } + + private const int G = 400; + + private const float PLAYER_JUMP_SPD = 350.0f; + private const float PLAYER_HOR_SPD = 200.0f; + private Platform[] envItems; + private Camera2D camera; + private Player player; + + public TestLevel(int screenHeight, int screenWidth, ref RenderTexture2D screenPlayer1, ref RenderTexture2D screenPlayer2) : base(screenHeight, screenWidth, ref screenPlayer1, ref screenPlayer2) + { + player = new Player(); + player.position = new Vector2(400, 280); + player.speed = 0; + player.canJump = false; + + envItems = new Platform[] { + new Platform(new Rectangle(0, 0, 1000, 400), 0, LIGHTGRAY), + new Platform(new Rectangle(0, 400, 1000, 200), 1, GRAY), + new Platform(new Rectangle(300, 200, 400, 10), 1, GRAY), + new Platform(new Rectangle(250, 300, 100, 10), 1, GRAY), + new Platform(new Rectangle(650, 300, 100, 10), 1, GRAY) + }; + + camera = new Camera2D(); + camera.target = player.position; + camera.offset = new Vector2(screenWidth / 2, screenHeight / 2); + camera.rotation = 0.0f; + camera.zoom = 1.0f; + + framesCounter = 0; + } + + public override void Update(float deltaTime) + { + base.Update(deltaTime); + + UpdatePlayer1(deltaTime); + UpdatePlayer2(deltaTime); + + Draw(); + } + + private void UpdatePlayer2(float deltaTime) + { + } + + private void UpdatePlayer1(float deltaTime) + { + UpdatePlayer(ref player, envItems, deltaTime); + UpdateCameraCenter(ref camera, ref player, envItems, deltaTime, screenWidth, screenHeight); + } + + private void Draw() + { + BeginTextureMode(screenPlayer1); + ClearBackground(LIGHTGRAY); + + BeginMode2D(camera); + + RenderPlatforms(); + RenderPlayer(); + EndMode2D(); + + RenderHelp(); + DrawText($"FPS: {GetFPS()}", 0, 0, 10, GOLD); + + EndTextureMode(); + + BeginTextureMode(screenPlayer2); + ClearBackground(WHITE); + + RenderPlayer2(); + + EndTextureMode(); + } + + private void RenderPlayer2() + { + DrawRot13AnimatedText("Wake up, Rex!", 10, 0, 220, 14, MAGENTA); + //DrawCenteredText("Zażółć gęślą jaźń", 50, 18, GREEN); + DrawRectangledTextEx(new Rectangle(20, 20, 120, 120), "This is a longer text with wrapping.", 14, DARKGREEN, GRAY); + + //DrawRectangle(0, 0, 319, 10, RED); + + Vector2 virtualMouse = GetVirtualMouse(); + + Vector2 pos = new Vector2(10.0f, 10.0f); + Vector2 size = new Vector2(10.0f, 10.0f); + + DrawText($"{virtualMouse.X}, {virtualMouse.Y}", 0, 0, 10, GOLD); + + Rectangle emojiRect = new Rectangle(pos.X, pos.Y, size.X, size.Y); + + if (CheckCollisionPointRec(virtualMouse, emojiRect)) + { + DrawRectangle((int)emojiRect.x, (int)emojiRect.y, (int)emojiRect.width, (int)emojiRect.height, RED); + } + else + { + DrawRectangle((int)emojiRect.x, (int)emojiRect.y, (int)emojiRect.width, (int)emojiRect.height, BLUE); + } + } + + private void RenderHelp() + { + DrawText("Controls:", 20, 20, 10, BLACK); + DrawText("- Right/Left to move", 40, 40, 10, DARKGRAY); + DrawText("- Space to jump", 40, 60, 10, DARKGRAY); + } + + private void RenderPlayer() + { + Rectangle playerRect = new Rectangle(player.position.X - 20, player.position.Y - 40, 40, 40); + DrawRectangleRec(playerRect, RED); + } + + private void RenderPlatforms() + { + for (int i = 0; i < envItems.Length; i++) + DrawRectangleRec(envItems[i].rect, envItems[i].color); + } + + public override void Unload() + { + base.Unload(); + } + } +} \ No newline at end of file diff --git a/Rex2/levels/testlevel.txt b/Rex2/levels/testlevel.txt new file mode 100644 index 0000000..1dc4b8e --- /dev/null +++ b/Rex2/levels/testlevel.txt @@ -0,0 +1,21 @@ +# file header +18,20,1,3 # 18 rows, 20 columns, player in position 1,3 +XXXXDDXXXX # DD are doors +XFFFFFFFFX +XFFFFFFFFX +XFFFFFFFFXXXXXXXXXX +XFFFFFFFFFFFFFFFFFX +XFFFFFFFFFFFFFFFFFX +XFFFFFFFFXXXXXXXFFX +XFFFFFFFFXXXXXXXFFX +XFFFFFFFFFFFFFFFFFFX +XXXXFF FFFFFFFFFFFX +XXXXFFXXXXXXXXXXXFFX +XXXXFFXXXX XFFX +XXXXFFXXXX XDDX +XXXXFFXXXX XXXX +XXXXFFXXXXXXXXXXXFFX +XXXXFFFFFFFFFFFFFFFX +XXXXFFFFFFFFFFFFFFFX +XXXXXXXXXXXXXXXXXXXX +0,10,5,0,0,0,0 # barrel at 10,5,0 position, with rotation 0,0,0 \ No newline at end of file