diff --git a/.gitignore b/.gitignore index f1abf07..104b544 100644 --- a/.gitignore +++ b/.gitignore @@ -1,24 +1,10 @@ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## -<<<<<<< HEAD -<<<<<<< HEAD -======= -<<<<<<< HEAD -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore -======= -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> c6d355b (0.0.3-prealpha) -## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore -======= -## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore ->>>>>>> 009817d (0.0.3-prealpha) -<<<<<<< HEAD -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) +## Get latest from `dotnet new gitignore` + +# dotenv files +.env # User-specific files *.rsuser @@ -40,22 +26,7 @@ mono_crash.* [Rr]eleases/ x64/ x86/ -<<<<<<< HEAD -<<<<<<< HEAD -======= -[Ww][Ii][Nn]32/ ->>>>>>> 009817d (0.0.3-prealpha) -======= -<<<<<<< HEAD -[Ww][Ii][Nn]32/ -======= ->>>>>>> 1523d8c (Initial commit) -======= -======= [Ww][Ii][Nn]32/ ->>>>>>> 009817d (0.0.3-prealpha) ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ @@ -89,31 +60,17 @@ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ -# .NET Core +# .NET project.lock.json project.fragment.lock.json artifacts/ -<<<<<<< HEAD -<<<<<<< HEAD -======= -<<<<<<< HEAD -# ASP.NET Scaffolding -ScaffoldingReadMe.txt +# Tye +.tye/ -======= ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> c6d355b (0.0.3-prealpha) -======= # ASP.NET Scaffolding ScaffoldingReadMe.txt ->>>>>>> 009817d (0.0.3-prealpha) -<<<<<<< HEAD -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # StyleCop StyleCopReport.xml @@ -139,22 +96,7 @@ StyleCopReport.xml *.tmp_proj *_wpftmp.csproj *.log -<<<<<<< HEAD -<<<<<<< HEAD -======= -*.tlog ->>>>>>> 009817d (0.0.3-prealpha) -======= -<<<<<<< HEAD *.tlog -======= ->>>>>>> 1523d8c (Initial commit) -======= -======= -*.tlog ->>>>>>> 009817d (0.0.3-prealpha) ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) *.vspscc *.vssscc .builds @@ -206,30 +148,11 @@ _TeamCity* .axoCover/* !.axoCover/settings.json -<<<<<<< HEAD -<<<<<<< HEAD -======= -======= -<<<<<<< HEAD -======= -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # Coverlet is a free, cross platform Code Coverage Tool coverage*.json coverage*.xml coverage*.info -<<<<<<< HEAD ->>>>>>> 009817d (0.0.3-prealpha) -======= -<<<<<<< HEAD -======= ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> 009817d (0.0.3-prealpha) ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # Visual Studio code coverage results *.coverage *.coveragexml @@ -377,15 +300,6 @@ node_modules/ # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw -<<<<<<< HEAD -<<<<<<< HEAD -======= -======= -<<<<<<< HEAD -======= -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # Visual Studio 6 auto-generated project file (contains which files were open etc.) *.vbp @@ -397,16 +311,6 @@ node_modules/ *.ncb *.aps -<<<<<<< HEAD ->>>>>>> 009817d (0.0.3-prealpha) -======= -<<<<<<< HEAD -======= ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> 009817d (0.0.3-prealpha) ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts @@ -463,26 +367,9 @@ ASALocalRun/ # Local History for Visual Studio .localhistory/ -<<<<<<< HEAD -<<<<<<< HEAD -======= -<<<<<<< HEAD -# Visual Studio History (VSHistory) files -.vshistory/ - -======= ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> c6d355b (0.0.3-prealpha) -======= # Visual Studio History (VSHistory) files .vshistory/ ->>>>>>> 009817d (0.0.3-prealpha) -<<<<<<< HEAD -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # BeatPulse healthcheck temp database healthchecksdb @@ -491,15 +378,6 @@ MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ -<<<<<<< HEAD -<<<<<<< HEAD -======= -======= -<<<<<<< HEAD -======= -======= ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) # Fody - auto-generated XML schema FodyWeavers.xsd @@ -524,13 +402,83 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml -<<<<<<< HEAD ->>>>>>> 009817d (0.0.3-prealpha) -======= -<<<<<<< HEAD -======= ->>>>>>> 1523d8c (Initial commit) -======= ->>>>>>> 009817d (0.0.3-prealpha) ->>>>>>> 415de6f (0.0.3-prealpha) ->>>>>>> c6d355b (0.0.3-prealpha) +.idea + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# Vim temporary swap files +*.swp diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index cfac8aa..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "name": "run building game", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/bin/Debug/net7.0-windows/BuildingGame.dll", - "args": [], - "requireExactSource": false, - "cwd": "${workspaceFolder}/bin/Debug/net7.0-windows/", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, - { - "name": ".NET Core Attach", - "type": "coreclr", - "request": "attach" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 5996fd7..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/BuildingGame.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/BuildingGame.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "--project", - "${workspaceFolder}/BuildingGame.csproj" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/Assets/IconLarge.png b/Assets/IconLarge.png new file mode 100644 index 0000000..a57e921 Binary files /dev/null and b/Assets/IconLarge.png differ diff --git a/Assets/TheWorld.png b/Assets/TheWorld.png new file mode 100644 index 0000000..5071031 Binary files /dev/null and b/Assets/TheWorld.png differ diff --git a/BuildingGame.sln b/BuildingGame.sln new file mode 100644 index 0000000..7670508 --- /dev/null +++ b/BuildingGame.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.002.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BuildingGame", "Sources\BuildingGame.csproj", "{0E5DFCAE-A388-4045-B463-B114D934BCE6}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0E5DFCAE-A388-4045-B463-B114D934BCE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0E5DFCAE-A388-4045-B463-B114D934BCE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0E5DFCAE-A388-4045-B463-B114D934BCE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0E5DFCAE-A388-4045-B463-B114D934BCE6}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F66DCBE7-11D8-4DE0-AE01-B1A4F0CA2304} + EndGlobalSection +EndGlobal diff --git a/ConfigParser.cs b/ConfigParser.cs deleted file mode 100644 index 109c741..0000000 --- a/ConfigParser.cs +++ /dev/null @@ -1,70 +0,0 @@ -namespace BuildingGame; - -public static class ConfigParser -{ - public static Dictionary Parse(string fileName) - { - var dict = new Dictionary(); - if (!File.Exists(fileName)) return dict; - - foreach (var line in File.ReadAllLines(fileName)) - { - string[] kv = line.Split(':'); - if (kv.Length > 2 || kv.Length < 2) continue; - dict.Add(kv[0].Trim(), kv[1].Trim()); - } - - return dict; - } - - public static Dictionary ParseTriple(string fileName) - { - var dict = new Dictionary(); - if (!File.Exists(fileName)) return dict; - - foreach (var line in File.ReadAllLines(fileName)) - { - string[] kv = line.Split(':'); - if (kv.Length != 3) continue; - dict.Add(kv[0].Trim(), (kv[1].Trim(), kv[2].Trim())); - } - - return dict; - } - - public static Dictionary ParseFourth(string fileName) - { - var dict = new Dictionary(); - if (!File.Exists(fileName)) return dict; - - foreach (var line in File.ReadAllLines(fileName)) - { - string[] kv = line.Split(':'); - if (kv.Length != 4) continue; - dict.Add(kv[0].Trim(), (kv[1].Trim(), kv[2].Trim(), kv[3].Trim())); - } - - return dict; - } - - public static void Write(string fileName, Dictionary data) - { - List strList = new List(); - - foreach (var key in data.Keys) - { - strList.Add($"{key}:{data[key]}"); - } - - File.WriteAllLines(fileName, strList.ToArray()); - } - - public static Vector2 ToVector2i(string str) - { - string[] xy = str.Split(','); - if (xy.Length < 2) return new Vector2(0, 0); - if (int.TryParse(xy[0], out int x) && int.TryParse(xy[1], out int y)) - return new(x, y); - return new Vector2(0, 0); - } -} \ No newline at end of file diff --git a/Gui.cs b/Gui.cs deleted file mode 100644 index 2a3a2ce..0000000 --- a/Gui.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace BuildingGame; - -public static class Gui -{ - public static Font GuiFont => _font; - public static bool ProcessGui { get; set; } = true; - private static Font _font; - - private static List _controls = - new List(); - - public static void SetGuiFont(Font font) - { - _font = font; - } - public static void SetGuiFont(string fileName) - { - _font = LoadFont(fileName); - } - public static void UnloadGuiFont() => UnloadFont(_font); - - public static ControlInfo[] GetControls() - { - return _controls.ToArray(); - } - - public static bool IsMouseOverControl - { - get - { - foreach (var control in GetControls()) - { - if (control.Control.Active && control.Control.IsMouseHovered() && (control.Holder == Program.currentScreen || control.MultiScreen)) - { - return true; - } - } - return false; - } - } - - public static void PutControl(ControlInfo info) - { - if (!_controls.Contains(info)) - _controls.Add(info); - - foreach (var child in info.Control.Children) - { - PutControl(new ControlInfo(child, info.Holder, info.MultiScreen)); - } - } - - public static void PutControl(Control control, Screen holder, bool multiscreen = false) - { - PutControl(new ControlInfo(control, holder, multiscreen)); - } - public static Control PopControl(string id) - { - var control = _controls.Find(m => m.Control.Name == id); - _controls.Remove(control); - return control.Control; - } - public static void RemoveControl(string id) - { - var control = _controls.Find(m => m.Control.Name == id); - _controls.Remove(control); - } - public static Control GetControl(string id) => _controls.Find(m => m.Control.Name == id).Control; - - public static void Update() - { - if (!ProcessGui) return; - - var curControls = GetControls(); - foreach (var control in curControls) - { - if (control.Holder == Program.currentScreen || control.MultiScreen) - { - control.Control.Update(); - foreach (var child in control.Control.Children) - { - var info = new ControlInfo(child, control.Holder, control.MultiScreen); - if (!_controls.Contains(info)) - _controls.Add(info); - } - } - - } - } - - public static void Draw() - { - if (!ProcessGui) return; - - var curControls = GetControls(); - - curControls.ToList().Sort((x, y) => x.Control.ZIndex.CompareTo(y.Control.ZIndex)); - foreach (var control in curControls) - { - if (control.Control.Active && (control.Holder == Program.currentScreen || control.MultiScreen)) - control.Control.Draw(); - } - } -} \ No newline at end of file diff --git a/GuiElements/BackgroundBlock.cs b/GuiElements/BackgroundBlock.cs deleted file mode 100644 index c945bdd..0000000 --- a/GuiElements/BackgroundBlock.cs +++ /dev/null @@ -1,20 +0,0 @@ -using BuildingGame.GuiElements.Brushes; - -namespace BuildingGame.GuiElements; - -public class BackgroundBlock : Control -{ - public IBrush? Background { get; set; } - - public BackgroundBlock(string name, IBrush? background = null) - : base(name) - { - Background = background; - } - - public override void Draw() - { - if (Background != null) - Background.FillArea(Area); - } -} \ No newline at end of file diff --git a/GuiElements/Brushes/ColorBrush.cs b/GuiElements/Brushes/ColorBrush.cs deleted file mode 100644 index a25f1c9..0000000 --- a/GuiElements/Brushes/ColorBrush.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace BuildingGame.GuiElements.Brushes; - -public class ColorBrush : IBrush -{ - public Color Color { get; set; } - - public ColorBrush(Color color) - { - Color = color; - } - - public void FillArea(Rectangle area) - { - DrawRectangleRec(area, Color); - } - - public static implicit operator Color(ColorBrush bg) => bg.Color; - public static implicit operator ColorBrush(Color color) => new ColorBrush(color); -} \ No newline at end of file diff --git a/GuiElements/Brushes/GradientBrush.cs b/GuiElements/Brushes/GradientBrush.cs deleted file mode 100644 index 0cd78e9..0000000 --- a/GuiElements/Brushes/GradientBrush.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace BuildingGame.GuiElements.Brushes; - -public class GradientBrush : IBrush -{ - public Color ColorA { get; set; } - public Color ColorB { get; set; } - - public GradientBrush(Color colorA, Color colorB) - { - ColorA = colorA; - ColorB = colorB; - } - - public void FillArea(Rectangle area) - { - DrawRectangleGradientV((int)area.x, (int)area.y, (int)area.width, (int)area.height, ColorA, ColorB); - } -} \ No newline at end of file diff --git a/GuiElements/CheckBox.cs b/GuiElements/CheckBox.cs deleted file mode 100644 index 803b6e4..0000000 --- a/GuiElements/CheckBox.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace BuildingGame.GuiElements; - -public class CheckBox : Control -{ - public bool Checked { get; set; } - public string? Text { get; set; } - public float TextSize { get; set; } - - public CheckBox(string name, string text, Vector2 position, float textSize) - : base(name) - { - Checked = false; - Text = text; - TextSize = textSize; - - var size = MeasureTextEx(Gui.GuiFont, Text, TextSize, 1); - Area = new Rectangle(position.X, position.Y, size.X + 18 + 8, 18 + Math.Abs(size.Y - 18)); - } - - public override void Update() - { - base.Update(); - - if (IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT) && IsMouseHovered()) - Checked = !Checked; - } - - public override void Draw() - { - DrawRectangle((int)Area.x, (int)Area.y, 18, 18, Color.LIGHTGRAY); - DrawRectangleLines((int)Area.x, (int)Area.y, 18, 18, Color.GRAY); - if (Checked) - DrawTexture(Program.check, (int)Area.x, (int)Area.y, Color.WHITE); - DrawTextEx(Gui.GuiFont, Text, new Vector2(Area.x + 8 + 18, Area.y), TextSize, 1, Color.WHITE); - } -} \ No newline at end of file diff --git a/GuiElements/Control.cs b/GuiElements/Control.cs deleted file mode 100644 index 52f9b8d..0000000 --- a/GuiElements/Control.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Linq.Expressions; - -namespace BuildingGame.GuiElements; - -public class Control -{ - public string Name { get; set; } - public Rectangle Area { get; set; } - public List Children { get; set; } = new(); - public event Action? Clicked; - public event Action? ClientUpdate; - public bool Active { get; set; } - public int ZIndex { get; set; } - public string? Tooltip { get; set; } - private bool _pressed = false; - private bool _oldPressed = false; - - public Control(string name) - { - Name = name; - Active = true; - } - - public virtual void Update() - { - _oldPressed = _pressed; - _pressed = IsMouseButtonDown(MouseButton.MOUSE_BUTTON_LEFT); - - if (_oldPressed && !_pressed && IsMouseHovered()) - { - Clicked?.Invoke(); - } - - foreach (var child in Children) - { - child.Active = Active; - if (child.ZIndex < ZIndex) - child.ZIndex = ZIndex + 1; - } - - ClientUpdate?.Invoke(); - } - - public virtual void Draw() - { - - } - - public bool IsMouseHovered() - { - return CheckCollisionPointRec(GetMousePosition(), Area) && Active; - } - - public void Adapt(Func position) - { - ClientUpdate += () => - { - var pos = position.Invoke(new Vector2(Program.WIDTH, Program.HEIGHT)); - Area = new Rectangle(pos.X, pos.Y, Area.width, Area.height); - }; - } - - public void Adapt(Func area) - { - ClientUpdate += () => - { - Area = area.Invoke(new Vector2(Program.WIDTH, Program.HEIGHT)); - }; - } -} \ No newline at end of file diff --git a/GuiElements/ControlInfo.cs b/GuiElements/ControlInfo.cs deleted file mode 100644 index 2dd0992..0000000 --- a/GuiElements/ControlInfo.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace BuildingGame; - -public struct ControlInfo -{ - public Control Control { get; } - public Screen Holder { get; } - public bool MultiScreen { get; } - - public ControlInfo(Control control, Screen holder, bool multiscreen) - { - Control = control; - Holder = holder; - MultiScreen = multiscreen; - } - public ControlInfo((Control control, Screen holder, bool multiscreen) oldControlInfo) - : this(oldControlInfo.control, oldControlInfo.holder, oldControlInfo.multiscreen) {} -} \ No newline at end of file diff --git a/GuiElements/HoverButton.cs b/GuiElements/HoverButton.cs deleted file mode 100644 index c65f852..0000000 --- a/GuiElements/HoverButton.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace BuildingGame.GuiElements; - -public class HoverButton : TextBlock -{ - public HoverButton(string name, string text, Vector2 position, float size = 12) - : base(name, text, position, size) - { - SetupButtonCollision(); - } - - public override void Update() - { - base.Update(); - - if (_centeredScreen) CenterScreen(); - - if (CheckCollisionPointRec(GetMousePosition(), Area)) - { - if (Text != null && !Text.StartsWith("> ")) - Text = Text.Insert(0, "> "); - } - else - { - if (Text != null && Text.StartsWith("> ")) - { - Text = Text.Remove(0, 2); - } - } - } - - public void SetupButtonCollision() - { - SetupCollision("> " + Text); - } -} \ No newline at end of file diff --git a/GuiElements/ImageArea.cs b/GuiElements/ImageArea.cs deleted file mode 100644 index b499f3c..0000000 --- a/GuiElements/ImageArea.cs +++ /dev/null @@ -1,26 +0,0 @@ -using BuildingGame.GuiElements.Brushes; - -namespace BuildingGame.GuiElements; - -public class ImageArea : Control -{ - public Texture2D Image { get; set; } - public Rectangle ImageSourceRect { get; set; } - public IBrush? Background { get; set; } - public Color Tint { get; set; } - - public ImageArea(string name, Texture2D image, Rectangle imageSourceRect, Color tint) - : base(name) - { - Image = image; - ImageSourceRect = imageSourceRect; - Tint = tint; - } - - public override void Draw() - { - if (Background != null) - Background.FillArea(Area); - DrawTexturePro(Image, ImageSourceRect, Area, Vector2.Zero, 0, Tint); - } -} \ No newline at end of file diff --git a/GuiElements/InputBox.cs b/GuiElements/InputBox.cs deleted file mode 100644 index 46d2bef..0000000 --- a/GuiElements/InputBox.cs +++ /dev/null @@ -1,77 +0,0 @@ -namespace BuildingGame.GuiElements; - -public class InputBox : Control -{ - public string? Text { get; set; } = string.Empty; - public Range CharacterRange { get; set; } = new Range(32, 125); - public float TextSize { get; set; } = 12f; - public int MaxCharacters { get; set; } = 3; - private bool _focused = false; - - public InputBox(string name, int maxChars = 3, float textSize = 12) - : base(name) - { - MaxCharacters = maxChars; - TextSize = textSize; - } - - public InputBox(string name, Range characterRange, int maxChars = 3, float textSize = 12) - : base(name) - { - CharacterRange = characterRange; - MaxCharacters = maxChars; - TextSize = textSize; - } - - public override void Update() - { - base.Update(); - - if (IsMouseButtonPressed(MouseButton.MOUSE_BUTTON_LEFT)) - { - if (IsMouseHovered()) _focused = true; - else _focused = false; - } - - if (_focused) - { - int @char = GetCharPressed(); - if (@char >= CharacterRange.Start.Value && @char <= CharacterRange.End.Value && Text != null && Text.Length < MaxCharacters) - { - Text += char.ConvertFromUtf32(@char); - } - } - if (IsKeyPressed(KeyboardKey.KEY_BACKSPACE) && Text != null && Text.Length != 0 && _focused) - { - Text = Text.Remove(Text.Length - 1); - Log.Information(Text); - } - } - - public override void Draw() - { - DrawRectangleRec(Area, Color.LIGHTGRAY); - DrawRectangleLinesEx(Area, 1, !_focused ? Color.BLACK : Color.GRAY); - DrawTextEx(Gui.GuiFont, Text, new Vector2(Area.x + 8, Area.y), TextSize, 1, Color.BLACK); - } - - public void ResizeTo(int charCount) - { - var size = MeasureTextEx(Gui.GuiFont, CopyString('@', charCount), TextSize, 1); - Area = new Rectangle( - Area.x, - Area.y, - size.X, - size.Y - ); - MaxCharacters = charCount; - } - - private string CopyString(char c, int count) - { - string str = ""; - for (int i = 0; i < count; i++) - str += c; - return str; - } -} \ No newline at end of file diff --git a/GuiElements/ListView.cs b/GuiElements/ListView.cs deleted file mode 100644 index 0ba6bd5..0000000 --- a/GuiElements/ListView.cs +++ /dev/null @@ -1,75 +0,0 @@ -using BuildingGame.Screens; - -namespace BuildingGame.GuiElements; - -public class ListView : Control -{ - private List _strItems = new List(); - public event Action? ItemClicked; - - public ListView(string name) - : base(name) - { - } - - public override void Update() - { - base.Update(); - - var children = Children; - var first = children.First(); - var last = children.Last(); - - if (GetMouseWheelMoveV().Y < 0) - { - foreach (var child in children) child.Area = new Rectangle(child.Area.x, child.Area.y - child.Area.height, child.Area.width, child.Area.height); - } - - if (GetMouseWheelMoveV().Y > 0) - { - foreach (var child in children) child.Area = new Rectangle(child.Area.x, child.Area.y + child.Area.height, child.Area.width, child.Area.height); - } - } - - public void RecreateItems() - { - foreach (var control in Children) Gui.RemoveControl(control.Name); - Children.Clear(); - - for (int i = 0; i < _strItems.Count; i++) - { - var text = _strItems[i]; - var block = new TextBlock(Name + "_item_" + i, text, - new Vector2(0, 10 + (24 + 6) * (i + 1)), 24); - block.CenterScreen(); - block.Color = Color.WHITE; - block.Clicked += () => ItemClicked?.Invoke(text); - - Children.Add(block); - } - } - - public void PutItem(string item) - { - _strItems.Add(item); - RecreateItems(); - } - - public void RemoveItem(string item) - { - _strItems.Remove(item); - RecreateItems(); - } - - public void ReplaceItem(string orig, string @new) - { - _strItems[_strItems.IndexOf(orig)] = @new; - RecreateItems(); - } - - public void ClearItems() - { - _strItems.Clear(); - RecreateItems(); - } -} \ No newline at end of file diff --git a/GuiElements/RgbBoxLine.cs b/GuiElements/RgbBoxLine.cs deleted file mode 100644 index 335045a..0000000 --- a/GuiElements/RgbBoxLine.cs +++ /dev/null @@ -1,92 +0,0 @@ -using BuildingGame.GuiElements.Brushes; - -namespace BuildingGame.GuiElements; - -public class RgbBoxLine : Control -{ - public Color DefaultColor { get; } - public byte R { get; private set; } - public byte G { get; private set; } - public byte B { get; private set; } - - private InputBox _rBox; - private InputBox _gBox; - private InputBox _bBox; - private BackgroundBlock _colorPreview; - private TextBlock _resetButton; - - public RgbBoxLine(string name, Vector2 position, Color defaultColor) - : base(name) - { - DefaultColor = defaultColor; - Area = new Rectangle(position.X, position.Y, (48 + 4) * 5, 18); - _colorPreview = new BackgroundBlock(Name + "_colorprev", (ColorBrush)Color.WHITE) - { - Area = new Rectangle(position.X, position.Y, 32, 18) - }; - - Range numRange = new Range('\u0030', '\u0039'); - - _rBox = new InputBox(Name + "_rbox", numRange, textSize: 18) - { - Area = new Rectangle(position.X + 48 + 4, position.Y, 0, 0), - }; - _rBox.ResizeTo(3); - - _gBox = new InputBox(Name + "_gbox", numRange, textSize: 18) - { - Area = new Rectangle(position.X + (48 + 4) * 2, position.Y, 0, 0), - }; - _gBox.ResizeTo(3); - - _bBox = new InputBox(Name + "_bbox", numRange, textSize: 18) - { - Area = new Rectangle(position.X + (48 + 4) * 3, position.Y, 0, 0) - }; - _bBox.ResizeTo(3); - - _resetButton = new TextBlock(Name + "_reset", "reset", new Vector2(position.X + (48 + 4) * 4, position.Y), 18); - _resetButton.Color = Color.WHITE; - _resetButton.Clicked += () => - { - ImportColor(DefaultColor); - }; - - _colorPreview.Adapt(_ => new Vector2(Area.x, Area.y)); - _rBox.Adapt(_ => new Vector2(Area.x + 48 + 4, Area.y)); - _gBox.Adapt(_ => new Vector2(Area.x + (48 + 4) * 2, Area.y)); - _bBox.Adapt(_ => new Vector2(Area.x + (48 + 4) * 3, Area.y)); - _resetButton.Adapt(_ => new Vector2(Area.x + (48 + 4) * 4, Area.y)); - - Children.Add(_colorPreview); - Children.Add(_rBox); - Children.Add(_gBox); - Children.Add(_bBox); - Children.Add(_resetButton); - // Gui.PutControl(id, this); - } - - public override void Update() - { - base.Update(); - - if (byte.TryParse(_rBox.Text, out var r)) R = r; - if (byte.TryParse(_gBox.Text, out var g)) G = g; - if (byte.TryParse(_bBox.Text, out var b)) B = b; - - _colorPreview.Background = (ColorBrush)new Color(R, G, B, (byte)255); - } - - public Color ExportColor() => new Color(R, G, B, (byte)255); - public void ImportColor(Color color) - { - R = color.r; - G = color.g; - B = color.b; - - _rBox.Text = R.ToString(); - _gBox.Text = G.ToString(); - _bBox.Text = B.ToString(); - _colorPreview.Background = (ColorBrush)new Color(R, G, B, (byte)255); - } -} \ No newline at end of file diff --git a/GuiElements/TextBlock.cs b/GuiElements/TextBlock.cs deleted file mode 100644 index 69ef5e3..0000000 --- a/GuiElements/TextBlock.cs +++ /dev/null @@ -1,59 +0,0 @@ -namespace BuildingGame.GuiElements; - -public class TextBlock : Control -{ - public string? Text { get; set; } - public float Size { get; set; } = 12f; - public Font Font { get; set; } = Gui.GuiFont; - public Color Color { get; set; } = Color.BLACK; - protected bool _centeredScreen = false; - - public TextBlock(string name, string text, Vector2 position, float size = 12) - : base(name) - { - Text = text; - Area = new Rectangle(position.X, position.Y, 0, 0); - Size = size; - SetupCollision(); - } - - public void Center() - { - Area = new Rectangle( - Area.x + Area.width / 2 - MeasureTextEx(Font, Text, Size, 1).X / 2, - Area.y, - Area.width, - Area.height - ); - } - public void CenterScreen() - { - Area = new Rectangle( - Program.WIDTH / 2 - MeasureTextEx(Font, Text, Size, 1).X / 2, - Area.y, - Area.width, - Area.height - ); - _centeredScreen = true; - } - - public void SetupCollision(string? text = null) - { - if (text == null) text = Text; - if (text != null) - { - var size = MeasureTextEx(Font, text, Size, 1); - - Area = new Rectangle( - Area.x, - Area.y, - size.X, - size.Y - ); - } - } - public override void Draw() - { - DrawTextEx(Font, Text, new Vector2(Area.x, Area.y), Size, 1, Color); - } -} \ No newline at end of file diff --git a/GuiElements/Tooltip.cs b/GuiElements/Tooltip.cs deleted file mode 100644 index 52cbbf6..0000000 --- a/GuiElements/Tooltip.cs +++ /dev/null @@ -1,52 +0,0 @@ -using BuildingGame.GuiElements.Brushes; - -namespace BuildingGame.GuiElements; - -public class Tooltip : Control -{ - public IBrush? Background { get; set; } - public float TextSize { get; set; } - private string _currentTrigger; - - public Tooltip(string name, float size = 18) - : base(name) - { - ZIndex = 100; - TextSize = size; - Active = false; - _currentTrigger = ""; - } - - public override void Update() - { - base.Update(); - - var mpos = GetMousePosition() + new Vector2(0, 16); - var size = MeasureTextEx(Gui.GuiFont, _currentTrigger, TextSize, 1); - Area = new Rectangle( - mpos.X - 16, - mpos.Y, - size.X + 16, - size.Y + 16 - ); - - bool isTriggered = false; - foreach (var trigger in Gui.GetControls()) - { - if (trigger.Control.Tooltip != null && trigger.Control.IsMouseHovered()) - { - _currentTrigger = trigger.Control.Tooltip; - isTriggered = true; - break; - } - } - Active = isTriggered; - } - - public override void Draw() - { - if (Background != null) - Background.FillArea(Area); - DrawTextEx(Gui.GuiFont, _currentTrigger, new Vector2(Area.x + 8, Area.y + 8), TextSize, 1, Color.WHITE); - } -} \ No newline at end of file diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 1e908b5..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Danil - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/Program.cs b/Program.cs deleted file mode 100644 index cd2893b..0000000 --- a/Program.cs +++ /dev/null @@ -1,188 +0,0 @@ -global using System.Numerics; -global using BuildingGame.GuiElements; -global using BuildingGame.GuiElements.Brushes; -global using BuildingGame.Screens; -global using BuildingGame.TilePacks; -global using BuildingGame.Tiles; -global using Raylib_cs; -global using Serilog; -global using static Raylib_cs.Raylib; -global using static Raylib_cs.Raymath; -using System.Diagnostics; - -namespace BuildingGame; - -#nullable disable -internal class Program -{ - internal static int WIDTH = 800; - static int preWidth = 800; - internal static int HEIGHT = 600; - static int preHeight = 600; - - internal static Texture2D origAtlas; - internal static Texture2D atlas; - internal static Texture2D check; - - internal static bool mustClose = false; - - internal static MenuScreen menuScreen; - internal static GameScreen gameScreen; - internal static WorldSelectScreen worldSelectScreen; - internal static CreateWorldScreen createWorldScreen; - internal static Screen currentScreen; - internal static SelectPackScreen selectPackScreen; - - internal static string version = ""; - - private static bool _isFullScreen = false; - - private static void Main() - { - Log.Logger = new LoggerConfiguration() - .MinimumLevel.Debug() - .WriteTo.Console() - .WriteTo.File("logs/latest.log", rollingInterval: RollingInterval.Minute) - .CreateLogger(); - - AppDomain.CurrentDomain.UnhandledException += (sender, ev) => - { - Log.Fatal("something fatal happened on code side.\nexception: " + ev.ExceptionObject.ToString()); - }; - InitWindow(WIDTH, HEIGHT, "BuildingGame"); - SetExitKey(KeyboardKey.KEY_NULL); - SetWindowState(ConfigFlags.FLAG_WINDOW_RESIZABLE); - SetWindowState(ConfigFlags.FLAG_VSYNC_HINT); - SetWindowMinSize(800, 600); - - Gui.SetGuiFont("assets/font.ttf"); - - BeginDrawing(); - ClearBackground(new Color(20, 20, 20, 255)); - - DrawTextEx(Gui.GuiFont, "Loading assets...", new Vector2(16, HEIGHT - 18 - 16), 18, 1, Color.WHITE); - - EndDrawing(); - - Log.Information("loading assets..."); - - var versionInfo = FileVersionInfo.GetVersionInfo(Path.Join(AppContext.BaseDirectory, "buildinggame.exe")); - version = versionInfo.FileVersion; - - origAtlas = LoadTexture("assets/atlas.png"); - atlas = origAtlas; - check = LoadTexture("assets/check.png"); - SetTextureFilter(atlas, TextureFilter.TEXTURE_FILTER_POINT); - SetTextureFilter(check, TextureFilter.TEXTURE_FILTER_POINT); - - Image icon = LoadImage("assets/icon.png"); - SetWindowIcon(icon); - - Settings.Load(); - TilePackManager.LoadPacks(); - Log.Information("loaded packs and settings"); - if (Settings.CurrentPack != "default") - TilePackManager.ApplyPack(Settings.CurrentPack); - else - TilePackManager.SetDefaultPack(); - Log.Information("toggled pack to 'currentPack' value in settings (" + Settings.CurrentPack + ")"); - - gameScreen = new GameScreen(); - gameScreen.Initialize(); - - menuScreen = new MenuScreen(); - menuScreen.Initialize(); - - worldSelectScreen = new WorldSelectScreen(); - worldSelectScreen.Initialize(); - - createWorldScreen = new CreateWorldScreen(); - createWorldScreen.Initialize(); - - selectPackScreen = new SelectPackScreen(); - selectPackScreen.Initialize(); - - currentScreen = menuScreen; - - Log.Information("initialized screens"); - Log.Information("going to the event loop"); - - while (!WindowShouldClose()) - { - // SetWindowTitle("BuldingGame - " + GetFPS() + " FPS"); - if (mustClose) break; - if (IsKeyPressed(KeyboardKey.KEY_F11)) - { - if (_isFullScreen) - { - WIDTH = preWidth; - HEIGHT = preHeight; - ClearWindowState(ConfigFlags.FLAG_WINDOW_UNDECORATED); - SetWindowPosition(GetMonitorWidth(GetCurrentMonitor()) / 2 - WIDTH / 2, - GetMonitorHeight(GetCurrentMonitor()) / 2 - HEIGHT / 2); - } - else - { - preWidth = WIDTH; - preHeight = HEIGHT; - - WIDTH = GetMonitorWidth(GetCurrentMonitor()); - HEIGHT = GetMonitorHeight(GetCurrentMonitor()); - - SetWindowPosition(0, 0); - SetWindowState(ConfigFlags.FLAG_WINDOW_UNDECORATED); - } - SetWindowSize(WIDTH, HEIGHT); - _isFullScreen = !_isFullScreen; - - - - } - - // take screenshot ONLY if player is in game right now - if (IsKeyPressed(KeyboardKey.KEY_F2) && currentScreen == gameScreen) Screenshot.Create(); - // toggle gui only if player is in game right now - if (IsKeyPressed(KeyboardKey.KEY_F1) && currentScreen == gameScreen) - { - Gui.ProcessGui = !Gui.ProcessGui; - gameScreen.showPreTile = !gameScreen.showPreTile; - } - - WIDTH = GetScreenWidth(); - HEIGHT = GetScreenHeight(); - if (IsWindowResized()) - { - var bgPanel = (BackgroundBlock)Gui.GetControl("bgPanel"); - var tooltip = (Tooltip)Gui.GetControl("tileTooltip"); - // gameScreen.RecreateTileMenu(bgPanel, tooltip); - gameScreen.camera.offset = new Vector2(Program.WIDTH / 2, Program.HEIGHT); - } - - currentScreen.Update(); - - Gui.Update(); - - BeginDrawing(); - - currentScreen.Draw(); - - Gui.Draw(); - DrawTextEx(Gui.GuiFont, GetFPS().ToString(), Vector2.Zero, 18, 1, Color.LIME); - - EndDrawing(); - } - - Log.Information("saving world..."); - gameScreen.World.Save(); - Log.Information("saving settings..."); - Settings.Save(); - - Log.Information("unloading textures..."); - UnloadImage(icon); - UnloadTexture(check); - UnloadTexture(origAtlas); - TilePackManager.UnloadPacks(); - Log.Information("goodbye!"); - CloseWindow(); - } -} \ No newline at end of file diff --git a/README.md b/README.md index 80b731c..ac343ba 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,31 @@ -# BuildingGame -### a game, where you need to place tiles... and... this is all what you can...? -![2023_03_12-11_41_50_938](https://user-images.githubusercontent.com/61111955/224536830-3f761bba-2c44-45ac-8648-445c988697bf.png) - -# tasks -✓ add infection block (and it's toggle)
-✓ add tile packs (custom atlas.png + atlas.txt w/o changing game assets)
- -# build -you need .net 7 sdk to build the game -## cli -- download source (download -> download zip) -- unpack folder somewhere -- open cmd in it -- dotnet build + + +### building game + +a game, where you just place tiles + +--- + +![the world](Assets/TheWorld.png) + +### rewriting progress +- [x] the world!!! +- [x] pause +- [x] settings +- [x] main menu +- [x] tile packs +- [x] dynamic tiles +- [x] infection ~~block~~ tile + +its finished!!!!! + +### new features +- bugs +- better camera +- localization +- bad, but more stable than previous, ui framework +- dynamic tile atlas (reloadable ingame) +- world menu but better + + +#### made with 🐛🐛🐛 by danilwhale (rooster) :3 \ No newline at end of file diff --git a/Screens/CreateWorldScreen.cs b/Screens/CreateWorldScreen.cs deleted file mode 100644 index 2c1f86e..0000000 --- a/Screens/CreateWorldScreen.cs +++ /dev/null @@ -1,59 +0,0 @@ -using BuildingGame.GuiElements; - -namespace BuildingGame.Screens; - -public class CreateWorldScreen : Screen -{ - public int worldIndex; - - public override void Draw() - { - ClearBackground(new Color(20, 20, 20, 255)); - // throw new NotImplementedException(); - } - - public override void Initialize() - { - var worldNameBox = new InputBox("worldNameBox", 16, 24) - { - Area = new Rectangle(Program.WIDTH / 2 - 300 / 2, Program.HEIGHT / 3, 300, 24) - }; - worldNameBox.ClientUpdate += () => worldNameBox.Area = new Rectangle(Program.WIDTH / 2 - 300 / 2, Program.HEIGHT / 3, 300, 24); - - var worldNameBoxSubtitle = new TextBlock("worldNameBoxSubtitle", "(max. 16 characters)", - new Vector2(worldNameBox.Area.x, worldNameBox.Area.y + 24), 18); - worldNameBoxSubtitle.ClientUpdate += () => worldNameBoxSubtitle.Area = new Rectangle(worldNameBox.Area.x, worldNameBox.Area.y + 18 + 5, 0, 0); - worldNameBoxSubtitle.Color = Color.WHITE; - - var createWorldButton = new HoverButton("createWorldButton", "create", - new Vector2(0, worldNameBox.Area.y + (24 + 5) * 3), 24); - createWorldButton.CenterScreen(); - createWorldButton.Color = Color.WHITE; - createWorldButton.Clicked += () => - { - File.WriteAllText("saves/" + worldIndex + "/info.txt", worldNameBox.Text); - Program.gameScreen.World.Load("saves/" + worldIndex + "/level.dat"); - Program.currentScreen = Program.gameScreen; - }; - - var backButton = new HoverButton("backButton", "back", - new Vector2(0, worldNameBox.Area.y + (24 + 5) * 4), 24); - backButton.CenterScreen(); - backButton.Color = Color.WHITE; - backButton.Clicked += () => - { - worldNameBox.Text = ""; - Program.currentScreen = Program.worldSelectScreen; - }; - - Gui.PutControl(worldNameBox, this); - Gui.PutControl(worldNameBoxSubtitle, this); - Gui.PutControl(createWorldButton, this); - Gui.PutControl(backButton, this); - } - - public override void Update() - { - // throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/Screens/GameScreen.cs b/Screens/GameScreen.cs deleted file mode 100644 index 0ae03ad..0000000 --- a/Screens/GameScreen.cs +++ /dev/null @@ -1,285 +0,0 @@ -namespace BuildingGame.Screens; - -public class GameScreen : Screen -{ - const float CAMERA_SPEED = 5; - - static byte _currentType = 1; - - public Camera2D camera; - bool _cameraRunning; - - World _world = null!; - public World World => _world; - - // toggles tile that will show when you move mouse over world - public bool showPreTile = true; - - bool flipTile = false; - float tileRot = 0; - - int mx = 0; - int my = 0; - - public override void Draw() - { - BeginMode2D(camera); - ClearBackground(Settings.SkyColor); - - DrawRectangleLinesEx( - new Rectangle(-26, -26, World.CHUNK_AREA * Chunk.SIZE * 48, World.CHUNK_AREA * Chunk.SIZE * 48), - 2f, Color.RED - ); - _world.Draw(); - if (!Gui.IsMouseOverControl && showPreTile) - Tile.GetTile(_currentType).Draw(mx, my, new TileFlags(tileRot, flipTile), new Color(255, 255, 255, 120)); - - EndMode2D(); - } - - public override void Initialize() - { - _world = new World(); - - camera = new Camera2D - { - offset = new Vector2(Program.WIDTH / 2, Program.HEIGHT / 2), - target = new Vector2(GetRandomValue(0, World.CHUNK_AREA * Chunk.SIZE * 48)), - zoom = 1, - rotation = 0 - }; - - CreateGui(); - } - - public override void Update() - { - #region Camera Movement - _cameraRunning = IsKeyDown(KeyboardKey.KEY_LEFT_SHIFT); - - float vx = IsKeyDown(KeyboardKey.KEY_D) - IsKeyDown(KeyboardKey.KEY_A); - float vy = IsKeyDown(KeyboardKey.KEY_S) - IsKeyDown(KeyboardKey.KEY_W); - - camera.target += new Vector2(vx, vy) * (_cameraRunning ? CAMERA_SPEED * 2 : CAMERA_SPEED); - #endregion - - #region Tile Placement - Vector2 wmPos = GetScreenToWorld2D(GetMousePosition(), camera); - - mx = (int)(Math.Round((float)((wmPos.X) / 48))); - my = (int)(Math.Round((float)((wmPos.Y) / 48))); - - if (IsMouseButtonDown(MouseButton.MOUSE_BUTTON_LEFT) && !Gui.IsMouseOverControl) - { - _world.PlaceTile(mx, my, new TileInfo(_currentType, new TileFlags(tileRot, flipTile))); - } - if (IsMouseButtonDown(MouseButton.MOUSE_BUTTON_RIGHT) && !Gui.IsMouseOverControl) - { - _world.PlaceTile(mx, my, 0); - } - #endregion - - #region Tile Flags Manipulation - if (IsKeyPressed(KeyboardKey.KEY_R)) - { - if (tileRot >= 360) tileRot = 0; - tileRot += 90; - } - if (IsKeyPressed(KeyboardKey.KEY_F)) - { - flipTile = !flipTile; - } - #endregion - - #region Tile Menu shortcut - if (IsKeyPressed(KeyboardKey.KEY_B) && !Gui.GetControl("pausePanel").Active) - { - var bgPanel = Gui.GetControl("bgPanel"); - bgPanel.Active = !bgPanel.Active; - } - #endregion - - #region Camera Zoom - camera.zoom += ((float)GetMouseWheelMove() * 0.05f); - - if (camera.zoom > 3.0f) camera.zoom = 3.0f; - else if (camera.zoom < 0.1f) camera.zoom = 0.1f; - #endregion - - _world.Update(); - } - - void CreateGui() - { - var bgPanel = new BackgroundBlock("bgPanel", - new GradientBrush(new Color(0, 0, 25, 100), new Color(0, 0, 0, 200)) - ) - { - Area = new Rectangle(Program.WIDTH / 2 - 340, Program.HEIGHT / 2 - 210, 680, 420), - Active = false, - ZIndex = 40 - }; - bgPanel.Adapt(windowSize => new Rectangle(windowSize.X / 2 - 340, windowSize.Y / 2 - 210, 680, 420)); - bgPanel.ClientUpdate += () => - { - if (IsKeyPressed(KeyboardKey.KEY_ESCAPE)) bgPanel.Active = false; - }; - - var pausePanel = new BackgroundBlock("pausePanel", bgPanel.Background) - { - Area = new Rectangle(0, 0, Program.WIDTH, Program.HEIGHT), - Active = false, - ZIndex = 50 - }; - pausePanel.Adapt(windowSize => new Rectangle(0, 0, windowSize.X, windowSize.Y)); - pausePanel.ClientUpdate += () => - { - if (Gui.GetControl("settingsPanel").Active && IsKeyPressed(KeyboardKey.KEY_ESCAPE)) Gui.GetControl("settingsPanel").Active = false; - else if (IsKeyPressed(KeyboardKey.KEY_ESCAPE) && !bgPanel.Active) pausePanel.Active = !pausePanel.Active; - }; - - var resumeGameButton = new HoverButton("resumeGameButton", "resume", Vector2.Zero, 24) - { - Area = new Rectangle(Program.WIDTH / 2, Program.HEIGHT / 3, 150, 24), - Color = Color.WHITE - }; - resumeGameButton.Clicked += () => - { - pausePanel.Active = false; - Gui.GetControl("settingsPanel").Active = false; - }; - resumeGameButton.CenterScreen(); - pausePanel.Children.Add(resumeGameButton); - - var menuGameButton = new HoverButton("menuGameButton", "menu", Vector2.Zero, 24) - { - Area = new Rectangle(Program.WIDTH / 2, Program.HEIGHT / 3 + 8 + 24, 150, 24), - Color = Color.WHITE - }; - menuGameButton.Clicked += () => - { - pausePanel.Active = false; - Gui.GetControl("settingsPanel").Active = false; - _world.Save(); - Program.currentScreen = Program.menuScreen; - }; - menuGameButton.CenterScreen(); - pausePanel.Children.Add(menuGameButton); - - var settingsGameButton = new HoverButton("settingsGameButton", "settings", Vector2.Zero, 24) - { - Area = new Rectangle(Program.WIDTH / 2, Program.HEIGHT / 3 + 8 + 24 + 56, 150, 24), - Color = Color.WHITE - }; - settingsGameButton.Clicked += () => - { - Gui.GetControl("settingsPanel").Active = !Gui.GetControl("settingsPanel").Active; - }; - settingsGameButton.CenterScreen(); - pausePanel.Children.Add(settingsGameButton); - - var tooltip = new Tooltip("tileTooltip"); - tooltip.Background = new GradientBrush(new Color(0, 0, 0, 50), new Color(16, 0, 16, 127)); - - #region Tile Preview (+ Tile Menu shortcut) - var texImg = new ImageArea("texImg", Program.atlas, new Rectangle(0, 0, 16, 16), new Color(255, 255, 255, 200)) - { - Area = new Rectangle(Program.WIDTH - 64 - 16, 16, 64, 64) - }; - texImg.Adapt(windowSize => new Vector2(windowSize.X - 64 - 16, 16)); - texImg.ClientUpdate += () => - { - var tile = Tile.GetTile(_currentType); - if (tile.IsUnknown) - { - texImg.ImageSourceRect = new Rectangle(0, 0, 48, 48); - texImg.Image = Tile.Unknown; - } - else - { - texImg.ImageSourceRect = new Rectangle(tile.AtlasOffset.X * 16, tile.AtlasOffset.Y * 16, 16, 16); - texImg.Image = Program.atlas; - } - }; - texImg.Clicked += () => - { - if (!pausePanel.Active) - bgPanel.Active = !bgPanel.Active; - }; - #endregion - - #region Tile Menu Generation - - - RecreateTileMenu(bgPanel); - TilePackManager.PackChanged += () => RecreateTileMenu(bgPanel); - #endregion - - Gui.PutControl(bgPanel, this); - Gui.PutControl(tooltip, this); - Gui.PutControl(texImg, this); - Gui.PutControl(pausePanel, this); - } - - private void RecreateTileMenu(BackgroundBlock bgPanel) - { - bgPanel.Children.Clear(); - - var sx = (float width) => width / 2 - 340 + 20; - var sy = (float height) => height / 2 - 210 + 20; - float x = 20; - float y = 20; - - Gui.RemoveControl("tileMenuTitle"); - var tileMenuTitle = new TextBlock("tileMenuTitle", "Select a tile", - Vector2.Zero, 32 - ); - tileMenuTitle.Adapt((windowSize) => new Vector2(bgPanel.Area.x + 250 , windowSize.Y / 2 - 210 + 6)); - tileMenuTitle.Active = true; - tileMenuTitle.Color = Color.WHITE; - tileMenuTitle.CenterScreen(); - bgPanel.Children.Add(tileMenuTitle); - - for (byte i = 0; i < Tile.DefaultTiles.Length; i++) - { - Gui.RemoveControl("tile_" + i); - var tile = Tile.DefaultTiles[i]; - byte idx = (byte)(i + 1); - var btn = new ImageArea("tile_" + i, Program.atlas, - new Rectangle(tile.AtlasOffset.X * 16, tile.AtlasOffset.Y * 16, 16, 16), Color.WHITE - ) - { - Area = new Rectangle(x, y, 48, 48), - ZIndex = 45 - }; - float lx = x; - float ly = y; - btn.Adapt((windowSize) => new Rectangle(sx.Invoke(windowSize.X) + lx, sy.Invoke(windowSize.Y) + ly, 48, 48)); - btn.ClientUpdate += () => - { - btn.Tooltip = Tile.GetTile(idx).DisplayName; - btn.Image = Program.atlas; - }; - - btn.Clicked += () => - { - _currentType = idx; - bgPanel.Active = false; - }; - - // Gui.PutControl(btn, this); - bgPanel.Children.Add(btn); - - if (x > 800 / 2 + 100) - { - x = 20; - y += 48 + 5; - continue; - } - - - - x += 48 + 5; - } - } -} \ No newline at end of file diff --git a/Screens/MenuScreen.cs b/Screens/MenuScreen.cs deleted file mode 100644 index fbf1e53..0000000 --- a/Screens/MenuScreen.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System.Diagnostics; -using System.Reflection; - - -namespace BuildingGame.Screens; - -public class MenuScreen : Screen -{ - private Dictionary _localControls = new Dictionary(); - public override void Draw() - { - ClearBackground(new Color(20, 20, 20, 255)); - } - - BackgroundBlock settingsPanel = null!; - TextBlock bgColorTitle = null!; - RgbBoxLine bgColorLine = null!; - CheckBox physicsCheckBox = null!; - CheckBox enableInfectionCheckBox = null!; - - public override void Initialize() - { - // create gui elements - settingsPanel = new BackgroundBlock("settingsPanel", (ColorBrush)new Color(0, 0, 0, 100)) - { - Area = new Rectangle(50, 50, Program.WIDTH - 50 * 2, Program.HEIGHT - 50 * 2), - Active = false, - ZIndex = 100 - }; - settingsPanel.ClientUpdate += () => - { - settingsPanel.Area = new Rectangle(50, 50, Program.WIDTH - 50 * 2, Program.HEIGHT - 50 * 2); - Settings.SkyColor = bgColorLine.ExportColor(); - Settings.EnablePhysics = physicsCheckBox.Checked; - Settings.EnableInfectionBlock = enableInfectionCheckBox.Checked; - }; - - bgColorTitle = new TextBlock( - "bgColorTitle", "sky color (rgb): ", - new Vector2(settingsPanel.Area.x + 8, settingsPanel.Area.y + 16), - 18 - ); - bgColorTitle.ClientUpdate += () => - { - var point = new Vector2(settingsPanel.Area.x + 8, settingsPanel.Area.y + 16); - bgColorTitle.Area = new Rectangle( - point.X, - point.Y, - bgColorTitle.Area.width, - bgColorTitle.Area.height - ); - }; - bgColorTitle.Color = Color.WHITE; - settingsPanel.Children.Add(bgColorTitle); - - bgColorLine = new RgbBoxLine("bgColorLine", - new Vector2(bgColorTitle.Area.x + bgColorTitle.Area.width + 8, bgColorTitle.Area.y), - Color.SKYBLUE - ); - bgColorLine.ImportColor(Settings.SkyColor); - settingsPanel.Children.Add(bgColorLine); - - physicsCheckBox = new CheckBox("physicsCheckBox", "enable dynamic tiles (can cause fps drops)", - Vector2.Zero, - 18 - ); - physicsCheckBox.Adapt(_ => new Vector2(settingsPanel.Area.x + 8, settingsPanel.Area.y + 16 + 8 + 18)); - physicsCheckBox.Checked = Settings.EnablePhysics; - settingsPanel.Children.Add(physicsCheckBox); - - enableInfectionCheckBox = new CheckBox("enableInfectionCheckBox", "enable infection tiles", - Vector2.Zero, 18); - enableInfectionCheckBox.Adapt(_ => new Vector2(settingsPanel.Area.x + 8, settingsPanel.Area.y + (16 + 8 + 18) * 2)); - enableInfectionCheckBox.Checked = Settings.EnableInfectionBlock; - settingsPanel.Children.Add(enableInfectionCheckBox); - - var title = new TextBlock("title", "building game", Vector2.Zero, 36); - title.Adapt((windowSize) => new Vector2(12, CalculateYForButton(windowSize, 0))); - title.Color = Color.WHITE; - - var playButton = new HoverButton("playButton", "play", - Vector2.Zero, 24); - playButton.Adapt((windowSize) => new Vector2(12, CalculateYForButton(windowSize, 1))); - playButton.Color = Color.WHITE; - playButton.Clicked += () => - { - Program.currentScreen = Program.worldSelectScreen; - settingsPanel.Active = false; - }; - - var settingsButton = new HoverButton("settingsButton", "settings", - Vector2.Zero, 24); - settingsButton.Adapt((windowSize) => new Vector2(12, CalculateYForButton(windowSize, 2))); - settingsButton.Color = Color.WHITE; - settingsButton.Clicked += () => - { - if (!(settingsPanel.Active && CheckCollisionPointRec(GetMousePosition(), settingsPanel.Area))) - { - settingsPanel.Active = !settingsPanel.Active; - } - }; - - var packsButton = new HoverButton("packsButton", "packs", - Vector2.Zero, 24); - packsButton.Adapt((windowSize) => new Vector2(12, CalculateYForButton(windowSize, 3))); - packsButton.Color = Color.WHITE; - packsButton.Clicked += () => Program.currentScreen = Program.selectPackScreen; - - var exitButton = new HoverButton("exitButton", "exit", - Vector2.Zero, 24 - ); - exitButton.Adapt((windowSize) => new Vector2(12, CalculateYForButton(windowSize, 4))); - exitButton.Color = Color.WHITE; - exitButton.Clicked += () => - { - Program.mustClose = true; - }; - - var versionBlock = new TextBlock("versionBlock", $"v{Program.version}", - Vector2.Zero, 18 - ); - versionBlock.Adapt((windowSize) => new Vector2(8, windowSize.Y - 8 - 18)); - versionBlock.Color = Color.WHITE; - - Gui.PutControl(settingsPanel, this, true); - Gui.PutControl(settingsButton, this); - Gui.PutControl(packsButton, this); - Gui.PutControl(playButton, this); - Gui.PutControl(exitButton, this); - Gui.PutControl(title, this); - Gui.PutControl(versionBlock, this); - - } - - public override void Update() - { - - } - - private float CalculateYForButton(Vector2 windowSize, int buttonNumber) - { - return windowSize.Y / 3 + 36 + (20 + 24 / 2) * buttonNumber; - } -} \ No newline at end of file diff --git a/Screens/Screen.cs b/Screens/Screen.cs deleted file mode 100644 index 74baf54..0000000 --- a/Screens/Screen.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace BuildingGame.Screens; - -public abstract class Screen -{ - public abstract void Initialize(); - public abstract void Update(); - public abstract void Draw(); -} \ No newline at end of file diff --git a/Screens/SelectPackScreen.cs b/Screens/SelectPackScreen.cs deleted file mode 100644 index 38eae2d..0000000 --- a/Screens/SelectPackScreen.cs +++ /dev/null @@ -1,78 +0,0 @@ -using BuildingGame.GuiElements; -using BuildingGame.TilePacks; -using BuildingGame.Tiles; - -namespace BuildingGame.Screens; - -#nullable disable -public class SelectPackScreen : Screen -{ - private ListView _packListView; - private HoverButton _backButton; - private TextBlock _currentPackLabel; - - public override void Draw() - { - ClearBackground(new Color(20, 20, 20, 255)); - } - - public override void Initialize() - { - var bg = new BackgroundBlock("spsBg", (ColorBrush)new Color(20, 20, 20, 255)); - bg.Adapt((windowSize) => new Rectangle(0, windowSize.Y - 115, windowSize.X, 115)); - bg.ZIndex = 2; - _currentPackLabel = new TextBlock("currentPackLabel", "Current pack: default", Vector2.Zero, 24); - _currentPackLabel.Color = Color.WHITE; - _currentPackLabel.ZIndex = 3; - _currentPackLabel.Adapt((windowSize) => - new Vector2( - windowSize.X / 2 - MeasureTextEx(_currentPackLabel.Font, _currentPackLabel.Text, 24, 1).X / 2, - windowSize.Y - 32 - ) - ); - _currentPackLabel.ClientUpdate += () => _currentPackLabel.Text = "Current pack: " + Settings.CurrentPack; - - _packListView = new ListView("packListView"); - _packListView.PutItem("default"); - _packListView.ItemClicked += (item) => - { - item = item.Replace(" (custom map)", null); - if (item == "default") - { - TilePackManager.SetDefaultPack(); - } - else - { - TilePackManager.ApplyPack(item); - } - - Settings.CurrentPack = item; - }; - - foreach (var pack in TilePackManager.TilePacks) - { - _packListView.PutItem(pack.Name + (!pack.IsVanilla ? " (custom map)" : "")); - } - - _backButton = new HoverButton("spsBackButton", "back", Vector2.Zero, 24); - _backButton.ZIndex = 3; - _backButton.Color = Color.WHITE; - _backButton.CenterScreen(); - _backButton.Adapt(windowSize => new Vector2(_backButton.Area.x, windowSize.Y - 100)); - - _backButton.Clicked += () => - { - Program.currentScreen = Program.menuScreen; - }; - - Gui.PutControl(_packListView, this); - Gui.PutControl(bg, this); - Gui.PutControl(_backButton, this); - Gui.PutControl(_currentPackLabel, this); - } - - public override void Update() - { - - } -} \ No newline at end of file diff --git a/Screens/WorldSelectScreen.cs b/Screens/WorldSelectScreen.cs deleted file mode 100644 index 2396518..0000000 --- a/Screens/WorldSelectScreen.cs +++ /dev/null @@ -1,91 +0,0 @@ -using BuildingGame.GuiElements; - -namespace BuildingGame.Screens; - -public class WorldSelectScreen : Screen -{ - List _worldButtons = new List(); - - public override void Draw() - { - ClearBackground(new Color(20, 20, 20, 255)); - } - - public override void Initialize() - { - int y = 80; - for (int i = 0; i < 10; i++) - { - var idx = i; - var worldButton = new HoverButton("world_" + i, FetchWorldName(idx), new Vector2(0, y), 32 - ); - worldButton.Clicked += () => LoadWorld(idx); - worldButton.Color = Color.WHITE; - worldButton.CenterScreen(); - - Gui.PutControl(worldButton, this); - y += 37; - } - - var menuButton = new HoverButton("menuSelectButton", "menu", new Vector2(0, y + 60), 32); - menuButton.Clicked += () => - { - Program.currentScreen = Program.menuScreen; - }; - menuButton.Color = Color.WHITE; - menuButton.CenterScreen(); - - Gui.PutControl(menuButton, this); - } - - private string FetchWorldName(int i) - { - if (IsExistsWorld(i)) - { - if (File.Exists("saves/" + i + "/info.txt")) - { - var lines = File.ReadAllLines("saves/" + i + "/info.txt"); - if (lines.Length < 1) return "world #" + (i + 1); - return lines.First().Replace("\n", ""); - } - - else - return "world #" + (i + 1); - } - return "world #" + (i + 1) + " (new)"; - } - - private void LoadWorld(int i) - { - if (((HoverButton)Gui.GetControl("world_" + i)).Text!.EndsWith("(new)")) - { - Program.createWorldScreen.worldIndex = i; - Program.currentScreen = Program.createWorldScreen; - } - else - { - Program.gameScreen.World.Load("saves/" + i + "/level.dat"); - Program.currentScreen = Program.gameScreen; - } - - } - - private bool IsExistsWorld(int i) - { - if (!Directory.Exists("saves/" + i)) - { - Directory.CreateDirectory("saves/" + i); - } - - return File.Exists("saves/" + i + "/level.dat"); - } - - public override void Update() - { - for (int i = 0; i < 10; i++) - { - int idx = i; - ((HoverButton)Gui.GetControl("world_" + i)).Text = FetchWorldName(idx); - } - } -} \ No newline at end of file diff --git a/Screenshot.cs b/Screenshot.cs deleted file mode 100644 index 70a2fa9..0000000 --- a/Screenshot.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace BuildingGame; - -public static class Screenshot -{ - public static readonly string ScreenshotRoot = "screenshots/"; - public static void Create() - { - // create root for screenshots - if (!Directory.Exists(ScreenshotRoot)) Directory.CreateDirectory(ScreenshotRoot); - - // take screenshot - TakeScreenshot(ScreenshotRoot + DateTime.Now.ToString("yyyy_MM_dd-HH_mm_ss_fff") + ".png"); - } -} \ No newline at end of file diff --git a/Settings.cs b/Settings.cs deleted file mode 100644 index f751747..0000000 --- a/Settings.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Serialization.Metadata; - -namespace BuildingGame; - -public class Settings -{ - public static Color SkyColor = Color.SKYBLUE; - public static bool EnablePhysics = false; - public static bool EnableInfectionBlock = false; - public static string CurrentPack = "default"; - - public static void Save() - { - try - { - ConfigParser.Write("settings.txt", new Dictionary() - { - { "skyColor", ToStringColor(SkyColor) }, - { "enablePhysics", EnablePhysics.ToString() }, - { "enableInfectionBlock", EnableInfectionBlock.ToString() }, - { "currentTilePack", CurrentPack } - }); - } - catch (Exception ex) { Log.Information(ex.ToString()); } - } - public static void Load() - { - try - { - var data = ConfigParser.Parse("settings.txt"); - - if (data.TryGetValue("skyColor", out var skyColorStr) && - TryParseColor(skyColorStr, out var skyColor)) - { - SkyColor = skyColor; - } - - if (data.TryGetValue("enablePhysics", out var enablePhysicsStr) && - bool.TryParse(enablePhysicsStr, out bool enablePhysics)) - EnablePhysics = enablePhysics; - if (data.TryGetValue("enableInfectionBlock", out var enableInfectionBlockStr) && - bool.TryParse(enableInfectionBlockStr, out bool enableInfectionBlock)) - EnableInfectionBlock = enableInfectionBlock; - if (data.TryGetValue("currentTilePack", out var currentPack)) - CurrentPack = currentPack; - } - catch (Exception ex) { Log.Information(ex.ToString()); } - } - - private static bool TryParseColor(string str, out Color color) - { - var split = str.Split(','); - color = Color.WHITE; - - if (split.Length < 3) return false; - - if (byte.TryParse(split[0].Trim(), out var r) && - byte.TryParse(split[1].Trim(), out var g) && - byte.TryParse(split[2].Trim(), out var b)) - { - color = new Color(r, g, b, (byte)255); - return true; - } - - return false; - } - - private static string ToStringColor(Color color) - { - return $"{color.r},{color.g},{color.b}"; - } -} \ No newline at end of file diff --git a/assets/atlas.png b/Sources/Assets/Atlas.png similarity index 100% rename from assets/atlas.png rename to Sources/Assets/Atlas.png diff --git a/Sources/Assets/Atlas.yaml b/Sources/Assets/Atlas.yaml new file mode 100644 index 0000000..80442b5 --- /dev/null +++ b/Sources/Assets/Atlas.yaml @@ -0,0 +1,212 @@ +# layer 0 (x: 0-9, y: 0) +smooth_stone: + atlas: + x: 0 + y: 0 + +wooden_planks: + atlas: + x: 1 + y: 0 + +dirt: + atlas: + x: 2 + y: 0 + +grass_block: + atlas: + x: 3 + y: 0 + +grass: + atlas: + x: 4 + y: 0 + +sand: + atlas: + x: 5 + y: 0 + +water: + atlas: + x: 6 + y: 0 + +lava: + atlas: + x: 7 + y: 0 + +obsidian: + atlas: + x: 8 + y: 0 + +door: + atlas: + x: 9 + y: 0 + size: + x: 1 + y: 2 + +# layer 1 (x: 0-8, y: 1) +nature_stone: + atlas: + x: 0 + y: 1 + +white_plate: + atlas: + x: 1 + y: 1 + +green_plate: + atlas: + x: 2 + y: 1 + +red_plate: + atlas: + x: 3 + y: 1 + +blue_plate: + atlas: + x: 4 + y: 1 + +chamomile: + atlas: + x: 5 + y: 1 + +red_tulip: + atlas: + x: 6 + y: 1 + +chamomile_pot: + atlas: + x: 7 + y: 1 + +red_tulip_pot: + atlas: + x: 8 + y: 1 + +# layer 2 (x: 0-9, y: 2) +wooden_stairs: + atlas: + x: 0 + y: 2 + +wooden_slab: + atlas: + x: 1 + y: 2 + +stone_stairs: + atlas: + x: 2 + y: 2 + +stone_slab: + atlas: + x: 3 + y: 2 + +wooden_pole: + atlas: + x: 4 + y: 2 + +wooden_pole_handle: + atlas: + x: 5 + y: 2 + +stone_pole: + atlas: + x: 6 + y: 2 + +stone_pole_handle: + atlas: + x: 7 + y: 2 + +log: + atlas: + x: 8 + y: 2 + +log_top: + atlas: + x: 9 + y: 2 + +# layer 3 (x: 0-9, y: 3) +foliage: + atlas: + x: 0 + y: 3 + +glass: + atlas: + x: 1 + y: 3 + +white_wool: + atlas: + x: 2 + y: 3 + +red_wool: + atlas: + x: 3 + y: 3 + +green_wool: + atlas: + x: 4 + y: 3 + +blue_wool: + atlas: + x: 5 + y: 3 + +yellow_wool: + atlas: + x: 6 + y: 3 + +black_wool: + atlas: + x: 7 + y: 3 + +dark_gray_wool: + atlas: + x: 8 + y: 3 + +gray_wool: + atlas: + x: 9 + y: 3 + +# layer 4 (x: 0-1, y: 4) +sponge: + atlas: + x: 0 + y: 4 + +infection_block: + atlas: + x: 1 + y: 4 \ No newline at end of file diff --git a/assets/check.png b/Sources/Assets/Checkmark.png similarity index 100% rename from assets/check.png rename to Sources/Assets/Checkmark.png diff --git a/assets/font.ttf b/Sources/Assets/Font.ttf similarity index 100% rename from assets/font.ttf rename to Sources/Assets/Font.ttf diff --git a/assets/icon.ico b/Sources/Assets/Icon.ico similarity index 100% rename from assets/icon.ico rename to Sources/Assets/Icon.ico diff --git a/assets/icon.png b/Sources/Assets/Icon.png similarity index 100% rename from assets/icon.png rename to Sources/Assets/Icon.png diff --git a/Sources/Assets/Translation.yaml b/Sources/Assets/Translation.yaml new file mode 100644 index 0000000..fb2827d --- /dev/null +++ b/Sources/Assets/Translation.yaml @@ -0,0 +1,75 @@ +# layer 0 (x: 0-9, y: 0) +smooth_stone: "Smooth Stone" +wooden_planks: "Wooden Planks" +dirt: "Dirt" +grass_block: "Grass Block" +grass: "Grass" +sand: "Sand" +water: "Water" +lava: "Lava" +obsidian: "Obsidian" +door: "Door" + +# layer 1 (x: 0-8, y: 1) +nature_stone: "Nature Stone" +white_plate: "White Plate" +green_plate: "Green Plate" +red_plate: "Red Plate" +blue_plate: "Blue Plate" +chamomile: "Chamomile" +red_tulip: "Red Tulip" +chamomile_pot: "Chamomile in a Pot" +red_tulip_pot: "Red Tulip in a Pot" + +# layer 2 (x: 0-9, y: 2) +wooden_stairs: "Wooden Stairs" +wooden_slab: "Wooden Slab" +stone_stairs: "Stone Stairs" +stone_slab: "Stone Slab" +wooden_pole: "Wooden Pole" +wooden_pole_handle: "Wooden Pole Handle" +stone_pole: "Stone Pole" +stone_pole_handle: "Stone Pole Handle" +log: "Log" +log_top: "Log Top" + +# layer 3 (x: 0-9, y: 3) +foliage: "Foliage" +glass: "Glass" +white_wool: "White Wool" +red_wool: "Red Wool" +green_wool: "Green Wool" +blue_wool: "Blue Wool" +yellow_wool: "Yellow Wool" +black_wool: "Black Wool" +dark_gray_wool: "Dark Gray Wool" +gray_wool: "Gray Wool" + +# layer 4 (x: 0-1, y: 4) +sponge: "Sponge" +infection_block: "Infection Block" + +# UI +title: "building game rewritten" +version_format: "v{0}.{1}.{2}" # {0} - major, {1} - minor, {2} - revision + +play_button: "play" +settings_button: "settings" +packs_button: "packs" +exit_button: "exit" + +sky_color_line: "sky color (RGB): " +enable_dynamic_tiles: "enable dynamic tiles" +enable_infection_tile: "enable infection tile" + +resume_button: "resume" +menu_button: "menu" + +block_ui_title: "Select a tile" + +reset_button: "reset" + +default_world_title: "select world pls" +play_time_format: "played {0}" # {0} - TimeSpan +create_world_button: "create" +delete_world_button: "delete" \ No newline at end of file diff --git a/BuildingGame.csproj b/Sources/BuildingGame.csproj similarity index 66% rename from BuildingGame.csproj rename to Sources/BuildingGame.csproj index 7f1e567..63d28d2 100644 --- a/BuildingGame.csproj +++ b/Sources/BuildingGame.csproj @@ -6,6 +6,7 @@ enable enable assets/icon.ico + true @@ -26,10 +27,11 @@ - - - + + + + @@ -38,4 +40,14 @@ + + <_DeploymentManifestIconFile Remove="assets\icon.ico" /> + + + + + Always + + + diff --git a/Sources/Player.cs b/Sources/Player.cs new file mode 100644 index 0000000..2bbe8df --- /dev/null +++ b/Sources/Player.cs @@ -0,0 +1,145 @@ +using System.Numerics; +using BuildingGame.Tiles; +using BuildingGame.Tiles.Data; +using BuildingGame.UI; + +namespace BuildingGame; + +public class Player +{ + public static TileInfo CurrentTile = new TileInfo(1, TileFlags.Default); + + public Camera2D Camera; + public World World; + public float Speed; + public float LerpSpeed; + + private Vector2 _targetPosition; + private float _targetZoom; + private int _tileX; + private int _tileY; + + public Player(World world, Vector2 position, float zoom, float speed, float lerpSpeed) + { + World = world; + Camera = new Camera2D + { + Offset = new Vector2(GetScreenWidth(), GetScreenHeight()) / 2, + Target = position, + Zoom = zoom + }; + _targetPosition = position; + _targetZoom = zoom; + Speed = speed; + LerpSpeed = lerpSpeed; + } + + public void Update() + { + Camera.Target = Vector2.Lerp(Camera.Target, _targetPosition, LerpSpeed); // lerp camera position to target position + Camera.Zoom = (Camera.Zoom * (1.0f - LerpSpeed)) + (_targetZoom * LerpSpeed); // lerp camera zoom to target zoom + + // zoom in/zoom out camera + float scrollDelta = GetMouseWheelMove() * (GuiManager.IsMouseOverElement() ? 0 : 1); + if (scrollDelta < 0) ZoomOut(Speed * 0.075f); + if (scrollDelta > 0) ZoomIn(Speed * 0.075f); + + // get speed depending if user is sprinting or not + float speed = IsKeyDown(KeyboardKey.LeftControl) || IsKeyDown(KeyboardKey.LeftShift) + ? Speed * 1.25f + : Speed * 0.5f; + speed /= Camera.Zoom; + speed = Math.Clamp(speed, 0, Speed * 2); + + // move camera + if (IsKeyDown(KeyboardKey.W) && !GuiManager.IsFocused) Move(0, -speed); + if (IsKeyDown(KeyboardKey.S) && !GuiManager.IsFocused) Move(0, speed); + if (IsKeyDown(KeyboardKey.A) && !GuiManager.IsFocused) Move(-speed, 0); + if (IsKeyDown(KeyboardKey.D) && !GuiManager.IsFocused) Move(speed, 0); + + // adapt camera center when windows is resized + if (IsWindowResized()) + { + Camera.Offset = new Vector2(GetScreenWidth(), GetScreenHeight()) / 2; + } + + UpdateTileControls(); + } + + public void Draw() + { + if (!GuiManager.IsMouseOverElement() && Tiles.Tiles.TryGetTile(CurrentTile.Id, out var tile)) + { + tile.DrawPreview( + World, CurrentTile, + _tileX, _tileY + ); + } + } + + private void UpdateTileControls() + { + Vector2 worldMousePos = GetScreenToWorld2D(GetMousePosition(), Camera) + new Vector2(Tile.RealTileSize / 2); + + _tileX = (int)(worldMousePos.X / Tile.RealTileSize); + _tileY = (int)(worldMousePos.Y / Tile.RealTileSize); + + if (IsMouseButtonDown(MouseButton.Left) && !GuiManager.IsMouseOverElement()) + { + World[_tileX, _tileY] = CurrentTile; + } + + if (IsMouseButtonDown(MouseButton.Right) && !GuiManager.IsMouseOverElement()) + { + World[_tileX, _tileY] = 0; + } + + if (IsKeyReleased(KeyboardKey.R)) + { + CurrentTile.Flags.Rotation = (TileRotation)(CurrentTile.Flags.Rotation + 1); + if ((int)CurrentTile.Flags.Rotation > (int)TileRotation.Right) + CurrentTile.Flags.Rotation = TileRotation.Up; + } + + if (IsKeyReleased(KeyboardKey.T)) + { + CurrentTile.Flags.FlipRotation(); + } + } + + public void ZoomIn(float a) + { + _targetZoom += a * GetFrameTime(); + } + + public void ZoomOut(float a) + { + _targetZoom -= a * GetFrameTime(); + // clamp zoom, so camera wont get inverted + if (_targetZoom < 0.01f) _targetZoom = 0.01f; + } + + public void MoveTo(float x, float y) + { + _targetPosition = new Vector2(x, y); + } + + public void Move(float x, float y) + { + _targetPosition += new Vector2(x, y) * Speed * GetFrameTime(); + } + + public void PushCameraInfo() + { + World.PlayerPosition = Camera.Target; + World.PlayerZoom = Camera.Zoom; + } + + public Rectangle GetViewRectangle() + { + Vector2 min = GetScreenToWorld2D(new Vector2(0, 0), Camera); + Vector2 max = GetScreenToWorld2D(new Vector2(GetScreenWidth(), GetScreenHeight()), Camera); + + return new Rectangle(min.X, min.Y, max.X - min.X, max.Y - min.Y); + } +} \ No newline at end of file diff --git a/Sources/Program.cs b/Sources/Program.cs new file mode 100644 index 0000000..2eafc1b --- /dev/null +++ b/Sources/Program.cs @@ -0,0 +1,107 @@ +global using static Raylib_cs.Raylib; +global using static BuildingGame.Raylib; +global using Raylib_cs; +using System.Numerics; +using System.Runtime.CompilerServices; +using BuildingGame; +using BuildingGame.Tiles; +using BuildingGame.Tiles.Dynamic; +using BuildingGame.Tiles.IO; +using BuildingGame.Tiles.Packs; +using BuildingGame.UI; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Interfaces; +using BuildingGame.UI.Screens; + +// remove this when raylib-cs will have rl 5.0 functions +[assembly: DisableRuntimeMarshalling] + +internal class Program +{ + public static bool Running = true; + public static bool Paused = false; + + public static void Main(string[] args) + { + SetConfigFlags(ConfigFlags.VSyncHint); + SetConfigFlags(ConfigFlags.ResizableWindow); + InitWindow(1024, 768, "building game"); + SetExitKey(KeyboardKey.Null); + + Initialize(); + + + while (Running && !WindowShouldClose()) + { + Update(); + Draw(); + } + + Closing(); + CloseWindow(); + } + + public static void LoadWindowIcon() + { + var icon = Resources.GetImage("Icon.png"); + SetWindowIcon(icon); + } + + private static void Initialize() + { + Settings.Load(); + + TilePackManager.Load(); + + var pack = TilePackManager.Find(Settings.CurrentTilePack); + if (string.IsNullOrWhiteSpace(pack.Path)) + { + pack = TilePackManager.Find("Default"); + } + + TilePackManager.Apply(pack); + + BGWorld21Format.Register(); + BGWorld2Format.Register(); + LvlFormat.Register(); + + WorldIO.AddDeserializerAsBackupable(new BGWorld2Format.Deserializer()); + WorldIO.AddDeserializerAsBackupable(new LvlFormat.Deserializer()); + + Tiles.RegisterCustomTile("sand", new SandTile()); + Tiles.RegisterCustomTile("water", new WaterTile()); + Tiles.RegisterCustomTile("lava", new LavaTile()); + Tiles.RegisterCustomTile("infection_block", new InfectionTile()); + + ScreenManager.CurrentScreen = new MenuScreen(); + + ScreenManager.Initialize(); + UIInterfaceManager.Initialize(); + } + + private static void Update() + { + ScreenManager.Update(); + UIInterfaceManager.Update(); + } + + private static void Draw() + { + BeginDrawing(); + + ScreenManager.Draw(); + + EndDrawing(); + } + + private static void Closing() + { + ScreenManager.Free(); + UIInterfaceManager.Destroy(); + + Settings.Save(); + + Resources.Free(); + } +} \ No newline at end of file diff --git a/Sources/Raylib.ExtraMethods.cs b/Sources/Raylib.ExtraMethods.cs new file mode 100644 index 0000000..9dc7ff4 --- /dev/null +++ b/Sources/Raylib.ExtraMethods.cs @@ -0,0 +1,34 @@ +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BuildingGame; + + +/// +/// contains some extra methods to simplify transition from raylib-csharp-vinculum +/// +public static partial class Raylib +{ + // raylib-cs has rayib 4.5 dll lmao + // [LibraryImport(NativeLibName, EntryPoint = "IsKeyPressedRepeat")] + // public static partial CBool IsKeyPressedRepeat(KeyboardKey key); + + // https://github.com/raysan5/raylib/blob/d2b1256e5c3567484486ad70cc2bb69495abfbf4/src/rtext.c#L1108 + public static void DrawText(string text, float posX, float posY, float fontSize, Color color) + { + if (GetFontDefault().Texture.Id == 0) return; + + var pos = new Vector2(posX, posY); + var spacing = GetSpacing(fontSize); + + DrawTextEx(GetFontDefault(), text, pos, fontSize, spacing, color); + } + + public static float GetSpacing(float fontSize) + { + var defaultFontSize = 10.0f; + if (fontSize < defaultFontSize) fontSize = defaultFontSize; + return fontSize / defaultFontSize; + } +} \ No newline at end of file diff --git a/Sources/Resources.cs b/Sources/Resources.cs new file mode 100644 index 0000000..52db6e4 --- /dev/null +++ b/Sources/Resources.cs @@ -0,0 +1,71 @@ +namespace BuildingGame; + +public static class Resources +{ + public static string ResourcesPath = FallbackResourcesPath; + public const string FallbackResourcesPath = "Assets/"; + + private static Dictionary _textures = new Dictionary(); + private static Dictionary _images = new Dictionary(); + + public static Texture2D GetTexture(string key) + { + key = key.ToLower(); + if (_textures.TryGetValue(key, out var texture)) return texture; + + texture = LoadTexture(GetPath(key)); + _textures[key] = texture; + return texture; + } + + public static Image GetImage(string key) + { + key = key.ToLower(); + if (_images.TryGetValue(key, out var image)) return image; + + image = LoadImage(GetPath(key)); + _images[key] = image; + return image; + } + + public static string GetText(string key) + { + return File.ReadAllText(GetPath(key)); + } + + public static string GetPath(string key) + { + var root = !File.Exists(Path.Join(ResourcesPath, key)) ? FallbackResourcesPath : ResourcesPath; + return Path.Join(root, key); + } + + public static void Reload(string newResourcesPath) + { + ResourcesPath = newResourcesPath; + + foreach (var texture in _textures) + { + _textures[texture.Key] = LoadTexture(GetPath(texture.Key)); + } + + foreach (var image in _images) + { + _images[image.Key] = LoadImage(GetPath(image.Key)); + } + } + + public static void Free() + { + foreach (var texture in _textures) + { + UnloadTexture(texture.Value); + } + _textures.Clear(); + + foreach (var image in _images) + { + UnloadImage(image.Value); + } + _images.Clear(); + } +} \ No newline at end of file diff --git a/Sources/Settings.cs b/Sources/Settings.cs new file mode 100644 index 0000000..b45e350 --- /dev/null +++ b/Sources/Settings.cs @@ -0,0 +1,61 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace BuildingGame; + +public static class Settings +{ + public record struct Record(Color SkyColor, bool EnableDynamicTiles, bool EnableInfectionTile, string CurrentTilePack); + + public const string SettingsFile = "Settings.yaml"; + + private static readonly IDeserializer _deserializer = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + + private static readonly ISerializer _serializer = new SerializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + + public static Color SkyColor = Color.SkyBlue; + public static bool EnableDynamicTiles = true; + public static bool EnableInfectionTile = true; + public static string CurrentTilePack = "Default"; + + public static void Load() + { + try + { + var content = File.ReadAllText(SettingsFile); + var record = _deserializer.Deserialize(content); + + SkyColor = record.SkyColor; + EnableDynamicTiles = record.EnableDynamicTiles; + EnableInfectionTile = record.EnableInfectionTile; + CurrentTilePack = record.CurrentTilePack; + } + catch (FileNotFoundException) + { + Console.WriteLine("no " + SettingsFile); + } + catch (Exception ex) + { + Console.WriteLine("whoops: " + ex.ToString()); + } + } + + public static void Save() + { + try + { + var record = new Record(SkyColor, EnableDynamicTiles, EnableInfectionTile, CurrentTilePack); + + var content = _serializer.Serialize(record); + File.WriteAllText(SettingsFile, content); + } + catch (Exception ex) + { + Console.WriteLine("whoops: " + ex); + } + } +} \ No newline at end of file diff --git a/Sources/TilePacks/Default/Pack.yaml b/Sources/TilePacks/Default/Pack.yaml new file mode 100644 index 0000000..5b8f4bf --- /dev/null +++ b/Sources/TilePacks/Default/Pack.yaml @@ -0,0 +1,2 @@ +name: "Default" +assets_path: "../../Assets" \ No newline at end of file diff --git a/Sources/Tiles/Atlas/AtlasLoader.cs b/Sources/Tiles/Atlas/AtlasLoader.cs new file mode 100644 index 0000000..2a83a42 --- /dev/null +++ b/Sources/Tiles/Atlas/AtlasLoader.cs @@ -0,0 +1,39 @@ +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace BuildingGame.Tiles.Atlas; + +public static class AtlasLoader +{ + public const string AtlasFile = "Assets/Atlas.yaml"; + + private static readonly IDeserializer _yaml = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + public static Dictionary LoadTiles() + { + if (!File.Exists(AtlasFile)) + throw new InvalidOperationException("Can't load Atlas.json because it doesn't exists"); + + string yamlText = File.ReadAllText(AtlasFile); + + return _yaml.Deserialize>(yamlText) ?? + throw new InvalidOperationException("Atlas.yaml is empty");; + } + + public static Dictionary ConvertTiles(Dictionary tiles) + { + byte i = 1; + Dictionary flatTiles = new Dictionary(); + + foreach (var kv in tiles) + { + flatTiles.Add(new AtlasTileKey(kv.Key, i), new Tile(kv.Value.Atlas, kv.Value.Size, kv.Key)); + i++; + } + + return flatTiles; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Atlas/AtlasTile.cs b/Sources/Tiles/Atlas/AtlasTile.cs new file mode 100644 index 0000000..2dff88b --- /dev/null +++ b/Sources/Tiles/Atlas/AtlasTile.cs @@ -0,0 +1,16 @@ +using System.ComponentModel; + +namespace BuildingGame.Tiles.Atlas; + +public struct AtlasTile +{ + public AtlasTile() + { + Atlas = default; + } + + public AtlasVec2 Atlas { get; set; } + public AtlasVec2 Size { get; set; } = new AtlasVec2(1, 1); + + +} \ No newline at end of file diff --git a/Sources/Tiles/Atlas/AtlasTileKey.cs b/Sources/Tiles/Atlas/AtlasTileKey.cs new file mode 100644 index 0000000..63f88a2 --- /dev/null +++ b/Sources/Tiles/Atlas/AtlasTileKey.cs @@ -0,0 +1,13 @@ +namespace BuildingGame.Tiles.Atlas; + +public struct AtlasTileKey +{ + public string Name; + public byte Id; + + public AtlasTileKey(string name, byte id) + { + Name = name; + Id = id; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Atlas/AtlasVec2.cs b/Sources/Tiles/Atlas/AtlasVec2.cs new file mode 100644 index 0000000..55808d2 --- /dev/null +++ b/Sources/Tiles/Atlas/AtlasVec2.cs @@ -0,0 +1,17 @@ +using System.Numerics; + +namespace BuildingGame.Tiles.Atlas; + +public struct AtlasVec2 +{ + public AtlasVec2(float x, float y) + { + X = x; + Y = y; + } + + public float X { get; set; } + public float Y { get; set; } + + public static implicit operator Vector2(AtlasVec2 vec) => new Vector2(vec.X, vec.Y); +} \ No newline at end of file diff --git a/Sources/Tiles/Chunk.cs b/Sources/Tiles/Chunk.cs new file mode 100644 index 0000000..2c96de3 --- /dev/null +++ b/Sources/Tiles/Chunk.cs @@ -0,0 +1,123 @@ +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles; + +public struct Chunk +{ + public const int Size = 16; + public const float ViewSize = Size * Tile.RealTileSize; + + public World World; + public readonly int X, Y; + + private TileInfo[][] _tiles; + + public Chunk(World world, int x, int y) + { + World = world; + X = x; + Y = y; + + _tiles = new TileInfo[Size][]; + for (int i = 0; i < Size; i++) + { + _tiles[i] = new TileInfo[Size]; + for (int j = 0; j < Size; j++) + { + _tiles[i][j] = 0; + } + } + } + + public void Update() + { + + + for (int x = Size - 1; x >= 0; x--) + { + for (int y = Size - 1; y >= 0; y--) + { + TileInfo info = _tiles[x][y]; + if (info == 0) continue; + if (!Tiles.TryGetTile(info, out var tile)) continue; + + tile.OnUpdate(World, info, X * Size + x, Y * Size + y); + } + } + + for (var i = 0; i < Size * Size; i++) + { + var x = Random.Shared.Next(0, Size); + var y = Random.Shared.Next(0, Size); + + TileInfo info = _tiles[x][y]; + if (info == 0) continue; + if (!Tiles.TryGetTile(info, out var tile)) continue; + + tile.OnRandomUpdate(World, info, x, y); + } + } + + public void Draw() + { + for (int x = 0; x < Size; x++) + { + for (int y = 0; y < Size; y++) + { + TileInfo info = _tiles[x][y]; + if (info== 0) continue; + if (!Tiles.TryGetTile(info, out var tile)) continue; + + tile.Draw(World, info, X * Size + x, Y * Size + y, Color.White); + } + } + } + + public TileInfo this[int x, int y] + { + get + { + if (x < 0 || x >= Size || y < 0 || y >= Size) + return 0; + + return _tiles[x][y]; + } + set + { + if (x < 0 || x >= Size || y < 0 || y >= Size) + return; + + var oldTileInfo = _tiles[x][y]; + + _tiles[x][y] = value; + + if (!Tiles.TryGetTile(oldTileInfo, out var oldTile)) return; + if (!Tiles.TryGetTile(value, out var tile)) return; + + oldTile.OnInfoUpdate(World, oldTileInfo, value, x, y); + tile.OnPlace(World, value, x, y); + + NotifyTileInfoUpdate(value, oldTileInfo, x, y, x - 1, y); + NotifyTileInfoUpdate(value, oldTileInfo, x, y, x + 1, y); + NotifyTileInfoUpdate(value, oldTileInfo, x, y, x, y - 1); + NotifyTileInfoUpdate(value, oldTileInfo, x, y, x, y + 1); + } + } + + private void NotifyTileInfoUpdate(TileInfo newInfo, TileInfo oldInfo, int x, int y, int nx, int ny) + { + var tileInfo = World[Size * X + nx, Size * Y + ny]; + + if (!Tiles.TryGetTile(tileInfo, out var tile)) return; + + tile.OnNeighbourInfoUpdate( + World, + oldInfo, + newInfo, + Size * X + nx, + Size * Y + ny, + Size * X + x, + Size * Y + y + ); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Data/ChunkPosition.cs b/Sources/Tiles/Data/ChunkPosition.cs new file mode 100644 index 0000000..6d24ba4 --- /dev/null +++ b/Sources/Tiles/Data/ChunkPosition.cs @@ -0,0 +1,17 @@ +namespace BuildingGame.Tiles.Data; + +public record ChunkPosition(int X, int Y) +{ + /// + /// Calculates local to chunk position from world position + /// + /// World X + /// World Y + /// Output tile X position + /// Output tile Y position + public void WorldToTile(int x, int y, out int tx, out int ty) + { + tx = x - X * Chunk.Size; + ty = y - Y * Chunk.Size; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Data/TileData.cs b/Sources/Tiles/Data/TileData.cs new file mode 100644 index 0000000..c7204cd --- /dev/null +++ b/Sources/Tiles/Data/TileData.cs @@ -0,0 +1,9 @@ +using BuildingGame.Tiles.Dynamic; + +namespace BuildingGame.Tiles.Data; + +public class TileData +{ + public int CurrentTick = 0; + public float TickTimer = 1.0f / Tile.TickCount; +} \ No newline at end of file diff --git a/Sources/Tiles/Data/TileFlags.cs b/Sources/Tiles/Data/TileFlags.cs new file mode 100644 index 0000000..530df84 --- /dev/null +++ b/Sources/Tiles/Data/TileFlags.cs @@ -0,0 +1,49 @@ +namespace BuildingGame.Tiles.Data; + +public struct TileFlags +{ + public static TileFlags Default => new TileFlags(TileRotation.Up); + + public TileRotation Rotation; + + public TileFlags(TileRotation rotation) + { + Rotation = rotation; + } + + public TileFlags(float rotation) + { + Rotation = FloatAsRotation(rotation); + } + + public float RotationAsFloat() + { + return Rotation switch + { + TileRotation.Up => 0, + TileRotation.Left => 90, + TileRotation.Down => 180, + TileRotation.Right => 270 + }; + } + + public void FlipRotation() + { + Rotation = Rotation switch + { + TileRotation.Up => TileRotation.Down, + TileRotation.Down => TileRotation.Up, + TileRotation.Left => TileRotation.Right, + TileRotation.Right => TileRotation.Left + }; + } + + private static TileRotation FloatAsRotation(float rotation) + { + if (rotation is > 0 and <= 90) return TileRotation.Left; + if (rotation is > 90 and <= 180) return TileRotation.Down; + if (rotation is > 180 and <= 270) return TileRotation.Right; + + return TileRotation.Up; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Data/TileInfo.cs b/Sources/Tiles/Data/TileInfo.cs new file mode 100644 index 0000000..cb1b514 --- /dev/null +++ b/Sources/Tiles/Data/TileInfo.cs @@ -0,0 +1,22 @@ +namespace BuildingGame.Tiles.Data; + +public struct TileInfo +{ + public byte Id; + public TileFlags Flags; + public TileData Data = new TileData(); + + public TileInfo(byte id, TileFlags flags) + { + Id = id; + Flags = flags; + } + + public TileInfo Clone() + { + return new TileInfo(Id, Flags); + } + + public static implicit operator TileInfo(byte id) => new TileInfo(id, TileFlags.Default); + public static implicit operator byte(TileInfo info) => info.Id; +} \ No newline at end of file diff --git a/Sources/Tiles/Data/TileRotation.cs b/Sources/Tiles/Data/TileRotation.cs new file mode 100644 index 0000000..1324874 --- /dev/null +++ b/Sources/Tiles/Data/TileRotation.cs @@ -0,0 +1,9 @@ +namespace BuildingGame.Tiles.Data; + +public enum TileRotation : byte +{ + Up, + Left, + Down, + Right +} \ No newline at end of file diff --git a/Sources/Tiles/Data/WorldInfo.cs b/Sources/Tiles/Data/WorldInfo.cs new file mode 100644 index 0000000..df21dd7 --- /dev/null +++ b/Sources/Tiles/Data/WorldInfo.cs @@ -0,0 +1,6 @@ +namespace BuildingGame.Tiles.Data; + +public record struct WorldInfo(string Path, WorldInfo.InfoRecord Info) +{ + public record struct InfoRecord(string Name, TimeSpan PlayTime); +} \ No newline at end of file diff --git a/Sources/Tiles/Dynamic/InfectionTile.cs b/Sources/Tiles/Dynamic/InfectionTile.cs new file mode 100644 index 0000000..7ea9404 --- /dev/null +++ b/Sources/Tiles/Dynamic/InfectionTile.cs @@ -0,0 +1,18 @@ +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.Dynamic; + +public class InfectionTile : Tile +{ + protected override void OnTick(World world, TileInfo info, int x, int y) + { + if (!Settings.EnableInfectionTile) return; + + if (info.Data.CurrentTick % 3 != 0) return; + + if (world[x - 1, y] != 0) world[x - 1, y] = info.Clone(); + if (world[x + 1, y] != 0) world[x + 1, y] = info.Clone(); + if (world[x, y - 1] != 0) world[x, y - 1] = info.Clone(); + if (world[x, y + 1] != 0) world[x, y + 1] = info.Clone(); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Dynamic/LavaTile.cs b/Sources/Tiles/Dynamic/LavaTile.cs new file mode 100644 index 0000000..7442194 --- /dev/null +++ b/Sources/Tiles/Dynamic/LavaTile.cs @@ -0,0 +1,9 @@ +namespace BuildingGame.Tiles.Dynamic; + +public class LavaTile : LiquidTile +{ + public LavaTile() + { + SpreadSpeed = 3; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Dynamic/LiquidTile.cs b/Sources/Tiles/Dynamic/LiquidTile.cs new file mode 100644 index 0000000..2ca32b1 --- /dev/null +++ b/Sources/Tiles/Dynamic/LiquidTile.cs @@ -0,0 +1,36 @@ +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.Dynamic; + +public class LiquidTile : Tile +{ + public int SpreadSpeed = 1; + + protected override void OnTick(World world, TileInfo info, int x, int y) + { + if (!Settings.EnableDynamicTiles) return; + + if (info.Data.CurrentTick % (TickCount - SpreadSpeed) != 0) return; + if (world[x, y + 1] == info.Id) return; + + if (CanExtendTo(world, info, x, y + 1)) + { + world[x, y + 1] = info.Clone(); + } + else if (CanExtendTo(world, info, x + 1, y)) + { + world[x + 1, y] = info.Clone(); + } + else if (CanExtendTo(world, info, x - 1, y)) + { + world[x - 1, y] = info.Clone(); + } + } + + private static bool CanExtendTo(World world, TileInfo info, int toX, int toY) + { + var toInfo = world[toX, toY]; + + return toInfo.Id == 0; + } +} \ No newline at end of file diff --git a/Sources/Tiles/Dynamic/SandTile.cs b/Sources/Tiles/Dynamic/SandTile.cs new file mode 100644 index 0000000..d8fb509 --- /dev/null +++ b/Sources/Tiles/Dynamic/SandTile.cs @@ -0,0 +1,36 @@ +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.Dynamic; + +public class SandTile : Tile +{ + private static readonly byte WaterId; + private static readonly byte LavaId; + private static readonly byte GlassId; + + static SandTile() + { + WaterId = Tiles.GetId("water"); + LavaId = Tiles.GetId("lava"); + GlassId = Tiles.GetId("glass"); + } + + protected override void OnTick(World world, TileInfo info, int x, int y) + { + if (!Settings.EnableDynamicTiles) return; + + if (world.IsTileNear(x, y, LavaId)) + { + world[x, y] = GlassId; + return; + } + + var tileBelow = world[x, y + 1]; + + if (y >= world.Height - 1) return; + if (tileBelow == info.Id || (tileBelow != 0 && tileBelow != WaterId)) return; + + world[x, y] = 0; + world[x, y + 1] = info.Clone(); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Dynamic/WaterTile.cs b/Sources/Tiles/Dynamic/WaterTile.cs new file mode 100644 index 0000000..98feef9 --- /dev/null +++ b/Sources/Tiles/Dynamic/WaterTile.cs @@ -0,0 +1,30 @@ +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.Dynamic; + +public class WaterTile : LiquidTile +{ + private static readonly byte LavaId; + private static readonly byte ObsidianId; + + static WaterTile() + { + LavaId = Tiles.GetId("lava"); + ObsidianId = Tiles.GetId("obsidian"); + } + + public WaterTile() + { + SpreadSpeed = 10; + } + + protected override void OnTick(World world, TileInfo info, int x, int y) + { + base.OnTick(world, info, x, y); + + if (world.IsTileNear(x, y, LavaId)) + { + world[x, y] = ObsidianId; + } + } +} \ No newline at end of file diff --git a/Sources/Tiles/IO/BGWorld21Format.cs b/Sources/Tiles/IO/BGWorld21Format.cs new file mode 100644 index 0000000..16dd37f --- /dev/null +++ b/Sources/Tiles/IO/BGWorld21Format.cs @@ -0,0 +1,72 @@ +using System.Numerics; +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.IO; + +public static class BGWorld21Format +{ + public const string Header = "BGWORLD21"; + + public class Serializer : IWorldSerializer + { + public string Header => BGWorld21Format.Header; + + public bool TrySerialize(BinaryWriter writer, World world, out string? log) + { + writer.Write(world.PlayerPosition.X); + writer.Write(world.PlayerPosition.Y); + writer.Write(world.PlayerZoom); + + for (int x = 0; x < world.Width; x++) + { + for (int y = 0; y < world.Height; y++) + { + PushTile(writer, world[x, y]); + } + } + + log = null; + return true; + } + } + + public class Deserializer : IWorldDeserializer + { + public string Header => BGWorld21Format.Header; + + public bool TryDeserialize(BinaryReader reader, out World world, out string? log) + { + world = new World(); + world.PlayerPosition = new Vector2(reader.ReadSingle(), reader.ReadSingle()); + world.PlayerZoom = reader.ReadSingle(); + + for (int x = 0; x < world.Width; x++) + { + for (int y = 0; y < world.Height; y++) + { + world[x, y] = PopTile(reader); + } + } + + log = null; + return true; + } + } + + public static void Register() + { + WorldIO.RegisterSerializer(new Serializer()); + WorldIO.RegisterDeserializer(new Deserializer()); + } + + private static void PushTile(BinaryWriter bw, TileInfo tile) + { + bw.Write(tile.Id); + bw.Write((byte)tile.Flags.Rotation); + } + + private static TileInfo PopTile(BinaryReader br) + { + return new TileInfo(br.ReadByte(), new TileFlags((TileRotation)br.ReadByte())); + } +} \ No newline at end of file diff --git a/Sources/Tiles/IO/BGWorld2Format.cs b/Sources/Tiles/IO/BGWorld2Format.cs new file mode 100644 index 0000000..42d2a6c --- /dev/null +++ b/Sources/Tiles/IO/BGWorld2Format.cs @@ -0,0 +1,74 @@ +using System.Numerics; +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles.IO; + +/* Latest world format (BGWorld2) + * + * Reading order: + * 1. (float) Last Camera X + * 2. (float) Last Camera Y + * + * Per each tile in world file (256x256 tiles): + * 1. (byte) Tile Id + * 2. (float) Tile Flags: Rotation + * 3. (bool) Tile Flags: Flip + */ +public static class BGWorld2Format +{ + public const string Header = "BGWORLD2"; + + public class Serializer : IWorldSerializer + { + public string Header => BGWorld2Format.Header; + + public bool TrySerialize(BinaryWriter writer, World world, out string? log) + { + log = "Serialization as BGWORLD2 is not supported anymore"; + return false; + } + } + + public class Deserializer : IWorldDeserializer + { + public string Header => BGWorld2Format.Header; + + public bool TryDeserialize(BinaryReader reader, out World world, out string? log) + { + world = new World(); + world!.PlayerPosition = new Vector2(reader.ReadSingle(), reader.ReadSingle()); + + for (int x = 0; x < world.Width; x++) + { + for (int y = 0; y < world.Height; y++) + { + world[x, y] = PopTile(reader); + } + } + + log = null; + return true; + } + } + + public static void Register() + { + WorldIO.RegisterSerializer(new Serializer()); + WorldIO.RegisterDeserializer(new Deserializer()); + } + + private static void PushTile(BinaryWriter bw, TileInfo tile) + { + bw.Write(tile.Id); + bw.Write(tile.Flags.RotationAsFloat()); + bw.Write(false); // we dont know if tile is flipped + } + + private static TileInfo PopTile(BinaryReader br) + { + TileFlags flags = new TileFlags(br.ReadSingle()); + if (br.ReadBoolean()) flags.FlipRotation(); + + return new TileInfo(br.ReadByte(), flags); + } +} \ No newline at end of file diff --git a/Sources/Tiles/IO/IWorldDeserializer.cs b/Sources/Tiles/IO/IWorldDeserializer.cs new file mode 100644 index 0000000..19ebb43 --- /dev/null +++ b/Sources/Tiles/IO/IWorldDeserializer.cs @@ -0,0 +1,8 @@ +namespace BuildingGame.Tiles.IO; + +public interface IWorldDeserializer +{ + string Header { get; } + + bool TryDeserialize(BinaryReader reader, out World world, out string? log); +} \ No newline at end of file diff --git a/Sources/Tiles/IO/IWorldSerializer.cs b/Sources/Tiles/IO/IWorldSerializer.cs new file mode 100644 index 0000000..7788423 --- /dev/null +++ b/Sources/Tiles/IO/IWorldSerializer.cs @@ -0,0 +1,8 @@ +namespace BuildingGame.Tiles.IO; + +public interface IWorldSerializer +{ + string Header { get; } + + bool TrySerialize(BinaryWriter writer, World world, out string? log); +} \ No newline at end of file diff --git a/Sources/Tiles/IO/LvlFormat.cs b/Sources/Tiles/IO/LvlFormat.cs new file mode 100644 index 0000000..bc9aa90 --- /dev/null +++ b/Sources/Tiles/IO/LvlFormat.cs @@ -0,0 +1,47 @@ +namespace BuildingGame.Tiles.IO; + +/// +/// WARNING: There's no implemented conversion for LVL format +/// +public static class LvlFormat +{ + public const string Header = "LVL"; + + public class Serializer : IWorldSerializer + { + public string Header => LvlFormat.Header; + + public bool TrySerialize(BinaryWriter writer, World world, out string? log) + { + log = "Serialization as LVL is not supported anymore"; + return false; + } + } + + public class Deserializer : IWorldDeserializer + { + public string Header => LvlFormat.Header; + + public bool TryDeserialize(BinaryReader reader, out World world, out string? log) + { + world = new World(); + + for (int x = 0; x < world.Width; x++) + { + for (int y = 0; y < world.Height; y++) + { + world[x, y] = reader.ReadByte(); + } + } + + log = null; + return true; + } + } + + public static void Register() + { + WorldIO.RegisterSerializer(new Serializer()); + WorldIO.RegisterDeserializer(new Deserializer()); + } +} \ No newline at end of file diff --git a/Sources/Tiles/IO/WorldIO.cs b/Sources/Tiles/IO/WorldIO.cs new file mode 100644 index 0000000..b27814c --- /dev/null +++ b/Sources/Tiles/IO/WorldIO.cs @@ -0,0 +1,98 @@ +using System.Diagnostics.CodeAnalysis; +using System.IO.Compression; + +namespace BuildingGame.Tiles.IO; + +public static class WorldIO +{ + private static List _deserializers = new List(); + private static List _serializers = new List(); + + private static List _backupDeserializers = new List(); + + public static void RegisterDeserializer(T deserializer) where T : IWorldDeserializer + { + if (_deserializers.Find(m => m.GetType() == typeof(T)) != null) + throw new InvalidOperationException($"Deserializer of type {typeof(T)} is already registered"); + _deserializers.Add(deserializer); + } + + public static void RegisterSerializer(T serializer) where T : IWorldSerializer + { + if (_serializers.Find(m => m.GetType() == typeof(T)) != null) + throw new InvalidOperationException($"Serializer of type {typeof(T)} is already registered"); + _serializers.Add(serializer); + } + + public static bool TryDeserializeWorld(string path, [NotNullWhen(true)] out World? world) + { + world = null; + if (!File.Exists(path)) return false; + + try + { + using var fileStream = File.OpenRead(path); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress); + using var reader = new BinaryReader(gzipStream); + + string header = reader.ReadString(); + IWorldDeserializer deserializer = + _deserializers.Find(d => string.Equals(d.Header, header, StringComparison.CurrentCultureIgnoreCase)) + ?? throw new IOException("Unknown or corrupted world header: " + header); + + if (_backupDeserializers.FindIndex(d => d.GetType() == deserializer.GetType()) >= 0) + { + File.Copy(path, path + ".old"); + } + + if (deserializer.TryDeserialize(reader, out var outWorld, out var log)) + { + world = outWorld; + return true; + } + + // TODO: print log from deserializer + return false; + } + catch (Exception ex) + { + // TODO: implement exception logging + return false; + } + } + + public static bool TrySerializeWorld(string path, World world) where TSerializer : IWorldSerializer + { + try + { + using var fileStream = File.OpenWrite(path); + using var gzipStream = new GZipStream(fileStream, CompressionMode.Compress); + using var writer = new BinaryWriter(gzipStream); + + IWorldSerializer serializer = + _serializers.Find(d => d.GetType() == typeof(TSerializer)) + ?? throw new IOException("Unable to find serializer of type " + typeof(TSerializer)); + + writer.Write(serializer.Header); + + if (serializer.TrySerialize(writer, world, out var log)) + { + return true; + } + + // TODO: print log from serializer + return false; + } + catch (Exception ex) + { + // TODO: implement exception logging + return false; + } + } + + public static void AddDeserializerAsBackupable(TDeserializer deserializer) + where TDeserializer : IWorldDeserializer + { + _backupDeserializers.Add(deserializer); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Packs/TilePack.cs b/Sources/Tiles/Packs/TilePack.cs new file mode 100644 index 0000000..9db049d --- /dev/null +++ b/Sources/Tiles/Packs/TilePack.cs @@ -0,0 +1,3 @@ +namespace BuildingGame.Tiles.Packs; + +public record struct TilePack(string Path, TilePackInfo Info); \ No newline at end of file diff --git a/Sources/Tiles/Packs/TilePackInfo.cs b/Sources/Tiles/Packs/TilePackInfo.cs new file mode 100644 index 0000000..af64751 --- /dev/null +++ b/Sources/Tiles/Packs/TilePackInfo.cs @@ -0,0 +1,3 @@ +namespace BuildingGame.Tiles.Packs; + +public record struct TilePackInfo(string Name, string AssetsPath); \ No newline at end of file diff --git a/Sources/Tiles/Packs/TilePackManager.cs b/Sources/Tiles/Packs/TilePackManager.cs new file mode 100644 index 0000000..d051c56 --- /dev/null +++ b/Sources/Tiles/Packs/TilePackManager.cs @@ -0,0 +1,68 @@ +using BuildingGame.Translation; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace BuildingGame.Tiles.Packs; + +public static class TilePackManager +{ + private const string TilePacksPath = "TilePacks"; + private const string PackFileName = "pack"; + + public static List TilePacks = new(); + + private static readonly IDeserializer _yaml = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + public static void Load() + { + TilePacks.Clear(); + + if (!Directory.Exists(TilePacksPath)) + { + Directory.CreateDirectory(TilePacksPath); + return; + } + + foreach (var dir in Directory.EnumerateDirectories(TilePacksPath)) + foreach (var file in Directory.EnumerateFiles(dir)) + { + if (Path.GetExtension(file) is not (".yaml" or ".yml") || + Path.GetFileNameWithoutExtension(file).ToLower() != PackFileName) continue; + + try + { + var tilePack = _yaml.Deserialize(File.ReadAllText(file)); + TilePacks.Add(new TilePack(dir, tilePack)); + break; + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } + + public static void Apply(TilePack pack) + { + Resources.Reload(Path.Join(pack.Path, pack.Info.AssetsPath)); + TranslationContainer.Default.Reload(TranslationLoader.TranslationPath); + + Settings.CurrentTilePack = pack.Path.Split('/', '\\').Last(); + + Program.LoadWindowIcon(); + } + + public static TilePack Find(string path) + { + return TilePacks.FirstOrDefault(i => + { + var packDirectoryName = i.Path.Split('/', '\\').Last(); + var pathDirectoryName = path.Split('/', '\\').Last(); + + return string.Equals(packDirectoryName, pathDirectoryName, StringComparison.CurrentCultureIgnoreCase); + }); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Tile.cs b/Sources/Tiles/Tile.cs new file mode 100644 index 0000000..03cd3ec --- /dev/null +++ b/Sources/Tiles/Tile.cs @@ -0,0 +1,113 @@ +using System.Numerics; +using BuildingGame.Tiles.Data; + +namespace BuildingGame.Tiles; + +public class Tile +{ + public const int TickCount = 20; + public const int TileSize = 16; + public const float TileUpscale = 3; + public const float RealTileSize = TileSize * TileUpscale; + public const float AtlasFraction = 0.25f; + + public Vector2 TexCoord; + public Vector2 Size = Vector2.One; + public string TranslationKey = ""; + + public Tile() + { + TexCoord = Vector2.Zero; + } + + public Tile(Vector2 texCoord) + { + TexCoord = texCoord; + } + + public Tile(Vector2 texCoord, Vector2 size) + { + TexCoord = texCoord; + Size = size; + } + + public Tile(Vector2 texCoord, Vector2 size, string translationKey) + { + TexCoord = texCoord; + Size = size; + TranslationKey = translationKey; + } + + public Tile(float tx, float ty) + { + TexCoord = new Vector2(tx, ty); + } + + public virtual void OnPlace(World world, TileInfo info, int x, int y) + { + } + + public virtual void OnUpdate(World world, TileInfo info, int x, int y) + { + info.Data.TickTimer -= GetFrameTime(); + + if (info.Data.TickTimer > 0.0f) return; + + info.Data.TickTimer = 1.0f / TickCount; + info.Data.CurrentTick--; + if (info.Data.CurrentTick < 0) info.Data.CurrentTick = TickCount; + OnTick(world, info, x, y); + } + + public virtual void OnRandomUpdate(World world, TileInfo info, int x, int y) + { + } + + protected virtual void OnTick(World world, TileInfo info, int x, int y) + { + } + + public virtual void OnNeighbourInfoUpdate(World world, TileInfo oldInfo, TileInfo newInfo, int x, int y, + int neighbourX, int neighbourY) + { + } + + public virtual void OnInfoUpdate(World world, TileInfo oldInfo, TileInfo newInfo, int x, int y) + { + } + + public virtual void Draw(World world, TileInfo info, float x, float y, Color tint) + { + DrawTexturePro(Resources.GetTexture("Atlas.png"), + // we add a fraction to the source rectangle, so we wont see flickering parts of atlas + new Rectangle( + TexCoord.X * TileSize + AtlasFraction, + TexCoord.Y * TileSize + AtlasFraction, + Size.X * TileSize - AtlasFraction, + Size.Y * TileSize - AtlasFraction + ), + new Rectangle( + x * TileSize * TileUpscale, + y * TileSize * TileUpscale, + Size.X * TileSize * TileUpscale, + Size.Y * TileSize * TileUpscale + ), + new Vector2(RealTileSize / 2), info.Flags.RotationAsFloat(), tint + ); + } + + public virtual void DrawPreview(World world, TileInfo info, float x, float y) + { + Draw(world, info, x, y, new Color(255, 255, 255, 120)); + } + + public override bool Equals(object? obj) + { + return obj is Tile t && t.TexCoord == TexCoord && t.Size == Size && string.Equals(t.TranslationKey, TranslationKey, StringComparison.CurrentCultureIgnoreCase); + } + + public override int GetHashCode() + { + return HashCode.Combine(TexCoord, Size, TranslationKey); + } +} \ No newline at end of file diff --git a/Sources/Tiles/Tiles.cs b/Sources/Tiles/Tiles.cs new file mode 100644 index 0000000..7068e2d --- /dev/null +++ b/Sources/Tiles/Tiles.cs @@ -0,0 +1,79 @@ +using System.Diagnostics.CodeAnalysis; +using BuildingGame.Tiles.Atlas; +using BuildingGame.Translation; + +namespace BuildingGame.Tiles; + +public static class Tiles +{ + private static Dictionary _Tiles = AtlasLoader.ConvertTiles(AtlasLoader.LoadTiles()); + + public static void Reload() + { + _Tiles = AtlasLoader.ConvertTiles(AtlasLoader.LoadTiles()); + } + + [Obsolete("use TryGetTile instead pls ty", true)] + public static Tile GetTile(byte id) + { + throw new Exception("use TryGetTile instead pls ty"); + } + + [Obsolete("use TryGetTile instead pls ty", true)] + public static Tile GetTile(string name) + { + throw new Exception("use TryGetTile instead pls ty"); + } + + public static bool TryGetTile(byte id, [NotNullWhen(true)] out Tile? tile) + { + tile = _Tiles.FirstOrDefault(kv => kv.Key.Id == id).Value; + return tile != null; + } + + public static bool TryGetTile(string name, [NotNullWhen(true)] out Tile? tile) + { + tile = _Tiles.FirstOrDefault(kv => string.Equals(kv.Key.Name, name, StringComparison.CurrentCultureIgnoreCase)).Value; + return tile != null; + } + + public static byte GetId(Tile tile) + { + var key = _Tiles.FirstOrDefault(kv => kv.Value == tile).Key; + return key.Id; + } + + public static byte GetId(string name) + { + return !TryGetTile(name, out var tile) ? (byte)0 : GetId(tile); + } + + public static Tile[] GetTiles() + { + return _Tiles.Values.ToArray(); + } + + public static void RegisterCustomTile(string name, T tile) where T : Tile + { + var key = _Tiles.Keys.First(k => string.Equals(k.Name, name, StringComparison.CurrentCultureIgnoreCase)); + var value = _Tiles[key]; + + tile.Size = value.Size; + tile.TexCoord = value.TexCoord; + tile.TranslationKey = value.TranslationKey; + + _Tiles[key] = tile; + } + + public static void RegisterCustomTile(byte id, T tile) where T : Tile + { + var key = _Tiles.Keys.First(k => k.Id == id); + var value = _Tiles[key]; + + tile.Size = value.Size; + tile.TexCoord = value.TexCoord; + tile.TranslationKey = value.TranslationKey; + + _Tiles[key] = tile; + } +} \ No newline at end of file diff --git a/Sources/Tiles/World.Utils.cs b/Sources/Tiles/World.Utils.cs new file mode 100644 index 0000000..257d48e --- /dev/null +++ b/Sources/Tiles/World.Utils.cs @@ -0,0 +1,12 @@ +namespace BuildingGame.Tiles; + +public partial class World +{ + public bool IsTileNear(int x, int y, byte nearTileId) + { + return this[x - 1, y] == nearTileId || + this[x + 1, y] == nearTileId || + this[x, y - 1] == nearTileId || + this[x, y + 1] == nearTileId; + } +} \ No newline at end of file diff --git a/Sources/Tiles/World.cs b/Sources/Tiles/World.cs new file mode 100644 index 0000000..4de0569 --- /dev/null +++ b/Sources/Tiles/World.cs @@ -0,0 +1,123 @@ +using System.Numerics; +using BuildingGame.Tiles.Data; +using BuildingGame.Tiles.IO; + +namespace BuildingGame.Tiles; + +public partial class World +{ + public const int DefaultSize = 256; + + public readonly int Width; + public readonly int Height; + public readonly int ChunkWidth; + public readonly int ChunkHeight; + + public Vector2 PlayerPosition; + public float PlayerZoom = 1.0f; + + private Chunk[][] _chunks; + + public World() + : this(DefaultSize, DefaultSize) + { + + } + + public World(int width, int height) + { + Width = width; + Height = height; + ChunkWidth = width / Chunk.Size; + ChunkHeight = height / Chunk.Size; + + _chunks = new Chunk[ChunkWidth][]; + for (int x = 0; x < ChunkWidth; x++) + { + _chunks[x] = new Chunk[ChunkHeight]; + for (int y = 0; y < ChunkHeight; y++) + { + _chunks[x][y] = new Chunk(this, x, y); + } + } + } + + public void Update() + { + for (int x = ChunkWidth - 1; x >= 0; x--) + { + for (int y = ChunkHeight - 1; y >= 0; y--) + { + _chunks[x][y].Update(); + } + } + } + + public void Draw(Player player) + { + Rectangle view = player.GetViewRectangle(); + + for (int x = 0; x < ChunkWidth; x++) + { + for (int y = 0; y < ChunkHeight; y++) + { + if (CheckCollisionRecs(new Rectangle(x * Chunk.ViewSize, y * Chunk.ViewSize, Chunk.ViewSize, Chunk.ViewSize), view)) + _chunks[x][y].Draw(); + } + } + } + + public ChunkPosition WorldToChunk(int x, int y) + { + int cx = x / Chunk.Size; + int cy = y / Chunk.Size; + + return new ChunkPosition(cx, cy); + } + + public void Load(string path) + { + if (!WorldIO.TryDeserializeWorld(path, out var world)) return; + if (Width != world.Width || Height != world.Height) return; + + PlayerPosition = world.PlayerPosition; + PlayerZoom = world.PlayerZoom; + + for (int x = 0; x < Width; x++) + { + for (int y = 0; y < Height; y++) + { + this[x, y] = world[x, y]; + } + } + } + + public void Save(string path) + { + WorldIO.TrySerializeWorld(path, this); + } + + public TileInfo this[int x, int y] + { + get + { + if (x < 0 || x >= Width || y < 0 || y >= Height) + return 0; + + ChunkPosition pos = WorldToChunk(x, y); + pos.WorldToTile(x, y, out int tx, out int ty); + + return _chunks[pos.X][pos.Y][tx, ty]; + } + set + { + if (x < 0 || x >= Width || y < 0 || y >= Height) + return; + + ChunkPosition pos = WorldToChunk(x, y); + pos.WorldToTile(x, y, out int tx, out int ty); + + _chunks[pos.X][pos.Y][tx, ty] = value; + } + } +} \ No newline at end of file diff --git a/Sources/Tiles/WorldManager.cs b/Sources/Tiles/WorldManager.cs new file mode 100644 index 0000000..45eafc7 --- /dev/null +++ b/Sources/Tiles/WorldManager.cs @@ -0,0 +1,104 @@ +using BuildingGame.Tiles.Data; +using BuildingGame.Tiles.IO; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace BuildingGame.Tiles; + +public static class WorldManager +{ + public const string WorldsPath = "Worlds"; + public const string WorldInfoFile = "Info"; + public const string WorldLevelFile = "Level.dat"; + + public static readonly List Worlds = new List(); + + private static readonly IDeserializer _deserializer = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + private static readonly ISerializer _serializer = new SerializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .Build(); + + public static void ReadWorlds() + { + Worlds.Clear(); + + foreach (var dir in Directory.EnumerateDirectories(WorldsPath)) + { + foreach (var file in Directory.EnumerateFiles(dir)) + { + var fileName = Path.GetFileNameWithoutExtension(file).ToLower(); + if (fileName != WorldInfoFile.ToLower()) continue; + + var ext = Path.GetExtension(file).ToLower(); + + var content = File.ReadAllText(file); + + switch (ext) + { + case ".txt": + Worlds.Add(new WorldInfo(dir, new WorldInfo.InfoRecord(content, TimeSpan.Zero))); + break; + + case ".yaml": + var deserialized = _deserializer.Deserialize(content); + Worlds.Add(new WorldInfo(dir, deserialized)); + break; + + default: + continue; + } + + break; + } + } + } + + public static WorldInfo CreateInfo(string name) + { + var invalidChars = Path.GetInvalidPathChars(); + var pathChars = name.Select(c => Array.IndexOf(invalidChars, c) >= 0 ? ' ' : c); + var path = Path.Join(WorldsPath, new string(pathChars.ToArray())); + + var info = new WorldInfo(path, new WorldInfo.InfoRecord(name, TimeSpan.Zero)); + + return info; + } + + public static WorldInfo Find(string path) + { + return Worlds.FirstOrDefault(i => + string.Equals(i.Path, path, StringComparison.CurrentCultureIgnoreCase)); + } + + public static void LoadWorld(ref World world, string name) + { + var info = Find(name); + if (string.IsNullOrWhiteSpace(info.Path)) return; + + world.Load(Path.Join(info.Path, WorldLevelFile)); + } + + public static void WriteWorld(World world, WorldInfo info) + { + WriteInfo(info); + world.Save(Path.Join(info.Path, WorldLevelFile)); + } + + public static void WriteInfo(WorldInfo info) + { + Directory.CreateDirectory(info.Path); + + var content = _serializer.Serialize(info.Info); + File.WriteAllText(Path.Join(info.Path, WorldInfoFile + ".yaml"), content); + } + + public static void DeleteWorld(WorldInfo info) + { + Directory.Delete(info.Path, true); + Worlds.Remove(info); + } +} \ No newline at end of file diff --git a/Sources/Translation/TranslationContainer.cs b/Sources/Translation/TranslationContainer.cs new file mode 100644 index 0000000..68897ab --- /dev/null +++ b/Sources/Translation/TranslationContainer.cs @@ -0,0 +1,44 @@ +namespace BuildingGame.Translation; + +public class TranslationContainer +{ + private static TranslationContainer _default = new(); + + public static TranslationContainer Default + { + get + { + if (!_default.IsEmpty()) return _default; + + TranslationLoader.TryLoadTranslation(TranslationLoader.TranslationPath, out _default); + return _default; + } + } + private Dictionary _translations; + + public TranslationContainer() + { + _translations = new Dictionary(); + } + + public TranslationContainer(Dictionary translations) + { + _translations = translations; + } + + public string GetTranslatedName(string name) + { + return _translations.GetValueOrDefault(name) ?? name; + } + + public void Reload(string path) + { + TranslationLoader.TryLoadTranslation(path, out var translation); + _translations = translation._translations; + } + + private bool IsEmpty() + { + return _translations is { Count: > 0 }; + } +} \ No newline at end of file diff --git a/Sources/Translation/TranslationLoader.cs b/Sources/Translation/TranslationLoader.cs new file mode 100644 index 0000000..edb82a7 --- /dev/null +++ b/Sources/Translation/TranslationLoader.cs @@ -0,0 +1,28 @@ +using System.Diagnostics.CodeAnalysis; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace BuildingGame.Translation; + +public static class TranslationLoader +{ + public static readonly string TranslationPath = "Translation.yaml"; + + private static readonly IDeserializer _yaml = new DeserializerBuilder() + .WithNamingConvention(UnderscoredNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + public static bool TryLoadTranslation(string path, out TranslationContainer translation) + { + translation = new TranslationContainer(); + + if (!File.Exists(Resources.GetPath(path))) return false; + + var content = Resources.GetText(path); + var translationMap = _yaml.Deserialize>(content); + translation = new TranslationContainer(translationMap); + + return true; + } +} \ No newline at end of file diff --git a/Sources/UI/Alignment.cs b/Sources/UI/Alignment.cs new file mode 100644 index 0000000..057d094 --- /dev/null +++ b/Sources/UI/Alignment.cs @@ -0,0 +1,14 @@ +namespace BuildingGame.UI; + +public enum Alignment +{ + TopLeft, + TopRight, + TopCenter, + CenterLeft, + CenterRight, + Center, + BottomLeft, + BottomRight, + BottomCenter +} \ No newline at end of file diff --git a/Sources/UI/Brushes/GradientBrush.cs b/Sources/UI/Brushes/GradientBrush.cs new file mode 100644 index 0000000..45ec146 --- /dev/null +++ b/Sources/UI/Brushes/GradientBrush.cs @@ -0,0 +1,33 @@ +namespace BuildingGame.UI.Brushes; + +public class GradientBrush : IBrush +{ + public Color ColorA; + public Color ColorB; + public GradientDirection Direction; + + public GradientBrush(Color colorA, Color colorB, GradientDirection direction = GradientDirection.Vertical) + { + ColorA = colorA; + ColorB = colorB; + Direction = direction; + } + + public void FillArea(Rectangle area) + { + int x = (int)area.X; + int y = (int)area.Y; + int width = (int)area.Width; + int height = (int)area.Height; + + switch (Direction) + { + case GradientDirection.Vertical: + DrawRectangleGradientV(x, y, width, height, ColorA, ColorB); + break; + case GradientDirection.Horizontal: + DrawRectangleGradientH(x, y, width, height, ColorA, ColorB); + break; + } + } +} \ No newline at end of file diff --git a/Sources/UI/Brushes/GradientDirection.cs b/Sources/UI/Brushes/GradientDirection.cs new file mode 100644 index 0000000..2a1a876 --- /dev/null +++ b/Sources/UI/Brushes/GradientDirection.cs @@ -0,0 +1,7 @@ +namespace BuildingGame.UI.Brushes; + +public enum GradientDirection +{ + Vertical, + Horizontal +} \ No newline at end of file diff --git a/GuiElements/Brushes/IBrush.cs b/Sources/UI/Brushes/IBrush.cs similarity index 58% rename from GuiElements/Brushes/IBrush.cs rename to Sources/UI/Brushes/IBrush.cs index 79e7914..04b24ef 100644 --- a/GuiElements/Brushes/IBrush.cs +++ b/Sources/UI/Brushes/IBrush.cs @@ -1,4 +1,4 @@ -namespace BuildingGame.GuiElements.Brushes; +namespace BuildingGame.UI.Brushes; public interface IBrush { diff --git a/Sources/UI/Brushes/LineBrush.cs b/Sources/UI/Brushes/LineBrush.cs new file mode 100644 index 0000000..133b36f --- /dev/null +++ b/Sources/UI/Brushes/LineBrush.cs @@ -0,0 +1,17 @@ +namespace BuildingGame.UI.Brushes; + +public class LineBrush : IBrush +{ + public Color Color; + public float LineThick = 1; + + public LineBrush(Color color) + { + Color = color; + } + + public void FillArea(Rectangle area) + { + DrawRectangleLinesEx(area, LineThick, Color); + } +} \ No newline at end of file diff --git a/Sources/UI/Brushes/OutlineBrush.cs b/Sources/UI/Brushes/OutlineBrush.cs new file mode 100644 index 0000000..cdfc51c --- /dev/null +++ b/Sources/UI/Brushes/OutlineBrush.cs @@ -0,0 +1,20 @@ +namespace BuildingGame.UI.Brushes; + +public class OutlineBrush : IBrush +{ + public Color LineColor; + public Color FillColor; + public float LineThick = 1; + + public OutlineBrush(Color lineColor, Color fillColor) + { + LineColor = lineColor; + FillColor = fillColor; + } + + public void FillArea(Rectangle area) + { + DrawRectangleRec(area, FillColor); + DrawRectangleLinesEx(area, LineThick, LineColor); + } +} \ No newline at end of file diff --git a/Sources/UI/Brushes/SolidBrush.cs b/Sources/UI/Brushes/SolidBrush.cs new file mode 100644 index 0000000..05a8f19 --- /dev/null +++ b/Sources/UI/Brushes/SolidBrush.cs @@ -0,0 +1,17 @@ +namespace BuildingGame.UI.Brushes; + +public class SolidBrush : IBrush +{ + public Color Color; + + public SolidBrush(Color color) + { + Color = color; + } + + public void FillArea(Rectangle area) + { + Rlgl.SetBlendMode(0); + DrawRectangleRec(area, Color); + } +} \ No newline at end of file diff --git a/Sources/UI/Brushes/TextureBrush.cs b/Sources/UI/Brushes/TextureBrush.cs new file mode 100644 index 0000000..e4d9730 --- /dev/null +++ b/Sources/UI/Brushes/TextureBrush.cs @@ -0,0 +1,21 @@ +using System.Numerics; + +namespace BuildingGame.UI.Brushes; + +public class TextureBrush : IBrush +{ + public Texture2D Texture; + public Color Tint = Color.White; + public Rectangle CropArea; + + public TextureBrush(Texture2D texture) + { + Texture = texture; + CropArea = new Rectangle(0, 0, texture.Width, texture.Height); + } + + public void FillArea(Rectangle area) + { + DrawTexturePro(Texture, CropArea, area, Vector2.Zero, 0, Tint); + } +} \ No newline at end of file diff --git a/Sources/UI/Element.cs b/Sources/UI/Element.cs new file mode 100644 index 0000000..ee7443e --- /dev/null +++ b/Sources/UI/Element.cs @@ -0,0 +1,234 @@ +using System.Numerics; + +namespace BuildingGame.UI; + +public class Element : IDisposable +{ + private Rectangle _Area; + + public Rectangle Area + { + get => _Area; + set + { + _Area = value; + + UnloadRenderTexture(_controlTexture); + _controlTexture = LoadRenderTexture((int)value.Width, (int)value.Height); + } + } + + public Vector2 GlobalPosition + { + get => new Vector2(_Area.X, _Area.Y); + set + { + _Area = new Rectangle(value.X, value.Y, _Area.Width, _Area.Height); + + foreach (var child in Children) + { + child.GlobalPosition = GlobalPosition + child.LocalPosition; + } + } + } + + public Vector2 Size + { + get => new Vector2(_Area.Width, _Area.Height); + set + { + if (value == new Vector2(Area.Width, Area.Height)) return; + + Area = new Rectangle(_Area.X, _Area.Y, value.X, value.Y); + } + } + + private Vector2 _localPosition; + public Vector2 LocalPosition + { + get => Parent != null ? _localPosition : GlobalPosition; + set + { + if (Parent == null) + { + GlobalPosition = value; + } + else + { + GlobalPosition = Parent.GlobalPosition + value; + _localPosition = value; + } + } + } + + private Element? _parent; + + public Element? Parent + { + get => _parent; + set + { + if (value == null && _parent != null) + { + _parent.Children.Remove(this); + } + else if (value != null && _parent == null) + { + value.Children.Add(this); + } + + _parent = value; + } + } + + public List Children = new List(); + + public ElementId Id; + + private short _zIndex; + + public short ZIndex + { + get + { + if (Parent == null) return _zIndex; + return (short)(Parent.ZIndex + _zIndex); + } + set + { + if (Parent == null) + { + _zIndex = value; + return; + } + + _zIndex = (short)(Parent.ZIndex + value); + return; + } + } + + private bool _active = true; + + public bool Active + { + get => _active; + set + { + foreach (var child in Children) + { + child.Active = value; + } + + _active = value; + } + } + + private bool _visible = true; + + public bool Visible + { + get => _visible; + set + { + foreach (var child in Children) + { + child.Visible = value; + } + + _visible = value; + } + } + + public Alignment Origin = Alignment.TopLeft; + public float Rotation = 0; + public string TooltipText = string.Empty; + + private bool _ignorePause = false; + + public bool IgnorePause + { + get => _ignorePause; + set + { + foreach (var child in Children) + { + child.IgnorePause = value; + } + + _ignorePause = value; + } + } + + private RenderTexture2D _controlTexture; + + public Element(ElementId id) + { + Id = id; + Area = new Rectangle(0, 0, 128, 128); + GuiManager.Add(this); + } + + public virtual void Update() + { + } + + public void Draw() + { + Vector2 offset = Origin switch + { + Alignment.TopLeft => new Vector2(0, 0), + Alignment.TopRight => new Vector2(_Area.Width, 0), + Alignment.TopCenter => new Vector2(_Area.Width / 2, 0), + Alignment.CenterLeft => new Vector2(0, _Area.Height / 2), + Alignment.CenterRight => new Vector2(_Area.Width, _Area.Height / 2), + Alignment.Center => new Vector2(_Area.Width / 2, _Area.Height / 2), + Alignment.BottomLeft => new Vector2(0, _Area.Height), + Alignment.BottomRight => new Vector2(_Area.Width, _Area.Height), + Alignment.BottomCenter => new Vector2(_Area.Width / 2, _Area.Height), + _ => new Vector2(0, 0) + }; + + BeginTextureMode(_controlTexture); + ClearBackground(Color.Blank); + + Render(); + + EndTextureMode(); + + DrawTexturePro( + _controlTexture.Texture, + new Rectangle( + 0.25f, 0.25f, + _Area.Width - 0.25f, -_Area.Height + 0.25f // we need to negate render texture height because opengl uses bottom-left instead of top-left + ), + _Area, offset, Rotation, + Color.White + ); + } + + protected virtual void Render() + { + ClearBackground(Color.Black); + DrawText(":(", 0, 0, 16, Color.White); + } + + public bool IsUnderMouse() + { + return CheckCollisionPointRec(GetMousePosition(), Area) && Visible && Active; + } + + public bool IsClicked() + { + return IsUnderMouse() && IsMouseButtonReleased(MouseButton.Left) && Visible && Active; + } + + public bool IsPressed() + { + return IsUnderMouse() && IsMouseButtonDown(MouseButton.Left) && Visible && Active; + } + + public void Dispose() + { + UnloadRenderTexture(_controlTexture); + } +} \ No newline at end of file diff --git a/Sources/UI/ElementComparer.cs b/Sources/UI/ElementComparer.cs new file mode 100644 index 0000000..f89306a --- /dev/null +++ b/Sources/UI/ElementComparer.cs @@ -0,0 +1,13 @@ +namespace BuildingGame.UI; + +public class ElementComparer : IComparer +{ + public int Compare(Element? x, Element? y) + { + if (x == null && y == null) return 0; + if (x == null) return -1; + if (y == null) return 1; + + return x.ZIndex.CompareTo(y.ZIndex); + } +} \ No newline at end of file diff --git a/Sources/UI/ElementId.cs b/Sources/UI/ElementId.cs new file mode 100644 index 0000000..656abde --- /dev/null +++ b/Sources/UI/ElementId.cs @@ -0,0 +1,81 @@ +using System.Text; + +namespace BuildingGame.UI; + +public struct ElementId +{ + public readonly string? Root; + public string Name; + + public ElementId(ElementId root, string name) + { + Root = root.ToString(); + Name = name; + } + + public ElementId(string root, string name) + { + Root = root; + Name = name; + } + + public ElementId(string name) + { + var split = name.Split("::"); + + switch (split.Length) + { + case >= 2: + Root = split[0]; + Name = split[1]; + break; + case >= 1: + Name = split[0]; + break; + } + } + + public override bool Equals(object? obj) + { + if (obj is string str) return Equals(str); + if (obj is ElementId id) return Equals(id); + + return false; + } + + public override int GetHashCode() + { + return HashCode.Combine(Root, Name); + } + + private bool Equals(ElementId id) + { + return string.Equals(Root, id.Root, StringComparison.CurrentCultureIgnoreCase) && + string.Equals(Name, id.Name, StringComparison.CurrentCultureIgnoreCase); + } + + private bool Equals(string id) + { + return string.Equals(id, ToString(), StringComparison.CurrentCultureIgnoreCase); + } + + public override string ToString() + { + var builder = new StringBuilder(); + if (Root != null) + { + builder.Append(Root); + builder.Append("::"); + } + builder.Append(Name); + return builder.ToString(); + } + + public static implicit operator string(ElementId id) => id.ToString(); + + public static bool operator ==(ElementId a, ElementId b) => a.Equals(b); + public static bool operator !=(ElementId a, ElementId b) => !(a == b); + + public static bool operator ==(ElementId a, string b) => a.Equals(b); + public static bool operator !=(ElementId a, string b) => !(a == b); +} \ No newline at end of file diff --git a/Sources/UI/Elements/Button.cs b/Sources/UI/Elements/Button.cs new file mode 100644 index 0000000..d6470d9 --- /dev/null +++ b/Sources/UI/Elements/Button.cs @@ -0,0 +1,32 @@ +namespace BuildingGame.UI.Elements; + +public class Button : TextElement +{ + public const string HoverText = "> "; + + public bool ShowHoverText = true; + public event Action? OnClick; + + public Button(ElementId id) : base(id) + { + } + + public override void Update() + { + base.Update(); + + if (IsClicked()) + { + OnClick?.Invoke(); + } + + if (ShowHoverText && IsUnderMouse() && !Text.StartsWith(HoverText)) + { + Text = HoverText + Text; + } + else if (!IsUnderMouse() && Text.StartsWith(HoverText)) + { + Text = Text.Replace(HoverText, null); + } + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/CheckBox.cs b/Sources/UI/Elements/CheckBox.cs new file mode 100644 index 0000000..4fe2824 --- /dev/null +++ b/Sources/UI/Elements/CheckBox.cs @@ -0,0 +1,51 @@ +using System.Numerics; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class CheckBox : Element +{ + public bool Checked; + + public IBrush? CheckBoxBrush = new OutlineBrush(Color.Gray, Color.LightGray); + public float BoxScale = 1f; + + public string Text = string.Empty; + public float TextSize = 12; + public Color TextColor = Color.White; + public event Action? OnCheck; + + public CheckBox(ElementId id) : base(id) + { + } + + public override void Update() + { + if (IsClicked()) + { + Checked = !Checked; + OnCheck?.Invoke(Checked); + } + } + + protected override void Render() + { + Rectangle boxArea = new Rectangle( + 0, + 0 + Area.Height / 2 - 18 * BoxScale / 2, + 18 * BoxScale, + 18 * BoxScale + ); + CheckBoxBrush?.FillArea(boxArea); + if (Checked) + { + DrawTexturePro( + Resources.GetTexture("Checkmark.png"), + new Rectangle(0, 0, 18, 18), + boxArea, + Vector2.Zero, 0, Color.White); + } + + DrawText(Text, 8 + 18 * BoxScale, Area.Height / 2 - TextSize / 2f, TextSize, TextColor); + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/ColorLine.cs b/Sources/UI/Elements/ColorLine.cs new file mode 100644 index 0000000..906aa7b --- /dev/null +++ b/Sources/UI/Elements/ColorLine.cs @@ -0,0 +1,179 @@ +using System.Numerics; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class ColorLine : Element +{ + public byte R + { + get + { + if (!byte.TryParse(_redBox.Text, out var r)) return 0; + return r; + } + set + { + _redBox.Text = value.ToString(); + ((OutlineBrush)_colorPreview.Brush!).FillColor.R = value; + } + } + + public byte G + { + get + { + if (!byte.TryParse(_greenBox.Text, out var g)) return 0; + return g; + } + set + { + _greenBox.Text = value.ToString(); + ((OutlineBrush)_colorPreview.Brush!).FillColor.G = value; + } + } + + public byte B + { + get + { + if (!byte.TryParse(_blueBox.Text, out var b)) return 0; + return b; + } + set + { + _blueBox.Text = value.ToString(); + ((OutlineBrush)_colorPreview.Brush!).FillColor.B = value; + } + } + + public Color Color + { + get => new Color(R, G, B, (byte)255); + set => (R, G, B) = (value.R, value.G, value.B); + } + + public Color DefaultColor = Color.White; + + private Panel _colorPreview; + private TextBox _redBox; + private TextBox _greenBox; + private TextBox _blueBox; + private Button _resetButton; + + public event Action? OnColorUpdate; + + public ColorLine(ElementId id) : base(id) + { + var translation = TranslationContainer.Default; + + _colorPreview = new Panel(new ElementId(id, "colorPreview")) + { + Brush = new OutlineBrush(Color.DarkGray, DefaultColor), + LocalPosition = new Vector2(0, 0), + Parent = this, + Size = new Vector2(40.0f, 20.0f) + }; + + _redBox = new TextBox(new ElementId(id, "redBox")) + { + TextSize = 16.0f, + Size = new Vector2(40.0f, 20.0f), + CharacterRange = new Range(48, 57), + Text = DefaultColor.R.ToString(), + Parent = this, + LocalPosition = new Vector2(_colorPreview.LocalPosition.X + _colorPreview.Size.X + 8, 0), + }; + + _greenBox = new TextBox(new ElementId(id, "greenBox")) + { + TextSize = 16.0f, + Size = new Vector2(40.0f, 20.0f), + CharacterRange = new Range(48, 57), + Text = DefaultColor.G.ToString(), + Parent = this, + LocalPosition = new Vector2(_redBox.LocalPosition.X + _redBox.Size.X + 8, 0) + }; + + _blueBox = new TextBox(new ElementId(id, "blueBox")) + { + TextSize = 16.0f, + Size = new Vector2(40.0f, 20.0f), + CharacterRange = new Range(48, 57), + Text = DefaultColor.B.ToString(), + Parent = this, + LocalPosition = new Vector2(_greenBox.LocalPosition.X + _greenBox.Size.X + 8, 0) + }; + + _resetButton = new Button(new ElementId(id, "resetButton")) + { + TextSize = 16.0f, + Size = new Vector2(64.0f, 20.0f), + Text = translation.GetTranslatedName("reset_button"), + ShowHoverText = false, + Parent = this, + LocalPosition = new Vector2(_blueBox.LocalPosition.X + _blueBox.Size.X + 8, 0) + }; + _resetButton.OnClick += () => + { + Color = DefaultColor; + }; + + _redBox.OnTextUpdate += RedBoxOnTextUpdate; + _greenBox.OnTextUpdate += GreenBoxOnTextUpdate; + _blueBox.OnTextUpdate += BlueBoxOnTextUpdate; + + Size = _colorPreview.Size with { X = _colorPreview.Size.X + _redBox.Size.X + _greenBox.Size.X + _blueBox.Size.X + 8 * 3 }; + } + + private void RedBoxOnTextUpdate(string arg2) + { + _ = int.TryParse(arg2, out var r); + r = Math.Clamp(r, 0, 255); + + _redBox.Text = r.ToString(); + ((OutlineBrush)_colorPreview.Brush!).FillColor.R = (byte)r; + + OnColorUpdate?.Invoke(Color); + } + + private void GreenBoxOnTextUpdate(string arg2) + { + _ = int.TryParse(arg2, out var g); + g = Math.Clamp(g, 0, 255); + + _greenBox.Text = g.ToString(); + ((OutlineBrush)_colorPreview.Brush!).FillColor.G = (byte)g; + + OnColorUpdate?.Invoke(Color); + } + + private void BlueBoxOnTextUpdate(string arg2) + { + _ = int.TryParse(arg2, out var b); + b = Math.Clamp(b, 0, 255); + + ((OutlineBrush)_colorPreview.Brush!).FillColor.B = (byte)b; + _blueBox.Text = b.ToString(); + + OnColorUpdate?.Invoke(Color); + } + + public override void Update() + { + // _colorPreview.GlobalPosition = GlobalPosition + new Vector2(0, Size.Y / 2 - _colorPreview.Size.Y / 2); + // _redBox.GlobalPosition = _colorPreview.GlobalPosition + new Vector2(_colorPreview.Size.X + 8, 0); + // _greenBox.GlobalPosition = _redBox.GlobalPosition + new Vector2(+ _redBox.Size.X + 8, 0); + // _blueBox.GlobalPosition = _greenBox.GlobalPosition + new Vector2(_greenBox.Size.X + 8, 0); + // _resetButton.GlobalPosition = _blueBox.GlobalPosition + new Vector2(_blueBox.Size.X + 8, 0); + // + // _colorPreview.Visible = _redBox.Visible = _greenBox.Visible = _blueBox.Visible = _resetButton.Visible = Visible; + // _colorPreview.Active = _redBox.Active = _greenBox.Active = _blueBox.Active = _resetButton.Active = Active; + } + + protected override void Render() + { + + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/ListBox.cs b/Sources/UI/Elements/ListBox.cs new file mode 100644 index 0000000..7e1f8aa --- /dev/null +++ b/Sources/UI/Elements/ListBox.cs @@ -0,0 +1,92 @@ +using System.Numerics; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class ListBox : Element +{ + public const float ScrollSpeed = 1000; + + public List Items = new List(); + public event Action? OnItemSelect; + + public IBrush? BackgroundBrush = null; + public IBrush? HighlightBrush = new SolidBrush(new Color(90, 95, 100, 255)); + public IBrush? SelectionBrush = new SolidBrush(new Color(60, 70, 80, 255)); + + public float ItemTextSize = 12; + public float ItemPadding = 4; + public Color ItemColor = Color.White; + public int SelectedItem = -1; + + private float _scroll = 0; + private int _highlightIndex = -1; + + public ListBox(ElementId id) : base(id) + { + } + + public override void Update() + { + if (IsUnderMouse()) + { + // finding index for highlight + float localMouseY = GetMousePosition().Y - GlobalPosition.Y; + int index = (int)((localMouseY - _scroll) / ItemTextSize); + _highlightIndex = index; + + // list scrolling + float boxHeight = Items.Count * ItemTextSize + Items.Count * ItemPadding; + float wheel = GetMouseWheelMove(); + float wheelAxis = wheel * ScrollSpeed * GetFrameTime(); + + if (boxHeight > Area.Height) + { + _scroll += wheelAxis; + if (_scroll > 0) _scroll = 0; + if (_scroll <= -boxHeight / 2) _scroll = -boxHeight / 2; + } + } + + if (IsClicked()) + { + if (_highlightIndex >= 0) + { + SelectedItem = _highlightIndex; + + if (SelectedItem >= 0 && SelectedItem < Items.Count) + { + OnItemSelect?.Invoke(Items[SelectedItem]); + } + else + { + OnItemSelect?.Invoke(string.Empty); + } + } + } + } + + protected override void Render() + { + BackgroundBrush?.FillArea(Area); + for (int i = 0; i < Items.Count; i++) + { + Rectangle area = new Rectangle( + ItemPadding, + i * ItemTextSize + ItemPadding + _scroll, + Area.Width, + ItemTextSize + ); + if (i == SelectedItem) + { + SelectionBrush?.FillArea(area); + } + else if (i == _highlightIndex) + { + HighlightBrush?.FillArea(area); + } + + DrawText(Items[i], area.X, area.Y, ItemTextSize, ItemColor); + } + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/Panel.cs b/Sources/UI/Elements/Panel.cs new file mode 100644 index 0000000..7ad096d --- /dev/null +++ b/Sources/UI/Elements/Panel.cs @@ -0,0 +1,18 @@ +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class Panel : Element +{ + public IBrush? Brush; + public int Padding = 0; + + public Panel(ElementId id) : base(id) + { + } + + protected override void Render() + { + Brush?.FillArea(new Rectangle(Padding, Padding, Area.Width - Padding - 1, Area.Height - Padding - 1)); + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/TextBox.cs b/Sources/UI/Elements/TextBox.cs new file mode 100644 index 0000000..8d7c27a --- /dev/null +++ b/Sources/UI/Elements/TextBox.cs @@ -0,0 +1,54 @@ +using System.Runtime.InteropServices; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class TextBox : TextElement +{ + public int MaxCharacters = 16; + + public Range CharacterRange = new Range(32, 125); + + public event Action? OnTextUpdate; + + private bool _focused; + private OutlineBrush _brush; + + public TextBox(ElementId id) : base(id) + { + _brush = new OutlineBrush(Color.Black, Color.LightGray); + TextColor = Color.Black; + } + + public override void Update() + { + base.Update(); + + if (IsMouseButtonPressed(MouseButton.Left)) + { + _focused = IsUnderMouse(); + _brush.LineColor = _focused ? Color.Gray : Color.Black; + // _brush.LineThick = _focused ? 1.5f : 1; + + GuiManager.IsFocused = _focused; + } + + BackgroundBrush = _brush; + + GatherInput(); + } + + private void GatherInput() + { + if (!_focused) return; + + int c = GetCharPressed(); + if (c >= CharacterRange.Start.Value && c <= CharacterRange.End.Value && Text.Length < MaxCharacters) + Text += char.ConvertFromUtf32(c); + + if (IsKeyPressedRepeat(KeyboardKey.Backspace) && Text.Length > 0) + Text = Text.Remove(Text.Length - 1); + + OnTextUpdate?.Invoke(Text); + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/TextElement.cs b/Sources/UI/Elements/TextElement.cs new file mode 100644 index 0000000..114f257 --- /dev/null +++ b/Sources/UI/Elements/TextElement.cs @@ -0,0 +1,65 @@ +using System.Numerics; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class TextElement : Element +{ + public IBrush? BackgroundBrush; + + public Color TextColor = Color.White; + public string Text = string.Empty; + public float TextSize = 10; + public Alignment TextAlignment = Alignment.TopLeft; + public bool AutoExtend = true; + + public float Padding = 0; + + public TextElement(ElementId id) : base(id) + { + } + + public Vector2 GetTextSize() + { + return MeasureTextEx(GuiManager.Font, Text, TextSize, TextSize / GuiManager.FontSize); + } + + public override void Update() + { + var textSize = GetTextSize(); + if (AutoExtend && (Size.X < textSize.X || Size.Y < textSize.Y)) + { + Size = textSize + new Vector2(Padding + 4.0f); + } + } + + protected override void Render() + { + var textSize = GetTextSize(); + float width = Area.Width - Padding - 1; + float height = Area.Height - Padding - 1; + + float xLeft = Padding + 4; + float xRight = Area.Width - textSize.X - Padding * 2; + float xCenter = Area.Width / 2 - textSize.X / 2; + float yTop = Padding + 4; + float yBottom = Area.Height - textSize.Y - Padding * 2; + float yCenter = Area.Height / 2 - textSize.Y / 2; + + Vector2 xy = TextAlignment switch + { + Alignment.TopLeft => new Vector2(xLeft, yTop), + Alignment.TopRight => new Vector2(xRight, yTop), + Alignment.TopCenter => new Vector2(xCenter, yTop), + Alignment.CenterLeft => new Vector2(xLeft, yCenter), + Alignment.CenterRight => new Vector2(xRight, yCenter), + Alignment.Center => new Vector2(xCenter, yCenter), + Alignment.BottomLeft => new Vector2(xLeft, yBottom), + Alignment.BottomRight => new Vector2(xRight, yBottom), + Alignment.BottomCenter => new Vector2(xCenter, yBottom) + }; + + BackgroundBrush?.FillArea(new Rectangle(Padding, Padding, width, height)); + DrawText(Text, xy.X, xy.Y, TextSize, TextColor); + } +} \ No newline at end of file diff --git a/Sources/UI/Elements/Tooltip.cs b/Sources/UI/Elements/Tooltip.cs new file mode 100644 index 0000000..5504d9e --- /dev/null +++ b/Sources/UI/Elements/Tooltip.cs @@ -0,0 +1,43 @@ +using System.Numerics; +using BuildingGame.UI.Brushes; + +namespace BuildingGame.UI.Elements; + +public class Tooltip : Element +{ + public string Text; + public float TextSize; + private GradientBrush _brush; + + public Tooltip(ElementId id) : base(id) + { + _brush = new GradientBrush(new Color(0, 0, 0, 50), new Color(16, 0, 16, 127)); + Visible = false; + } + + public override void Update() + { + base.Update(); + + Element? el = GuiManager.GetElementUnderMouse(el => el.IsUnderMouse() && !string.IsNullOrWhiteSpace(el.TooltipText)); + + if (el != null) + { + Text = el.TooltipText; + Visible = true; + } + else Visible = false; + + var pos = GetMousePosition() + new Vector2(0, 16); + var size = MeasureTextEx(GetFontDefault(), Text, TextSize, GetSpacing(TextSize)); + + GlobalPosition = pos with { X = pos.X - 16.0f }; + Size = size + new Vector2(16); + } + + protected override void Render() + { + _brush.FillArea(new Rectangle(0, 0, Size.X, Size.Y)); + DrawText(Text, 8.0f, 8.0f, TextSize, Color.White); + } +} \ No newline at end of file diff --git a/Sources/UI/GuiManager.cs b/Sources/UI/GuiManager.cs new file mode 100644 index 0000000..1209d49 --- /dev/null +++ b/Sources/UI/GuiManager.cs @@ -0,0 +1,81 @@ +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI; + +public static class GuiManager +{ + public static bool IsFocused = false; + + public static readonly Font Font = GetFontDefault(); + public static readonly int FontSize = 10; + + public static void Add(Element element) + { + ScreenManager.CurrentScreen?.Elements.Add(element); + } + + public static void Remove(Element element) + { + ScreenManager.CurrentScreen?.Elements.Remove(element); + } + + public static void Remove(ElementId id) + { + if (ScreenManager.CurrentScreen == null) return; + + int index = ScreenManager.CurrentScreen.Elements.FindIndex(e => e.Id == id); + if (index < 0) return; + ScreenManager.CurrentScreen.Elements.RemoveAt(index); + } + + public static Element? Get(ElementId id) + { + if (ScreenManager.CurrentScreen == null) return null; + + int index = ScreenManager.CurrentScreen.Elements.FindIndex(e => e.Id == id); + if (index < 0) return null; + return ScreenManager.CurrentScreen.Elements[index]; + } + + public static TElement? GetAs(ElementId id) where TElement : Element + { + if (ScreenManager.CurrentScreen == null) return default; + + int index = ScreenManager.CurrentScreen.Elements.FindIndex(e => + e.GetType() == typeof(TElement) && e.Id == id); + if (index < 0) return default; + return ScreenManager.CurrentScreen.Elements[index] as TElement; + } + + public static bool IsMouseOverElement() + { + return IsMouseOverElement(el => el.IsUnderMouse()); + } + + public static bool IsMouseOverElement(Func predicate) + { + if (ScreenManager.CurrentScreen == null) return false; + + var elements = ScreenManager.CurrentScreen.ElementsSorted; + return elements.Any(predicate); + } + + public static Element? GetElementUnderMouse() + { + return GetElementUnderMouse(el => el.IsUnderMouse()); + } + + public static Element? GetElementUnderMouse(Func predicate) + { + if (ScreenManager.CurrentScreen == null) return null; + + var elements = ScreenManager.CurrentScreen.ElementsSorted; + return elements.FirstOrDefault(predicate); + } + + [Obsolete("Use ScreenManager.Draw instead")] + public static void Draw() { } + + [Obsolete("Use ScreenManager.Update instead")] + public static void Update() { } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/BlockUI.cs b/Sources/UI/Interfaces/BlockUI.cs new file mode 100644 index 0000000..c51353c --- /dev/null +++ b/Sources/UI/Interfaces/BlockUI.cs @@ -0,0 +1,129 @@ +using System.Numerics; +using BuildingGame.Tiles; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; + +namespace BuildingGame.UI.Interfaces; + +public class BlockUI : UIInterface +{ + private Panel _background = null!; + private TextElement _menuTitle = null!; + private Button[] _tileButtons = null!; + + public override void Initialize() + { + base.Initialize(); + + var translation = TranslationContainer.Default; + + _background = new Panel(new ElementId("blockMenu", "background")) + { + Brush = new GradientBrush(new Color(0, 0, 25, 100), new Color(0, 0, 0, 200)), + Size = new Vector2(680, 420), + ZIndex = -1 + }; + Elements.Add(_background); + + _menuTitle = new TextElement(new ElementId("blockMenu", "title")) + { + Text = translation.GetTranslatedName("block_ui_title"), + TextColor = Color.White, + TextSize = 32, + TextAlignment = Alignment.Center, + Parent = _background, + ZIndex = 1 + }; + Elements.Add(_menuTitle); + + // new TextBox("tv3ewds") + // { + // Position = new Vector2(86, 86), + // TextColor = BLACK, + // MaxCharacters = 128 + // }; + + Tile[] tiles = Tiles.Tiles.GetTiles(); + _tileButtons = new Button[tiles.Length]; + + float x = 20; + float y = 40; + + for (int i = 0; i < tiles.Length; i++) + { + float aspectX = Tile.TileSize / (Tile.TileSize * tiles[i].Size.X); + float aspectY = Tile.TileSize / (Tile.TileSize * tiles[i].Size.Y); + + float ratio = aspectX < aspectY ? aspectX : aspectY; + + var tileButton = _tileButtons[i] = new Button(new ElementId("blockMenu", "tile_" + i)) + { + Size = new Vector2(1) * Tile.RealTileSize, + BackgroundBrush = new TextureBrush(Resources.GetTexture("Atlas.png")) + { + CropArea = new Rectangle( + tiles[i].TexCoord.X * Tile.TileSize, + tiles[i].TexCoord.Y * Tile.TileSize, + Tile.TileSize, + Tile.TileSize + ) + }, + ShowHoverText = false, + TooltipText = translation.GetTranslatedName(tiles[i].TranslationKey), + Parent = _background, + ZIndex = 1, + LocalPosition = new Vector2(x, y) + }; + + int ii = i; + tileButton.OnClick += () => + { + Player.CurrentTile = (byte)(ii + 1); + Visible = false; + }; + + x += Tile.RealTileSize + 8; + + if (x > _background.Size.X - Tile.RealTileSize) + { + x = 20; + y += Tile.RealTileSize + 8; + } + + Elements.Add(tileButton); + } + + Configure(); + + Visible = false; + } + + public override void Configure() + { + base.Configure(); + + Vector2 screenSize = new Vector2(GetScreenWidth(), GetScreenHeight()); + + _background.GlobalPosition = screenSize / 2 - _background.Size / 2; + + _menuTitle.Size = _background.Size with { Y = 38 }; + } + + public override void Resized() + { + base.Resized(); + + Configure(); + } + + public override void Update() + { + base.Update(); + + if (IsKeyPressed(KeyboardKey.B)) Visible = !Visible; + if (IsMouseButtonPressed(MouseButton.Left) && !_background.IsUnderMouse() && + GuiManager.GetElementUnderMouse()?.Id != "gameHud::tileMenuButton") // 🤟😏 + Visible = false; + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/CreateWorldUI.cs b/Sources/UI/Interfaces/CreateWorldUI.cs new file mode 100644 index 0000000..c73bd86 --- /dev/null +++ b/Sources/UI/Interfaces/CreateWorldUI.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +using BuildingGame.Tiles; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI.Interfaces; +public class CreateWorldUI : UIInterface +{ + private TextBox _worldNameTextBox; + private TextElement _maxCharacterText; + private Button _createButton; + private Button _backButton; + + public override void Initialize() + { + base.Initialize(); + + _worldNameTextBox = new TextBox(new ElementId("createWorldScreen", "worldNameTextBox")) + { + TextSize = 28.0f, + Size = new Vector2(512.0f, 32.0f) + }; + Elements.Add(_worldNameTextBox); + + _maxCharacterText = new TextElement(new ElementId("createWorldScreen", "maxCharacterText")) + { + Text = "(max. 16 characters)", + TextSize = 20.0f, + Size = _worldNameTextBox.Size with { Y = 24.0f }, + TextAlignment = Alignment.CenterLeft + }; + Elements.Add(_maxCharacterText); + + _createButton = new Button(new ElementId("createWorldScreen", "createButton")) + { + Text = "create", + TextSize = 24.0f, + Size = _worldNameTextBox.Size with { Y = 28.0f }, + TextAlignment = Alignment.Center + }; + _createButton.OnClick += () => + { + if (string.IsNullOrWhiteSpace(_worldNameTextBox.Text)) return; + if (WorldManager.Find(Path.Join(WorldManager.WorldsPath, _worldNameTextBox.Text)).Path != null) return; + + var info = WorldManager.CreateInfo(_worldNameTextBox.Text); + WorldManager.WriteWorld(new World(), info); + ScreenManager.Switch(new WorldSelectionScreen()); + }; + Elements.Add(_createButton); + + _backButton = new Button(new ElementId("createWorldScreen", "backButton")) + { + Text = "back", + TextSize = 24.0f, + Size = _worldNameTextBox.Size with { Y = 28.0f }, + TextAlignment = Alignment.Center + }; + _backButton.OnClick += () => + { + ScreenManager.Switch(new WorldSelectionScreen()); + }; + Elements.Add(_backButton); + + Configure(); + } + + public override void Configure() + { + _worldNameTextBox.GlobalPosition = new Vector2(GetScreenWidth() / 2 - _worldNameTextBox.Size.X / 2, GetScreenHeight() / 2 - 64.0f); + _maxCharacterText.GlobalPosition = _worldNameTextBox.GlobalPosition + new Vector2(0.0f, _worldNameTextBox.Size.Y); + _createButton.GlobalPosition = _maxCharacterText.GlobalPosition + new Vector2(0.0f, 40.0f); + _backButton.GlobalPosition = _createButton.GlobalPosition + new Vector2(0.0f, _createButton.Size.Y); + } + + public override void Resized() + { + Configure(); + } +} diff --git a/Sources/UI/Interfaces/GameUI.cs b/Sources/UI/Interfaces/GameUI.cs new file mode 100644 index 0000000..b34767b --- /dev/null +++ b/Sources/UI/Interfaces/GameUI.cs @@ -0,0 +1,82 @@ +using System.Numerics; +using BuildingGame.Tiles; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; + +namespace BuildingGame.UI.Interfaces; + +public class GameUI : UIInterface +{ + private const float TileButtonOffset = Tile.RealTileSize / 2; + private const float TileButtonSize = Tile.RealTileSize * 2; + + private BlockUI _blockUi; + private Tooltip _tooltip; + + private Button _tileMenuButton = null!; + + public GameUI(BlockUI blockUi) + { + _blockUi = blockUi; + } + + public override void Initialize() + { + base.Initialize(); + + _tileMenuButton = new Button(new ElementId("gameHud", "tileMenuButton")) + { + BackgroundBrush = new TextureBrush(Resources.GetTexture("Atlas.png")) + { + CropArea = new Rectangle(0, 0, Tile.TileSize, Tile.TileSize) + }, + ShowHoverText = false + }; + _tileMenuButton.OnClick += () => + { + _blockUi.Visible = !_blockUi.Visible; + }; + Elements.Add(_tileMenuButton); + + _tooltip = new Tooltip(new ElementId("gameHud", "tooltip")) + { + TextSize = 16.0f, + Size = new Vector2(0.0f, 36.0f), + }; + Elements.Add(_tooltip); + + Configure(); + } + + public override void Update() + { + base.Update(); + + if (!_blockUi.Visible && IsKeyPressed(KeyboardKey.Escape)) + { + _blockUi.Visible = false; + } + + if (!Tiles.Tiles.TryGetTile(Player.CurrentTile, out var tile)) return; + + var brush = (TextureBrush)_tileMenuButton.BackgroundBrush!; + (brush.CropArea.X, brush.CropArea.Y) = (tile.TexCoord.X * Tile.TileSize, tile.TexCoord.Y * Tile.TileSize); + } + + public override void Configure() + { + base.Configure(); + + _tileMenuButton.Area = new Rectangle( + GetScreenWidth() - TileButtonSize - TileButtonOffset, TileButtonOffset, + TileButtonSize, TileButtonSize + ); + } + + public override void Resized() + { + base.Resized(); + + Configure(); + } +} diff --git a/Sources/UI/Interfaces/MenuUI.cs b/Sources/UI/Interfaces/MenuUI.cs new file mode 100644 index 0000000..22f808a --- /dev/null +++ b/Sources/UI/Interfaces/MenuUI.cs @@ -0,0 +1,115 @@ +using System.Numerics; +using System.Reflection; +using BuildingGame.Translation; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI.Interfaces; + +public class MenuUI : UIInterface +{ + private TextElement _title; + private Button _playButton; + private Button _settingsButton; + private Button _packsButton; + private Button _exitButton; + private TextElement _versionText; + private SettingsUI _settingsUi = new SettingsUI(); + + public override void Initialize() + { + var translation = TranslationContainer.Default; + + _title = new TextElement(new ElementId("menuUi", "title")) + { + TextSize = 36.0f, + Text = translation.GetTranslatedName("title") + }; + Elements.Add(_title); + + _playButton = new Button(new ElementId("menuUi", "playButton")) + { + Text = translation.GetTranslatedName("play_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f) + }; + _playButton.OnClick += () => + { + ScreenManager.Switch(new WorldSelectionScreen()); + }; + Elements.Add(_playButton); + + _settingsButton = new Button(new ElementId("menuUi", "settingsButton")) + { + Text = translation.GetTranslatedName("settings_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f) + }; + _settingsButton.OnClick += () => + { + _settingsUi.Visible = !_settingsUi.Visible; + }; + Elements.Add(_settingsButton); + + _packsButton = new Button(new ElementId("menuUi", "packsButton")) + { + Text = translation.GetTranslatedName("packs_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f) + }; + _packsButton.OnClick += () => + { + ScreenManager.Switch(new TilePacksScreen()); + }; + Elements.Add(_packsButton); + + _exitButton = new Button(new ElementId("menuUi", "exitButton")) + { + Text = translation.GetTranslatedName("exit_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f) + }; + _exitButton.OnClick += () => + { + Program.Running = false; + }; + Elements.Add(_exitButton); + + var version = Assembly.GetExecutingAssembly().GetName().Version ?? new Version(); + + _versionText = new TextElement(new ElementId("menuUi", "versionText")) + { + Text = string.Format(translation.GetTranslatedName("version_format"), version.Major, version.Minor, version.Revision), + TextSize = 18.0f, + Size = new Vector2(100.0f, 18.0f) + }; + Elements.Add(_versionText); + + Configure(); + } + + public override void Configure() + { + _title.GlobalPosition = new Vector2(12, GetYAt(0)); + _playButton.GlobalPosition = new Vector2(12, GetYAt(1)); + _settingsButton.GlobalPosition = new Vector2(12, GetYAt(2)); + _packsButton.GlobalPosition = new Vector2(12, GetYAt(3)); + _exitButton.GlobalPosition = new Vector2(12, GetYAt(4)); + _versionText.GlobalPosition = new Vector2(8, GetScreenHeight() - 8 - _versionText.TextSize); + } + + public override void Resized() + { + Configure(); + } + + private float GetYAt(int index) + { + var height = GetScreenHeight(); + + var origin = height / 3.0f + 36.0f; + var indexY = 32.0f * index; + + return origin + indexY; + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/PauseUI.cs b/Sources/UI/Interfaces/PauseUI.cs new file mode 100644 index 0000000..0faf3b0 --- /dev/null +++ b/Sources/UI/Interfaces/PauseUI.cs @@ -0,0 +1,132 @@ +using System.Numerics; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI.Interfaces; + +public class PauseUI : UIInterface +{ + private Panel _background; + private Button _resumeButton; + private Button _menuButton; + private Button _settingsButton; + + private SettingsUI _settingsUi = new SettingsUI(); + private BlockUI _blockUi; + + public PauseUI(BlockUI blockUi) + { + IgnorePause = true; + _blockUi = blockUi; + } + + public override void Initialize() + { + var translation = TranslationContainer.Default; + + _background = new Panel(new ElementId("pauseScreen", "background")) + { + Brush = new GradientBrush(Color.Blank, Color.Black), + IgnorePause = true, + ZIndex = 100 + }; + Elements.Add(_background); + + _resumeButton = new Button(new ElementId("pauseScreen", "resumeButton")) + { + Text = translation.GetTranslatedName("resume_button"), + TextSize = 24, + TextAlignment = Alignment.CenterLeft, + IgnorePause = true, + ZIndex = 1, + Parent = _background + }; + _resumeButton.OnClick += () => + { + Visible = false; + Program.Paused = false; + }; + Elements.Add(_resumeButton); + + _settingsButton = new Button(new ElementId("pauseScreen", "settingsButton")) + { + Text = translation.GetTranslatedName("settings_button"), + TextSize = 24, + TextAlignment = Alignment.CenterLeft, + IgnorePause = true, + ZIndex = 1, + Size = new Vector2(148, 24), + Parent = _background + }; + _settingsButton.OnClick += () => + { + _settingsUi.Visible = !_settingsUi.Visible; + }; + Elements.Add(_settingsButton); + + _menuButton = new Button(new ElementId("pauseScreen", "menuButton")) + { + Text = translation.GetTranslatedName("menu_button"), + TextSize = 24, + TextAlignment = Alignment.CenterLeft, + IgnorePause = true, + ZIndex = 1, + Size = new Vector2(148, 24), + Parent = _background + }; + _menuButton.OnClick += () => + { + Program.Paused = false; + ScreenManager.Switch(new MenuScreen()); + }; + Elements.Add(_menuButton); + + Configure(); + Visible = false; + } + + public override void Configure() + { + base.Configure(); + + _background.Area = new Rectangle(-1, -1, GetScreenWidth() + 2, GetScreenHeight() + 2); + + float buttonsOriginX = 16; + float buttonsOriginY = GetScreenHeight() / 2 - _resumeButton.TextSize; + + _resumeButton.Area = new Rectangle(buttonsOriginX, buttonsOriginY, 148, 24); + _settingsButton.GlobalPosition = new Vector2(buttonsOriginX, _resumeButton.GlobalPosition.Y + _resumeButton.TextSize + 16); + _menuButton.GlobalPosition = new Vector2(buttonsOriginX, _settingsButton.GlobalPosition.Y + _settingsButton.TextSize + 16); + } + + public override void Resized() + { + base.Resized(); + + Configure(); + } + + public override void Update() + { + base.Update(); + + if (!IsKeyPressed(KeyboardKey.Escape)) return; + + if (Visible && _settingsUi.Visible) + { + _settingsUi.Visible = false; + return; + } + + if (!Visible && _blockUi.Visible) + { + _blockUi.Visible = false; + return; + } + + Visible = !Visible; + Program.Paused = !Program.Paused; + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/SettingsUI.cs b/Sources/UI/Interfaces/SettingsUI.cs new file mode 100644 index 0000000..0d1b27a --- /dev/null +++ b/Sources/UI/Interfaces/SettingsUI.cs @@ -0,0 +1,97 @@ +using System.Numerics; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; + +namespace BuildingGame.UI.Interfaces; + +public class SettingsUI : UIInterface +{ + private Panel _background; + private TextElement _skyColorLineText; + private ColorLine _skyColorLine; + private CheckBox _enableDynamicTilesBox; + private CheckBox _enableInfectionTileBox; + + public override void Initialize() + { + var translation = TranslationContainer.Default; + + _background = new Panel(new ElementId("settings", "background")) + { + Brush = new SolidBrush(new Color(0, 0, 0, 100)), + ZIndex = 200 + }; + Elements.Add(_background); + + _skyColorLineText = new TextElement(new ElementId("settings", "skyColorLineText")) + { + Text = translation.GetTranslatedName("sky_color_line"), + Size = new Vector2(128.0f, 20.0f), + TextSize = 16.0f, + ZIndex = 1, + Parent = _background + }; + Elements.Add(_skyColorLineText); + + _skyColorLine = new ColorLine(new ElementId("settings", "skyColorLine")) + { + Color = Settings.SkyColor, + DefaultColor = Color.SkyBlue, + Parent = _skyColorLineText, + LocalPosition = new Vector2(_skyColorLineText.Size.X + 16.0f, 2.0f) + }; + _skyColorLine.OnColorUpdate += color => + { + Settings.SkyColor = color; + }; + Elements.Add(_skyColorLine); + + _enableDynamicTilesBox = new CheckBox(new ElementId("settings", "enableDynamicTilesBox")) + { + Text = translation.GetTranslatedName("enable_dynamic_tiles"), + Size = new Vector2(512.0f, 20.0f), + TextSize = 16.0f, + ZIndex = 1, + Checked = Settings.EnableDynamicTiles, + Parent = _background + }; + _enableDynamicTilesBox.OnCheck += @checked => + { + Settings.EnableDynamicTiles = @checked; + }; + Elements.Add(_enableDynamicTilesBox); + + _enableInfectionTileBox = new CheckBox(new ElementId("settings", "enableInfectionTileBox")) + { + Text = translation.GetTranslatedName("enable_infection_tile"), + Size = new Vector2(512.0f, 20.0f), + TextSize = 16.0f, + ZIndex = 1, + Checked = Settings.EnableInfectionTile, + Parent = _background + }; + _enableInfectionTileBox.OnCheck += @checked => + { + Settings.EnableInfectionTile = @checked; + }; + Elements.Add(_enableInfectionTileBox); + + Configure(); + Visible = false; + } + + public override void Resized() + { + Configure(); + } + + public override void Configure() + { + _background.Area = new Rectangle(50.0f, 50.0f, GetScreenWidth() - 100.0f, GetScreenHeight() - 100.0f); + _skyColorLineText.GlobalPosition = _background.GlobalPosition + new Vector2(8.0f, 16.0f); + _enableDynamicTilesBox.GlobalPosition = _skyColorLineText.GlobalPosition + new Vector2(0.0f, _skyColorLineText.Size.Y + 8.0f); + _enableInfectionTileBox.GlobalPosition = _enableDynamicTilesBox.GlobalPosition + + new Vector2(0.0f, _enableInfectionTileBox.Size.Y + 8.0f); + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/TilePacksMenuUI.cs b/Sources/UI/Interfaces/TilePacksMenuUI.cs new file mode 100644 index 0000000..b5d53a0 --- /dev/null +++ b/Sources/UI/Interfaces/TilePacksMenuUI.cs @@ -0,0 +1,87 @@ +using System.Numerics; +using BuildingGame.Tiles.Packs; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI.Interfaces; + +public class TilePacksMenuUI : UIInterface +{ + private ListBox _tilePacksList; + private Button _menuButton; + + private string[] _paths; + + public override void Initialize() + { + _paths = TilePackManager.TilePacks.Select(t => t.Path).ToArray(); + + var translation = TranslationContainer.Default; + + _tilePacksList = new ListBox(new ElementId("tilePacksMenu", "tilePacksList")) + { + ItemTextSize = 24.0f, + BackgroundBrush = null, + ItemColor = Color.White, + Items = TilePackManager.TilePacks.Select(t => t.Info.Name).ToList() + }; + _tilePacksList.OnItemSelect += item => + { + var index = _tilePacksList.SelectedItem; + + if (index < 0 || index >= _paths.Length) return; + + var oldTilePack = TilePackManager.Find(Settings.CurrentTilePack); + SetTilePackActive(oldTilePack, false); + + var tilePack = TilePackManager.Find(_paths[index]); + TilePackManager.Apply(tilePack); + + SetTilePackActive(tilePack, true); + }; + Elements.Add(_tilePacksList); + + _menuButton = new Button(new ElementId("tilePacksMenu", "menuButton")) + { + Text = translation.GetTranslatedName("menu_button"), + TextSize = 24.0f, + Size = new Vector2(256.0f, 32.0f), + TextAlignment = Alignment.Center + }; + _menuButton.OnClick += () => + { + ScreenManager.Switch(new MenuScreen()); + }; + Elements.Add(_menuButton); + + SetTilePackActive(TilePackManager.Find(Settings.CurrentTilePack), true); + + Configure(); + } + + private void SetTilePackActive(TilePack tilePack, bool isActive) + { + if (string.IsNullOrWhiteSpace(tilePack.Path)) return; + + var index = Array.IndexOf(_paths, tilePack.Path); + if (index < 0) return; + + _tilePacksList.Items[index] = (isActive ? "> " : string.Empty) + tilePack.Info.Name; + } + + public override void Configure() + { + _tilePacksList.Size = new Vector2(GetScreenWidth() / 3.0f, GetScreenHeight() - _menuButton.Size.Y - 8.0f); + _tilePacksList.GlobalPosition = new Vector2(GetScreenWidth() / 2.0f - _tilePacksList.Size.X / 2.0f, 0.0f); + + _menuButton.GlobalPosition = new Vector2(GetScreenWidth() / 2.0f - _menuButton.Size.X / 2.0f, + GetScreenHeight() - _menuButton.Size.Y - 8.0f); + } + + public override void Resized() + { + Configure(); + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/UIInterface.cs b/Sources/UI/Interfaces/UIInterface.cs new file mode 100644 index 0000000..43a082c --- /dev/null +++ b/Sources/UI/Interfaces/UIInterface.cs @@ -0,0 +1,71 @@ +using System.Numerics; + +namespace BuildingGame.UI.Interfaces; + +public class UIInterface +{ + public bool Visible + { + get => _visible; + set + { + _visible = value; + foreach (var el in Elements) + { + el.Visible = value; + } + } + } + + public bool Active + { + get => _active; + set + { + _active = value; + foreach (var el in Elements) + { + el.Active = value; + } + } + } + + public bool CanIgnorePause => IgnorePause; + + protected List Elements = new List(); + protected Vector2 Position; + protected bool IgnorePause; + private bool _visible = true; + private bool _active = true; + + public UIInterface() + { + UIInterfaceManager.Add(this); + } + + public virtual void Initialize() { } + + public virtual void Configure() { } + + public virtual void Update() + { + if (IsWindowResized()) Resized(); + if (!_active) return; + } + public virtual void Resized() { } + + public virtual void Destroy() + { + DestroyElements(); + } + + protected void DestroyElements() + { + foreach (var el in Elements) + { + GuiManager.Remove(el); + } + + Elements.Clear(); + } +} \ No newline at end of file diff --git a/Sources/UI/Interfaces/UIInterfaceManager.cs b/Sources/UI/Interfaces/UIInterfaceManager.cs new file mode 100644 index 0000000..e9d80ba --- /dev/null +++ b/Sources/UI/Interfaces/UIInterfaceManager.cs @@ -0,0 +1,37 @@ +namespace BuildingGame.UI.Interfaces; + +public static class UIInterfaceManager +{ + private static List _interfaces = new List(); + + public static int Add(UIInterface uiInterface) + { + _interfaces.Add(uiInterface); + return _interfaces.Count - 1; + } + + public static void Remove(int id) + { + _interfaces.RemoveAt(id); + } + + public static void Initialize() + { + foreach (var i in _interfaces) i.Initialize(); + } + + public static void Update() + { + foreach (var i in _interfaces) + { + if (!i.CanIgnorePause && Program.Paused) continue; + i.Update(); + } + } + + public static void Destroy() + { + foreach (var i in _interfaces) i.Destroy(); + _interfaces.Clear(); + } +} diff --git a/Sources/UI/Interfaces/WorldMenuUI.cs b/Sources/UI/Interfaces/WorldMenuUI.cs new file mode 100644 index 0000000..ae3fa1b --- /dev/null +++ b/Sources/UI/Interfaces/WorldMenuUI.cs @@ -0,0 +1,170 @@ +using System.Numerics; +using BuildingGame.Tiles; +using BuildingGame.Translation; +using BuildingGame.UI.Brushes; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Screens; + +namespace BuildingGame.UI.Interfaces; + +public class WorldMenuUI : UIInterface +{ + private ListBox _worldList; + private Panel _sidePanel; + private Panel _bottomPanel; + private TextElement _selectedWorldTitle; + private TextElement _selectedWorldPlayTime; + private Button _playWorldButton; + private Button _menuButton; + private Button _createWorldButton; + private Button _deleteWorldButton; + + private string[] _paths; + + public override void Initialize() + { + var translation = TranslationContainer.Default; + + _paths = WorldManager.Worlds.Select(w => w.Path).ToArray(); + + _worldList = new ListBox(new ElementId("worldScreen", "list")) + { + ItemTextSize = 20.0f, + GlobalPosition = new Vector2(0.0f, 0.0f), + BackgroundBrush = null, + ItemColor = Color.White, + Items = WorldManager.Worlds.Select(w => $"{w.Info.Name} ['{Path.GetFileName(w.Path)}']").ToList() + }; + _worldList.OnItemSelect += (item) => + { + if (string.IsNullOrWhiteSpace(item)) + { + _selectedWorldTitle.Text = translation.GetTranslatedName("default_world_title"); + + _selectedWorldPlayTime.Visible = false; + _playWorldButton.Visible = false; + _deleteWorldButton.Visible = false; + + return; + } + + _selectedWorldTitle.Text = item; + + _selectedWorldPlayTime.Visible = true; + _playWorldButton.Visible = true; + _deleteWorldButton.Visible = true; + + var world = WorldManager.Find(_paths[_worldList.SelectedItem]); + _selectedWorldPlayTime.Text = string.Format(translation.GetTranslatedName("play_time_format"), world.Info.PlayTime); + }; + Elements.Add(_worldList); + + _sidePanel = new Panel(new ElementId("worldScreen", "sidePanel")); + Elements.Add(_sidePanel); + + _bottomPanel = new Panel(new ElementId("worldScreen", "bottomPanel")); + Elements.Add(_bottomPanel); + + _selectedWorldTitle = new TextElement(new ElementId(_sidePanel.Id, "title")) + { + Text = translation.GetTranslatedName("default_world_title"), + TextSize = 28.0f, + TextAlignment = Alignment.Center, + Parent = _sidePanel + }; + + _selectedWorldPlayTime = new TextElement(new ElementId(_sidePanel.Id, "playTime")) + { + Text = string.Format(translation.GetTranslatedName("play_time_format"), TimeSpan.Zero), + TextSize = 20.0f, + TextAlignment = Alignment.TopCenter, + Parent = _sidePanel, + LocalPosition = new Vector2(0.0f, 40.0f), + Visible = false + }; + + _playWorldButton = new Button(new ElementId(_sidePanel.Id, "playButton")) + { + Text = translation.GetTranslatedName("play_button"), + TextSize = 32.0f, + TextAlignment = Alignment.Center, + Parent = _sidePanel, + LocalPosition = new Vector2(0.0f, 100.0f), + Visible = false + }; + _playWorldButton.OnClick += () => + { + ScreenManager.Switch(new GameScreen(_paths[_worldList.SelectedItem])); + }; + + _menuButton = new Button(new ElementId(_bottomPanel.Id, "menuButton")) + { + Text = translation.GetTranslatedName("menu_button"), + TextSize = 24.0f, + Size = new Vector2(64.0f, 28.0f), + TextAlignment = Alignment.Center, + Parent = _bottomPanel + }; + _menuButton.OnClick += () => + { + ScreenManager.Switch(new MenuScreen()); + }; + + _createWorldButton = new Button(new ElementId(_bottomPanel.Id, "createWorldButton")) + { + Text = translation.GetTranslatedName("create_world_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f), + TextAlignment = Alignment.Center, + Parent = _bottomPanel + }; + _createWorldButton.OnClick += () => + { + ScreenManager.Switch(new CreateWorldScreen()); + }; + + _deleteWorldButton = new Button(new ElementId(_bottomPanel.Id, "deleteWorldButton")) + { + Text = translation.GetTranslatedName("delete_world_button"), + TextSize = 24.0f, + Size = new Vector2(100.0f, 28.0f), + TextAlignment = Alignment.Center, + Visible = false, + Parent = _bottomPanel, + }; + _deleteWorldButton.OnClick += () => + { + var world = WorldManager.Find(_paths[_worldList.SelectedItem]); + WorldManager.DeleteWorld(world); + _worldList.Items.RemoveAt(_worldList.SelectedItem); + _deleteWorldButton.Visible = false; + }; + + Configure(); + } + + public override void Configure() + { + _worldList.Size = new Vector2(GetScreenWidth() / 2.0f, GetScreenHeight() - 64.0f); + + _bottomPanel.Size = new Vector2(GetScreenWidth(), 64.0f); + _bottomPanel.GlobalPosition = new Vector2(0.0f, GetScreenHeight() - 64.0f); + + _sidePanel.Size = (new Vector2(GetScreenWidth(), GetScreenHeight()) - _worldList.Size) with { Y = GetScreenHeight() - _bottomPanel.Size.Y }; + _sidePanel.GlobalPosition = _worldList.Size with { Y = 0 }; + + _selectedWorldTitle.Size = new Vector2(_sidePanel.Size.X, 40.0f); + _selectedWorldPlayTime.Size = new Vector2(_sidePanel.Size.X, 24.0f); + _playWorldButton.Size = new Vector2(_sidePanel.Size.X, 36.0f); + + _menuButton.LocalPosition = new Vector2(_bottomPanel.Size.X - _menuButton.Size.X - 32.0f, _bottomPanel.Size.Y / 2 - _menuButton.Size.Y / 2); + + _createWorldButton.LocalPosition = new Vector2(8.0f, _bottomPanel.Size.Y / 2 - _createWorldButton.Size.Y / 2); + _deleteWorldButton.LocalPosition = _createWorldButton.LocalPosition + _createWorldButton.Size with { Y = 0 } + new Vector2(16.0f, 0.0f); + } + + public override void Resized() + { + Configure(); + } +} \ No newline at end of file diff --git a/Sources/UI/Screens/CreateWorldScreen.cs b/Sources/UI/Screens/CreateWorldScreen.cs new file mode 100644 index 0000000..16d4975 --- /dev/null +++ b/Sources/UI/Screens/CreateWorldScreen.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; +public class CreateWorldScreen : Screen +{ + private CreateWorldUI _ui; + + public override void Initialize() + { + base.Initialize(); + + _ui = new CreateWorldUI(); + } + + public override void Draw() + { + ClearBackground(new Color(20, 20, 20, 255)); + + base.Draw(); + } +} diff --git a/Sources/UI/Screens/GameScreen.cs b/Sources/UI/Screens/GameScreen.cs new file mode 100644 index 0000000..58a49cd --- /dev/null +++ b/Sources/UI/Screens/GameScreen.cs @@ -0,0 +1,80 @@ +using BuildingGame.Tiles; +using BuildingGame.Tiles.Data; +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public class GameScreen : Screen +{ + private World _world; + private WorldInfo _info; + + private Player _player; + private BlockUI _blockUi; + private GameUI _ui; + private PauseUI _pause; + private TimeSpan _playTime = TimeSpan.Zero; + + public GameScreen(string worldPath) + { + _info = WorldManager.Find(worldPath); + _playTime = _info.Info.PlayTime; + } + + public override void Initialize() + { + base.Initialize(); + + _world = new World(); + WorldManager.LoadWorld(ref _world, _info.Path); + + _player = new Player(_world, _world.PlayerPosition, _world.PlayerZoom, 50, 0.1f); + + _blockUi = new BlockUI(); + _ui = new GameUI(_blockUi); + _pause = new PauseUI(_blockUi); + } + + public override void Update() + { + base.Update(); + + if (IsKeyPressed(KeyboardKey.R)) + Tiles.Tiles.Reload(); + + if (!Program.Paused) + { + _playTime += TimeSpan.FromSeconds(GetFrameTime()); + + _player.Update(); + _world.Update(); + } + } + + public override void Draw() + { + ClearBackground(Settings.SkyColor); + + BeginMode2D(_player.Camera); + { + _world.Draw(_player); + _player.Draw(); + } + EndMode2D(); + + base.Draw(); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) return; + + _player.PushCameraInfo(); + + _info.Info = new WorldInfo.InfoRecord(_info.Info.Name, _playTime); + + WorldManager.WriteWorld(_world, _info); + } +} \ No newline at end of file diff --git a/Sources/UI/Screens/MenuScreen.cs b/Sources/UI/Screens/MenuScreen.cs new file mode 100644 index 0000000..6fab343 --- /dev/null +++ b/Sources/UI/Screens/MenuScreen.cs @@ -0,0 +1,23 @@ +using BuildingGame.UI.Elements; +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public class MenuScreen : Screen +{ + private MenuUI _ui; + + public override void Initialize() + { + base.Initialize(); + + _ui = new MenuUI(); + } + + public override void Draw() + { + ClearBackground(new Color(20, 20, 20, 255)); + + base.Draw(); + } +} \ No newline at end of file diff --git a/Sources/UI/Screens/Screen.cs b/Sources/UI/Screens/Screen.cs new file mode 100644 index 0000000..b95eb3d --- /dev/null +++ b/Sources/UI/Screens/Screen.cs @@ -0,0 +1,82 @@ +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public class Screen : IDisposable +{ + public static readonly ElementComparer Comparer = new ElementComparer(); + + public bool IsCurrent + { + get => ReferenceEquals(this, ScreenManager.CurrentScreen); + set => ScreenManager.CurrentScreen = this; + } + + public List Elements = new List(); + public IOrderedEnumerable ElementsSorted => Elements.OrderBy(e => e, Comparer); + + public List Interfaces = new List(); + + public virtual void Initialize() + { + + } + + public virtual void Update() + { + for (int i = 0; i < Elements.Count; i++) + { + // handle list modifying + if (i >= Elements.Count) break; + + var element = Elements[i]; + if (!element.Active) continue; + + element.Update(); + } + + for (int i = 0; i < Interfaces.Count; i++) + { + // handle list modifying + if (i >= Interfaces.Count) break; + + Interfaces[i].Update(); + } + } + + public virtual void Draw() + { + foreach (var el in ElementsSorted) + { + if (!el.Active || !el.Visible) continue; + el.Draw(); + } + } + + protected virtual void Dispose(bool disposing) + { + + } + + public void Dispose() + { + for (int i = 0; i < Elements.Count; i++) + { + // handle list modifying + if (i >= Elements.Count) break; + + Elements[i].Dispose(); + } + + for (int i = 0; i < Interfaces.Count; i++) + { + // handle list modifying + if (i >= Interfaces.Count) break; + + Interfaces[i].Destroy(); + } + + Dispose(true); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/Sources/UI/Screens/ScreenManager.cs b/Sources/UI/Screens/ScreenManager.cs new file mode 100644 index 0000000..699c1d5 --- /dev/null +++ b/Sources/UI/Screens/ScreenManager.cs @@ -0,0 +1,40 @@ +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public static class ScreenManager +{ + public static Screen? CurrentScreen; + + public static void Switch(Screen newScreen) + { + UIInterfaceManager.Destroy(); + Free(); + + newScreen.IsCurrent = true; + Initialize(); + UIInterfaceManager.Initialize(); + } + + public static void Initialize() + { + CurrentScreen?.Initialize(); + } + + public static void Update() + { + CurrentScreen?.Update(); + } + + public static void Draw() + { + CurrentScreen?.Draw(); + } + + public static void Free() + { + CurrentScreen?.Dispose(); + CurrentScreen = null; + } + +} \ No newline at end of file diff --git a/Sources/UI/Screens/TilePacksScreen.cs b/Sources/UI/Screens/TilePacksScreen.cs new file mode 100644 index 0000000..c7d26cc --- /dev/null +++ b/Sources/UI/Screens/TilePacksScreen.cs @@ -0,0 +1,24 @@ +using BuildingGame.Tiles.Packs; +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public class TilePacksScreen : Screen +{ + private TilePacksMenuUI _tilePacksMenu; + + public override void Initialize() + { + base.Initialize(); + + TilePackManager.Load(); + _tilePacksMenu = new TilePacksMenuUI(); + } + + public override void Draw() + { + ClearBackground(new Color(20, 20, 20, 255)); + + base.Draw(); + } +} \ No newline at end of file diff --git a/Sources/UI/Screens/WorldSelectionScreen.cs b/Sources/UI/Screens/WorldSelectionScreen.cs new file mode 100644 index 0000000..35f30bc --- /dev/null +++ b/Sources/UI/Screens/WorldSelectionScreen.cs @@ -0,0 +1,25 @@ +using BuildingGame.Tiles; +using BuildingGame.UI.Elements; +using BuildingGame.UI.Interfaces; + +namespace BuildingGame.UI.Screens; + +public class WorldSelectionScreen : Screen +{ + private WorldMenuUI _ui; + + public override void Initialize() + { + base.Initialize(); + + WorldManager.ReadWorlds(); + _ui = new WorldMenuUI(); + } + + public override void Draw() + { + ClearBackground(new Color(20, 20, 20, 255)); + + base.Draw(); + } +} \ No newline at end of file diff --git a/TilePacks/TilePack.cs b/TilePacks/TilePack.cs deleted file mode 100644 index 6b590a6..0000000 --- a/TilePacks/TilePack.cs +++ /dev/null @@ -1,83 +0,0 @@ -namespace BuildingGame.TilePacks; - -public struct TilePack -{ - public const byte PACK_FORMAT = 0; - - public string Root { get; } = string.Empty; - public string Name { get; } = string.Empty; - public byte Version { get; } = 0; - public bool IsVanilla { get; } = true; - - public string AtlasPath { get; } = string.Empty; - public string TileAtlasPath { get; } = string.Empty; - - internal Texture2D Atlas { get; set; } - - public TilePack(string path) - { - Atlas = Program.atlas; - try - { - Root = path; - - // try to get some data from pack - if (Check(path)) - { - // parse info file - var data = ConfigParser.Parse(Path.Combine(path, "info.txt")); - - // get pack name - if (data.TryGetValue("name", out var name)) Name = name; - // if pack is vanilla, it won't use custom atlas.txt - if (data.TryGetValue("isVanilla", out var isVanillaStr) && - bool.TryParse(isVanillaStr, out var isVanilla)) IsVanilla = isVanilla; - if (data.TryGetValue("supportedVersion", out var supportedVersionStr) && - byte.TryParse(supportedVersionStr, out var supportedVersion)) - Version = supportedVersion; - } - else - { - Log.Error("invalid pack. check if pack is valid using TilePack.Check(string) before creating pack"); - return; - } - // try get atlas - if (File.Exists(Path.Combine(path, "atlas.png"))) - { - AtlasPath = Path.Combine(path, "atlas.png"); - Atlas = LoadTexture(AtlasPath); - } - // try get tile atlas or ignore if pack is vanilla - if (File.Exists(Path.Combine(path, "atlas.txt")) && !IsVanilla) - TileAtlasPath = Path.Combine(path, "atlas.txt"); - } - catch (Exception ex) { Log.Error(ex.ToString()); } - } - - public static bool Check(string path) - { - return File.Exists(Path.Combine(path, "info.txt")); - } - - public void Apply() - { - if (AtlasPath != string.Empty) - { - Program.atlas = Atlas; - } - if (TileAtlasPath != string.Empty && !IsVanilla) - { - Tile.DefaultTiles = Tile.GenerateTiles(TileAtlasPath); - } - } - - public void Unload() - { - UnloadTexture(Atlas); - } - - public override string ToString() - { - return $"Name: {Name}; IsVanilla: {IsVanilla}; PackFormat: {Version}"; - } -} \ No newline at end of file diff --git a/TilePacks/TilePackManager.cs b/TilePacks/TilePackManager.cs deleted file mode 100644 index 8ffd4a2..0000000 --- a/TilePacks/TilePackManager.cs +++ /dev/null @@ -1,47 +0,0 @@ -using BuildingGame.Tiles; - -namespace BuildingGame.TilePacks; - -public static class TilePackManager -{ - public static List TilePacks { get; } = new List(); - public static event Action? PackChanged; - - public static void LoadPacks(string root = "packs") - { - TilePacks.Clear(); - - if (!Directory.Exists(root)) - { - Directory.CreateDirectory(root); - return; - } - - foreach (var packFolder in Directory.GetDirectories(root).Where(s => TilePack.Check(s))) - { - var pack = new TilePack(packFolder); - Log.Information(pack.ToString()); - TilePacks.Add(pack); - } - } - - public static void SetDefaultPack() - { - Program.atlas = Program.origAtlas; - Tile.DefaultTiles = Tile.GenerateTiles(); - PackChanged?.Invoke(); - } - - public static void ApplyPack(string name) - { - var pack = TilePacks.Find(m => m.Name == name); - if (pack.Equals(default(TilePack))) return; - pack.Apply(); - PackChanged?.Invoke(); - } - - internal static void UnloadPacks() - { - foreach (var pack in TilePacks) pack.Unload(); - } -} \ No newline at end of file diff --git a/Tiles/Chunk.cs b/Tiles/Chunk.cs deleted file mode 100644 index 3c471a8..0000000 --- a/Tiles/Chunk.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace BuildingGame.Tiles; - -public class Chunk -{ - public const int SIZE = 16; - - public TileInfo[,] Tiles { get; } - - public Chunk() - { - Tiles = new TileInfo[SIZE, SIZE]; - - for (int x = 0; x < SIZE; x++) - { - for (int y = 0; y < SIZE; y++) - { - Tiles[x, y] = 0; - } - } - } - - public void Draw(int wx, int wy) - { - for (int x = 0; x < SIZE; x++) - { - for (int y = 0; y < SIZE; y++) - { - TileInfo tile = Tiles[x, y]; - - if (tile != null && tile > 0) - { - Tile.GetTile(tile.Type).Draw(wx + x, wy + y, tile.Flags); - } - - } - } - } - - public bool IsValidTile(int x, int y) - { - return x >= 0 && x < SIZE && y >= 0 && y < SIZE; - } -} \ No newline at end of file diff --git a/Tiles/PhysicTileType.cs b/Tiles/PhysicTileType.cs deleted file mode 100644 index feeaccf..0000000 --- a/Tiles/PhysicTileType.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace BuildingGame.Tiles; - -public enum PhysicTileType -{ - Not, - Water, - Lava, - Sand, - Obsidian, - SandCook, - Infection -} \ No newline at end of file diff --git a/Tiles/Tile.cs b/Tiles/Tile.cs deleted file mode 100644 index 24f9908..0000000 --- a/Tiles/Tile.cs +++ /dev/null @@ -1,92 +0,0 @@ -namespace BuildingGame.Tiles; - -public class Tile -{ - internal static Tile[] DefaultTiles = GenerateTiles(); - internal static readonly Texture2D Unknown = LoadTextureFromImage(GenImageChecked(48, 48, 24, 24, Color.MAGENTA, Color.BLACK)); - - public static Tile GetTile(byte type) - { - if (type - 1 > DefaultTiles.Length - 1) - { - return new Tile(Vector2.Zero, "unknown", "?") { IsUnknown = true }; - } - return DefaultTiles[type - 1]; - } - public static Tile GetTile(string id) => DefaultTiles.First(t => t.Id.ToLower() == id.ToLower()); - public static byte GetNId(string id) => (byte)((byte)Array.IndexOf(DefaultTiles, GetTile(id)) + 1); - public static string GetId(byte id) => GetTile(id).Id; - - public Vector2 AtlasOffset { get; } - public Vector2 Size { get; } - public string Id { get; } - public string DisplayName { get; } - public bool IsUnknown { get; set; } - - public Tile(Vector2 atlasOffset, string id, string displayName) - { - AtlasOffset = atlasOffset; - Size = new Vector2(1, 1); - Id = id; - DisplayName = displayName; - } - - public Tile(Vector2 atlasOffset, Vector2 size, string id, string displayName) - { - AtlasOffset = atlasOffset; - Size = size; - Id = id; - DisplayName = displayName; - } - - public void Draw(int x, int y, TileFlags flags) - { - Draw(x, y, flags, Color.WHITE); - } - - public void Draw(int x, int y, TileFlags flags, Color tint) - { - if (IsUnknown) - { - DrawTexturePro( - Unknown, - new Rectangle(0.25f, 0.25f, 48 - 0.25f, 48 - 0.25f), - new Rectangle(x * 48, y * 48, Size.X * 48, Size.Y * 48), - new Vector2(24, 24), - flags.Rotation, - tint - ); - return; - } - DrawTexturePro( - Program.atlas, - new Rectangle(AtlasOffset.X * 16 + 0.25f, AtlasOffset.Y * 16 + 0.25f, Size.X * 16 - 0.25f, Size.Y * 16 - 0.25f), - new Rectangle(x * 48, y * 48, Size.X * 48, Size.Y * 48), - new Vector2(24, 24), - flags.Rotation, - tint - ); - } - - internal static Tile[] GenerateTiles(string atlasText = "assets/atlas.txt") - { - List tiles = new List(); - var ids = ConfigParser.ParseFourth(atlasText); - - for (int i = 0; i < ids.Count; i++) - { - var id = ids.Keys.ToArray()[i]; - var kv = ids[id]; - var coords = ConfigParser.ToVector2i(kv.left); - var size = ConfigParser.ToVector2i(kv.middle); - var dn = kv.right; - - tiles.Add(new Tile(coords, size, id, dn)); - } - - return tiles.ToArray(); - } - - public static implicit operator Tile(string id) => Tile.GetTile(id); - public static implicit operator Tile(byte nid) => Tile.GetTile(nid); -} \ No newline at end of file diff --git a/Tiles/TileFlags.cs b/Tiles/TileFlags.cs deleted file mode 100644 index b2cb02d..0000000 --- a/Tiles/TileFlags.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace BuildingGame.Tiles; - -public struct TileFlags -{ - public static TileFlags Default => new TileFlags(0, false); - public float Rotation { get; set; } - public bool Flip { get; set; } - - public TileFlags(float rotation, bool flip) - { - Rotation = rotation; - Flip = flip; - } -} \ No newline at end of file diff --git a/Tiles/TileInfo.cs b/Tiles/TileInfo.cs deleted file mode 100644 index 7f81ab6..0000000 --- a/Tiles/TileInfo.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace BuildingGame.Tiles; - -public class TileInfo -{ - public byte Type { get; } - public TileFlags Flags { get; } - - public TileInfo(byte type, TileFlags flags) - { - Type = type; - Flags = flags; - } - - public static implicit operator byte(TileInfo info) => info.Type; - public static implicit operator string(TileInfo info) - { - if (info.Type > 0) return Tile.GetId(info.Type); - return ""; - } - public static implicit operator Tile(TileInfo info) => Tile.GetTile(info.Type); - public static implicit operator TileInfo(byte type) => new TileInfo(type, TileFlags.Default); - public static implicit operator TileInfo(string id) => new TileInfo(Tile.GetNId(id), TileFlags.Default); -} \ No newline at end of file diff --git a/Tiles/World.cs b/Tiles/World.cs deleted file mode 100644 index 4d0e8b5..0000000 --- a/Tiles/World.cs +++ /dev/null @@ -1,368 +0,0 @@ -using System.Diagnostics; -using System.IO.Compression; - -namespace BuildingGame.Tiles; - -public class World -{ - public const string WORLD_SAVE_HEADER = "BGWORLD2"; - public string WorldFile { get; set; } = "level.dat"; - public const int CHUNK_AREA = 16; - public Chunk[,] Chunks { get; private set; } = null!; - - public World() - { - FlushChunks(); - } - - public void Draw() - { - for (int cx = 0; cx < CHUNK_AREA; cx++) - { - for (int cy = 0; cy < CHUNK_AREA; cy++) - { - var chunk = Chunks[cx, cy]; - - if (chunk != null) - chunk.Draw(cx * 16, cy * 16); - } - } - } - - List<(int x, int y)> liquidBlocks = new List<(int x, int y)>(); - private void UpdateLiqiud(string id) - { - - for (int x = 0; x < CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < CHUNK_AREA * Chunk.SIZE; y++) - { - if (IsTileA(x, y, id)) - { - liquidBlocks.Add((x, y)); - } - } - } - - foreach (var liquidBlock in liquidBlocks) - { - int x = liquidBlock.x; - int y = liquidBlock.y; - - if (IsTileA(x - 1, y, 0) && !IsTileAOrAir(x, y + 1, id)) - PlaceTile(x - 1, y, id); - else if (IsTileA(x + 1, y, 0) && !IsTileAOrAir(x, y + 1, id)) - PlaceTile(x + 1, y, id); - else if (IsTileA(x, y + 1, 0)) - PlaceTile(x, y + 1, id); - } - - liquidBlocks.Clear(); - } - - List<(int x, int y)> lavaBlocks = new List<(int x, int y)>(); - - public void UpdateObsidian() - { - - for (int x = 0; x < CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < CHUNK_AREA * Chunk.SIZE; y++) - { - if (IsTileA(x, y, "lava")) - { - lavaBlocks.Add((x, y)); - } - } - } - - foreach (var lavaBlock in lavaBlocks) - { - int x = lavaBlock.x; - int y = lavaBlock.y; - - if (CanBeCooked(x, y, "water", "lava")) - PlaceTile(x, y, "obsidian"); - } - - lavaBlocks.Clear(); - } - - List<(int x, int y)> sandBlocks = new List<(int x, int y)>(); - - public void UpdateSand() - { - - for (int x = 0; x < CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < CHUNK_AREA * Chunk.SIZE; y++) - { - if (IsTileA(x, y, "sand")) - { - sandBlocks.Add((x, y)); - } - } - } - - foreach (var sandBlock in sandBlocks) - { - int x = sandBlock.x; - int y = sandBlock.y; - - if (IsTileAOrAir(x, y + 1, "water")) - { - PlaceTile(x, y + 1, Tile.GetNId("sand")); - PlaceTile(x, y, 0); - } - if (CanBeCooked(x, y, "lava", "sand")) - { - PlaceTile(x, y, Tile.GetNId("glass")); - } - } - - sandBlocks.Clear(); - } - - List<(int x, int y)> infectionBlocks = new List<(int x, int y)>(); - - public void UpdateInfection() - { - for (int x = 0; x < CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < CHUNK_AREA * Chunk.SIZE; y++) - { - if (IsTileA(x, y, "infection_block")) - { - infectionBlocks.Add((x, y)); - } - } - } - - foreach (var infectionBlock in infectionBlocks) - { - int x = infectionBlock.x; - int y = infectionBlock.y; - - if (!IsTileAOrAir(x - 1, y, "infection_block")) - PlaceTile(x - 1, y, Tile.GetNId("infection_block")); - if (!IsTileAOrAir(x + 1, y, "infection_block")) - PlaceTile(x + 1, y, Tile.GetNId("infection_block")); - if (!IsTileAOrAir(x, y - 1, "infection_block")) - PlaceTile(x, y - 1, Tile.GetNId("infection_block")); - if (!IsTileAOrAir(x, y + 1, "infection_block")) - PlaceTile(x, y + 1, Tile.GetNId("infection_block")); - } - - infectionBlocks.Clear(); - } - - private bool CanBeCooked(int x, int y, string idSecond, string idMain) - { - if (((IsTileA(x - 1, y, idSecond) || IsTileA(x + 1, y, idSecond)) || - (IsTileA(x, y - 1, idSecond) || IsTileA(x, y + 1, idSecond))) && - IsTileA(x, y, idMain)) - return true; - return false; - } - - byte tick = 0; - - public void Update() - { - if (tick >= 20) tick = 0; - tick++; - - if (!Settings.EnablePhysics) return; - - if (tick == 7) UpdateObsidian(); - if (tick == 9 && Settings.EnableInfectionBlock) UpdateInfection(); - if (tick == 15) UpdateSand(); - if (tick == 10) UpdateLiqiud("water"); - if (tick == 20) UpdateLiqiud("lava"); - } - - public bool IsTileA(int x, int y, string id) - { - return IsValidTile(x, y) && GetTile(x, y) == id; - } - - public bool IsTileA(int x, int y, byte nid) - { - return IsValidTile(x, y) && GetTile(x, y) == nid; - } - - public bool IsTileAOrAir(int x, int y, string id) - { - return IsValidTile(x, y) && (GetTile(x, y) == id || GetTile(x, y) == 0); - } - - public bool IsTileAOrAir(int x, int y, byte nid) - { - return IsValidTile(x, y) && (GetTile(x, y) == nid || GetTile(x, y) == 0); - } - - public void PlaceTile(int x, int y, TileInfo tile) - { - var area = GetChunkArea(x, y); - var chunk = Chunks[area.cx, area.cy]; - - if (chunk != null) - { - chunk.Tiles[area.lcx, area.lcy] = tile; - } - - } - - public void PlaceTile(int cx, int cy, int x, int y, TileInfo tile) - { - var chunk = Chunks[cx, cy]; - - if (chunk != null) - { - chunk.Tiles[x, y] = tile; - } - - } - - public TileInfo GetTile(int x, int y) - { - var area = GetChunkArea(x, y); - var chunk = Chunks[area.cx, area.cy]; - - if (chunk != null) - return chunk.Tiles[area.lcx, area.lcy]; - return 0; - } - - public bool IsValidTile(int x, int y) - { - if (x < 0 || x >= CHUNK_AREA * Chunk.SIZE || y < 0 || y >= CHUNK_AREA * Chunk.SIZE) - return false; - return true; - } - - public TileInfo GetTile(int cx, int cy, int x, int y) - { - var chunk = Chunks[cx, cy]; - if (chunk != null) - return chunk.Tiles[x, y]; - return 0; - } - - public (int cx, int cy, int lcx, int lcy) GetChunkArea(int wx, int wy) - { - - int cx = Math.Clamp((int)(wx / Chunk.SIZE), 0, CHUNK_AREA - 1); - int cy = Math.Clamp((int)(wy / Chunk.SIZE), 0, CHUNK_AREA - 1); - - int lcx = Math.Clamp((wx - Chunk.SIZE * cx), 0, Chunk.SIZE - 1); - int lcy = Math.Clamp((wy - Chunk.SIZE * cy), 0, Chunk.SIZE - 1); - - return (cx, cy, lcx, lcy); - } - - public void Save() - { - try - { - using FileStream fs = File.OpenWrite(WorldFile); - using GZipStream gs = new GZipStream(fs, CompressionMode.Compress); - using BinaryWriter bw = new BinaryWriter(gs); - - bw.Write(WORLD_SAVE_HEADER); - - bw.Write(Program.gameScreen.camera.target.X); - bw.Write(Program.gameScreen.camera.target.Y); - - for (int x = 0; x < World.CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < World.CHUNK_AREA * Chunk.SIZE; y++) - { - WriteTile(bw, GetTile(x, y)); - } - } - } - catch (Exception ex) { Log.Information(ex.ToString()); } - } - - private void FlushChunks() - { - Chunks = new Chunk[CHUNK_AREA, CHUNK_AREA]; - - for (int x = 0; x < CHUNK_AREA; x++) - { - for (int y = 0; y < CHUNK_AREA; y++) - { - Chunks[x, y] = new Chunk(); - } - } - } - - public void Load(string path = "level.dat") - { - try - { - WorldFile = path; - - FlushChunks(); - if (!File.Exists(path)) return; - - using FileStream fs = File.OpenRead(path); - using GZipStream gs = new GZipStream(fs, CompressionMode.Decompress); - using BinaryReader br = new BinaryReader(gs); - string header = br.ReadString(); - // throw new Exception("test"); - - if (header == "LVL") - { - Log.Information("world using old format, reading it using it"); - Log.Information("creating backup"); - - using FileStream backupFs = File.OpenWrite(path + ".old"); - using BinaryWriter backupBw = new BinaryWriter(backupFs); - backupBw.Write("LVL"); - - Log.Information("reading world"); - - for (int x = 0; x < World.CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < World.CHUNK_AREA * Chunk.SIZE; y++) - { - byte tile = br.ReadByte(); - PlaceTile(x, y, tile); - backupBw.Write(tile); - } - } - - Log.Information("success"); - } - else if (header != WORLD_SAVE_HEADER) throw new IOException("world header is invalid"); - else - { - Program.gameScreen.camera.target = new Vector2(br.ReadSingle(), br.ReadSingle()); - for (int x = 0; x < World.CHUNK_AREA * Chunk.SIZE; x++) - { - for (int y = 0; y < World.CHUNK_AREA * Chunk.SIZE; y++) - { - PlaceTile(x, y, ReadTile(br)); - } - } - } - - } - catch (IOException ex) { Log.Information("bad world file\n" + ex); } - catch (Exception ex) { Log.Information(ex.ToString()); } - } - - private void WriteTile(BinaryWriter bw, TileInfo tile) - { - bw.Write(tile.Type); - bw.Write(tile.Flags.Rotation); - bw.Write(tile.Flags.Flip); - } - - private TileInfo ReadTile(BinaryReader br) - { - return new TileInfo(br.ReadByte(), new TileFlags(br.ReadSingle(), br.ReadBoolean())); - } -} \ No newline at end of file diff --git a/assets/atlas.txt b/assets/atlas.txt deleted file mode 100644 index fb1c98d..0000000 --- a/assets/atlas.txt +++ /dev/null @@ -1,41 +0,0 @@ -smooth_stone:0,0:1,1:Smooth Stone -wooden_planks:1,0:1,1:Wooden Planks -dirt:2,0:1,1:Dirt -grass_block:3,0:1,1:Grass Block -grass:4,0:1,1:Grass -sand:5,0:1,1:Sand -water:6,0:1,1:Water -lava:7,0:1,1:Lava -obsidian:8,0:1,1:Obsidian -door:9,0:1,2:Door -nature_stone:0,1:1,1:Nature Stone -white_plate:1,1:1,1:White Plate -green_plate:2,1:1,1:Green Plate -red_plate:3,1:1,1:Red Plate -blue_plate:4,1:1,1:Blue Plate -chamomile:5,1:1,1:Chamomile -red_tulip:6,1:1,1:Red Tulip -chamomile_pot:7,1:1,1:Chamomile in a Pot -red_tulip_pot:8,1:1,1:Red Tulip in a Pot -wooden_stairs:0,2:1,1:Wooden Stairs -wooden_slab:1,2:1,1:Wooden Slab -stone_stairs:2,2:1,1:Stone Stairs -stone_slab:3,2:1,1:Stone Slab -wooden_pole:4,2:1,1:Wooden Pole -wooden_pole_handle:5,2:1,1:Wooden Pole Handle -stone_pole:6,2:1,1:Stone Pole -stone_pole_handle:7,2:1,1:Stone Pole Handle -log:8,2:1,1:Log -log_top:9,2:1,1:Log Top -foliage:0,3:1,1:Foliage -glass:1,3:1,1:Glass -white_wool:2,3:1,1:White Wool -red_wool:3,3:1,1:Red Wool -green_wool:4,3:1,1:Green Wool -blue_wool:5,3:1,1:Blue Wool -yellow_wool:6,3:1,1:Yellow Wool -black_wool:7,3:1,1:Black Wool -dark_gray_wool:8,3:1,1:Dark Gray Wool -gray_wool:9,3:1,1:Gray Wool -sponge:0,4:1,1:Sponge -infection_block:1,4:1,1:Infection \ No newline at end of file diff --git a/assets/font_old.ttf b/assets/font_old.ttf deleted file mode 100644 index ca2d06e..0000000 Binary files a/assets/font_old.ttf and /dev/null differ