1
0
Fork 0
mirror of https://github.com/Ellpeck/MLEM.git synced 2024-05-09 02:58:45 +02:00

Compare commits

...

73 commits

Author SHA1 Message Date
Ell 8f2dc2e507 removed woodpecker
(cherry picked from commit 604ac7d825)
2024-04-04 23:34:55 +02:00
Ell 0fde5b7aa0 Merge branch 'main' into release
# Conflicts:
#	build.cake
2024-04-04 22:26:42 +02:00
Ell 11256b02f5 Release 6.3.0 2024-04-04 22:25:45 +02:00
Ell 3bb3ae20c0 further improved performance of panels when removing a large amount of children 2024-04-02 13:36:46 +02:00
Ell 7f4cc773ec Fixed scrolling panels calculating their height incorrectly when their first child is hidden 2024-03-30 12:24:57 +01:00
Ell 5d4f1293c7 fixed SetWidth/HeightBasedOnAspect behaving unexpectedly if an image has no texture 2024-03-30 12:10:50 +01:00
Ell 60af21aff4 Improved UiParser.ParseImage with locks and a callback action 2024-03-30 11:52:23 +01:00
Ell 23103613cd Allow formatting codes applied later to override settings of earlier ones 2024-03-17 21:54:06 +01:00
Ell 0c5369e687 Fixed bold and italic formatting code closing tags working on each other 2024-03-17 21:43:53 +01:00
Ell 62ef75441a Added ToHexStringRgba and ToHexStringRgb to ColorExtensions
Closes #19
2024-03-15 20:04:57 +01:00
Ell 0f8072d83e update copyright year 2024-02-24 22:16:38 +01:00
Ell fd438f6b1d fixed pre-push hook 2024-02-04 14:55:00 +01:00
Ell 326462fb58 added changelog enforcer action 2024-02-04 14:05:59 +01:00
Ell 08d7af2b80 added pre-commit hook to ensure changelog was updated 2024-02-04 13:54:36 +01:00
Ell 3e76364c5d add changelog entry for ed5c4b4 2024-01-30 20:47:54 +01:00
Ell 2eaf0c0cee Expose character and line spacing in GenericStashFont
closes #16
2024-01-30 20:47:05 +01:00
Ell ed5c4b44d4 fixed empty nine patch regions stalling when using tile mode
closes #17
2024-01-30 20:34:36 +01:00
Ell 17b6a3297a added discord link to the readme 2024-01-18 10:17:15 +01:00
Ell cd8b10bd4d actions: fixed env variable names 2024-01-14 22:29:53 +01:00
Ell 6487ad3644 actions: potentially fixed passing ref name xvfb 2024-01-14 22:21:43 +01:00
Ell dfc56611f2
Switch to GitHub Actions (#15)
* switch to github actions for workflows

* fixed action names

* setup java in build and restore in docs

* use java 17 for android sdk

* clean up restore step
2024-01-14 22:12:07 +01:00
Ell bd0a723d86 Added some useful additional constructors to various elements 2023-12-28 17:16:31 +01:00
Ell 1436cdb987 Also use IsAotCompatible for FNA projects 2023-12-20 20:54:23 +01:00
Ell 7e345e7437 Merge remote-tracking branch 'origin/main' 2023-12-20 20:50:02 +01:00
Ell 236ecfa116 Use IsAotCompatible instead of IsTrimmable to enable more warnings 2023-12-20 20:49:52 +01:00
Ell 7d314a589e improved Element ToString 2023-12-16 21:37:49 +01:00
Ell b935bd0a61 Added the ability to set a custom SamplerState for images 2023-12-13 22:57:23 +01:00
Ell 6a8e9639c1 updated dependencies 2023-12-13 22:11:31 +01:00
Ell e191d4919b Fixed InputHandler touch states being initialized incorrectly when touch handling is disabled
Closes #10
2023-12-04 10:20:29 +01:00
Ell 764b29e120 Text input improvements:
- Allow using control and arrow keys to move the visible area of a text input
- Don't reset the caret position of a text field when selecting or deselecting it
2023-12-02 19:28:59 +01:00
Ell 294af052ae Added SetWidthBasedOnAspect and SetHeightBasedOnAspect to images 2023-11-23 22:16:31 +01:00
Ell ae5d2b7a37 suppress android demo deprecation warning for SystemUiVisibility 2023-11-23 22:14:48 +01:00
Ell 6c4d241d91 use higher verbosity for ci tests 2023-11-23 10:26:58 +01:00
Ell aca1ece870 fixed cake build 2023-11-22 21:56:27 +01:00
Ell a892d2424e fix new trimmable warnings 2023-11-22 12:17:21 +01:00
Ell 3b22a8f228 don't restore dotnet tools in project files 2023-11-22 12:12:36 +01:00
Ell b4fd0219cd avoid using local package directory 2023-11-22 12:01:30 +01:00
Ell d7d768d0a9 dotnet install script requires a full version 2023-11-22 10:50:03 +01:00
Ell b8f46ff5d2 updated to .net 8 and reorganized third party deps 2023-11-22 10:39:13 +01:00
Ell 56a4833a49 further panel performance improvements 2023-11-11 13:09:34 +01:00
Ell 5fcee515e2 this would never happen 2023-11-11 12:43:17 +01:00
Ell 1fa563be46 Improved Panel performance when adding and removing a lot of children 2023-11-11 12:40:11 +01:00
Ell a233477b1e cleaned up element ui addition order (0fab7fe) 2023-11-11 12:15:21 +01:00
Ell 0fab7fe859 Don't unnecessarily set areas dirty when removing a root element from the ui 2023-11-11 12:06:28 +01:00
Ell 0293ea435e Added UiControls.NavType, which stores the most recently used type of ui navigation 2023-11-08 10:31:36 +01:00
Ell 476e1dd2a6 Improved text formatter tokenization performance 2023-10-14 18:58:32 +02:00
Ell 5e2f48db9d further improved auto-hiding loop detection 2023-10-14 17:53:23 +02:00
Ell 95b28c6039 improved 0571e8a implementation 2023-10-14 17:34:43 +02:00
Ell 281a6f7588 Revert "fixed 0571e8a not updating older panels correctly"
This reverts commit 7bfe44de07.
2023-10-14 17:22:05 +02:00
Ell 7bfe44de07 fixed 0571e8a not updating older panels correctly 2023-10-14 17:05:41 +02:00
Ell 0571e8a4e1 Fixed a stack overflow exception when a panel's scroll bar auto-hiding causes elements to gain height 2023-10-14 15:02:58 +02:00
Ell 6c07a7e900 Fixed panels updating their relevant children too much when the scroll bar is hidden 2023-10-14 14:28:27 +02:00
Ell 41a1a8aef1 dependency update 2023-10-04 15:54:05 +02:00
Ell de1fc28376 Color parsing improvements
- Added ColorHelper.TryFromHexString, a non-throwing version of FromHexString
- Stopped the text formatter from throwing if a color can't be parsed
2023-09-30 22:50:18 +02:00
Ell 8eff529b9d Fixed various exception types not being wrapped by ContentLoadExceptions when loading raw or JSON content 2023-09-27 18:58:09 +02:00
Ell ebeba463b4 updated docfx and improved docs navbar 2023-09-15 15:10:03 +02:00
Ell 1a06bcc7fd ci: don't push on pull request 2023-09-06 12:42:47 +02:00
Ell b49ac1d053 Added the ability to draw single corners of AutoTiling's extended auto tiles 2023-08-28 01:51:25 +02:00
Ell 7720ab0ea5 fixed newly added Panel children not scrolling correctly since f6bc206 2023-08-15 10:30:49 +02:00
Ell a119db553f fixed a potential stack overflow introduced by 237334b 2023-08-14 18:32:27 +02:00
Ell 7bf22fa8f3 changelog cleanup 2023-08-14 17:54:32 +02:00
Ell 4d7d628486 fixed FNA 2023-08-14 17:52:16 +02:00
Ell 237334b1c9 Allow dropdowns to have scrolling panels closes #8 2023-08-14 17:50:07 +02:00
Ell f6bc206c1f Allow scrolling panels to contain other scrolling panels 2023-08-14 17:37:26 +02:00
Ell 550bf28320 Allow scrolling panels to contain other scrolling panels 2023-08-14 16:02:28 +02:00
Ell 7e64b8a990 Added GetRandomEntry and GetRandomWeightedEntry to SingleRandom 2023-08-07 19:00:34 +02:00
Ell 2c7ffee427 Added Zero, One, Linear and Clamp to Easings 2023-08-03 11:29:51 +02:00
Ell 62a7a89834 additional text input fixes for emoji 2023-07-17 15:56:25 +02:00
Ell fda22de83d Fixed TextInput not working correctly when using surrogate pairs 2023-07-17 15:20:36 +02:00
Ell 50da081be9 Added WithRenderTargets, a multi-target version of WithRenderTarget 2023-07-11 12:11:12 +02:00
Ell 840c528f06 use dotnet nuget in cake rather than nuget.exe 2023-07-10 17:33:45 +02:00
Ell 053ad5967b added woodpecker 2023-07-10 17:18:09 +02:00
Ell f8aae9f5c2 bump upcoming version 2023-06-28 13:57:05 +02:00
99 changed files with 1002 additions and 1140 deletions

8
.githooks/pre-push Normal file
View file

@ -0,0 +1,8 @@
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting the 'pre-push' file in the hooks directory (set by 'core.hookspath'; usually '.git/hooks').\n"; exit 2; }
git lfs pre-push "$@"
if ! git diff origin --name-status | grep -E -q "M\s+CHANGELOG.md"; then
echo "The changelog was not updated. Please document your changes in CHANGELOG.md before pushing."
exit 1
fi

11
.github/workflows/enforce-changelog.yml vendored Normal file
View file

@ -0,0 +1,11 @@
on: pull_request
jobs:
enforce-changelog:
runs-on: ubuntu-latest
steps:
- uses: dangoslen/changelog-enforcer@v3
with:
changeLogPath: CHANGELOG.md
missingUpdateErrorMessage: |
The changelog was not updated. Please document your changes in CHANGELOG.md.
Run `git config core.hooksPath .githooks` to enable a git hook that ensures you updated the changelog before pushing.

51
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,51 @@
on: [push, pull_request]
jobs:
build-publish:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Setup Java
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
- name: Restore tools
run: dotnet tool restore
- name: Run cake
uses: coactions/setup-xvfb@v1
with:
run: dotnet cake --target Publish --branch ${{ github.ref_name }}
env:
NUGET_KEY: ${{ secrets.NUGET_KEY }}
BAGET_KEY: ${{ secrets.BAGET_KEY }}
docs:
runs-on: ubuntu-latest
steps:
- name: Clone repository
uses: actions/checkout@v4
with:
submodules: recursive
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: '8.0.x'
- name: Restore tools
run: dotnet tool restore
- name: Run cake
run: dotnet cake --target Document --branch $GITHUB_REF_NAME
- name: Deploy
if: github.event_name == 'push' && github.ref_name == 'release'
# this is a beautiful way to deploy a website and i will not take any criticism
run: |
curl -L --output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb && sudo dpkg -i cloudflared.deb
mkdir ~/.ssh && echo "${{ secrets.ELLBOT_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa
rsync -rv --delete -e 'ssh -o "ProxyCommand cloudflared access ssh --hostname %h" -o "StrictHostKeyChecking=no"' Docs/_site/. ellbot@ssh.ellpeck.de:/var/www/MLEM

4
.gitmodules vendored
View file

@ -1,6 +1,6 @@
[submodule "FNA"]
path = FNA
path = ThirdParty/FNA
url = https://github.com/FNA-XNA/FNA
[submodule "FontStashSharp"]
path = FontStashSharp
path = ThirdParty/FontStashSharp
url = https://github.com/FontStashSharp/FontStashSharp

View file

@ -1,16 +0,0 @@
steps:
build:
image: runmymind/docker-android-sdk:ubuntu-standalone
commands:
# install xvfb to allow for graphics-dependent tests
- apt-get update && apt-get install -y --no-install-recommends xauth xvfb openjdk-11-jdk
# install dotnet
- curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --version 7.0.305
- export DOTNET_ROOT=$HOME/.dotnet
- export PATH=$PATH:$DOTNET_ROOT:$DOTNET_ROOT/tools
# run cake
- dotnet tool restore
- xvfb-run -a dotnet cake --target Publish --branch $CI_COMMIT_BRANCH
secrets:
- nuget_key
- baget_key

View file

@ -1,16 +0,0 @@
steps:
document:
image: mcr.microsoft.com/dotnet/sdk:7.0.305
commands:
- dotnet tool restore
- dotnet cake --target Document --branch $CI_COMMIT_BRANCH
deploy:
image: debian:latest
when:
- event: [push, manual]
branch: release
commands:
- rm -rfv /var/www/MLEM/*
- cp -rv Docs/_site/. /var/www/MLEM/
volumes:
- /var/www/MLEM:/var/www/MLEM

View file

@ -2,6 +2,7 @@
MLEM tries to adhere to [semantic versioning](https://semver.org/). Potentially breaking changes are written in **bold**.
Jump to version:
- [6.3.0](#630)
- [6.2.0](#620)
- [6.1.0](#610)
- [6.0.0](#600)
@ -10,6 +11,57 @@ Jump to version:
- [5.1.0](#510)
- [5.0.0](#500)
## 6.3.0
### MLEM
Additions
- Added GraphicsExtensions.WithRenderTargets, a multi-target version of WithRenderTarget
- Added Zero, One, Linear and Clamp to Easings
- Added GetRandomEntry and GetRandomWeightedEntry to SingleRandom
- Added the ability to draw single corners of AutoTiling's extended auto tiles
- Added ColorHelper.TryFromHexString, a non-throwing version of FromHexString
- Added ToHexStringRgba and ToHexStringRgb to ColorExtensions
Improvements
- Stopped the text formatter throwing if a color can't be parsed
- Improved text formatter tokenization performance
- Allow using control and arrow keys to move the visible area of a text input
- Allow formatting codes applied later to override settings of earlier ones
Fixes
- Fixed TextInput not working correctly when using surrogate pairs
- Fixed InputHandler touch states being initialized incorrectly when touch handling is disabled
- Fixed empty NinePatch regions stalling when using tile mode
- Fixed bold and italic formatting code closing tags working on each other
### MLEM.Ui
Additions
- Added UiControls.NavType, which stores the most recently used type of ui navigation
- Added SetWidthBasedOnAspect and SetHeightBasedOnAspect to images
- Added the ability to set a custom SamplerState for images
- Added some useful additional constructors to various elements
Improvements
- Allow scrolling panels to contain other scrolling panels
- Allow dropdowns to have scrolling panels
- Improved Panel performance when adding and removing a lot of children
- Don't reset the caret position of a text field when selecting or deselecting it
- Improved UiParser.ParseImage with locks and a callback action
Fixes
- Fixed panels updating their relevant children too much when the scroll bar is hidden
- Fixed a stack overflow exception when a panel's scroll bar auto-hiding causes elements to gain height
- Fixed scrolling panels calculating their height incorrectly when their first child is hidden
### MLEM.Extended
Improvements
- Updated to FontStashSharp 1.3.0's API
- Expose character and line spacing in GenericStashFont
### MLEM.Data
Fixes
- Fixed various exception types not being wrapped by ContentLoadExceptions when loading raw or JSON content
## 6.2.0
### MLEM

View file

@ -48,10 +48,10 @@ public class Activity1 : AndroidGameActivity {
base.OnWindowFocusChanged(hasFocus);
// hide the status bar
if (hasFocus) {
#pragma warning disable CA1422
#pragma warning disable CS0618
// TODO this is deprecated, find out how to replace it
this.Window.DecorView.SystemUiVisibility = (StatusBarVisibility) (SystemUiFlags.ImmersiveSticky | SystemUiFlags.LayoutStable | SystemUiFlags.LayoutHideNavigation | SystemUiFlags.LayoutFullscreen | SystemUiFlags.HideNavigation | SystemUiFlags.Fullscreen);
#pragma warning restore CA1422
#pragma warning restore CS0618
}
}

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-android</TargetFramework>
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>31</SupportedOSPlatformVersion>
<OutputType>Exe</OutputType>
<ApplicationId>de.ellpeck.mlem.demos.android</ApplicationId>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ApplicationIcon>Icon.ico</ApplicationIcon>
<AssemblyName>MLEM Desktop Demos</AssemblyName>
<RootNamespace>Demos.DesktopGL</RootNamespace>
@ -21,7 +21,7 @@
<ItemGroup>
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj" />
</ItemGroup>
<ItemGroup>
@ -29,7 +29,7 @@
<Content Include="..\Demos\Content\*\**" />
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
<Content Include="../FnaNative/**">
<Content Include="../ThirdParty/Native/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</Content>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ApplicationIcon>Icon.ico</ApplicationIcon>
<AssemblyName>MLEM Desktop Demos</AssemblyName>
<IsPackable>false</IsPackable>
@ -26,9 +26,4 @@
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project>

View file

@ -12,12 +12,16 @@ Strikethrough with ~~two tildes~~.
[I'm an inline-style link](https://www.google.com)
<http://www.example.com>
Logo:
![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Logo.png)
Wide logo:
![](https://raw.githubusercontent.com/Ellpeck/MLEM/main/Media/Banner.png)
Some `inline code` right here
```js
function codeBlock() {
}
```
```

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Demos</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<IsPackable>false</IsPackable>
@ -14,7 +14,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>

View file

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

View file

@ -15,7 +15,7 @@ namespace Demos {
private const string Text =
"MLEM's text formatting system allows for various <b>formatting codes</b> to be applied in the middle of a string. Here's a demonstration of some of them.\n\n" +
"You can write in <b>bold</i>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
"You can write in <b>bold</b>, <i>italics</i>, <u>with an underline</u>, <st>strikethrough</st>, with a <s>drop shadow</s> whose <s #ff0000 4>color</s> and <s #000000 10>offset</s> you can modify in each application of the code, with an <o>outline</o> that you can also <o #ff0000 4>modify</o> <o #ff00ff 2>dynamically</o>, or with various types of <b>combined <c Pink>formatting</c> codes</b>.\n\n" +
"You can apply <c CornflowerBlue>custom</c> <c Yellow>colors</c> to text, including all default <c Orange>MonoGame colors</c> and <c #aabb00>inline custom colors</c>.\n\n" +
"You can also use animations like <a wobbly>a wobbly one</a>, as well as create custom ones using the <a wobbly>Code class</a>.\n\n" +
"You can also display <i grass> icons in your text, and use super<sup>script</sup> or sub<sub>script</sub> formatting!\n\n" +

View file

@ -223,6 +223,15 @@ namespace Demos {
PositionOffset = new Vector2(0, 1)
});
var subPanel = this.root.AddChild(new Panel(Anchor.AutoLeft, new Vector2(1, 25), Vector2.Zero, false, true) {
PositionOffset = new Vector2(0, 1),
Texture = null,
ChildPadding = Padding.Empty
});
subPanel.AddChild(new Paragraph(Anchor.AutoLeft, 1, "This is a nested scrolling panel!"));
for (var i = 1; i <= 5; i++)
subPanel.AddChild(new Button(Anchor.AutoLeft, new Vector2(1, 10), $"Button {i}") {PositionOffset = new Vector2(0, 1)});
const string alignText = "Paragraphs can have <l Left>left</l> aligned text, <l Right>right</l> aligned text and <l Center>center</l> aligned text.";
this.root.AddChild(new VerticalSpace(3));
var alignPar = this.root.AddChild(new Paragraph(Anchor.AutoLeft, 1, alignText));

View file

@ -49,7 +49,7 @@
"globalMetadata": {
"_appTitle": "MLEM Documentation",
"_appLogoPath": "Logo.svg",
"_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">&copy; 2019-2023 Ellpeck</a> &ndash; <a href=\"https://ellpeck.de/impressum\">Impressum</a> &ndash; <a href=\"https://ellpeck.de/privacy\">Privacy</a> &ndash; <a href=\"https://status.ellpeck.de\">Status</a>",
"_appFooter": "<a href=\"https://github.com/Ellpeck/MLEM\">&copy; 2019-2024 Ellpeck</a> &ndash; <a href=\"https://ellpeck.de/impressum\">Impressum</a> &ndash; <a href=\"https://ellpeck.de/privacy\">Privacy</a> &ndash; <a href=\"https://status.ellpeck.de\">Status</a>",
"_enableSearch": true
},
"dest": "_site",

1
FNA

@ -1 +0,0 @@
Subproject commit 697cc63662914c0dc26c500bc9b8498b5ca8a68f

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

@ -1 +0,0 @@
Subproject commit f11f97b709e50960dd8ce1f727974744c4f8a0dd

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019-2023 Ellpeck
Copyright (c) 2019-2024 Ellpeck
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View file

@ -100,7 +100,7 @@ namespace MLEM.Data.Content {
r.Name = assetName;
return t;
}
} catch (FileNotFoundException) {}
} catch (IOException) {}
}
}
throw new ContentLoadException($"Asset {assetName} not found. Tried files {string.Join(", ", triedFiles)}");

View file

@ -68,7 +68,7 @@ namespace MLEM.Data {
using (var reader = new JsonTextReader(stream))
return serializerToUse.Deserialize<T>(reader);
}
} catch (FileNotFoundException) {}
} catch (IOException) {}
}
throw new ContentLoadException($"Asset {name} not found. Tried files {string.Join(", ", triedFiles)}");
}

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<RootNamespace>MLEM.Data</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1701</NoWarn>
@ -28,14 +28,14 @@
<PackageReference Include="Lidgren.Network" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json" Version="13.0.2">
<PackageReference Include="Newtonsoft.Json" Version="13.0.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Newtonsoft.Json.Bson" Version="1.0.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<ProjectReference Include="..\FNA\FNA.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>

View file

@ -11,6 +11,7 @@ namespace MLEM.Extended.Font {
/// The <see cref="SpriteFontBase"/> that is being wrapped by this generic font
/// </summary>
public readonly SpriteFontBase Font;
/// <inheritdoc />
public override GenericFont Bold { get; }
/// <inheritdoc />
@ -18,6 +19,15 @@ namespace MLEM.Extended.Font {
/// <inheritdoc />
public override float LineHeight => this.Font.LineHeight;
/// <summary>
/// The character spacing that will be passed to the underlying <see cref="Font"/>.
/// </summary>
public float CharacterSpacing { get; set; }
/// <summary>
/// The line spacing that will be passed to the underlying <see cref="Font"/>.
/// </summary>
public float LineSpacing { get; set; }
/// <summary>
/// Creates a new generic font using <see cref="SpriteFontBase"/>.
/// Optionally, a bold and italic version of the font can be supplied.
@ -33,12 +43,12 @@ namespace MLEM.Extended.Font {
/// <inheritdoc />
protected override float MeasureCharacter(int codePoint) {
return this.Font.MeasureString(CodePointSource.ToString(codePoint)).X;
return this.Font.MeasureString(CodePointSource.ToString(codePoint), null, this.CharacterSpacing, this.LineSpacing).X;
}
/// <inheritdoc />
protected override void DrawCharacter(SpriteBatch batch, int codePoint, string character, Vector2 position, Color color, float rotation, Vector2 scale, SpriteEffects effects, float layerDepth) {
this.Font.DrawText(batch, character, position, color, scale, rotation, Vector2.Zero, layerDepth);
this.Font.DrawText(batch, character, position, color, rotation, Vector2.Zero, scale, layerDepth, this.CharacterSpacing, this.LineSpacing);
}
}

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<RootNamespace>MLEM.Extended</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
<NoWarn>NU1702</NoWarn>
@ -24,10 +24,10 @@
<ItemGroup>
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
<ProjectReference Include="..\ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
<ProjectReference Include="..\FNA\FNA.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup>
<PropertyGroup>
@ -27,7 +27,7 @@
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8">
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">

View file

@ -44,7 +44,8 @@ namespace MLEM.Extended.Tiled {
/// <param name="key">The key by which to get a property</param>
/// <returns>The color property</returns>
public static Color GetColor(this TiledMapProperties properties, string key) {
return ColorHelper.FromHexString(properties.Get(key));
ColorHelper.TryFromHexString(properties.Get(key), out var val);
return val;
}
/// <summary>

View file

@ -16,11 +16,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests.FNA", "Tests\Tests.FN
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MLEM.Extended.FNA", "MLEM.Extended\MLEM.Extended.FNA.csproj", "{A5B22930-DF4B-4A62-93ED-A6549F7B666B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA", "ThirdParty\FNA\FNA.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FontStashSharp.FNA", "ThirdParty\FontStashSharp\src\XNA\FontStashSharp.FNA.csproj", "{39249E92-EBF2-4951-A086-AB4951C3CCE1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "FNA\FNA.Core.csproj", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FNA.Core", "ThirdParty\FNA\FNA.Core.csproj", "{458FFA5E-A1C4-4B23-A5D8-259385FEECED}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<RootNamespace>MLEM.Startup</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
@ -22,11 +22,11 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Coroutine" Version="2.1.4" />
<PackageReference Include="Coroutine" Version="2.1.5" />
<ProjectReference Include="..\MLEM.Ui\MLEM.Ui.FNA.csproj" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FNA\FNA.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>

View file

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup>
<PropertyGroup>

View file

@ -1,12 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsTrimmable>true</IsTrimmable>
<NoWarn>NU5128</NoWarn>
</PropertyGroup>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<PublishReadyToRun>false</PublishReadyToRun>
<TieredCompilation>false</TieredCompilation>
<ApplicationIcon>Icon.ico</ApplicationIcon>
@ -19,9 +19,4 @@
<EmbeddedResource Include="Icon.ico" />
<EmbeddedResource Include="Icon.bmp" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project>

View file

@ -87,6 +87,13 @@ namespace MLEM.Ui.Elements {
private bool isDisabled;
/// <summary>
/// Creates a new button with the given settings and no text or tooltip.
/// </summary>
/// <param name="anchor">The button's anchor</param>
/// <param name="size">The button's size</param>
public Button(Anchor anchor, Vector2 size) : base(anchor, size) {}
/// <summary>
/// Creates a new button with the given settings
/// </summary>
@ -94,7 +101,7 @@ namespace MLEM.Ui.Elements {
/// <param name="size">The button's size</param>
/// <param name="text">The text that should be displayed on the button</param>
/// <param name="tooltipText">The text that should be displayed in a <see cref="Tooltip"/> when hovering over this button</param>
public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size) {
public Button(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : this(anchor, size) {
if (text != null) {
this.Text = new Paragraph(Anchor.Center, 1, text, true);
this.Text.Padding = this.Text.Padding.OrStyle(new Padding(1), 1);
@ -104,6 +111,23 @@ namespace MLEM.Ui.Elements {
this.Tooltip = this.AddTooltip(tooltipText);
}
/// <summary>
/// Creates a new button with the given settings
/// </summary>
/// <param name="anchor">The button's anchor</param>
/// <param name="size">The button's size</param>
/// <param name="textCallback">The text that should be displayed on the button</param>
/// <param name="tooltipTextCallback">The text that should be displayed in a <see cref="Tooltip"/> when hovering over this button</param>
public Button(Anchor anchor, Vector2 size, Paragraph.TextCallback textCallback = null, Paragraph.TextCallback tooltipTextCallback = null) : this(anchor, size) {
if (textCallback != null) {
this.Text = new Paragraph(Anchor.Center, 1, textCallback, true);
this.Text.Padding = this.Text.Padding.OrStyle(new Padding(1), 1);
this.AddChild(this.Text);
}
if (tooltipTextCallback != null)
this.Tooltip = this.AddTooltip(tooltipTextCallback);
}
/// <inheritdoc />
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
var tex = this.Texture;

View file

@ -12,8 +12,7 @@ namespace MLEM.Ui.Elements {
/// <summary>
/// The panel that this dropdown contains. It will be displayed upon pressing the dropdown button.
/// </summary>
public readonly Panel Panel;
public Panel Panel { get; private set; }
/// <summary>
/// This property stores whether the dropdown is currently opened or not
/// </summary>
@ -29,6 +28,18 @@ namespace MLEM.Ui.Elements {
/// </summary>
public GenericCallback OnOpenedOrClosed;
/// <summary>
/// Creates a new dropdown with the given settings and no text or tooltip.
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
public Dropdown(Anchor anchor, Vector2 size, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
/// <summary>
/// Creates a new dropdown with the given settings
/// </summary>
@ -36,31 +47,25 @@ namespace MLEM.Ui.Elements {
/// <param name="size">The dropdown button's size</param>
/// <param name="text">The text displayed on the dropdown button</param>
/// <param name="tooltipText">The text displayed as a tooltip when hovering over the dropdown button</param>
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null) : base(anchor, size, text, tooltipText) {
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, Vector2.Zero, true) {
IsHidden = true
});
this.OnAreaUpdated += e => {
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, 0);
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
};
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
this.OnPressed += e => {
this.IsOpen = !this.IsOpen;
// close other dropdowns in the same root when we open
if (this.IsOpen) {
this.Root.Element.AndChildren(o => {
if (o != this && o is Dropdown d && d.IsOpen)
d.IsOpen = false;
});
}
};
this.GetGamepadNextElement = (dir, usualNext) => {
// Force navigate down to our first child if we're open
if (this.IsOpen && dir == Direction2.Down)
return this.Panel.GetChildren().FirstOrDefault(c => c.CanBeSelected) ?? usualNext;
return usualNext;
};
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
public Dropdown(Anchor anchor, Vector2 size, string text = null, string tooltipText = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, text, tooltipText) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
/// <summary>
/// Creates a new dropdown with the given settings
/// </summary>
/// <param name="anchor">The dropdown's anchor</param>
/// <param name="size">The dropdown button's size</param>
/// <param name="textCallback">The text displayed on the dropdown button</param>
/// <param name="tooltipTextCallback">The text displayed as a tooltip when hovering over the dropdown button</param>
/// <param name="panelHeight">The height of the <see cref="Panel"/>. If this is 0, the panel will be set to <see cref="Element.SetHeightBasedOnChildren"/>.</param>
/// <param name="scrollPanel">Whether this dropdown's <see cref="Panel"/> should automatically add a scroll bar to scroll towards elements that are beyond the area it covers.</param>
/// <param name="autoHidePanelScrollbar">Whether this dropdown's <see cref="Panel"/>'s scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollPanel"/> is <see langword="true"/>.</param>
public Dropdown(Anchor anchor, Vector2 size, Paragraph.TextCallback textCallback = null, Paragraph.TextCallback tooltipTextCallback = null, float panelHeight = 0, bool scrollPanel = false, bool autoHidePanelScrollbar = true) : base(anchor, size, textCallback, tooltipTextCallback) {
this.Initialize(panelHeight, scrollPanel, autoHidePanelScrollbar);
}
/// <summary>
@ -116,5 +121,32 @@ namespace MLEM.Ui.Elements {
return paragraph;
}
private void Initialize(float panelHeight, bool scrollPanel, bool autoHidePanelScrollbar) {
this.Panel = this.AddChild(new Panel(Anchor.TopCenter, Vector2.Zero, panelHeight == 0, scrollPanel, autoHidePanelScrollbar) {
IsHidden = true
});
this.OnAreaUpdated += e => {
this.Panel.Size = new Vector2(e.Area.Width / e.Scale, panelHeight);
this.Panel.PositionOffset = new Vector2(0, e.Area.Height / e.Scale);
};
this.OnOpenedOrClosed += e => this.Priority = this.IsOpen ? 10000 : 0;
this.OnPressed += e => {
this.IsOpen = !this.IsOpen;
// close other dropdowns in the same root when we open
if (this.IsOpen) {
this.Root.Element.AndChildren(o => {
if (o != this && o is Dropdown d && d.IsOpen)
d.IsOpen = false;
});
}
};
this.GetGamepadNextElement = (dir, usualNext) => {
// Force navigate down to our first child if we're open
if (this.IsOpen && dir == Direction2.Down)
return this.Panel.GetChildren().FirstOrDefault(c => c.CanBeSelected) ?? usualNext;
return usualNext;
};
}
}
}

View file

@ -1236,12 +1236,13 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc />
public override string ToString() {
var ret = this.GetType().ToString();
// elements will contain their path up to the root (Paragraph@Panel@...@RootName)
var ret = this.GetType().Name;
// elements will contain their path up to the root and their index in each parent
// eg Paragraph 2 @ Panel 3 @ ... @ Group RootName
if (this.Parent != null) {
ret += $"@{this.Parent}";
ret += $" {this.Parent.Children.IndexOf(this)} @ {this.Parent}";
} else if (this.Root?.Element == this) {
ret += $"@{this.Root.Name}";
ret += $" {this.Root.Name}";
}
return ret;
}

View file

@ -14,8 +14,18 @@ namespace MLEM.Ui.Elements {
/// </summary>
/// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height</param>
public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : base(anchor, size) {
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param>
public Group(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = true) : this(anchor, size, false, setHeightBasedOnChildren) {}
/// <summary>
/// Creates a new group with the given settings
/// </summary>
/// <param name="anchor">The group's anchor</param>
/// <param name="size">The group's size</param>
/// <param name="setWidthBasedOnChildren">Whether the group's width should be based on its children's width, see <see cref="Element.SetWidthBasedOnChildren"/>.</param>
/// <param name="setHeightBasedOnChildren">Whether the group's height should be based on its children's height, see <see cref="Element.SetHeightBasedOnChildren"/>.</param>
public Group(Anchor anchor, Vector2 size, bool setWidthBasedOnChildren, bool setHeightBasedOnChildren) : base(anchor, size) {
this.SetWidthBasedOnChildren = setWidthBasedOnChildren;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
this.CanBeSelected = false;
}

View file

@ -69,11 +69,45 @@ namespace MLEM.Ui.Elements {
/// Note that increased rotation does not increase this component's size, even if the rotated texture would go out of bounds of this component.
/// </summary>
public float ImageRotation;
/// <summary>
/// Whether this image's width should automatically be calculated based on this image's calculated height in relation to its <see cref="Texture"/>'s aspect ratio.
/// Note that, if this is <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's width.
/// </summary>
public bool SetWidthBasedOnAspect {
get => this.setWidthBasedOnAspect;
set {
if (this.setWidthBasedOnAspect != value) {
this.setWidthBasedOnAspect = value;
this.SetAreaDirty();
}
}
}
/// <summary>
/// Whether this image's height should automatically be calculated based on this image's calculated width in relation to its <see cref="Texture"/>'s aspect ratio.
/// This behavior is useful if an image should take up a certain width, but the aspect ratio of its texture can vary and the image should not take up more height than is necessary.
/// Note that, if this is <see langword="true"/>, the <see cref="Element.AutoSizeAddedAbsolute"/> value will still be applied to this image's height.
/// </summary>
public bool SetHeightBasedOnAspect {
get => this.setHeightBasedOnAspect;
set {
if (this.setHeightBasedOnAspect != value) {
this.setHeightBasedOnAspect = value;
this.SetAreaDirty();
}
}
}
/// <summary>
/// The sampler state that this image's <see cref="Texture"/> should be drawn with.
/// If this is <see langword="null"/>, the current <see cref="SpriteBatchContext"/>'s <see cref="SpriteBatchContext.SamplerState"/> will be used, which will likely be the same as <see cref="UiSystem.SpriteBatchContext"/>.
/// </summary>
public SamplerState SamplerState;
/// <inheritdoc />
public override bool IsHidden => base.IsHidden || this.Texture == null;
private bool scaleToImage;
private bool setWidthBasedOnAspect;
private bool setHeightBasedOnAspect;
private TextureRegion explicitlySetTexture;
private TextureRegion displayedTexture;
@ -86,7 +120,7 @@ namespace MLEM.Ui.Elements {
/// <param name="scaleToImage">Whether this image's size should be based on the texture's size</param>
public Image(Anchor anchor, Vector2 size, TextureRegion texture, bool scaleToImage = false) : base(anchor, size) {
this.Texture = texture;
this.scaleToImage = scaleToImage;
this.ScaleToImage = scaleToImage;
this.CanBeSelected = false;
this.CanBeMoused = false;
}
@ -95,14 +129,29 @@ namespace MLEM.Ui.Elements {
public Image(Anchor anchor, Vector2 size, TextureCallback getTextureCallback, bool scaleToImage = false) : base(anchor, size) {
this.GetTextureCallback = getTextureCallback;
this.Texture = getTextureCallback(this);
this.scaleToImage = scaleToImage;
this.ScaleToImage = scaleToImage;
this.CanBeSelected = false;
this.CanBeMoused = false;
}
/// <inheritdoc />
protected override Vector2 CalcActualSize(RectangleF parentArea) {
return this.Texture != null && this.scaleToImage ? this.Texture.Size.ToVector2() * this.Scale : base.CalcActualSize(parentArea);
var ret = base.CalcActualSize(parentArea);
if (this.Texture != null) {
if (this.ScaleToImage)
ret = this.Texture.Size.ToVector2() * this.Scale;
if (this.SetWidthBasedOnAspect)
ret.X = ret.Y * this.Texture.Width / this.Texture.Height + this.ScaledAutoSizeAddedAbsolute.X;
if (this.SetHeightBasedOnAspect)
ret.Y = ret.X * this.Texture.Height / this.Texture.Width + this.ScaledAutoSizeAddedAbsolute.Y;
} else {
// if we don't have a texture and we auto-set width or height, calculate as if we had a texture with a size of 0
if (this.SetWidthBasedOnAspect)
ret.X = this.ScaledAutoSizeAddedAbsolute.X;
if (this.SetHeightBasedOnAspect)
ret.Y = this.ScaledAutoSizeAddedAbsolute.Y;
}
return ret;
}
/// <inheritdoc />
@ -115,6 +164,14 @@ namespace MLEM.Ui.Elements {
public override void Draw(GameTime time, SpriteBatch batch, float alpha, SpriteBatchContext context) {
if (this.Texture == null)
return;
if (this.SamplerState != null) {
batch.End();
var localContext = context;
localContext.SamplerState = this.SamplerState;
batch.Begin(localContext);
}
var center = new Vector2(this.Texture.Width / 2F, this.Texture.Height / 2F);
var color = this.Color.OrDefault(Microsoft.Xna.Framework.Color.White) * alpha;
if (this.MaintainImageAspect) {
@ -125,6 +182,12 @@ namespace MLEM.Ui.Elements {
var scale = new Vector2(1F / this.Texture.Width, 1F / this.Texture.Height) * this.DisplayArea.Size;
batch.Draw(this.Texture, this.DisplayArea.Location + center * scale, color, this.ImageRotation, center, scale * this.ImageScale, this.ImageEffects, 0);
}
if (this.SamplerState != null) {
batch.End();
batch.Begin(context);
}
base.Draw(time, batch, alpha, context);
}
@ -134,7 +197,7 @@ namespace MLEM.Ui.Elements {
return;
var nullChanged = this.displayedTexture == null != (newTexture == null);
this.displayedTexture = newTexture;
if (nullChanged || this.scaleToImage)
if (nullChanged || this.ScaleToImage || this.SetWidthBasedOnAspect || this.SetHeightBasedOnAspect)
this.SetAreaDirty();
}

View file

@ -55,12 +55,16 @@ namespace MLEM.Ui.Elements {
}
private readonly List<Element> relevantChildren = new List<Element>();
private readonly HashSet<Element> scrolledChildren = new HashSet<Element>();
private readonly float[] scrollBarMaxHistory;
private readonly bool scrollOverflow;
private RenderTarget2D renderTarget;
private bool relevantChildrenDirty;
private float scrollBarChildOffset;
private StyleProp<float> scrollBarOffset;
private float lastScrollOffset;
private bool childrenDirtyForScroll;
/// <summary>
/// Creates a new panel with the given settings.
@ -70,7 +74,7 @@ namespace MLEM.Ui.Elements {
/// <param name="positionOffset">The panel's offset from its anchor point</param>
/// <param name="setHeightBasedOnChildren">Whether the panel should automatically calculate its height based on its children's size</param>
/// <param name="scrollOverflow">Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers</param>
/// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling</param>
/// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollOverflow"/> is <see langword="true"/>.</param>
public Panel(Anchor anchor, Vector2 size, Vector2 positionOffset, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : base(anchor, size) {
this.PositionOffset = positionOffset;
this.SetHeightBasedOnChildren = setHeightBasedOnChildren;
@ -94,9 +98,23 @@ namespace MLEM.Ui.Elements {
this.ScrollToElement(e);
};
this.AddChild(this.ScrollBar);
this.scrollBarMaxHistory = new float[3];
for (var i = 0; i < this.scrollBarMaxHistory.Length; i++)
this.scrollBarMaxHistory[i] = -1;
}
}
/// <summary>
/// Creates a new panel with the given settings.
/// </summary>
/// <param name="anchor">The panel's anchor</param>
/// <param name="size">The panel's default size</param>
/// <param name="setHeightBasedOnChildren">Whether the panel should automatically calculate its height based on its children's size</param>
/// <param name="scrollOverflow">Whether this panel should automatically add a scroll bar to scroll towards elements that are beyond the area this panel covers</param>
/// <param name="autoHideScrollbar">Whether the scroll bar should be hidden automatically if the panel does not contain enough children to allow for scrolling. This only has an effect if <paramref name="scrollOverflow"/> is <see langword="true"/>.</param>
public Panel(Anchor anchor, Vector2 size, bool setHeightBasedOnChildren = false, bool scrollOverflow = false, bool autoHideScrollbar = true) : this(anchor, size, Vector2.Zero, setHeightBasedOnChildren, scrollOverflow, autoHideScrollbar) {}
/// <inheritdoc />
public override void ForceUpdateArea() {
if (this.scrollOverflow) {
@ -106,11 +124,15 @@ namespace MLEM.Ui.Elements {
foreach (var child in this.Children) {
if (child != this.ScrollBar && !child.Anchor.IsAuto())
throw new NotSupportedException($"A panel that handles overflow can't contain non-automatic anchors ({child})");
if (child is Panel panel && panel.scrollOverflow)
throw new NotSupportedException($"A panel that scrolls overflow cannot contain another panel that scrolls overflow ({child})");
}
}
base.ForceUpdateArea();
if (this.scrollOverflow) {
for (var i = 0; i < this.scrollBarMaxHistory.Length; i++)
this.scrollBarMaxHistory[i] = -1;
}
this.SetScrollBarStyle();
}
@ -136,8 +158,16 @@ namespace MLEM.Ui.Elements {
// when removing children, our scroll bar might have to be hidden
// if we don't do this before adding children again, they might incorrectly assume that the scroll bar will still be visible and adjust their size accordingly
if (this.System != null)
this.childrenDirtyForScroll = true;
}
/// <inheritdoc />
public override T AddChild<T>(T element, int index = -1) {
// if children were recently removed, make sure to update the scroll bar before adding new ones so that they can't incorrectly assume the scroll bar will be visible
if (this.childrenDirtyForScroll && this.System != null)
this.ScrollSetup();
return base.AddChild(element, index);
}
/// <inheritdoc />
@ -202,10 +232,10 @@ namespace MLEM.Ui.Elements {
/// </summary>
/// <param name="elementY">The y coordinate to scroll to, which should have this element's <see cref="Element.Scale"/> applied.</param>
public void ScrollToElement(float elementY) {
var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
if (firstChild == null)
var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden);
if (highestValidChild == null)
return;
this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - firstChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2;
this.ScrollBar.CurrentValue = (elementY - this.Area.Height / 2 - highestValidChild.Area.Top) / this.Scale + this.ChildPadding.Value.Height / 2;
}
/// <inheritdoc />
@ -234,13 +264,12 @@ namespace MLEM.Ui.Elements {
/// <inheritdoc />
protected override void OnChildAreaDirty(Element child, bool grandchild) {
base.OnChildAreaDirty(child, grandchild);
// we only need to scroll when a grandchild changes, since all of our children are forced
// to be auto-anchored and so will automatically propagate their changes up to us
if (grandchild) {
if (grandchild && !this.AreaDirty) {
// we only need to scroll when a grandchild changes, since all of our children are forced
// to be auto-anchored and so will automatically propagate their changes up to us
this.ScrollChildren();
// we also need to re-setup here in case the child is involved in a special GetTotalCoveredArea
if (!this.AreaDirty)
this.ScrollSetup();
this.ScrollSetup();
}
}
@ -257,32 +286,41 @@ namespace MLEM.Ui.Elements {
/// Prepares the panel for auto-scrolling, creating the render target and setting up the scroll bar's maximum value.
/// </summary>
protected virtual void ScrollSetup() {
this.childrenDirtyForScroll = false;
if (!this.scrollOverflow || this.IsHidden)
return;
float childrenHeight;
if (this.Children.Count > 1) {
var firstChild = this.Children.FirstOrDefault(c => c != this.ScrollBar);
var highestValidChild = this.Children.FirstOrDefault(c => c != this.ScrollBar && !c.IsHidden);
var lowestChild = this.GetLowestChild(c => c != this.ScrollBar && !c.IsHidden, true);
childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - firstChild.Area.Top;
childrenHeight = lowestChild.GetTotalCoveredArea(false).Bottom - highestValidChild.Area.Top;
} else {
// if we only have one child (the scroll bar), then the children take up no visual height
childrenHeight = 0;
}
// the max value of the scroll bar is the amount of non-scaled pixels taken up by overflowing components
var scrollBarMax = (childrenHeight - this.ChildPaddedArea.Height) / this.Scale;
var scrollBarMax = Math.Max(0, (childrenHeight - this.ChildPaddedArea.Height) / this.Scale);
if (!this.ScrollBar.MaxValue.Equals(scrollBarMax, Element.Epsilon)) {
this.ScrollBar.MaxValue = scrollBarMax;
this.relevantChildrenDirty = true;
// avoid a show/hide oscillation that occurs while updating our area with children that can lose height when the scroll bar is shown (like long paragraphs)
if (!this.scrollBarMaxHistory[0].Equals(this.scrollBarMaxHistory[2], Element.Epsilon) || !this.scrollBarMaxHistory[1].Equals(scrollBarMax, Element.Epsilon)) {
this.scrollBarMaxHistory[0] = this.scrollBarMaxHistory[1];
this.scrollBarMaxHistory[1] = this.scrollBarMaxHistory[2];
this.scrollBarMaxHistory[2] = scrollBarMax;
this.ScrollBar.MaxValue = scrollBarMax;
this.relevantChildrenDirty = true;
}
}
// update child padding based on whether the scroll bar is visible
var childOffset = this.ScrollBar.IsHidden ? 0 : this.ScrollerSize.Value.X + this.ScrollBarOffset;
if (!this.scrollBarChildOffset.Equals(childOffset, Element.Epsilon)) {
this.ChildPadding += new Padding(0, -this.scrollBarChildOffset + childOffset, 0, 0);
var childOffsetDelta = childOffset - this.scrollBarChildOffset;
if (!childOffsetDelta.Equals(0, Element.Epsilon)) {
this.scrollBarChildOffset = childOffset;
this.SetAreaDirty();
this.ChildPadding += new Padding(0, childOffsetDelta, 0, 0);
}
// the scroller height has the same relation to the scroll bar height as the visible area has to the total height of the panel's content
@ -290,15 +328,15 @@ namespace MLEM.Ui.Elements {
this.ScrollBar.ScrollerSize = new Vector2(this.ScrollerSize.Value.X, Math.Max(this.ScrollerSize.Value.Y, scrollerHeight));
// update the render target
var targetArea = (Rectangle) this.GetRenderTargetArea();
if (targetArea.Width <= 0 || targetArea.Height <= 0) {
var area = (Rectangle) this.GetRenderTargetArea();
if (area.Width <= 0 || area.Height <= 0) {
this.renderTarget?.Dispose();
this.renderTarget = null;
return;
}
if (this.renderTarget == null || targetArea.Width != this.renderTarget.Width || targetArea.Height != this.renderTarget.Height) {
if (this.renderTarget == null || area.Width != this.renderTarget.Width || area.Height != this.renderTarget.Height) {
this.renderTarget?.Dispose();
this.renderTarget = targetArea.IsEmpty ? null : new RenderTarget2D(this.System.Game.GraphicsDevice, targetArea.Width, targetArea.Height);
this.renderTarget = new RenderTarget2D(this.System.Game.GraphicsDevice, area.Width, area.Height, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
this.relevantChildrenDirty = true;
}
}
@ -330,7 +368,7 @@ namespace MLEM.Ui.Elements {
}
private RectangleF GetRenderTargetArea() {
var area = this.ChildPaddedArea;
var area = this.ChildPaddedArea.OffsetCopy(this.ScaledScrollOffset);
area.X = this.DisplayArea.X;
area.Width = this.DisplayArea.Width;
return area;
@ -339,9 +377,21 @@ namespace MLEM.Ui.Elements {
private void ScrollChildren() {
if (!this.scrollOverflow)
return;
var currentChildren = new HashSet<Element>();
// scroll all our children (and cache newly added ones)
// we ignore false grandchildren so that the children of the scroll bar stay in place
foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true))
child.ScrollOffset.Y = -this.ScrollBar.CurrentValue;
foreach (var child in this.GetChildren(c => c != this.ScrollBar, true, true)) {
// if a child was newly added later, the last scroll offset was never applied
if (this.scrolledChildren.Add(child))
child.ScrollOffset.Y -= this.lastScrollOffset;
child.ScrollOffset.Y += (this.lastScrollOffset - this.ScrollBar.CurrentValue);
currentChildren.Add(child);
}
// remove cached scrolled children that aren't our children anymore
this.scrolledChildren.IntersectWith(currentChildren);
this.lastScrollOffset = this.ScrollBar.CurrentValue;
this.relevantChildrenDirty = true;
}

View file

@ -166,6 +166,30 @@ namespace MLEM.Ui.Elements {
private float textScaleMultiplier = 1;
private bool autoAdjustWidth;
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="textCallback">The paragraph's text</param>
/// <param name="alignment">The paragraph's text alignment.</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, TextCallback textCallback, TextAlignment alignment, bool autoAdjustWidth = false) : this(anchor, width, textCallback, autoAdjustWidth) {
this.Alignment = alignment;
}
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="text">The paragraph's text</param>
/// <param name="alignment">The paragraph's text alignment.</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, string text, TextAlignment alignment, bool autoAdjustWidth = false) : this(anchor, width, text, autoAdjustWidth) {
this.Alignment = alignment;
}
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
@ -177,7 +201,13 @@ namespace MLEM.Ui.Elements {
this.GetTextCallback = textCallback;
}
/// <inheritdoc cref="Paragraph(Anchor,float,TextCallback,bool)"/>
/// <summary>
/// Creates a new paragraph with the given settings.
/// </summary>
/// <param name="anchor">The paragraph's anchor</param>
/// <param name="width">The paragraph's width. Note that its height is automatically calculated.</param>
/// <param name="text">The paragraph's text</param>
/// <param name="autoAdjustWidth">Whether the paragraph's width should automatically be calculated based on the text within it.</param>
public Paragraph(Anchor anchor, float width, string text, bool autoAdjustWidth = false) : base(anchor, new Vector2(width, 0)) {
this.Text = text;
this.AutoAdjustWidth = autoAdjustWidth;
@ -232,7 +262,7 @@ namespace MLEM.Ui.Elements {
private void SetTextDirty() {
this.tokenizedText = null;
// only set our area dirty if our size changed as a result of this action
if (!this.AreaDirty && !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon))
if (!this.AreaDirty && (this.System == null || !this.CalcActualSize(this.ParentArea).Equals(this.DisplayArea.Size, Element.Epsilon)))
this.SetAreaDirty();
}

View file

@ -158,7 +158,7 @@ namespace MLEM.Ui.Elements {
if (this.isMouseScrolling)
this.ScrollToPos(this.TransformInverseAll(this.Input.ViewportMousePosition.ToVector2()));
if (!this.Horizontal) {
if (moused != null && (moused == this.Parent || moused.GetParentTree().Contains(this.Parent))) {
if (this.IsMousedForScrolling(moused)) {
var scroll = this.Input.LastScrollWheel - this.Input.ScrollWheel;
if (scroll != 0)
this.CurrentValue += this.StepPerScroll * Math.Sign(scroll);
@ -244,6 +244,23 @@ namespace MLEM.Ui.Elements {
this.SmoothScrollFactor = this.SmoothScrollFactor.OrStyle(style.ScrollBarSmoothScrollFactor);
}
private bool IsMousedForScrolling(Element moused) {
if (moused == null || (moused != this.Parent && !moused.GetParentTree().Contains(this.Parent)))
return false;
// if we're moused, check if there are any scroll bars deeper than us that should take precedence
var foundMe = false;
foreach (var child in this.Parent.GetChildren(regardGrandchildren: true)) {
if (foundMe) {
if (child is ScrollBar b && !b.Horizontal && b.IsMousedForScrolling(moused))
return false;
} else if (child == this) {
// once we found ourselves, all subsequent children are deeper/older!
foundMe = true;
}
}
return true;
}
/// <summary>
/// A delegate method used for <see cref="ScrollBar.OnValueChanged"/>
/// </summary>

View file

@ -204,8 +204,6 @@ namespace MLEM.Ui.Elements {
if (this.IsSelectedActive && !this.IsHidden && !this.textInput.OnTextInput(key, character) && key == Keys.Enter && !this.Multiline)
this.EnterReceiver?.Controls?.PressElement(this.EnterReceiver);
};
this.OnDeselected += e => this.CaretPos = 0;
this.OnSelected += e => this.CaretPos = this.textInput.Length;
}
/// <inheritdoc />

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<RootNamespace>MLEM.Ui</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
@ -24,7 +24,7 @@
<PackageReference Include="TextCopy" Version="6.2.0" Condition="'$(TargetFramework)'!='net452'" />
<ProjectReference Include="..\MLEM\MLEM.FNA.csproj" />
<ProjectReference Include="..\FNA\FNA.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup>
<PropertyGroup>

View file

@ -139,21 +139,32 @@ namespace MLEM.Ui.Parsers {
/// This method invokes an asynchronouns action, meaning the <see cref="Image"/>'s <see cref="Image.Texture"/> will likely not have loaded in when this method returns.
/// </summary>
/// <param name="path">The absolute, relative or web path to the image.</param>
/// <param name="onImageFetched">An action that is invoked with the loaded image once it is fetched. Note that this action will be invoked asynchronously.</param>
/// <returns>The loaded image.</returns>
/// <exception cref="NullReferenceException">Thrown if <see cref="GraphicsDevice"/> is null, or if there is an <see cref="Exception"/> loading the image and <see cref="ImageExceptionHandler"/> is unset.</exception>
protected Image ParseImage(string path) {
protected Image ParseImage(string path, Action<TextureRegion> onImageFetched = null) {
if (this.GraphicsDevice == null)
throw new NullReferenceException("A UI parser requires a GraphicsDevice for parsing images");
var imageLock = new object();
TextureRegion image = null;
return new Image(Anchor.AutoLeft, new Vector2(1, -1), _ => image) {
return new Image(Anchor.AutoLeft, Vector2.One, _ => {
lock (imageLock)
return image;
}) {
SetHeightBasedOnAspect = true,
OnAddedToUi = e => {
if (image == null)
bool imageNull;
lock (imageLock)
imageNull = image == null;
if (imageNull)
LoadImageAsync();
},
OnRemovedFromUi = e => {
image?.Texture.Dispose();
image = null;
lock (imageLock) {
image?.Texture.Dispose();
image = null;
}
}
};
@ -178,7 +189,12 @@ namespace MLEM.Ui.Parsers {
using (var stream = Path.IsPathRooted(path) ? File.OpenRead(path) : TitleContainer.OpenStream(path))
tex = Texture2D.FromStream(this.GraphicsDevice, stream);
}
image = new TextureRegion(tex);
lock (imageLock) {
if (image == null) {
image = new TextureRegion(tex);
onImageFetched?.Invoke(image);
}
}
} catch (Exception e) {
if (this.ImageExceptionHandler != null) {
this.ImageExceptionHandler.Invoke(path, e);

View file

@ -103,7 +103,7 @@ namespace MLEM.Ui {
/// </summary>
public bool HandleGamepad = true;
/// <summary>
/// If this value is true, the ui controls are in automatic navigation mode.
/// If this value is true, the ui controls are in automatic navigation mode. The state of automatic navigation is usually based on the current <see cref="NavType"/>.
/// This means that the <see cref="UiStyle.SelectionIndicator"/> will be drawn around the <see cref="SelectedElement"/>.
/// </summary>
public bool IsAutoNavMode {
@ -115,12 +115,29 @@ namespace MLEM.Ui {
}
}
}
/// <summary>
/// The current <see cref="NavigationType"/> of these ui controls, which represents the last type of interaction that was used to interact with the underlying <see cref="UiSystem"/>.
/// </summary>
public NavigationType NavType {
get => this.navType;
set {
if (this.navType != value) {
var last = this.navType;
this.navType = value;
this.NavTypeChanged?.Invoke(last, value);
}
}
}
/// <summary>
/// An event that is raised when <see cref="IsAutoNavMode"/> is changed.
/// This can be used for custom actions like hiding the mouse cursor when automatic navigation is enabled.
/// </summary>
public event Action<bool> AutoNavModeChanged;
/// <summary>
/// An event that is raised when <see cref="NavType"/> is changed. It receives the previous navigation type, as well as the newly set navigation type.
/// </summary>
public event Action<NavigationType, NavigationType> NavTypeChanged;
/// <summary>
/// This value ist true if the <see cref="InputHandler"/> was created by this ui controls instance, or if it was passed in.
@ -134,6 +151,7 @@ namespace MLEM.Ui {
private readonly Dictionary<string, Element> selectedElements = new Dictionary<string, Element>();
private bool isAutoNavMode;
private NavigationType navType;
/// <summary>
/// Creates a new instance of the ui controls.
@ -167,6 +185,7 @@ namespace MLEM.Ui {
if (this.Input.IsPressedAvailable(MouseButton.Left)) {
this.IsAutoNavMode = false;
this.NavType = NavigationType.Mouse;
var selectedNow = mousedNow != null && mousedNow.CanBeSelected ? mousedNow : null;
this.SelectElement(this.ActiveRoot, selectedNow);
if (mousedNow != null && mousedNow.CanBePressed) {
@ -175,6 +194,7 @@ namespace MLEM.Ui {
}
} else if (this.Input.IsPressedAvailable(MouseButton.Right)) {
this.IsAutoNavMode = false;
this.NavType = NavigationType.Mouse;
if (mousedNow != null && mousedNow.CanBePressed) {
this.PressElement(mousedNow, true);
this.Input.TryConsumePressed(MouseButton.Right);
@ -187,12 +207,14 @@ namespace MLEM.Ui {
if (this.HandleKeyboard) {
if (this.KeyboardButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Keyboard;
// primary or secondary action on element using space or enter
this.PressElement(this.SelectedElement, this.Input.IsModifierKeyDown(ModifierKey.Shift));
this.KeyboardButtons.TryConsumePressed(this.Input, this.GamepadIndex);
}
} else if (this.Input.IsPressedAvailable(Keys.Tab)) {
this.IsAutoNavMode = true;
this.NavType = NavigationType.Keyboard;
// tab or shift-tab to next or previous element
var backward = this.Input.IsModifierKeyDown(ModifierKey.Shift);
var next = this.GetTabNextElement(backward);
@ -208,12 +230,14 @@ namespace MLEM.Ui {
if (this.HandleTouch) {
if (this.Input.GetViewportGesture(GestureType.Tap, out var tap)) {
this.IsAutoNavMode = false;
this.NavType = NavigationType.Touch;
var tapped = this.GetElementUnderPos(tap.Position);
this.SelectElement(this.ActiveRoot, tapped);
if (tapped != null && tapped.CanBePressed)
this.PressElement(tapped);
} else if (this.Input.GetViewportGesture(GestureType.Hold, out var hold)) {
this.IsAutoNavMode = false;
this.NavType = NavigationType.Touch;
var held = this.GetElementUnderPos(hold.Position);
this.SelectElement(this.ActiveRoot, held);
if (held != null && held.CanBePressed)
@ -224,6 +248,7 @@ namespace MLEM.Ui {
foreach (var location in this.Input.ViewportTouchState) {
var element = this.GetElementUnderPos(location.Position);
if (location.State == TouchLocationState.Pressed) {
this.NavType = NavigationType.Touch;
// start touching an element if we just touched down on it
this.SetTouchedElement(element);
} else if (element != this.TouchedElement) {
@ -239,11 +264,13 @@ namespace MLEM.Ui {
if (this.HandleGamepad) {
if (this.GamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Gamepad;
this.PressElement(this.SelectedElement);
this.GamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
}
} else if (this.SecondaryGamepadButtons.IsPressedAvailable(this.Input, this.GamepadIndex)) {
if (this.SelectedElement?.Root != null && this.SelectedElement.CanBePressed) {
this.NavType = NavigationType.Gamepad;
this.PressElement(this.SelectedElement, true);
this.SecondaryGamepadButtons.TryConsumePressed(this.Input, this.GamepadIndex);
}
@ -286,8 +313,9 @@ namespace MLEM.Ui {
/// </summary>
/// <param name="root">The root element of the <see cref="Element"/></param>
/// <param name="element">The element to select, or null to deselect the selected element.</param>
/// <param name="autoNav">Whether automatic navigation should be forced on</param>
public void SelectElement(RootElement root, Element element, bool? autoNav = null) {
/// <param name="autoNav">Whether automatic navigation should be forced on. If this is <see langword="null"/>, the automatic navigation state will stay the same.</param>
/// <param name="navType">An optional <see cref="NavigationType"/> to set. If this is <see langword="null"/>, the navigation type will stay the same.</param>
public void SelectElement(RootElement root, Element element, bool? autoNav = null, NavigationType? navType = null) {
if (root == null)
return;
if (element != null && !element.CanBeSelected)
@ -308,6 +336,8 @@ namespace MLEM.Ui {
if (autoNav != null)
this.IsAutoNavMode = autoNav.Value;
if (navType != null)
this.NavType = navType.Value;
}
/// <summary>
@ -444,6 +474,7 @@ namespace MLEM.Ui {
private bool HandleGamepadNextElement(Direction2 dir) {
this.IsAutoNavMode = true;
this.NavType = NavigationType.Gamepad;
var next = this.GetGamepadNextElement(dir);
if (this.SelectedElement != null)
next = this.SelectedElement.GetGamepadNextElement(dir, next);
@ -454,5 +485,34 @@ namespace MLEM.Ui {
return false;
}
/// <summary>
/// An enumeration type that represents the possible types of navigation that a <see cref="UiControls"/> instance supports.
/// This is used by <see cref="UiControls.NavType"/>, which stores the most recently used navigation type for a <see cref="UiSystem"/>.
/// </summary>
public enum NavigationType {
/// <summary>
/// An unknown navigation type, which usually means there has not been any ui navigation of any type yet.
/// </summary>
Unknown = 0,
/// <summary>
/// Mouse cursor and mouse button navigation.
/// </summary>
Mouse,
/// <summary>
/// Keyboard navigation.
/// </summary>
Keyboard,
/// <summary>
/// Touch and gesture navigation.
/// </summary>
Touch,
/// <summary>
/// Gamepad-style navigation, which may also include arrow key-based navigation based on current <see cref="UiControls"/> settings.
/// </summary>
Gamepad
}
}
}

View file

@ -608,9 +608,10 @@ namespace MLEM.Ui {
/// Optionally, automatic navigation can be forced on, causing the <see cref="UiStyle.SelectionIndicator"/> to be drawn around the element.
/// </summary>
/// <param name="element">The element to select, or null to deselect the selected element.</param>
/// <param name="autoNav">Whether automatic navigation should be forced on</param>
public void SelectElement(Element element, bool? autoNav = null) {
this.System.Controls.SelectElement(this, element, autoNav);
/// <param name="autoNav">Whether automatic navigation should be forced on. If this is <see langword="null"/>, the automatic navigation state will stay the same.</param>
/// <param name="navType">An optional <see cref="UiControls.NavigationType"/> to set. If this is <see langword="null"/>, the navigation type will stay the same.</param>
public void SelectElement(Element element, bool? autoNav = null, UiControls.NavigationType? navType = null) {
this.System.Controls.SelectElement(this, element, autoNav, navType);
}
/// <summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Globalization;
using Microsoft.Xna.Framework;
@ -36,6 +37,27 @@ namespace MLEM.Extensions {
return new Color(color.ToVector4() * other.ToVector4());
}
/// <summary>
/// Returns the hexadecimal representation of this color as a string in the format <c>#AARRGGBB</c>, or optionally <c>AARRGGBB</c>, without the pound symbol.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <param name="hash">Whether a # should prepend the string.</param>
/// <returns>The resulting hex string.</returns>
public static string ToHexStringRgba(this Color color, bool hash = true) {
return $"{(hash ? "#" : string.Empty)}{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}";
}
/// <summary>
/// Returns the hexadecimal representation of this color as a string in the format <c>#RRGGBB</c>, or optionally <c>RRGGBB</c>, without the pound symbol.
/// The alpha channel is ignored.
/// </summary>
/// <param name="color">The color to convert.</param>
/// <param name="hash">Whether a # should prepend the string.</param>
/// <returns>The resulting hex string.</returns>
public static string ToHexStringRgb(this Color color, bool hash = true) {
return $"{(hash ? "#" : string.Empty)}{color.R:X2}{color.G:X2}{color.B:X2}";
}
}
/// <summary>
@ -64,16 +86,34 @@ namespace MLEM.Extensions {
}
/// <summary>
/// Parses a hexadecimal string into a color.
/// The string can either be formatted as RRGGBB or AARRGGBB and can optionally start with a <c>#</c>.
/// Parses a hexadecimal string into a color and throws a <see cref="FormatException"/> if parsing fails.
/// The string can either be formatted as <c>RRGGBB</c> or <c>AARRGGBB</c> and can optionally start with a <c>#</c>.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <returns>The resulting color.</returns>
/// <exception cref="FormatException">Thrown if parsing fails.</exception>
public static Color FromHexString(string value) {
if (!ColorHelper.TryFromHexString(value, out var val))
throw new FormatException($"Cannot parse hex string {value}");
return val;
}
/// <summary>
/// Tries to parse a hexadecimal string into a color and returns whether a color was successfully parsed.
/// The string can either be formatted as <c>RRGGBB</c> or <c>AARRGGBB</c> and can optionally start with a <c>#</c>.
/// </summary>
/// <param name="value">The string to parse.</param>
/// <param name="color">The resulting color.</param>
/// <returns>Whether parsing was successful.</returns>
public static bool TryFromHexString(string value, out Color color) {
if (value.StartsWith("#"))
value = value.Substring(1);
var val = int.Parse(value, NumberStyles.HexNumber);
return value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
if (int.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var val)) {
color = value.Length > 6 ? ColorHelper.FromHexRgba(val) : ColorHelper.FromHexRgb(val);
return true;
}
color = default;
return false;
}
}

View file

@ -69,11 +69,22 @@ namespace MLEM.Extensions {
/// </summary>
/// <param name="device">The graphics device</param>
/// <param name="target">The render target to apply</param>
/// <returns></returns>
/// <returns>The render target context, to be used in a <c>using</c> statement</returns>
public static TargetContext WithRenderTarget(this GraphicsDevice device, RenderTarget2D target) {
return new TargetContext(device, target);
}
/// <summary>
/// Starts a new <see cref="TargetContext"/> using the specified render target bindings.
/// The returned context automatically disposes when used in a <c>using</c> statement, which causes any previously applied render targets to be reapplied automatically.
/// </summary>
/// <param name="device">The graphics device</param>
/// <param name="targets">The render targets to apply</param>
/// <returns>The render target context, to be used in a <c>using</c> statement</returns>
public static TargetContext WithRenderTargets(this GraphicsDevice device, params RenderTargetBinding[] targets) {
return new TargetContext(device, targets);
}
/// <summary>
/// Represents a context in which a <see cref="RenderTarget2D"/> is applied.
/// This class should be used with <see cref="GraphicsExtensions.WithRenderTarget"/>.
@ -88,7 +99,20 @@ namespace MLEM.Extensions {
/// </summary>
/// <param name="device">The graphics device to apply the target on</param>
/// <param name="target">The target to apply</param>
public TargetContext(GraphicsDevice device, RenderTarget2D target) {
public TargetContext(GraphicsDevice device, RenderTarget2D target) : this(device) {
device.SetRenderTarget(target);
}
/// <summary>
/// Creates a new target context with the given settings.
/// </summary>
/// <param name="device">The graphics device to apply the target on</param>
/// <param name="targets">The targets to apply</param>
public TargetContext(GraphicsDevice device, RenderTargetBinding[] targets) : this(device) {
device.SetRenderTargets(targets);
}
private TargetContext(GraphicsDevice device) {
this.device = device;
#if FNA
// RenderTargetCount doesn't exist in FNA but we still want the optimization in MG
@ -96,7 +120,6 @@ namespace MLEM.Extensions {
#else
this.lastTargets = device.RenderTargetCount <= 0 ? null : device.GetRenderTargets();
#endif
device.SetRenderTarget(target);
}
/// <summary>

View file

@ -16,8 +16,7 @@ namespace MLEM.Extensions {
/// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry</returns>
public static T GetRandomEntry<T>(this Random random, ICollection<T> entries) {
// ElementAt internally optimizes for IList access so we don't have to here
return entries.ElementAt(random.Next(entries.Count));
return RandomExtensions.GetRandomEntry(entries, random.NextSingle());
}
/// <summary>
@ -31,28 +30,12 @@ namespace MLEM.Extensions {
/// <returns>A random entry, based on the entries' weight</returns>
/// <exception cref="IndexOutOfRangeException">If the weight function returns different weights for the same entry</exception>
public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, int> weightFunc) {
var totalWeight = entries.Sum(weightFunc);
var goalWeight = random.Next(totalWeight);
var currWeight = 0;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle());
}
/// <inheritdoc cref="GetRandomWeightedEntry{T}(System.Random,System.Collections.Generic.ICollection{T},System.Func{T,int})"/>
public static T GetRandomWeightedEntry<T>(this Random random, ICollection<T> entries, Func<T, float> weightFunc) {
var totalWeight = entries.Sum(weightFunc);
var goalWeight = random.NextDouble() * totalWeight;
var currWeight = 0F;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, random.NextSingle());
}
/// <summary>
@ -87,5 +70,32 @@ namespace MLEM.Extensions {
}
#endif
internal static T GetRandomEntry<T>(ICollection<T> entries, float randomValue) {
// ElementAt internally optimizes for IList access so we don't have to here
return entries.ElementAt((int) (randomValue * entries.Count));
}
internal static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, int> weightFunc, float randomValue) {
var goalWeight = randomValue * entries.Sum(weightFunc);
var currWeight = 0;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
}
internal static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, float> weightFunc, float randomValue) {
var goalWeight = randomValue * entries.Sum(weightFunc);
var currWeight = 0F;
foreach (var entry in entries) {
currWeight += weightFunc(entry);
if (currWeight > goalWeight)
return entry;
}
throw new IndexOutOfRangeException();
}
}
}

View file

@ -63,6 +63,18 @@ namespace MLEM.Font {
return (curr, 1);
}
/// <summary>
/// Returns an index in this code point source that is as close to <paramref name="index"/> as possible, but not between two members of a surrogate pair. If the <paramref name="index"/> is already not between surrogate pairs, it is returned unchanged.
/// </summary>
/// <param name="index">The index to ensure is not between surrogates.</param>
/// <param name="increase">Whether the returned index should be increased by 1 (instead of decreased by 1) when it is between surrogates.</param>
/// <returns>An index close to <paramref name="index"/>, but not between surrogates.</returns>
public int EnsureSurrogateBoundary(int index, bool increase) {
if (index < this.Length && char.IsLowSurrogate(this[index]))
return increase || index <= 0 ? index + 1 : index - 1;
return index;
}
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1" /> that can be used to iterate through the collection.</returns>
/// <filterpriority>1</filterpriority>

View file

@ -23,9 +23,9 @@ namespace MLEM.Formatting.Codes {
public readonly Match Match;
/// <summary>
/// The tokens that this formatting code is a part of.
/// Note that this array only has multiple entries if additional tokens have to be started while this code is still applied.
/// Note that this collection only has multiple entries if additional tokens have to be started while this code is still applied.
/// </summary>
public IList<Token> Tokens { get; internal set; }
public readonly List<Token> Tokens = new List<Token>();
/// <summary>
/// Creates a new formatting code based on a formatting code regex and its match.

View file

@ -18,5 +18,11 @@ namespace MLEM.Formatting.Codes {
return this.font?.Invoke(defaultPick);
}
/// <inheritdoc />
public override bool EndsHere(Code other) {
// turning a string bold/italic should only end when that specific code is ended using SimpleEndCode
return false;
}
}
}

View file

@ -102,7 +102,7 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex("<b>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Bold));
this.Codes.Add(new Regex("<i>"), (f, m, r) => new FontCode(m, r, fnt => fnt.Italic));
this.Codes.Add(new Regex(@"<s(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new ShadowCode(m, r,
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultShadowColor,
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : this.DefaultShadowColor,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var offset) ? new Vector2(offset) : this.DefaultShadowOffset));
this.Codes.Add(new Regex("<u>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.UnderlineOffset));
this.Codes.Add(new Regex("<st>"), (f, m, r) => new UnderlineCode(m, r, this.LineThickness, this.StrikethroughOffset));
@ -111,7 +111,7 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex(@"<sup(?: ([+-.0-9]+))?>"), (f, m, r) => new SubSupCode(m, r,
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var off) ? -off : this.DefaultSupOffset));
this.Codes.Add(new Regex(@"<o(?: #([0-9\w]{6,8}) (([+-.0-9]*)))?>"), (f, m, r) => new OutlineCode(m, r,
m.Groups[1].Success ? ColorHelper.FromHexString(m.Groups[1].Value) : this.DefaultOutlineColor,
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : this.DefaultOutlineColor,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var thickness) ? thickness : this.DefaultOutlineThickness,
this.OutlineDiagonals));
}
@ -124,12 +124,13 @@ namespace MLEM.Formatting {
this.Codes.Add(new Regex($"<c {c.Name}>"), (f, m, r) => new ColorCode(m, r, value));
}
}
this.Codes.Add(new Regex(@"<c #([0-9\w]{6,8})>"), (f, m, r) => new ColorCode(m, r, ColorHelper.FromHexString(m.Groups[1].Value)));
this.Codes.Add(new Regex(@"<c #([0-9\w]{6,8})>"), (f, m, r) => new ColorCode(m, r,
ColorHelper.TryFromHexString(m.Groups[1].Value, out var color) ? color : Color.Red));
}
// animation codes
if (hasAnimations) {
this.Codes.Add(new Regex(@"<a wobbly(?: ([+-.0-9]*) ([+-.0-9]*))?>"), (f, m, r) => new WobblyCode(m, r,
this.Codes.Add(new Regex("<a wobbly(?: ([+-.0-9]*) ([+-.0-9]*))?>"), (f, m, r) => new WobblyCode(m, r,
float.TryParse(m.Groups[1].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var mod) ? mod : this.DefaultWobblyModifier,
float.TryParse(m.Groups[2].Value, NumberStyles.Number, CultureInfo.InvariantCulture, out var heightMod) ? heightMod : this.DefaultWobblyHeight));
}
@ -155,11 +156,12 @@ namespace MLEM.Formatting {
// resolve macros
s = this.ResolveMacros(s);
var tokens = new List<Token>();
var codes = new List<Code>();
var applied = new List<Code>();
var allCodes = new List<Code>();
// add the formatting code right at the start of the string
var firstCode = this.GetNextCode(s, 0, 0);
if (firstCode != null)
codes.Add(firstCode);
applied.Add(firstCode);
var index = 0;
var rawIndex = 0;
while (rawIndex < s.Length) {
@ -167,24 +169,25 @@ namespace MLEM.Formatting {
// if we've reached the end of the string
if (next == null) {
var sub = s.Substring(rawIndex, s.Length - rawIndex);
tokens.Add(new Token(codes.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, codes), sub));
tokens.Add(new Token(applied.ToArray(), index, rawIndex, TextFormatter.StripFormatting(font, sub, applied), sub));
break;
}
allCodes.Add(next);
// create a new token for the content up to the next code
var ret = s.Substring(rawIndex, next.Match.Index - rawIndex);
var strippedRet = TextFormatter.StripFormatting(font, ret, codes);
tokens.Add(new Token(codes.ToArray(), index, rawIndex, strippedRet, ret));
var strippedRet = TextFormatter.StripFormatting(font, ret, applied);
tokens.Add(new Token(applied.ToArray(), index, rawIndex, strippedRet, ret));
// move to the start of the next code
rawIndex = next.Match.Index;
index += strippedRet.Length;
// remove all codes that are incompatible with the next one and apply it
codes.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c));
codes.Add(next);
applied.RemoveAll(c => c.EndsHere(next) || next.EndsOther(c));
applied.Add(next);
}
return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, tokens.SelectMany(t => t.AppliedCodes)), tokens.ToArray());
return new TokenizedString(font, alignment, s, TextFormatter.StripFormatting(font, s, allCodes), tokens.ToArray(), allCodes.ToArray());
}
/// <summary>

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
@ -14,6 +15,7 @@ namespace MLEM.Formatting {
/// <summary>
/// The formatting codes that are applied on this token.
/// Codes are stored application order, with the first entry in the array being the code that was most recently applied.
/// </summary>
public readonly Code[] AppliedCodes;
/// <summary>
@ -45,11 +47,14 @@ namespace MLEM.Formatting {
internal float[] InnerOffsets;
internal Token(Code[] appliedCodes, int index, int rawIndex, string substring, string rawSubstring) {
Array.Reverse(appliedCodes);
this.AppliedCodes = appliedCodes;
this.Index = index;
this.RawIndex = rawIndex;
this.Substring = substring;
this.RawSubstring = rawSubstring;
foreach (var code in appliedCodes)
code.Tokens.Add(this);
}
/// <summary>

View file

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Extensions;
using MLEM.Font;
using MLEM.Formatting.Codes;
using MLEM.Misc;
@ -42,17 +40,11 @@ namespace MLEM.Formatting {
private float initialInnerOffset;
private RectangleF area;
internal TokenizedString(GenericFont font, TextAlignment alignment, string rawString, string strg, Token[] tokens) {
internal TokenizedString(GenericFont font, TextAlignment alignment, string rawString, string strg, Token[] tokens, Code[] allCodes) {
this.RawString = rawString;
this.String = strg;
this.Tokens = tokens;
// since a code can be present in multiple tokens, we use Distinct here
this.AllCodes = tokens.SelectMany(t => t.AppliedCodes).Distinct().ToArray();
// TODO this can probably be optimized by keeping track of a code's tokens while tokenizing
foreach (var code in this.AllCodes)
code.Tokens = new ReadOnlyCollection<Token>(this.Tokens.Where(t => t.AppliedCodes.Contains(code)).ToList());
this.AllCodes = allCodes;
this.Realign(font, alignment);
}

View file

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MLEM.Misc;
using MLEM.Textures;
namespace MLEM.Graphics {
@ -86,92 +87,94 @@ namespace MLEM.Graphics {
/// <param name="layerDepth">The layer depth to draw with.</param>
/// <param name="overlayDepthOffset">An optional depth offset from <paramref name="layerDepth"/> that the overlay should be drawn with</param>
public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (r1, r2, r3, r4) = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo);
if (backgroundTexture != null)
batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
if (r1 != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (r2 != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, r2, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (r3 != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (r4 != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, r4, overlayColor, 0, orig, sc, SpriteEffects.None, od);
batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
var od = layerDepth + overlayDepthOffset;
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od);
}
/// <summary>
/// This method allows for a single corner of a tiled texture to be drawn in an auto-tiling mode.
/// This allows, for example, a grass patch on a tilemap to have nice looking edges that transfer over into a path without any hard edges between tiles.
///
/// For more information, and to draw all four corners at once, see <see cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
/// </summary>
/// <param name="batch">The sprite batch to use for drawing.</param>
/// <param name="pos">The position to draw at.</param>
/// <param name="overlayTexture">The first overlay region, as described in the summary.</param>
/// <param name="connectsTo">A function that determines whether two positions should connect.</param>
/// <param name="overlayColor">The color to draw border and corner textures with.</param>
/// <param name="corner">The corner of the auto-tile to draw. Can be <see cref="Direction2.UpLeft"/>, <see cref="Direction2.UpRight"/>, <see cref="Direction2.DownLeft"/> or <see cref="Direction2.DownRight"/>.</param>
/// <param name="origin">The origin to draw from.</param>
/// <param name="scale">The scale to draw with.</param>
/// <param name="layerDepth">The layer depth to draw with.</param>
public static void DrawExtendedAutoTileCorner(SpriteBatch batch, Vector2 pos, TextureRegion overlayTexture, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) {
var src = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo, corner);
if (src != Rectangle.Empty)
batch.Draw(overlayTexture.Texture, pos, src, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
}
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void DrawExtendedAutoTile(SpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
if (backgroundTexture != null)
batch.Draw(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
if (xUl >= 0)
batch.Draw(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (xUr >= 0)
batch.Draw(overlayTextures(xUr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (xDl >= 0)
batch.Draw(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
if (xDr >= 0)
batch.Draw(overlayTextures(xDr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
batch.Draw(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
var od = layerDepth + overlayDepthOffset;
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od);
AutoTiling.DrawExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od);
}
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/>
public static void DrawExtendedAutoTileCorner(SpriteBatch batch, Vector2 pos, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0) {
var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
if (src >= 0)
batch.Draw(overlayTextures(src), pos, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
}
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, TextureRegion overlayTexture, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (r1, r2, r3, r4) = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo);
if (backgroundTexture != null) {
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(background);
}
if (r1 != Rectangle.Empty) {
var o1 = batch.Add(overlayTexture.Texture, pos, r1, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o1);
}
if (r2 != Rectangle.Empty) {
var o2 = batch.Add(overlayTexture.Texture, pos, r2, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o2);
}
if (r3 != Rectangle.Empty) {
var o3 = batch.Add(overlayTexture.Texture, pos, r3, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o3);
}
if (r4 != Rectangle.Empty) {
var o4 = batch.Add(overlayTexture.Texture, pos, r4, overlayColor, 0, orig, sc, SpriteEffects.None, od);
var od = layerDepth + overlayDepthOffset;
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTexture, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items);
}
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/>
public static void AddExtendedAutoTileCorner(StaticSpriteBatch batch, Vector2 pos, TextureRegion overlayTexture, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var src = AutoTiling.CalculateExtendedAutoTile(overlayTexture.Area, connectsTo, corner);
if (src != Rectangle.Empty) {
var o4 = batch.Add(overlayTexture.Texture, pos, src, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(o4);
}
}
/// <inheritdoc cref="DrawExtendedAutoTile(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,Func{int,MLEM.Textures.TextureRegion},MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,Microsoft.Xna.Framework.Color,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float,float)"/>
public static void AddExtendedAutoTile(StaticSpriteBatch batch, Vector2 pos, TextureRegion backgroundTexture, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color backgroundColor, Color overlayColor, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, float overlayDepthOffset = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var orig = origin ?? Vector2.Zero;
var sc = scale ?? Vector2.One;
var od = layerDepth + overlayDepthOffset;
var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
if (backgroundTexture != null) {
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, orig, sc, SpriteEffects.None, layerDepth);
var background = batch.Add(backgroundTexture, pos, backgroundColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(background);
}
if (xUl >= 0) {
var o1 = batch.Add(overlayTextures(xUl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o1);
}
if (xUr >= 0) {
var o2 = batch.Add(overlayTextures(xUr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o2);
}
if (xDl >= 0) {
var o3 = batch.Add(overlayTextures(xDl), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
items?.Add(o3);
}
if (xDr >= 0) {
var o4 = batch.Add(overlayTextures(xDr), pos, overlayColor, 0, orig, sc, SpriteEffects.None, od);
var od = layerDepth + overlayDepthOffset;
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpLeft, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.UpRight, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownLeft, origin, scale, od, items);
AutoTiling.AddExtendedAutoTileCorner(batch, pos, overlayTextures, connectsTo, overlayColor, Direction2.DownRight, origin, scale, od, items);
}
/// <inheritdoc cref="DrawExtendedAutoTileCorner(Microsoft.Xna.Framework.Graphics.SpriteBatch,Microsoft.Xna.Framework.Vector2,MLEM.Textures.TextureRegion,MLEM.Graphics.AutoTiling.ConnectsTo,Microsoft.Xna.Framework.Color,MLEM.Misc.Direction2,System.Nullable{Microsoft.Xna.Framework.Vector2},System.Nullable{Microsoft.Xna.Framework.Vector2},float)"/>
public static void AddExtendedAutoTileCorner(StaticSpriteBatch batch, Vector2 pos, Func<int, TextureRegion> overlayTextures, ConnectsTo connectsTo, Color overlayColor, Direction2 corner, Vector2? origin = null, Vector2? scale = null, float layerDepth = 0, ICollection<StaticSpriteBatch.Item> items = null) {
var src = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
if (src >= 0) {
var o4 = batch.Add(overlayTextures(src), pos, overlayColor, 0, origin ?? Vector2.Zero, scale ?? Vector2.One, SpriteEffects.None, layerDepth);
items?.Add(o4);
}
}
@ -194,26 +197,36 @@ namespace MLEM.Graphics {
new Vector2(pos.X + w2 * scale.X, pos.Y + h2 * scale.Y), new Rectangle(textureRegion.X + w2 + xDr * w, textureRegion.Y + h2, w2, h2));
}
private static (int, int, int, int) CalculateExtendedAutoTileOffsets(ConnectsTo connectsTo) {
var up = connectsTo(0, -1);
var down = connectsTo(0, 1);
var left = connectsTo(-1, 0);
var right = connectsTo(1, 0);
return (
up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4,
up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5,
down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6,
down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7);
private static int CalculateExtendedAutoTileOffset(ConnectsTo connectsTo, Direction2 corner) {
switch (corner) {
case Direction2.UpLeft: {
var up = connectsTo(0, -1);
var left = connectsTo(-1, 0);
return up && left ? connectsTo(-1, -1) ? -1 : 12 : left ? 0 : up ? 8 : 4;
}
case Direction2.UpRight: {
var up = connectsTo(0, -1);
var right = connectsTo(1, 0);
return up && right ? connectsTo(1, -1) ? -1 : 13 : right ? 1 : up ? 9 : 5;
}
case Direction2.DownLeft: {
var down = connectsTo(0, 1);
var left = connectsTo(-1, 0);
return down && left ? connectsTo(-1, 1) ? -1 : 14 : left ? 2 : down ? 10 : 6;
}
case Direction2.DownRight: {
var down = connectsTo(0, 1);
var right = connectsTo(1, 0);
return down && right ? connectsTo(1, 1) ? -1 : 15 : right ? 3 : down ? 11 : 7;
}
default:
throw new ArgumentOutOfRangeException(nameof(corner), corner, null);
}
}
private static (Rectangle, Rectangle, Rectangle, Rectangle) CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo) {
var (xUl, xUr, xDl, xDr) = AutoTiling.CalculateExtendedAutoTileOffsets(connectsTo);
var (w, h) = (textureRegion.Width, textureRegion.Height);
return (
xUl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xUl * w, textureRegion.Y, w, h),
xUr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xUr * w, textureRegion.Y, w, h),
xDl < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xDl * w, textureRegion.Y, w, h),
xDr < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + xDr * w, textureRegion.Y, w, h));
private static Rectangle CalculateExtendedAutoTile(Rectangle textureRegion, ConnectsTo connectsTo, Direction2 corner) {
var off = AutoTiling.CalculateExtendedAutoTileOffset(connectsTo, corner);
return off < 0 ? Rectangle.Empty : new Rectangle(textureRegion.X + off * textureRegion.Width, textureRegion.Y, textureRegion.Width, textureRegion.Height);
}
/// <summary>

View file

@ -121,11 +121,11 @@ namespace MLEM.Input {
/// <summary>
/// Contains the <see cref="LastTouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary>
public IList<TouchLocation> LastViewportTouchState { get; private set; }
public IList<TouchLocation> LastViewportTouchState { get; private set; } = new List<TouchLocation>();
/// <summary>
/// Contains the <see cref="TouchState"/>, but with the <see cref="GraphicsDevice.Viewport"/> taken into account.
/// </summary>
public IList<TouchLocation> ViewportTouchState { get; private set; }
public IList<TouchLocation> ViewportTouchState { get; private set; } = new List<TouchLocation>();
/// <summary>
/// Contains the amount of gamepads that are currently connected. Note that this value will be set to 0 if <see cref="HandleGamepads"/> is false.
/// This field is automatically updated in <see cref="Update()"/>.
@ -342,6 +342,7 @@ namespace MLEM.Input {
}
} else {
this.TouchState = new TouchCollection(InputHandler.EmptyTouchLocations);
this.ViewportTouchState = this.TouchState;
this.gestures.Clear();
}

View file

@ -105,7 +105,8 @@ namespace MLEM.Input {
set {
var val = (int) MathHelper.Clamp(value, 0F, this.text.Length);
if (this.caretPos != val) {
this.caretPos = val;
// ensure that we don't move to a location that is between high and low surrogates
this.caretPos = new CodePointSource(this.text).EnsureSurrogateBoundary(val, val > this.caretPos);
this.caretBlinkTimer = 0;
this.SetTextDataDirty(false);
}
@ -203,6 +204,21 @@ namespace MLEM.Input {
}
}
/// <summary>
/// The maximum amount of lines that can be visible in this text input, based on its <see cref="Size"/>, the used <see cref="Font"/> and its <see cref="TextScale"/>.
/// Note that this may return a number higher than 1 even if this is not a <see cref="Multiline"/> text input.
/// </summary>
public int MaxDisplayedLines => (this.Size.Y / (this.Font.LineHeight * this.TextScale)).Floor();
/// <summary>
/// The index of the first line that is currently visible.
/// This value can be changed using <see cref="ShowLine"/>.
/// </summary>
public int FirstVisibleLine { get; private set; }
/// <summary>
/// The total amount of lines of text that this text input currently has, including additional lines added by automatic wrapping.
/// If this is not a <see cref="Multiline"/> text input, this value is always 1.
/// </summary>
public int Lines { get; private set; }
/// <summary>
/// A function that is invoked when a string of text should be copied to the clipboard.
/// MLEM.Ui uses the TextCopy package for this, but other options are available.
/// </summary>
@ -217,10 +233,9 @@ namespace MLEM.Input {
private char? maskingCharacter;
private double caretBlinkTimer;
private string displayedText;
private string[] splitText;
private string visibleText;
private string[] multilineSplitText;
private int textOffset;
private int lineOffset;
private int caretPos;
private int caretLine;
private int caretPosInLine;
@ -301,9 +316,9 @@ namespace MLEM.Input {
this.CaretPos--;
} else if (this.CaretPos < this.text.Length && input.TryConsumePressed(Keys.Right)) {
this.CaretPos++;
} else if (this.Multiline && input.IsPressedAvailable(Keys.Up) && this.MoveCaretToLine(this.CaretLine - 1)) {
} else if (this.Multiline && input.IsPressedAvailable(Keys.Up) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine - 1) : this.MoveCaretToLine(this.CaretLine - 1))) {
input.TryConsumePressed(Keys.Up);
} else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && this.MoveCaretToLine(this.CaretLine + 1)) {
} else if (this.Multiline && input.IsPressedAvailable(Keys.Down) && (input.IsModifierKeyDown(ModifierKey.Control) ? this.ShowLine(this.FirstVisibleLine + 1) : this.MoveCaretToLine(this.CaretLine + 1))) {
input.TryConsumePressed(Keys.Down);
} else if (this.CaretPos != 0 && input.TryConsumePressed(Keys.Home)) {
this.CaretPos = 0;
@ -339,12 +354,12 @@ namespace MLEM.Input {
this.UpdateTextDataIfDirty();
var scale = this.TextScale * drawScale;
this.Font.DrawString(batch, this.displayedText, textPos, textColor, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
this.Font.DrawString(batch, this.visibleText, textPos, textColor, 0, Vector2.Zero, scale, SpriteEffects.None, 0);
if (caretWidth > 0 && this.caretBlinkTimer < 0.5F) {
var caretDrawPos = textPos + new Vector2(this.caretDrawOffset * scale, 0);
if (this.Multiline)
caretDrawPos.Y += this.Font.LineHeight * (this.CaretLine - this.lineOffset) * scale;
caretDrawPos.Y += this.Font.LineHeight * (this.CaretLine - this.FirstVisibleLine) * scale;
batch.Draw(batch.GetBlankTexture(), new RectangleF(caretDrawPos, new Vector2(caretWidth * drawScale, this.Font.LineHeight * scale)), null, textColor);
}
}
@ -360,7 +375,7 @@ namespace MLEM.Input {
if (!this.FilterText(ref strg, removeMismatching))
return;
if (this.MaximumCharacters != null && strg.Length > this.MaximumCharacters)
strg = strg.Substring(0, this.MaximumCharacters.Value);
strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value, false));
this.text.Clear();
this.text.Append(strg);
this.CaretPos = this.text.Length;
@ -378,7 +393,7 @@ namespace MLEM.Input {
if (!this.FilterText(ref strg, removeMismatching))
return false;
if (this.MaximumCharacters != null && this.text.Length + strg.Length > this.MaximumCharacters)
strg = strg.Substring(0, this.MaximumCharacters.Value - this.text.Length);
strg = strg.Substring(0, new CodePointSource(strg).EnsureSurrogateBoundary(this.MaximumCharacters.Value - this.text.Length, false));
this.text.Insert(this.CaretPos, strg);
this.CaretPos += strg.Length;
this.SetTextDataDirty();
@ -393,7 +408,8 @@ namespace MLEM.Input {
public bool RemoveText(int index, int length) {
if (index < 0 || index >= this.text.Length)
return false;
this.text.Remove(index, length);
var source = new CodePointSource(this.text);
this.text.Remove(source.EnsureSurrogateBoundary(index, false), source.EnsureSurrogateBoundary(index + length, true) - index);
// ensure that caret pos is still in bounds
this.CaretPos = this.CaretPos;
this.SetTextDataDirty();
@ -417,7 +433,7 @@ namespace MLEM.Input {
this.CaretPos = destStart + destAccum.Length;
return true;
}
destAccum += this.text[destStart + destAccum.Length];
destAccum += CodePointSource.ToString(new CodePointSource(this.text).GetCodePoint(destStart + destAccum.Length).CodePoint);
}
// if we don't find a proper position, just move to the end of the destination line
this.CaretPos = destEnd;
@ -426,6 +442,26 @@ namespace MLEM.Input {
return false;
}
/// <summary>
/// Moves visual focus into such bounds that the given line will be the first visible line of this text input.
/// </summary>
/// <param name="line">The first line that should be visible.</param>
/// <returns>Whether the line can be the fist visible line, and wasn't already the first visible line.</returns>
public bool ShowLine(int line) {
if (this.FirstVisibleLine != line && line >= 0 && line < this.Lines - (this.MaxDisplayedLines - 1)) {
this.FirstVisibleLine = line;
// move the caret into visible bounds if necessary
var clampedCaretLine = (int) MathHelper.Clamp(this.CaretLine, line, line + this.MaxDisplayedLines - 1F);
if (clampedCaretLine != this.CaretLine)
this.MoveCaretToLine(clampedCaretLine);
this.SetTextDataDirty(false);
return true;
}
return false;
}
private bool FilterText(ref string text, bool removeMismatching) {
var result = new StringBuilder();
foreach (var codePoint in new CodePointSource(text)) {
@ -458,49 +494,50 @@ namespace MLEM.Input {
if (this.Multiline) {
// soft wrap if we're multiline
this.splitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray();
this.displayedText = string.Join("\n", this.splitText);
this.multilineSplitText = this.Font.SplitStringSeparate(visualText, this.Size.X, this.TextScale).ToArray();
this.visibleText = string.Join("\n", this.multilineSplitText);
this.Lines = this.visibleText.Count(c => c == '\n') + 1;
this.UpdateCaretData();
if (this.Font.MeasureString(this.displayedText).Y * this.TextScale > this.Size.Y) {
var maxLines = (this.Size.Y / (this.Font.LineHeight * this.TextScale)).Floor();
if (this.lineOffset > this.CaretLine) {
if (this.Font.MeasureString(this.visibleText).Y * this.TextScale > this.Size.Y) {
if (this.FirstVisibleLine > this.CaretLine) {
// if we're moving up
this.lineOffset = this.CaretLine;
} else if (this.CaretLine >= maxLines) {
this.FirstVisibleLine = this.CaretLine;
} else if (this.CaretLine >= this.MaxDisplayedLines) {
// if we're moving down
var limit = this.CaretLine - (maxLines - 1);
if (limit > this.lineOffset)
this.lineOffset = limit;
var limit = this.CaretLine - (this.MaxDisplayedLines - 1);
if (limit > this.FirstVisibleLine)
this.FirstVisibleLine = limit;
}
// calculate resulting string
var ret = new StringBuilder();
var lines = 0;
var originalIndex = 0;
for (var i = 0; i < this.displayedText.Length; i++) {
if (lines >= this.lineOffset) {
for (var i = 0; i < this.visibleText.Length; i++) {
if (lines >= this.FirstVisibleLine) {
if (ret.Length <= 0)
this.textOffset = originalIndex;
ret.Append(this.displayedText[i]);
ret.Append(this.visibleText[i]);
}
if (this.displayedText[i] == '\n') {
if (this.visibleText[i] == '\n') {
lines++;
if (visualText[originalIndex] == '\n')
originalIndex++;
} else {
originalIndex++;
}
if (lines - this.lineOffset >= maxLines)
if (lines - this.FirstVisibleLine >= this.MaxDisplayedLines)
break;
}
this.displayedText = ret.ToString();
this.visibleText = ret.ToString();
} else {
this.lineOffset = 0;
this.FirstVisibleLine = 0;
this.textOffset = 0;
}
} else {
this.splitText = null;
this.lineOffset = 0;
this.multilineSplitText = null;
this.FirstVisibleLine = 0;
this.Lines = 1;
// not multiline, so scroll horizontally based on caret position
if (this.Font.MeasureString(visualText).X * this.TextScale > this.Size.X) {
if (this.textOffset > this.CaretPos) {
@ -514,9 +551,9 @@ namespace MLEM.Input {
this.textOffset = bound;
}
var visible = visualText.ToString(this.textOffset, visualText.Length - this.textOffset);
this.displayedText = this.Font.TruncateString(visible, this.Size.X, this.TextScale);
this.visibleText = this.Font.TruncateString(visible, this.Size.X, this.TextScale);
} else {
this.displayedText = visualText.ToString();
this.visibleText = visualText.ToString();
this.textOffset = 0;
}
this.UpdateCaretData();
@ -524,9 +561,9 @@ namespace MLEM.Input {
}
private void UpdateCaretData() {
if (this.splitText != null) {
if (this.multilineSplitText != null) {
// the code below will never execute if our text is empty, so reset our caret position fully
if (this.splitText.Length <= 0) {
if (this.multilineSplitText.Length <= 0) {
this.caretLine = 0;
this.caretPosInLine = 0;
this.caretDrawOffset = 0;
@ -535,9 +572,9 @@ namespace MLEM.Input {
var line = 0;
var index = 0;
for (var d = 0; d < this.splitText.Length; d++) {
for (var d = 0; d < this.multilineSplitText.Length; d++) {
var startOfLine = 0;
var split = this.splitText[d];
var split = this.multilineSplitText[d];
for (var i = 0; i <= split.Length; i++) {
if (index == this.CaretPos) {
this.caretLine = line;
@ -557,20 +594,20 @@ namespace MLEM.Input {
// max width splits
line++;
}
} else if (this.displayedText != null) {
} else if (this.visibleText != null) {
this.caretLine = 0;
this.caretPosInLine = this.CaretPos;
this.caretDrawOffset = this.Font.MeasureString(this.displayedText.Substring(0, this.CaretPos - this.textOffset)).X;
this.caretDrawOffset = this.Font.MeasureString(this.visibleText.Substring(0, this.CaretPos - this.textOffset)).X;
}
}
private (int, int) GetLineBounds(int boundLine) {
if (this.splitText != null) {
if (this.multilineSplitText != null) {
var line = 0;
var index = 0;
var startOfLineIndex = 0;
for (var d = 0; d < this.splitText.Length; d++) {
var split = this.splitText[d];
for (var d = 0; d < this.multilineSplitText.Length; d++) {
var split = this.multilineSplitText[d];
for (var i = 0; i < split.Length; i++) {
index++;
if (split[i] == '\n') {

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
<RootNamespace>MLEM</RootNamespace>
<DefineConstants>$(DefineConstants);FNA</DefineConstants>
</PropertyGroup>
@ -21,7 +21,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.csproj">
<ProjectReference Include="..\ThirdParty\FNA\FNA.csproj">
<PrivateAssets>all</PrivateAssets>
</ProjectReference>

View file

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net452;netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>net452;netstandard2.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<ProduceReferenceAssembly>true</ProduceReferenceAssembly>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible Condition="'$(TargetFramework)'=='net8.0'">true</IsAotCompatible>
</PropertyGroup>
<PropertyGroup>

View file

@ -1,4 +1,5 @@
using System;
using Microsoft.Xna.Framework;
namespace MLEM.Misc {
/// <summary>
@ -8,6 +9,21 @@ namespace MLEM.Misc {
/// </summary>
public static class Easings {
/// <summary>
/// An easing function that constantly returns 0, regardless of the input percentage.
/// This is useful for chaining using <see cref="AndThen(MLEM.Misc.Easings.Easing,MLEM.Misc.Easings.Easing)"/>.
/// </summary>
public static readonly Easing Zero = p => 0;
/// <summary>
/// An easing function that constantly returns 1, regardless of the input percentage.
/// This is useful for chaining using <see cref="AndThen(MLEM.Misc.Easings.Easing,MLEM.Misc.Easings.Easing)"/>.
/// </summary>
public static readonly Easing One = p => 1;
/// <summary>
/// A linear easing function that returns the input percentage without modifying it.
/// </summary>
public static readonly Easing Linear = p => p;
/// <summary>https://easings.net/#easeInSine</summary>
public static readonly Easing InSine = p => 1 - (float) Math.Cos(p * Math.PI / 2);
/// <summary>https://easings.net/#easeOutSine</summary>
@ -170,6 +186,17 @@ namespace MLEM.Misc {
};
}
/// <summary>
/// Causes output from the easing function to be clamped between the <paramref name="min"/> and <paramref name="max"/> values passed.
/// </summary>
/// <param name="easing">The easing function to clamp.</param>
/// <param name="min">The minimum output value to clamp to, defaults to 0.</param>
/// <param name="max">The maximum output value to clamp to, defaults to 1.</param>
/// <returns>A clamped easing function.</returns>
public static Easing Clamp(this Easing easing, float min = 0, float max = 1) {
return p => MathHelper.Clamp(easing(p), min, max);
}
/// <summary>
/// A delegate method used by <see cref="Easings"/>.
/// </summary>

View file

@ -1,4 +1,8 @@
namespace MLEM.Misc {
using System;
using System.Collections.Generic;
using MLEM.Extensions;
namespace MLEM.Misc {
/// <summary>
/// The SingleRandom class allows generating single, one-off pseudorandom numbers based on a seed or a <see cref="SeedSource"/>.
/// The types of numbers that can be generated are <see cref="int"/> and <see cref="float"/>, both of which can be generated with specific minimum and maximum values if desired.
@ -138,5 +142,35 @@
return (maxValue - minValue) * SingleRandom.Single(source) + minValue;
}
/// <summary>
/// Gets a random entry from the given collection with uniform chance.
/// </summary>
/// <param name="entries">The entries to choose from</param>
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
/// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry</returns>
public static T GetRandomEntry<T>(ICollection<T> entries, SeedSource source) {
return RandomExtensions.GetRandomEntry(entries, SingleRandom.Single(source));
}
/// <summary>
/// Returns a random entry from the given collection based on the specified weight function.
/// A higher weight for an entry increases its likeliness of being picked.
/// </summary>
/// <param name="entries">The entries to choose from</param>
/// <param name="weightFunc">A function that applies weight to each entry</param>
/// <param name="source">The <see cref="SeedSource"/> to use.</param>
/// <typeparam name="T">The entries' type</typeparam>
/// <returns>A random entry, based on the entries' weight</returns>
/// <exception cref="IndexOutOfRangeException">If the weight function returns different weights for the same entry</exception>
public static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, int> weightFunc, SeedSource source) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, SingleRandom.Single(source));
}
/// <inheritdoc cref="GetRandomWeightedEntry{T}(System.Collections.Generic.ICollection{T},System.Func{T,int},MLEM.Misc.SeedSource)"/>
public static T GetRandomWeightedEntry<T>(ICollection<T> entries, Func<T, float> weightFunc, SeedSource source) {
return RandomExtensions.GetRandomWeightedEntry(entries, weightFunc, SingleRandom.Single(source));
}
}
}

View file

@ -158,11 +158,13 @@ namespace MLEM.Textures {
case NinePatchMode.Tile:
var width = src.Width * patchScale;
var height = src.Height * patchScale;
for (var x = 0F; x < rect.Width; x += width) {
for (var y = 0F; y < rect.Height; y += height) {
var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height));
var srcSize = (size / patchScale).CeilCopy().ToPoint();
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth);
if (width > 0 && height > 0) {
for (var x = 0F; x < rect.Width; x += width) {
for (var y = 0F; y < rect.Height; y += height) {
var size = new Vector2(Math.Min(rect.Width - x, width), Math.Min(rect.Height - y, height));
var srcSize = (size / patchScale).CeilCopy().ToPoint();
batch.Draw(texture.Region.Texture, new RectangleF(rect.Location + new Vector2(x, y), size), new Rectangle(src.X, src.Y, srcSize.X, srcSize.Y), color, rotation, origin, effects, layerDepth);
}
}
}
break;

View file

@ -1,5 +0,0 @@
<configuration>
<config>
<add key="globalPackagesFolder" value="./packages" />
</config>
</configuration>

View file

@ -11,6 +11,7 @@ MLEM is platform-agnostic and multi-targets .NET Standard 2.0, .NET 6.0 and .NET
- See tutorials and API documentation on [the website](https://mlem.ellpeck.de/)
- Check out [the demos](https://github.com/Ellpeck/MLEM/tree/main/Demos) on [Desktop](https://github.com/Ellpeck/MLEM/tree/main/Demos.DesktopGL) or [Android](https://github.com/Ellpeck/MLEM/tree/main/Demos.Android)
- See [the changelog](https://github.com/Ellpeck/MLEM/blob/main/CHANGELOG.md) for information on updates
- Join [the Discord server](https://link.ellpeck.de/discordweb) to ask questions
# Packages
- **MLEM** is the base package, which provides various small addons and abstractions for MonoGame and FNA, including a text formatting system and simple input handling

View file

@ -10,8 +10,6 @@
#-------------------------------- References --------------------------------#
/reference:..\..\packages\monogame.extended.content.pipeline\3.8.0\tools\MonoGame.Extended.Content.Pipeline.dll
#---------------------------------- Content ---------------------------------#
#begin Fonts/Cadman_Roman.otf
@ -20,11 +18,6 @@
#begin Fonts/Symbola-Emoji.ttf
/copy:Fonts/Symbola-Emoji.ttf
#begin Fonts/Regular.fnt
/importer:BitmapFontImporter
/processor:BitmapFontProcessor
/build:Fonts/Regular.fnt
#begin Fonts/RegularTexture.png
/importer:TextureImporter
/processor:TextureProcessor
@ -64,5 +57,3 @@
#begin Textures/Test.png
/copy:Textures/Test.png

View file

@ -1,330 +0,0 @@
<?xml version="1.0"?>
<font>
<info face="BitPotionExt" size="-16" bold="0" italic="0" charset="" unicode="1" stretchH="100" smooth="0" aa="1" padding="0,0,0,0" spacing="1,1" outline="0"/>
<common lineHeight="14" base="11" scaleW="128" scaleH="128" pages="1" packed="0" alphaChnl="0" redChnl="4" greenChnl="4" blueChnl="4"/>
<pages>
<page id="0" file="RegularTexture.png" />
</pages>
<chars count="320">
<char id="32" x="41" y="106" width="3" height="1" xoffset="-1" yoffset="13" xadvance="5" page="0" chnl="15" />
<char id="33" x="10" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="34" x="124" y="98" width="3" height="2" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="35" x="12" y="95" width="7" height="6" xoffset="0" yoffset="5" xadvance="8" page="0" chnl="15" />
<char id="36" x="35" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="37" x="45" y="93" width="4" height="6" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="38" x="121" y="51" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="39" x="13" y="109" width="1" height="2" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="40" x="125" y="83" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="41" x="125" y="75" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="42" x="39" y="93" width="5" height="6" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="43" x="92" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="44" x="109" y="98" width="2" height="3" xoffset="0" yoffset="10" xadvance="3" page="0" chnl="15" />
<char id="45" x="27" y="106" width="4" height="1" xoffset="0" yoffset="8" xadvance="5" page="0" chnl="15" />
<char id="46" x="49" y="106" width="1" height="1" xoffset="0" yoffset="10" xadvance="2" page="0" chnl="15" />
<char id="47" x="97" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="48" x="40" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="49" x="109" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="50" x="45" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="51" x="50" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="52" x="55" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="53" x="65" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="54" x="70" y="84" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="55" x="80" y="84" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="56" x="33" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="57" x="43" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="58" x="81" y="98" width="1" height="5" xoffset="0" yoffset="6" xadvance="2" page="0" chnl="15" />
<char id="59" x="0" y="95" width="2" height="7" xoffset="0" yoffset="6" xadvance="3" page="0" chnl="15" />
<char id="60" x="30" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="61" x="104" y="98" width="4" height="3" xoffset="0" yoffset="7" xadvance="5" page="0" chnl="15" />
<char id="62" x="50" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="63" x="48" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="64" x="41" y="61" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="65" x="53" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="66" x="58" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="67" x="63" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="68" x="68" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="69" x="73" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="70" x="78" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="71" x="83" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="72" x="30" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="73" x="6" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="74" x="88" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="75" x="98" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="76" x="103" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="77" x="6" y="71" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="78" x="108" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="79" x="113" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="80" x="118" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="81" x="123" y="67" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="82" x="0" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="83" x="5" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="84" x="93" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="85" x="10" y="79" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="86" x="15" y="78" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="87" x="91" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="88" x="20" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="89" x="97" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="90" x="25" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="91" x="121" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="92" x="61" y="61" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="93" x="109" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="94" x="0" y="109" width="3" height="2" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="95" x="32" y="106" width="4" height="1" xoffset="0" yoffset="12" xadvance="5" page="0" chnl="15" />
<char id="96" x="4" y="109" width="2" height="2" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="97" x="40" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="98" x="30" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="99" x="77" y="98" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="100" x="35" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="101" x="35" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="102" x="113" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="103" x="40" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="104" x="50" y="77" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="105" x="8" y="95" width="1" height="7" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="106" x="115" y="32" width="2" height="9" xoffset="-1" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="107" x="73" y="98" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="108" x="3" y="95" width="2" height="7" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="109" x="73" y="92" width="6" height="5" xoffset="0" yoffset="6" xadvance="7" page="0" chnl="15" />
<char id="110" x="86" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="111" x="20" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="112" x="105" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="113" x="55" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="114" x="0" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="115" x="121" y="92" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="116" x="116" y="92" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="117" x="10" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="118" x="5" y="103" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="119" x="80" y="92" width="5" height="5" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="120" x="69" y="99" width="3" height="5" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="121" x="60" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="122" x="25" y="100" width="4" height="5" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="123" x="30" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="124" x="0" y="44" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="125" x="80" y="32" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="126" x="112" y="98" width="6" height="2" xoffset="0" yoffset="7" xadvance="7" page="0" chnl="15" />
<char id="160" x="37" y="106" width="3" height="1" xoffset="-1" yoffset="13" xadvance="5" page="0" chnl="15" />
<char id="161" x="124" y="32" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="162" x="70" y="76" width="4" height="7" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="163" x="75" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="164" x="88" y="98" width="4" height="4" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="165" x="109" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="166" x="126" y="32" width="1" height="9" xoffset="0" yoffset="4" xadvance="2" page="0" chnl="15" />
<char id="167" x="85" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="168" x="45" y="106" width="3" height="1" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="169" x="2" y="44" width="7" height="8" xoffset="0" yoffset="3" xadvance="8" page="0" chnl="15" />
<char id="170" x="45" y="100" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="171" x="104" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="172" x="119" y="98" width="4" height="2" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="174" x="10" y="43" width="7" height="8" xoffset="0" yoffset="3" xadvance="8" page="0" chnl="15" />
<char id="175" x="22" y="106" width="4" height="1" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="176" x="83" y="98" width="4" height="4" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="177" x="27" y="93" width="5" height="6" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="178" x="55" y="99" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="179" x="60" y="99" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="180" x="10" y="109" width="2" height="2" xoffset="1" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="181" x="90" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="182" x="82" y="22" width="5" height="9" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="183" x="7" y="109" width="2" height="2" xoffset="1" yoffset="8" xadvance="4" page="0" chnl="15" />
<char id="184" x="101" y="98" width="2" height="4" xoffset="1" yoffset="10" xadvance="4" page="0" chnl="15" />
<char id="185" x="97" y="98" width="3" height="4" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="186" x="15" y="102" width="4" height="5" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="187" x="110" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="188" x="68" y="22" width="7" height="9" xoffset="0" yoffset="2" xadvance="8" page="0" chnl="15" />
<char id="189" x="60" y="22" width="7" height="9" xoffset="0" yoffset="2" xadvance="8" page="0" chnl="15" />
<char id="190" x="8" y="0" width="7" height="10" xoffset="0" yoffset="1" xadvance="8" page="0" chnl="15" />
<char id="191" x="95" y="76" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="192" x="10" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="193" x="15" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="194" x="45" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="195" x="55" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="196" x="5" y="34" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="197" x="65" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="198" x="0" y="63" width="8" height="7" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
<char id="199" x="15" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="200" x="80" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="201" x="85" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="202" x="100" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="203" x="20" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="204" x="125" y="11" width="2" height="10" xoffset="0" yoffset="1" xadvance="3" page="0" chnl="15" />
<char id="205" x="57" y="22" width="2" height="10" xoffset="1" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="206" x="42" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="207" x="120" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="208" x="0" y="71" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="209" x="0" y="23" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="210" x="5" y="23" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="211" x="15" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="212" x="20" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="213" x="28" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="214" x="60" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="215" x="65" y="99" width="3" height="5" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="216" x="18" y="61" width="7" height="7" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="217" x="53" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="218" x="58" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="219" x="118" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="220" x="25" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="221" x="38" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="222" x="50" y="93" width="4" height="6" xoffset="0" yoffset="5" xadvance="5" page="0" chnl="15" />
<char id="223" x="60" y="52" width="4" height="8" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="224" x="108" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="225" x="93" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="226" x="78" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="227" x="55" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="228" x="110" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="229" x="80" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="230" x="64" y="93" width="8" height="5" xoffset="0" yoffset="6" xadvance="9" page="0" chnl="15" />
<char id="231" x="117" y="84" width="3" height="7" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="232" x="98" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="233" x="103" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="234" x="35" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="235" x="115" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="236" x="118" y="51" width="2" height="8" xoffset="0" yoffset="3" xadvance="3" page="0" chnl="15" />
<char id="237" x="115" y="51" width="2" height="8" xoffset="1" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="238" x="107" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="239" x="85" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="240" x="90" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="241" x="67" y="42" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="242" x="88" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="243" x="123" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="244" x="0" y="54" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="245" x="20" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="246" x="120" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="247" x="98" y="92" width="5" height="5" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="248" x="20" y="93" width="6" height="6" xoffset="0" yoffset="5" xadvance="7" page="0" chnl="15" />
<char id="249" x="25" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="250" x="50" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="251" x="75" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="252" x="0" y="87" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="253" x="63" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="254" x="68" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="255" x="108" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="256" x="50" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="257" x="5" y="87" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="258" x="83" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="259" x="118" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="260" x="35" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="261" x="10" y="87" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="262" x="0" y="12" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="263" x="99" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="264" x="123" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="265" x="103" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="266" x="98" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="267" x="121" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="268" x="113" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="269" x="95" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="270" x="103" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="271" x="26" y="61" width="7" height="7" xoffset="0" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="272" x="121" y="59" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="273" x="61" y="42" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="274" x="40" y="33" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="275" x="15" y="86" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="276" x="48" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="277" x="83" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="278" x="85" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="279" x="20" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="280" x="70" y="32" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="281" x="25" y="85" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="282" x="38" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="283" x="70" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="284" x="33" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="285" x="10" y="22" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="286" x="95" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="287" x="40" y="11" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="288" x="0" y="34" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="289" x="123" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="290" x="118" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="291" x="88" y="0" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="292" x="108" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="293" x="93" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="294" x="34" y="61" width="6" height="7" xoffset="0" yoffset="4" xadvance="7" page="0" chnl="15" />
<char id="295" x="49" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="296" x="78" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="297" x="5" y="53" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="298" x="94" y="32" width="3" height="9" xoffset="0" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="299" x="101" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="300" x="30" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="301" x="111" y="51" width="3" height="8" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="302" x="112" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="303" x="118" y="32" width="2" height="9" xoffset="0" yoffset="4" xadvance="3" page="0" chnl="15" />
<char id="304" x="106" y="32" width="2" height="9" xoffset="1" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="305" x="126" y="91" width="1" height="5" xoffset="1" yoffset="6" xadvance="3" page="0" chnl="15" />
<char id="306" x="48" y="61" width="6" height="7" xoffset="1" yoffset="4" xadvance="8" page="0" chnl="15" />
<char id="307" x="90" y="32" width="3" height="9" xoffset="1" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="308" x="16" y="0" width="5" height="10" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
<char id="309" x="50" y="22" width="3" height="10" xoffset="0" yoffset="3" xadvance="4" page="0" chnl="15" />
<char id="310" x="10" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="311" x="89" y="84" width="3" height="7" xoffset="0" yoffset="6" xadvance="4" page="0" chnl="15" />
<char id="312" x="93" y="98" width="3" height="4" xoffset="0" yoffset="7" xadvance="4" page="0" chnl="15" />
<char id="313" x="50" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="314" x="5" y="0" width="2" height="11" xoffset="0" yoffset="0" xadvance="3" page="0" chnl="15" />
<char id="315" x="103" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="316" x="54" y="22" width="2" height="10" xoffset="0" yoffset="3" xadvance="3" page="0" chnl="15" />
<char id="317" x="18" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="318" x="23" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="319" x="28" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="320" x="105" y="84" width="3" height="7" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="321" x="79" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="322" x="38" y="69" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="323" x="98" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="324" x="25" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="325" x="113" y="22" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="326" x="85" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="327" x="73" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="328" x="37" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="329" x="18" y="43" width="6" height="8" xoffset="0" yoffset="3" xadvance="7" page="0" chnl="15" />
<char id="330" x="73" y="60" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="331" x="67" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="332" x="93" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="333" x="93" y="68" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="334" x="43" y="0" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="335" x="40" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="336" x="5" y="12" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="337" x="45" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="338" x="9" y="62" width="8" height="7" xoffset="0" yoffset="4" xadvance="9" page="0" chnl="15" />
<char id="339" x="55" y="93" width="8" height="5" xoffset="0" yoffset="6" xadvance="9" page="0" chnl="15" />
<char id="340" x="20" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="341" x="55" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="342" x="45" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="343" x="115" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="344" x="25" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="345" x="43" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="346" x="30" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="347" x="85" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="348" x="35" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="349" x="60" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="350" x="55" y="33" width="4" height="9" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="351" x="45" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="352" x="70" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="353" x="65" y="51" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="354" x="102" y="32" width="3" height="9" xoffset="0" yoffset="4" xadvance="4" page="0" chnl="15" />
<char id="355" x="65" y="77" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="356" x="46" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="357" x="33" y="93" width="5" height="6" xoffset="0" yoffset="5" xadvance="6" page="0" chnl="15" />
<char id="358" x="55" y="61" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="359" x="80" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="360" x="25" y="22" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="361" x="73" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="362" x="75" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="363" x="100" y="76" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="364" x="75" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="365" x="15" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="366" x="0" y="0" width="4" height="11" xoffset="0" yoffset="0" xadvance="5" page="0" chnl="15" />
<char id="367" x="65" y="32" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="368" x="90" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="369" x="113" y="42" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="370" x="76" y="22" width="5" height="9" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="371" x="103" y="60" width="5" height="7" xoffset="0" yoffset="6" xadvance="6" page="0" chnl="15" />
<char id="372" x="22" y="0" width="5" height="10" xoffset="0" yoffset="1" xadvance="6" page="0" chnl="15" />
<char id="373" x="31" y="43" width="5" height="8" xoffset="0" yoffset="3" xadvance="6" page="0" chnl="15" />
<char id="374" x="34" y="22" width="3" height="10" xoffset="0" yoffset="1" xadvance="4" page="0" chnl="15" />
<char id="375" x="105" y="11" width="4" height="10" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="376" x="98" y="32" width="3" height="9" xoffset="0" yoffset="2" xadvance="4" page="0" chnl="15" />
<char id="377" x="110" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="378" x="10" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="379" x="88" y="22" width="4" height="9" xoffset="0" yoffset="2" xadvance="5" page="0" chnl="15" />
<char id="380" x="60" y="85" width="4" height="7" xoffset="0" yoffset="4" xadvance="5" page="0" chnl="15" />
<char id="381" x="115" y="11" width="4" height="10" xoffset="0" yoffset="1" xadvance="5" page="0" chnl="15" />
<char id="382" x="30" y="52" width="4" height="8" xoffset="0" yoffset="3" xadvance="5" page="0" chnl="15" />
<char id="956" x="75" y="84" width="4" height="7" xoffset="0" yoffset="6" xadvance="5" page="0" chnl="15" />
<char id="8364" x="12" y="70" width="5" height="7" xoffset="0" yoffset="4" xadvance="6" page="0" chnl="15" />
<char id="12288" x="15" y="108" width="6" height="1" xoffset="-2" yoffset="13" xadvance="16" page="0" chnl="15" />
</chars>
</font>

View file

@ -1,274 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.2" tiledversion="1.3.1" orientation="orthogonal" renderorder="right-down" compressionlevel="0" width="50" height="50" tilewidth="16" tileheight="16" infinite="0" nextlayerid="7" nextobjectid="32">
<tileset firstgid="1" source="Tileset.tsx" />
<layer id="1" name="Ground" width="50" height="50">
<data encoding="csv">
450,450,450,450,450,451,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,450,451,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,420,483,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,164,40,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,420,483,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,420,483,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,100,72,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,451,3,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,452,419,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,451,3,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,452,419,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,39,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,38,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
450,450,450,450,451,3,3,36,66,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
450,450,450,420,483,3,3,68,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,69,35,37,37,37,37,37,37,37,37,37,37,37,37,37,37,37,
450,450,450,451,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,34,69,69,69,69,69,69,69,69,69,69,69,69,69,
450,450,450,452,419,3,3,3,3,3,100,72,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,417,418,419,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,3,3,417,418,
450,450,450,450,451,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,417,453,450,451,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,3,417,453,2684355010,
450,450,450,450,451,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,
450,450,450,450,451,3,3,3,3,3,164,40,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,3,449,2684355010,2684355010,2684355010,
450,450,450,420,483,3,3,3,3,3,3,132,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,
450,450,450,451,3,3,3,3,3,3,3,164,40,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,2684355010,
450,450,420,483,3,3,3,3,3,3,3,3,132,133,133,71,102,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,420,483,3,3,3,3,3,3,3,3,3,164,40,133,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,417,418,418,419,3,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,164,40,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,3,449,2684355010,2684355010,452,419,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,417,453,2684355010,2684355010,2684355010,451,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,451,3,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,36,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,449,2684355010,2684355010,2684355010,2684355010,2684355010,
450,452,419,3,3,3,3,3,3,3,3,3,3,3,132,133,134,3,3,3,3,3,3,3,36,37,38,3,3,3,3,3,3,3,36,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,481,482,421,2684355010,2684355010,2684355010,
450,450,451,3,3,3,3,3,3,3,3,3,3,3,132,133,71,102,3,100,101,101,101,101,36,37,66,5,5,5,5,5,5,5,67,37,38,449,2684355010,2684355010,2684355010,2684355010,451,3,3,3,481,482,482,482,
450,450,451,3,3,3,3,3,3,3,3,3,3,100,72,133,133,71,101,72,133,133,133,133,36,37,37,37,37,37,37,37,37,37,37,37,38,481,421,2684355010,2684355010,420,483,3,3,3,3,16,16,16,
450,450,451,3,3,3,3,3,3,3,3,3,100,72,133,133,133,133,133,133,133,39,165,165,68,69,69,69,69,69,69,69,69,69,69,69,70,3,481,482,482,483,3,3,16,16,16,16,16,16,
450,450,451,3,3,3,132,133,71,101,101,101,72,133,133,133,39,165,165,165,165,166,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,
450,450,451,3,3,3,132,133,133,133,133,133,133,133,39,165,166,3,3,3,3,3,3,3,417,418,418,418,418,418,418,418,418,418,418,419,3,3,3,3,3,16,16,16,16,16,16,16,16,16,
450,450,452,419,3,3,164,165,165,165,165,165,165,165,166,3,3,3,3,3,3,3,3,417,453,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,451,3,3,3,3,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,481,421,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,2684355010,420,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,481,482,482,482,482,482,482,482,482,482,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,
450,450,450,451,3,3,3,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,
2684355010,450,420,483,3,3,3,3,3,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,
2684355010,420,483,3,3,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,
482,483,3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,
3,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
16,16,16,16,16,16,16,16,16,16,16,16,16,16,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3
</data>
</layer>
<layer id="2" name="Ground1" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,18,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,18,19,19,54,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,18,19,19,54,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,18,54,0,0,0,21,83,83,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,18,19,54,0,0,21,83,84,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,18,54,0,0,21,83,84,17,17,17,17,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,21,84,17,17,17,17,17,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,0,21,84,17,17,17,17,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,17,17,17,17,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,19,54,0,21,83,83,83,84,17,17,17,0,0,0,0,0,
0,0,0,0,0,0,17,17,17,17,17,17,17,17,17,17,17,18,19,54,0,0,0,0,0,0,0,0,0,0,0,0,0,21,83,83,83,84,17,17,17,17,17,17,0,0,0,0,0,0,
0,0,0,17,17,17,17,17,17,17,17,17,17,17,18,19,19,54,0,0,0,0,0,0,0,0,21,83,83,83,83,83,83,84,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,
0,17,17,17,17,17,17,17,17,17,17,18,19,19,54,0,0,0,21,83,83,83,83,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,
17,17,17,17,17,17,18,19,19,19,19,54,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,
17,17,18,19,19,19,54,0,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
19,19,54,0,0,0,21,83,83,83,84,17,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,21,84,17,17,17,17,17,17,17,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="3" name="Objects" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,365,430,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,368,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,237,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,397,0,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,303,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,366,366,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,0,397,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,397,0,0,
0,0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,199,198,198,198,198,198,199,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,368,0,0,
0,365,366,366,430,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,0,0,303,0,0,0,0,0,0,397,0,0,
0,397,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,237,0,0,0,0,0,0,0,0,397,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,265,266,266,266,267,266,266,266,266,266,267,266,266,266,268,0,0,0,0,0,111,0,0,0,0,0,0,0,0,397,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,365,366,430,0,0,
0,397,0,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,197,198,198,198,199,198,198,198,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,303,0,
303,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,265,266,266,266,267,266,266,290,266,266,267,266,266,266,268,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,368,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,197,198,198,198,199,198,198,322,198,198,199,198,198,198,200,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,0,7,7,0,0,0,0,0,0,0,0,7,0,0,0,0,0,0,0,0,77,0,301,302,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,194,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,0,397,0,0,0,0,
0,397,0,193,194,195,194,290,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,0,399,0,97,98,98,
0,397,0,193,194,195,194,322,194,195,194,196,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,0,97,98,98,74,0,0,
0,397,301,225,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,77,0,0,0,0,97,74,0,0,0,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,237,109,46,46,46,46,46,46,46,46,46,46,46,46,46,110,0,0,97,98,74,0,0,0,0,0,0,
0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,0,0,301,302,0,0,0,0,0,97,74,0,0,0,0,0,0,0,0,
0,429,366,367,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,301,302,0,0,0,0,0,0,0,0,0,0,0,0,0,97,74,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,41,162,
0,301,302,397,0,0,0,0,0,0,0,0,0,0,0,0,0,0,97,98,98,98,98,98,98,98,98,98,98,98,98,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,
0,0,0,397,0,0,0,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,0,0,
0,0,0,397,0,0,0,0,0,0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,163,0,0,0,0,0,
0,0,0,399,0,303,97,98,98,98,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,163,0,0,0,301,302,0,
0,0,0,97,98,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,163,0,0,0,0,0,0,0,0,
0,97,98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,163,0,0,0,0,0,0,0,0,0,0,0,
98,74,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,162,162,162,162,162,162,162,163,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,162,162,163,0,0,301,302,0,0,0,0,0,0,0,0,303,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,41,162,162,162,162,163,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,303,0,0,0,0,
0,0,0,0,0,0,0,0,41,162,162,162,162,163,0,0,0,0,0,0,0,303,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="4" name="Objects1" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,237,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,365,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,298,299,300,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,330,331,332,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,362,363,364,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,387,0,354,0,0,0,354,0,385,386,387,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,103,104,105,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,385,386,387,0,0,135,0,137,0,0,385,386,387,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,135,0,137,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,385,386,387,0,0,0,0,0,0,0,0,0,229,301,302,230,231,230,230,230,230,230,231,303,230,230,232,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,257,258,259,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,354,0,289,0,291,0,354,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,289,0,291,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,302,226,227,226,226,226,227,226,228,0,0,0,0,0,0,0,0,0,0,0,0,7,7,7,7,7,7,7,7,7,7,7,7,7,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="6" name="Above" width="50" height="50">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,333,335,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,336,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,336,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,333,334,334,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,261,263,264,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,261,262,262,262,262,293,327,296,262,262,262,262,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,261,293,326,326,326,326,325,327,328,326,326,326,326,296,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,336,0,0,
0,333,334,334,335,0,0,0,0,0,0,0,0,0,0,0,0,292,293,325,326,326,326,326,325,327,328,326,326,326,326,328,296,297,0,0,0,0,173,0,271,0,0,0,0,0,0,0,0,0,
0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,356,325,325,326,326,326,326,357,359,360,326,326,326,326,328,328,361,0,0,0,0,205,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,356,325,357,358,358,358,358,389,0,392,358,358,358,358,360,328,361,0,0,0,0,79,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,356,357,389,0,0,0,0,0,0,0,0,0,0,0,392,360,361,0,0,0,0,0,0,0,0,0,0,0,333,334,335,0,0,
0,0,0,0,0,261,262,262,262,264,0,269,270,0,0,0,0,388,389,0,0,0,0,0,0,0,0,0,0,0,0,0,392,393,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,
271,0,0,261,262,293,326,326,326,296,262,264,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,336,292,293,326,325,326,326,326,328,326,296,297,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,356,325,326,357,358,358,358,360,326,328,361,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,
0,0,356,357,358,389,0,0,0,392,358,360,361,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,388,389,0,0,0,0,0,0,0,392,393,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,43,43,43,43,43,43,0,0,0,0,0,0,0,0,336,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,8,2147483656,9,1073741833,3221225545,43,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,173,0,0,0,0,0,0,43,43,43,43,43,43,43,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,205,13,14,14,14,14,14,14,14,14,14,14,14,14,14,15,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,333,334,335,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,269,270,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,336,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,269,270,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,271,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
</map>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View file

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.2" tiledversion="1.3.1" name="TownTiles" tilewidth="16" tileheight="16" tilecount="512" columns="32">
<image source="Tiles.png" width="512" height="256" />
<tile id="3">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="4">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="5">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="7">
<objectgroup draworder="index" id="5">
<object id="4" x="8" y="3" width="5" height="10" />
</objectgroup>
</tile>
<tile id="8">
<objectgroup draworder="index" id="2">
<object id="1" x="3" y="2" width="10" height="5" />
</objectgroup>
</tile>
<tile id="15">
<animation>
<frame tileid="15" duration="1000" />
<frame tileid="47" duration="1000" />
</animation>
</tile>
<tile id="33">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="34">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="35">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="36">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="37">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="65">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="66">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="67">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="68">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="69">
<properties>
<property name="Walkability" type="int" value="30" />
</properties>
</tile>
<tile id="72">
<objectgroup draworder="index" id="2">
<object id="1" x="12" y="0" width="4" height="4" />
</objectgroup>
</tile>
<tile id="132">
<properties>
<property name="Walkability" type="int" value="60" />
</properties>
</tile>
<tile id="172">
<properties>
<property name="LightColor" type="color" value="#ffaa6300" />
<property name="LightRadius" type="float" value="5.5" />
<property name="LightType" value="Lamp" />
</properties>
</tile>
<tile id="289">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="1.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
<tile id="353">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="2.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
<tile id="385">
<properties>
<property name="LightColor" type="color" value="#ff676e73" />
<property name="LightRadius" type="float" value="2.5" />
<property name="LightType" value="Window" />
</properties>
</tile>
</tileset>

View file

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
@ -19,16 +19,11 @@
<PackageReference Include="MonoGame.Extended.Content.Pipeline" Version="3.8.0" />
<PackageReference Include="MonoGame.Extended.Tiled" Version="3.8.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="FontStashSharp.MonoGame" Version="1.2.8" />
<PackageReference Include="FontStashSharp.MonoGame" Version="1.3.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<Content Include="Content\Fonts\Symbola-Emoji.ttf" />
</ItemGroup>
<Target Name="RestoreDotnetTools" BeforeTargets="Restore">
<Message Text="Restoring dotnet tools" Importance="High" />
<Exec Command="dotnet tool restore" />
</Target>
</Project>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger>
<VSTestResultsDirectory>TestResults.FNA</VSTestResultsDirectory>
<RunSettingsFilePath>Tests.FNA.runsettings</RunSettingsFilePath>
@ -18,24 +18,21 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FNA\FNA.Core.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<ProjectReference Include="..\ThirdParty\FNA\FNA.Core.csproj" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.127" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.1.15" />
</ItemGroup>
<ItemGroup>
<Content Include="Content/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="../FnaNative/**">
<Content Include="../ThirdParty/Native/**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<Link>%(Filename)%(Extension)</Link>
</Content>

View file

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<VSTestLogger>nunit</VSTestLogger>
<VSTestResultsDirectory>TestResults</VSTestResultsDirectory>
<RunSettingsFilePath>Tests.runsettings</RunSettingsFilePath>
@ -20,14 +20,11 @@
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="MonoGame.Extended" Version="3.8.0" />
<PackageReference Include="coverlet.collector" Version="3.2.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.4.2" />
<PackageReference Include="NunitXml.TestLogger" Version="3.0.131" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
<PackageReference Include="NunitXml.TestLogger" Version="3.1.15" />
</ItemGroup>
<ItemGroup>

1
ThirdParty/FNA vendored Submodule

@ -0,0 +1 @@
Subproject commit 354e2161b759fa052b25e94209d6ea463aaf098f

1
ThirdParty/FontStashSharp vendored Submodule

@ -0,0 +1 @@
Subproject commit 2d40e9f0f681595dbd4341a3e5a64ed6e31f9556

BIN
ThirdParty/Native/FAudio.dll vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/FNA3D.dll vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
ThirdParty/Native/libFAudio.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFAudio.so.0 vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFNA3D.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libFNA3D.so.0 vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libSDL2-2.0.0.dylib vendored Normal file

Binary file not shown.

BIN
ThirdParty/Native/libSDL2-2.0.so.0 vendored Normal file

Binary file not shown.

View file

@ -2,7 +2,7 @@
#tool dotnet:?package=docfx&version=2.70.3
// this is the upcoming version, for prereleases
var version = Argument("version", "6.2.0");
var version = Argument("version", "6.3.0");
var target = Argument("target", "Default");
var branch = Argument("branch", "main");
var config = Argument("configuration", "Release");
@ -14,8 +14,8 @@ Task("Prepare").Does(() => {
DotNetRestore("MLEM.FNA.sln");
if (branch != "release") {
var buildNum = EnvironmentVariable("CI_PIPELINE_NUMBER");
if (buildNum != null)
var buildNum = EnvironmentVariable("GITHUB_RUN_NUMBER");
if (!string.IsNullOrEmpty(buildNum))
version += "-ci." + buildNum;
}
@ -25,7 +25,9 @@ Task("Prepare").Does(() => {
Task("Build").IsDependentOn("Prepare").Does(() =>{
var settings = new DotNetBuildSettings {
Configuration = config,
ArgumentCustomization = args => args.Append($"/p:Version={version}")
ArgumentCustomization = args => args.Append($"/p:Version={version}"),
// .net 8 has an issue that causes simultaneous tool restores during build to fail
MSBuildSettings = new DotNetMSBuildSettings { MaxCpuCount = 1 }
};
DotNetBuild("MLEM.sln", settings);
DotNetBuild("MLEM.FNA.sln", settings);
@ -34,7 +36,8 @@ Task("Build").IsDependentOn("Prepare").Does(() =>{
Task("Test").IsDependentOn("Build").Does(() => {
var settings = new DotNetTestSettings {
Configuration = config,
Collectors = {"XPlat Code Coverage"}
Collectors = {"XPlat Code Coverage"},
Loggers = {"console;verbosity=normal"}
};
DotNetTest("MLEM.sln", settings);
DotNetTest("MLEM.FNA.sln", settings);
@ -49,7 +52,7 @@ Task("Pack").IsDependentOn("Test").Does(() => {
DotNetPack("MLEM.FNA.sln", settings);
});
Task("Push").WithCriteria(branch == "main" || branch == "release").IsDependentOn("Pack").Does(() => {
Task("Push").WithCriteria(branch == "main" || branch == "release", "Not on main or release branch").IsDependentOn("Pack").Does(() => {
DotNetNuGetPushSettings settings;
if (branch == "release") {
settings = new DotNetNuGetPushSettings {