diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..53387fc94 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +**/bin +**/obj +**/node_modules +docker/nfs +**/publish +**/debug +**/release +**/dist +*.DotSettings.user +.vs/ +.idea/ +**/.DS_Store diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..b9df232a8 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +### Summary + + + +### Contributor Declaration + +- [ ] I certify that no LLM's were used to generate any code or documentation in this contribution. + diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 000000000..5935780e0 --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,200 @@ +name: Build FreeSO + +on: + push: + branches: [ "**" ] + pull_request: + branches: [ "master" ] + +jobs: + + build-win: + strategy: + matrix: + configuration: [Release] + + runs-on: windows-latest # For a list of available runner types, refer to + # https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on + + steps: + - name: Checkout + uses: actions/checkout@v5 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 9.0.x + + - run: dotnet publish TSOClient/FSO.IDE -c Release /p:ContinuousIntegrationBuild=true /p:Deterministic=true /p:EnableSourceLink=false /p:IncludeSourceRevisionInInformationalVersion=false -r win-x64 --self-contained -o Artifacts/Client + - run: dotnet publish TSOClient/FSO.Server.Core -c Release /p:ContinuousIntegrationBuild=true /p:Deterministic=true /p:EnableSourceLink=false /p:IncludeSourceRevisionInInformationalVersion=false -r win-x64 --self-contained -o Artifacts/Server + + - name: Upload client artifacts + uses: actions/upload-artifact@v7 + with: + name: FreeSOClient + path: Artifacts/Client + + - name: Upload server artifacts + uses: actions/upload-artifact@v7 + with: + name: FreeSOServer + path: Artifacts/Server + + build-mac: + strategy: + matrix: + configuration: [Release] + + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v5 + + # Install the .NET Core workload + - name: Install .NET Core + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 9.0.x + + - run: dotnet publish TSOClient/FSO.Unix -c Release /p:ContinuousIntegrationBuild=true /p:Deterministic=true /p:EnableSourceLink=false /p:IncludeSourceRevisionInInformationalVersion=false /p:ForceSign=true -r osx-arm64 --self-contained -o Artifacts/Client + - run: dotnet publish TSOClient/FSO.Server.Core -c Release /p:ContinuousIntegrationBuild=true /p:Deterministic=true /p:EnableSourceLink=false /p:IncludeSourceRevisionInInformationalVersion=false -r osx-arm64 --self-contained -o Artifacts/Server + + - name: Tar client + run: tar -czvf mac.tar.gz -C Artifacts/Client FreeSO.app + + - name: Upload client artifacts + uses: actions/upload-artifact@v7 + with: + name: FreeSOClientMac + path: mac.tar.gz + archive: false + + - name: Tar server + run: tar -czvf mac-server.tar.gz -C Artifacts/Server . + + - name: Upload server artifacts + uses: actions/upload-artifact@v7 + with: + name: FreeSOServerMac + path: mac-server.tar.gz + archive: false + + create-release: + needs: [build-win, build-mac] + runs-on: macos-latest + permissions: + contents: write + environment: + name: "Release" + + steps: + - name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + + - uses: actions/setup-node@v7 + with: + node-version: 24 + + - name: Install .NET Core + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 9.0.x + + - name: Download Client + uses: actions/download-artifact@v8 + with: + name: FreeSOClient + path: tempUpdate/windows + + - name: Download Server + uses: actions/download-artifact@v8 + with: + name: FreeSOServer + path: tempUpdate/windows-server + + - name: Download Client (mac) + uses: actions/download-artifact@v8 + with: + name: mac.tar.gz + path: tempUpdate + + - name: Download Server (mac) + uses: actions/download-artifact@v8 + with: + name: mac-server.tar.gz + path: tempUpdate + + - name: Untar client (mac) + run: mkdir tempUpdate/mac/ && tar -xzvf tempUpdate/mac.tar.gz -C tempUpdate/mac/ + + - name: Untar server (mac) + run: mkdir tempUpdate/mac-server/ && tar -xzvf tempUpdate/mac-server.tar.gz -C tempUpdate/mac-server/ + + - name: Install create-dmg + run: | + npm install --global create-dmg + + - name: Building Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FSO_UPDATE_GITHUB_REPO: ${{ github.repository }} + FSO_UPDATE_CHANNEL_URL: ${{ vars.FSO_UPDATE_CHANNEL_URL }} + FSO_UPDATE_PUBLIC_KEY: ${{ secrets.FSO_UPDATE_PUBLIC_KEY }} + FSO_UPDATE_PRIVATE_KEY: ${{ secrets.FSO_UPDATE_PRIVATE_KEY }} + FSO_UPDATE_TARGETS: "windows,mac" + run: | + dotnet run -c Release -- "../../../tempUpdate/" + working-directory: Other/tools/FSO.UpdateBuilder + + - name: Upload release manifest + uses: actions/upload-artifact@v7 + with: + name: ReleaseManifest + path: tempUpdate/windows/version.json + archive: false + + release-msi: + needs: [create-release] + runs-on: windows-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Install .NET Core + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 9.0.x + + - name: Download Client + uses: actions/download-artifact@v8 + with: + name: FreeSOClient + path: Artifacts/Client + + - name: Download Version Manifest + uses: actions/download-artifact@v8 + with: + name: version.json + path: Artifacts/Client + + - name: Building Windows Installer (.msi) + run: dotnet build TSOClient/FSO.Installer.Windows -c Release -o Artifacts/Installer + + - name: Updating Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + FSO_UPDATE_GITHUB_REPO: ${{ github.repository }} + FSO_UPDATE_CHANNEL_URL: ${{ vars.FSO_UPDATE_CHANNEL_URL }} + FSO_UPDATE_PUBLIC_KEY: ${{ secrets.FSO_UPDATE_PUBLIC_KEY }} + FSO_UPDATE_PRIVATE_KEY: ${{ secrets.FSO_UPDATE_PRIVATE_KEY }} + FSO_UPDATE_TARGETS: "windows,mac" + run: | + dotnet run -c Release -- --windowsMsi + working-directory: Other/tools/FSO.UpdateBuilder diff --git a/.gitignore b/.gitignore index e1d397fee..d2a347358 100644 --- a/.gitignore +++ b/.gitignore @@ -85,4 +85,13 @@ TSOClient/tso.client/GlobalSettings1.Designer.cs TSOClient/.vs/config/applicationhost.config *.nvuser -TSOClient/Mario \ No newline at end of file +# Rider IDE +.idea/ + +# Docker +docker/nfs/ +docker/archives/ + +TSOClient/Mario + +Artifacts/ diff --git a/Documentation/Generating Archive Data.md b/Documentation/Generating Archive Data.md new file mode 100644 index 000000000..e94dd65ba --- /dev/null +++ b/Documentation/Generating Archive Data.md @@ -0,0 +1,43 @@ +Here's the full process used to generate the FreeSO archive data from server data exported using mariadb-dump on The Save Date. + +- Build and configure FSO.Server.Core to point to a non-existent sqlite database file, and a copy of the NFS that you want to migrate to archive. These operations are destructive, so make sure you copy the NFS if you want to keep the original. + - Add `"engine": "sqlite",` to the database object, and then use a connection string like this: + - `Data Source=fsoarchive.db;Version=3;UTF8Encoding=True` +- `dotnet run sqlite-import ` + - This imports the database from an SQL dump made with MariaDB dump 10.19. There should be one file per table, with triggers and functions, and the first alphabetically should be `fso_fso_auth_attempts.sql`. +- `dotnet run archive-convert` + - This converts the database into the archive format. This involved deleting all users, user related tables, authentication and adding tables/columns for archive features. All existing avatars are transferred to an "archive user" with ID 1. +- `dotnet run backup-selection` + - This step scans all lot saves to find cases where a roommate has left and taken a large number of objects with them. If this is the case, then the entry for that lot is updated to load that backup instead. + - When possible, the lot will attempt to "steal" recovered objects from the owner's inventory if it's not on another lot. + - This is useful for restoring the state of "abandoned" properties, after players have left and become roommate somewhere else. +- `dotnet run data-trim -a` + - This step trims excess data to reduce the filesize of the archive, and can remove any data that might be personally identifying such as inbox, bookmarks etc. (the -a flag) + - This is a destructive process. The following information will be lost: + - Relationships with an invalid source, or insignificant value (-5 to 5). + - Object inventory data for objects that are on a lot (as the lot has authority in this case) + - Object inventory and plugin data for objects that have been deleted + - Lot backups apart from the newest and the oldest (or whatever `backup-selection` picked) + - Usually there are 10, trimming down to 2 backups saves a lot of space. + - Lot data that doesn't have a database entry (likely deleted) + - All rows in server state tables: + - `fso_auth_attempts`, `fso_auth_tickets`, `fso_lot_server_tickets`, `fso_shard_tickets`, `fso_tasks`, `fso_transactions` + - If the `-a` flag is provided (anonymize), all rows in these tables: + - `fso_inbox`, `fso_bookmarks`, `fso_bulletin_posts (deleted=1)`, `fso_election_votes`, `fso_election_freevotes`, `fso_election_candidates`, `fso_ip_ban`, `fso_lot_visits`, `fso_nhood_ban`, `fso_lot_admit` + - `from_user_id` from `fso_mayor_ratings` gets cleared somehow +- `dotnet run plugin-anonymize ` + - This step tries to find objects with specific plugin data (signs, draw a card) that could potentially contain private information. + - Any signs or card dispensers that are in a user's inventory have their plugin data deleted. + - If on a lot, the tool loads the lot and determines if the object is accessible to fresh visitors. If it isn't, the data is deleted. + - allow all or ban list, and you can route to the object from the mailbox with visitor status. There's some special logic that allows routing through teleporters. + - Without an input file, the tool will output a list of all data for the user to review in `pluginReview.json` local to the game executable, and the automated decisions it made based on routing. + - You can feed this data back in immediately with `dotnet run plugin-anonymize pluginReview.json`, though it's recommended to do a pass through the data for anything that might be offensive, or to manually allow data that doesn't appear to be private. +- `dotnet run import-archive-featured ` + - A tool for importing manually featured lots for the archive data. Just a JSON array containing objects with `name`, `lot_id`, `category`, `description`. +- Manual Cleanup + - Feel free to perform manual cleanups on the database file with a tool like https://sqlitebrowser.org/ . + - If you want to make sure something is definitely deleted/committed before distribution, run: + - `PRAGMA wal_checkpoint(TRUNCATE)` + - `vacuum` + - `PRAGMA wal_checkpoint(TRUNCATE)` + - You can also load up the save with the archive client and make changes there. This was done with the FreeSO archive data to finish the final town hall, cleanup the event lots, import some special lot data, and add a few easter eggs. \ No newline at end of file diff --git a/Documentation/media/3d.png b/Documentation/media/3d.png new file mode 100644 index 000000000..5ffc17c95 Binary files /dev/null and b/Documentation/media/3d.png differ diff --git a/Documentation/media/band.png b/Documentation/media/band.png new file mode 100644 index 000000000..b2c76083f Binary files /dev/null and b/Documentation/media/band.png differ diff --git a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Properties/AssemblyInfo.cs b/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Properties/AssemblyInfo.cs deleted file mode 100644 index c9d0e1998..000000000 --- a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Simple Palette Quantizer Demo")] -[assembly: AssemblyDescription("An example of slightly tweaked palette quantizer.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Ying-Yang")] -[assembly: AssemblyProduct("CodeProject - Simple Palette Quantizer")] -[assembly: AssemblyCopyright("Copyright © Ying-Yang 2010")] -[assembly: AssemblyTrademark("Ying-Yang")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("462b9342-f002-4577-ac5c-66dfde08275e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Quantizers/DistinctSelection/DistinctSelectionQuantizer.cs b/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Quantizers/DistinctSelection/DistinctSelectionQuantizer.cs index 8ab4d0b73..aad2d6e37 100644 --- a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Quantizers/DistinctSelection/DistinctSelectionQuantizer.cs +++ b/Other/libs/ColorQuantizer/SimplePaletteQuantizer/Quantizers/DistinctSelection/DistinctSelectionQuantizer.cs @@ -129,7 +129,7 @@ protected override List OnGetPaletteToCache(Int32 colorCount) ToList(); // workaround for backgrounds, the most prevalent color - DistinctColorInfo background = colorInfoList.MaxBy(info => info.Count); + DistinctColorInfo background = Enumerable.MaxBy(colorInfoList, info => info.Count); colorInfoList.Remove(background); colorCount--; diff --git a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/SimplePaletteQuantizer.csproj b/Other/libs/ColorQuantizer/SimplePaletteQuantizer/SimplePaletteQuantizer.csproj index 79cf0aa2f..58d80c8fa 100644 --- a/Other/libs/ColorQuantizer/SimplePaletteQuantizer/SimplePaletteQuantizer.csproj +++ b/Other/libs/ColorQuantizer/SimplePaletteQuantizer/SimplePaletteQuantizer.csproj @@ -1,194 +1,27 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {37812A22-91F3-4220-891E-5C26DA64A975} + net9.0-windows + enable + disable Library Properties SimplePaletteQuantizer SimplePaletteQuantizer - v4.5 512 Aero - Ying Yang.ico - - - 3.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - + true + partial + true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - AllRules.ruleset - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - AllRules.ruleset - false - - - - - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - false - prompt - AllRules.ruleset - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - - - - - - - - - - - - + - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - true - - - False - Windows Installer 3.1 - true - + + - - - + + - - - - \ No newline at end of file + + diff --git a/Other/libs/FSOMina.NET b/Other/libs/FSOMina.NET index b2216c27f..59fa77a75 160000 --- a/Other/libs/FSOMina.NET +++ b/Other/libs/FSOMina.NET @@ -1 +1 @@ -Subproject commit b2216c27fe6e17e4392130f778b6b54bf8c62bda +Subproject commit 59fa77a75d98ab1b14e0d56772b2685c6b2afaab diff --git a/Other/libs/FSOMonoGame b/Other/libs/FSOMonoGame index 74f7ef558..31547ae12 160000 --- a/Other/libs/FSOMonoGame +++ b/Other/libs/FSOMonoGame @@ -1 +1 @@ -Subproject commit 74f7ef558111e62a7d0a2228b6f9714980e000fa +Subproject commit 31547ae129d0ea7289721d2f8412591ef5c46488 diff --git a/Other/libs/MSDFData/FieldAtlas.cs b/Other/libs/MSDFData/FieldAtlas.cs index 61090f3be..05885e555 100644 --- a/Other/libs/MSDFData/FieldAtlas.cs +++ b/Other/libs/MSDFData/FieldAtlas.cs @@ -1,24 +1,15 @@ using Microsoft.Xna.Framework.Content; -using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MSDFData { - public class FieldAtlas + public readonly struct FieldAtlas { - [ContentSerializer] private readonly int WidthBackend; - [ContentSerializer] private readonly int HeightBackend; - [ContentSerializer] private readonly int GlyphSizeBackend; - [ContentSerializer] private readonly byte[] PNGDataBackend; - [ContentSerializer] private readonly char[] CharMapBackend; - - public FieldAtlas() - { - } + private readonly int WidthBackend; + private readonly int HeightBackend; + private readonly int GlyphSizeBackend; + private readonly byte[] PNGDataBackend; + private readonly char[] CharMapBackend; public FieldAtlas(int width, int height, int glyphSize, byte[] pngData, char[] charMap) { @@ -26,7 +17,6 @@ public FieldAtlas(int width, int height, int glyphSize, byte[] pngData, char[] c HeightBackend = height; GlyphSizeBackend = glyphSize; PNGDataBackend = pngData; - File.WriteAllBytes("test.png", pngData); CharMapBackend = charMap; } diff --git a/Other/libs/MSDFData/FieldFont.cs b/Other/libs/MSDFData/FieldFont.cs index 32df6612d..501bdb34b 100644 --- a/Other/libs/MSDFData/FieldFont.cs +++ b/Other/libs/MSDFData/FieldFont.cs @@ -6,11 +6,11 @@ namespace MSDFData { public class FieldFont { - [ContentSerializer] private readonly Dictionary Glyphs; - [ContentSerializer] private readonly string NameBackend; - [ContentSerializer] private readonly float PxRangeBackend; - [ContentSerializer] private readonly List KerningPairsBackend; - [ContentSerializer] private readonly FieldAtlas AtlasBackend; + private readonly Dictionary Glyphs; + private readonly string NameBackend; + private readonly float PxRangeBackend; + private readonly List KerningPairsBackend; + private readonly FieldAtlas AtlasBackend; public FieldFont() { @@ -35,6 +35,15 @@ public FieldFont(string name, IReadOnlyCollection glyphs, IReadOnlyC } } + public FieldFont(string name, Dictionary glyphs, IReadOnlyCollection kerningPairs, float pxRange, FieldAtlas atlas) + { + this.NameBackend = name; + this.PxRangeBackend = pxRange; + this.KerningPairsBackend = kerningPairs.ToList(); + this.AtlasBackend = atlas; + this.Glyphs = glyphs; + } + /// /// Name of the font /// @@ -74,11 +83,13 @@ public Dictionary StringToPair { /// Characters supported by this font /// public FieldAtlas Atlas => AtlasBackend; + + public Dictionary GlyphsRaw => Glyphs; /// /// Returns the glyph for the given character, or returns null when the glyph is not supported by this font /// - public FieldGlyph GetGlyph(char c) + public FieldGlyph? GetGlyph(char c) { if (this.Glyphs.TryGetValue(c, out FieldGlyph glyph)) { diff --git a/Other/libs/MSDFData/FieldFontReader.cs b/Other/libs/MSDFData/FieldFontReader.cs new file mode 100644 index 000000000..e2e4d3200 --- /dev/null +++ b/Other/libs/MSDFData/FieldFontReader.cs @@ -0,0 +1,75 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content; +using System.Collections.Generic; + +namespace MSDFData +{ + public class FieldFontReader : ContentTypeReader + { + private Metrics ReadMetrics(ContentReader input) + { + float advance = input.ReadSingle(); + float scale = input.ReadSingle(); + Vector2 translation = input.ReadVector2(); + + return new Metrics(advance, scale, translation); + } + + private FieldGlyph ReadGlyph(ContentReader input) + { + char character = input.ReadChar(); + int atlasIndex = input.ReadInt32(); + var metrics = ReadMetrics(input); + + return new FieldGlyph(character, atlasIndex, metrics); + } + + private KerningPair ReadKerningPair(ContentReader input) + { + char left = input.ReadChar(); + char right = input.ReadChar(); + float advance = input.ReadSingle(); + + return new KerningPair(left, right, advance); + } + + private FieldAtlas ReadAtlas(ContentReader input) + { + int width = input.ReadInt32(); + int height = input.ReadInt32(); + int glyphSize = input.ReadInt32(); + + int pngDataSize = input.ReadInt32(); + var pngData = input.ReadBytes(pngDataSize); + + int charMapSize = input.ReadInt32(); + var charMap = input.ReadChars(charMapSize); + + return new FieldAtlas(width, height, glyphSize, pngData, charMap); + } + + protected override FieldFont Read(ContentReader input, FieldFont existingInstance) + { + string name = input.ReadString(); + float pxRange = input.ReadSingle(); + + int glyphCount = input.ReadInt32(); + var glyphs = new Dictionary(); + for (int i = 0; i < glyphCount; i++) + { + glyphs.Add(input.ReadChar(), ReadGlyph(input)); + } + + int pairCount = input.ReadInt32(); + var pairs = new List(); + for (int i = 0; i < pairCount; i++) + { + pairs.Add(ReadKerningPair(input)); + } + + var atlas = ReadAtlas(input); + + return new FieldFont(name, glyphs, pairs, pxRange, atlas); + } + } +} diff --git a/Other/libs/MSDFData/FieldGlyph.cs b/Other/libs/MSDFData/FieldGlyph.cs index 58758a8e1..d739f9876 100644 --- a/Other/libs/MSDFData/FieldGlyph.cs +++ b/Other/libs/MSDFData/FieldGlyph.cs @@ -1,17 +1,10 @@ -using Microsoft.Xna.Framework.Content; - -namespace MSDFData +namespace MSDFData { - public class FieldGlyph + public readonly struct FieldGlyph { - [ContentSerializer] private readonly char CharacterBackend; - [ContentSerializer] private readonly int AtlasIndexBackend; - [ContentSerializer] private readonly Metrics MetricsBackend; - - public FieldGlyph() - { - - } + private readonly char CharacterBackend; + private readonly int AtlasIndexBackend; + private readonly Metrics MetricsBackend; public FieldGlyph(char character, int atlasIndex, Metrics metrics) { diff --git a/Other/libs/MSDFData/KerningPair.cs b/Other/libs/MSDFData/KerningPair.cs index f6fc6dca5..d5f4be9ea 100644 --- a/Other/libs/MSDFData/KerningPair.cs +++ b/Other/libs/MSDFData/KerningPair.cs @@ -2,16 +2,11 @@ namespace MSDFData { - public class KerningPair + public readonly struct KerningPair { - [ContentSerializer] private readonly char LeftBackend; - [ContentSerializer] private readonly char RightBackend; - [ContentSerializer] private readonly float AdvanceBackend; - - public KerningPair() - { - - } + private readonly char LeftBackend; + private readonly char RightBackend; + private readonly float AdvanceBackend; public KerningPair(char left, char right, float advance) { diff --git a/Other/libs/MSDFData/MSDFData.csproj b/Other/libs/MSDFData/MSDFData.csproj index ce71aeb93..fe61aabd4 100644 --- a/Other/libs/MSDFData/MSDFData.csproj +++ b/Other/libs/MSDFData/MSDFData.csproj @@ -1,61 +1,18 @@ - - - + + - Debug - AnyCPU - {EABEA510-3E53-4F19-9F0B-75C5CA9DFA3B} - Library - Properties + net8.0 MSDFData MSDFData - v4.5 512 + true + True + true + link - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\TSOClient\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - - - ..\..\..\TSOClient\packages\MonoGame.Framework.Content.Pipeline.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.Content.Pipeline.dll - - - - - - - - - - - - - - - - - - - + - + - - \ No newline at end of file + + diff --git a/Other/libs/MSDFData/Metrics.cs b/Other/libs/MSDFData/Metrics.cs index d5ee0eb9e..51b2ed17c 100644 --- a/Other/libs/MSDFData/Metrics.cs +++ b/Other/libs/MSDFData/Metrics.cs @@ -3,16 +3,11 @@ namespace MSDFData { - public class Metrics + public readonly struct Metrics { - [ContentSerializer] private readonly float AdvanceBackend; - [ContentSerializer] private readonly float ScaleBackend; - [ContentSerializer] private readonly Vector2 TranslationBackend; - - public Metrics() - { - - } + private readonly float AdvanceBackend; + private readonly float ScaleBackend; + private readonly Vector2 TranslationBackend; public Metrics(float advance, float scale, Vector2 translation) { diff --git a/Other/libs/MSDFData/Properties/AssemblyInfo.cs b/Other/libs/MSDFData/Properties/AssemblyInfo.cs deleted file mode 100644 index 67191d43e..000000000 --- a/Other/libs/MSDFData/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("MSDFData")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("MSDFData")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("eabea510-3e53-4f19-9f0b-75c5ca9dfa3b")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/MSDFData/packages.config b/Other/libs/MSDFData/packages.config deleted file mode 100644 index 0946c9e46..000000000 --- a/Other/libs/MSDFData/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/Other/libs/MSDFExtension/AtlasBuilder.cs b/Other/libs/MSDFExtension/AtlasBuilder.cs index 1b0342358..64541498a 100644 --- a/Other/libs/MSDFExtension/AtlasBuilder.cs +++ b/Other/libs/MSDFExtension/AtlasBuilder.cs @@ -8,85 +8,80 @@ namespace MSDFExtension { public class AtlasBuilder { - public int Width; - public int Height; - public char[] CharMap; - private Rgba32[] RawData; - public int GlyphSize; + public int Width { get; } + public int Height { get; } + public char[] CharMap { get; } + private readonly Rgba32[] RawData; + public int GlyphSize { get; } private int Progress; public AtlasBuilder(int totalChars, int size) { - Progress = 0; int width = 1; int height = 1; while (width * height < totalChars) { width *= 2; if (width * height < totalChars) - { height *= 2; - } } Width = width; Height = height; GlyphSize = size; CharMap = new char[totalChars]; + RawData = new Rgba32[Width * Height * GlyphSize * GlyphSize]; Progress = 0; - - RawData = new Rgba32[width * height * size * size]; } public int AddChar(char c, Stream imageData) { - var image = Image.Load(imageData); + using var image = Image.Load(imageData); + + if (image.Width != GlyphSize || image.Height != GlyphSize) + { + throw new ArgumentException($"Glyph image size must be {GlyphSize}x{GlyphSize} pixels."); + } + var buf = new Rgba32[image.Width * image.Height]; - image.SavePixelData(buf); + image.CopyPixelDataTo(buf); + return AddChar(c, buf); } public int AddChar(char c, Rgba32[] imageData) { + if (imageData.Length != GlyphSize * GlyphSize) + throw new ArgumentException($"Pixel array length must be {GlyphSize * GlyphSize}."); + lock (this) { + if (Progress >= CharMap.Length) + throw new InvalidOperationException("Atlas is already full."); + CharMap[Progress] = c; - var x = (Progress % Width) * GlyphSize; - var y = (Progress / Width) * GlyphSize; - var lineWidth = (Width * GlyphSize); - var lineInd = x + y * lineWidth; - var ind = lineInd; - var srcInd = 0; - for (int oy = 0; oy < GlyphSize; oy++) + + int xOffset = (Progress % Width) * GlyphSize; + int yOffset = (Progress / Width) * GlyphSize; + int lineWidth = Width * GlyphSize; + + for (int y = 0; y < GlyphSize; y++) { - for (int ox = 0; ox < GlyphSize; ox++) - { - if (ind >= RawData.Length || ind < 0) - { - throw new Exception("dst oob: " + ind + "/" + RawData.Length); - } - if (srcInd >= imageData.Length || srcInd < 0) - { - throw new Exception("src oob: " + srcInd + "/" + imageData.Length); - } - RawData[ind++] = imageData[srcInd++]; - } - lineInd += lineWidth; - ind = lineInd; + int dstIndex = (yOffset + y) * lineWidth + xOffset; + int srcIndex = y * GlyphSize; + Array.Copy(imageData, srcIndex, RawData, dstIndex, GlyphSize); } + return Progress++; } } public byte[] Save() { - Image result = Image.LoadPixelData(RawData, Width * GlyphSize, Height * GlyphSize); - - using (var str = new MemoryStream()) - { - result.SaveAsPng(str); - return str.ToArray(); - } + using var result = Image.LoadPixelData(RawData, Width * GlyphSize, Height * GlyphSize); + using var ms = new MemoryStream(); + result.SaveAsPng(ms); + return ms.ToArray(); } public FieldAtlas Finish() diff --git a/Other/libs/MSDFExtension/FieldFontImporter.cs b/Other/libs/MSDFExtension/FieldFontImporter.cs index 1a24d3d23..3f8376f7b 100644 --- a/Other/libs/MSDFExtension/FieldFontImporter.cs +++ b/Other/libs/MSDFExtension/FieldFontImporter.cs @@ -1,34 +1,74 @@ -using System; +using Microsoft.Xna.Framework.Content.Pipeline; +using MSDFData; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using IniParser; -using Microsoft.Xna.Framework.Content.Pipeline; -using MSDFData; namespace MSDFExtension -{ +{ [ContentImporter(".ini", DisplayName = "Field Font Importer", DefaultProcessor = "FieldFontProcessor")] public class FieldFontImporter : ContentImporter - { + { public override FontDescription Import(string filename, ContentImporterContext context) { - return Parse(filename); + return Parse(filename); } private static FontDescription Parse(string filename) - { - var parser = new FileIniDataParser(); - var data = parser.ReadFile(filename, System.Text.Encoding.UTF8); + { + var iniData = ReadIniFile(filename); + + if (!iniData.TryGetValue("font", out var fontSection)) + throw new Exception("Missing [font] section in INI file."); + + if (!fontSection.TryGetValue("path", out var path)) + throw new Exception("Missing 'path' in [font] section."); - var fontSection = data.Sections["font"]; - var path = fontSection["path"]; + var characterSection = iniData.ContainsKey("characters") ? iniData["characters"] : fontSection; - var characterSection = data.Sections["characters"] ?? fontSection; - var characters = ParseRanges(characterSection["ranges"]); + if (!characterSection.TryGetValue("ranges", out var ranges)) + throw new Exception("Missing 'ranges' in [characters] or [font] section."); + + var characters = ParseRanges(ranges); return new FontDescription(path, characters); } + // Reads a simple INI file into a dictionary of sections, each containing a dictionary of key-value pairs + private static Dictionary> ReadIniFile(string filename) + { + var result = new Dictionary>(StringComparer.OrdinalIgnoreCase); + Dictionary? currentSection = null; + + foreach (var rawLine in File.ReadAllLines(filename)) + { + var line = rawLine.Trim(); + + if (string.IsNullOrEmpty(line) || line.StartsWith(";") || line.StartsWith("#")) + continue; // Skip comments and empty lines + + if (line.StartsWith("[") && line.EndsWith("]")) + { + var sectionName = line[1..^1].Trim(); + currentSection = new Dictionary(StringComparer.OrdinalIgnoreCase); + result[sectionName] = currentSection; + } + else if (currentSection != null && line.Contains('=')) + { + var parts = line.Split('=', 2); + var key = parts[0].Trim(); + var value = parts[1].Trim(); + currentSection[key] = value; + } + else + { + throw new Exception($"Invalid line in INI file: {line}"); + } + } + + return result; + } private static char[] ParseRanges(string ranges) { @@ -37,61 +77,47 @@ private static char[] ParseRanges(string ranges) var characters = new HashSet(); foreach (var tuple in tuples) { - // Every tuple should consist of two characters seperated by a comma var parts = tuple.Split(','); if (parts.Length != 2) - { throw new Exception($"Unexpected number of tuple elements in tuple: {tuple}"); - } if (parts[0].Length != 1 || parts[1].Length != 1) - { - throw new Exception($"A tuple can only contain two characters seperated by a comma: {tuple}"); - } + throw new Exception($"A tuple can only contain two characters separated by a comma: {tuple}"); - // Compute the entire character range from the two extremes (inclusive) var start = parts[0][0]; var end = parts[1][0]; - - for (int i = start; i <= end; i++) - { - characters.Add((char) i); - } + for (int i = start; i <= end; i++) + characters.Add((char)i); } return characters.ToArray(); } - /// - /// Parses tuples, and returns an enumerable with the contents of each tuple (so without the braces) - /// private static IEnumerable ParseTuples(string ranges) { var tuples = new List(); - - // -1 signals the we have not seen the opening brace of the tuple yet var start = -1; + for (var i = 0; i < ranges.Length; i++) { var c = ranges[i]; + if (start > -1) - { + { if (c == ')') { var length = i - start - 1; if (length < 1) - { throw new Exception($"Empty tuple at position {start}"); - } + tuples.Add(ranges.Substring(start + 1, length)); start = -1; - } - else if (c == '(') + } + else if (c == '(') { - throw new Exception( - $"Unexpected character '(', tuple was already openened at position {start}"); - } + throw new Exception($"Unexpected character '(', tuple was already opened at position {start}"); + } } else if (c == '(') { @@ -102,5 +128,4 @@ private static IEnumerable ParseTuples(string ranges) return tuples; } } - } diff --git a/Other/libs/MSDFExtension/FieldFontProcessor.cs b/Other/libs/MSDFExtension/FieldFontProcessor.cs index 878cf8877..8e82492c9 100644 --- a/Other/libs/MSDFExtension/FieldFontProcessor.cs +++ b/Other/libs/MSDFExtension/FieldFontProcessor.cs @@ -1,21 +1,21 @@ -using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Content.Pipeline; +using MSDFData; +using RoyT.TrueType.Helpers; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Content.Pipeline; -using MSDFData; -using RoyT.TrueType.Helpers; namespace MSDFExtension { - + [ContentProcessor(DisplayName = "Field Font Processor")] public class FieldFontProcessor : ContentProcessor - { + { [DisplayName("msdfgen path")] [Description("Path to the msdfgen binary used to generate the multi-spectrum signed distance field")] [DefaultValue("msdfgen.exe")] @@ -41,7 +41,7 @@ public override FieldFont Process(FontDescription input, ContentProcessorContext if (File.Exists(msdfgen)) { - var glyphs = new FieldGlyph[input.Characters.Count]; + var glyphs = new FieldGlyph?[input.Characters.Count]; Atlas = new AtlasBuilder(input.Characters.Count, (int)Resolution); // Generate a distance field for each character using msdfgen @@ -51,11 +51,11 @@ public override FieldFont Process(FontDescription input, ContentProcessorContext i => { var c = input.Characters[i]; - glyphs[i] = CreateFieldGlyphForCharacter(c, input, msdfgen, objPath); + glyphs[i] = CreateFieldGlyphForCharacter(c, input, msdfgen, objPath); }); - + var kerning = ReadKerningInformation(input.Path, input.Characters); - return new FieldFont(input.Path, glyphs.Where(x => x != null).ToArray(), kerning, this.Range, Atlas.Finish()); + return new FieldFont(input.Path, glyphs.Where(x => x != null).Select(x => x.Value).ToArray(), kerning, this.Range, Atlas.Finish()); } throw new FileNotFoundException( @@ -63,8 +63,8 @@ public override FieldFont Process(FontDescription input, ContentProcessorContext msdfgen); } - private FieldGlyph CreateFieldGlyphForCharacter(char c, FontDescription input, string msdfgen, string objPath) - { + private FieldGlyph? CreateFieldGlyphForCharacter(char c, FontDescription input, string msdfgen, string objPath) + { var metrics = CreateDistanceFieldForCharacter(input, msdfgen, objPath, c); var path = GetOuputPath(objPath, input, c); int atlasIndex = 0; @@ -72,7 +72,8 @@ private FieldGlyph CreateFieldGlyphForCharacter(char c, FontDescription input, s { using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) atlasIndex = Atlas.AddChar(c, stream); - } catch (Exception) + } + catch (Exception) { return null; } @@ -80,19 +81,23 @@ private FieldGlyph CreateFieldGlyphForCharacter(char c, FontDescription input, s var glyph = new FieldGlyph(c, atlasIndex, metrics); return glyph; - } + } private Metrics CreateDistanceFieldForCharacter(FontDescription font, string msdfgen, string objPath, char c) { var outputPath = GetOuputPath(objPath, font, c); var res = this.Resolution; - var startInfo = new ProcessStartInfo(msdfgen) + var msdfgenArgs = $"-font \"{font.Path}\" {(int)c} -o \"{outputPath}\" -size {res} {res} -pxrange {this.Range} -autoframe -printmetrics"; + + // On Linux/macOS, run .exe files through Wine + var isWindows = Environment.OSVersion.Platform == PlatformID.Win32NT; + var startInfo = new ProcessStartInfo(isWindows ? msdfgen : "wine") { UseShellExecute = false, RedirectStandardOutput = true, - Arguments = $"-font \"{font.Path}\" {(int)c} -o \"{outputPath}\" -size {res} {res} -pxrange {this.Range} -autoframe -printmetrics" + Arguments = isWindows ? msdfgenArgs : $"\"{msdfgen}\" {msdfgenArgs}" }; - + var process = System.Diagnostics.Process.Start(startInfo); if (process == null) { @@ -100,7 +105,7 @@ private Metrics CreateDistanceFieldForCharacter(FontDescription font, string msd } var output = process.StandardOutput.ReadToEnd(); - return ParseOutput(output); + return ParseOutput(output); } private static Metrics ParseOutput(string output) @@ -130,36 +135,42 @@ private static Vector2 ParseVector2(string text) var args = text.Split(','); return new Vector2(FloatHelper.ParseInvariant(args[0]), FloatHelper.ParseInvariant(args[1])); } - - private static void ParseLine(string line, string match, Func resultParser, ref T result) + + private static void ParseLine(string line, string match, Func resultParser, ref T result) { if (line.StartsWith(match, StringComparison.InvariantCultureIgnoreCase)) { var value = line.Substring(match.Length).Trim(); - result = resultParser(value); - } + result = resultParser(value); + } } private static List ReadKerningInformation(string path, IReadOnlyList characters) { var pairs = new List(); - var font = RoyT.TrueType.TrueTypeFont.FromFile(path); foreach (var left in characters) { foreach (var right in characters) { - var kerning = KerningHelper.GetHorizontalKerning(left, right, font); - if (kerning > 0 || kerning < 0) + try + { + var kerning = KerningHelper.GetHorizontalKerning(left, right, font); + + if (kerning != 0) + { + pairs.Add(new KerningPair(left, right, kerning / 64.0f)); + } + } + catch (IndexOutOfRangeException) { - // Scale the kerning by the same factor MSDFGEN scales it - pairs.Add(new KerningPair(left, right, kerning / 64.0f)); + continue; } } } return pairs; } - } + } } \ No newline at end of file diff --git a/Other/libs/MSDFExtension/FieldFontWriter.cs b/Other/libs/MSDFExtension/FieldFontWriter.cs new file mode 100644 index 000000000..af14282d0 --- /dev/null +++ b/Other/libs/MSDFExtension/FieldFontWriter.cs @@ -0,0 +1,85 @@ +using Microsoft.Xna.Framework.Content.Pipeline; +using Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compiler; +using System; + +namespace MSDFData +{ + [ContentTypeWriter] + public class FieldFontWriter : ContentTypeWriter + { + public override string GetRuntimeReader(TargetPlatform targetPlatform) + { + var targetType = typeof(FieldFontReader); + return targetType.FullName + ", " + targetType.Assembly.FullName; + /* + return typeof(FieldFontReader).AssemblyQualifiedName ?? string.Empty; + */ + } + /* + + public override string GetRuntimeType(TargetPlatform targetPlatform) + { + //_targetType.FullName + ", " + _targetType.Assembly.FullName + return typeof(FieldFont).AssemblyQualifiedName ?? string.Empty; + } + */ + + private void WriteMetrics(ContentWriter output, Metrics value) + { + output.Write(value.Advance); + output.Write(value.Scale); + output.Write(value.Translation); + } + + private void WriteFieldGlyph(ContentWriter output, FieldGlyph value) + { + output.Write(value.Character); + output.Write(value.AtlasIndex); + WriteMetrics(output, value.Metrics); + } + + private void WriteKerningPair(ContentWriter output, KerningPair value) + { + output.Write(value.Left); + output.Write(value.Right); + output.Write(value.Advance); + } + + private void WriteAtlas(ContentWriter output, FieldAtlas value) + { + output.Write(value.Width); + output.Write(value.Height); + output.Write(value.GlyphSize); + + output.Write(value.PNGData.Length); + output.Write(value.PNGData); + + output.Write(value.CharMap.Length); + output.Write(value.CharMap); + } + + protected override void Write(ContentWriter output, FieldFont value) + { + output.Write(value.Name); + output.Write(value.PxRange); + + var glyphs = value.GlyphsRaw; + output.Write(glyphs.Count); + + foreach (var glyph in glyphs) + { + output.Write(glyph.Key); + WriteFieldGlyph(output, glyph.Value); + } + + var pairs = value.KerningPairs; + output.Write(pairs.Count); + foreach (var pair in pairs) + { + WriteKerningPair(output, pair); + } + + WriteAtlas(output, value.Atlas); + } + } +} diff --git a/Other/libs/MSDFExtension/MSDFExtension.csproj b/Other/libs/MSDFExtension/MSDFExtension.csproj index eb42dbaf9..0ba2ba6b6 100644 --- a/Other/libs/MSDFExtension/MSDFExtension.csproj +++ b/Other/libs/MSDFExtension/MSDFExtension.csproj @@ -1,223 +1,33 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {EBF08DC7-916D-4133-BADE-38C31E29F18A} - Library - Properties + net8.0 MSDFExtension MSDFExtension - v4.7 512 - - - + true + True + true + link - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\..\..\TSOClient\packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll - - - ..\..\..\TSOClient\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll - - - ..\..\..\TSOClient\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - - - ..\..\..\TSOClient\packages\MonoGame.Framework.Content.Pipeline.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.Content.Pipeline.dll - - - ..\..\..\TSOClient\packages\RoyT.TrueType.0.1.1\lib\netstandard1.6\RoyT.TrueType.dll - - - ..\..\..\TSOClient\packages\SixLabors.Core.1.0.0-beta0005\lib\netstandard1.1\SixLabors.Core.dll - - - ..\..\..\TSOClient\packages\SixLabors.ImageSharp.1.0.0-beta0004\lib\netstandard2.0\SixLabors.ImageSharp.dll - - - - ..\..\..\TSOClient\packages\System.AppContext.4.3.0\lib\net463\System.AppContext.dll - - - ..\..\..\TSOClient\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - ..\..\..\TSOClient\packages\System.Console.4.3.0\lib\net46\System.Console.dll - True - True - - - ..\..\..\TSOClient\packages\System.Diagnostics.DiagnosticSource.4.3.0\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - ..\..\..\TSOClient\packages\System.Diagnostics.Tracing.4.3.0\lib\net462\System.Diagnostics.Tracing.dll - True - True - - - ..\..\..\TSOClient\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll - True - True - - - ..\..\..\TSOClient\packages\System.IO.4.3.0\lib\net462\System.IO.dll - True - True - - - ..\..\..\TSOClient\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - True - True - - - - ..\..\..\TSOClient\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll - True - True - - - ..\..\..\TSOClient\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - True - True - - - ..\..\..\TSOClient\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - True - True - - - ..\..\..\TSOClient\packages\System.Linq.4.3.0\lib\net463\System.Linq.dll - True - True - - - ..\..\..\TSOClient\packages\System.Linq.Expressions.4.3.0\lib\net463\System.Linq.Expressions.dll - True - True - - - ..\..\..\TSOClient\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll - - - ..\..\..\TSOClient\packages\System.Net.Http.4.3.0\lib\net46\System.Net.Http.dll - True - True - - - ..\..\..\TSOClient\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll - True - True - - - - ..\..\..\TSOClient\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll - - - ..\..\..\TSOClient\packages\System.Reflection.4.3.0\lib\net462\System.Reflection.dll - True - True - - - ..\..\..\TSOClient\packages\System.Runtime.4.3.0\lib\net462\System.Runtime.dll - True - True - - - ..\..\..\TSOClient\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\..\..\TSOClient\packages\System.Runtime.Extensions.4.3.0\lib\net462\System.Runtime.Extensions.dll - True - True - - - ..\..\..\TSOClient\packages\System.Runtime.InteropServices.4.3.0\lib\net463\System.Runtime.InteropServices.dll - True - True - - - ..\..\..\TSOClient\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - True - True - - - ..\..\..\TSOClient\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll - True - True - - - ..\..\..\TSOClient\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - True - - - ..\..\..\TSOClient\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - True - - - ..\..\..\TSOClient\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - True - - - ..\..\..\TSOClient\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll - True - True - - - - - ..\..\..\TSOClient\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - True - True - - + - - - - - + + + + - - + + - - {eabea510-3e53-4f19-9f0b-75c5ca9dfa3b} - MSDFData - + + Always + + + Always + - - - \ No newline at end of file + + diff --git a/Other/libs/MSDFExtension/Properties/AssemblyInfo.cs b/Other/libs/MSDFExtension/Properties/AssemblyInfo.cs deleted file mode 100644 index 81ec90097..000000000 --- a/Other/libs/MSDFExtension/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FontExtension")] -[assembly: AssemblyProduct("FontExtension")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("597b4008-2083-4375-a6eb-5f6e0d801e5d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/MSDFExtension/app.config b/Other/libs/MSDFExtension/app.config deleted file mode 100644 index d851c80e3..000000000 --- a/Other/libs/MSDFExtension/app.config +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Other/libs/MSDFExtension/packages.config b/Other/libs/MSDFExtension/packages.config deleted file mode 100644 index 657b505e1..000000000 --- a/Other/libs/MSDFExtension/packages.config +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Other/libs/Mp3Sharp.License.txt b/Other/libs/Mp3Sharp.License.txt deleted file mode 100644 index 65c5ca88a..000000000 --- a/Other/libs/Mp3Sharp.License.txt +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/Other/libs/TargaImage/Properties/AssemblyInfo.cs b/Other/libs/TargaImage/Properties/AssemblyInfo.cs deleted file mode 100644 index f1b2c93a3..000000000 --- a/Other/libs/TargaImage/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TargaImage")] -[assembly: AssemblyDescription("Loads Targa image files using pure .NET code.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Paloma")] -[assembly: AssemblyProduct("TargaImage")] -[assembly: AssemblyCopyright("Copyright © 2008")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0477cc6f-1738-4380-826f-386072ba69ff")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.1")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/TargaImage/TargaImage.csproj b/Other/libs/TargaImage/TargaImage.csproj index b4bde8d79..65c873f8a 100644 --- a/Other/libs/TargaImage/TargaImage.csproj +++ b/Other/libs/TargaImage/TargaImage.csproj @@ -1,91 +1,26 @@ - - + + - Debug - AnyCPU - 9.0.21022 - 2.0 - {56F4BD87-2404-4263-80D5-6FA2161EB0A4} + net9.0-windows + enable + disable Library Properties TargaImage TargaImage - v4.5 512 Resources\Paloma.ico - - - - - 3.5 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - AllRules.ruleset - false - true - - - pdbonly - true - ..\..\..\TSOClient\MonoTSOClient\tso.client\tso.client\bin\WindowsGL\Debug\ - TRACE - prompt - 4 - bin\Release\TargaImage.xml - AllRules.ruleset - false - true - - - bin\ServerRelease\ - TRACE - bin\Release\TargaImage.xml - true - pdbonly - AnyCPU - prompt - AllRules.ruleset + true + partial + - - - - - - - - True - True - Resource1.resx - - - + + - - - - - ResXFileCodeGenerator - Resource1.Designer.cs - Designer - + - - - \ No newline at end of file + + diff --git a/Other/libs/TargaImagePCL/Properties/AssemblyInfo.cs b/Other/libs/TargaImagePCL/Properties/AssemblyInfo.cs deleted file mode 100644 index 8361fae85..000000000 --- a/Other/libs/TargaImagePCL/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TargaImagePCL")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TargaImagePCL")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/TargaImagePCL/TargaImagePCL.csproj b/Other/libs/TargaImagePCL/TargaImagePCL.csproj index ed0bc7e94..ea37ffb4f 100644 --- a/Other/libs/TargaImagePCL/TargaImagePCL.csproj +++ b/Other/libs/TargaImagePCL/TargaImagePCL.csproj @@ -1,66 +1,22 @@ - - - + + - 10.0 - Debug - AnyCPU - {D8232422-9D79-4200-A981-EB70ED82CCF3} + net9.0 + enable + disable Library - Properties TargaImagePCL TargaImagePCL - en-US 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Profile7 - v4.5 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset + true + true + true + full + - - - - - - - - - - \ No newline at end of file + + diff --git a/Other/libs/VoronoiLib/Properties/AssemblyInfo.cs b/Other/libs/VoronoiLib/Properties/AssemblyInfo.cs deleted file mode 100644 index 8d739771f..000000000 --- a/Other/libs/VoronoiLib/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Resources; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("VoronoiLib")] -[assembly: AssemblyDescription("Fortune Algorithm")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("VoronoiLib")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Other/libs/VoronoiLib/VoronoiLib.csproj b/Other/libs/VoronoiLib/VoronoiLib.csproj index d3db48714..d9d2addfa 100644 --- a/Other/libs/VoronoiLib/VoronoiLib.csproj +++ b/Other/libs/VoronoiLib/VoronoiLib.csproj @@ -1,67 +1,13 @@ - - - + + - Debug - AnyCPU - {5D6B850B-3084-4C45-A8D7-7CCF67260B21} + net9.0 + enable + disable Library - Properties VoronoiLib VoronoiLib - v4.5 512 - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/App.ico b/Other/libs/mp3sharp/ManagedDirectsoundDemo/App.ico deleted file mode 100644 index 3a5525fd7..000000000 Binary files a/Other/libs/mp3sharp/ManagedDirectsoundDemo/App.ico and /dev/null differ diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/AssemblyInfo.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/AssemblyInfo.cs deleted file mode 100644 index 9f89a3282..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/AssemblyInfo.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -// -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -// -[assembly: AssemblyTitle("")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion("1.0.*")] - -// -// In order to sign your assembly you must specify a key to use. Refer to the -// Microsoft .NET Framework documentation for more information on assembly signing. -// -// Use the attributes below to control which key is used for signing. -// -// Notes: -// (*) If no key is specified, the assembly is not signed. -// (*) KeyName refers to a key that has been installed in the Crypto Service -// Provider (CSP) on your machine. KeyFile refers to a file which contains -// a key. -// (*) If the KeyFile and the KeyName values are both specified, the -// following processing occurs: -// (1) If the KeyName can be found in the CSP, that key is used. -// (2) If the KeyName does not exist and the KeyFile does exist, the key -// in the KeyFile is installed into the CSP and used. -// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. -// When specifying the KeyFile, the location of the KeyFile should be -// relative to the project output directory which is -// %Project Directory%\obj\. For example, if your KeyFile is -// located in the project directory, you would specify the AssemblyKeyFile -// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] -// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework -// documentation for more information on this. -// -[assembly: AssemblyDelaySign(false)] -[assembly: AssemblyKeyFile("")] -[assembly: AssemblyKeyName("")] diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/DXUtil.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/DXUtil.cs deleted file mode 100644 index 2a1cb3dcf..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/DXUtil.cs +++ /dev/null @@ -1,313 +0,0 @@ -//----------------------------------------------------------------------------- -// File: DXUtil.cs -// -// Desc: Shortcut macros and functions for using DX objects -// -// Copyright (c) Microsoft Corporation. All rights reserved -//----------------------------------------------------------------------------- -using System; -using System.IO; -using System.Runtime.InteropServices; - - - - -/// -/// Enumeration for various actions our timer can perform -/// -public enum DirectXTimer -{ - Reset, - Start, - Stop, - Advance, - GetAbsoluteTime, - GetApplicationTime, - GetElapsedTime -}; - - - - -/// -/// Generic utility functions for our samples -/// -public class DXUtil -{ - #region Timer Internal Stuff - [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously - [DllImport("kernel32")] - private static extern bool QueryPerformanceFrequency(ref long PerformanceFrequency); - [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously - [DllImport("kernel32")] - private static extern bool QueryPerformanceCounter(ref long PerformanceCount); - [System.Security.SuppressUnmanagedCodeSecurity] // We won't use this maliciously - [DllImport("winmm.dll")] - public static extern int timeGetTime(); - private static bool isTimerInitialized = false; - private static bool m_bUsingQPF = false; - private static bool m_bTimerStopped = true; - private static long m_llQPFTicksPerSec = 0; - private static long m_llStopTime = 0; - private static long m_llLastElapsedTime = 0; - private static long m_llBaseTime = 0; - private static double m_fLastElapsedTime = 0.0; - private static double m_fBaseTime = 0.0; - private static double m_fStopTime = 0.0; - #endregion - - // Constants for SDK Path registry keys - private const string sdkPath = "Software\\Microsoft\\DirectX SDK"; - private const string sdkKey = "DX9S4SDK Samples Path"; - - private DXUtil() { /* Private Constructor */ } - - - - - /// - /// Returns the DirectX SDK media path - /// - public static string SdkMediaPath - { - get - { - Microsoft.Win32.RegistryKey rKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(sdkPath); - string sReg = string.Empty; - if (rKey != null) - { - sReg = (string)rKey.GetValue(sdkKey); - rKey.Close(); - } - if (sReg != null) - sReg += @"\Media\"; - else - return string.Empty; - - return sReg; - } - } - - - - - /// - /// Performs timer opertations. Use the following commands: - /// - /// DirectXTimer.Reset - to reset the timer - /// DirectXTimer.Start - to start the timer - /// DirectXTimer.Stop - to stop (or pause) the timer - /// DirectXTimer.Advance - to advance the timer by 0.1 seconds - /// DirectXTimer.GetAbsoluteTime - to get the absolute system time - /// DirectXTimer.GetApplicationTime - to get the current time - /// DirectXTimer.GetElapsedTime - to get the time that elapsed between TIMER_GETELAPSEDTIME calls - /// - /// - public static float Timer(DirectXTimer command) - { - if (!isTimerInitialized) - { - isTimerInitialized = true; - - // Use QueryPerformanceFrequency() to get frequency of timer. If QPF is - // not supported, we will timeGetTime() which returns milliseconds. - long qwTicksPerSec = 0; - m_bUsingQPF = QueryPerformanceFrequency(ref qwTicksPerSec); - if (m_bUsingQPF) - m_llQPFTicksPerSec = qwTicksPerSec; - } - if (m_bUsingQPF) - { - double time; - double fElapsedTime; - long qwTime = 0; - - // Get either the current time or the stop time, depending - // on whether we're stopped and what command was sent - if (m_llStopTime != 0 && command != DirectXTimer.Start && command != DirectXTimer.GetAbsoluteTime) - qwTime = m_llStopTime; - else - QueryPerformanceCounter(ref qwTime); - - // Return the elapsed time - if (command == DirectXTimer.GetElapsedTime) - { - fElapsedTime = (double) (qwTime - m_llLastElapsedTime) / (double) m_llQPFTicksPerSec; - m_llLastElapsedTime = qwTime; - return (float)fElapsedTime; - } - - // Return the current time - if (command == DirectXTimer.GetApplicationTime) - { - double fAppTime = (double) (qwTime - m_llBaseTime) / (double) m_llQPFTicksPerSec; - return (float)fAppTime; - } - - // Reset the timer - if (command == DirectXTimer.Reset) - { - m_llBaseTime = qwTime; - m_llLastElapsedTime = qwTime; - m_llStopTime = 0; - m_bTimerStopped = false; - return 0.0f; - } - - // Start the timer - if (command == DirectXTimer.Start) - { - if (m_bTimerStopped) - m_llBaseTime += qwTime - m_llStopTime; - m_llStopTime = 0; - m_llLastElapsedTime = qwTime; - m_bTimerStopped = false; - return 0.0f; - } - - // Stop the timer - if (command == DirectXTimer.Stop) - { - if (!m_bTimerStopped) - { - m_llStopTime = qwTime; - m_llLastElapsedTime = qwTime; - m_bTimerStopped = true; - } - return 0.0f; - } - - // Advance the timer by 1/10th second - if (command == DirectXTimer.Advance) - { - m_llStopTime += m_llQPFTicksPerSec/10; - return 0.0f; - } - - if (command == DirectXTimer.GetAbsoluteTime) - { - time = qwTime / (double) m_llQPFTicksPerSec; - return (float)time; - } - - return -1.0f; // Invalid command specified - } - else - { - // Get the time using timeGetTime() - double time; - double fElapsedTime; - - // Get either the current time or the stop time, depending - // on whether we're stopped and what command was sent - if (m_fStopTime != 0.0 && command != DirectXTimer.Start && command != DirectXTimer.GetAbsoluteTime) - time = m_fStopTime; - else - time = timeGetTime() * 0.001; - - // Return the elapsed time - if (command == DirectXTimer.GetElapsedTime) - { - fElapsedTime = (double) (time - m_fLastElapsedTime); - m_fLastElapsedTime = time; - return (float) fElapsedTime; - } - - // Return the current time - if (command == DirectXTimer.GetApplicationTime) - { - return (float) (time - m_fBaseTime); - } - - // Reset the timer - if (command == DirectXTimer.Reset) - { - m_fBaseTime = time; - m_fLastElapsedTime = time; - m_fStopTime = 0; - m_bTimerStopped = false; - return 0.0f; - } - - // Start the timer - if (command == DirectXTimer.Start) - { - if (m_bTimerStopped) - m_fBaseTime += time - m_fStopTime; - m_fStopTime = 0.0f; - m_fLastElapsedTime = time; - m_bTimerStopped = false; - return 0.0f; - } - - // Stop the timer - if (command == DirectXTimer.Stop) - { - if (!m_bTimerStopped) - { - m_fStopTime = time; - m_fLastElapsedTime = time; - m_bTimerStopped = true; - } - return 0.0f; - } - - // Advance the timer by 1/10th second - if (command == DirectXTimer.Advance) - { - m_fStopTime += 0.1f; - return 0.0f; - } - - if (command == DirectXTimer.GetAbsoluteTime) - { - return (float) time; - } - - return -1.0f; // Invalid command specified - } - } - - - - - /// - /// Returns a valid path to a DXSDK media file - /// - /// Initial path to search - /// Filename we're searching for - /// Full path to the file - public static string FindMediaFile(string path, string filename) - { - // First try to load the file in the full path - if (path != null) - { - if (File.Exists(AppendDirectorySeparator(path) + filename)) - return AppendDirectorySeparator(path) + filename; - } - - // if not try to find the filename in the current folder. - if (File.Exists(filename)) - return AppendDirectorySeparator(Directory.GetCurrentDirectory()) + filename; - - // last, check if the file exists in the media directory - if (File.Exists(AppendDirectorySeparator(SdkMediaPath) + filename)) - return AppendDirectorySeparator(SdkMediaPath) + filename; - - throw new FileNotFoundException("Could not find this file.", filename); - } - - - - - /// - /// Returns a valid string with a directory separator at the end. - /// - private static string AppendDirectorySeparator(string filename) - { - if (!filename.EndsWith(@"\")) - return filename + @"\"; - - return filename; - } -} diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.cs deleted file mode 100644 index 5ff6b710c..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.cs +++ /dev/null @@ -1,334 +0,0 @@ -using System; -using System.Drawing; -using System.Collections; -using System.ComponentModel; -using System.Windows.Forms; -using System.Threading; -using System.IO; - -using Microsoft.DirectX; -using Microsoft.DirectX.DirectSound; -using Buffer = Microsoft.DirectX.DirectSound.Buffer; -namespace Mp3Sharp -{ - - - /// - /// Represents the method that handles a buffer notification event. - /// To properly handle the event, the NewSoundByte field should be set to an array of bytes less than or equal to the - /// NumBytesRequired property. If less than the required number of bytes are provided, the stream will fill the remainder - /// with silence. SoundFinished defaults to false, and should be set to indicate - /// if the bytes contained in NewSoundByte represent the end of the sound. - /// - public delegate void BufferNotificationEventHandler(object sender, BufferNotificationEventArgs e); - - /// - /// Describes a buffer notification event. - /// To properly handle the event, the NewSoundByte field should be set to an array of bytes less than or equal to the - /// NumBytesRequired property. If less than the required number of bytes are provided, the stream will fill the remainder - /// with silence. SoundFinished defaults to false, and should be set to indicate - /// if the bytes contained in NewSoundByte represent the end of the sound. - /// - public class BufferNotificationEventArgs : EventArgs - { - public BufferNotificationEventArgs (int numBytesRequired) - { - NumBytesRequiredRep = numBytesRequired; - } - - /// - /// Gets or sets whether these represent the final bytes in the sound. - /// - public bool SoundFinished { get { return SoundFinishedRep; } set { SoundFinishedRep = value; } } - private bool SoundFinishedRep = false; - - /// - /// Gets the number of bytes required for this event. - /// - public int NumBytesRequired { get { return NumBytesRequiredRep; } } - internal int NumBytesRequiredRep; - - /// - /// Set this field to the new bytes provided for the sound. - /// - public byte[] NewSoundByte; - } - - /// - /// Component representing a secondary buffer that can be used for streaming. - /// The buffer raises events when it reaches its half-way mark, as well as its end. - /// - public class EventRaisingSoundBuffer - { - public EventRaisingSoundBuffer(Device device, WaveFormat waveFormat, TimeSpan bufferLength) - { - Device = device; WaveFormat = waveFormat; BufferLength = bufferLength; - } - - - /// - /// Gets or sets the format of the buffer. - /// The format can be set only if the buffer is not playing. - /// Defaults to 22050Hz, 16-bit stereo sound. Hint: Use SoundUtil.CreateWaveFormat to quickly build WaveFormat objects. - /// - public WaveFormat WaveFormat - { - set - { - if (Playing) throw new ApplicationException("Can't change the format of the event-raising sound buffer while the buffer is playing."); - bool hasChanged = WaveFormat.BitsPerSample != value.BitsPerSample - || WaveFormat.Channels != value.Channels - || WaveFormat.SamplesPerSecond != value.SamplesPerSecond; - WaveFormatRep = value; - if (SB != null && hasChanged) { SB.Dispose(); SB = null; } - } - get { return WaveFormatRep; } - } - private WaveFormat WaveFormatRep = SoundUtil.CreateWaveFormat(22050, 16, 2); - - /// - /// Gets or sets the length of the buffer. - /// The buffer length can be set only if the buffer is not playing. - /// Defaults to 1.0 seconds. - /// - public TimeSpan BufferLength - { - set - { - if (BufferLengthRep == value) return; - if (Playing) throw new ApplicationException("Can't change the buffer length of the event-raising sound buffer while the buffer is playing."); - BufferLengthRep = value; - if (SB != null) { SB.Dispose(); SB = null; } - } - get { return BufferLengthRep; } - } - private TimeSpan BufferLengthRep = TimeSpan.FromSeconds(1.0); - - /// - /// Gets the span of time between events raised by the buffer. Equal to half the buffer's length. - /// - public TimeSpan EventInterval - { - get { return TimeSpan.FromSeconds(BufferLengthRep.TotalSeconds / 2); } - } - - public SecondaryBuffer SecondaryBuffer { get { return SB; } } - - SecondaryBuffer SB; - Notify Notify; - - protected Device Device; - - protected AutoResetEvent NotificationEvent = new AutoResetEvent(false); - - - /// - /// Initialize the SecondaryBuffer and Notify instances. - /// - protected void InitSecondaryBuffer() - { - if (SB != null) SB.Dispose(); - if (Notify != null) Notify.Dispose(); - - BufferDescription description = new BufferDescription(WaveFormat); - description.ControlPositionNotify = true; - description.BufferBytes = (int)Math.Round(((double)WaveFormat.AverageBytesPerSecond * this.BufferLength.TotalSeconds)); - description.ControlVolume = true; - description.ControlEffects = false; - description.Control3D = false; - description.StickyFocus = true; - - SB = new SecondaryBuffer(description, Device); - int length = SB.Caps.BufferBytes; - byte[] bytes = new byte[length]; - Random r = new Random(); - r.NextBytes(bytes); - - Notify = new Notify(SB); - BufferPositionNotify []bpn = new BufferPositionNotify[3]; - bpn[0] = new BufferPositionNotify(); - bpn[0].Offset = length/2-1; - bpn[0].EventNotifyHandle = NotificationEvent.Handle; - - bpn[1] = new BufferPositionNotify(); - bpn[1].Offset = length-1; - bpn[1].EventNotifyHandle = NotificationEvent.Handle; - - bpn[2] = new BufferPositionNotify(); - bpn[2].Offset = (int)PositionNotifyFlag.OffsetStop; - bpn[2].EventNotifyHandle = NotificationEvent.Handle; - - Notify.SetNotificationPositions(bpn, 3); - - if (Initialized != null) Initialized(this, new EventArgs()); - } - - /// - /// Event that is raised after the secondary buffer is initialized. - /// - public event EventHandler Initialized; - - - public void Play() - { - if (SB == null) InitSecondaryBuffer(); - - Thread t = new Thread(new ThreadStart(StreamControlThread)); - t.Name = "Event-Raising Sound Buffer Control Thread"; - t.IsBackground = true; - t.Start(); - - BufferPlayFlags flags = BufferPlayFlags.Looping; - //ApplyEffectsInfo(); - - SB.Play(0, flags); - } - - private void GetBytesByRaisingEvent(int locationInSecondaryBuffer, int numBytesToAcquire, BufferNotificationEventArgs e) - { - e.NumBytesRequiredRep = numBytesToAcquire; - if (BufferNotification != null) BufferNotification(this, e); - - if (e.NewSoundByte == null) e.NewSoundByte = new byte[0]; - - //Console.WriteLine("Request issued for " + numBytesToAcquire + " bytes; " + e.NewSoundByte.Length + " obtained."); - } - - private enum NextNotificationTask - { - FillSectionWithNewSound, - FillSectionWithSilence, - StopSecondaryBufferAndThread - } - - private NextNotificationTask HandleNewBytesInControlThread(int nextPlaceForBytes, int byteWindowSize, BufferNotificationEventArgs ea) - { - LockFlag lockFlag = LockFlag.None; - int bytesObtained = ea.NewSoundByte.Length; - if (bytesObtained > byteWindowSize) - { - SB.Stop(); - throw new ApplicationException("An event handler provided the streaming buffer with " + bytesObtained + " bytes of sound, but it only requested " + byteWindowSize + " bytes."); - } - else if (bytesObtained == byteWindowSize) - { - SB.Write(nextPlaceForBytes, ea.NewSoundByte, lockFlag); - } - else - { - // Fill the remainder of the segment with silence. - if (ea.NewSoundByte.Length > 0) SB.Write(nextPlaceForBytes, ea.NewSoundByte, lockFlag); - SB.Write(nextPlaceForBytes+ea.NewSoundByte.Length, new byte[byteWindowSize-ea.NewSoundByte.Length], lockFlag); - - if (ea.SoundFinished) return NextNotificationTask.FillSectionWithSilence; - } - return NextNotificationTask.FillSectionWithNewSound; - } - - /// - /// The stream control thread raises events every half the stream. - /// When the BufferNotificationEventArgs contains a SoundFinished property set to true, the - /// current buffer segment is padded with silence. At the next notification, the next - /// buffer segment is filled with silence, and no event is raised. - /// At the next notification, which will come when the padded segment - /// (not the completely silent segment) is finished, the SecondaryBuffer is stopped and - /// the thread terminated. - /// - private void StreamControlThread() - { - int nextPlaceForBytes = 0; - int wholeBufferSize = SB.Caps.BufferBytes; - int byteWindowSize = wholeBufferSize / 2; - NextNotificationTask task = NextNotificationTask.FillSectionWithNewSound; - - //BufferNotificationEventArgs ssea = new BufferNotificationEventArgs(SB.Caps.BufferBytes); - BufferNotificationEventArgs firstNotificationEventArgs = new BufferNotificationEventArgs(wholeBufferSize); - GetBytesByRaisingEvent(0, wholeBufferSize, firstNotificationEventArgs); - task = HandleNewBytesInControlThread(nextPlaceForBytes, wholeBufferSize, firstNotificationEventArgs); - - - bool terminate = false; - - while (!terminate) - { - NotificationEvent.Reset(); - NotificationEvent.WaitOne(); - - if (SB.Disposed || (!Playing)) break; - - /// Very strange behavior from DirectSound!! - /// SB.PlayPosition returns a value slightly less than the actual position. Either that or the event is raised - /// So you can use that to determine which section to fill. Fill the half that you're currently "playing" - /// according to the PlayPosition. - /// If anyone knows how to do this properly, please e-mail me, rob@mle.ie. - int playPosition = SB.PlayPosition; - int distToBegin = Math.Abs(playPosition - 0); - int distToEnd = Math.Abs(playPosition - wholeBufferSize); - int distToMid = Math.Abs(playPosition - byteWindowSize); - - if (distToMid < distToEnd && distToMid < distToBegin) - nextPlaceForBytes = 0; - else - nextPlaceForBytes = byteWindowSize; - //Console.WriteLine(DateTime.Now + ": Received request for bytes at " + nextPlaceForBytes + " and I'm now at " + SB.PlayPosition); - switch(task) - { - case NextNotificationTask.FillSectionWithNewSound: - BufferNotificationEventArgs nextNotificationEventArgs = new BufferNotificationEventArgs(byteWindowSize); - GetBytesByRaisingEvent(nextPlaceForBytes, byteWindowSize, nextNotificationEventArgs); - task = HandleNewBytesInControlThread(nextPlaceForBytes, byteWindowSize, nextNotificationEventArgs); - break; - case NextNotificationTask.FillSectionWithSilence: - task = NextNotificationTask.StopSecondaryBufferAndThread; - //Console.WriteLine("Filling section with silence at " + nextPlaceForBytes); - int currentPosition = 0; int writePos = 0; - SB.GetCurrentPosition(out currentPosition, out writePos); - //Console.WriteLine("Current pos " + currentPosition + " and writing " + byteWindowSize + " at " + nextPlaceForBytes); - SB.Write(nextPlaceForBytes, new byte[byteWindowSize], LockFlag.None); - break; - default: // NextNotificationTask.StopSecondaryBufferAndThread - SB.Stop(); - //Console.WriteLine("stream control thread dies."); - return; - } - //nextPlaceForBytes += byteWindowSize; if (nextPlaceForBytes >= SB.Caps.BufferBytes) nextPlaceForBytes = 0; - } - //Console.WriteLine("stream control thread dies."); - } - - /// - /// Event that is raised when the buffer notification event occurs. - /// - public event BufferNotificationEventHandler BufferNotification; - - - - /// - /// Gets or sets whether the sound buffer is playing. On a set, plays (but does not loop) the sound. - /// - public bool Playing - { - get { return SB != null && (SB.Status.Looping || SB.Status.Playing); } - set - { - if (value == false) SB.Stop(); - else if (!Playing) Play(); - } - } - - - public void Stop() - { - if (Playing) - { - SB.Stop(); - //NotificationEvent.Set(); - //if (RewindBufferOnStop) Rewind(); - } - } - - - - } - -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.resx b/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.resx deleted file mode 100644 index 3f337e081..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/EventRaisingSoundBuffer.resx +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.0.0.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.cs deleted file mode 100644 index ef411c151..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using System.Drawing; -using System.Windows.Forms; -using System.IO; -using Microsoft.DirectX; -using Microsoft.DirectX.DirectSound; -using Buffer = Microsoft.DirectX.DirectSound.SecondaryBuffer; - -using Mp3Sharp; - -namespace Mp3Sharp -{ - - /// - /// This is a modified version of the "PlaySound" Managed DirectSound sample. - /// - public class MainForm : Form - { - private System.ComponentModel.Container components = null; - private Button btnSoundfile; - private Label lblFilename; - private Button btnPlay; - private Button btnStop; - private Button btnCancel; - - //private SecondaryBuffer ApplicationStreamedSound = null; - private Device ApplicationDevice = null; - private string PathSoundFile = string.Empty; - private System.Windows.Forms.CheckBox cbLoopCheck; - - private StreamedSound ApplicationStreamedSound = null; - - public static int Main(string[] Args) - { - Application.Run(new MainForm()); - return 0; - } - - protected override void Dispose( bool disposing ) - { - if(disposing) - { - if (null != components) - { - components.Dispose(); - } - } - base.Dispose(disposing); - } - public MainForm() - { - // - // Required for Windows Form Designer support - // - InitializeComponent(); - } - #region InitializeComponent code - private void InitializeComponent() - { - this.btnSoundfile = new System.Windows.Forms.Button(); - this.lblFilename = new System.Windows.Forms.Label(); - this.btnPlay = new System.Windows.Forms.Button(); - this.btnStop = new System.Windows.Forms.Button(); - this.btnCancel = new System.Windows.Forms.Button(); - this.cbLoopCheck = new System.Windows.Forms.CheckBox(); - this.SuspendLayout(); - // - // btnSoundfile - // - this.btnSoundfile.Location = new System.Drawing.Point(12, 13); - this.btnSoundfile.Name = "btnSoundfile"; - this.btnSoundfile.Size = new System.Drawing.Size(83, 24); - this.btnSoundfile.TabIndex = 0; - this.btnSoundfile.Text = "Sound &file..."; - this.btnSoundfile.Click += new System.EventHandler(this.btnSoundfile_Click); - // - // lblFilename - // - this.lblFilename.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D; - this.lblFilename.Location = new System.Drawing.Point(113, 13); - this.lblFilename.Name = "lblFilename"; - this.lblFilename.Size = new System.Drawing.Size(414, 24); - this.lblFilename.TabIndex = 1; - this.lblFilename.Text = "No file loaded."; - this.lblFilename.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // btnPlay - // - this.btnPlay.Enabled = false; - this.btnPlay.Location = new System.Drawing.Point(125, 55); - this.btnPlay.Name = "btnPlay"; - this.btnPlay.Size = new System.Drawing.Size(90, 27); - this.btnPlay.TabIndex = 3; - this.btnPlay.Text = "&Play"; - this.btnPlay.Click += new System.EventHandler(this.btnPlay_Click); - // - // btnStop - // - this.btnStop.Enabled = false; - this.btnStop.Location = new System.Drawing.Point(211, 55); - this.btnStop.Name = "btnStop"; - this.btnStop.Size = new System.Drawing.Size(90, 27); - this.btnStop.TabIndex = 4; - this.btnStop.Text = "&Stop"; - this.btnStop.Click += new System.EventHandler(this.btnStop_Click); - // - // btnCancel - // - this.btnCancel.Location = new System.Drawing.Point(437, 55); - this.btnCancel.Name = "btnCancel"; - this.btnCancel.Size = new System.Drawing.Size(90, 27); - this.btnCancel.TabIndex = 5; - this.btnCancel.Text = "E&xit"; - this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click); - // - // cbLoopCheck - // - this.cbLoopCheck.Enabled = false; - this.cbLoopCheck.Location = new System.Drawing.Point(11, 51); - this.cbLoopCheck.Name = "cbLoopCheck"; - this.cbLoopCheck.Size = new System.Drawing.Size(104, 18); - this.cbLoopCheck.TabIndex = 2; - this.cbLoopCheck.Text = "&Loop sound"; - // - // MainForm - // - this.AutoScaleBaseSize = new System.Drawing.Size(6, 15); - this.ClientSize = new System.Drawing.Size(540, 88); - this.Controls.Add(this.btnSoundfile); - this.Controls.Add(this.lblFilename); - this.Controls.Add(this.cbLoopCheck); - this.Controls.Add(this.btnPlay); - this.Controls.Add(this.btnStop); - this.Controls.Add(this.btnCancel); - this.Name = "MainForm"; - this.Text = "PlayMP3"; - this.Load += new System.EventHandler(this.MainForm_Load); - this.ResumeLayout(false); - - } - #endregion - - private void btnCancel_Click(object sender, System.EventArgs e) - { - Close(); - } - - private void btnStop_Click(object sender, System.EventArgs e) - { - if(null != ApplicationStreamedSound) - ApplicationStreamedSound.Stop(); - } - - private void btnSoundfile_Click(object sender, System.EventArgs e) - { - OpenFileDialog ofd = new OpenFileDialog(); - - if(string.Empty == PathSoundFile) - PathSoundFile = DXUtil.SdkMediaPath; - - ofd.InitialDirectory = PathSoundFile; - ofd.Filter= "Mp3 files(*.mp3)|*.mp3"; - - if( DialogResult.Cancel == ofd.ShowDialog() ) - return; - - if(LoadSoundFile(ofd.FileName)) - { - PathSoundFile = Path.GetDirectoryName(ofd.FileName); - lblFilename.Text = Path.GetFileName(ofd.FileName); - EnablePlayUI(true); - } - else - { - lblFilename.Text = "No file loaded."; - EnablePlayUI(false); - } - } - - private bool LoadSoundFile(string name) - { - try - { - ApplicationStreamedSound = new StreamedMp3Sound(ApplicationDevice, new Mp3Stream(name)); - } - catch(SoundException) - { - return false; - } - return true; - } - - private void EnablePlayUI(bool enable) - { - if (enable) - { - cbLoopCheck.Enabled = true; - btnCancel.Enabled = true; - btnPlay.Enabled = true; - btnStop.Enabled = true; - } - else - { - cbLoopCheck.Enabled = false; - btnCancel.Enabled = false; - btnPlay.Enabled = false; - btnStop.Enabled = false; - } - } - - private void MainForm_Load(object sender, System.EventArgs e) - { - ApplicationDevice = new Device(); - ApplicationDevice.SetCooperativeLevel(this, CooperativeLevel.Priority); - } - - private void btnPlay_Click(object sender, System.EventArgs e) - { - if(null != ApplicationStreamedSound) - if (cbLoopCheck.Checked) ApplicationStreamedSound.Loop(); else ApplicationStreamedSound.Play(); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.resx b/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.resx deleted file mode 100644 index 28419a525..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Main.resx +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - False - - - Private - - - Private - - - False - - - Private - - - Private - - - False - - - Private - - - Private - - - False - - - Private - - - Private - - - False - - - Private - - - Private - - - False - - - Private - - - Private - - - False - - - (Default) - - - False - - - False - - - 8, 8 - - - MainForm - - - True - - - 80 - - - True - - - Private - - \ No newline at end of file diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/ManagedDirectSoundDemo.csproj b/Other/libs/mp3sharp/ManagedDirectsoundDemo/ManagedDirectSoundDemo.csproj deleted file mode 100644 index 440611814..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/ManagedDirectSoundDemo.csproj +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Readme.txt b/Other/libs/mp3sharp/ManagedDirectsoundDemo/Readme.txt deleted file mode 100644 index 5c62e4c1c..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/Readme.txt +++ /dev/null @@ -1,23 +0,0 @@ -Streaming MP3 Demo using Mp3Sharp and Managed DirectSound -Robert Burke, 25 Feb 04 -rob@mle.ie - -Here is a sample, admittedly a little rough around the edges, of how to -do streaming MP3 audio using the Mp3Stream decoder. - -Main.cs is a modified version of the "Playsound" Managed DirectX sample. -It creates an instance of the StreamedMp3Sound class: - ApplicationStreamedSound = new StreamedMp3Sound(ApplicationDevice, new Mp3Stream(name)); - -StreamedMp3Sound derives from StreamedSound, which uses an EventRaisingSoundBuffer to -implement streaming PCM audio. The only thing StreamedMp3Sound does differently from -StreamedSound that makes it MP3-specific is that it looks at the first header in the -MP3 file to see what the frequency and channel count of the bytestream is. The -frequency of the secondary buffer used by the EventRaisingSoundBuffer is set accordingly. - -If you have your own streaming classes, you can toss all of this and just use Mp3Stream -to provide you with a PCM audio stream. - -Let me know how this works for you! I tested it a little, but not extensively. -This is one of those weekend projects that is starting to take on a life of its own! - diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/SoundUtil.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/SoundUtil.cs deleted file mode 100644 index 4cdc38d3a..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/SoundUtil.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using Microsoft.DirectX.DirectSound; - -namespace Mp3Sharp -{ - - /// - /// Utility functions for working with sound. - /// - public class SoundUtil - { - private SoundUtil() { } - - /// - /// Helper method for creating WaveFormat instances - /// - /// Sampling rate - /// Bits per sample - /// Channels - /// - public static WaveFormat CreateWaveFormat(int samplingRate, short bitsPerSample, short numChannels) - { - WaveFormat wf = new WaveFormat(); - - wf.FormatTag = WaveFormatTag.Pcm; - wf.SamplesPerSecond = samplingRate; - wf.BitsPerSample = bitsPerSample; - wf.Channels = numChannels; - - wf.BlockAlign = (short)(wf.Channels * (wf.BitsPerSample / 8)); - wf.AverageBytesPerSecond = wf.SamplesPerSecond * wf.BlockAlign; - - return wf; - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedMp3Sound.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedMp3Sound.cs deleted file mode 100644 index 159c76f09..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedMp3Sound.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.IO; -using Microsoft.DirectX.DirectSound; - -namespace Mp3Sharp -{ - /// - /// A modified version of the StreamedSound class that sets the frequency of the Secondary Buffer based on the - /// frequency of the first frame of the MP3 file. - /// - public class StreamedMp3Sound : StreamedSound - { - public StreamedMp3Sound(Device device, Mp3Stream mp3SourceStream) - : base(device, mp3SourceStream, SoundUtil.CreateWaveFormat(22050, 16, 2)) - { - } - - protected override void OnBufferInitializing() - { - Mp3Stream stream = Stream as Mp3Stream; - if (stream == null) throw new ApplicationException("The stream used by the StreamedMp3Sound class should be of type Mp3Stream."); - - if (stream.Frequency < 0) stream.DecodeFrames(1); - if (stream.Frequency > 0 && stream.ChannelCount > 0) - { - this.WaveFormat = SoundUtil.CreateWaveFormat(stream.Frequency, 16, stream.ChannelCount); - } - - } - } -} diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedSound.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedSound.cs deleted file mode 100644 index e4d016f61..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/StreamedSound.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using System.IO; - -using Microsoft.DirectX.DirectSound; - -namespace Mp3Sharp -{ - - /// - /// Plays streamed PCM-format sounds. - /// - public class StreamedSound - { - public StreamedSound(Device device, Stream stream, WaveFormat waveFormat) - { - Device = device; Stream = stream; WaveFormat = waveFormat; - } - - private Device Device; - - /// - /// Gets or sets the source stream used to provide PCM-encoded bytes. - /// - public Stream Stream - { - get { return StreamRep; } - set { StreamRep = value; } - } - private Stream StreamRep = null; - - private void InitBuffer() - { - OnBufferInitializing(); - if (ERSB == null) - { - ERSB = new EventRaisingSoundBuffer(Device, WaveFormat, BufferLength); - ERSB.BufferNotification += new BufferNotificationEventHandler(OnBufferNotification); - } - else - { - ERSB.WaveFormat = WaveFormat; - ERSB.BufferLength = BufferLength; - } - OnBufferInitialized(); - } - - protected virtual void OnBufferInitializing() {} - protected virtual void OnBufferInitialized() {} - - public WaveFormat WaveFormat - { - get { return WaveFormatRep; } - set { WaveFormatRep = value; if (ERSB != null) ERSB.WaveFormat = value; } - } - public WaveFormat WaveFormatRep; - - public TimeSpan BufferLength - { - get { return BufferLengthRep; } - set { BufferLengthRep = value; if (ERSB != null) ERSB.BufferLength = value; } - } - private TimeSpan BufferLengthRep = TimeSpan.FromSeconds(1); - - /// - /// The event-raising sound buffer used by the streamed sound. - /// - protected EventRaisingSoundBuffer ERSB; - - public bool Playing - { - get { return ERSB.Playing; } - set - { - if (value) Play(); else Stop(); - } - } - - public bool Looping - { - get { return ERSB.Playing; } - set - { - if (value) Loop(); else Stop(); - } - } - - - - public void OnBufferNotification(object sender, BufferNotificationEventArgs e) - { - if (e.NewSoundByte == null || e.NewSoundByte.Length != e.NumBytesRequired) - e.NewSoundByte = new byte[e.NumBytesRequired]; - - int bytesRead = Stream.Read(e.NewSoundByte, 0, e.NumBytesRequired); - if (bytesRead != e.NumBytesRequired) - { - byte[] trimmedBytes = new byte[bytesRead]; - Array.Copy(e.NewSoundByte, trimmedBytes, bytesRead); - e.NewSoundByte = trimmedBytes; - } - e.SoundFinished = Stream.Length == Stream.Position; - - if (BufferNotification != null) BufferNotification(sender, e); - } - - /// - /// Event that is raised after bytes are added to the buffer. The event arguments will contain - /// any new bytes being provided by the streamed sound. - /// - public event BufferNotificationEventHandler BufferNotification; - - - - public void Play() - { - InitBuffer(); - ERSB.Play(); - } - - public void Loop() - { - InitBuffer(); - ERSB.Play(); - } - - public void Stop() - { - ERSB.Stop(); - } - - public void Rewind() - { - if (Playing) Stop(); - Stream.Position = 0; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/ManagedDirectsoundDemo/WaveFormat.cs b/Other/libs/mp3sharp/ManagedDirectsoundDemo/WaveFormat.cs deleted file mode 100644 index 250ffaf16..000000000 --- a/Other/libs/mp3sharp/ManagedDirectsoundDemo/WaveFormat.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Mp3Sharp -{ - - public enum WaveFormats - { - Pcm = 1, - Float = 3 - } - - [StructLayout(LayoutKind.Sequential)] - public class WaveFmt - { - public short FormatTag; - public short ChannelCount; - public int SamplesPerSecond; - public int AverageBytesPerSecond; - public short BlockAlign; - public short BitsPerSample; - public short CBSize; - - public WaveFmt(int samplingRate, short bitsPerSample, short numChannels) - { - FormatTag = (short)WaveFormats.Pcm; - ChannelCount = (short)numChannels; - SamplesPerSecond = samplingRate; - BitsPerSample = (short)bitsPerSample; - CBSize = 0; - - BlockAlign = (short)(numChannels * (bitsPerSample / 8)); - AverageBytesPerSecond = SamplesPerSecond * BlockAlign; - } - } - -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/Mp3Sharp.sln b/Other/libs/mp3sharp/Mp3Sharp.sln deleted file mode 100644 index 8bdd1be86..000000000 --- a/Other/libs/mp3sharp/Mp3Sharp.sln +++ /dev/null @@ -1,30 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 8.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mp3Sharp", "Mp3Sharp\Mp3Sharp.csproj", "{834CAB58-648D-47CC-AC6F-D01C08C809A4}" - ProjectSection(ProjectDependencies) = postProject - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedDirectSoundDemo", "ManagedDirectsoundDemo\ManagedDirectSoundDemo.csproj", "{8C85B99A-9915-4896-A8E8-891726C0A316}" - ProjectSection(ProjectDependencies) = postProject - {834CAB58-648D-47CC-AC6F-D01C08C809A4} = {834CAB58-648D-47CC-AC6F-D01C08C809A4} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfiguration) = preSolution - Debug = Debug - Release = Release - EndGlobalSection - GlobalSection(ProjectConfiguration) = postSolution - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug.ActiveCfg = Debug|.NET - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug.Build.0 = Debug|.NET - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release.ActiveCfg = Release|.NET - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release.Build.0 = Release|.NET - {8C85B99A-9915-4896-A8E8-891726C0A316}.Debug.ActiveCfg = Debug|.NET - {8C85B99A-9915-4896-A8E8-891726C0A316}.Debug.Build.0 = Debug|.NET - {8C85B99A-9915-4896-A8E8-891726C0A316}.Release.ActiveCfg = Release|.NET - {8C85B99A-9915-4896-A8E8-891726C0A316}.Release.Build.0 = Release|.NET - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - EndGlobalSection - GlobalSection(ExtensibilityAddIns) = postSolution - EndGlobalSection -EndGlobal diff --git a/Other/libs/mp3sharp/mp3sharp/AssemblyInfo.cs b/Other/libs/mp3sharp/mp3sharp/AssemblyInfo.cs deleted file mode 100644 index f1f1d69f7..000000000 --- a/Other/libs/mp3sharp/mp3sharp/AssemblyInfo.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.CompilerServices; - -// -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -// -[assembly: AssemblyTitle("Mp3Sharp")] -[assembly: AssemblyDescription("Mp3 Decoder for the .NET Framework")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Robert Burke (rob@mle.ie)")] -[assembly: AssemblyProduct("")] -[assembly: AssemblyCopyright("100% Freeware")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: - -[assembly: AssemblyVersion("1.4.*")] - -// -// In order to sign your assembly you must specify a key to use. Refer to the -// Microsoft .NET Framework documentation for more information on assembly signing. -// -// Use the attributes below to control which key is used for signing. -// -// Notes: -// (*) If no key is specified, the assembly is not signed. -// (*) KeyName refers to a key that has been installed in the Crypto Service -// Provider (CSP) on your machine. KeyFile refers to a file which contains -// a key. -// (*) If the KeyFile and the KeyName values are both specified, the -// following processing occurs: -// (1) If the KeyName can be found in the CSP, that key is used. -// (2) If the KeyName does not exist and the KeyFile does exist, the key -// in the KeyFile is installed into the CSP and used. -// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. -// When specifying the KeyFile, the location of the KeyFile should be -// relative to the project output directory which is -// %Project Directory%\obj\. For example, if your KeyFile is -// located in the project directory, you would specify the AssemblyKeyFile -// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] -// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework -// documentation for more information on this. -// -[assembly: AssemblyDelaySign(false)] -[assembly: AssemblyKeyFile("")] -[assembly: AssemblyKeyName("")] - - -[assembly: CLSCompliant(false)] -[assembly: ComVisible(true)] - diff --git a/Other/libs/mp3sharp/mp3sharp/Mp3Sharp.csproj b/Other/libs/mp3sharp/mp3sharp/Mp3Sharp.csproj deleted file mode 100644 index 55aab19e4..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Mp3Sharp.csproj +++ /dev/null @@ -1,225 +0,0 @@ - - - - Local - 7.10.3077 - 2.0 - {834CAB58-648D-47CC-AC6F-D01C08C809A4} - Debug - AnyCPU - - - Mp3Sharp - - JScript - Grid - IE50 - false - Library - - OnBuildSuccess - - - - v4.5 - - - 0.0 - - - - bin\Debug\ - false - 285212672 - false - - - - true - 4096 - false - - false - false - false - false - 1 - full - prompt - false - - - bin\Release\ - false - 285212672 - false - - - - false - 4096 - false - - false - false - false - false - 1 - none - prompt - false - true - - - bin\ServerRelease\ - 285212672 - 1 - 4096 - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - mscorlib - - - System - - - System.Data - - - System.Design - - - System.Management - - - System.Windows.Forms - - - System.XML - - - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - Code - - - - - Preview - - - - - - - - \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/Mp3Stream.cs b/Other/libs/mp3sharp/mp3sharp/Mp3Stream.cs deleted file mode 100644 index 5c569316f..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Mp3Stream.cs +++ /dev/null @@ -1,447 +0,0 @@ -// $Id: Mp3Stream.cs,v 1.3 2004/08/03 16:20:37 tekhedd Exp $ -// -// Fri Jul 30 20:39:30 EDT 2004 -// Rewrote the buffer object to hold one frame at a time for -// efficiency. Commented out some functions rather than taking -// the time to port them. --t/DD - -// Rob, Sept 1: -// - Changed access for all classes in this project except Mp3Sharp and the Exceptions to internal -// - Removed commenting from DecodeFrame method of Mp3Stream -// - Added GPL license to Mp3Sharp.cs -// - Changed version number to 1.4 - -/* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ - - -using System; -using System.Diagnostics; -using System.IO; -using System.Collections; - -namespace Mp3Sharp -{ - - /// - /// Provides a view of the sequence of bytes that are produced during the conversion of an MP3 stream - /// into a 16-bit PCM-encoded ("WAV" format) stream. - /// - public class Mp3Stream : Stream - { - /// - /// Creates a new stream instance using the provided filename, and the default chunk size of 4096 bytes. - /// - public Mp3Stream(string fileName) - :this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { } - /// - /// Creates a new stream instance using the provided filename and chunk size. - /// - public Mp3Stream(string fileName, int chunkSize) - :this(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), chunkSize) - { } - /// - /// Creates a new stream instance using the provided stream as a source, and the default chunk size of 4096 bytes. - /// - public Mp3Stream(Stream sourceStream) - : this(sourceStream, 4096) {} - - /// - /// Creates a new stream instance using the provided stream as a source. - /// - /// TODO: allow selecting stereo or mono in the constructor (note that - /// this also requires "implementing" the stereo format). - /// - public Mp3Stream(Stream sourceStream, int chunkSize) - { - FormatRep = SoundFormat.Pcm16BitStereo; - SourceStream = sourceStream; - JZBitStream = new javazoom.jl.decoder.Bitstream(new javazoom.jl.decoder.BackStream(SourceStream, chunkSize)); - QueueOBuffer = new OBuffer16BitStereo(); - - JZDecoder.OutputBuffer = QueueOBuffer; - } - - public int ChunkSize { get { return BackStreamByteCountRep; } } - private int BackStreamByteCountRep; - - /// - /// Used to interface with javaZoom. - /// - private javazoom.jl.decoder.Decoder JZDecoder = new javazoom.jl.decoder.Decoder(javazoom.jl.decoder.Decoder.DefaultParams); - /// - /// Used to interface with javaZoom. - /// - private javazoom.jl.decoder.Bitstream JZBitStream; - - - private Stream SourceStream; - - public override bool CanRead { get { return SourceStream.CanRead; } } - public override bool CanSeek { get { return SourceStream.CanSeek; } } - public override bool CanWrite { get { return SourceStream.CanWrite; } } - public override long Length { get { return SourceStream.Length; } } - - public override void Flush() { SourceStream.Flush(); } - - /// - /// Gets or sets the position of the source stream. This is relative to the number of bytes in the MP3 file, rather than - /// the total number of PCM bytes (typically signicantly greater) contained in the Mp3Stream's output. - /// - public override long Position - { - get { return SourceStream.Position; } - set { SourceStream.Position = value; } - } - /// - /// Sets the position of the source stream. - /// - public override long Seek(long pos, SeekOrigin origin) - { - return SourceStream.Seek(pos, origin); - } - /// - /// This method is not valid for an Mp3Stream. - /// - public override void SetLength(long len) - { - throw new InvalidOperationException(); - } - /// - /// This method is not valid for an Mp3Stream. - /// - public override void Write(byte[] buf, int ofs, int count) - { - throw new InvalidOperationException(); - } - - /// - /// Gets the frequency of the audio being decoded. - /// Initially set to -1. Initialized during the first call to either of the Read and DecodeFrames methods, - /// and updated during every subsequent call to one of those methods to reflect the most recent header information - /// from the MP3 stream. - /// - public int Frequency { get { return FrequencyRep; } } - private int FrequencyRep = -1; - - /// - /// Gets the number of channels available in the audio being decoded. - /// Initially set to -1. Initialized during the first call to either of the Read and DecodeFrames methods, - /// and updated during every subsequent call to one of those methods to reflect the most recent header information - /// from the MP3 stream. - /// - public short ChannelCount { get { return ChannelCountRep; } } - private short ChannelCountRep = -1; - - /// - /// Gets or sets the PCM output format of this stream. - /// - public SoundFormat Format - { - get { return FormatRep; } - - // Note: the buffers are stored in an optimized format--changing - // the Format involves flushing the buffers and so on, so - // let's just not, OK? - // set { FormatRep = value; } - } - protected SoundFormat FormatRep = SoundFormat.Pcm16BitStereo; - - /// - /// Decodes the requested number of frames from the MP3 stream - /// and caches their PCM-encoded bytes. These can subsequently be obtained using the Read method. - /// Returns the number of frames that were successfully decoded. - /// - public int DecodeFrames(int frameCount) - { - int framesDecoded = 0; - bool aFrameWasRead = true; - while (framesDecoded < frameCount && aFrameWasRead) - { - aFrameWasRead = ReadFrame(); - if (aFrameWasRead) framesDecoded++; - } - return framesDecoded; - } - - /// - /// Reads the MP3 stream as PCM-encoded bytes. Decodes a portion of the stream if necessary. - /// Returns the number of bytes read. - /// - public override int Read(byte[] buffer, int offset, int count) - { - // Copy from queue buffers, reading new ones as necessary, - // until we can't read more or we have read "count" bytes - int bytesRead = 0; - while (true) - { - if (QueueOBuffer.bytesLeft <= 0) - { - if (!ReadFrame()) // out of frames or end of stream? - break; - } - - // Copy as much as we can from the current buffer: - bytesRead += QueueOBuffer.Read( buffer, - offset + bytesRead, - count - bytesRead ); - - if (bytesRead >= count) - break; - } - return bytesRead; - } - - - // bool aFrameWasRead = true; - // while (QueueOBuffer.QueuedByteCount < count && aFrameWasRead) - // { - // aFrameWasRead = ReadFrame(); - // } - // int bytesToReturn = Math.Min(QueueOBuffer.QueuedByteCount, count); - // int bytesRead = 0; - // switch(Format) - // { - // case SoundFormat.Pcm16BitMono: - // bytesRead = QueueOBuffer.DequeueAs16BitPcmMono(buffer, offset, bytesToReturn); - // break; - // case SoundFormat.Pcm16BitStereo: - // bytesRead = QueueOBuffer.DequeueAs16BitPcmStereo(buffer, offset, bytesToReturn); - // break; - // default: - // throw new ApplicationException("Unknown sound format in Mp3Stream Read call: " + Format); - // } - // return bytesRead; - - /// - /// Reads a single byte of the PCM-encoded stream. - /// - // public override int ReadByte() - // { - // byte[] ret = new byte[1]; - // int result = Read(ret,0,1); - // if (result == 0) return -1; else return ret[0]; - // } - - /// - /// Closes the source stream and releases any associated resources. - /// If you don't call this, you may be leaking file descriptors. - /// - public override void Close() - { - JZBitStream.close(); // This should close SourceStream as well. - // SourceStream.Close(); - } - - private OBuffer16BitStereo QueueOBuffer; - - /// - /// Reads a frame from the MP3 stream. Returns whether the operation was successful. If it wasn't, - /// the source stream is probably at its end. - /// - private bool ReadFrame() - { - // Read a frame from the bitstream. - javazoom.jl.decoder.Header header = JZBitStream.readFrame(); - if (header == null) - return false; - - try - { - // Set the channel count and frequency values for the stream. - if (header.mode() == javazoom.jl.decoder.Header.SINGLE_CHANNEL) - ChannelCountRep = (short)1; - else - ChannelCountRep = (short)2; - - FrequencyRep = header.frequency(); - - // Decode the frame. - javazoom.jl.decoder.Obuffer decoderOutput = JZDecoder.decodeFrame(header, JZBitStream); - - // Apparently, the way JavaZoom sets the output buffer - // on the decoder is a bit dodgy. Even though - // this exception should never happen, we test to be sure. - if (decoderOutput != QueueOBuffer) - throw new System.ApplicationException("Output buffers are different."); - - // And we're done. - } - finally - { - // No resource leaks please! - JZBitStream.closeFrame(); - } - return true; - } - - } - - /// - /// Describes sound formats that can be produced by the Mp3Stream class. - /// - public enum SoundFormat - { - /// - /// PCM encoded, 16-bit Mono sound format. - /// - Pcm16BitMono, - /// - /// PCM encoded, 16-bit Stereo sound format. - /// - Pcm16BitStereo, - } - - /// - /// Internal class used to queue samples that are being obtained - /// from an Mp3 stream. This merges the old mp3stream OBuffer with - /// the javazoom SampleBuffer code for the highest efficiency... - /// well, not the highest possible. The highest I'm willing to sweat - /// over. --trs - /// - /// This class handles stereo 16-bit data! Switch it out if you want mono or something. - /// - internal class OBuffer16BitStereo - : javazoom.jl.decoder.Obuffer - { - // This is stereo! - static readonly int CHANNELS = 2; - - // Read offset used to read from the stream, in bytes. - int _offset; - - // end marker, one past end of array. Same as bufferp[0], but - // without the array bounds check. - int _end; - - // Write offset used in append_bytes - byte [] buffer = new byte[OBUFFERSIZE * 2]; // all channels interleaved - int [] bufferp = new int[MAXCHANNELS]; // offset in each channel not same! - - public OBuffer16BitStereo() - { - // Initialize the buffer pointers - clear_buffer(); - } - - public int bytesLeft - { - get - { - // Note: should be Math.Max( bufferp[0], bufferp[1]-1 ). - // Heh. - return _end - _offset; - - // This results in a measurable performance improvement, but - // is actually incorrect. Is there a trick to optimize this? - // return (OBUFFERSIZE * 2) - _offset; - } - } - - /// - /// Copies as much of this buffer as will fit into hte output - /// buffer. - /// - /// \return The amount of bytes copied. - /// - public int Read(byte[] buffer_out, int offset, int count) - { - int remaining = bytesLeft; - int copySize; - if (count > remaining) - { - copySize = remaining; - Debug.Assert( copySize % (2 * CHANNELS) == 0 ); - } - else - { - // Copy an even number of sample frames - int remainder = count % (2 * CHANNELS); - copySize = count - remainder; - } - - Array.Copy( buffer, _offset, buffer_out, offset, copySize ); - - _offset += copySize; - return copySize; - } - - // Inefficiently write one sample value - public override void append(int channel, short value) - { - buffer[bufferp[channel]] = (byte)(value & 0xff); - buffer[bufferp[channel] + 1] = (byte)(value >> 8); - - bufferp[channel] += CHANNELS * 2; - } - - // efficiently write 32 samples - public override void appendSamples(int channel, float[] f) - { - // Always, 32 samples are appended - int pos = bufferp[channel]; - - short s; - float fs; - for (int i = 0; i < 32; i++) - { - fs = f[i]; - if (fs > 32767.0f) // can this happen? - fs = 32767.0f; - else if (fs < - 32767.0f) - fs = - 32767.0f; - - int sample = (int) fs; - buffer[pos] = (byte)(sample & 0xff); - buffer[pos + 1] = (byte)(sample >> 8); - - pos += CHANNELS * 2; - } - - bufferp[channel] = pos; - } - - - /// - /// This implementation does not clear the buffer. - /// - public override void clear_buffer() - { - _offset = 0; - _end = 0; - - for (int i = 0; i < CHANNELS; i++) - bufferp[i] = i * 2; // two bytes per channel - } - - public override void set_stop_flag() { } - public override void write_buffer(int val) - { - _offset = 0; - - // speed optimization - save end marker, and avoid - // array access at read time. Can you believe this saves - // like 1-2% of the cpu on a PIII? I guess allocating - // that temporary "new int(0)" is expensive, too. - _end = bufferp[0]; - } - public override void close() {} - - } - -} diff --git a/Other/libs/mp3sharp/mp3sharp/Mp3StreamOLD.cs b/Other/libs/mp3sharp/mp3sharp/Mp3StreamOLD.cs deleted file mode 100644 index 7ba1dd3f1..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Mp3StreamOLD.cs +++ /dev/null @@ -1,315 +0,0 @@ -using System; -using System.IO; -using System.Collections; - -namespace Mp3Sharp -{ - - /// - /// Provides a view of the sequence of bytes that are produced during the conversion of an MP3 stream - /// into a 16-bit PCM-encoded ("WAV" format) stream. - /// - public class Mp3Stream : Stream - { - /// - /// Creates a new stream instance using the provided filename, and the default chunk size of 4096 bytes. - /// - public Mp3Stream(string fileName) - :this(new FileStream(fileName, FileMode.Open)) - { } - /// - /// Creates a new stream instance using the provided filename and chunk size. - /// - public Mp3Stream(string fileName, int chunkSize) - :this(new FileStream(fileName, FileMode.Open), chunkSize) - { } - /// - /// Creates a new stream instance using the provided stream as a source, and the default chunk size of 4096 bytes. - /// - public Mp3Stream(Stream sourceStream) - : this(sourceStream, 4096) {} - - /// - /// Creates a new stream instance using the provided stream as a source. - /// - public Mp3Stream(Stream sourceStream, int chunkSize) - { - SourceStream = sourceStream; - JZBitStream = new javazoom.jl.decoder.Bitstream(new javazoom.jl.decoder.BackStream(SourceStream, chunkSize)); - QueueOBuffer = new QueueOBuffer(); - - JZDecoder.OutputBuffer = QueueOBuffer; - - } - - public int ChunkSize { get { return BackStreamByteCountRep; } } - private int BackStreamByteCountRep; - - /// - /// Used to interface with javaZoom. - /// - private javazoom.jl.decoder.Decoder JZDecoder = new javazoom.jl.decoder.Decoder(javazoom.jl.decoder.Decoder.DefaultParams); - /// - /// Used to interface with javaZoom. - /// - private javazoom.jl.decoder.Bitstream JZBitStream; - - - private Stream SourceStream; - - public override bool CanRead { get { return SourceStream.CanRead; } } - public override bool CanSeek { get { return SourceStream.CanSeek; } } - public override bool CanWrite { get { return SourceStream.CanWrite; } } - public override long Length { get { return SourceStream.Length; } } - - public override void Flush() { SourceStream.Flush(); } - - /// - /// Gets or sets the position of the source stream. This is relative to the number of bytes in the MP3 file, rather than - /// the Mp3Stream's output. - /// - public override long Position - { - get { return SourceStream.Position; } - set { SourceStream.Position = value; } - } - /// - /// Sets the position of the source stream. - /// - public override long Seek(long pos, SeekOrigin origin) - { - return SourceStream.Seek(pos, origin); - } - /// - /// This method is not valid for an Mp3Stream. - /// - public override void SetLength(long len) - { - throw new InvalidOperationException(); - } - /// - /// This method is not valid for an Mp3Stream. - /// - public override void Write(byte[] buf, int ofs, int count) - { - throw new InvalidOperationException(); - } - - /// - /// Gets the frequency of the audio being decoded. - /// Initially set to -1. Initialized during the first call to either of the Read and DecodeFrames methods, - /// and updated during every subsequent call to one of those methods to reflect the most recent header information - /// from the MP3 stream. - /// - public int Frequency { get { return FrequencyRep; } } - private int FrequencyRep = -1; - - /// - /// Gets the number of channels available in the audio being decoded. - /// Initially set to -1. Initialized during the first call to either of the Read and DecodeFrames methods, - /// and updated during every subsequent call to one of those methods to reflect the most recent header information - /// from the MP3 stream. - /// - public short ChannelCount { get { return ChannelCountRep; } } - private short ChannelCountRep = -1; - - /// - /// Gets or sets the PCM output format of this stream. - /// - public SoundFormat Format - { - get { return FormatRep; } set { FormatRep = value; } - } - public SoundFormat FormatRep = SoundFormat.Pcm16BitStereo; - - /// - /// Decodes the requested number of frames from the MP3 stream - /// and caches their PCM-encoded bytes. These can subsequently be obtained using the Read method. - /// Returns the number of frames that were successfully decoded. - /// - public int DecodeFrames(int frameCount) - { - int framesDecoded = 0; - bool aFrameWasRead = true; - while (framesDecoded < frameCount && aFrameWasRead) - { - aFrameWasRead = ReadFrame(); - if (aFrameWasRead) framesDecoded++; - } - return framesDecoded; - } - - /// - /// Reads the MP3 stream as PCM-encoded bytes. Decodes a portion of the stream if necessary. - /// - public override int Read(byte[] buffer, int offset, int count) - { - bool aFrameWasRead = true; - while (QueueOBuffer.QueuedByteCount < count && aFrameWasRead) - { - aFrameWasRead = ReadFrame(); - } - int bytesToReturn = Math.Min(QueueOBuffer.QueuedByteCount, count); - int bytesRead = 0; - switch(Format) - { - case SoundFormat.Pcm16BitMono: - bytesRead = QueueOBuffer.DequeueAs16BitPcmMono(buffer, offset, bytesToReturn); - break; - case SoundFormat.Pcm16BitStereo: - bytesRead = QueueOBuffer.DequeueAs16BitPcmStereo(buffer, offset, bytesToReturn); - break; - default: - throw new ApplicationException("Unknown sound format in Mp3Stream Read call: " + Format); - } - return bytesRead; - } - /// - /// Reads a single byte of the PCM-encoded stream. - /// - public override int ReadByte() - { - byte[] ret = new byte[1]; - int result = Read(ret,0,1); - if (result == 0) return -1; else return ret[0]; - } - - /// - /// Closes the source stream and releases any associated resources. - /// - public override void Close() - { - SourceStream.Close(); - } - - private QueueOBuffer QueueOBuffer; - - /// - /// Reads a frame from the MP3 stream. Returns whether the operation was successful. If it wasn't, - /// the source stream is probably at its end. - /// - private bool ReadFrame() - { - // Read a frame from the bitstream. - javazoom.jl.decoder.Header header = JZBitStream.readFrame(); - if (header == null) return false; - - // Set the channel count and frequency values for the stream. - ChannelCountRep = (header.mode() == javazoom.jl.decoder.Header.SINGLE_CHANNEL)?(short)1:(short)2; - FrequencyRep = header.frequency(); - - // Decode the frame. - javazoom.jl.decoder.Obuffer decoderOutput = JZDecoder.decodeFrame(header, JZBitStream); - - // Apparently, the way JavaZoom sets the output buffer - // on the decoder is a bit dodgy. Even though - // this exception should never happen, we test to be sure. - if (decoderOutput != QueueOBuffer) - throw new System.ApplicationException("Output buffers are different."); - - // And we're done. - JZBitStream.closeFrame(); - return true; - } - - } - - /// - /// Describes sound formats that can be produced by the Mp3Stream class. - /// - public enum SoundFormat - { - /// - /// PCM encoded, 16-bit Mono sound format. - /// - Pcm16BitMono, - /// - /// PCM encoded, 16-bit Stereo sound format. - /// - Pcm16BitStereo, - } - - /// - /// Internal class used to queue samples that are being obtained from an Mp3 stream. - /// - internal class QueueOBuffer :javazoom.jl.decoder.Obuffer - { - private static int MaxChannels = 2; - public QueueOBuffer() - { - ChannelQueue = new Queue[MaxChannels]; - for (int i = 0; i < ChannelQueue.Length;i++) ChannelQueue[i] = new Queue(); - } - - /// - /// TODO in C# 2.0: Convert this to Generic Queues of shorts. - /// - private Queue[] ChannelQueue = new Queue[0]; - - public Queue GetChannelQueue(int channelNumber) - { - return ChannelQueue[channelNumber]; - } - - /// - /// Gets the total number of bytes queued in the buffer. - /// - public int QueuedByteCount - { - get - { - int total = 0; - for (int i = 0; i < ChannelQueue.Length; i++) total += 2*ChannelQueue[i].Count; - return total; - } - } - - /// - /// Dequeues bytes out of the buffer in 16-it stereo PCM format (16-bit values with alternating channels) - /// - public int DequeueAs16BitPcmStereo(byte[] buffer, int offset, int count) - { - System.ComponentModel.ByteConverter bc = new System.ComponentModel.ByteConverter(); - if (count %2 == 1) count--; - int firstOffset = offset; - int lastOffset = count + offset; - int channelNumber = -1; - while (offset < lastOffset) - { - channelNumber++; channelNumber %= ChannelQueue.Length; - short sample = (short)ChannelQueue[channelNumber].Dequeue(); - byte[] bytes = BitConverter.GetBytes(sample); - buffer[offset+0] = bytes[0]; - buffer[offset+1] = bytes[1]; - offset += 2; - } - return offset - firstOffset; - } - - /// - /// Dequeues bytes out of the buffer in PCM format (16-bit values with alternating channels) - /// - public int DequeueAs16BitPcmMono(byte[] buffer, int offset, int count) - { - throw new ApplicationException("MP3Sharp Mono output not implemented."); - /// TODO - return 0; - } - - - public override void append(int channel, short value) - { - ChannelQueue[channel].Enqueue(value); - } - - /// - /// This implementation does not clear the buffer. - /// - public override void clear_buffer() { } - public override void set_stop_flag() { } - public override void write_buffer(int val) { } - public override void close() {} - - } - -} diff --git a/Other/libs/mp3sharp/mp3sharp/Sample.cs b/Other/libs/mp3sharp/mp3sharp/Sample.cs deleted file mode 100644 index d6ecd527a..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Sample.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace Mp3Sharp -{ - /// - /// Some samples that show the use of the Mp3Stream class. - /// - internal class Sample - { - public static readonly string Mp3FilePath = @"c:\sample.mp3"; - - /// - /// Sample showing how to read through an MP3 file and obtain its contents as a PCM byte stream. - /// - public static void ReadAllTheWayThroughMp3File() - { - Mp3Stream stream = new Mp3Stream(Mp3FilePath); - - // Create the buffer - int numberOfPcmBytesToReadPerChunk = 512; - byte[] buffer = new byte[numberOfPcmBytesToReadPerChunk]; - - int bytesReturned = -1; - int totalBytes = 0; - while (bytesReturned != 0) - { - bytesReturned = stream.Read(buffer, 0, buffer.Length); - totalBytes += bytesReturned; - } - Console.WriteLine("Read a total of " + totalBytes + " bytes."); - } - - } -} diff --git a/Other/libs/mp3sharp/mp3sharp/Support/Erik.xml b/Other/libs/mp3sharp/mp3sharp/Support/Erik.xml deleted file mode 100644 index 6850a0c37..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Support/Erik.xml +++ /dev/null @@ -1,471 +0,0 @@ - - - - - - - - - - - -
- Format of parameters of method 'java.lang.Class.forName' are different in the equivalent in .NET. - Method 'java.lang.ClassLoader.loadClass' was not converted. -
-
- Class 'java.lang.ClassLoader' was not converted. -
- - -
- Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. - Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. -
-
-
- - -
- Narrowing conversions may produce unexpected results in C#. -
-
-
- - -
- Class 'java.lang.ClassLoader' was not converted. - Method 'java.lang.Class.getClassLoader' was not converted. -
-
-
- - -
- Class 'java.net.URL' was converted to a 'System.Uri' which does not throw an exception if a URL specifies an unknown protocol. -
-
-
- - - - -
- Method 'java.applet.Applet.getParameter' was not converted. - Applet parameter was not converted because it requires a string literal as parameter name. -
-
- Class 'java.net.URL' was converted to a 'System.Uri' which does not throw an exception if a URL specifies an unknown protocol. - Method 'java.applet.Applet.getDocumentBase' was not converted. -
-
- The equivalent of method 'java.applet.Applet.init' is not an override method. - The equivalent of method 'java.applet.Applet.start' is not an override method. - The equivalent of method 'java.applet.Applet.stop' is not an override method. - This function is not marked as virtual in the base class. - The equivalent of method 'java.lang.Runnable.run' is not an override method. -
- - - - - - - - The equivalent of method 'java.lang.Object.clone' is not an override method. - - - - - Method 'setFrom' was converted to a set modifier. This name conflicts with another property. - - - - - - - - The equivalent of method 'java.lang.Throwable.printStackTrace' is not an override method. - - - - -
- Method 'java.io.ObjectInputStream.readObject' was converted to 'SupportClass.Deserialize' which may throw an exception. - The equivalent in .NET for method 'java.lang.Throwable.toString' may return a different value. -
-
- Method 'java.lang.Class.getResourceAsStream' was not converted. -
-
-
- - - - - -
- Narrowing conversions may produce unexpected results in C#. -
-
-
- - - -
- Narrowing conversions may produce unexpected results in C#. -
-
-
- - - - - -
- Method 'java.io.FilterInputStream.close' was converted to 'System.IO.BinaryReader.Close' which has a different behavior. -
-
-
- - -
- Method 'java.io.InputStream.mark' was not converted. - Method 'java.io.InputStream.reset' was not converted. - Method 'java.io.InputStream.markSupported' was not converted. -
-
- -
- 'java.lang.System.out' was converted to 'System.Console.Out' which is not valid in this expression. -
-
- The equivalent in .NET for method 'java.Object.toString' may return a different value. - The equivalent in .NET for method 'java.Object.toString' may return a different value. -
-
- The equivalent in .NET for method 'java.Object.toString' may return a different value. - The equivalent in .NET for method 'java.Object.toString' may return a different value. -
-
- The equivalent in .NET for method 'java.Object.toString' may return a different value. -
-
-
- - -
- 'java.lang.System.out' was converted to 'System.Console.Out' which is not valid in this expression. -
-
-
- - - - The startup class doesn't exist in the project -
- diff --git a/Other/libs/mp3sharp/mp3sharp/Support/SupportClass.cs b/Other/libs/mp3sharp/mp3sharp/Support/SupportClass.cs deleted file mode 100644 index 785ece26f..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Support/SupportClass.cs +++ /dev/null @@ -1,500 +0,0 @@ -using System; - -namespace Support -{ - - internal interface IThreadRunnable - { - void Run(); - } - - internal class SupportClass - { - /// - /// Creates an instance of a received Type - /// - /// The Type of the new class instance to return - /// An Object containing the new instance - public static System.Object CreateNewInstance(System.Type classType) - { - System.Reflection.ConstructorInfo[] constructors = classType.GetConstructors(); - - if (constructors.Length == 0) - return null; - - System.Reflection.ParameterInfo[] firstConstructor = constructors[0].GetParameters(); - int countParams = firstConstructor.Length; - - System.Type[] constructor = new System.Type[countParams]; - for( int i = 0; i < countParams; i++) - constructor[i] = firstConstructor[i].ParameterType; - - return classType.GetConstructor(constructor).Invoke(new System.Object[]{}); - } - - /*******************************/ - public static System.Object PutElement(System.Collections.Hashtable hashTable, System.Object key, System.Object newValue) - { - System.Object element = hashTable[key]; - hashTable[key] = newValue; - return element; - } - - /*******************************/ - /// - /// Removes the element with the specified key from a Hashtable instance. - /// - /// The Hashtable instance - /// The key of the element to remove - /// The element removed - public static System.Object HashtableRemove(System.Collections.Hashtable hashtable, System.Object key) - { - System.Object element = hashtable[key]; - hashtable.Remove(key); - return element; - } - - /*******************************/ - public static int URShift(int number, int bits) - { - if ( number >= 0) - return number >> bits; - else - return (number >> bits) + (2 << ~bits); - } - - public static int URShift(int number, long bits) - { - return URShift(number, (int)bits); - } - - public static long URShift(long number, int bits) - { - if ( number >= 0) - return number >> bits; - else - return (number >> bits) + (2L << ~bits); - } - - public static long URShift(long number, long bits) - { - return URShift(number, (int)bits); - } - - /*******************************/ - public static void WriteStackTrace(System.Exception throwable, System.IO.TextWriter stream) - { - stream.Write(throwable.StackTrace); - stream.Flush(); - } - - /*******************************/ - internal class ThreadClass:IThreadRunnable - { - private System.Threading.Thread threadField; - - public ThreadClass() - { - threadField = new System.Threading.Thread(new System.Threading.ThreadStart(Run)); - } - - public ThreadClass(System.Threading.ThreadStart p1) - { - threadField = new System.Threading.Thread(p1); - } - - public virtual void Run() - { - } - - public virtual void Start() - { - threadField.Start(); - } - - public System.Threading.Thread Instance - { - get - { - return threadField; - } - set - { - threadField = value; - } - } - - public System.String Name - { - get - { - return threadField.Name; - } - set - { - if (threadField.Name == null) - threadField.Name = value; - } - } - - public System.Threading.ThreadPriority Priority - { - get - { - return threadField.Priority; - } - set - { - threadField.Priority = value; - } - } - - public bool IsAlive - { - get - { - return threadField.IsAlive; - } - } - - public bool IsBackground - { - get - { - return threadField.IsBackground; - } - set - { - threadField.IsBackground = value; - } - } - - public void Join() - { - threadField.Join(); - } - - public void Join(long p1) - { - lock(this) - { - threadField.Join(new System.TimeSpan(p1 * 10000)); - } - } - - public void Join(long p1, int p2) - { - lock(this) - { - threadField.Join(new System.TimeSpan(p1 * 10000 + p2 * 100)); - } - } - - public void Resume() - { - threadField.Resume(); - } - - public void Abort() - { - threadField.Abort(); - } - - public void Abort(System.Object stateInfo) - { - lock(this) - { - threadField.Abort(stateInfo); - } - } - - public void Suspend() - { - threadField.Suspend(); - } - - public override System.String ToString() - { - return "Thread[" + Name + "," + Priority.ToString() + "," + "" + "]"; - } - - public static ThreadClass Current() - { - ThreadClass CurrentThread = new ThreadClass(); - CurrentThread.Instance = System.Threading.Thread.CurrentThread; - return CurrentThread; - } - } - - /*******************************/ - /// - /// This method is used as a dummy method to simulate VJ++ behavior - /// - /// The literal to return - /// The received value - public static long Identity(long literal) - { - return literal; - } - - /// - /// This method is used as a dummy method to simulate VJ++ behavior - /// - /// The literal to return - /// The received value - public static ulong Identity(ulong literal) - { - return literal; - } - - /// - /// This method is used as a dummy method to simulate VJ++ behavior - /// - /// The literal to return - /// The received value - public static float Identity(float literal) - { - return literal; - } - - /// - /// This method is used as a dummy method to simulate VJ++ behavior - /// - /// The literal to return - /// The received value - public static double Identity(double literal) - { - return literal; - } - - /*******************************/ - /// Reads a number of characters from the current source Stream and writes the data to the target array at the specified index. - /// The source Stream to read from - /// Contains the array of characteres read from the source Stream. - /// The starting index of the target array. - /// The maximum number of characters to read from the source Stream. - /// The number of characters read. The number will be less than or equal to count depending on the data available in the source Stream. - public static System.Int32 ReadInput(System.IO.Stream sourceStream, ref sbyte[] target, int start, int count) - { - byte[] receiver = new byte[target.Length]; - int bytesRead = sourceStream.Read(receiver, start, count); - - for(int i = start; i < start + bytesRead; i++) - target[i] = (sbyte)receiver[i]; - - return bytesRead; - } - - /// Reads a number of characters from the current source TextReader and writes the data to the target array at the specified index. - /// The source TextReader to read from - /// Contains the array of characteres read from the source TextReader. - /// The starting index of the target array. - /// The maximum number of characters to read from the source TextReader. - /// The number of characters read. The number will be less than or equal to count depending on the data available in the source TextReader. - public static System.Int32 ReadInput(System.IO.TextReader sourceTextReader, ref sbyte[] target, int start, int count) - { - char[] charArray = new char[target.Length]; - int bytesRead = sourceTextReader.Read(charArray, start, count); - - for(int index=start; index - /// Writes an object to the specified Stream - /// - /// The target Stream - /// The object to be sent - public static void Serialize(System.IO.Stream stream, System.Object objectToSend) - { - System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); - formatter.Serialize(stream, objectToSend); - } - - /// - /// Writes an object to the specified BinaryWriter - /// - /// The target BinaryWriter - /// The object to be sent - public static void Serialize(System.IO.BinaryWriter binaryWriter, System.Object objectToSend) - { - System.Runtime.Serialization.Formatters.Binary.BinaryFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(); - formatter.Serialize(binaryWriter.BaseStream, objectToSend); - } - - /*******************************/ - - - internal class BackInputStream : System.IO.BinaryReader - { - protected byte[] buffer; - protected int position = 1; - - public BackInputStream(System.IO.Stream streamReader, System.Int32 size) : base(streamReader) - { - this.buffer = new byte[size]; - //this.position = size; - this.position = 0; // why would you not do that? - } - - public BackInputStream(System.IO.Stream streamReader) : base(streamReader) - { - this.buffer = new byte[position]; - } - - public bool MarkSupported() - { - return false; - } - - public override int Read() - { - if (position >= 0 && position < buffer.Length) - return (int)this.buffer[position++]; - return base.Read(); - } - - public override int Read(byte[] array, int index, int count) - { - int byteCount = 0; - int readLimit = count + index; - - for(byteCount = 0;index <= buffer.Length && index < readLimit;byteCount++) - array[index++] = buffer[position++]; - - - if (index < readLimit) - byteCount += base.Read(array,index, readLimit - index); - - return byteCount; - } - - public void UnRead(int i) - { - this.position--; - this.buffer[position] = (byte)i; - } - - public void UnRead(byte[] array, int index, int count) - { - this.Move(array,index,count); - } - - public void UnRead(byte[] array) - { - this.Move(array, 0,array.Length-1); - } - - public void Move(byte[] array, int index, int count) - { - for(int arrayPosition= index + count; arrayPosition >= index; arrayPosition--) - this.UnRead(array[ arrayPosition]); - } - } - - /*******************************/ - /// - /// Converts an array of sbytes to an array of bytes - /// - /// The array of sbytes to be converted - /// The new array of bytes - public static byte[] ToByteArray(sbyte[] sbyteArray) - { - byte[] byteArray = new byte[sbyteArray.Length]; - for(int index=0; index < sbyteArray.Length; index++) - byteArray[index] = (byte) sbyteArray[index]; - return byteArray; - } - - /// - /// Converts a string to an array of bytes - /// - /// The string to be converted - /// The new array of bytes - public static byte[] ToByteArray(string sourceString) - { - byte[] byteArray = new byte[sourceString.Length]; - for (int index=0; index < sourceString.Length; index++) - byteArray[index] = (byte) sourceString[index]; - return byteArray; - } - - /*******************************/ - internal class RandomAccessFileSupport - { - public static System.IO.FileStream CreateRandomAccessFile(string fileName, string mode) - { - System.IO.FileStream newFile = null; - - if (mode.CompareTo("rw") == 0) - newFile = new System.IO.FileStream(fileName, System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.ReadWrite); - else if (mode.CompareTo("r") == 0 ) - newFile = new System.IO.FileStream(fileName, System.IO.FileMode.Open, System.IO.FileAccess.Read); - else - throw new System.ArgumentException(); - - return newFile; - } - - public static System.IO.FileStream CreateRandomAccessFile(System.IO.FileInfo fileName, string mode) - { - return CreateRandomAccessFile(fileName.FullName, mode); - } - - public static void WriteBytes(string data,System.IO.FileStream fileStream) - { - int index = 0; - int length = data.Length; - - while(index < length) - fileStream.WriteByte((byte)data[index++]); - } - - public static void WriteChars(string data,System.IO.FileStream fileStream) - { - WriteBytes(data, fileStream); - } - - public static void WriteRandomFile(sbyte[] sByteArray,System.IO.FileStream fileStream) - { - byte[] byteArray = ToByteArray(sByteArray); - fileStream.Write(byteArray, 0, byteArray.Length); - } - } - - /*******************************/ - /// - /// Method that copies an array of sbytes from a String to a received array . - /// - /// The String to get the sbytes. - /// Position in the String to start getting sbytes. - /// Position in the String to end getting sbytes. - /// Array to store the bytes. - /// Position in the destination array to start storing the sbytes. - /// An array of sbytes - public static void GetSBytesFromString(string sourceString, int sourceStart, int sourceEnd, ref sbyte[] destinationArray, int destinationStart) - { - int sourceCounter; - int destinationCounter; - sourceCounter = sourceStart; - destinationCounter = destinationStart; - while (sourceCounter < sourceEnd) - { - destinationArray[destinationCounter] = (sbyte) sourceString[sourceCounter]; - sourceCounter++; - destinationCounter++; - } - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/Support/_ConversionReport.htm b/Other/libs/mp3sharp/mp3sharp/Support/_ConversionReport.htm deleted file mode 100644 index 39b44632c..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Support/_ConversionReport.htm +++ /dev/null @@ -1,648 +0,0 @@ - - - - - Erik Conversion Report - - - - - -

Conversion Report for Erik

- -

- Time of Conversion: 07/03/2003 16:35
-

- -

List of Project Files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
New FilenameOriginal FilenameStatusErrorsWarningsTotal Issues
 (Global Issues)9110
 Converter.csConverter.java - Converted with issues 909
 jlc.csjlc.java - Converted with issues 101
 RiffFile.csRiffFile.java - Converted 000
 WaveFile.csWaveFile.java - Converted 000
 WaveFileObuffer.csWaveFileObuffer.java - Converted 000
 BitReserve.csBitReserve.java - Converted 000
 Bitstream.csBitstream.java - Converted with issues 101
 BitstreamErrors.csBitstreamErrors.java - Converted 000
 BitstreamException.csBitstreamException.java - Converted 000
 Control.csControl.java - Converted 000
 Crc16.csCrc16.java - Converted 000
 Decoder.csDecoder.java - Converted 000
 DecoderErrors.csDecoderErrors.java - Converted 000
 DecoderException.csDecoderException.java - Converted 000
 Equalizer.csEqualizer.java - Converted 000
 FrameDecoder.csFrameDecoder.java - Converted 000
 Header.csHeader.java - Converted 000
 huffcodetab.cshuffcodetab.java - Converted 000
 InputStreamSource.csInputStreamSource.java - Converted 000
 JavaLayerError.csJavaLayerError.java - Converted 000
 JavaLayerErrors.csJavaLayerErrors.java - Converted 000
 Mp3SharpException.csMp3SharpException.java - Converted 000
 JavaLayerHook.csJavaLayerHook.java - Converted 000
 JavaLayerUtils.csJavaLayerUtils.java - Converted with issues 213
 LayerIDecoder.csLayerIDecoder.java - Converted 000
 LayerIIDecoder.csLayerIIDecoder.java - Converted 000
 LayerIIIDecoder.csLayerIIIDecoder.java - Converted 000
 Manager.csManager.java - Converted 000
 Obuffer.csObuffer.java - Converted with issues 011
 OutputChannels.csOutputChannels.java - Converted 000
 SampleBuffer.csSampleBuffer.java - Converted with issues 011
 Source.csSource.java - Converted 000
 SynthesisFilter.csSynthesisFilter.java - Converted 000
 AudioDevice.csAudioDevice.java - Converted 000
 AudioDeviceBase.csAudioDeviceBase.java - Converted 000
 AudioDeviceFactory.csAudioDeviceFactory.java - Converted with issues 202
 FactoryRegistry.csFactoryRegistry.java - Converted with issues 202
 JavaSoundAudioDevice.csJavaSoundAudioDevice.java - Converted with issues 011
 JavaSoundAudioDeviceFactory.csJavaSoundAudioDeviceFactory.java - Converted with issues 202
 jlp.csjlp.java - Converted with issues 101
 NullAudioDevice.csNullAudioDevice.java - Converted 000
 Player.csPlayer.java - Converted 000
 PlayerApplet.csPlayerApplet.java - Converted with issues 404
43 File(s) 33538
-

-

-

Conversion Settings

-

- LogFile: Erik.xml
- OutputDir: C:\Documents and Settings\rob\Desktop\javalayer0.2\src\javazoom\jl\Erik.NET
- ProjectName: Erik
- ProjectPath: C:\Documents and Settings\rob\Desktop\javalayer0.2\src\javazoom\jl -
-

- - diff --git a/Other/libs/mp3sharp/mp3sharp/Support/_ConversionSummary.txt b/Other/libs/mp3sharp/mp3sharp/Support/_ConversionSummary.txt deleted file mode 100644 index 5c2827c9d..000000000 --- a/Other/libs/mp3sharp/mp3sharp/Support/_ConversionSummary.txt +++ /dev/null @@ -1,173 +0,0 @@ -FileCount 43 -Size 3552 -Size 4696 -Size 3027 -Size 3309 -Size 4785 -Size 2562 -Size 4014 -Size 1248 -Size 5572 -Size 5417 -Size 4375 -Size 16300 -Size 1920 -Size 2112 -Size 543 -Size 1941 -Size 8877 -Size 1349 -Size 1746 -Size 4988 -Size 1369 -Size 29955 -Size 1959 -Size 1144 -Size 1456 -Size 1930 -Size 432 -Size 5720 -Size 12880 -Size 91006 -Size 1244 -Size 2823 -Size 3862 -Size 2984 -Size 1372 -Size 54668 -Size 55298 -Size 12994 -Size 11026 -Size 5134 -Size 12072 -Size 13345 -Size 2959 - 138 -java.lang.Class.newInstance 1 -java.lang.ClassNotFoundException 1 -java.lang.StringBuffer.append 13 -java.net.URL 13 -java.util.Hashtable 2 -java.lang.Integer.MAX_VALUE 2 -java.lang.Throwable.toString 1 -java.lang.Class 29 -java.lang.String.startsWith 1 -java.lang.Math 68 -java.io.BufferedInputStream 15 -java.io.OutputStream 3 -java.io.FileInputStream.FileInputStream 2 -java.lang.Cloneable 1 -java.io.PushbackInputStream 3 -java.lang.Throwable.getLocalizedMessage 3 -java.lang.InternalError 2 -java.net.URL.URL 2 -java.util.Enumeration.hasMoreElements 1 -java.lang.NullPointerException 9 -java.lang.NullPointerException.NullPointerException 9 -java.lang.Exception 14 -java.lang.Thread.Thread 1 -java.lang.reflect.Array.getLength 1 -java.io.PushbackInputStream.read 2 -java.lang.ClassLoader 6 -java.lang.Class.getComponentType 1 -java.io.InputStream.markSupported 1 -java.lang.String 554 -java.io.ObjectOutputStream.ObjectOutputStream 1 -java.io.InputStream.mark 1 -java.io.InputStream 68 -java.io.PrintStream.println 23 -java.lang.Class.getResourceAsStream 1 -java.io.InvalidClassException.InvalidClassException 1 -java.io.ObjectOutputStream 4 -java.io.RandomAccessFile.readByte 1 -java.io.RandomAccessFile.writeInt 1 -java.io.InputStream.reset 1 -java.net.URL.openStream 2 -java.lang.String.length 2 -java.lang.Throwable 38 -java.lang.Integer 10 -java.io.PrintStream 3 -java.io.RandomAccessFile.write 5 -java.lang.Float.TYPE 1 -java.io.ObjectInputStream.ObjectInputStream 1 -java.lang.Thread.start 1 -java.lang.System.exit 4 -java.lang.Class.isInstance 1 -java.lang.Throwable.printStackTrace 4 -java.applet.Applet.getDocumentBase 1 -java.io.RandomAccessFile.RandomAccessFile 2 -java.io.InvalidClassException 1 -java.lang.Class.forName 1 -java.io.InvalidObjectException 4 -java.lang.~array.length 19 -java.io.FilterInputStream.close 1 -java.lang.String.equals 3 -java.io.RandomAccessFile.seek 4 -java.util.Hashtable.remove 2 -java.lang.Error 1 -java.io.PrintWriter.flush 3 -java.lang.Exception.Exception 2 -java.lang.Object.getClass 4 -java.lang.StringBuffer.StringBuffer 1 -java.util.Enumeration 3 -java.io.File.File 1 -java.lang.StringBuffer.toString 1 -java.lang.StringBuffer 4 -java.io.PushbackInputStream.PushbackInputStream 1 -java.lang.RuntimeException 4 -java.lang.System.err 12 -java.util.Hashtable.elements 1 -java.util.Enumeration.nextElement 1 -java.lang.System.out 17 -java.lang.ExceptionInInitializerError.ExceptionInInitializerError 1 -java.io.BufferedInputStream.BufferedInputStream 4 -java.io.FileInputStream 6 -java.lang.Integer.toHexString 2 -java.lang.String.charAt 5 -java.io.InputStream.close 1 -java.lang.Float 4 -java.lang.Class.getClassLoader 1 -java.io.PrintWriter.print 2 -java.io.File 4 -java.io.InputStream.read 1 -java.applet.Applet.getParameter 1 -java.lang.String.getBytes 1 -java.lang.Math.cos 31 -java.io.PrintWriter 6 -java.lang.Object.clone 1 -java.io.IOException 35 -java.lang.Integer.parseInt 1 -java.lang.IllegalArgumentException 4 -java.lang.IllegalArgumentException.IllegalArgumentException 4 -java.lang.NumberFormatException 1 -java.applet.Applet 1 -java.lang.CloneNotSupportedException 1 -java.io.PushbackInputStream.unread 2 -java.lang.System 58 -java.lang.ClassLoader.loadClass 1 -java.io.ObjectInputStream 4 -java.io.ObjectOutputStream.writeObject 1 -java.io.PrintWriter.PrintWriter 2 -java.lang.Math.pow 3 -java.lang.ExceptionInInitializerError 1 -java.io.ObjectInputStream.readObject 1 -java.util.Hashtable.put 1 -java.io.RandomAccessFile.writeShort 1 -java.util.Hashtable.Hashtable 1 -java.io.InvalidObjectException.InvalidObjectException 4 -java.lang.Object 25 -java.lang.InternalError.InternalError 2 -java.lang.System.currentTimeMillis 3 -java.io.IOException.IOException 1 -java.lang.LinkageError 4 -java.lang.Thread 6 -java.io.RandomAccessFile.close 4 -java.io.RandomAccessFile 7 -java.lang.reflect.Array 2 -java.io.RandomAccessFile.getFilePointer 1 -java.lang.Float.NEGATIVE_INFINITY 1 -java.util.Hashtable.size 1 -java.lang.Class.isArray 1 -java.io.PrintWriter.println 8 -java.io.RandomAccessFile.read 2 -java.lang.String.substring 1 \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport.css b/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport.css deleted file mode 100644 index 07d7dbf08..000000000 --- a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport.css +++ /dev/null @@ -1,208 +0,0 @@ -BODY -{ - BACKGROUND-COLOR: white; - FONT-FAMILY: "Verdana", sans-serif; - FONT-SIZE: 100%; - MARGIN-LEFT: 0px; - MARGIN-TOP: 0px -} -P -{ - FONT-FAMILY: "Verdana", sans-serif; - FONT-SIZE: 70%; - LINE-HEIGHT: 12pt; - MARGIN-BOTTOM: 0px; - MARGIN-LEFT: 10px; - MARGIN-TOP: 10px -} -.note -{ - BACKGROUND-COLOR: #ffffff; - COLOR: #336699; - FONT-FAMILY: "Verdana", sans-serif; - FONT-SIZE: 100%; - LINE-HEIGHT: 12pt; - MARGIN-BOTTOM: 0px; - MARGIN-LEFT: 0px; - MARGIN-TOP: 0px; - PADDING-RIGHT: 10px -} -.infotable -{ - BACKGROUND-COLOR: #f0f0e0; - BORDER-BOTTOM: #ffffff 0px solid; - BORDER-COLLAPSE: collapse; - BORDER-LEFT: #ffffff 0px solid; - BORDER-RIGHT: #ffffff 0px solid; - BORDER-TOP: #ffffff 0px solid; - FONT-SIZE: 70%; - MARGIN-LEFT: 10px -} -.issuetable -{ - BACKGROUND-COLOR: #ffffe8; - BORDER-COLLAPSE: collapse; - COLOR: #000000; - FONT-SIZE: 100%; - MARGIN-BOTTOM: 10px; - MARGIN-LEFT: 13px; - MARGIN-TOP: 0px -} -.issuetitle -{ - BACKGROUND-COLOR: #ffffff; - BORDER-BOTTOM: #dcdcdc 1px solid; - BORDER-TOP: #dcdcdc 1px; - COLOR: #003366; - FONT-WEIGHT: normal -} -.header -{ - BACKGROUND-COLOR: #cecf9c; - BORDER-BOTTOM: #ffffff 1px solid; - BORDER-LEFT: #ffffff 1px solid; - BORDER-RIGHT: #ffffff 1px solid; - BORDER-TOP: #ffffff 1px solid; - COLOR: #000000; - FONT-WEIGHT: bold -} -.issuehdr -{ - BACKGROUND-COLOR: #E0EBF5; - BORDER-BOTTOM: #dcdcdc 1px solid; - BORDER-TOP: #dcdcdc 1px solid; - COLOR: #000000; - FONT-WEIGHT: normal -} -.issuenone -{ - BACKGROUND-COLOR: #ffffff; - BORDER-BOTTOM: 0px; - BORDER-LEFT: 0px; - BORDER-RIGHT: 0px; - BORDER-TOP: 0px; - COLOR: #000000; - FONT-WEIGHT: normal -} -.content -{ - BACKGROUND-COLOR: #e7e7ce; - BORDER-BOTTOM: #ffffff 1px solid; - BORDER-LEFT: #ffffff 1px solid; - BORDER-RIGHT: #ffffff 1px solid; - BORDER-TOP: #ffffff 1px solid; - PADDING-LEFT: 3px -} -.issuecontent -{ - BACKGROUND-COLOR: #ffffff; - BORDER-BOTTOM: #dcdcdc 1px solid; - BORDER-TOP: #dcdcdc 1px solid; - PADDING-LEFT: 3px -} -A:link -{ - COLOR: #cc6633; - TEXT-DECORATION: underline -} -A:visited -{ - COLOR: #cc6633; -} -A:active -{ - COLOR: #cc6633; -} -A:hover -{ - COLOR: #cc3300; - TEXT-DECORATION: underline -} -H1 -{ - BACKGROUND-COLOR: #003366; - BORDER-BOTTOM: #336699 6px solid; - COLOR: #ffffff; - FONT-SIZE: 130%; - FONT-WEIGHT: normal; - MARGIN: 0em 0em 0em -20px; - PADDING-BOTTOM: 8px; - PADDING-LEFT: 30px; - PADDING-TOP: 16px -} -H2 -{ - COLOR: #000000; - FONT-SIZE: 80%; - FONT-WEIGHT: bold; - MARGIN-BOTTOM: 3px; - MARGIN-LEFT: 10px; - MARGIN-TOP: 20px; - PADDING-LEFT: 0px -} -H3 -{ - COLOR: #000000; - FONT-SIZE: 80%; - FONT-WEIGHT: bold; - MARGIN-BOTTOM: -5px; - MARGIN-LEFT: 10px; - MARGIN-TOP: 20px -} -H4 -{ - COLOR: #000000; - FONT-SIZE: 70%; - FONT-WEIGHT: bold; - MARGIN-BOTTOM: 0px; - MARGIN-TOP: 15px; - PADDING-BOTTOM: 0px -} -UL -{ - COLOR: #000000; - FONT-SIZE: 70%; - LIST-STYLE: square; - MARGIN-BOTTOM: 0pt; - MARGIN-TOP: 0pt -} -OL -{ - COLOR: #000000; - FONT-SIZE: 70%; - LIST-STYLE: square; - MARGIN-BOTTOM: 0pt; - MARGIN-TOP: 0pt -} -LI -{ - LIST-STYLE: square; - MARGIN-LEFT: 0px -} -.expandable -{ - CURSOR: hand -} -.expanded -{ - color: black -} -.collapsed -{ - DISPLAY: none -} -.foot -{ -BACKGROUND-COLOR: #ffffff; -BORDER-BOTTOM: #cecf9c 1px solid; -BORDER-TOP: #cecf9c 2px solid -} -.settings -{ -MARGIN-LEFT: 25PX; -} -.help -{ -TEXT-ALIGN: right; -margin-right: 10px; -} diff --git a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Minus.gif b/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Minus.gif deleted file mode 100644 index 17751cb2f..000000000 Binary files a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Minus.gif and /dev/null differ diff --git a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Plus.gif b/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Plus.gif deleted file mode 100644 index f6009ca3f..000000000 Binary files a/Other/libs/mp3sharp/mp3sharp/_ConversionReport_Files/ConversionReport_Plus.gif and /dev/null differ diff --git a/Other/libs/mp3sharp/mp3sharp/converter/Converter.cs b/Other/libs/mp3sharp/mp3sharp/converter/Converter.cs deleted file mode 100644 index f7aad15fa..000000000 --- a/Other/libs/mp3sharp/mp3sharp/converter/Converter.cs +++ /dev/null @@ -1,422 +0,0 @@ -/* -* 12/12/99 Original verion. mdm@techie.com. -*/ -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.converter -{ - using System; - using javazoom.jl.decoder; - /// The Converter class implements the conversion of - /// an MPEG audio file to a .WAV file. To convert an MPEG audio stream, - /// just create an instance of this class and call the {@link convert() convert()} - /// method, passing in the names of the input and output files. You can - /// pass in optional ProgressListener and - /// Decoder.Params objects also to customize the conversion. - /// * - /// - /// MDM 12/12/99 - /// @since 0.0.7 - /// * - /// - /// - public class Converter - { - - /// Creates a new converter instance. - /// - public Converter() - { - } - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'convert'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - public virtual void convert(System.String sourceName, System.String destName) - { - lock (this) - { - convert(sourceName, destName, null, null); - } - } - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'convert'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - public virtual void convert(System.String sourceName, System.String destName, ProgressListener progressListener) - { - lock (this) - { - convert(sourceName, destName, progressListener, null); - } - } - - - public virtual void convert(System.String sourceName, System.String destName, ProgressListener progressListener, Decoder.Params decoderParams) - { - if (destName.Length == 0) - destName = null; - try - { - System.IO.Stream in_Renamed = openInput(sourceName); - convert(in_Renamed, destName, progressListener, decoderParams); - in_Renamed.Close(); - } - catch (System.IO.IOException ioe) - { - throw new JavaLayerException(ioe.Message, ioe); - } - } - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'convert'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - public virtual void convert(System.IO.Stream sourceStream, System.String destName, ProgressListener progressListener, Decoder.Params decoderParams) - { - lock (this) - { - if (progressListener == null) - progressListener = PrintWriterProgressListener.newStdOut(PrintWriterProgressListener.NO_DETAIL); - try - { - if (!(sourceStream is System.IO.BufferedStream)) - sourceStream = new System.IO.BufferedStream(sourceStream); - int frameCount = - 1; - //UPGRADE_ISSUE: Method 'java.io.InputStream.markSupported' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javaioInputStreammarkSupported"' - if (sourceStream.markSupported()) - { - //UPGRADE_ISSUE: Method 'java.io.InputStream.mark' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javaioInputStreammark_int"' - sourceStream.mark(- 1); - frameCount = countFrames(sourceStream); - //UPGRADE_ISSUE: Method 'java.io.InputStream.reset' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javaioInputStreamreset"' - sourceStream.reset(); - } - progressListener.converterUpdate(javazoom.jl.converter.Converter.ProgressListener_Fields.UPDATE_FRAME_COUNT, frameCount, 0); - - - Obuffer output = null; - Decoder decoder = new Decoder(decoderParams); - Bitstream stream = new Bitstream(sourceStream); - - if (frameCount == - 1) - frameCount = System.Int32.MaxValue; - - int frame = 0; - long startTime = (System.DateTime.Now.Ticks - 621355968000000000) / 10000; - - try - { - for (; frame < frameCount; frame++) - { - try - { - Header header = stream.readFrame(); - if (header == null) - break; - - progressListener.readFrame(frame, header); - - if (output == null) - { - // REVIEW: Incorrect functionality. - // the decoder should provide decoded - // frequency and channels output as it may differ from - // the source (e.g. when downmixing stereo to mono.) - int channels = (header.mode() == Header.SINGLE_CHANNEL)?1:2; - int freq = header.frequency(); - output = new WaveFileObuffer(channels, freq, destName); - decoder.OutputBuffer = output; - } - - Obuffer decoderOutput = decoder.decodeFrame(header, stream); - - // REVIEW: the way the output buffer is set - // on the decoder is a bit dodgy. Even though - // this exception should never happen, we test to be sure. - if (decoderOutput != output) - throw new System.ApplicationException("Output buffers are different."); - - - progressListener.decodedFrame(frame, header, output); - - stream.closeFrame(); - } - catch (System.Exception ex) - { - bool stop = !progressListener.converterException(ex); - - if (stop) - { - throw new JavaLayerException(ex.Message, ex); - } - } - } - } - finally - { - - if (output != null) - output.close(); - } - - int time = (int) ((System.DateTime.Now.Ticks - 621355968000000000) / 10000 - startTime); - progressListener.converterUpdate(javazoom.jl.converter.Converter.ProgressListener_Fields.UPDATE_CONVERT_COMPLETE, time, frame); - } - catch (System.IO.IOException ex) - { - throw new JavaLayerException(ex.Message, ex); - } - } - } - - - protected internal virtual int countFrames(System.IO.Stream in_Renamed) - { - return - 1; - } - - - protected internal virtual System.IO.Stream openInput(System.String fileName) - { - // ensure name is abstract path name - System.IO.FileInfo file = new System.IO.FileInfo(fileName); - System.IO.Stream fileIn = new System.IO.FileStream(file.FullName, System.IO.FileMode.Open, System.IO.FileAccess.Read); - System.IO.BufferedStream bufIn = new System.IO.BufferedStream(fileIn); - - return bufIn; - } - - - /// This interface is used by the Converter to provide - /// notification of tasks being carried out by the converter, - /// and to provide new information as it becomes available. - /// - public enum ProgressListener_FieldsEnum - { - UPDATE_FRAME_COUNT = 1, - UPDATE_CONVERT_COMPLETE = 2 - } - - - public struct ProgressListener_Fields - { - public readonly static int UPDATE_FRAME_COUNT = 1; - public readonly static int UPDATE_CONVERT_COMPLETE = 2; - } - public interface ProgressListener - { - //UPGRADE_NOTE: Members of interface 'ProgressListener' were extracted into structure 'ProgressListener_Fields'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1045"' - /// Conversion is complete. Param1 contains the time - /// to convert in milliseconds. Param2 contains the number - /// of MPEG audio frames converted. - /// - /// Notifies the listener that new information is available. - /// * - /// - /// indicating the information that has been - /// updated. - /// * - /// - /// whose value depends upon the update code. - /// - /// whose value depends upon the update code. - /// * - /// The updateID parameter can take these values: - /// * - /// UPDATE_FRAME_COUNT: param1 is the frame count, or -1 if not known. - /// UPDATE_CONVERT_COMPLETE: param1 is the conversion time, param2 - /// is the number of frames converted. - /// - /// - void converterUpdate(int updateID, int param1, int param2); - /// If the converter wishes to make a first pass over the - /// audio frames, this is called as each frame is parsed. - /// - void parsedFrame(int frameNo, Header header); - /// This method is called after each frame has been read, - /// but before it has been decoded. - /// * - /// - /// 0-based sequence number of the frame. - /// - /// Header rerpesenting the frame just read. - /// - /// - void readFrame(int frameNo, Header header); - /// This method is called after a frame has been decoded. - /// * - /// - /// 0-based sequence number of the frame. - /// - /// Header rerpesenting the frame just read. - /// - /// Obuffer the deocded data was written to. - /// - /// - void decodedFrame(int frameNo, Header header, Obuffer o); - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - /// Called when an exception is thrown during while converting - /// a frame. - /// * - /// - /// Throwable instance that - /// was thrown. - /// * - /// - /// true to continue processing, or false - /// to abort conversion. - /// * - /// If this method returns false, the exception - /// is propagated to the caller of the convert() method. If - /// true is returned, the exception is silently - /// ignored and the converter moves onto the next frame. - /// - /// - bool converterException(System.Exception t); - } - - - /// Implementation of ProgressListener that writes - /// notification text to a PrintWriter. - /// - // REVIEW: i18n of text and order required. - public class PrintWriterProgressListener : ProgressListener - { - public const int NO_DETAIL = 0; - - /// Level of detail typically expected of expert - /// users. - /// - public const int EXPERT_DETAIL = 1; - - /// Verbose detail. - /// - public const int VERBOSE_DETAIL = 2; - - /// Debug detail. All frame read notifications are shown. - /// - public const int DEBUG_DETAIL = 7; - - public const int MAX_DETAIL = 10; - - private System.IO.StreamWriter pw; - - private int detailLevel; - - static public PrintWriterProgressListener newStdOut(int detail) - { - System.IO.StreamWriter temp_writer; - //UPGRADE_ISSUE: 'java.lang.System.out' was converted to 'System.Console.Out' which is not valid in this expression. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1109"' - temp_writer = new System.IO.StreamWriter(System.Console.Out); - temp_writer.AutoFlush = true; - return new PrintWriterProgressListener(temp_writer, detail); - } - - public PrintWriterProgressListener(System.IO.StreamWriter writer, int detailLevel) - { - this.pw = writer; - this.detailLevel = detailLevel; - } - - - public virtual bool isDetail(int detail) - { - return (this.detailLevel >= detail); - } - - public virtual void converterUpdate(int updateID, int param1, int param2) - { - if (isDetail(VERBOSE_DETAIL)) - { - switch (updateID) - { - - case (int)javazoom.jl.converter.Converter.ProgressListener_FieldsEnum.UPDATE_CONVERT_COMPLETE: - if (param2 == 0) - param2 = 1; - - pw.WriteLine(); - pw.WriteLine("Converted " + param2 + " frames in " + param1 + " ms (" + (param1 / param2) + " ms per frame.)"); - break; - } - } - } - - public virtual void parsedFrame(int frameNo, Header header) - { - if ((frameNo == 0) && isDetail(VERBOSE_DETAIL)) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.Object.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - System.String headerString = header.ToString(); - pw.WriteLine("File is a " + headerString); - } - else if (isDetail(MAX_DETAIL)) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.Object.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - System.String headerString = header.ToString(); - pw.WriteLine("Prased frame " + frameNo + ": " + headerString); - } - } - - public virtual void readFrame(int frameNo, Header header) - { - if ((frameNo == 0) && isDetail(VERBOSE_DETAIL)) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.Object.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - System.String headerString = header.ToString(); - pw.WriteLine("File is a " + headerString); - } - else if (isDetail(MAX_DETAIL)) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.Object.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - System.String headerString = header.ToString(); - pw.WriteLine("Read frame " + frameNo + ": " + headerString); - } - } - - public virtual void decodedFrame(int frameNo, Header header, Obuffer o) - { - if (isDetail(MAX_DETAIL)) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.Object.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - System.String headerString = header.ToString(); - pw.WriteLine("Decoded frame " + frameNo + ": " + headerString); - pw.WriteLine("Output: " + o); - } - else if (isDetail(VERBOSE_DETAIL)) - { - if (frameNo == 0) - { - pw.Write("Converting."); - pw.Flush(); - } - - if ((frameNo % 10) == 0) - { - pw.Write('.'); - pw.Flush(); - } - } - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public virtual bool converterException(System.Exception t) - { - if (this.detailLevel > NO_DETAIL) - { - SupportClass.WriteStackTrace(t, pw); - pw.Flush(); - } - return false; - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/converter/RiffFile.cs b/Other/libs/mp3sharp/mp3sharp/converter/RiffFile.cs deleted file mode 100644 index 9406ed248..000000000 --- a/Other/libs/mp3sharp/mp3sharp/converter/RiffFile.cs +++ /dev/null @@ -1,620 +0,0 @@ -using Support; -/* -* 02/23/99 JavaConversion by E.B, JavaLayer -*/ -/*=========================================================================== - -riff.h - Don Cross, April 1993. - -RIFF file format classes. -See Chapter 8 of "Multimedia Programmer's Reference" in -the Microsoft Windows SDK. - -See also: -..\source\riff.cpp -ddc.h - -===========================================================================*/ -namespace javazoom.jl.converter -{ - using System; - /// Class to manage RIFF files - /// - internal class RiffFile - { - //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'RiffChunkHeader' to access its enclosing instance. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1019"' - internal class RiffChunkHeader - { - private void InitBlock(RiffFile enclosingInstance) - { - this.enclosingInstance = enclosingInstance; - } - private RiffFile enclosingInstance; - public RiffFile Enclosing_Instance - { - get - { - return enclosingInstance; - } - - } - public int ckID = 0; // Four-character chunk ID - public int ckSize = 0; - // Length of data in chunk - public RiffChunkHeader(RiffFile enclosingInstance) - { - InitBlock(enclosingInstance); - } - } - - - // DDCRET - public const int DDC_SUCCESS = 0; // The operation succeded - public const int DDC_FAILURE = 1; // The operation failed for unspecified reasons - public const int DDC_OUT_OF_MEMORY = 2; // Operation failed due to running out of memory - public const int DDC_FILE_ERROR = 3; // Operation encountered file I/O error - public const int DDC_INVALID_CALL = 4; // Operation was called with invalid parameters - public const int DDC_USER_ABORT = 5; // Operation was aborted by the user - public const int DDC_INVALID_FILE = 6; // File format does not match - - // RiffFileMode - public const int RFM_UNKNOWN = 0; // undefined type (can use to mean "N/A" or "not open") - public const int RFM_WRITE = 1; // open for write - public const int RFM_READ = 2; // open for read - - private RiffChunkHeader riff_header; // header for whole file - protected internal int fmode; // current file I/O mode - //protected internal System.IO.FileStream file; // I/O stream to use - protected internal System.IO.Stream file; // I/O stream to use - - /// Dummy Constructor - /// - public RiffFile() - { - file = null; - fmode = RFM_UNKNOWN; - riff_header = new RiffChunkHeader(this); - - riff_header.ckID = FourCC("RIFF"); - riff_header.ckSize = 0; - } - - /// Return File Mode. - /// - public virtual int CurrentFileMode() - { - return fmode; - } - - /// Open a RIFF file. - /// - public virtual int Open(System.String Filename, int NewMode) - { - int retcode = DDC_SUCCESS; - - if (fmode != RFM_UNKNOWN) - { - retcode = Close(); - } - - if (retcode == DDC_SUCCESS) - { - switch (NewMode) - { - - case RFM_WRITE: - try - { - file = SupportClass.RandomAccessFileSupport.CreateRandomAccessFile(Filename, "rw"); - - try - { - // Write the RIFF header... - // We will have to come back later and patch it! - sbyte[] br = new sbyte[8]; - br[0] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 24)) & 0x000000FF); - br[1] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 16)) & 0x000000FF); - br[2] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 8)) & 0x000000FF); - br[3] = (sbyte) (riff_header.ckID & 0x000000FF); - - sbyte br4 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 24)) & 0x000000FF); - sbyte br5 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 16)) & 0x000000FF); - sbyte br6 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 8)) & 0x000000FF); - sbyte br7 = (sbyte) (riff_header.ckSize & 0x000000FF); - - br[4] = br7; - br[5] = br6; - br[6] = br5; - br[7] = br4; - - file.Write(SupportClass.ToByteArray(br), 0, 8); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - file.Close(); - fmode = RFM_UNKNOWN; - } - } - catch (System.IO.IOException ioe) - { - fmode = RFM_UNKNOWN; - retcode = DDC_FILE_ERROR; - } - break; - - - case RFM_READ: - try - { - file = SupportClass.RandomAccessFileSupport.CreateRandomAccessFile(Filename, "r"); - try - { - // Try to read the RIFF header... - sbyte[] br = new sbyte[8]; - SupportClass.ReadInput(file, ref br, 0, 8); - fmode = RFM_READ; - riff_header.ckID = ((br[0] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((br[1] << 16) & 0x00FF0000) | ((br[2] << 8) & 0x0000FF00) | (br[3] & 0x000000FF); - riff_header.ckSize = ((br[4] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((br[5] << 16) & 0x00FF0000) | ((br[6] << 8) & 0x0000FF00) | (br[7] & 0x000000FF); - } - catch (System.IO.IOException ioe) - { - file.Close(); - fmode = RFM_UNKNOWN; - } - } - catch (System.IO.IOException ioe) - { - fmode = RFM_UNKNOWN; - retcode = DDC_FILE_ERROR; - } - break; - - default: - retcode = DDC_INVALID_CALL; - break; - - } - } - return retcode; - } - - - /// Open a RIFF STREAM. - /// - public virtual int Open(System.IO.Stream stream, int NewMode) - { - int retcode = DDC_SUCCESS; - - if (fmode != RFM_UNKNOWN) - { - retcode = Close(); - } - - if (retcode == DDC_SUCCESS) - { - switch (NewMode) - { - - case RFM_WRITE: - try - { - //file = SupportClass.RandomAccessFileSupport.CreateRandomAccessFile(Filename, "rw"); - file = stream; - - try - { - // Write the RIFF header... - // We will have to come back later and patch it! - sbyte[] br = new sbyte[8]; - br[0] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 24)) & 0x000000FF); - br[1] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 16)) & 0x000000FF); - br[2] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 8)) & 0x000000FF); - br[3] = (sbyte) (riff_header.ckID & 0x000000FF); - - sbyte br4 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 24)) & 0x000000FF); - sbyte br5 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 16)) & 0x000000FF); - sbyte br6 = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 8)) & 0x000000FF); - sbyte br7 = (sbyte) (riff_header.ckSize & 0x000000FF); - - br[4] = br7; - br[5] = br6; - br[6] = br5; - br[7] = br4; - - file.Write(SupportClass.ToByteArray(br), 0, 8); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - file.Close(); - fmode = RFM_UNKNOWN; - } - } - catch (System.IO.IOException ioe) - { - fmode = RFM_UNKNOWN; - retcode = DDC_FILE_ERROR; - } - break; - - - case RFM_READ: - try - { - file = stream; - //file = SupportClass.RandomAccessFileSupport.CreateRandomAccessFile(Filename, "r"); - try - { - // Try to read the RIFF header... - sbyte[] br = new sbyte[8]; - SupportClass.ReadInput(file, ref br, 0, 8); - fmode = RFM_READ; - riff_header.ckID = ((br[0] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((br[1] << 16) & 0x00FF0000) | ((br[2] << 8) & 0x0000FF00) | (br[3] & 0x000000FF); - riff_header.ckSize = ((br[4] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((br[5] << 16) & 0x00FF0000) | ((br[6] << 8) & 0x0000FF00) | (br[7] & 0x000000FF); - } - catch (System.IO.IOException ioe) - { - file.Close(); - fmode = RFM_UNKNOWN; - } - } - catch (System.IO.IOException ioe) - { - fmode = RFM_UNKNOWN; - retcode = DDC_FILE_ERROR; - } - break; - - default: - retcode = DDC_INVALID_CALL; - break; - - } - } - return retcode; - } - - - - /// Write NumBytes data. - /// - public virtual int Write(sbyte[] Data, int NumBytes) - { - if (fmode != RFM_WRITE) - { - return DDC_INVALID_CALL; - } - try - { - file.Write(SupportClass.ToByteArray(Data), 0, NumBytes); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - riff_header.ckSize += NumBytes; - return DDC_SUCCESS; - } - - - - /// Write NumBytes data. - /// - public virtual int Write(short[] Data, int NumBytes) - { - sbyte[] theData = new sbyte[NumBytes]; - int yc = 0; - for (int y = 0; y < NumBytes; y = y + 2) - { - theData[y] = (sbyte) (Data[yc] & 0x00FF); - theData[y + 1] = (sbyte) ((SupportClass.URShift(Data[yc++], 8)) & 0x00FF); - } - if (fmode != RFM_WRITE) - { - return DDC_INVALID_CALL; - } - try - { - file.Write(SupportClass.ToByteArray(theData), 0, NumBytes); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - riff_header.ckSize += NumBytes; - return DDC_SUCCESS; - } - - /// Write NumBytes data. - /// - public virtual int Write(RiffChunkHeader Triff_header, int NumBytes) - { - sbyte[] br = new sbyte[8]; - br[0] = (sbyte) ((SupportClass.URShift(Triff_header.ckID, 24)) & 0x000000FF); - br[1] = (sbyte) ((SupportClass.URShift(Triff_header.ckID, 16)) & 0x000000FF); - br[2] = (sbyte) ((SupportClass.URShift(Triff_header.ckID, 8)) & 0x000000FF); - br[3] = (sbyte) (Triff_header.ckID & 0x000000FF); - - sbyte br4 = (sbyte) ((SupportClass.URShift(Triff_header.ckSize, 24)) & 0x000000FF); - sbyte br5 = (sbyte) ((SupportClass.URShift(Triff_header.ckSize, 16)) & 0x000000FF); - sbyte br6 = (sbyte) ((SupportClass.URShift(Triff_header.ckSize, 8)) & 0x000000FF); - sbyte br7 = (sbyte) (Triff_header.ckSize & 0x000000FF); - - br[4] = br7; - br[5] = br6; - br[6] = br5; - br[7] = br4; - - if (fmode != RFM_WRITE) - { - return DDC_INVALID_CALL; - } - try - { - file.Write(SupportClass.ToByteArray(br), 0, NumBytes); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - riff_header.ckSize += NumBytes; - return DDC_SUCCESS; - } - - /// Write NumBytes data. - /// - public virtual int Write(short Data, int NumBytes) - { - short theData = Data;//(short) (((SupportClass.URShift(Data, 8)) & 0x00FF) | ((Data << 8) & 0xFF00)); - if (fmode != RFM_WRITE) - { - return DDC_INVALID_CALL; - } - try - { - System.IO.BinaryWriter temp_BinaryWriter; - temp_BinaryWriter = new System.IO.BinaryWriter(file); - temp_BinaryWriter.Write((System.Int16) theData); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - riff_header.ckSize += NumBytes; - return DDC_SUCCESS; - } - /// Write NumBytes data. - /// - public virtual int Write(int Data, int NumBytes) - { - short theDataL = (short) ((SupportClass.URShift(Data, 16)) & 0x0000FFFF); - short theDataR = (short) (Data & 0x0000FFFF); - short theDataLI = (short) (((SupportClass.URShift(theDataL, 8)) & 0x00FF) | ((theDataL << 8) & 0xFF00)); - short theDataRI = (short) (((SupportClass.URShift(theDataR, 8)) & 0x00FF) | ((theDataR << 8) & 0xFF00)); - int theData = Data;//((theDataRI << 16) & (int) SupportClass.Identity(0xFFFF0000)) | (theDataLI & 0x0000FFFF); - if (fmode != RFM_WRITE) - { - return DDC_INVALID_CALL; - } - try - { - System.IO.BinaryWriter temp_BinaryWriter; - temp_BinaryWriter = new System.IO.BinaryWriter(file); - temp_BinaryWriter.Write((System.Int32) theData); - fmode = RFM_WRITE; - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - riff_header.ckSize += NumBytes; - return DDC_SUCCESS; - } - - - - /// Read NumBytes data. - /// - public virtual int Read(sbyte[] Data, int NumBytes) - { - int retcode = DDC_SUCCESS; - try - { - SupportClass.ReadInput(file, ref Data, 0, NumBytes); - } - catch (System.IO.IOException ioe) - { - retcode = DDC_FILE_ERROR; - } - return retcode; - } - - /// Expect NumBytes data. - /// - public virtual int Expect(System.String Data, int NumBytes) - { - sbyte target = 0; - int cnt = 0; - try - { - while ((NumBytes--) != 0) - { - target = (sbyte) file.ReadByte(); - if (target != Data[cnt++]) - return DDC_FILE_ERROR; - } - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - return DDC_SUCCESS; - } - - /// Close Riff File. - /// Length is written too. - /// - public virtual int Close() - { - int retcode = DDC_SUCCESS; - - switch (fmode) - { - - case RFM_WRITE: - try - { - file.Seek(0, System.IO.SeekOrigin.Begin); - try - { - sbyte[] br = new sbyte[8]; - br[0] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 24)) & 0x000000FF); - br[1] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 16)) & 0x000000FF); - br[2] = (sbyte) ((SupportClass.URShift(riff_header.ckID, 8)) & 0x000000FF); - br[3] = (sbyte) (riff_header.ckID & 0x000000FF); - - br[7] = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 24)) & 0x000000FF); - br[6] = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 16)) & 0x000000FF); - br[5] = (sbyte) ((SupportClass.URShift(riff_header.ckSize, 8)) & 0x000000FF); - br[4] = (sbyte) (riff_header.ckSize & 0x000000FF); - file.Write(SupportClass.ToByteArray(br), 0, 8); - file.Close(); - } - catch (System.IO.IOException ioe) - { - retcode = DDC_FILE_ERROR; - } - } - catch (System.IO.IOException ioe) - { - retcode = DDC_FILE_ERROR; - } - break; - - - case RFM_READ: - try - { - file.Close(); - } - catch (System.IO.IOException ioe) - { - retcode = DDC_FILE_ERROR; - } - break; - } - file = null; - fmode = RFM_UNKNOWN; - return retcode; - } - - /// Return File Position. - /// - public virtual long CurrentFilePosition() - { - long position; - try - { - position = file.Position; - } - catch (System.IO.IOException ioe) - { - position = - 1; - } - return position; - } - - /// Write Data to specified offset. - /// - public virtual int Backpatch(long FileOffset, RiffChunkHeader Data, int NumBytes) - { - if (file == null) - { - return DDC_INVALID_CALL; - } - try - { - file.Seek(FileOffset, System.IO.SeekOrigin.Begin); - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - return Write(Data, NumBytes); - } - - public virtual int Backpatch(long FileOffset, sbyte[] Data, int NumBytes) - { - if (file == null) - { - return DDC_INVALID_CALL; - } - try - { - file.Seek(FileOffset, System.IO.SeekOrigin.Begin); - } - catch (System.IO.IOException ioe) - { - return DDC_FILE_ERROR; - } - return Write(Data, NumBytes); - } - - - /// Seek in the File. - /// - protected internal virtual int Seek(long offset) - { - int rc; - try - { - file.Seek(offset, System.IO.SeekOrigin.Begin); - rc = DDC_SUCCESS; - } - catch (System.IO.IOException ioe) - { - rc = DDC_FILE_ERROR; - } - return rc; - } - - /// Error Messages. - /// - private System.String DDCRET_String(int retcode) - { - switch (retcode) - { - - case DDC_SUCCESS: return "DDC_SUCCESS"; - - case DDC_FAILURE: return "DDC_FAILURE"; - - case DDC_OUT_OF_MEMORY: return "DDC_OUT_OF_MEMORY"; - - case DDC_FILE_ERROR: return "DDC_FILE_ERROR"; - - case DDC_INVALID_CALL: return "DDC_INVALID_CALL"; - - case DDC_USER_ABORT: return "DDC_USER_ABORT"; - - case DDC_INVALID_FILE: return "DDC_INVALID_FILE"; - } - return "Unknown Error"; - } - - /// Fill the header. - /// - public static int FourCC(System.String ChunkName) - { - sbyte[] p = new sbyte[]{(sbyte) (0x20), (sbyte) (0x20), (sbyte) (0x20), (sbyte) (0x20)}; - SupportClass.GetSBytesFromString(ChunkName, 0, 4, ref p, 0); - int ret = (((p[0] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((p[1] << 16) & 0x00FF0000) | ((p[2] << 8) & 0x0000FF00) | (p[3] & 0x000000FF)); - return ret; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/converter/WaveFile.cs b/Other/libs/mp3sharp/mp3sharp/converter/WaveFile.cs deleted file mode 100644 index 816fc47eb..000000000 --- a/Other/libs/mp3sharp/mp3sharp/converter/WaveFile.cs +++ /dev/null @@ -1,547 +0,0 @@ -using Support; -/* -* 02/23/99 JavaConversion by E.B, JavaLayer -*/ -/*=========================================================================== - -riff.h - Don Cross, April 1993. - -RIFF file format classes. -See Chapter 8 of "Multimedia Programmer's Reference" in -the Microsoft Windows SDK. - -See also: -..\source\riff.cpp -ddc.h - -===========================================================================*/ -namespace javazoom.jl.converter -{ - using System; - - /// Class allowing WaveFormat Access - /// - internal class WaveFile:RiffFile - { - public const int MAX_WAVE_CHANNELS = 2; - - //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'WaveFormat_ChunkData' to access its enclosing instance. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1019"' - internal class WaveFormat_ChunkData - { - private void InitBlock(WaveFile enclosingInstance) - { - this.enclosingInstance = enclosingInstance; - } - private WaveFile enclosingInstance; - public WaveFile Enclosing_Instance - { - get - { - return enclosingInstance; - } - - } - public short wFormatTag = 0; // Format category (PCM=1) - public short nChannels = 0; // Number of channels (mono=1, stereo=2) - public int nSamplesPerSec = 0; // Sampling rate [Hz] - public int nAvgBytesPerSec = 0; - public short nBlockAlign = 0; - public short nBitsPerSample = 0; - - public WaveFormat_ChunkData(WaveFile enclosingInstance) - { - InitBlock(enclosingInstance); - wFormatTag = 1; // PCM - Config(44100, (short) 16, (short) 1); - } - - public virtual void Config(int NewSamplingRate, short NewBitsPerSample, short NewNumChannels) - { - nSamplesPerSec = NewSamplingRate; - nChannels = NewNumChannels; - nBitsPerSample = NewBitsPerSample; - nAvgBytesPerSec = (nChannels * nSamplesPerSec * nBitsPerSample) / 8; - nBlockAlign = (short) ((nChannels * nBitsPerSample) / 8); - } - } - - - //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'WaveFormat_Chunk' to access its enclosing instance. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1019"' - internal class WaveFormat_Chunk - { - private void InitBlock(WaveFile enclosingInstance) - { - this.enclosingInstance = enclosingInstance; - } - private WaveFile enclosingInstance; - public WaveFile Enclosing_Instance - { - get - { - return enclosingInstance; - } - - } - public RiffChunkHeader header; - public WaveFormat_ChunkData data; - - public WaveFormat_Chunk(WaveFile enclosingInstance) - { - InitBlock(enclosingInstance); - header = new RiffChunkHeader(enclosingInstance); - data = new WaveFormat_ChunkData(enclosingInstance); - header.ckID = javazoom.jl.converter.RiffFile.FourCC("fmt "); - header.ckSize = 16; - } - - public virtual int VerifyValidity() - { - bool ret = header.ckID == javazoom.jl.converter.RiffFile.FourCC("fmt ") && (data.nChannels == 1 || data.nChannels == 2) && data.nAvgBytesPerSec == (data.nChannels * data.nSamplesPerSec * data.nBitsPerSample) / 8 && data.nBlockAlign == (data.nChannels * data.nBitsPerSample) / 8; - if (ret == true) - return 1; - else - return 0; - } - } - - //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'WaveFileSample' to access its enclosing instance. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1019"' - internal class WaveFileSample - { - private void InitBlock(WaveFile enclosingInstance) - { - this.enclosingInstance = enclosingInstance; - } - private WaveFile enclosingInstance; - public WaveFile Enclosing_Instance - { - get - { - return enclosingInstance; - } - - } - public short[] chan; - - public WaveFileSample(WaveFile enclosingInstance) - { - InitBlock(enclosingInstance); - chan = new short[WaveFile.MAX_WAVE_CHANNELS]; - } - } - - private WaveFormat_Chunk wave_format; - private RiffChunkHeader pcm_data; - private long pcm_data_offset = 0; // offset of 'pcm_data' in output file - private int num_samples = 0; - - - /// Constructs a new WaveFile instance. - /// - public WaveFile() - { - pcm_data = new RiffChunkHeader(this); - wave_format = new WaveFormat_Chunk(this); - pcm_data.ckID = FourCC("data"); - pcm_data.ckSize = 0; - num_samples = 0; - } - - /// * - /// * - /// public int OpenForRead (String Filename) - /// { - /// // Verify filename parameter as best we can... - /// if (Filename == null) - /// { - /// return DDC_INVALID_CALL; - /// } - /// int retcode = Open ( Filename, RFM_READ ); - /// - /// if ( retcode == DDC_SUCCESS ) - /// { - /// retcode = Expect ( "WAVE", 4 ); - /// - /// if ( retcode == DDC_SUCCESS ) - /// { - /// retcode = Read(wave_format,24); - /// - /// if ( retcode == DDC_SUCCESS && !wave_format.VerifyValidity() ) - /// { - /// // This isn't standard PCM, so we don't know what it is! - /// retcode = DDC_FILE_ERROR; - /// } - /// - /// if ( retcode == DDC_SUCCESS ) - /// { - /// pcm_data_offset = CurrentFilePosition(); - /// - /// // Figure out number of samples from - /// // file size, current file position, and - /// // WAVE header. - /// retcode = Read (pcm_data, 8 ); - /// num_samples = filelength(fileno(file)) - CurrentFilePosition(); - /// num_samples /= NumChannels(); - /// num_samples /= (BitsPerSample() / 8); - /// } - /// } - /// } - /// return retcode; - /// } - /// - - - /// - /// Pass in either a FileName or a Stream. - /// - public virtual int OpenForWrite(System.String Filename, System.IO.Stream stream, int SamplingRate, short BitsPerSample, short NumChannels) - { - // Verify parameters... - if ((BitsPerSample != 8 && BitsPerSample != 16) || NumChannels < 1 || NumChannels > 2) - { - return DDC_INVALID_CALL; - } - - wave_format.data.Config(SamplingRate, BitsPerSample, NumChannels); - - int retcode = 0; - if (stream != null) - Open(stream, RFM_WRITE); - else - Open(Filename, RFM_WRITE); - - if (retcode == DDC_SUCCESS) - { - sbyte[] theWave = new sbyte[]{(sbyte) SupportClass.Identity('W'), (sbyte) SupportClass.Identity('A'), (sbyte) SupportClass.Identity('V'), (sbyte) SupportClass.Identity('E')}; - retcode = Write(theWave, 4); - - if (retcode == DDC_SUCCESS) - { - // Ecriture de wave_format - retcode = Write(wave_format.header, 8); - retcode = Write(wave_format.data.wFormatTag, 2); - retcode = Write(wave_format.data.nChannels, 2); - retcode = Write(wave_format.data.nSamplesPerSec, 4); - retcode = Write(wave_format.data.nAvgBytesPerSec, 4); - retcode = Write(wave_format.data.nBlockAlign, 2); - retcode = Write(wave_format.data.nBitsPerSample, 2); - - if (retcode == DDC_SUCCESS) - { - pcm_data_offset = CurrentFilePosition(); - retcode = Write(pcm_data, 8); - } - } - } - - return retcode; - } - - /// * - /// * - /// public int ReadSample ( short[] Sample ) - /// { - /// - /// } - /// - - /// * - /// * - /// public int WriteSample( short[] Sample ) - /// { - /// int retcode = DDC_SUCCESS; - /// switch ( wave_format.data.nChannels ) - /// { - /// case 1: - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// pcm_data.ckSize += 1; - /// retcode = Write ( Sample, 1 ); - /// break; - /// - /// case 16: - /// pcm_data.ckSize += 2; - /// retcode = Write ( Sample, 2 ); - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// break; - /// - /// case 2: - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// retcode = Write ( Sample, 1 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// // &Sample[1] - /// retcode = Write (Sample, 1 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// pcm_data.ckSize += 2; - /// } - /// } - /// break; - /// - /// case 16: - /// retcode = Write ( Sample, 2 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// // &Sample[1] - /// retcode = Write (Sample, 2 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// pcm_data.ckSize += 4; - /// } - /// } - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// - /// return retcode; - /// } - /// - - /// * - /// * - /// public int SeekToSample ( long SampleIndex ) - /// { - /// if ( SampleIndex >= NumSamples() ) - /// { - /// return DDC_INVALID_CALL; - /// } - /// int SampleSize = (BitsPerSample() + 7) / 8; - /// int rc = Seek ( pcm_data_offset + 8 + - /// SampleSize * NumChannels() * SampleIndex ); - /// return rc; - /// } - /// - - /// Write 16-bit audio - /// - public virtual int WriteData(short[] data, int numData) - { - int extraBytes = numData * 2; - pcm_data.ckSize += extraBytes; - return base.Write(data, extraBytes); - } - - /// Read 16-bit audio. - /// * - /// public int ReadData (short[] data, int numData) - /// {return super.Read ( data, numData * 2);} - /// - - /// Write 8-bit audio. - /// * - /// public int WriteData ( byte[] data, int numData ) - /// { - /// pcm_data.ckSize += numData; - /// return super.Write ( data, numData ); - /// } - /// - - /// Read 8-bit audio. - /// * - /// public int ReadData ( byte[] data, int numData ) - /// {return super.Read ( data, numData );} - /// - - - /// * - /// * - /// public int ReadSamples (int num, int [] WaveFileSample) - /// { - /// - /// } - /// - - /// * - /// * - /// public int WriteMonoSample ( short[] SampleData ) - /// { - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// pcm_data.ckSize += 1; - /// return Write ( SampleData, 1 ); - /// - /// case 16: - /// pcm_data.ckSize += 2; - /// return Write ( SampleData, 2 ); - /// } - /// return DDC_INVALID_CALL; - /// } - /// - - /// * - /// * - /// public int WriteStereoSample ( short[] LeftSample, short[] RightSample ) - /// { - /// int retcode = DDC_SUCCESS; - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// retcode = Write ( LeftSample, 1 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// retcode = Write ( RightSample, 1 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// pcm_data.ckSize += 2; - /// } - /// } - /// break; - /// - /// case 16: - /// retcode = Write ( LeftSample, 2 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// retcode = Write ( RightSample, 2 ); - /// if ( retcode == DDC_SUCCESS ) - /// { - /// pcm_data.ckSize += 4; - /// } - /// } - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// return retcode; - /// } - /// - - /// * - /// * - /// public int ReadMonoSample ( short[] Sample ) - /// { - /// int retcode = DDC_SUCCESS; - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// byte[] x = {0}; - /// retcode = Read ( x, 1 ); - /// Sample[0] = (short)(x[0]); - /// break; - /// - /// case 16: - /// retcode = Read ( Sample, 2 ); - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// return retcode; - /// } - /// - - /// * - /// * - /// public int ReadStereoSample ( short[] LeftSampleData, short[] RightSampleData ) - /// { - /// int retcode = DDC_SUCCESS; - /// byte[] x = new byte[2]; - /// short[] y = new short[2]; - /// switch ( wave_format.data.nBitsPerSample ) - /// { - /// case 8: - /// retcode = Read ( x, 2 ); - /// L[0] = (short) ( x[0] ); - /// R[0] = (short) ( x[1] ); - /// break; - /// - /// case 16: - /// retcode = Read ( y, 4 ); - /// L[0] = (short) ( y[0] ); - /// R[0] = (short) ( y[1] ); - /// break; - /// - /// default: - /// retcode = DDC_INVALID_CALL; - /// } - /// return retcode; - /// } - /// - - - /// * - /// - public override int Close() - { - int rc = DDC_SUCCESS; - - if (fmode == RFM_WRITE) - rc = Backpatch(pcm_data_offset, pcm_data, 8); - if (!JustWriteLengthBytes) - { - if (rc == DDC_SUCCESS) - rc = base.Close(); - } - return rc; - } - public int Close(bool justWriteLengthBytes) - { - JustWriteLengthBytes = justWriteLengthBytes; - int ret = Close(); - JustWriteLengthBytes = false; - return ret; - } - bool JustWriteLengthBytes = false; - - - - // [Hz] - public virtual int SamplingRate() - { - return wave_format.data.nSamplesPerSec; - } - - public virtual short BitsPerSample() - { - return wave_format.data.nBitsPerSample; - } - - public virtual short NumChannels() - { - return wave_format.data.nChannels; - } - - public virtual int NumSamples() - { - return num_samples; - } - - - /// Open for write using another wave file's parameters... - /// - public virtual int OpenForWrite(System.String Filename, WaveFile OtherWave) - { - return OpenForWrite(Filename, null, OtherWave.SamplingRate(), OtherWave.BitsPerSample(), OtherWave.NumChannels()); - } - - /// * - /// - public override long CurrentFilePosition() - { - return base.CurrentFilePosition(); - } - - /* public int FourCC(String ChunkName) - { - byte[] p = {0x20,0x20,0x20,0x20}; - ChunkName.getBytes(0,4,p,0); - int ret = (((p[0] << 24)& 0xFF000000) | ((p[1] << 16)&0x00FF0000) | ((p[2] << 8)&0x0000FF00) | (p[3]&0x000000FF)); - return ret; - }*/ - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/converter/WaveFileObuffer.cs b/Other/libs/mp3sharp/mp3sharp/converter/WaveFileObuffer.cs deleted file mode 100644 index 979e32a12..000000000 --- a/Other/libs/mp3sharp/mp3sharp/converter/WaveFileObuffer.cs +++ /dev/null @@ -1,153 +0,0 @@ -/* -* 12/12/99 0.0.7 Renamed class, additional constructor arguments -* and larger write buffers. mdm@techie.com. -* -* 15/02/99 ,Java Conversion by E.B ,ebsp@iname.com, JavaLayer -*/ -namespace javazoom.jl.converter -{ - using System; - using Obuffer = javazoom.jl.decoder.Obuffer; - /// Implements an Obuffer by writing the data to - /// a file in RIFF WAVE format. - /// - /// @since 0.0 - /// - - - internal class WaveFileObuffer:Obuffer - { - private void InitBlock() - { - myBuffer = new short[2]; - } - private short[] buffer; - private short[] bufferp; - private int channels; - private WaveFile outWave; - - /// Creates a new WareFileObuffer instance. - /// - /// - /// number_of_channels - /// The number of channels of audio data - /// this buffer will receive. - /// - /// - /// sample frequency of the samples in the buffer. - /// - /// - /// filename to write the data to. - /// - /// - public WaveFileObuffer(int number_of_channels, int freq, System.String FileName) - { - InitBlock(); - if (FileName == null) - throw new System.NullReferenceException("FileName"); - - buffer = new short[OBUFFERSIZE]; - bufferp = new short[MAXCHANNELS]; - channels = number_of_channels; - - for (int i = 0; i < number_of_channels; ++i) - bufferp[i] = (short) i; - - outWave = new WaveFile(); - - int rc = outWave.OpenForWrite(FileName, null, freq, (short) 16, (short) channels); - } - - public WaveFileObuffer(int number_of_channels, int freq, System.IO.Stream stream) - { - InitBlock(); - - buffer = new short[OBUFFERSIZE]; - bufferp = new short[MAXCHANNELS]; - channels = number_of_channels; - - for (int i = 0; i < number_of_channels; ++i) - bufferp[i] = (short) i; - - outWave = new WaveFile(); - - int rc = outWave.OpenForWrite(null, stream, freq, (short) 16, (short) channels); - } - - - /// Takes a 16 Bit PCM sample. - /// - public override void append(int channel, short value_Renamed) - { - buffer[bufferp[channel]] = value_Renamed; - bufferp[channel] = (short) (bufferp[channel] + channels); - } - - /// Write the samples to the file (Random Acces). - /// - //UPGRADE_NOTE: The initialization of 'myBuffer' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - internal short[] myBuffer; - public override void write_buffer(int val) - { - - int k = 0; - int rc = 0; - - rc = outWave.WriteData(buffer, bufferp[0]); - // REVIEW: handle RiffFile errors. - /* - for (int j=0;j>8)&0x000000FF) | ((buffer[j]<<8)&0x0000FF00)); - //myBuffer[1] = (short) (((buffer[j+1]>>8)&0x000000FF) | ((buffer[j+1]<<8)&0x0000FF00)); - myBuffer[0] = buffer[j]; - myBuffer[1] = buffer[j+1]; - rc = outWave.WriteData (myBuffer,2); - } - */ - for (int i = 0; i < channels; ++i) - bufferp[i] = (short) i; - } - - public void close(bool justWriteLengthBytes) - { - outWave.Close(justWriteLengthBytes); - } - - public override void close() - { - outWave.Close(); - } - - - /// * - /// - public override void clear_buffer() - { - } - - /// * - /// - public override void set_stop_flag() - { - } - - /* - * Create STDOUT buffer - * - * - public static Obuffer create_stdout_obuffer(MPEG_Args maplay_args) - { - Obuffer thebuffer = null; - int mode = maplay_args.MPEGheader.mode(); - int which_channels = maplay_args.which_c; - if (mode == Header.single_channel || which_channels != MPEG_Args.both) - thebuffer = new FileObuffer(1,maplay_args.output_filename); - else - thebuffer = new FileObuffer(2,maplay_args.output_filename); - return(thebuffer); - } - */ - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/converter/jlc.cs b/Other/libs/mp3sharp/mp3sharp/converter/jlc.cs deleted file mode 100644 index 82dd9ce37..000000000 --- a/Other/libs/mp3sharp/mp3sharp/converter/jlc.cs +++ /dev/null @@ -1,194 +0,0 @@ -/* 12/12/99 JavaLayer 0.0.7 mdm@techie.com -* Adapted from javalayer and MPEG_Args. -* Doc'ed and integerated with JL converter. Removed -* Win32 specifics from original Maplay code. -* -* MPEG_Args Based Class - E.B 14/02/99 , JavaLayer -*/ -namespace javazoom.jl.converter -{ - using System; - using javazoom.jl.decoder; - /// The jlc class presents the JavaLayer - /// Conversion functionality as a command-line program. - /// * - /// @since 0.0.7 - /// - - public class jlc - { - - [STAThread] - static public void Main(System.String[] args) - { - System.String[] argv; - long start = (System.DateTime.Now.Ticks - 621355968000000000) / 10000; - int argc = args.Length + 1; - argv = new System.String[argc]; - argv[0] = "jlc"; - for (int i = 0; i < args.Length; i++) - argv[i + 1] = args[i]; - - jlcArgs ma = new jlcArgs(); - if (!ma.processArgs(argv)) - System.Environment.Exit(1); - - Converter conv = new Converter(); - - int detail = (ma.verbose_mode?ma.verbose_level:Converter.PrintWriterProgressListener.NO_DETAIL); - - System.IO.StreamWriter temp_writer; - //UPGRADE_ISSUE: 'java.lang.System.out' was converted to 'System.Console.Out' which is not valid in this expression. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1109"' - temp_writer = new System.IO.StreamWriter(System.Console.Out); - temp_writer.AutoFlush = true; - Converter.ProgressListener listener = new Converter.PrintWriterProgressListener(temp_writer, detail); - - try - { - conv.convert(ma.filename, ma.output_filename, listener); - } - catch (JavaLayerException ex) - { - System.Console.Error.WriteLine("Convertion failure: " + ex); - } - - System.Environment.Exit(0); - } - - - /// Class to contain arguments for maplay. - /// - internal class jlcArgs - { - // channel constants moved into OutputChannels class. - //public static final int both = 0; - //public static final int left = 1; - //public static final int right = 2; - //public static final int downmix = 3; - - public int which_c; - public int output_mode; - public bool use_own_scalefactor; - public float scalefactor; - public System.String output_filename; - public System.String filename; - //public boolean stdout_mode; - public bool verbose_mode; - public int verbose_level = 3; - - public jlcArgs() - { - which_c = OutputChannels.BOTH_CHANNELS; - use_own_scalefactor = false; - scalefactor = (float) SupportClass.Identity(32768.0); - //stdout_mode = false; - verbose_mode = false; - } - - /// Process user arguments. - /// * - /// Returns true if successful. - /// - public virtual bool processArgs(System.String[] argv) - { - filename = null; - Crc16[] crc; - crc = new Crc16[1]; - int i; - int argc = argv.Length; - - //stdout_mode = false; - verbose_mode = false; - output_mode = OutputChannels.BOTH_CHANNELS; - output_filename = ""; - if (argc < 2 || argv[1].Equals("-h")) - return Usage(); - - i = 1; - while (i < argc) - { - /* System.out.println("Option = "+argv[i]);*/ - if (argv[i][0] == '-') - { - if (argv[i].StartsWith("-v")) - { - verbose_mode = true; - if (argv[i].Length > 2) - { - try - { - System.String level = argv[i].Substring(2); - verbose_level = System.Int32.Parse(level); - } - catch (System.FormatException ex) - { - System.Console.Error.WriteLine("Invalid verbose level. Using default."); - } - } - System.Console.Out.WriteLine("Verbose Activated (level " + verbose_level + ")"); - } - /* else if (argv[i].equals("-s")) - ma.stdout_mode = true; */ - else if (argv[i].Equals("-p")) - { - if (++i == argc) - { - System.Console.Out.WriteLine("Please specify an output filename after the -p option!"); - System.Environment.Exit(1); - } - //output_mode = O_WAVEFILE; - output_filename = argv[i]; - } - /*else if (argv[i].equals("-f")) - { - if (++i == argc) - { - System.out.println("Please specify a new scalefactor after the -f option!"); - System.exit(1); - } - ma.use_own_scalefactor = true; - // ma.scalefactor = argv[i]; - }*/ - else - return Usage(); - } - else - { - filename = argv[i]; - System.Console.Out.WriteLine("FileName = " + argv[i]); - if (filename == null) - return Usage(); - } - i++; - } - if (filename == null) - return Usage(); - - return true; - } - - - /// Usage of JavaLayer. - /// - public virtual bool Usage() - { - System.Console.Out.WriteLine("JavaLayer Converter V0.0.8 :"); - System.Console.Out.WriteLine(" -v[x] verbose mode. "); - System.Console.Out.WriteLine(" default = 2"); - /* System.out.println(" -s write u-law samples at 8 kHz rate to stdout"); - System.out.println(" -l decode only the left channel"); - System.out.println(" -r decode only the right channel"); - System.out.println(" -d downmix mode (layer III only)"); - System.out.println(" -s write pcm samples to stdout"); - System.out.println(" -d downmix mode (layer III only)");*/ - System.Console.Out.WriteLine(" -p name output as a PCM wave file"); - System.Console.Out.WriteLine(""); - System.Console.Out.WriteLine(" More info on http://www.javazoom.net"); - /* System.out.println(" -f ushort use this scalefactor instead of the default value 32768");*/ - return false; - } - } - - } - -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/BackStream.cs b/Other/libs/mp3sharp/mp3sharp/decoder/BackStream.cs deleted file mode 100644 index 24ddbcf93..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/BackStream.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System; -using System.IO; - -using javazoom.jl.converter; -using javazoom.jl.decoder; - - -///A BackStream (such a beast doesn't exist in C#'s libraries to my knowledge) -namespace javazoom.jl.decoder -{ - [Serializable] - internal class CircularByteBuffer - { - byte[] dataArray = null; - int length = 1; - int index = 0; - int numValid = 0; - - public CircularByteBuffer(int size) - { - dataArray = new byte[size]; - length = size; - } - - /// - /// Initialize by copying the CircularByteBuffer passed in - /// - public CircularByteBuffer(CircularByteBuffer cdb) - { - lock(cdb) - { - length = cdb.length; - numValid = cdb.numValid; - index = cdb.index; - dataArray = new byte[length]; - for (int c=0; c < length; c++) - { - dataArray[c] = cdb.dataArray[c]; - } - } - } - - public CircularByteBuffer Copy() - { - return new CircularByteBuffer(this); - } - - /// - /// The physical size of the Buffer (read/write) - /// - public int BufferSize - { - get - { - return length; - } - set - { - byte[] newDataArray = new byte[value]; - - int minLength = (length>value) ? value : length; - for(int i=0;i - /// Push a byte into the buffer. Returns the value of whatever comes off. - /// - public byte Push(byte newValue) - { - byte ret; - lock(this) - { - ret = InternalGet(length); - dataArray[index] = newValue; - numValid++; if (numValid>length) numValid = length; - index++; - index %= length; - } - return ret; - } - - /// - /// Pop an integer off the start of the buffer. Throws an exception if the buffer is empty (NumValid == 0) - /// - public byte Pop() - { - lock(this) - { - if (numValid == 0) throw new Exception("Can't pop off an empty CircularByteBuffer"); - numValid--; - return this[numValid]; - } - } - - /// - /// Returns what would fall out of the buffer on a Push. NOT the same as what you'd get with a Pop(). - /// - public byte Peek() - { - lock(this) - { - return InternalGet(length); - } - } - - /// - /// e.g. Offset[0] is the current value - /// - public byte this [int index] - { - get - { - return InternalGet(-1-index); - } - set - { - InternalSet(-1-index, value); - } - } - - private byte InternalGet(int offset) - { - int ind=index+offset; - - // Do thin modulo (should just drop through) - for(;ind>=length;ind-=length); - for(;ind<0;ind+=length); - // Set value - return dataArray[ind]; - } - - private void InternalSet(int offset, byte valueToSet) - { - int ind=index+offset; - - // Do thin modulo (should just drop through) - for(;ind>length;ind-=length); - for(;ind<0;ind+=length); - // Set value - dataArray[ind] = valueToSet; - } - - - /// - /// How far back it is safe to look (read/write). Write only to reduce NumValid. - /// - public int NumValid - { - get - { - return numValid; - } - set - { - if (value > numValid) throw new Exception("Can't set NumValid to " + value + " which is greater than the current numValid value of " + numValid); - numValid = value; - } - } - - /// - /// Returns a range (in terms of Offsets) in an int array in chronological (oldest-to-newest) order. e.g. (3, 0) returns the last four ints pushed, with result[3] being the most recent. - /// - public byte[] GetRange(int str, int stp) - { - byte[]outByte = new byte[str-stp+1]; - - for(int i=str,j=0;i>=stp;i--,j++) - { - outByte[j] = this[i]; - } - - return outByte; - } - - public override String ToString() - { - String ret = ""; - for(int i=0;i Implementation of Bit Reservoir for Layer III. - ///

- /// The implementation stores single bits as a word in the buffer. If - /// a bit is set, the corresponding word in the buffer will be non-zero. - /// If a bit is clear, the corresponding word is zero. Although this - /// may seem waseful, this can be a factor of two quicker than - /// packing 8 bits to a byte and extracting. - ///

- /// - - // REVIEW: there is no range checking, so buffer underflow or overflow - // can silently occur. - sealed class BitReserve - { - private void InitBlock() - { - buf = new int[BUFSIZE]; - } - ///

Size of the internal buffer to store the reserved bits. - /// Must be a power of 2. And x8, as each bit is stored as a single - /// entry. - /// - private const int BUFSIZE = 4096 * 8; - - /// Mask that can be used to quickly implement the - /// modulus operation on BUFSIZE. - /// - //UPGRADE_NOTE: Final was removed from the declaration of 'BUFSIZE_MASK '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly int BUFSIZE_MASK = BUFSIZE - 1; - - private int offset, totbit, buf_byte_idx; - //UPGRADE_NOTE: Final was removed from the declaration of 'buf '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'buf' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int[] buf; - private int buf_bit_idx; - - internal BitReserve() - { - InitBlock(); - - offset = 0; - totbit = 0; - buf_byte_idx = 0; - } - - - /// Return totbit Field. - /// - public int hsstell() - { - return (totbit); - } - - /// Read a number bits from the bit stream. - /// - /// the number of - /// - /// - public int hgetbits(int N) - { - totbit += N; - - int val = 0; - - int pos = buf_byte_idx; - if (pos + N < BUFSIZE) - { - while (N-- > 0) - { - val <<= 1; - val |= ((buf[pos++] != 0)?1:0); - } - } - else - { - while (N-- > 0) - { - val <<= 1; - val |= ((buf[pos] != 0)?1:0); - pos = (pos + 1) & BUFSIZE_MASK; - } - } - buf_byte_idx = pos; - return val; - } - - - - /// Read 1 bit from the bit stream. - /// - /* - public int hget1bit_old() - { - int val; - totbit++; - if (buf_bit_idx == 0) - { - buf_bit_idx = 8; - buf_byte_idx++; - } - // BUFSIZE = 4096 = 2^12, so - // buf_byte_idx%BUFSIZE == buf_byte_idx & 0xfff - val = buf[buf_byte_idx & BUFSIZE_MASK] & putmask[buf_bit_idx]; - buf_bit_idx--; - val = val >>> buf_bit_idx; - return val; - } - */ - /// Returns next bit from reserve. - /// - /// s 0 if next bit is reset, or 1 if next bit is set. - /// - /// - public int hget1bit() - { - totbit++; - int val = buf[buf_byte_idx]; - buf_byte_idx = (buf_byte_idx + 1) & BUFSIZE_MASK; - return val; - } - - /// Retrieves bits from the reserve. - /// - /* - public int readBits(int[] out, int len) - { - if (buf_bit_idx == 0) - { - buf_bit_idx = 8; - buf_byte_idx++; - current = buf[buf_byte_idx & BUFSIZE_MASK]; - } - - - - // save total number of bits returned - len = buf_bit_idx; - buf_bit_idx = 0; - - int b = current; - int count = len-1; - - while (count >= 0) - { - out[count--] = (b & 0x1); - b >>>= 1; - } - - totbit += len; - return len; - } - */ - - /// Write 8 bits into the bit stream. - /// - public void hputbuf(int val) - { - int ofs = offset; - buf[ofs++] = val & 0x80; - buf[ofs++] = val & 0x40; - buf[ofs++] = val & 0x20; - buf[ofs++] = val & 0x10; - buf[ofs++] = val & 0x08; - buf[ofs++] = val & 0x04; - buf[ofs++] = val & 0x02; - buf[ofs++] = val & 0x01; - - if (ofs == BUFSIZE) - offset = 0; - else - offset = ofs; - } - - /// Rewind N bits in Stream. - /// - public void rewindNbits(int N) - { - totbit -= N; - buf_byte_idx -= N; - if (buf_byte_idx < 0) - buf_byte_idx += BUFSIZE; - } - - /// Rewind N bytes in Stream. - /// - public void rewindNbytes(int N) - { - int bits = (N << 3); - totbit -= bits; - buf_byte_idx -= bits; - if (buf_byte_idx < 0) - buf_byte_idx += BUFSIZE; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Bitstream.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Bitstream.cs deleted file mode 100644 index ad3a91a64..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Bitstream.cs +++ /dev/null @@ -1,584 +0,0 @@ -using Support; -using Mp3Sharp; -/* -* 12/12/99 Based on Ibitstream. Exceptions thrown on errors, -* Tempoarily removed seek functionality. mdm@techie.com -* -* 02/12/99 : Java Conversion by E.B , ebsp@iname.com , JavaLayer -* -*---------------------------------------------------------------------- -* @(#) ibitstream.h 1.5, last edit: 6/15/94 16:55:34 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -* -* Changes made by Jeff Tsay : -* 04/14/97 : Added function prototypes for new syncing and seeking -* mechanisms. Also made this file portable. -*----------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - using System.Diagnostics; - - /// The Bistream class is responsible for parsing - /// an MPEG audio bitstream. - /// * - /// REVIEW: much of the parsing currently occurs in the - /// various decoders. This should be moved into this class and associated - /// inner classes. - /// - internal sealed class Bitstream : BitstreamErrors - { - private void InitBlock() - { - crc = new Crc16[1]; - syncbuf = new sbyte[4]; - frame_bytes = new sbyte[BUFFER_INT_SIZE * 4]; - framebuffer = new int[BUFFER_INT_SIZE]; - header = new Header(); - } - - /// Syncrhronization control constant for the initial - /// synchronization to the start of a frame. - /// - internal static sbyte INITIAL_SYNC = 0; - - /// Syncrhronization control constant for non-iniital frame - /// synchronizations. - /// - internal static sbyte STRICT_SYNC = 1; - - // max. 1730 bytes per frame: 144 * 384kbit/s / 32000 Hz + 2 Bytes CRC - /// Maximum size of the frame buffer. - /// - private const int BUFFER_INT_SIZE = 433; - - - /// The frame buffer that holds the data for the current frame. - /// - //UPGRADE_NOTE: Final was removed from the declaration of 'framebuffer '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'framebuffer' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int[] framebuffer; - - /// Number of valid bytes in the frame buffer. - /// - private int framesize; - - /// The bytes read from the stream. - /// - //UPGRADE_NOTE: The initialization of 'frame_bytes' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte[] frame_bytes; - - /// Index into framebuffer where the next bits are - /// retrieved. - /// - private int wordpointer; - - /// Number (0-31, from MSB to LSB) of next bit for get_bits() - /// - private int bitindex; - - /// The current specified syncword - /// - private int syncword; - - /// * - /// - private bool single_ch_mode; - //private int current_frame_number; - //private int last_frame_number; - - //UPGRADE_NOTE: Final was removed from the declaration of 'bitmask '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private int[] bitmask = new int[]{0, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF}; - - //UPGRADE_NOTE: Final was removed from the declaration of 'source '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private BackStream source; - - //UPGRADE_NOTE: Final was removed from the declaration of 'header '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'header' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Header header; - - //UPGRADE_NOTE: Final was removed from the declaration of 'syncbuf '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'syncbuf' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte[] syncbuf; - - //UPGRADE_NOTE: The initialization of 'crc' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Crc16[] crc; - - //private ByteArrayOutputStream _baos = null; // E.B - - - /// Construct a IBitstream that reads data from a - /// given InputStream. - /// * - /// - /// InputStream to read from. - /// - /// - internal Bitstream(BackStream in_Renamed) - { - InitBlock(); - if (in_Renamed == null) - throw new System.NullReferenceException("in"); - - source = in_Renamed; // ROB - fuck the SupportClass, let's roll our own. new SupportClass.BackInputStream(in_Renamed, 1024); - - //_baos = new ByteArrayOutputStream(); // E.B - - closeFrame(); - //current_frame_number = -1; - //last_frame_number = -1; - } - - public void close() - { - try - { - //UPGRADE_TODO: Method 'java.io.FilterInputStream.close' was converted to 'System.IO.BinaryReader.Close' which has a different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1073_javaioFilterInputStreamclose"' - source.Close(); - //_baos = null; - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - } - - /// Reads and parses the next frame from the input source. - /// - /// the Header describing details of the frame read, - /// or null if the end of the stream has been reached. - /// - /// - internal Header readFrame() - { - Header result = null; - try - { - result = readNextFrame(); - } - catch (BitstreamException ex) - { - if (ex.ErrorCode != javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF) - { - // wrap original exception so stack trace is maintained. - throw newBitstreamException(ex.ErrorCode, ex); - } - } - return result; - } - - private Header readNextFrame() - { - if (framesize == - 1) - { - nextFrame(); - } - - return header; - } - - - /// * - /// - private void nextFrame() - { - // entire frame is read by the header class. - header.read_header(this, crc); - } - - /// Unreads the bytes read from the frame. - /// @throws BitstreamException - /// - // REVIEW: add new error codes for this. - public void unreadFrame() - { - if (wordpointer == - 1 && bitindex == - 1 && (framesize > 0)) - { - try - { - //source.UnRead(SupportClass.ToByteArray(frame_bytes), 0, framesize); - source.UnRead(framesize); - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR); - } - } - } - - public void closeFrame() - { - framesize = - 1; - wordpointer = - 1; - bitindex = - 1; - } - - /// Determines if the next 4 bytes of the stream represent a - /// frame header. - /// - public bool isSyncCurrentPosition(int syncmode) - { - int read = readBytes(syncbuf, 0, 4); - int headerstring = ((syncbuf[0] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((syncbuf[1] << 16) & 0x00FF0000) | ((syncbuf[2] << 8) & 0x0000FF00) | ((syncbuf[3] << 0) & 0x000000FF); - - try - { - //source.UnRead(SupportClass.ToByteArray(syncbuf), 0, read); - source.UnRead(read); - } - catch (System.IO.IOException ex) - { - } - - bool sync = false; - switch (read) - { - - case 0: - Trace.WriteLine( "0 bytes read == sync?", "Bitstream" ); - sync = true; - break; - - case 4: - sync = isSyncMark(headerstring, syncmode, syncword); - break; - } - - return sync; - } - - - // REVIEW: this class should provide inner classes to - // parse the frame contents. Eventually, readBits will - // be removed. - public int readBits(int n) - { - return get_bits(n); - } - - public int readCheckedBits(int n) - { - // REVIEW: implement CRC check. - return get_bits(n); - } - - protected internal BitstreamException newBitstreamException(int errorcode) - { - return new BitstreamException(errorcode, null); - } - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - protected internal BitstreamException newBitstreamException(int errorcode, System.Exception throwable) - { - return new BitstreamException(errorcode, throwable); - } - - - /// Get next 32 bits from bitstream. - /// They are stored in the headerstring. - /// syncmod allows Synchro flag ID - /// The returned value is False at the end of stream. - /// - - internal int syncHeader(sbyte syncmode) - { - bool sync; - int headerstring; - - // read additinal 2 bytes - int bytesRead = readBytes(syncbuf, 0, 3); - - if (bytesRead != 3) - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF, null); - - //_baos.write(syncbuf, 0, 3); // E.B - - headerstring = ((syncbuf[0] << 16) & 0x00FF0000) | ((syncbuf[1] << 8) & 0x0000FF00) | ((syncbuf[2] << 0) & 0x000000FF); - - -#if THROW_ON_SYNC_LOSS - // t/DD: If we don't resync in a reasonable amount of time, - // throw an exception - int bytesSkipped = 0; - bool lostSyncYet = false; -#endif - - do - { - headerstring <<= 8; - - if (readBytes(syncbuf, 3, 1) != 1) - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF, null); - - //_baos.write(syncbuf, 3, 1); // E.B - - headerstring |= (syncbuf[3] & 0x000000FF); - - sync = isSyncMark(headerstring, syncmode, syncword); - -#if THROW_ON_SYNC_LOSS - // Just for debugging -- if we lost sync, bitch - if (!sync && !lostSyncYet) - { - lostSyncYet = true; - Trace.WriteLine( "Lost Sync :(", "Bitstream" ); - } - - if (lostSyncYet && sync) - { - Trace.WriteLine( "Found Sync", "Bitstream" ); - } - - - // If we haven't resynced within a frame (or so) give up and - // throw an exception. (Could try harder?) - ++ bytesSkipped; - if ((bytesSkipped % 2048) == 0) // A paranoia check -- is the code hanging in a loop here? - { - Trace.WriteLine( "Sync still not found", "Bitstream" ); - // throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, - // null); - } -#endif - - } - while (!sync); - - //current_frame_number++; - //if (last_frame_number < current_frame_number) last_frame_number = current_frame_number; - - return headerstring; - } - - public bool isSyncMark(int headerstring, int syncmode, int word) - { - bool sync = false; - - if (syncmode == INITIAL_SYNC) - { - //sync = ((headerstring & 0xFFF00000) == 0xFFF00000); - sync = ((headerstring & 0xFFE00000) == 0xFFE00000); // SZD: MPEG 2.5 - } - else - { - //sync = ((headerstring & 0xFFF80C00) == word) - sync = ((headerstring & 0xFFE00000) == 0xFFE00000) // ROB -- THIS IS PROBABLY WRONG. A WEAKER CHECK. - && (((headerstring & 0x000000C0) == 0x000000C0) == single_ch_mode); - } - - // filter out invalid sample rate - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 10)) & 3) != 3); - if (!sync) Trace.WriteLine("INVALID SAMPLE RATE DETECTED", "Bitstream"); - } - // filter out invalid layer - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 17)) & 3) != 0); - if (!sync) Trace.WriteLine("INVALID LAYER DETECTED", "Bitstream"); - } - // filter out invalid version - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 19)) & 3) != 1); - if (!sync) Console.WriteLine("INVALID VERSION DETECTED"); - } - - return sync; - } - - /// Reads the data for the next frame. The frame is not parsed - /// until parse frame is called. - /// - internal void read_frame_data(int bytesize) - { - int numread = 0; - - readFully(frame_bytes, 0, bytesize); - framesize = bytesize; - wordpointer = - 1; - bitindex = - 1; - } - - /// Parses the data previously read with read_frame_data(). - /// - internal void parse_frame() - { - // Convert Bytes read to int - int b = 0; - sbyte[] byteread = frame_bytes; - int bytesize = framesize; - - for (int k = 0; k < bytesize; k = k + 4) - { - int convert = 0; - sbyte b0 = 0; - sbyte b1 = 0; - sbyte b2 = 0; - sbyte b3 = 0; - b0 = byteread[k]; - if (k + 1 < bytesize) - b1 = byteread[k + 1]; - if (k + 2 < bytesize) - b2 = byteread[k + 2]; - if (k + 3 < bytesize) - b3 = byteread[k + 3]; - framebuffer[b++] = ((b0 << 24) & (int) SupportClass.Identity(0xFF000000)) | ((b1 << 16) & 0x00FF0000) | ((b2 << 8) & 0x0000FF00) | (b3 & 0x000000FF); - } - - wordpointer = 0; - bitindex = 0; - } - - /// Read bits from buffer into the lower bits of an unsigned int. - /// The LSB contains the latest read bit of the stream. - /// (1 <= number_of_bits <= 16) - /// - public int get_bits(int number_of_bits) - { - - int returnvalue = 0; - int sum = bitindex + number_of_bits; - - // E.B - // There is a problem here, wordpointer could be -1 ?! - if (wordpointer < 0) - wordpointer = 0; - // E.B : End. - - if (sum <= 32) - { - // all bits contained in *wordpointer - returnvalue = (SupportClass.URShift(framebuffer[wordpointer], (32 - sum))) & bitmask[number_of_bits]; - // returnvalue = (wordpointer[0] >> (32 - sum)) & bitmask[number_of_bits]; - if ((bitindex += number_of_bits) == 32) - { - bitindex = 0; - wordpointer++; // added by me! - } - return returnvalue; - } - - // Magouille a Voir - //((short[])&returnvalue)[0] = ((short[])wordpointer + 1)[0]; - //wordpointer++; // Added by me! - //((short[])&returnvalue + 1)[0] = ((short[])wordpointer)[0]; - int Right = (framebuffer[wordpointer] & 0x0000FFFF); - wordpointer++; - int Left = (framebuffer[wordpointer] & (int) SupportClass.Identity(0xFFFF0000)); - returnvalue = ((Right << 16) & (int) SupportClass.Identity(0xFFFF0000)) | ((SupportClass.URShift(Left, 16)) & 0x0000FFFF); - - returnvalue = SupportClass.URShift(returnvalue, 48 - sum); // returnvalue >>= 16 - (number_of_bits - (32 - bitindex)) - returnvalue &= bitmask[number_of_bits]; - bitindex = sum - 32; - return returnvalue; - } - - /// Set the word we want to sync the header to. - /// In Big-Endian byte order - /// - internal void set_syncword(int syncword0) - { - syncword = syncword0 & unchecked((int)0xFFFFFF3F); - single_ch_mode = ((syncword0 & 0x000000C0) == 0x000000C0); - } - /// Reads the exact number of bytes from the source - /// input stream into a byte array. - /// * - /// - /// byte array to read the specified number - /// of bytes into. - /// - /// index in the array where the first byte - /// read should be stored. - /// - /// number of bytes to read. - /// * - /// - /// BitstreamException is thrown if the specified - /// number of bytes could not be read from the stream. - /// - /// - private void readFully(sbyte[] b, int offs, int len) - { - try - { - while (len > 0) - { - int bytesread = source.Read(b, offs, len); - if (bytesread == - 1 - || bytesread == 0) // t/DD -- .NET returns 0 at end-of-stream! - { - // t/DD: this really SHOULD throw an exception here... - Trace.WriteLine( "readFully -- returning success at EOF? (" + bytesread + ")", - "Bitstream" ); - while (len-- > 0) - { - b[offs++] = 0; - } - break; - //throw newBitstreamException(UNEXPECTED_EOF, new EOFException()); - } - - offs += bytesread; - len -= bytesread; - } - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - } - - /// Simlar to readFully, but doesn't throw exception when - /// EOF is reached. - /// - private int readBytes(sbyte[] b, int offs, int len) - { - int totalBytesRead = 0; - try - { - while (len > 0) - { - int bytesread = source.Read(b, offs, len); -// for (int i = 0; i < len; i++) b[i] = (sbyte)Temp[i]; - if (bytesread == - 1 || bytesread == 0) - { - break; - } - totalBytesRead += bytesread; - offs += bytesread; - len -= bytesread; - } - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - return totalBytesRead; - } - - /// Returns ID3v2 tags. - /// - /*public ByteArrayOutputStream getID3v2() - { - return _baos; - }*/ - } -} diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamErrors.cs b/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamErrors.cs deleted file mode 100644 index 3bd01083a..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamErrors.cs +++ /dev/null @@ -1,67 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// This interface describes all error codes that can be thrown - /// in BistreamExceptions. - /// - /// - /// BitstreamException - /// - /// - /// MDM 12/12/99 - /// @since 0.0.6 - /// - /// - - internal struct BitstreamErrors_Fields{ - public readonly static int UNKNOWN_ERROR; - public readonly static int UNKNOWN_SAMPLE_RATE; - public readonly static int STREAM_ERROR; - public readonly static int UNEXPECTED_EOF; - public readonly static int STREAM_EOF; - public readonly static int BITSTREAM_LAST = 0x1ff; - static BitstreamErrors_Fields() - { - UNKNOWN_ERROR = javazoom.jl.decoder.JavaLayerErrors_Fields.BITSTREAM_ERROR + 0; - UNKNOWN_SAMPLE_RATE = javazoom.jl.decoder.JavaLayerErrors_Fields.BITSTREAM_ERROR + 1; - STREAM_ERROR = javazoom.jl.decoder.JavaLayerErrors_Fields.BITSTREAM_ERROR + 2; - UNEXPECTED_EOF = javazoom.jl.decoder.JavaLayerErrors_Fields.BITSTREAM_ERROR + 3; - STREAM_EOF = javazoom.jl.decoder.JavaLayerErrors_Fields.BITSTREAM_ERROR + 4; - } - } - internal interface BitstreamErrors : JavaLayerErrors - { - //UPGRADE_NOTE: Members of interface 'BitstreamErrors' were extracted into structure 'BitstreamErrors_Fields'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1045"' - /// An undeterminable error occurred. - /// - /// The header describes an unknown sample rate. - /// - /// A problem occurred reading from the stream. - /// - /// The end of the stream was reached prematurely. - /// - /// The end of the stream was reached. - /// - /// - /// - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamException.cs b/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamException.cs deleted file mode 100644 index 4be8abcb2..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamException.cs +++ /dev/null @@ -1,85 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -using javazoom.jl.decoder; -using javazoom; -using javazoom.jl.converter; -using javazoom.jl; -namespace Mp3Sharp -{ - using System; - - /// Instances of BitstreamException are thrown - /// when operations on a Bitstream fail. - ///

- /// The exception provides details of the exception condition - /// in two ways: - ///

  1. - /// as an error-code describing the nature of the error - ///


  2. - /// as the Throwable instance, if any, that was thrown - /// indicating that an exceptional condition has occurred. - ///

- /// - /// @since 0.0.6 - ///
- /// MDM 12/12/99 - /// - /// - - public class BitstreamException : Mp3SharpException, BitstreamErrors - { - private void InitBlock() - { - errorcode = javazoom.jl.decoder.BitstreamErrors_Fields.UNKNOWN_ERROR; - } - virtual public int ErrorCode - { - get - { - return errorcode; - } - - } - //UPGRADE_NOTE: The initialization of 'errorcode' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int errorcode; - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public BitstreamException(System.String msg, System.Exception t):base(msg, t) - { - InitBlock(); - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public BitstreamException(int errorcode, System.Exception t):this(getErrorString(errorcode), t) - { - InitBlock(); - this.errorcode = errorcode; - } - - - - static public System.String getErrorString(int errorcode) - { - // REVIEW: use resource bundle to map error codes - // to locale-sensitive strings. - - return "Bitstream errorcode " + System.Convert.ToString(errorcode, 16); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamOLD.cs b/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamOLD.cs deleted file mode 100644 index 71e9affbc..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/BitstreamOLD.cs +++ /dev/null @@ -1,542 +0,0 @@ -using Support; -/* -* 12/12/99 Based on Ibitstream. Exceptions thrown on errors, -* Tempoarily removed seek functionality. mdm@techie.com -* -* 02/12/99 : Java Conversion by E.B , ebsp@iname.com , JavaLayer -* -*---------------------------------------------------------------------- -* @(#) ibitstream.h 1.5, last edit: 6/15/94 16:55:34 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -* -* Changes made by Jeff Tsay : -* 04/14/97 : Added function prototypes for new syncing and seeking -* mechanisms. Also made this file portable. -*----------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// The Bistream class is responsible for parsing - /// an MPEG audio bitstream. - /// * - /// REVIEW: much of the parsing currently occurs in the - /// various decoders. This should be moved into this class and associated - /// inner classes. - /// - public sealed class Bitstream : BitstreamErrors - { - private void InitBlock() - { - crc = new Crc16[1]; - syncbuf = new sbyte[4]; - frame_bytes = new sbyte[BUFFER_INT_SIZE * 4]; - framebuffer = new int[BUFFER_INT_SIZE]; - header = new Header(); - } - - /// Syncrhronization control constant for the initial - /// synchronization to the start of a frame. - /// - internal static sbyte INITIAL_SYNC = 0; - - /// Syncrhronization control constant for non-iniital frame - /// synchronizations. - /// - internal static sbyte STRICT_SYNC = 1; - - // max. 1730 bytes per frame: 144 * 384kbit/s / 32000 Hz + 2 Bytes CRC - /// Maximum size of the frame buffer. - /// - private const int BUFFER_INT_SIZE = 433; - - - /// The frame buffer that holds the data for the current frame. - /// - //UPGRADE_NOTE: Final was removed from the declaration of 'framebuffer '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'framebuffer' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int[] framebuffer; - - /// Number of valid bytes in the frame buffer. - /// - private int framesize; - - /// The bytes read from the stream. - /// - //UPGRADE_NOTE: The initialization of 'frame_bytes' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte[] frame_bytes; - - /// Index into framebuffer where the next bits are - /// retrieved. - /// - private int wordpointer; - - /// Number (0-31, from MSB to LSB) of next bit for get_bits() - /// - private int bitindex; - - /// The current specified syncword - /// - private int syncword; - - /// * - /// - private bool single_ch_mode; - //private int current_frame_number; - //private int last_frame_number; - - //UPGRADE_NOTE: Final was removed from the declaration of 'bitmask '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private int[] bitmask = new int[]{0, 0x00000001, 0x00000003, 0x00000007, 0x0000000F, 0x0000001F, 0x0000003F, 0x0000007F, 0x000000FF, 0x000001FF, 0x000003FF, 0x000007FF, 0x00000FFF, 0x00001FFF, 0x00003FFF, 0x00007FFF, 0x0000FFFF, 0x0001FFFF}; - - //UPGRADE_NOTE: Final was removed from the declaration of 'source '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private BackStream source; - - //UPGRADE_NOTE: Final was removed from the declaration of 'header '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'header' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Header header; - - //UPGRADE_NOTE: Final was removed from the declaration of 'syncbuf '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'syncbuf' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte[] syncbuf; - - //UPGRADE_NOTE: The initialization of 'crc' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Crc16[] crc; - - //private ByteArrayOutputStream _baos = null; // E.B - - - /// Construct a IBitstream that reads data from a - /// given InputStream. - /// * - /// - /// InputStream to read from. - /// - /// - public Bitstream(BackStream in_Renamed) - { - InitBlock(); - if (in_Renamed == null) - throw new System.NullReferenceException("in"); - - source = in_Renamed; // ROB - fuck the SupportClass, let's roll our own. new SupportClass.BackInputStream(in_Renamed, 1024); - - //_baos = new ByteArrayOutputStream(); // E.B - - closeFrame(); - //current_frame_number = -1; - //last_frame_number = -1; - } - - public void close() - { - try - { - //UPGRADE_TODO: Method 'java.io.FilterInputStream.close' was converted to 'System.IO.BinaryReader.Close' which has a different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1073_javaioFilterInputStreamclose"' - source.Close(); - //_baos = null; - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - } - - /// Reads and parses the next frame from the input source. - /// - /// the Header describing details of the frame read, - /// or null if the end of the stream has been reached. - /// - /// - public Header readFrame() - { - Header result = null; - try - { - result = readNextFrame(); - } - catch (BitstreamException ex) - { - if (ex.ErrorCode != javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF) - { - // wrap original exception so stack trace is maintained. - throw newBitstreamException(ex.ErrorCode, ex); - } - } - return result; - } - - private Header readNextFrame() - { - if (framesize == - 1) - { - nextFrame(); - } - - return header; - } - - - /// * - /// - private void nextFrame() - { - // entire frame is read by the header class. - header.read_header(this, crc); - } - - /// Unreads the bytes read from the frame. - /// @throws BitstreamException - /// - // REVIEW: add new error codes for this. - public void unreadFrame() - { - if (wordpointer == - 1 && bitindex == - 1 && (framesize > 0)) - { - try - { - //source.UnRead(SupportClass.ToByteArray(frame_bytes), 0, framesize); - source.UnRead(framesize); - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR); - } - } - } - - public void closeFrame() - { - framesize = - 1; - wordpointer = - 1; - bitindex = - 1; - } - - /// Determines if the next 4 bytes of the stream represent a - /// frame header. - /// - public bool isSyncCurrentPosition(int syncmode) - { - int read = readBytes(syncbuf, 0, 4); - int headerstring = ((syncbuf[0] << 24) & (int) SupportClass.Identity(0xFF000000)) | ((syncbuf[1] << 16) & 0x00FF0000) | ((syncbuf[2] << 8) & 0x0000FF00) | ((syncbuf[3] << 0) & 0x000000FF); - - try - { - //source.UnRead(SupportClass.ToByteArray(syncbuf), 0, read); - source.UnRead(read); - } - catch (System.IO.IOException ex) - { - } - - bool sync = false; - switch (read) - { - - case 0: - sync = true; - break; - - case 4: - sync = isSyncMark(headerstring, syncmode, syncword); - break; - } - - return sync; - } - - - // REVIEW: this class should provide inner classes to - // parse the frame contents. Eventually, readBits will - // be removed. - public int readBits(int n) - { - return get_bits(n); - } - - public int readCheckedBits(int n) - { - // REVIEW: implement CRC check. - return get_bits(n); - } - - protected internal BitstreamException newBitstreamException(int errorcode) - { - return new BitstreamException(errorcode, null); - } - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - protected internal BitstreamException newBitstreamException(int errorcode, System.Exception throwable) - { - return new BitstreamException(errorcode, throwable); - } - - - /// Get next 32 bits from bitstream. - /// They are stored in the headerstring. - /// syncmod allows Synchro flag ID - /// The returned value is False at the end of stream. - /// - - internal int syncHeader(sbyte syncmode) - { - bool sync; - int headerstring; - - // read additinal 2 bytes - int bytesRead = readBytes(syncbuf, 0, 3); - - if (bytesRead != 3) - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF, null); - - //_baos.write(syncbuf, 0, 3); // E.B - - headerstring = ((syncbuf[0] << 16) & 0x00FF0000) | ((syncbuf[1] << 8) & 0x0000FF00) | ((syncbuf[2] << 0) & 0x000000FF); - - do - { - headerstring <<= 8; - - if (readBytes(syncbuf, 3, 1) != 1) - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_EOF, null); - - //_baos.write(syncbuf, 3, 1); // E.B - - headerstring |= (syncbuf[3] & 0x000000FF); - - sync = isSyncMark(headerstring, syncmode, syncword); - } - while (!sync); - - //current_frame_number++; - //if (last_frame_number < current_frame_number) last_frame_number = current_frame_number; - - return headerstring; - } - - public bool isSyncMark(int headerstring, int syncmode, int word) - { - bool sync = false; - - if (syncmode == INITIAL_SYNC) - { - //sync = ((headerstring & 0xFFF00000) == 0xFFF00000); - sync = ((headerstring & 0xFFE00000) == 0xFFE00000); // SZD: MPEG 2.5 - } - else - { - //sync = ((headerstring & 0xFFF80C00) == word) - sync = ((headerstring & 0xFFE00000) == 0xFFE00000) // ROB -- THIS IS PROBABLY WRONG. A WEAKER CHECK. - && (((headerstring & 0x000000C0) == 0x000000C0) == single_ch_mode); - } - - // filter out invalid sample rate - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 10)) & 3) != 3); - if (!sync) Console.WriteLine("INVALID SAMPLE RATE DETECTED"); - } - // filter out invalid layer - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 17)) & 3) != 0); - if (!sync) Console.WriteLine("INVALID LAYER DETECTED"); - } - // filter out invalid version - if (sync) - { - sync = (((SupportClass.URShift(headerstring, 19)) & 3) != 1); - if (!sync) Console.WriteLine("INVALID VERSION DETECTED"); - } - - return sync; - } - - /// Reads the data for the next frame. The frame is not parsed - /// until parse frame is called. - /// - internal void read_frame_data(int bytesize) - { - int numread = 0; - - readFully(frame_bytes, 0, bytesize); - framesize = bytesize; - wordpointer = - 1; - bitindex = - 1; - } - - /// Parses the data previously read with read_frame_data(). - /// - internal void parse_frame() - { - // Convert Bytes read to int - int b = 0; - sbyte[] byteread = frame_bytes; - int bytesize = framesize; - - for (int k = 0; k < bytesize; k = k + 4) - { - int convert = 0; - sbyte b0 = 0; - sbyte b1 = 0; - sbyte b2 = 0; - sbyte b3 = 0; - b0 = byteread[k]; - if (k + 1 < bytesize) - b1 = byteread[k + 1]; - if (k + 2 < bytesize) - b2 = byteread[k + 2]; - if (k + 3 < bytesize) - b3 = byteread[k + 3]; - framebuffer[b++] = ((b0 << 24) & (int) SupportClass.Identity(0xFF000000)) | ((b1 << 16) & 0x00FF0000) | ((b2 << 8) & 0x0000FF00) | (b3 & 0x000000FF); - } - - wordpointer = 0; - bitindex = 0; - } - - /// Read bits from buffer into the lower bits of an unsigned int. - /// The LSB contains the latest read bit of the stream. - /// (1 <= number_of_bits <= 16) - /// - public int get_bits(int number_of_bits) - { - - int returnvalue = 0; - int sum = bitindex + number_of_bits; - - // E.B - // There is a problem here, wordpointer could be -1 ?! - if (wordpointer < 0) - wordpointer = 0; - // E.B : End. - - if (sum <= 32) - { - // all bits contained in *wordpointer - returnvalue = (SupportClass.URShift(framebuffer[wordpointer], (32 - sum))) & bitmask[number_of_bits]; - // returnvalue = (wordpointer[0] >> (32 - sum)) & bitmask[number_of_bits]; - if ((bitindex += number_of_bits) == 32) - { - bitindex = 0; - wordpointer++; // added by me! - } - return returnvalue; - } - - // Magouille a Voir - //((short[])&returnvalue)[0] = ((short[])wordpointer + 1)[0]; - //wordpointer++; // Added by me! - //((short[])&returnvalue + 1)[0] = ((short[])wordpointer)[0]; - int Right = (framebuffer[wordpointer] & 0x0000FFFF); - wordpointer++; - int Left = (framebuffer[wordpointer] & (int) SupportClass.Identity(0xFFFF0000)); - returnvalue = ((Right << 16) & (int) SupportClass.Identity(0xFFFF0000)) | ((SupportClass.URShift(Left, 16)) & 0x0000FFFF); - - returnvalue = SupportClass.URShift(returnvalue, 48 - sum); // returnvalue >>= 16 - (number_of_bits - (32 - bitindex)) - returnvalue &= bitmask[number_of_bits]; - bitindex = sum - 32; - return returnvalue; - } - - /// Set the word we want to sync the header to. - /// In Big-Endian byte order - /// - internal void set_syncword(int syncword0) - { - syncword = syncword0 & unchecked((int)0xFFFFFF3F); - single_ch_mode = ((syncword0 & 0x000000C0) == 0x000000C0); - } - /// Reads the exact number of bytes from the source - /// input stream into a byte array. - /// * - /// - /// byte array to read the specified number - /// of bytes into. - /// - /// index in the array where the first byte - /// read should be stored. - /// - /// number of bytes to read. - /// * - /// - /// BitstreamException is thrown if the specified - /// number of bytes could not be read from the stream. - /// - /// - private void readFully(sbyte[] b, int offs, int len) - { - try - { - while (len > 0) - { - int bytesread = source.Read(b, offs, len); - if (bytesread == - 1) - { - while (len-- > 0) - { - b[offs++] = 0; - } - break; - //throw newBitstreamException(UNEXPECTED_EOF, new EOFException()); - } - - offs += bytesread; - len -= bytesread; - } - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - } - - /// Simlar to readFully, but doesn't throw exception when - /// EOF is reached. - /// - private int readBytes(sbyte[] b, int offs, int len) - { - int totalBytesRead = 0; - try - { - while (len > 0) - { - int bytesread = source.Read(b, offs, len); -// for (int i = 0; i < len; i++) b[i] = (sbyte)Temp[i]; - if (bytesread == - 1 || bytesread == 0) - { - break; - } - totalBytesRead += bytesread; - offs += bytesread; - len -= bytesread; - } - } - catch (System.IO.IOException ex) - { - throw newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.STREAM_ERROR, ex); - } - return totalBytesRead; - } - - /// Returns ID3v2 tags. - /// - /*public ByteArrayOutputStream getID3v2() - { - return _baos; - }*/ - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Control.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Control.cs deleted file mode 100644 index 099a4843a..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Control.cs +++ /dev/null @@ -1,39 +0,0 @@ -namespace javazoom.jl.decoder -{ - using System; - - /// Work in progress. - /// - - internal interface Control - { - bool Playing - { - get; - - } - bool RandomAccess - { - get; - - } - /// Retrieves the current position. - /// - /// - /// - double Position - { - get; - - set; - - } - /// Starts playback of the media presented by this control. - /// - void start(); - /// Stops playback of the media presented by this control. - /// - void stop(); - void pause(); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Crc16.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Crc16.cs deleted file mode 100644 index 223f945e4..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Crc16.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Support; -/* -* 02/12/99 : Java Conversion by E.B , ebsp@iname.com, JavaLayer -* -*----------------------------------------------------------------------- -* @(#) crc.h 1.5, last edit: 6/15/94 16:55:32 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*----------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// 16-Bit CRC checksum - /// - internal sealed class Crc16 - { - //UPGRADE_NOTE: The initialization of 'polynomial' was moved to static method 'javazoom.jl.decoder.Crc16'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private static short polynomial; - private short crc; - - /// Dummy Constructor - /// - public Crc16() - { - crc = (short) SupportClass.Identity(0xFFFF); - } - - /// Feed a bitstring to the crc calculation (0 < length <= 32). - /// - public void add_bits(int bitstring, int length) - { - int bitmask = 1 << (length - 1); - do - if (((crc & 0x8000) == 0) ^ ((bitstring & bitmask) == 0)) - { - crc <<= 1; - crc ^= polynomial; - } - else - crc <<= 1; - while ((bitmask = SupportClass.URShift(bitmask, 1)) != 0); - } - - /// Return the calculated checksum. - /// Erase it for next calls to add_bits(). - /// - public short checksum() - { - short sum = crc; - crc = (short) SupportClass.Identity(0xFFFF); - return sum; - } - static Crc16() - { - polynomial = (short) SupportClass.Identity(0x8005); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Decoder.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Decoder.cs deleted file mode 100644 index 85eb37559..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Decoder.cs +++ /dev/null @@ -1,403 +0,0 @@ -/* -* 1/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// The Decoder class encapsulates the details of - /// decoding an MPEG audio frame. - /// - /// - /// MDM - /// - /// 0.0.7 12/12/99 - /// @since 0.0.5 - /// - /// - - internal class Decoder : DecoderErrors - { - private void InitBlock() - { - equalizer = new Equalizer(); - } - static public Params DefaultParams - { - get - { - return (Params) DEFAULT_PARAMS.Clone(); // MemberwiseClone(); - } - - } - virtual public Equalizer Equalizer - { - set - { - if (value == null) - value = decoder.Equalizer.PASS_THRU_EQ; - - equalizer.FromEqualizer = value; - - float[] factors = equalizer.BandFactors; - if (filter1 != null) - filter1.EQ = factors; - - if (filter2 != null) - filter2.EQ = factors; - } - - } - /// Changes the output buffer. This will take effect the next time - /// decodeFrame() is called. - /// - virtual public Obuffer OutputBuffer - { - set - { - output = value; - } - - } - /// Retrieves the sample frequency of the PCM samples output - /// by this decoder. This typically corresponds to the sample - /// rate encoded in the MPEG audio stream. - /// - /// - /// sample rate (in Hz) of the samples written to the - /// output buffer when decoding. - /// - /// - virtual public int OutputFrequency - { - get - { - return outputFrequency; - } - - } - /// Retrieves the number of channels of PCM samples output by - /// this decoder. This usually corresponds to the number of - /// channels in the MPEG audio stream, although it may differ. - /// - /// - /// The number of output channels in the decoded samples: 1 - /// for mono, or 2 for stereo. - /// - /// - /// - virtual public int OutputChannels - { - get - { - return outputChannels; - } - - } - /// Retrieves the maximum number of samples that will be written to - /// the output buffer when one frame is decoded. This can be used to - /// help calculate the size of other buffers whose size is based upon - /// the number of samples written to the output buffer. NB: this is - /// an upper bound and fewer samples may actually be written, depending - /// upon the sample rate and number of channels. - /// - /// - /// The maximum number of samples that are written to the - /// output buffer when decoding a single frame of MPEG audio. - /// - /// - virtual public int OutputBlockSize - { - get - { - return javazoom.jl.decoder.Obuffer.OBUFFERSIZE; - } - - } - //UPGRADE_NOTE: Final was removed from the declaration of 'DEFAULT_PARAMS '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly Params DEFAULT_PARAMS = new Params(); - - /// The Bistream from which the MPEG audio frames are read. - /// - //private Bitstream stream; - - /// The Obuffer instance that will receive the decoded - /// PCM samples. - /// - private Obuffer output; - - /// Synthesis filter for the left channel. - /// - private SynthesisFilter filter1; - - /// Sythesis filter for the right channel. - /// - private SynthesisFilter filter2; - - /// The decoder used to decode layer III frames. - /// - private LayerIIIDecoder l3decoder; - private LayerIIDecoder l2decoder; - private LayerIDecoder l1decoder; - - private int outputFrequency; - private int outputChannels; - - //UPGRADE_NOTE: The initialization of 'equalizer' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Equalizer equalizer; - - private Params params_Renamed; - - private bool initialized; - - - /// Creates a new Decoder instance with default - /// parameters. - /// - - public Decoder():this(null) - { - InitBlock(); - } - - /// Creates a new Decoder instance with default - /// parameters. - /// - /// - /// Params instance that describes - /// the customizable aspects of the decoder. - /// - /// - public Decoder(Params params0) - { - InitBlock(); - if (params0 == null) - params0 = DEFAULT_PARAMS; - - params_Renamed = params0; - - Equalizer eq = params_Renamed.InitialEqualizerSettings; - if (eq != null) - { - equalizer.FromEqualizer = eq; - } - } - - - - /// Decodes one frame from an MPEG audio bitstream. - /// - /// - /// header describing the frame to decode. - /// - /// bistream that provides the bits for te body of the frame. - /// - /// - /// A SampleBuffer containing the decoded samples. - /// - /// - public virtual Obuffer decodeFrame(Header header, Bitstream stream) - { - if (!initialized) - { - initialize(header); - } - - int layer = header.layer(); - - output.clear_buffer(); - - FrameDecoder decoder = retrieveDecoder(header, stream, layer); - - decoder.decodeFrame(); - - output.write_buffer(1); - - return output; - } - - - - - - - protected internal virtual DecoderException newDecoderException(int errorcode) - { - return new DecoderException(errorcode, null); - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - protected internal virtual DecoderException newDecoderException(int errorcode, System.Exception throwable) - { - return new DecoderException(errorcode, throwable); - } - - protected internal virtual FrameDecoder retrieveDecoder(Header header, Bitstream stream, int layer) - { - FrameDecoder decoder = null; - - // REVIEW: allow channel output selection type - // (LEFT, RIGHT, BOTH, DOWNMIX) - switch (layer) - { - - case 3: - if (l3decoder == null) - { - l3decoder = new LayerIIIDecoder(stream, header, filter1, filter2, output, (int)OutputChannelsEnum.BOTH_CHANNELS); - } - - decoder = l3decoder; - break; - - case 2: - if (l2decoder == null) - { - l2decoder = new LayerIIDecoder(); - l2decoder.create(stream, header, filter1, filter2, output, (int)OutputChannelsEnum.BOTH_CHANNELS); - } - decoder = l2decoder; - break; - - case 1: - if (l1decoder == null) - { - l1decoder = new LayerIDecoder(); - l1decoder.create(stream, header, filter1, filter2, output, (int)OutputChannelsEnum.BOTH_CHANNELS); - } - decoder = l1decoder; - break; - } - - if (decoder == null) - { - throw newDecoderException(javazoom.jl.decoder.DecoderErrors_Fields.UNSUPPORTED_LAYER, null); - } - - return decoder; - } - - private void initialize(Header header) - { - - // REVIEW: allow customizable scale factor - float scalefactor = 32700.0f; - - int mode = header.mode(); - int layer = header.layer(); - int channels = mode == Header.SINGLE_CHANNEL?1:2; - - - // set up output buffer if not set up by client. - if (output == null) - output = new SampleBuffer(header.frequency(), channels); - - float[] factors = equalizer.BandFactors; - //Console.WriteLine("NOT CREATING SYNTHESIS FILTERS"); - filter1 = new SynthesisFilter(0, scalefactor, factors); - - // REVIEW: allow mono output for stereo - if (channels == 2) - filter2 = new SynthesisFilter(1, scalefactor, factors); - - outputChannels = channels; - outputFrequency = header.frequency(); - - initialized = true; - } - - /// The Params class presents the customizable - /// aspects of the decoder. - ///

- /// Instances of this class are not thread safe. - ///

- internal class Params : System.ICloneable - { - private void InitBlock() - { - outputChannels = OutputChannels.BOTH; - equalizer = new Equalizer(); - } - virtual public OutputChannels OutputChannels - { - get - { - return outputChannels; - } - - set - { - if (value == null) - throw new System.NullReferenceException("out"); - - outputChannels = value; - } - - } - /// Retrieves the equalizer settings that the decoder's equalizer - /// will be initialized from. - ///

- /// The Equalizer instance returned - /// cannot be changed in real time to affect the - /// decoder output as it is used only to initialize the decoders - /// EQ settings. To affect the decoder's output in realtime, - /// use the Equalizer returned from the getEqualizer() method on - /// the decoder. - /// - ///

- /// The Equalizer used to initialize the - /// EQ settings of the decoder. - /// - /// - virtual public Equalizer InitialEqualizerSettings - { - get - { - return equalizer; - } - - } - //UPGRADE_NOTE: The initialization of 'outputChannels' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private OutputChannels outputChannels; - - //UPGRADE_NOTE: The initialization of 'equalizer' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private Equalizer equalizer; - - public Params() - { - } - - //UPGRADE_TODO: The equivalent of method 'java.lang.Object.clone' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - public System.Object Clone() - { - //UPGRADE_NOTE: Exception 'java.lang.CloneNotSupportedException' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - try - { - return base.MemberwiseClone(); - } - catch (System.Exception ex) - { - throw new System.ApplicationException(this + ": " + ex); - } - } - - - - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/DecoderErrors.cs b/Other/libs/mp3sharp/mp3sharp/decoder/DecoderErrors.cs deleted file mode 100644 index c92db8bf1..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/DecoderErrors.cs +++ /dev/null @@ -1,46 +0,0 @@ -/* -* 1/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// This interface provides constants describing the error - /// codes used by the Decoder to indicate errors. - /// - /// - /// MDM - /// - /// - - internal struct DecoderErrors_Fields{ - public readonly static int UNKNOWN_ERROR; - public readonly static int UNSUPPORTED_LAYER; - static DecoderErrors_Fields() - { - UNKNOWN_ERROR = javazoom.jl.decoder.JavaLayerErrors_Fields.DECODER_ERROR + 0; - UNSUPPORTED_LAYER = javazoom.jl.decoder.JavaLayerErrors_Fields.DECODER_ERROR + 1; - } - } - internal interface DecoderErrors : JavaLayerErrors - { - //UPGRADE_NOTE: Members of interface 'DecoderErrors' were extracted into structure 'DecoderErrors_Fields'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1045"' - /// Layer not supported by the decoder. - /// - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/DecoderException.cs b/Other/libs/mp3sharp/mp3sharp/decoder/DecoderException.cs deleted file mode 100644 index 2f9b837ab..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/DecoderException.cs +++ /dev/null @@ -1,72 +0,0 @@ -using Mp3Sharp; -/* -* 1/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// The DecoderException represents the class of - /// errors that can occur when decoding MPEG audio. - /// - /// - /// MDM - /// - /// - - internal class DecoderException:Mp3SharpException, DecoderErrors - { - private void InitBlock() - { - errorcode = javazoom.jl.decoder.DecoderErrors_Fields.UNKNOWN_ERROR; - } - virtual public int ErrorCode - { - get - { - return errorcode; - } - - } - //UPGRADE_NOTE: The initialization of 'errorcode' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int errorcode; - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public DecoderException(System.String msg, System.Exception t):base(msg, t) - { - InitBlock(); - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public DecoderException(int errorcode, System.Exception t):this(getErrorString(errorcode), t) - { - InitBlock(); - this.errorcode = errorcode; - } - - - - static public System.String getErrorString(int errorcode) - { - // REVIEW: use resource file to map error codes - // to locale-sensitive strings. - - return "Decoder errorcode " + System.Convert.ToString(errorcode, 16); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Equalizer.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Equalizer.cs deleted file mode 100644 index f388c433d..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Equalizer.cs +++ /dev/null @@ -1,257 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// The Equalizer class can be used to specify - /// equalization settings for the MPEG audio decoder. - ///

- /// The equalizer consists of 32 band-pass filters. - /// Each band of the equalizer can take on a fractional value between - /// -1.0 and +1.0. - /// At -1.0, the input signal is attenuated by 6dB, at +1.0 the signal is - /// amplified by 6dB. - /// - ///

- /// Decoder - /// - /// - /// MDM - /// - /// - - internal class Equalizer - { - private void InitBlock() - { - settings = new float[BANDS]; - } - public virtual float[] FromFloatArray - { - set - { - reset(); - int max = (value.Length > BANDS)?BANDS:value.Length; - - for (int i = 0; i < max; i++) - { - settings[i] = limit(value[i]); - } - } - - } - //UPGRADE_TODO: Method 'setFrom' was converted to a set modifier. This name conflicts with another property. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1137"' - /// Sets the bands of this equalizer to the value the bands of - /// another equalizer. Bands that are not present in both equalizers are ignored. - /// - public virtual Equalizer FromEqualizer - { - set - { - if (value != this) - { - FromFloatArray = value.settings; - } - } - - } - //UPGRADE_TODO: Method 'setFrom' was converted to a set modifier. This name conflicts with another property. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1137"' - public virtual EQFunction FromEQFunction - { - set - { - reset(); - int max = BANDS; - - for (int i = 0; i < max; i++) - { - settings[i] = limit(value.getBand(i)); - } - } - - } - /// Retrieves the number of bands present in this equalizer. - /// - public virtual int BandCount - { - get - { - return settings.Length; - } - - } - /// Retrieves an array of floats whose values represent a - /// scaling factor that can be applied to linear samples - /// in each band to provide the equalization represented by - /// this instance. - /// - /// - /// an array of factors that can be applied to the - /// subbands. - /// - /// - internal virtual float[] BandFactors - { - get - { - float[] factors = new float[BANDS]; - for (int i = 0, maxCount = BANDS; i < maxCount; i++) - { - factors[i] = getBandFactor(settings[i]); - } - - return factors; - } - - } - /// Equalizer setting to denote that a given band will not be - /// present in the output signal. - /// - //UPGRADE_NOTE: Final was removed from the declaration of 'BAND_NOT_PRESENT '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float BAND_NOT_PRESENT = System.Single.NegativeInfinity; - - //UPGRADE_NOTE: Final was removed from the declaration of 'PASS_THRU_EQ '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly Equalizer PASS_THRU_EQ = new Equalizer(); - - private const int BANDS = 32; - - //UPGRADE_NOTE: Final was removed from the declaration of 'settings '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'settings' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private float[] settings; - - /// Creates a new Equalizer instance. - /// - public Equalizer() - { - InitBlock(); - } - - // private Equalizer(float b1, float b2, float b3, float b4, float b5, - // float b6, float b7, float b8, float b9, float b10, float b11, - // float b12, float b13, float b14, float b15, float b16, - // float b17, float b18, float b19, float b20); - - public Equalizer(float[] settings) - { - InitBlock(); - FromFloatArray = settings; - } - - public Equalizer(EQFunction eq) - { - InitBlock(); - FromEQFunction = eq; - } - - - - - - - - /// Sets all bands to 0.0 - /// - public void reset() - { - for (int i = 0; i < BANDS; i++) - { - settings[i] = 0.0f; - } - } - - - - public float setBand(int band, float neweq) - { - float eq = 0.0f; - - if ((band >= 0) && (band < BANDS)) - { - eq = settings[band]; - settings[band] = limit(neweq); - } - - return eq; - } - - - - /// Retrieves the eq setting for a given band. - /// - public float getBand(int band) - { - float eq = 0.0f; - - if ((band >= 0) && (band < BANDS)) - { - eq = settings[band]; - } - - return eq; - } - - private float limit(float eq) - { - if (eq == BAND_NOT_PRESENT) - return eq; - if (eq > 1.0f) - return 1.0f; - if (eq < - 1.0f) - return - 1.0f; - - return eq; - } - - - /// Converts an equalizer band setting to a sample factor. - /// The factor is determined by the function f = 2^n where - /// n is the equalizer band setting in the range [-1.0,1.0]. - /// - /// - internal float getBandFactor(float eq) - { - if (eq == BAND_NOT_PRESENT) - return 0.0f; - - float f = (float) System.Math.Pow(2.0, eq); - return f; - } - - - abstract internal class EQFunction - { - /// Returns the setting of a band in the equalizer. - /// - /// - /// index of the band to retrieve the setting - /// for. - /// - /// - /// the setting of the specified band. This is a value between - /// -1 and +1. - /// - /// - public virtual float getBand(int band) - { - return 0.0f; - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/FrameDecoder.cs b/Other/libs/mp3sharp/mp3sharp/decoder/FrameDecoder.cs deleted file mode 100644 index 248678b18..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/FrameDecoder.cs +++ /dev/null @@ -1,36 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Implementations of FrameDecoder are responsible for decoding - /// an MPEG audio frame. - /// - /// - //REVIEW: the interface currently is too thin. There should be - // methods to specify the output buffer, the synthesis filters and - // possibly other objects used by the decoder. - internal interface FrameDecoder - { - /// Decodes one frame of MPEG audio. - /// - void decodeFrame(); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Header.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Header.cs deleted file mode 100644 index 9577e6275..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Header.cs +++ /dev/null @@ -1,570 +0,0 @@ -using Support; -/* -* 02/13/99 : Java Conversion by E.B , ebsp@iname.com -* -*--------------------------------------------------------------------------- -* Declarations for MPEG header class -* A few layer III, MPEG-2 LSF, and seeking modifications made by Jeff Tsay. -* Last modified : 04/19/97 -* -* @(#) header.h 1.7, last edit: 6/15/94 16:55:33 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*-------------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Class for extracting information from a frame header. - /// * - /// * - /// - // TODO: move strings into resources - - internal class Header - { - private void InitBlock() - { - syncmode = Bitstream.INITIAL_SYNC; - } - /// Returns synchronized header. - /// - public virtual int SyncHeader - { - // E.B - - get - { - return _headerstring; - } - - } - public static readonly int[][] frequencies = {new int[]{22050, 24000, 16000, 1}, new int[]{44100, 48000, 32000, 1}, new int[]{11025, 12000, 8000, 1}}; // SZD: MPEG25 - - /// Constant for MPEG-2 LSF version - /// - public const int MPEG2_LSF = 0; - public const int MPEG25_LSF = 2; // SZD - - /// Constant for MPEG-1 version - /// - public const int MPEG1 = 1; - - public const int STEREO = 0; - public const int JOINT_STEREO = 1; - public const int DUAL_CHANNEL = 2; - public const int SINGLE_CHANNEL = 3; - public const int FOURTYFOUR_POINT_ONE = 0; - public const int FOURTYEIGHT = 1; - public const int THIRTYTWO = 2; - - private int h_layer, h_protection_bit, h_bitrate_index, h_padding_bit, h_mode_extension; - private int h_version; - private int h_mode; - private int h_sample_frequency; - private int h_number_of_subbands, h_intensity_stereo_bound; - private bool h_copyright, h_original; - //UPGRADE_NOTE: The initialization of 'syncmode' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte syncmode; - private Crc16 crc; - - public short checksum; - public int framesize; - public int nSlots; - - private int _headerstring = - 1; - // E.B - - internal Header() - { - InitBlock(); - } - public override System.String ToString() - { - System.Text.StringBuilder buffer = new System.Text.StringBuilder(200); - buffer.Append("Layer "); - buffer.Append(layer_string()); - buffer.Append(" frame "); - buffer.Append(mode_string()); - buffer.Append(' '); - buffer.Append(version_string()); - if (!checksums()) - buffer.Append(" no"); - buffer.Append(" checksums"); - buffer.Append(' '); - buffer.Append(sample_frequency_string()); - buffer.Append(','); - buffer.Append(' '); - buffer.Append(bitrate_string()); - - System.String s = buffer.ToString(); - return s; - } - - /// Read a 32-bit header from the bitstream. - /// - internal void read_header(Bitstream stream, Crc16[] crcp) - { - int headerstring; - int channel_bitrate; - - bool sync = false; - - do - { - - headerstring = stream.syncHeader(syncmode); - _headerstring = headerstring; // E.B - - if (syncmode == Bitstream.INITIAL_SYNC) - { - h_version = ((SupportClass.URShift(headerstring, 19)) & 1); - if (((SupportClass.URShift(headerstring, 20)) & 1) == 0) - // SZD: MPEG2.5 detection - if (h_version == MPEG2_LSF) - h_version = MPEG25_LSF; - else - throw stream.newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.UNKNOWN_ERROR); - - - if ((h_sample_frequency = ((SupportClass.URShift(headerstring, 10)) & 3)) == 3) - { - throw stream.newBitstreamException(javazoom.jl.decoder.BitstreamErrors_Fields.UNKNOWN_ERROR); - } - } - - h_layer = 4 - (SupportClass.URShift(headerstring, 17)) & 3; - h_protection_bit = (SupportClass.URShift(headerstring, 16)) & 1; - h_bitrate_index = (SupportClass.URShift(headerstring, 12)) & 0xF; - h_padding_bit = (SupportClass.URShift(headerstring, 9)) & 1; - h_mode = ((SupportClass.URShift(headerstring, 6)) & 3); - h_mode_extension = (SupportClass.URShift(headerstring, 4)) & 3; - if (h_mode == JOINT_STEREO) - h_intensity_stereo_bound = (h_mode_extension << 2) + 4; - else - h_intensity_stereo_bound = 0; - // should never be used - if (((SupportClass.URShift(headerstring, 3)) & 1) == 1) - h_copyright = true; - if (((SupportClass.URShift(headerstring, 2)) & 1) == 1) - h_original = true; - - - // calculate number of subbands: - if (h_layer == 1) - h_number_of_subbands = 32; - else - { - channel_bitrate = h_bitrate_index; - // calculate bitrate per channel: - if (h_mode != SINGLE_CHANNEL) - if (channel_bitrate == 4) - channel_bitrate = 1; - else - channel_bitrate -= 4; - - if ((channel_bitrate == 1) || (channel_bitrate == 2)) - if (h_sample_frequency == THIRTYTWO) - h_number_of_subbands = 12; - else - h_number_of_subbands = 8; - else if ((h_sample_frequency == FOURTYEIGHT) || ((channel_bitrate >= 3) && (channel_bitrate <= 5))) - h_number_of_subbands = 27; - else - h_number_of_subbands = 30; - } - if (h_intensity_stereo_bound > h_number_of_subbands) - h_intensity_stereo_bound = h_number_of_subbands; - // calculate framesize and nSlots - calculate_framesize(); - - // read framedata: - stream.read_frame_data(framesize); - - if (stream.isSyncCurrentPosition(syncmode)) - { - if (syncmode == Bitstream.INITIAL_SYNC) - { - syncmode = Bitstream.STRICT_SYNC; - stream.set_syncword(headerstring & unchecked((int)0xFFF80CC0)); - } - sync = true; - } - else - { - stream.unreadFrame(); - } - } - while (!sync); - - stream.parse_frame(); - - if (h_protection_bit == 0) - { - // frame contains a crc checksum - checksum = (short) stream.get_bits(16); - if (crc == null) - crc = new Crc16(); - crc.add_bits(headerstring, 16); - crcp[0] = crc; - } - else - crcp[0] = null; - if (h_sample_frequency == FOURTYFOUR_POINT_ONE) - { - /* - if (offset == null) - { - int max = max_number_of_frames(stream); - offset = new int[max]; - for(int i=0; i 0) && (cf == lf)) - { - offset[cf] = offset[cf-1] + h_padding_bit; - } - else - { - offset[0] = h_padding_bit; - } - */ - } - } - - // Functions to query header contents: - /// Returns version. - /// - public int version() - { - return h_version; - } - - /// Returns Layer ID. - /// - public int layer() - { - return h_layer; - } - - /// Returns bitrate index. - /// - public int bitrate_index() - { - return h_bitrate_index; - } - - /// Returns Sample Frequency. - /// - public int sample_frequency() - { - return h_sample_frequency; - } - - /// Returns Frequency. - /// - public int frequency() - { - return frequencies[h_version][h_sample_frequency]; - } - - /// Returns Mode. - /// - public int mode() - { - return h_mode; - } - - /// Returns Protection bit. - /// - public bool checksums() - { - if (h_protection_bit == 0) - return true; - else - return false; - } - - /// Returns Copyright. - /// - public bool copyright() - { - return h_copyright; - } - - /// Returns Original. - /// - public bool original() - { - return h_original; - } - - /// Returns Checksum flag. - /// Compares computed checksum with stream checksum. - /// - public bool checksum_ok() - { - return (checksum == crc.checksum()); - } - - // Seeking and layer III stuff - /// Returns Layer III Padding bit. - /// - public bool padding() - { - if (h_padding_bit == 0) - return false; - else - return true; - } - - /// Returns Slots. - /// - public int slots() - { - return nSlots; - } - - /// Returns Mode Extension. - /// - public int mode_extension() - { - return h_mode_extension; - } - - // E.B -> private to public - public static readonly int[][][] bitrates = {new int[][]{new int[]{0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}}, new int[][]{new int[]{0, 32000, 64000, 96000, 128000, 160000, 192000, 224000, 256000, 288000, 320000, 352000, 384000, 416000, 448000, 0}, new int[]{0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 384000, 0}, new int[]{0, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000, 0}}, new int[][]{new int[]{0, 32000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 176000, 192000, 224000, 256000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}, new int[]{0, 8000, 16000, 24000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 144000, 160000, 0}}}; - - // E.B -> private to public - /// Calculate Frame size. - /// Calculates framesize in bytes excluding header size. - /// - public int calculate_framesize() - { - - if (h_layer == 1) - { - framesize = (12 * bitrates[h_version][0][h_bitrate_index]) / frequencies[h_version][h_sample_frequency]; - if (h_padding_bit != 0) - framesize++; - framesize <<= 2; // one slot is 4 bytes long - nSlots = 0; - } - else - { - framesize = (144 * bitrates[h_version][h_layer - 1][h_bitrate_index]) / frequencies[h_version][h_sample_frequency]; - if (h_version == MPEG2_LSF || h_version == MPEG25_LSF) - framesize >>= 1; - // SZD - if (h_padding_bit != 0) - framesize++; - // Layer III slots - if (h_layer == 3) - { - if (h_version == MPEG1) - { - nSlots = framesize - ((h_mode == SINGLE_CHANNEL)?17:32) - ((h_protection_bit != 0)?0:2) - 4; // header size - } - else - { - // MPEG-2 LSF, SZD: MPEG-2.5 LSF - nSlots = framesize - ((h_mode == SINGLE_CHANNEL)?9:17) - ((h_protection_bit != 0)?0:2) - 4; // header size - } - } - else - { - nSlots = 0; - } - } - framesize -= 4; // subtract header size - return framesize; - } - - /// Returns the maximum number of frames in the stream. - /// - public int max_number_of_frames(int streamsize) - // E.B - { - if ((framesize + 4 - h_padding_bit) == 0) - return 0; - else - return (streamsize / (framesize + 4 - h_padding_bit)); - } - - /// Returns the maximum number of frames in the stream. - /// - public int min_number_of_frames(int streamsize) - // E.B - { - if ((framesize + 5 - h_padding_bit) == 0) - return 0; - else - return (streamsize / (framesize + 5 - h_padding_bit)); - } - - - /// Returns ms/frame. - /// - public float ms_per_frame() - // E.B - { - float[][] ms_per_frame_array = {new float[]{8.707483f, 8.0f, 12.0f}, new float[]{26.12245f, 24.0f, 36.0f}, new float[]{26.12245f, 24.0f, 36.0f}}; - return (ms_per_frame_array[h_layer - 1][h_sample_frequency]); - } - - /// Returns total ms. - /// - public float total_ms(int streamsize) - // E.B - { - return (max_number_of_frames(streamsize) * ms_per_frame()); - } - - - // functions which return header informations as strings: - /// Return Layer version. - /// - public System.String layer_string() - { - switch (h_layer) - { - - case 1: - return "I"; - - case 2: - return "II"; - - case 3: - return "III"; - } - return null; - } - - // E.B -> private to public - public static readonly System.String[][][] bitrate_str = {new System.String[][]{new System.String[]{"free format", "32 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "176 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "forbidden"}, new System.String[]{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "forbidden"}, new System.String[]{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "forbidden"}}, new System.String[][]{new System.String[]{"free format", "32 kbit/s", "64 kbit/s", "96 kbit/s", "128 kbit/s", "160 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "288 kbit/s", "320 kbit/s", "352 kbit/s", "384 kbit/s", "416 kbit/s", "448 kbit/s", "forbidden"}, new System.String[]{"free format", "32 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "160 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "320 kbit/s", "384 kbit/s", "forbidden"}, new System.String[]{"free format", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "160 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "320 kbit/s", "forbidden"}}, new System.String[][]{new System.String[]{"free format", "32 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "176 kbit/s", "192 kbit/s", "224 kbit/s", "256 kbit/s", "forbidden"}, new System.String[]{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "forbidden"}, new System. - String[]{"free format", "8 kbit/s", "16 kbit/s", "24 kbit/s", "32 kbit/s", "40 kbit/s", "48 kbit/s", "56 kbit/s", "64 kbit/s", "80 kbit/s", "96 kbit/s", "112 kbit/s", "128 kbit/s", "144 kbit/s", "160 kbit/s", "forbidden"}}}; - - /// Returns Bitrate. - /// - public System.String bitrate_string() - { - return bitrate_str[h_version][h_layer - 1][h_bitrate_index]; - } - - /// Returns Frequency - /// - public System.String sample_frequency_string() - { - switch (h_sample_frequency) - { - - case THIRTYTWO: - if (h_version == MPEG1) - return "32 kHz"; - else if (h_version == MPEG2_LSF) - return "16 kHz"; - // SZD - else - return "8 kHz"; - goto case FOURTYFOUR_POINT_ONE; - - case FOURTYFOUR_POINT_ONE: - if (h_version == MPEG1) - return "44.1 kHz"; - else if (h_version == MPEG2_LSF) - return "22.05 kHz"; - // SZD - else - return "11.025 kHz"; - goto case FOURTYEIGHT; - - case FOURTYEIGHT: - if (h_version == MPEG1) - return "48 kHz"; - else if (h_version == MPEG2_LSF) - return "24 kHz"; - // SZD - else - return "12 kHz"; - break; - } - return (null); - } - - /// Returns Mode. - /// - public System.String mode_string() - { - switch (h_mode) - { - - case STEREO: - return "Stereo"; - - case JOINT_STEREO: - return "Joint stereo"; - - case DUAL_CHANNEL: - return "Dual channel"; - - case SINGLE_CHANNEL: - return "Single channel"; - } - return null; - } - - /// Returns Version. - /// - public System.String version_string() - { - switch (h_version) - { - - case MPEG1: - return "MPEG-1"; - - case MPEG2_LSF: - return "MPEG-2 LSF"; - - case MPEG25_LSF: - return "MPEG-2.5 LSF"; - } - return (null); - } - - /// Returns the number of subbands in the current frame. - /// - public int number_of_subbands() - { - return h_number_of_subbands; - } - - /// Returns Intensity Stereo. - /// Layer II joint stereo only). - /// Returns the number of subbands which are in stereo mode, - /// subbands above that limit are in intensity stereo mode. - /// - public int intensity_stereo_bound() - { - return h_intensity_stereo_bound; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/InputStreamSource.cs b/Other/libs/mp3sharp/mp3sharp/decoder/InputStreamSource.cs deleted file mode 100644 index 705ff8a9d..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/InputStreamSource.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Support; -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// Work In Progress. - /// - /// An instance of InputStreamSource implements a - /// Source that provides data from an InputStream - /// . Seeking functionality is not supported. - /// - /// - /// MDM - /// - /// - internal class InputStreamSource : Source - { - virtual public bool Seekable - { - get - { - return false; - } - - } - //UPGRADE_NOTE: Final was removed from the declaration of 'in '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private System.IO.Stream in_Renamed; - - public InputStreamSource(System.IO.Stream in_Renamed) - { - if (in_Renamed == null) - throw new System.NullReferenceException("in"); - - this.in_Renamed = in_Renamed; - } - - public virtual int read(sbyte[] b, int offs, int len) - { - int read = SupportClass.ReadInput(in_Renamed, ref b, offs, len); - return read; - } - - public virtual bool willReadBlock() - { - return true; - //boolean block = (in.available()==0); - //return block; - } - - - public virtual long tell() - { - return - 1; - } - - public virtual long seek(long to) - { - return - 1; - } - - public virtual long length() - { - return - 1; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerError.cs b/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerError.cs deleted file mode 100644 index d074d6cec..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerError.cs +++ /dev/null @@ -1,31 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Work in progress. - /// - /// API usage errors may be handled by throwing an instance of this - /// class, as per JMF 2.0. - /// - internal class JavaLayerError:System.ApplicationException - { - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerErrors.cs b/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerErrors.cs deleted file mode 100644 index 9b87576da..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerErrors.cs +++ /dev/null @@ -1,40 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Exception erorr codes for components of the JavaLayer API. - /// - - internal struct JavaLayerErrors_Fields{ - public readonly static int BITSTREAM_ERROR = 0x100; - public readonly static int DECODER_ERROR = 0x200; - } - internal interface JavaLayerErrors - { - //UPGRADE_NOTE: Members of interface 'JavaLayerErrors' were extracted into structure 'JavaLayerErrors_Fields'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1045"' - /// The first bitstream error code. See the {@link DecoderErrors DecoderErrors} - /// interface for other bitstream error codes. - /// - /// The first decoder error code. See the {@link DecoderErrors DecoderErrors} - /// interface for other decoder error codes. - /// - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerException.cs b/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerException.cs deleted file mode 100644 index a6310300d..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerException.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Support; -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace Mp3Sharp -{ - using System; - /// The Mp3SharpException is the base class for all API-level - /// exceptions thrown by JavaLayer. To facilitate conversion and - /// common handling of exceptions from other domains, the class - /// can delegate some functionality to a contained Throwable instance. - ///

- /// - ///

- /// MDM - /// - /// - public class Mp3SharpException:System.Exception - { - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - virtual public System.Exception Exception - { - get - { - return exception; - } - - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - private System.Exception exception; - - - public Mp3SharpException() - { - } - - public Mp3SharpException(System.String msg):base(msg) - { - } - - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - public Mp3SharpException(System.String msg, System.Exception t):base(msg) - { - exception = t; - } - - - - //UPGRADE_TODO: The equivalent of method 'java.lang.Throwable.printStackTrace' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - public void printStackTrace() - { - SupportClass.WriteStackTrace(this, System.Console.Error); - } - - //UPGRADE_TODO: The equivalent of method 'java.lang.Throwable.printStackTrace' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - public void printStackTrace(System.IO.StreamWriter ps) - { - if (this.exception == null) - { - SupportClass.WriteStackTrace((System.Exception) this, ps); - } - else - { - SupportClass.WriteStackTrace(exception, Console.Error); - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerHook.cs b/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerHook.cs deleted file mode 100644 index 4ecb585ed..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerHook.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace javazoom.jl.decoder -{ - using System; - /// The JavaLayerHooks class allows developers to change - /// the way the JavaLayer library uses Resources. - /// - - internal interface JavaLayerHook - { - /// Retrieves the named resource. This allows resources to be - /// obtained without specifying how they are retrieved. - /// - System.IO.Stream getResourceAsStream(System.String name); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerUtils.cs b/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerUtils.cs deleted file mode 100644 index c986189de..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/JavaLayerUtils.cs +++ /dev/null @@ -1,225 +0,0 @@ -/* -* 12/12/99 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// The JavaLayerUtils class is not strictly part of the JavaLayer API. - /// It serves to provide useful methods and system-wide hooks. - /// - /// - /// MDM - /// - /// - - internal class JavaLayerUtils - { - //UPGRADE_NOTE: Synchronized keyword was removed from method 'getHook'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - /// Sets the system-wide JavaLayer hook. - /// - //UPGRADE_NOTE: Synchronized keyword was removed from method 'setHook'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - static public JavaLayerHook Hook - { - get - { - lock (typeof(javazoom.jl.decoder.JavaLayerUtils)) - { - return hook; - } - } - - set - { - lock (typeof(javazoom.jl.decoder.JavaLayerUtils)) - { - hook = value; - } - } - - } - private static JavaLayerHook hook = null; - /* - /// Deserializes the object contained in the given input stream. - /// - /// input stream to deserialize an object from. - /// - /// expected class of the deserialized object. - /// - /// - static public System.Object deserialize(System.IO.Stream in_Renamed, System.Type cls) - { - if (cls == null) - throw new System.NullReferenceException("cls"); - - System.Object obj = deserialize(in_Renamed, cls); - if (!cls.IsInstanceOfType(obj)) - { - throw new System.IO.IOException("type of deserialized instance not of required class."); - } - - return obj; - } - - /// Deserializes an object from the given InputStream. - /// The deserialization is delegated to an - /// ObjectInputStream instance. - /// - /// - /// InputStream to deserialize an object - /// from. - /// - /// - /// The object deserialized from the stream. - /// - /// IOException is thrown if there was a problem reading - /// the underlying stream, or an object could not be deserialized - /// from the stream. - /// - /// - /// java.io.ObjectInputStream - /// - /// - static public System.Object deserialize(System.IO.Stream in_Renamed) - { - if (in_Renamed == null) - throw new System.NullReferenceException("in"); - - System.IO.BinaryReader objIn = new System.IO.BinaryReader(in_Renamed); - - System.Object obj; - - //UPGRADE_NOTE: Exception 'java.lang.ClassNotFoundException' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - try - { - //UPGRADE_WARNING: Method 'java.io.ObjectInputStream.readObject' was converted to 'SupportClass.Deserialize' which may throw an exception. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1101"' - obj = SupportClass.Deserialize(objIn); - } - catch (System.Exception ex) - { - //UPGRADE_TODO: The equivalent in .NET for method 'java.lang.Throwable.toString' may return a different value. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1043"' - throw new System.IO.IOException(ex.ToString()); - } - - return obj; - } - - /// Deserializes an array from a given InputStream. - /// - /// - /// InputStream to - /// deserialize an object from. - /// - /// - /// class denoting the type of the array - /// elements. - /// - /// expected length of the array, or -1 if - /// any length is expected. - /// - /// - static public System.Object deserializeArray(System.IO.Stream in_Renamed, System.Type elemType, int length) - { - if (elemType == null) - throw new System.NullReferenceException("elemType"); - - if (length < - 1) - throw new System.ArgumentException("length"); - - System.Object obj = deserialize(in_Renamed); - - System.Type cls = obj.GetType(); - - - if (!cls.IsArray) - throw new System.IO.IOException("object is not an array"); - - System.Type arrayElemType = cls.GetElementType(); - if (arrayElemType != elemType) - throw new System.IO.IOException("unexpected array component type"); - - if (length != - 1) - { - int arrayLength = ((System.Array) obj).Length; - if (arrayLength != length) - throw new System.IO.IOException("array length mismatch"); - } - - return obj; - } - - static public System.Object deserializeArrayResource(System.String name, System.Type elemType, int length) - { - System.IO.Stream str = getResourceAsStream(name); - if (str == null) - throw new System.IO.IOException("unable to load resource '" + name + "'"); - - System.Object obj = deserializeArray(str, elemType, length); - - return obj; - } - - static public void serialize(System.IO.Stream out_Renamed, System.Object obj) - { - if (out_Renamed == null) - throw new System.NullReferenceException("out"); - - if (obj == null) - throw new System.NullReferenceException("obj"); - - System.IO.BinaryWriter objOut = new System.IO.BinaryWriter(out_Renamed); - SupportClass.Serialize(objOut, obj); - } - - - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'getResourceAsStream'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - /// Retrieves an InputStream for a named resource. - /// - /// - /// name of the resource. This must be a simple - /// name, and not a qualified package name. - /// - /// - /// The InputStream for the named resource, or null if - /// the resource has not been found. If a hook has been - /// provided, its getResourceAsStream() method is called - /// to retrieve the resource. - /// - /// - static public System.IO.Stream getResourceAsStream(System.String name) - { - lock (typeof(javazoom.jl.decoder.JavaLayerUtils)) - { - System.IO.Stream is_Renamed = null; - - if (hook != null) - { - is_Renamed = hook.getResourceAsStream(name); - } - else - { - System.Type cls = typeof(JavaLayerUtils); - //UPGRADE_ISSUE: Method 'java.lang.Class.getResourceAsStream' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javalangClassgetResourceAsStream_javalangString"' - is_Renamed = cls.getResourceAsStream(name); - } - - return is_Renamed; - } - }*/ - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIDecoder.cs b/Other/libs/mp3sharp/mp3sharp/decoder/LayerIDecoder.cs deleted file mode 100644 index 140aefcb4..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIDecoder.cs +++ /dev/null @@ -1,392 +0,0 @@ -/* -* 12/12/99 Initial version. Adapted from javalayer.java -* and Subband*.java. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Implements decoding of MPEG Audio Layer I frames. - /// - - class LayerIDecoder : FrameDecoder - { - protected internal Bitstream stream; - protected internal Header header; - protected internal SynthesisFilter filter1, filter2; - protected internal Obuffer buffer; - protected internal int which_channels; - protected internal int mode; - - protected internal int num_subbands; - protected internal Subband[] subbands; - protected internal Crc16 crc = null; - // new Crc16[1] to enable CRC checking. - - public LayerIDecoder() - { - crc = new Crc16(); - } - - public virtual void create(Bitstream stream0, Header header0, SynthesisFilter filtera, SynthesisFilter filterb, Obuffer buffer0, int which_ch0) - { - stream = stream0; - header = header0; - filter1 = filtera; - filter2 = filterb; - buffer = buffer0; - which_channels = which_ch0; - } - - - - public virtual void decodeFrame() - { - - num_subbands = header.number_of_subbands(); - subbands = new Subband[32]; - mode = header.mode(); - - createSubbands(); - - readAllocation(); - readScaleFactorSelection(); - - if ((crc != null) || header.checksum_ok()) - { - readScaleFactors(); - - readSampleData(); - } - } - - protected internal virtual void createSubbands() - { - int i; - if (mode == Header.SINGLE_CHANNEL) - for (i = 0; i < num_subbands; ++i) - subbands[i] = new SubbandLayer1(i); - else if (mode == Header.JOINT_STEREO) - { - for (i = 0; i < header.intensity_stereo_bound(); ++i) - subbands[i] = new SubbandLayer1Stereo(i); - for (; i < num_subbands; ++i) - subbands[i] = new SubbandLayer1IntensityStereo(i); - } - else - { - for (i = 0; i < num_subbands; ++i) - subbands[i] = new SubbandLayer1Stereo(i); - } - } - - protected internal virtual void readAllocation() - { - // start to read audio data: - for (int i = 0; i < num_subbands; ++i) - subbands[i].read_allocation(stream, header, crc); - } - - protected internal virtual void readScaleFactorSelection() - { - // scale factor selection not present for layer I. - } - - protected internal virtual void readScaleFactors() - { - for (int i = 0; i < num_subbands; ++i) - subbands[i].read_scalefactor(stream, header); - } - - protected internal virtual void readSampleData() - { - bool read_ready = false; - bool write_ready = false; - int mode = header.mode(); - int i; - do - { - for (i = 0; i < num_subbands; ++i) - read_ready = subbands[i].read_sampledata(stream); - do - { - for (i = 0; i < num_subbands; ++i) - write_ready = subbands[i].put_next_sample(which_channels, filter1, filter2); - - filter1.calculate_pcm_samples(buffer); - if ((which_channels == OutputChannels.BOTH_CHANNELS) && (mode != Header.SINGLE_CHANNEL)) - filter2.calculate_pcm_samples(buffer); - } - while (!write_ready); - } - while (!read_ready); - } - - /// Abstract base class for subband classes of layer I and II - /// - public abstract class Subband - { - /* - * Changes from version 1.1 to 1.2: - * - array size increased by one, although a scalefactor with index 63 - * is illegal (to prevent segmentation faults) - */ - // Scalefactors for layer I and II, Annex 3-B.1 in ISO/IEC DIS 11172: - public static readonly float[] scalefactors = new float[]{2.00000000000000f, 1.58740105196820f, 1.25992104989487f, 1.00000000000000f, 0.79370052598410f, 0.62996052494744f, 0.50000000000000f, 0.39685026299205f, 0.31498026247372f, 0.25000000000000f, 0.19842513149602f, 0.15749013123686f, 0.12500000000000f, 0.09921256574801f, 0.07874506561843f, 0.06250000000000f, 0.04960628287401f, 0.03937253280921f, 0.03125000000000f, 0.02480314143700f, 0.01968626640461f, 0.01562500000000f, 0.01240157071850f, 0.00984313320230f, 0.00781250000000f, 0.00620078535925f, 0.00492156660115f, 0.00390625000000f, 0.00310039267963f, 0.00246078330058f, 0.00195312500000f, 0.00155019633981f, 0.00123039165029f, 0.00097656250000f, 0.00077509816991f, 0.00061519582514f, 0.00048828125000f, 0.00038754908495f, 0.00030759791257f, 0.00024414062500f, 0.00019377454248f, 0.00015379895629f, 0.00012207031250f, 0.00009688727124f, 0.00007689947814f, 0.00006103515625f, 0.00004844363562f, 0.00003844973907f, 0.00003051757813f, 0.00002422181781f, 0.00001922486954f, 0.00001525878906f, 0.00001211090890f, 0.00000961243477f, 0.00000762939453f, 0.00000605545445f, 0.00000480621738f, 0.00000381469727f, 0.00000302772723f, 0.00000240310869f, 0.00000190734863f, 0.00000151386361f, 0.00000120155435f, 0.00000000000000f}; - - public abstract void read_allocation(Bitstream stream, Header header, Crc16 crc); - public abstract void read_scalefactor(Bitstream stream, Header header); - public abstract bool read_sampledata(Bitstream stream); - public abstract bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2); - } - - - /// Class for layer I subbands in single channel mode. - /// Used for single channel mode - /// and in derived class for intensity stereo mode - /// - internal class SubbandLayer1:Subband - { - - // Factors and offsets for sample requantization - public static readonly float[] table_factor = new float[]{0.0f, (1.0f / 2.0f) * (4.0f / 3.0f), (1.0f / 4.0f) * (8.0f / 7.0f), (1.0f / 8.0f) * (16.0f / 15.0f), (1.0f / 16.0f) * (32.0f / 31.0f), (1.0f / 32.0f) * (64.0f / 63.0f), (1.0f / 64.0f) * (128.0f / 127.0f), (1.0f / 128.0f) * (256.0f / 255.0f), (1.0f / 256.0f) * (512.0f / 511.0f), (1.0f / 512.0f) * (1024.0f / 1023.0f), (1.0f / 1024.0f) * (2048.0f / 2047.0f), (1.0f / 2048.0f) * (4096.0f / 4095.0f), (1.0f / 4096.0f) * (8192.0f / 8191.0f), (1.0f / 8192.0f) * (16384.0f / 16383.0f), (1.0f / 16384.0f) * (32768.0f / 32767.0f)}; - - public static readonly float[] table_offset = new float[]{0.0f, ((1.0f / 2.0f) - 1.0f) * (4.0f / 3.0f), ((1.0f / 4.0f) - 1.0f) * (8.0f / 7.0f), ((1.0f / 8.0f) - 1.0f) * (16.0f / 15.0f), ((1.0f / 16.0f) - 1.0f) * (32.0f / 31.0f), ((1.0f / 32.0f) - 1.0f) * (64.0f / 63.0f), ((1.0f / 64.0f) - 1.0f) * (128.0f / 127.0f), ((1.0f / 128.0f) - 1.0f) * (256.0f / 255.0f), ((1.0f / 256.0f) - 1.0f) * (512.0f / 511.0f), ((1.0f / 512.0f) - 1.0f) * (1024.0f / 1023.0f), ((1.0f / 1024.0f) - 1.0f) * (2048.0f / 2047.0f), ((1.0f / 2048.0f) - 1.0f) * (4096.0f / 4095.0f), ((1.0f / 4096.0f) - 1.0f) * (8192.0f / 8191.0f), ((1.0f / 8192.0f) - 1.0f) * (16384.0f / 16383.0f), ((1.0f / 16384.0f) - 1.0f) * (32768.0f / 32767.0f)}; - - protected internal int subbandnumber; - protected internal int samplenumber; - protected internal int allocation; - protected internal float scalefactor; - protected internal int samplelength; - protected internal float sample; - protected internal float factor, offset; - - /// Construtor. - /// - public SubbandLayer1(int subbandnumber):base() - { - this.subbandnumber = subbandnumber; - samplenumber = 0; - } - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - if ((allocation = stream.get_bits(4)) == 15) - { - } - // cerr << "WARNING: stream contains an illegal allocation!\n"; - // MPEG-stream is corrupted! - if (crc != null) - crc.add_bits(allocation, 4); - if (allocation != 0) - { - samplelength = allocation + 1; - factor = table_factor[allocation]; - offset = table_offset[allocation]; - } - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - if (allocation != 0) - scalefactor = scalefactors[stream.get_bits(6)]; - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - if (allocation != 0) - { - sample = (float) (stream.get_bits(samplelength)); - } - if (++samplenumber == 12) - { - samplenumber = 0; - return true; - } - return false; - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - if ((allocation != 0) && (channels != OutputChannels.RIGHT_CHANNEL)) - { - float scaled_sample = (sample * factor + offset) * scalefactor; - filter1.input_sample(scaled_sample, subbandnumber); - } - return true; - } - } - - - /// Class for layer I subbands in joint stereo mode. - /// - internal class SubbandLayer1IntensityStereo:SubbandLayer1 - { - protected internal float channel2_scalefactor; - - /// Constructor - /// - public SubbandLayer1IntensityStereo(int subbandnumber):base(subbandnumber) - { - } - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - base.read_allocation(stream, header, crc); - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - if (allocation != 0) - { - scalefactor = scalefactors[stream.get_bits(6)]; - channel2_scalefactor = scalefactors[stream.get_bits(6)]; - } - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - return base.read_sampledata(stream); - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - if (allocation != 0) - { - sample = sample * factor + offset; // requantization - if (channels == OutputChannels.BOTH_CHANNELS) - { - float sample1 = sample * scalefactor, sample2 = sample * channel2_scalefactor; - filter1.input_sample(sample1, subbandnumber); - filter2.input_sample(sample2, subbandnumber); - } - else if (channels == OutputChannels.LEFT_CHANNEL) - { - float sample1 = sample * scalefactor; - filter1.input_sample(sample1, subbandnumber); - } - else - { - float sample2 = sample * channel2_scalefactor; - filter1.input_sample(sample2, subbandnumber); - } - } - return true; - } - } - - - /// Class for layer I subbands in stereo mode. - /// - internal class SubbandLayer1Stereo:SubbandLayer1 - { - protected internal int channel2_allocation; - protected internal float channel2_scalefactor; - protected internal int channel2_samplelength; - protected internal float channel2_sample; - protected internal float channel2_factor, channel2_offset; - - - /// Constructor - /// - public SubbandLayer1Stereo(int subbandnumber):base(subbandnumber) - { - } - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - allocation = stream.get_bits(4); - channel2_allocation = stream.get_bits(4); - if (crc != null) - { - crc.add_bits(allocation, 4); - crc.add_bits(channel2_allocation, 4); - } - if (allocation != 0) - { - samplelength = allocation + 1; - factor = table_factor[allocation]; - offset = table_offset[allocation]; - } - if (channel2_allocation != 0) - { - channel2_samplelength = channel2_allocation + 1; - channel2_factor = table_factor[channel2_allocation]; - channel2_offset = table_offset[channel2_allocation]; - } - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - if (allocation != 0) - scalefactor = scalefactors[stream.get_bits(6)]; - if (channel2_allocation != 0) - channel2_scalefactor = scalefactors[stream.get_bits(6)]; - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - bool returnvalue = base.read_sampledata(stream); - if (channel2_allocation != 0) - { - channel2_sample = (float) (stream.get_bits(channel2_samplelength)); - } - return (returnvalue); - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - base.put_next_sample(channels, filter1, filter2); - if ((channel2_allocation != 0) && (channels != OutputChannels.LEFT_CHANNEL)) - { - float sample2 = (channel2_sample * channel2_factor + channel2_offset) * channel2_scalefactor; - if (channels == OutputChannels.BOTH_CHANNELS) - filter2.input_sample(sample2, subbandnumber); - else - filter1.input_sample(sample2, subbandnumber); - } - return true; - } - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIDecoder.cs b/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIDecoder.cs deleted file mode 100644 index ceb4ab61e..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIDecoder.cs +++ /dev/null @@ -1,725 +0,0 @@ -/* -* 12/12/99 Initial version. Adapted from javalayer.java -* and Subband*.java. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ - -/// ******************************************************************* -/// date programmers comment * -/// * -/// 29/05/01 Michael Scheerer, Fixed some C++ to Java porting bugs. * -/// * -/// * -/// 16/07/01 Michael Scheerer, Catched a bug in method * -/// read_sampledata, which causes an outOfIndexException. * -/// * -/// ********************************************************************* -/// * -/// ******************************************************************** -/// -namespace javazoom.jl.decoder -{ - using System; - - - /// Implements decoding of MPEG Audio Layer II frames. - /// - - class LayerIIDecoder:LayerIDecoder, FrameDecoder - { - - public LayerIIDecoder() - { - } - - - protected internal override void createSubbands() - { - int i; - if (mode == Header.SINGLE_CHANNEL) - for (i = 0; i < num_subbands; ++i) - subbands[i] = new SubbandLayer2(i); - else if (mode == Header.JOINT_STEREO) - { - for (i = 0; i < header.intensity_stereo_bound(); ++i) - subbands[i] = new SubbandLayer2Stereo(i); - for (; i < num_subbands; ++i) - subbands[i] = new SubbandLayer2IntensityStereo(i); - } - else - { - for (i = 0; i < num_subbands; ++i) - subbands[i] = new SubbandLayer2Stereo(i); - } - } - - protected internal override void readScaleFactorSelection() - { - for (int i = 0; i < num_subbands; ++i) - ((SubbandLayer2) subbands[i]).read_scalefactor_selection(stream, crc); - } - - - - /// Class for layer II subbands in single channel mode. - /// - internal class SubbandLayer2:Subband - { - private void InitBlock() - { - samples = new float[3]; - groupingtable = new float[2][]; - } - // this table contains 3 requantized samples for each legal codeword - // when grouped in 5 bits, i.e. 3 quantization steps per sample - //UPGRADE_NOTE: Final was removed from the declaration of 'grouping_5bits '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float[] grouping_5bits = new float[]{- 2.0f / 3.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, 0.0f, 0.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, 0.0f, - 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f / 3.0f, 0.0f, 0.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, 0.0f, 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, 0.0f, 0.0f, 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, - 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, 0.0f, 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f}; - - // this table contains 3 requantized samples for each legal codeword - // when grouped in 7 bits, i.e. 5 quantizationsteps per sample - //UPGRADE_NOTE: Final was removed from the declaration of 'grouping_7bits '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float[] grouping_7bits = new float[]{- 0.8f, - 0.8f, - 0.8f, - 0.4f, - 0.8f, - 0.8f, 0.0f, - 0.8f, - 0.8f, 0.4f, - 0.8f, - 0.8f, 0.8f, - 0.8f, - 0.8f, - 0.8f, - 0.4f, - 0.8f, - 0.4f, - 0.4f, - 0.8f, 0.0f, - 0.4f, - 0.8f, 0.4f, - 0.4f, - 0.8f, 0.8f, - 0.4f, - 0.8f, - 0.8f, 0.0f, - 0.8f, - 0.4f, 0.0f, - 0.8f, 0.0f, 0.0f, - 0.8f, 0.4f, 0.0f, - 0.8f, 0.8f, 0.0f, - 0.8f, - 0.8f, 0.4f, - 0.8f, - 0.4f, 0.4f, - 0.8f, 0.0f, 0.4f, - 0.8f, 0.4f, 0.4f, - 0.8f, 0.8f, 0.4f, - 0.8f, - 0.8f, 0.8f, - 0.8f, - 0.4f, 0.8f, - 0.8f, 0.0f, 0.8f, - 0.8f, 0.4f, 0.8f, - 0.8f, 0.8f, 0.8f, - 0.8f, - 0.8f, - 0.8f, - 0.4f, - 0.4f, - 0.8f, - 0.4f, 0.0f, - 0.8f, - 0.4f, 0.4f, - 0.8f, - 0.4f, 0.8f, - 0.8f, - 0.4f, - 0.8f, - 0.4f, - 0.4f, - 0.4f, - 0.4f, - 0.4f, 0.0f, - 0.4f, - 0.4f, 0.4f, - 0.4f, - 0.4f, 0.8f, - 0.4f, - 0.4f, - 0.8f, 0.0f, - 0.4f, - 0.4f, 0.0f, - 0.4f, 0.0f, 0.0f, - 0.4f, 0.4f, 0.0f, - 0.4f, 0.8f, 0.0f, - 0.4f, - 0.8f, 0.4f, - 0.4f, - 0.4f, 0.4f, - 0.4f, 0.0f, 0.4f, - 0.4f, 0.4f, 0.4f, - 0.4f, 0.8f, 0.4f, - 0.4f, - 0.8f, 0.8f, - 0.4f, - 0.4f, 0.8f, - 0.4f, 0.0f, 0.8f, - 0.4f, 0.4f, 0.8f, - 0.4f, 0.8f, 0.8f, - 0.4f, - 0.8f, - 0.8f, 0.0f, - 0.4f, - 0.8f, 0.0f, 0.0f, - 0.8f, 0.0f, 0.4f, - 0.8f, 0.0f, 0.8f, - 0.8f, 0.0f, - 0.8f, - 0.4f, 0.0f, - 0.4f, - 0.4f, 0.0f, 0.0f, - 0.4f, 0.0f, 0.4f, - 0.4f, 0.0f, 0.8f, - 0.4f, 0.0f, - 0.8f, 0.0f, 0.0f, - 0.4f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.4f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f, - 0.8f, 0.4f, 0.0f, - 0.4f, 0.4f, 0.0f, 0.0f, 0.4f, 0.0f, 0.4f, 0.4f, 0.0f, 0.8f, 0.4f, 0.0f, - 0.8f, 0.8f, 0.0f, - 0.4f, 0.8f, 0.0f, 0.0f, 0.8f, 0.0f, 0.4f, 0.8f, 0.0f, 0.8f, 0.8f, 0.0f, - 0.8f, - 0.8f, 0.4f, - 0.4f, - 0.8f, 0.4f, 0.0f, - 0.8f, 0.4f, 0.4f, - 0.8f, 0.4f, 0.8f, - 0.8f, 0.4f, - 0.8f, - 0.4f, 0.4f, - 0.4f, - 0.4f, 0.4f, 0.0f, - 0.4f, 0.4f, 0.4f, - 0.4f, 0.4f, 0.8f, - 0.4f, 0.4f, - 0.8f, 0.0f, 0.4f, - 0.4f, 0.0f, 0.4f, 0.0f, 0.0f, 0.4f, 0.4f, 0.0f, 0.4f, 0.8f, 0.0f, 0.4f, - 0.8f, 0.4f, 0.4f, - 0.4f, 0.4f, 0.4f, 0.0f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.8f, 0.4f, 0.4f, - - 0.8f, 0.8f, 0.4f, - 0.4f, 0.8f, 0.4f, 0.0f, 0.8f, 0.4f, 0.4f, 0.8f, 0.4f, 0.8f, 0.8f, 0.4f, - 0.8f, - 0.8f, 0.8f, - 0.4f, - 0.8f, 0.8f, 0.0f, - 0.8f, 0.8f, 0.4f, - 0.8f, 0.8f, 0.8f, - 0.8f, 0.8f, - 0.8f, - 0.4f, 0.8f, - 0.4f, - 0.4f, 0.8f, 0.0f, - 0.4f, 0.8f, 0.4f, - 0.4f, 0.8f, 0.8f, - 0.4f, 0.8f, - 0.8f, 0.0f, 0.8f, - 0.4f, 0.0f, 0.8f, 0.0f, 0.0f, 0.8f, 0.4f, 0.0f, 0.8f, 0.8f, 0.0f, 0.8f, - 0.8f, 0.4f, 0.8f, - 0.4f, 0.4f, 0.8f, 0.0f, 0.4f, 0.8f, 0.4f, 0.4f, 0.8f, 0.8f, 0.4f, 0.8f, - 0.8f, 0.8f, 0.8f, - 0.4f, 0.8f, 0.8f, 0.0f, 0.8f, 0.8f, 0.4f, 0.8f, 0.8f, 0.8f, 0.8f, 0.8f}; - - // this table contains 3 requantized samples for each legal codeword - // when grouped in 10 bits, i.e. 9 quantizationsteps per sample - public static readonly float[] grouping_10bits = new float[]{- 8.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 0.0f, 0.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - - 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 0.0f, 0.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / - 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f - , - 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, -2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 0.0f, 0.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, -- 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 0.0f, 0.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 2.0f - / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 0.0f, - 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 0.0f, - 6.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 0.0f, - - 4.0f / 9.0f, 0.0f, -2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 0.0f, - 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 0.0f, 0.0f, - 6.0f / 9.0f, 0.0f, 0.0f, - 4.0f / 9.0f, 0.0f, 0.0f, - 2.0f / 9.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 2.0f / 9.0f, 0.0f, 0.0f, 4.0f / 9.0f, 0.0f, 0.0f, 6.0f / 9.0f, 0.0f, 0.0f, 8.0f / 9.0f, 0.0f, 0.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, 0.0f, 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, 0.0f, 4.0f / 9.0f, 0.0f, 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 0.0f, 0.0f, 6.0f / 9.0f, 0.0f, 2.0f / 9.0f, 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, 0.0f, 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 8.0f - / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, 0.0f, 0.0f, 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, 2.0f / 9.0f, 6.0f / 9.0f, 0.0f, 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 0.0f, 2.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f - / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, 0.0f, 0.0f, 4.0f / 9.0f, 2.0f / 9.0f, 0.0f, 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 0.0f, 2.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 0.0f, 4.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 8.0f / - 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, 0.0f, 0.0f, 6.0f / 9.0f, 2.0f / 9.0f, 0.0f, 6.0f / 9.0f, 4.0f / 9.0f, 0.0f, 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / - 9.0f, 0.0f, 2.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 0.0f, 4.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 0.0f, 6.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f - / 9.0f, - 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, 0.0f, 0.0f, 8.0f / 9.0f, 2.0f / 9.0f, 0.0f, 8.0f / 9.0f, 4.0f / 9.0f, 0.0f, 8.0f / 9.0f, 6.0f / 9.0f, 0.0f, 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 0.0f, 2.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 0.0f, 4.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 0.0f, 6.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, - 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, - 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 0.0f, 8.0f / 9.0f, 8.0f / 9.0f, 2.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 4.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 6.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f, 8.0f / 9.0f}; - - // data taken from ISO/IEC DIS 11172, Annexes 3-B.2[abcd] and 3-B.4: - - // subbands 0-2 in tables 3-B.2a and 2b: (index is allocation) - public static readonly int[] table_ab1_codelength = new int[]{0, 5, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; - - //UPGRADE_NOTE: Final was removed from the declaration of 'table_ab1_groupingtables '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float[][] table_ab1_groupingtables = {null, grouping_5bits, null, null, null, null, null, null, null, null, null, null, null, null, null, null}; - - public static readonly float[] table_ab1_factor = new float[]{0.0f, 1.0f / 2.0f, 1.0f / 4.0f, 1.0f / 8.0f, 1.0f / 16.0f, 1.0f / 32.0f, 1.0f / 64.0f, 1.0f / 128.0f, 1.0f / 256.0f, 1.0f / 512.0f, 1.0f / 1024.0f, 1.0f / 2048.0f, 1.0f / 4096.0f, 1.0f / 8192.0f, 1.0f / 16384.0f, 1.0f / 32768.0f}; - - public static readonly float[] table_ab1_c = new float[]{0.0f, 1.33333333333f, 1.14285714286f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f, 1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00006103888f, 1.00003051851f, 1.00001525902f}; - - public static readonly float[] table_ab1_d = new float[]{0.0f, 0.50000000000f, 0.25000000000f, 0.12500000000f, 0.06250000000f, 0.03125000000f, 0.01562500000f, 0.00781250000f, 0.00390625000f, 0.00195312500f, 0.00097656250f, 0.00048828125f, 0.00024414063f, 0.00012207031f, 0.00006103516f, 0.00003051758f}; - - // subbands 3-... tables 3-B.2a and 2b: - //UPGRADE_NOTE: Final was removed from the declaration of 'table_ab234_groupingtables '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float[][] table_ab234_groupingtables = {null, grouping_5bits, grouping_7bits, null, grouping_10bits, null, null, null, null, null, null, null, null, null, null, null}; - - // subbands 3-10 in tables 3-B.2a and 2b: - public static readonly int[] table_ab2_codelength = new int[]{0, 5, 7, 3, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16}; - public static readonly float[] table_ab2_factor = new float[]{0.0f, 1.0f / 2.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 8.0f, 1.0f / 8.0f, 1.0f / 16.0f, 1.0f / 32.0f, 1.0f / 64.0f, 1.0f / 128.0f, 1.0f / 256.0f, 1.0f / 512.0f, 1.0f / 1024.0f, 1.0f / 2048.0f, 1.0f / 4096.0f, 1.0f / 32768.0f}; - public static readonly float[] table_ab2_c = new float[]{0.0f, 1.33333333333f, 1.60000000000f, 1.14285714286f, 1.77777777777f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f, 1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00001525902f}; - public static readonly float[] table_ab2_d = new float[]{0.0f, 0.50000000000f, 0.50000000000f, 0.25000000000f, 0.50000000000f, 0.12500000000f, 0.06250000000f, 0.03125000000f, 0.01562500000f, 0.00781250000f, 0.00390625000f, 0.00195312500f, 0.00097656250f, 0.00048828125f, 0.00024414063f, 0.00003051758f}; - - // subbands 11-22 in tables 3-B.2a and 2b: - public static readonly int[] table_ab3_codelength = new int[]{0, 5, 7, 3, 10, 4, 5, 16}; - public static readonly float[] table_ab3_factor = new float[]{0.0f, 1.0f / 2.0f, 1.0f / 4.0f, 1.0f / 4.0f, 1.0f / 8.0f, 1.0f / 8.0f, 1.0f / 16.0f, 1.0f / 32768.0f}; - public static readonly float[] table_ab3_c = new float[]{0.0f, 1.33333333333f, 1.60000000000f, 1.14285714286f, 1.77777777777f, 1.06666666666f, 1.03225806452f, 1.00001525902f}; - public static readonly float[] table_ab3_d = new float[]{0.0f, 0.50000000000f, 0.50000000000f, 0.25000000000f, 0.50000000000f, 0.12500000000f, 0.06250000000f, 0.00003051758f}; - - // subbands 23-... in tables 3-B.2a and 2b: - public static readonly int[] table_ab4_codelength = new int[]{0, 5, 7, 16}; - public static readonly float[] table_ab4_factor = new float[]{0.0f, 1.0f / 2.0f, 1.0f / 4.0f, 1.0f / 32768.0f}; - public static readonly float[] table_ab4_c = new float[]{0.0f, 1.33333333333f, 1.60000000000f, 1.00001525902f}; - public static readonly float[] table_ab4_d = new float[]{0.0f, 0.50000000000f, 0.50000000000f, 0.00003051758f}; - - // subbands in tables 3-B.2c and 2d: - public static readonly int[] table_cd_codelength = new int[]{0, 5, 7, 10, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; - //UPGRADE_NOTE: Final was removed from the declaration of 'table_cd_groupingtables '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly float[][] table_cd_groupingtables = {null, grouping_5bits, grouping_7bits, grouping_10bits, null, null, null, null, null, null, null, null, null, null, null, null}; - public static readonly float[] table_cd_factor = new float[]{0.0f, 1.0f / 2.0f, 1.0f / 4.0f, 1.0f / 8.0f, 1.0f / 8.0f, 1.0f / 16.0f, 1.0f / 32.0f, 1.0f / 64.0f, 1.0f / 128.0f, 1.0f / 256.0f, 1.0f / 512.0f, 1.0f / 1024.0f, 1.0f / 2048.0f, 1.0f / 4096.0f, 1.0f / 8192.0f, 1.0f / 16384.0f}; - public static readonly float[] table_cd_c = new float[]{0.0f, 1.33333333333f, 1.60000000000f, 1.77777777777f, 1.06666666666f, 1.03225806452f, 1.01587301587f, 1.00787401575f, 1.00392156863f, 1.00195694716f, 1.00097751711f, 1.00048851979f, 1.00024420024f, 1.00012208522f, 1.00006103888f, 1.00003051851f}; - public static readonly float[] table_cd_d = new float[]{0.0f, 0.50000000000f, 0.50000000000f, 0.50000000000f, 0.12500000000f, 0.06250000000f, 0.03125000000f, 0.01562500000f, 0.00781250000f, 0.00390625000f, 0.00195312500f, 0.00097656250f, 0.00048828125f, 0.00024414063f, 0.00012207031f, 0.00006103516f}; - - - - protected internal int subbandnumber; - protected internal int allocation; - protected internal int scfsi; - protected internal float scalefactor1, scalefactor2, scalefactor3; - protected internal int[] codelength = new int[]{0}; - //UPGRADE_NOTE: The initialization of 'groupingtable' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - protected internal float[][] groupingtable; - //protected float[][] groupingtable = {{0},{0}} ; - protected internal float[] factor = new float[]{0.0f}; - protected internal int groupnumber; - protected internal int samplenumber; - //UPGRADE_NOTE: The initialization of 'samples' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - protected internal float[] samples; - protected internal float[] c = new float[]{0}; - protected internal float[] d = new float[]{0}; - /// Constructor - /// - public SubbandLayer2(int subbandnumber) - { - this.subbandnumber = subbandnumber; - groupnumber = samplenumber = 0; - } - - - /// * - /// - protected internal virtual int get_allocationlength(Header header) - { - if (header.version() == Header.MPEG1) - { - int channel_bitrate = header.bitrate_index(); - - // calculate bitrate per channel: - if (header.mode() != Header.SINGLE_CHANNEL) - if (channel_bitrate == 4) - channel_bitrate = 1; - else - channel_bitrate -= 4; - - if (channel_bitrate == 1 || channel_bitrate == 2) - // table 3-B.2c or 3-B.2d - if (subbandnumber <= 1) - return 4; - else - return 3; - // tables 3-B.2a or 3-B.2b - else if (subbandnumber <= 10) - return 4; - else if (subbandnumber <= 22) - return 3; - else - return 2; - } - else - { - // MPEG-2 LSF -- Jeff - - // table B.1 of ISO/IEC 13818-3 - if (subbandnumber <= 3) - return 4; - else if (subbandnumber <= 10) - return 3; - else - return 2; - } - } - - /// * - /// - protected internal virtual void prepare_sample_reading(Header header, int allocation, int channel, float[] factor, int[] codelength, float[] c, float[] d) - { - int channel_bitrate = header.bitrate_index(); - // calculate bitrate per channel: - if (header.mode() != Header.SINGLE_CHANNEL) - if (channel_bitrate == 4) - channel_bitrate = 1; - else - channel_bitrate -= 4; - - if (channel_bitrate == 1 || channel_bitrate == 2) - { - // table 3-B.2c or 3-B.2d - groupingtable[channel] = table_cd_groupingtables[allocation]; - factor[0] = table_cd_factor[allocation]; - codelength[0] = table_cd_codelength[allocation]; - c[0] = table_cd_c[allocation]; - d[0] = table_cd_d[allocation]; - } - else - { - // tables 3-B.2a or 3-B.2b - if (subbandnumber <= 2) - { - groupingtable[channel] = table_ab1_groupingtables[allocation]; - factor[0] = table_ab1_factor[allocation]; - codelength[0] = table_ab1_codelength[allocation]; - c[0] = table_ab1_c[allocation]; - d[0] = table_ab1_d[allocation]; - } - else - { - groupingtable[channel] = table_ab234_groupingtables[allocation]; - if (subbandnumber <= 10) - { - factor[0] = table_ab2_factor[allocation]; - codelength[0] = table_ab2_codelength[allocation]; - c[0] = table_ab2_c[allocation]; - d[0] = table_ab2_d[allocation]; - } - else if (subbandnumber <= 22) - { - factor[0] = table_ab3_factor[allocation]; - codelength[0] = table_ab3_codelength[allocation]; - c[0] = table_ab3_c[allocation]; - d[0] = table_ab3_d[allocation]; - } - else - { - factor[0] = table_ab4_factor[allocation]; - codelength[0] = table_ab4_codelength[allocation]; - c[0] = table_ab4_c[allocation]; - d[0] = table_ab4_d[allocation]; - } - } - } - } - - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - int length = get_allocationlength(header); - allocation = stream.get_bits(length); - if (crc != null) - crc.add_bits(allocation, length); - } - - /// * - /// - public virtual void read_scalefactor_selection(Bitstream stream, Crc16 crc) - { - if (allocation != 0) - { - scfsi = stream.get_bits(2); - if (crc != null) - crc.add_bits(scfsi, 2); - } - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - if (allocation != 0) - { - switch (scfsi) - { - - case 0: - scalefactor1 = scalefactors[stream.get_bits(6)]; - scalefactor2 = scalefactors[stream.get_bits(6)]; - scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - case 1: - scalefactor1 = scalefactor2 = scalefactors[stream.get_bits(6)]; - scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - case 2: - scalefactor1 = scalefactor2 = scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - case 3: - scalefactor1 = scalefactors[stream.get_bits(6)]; - scalefactor2 = scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - } - prepare_sample_reading(header, allocation, 0, factor, codelength, c, d); - } - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - if (allocation != 0) - if (groupingtable[0] != null) - { - int samplecode = stream.get_bits(codelength[0]); - // create requantized samples: - samplecode += samplecode << 1; - float[] target = samples; - float[] source = groupingtable[0]; - /* - int tmp = 0; - int temp = 0; - target[tmp++] = source[samplecode + temp]; - temp++; - target[tmp++] = source[samplecode + temp]; - temp++; - target[tmp] = source[samplecode + temp]; - */ - //Bugfix: - int tmp = 0; - int temp = samplecode; - - if (temp > source.Length - 3) - temp = source.Length - 3; - - target[tmp] = source[temp]; - temp++; tmp++; - target[tmp] = source[temp]; - temp++; tmp++; - target[tmp] = source[temp]; - - // memcpy (samples, groupingtable + samplecode, 3 * sizeof (real)); - } - else - { - samples[0] = (float) ((stream.get_bits(codelength[0])) * factor[0] - 1.0); - samples[1] = (float) ((stream.get_bits(codelength[0])) * factor[0] - 1.0); - samples[2] = (float) ((stream.get_bits(codelength[0])) * factor[0] - 1.0); - } - - samplenumber = 0; - if (++groupnumber == 12) - return true; - else - return false; - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - if ((allocation != 0) && (channels != OutputChannels.RIGHT_CHANNEL)) - { - float sample = samples[samplenumber]; - - if (groupingtable[0] == null) - sample = (sample + d[0]) * c[0]; - if (groupnumber <= 4) - sample *= scalefactor1; - else if (groupnumber <= 8) - sample *= scalefactor2; - else - sample *= scalefactor3; - filter1.input_sample(sample, subbandnumber); - } - - if (++samplenumber == 3) - return true; - else - return false; - } - } - - - /// Class for layer II subbands in joint stereo mode. - /// - internal class SubbandLayer2IntensityStereo:SubbandLayer2 - { - protected internal int channel2_scfsi; - protected internal float channel2_scalefactor1, channel2_scalefactor2, channel2_scalefactor3; - - /// Constructor - /// - public SubbandLayer2IntensityStereo(int subbandnumber):base(subbandnumber) - { - } - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - base.read_allocation(stream, header, crc); - } - - /// * - /// - public override void read_scalefactor_selection(Bitstream stream, Crc16 crc) - { - if (allocation != 0) - { - scfsi = stream.get_bits(2); - channel2_scfsi = stream.get_bits(2); - if (crc != null) - { - crc.add_bits(scfsi, 2); - crc.add_bits(channel2_scfsi, 2); - } - } - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - if (allocation != 0) - { - base.read_scalefactor(stream, header); - switch (channel2_scfsi) - { - - case 0: - channel2_scalefactor1 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor2 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 1: - channel2_scalefactor1 = channel2_scalefactor2 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 2: - channel2_scalefactor1 = channel2_scalefactor2 = channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 3: - channel2_scalefactor1 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor2 = channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - } - } - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - return base.read_sampledata(stream); - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - if (allocation != 0) - { - float sample = samples[samplenumber]; - - if (groupingtable[0] == null) - sample = (sample + d[0]) * c[0]; - if (channels == OutputChannels.BOTH_CHANNELS) - { - float sample2 = sample; - if (groupnumber <= 4) - { - sample *= scalefactor1; - sample2 *= channel2_scalefactor1; - } - else if (groupnumber <= 8) - { - sample *= scalefactor2; - sample2 *= channel2_scalefactor2; - } - else - { - sample *= scalefactor3; - sample2 *= channel2_scalefactor3; - } - filter1.input_sample(sample, subbandnumber); - filter2.input_sample(sample2, subbandnumber); - } - else if (channels == OutputChannels.LEFT_CHANNEL) - { - if (groupnumber <= 4) - sample *= scalefactor1; - else if (groupnumber <= 8) - sample *= scalefactor2; - else - sample *= scalefactor3; - filter1.input_sample(sample, subbandnumber); - } - else - { - if (groupnumber <= 4) - sample *= channel2_scalefactor1; - else if (groupnumber <= 8) - sample *= channel2_scalefactor2; - else - sample *= channel2_scalefactor3; - filter1.input_sample(sample, subbandnumber); - } - } - - if (++samplenumber == 3) - return true; - else - return false; - } - } - - - /// Class for layer II subbands in stereo mode. - /// - internal class SubbandLayer2Stereo:SubbandLayer2 - { - protected internal int channel2_allocation; - protected internal int channel2_scfsi; - protected internal float channel2_scalefactor1, channel2_scalefactor2, channel2_scalefactor3; - //protected boolean channel2_grouping; ???? Never used! - protected internal int[] channel2_codelength = new int[]{0}; - //protected float[][] channel2_groupingtable = {{0},{0}}; - protected internal float[] channel2_factor = new float[]{0}; - protected internal float[] channel2_samples; - protected internal float[] channel2_c = new float[]{0}; - protected internal float[] channel2_d = new float[]{0}; - - /// Constructor - /// - public SubbandLayer2Stereo(int subbandnumber):base(subbandnumber) - { - channel2_samples = new float[3]; - } - - /// * - /// - public override void read_allocation(Bitstream stream, Header header, Crc16 crc) - { - int length = get_allocationlength(header); - allocation = stream.get_bits(length); - channel2_allocation = stream.get_bits(length); - if (crc != null) - { - crc.add_bits(allocation, length); - crc.add_bits(channel2_allocation, length); - } - } - - /// * - /// - public override void read_scalefactor_selection(Bitstream stream, Crc16 crc) - { - if (allocation != 0) - { - scfsi = stream.get_bits(2); - if (crc != null) - crc.add_bits(scfsi, 2); - } - if (channel2_allocation != 0) - { - channel2_scfsi = stream.get_bits(2); - if (crc != null) - crc.add_bits(channel2_scfsi, 2); - } - } - - /// * - /// - public override void read_scalefactor(Bitstream stream, Header header) - { - base.read_scalefactor(stream, header); - if (channel2_allocation != 0) - { - switch (channel2_scfsi) - { - - case 0: - channel2_scalefactor1 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor2 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 1: - channel2_scalefactor1 = channel2_scalefactor2 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 2: - channel2_scalefactor1 = channel2_scalefactor2 = channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - - - case 3: - channel2_scalefactor1 = scalefactors[stream.get_bits(6)]; - channel2_scalefactor2 = channel2_scalefactor3 = scalefactors[stream.get_bits(6)]; - break; - } - prepare_sample_reading(header, channel2_allocation, 1, channel2_factor, channel2_codelength, channel2_c, channel2_d); - } - } - - /// * - /// - public override bool read_sampledata(Bitstream stream) - { - bool returnvalue = base.read_sampledata(stream); - - if (channel2_allocation != 0) - if (groupingtable[1] != null) - { - int samplecode = stream.get_bits(channel2_codelength[0]); - // create requantized samples: - samplecode += samplecode << 1; - /* - float[] target = channel2_samples; - float[] source = channel2_groupingtable[0]; - int tmp = 0; - int temp = 0; - target[tmp++] = source[samplecode + temp]; - temp++; - target[tmp++] = source[samplecode + temp]; - temp++; - target[tmp] = source[samplecode + temp]; - // memcpy (channel2_samples, channel2_groupingtable + samplecode, 3 * sizeof (real)); - */ - float[] target = channel2_samples; - float[] source = groupingtable[1]; - int tmp = 0; - int temp = samplecode; - target[tmp] = source[temp]; - temp++; tmp++; - target[tmp] = source[temp]; - temp++; tmp++; - target[tmp] = source[temp]; - } - else - { - channel2_samples[0] = (float) ((stream.get_bits(channel2_codelength[0])) * channel2_factor[0] - 1.0); - channel2_samples[1] = (float) ((stream.get_bits(channel2_codelength[0])) * channel2_factor[0] - 1.0); - channel2_samples[2] = (float) ((stream.get_bits(channel2_codelength[0])) * channel2_factor[0] - 1.0); - } - return returnvalue; - } - - /// * - /// - public override bool put_next_sample(int channels, SynthesisFilter filter1, SynthesisFilter filter2) - { - bool returnvalue = base.put_next_sample(channels, filter1, filter2); - if ((channel2_allocation != 0) && (channels != OutputChannels.LEFT_CHANNEL)) - { - float sample = channel2_samples[samplenumber - 1]; - - if (groupingtable[1] == null) - sample = (sample + channel2_d[0]) * channel2_c[0]; - - if (groupnumber <= 4) - sample *= channel2_scalefactor1; - else if (groupnumber <= 8) - sample *= channel2_scalefactor2; - else - sample *= channel2_scalefactor3; - if (channels == OutputChannels.BOTH_CHANNELS) - filter2.input_sample(sample, subbandnumber); - else - filter1.input_sample(sample, subbandnumber); - } - return returnvalue; - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIIDecoder.cs b/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIIDecoder.cs deleted file mode 100644 index 924b5f52b..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/LayerIIIDecoder.cs +++ /dev/null @@ -1,2550 +0,0 @@ -using Support; -/// 02/19/99 Java Conversion by E.B -/// ------------------------------------------------- -/// layer3.h -/// * -/// Declarations for the Layer III decoder object -/// ------------------------------------------------- -/// -/// ******************************************************************* -/// date programmers comment * -/// * -/// 18/06/01 Michael Scheerer, Fixed bugs which causes * -/// negative indexes in method huffmann_decode and in method * -/// dequanisize_sample. * -/// * -/// 16/07/01 Michael Scheerer, Catched a bug in method * -/// huffmann_decode, which causes an outOfIndexException. * -/// Cause : Indexnumber of 24 at SfBandIndex, * -/// which has only a length of 22. I have simply and dirty * -/// fixed the index to <= 22, because I'm not really be able * -/// to fix the bug. The Indexnumber is taken from the MP3 * -/// file and the origin Ma-Player with the same code works * -/// well. * -/// * -/// ********************************************************************* -/// * -/// ******************************************************************** -/// -namespace javazoom.jl.decoder -{ - using System; - /// Class Implementing Layer 3 Decoder. - /// * - /// @since 0.0 - /// - - sealed class LayerIIIDecoder : FrameDecoder - { - private void InitBlock() - { - rawout = new float[36]; - tsOutCopy = new float[18]; - is_ratio = new float[576]; - is_pos = new int[576]; - new_slen = new int[4]; - samples2 = new float[32]; - samples1 = new float[32]; - } - public int[] scalefac_buffer; - - // MDM: removed, as this wasn't being used. - //private float CheckSumOut1d = 0.0f; - private int CheckSumHuff = 0; - private int[] is_1d; - private float[][][] ro; - private float[][][] lr; - private float[] out_1d; - private float[][] prevblck; - private float[][] k; - private int[] nonzero; - private Bitstream stream; - private Header header; - private SynthesisFilter filter1, filter2; - private Obuffer buffer; - private int which_channels; - private BitReserve br; - private III_side_info_t si; - - private temporaire2[] III_scalefac_t; - private temporaire2[] scalefac; - // private III_scalefac_t scalefac; - - private int max_gr; - private int frame_start; - private int part2_start; - private int channels; - private int first_channel; - private int last_channel; - private int sfreq; - - - /// Constructor. - /// - // REVIEW: these constructor arguments should be moved to the - // decodeFrame() method, where possible, so that one - public LayerIIIDecoder(Bitstream stream0, Header header0, SynthesisFilter filtera, SynthesisFilter filterb, Obuffer buffer0, int which_ch0) - { - InitBlock(); - huffcodetab.inithuff(); - is_1d = new int[SBLIMIT * SSLIMIT + 4]; - ro = new float[2][][]; - for (int i = 0; i < 2; i++) - { - ro[i] = new float[SBLIMIT][]; - for (int i2 = 0; i2 < SBLIMIT; i2++) - { - ro[i][i2] = new float[SSLIMIT]; - } - } - lr = new float[2][][]; - for (int i3 = 0; i3 < 2; i3++) - { - lr[i3] = new float[SBLIMIT][]; - for (int i4 = 0; i4 < SBLIMIT; i4++) - { - lr[i3][i4] = new float[SSLIMIT]; - } - } - out_1d = new float[SBLIMIT * SSLIMIT]; - prevblck = new float[2][]; - for (int i5 = 0; i5 < 2; i5++) - { - prevblck[i5] = new float[SBLIMIT * SSLIMIT]; - } - k = new float[2][]; - for (int i6 = 0; i6 < 2; i6++) - { - k[i6] = new float[SBLIMIT * SSLIMIT]; - } - nonzero = new int[2]; - - //III_scalefact_t - III_scalefac_t = new temporaire2[2]; - III_scalefac_t[0] = new temporaire2(); - III_scalefac_t[1] = new temporaire2(); - scalefac = III_scalefac_t; - // L3TABLE INIT - - sfBandIndex = new SBI[9]; // SZD: MPEG2.5 +3 indices - int[] l0 = new int[]{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576}; - int[] s0 = new int[]{0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192}; - int[] l1 = new int[]{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 330, 394, 464, 540, 576}; - int[] s1 = new int[]{0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192}; - int[] l2 = new int[]{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576}; - int[] s2 = new int[]{0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192}; - - int[] l3 = new int[]{0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418, 576}; - int[] s3 = new int[]{0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192}; - int[] l4 = new int[]{0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384, 576}; - int[] s4 = new int[]{0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192}; - int[] l5 = new int[]{0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550, 576}; - int[] s5 = new int[]{0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192}; - // SZD: MPEG2.5 - int[] l6 = new int[]{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576}; - int[] s6 = new int[]{0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192}; - int[] l7 = new int[]{0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464, 522, 576}; - int[] s7 = new int[]{0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192}; - int[] l8 = new int[]{0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570, 572, 574, 576}; - int[] s8 = new int[]{0, 8, 16, 24, 36, 52, 72, 96, 124, 160, 162, 164, 166, 192}; - - sfBandIndex[0] = new SBI(l0, s0); - sfBandIndex[1] = new SBI(l1, s1); - sfBandIndex[2] = new SBI(l2, s2); - - sfBandIndex[3] = new SBI(l3, s3); - sfBandIndex[4] = new SBI(l4, s4); - sfBandIndex[5] = new SBI(l5, s5); - //SZD: MPEG2.5 - sfBandIndex[6] = new SBI(l6, s6); - sfBandIndex[7] = new SBI(l7, s7); - sfBandIndex[8] = new SBI(l8, s8); - // END OF L3TABLE INIT - - if (reorder_table == null) - { - // SZD: generate LUT - reorder_table = new int[9][]; - for (int i = 0; i < 9; i++) - reorder_table[i] = reorder(sfBandIndex[i].s); - } - - // Sftable - int[] ll0 = new int[]{0, 6, 11, 16, 21}; - int[] ss0 = new int[]{0, 6, 12}; - sftable = new Sftable(this, ll0, ss0); - // END OF Sftable - - // scalefac_buffer - scalefac_buffer = new int[54]; - // END OF scalefac_buffer - - stream = stream0; - header = header0; - filter1 = filtera; - filter2 = filterb; - buffer = buffer0; - which_channels = which_ch0; - - frame_start = 0; - channels = (header.mode() == Header.SINGLE_CHANNEL)?1:2; - max_gr = (header.version() == Header.MPEG1)?2:1; - - sfreq = header.sample_frequency() + ((header.version() == Header.MPEG1)?3:(header.version() == Header.MPEG25_LSF)?6:0); // SZD - - if (channels == 2) - { - switch (which_channels) - { - - case (int)OutputChannelsEnum.LEFT_CHANNEL: - case (int)OutputChannelsEnum.DOWNMIX_CHANNELS: - first_channel = last_channel = 0; - break; - - - case (int)OutputChannelsEnum.RIGHT_CHANNEL: - first_channel = last_channel = 1; - break; - - - case (int)OutputChannelsEnum.BOTH_CHANNELS: - default: - first_channel = 0; - last_channel = 1; - break; - } - } - else - { - first_channel = last_channel = 0; - } - - for (int ch = 0; ch < 2; ch++) - for (int j = 0; j < 576; j++) - prevblck[ch][j] = 0.0f; - - nonzero[0] = nonzero[1] = 576; - - br = new BitReserve(); - si = new III_side_info_t(); - } - - /// Notify decoder that a seek is being made. - /// - public void seek_notify() - { - frame_start = 0; - for (int ch = 0; ch < 2; ch++) - for (int j = 0; j < 576; j++) - prevblck[ch][j] = 0.0f; - br = new BitReserve(); - } - - public void decodeFrame() - { - decode(); - } - - /// Decode one frame, filling the buffer with the output samples. - /// - - // subband samples are buffered and passed to the - // SynthesisFilter in one go. - //UPGRADE_NOTE: The initialization of 'samples1' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private float[] samples1; - //UPGRADE_NOTE: The initialization of 'samples2' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private float[] samples2; - - public void decode() - { - int nSlots = header.slots(); - int flush_main; - int gr, ch, ss, sb, sb18; - int main_data_end; - int bytes_to_discard; - int i; - - get_side_info(); - - for (i = 0; i < nSlots; i++) - br.hputbuf(stream.get_bits(8)); - - main_data_end = SupportClass.URShift(br.hsstell(), 3); // of previous frame - - if ((flush_main = (br.hsstell() & 7)) != 0) - { - br.hgetbits(8 - flush_main); - main_data_end++; - } - - bytes_to_discard = frame_start - main_data_end - si.main_data_begin; - - frame_start += nSlots; - - if (bytes_to_discard < 0) - return ; - - if (main_data_end > 4096) - { - frame_start -= 4096; - br.rewindNbytes(4096); - } - - for (; bytes_to_discard > 0; bytes_to_discard--) - br.hgetbits(8); - - for (gr = 0; gr < max_gr; gr++) - { - - for (ch = 0; ch < channels; ch++) - { - part2_start = br.hsstell(); - - if (header.version() == Header.MPEG1) - get_scale_factors(ch, gr); - // MPEG-2 LSF, SZD: MPEG-2.5 LSF - else - get_LSF_scale_factors(ch, gr); - - huffman_decode(ch, gr); - // System.out.println("CheckSum HuffMan = " + CheckSumHuff); - dequantize_sample(ro[ch], ch, gr); - } - - stereo(gr); - - if ((which_channels == OutputChannels.DOWNMIX_CHANNELS) && (channels > 1)) - do_downmix(); - - for (ch = first_channel; ch <= last_channel; ch++) - { - - reorder(lr[ch], ch, gr); - antialias(ch, gr); - //for (int hb = 0;hb<576;hb++) CheckSumOut1d = CheckSumOut1d + out_1d[hb]; - //System.out.println("CheckSumOut1d = "+CheckSumOut1d); - - hybrid(ch, gr); - - //for (int hb = 0;hb<576;hb++) CheckSumOut1d = CheckSumOut1d + out_1d[hb]; - //System.out.println("CheckSumOut1d = "+CheckSumOut1d); - - for (sb18 = 18; sb18 < 576; sb18 += 36) - // Frequency inversion - for (ss = 1; ss < SSLIMIT; ss += 2) - out_1d[sb18 + ss] = - out_1d[sb18 + ss]; - - if ((ch == 0) || (which_channels == OutputChannels.RIGHT_CHANNEL)) - { - for (ss = 0; ss < SSLIMIT; ss++) - { - // Polyphase synthesis - sb = 0; - for (sb18 = 0; sb18 < 576; sb18 += 18) - { - samples1[sb] = out_1d[sb18 + ss]; - //filter1.input_sample(out_1d[sb18+ss], sb); - sb++; - } - //buffer.appendSamples(0, samples1); - //Console.WriteLine("Adding samples right into output buffer"); - filter1.input_samples(samples1); - filter1.calculate_pcm_samples(buffer); - } - } - else - { - for (ss = 0; ss < SSLIMIT; ss++) - { - // Polyphase synthesis - sb = 0; - for (sb18 = 0; sb18 < 576; sb18 += 18) - { - samples2[sb] = out_1d[sb18 + ss]; - //filter2.input_sample(out_1d[sb18+ss], sb); - sb++; - } - //buffer.appendSamples(1, samples2); - //Console.WriteLine("Adding samples right into output buffer"); - filter2.input_samples(samples2); - filter2.calculate_pcm_samples(buffer); - } - } - } - // channels - } - // granule - - - // System.out.println("Counter = ................................."+counter); - //if (counter < 609) - //{ - counter++; - buffer.write_buffer(1); - //} - //else if (counter == 609) - //{ - // buffer.close(); - // counter++; - //} - //else - //{ - //} - } - - /// Reads the side info from the stream, assuming the entire. - /// frame has been read already. - /// Mono : 136 bits (= 17 bytes) - /// Stereo : 256 bits (= 32 bytes) - /// - private bool get_side_info() - { - int ch, gr; - if (header.version() == Header.MPEG1) - { - - si.main_data_begin = stream.get_bits(9); - if (channels == 1) - si.private_bits = stream.get_bits(5); - else - si.private_bits = stream.get_bits(3); - - for (ch = 0; ch < channels; ch++) - { - si.ch[ch].scfsi[0] = stream.get_bits(1); - si.ch[ch].scfsi[1] = stream.get_bits(1); - si.ch[ch].scfsi[2] = stream.get_bits(1); - si.ch[ch].scfsi[3] = stream.get_bits(1); - } - - for (gr = 0; gr < 2; gr++) - { - for (ch = 0; ch < channels; ch++) - { - si.ch[ch].gr[gr].part2_3_length = stream.get_bits(12); - si.ch[ch].gr[gr].big_values = stream.get_bits(9); - si.ch[ch].gr[gr].global_gain = stream.get_bits(8); - si.ch[ch].gr[gr].scalefac_compress = stream.get_bits(4); - si.ch[ch].gr[gr].window_switching_flag = stream.get_bits(1); - if ((si.ch[ch].gr[gr].window_switching_flag) != 0) - { - si.ch[ch].gr[gr].block_type = stream.get_bits(2); - si.ch[ch].gr[gr].mixed_block_flag = stream.get_bits(1); - - si.ch[ch].gr[gr].table_select[0] = stream.get_bits(5); - si.ch[ch].gr[gr].table_select[1] = stream.get_bits(5); - - si.ch[ch].gr[gr].subblock_gain[0] = stream.get_bits(3); - si.ch[ch].gr[gr].subblock_gain[1] = stream.get_bits(3); - si.ch[ch].gr[gr].subblock_gain[2] = stream.get_bits(3); - - // Set region_count parameters since they are implicit in this case. - - if (si.ch[ch].gr[gr].block_type == 0) - { - // Side info bad: block_type == 0 in split block - return false; - } - else if (si.ch[ch].gr[gr].block_type == 2 && si.ch[ch].gr[gr].mixed_block_flag == 0) - { - si.ch[ch].gr[gr].region0_count = 8; - } - else - { - si.ch[ch].gr[gr].region0_count = 7; - } - si.ch[ch].gr[gr].region1_count = 20 - si.ch[ch].gr[gr].region0_count; - } - else - { - si.ch[ch].gr[gr].table_select[0] = stream.get_bits(5); - si.ch[ch].gr[gr].table_select[1] = stream.get_bits(5); - si.ch[ch].gr[gr].table_select[2] = stream.get_bits(5); - si.ch[ch].gr[gr].region0_count = stream.get_bits(4); - si.ch[ch].gr[gr].region1_count = stream.get_bits(3); - si.ch[ch].gr[gr].block_type = 0; - } - si.ch[ch].gr[gr].preflag = stream.get_bits(1); - si.ch[ch].gr[gr].scalefac_scale = stream.get_bits(1); - si.ch[ch].gr[gr].count1table_select = stream.get_bits(1); - } - } - } - else - { - // MPEG-2 LSF, SZD: MPEG-2.5 LSF - - si.main_data_begin = stream.get_bits(8); - if (channels == 1) - si.private_bits = stream.get_bits(1); - else - si.private_bits = stream.get_bits(2); - - for (ch = 0; ch < channels; ch++) - { - - si.ch[ch].gr[0].part2_3_length = stream.get_bits(12); - si.ch[ch].gr[0].big_values = stream.get_bits(9); - si.ch[ch].gr[0].global_gain = stream.get_bits(8); - si.ch[ch].gr[0].scalefac_compress = stream.get_bits(9); - si.ch[ch].gr[0].window_switching_flag = stream.get_bits(1); - - if ((si.ch[ch].gr[0].window_switching_flag) != 0) - { - - si.ch[ch].gr[0].block_type = stream.get_bits(2); - si.ch[ch].gr[0].mixed_block_flag = stream.get_bits(1); - si.ch[ch].gr[0].table_select[0] = stream.get_bits(5); - si.ch[ch].gr[0].table_select[1] = stream.get_bits(5); - - si.ch[ch].gr[0].subblock_gain[0] = stream.get_bits(3); - si.ch[ch].gr[0].subblock_gain[1] = stream.get_bits(3); - si.ch[ch].gr[0].subblock_gain[2] = stream.get_bits(3); - - // Set region_count parameters since they are implicit in this case. - - if (si.ch[ch].gr[0].block_type == 0) - { - // Side info bad: block_type == 0 in split block - return false; - } - else if (si.ch[ch].gr[0].block_type == 2 && si.ch[ch].gr[0].mixed_block_flag == 0) - { - si.ch[ch].gr[0].region0_count = 8; - } - else - { - si.ch[ch].gr[0].region0_count = 7; - si.ch[ch].gr[0].region1_count = 20 - si.ch[ch].gr[0].region0_count; - } - } - else - { - si.ch[ch].gr[0].table_select[0] = stream.get_bits(5); - si.ch[ch].gr[0].table_select[1] = stream.get_bits(5); - si.ch[ch].gr[0].table_select[2] = stream.get_bits(5); - si.ch[ch].gr[0].region0_count = stream.get_bits(4); - si.ch[ch].gr[0].region1_count = stream.get_bits(3); - si.ch[ch].gr[0].block_type = 0; - } - - si.ch[ch].gr[0].scalefac_scale = stream.get_bits(1); - si.ch[ch].gr[0].count1table_select = stream.get_bits(1); - } - // for(ch=0; ch* - /// - private void get_scale_factors(int ch, int gr) - { - int sfb, window; - gr_info_s gr_info = (si.ch[ch].gr[gr]); - int scale_comp = gr_info.scalefac_compress; - int length0 = slen[0][scale_comp]; - int length1 = slen[1][scale_comp]; - - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - if ((gr_info.mixed_block_flag) != 0) - { - // MIXED - for (sfb = 0; sfb < 8; sfb++) - scalefac[ch].l[sfb] = br.hgetbits(slen[0][gr_info.scalefac_compress]); - for (sfb = 3; sfb < 6; sfb++) - for (window = 0; window < 3; window++) - scalefac[ch].s[window][sfb] = br.hgetbits(slen[0][gr_info.scalefac_compress]); - for (sfb = 6; sfb < 12; sfb++) - for (window = 0; window < 3; window++) - scalefac[ch].s[window][sfb] = br.hgetbits(slen[1][gr_info.scalefac_compress]); - for (sfb = 12, window = 0; window < 3; window++) - scalefac[ch].s[window][sfb] = 0; - } - else - { - // SHORT - - scalefac[ch].s[0][0] = br.hgetbits(length0); - scalefac[ch].s[1][0] = br.hgetbits(length0); - scalefac[ch].s[2][0] = br.hgetbits(length0); - scalefac[ch].s[0][1] = br.hgetbits(length0); - scalefac[ch].s[1][1] = br.hgetbits(length0); - scalefac[ch].s[2][1] = br.hgetbits(length0); - scalefac[ch].s[0][2] = br.hgetbits(length0); - scalefac[ch].s[1][2] = br.hgetbits(length0); - scalefac[ch].s[2][2] = br.hgetbits(length0); - scalefac[ch].s[0][3] = br.hgetbits(length0); - scalefac[ch].s[1][3] = br.hgetbits(length0); - scalefac[ch].s[2][3] = br.hgetbits(length0); - scalefac[ch].s[0][4] = br.hgetbits(length0); - scalefac[ch].s[1][4] = br.hgetbits(length0); - scalefac[ch].s[2][4] = br.hgetbits(length0); - scalefac[ch].s[0][5] = br.hgetbits(length0); - scalefac[ch].s[1][5] = br.hgetbits(length0); - scalefac[ch].s[2][5] = br.hgetbits(length0); - scalefac[ch].s[0][6] = br.hgetbits(length1); - scalefac[ch].s[1][6] = br.hgetbits(length1); - scalefac[ch].s[2][6] = br.hgetbits(length1); - scalefac[ch].s[0][7] = br.hgetbits(length1); - scalefac[ch].s[1][7] = br.hgetbits(length1); - scalefac[ch].s[2][7] = br.hgetbits(length1); - scalefac[ch].s[0][8] = br.hgetbits(length1); - scalefac[ch].s[1][8] = br.hgetbits(length1); - scalefac[ch].s[2][8] = br.hgetbits(length1); - scalefac[ch].s[0][9] = br.hgetbits(length1); - scalefac[ch].s[1][9] = br.hgetbits(length1); - scalefac[ch].s[2][9] = br.hgetbits(length1); - scalefac[ch].s[0][10] = br.hgetbits(length1); - scalefac[ch].s[1][10] = br.hgetbits(length1); - scalefac[ch].s[2][10] = br.hgetbits(length1); - scalefac[ch].s[0][11] = br.hgetbits(length1); - scalefac[ch].s[1][11] = br.hgetbits(length1); - scalefac[ch].s[2][11] = br.hgetbits(length1); - scalefac[ch].s[0][12] = 0; - scalefac[ch].s[1][12] = 0; - scalefac[ch].s[2][12] = 0; - } - // SHORT - } - else - { - // LONG types 0,1,3 - - if ((si.ch[ch].scfsi[0] == 0) || (gr == 0)) - { - scalefac[ch].l[0] = br.hgetbits(length0); - scalefac[ch].l[1] = br.hgetbits(length0); - scalefac[ch].l[2] = br.hgetbits(length0); - scalefac[ch].l[3] = br.hgetbits(length0); - scalefac[ch].l[4] = br.hgetbits(length0); - scalefac[ch].l[5] = br.hgetbits(length0); - } - if ((si.ch[ch].scfsi[1] == 0) || (gr == 0)) - { - scalefac[ch].l[6] = br.hgetbits(length0); - scalefac[ch].l[7] = br.hgetbits(length0); - scalefac[ch].l[8] = br.hgetbits(length0); - scalefac[ch].l[9] = br.hgetbits(length0); - scalefac[ch].l[10] = br.hgetbits(length0); - } - if ((si.ch[ch].scfsi[2] == 0) || (gr == 0)) - { - scalefac[ch].l[11] = br.hgetbits(length1); - scalefac[ch].l[12] = br.hgetbits(length1); - scalefac[ch].l[13] = br.hgetbits(length1); - scalefac[ch].l[14] = br.hgetbits(length1); - scalefac[ch].l[15] = br.hgetbits(length1); - } - if ((si.ch[ch].scfsi[3] == 0) || (gr == 0)) - { - scalefac[ch].l[16] = br.hgetbits(length1); - scalefac[ch].l[17] = br.hgetbits(length1); - scalefac[ch].l[18] = br.hgetbits(length1); - scalefac[ch].l[19] = br.hgetbits(length1); - scalefac[ch].l[20] = br.hgetbits(length1); - } - - scalefac[ch].l[21] = 0; - scalefac[ch].l[22] = 0; - } - } - - /// * - /// - // MDM: new_slen is fully initialized before use, no need - // to reallocate array. - //UPGRADE_NOTE: Final was removed from the declaration of 'new_slen '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 'new_slen' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private int[] new_slen; - - private void get_LSF_scale_data(int ch, int gr) - { - - int scalefac_comp, int_scalefac_comp; - int mode_ext = header.mode_extension(); - int m; - int blocktypenumber; - int blocknumber = 0; - - gr_info_s gr_info = (si.ch[ch].gr[gr]); - - scalefac_comp = gr_info.scalefac_compress; - - if (gr_info.block_type == 2) - { - if (gr_info.mixed_block_flag == 0) - blocktypenumber = 1; - else if (gr_info.mixed_block_flag == 1) - blocktypenumber = 2; - else - blocktypenumber = 0; - } - else - { - blocktypenumber = 0; - } - - if (!(((mode_ext == 1) || (mode_ext == 3)) && (ch == 1))) - { - - if (scalefac_comp < 400) - { - - new_slen[0] = (SupportClass.URShift(scalefac_comp, 4)) / 5; - new_slen[1] = (SupportClass.URShift(scalefac_comp, 4)) % 5; - new_slen[2] = SupportClass.URShift((scalefac_comp & 0xF), 2); - new_slen[3] = (scalefac_comp & 3); - si.ch[ch].gr[gr].preflag = 0; - blocknumber = 0; - } - else if (scalefac_comp < 500) - { - - new_slen[0] = (SupportClass.URShift((scalefac_comp - 400), 2)) / 5; - new_slen[1] = (SupportClass.URShift((scalefac_comp - 400), 2)) % 5; - new_slen[2] = (scalefac_comp - 400) & 3; - new_slen[3] = 0; - si.ch[ch].gr[gr].preflag = 0; - blocknumber = 1; - } - else if (scalefac_comp < 512) - { - - new_slen[0] = (scalefac_comp - 500) / 3; - new_slen[1] = (scalefac_comp - 500) % 3; - new_slen[2] = 0; - new_slen[3] = 0; - si.ch[ch].gr[gr].preflag = 1; - blocknumber = 2; - } - } - - if ((((mode_ext == 1) || (mode_ext == 3)) && (ch == 1))) - { - int_scalefac_comp = SupportClass.URShift(scalefac_comp, 1); - - if (int_scalefac_comp < 180) - { - new_slen[0] = int_scalefac_comp / 36; - new_slen[1] = (int_scalefac_comp % 36) / 6; - new_slen[2] = (int_scalefac_comp % 36) % 6; - new_slen[3] = 0; - si.ch[ch].gr[gr].preflag = 0; - blocknumber = 3; - } - else if (int_scalefac_comp < 244) - { - new_slen[0] = SupportClass.URShift(((int_scalefac_comp - 180) & 0x3F), 4); - new_slen[1] = SupportClass.URShift(((int_scalefac_comp - 180) & 0xF), 2); - new_slen[2] = (int_scalefac_comp - 180) & 3; - new_slen[3] = 0; - si.ch[ch].gr[gr].preflag = 0; - blocknumber = 4; - } - else if (int_scalefac_comp < 255) - { - new_slen[0] = (int_scalefac_comp - 244) / 3; - new_slen[1] = (int_scalefac_comp - 244) % 3; - new_slen[2] = 0; - new_slen[3] = 0; - si.ch[ch].gr[gr].preflag = 0; - blocknumber = 5; - } - } - - for (int x = 0; x < 45; x++) - // why 45, not 54? - scalefac_buffer[x] = 0; - - m = 0; - for (int i = 0; i < 4; i++) - { - for (int j = 0; j < nr_of_sfb_block[blocknumber][blocktypenumber][i]; j++) - { - scalefac_buffer[m] = (new_slen[i] == 0)?0:br.hgetbits(new_slen[i]); - m++; - } - // for (unint32 j ... - } - // for (uint32 i ... - } - - /// * - /// - private void get_LSF_scale_factors(int ch, int gr) - { - int m = 0; - int sfb, window; - gr_info_s gr_info = (si.ch[ch].gr[gr]); - - get_LSF_scale_data(ch, gr); - - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - if (gr_info.mixed_block_flag != 0) - { - // MIXED - for (sfb = 0; sfb < 8; sfb++) - { - scalefac[ch].l[sfb] = scalefac_buffer[m]; - m++; - } - for (sfb = 3; sfb < 12; sfb++) - { - for (window = 0; window < 3; window++) - { - scalefac[ch].s[window][sfb] = scalefac_buffer[m]; - m++; - } - } - for (window = 0; window < 3; window++) - scalefac[ch].s[window][12] = 0; - } - else - { - // SHORT - - for (sfb = 0; sfb < 12; sfb++) - { - for (window = 0; window < 3; window++) - { - scalefac[ch].s[window][sfb] = scalefac_buffer[m]; - m++; - } - } - - for (window = 0; window < 3; window++) - scalefac[ch].s[window][12] = 0; - } - } - else - { - // LONG types 0,1,3 - - for (sfb = 0; sfb < 21; sfb++) - { - scalefac[ch].l[sfb] = scalefac_buffer[m]; - m++; - } - scalefac[ch].l[21] = 0; // Jeff - scalefac[ch].l[22] = 0; - } - } - - /// * - /// - internal int[] x = new int[]{0}; - internal int[] y = new int[]{0}; - internal int[] v = new int[]{0}; - internal int[] w = new int[]{0}; - private void huffman_decode(int ch, int gr) - { - x[0] = 0; - y[0] = 0; - v[0] = 0; - w[0] = 0; - - int part2_3_end = part2_start + si.ch[ch].gr[gr].part2_3_length; - int num_bits; - int region1Start; - int region2Start; - int index; - - int buf, buf1; - - huffcodetab h; - - // Find region boundary for short block case - - if (((si.ch[ch].gr[gr].window_switching_flag) != 0) && (si.ch[ch].gr[gr].block_type == 2)) - { - - // Region2. - //MS: Extrahandling for 8KHZ - region1Start = (sfreq == 8)?72:36; // sfb[9/3]*3=36 or in case 8KHZ = 72 - region2Start = 576; // No Region2 for short block case - } - else - { - // Find region boundary for long block case - - buf = si.ch[ch].gr[gr].region0_count + 1; - buf1 = buf + si.ch[ch].gr[gr].region1_count + 1; - - if (buf1 > sfBandIndex[sfreq].l.Length - 1) - buf1 = sfBandIndex[sfreq].l.Length - 1; - - region1Start = sfBandIndex[sfreq].l[buf]; - region2Start = sfBandIndex[sfreq].l[buf1]; /* MI */ - } - - index = 0; - // Read bigvalues area - for (int i = 0; i < (si.ch[ch].gr[gr].big_values << 1); i += 2) - { - if (i < region1Start) - h = huffcodetab.ht[si.ch[ch].gr[gr].table_select[0]]; - else if (i < region2Start) - h = huffcodetab.ht[si.ch[ch].gr[gr].table_select[1]]; - else - h = huffcodetab.ht[si.ch[ch].gr[gr].table_select[2]]; - - huffcodetab.huffman_decoder(h, x, y, v, w, br); - - is_1d[index++] = x[0]; - is_1d[index++] = y[0]; - CheckSumHuff = CheckSumHuff + x[0] + y[0]; - // System.out.println("x = "+x[0]+" y = "+y[0]); - } - - // Read count1 area - h = huffcodetab.ht[si.ch[ch].gr[gr].count1table_select + 32]; - num_bits = br.hsstell(); - - while ((num_bits < part2_3_end) && (index < 576)) - { - - huffcodetab.huffman_decoder(h, x, y, v, w, br); - - is_1d[index++] = v[0]; - is_1d[index++] = w[0]; - is_1d[index++] = x[0]; - is_1d[index++] = y[0]; - CheckSumHuff = CheckSumHuff + v[0] + w[0] + x[0] + y[0]; - // System.out.println("v = "+v[0]+" w = "+w[0]); - // System.out.println("x = "+x[0]+" y = "+y[0]); - num_bits = br.hsstell(); - } - - if (num_bits > part2_3_end) - { - br.rewindNbits(num_bits - part2_3_end); - index -= 4; - } - - num_bits = br.hsstell(); - - // Dismiss stuffing bits - if (num_bits < part2_3_end) - br.hgetbits(part2_3_end - num_bits); - - // Zero out rest - - if (index < 576) - nonzero[ch] = index; - else - nonzero[ch] = 576; - - if (index < 0) - index = 0; - - // may not be necessary - for (; index < 576; index++) - is_1d[index] = 0; - } - - /// * - /// - private void i_stereo_k_values(int is_pos, int io_type, int i) - { - if (is_pos == 0) - { - k[0][i] = 1.0f; - k[1][i] = 1.0f; - } - else if ((is_pos & 1) != 0) - { - k[0][i] = io[io_type][SupportClass.URShift((is_pos + 1), 1)]; - k[1][i] = 1.0f; - } - else - { - k[0][i] = 1.0f; - k[1][i] = io[io_type][SupportClass.URShift(is_pos, 1)]; - } - } - - /// * - /// - private void dequantize_sample(float[][] xr, int ch, int gr) - { - gr_info_s gr_info = (si.ch[ch].gr[gr]); - int cb = 0; - int next_cb_boundary; - int cb_begin = 0; - int cb_width = 0; - int index = 0, t_index, j; - float g_gain; - float[][] xr_1d = xr; - - // choose correct scalefactor band per block type, initalize boundary - - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - if (gr_info.mixed_block_flag != 0) - next_cb_boundary = sfBandIndex[sfreq].l[1]; - // LONG blocks: 0,1,3 - else - { - cb_width = sfBandIndex[sfreq].s[1]; - next_cb_boundary = (cb_width << 2) - cb_width; - cb_begin = 0; - } - } - else - { - next_cb_boundary = sfBandIndex[sfreq].l[1]; // LONG blocks: 0,1,3 - } - - // Compute overall (global) scaling. - - g_gain = (float) System.Math.Pow(2.0, (0.25 * (gr_info.global_gain - 210.0))); - - for (j = 0; j < nonzero[ch]; j++) - { - // Modif E.B 02/22/99 - int reste = j % SSLIMIT; - int quotien = (int) ((j - reste) / SSLIMIT); - if (is_1d[j] == 0) - xr_1d[quotien][reste] = 0.0f; - else - { - int abv = is_1d[j]; - if (is_1d[j] > 0) - xr_1d[quotien][reste] = g_gain * t_43[abv]; - else - xr_1d[quotien][reste] = - g_gain * t_43[- abv]; - } - } - - // apply formula per block type - - for (j = 0; j < nonzero[ch]; j++) - { - // Modif E.B 02/22/99 - int reste = j % SSLIMIT; - int quotien = (int) ((j - reste) / SSLIMIT); - - if (index == next_cb_boundary) - { - /* Adjust critical band boundary */ - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - if (gr_info.mixed_block_flag != 0) - { - - if (index == sfBandIndex[sfreq].l[8]) - { - next_cb_boundary = sfBandIndex[sfreq].s[4]; - next_cb_boundary = (next_cb_boundary << 2) - next_cb_boundary; - cb = 3; - cb_width = sfBandIndex[sfreq].s[4] - sfBandIndex[sfreq].s[3]; - - cb_begin = sfBandIndex[sfreq].s[3]; - cb_begin = (cb_begin << 2) - cb_begin; - } - else if (index < sfBandIndex[sfreq].l[8]) - { - - next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; - } - else - { - - next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1]; - next_cb_boundary = (next_cb_boundary << 2) - next_cb_boundary; - - cb_begin = sfBandIndex[sfreq].s[cb]; - cb_width = sfBandIndex[sfreq].s[cb + 1] - cb_begin; - cb_begin = (cb_begin << 2) - cb_begin; - } - } - else - { - - next_cb_boundary = sfBandIndex[sfreq].s[(++cb) + 1]; - next_cb_boundary = (next_cb_boundary << 2) - next_cb_boundary; - - cb_begin = sfBandIndex[sfreq].s[cb]; - cb_width = sfBandIndex[sfreq].s[cb + 1] - cb_begin; - cb_begin = (cb_begin << 2) - cb_begin; - } - } - else - { - // long blocks - - next_cb_boundary = sfBandIndex[sfreq].l[(++cb) + 1]; - } - } - - // Do long/short dependent scaling operations - - if ((gr_info.window_switching_flag != 0) && (((gr_info.block_type == 2) && (gr_info.mixed_block_flag == 0)) || ((gr_info.block_type == 2) && (gr_info.mixed_block_flag != 0) && (j >= 36)))) - { - - t_index = (index - cb_begin) / cb_width; - /* xr[sb][ss] *= pow(2.0, ((-2.0 * gr_info.subblock_gain[t_index]) - -(0.5 * (1.0 + gr_info.scalefac_scale) - * scalefac[ch].s[t_index][cb]))); */ - int idx = scalefac[ch].s[t_index][cb] << gr_info.scalefac_scale; - idx += (gr_info.subblock_gain[t_index] << 2); - - xr_1d[quotien][reste] *= two_to_negative_half_pow[idx]; - } - else - { - // LONG block types 0,1,3 & 1st 2 subbands of switched blocks - /* xr[sb][ss] *= pow(2.0, -0.5 * (1.0+gr_info.scalefac_scale) - * (scalefac[ch].l[cb] - + gr_info.preflag * pretab[cb])); */ - int idx = scalefac[ch].l[cb]; - - if (gr_info.preflag != 0) - idx += pretab[cb]; - - idx = idx << gr_info.scalefac_scale; - xr_1d[quotien][reste] *= two_to_negative_half_pow[idx]; - } - index++; - } - - for (j = nonzero[ch]; j < 576; j++) - { - // Modif E.B 02/22/99 - int reste = j % SSLIMIT; - int quotien = (int) ((j - reste) / SSLIMIT); - if (reste < 0) - reste = 0; - if (quotien < 0) - quotien = 0; - xr_1d[quotien][reste] = 0.0f; - } - - return ; - } - - /// * - /// - private void reorder(float[][] xr, int ch, int gr) - { - gr_info_s gr_info = (si.ch[ch].gr[gr]); - int freq, freq3; - int index; - int sfb, sfb_start, sfb_lines; - int src_line, des_line; - float[][] xr_1d = xr; - - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - - for (index = 0; index < 576; index++) - out_1d[index] = 0.0f; - - if (gr_info.mixed_block_flag != 0) - { - // NO REORDER FOR LOW 2 SUBBANDS - for (index = 0; index < 36; index++) - { - // Modif E.B 02/22/99 - int reste = index % SSLIMIT; - int quotien = (int) ((index - reste) / SSLIMIT); - out_1d[index] = xr_1d[quotien][reste]; - } - // REORDERING FOR REST SWITCHED SHORT - for (sfb = 3, sfb_start = sfBandIndex[sfreq].s[3], sfb_lines = sfBandIndex[sfreq].s[4] - sfb_start; sfb < 13; sfb++, sfb_start = sfBandIndex[sfreq].s[sfb], sfb_lines = sfBandIndex[sfreq].s[sfb + 1] - sfb_start) - { - int sfb_start3 = (sfb_start << 2) - sfb_start; - - for (freq = 0, freq3 = 0; freq < sfb_lines; freq++, freq3 += 3) - { - - src_line = sfb_start3 + freq; - des_line = sfb_start3 + freq3; - // Modif E.B 02/22/99 - int reste = src_line % SSLIMIT; - int quotien = (int) ((src_line - reste) / SSLIMIT); - - out_1d[des_line] = xr_1d[quotien][reste]; - src_line += sfb_lines; - des_line++; - - reste = src_line % SSLIMIT; - quotien = (int) ((src_line - reste) / SSLIMIT); - - out_1d[des_line] = xr_1d[quotien][reste]; - src_line += sfb_lines; - des_line++; - - reste = src_line % SSLIMIT; - quotien = (int) ((src_line - reste) / SSLIMIT); - - out_1d[des_line] = xr_1d[quotien][reste]; - } - } - } - else - { - // pure short - for (index = 0; index < 576; index++) - { - int j = reorder_table[sfreq][index]; - int reste = j % SSLIMIT; - int quotien = (int) ((j - reste) / SSLIMIT); - out_1d[index] = xr_1d[quotien][reste]; - } - } - } - else - { - // long blocks - for (index = 0; index < 576; index++) - { - // Modif E.B 02/22/99 - int reste = index % SSLIMIT; - int quotien = (int) ((index - reste) / SSLIMIT); - out_1d[index] = xr_1d[quotien][reste]; - } - } - } - - /// * - /// - - //UPGRADE_NOTE: The initialization of 'is_pos' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - internal int[] is_pos; - //UPGRADE_NOTE: The initialization of 'is_ratio' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - internal float[] is_ratio; - - private void stereo(int gr) - { - int sb, ss; - - if (channels == 1) - { - // mono , bypass xr[0][][] to lr[0][][] - - for (sb = 0; sb < SBLIMIT; sb++) - for (ss = 0; ss < SSLIMIT; ss += 3) - { - lr[0][sb][ss] = ro[0][sb][ss]; - lr[0][sb][ss + 1] = ro[0][sb][ss + 1]; - lr[0][sb][ss + 2] = ro[0][sb][ss + 2]; - } - } - else - { - - gr_info_s gr_info = (si.ch[0].gr[gr]); - int mode_ext = header.mode_extension(); - int sfb; - int i; - int lines, temp, temp2; - - bool ms_stereo = ((header.mode() == Header.JOINT_STEREO) && ((mode_ext & 0x2) != 0)); - bool i_stereo = ((header.mode() == Header.JOINT_STEREO) && ((mode_ext & 0x1) != 0)); - bool lsf = ((header.version() == Header.MPEG2_LSF || header.version() == Header.MPEG25_LSF)); // SZD - - int io_type = (gr_info.scalefac_compress & 1); - - // initialization - - for (i = 0; i < 576; i++) - { - is_pos[i] = 7; - - is_ratio[i] = 0.0f; - } - - if (i_stereo) - { - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2)) - { - if (gr_info.mixed_block_flag != 0) - { - - int max_sfb = 0; - - for (int j = 0; j < 3; j++) - { - int sfbcnt; - sfbcnt = 2; - for (sfb = 12; sfb >= 3; sfb--) - { - i = sfBandIndex[sfreq].s[sfb]; - lines = sfBandIndex[sfreq].s[sfb + 1] - i; - i = (i << 2) - i + (j + 1) * lines - 1; - - while (lines > 0) - { - if (ro[1][i / 18][i % 18] != 0.0f) - { - // MDM: in java, array access is very slow. - // Is quicker to compute div and mod values. - //if (ro[1][ss_div[i]][ss_mod[i]] != 0.0f) { - sfbcnt = sfb; - sfb = - 10; - lines = - 10; - } - - lines--; - i--; - } // while (lines > 0) - } - // for (sfb=12 ... - sfb = sfbcnt + 1; - - if (sfb > max_sfb) - max_sfb = sfb; - - while (sfb < 12) - { - temp = sfBandIndex[sfreq].s[sfb]; - sb = sfBandIndex[sfreq].s[sfb + 1] - temp; - i = (temp << 2) - temp + j * sb; - - for (; sb > 0; sb--) - { - is_pos[i] = scalefac[1].s[j][sfb]; - if (is_pos[i] != 7) - if (lsf) - i_stereo_k_values(is_pos[i], io_type, i); - else - is_ratio[i] = TAN12[is_pos[i]]; - - i++; - } - // for (; sb>0... - sfb++; - } // while (sfb < 12) - sfb = sfBandIndex[sfreq].s[10]; - sb = sfBandIndex[sfreq].s[11] - sfb; - sfb = (sfb << 2) - sfb + j * sb; - temp = sfBandIndex[sfreq].s[11]; - sb = sfBandIndex[sfreq].s[12] - temp; - i = (temp << 2) - temp + j * sb; - - for (; sb > 0; sb--) - { - is_pos[i] = is_pos[sfb]; - - if (lsf) - { - k[0][i] = k[0][sfb]; - k[1][i] = k[1][sfb]; - } - else - { - is_ratio[i] = is_ratio[sfb]; - } - i++; - } - // for (; sb > 0 ... - } - if (max_sfb <= 3) - { - i = 2; - ss = 17; - sb = - 1; - while (i >= 0) - { - if (ro[1][i][ss] != 0.0f) - { - sb = (i << 4) + (i << 1) + ss; - i = - 1; - } - else - { - ss--; - if (ss < 0) - { - i--; - ss = 17; - } - } - // if (ro ... - } // while (i>=0) - i = 0; - while (sfBandIndex[sfreq].l[i] <= sb) - i++; - sfb = i; - i = sfBandIndex[sfreq].l[i]; - for (; sfb < 8; sfb++) - { - sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; - for (; sb > 0; sb--) - { - is_pos[i] = scalefac[1].l[sfb]; - if (is_pos[i] != 7) - if (lsf) - i_stereo_k_values(is_pos[i], io_type, i); - else - is_ratio[i] = TAN12[is_pos[i]]; - i++; - } - // for (; sb>0 ... - } - // for (; sfb<8 ... - } - // for (j=0 ... - } - else - { - // if (gr_info.mixed_block_flag) - for (int j = 0; j < 3; j++) - { - int sfbcnt; - sfbcnt = - 1; - for (sfb = 12; sfb >= 0; sfb--) - { - temp = sfBandIndex[sfreq].s[sfb]; - lines = sfBandIndex[sfreq].s[sfb + 1] - temp; - i = (temp << 2) - temp + (j + 1) * lines - 1; - - while (lines > 0) - { - if (ro[1][i / 18][i % 18] != 0.0f) - { - // MDM: in java, array access is very slow. - // Is quicker to compute div and mod values. - //if (ro[1][ss_div[i]][ss_mod[i]] != 0.0f) { - sfbcnt = sfb; - sfb = - 10; - lines = - 10; - } - lines--; - i--; - } // while (lines > 0) */ - } - // for (sfb=12 ... - sfb = sfbcnt + 1; - while (sfb < 12) - { - temp = sfBandIndex[sfreq].s[sfb]; - sb = sfBandIndex[sfreq].s[sfb + 1] - temp; - i = (temp << 2) - temp + j * sb; - for (; sb > 0; sb--) - { - is_pos[i] = scalefac[1].s[j][sfb]; - if (is_pos[i] != 7) - if (lsf) - i_stereo_k_values(is_pos[i], io_type, i); - else - is_ratio[i] = TAN12[is_pos[i]]; - i++; - } - // for (; sb>0 ... - sfb++; - } // while (sfb<12) - - temp = sfBandIndex[sfreq].s[10]; - temp2 = sfBandIndex[sfreq].s[11]; - sb = temp2 - temp; - sfb = (temp << 2) - temp + j * sb; - sb = sfBandIndex[sfreq].s[12] - temp2; - i = (temp2 << 2) - temp2 + j * sb; - - for (; sb > 0; sb--) - { - is_pos[i] = is_pos[sfb]; - - if (lsf) - { - k[0][i] = k[0][sfb]; - k[1][i] = k[1][sfb]; - } - else - { - is_ratio[i] = is_ratio[sfb]; - } - i++; - } - // for (; sb>0 ... - } - // for (sfb=12 - } - // for (j=0 ... - } - else - { - // if (gr_info.window_switching_flag ... - i = 31; - ss = 17; - sb = 0; - while (i >= 0) - { - if (ro[1][i][ss] != 0.0f) - { - sb = (i << 4) + (i << 1) + ss; - i = - 1; - } - else - { - ss--; - if (ss < 0) - { - i--; - ss = 17; - } - } - } - i = 0; - while (sfBandIndex[sfreq].l[i] <= sb) - i++; - - sfb = i; - i = sfBandIndex[sfreq].l[i]; - for (; sfb < 21; sfb++) - { - sb = sfBandIndex[sfreq].l[sfb + 1] - sfBandIndex[sfreq].l[sfb]; - for (; sb > 0; sb--) - { - is_pos[i] = scalefac[1].l[sfb]; - if (is_pos[i] != 7) - if (lsf) - i_stereo_k_values(is_pos[i], io_type, i); - else - is_ratio[i] = TAN12[is_pos[i]]; - i++; - } - } - sfb = sfBandIndex[sfreq].l[20]; - for (sb = 576 - sfBandIndex[sfreq].l[21]; (sb > 0) && (i < 576); sb--) - { - is_pos[i] = is_pos[sfb]; // error here : i >=576 - - if (lsf) - { - k[0][i] = k[0][sfb]; - k[1][i] = k[1][sfb]; - } - else - { - is_ratio[i] = is_ratio[sfb]; - } - i++; - } - // if (gr_info.mixed_block_flag) - } - // if (gr_info.window_switching_flag ... - } - // if (i_stereo) - - i = 0; - for (sb = 0; sb < SBLIMIT; sb++) - for (ss = 0; ss < SSLIMIT; ss++) - { - if (is_pos[i] == 7) - { - if (ms_stereo) - { - lr[0][sb][ss] = (ro[0][sb][ss] + ro[1][sb][ss]) * 0.707106781f; - lr[1][sb][ss] = (ro[0][sb][ss] - ro[1][sb][ss]) * 0.707106781f; - } - else - { - lr[0][sb][ss] = ro[0][sb][ss]; - lr[1][sb][ss] = ro[1][sb][ss]; - } - } - else if (i_stereo) - { - - if (lsf) - { - lr[0][sb][ss] = ro[0][sb][ss] * k[0][i]; - lr[1][sb][ss] = ro[0][sb][ss] * k[1][i]; - } - else - { - lr[1][sb][ss] = ro[0][sb][ss] / (float) (1 + is_ratio[i]); - lr[0][sb][ss] = lr[1][sb][ss] * is_ratio[i]; - } - } - /* else { - System.out.println("Error in stereo processing\n"); - } */ - i++; - } - } - // channels == 2 - } - - /// * - /// - private void antialias(int ch, int gr) - { - int sb18, ss, sb18lim; - gr_info_s gr_info = (si.ch[ch].gr[gr]); - // 31 alias-reduction operations between each pair of sub-bands - // with 8 butterflies between each pair - - if ((gr_info.window_switching_flag != 0) && (gr_info.block_type == 2) && !(gr_info.mixed_block_flag != 0)) - return ; - - if ((gr_info.window_switching_flag != 0) && (gr_info.mixed_block_flag != 0) && (gr_info.block_type == 2)) - { - sb18lim = 18; - } - else - { - sb18lim = 558; - } - - for (sb18 = 0; sb18 < sb18lim; sb18 += 18) - { - for (ss = 0; ss < 8; ss++) - { - int src_idx1 = sb18 + 17 - ss; - int src_idx2 = sb18 + 18 + ss; - float bu = out_1d[src_idx1]; - float bd = out_1d[src_idx2]; - out_1d[src_idx1] = (bu * cs[ss]) - (bd * ca[ss]); - out_1d[src_idx2] = (bd * cs[ss]) + (bu * ca[ss]); - } - } - } - - /// * - /// - - // MDM: tsOutCopy and rawout do not need initializing, so the arrays - // can be reused. - //UPGRADE_NOTE: The initialization of 'tsOutCopy' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - internal float[] tsOutCopy; - //UPGRADE_NOTE: The initialization of 'rawout' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - internal float[] rawout; - - private void hybrid(int ch, int gr) - { - int bt; - int sb18; - gr_info_s gr_info = (si.ch[ch].gr[gr]); - float[] tsOut; - - float[][] prvblk; - - for (sb18 = 0; sb18 < 576; sb18 += 18) - { - bt = ((gr_info.window_switching_flag != 0) && (gr_info.mixed_block_flag != 0) && (sb18 < 36))?0:gr_info.block_type; - - tsOut = out_1d; - // Modif E.B 02/22/99 - for (int cc = 0; cc < 18; cc++) - tsOutCopy[cc] = tsOut[cc + sb18]; - - inv_mdct(tsOutCopy, rawout, bt); - - - for (int cc = 0; cc < 18; cc++) - tsOut[cc + sb18] = tsOutCopy[cc]; - // Fin Modif - - // overlap addition - prvblk = prevblck; - - tsOut[0 + sb18] = rawout[0] + prvblk[ch][sb18 + 0]; - prvblk[ch][sb18 + 0] = rawout[18]; - tsOut[1 + sb18] = rawout[1] + prvblk[ch][sb18 + 1]; - prvblk[ch][sb18 + 1] = rawout[19]; - tsOut[2 + sb18] = rawout[2] + prvblk[ch][sb18 + 2]; - prvblk[ch][sb18 + 2] = rawout[20]; - tsOut[3 + sb18] = rawout[3] + prvblk[ch][sb18 + 3]; - prvblk[ch][sb18 + 3] = rawout[21]; - tsOut[4 + sb18] = rawout[4] + prvblk[ch][sb18 + 4]; - prvblk[ch][sb18 + 4] = rawout[22]; - tsOut[5 + sb18] = rawout[5] + prvblk[ch][sb18 + 5]; - prvblk[ch][sb18 + 5] = rawout[23]; - tsOut[6 + sb18] = rawout[6] + prvblk[ch][sb18 + 6]; - prvblk[ch][sb18 + 6] = rawout[24]; - tsOut[7 + sb18] = rawout[7] + prvblk[ch][sb18 + 7]; - prvblk[ch][sb18 + 7] = rawout[25]; - tsOut[8 + sb18] = rawout[8] + prvblk[ch][sb18 + 8]; - prvblk[ch][sb18 + 8] = rawout[26]; - tsOut[9 + sb18] = rawout[9] + prvblk[ch][sb18 + 9]; - prvblk[ch][sb18 + 9] = rawout[27]; - tsOut[10 + sb18] = rawout[10] + prvblk[ch][sb18 + 10]; - prvblk[ch][sb18 + 10] = rawout[28]; - tsOut[11 + sb18] = rawout[11] + prvblk[ch][sb18 + 11]; - prvblk[ch][sb18 + 11] = rawout[29]; - tsOut[12 + sb18] = rawout[12] + prvblk[ch][sb18 + 12]; - prvblk[ch][sb18 + 12] = rawout[30]; - tsOut[13 + sb18] = rawout[13] + prvblk[ch][sb18 + 13]; - prvblk[ch][sb18 + 13] = rawout[31]; - tsOut[14 + sb18] = rawout[14] + prvblk[ch][sb18 + 14]; - prvblk[ch][sb18 + 14] = rawout[32]; - tsOut[15 + sb18] = rawout[15] + prvblk[ch][sb18 + 15]; - prvblk[ch][sb18 + 15] = rawout[33]; - tsOut[16 + sb18] = rawout[16] + prvblk[ch][sb18 + 16]; - prvblk[ch][sb18 + 16] = rawout[34]; - tsOut[17 + sb18] = rawout[17] + prvblk[ch][sb18 + 17]; - prvblk[ch][sb18 + 17] = rawout[35]; - } - } - - /// * - /// - private void do_downmix() - { - for (int sb = 0; sb < SSLIMIT; sb++) - { - for (int ss = 0; ss < SSLIMIT; ss += 3) - { - lr[0][sb][ss] = (lr[0][sb][ss] + lr[1][sb][ss]) * 0.5f; - lr[0][sb][ss + 1] = (lr[0][sb][ss + 1] + lr[1][sb][ss + 1]) * 0.5f; - lr[0][sb][ss + 2] = (lr[0][sb][ss + 2] + lr[1][sb][ss + 2]) * 0.5f; - } - } - } - - /// Fast INV_MDCT. - /// - - public void inv_mdct(float[] in_Renamed, float[] out_Renamed, int block_type) - { - float[] win_bt; - int i; - - float tmpf_0, tmpf_1, tmpf_2, tmpf_3, tmpf_4, tmpf_5, tmpf_6, tmpf_7, tmpf_8, tmpf_9; - float tmpf_10, tmpf_11, tmpf_12, tmpf_13, tmpf_14, tmpf_15, tmpf_16, tmpf_17; - - tmpf_0 = tmpf_1 = tmpf_2 = tmpf_3 = tmpf_4 = tmpf_5 = tmpf_6 = tmpf_7 = tmpf_8 = tmpf_9 = tmpf_10 = tmpf_11 = tmpf_12 = tmpf_13 = tmpf_14 = tmpf_15 = tmpf_16 = tmpf_17 = 0.0f; - - - - if (block_type == 2) - { - - /* - * - * Under MicrosoftVM 2922, This causes a GPF, or - * At best, an ArrayIndexOutOfBoundsExceptin. - for(int p=0;p<36;p+=9) - { - out[p] = out[p+1] = out[p+2] = out[p+3] = - out[p+4] = out[p+5] = out[p+6] = out[p+7] = - out[p+8] = 0.0f; - } - */ - out_Renamed[0] = 0.0f; - out_Renamed[1] = 0.0f; - out_Renamed[2] = 0.0f; - out_Renamed[3] = 0.0f; - out_Renamed[4] = 0.0f; - out_Renamed[5] = 0.0f; - out_Renamed[6] = 0.0f; - out_Renamed[7] = 0.0f; - out_Renamed[8] = 0.0f; - out_Renamed[9] = 0.0f; - out_Renamed[10] = 0.0f; - out_Renamed[11] = 0.0f; - out_Renamed[12] = 0.0f; - out_Renamed[13] = 0.0f; - out_Renamed[14] = 0.0f; - out_Renamed[15] = 0.0f; - out_Renamed[16] = 0.0f; - out_Renamed[17] = 0.0f; - out_Renamed[18] = 0.0f; - out_Renamed[19] = 0.0f; - out_Renamed[20] = 0.0f; - out_Renamed[21] = 0.0f; - out_Renamed[22] = 0.0f; - out_Renamed[23] = 0.0f; - out_Renamed[24] = 0.0f; - out_Renamed[25] = 0.0f; - out_Renamed[26] = 0.0f; - out_Renamed[27] = 0.0f; - out_Renamed[28] = 0.0f; - out_Renamed[29] = 0.0f; - out_Renamed[30] = 0.0f; - out_Renamed[31] = 0.0f; - out_Renamed[32] = 0.0f; - out_Renamed[33] = 0.0f; - out_Renamed[34] = 0.0f; - out_Renamed[35] = 0.0f; - - int six_i = 0; - - for (i = 0; i < 3; i++) - { - // 12 point IMDCT - // Begin 12 point IDCT - // Input aliasing for 12 pt IDCT - in_Renamed[15 + i] += in_Renamed[12 + i]; in_Renamed[12 + i] += in_Renamed[9 + i]; in_Renamed[9 + i] += in_Renamed[6 + i]; - in_Renamed[6 + i] += in_Renamed[3 + i]; in_Renamed[3 + i] += in_Renamed[0 + i]; - - // Input aliasing on odd indices (for 6 point IDCT) - in_Renamed[15 + i] += in_Renamed[9 + i]; in_Renamed[9 + i] += in_Renamed[3 + i]; - - // 3 point IDCT on even indices - float pp1, pp2, sum; - pp2 = in_Renamed[12 + i] * 0.500000000f; - pp1 = in_Renamed[6 + i] * 0.866025403f; - sum = in_Renamed[0 + i] + pp2; - tmpf_1 = in_Renamed[0 + i] - in_Renamed[12 + i]; - tmpf_0 = sum + pp1; - tmpf_2 = sum - pp1; - - // End 3 point IDCT on even indices - // 3 point IDCT on odd indices (for 6 point IDCT) - pp2 = in_Renamed[15 + i] * 0.500000000f; - pp1 = in_Renamed[9 + i] * 0.866025403f; - sum = in_Renamed[3 + i] + pp2; - tmpf_4 = in_Renamed[3 + i] - in_Renamed[15 + i]; - tmpf_5 = sum + pp1; - tmpf_3 = sum - pp1; - // End 3 point IDCT on odd indices - // Twiddle factors on odd indices (for 6 point IDCT) - - tmpf_3 *= 1.931851653f; - tmpf_4 *= 0.707106781f; - tmpf_5 *= 0.517638090f; - - // Output butterflies on 2 3 point IDCT's (for 6 point IDCT) - float save = tmpf_0; - tmpf_0 += tmpf_5; - tmpf_5 = save - tmpf_5; - save = tmpf_1; - tmpf_1 += tmpf_4; - tmpf_4 = save - tmpf_4; - save = tmpf_2; - tmpf_2 += tmpf_3; - tmpf_3 = save - tmpf_3; - - // End 6 point IDCT - // Twiddle factors on indices (for 12 point IDCT) - - tmpf_0 *= 0.504314480f; - tmpf_1 *= 0.541196100f; - tmpf_2 *= 0.630236207f; - tmpf_3 *= 0.821339815f; - tmpf_4 *= 1.306562965f; - tmpf_5 *= 3.830648788f; - - // End 12 point IDCT - - // Shift to 12 point modified IDCT, multiply by window type 2 - tmpf_8 = - tmpf_0 * 0.793353340f; - tmpf_9 = - tmpf_0 * 0.608761429f; - tmpf_7 = - tmpf_1 * 0.923879532f; - tmpf_10 = - tmpf_1 * 0.382683432f; - tmpf_6 = - tmpf_2 * 0.991444861f; - tmpf_11 = - tmpf_2 * 0.130526192f; - - tmpf_0 = tmpf_3; - tmpf_1 = tmpf_4 * 0.382683432f; - tmpf_2 = tmpf_5 * 0.608761429f; - - tmpf_3 = - tmpf_5 * 0.793353340f; - tmpf_4 = - tmpf_4 * 0.923879532f; - tmpf_5 = - tmpf_0 * 0.991444861f; - - tmpf_0 *= 0.130526192f; - - out_Renamed[six_i + 6] += tmpf_0; - out_Renamed[six_i + 7] += tmpf_1; - out_Renamed[six_i + 8] += tmpf_2; - out_Renamed[six_i + 9] += tmpf_3; - out_Renamed[six_i + 10] += tmpf_4; - out_Renamed[six_i + 11] += tmpf_5; - out_Renamed[six_i + 12] += tmpf_6; - out_Renamed[six_i + 13] += tmpf_7; - out_Renamed[six_i + 14] += tmpf_8; - out_Renamed[six_i + 15] += tmpf_9; - out_Renamed[six_i + 16] += tmpf_10; - out_Renamed[six_i + 17] += tmpf_11; - - six_i += 6; - } - } - else - { - // 36 point IDCT - // input aliasing for 36 point IDCT - in_Renamed[17] += in_Renamed[16]; in_Renamed[16] += in_Renamed[15]; in_Renamed[15] += in_Renamed[14]; in_Renamed[14] += in_Renamed[13]; - in_Renamed[13] += in_Renamed[12]; in_Renamed[12] += in_Renamed[11]; in_Renamed[11] += in_Renamed[10]; in_Renamed[10] += in_Renamed[9]; - in_Renamed[9] += in_Renamed[8]; in_Renamed[8] += in_Renamed[7]; in_Renamed[7] += in_Renamed[6]; in_Renamed[6] += in_Renamed[5]; - in_Renamed[5] += in_Renamed[4]; in_Renamed[4] += in_Renamed[3]; in_Renamed[3] += in_Renamed[2]; in_Renamed[2] += in_Renamed[1]; - in_Renamed[1] += in_Renamed[0]; - - // 18 point IDCT for odd indices - // input aliasing for 18 point IDCT - in_Renamed[17] += in_Renamed[15]; in_Renamed[15] += in_Renamed[13]; in_Renamed[13] += in_Renamed[11]; in_Renamed[11] += in_Renamed[9]; - in_Renamed[9] += in_Renamed[7]; in_Renamed[7] += in_Renamed[5]; in_Renamed[5] += in_Renamed[3]; in_Renamed[3] += in_Renamed[1]; - - float tmp0, tmp1, tmp2, tmp3, tmp4, tmp0_, tmp1_, tmp2_, tmp3_; - float tmp0o, tmp1o, tmp2o, tmp3o, tmp4o, tmp0_o, tmp1_o, tmp2_o, tmp3_o; - - // Fast 9 Point Inverse Discrete Cosine Transform - // - // By Francois-Raymond Boyer - // mailto:boyerf@iro.umontreal.ca - // http://www.iro.umontreal.ca/~boyerf - // - // The code has been optimized for Intel processors - // (takes a lot of time to convert float to and from iternal FPU representation) - // - // It is a simple "factorization" of the IDCT matrix. - - // 9 point IDCT on even indices - - // 5 points on odd indices (not realy an IDCT) - float i00 = in_Renamed[0] + in_Renamed[0]; - float iip12 = i00 + in_Renamed[12]; - - tmp0 = iip12 + in_Renamed[4] * 1.8793852415718f + in_Renamed[8] * 1.532088886238f + in_Renamed[16] * 0.34729635533386f; - tmp1 = i00 + in_Renamed[4] - in_Renamed[8] - in_Renamed[12] - in_Renamed[12] - in_Renamed[16]; - tmp2 = iip12 - in_Renamed[4] * 0.34729635533386f - in_Renamed[8] * 1.8793852415718f + in_Renamed[16] * 1.532088886238f; - tmp3 = iip12 - in_Renamed[4] * 1.532088886238f + in_Renamed[8] * 0.34729635533386f - in_Renamed[16] * 1.8793852415718f; - tmp4 = in_Renamed[0] - in_Renamed[4] + in_Renamed[8] - in_Renamed[12] + in_Renamed[16]; - - // 4 points on even indices - float i66_ = in_Renamed[6] * 1.732050808f; // Sqrt[3] - - tmp0_ = in_Renamed[2] * 1.9696155060244f + i66_ + in_Renamed[10] * 1.2855752193731f + in_Renamed[14] * 0.68404028665134f; - tmp1_ = (in_Renamed[2] - in_Renamed[10] - in_Renamed[14]) * 1.732050808f; - tmp2_ = in_Renamed[2] * 1.2855752193731f - i66_ - in_Renamed[10] * 0.68404028665134f + in_Renamed[14] * 1.9696155060244f; - tmp3_ = in_Renamed[2] * 0.68404028665134f - i66_ + in_Renamed[10] * 1.9696155060244f - in_Renamed[14] * 1.2855752193731f; - - // 9 point IDCT on odd indices - // 5 points on odd indices (not realy an IDCT) - float i0 = in_Renamed[0 + 1] + in_Renamed[0 + 1]; - float i0p12 = i0 + in_Renamed[12 + 1]; - - tmp0o = i0p12 + in_Renamed[4 + 1] * 1.8793852415718f + in_Renamed[8 + 1] * 1.532088886238f + in_Renamed[16 + 1] * 0.34729635533386f; - tmp1o = i0 + in_Renamed[4 + 1] - in_Renamed[8 + 1] - in_Renamed[12 + 1] - in_Renamed[12 + 1] - in_Renamed[16 + 1]; - tmp2o = i0p12 - in_Renamed[4 + 1] * 0.34729635533386f - in_Renamed[8 + 1] * 1.8793852415718f + in_Renamed[16 + 1] * 1.532088886238f; - tmp3o = i0p12 - in_Renamed[4 + 1] * 1.532088886238f + in_Renamed[8 + 1] * 0.34729635533386f - in_Renamed[16 + 1] * 1.8793852415718f; - tmp4o = (in_Renamed[0 + 1] - in_Renamed[4 + 1] + in_Renamed[8 + 1] - in_Renamed[12 + 1] + in_Renamed[16 + 1]) * 0.707106781f; // Twiddled - - // 4 points on even indices - float i6_ = in_Renamed[6 + 1] * 1.732050808f; // Sqrt[3] - - tmp0_o = in_Renamed[2 + 1] * 1.9696155060244f + i6_ + in_Renamed[10 + 1] * 1.2855752193731f + in_Renamed[14 + 1] * 0.68404028665134f; - tmp1_o = (in_Renamed[2 + 1] - in_Renamed[10 + 1] - in_Renamed[14 + 1]) * 1.732050808f; - tmp2_o = in_Renamed[2 + 1] * 1.2855752193731f - i6_ - in_Renamed[10 + 1] * 0.68404028665134f + in_Renamed[14 + 1] * 1.9696155060244f; - tmp3_o = in_Renamed[2 + 1] * 0.68404028665134f - i6_ + in_Renamed[10 + 1] * 1.9696155060244f - in_Renamed[14 + 1] * 1.2855752193731f; - - // Twiddle factors on odd indices - // and - // Butterflies on 9 point IDCT's - // and - // twiddle factors for 36 point IDCT - - float e, o; - e = tmp0 + tmp0_; o = (tmp0o + tmp0_o) * 0.501909918f; tmpf_0 = e + o; tmpf_17 = e - o; - e = tmp1 + tmp1_; o = (tmp1o + tmp1_o) * 0.517638090f; tmpf_1 = e + o; tmpf_16 = e - o; - e = tmp2 + tmp2_; o = (tmp2o + tmp2_o) * 0.551688959f; tmpf_2 = e + o; tmpf_15 = e - o; - e = tmp3 + tmp3_; o = (tmp3o + tmp3_o) * 0.610387294f; tmpf_3 = e + o; tmpf_14 = e - o; - tmpf_4 = tmp4 + tmp4o; tmpf_13 = tmp4 - tmp4o; - e = tmp3 - tmp3_; o = (tmp3o - tmp3_o) * 0.871723397f; tmpf_5 = e + o; tmpf_12 = e - o; - e = tmp2 - tmp2_; o = (tmp2o - tmp2_o) * 1.183100792f; tmpf_6 = e + o; tmpf_11 = e - o; - e = tmp1 - tmp1_; o = (tmp1o - tmp1_o) * 1.931851653f; tmpf_7 = e + o; tmpf_10 = e - o; - e = tmp0 - tmp0_; o = (tmp0o - tmp0_o) * 5.736856623f; tmpf_8 = e + o; tmpf_9 = e - o; - - // end 36 point IDCT */ - // shift to modified IDCT - win_bt = win[block_type]; - - out_Renamed[0] = - tmpf_9 * win_bt[0]; - out_Renamed[1] = - tmpf_10 * win_bt[1]; - out_Renamed[2] = - tmpf_11 * win_bt[2]; - out_Renamed[3] = - tmpf_12 * win_bt[3]; - out_Renamed[4] = - tmpf_13 * win_bt[4]; - out_Renamed[5] = - tmpf_14 * win_bt[5]; - out_Renamed[6] = - tmpf_15 * win_bt[6]; - out_Renamed[7] = - tmpf_16 * win_bt[7]; - out_Renamed[8] = - tmpf_17 * win_bt[8]; - out_Renamed[9] = tmpf_17 * win_bt[9]; - out_Renamed[10] = tmpf_16 * win_bt[10]; - out_Renamed[11] = tmpf_15 * win_bt[11]; - out_Renamed[12] = tmpf_14 * win_bt[12]; - out_Renamed[13] = tmpf_13 * win_bt[13]; - out_Renamed[14] = tmpf_12 * win_bt[14]; - out_Renamed[15] = tmpf_11 * win_bt[15]; - out_Renamed[16] = tmpf_10 * win_bt[16]; - out_Renamed[17] = tmpf_9 * win_bt[17]; - out_Renamed[18] = tmpf_8 * win_bt[18]; - out_Renamed[19] = tmpf_7 * win_bt[19]; - out_Renamed[20] = tmpf_6 * win_bt[20]; - out_Renamed[21] = tmpf_5 * win_bt[21]; - out_Renamed[22] = tmpf_4 * win_bt[22]; - out_Renamed[23] = tmpf_3 * win_bt[23]; - out_Renamed[24] = tmpf_2 * win_bt[24]; - out_Renamed[25] = tmpf_1 * win_bt[25]; - out_Renamed[26] = tmpf_0 * win_bt[26]; - out_Renamed[27] = tmpf_0 * win_bt[27]; - out_Renamed[28] = tmpf_1 * win_bt[28]; - out_Renamed[29] = tmpf_2 * win_bt[29]; - out_Renamed[30] = tmpf_3 * win_bt[30]; - out_Renamed[31] = tmpf_4 * win_bt[31]; - out_Renamed[32] = tmpf_5 * win_bt[32]; - out_Renamed[33] = tmpf_6 * win_bt[33]; - out_Renamed[34] = tmpf_7 * win_bt[34]; - out_Renamed[35] = tmpf_8 * win_bt[35]; - } - } - - private int counter = 0; - private const int SSLIMIT = 18; - private const int SBLIMIT = 32; - // Size of the table of whole numbers raised to 4/3 power. - // This may be adjusted for performance without any problems. - //public static final int POW_TABLE_LIMIT=512; - - /// ******************************************************** - /// - /* L3TABLE */ - /// ******************************************************** - /// - - internal class SBI - { - public int[] l; - public int[] s; - - public SBI() - { - l = new int[23]; - s = new int[14]; - } - public SBI(int[] thel, int[] thes) - { - l = thel; - s = thes; - } - } - - internal class gr_info_s - { - public int part2_3_length = 0; - public int big_values = 0; - public int global_gain = 0; - public int scalefac_compress = 0; - public int window_switching_flag = 0; - public int block_type = 0; - public int mixed_block_flag = 0; - public int[] table_select; - public int[] subblock_gain; - public int region0_count = 0; - public int region1_count = 0; - public int preflag = 0; - public int scalefac_scale = 0; - public int count1table_select = 0; - - /// Dummy Constructor - /// - public gr_info_s() - { - table_select = new int[3]; - subblock_gain = new int[3]; - } - } - - internal class temporaire - { - public int[] scfsi; - public gr_info_s[] gr; - - /// Dummy Constructor - /// - public temporaire() - { - scfsi = new int[4]; - gr = new gr_info_s[2]; - gr[0] = new gr_info_s(); - gr[1] = new gr_info_s(); - } - } - - internal class III_side_info_t - { - - public int main_data_begin = 0; - public int private_bits = 0; - public temporaire[] ch; - /// Dummy Constructor - /// - public III_side_info_t() - { - ch = new temporaire[2]; - ch[0] = new temporaire(); - ch[1] = new temporaire(); - } - } - - internal class temporaire2 - { - public int[] l; /* [cb] */ - public int[][] s; /* [window][cb] */ - - /// Dummy Constructor - /// - public temporaire2() - { - l = new int[23]; - s = new int[3][]; - for (int i = 0; i < 3; i++) - { - s[i] = new int[13]; - } - } - } - //class III_scalefac_t - //{ - // public temporaire2[] tab; - // /** - // * Dummy Constructor - // */ - // public III_scalefac_t() - // { - // tab = new temporaire2[2]; - // } - //} - - private static readonly int[][] slen = {new int[]{0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4}, new int[]{0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3}}; - - public static readonly int[] pretab = new int[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 3, 3, 2, 0}; - - private SBI[] sfBandIndex; // Init in the constructor. - - public static readonly float[] two_to_negative_half_pow = new float[]{1.0000000000e+00f, 7.0710678119e-01f, 5.0000000000e-01f, 3.5355339059e-01f, 2.5000000000e-01f, 1.7677669530e-01f, 1.2500000000e-01f, 8.8388347648e-02f, 6.2500000000e-02f, 4.4194173824e-02f, 3.1250000000e-02f, 2.2097086912e-02f, 1.5625000000e-02f, 1.1048543456e-02f, 7.8125000000e-03f, 5.5242717280e-03f, 3.9062500000e-03f, 2.7621358640e-03f, 1.9531250000e-03f, 1.3810679320e-03f, 9.7656250000e-04f, 6.9053396600e-04f, 4.8828125000e-04f, 3.4526698300e-04f, 2.4414062500e-04f, 1.7263349150e-04f, 1.2207031250e-04f, 8.6316745750e-05f, 6.1035156250e-05f, 4.3158372875e-05f, 3.0517578125e-05f, 2.1579186438e-05f, 1.5258789062e-05f, 1.0789593219e-05f, 7.6293945312e-06f, 5.3947966094e-06f, 3.8146972656e-06f, 2.6973983047e-06f, 1.9073486328e-06f, 1.3486991523e-06f, 9.5367431641e-07f, 6.7434957617e-07f, 4.7683715820e-07f, 3.3717478809e-07f, 2.3841857910e-07f, 1.6858739404e-07f, 1.1920928955e-07f, 8.4293697022e-08f, 5.9604644775e-08f, 4.2146848511e-08f, 2.9802322388e-08f, 2.1073424255e-08f, 1.4901161194e-08f, 1.0536712128e-08f, 7.4505805969e-09f, 5.2683560639e-09f, 3.7252902985e-09f, 2.6341780319e-09f, 1.8626451492e-09f, 1.3170890160e-09f, 9.3132257462e-10f, 6.5854450798e-10f, 4.6566128731e-10f, 3.2927225399e-10f}; - - - //UPGRADE_NOTE: Final was removed from the declaration of 't_43 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - //UPGRADE_NOTE: The initialization of 't_43' was moved to static method 'javazoom.jl.decoder.LayerIIIDecoder'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - public static readonly float[] t_43; - - static private float[] create_t_43() - { - float[] t43 = new float[8192]; - //UPGRADE_NOTE: Final was removed from the declaration of 'd43 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - double d43 = (4.0 / 3.0); - - for (int i = 0; i < 8192; i++) - { - t43[i] = (float) System.Math.Pow(i, d43); - } - return t43; - } - - public static readonly float[][] io = {new float[]{1.0000000000e+00f, 8.4089641526e-01f, 7.0710678119e-01f, 5.9460355751e-01f, 5.0000000001e-01f, 4.2044820763e-01f, 3.5355339060e-01f, 2.9730177876e-01f, 2.5000000001e-01f, 2.1022410382e-01f, 1.7677669530e-01f, 1.4865088938e-01f, 1.2500000000e-01f, 1.0511205191e-01f, 8.8388347652e-02f, 7.4325444691e-02f, 6.2500000003e-02f, 5.2556025956e-02f, 4.4194173826e-02f, 3.7162722346e-02f, 3.1250000002e-02f, 2.6278012978e-02f, 2.2097086913e-02f, 1.8581361173e-02f, 1.5625000001e-02f, 1.3139006489e-02f, 1.1048543457e-02f, 9.2906805866e-03f, 7.8125000006e-03f, 6.5695032447e-03f, 5.5242717285e-03f, 4.6453402934e-03f}, new float[]{1.0000000000e+00f, 7.0710678119e-01f, 5.0000000000e-01f, 3.5355339060e-01f, 2.5000000000e-01f, 1.7677669530e-01f, 1.2500000000e-01f, 8.8388347650e-02f, 6.2500000001e-02f, 4.4194173825e-02f, 3.1250000001e-02f, 2.2097086913e-02f, 1.5625000000e-02f, 1.1048543456e-02f, 7.8125000002e-03f, 5.5242717282e-03f, 3.9062500001e-03f, 2.7621358641e-03f, 1.9531250001e-03f, 1.3810679321e-03f, 9.7656250004e-04f, 6.9053396603e-04f, 4.8828125002e-04f, 3.4526698302e-04f, 2.4414062501e-04f, 1.7263349151e-04f, 1.2207031251e-04f, 8.6316745755e-05f, 6.1035156254e-05f, 4.3158372878e-05f, 3.0517578127e-05f, 2.1579186439e-05f}}; - - - - public static readonly float[] TAN12 = new float[]{0.0f, 0.26794919f, 0.57735027f, 1.0f, 1.73205081f, 3.73205081f, 9.9999999e10f, - 3.73205081f, - 1.73205081f, - 1.0f, - 0.57735027f, - 0.26794919f, 0.0f, 0.26794919f, 0.57735027f, 1.0f}; - - // REVIEW: in java, the array lookup may well be slower than - // the actual calculation - // 576 / 18 - /* - private static final int ss_div[] = - { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, - 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, - 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, - 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, - 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, - 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, - 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, - 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, - 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, - 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, - 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, - 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, - 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, - 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, - 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, - 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, - 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, - 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, - 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, - 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, - 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, - 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, - 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, - 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31 - }; - - // 576 % 18 - private static final int ss_mod[] = - { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 - };*/ - private static int[][] reorder_table; // SZD: will be generated on demand - - /// Loads the data for the reorder - /// - /*private static int[][] loadReorderTable() // SZD: table will be generated - { - try - { - Class elemType = int[][].class.getComponentType(); - Object o = JavaLayerUtils.deserializeArrayResource("l3reorder.ser", elemType, 6); - return (int[][])o; - } - catch (IOException ex) - { - throw new ExceptionInInitializerError(ex); - } - }*/ - - internal static int[] reorder(int[] scalefac_band) - { - // SZD: converted from LAME - int j = 0; - int[] ix = new int[576]; - for (int sfb = 0; sfb < 13; sfb++) - { - int start = scalefac_band[sfb]; - int end = scalefac_band[sfb + 1]; - for (int window = 0; window < 3; window++) - for (int i = start; i < end; i++) - ix[3 * i + window] = j++; - } - return ix; - } - - /*static final int reorder_table_data[][]; = - { - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 42, 48, 37, 43, 49, 38, 44, 50, 39, 45, 51, - 40, 46, 52, 41, 47, 53, 54, 60, 66, 55, 61, 67, 56, 62, 68, 57, - 63, 69, 58, 64, 70, 59, 65, 71, 72, 80, 88, 73, 81, 89, 74, 82, - 90, 75, 83, 91, 76, 84, 92, 77, 85, 93, 78, 86, 94, 79, 87, 95, - 96,106,116, 97,107,117, 98,108,118, 99,109,119,100,110,120,101, - 111,121,102,112,122,103,113,123,104,114,124,105,115,125,126,140, - 154,127,141,155,128,142,156,129,143,157,130,144,158,131,145,159, - 132,146,160,133,147,161,134,148,162,135,149,163,136,150,164,137, - 151,165,138,152,166,139,153,167,168,186,204,169,187,205,170,188, - 206,171,189,207,172,190,208,173,191,209,174,192,210,175,193,211, - 176,194,212,177,195,213,178,196,214,179,197,215,180,198,216,181, - 199,217,182,200,218,183,201,219,184,202,220,185,203,221,222,248, - 274,223,249,275,224,250,276,225,251,277,226,252,278,227,253,279, - 228,254,280,229,255,281,230,256,282,231,257,283,232,258,284,233, - 259,285,234,260,286,235,261,287,236,262,288,237,263,289,238,264, - 290,239,265,291,240,266,292,241,267,293,242,268,294,243,269,295, - 244,270,296,245,271,297,246,272,298,247,273,299,300,332,364,301, - 333,365,302,334,366,303,335,367,304,336,368,305,337,369,306,338, - 370,307,339,371,308,340,372,309,341,373,310,342,374,311,343,375, - 312,344,376,313,345,377,314,346,378,315,347,379,316,348,380,317, - 349,381,318,350,382,319,351,383,320,352,384,321,353,385,322,354, - 386,323,355,387,324,356,388,325,357,389,326,358,390,327,359,391, - 328,360,392,329,361,393,330,362,394,331,363,395,396,438,480,397, - 439,481,398,440,482,399,441,483,400,442,484,401,443,485,402,444, - 486,403,445,487,404,446,488,405,447,489,406,448,490,407,449,491, - 408,450,492,409,451,493,410,452,494,411,453,495,412,454,496,413, - 455,497,414,456,498,415,457,499,416,458,500,417,459,501,418,460, - 502,419,461,503,420,462,504,421,463,505,422,464,506,423,465,507, - 424,466,508,425,467,509,426,468,510,427,469,511,428,470,512,429, - 471,513,430,472,514,431,473,515,432,474,516,433,475,517,434,476, - 518,435,477,519,436,478,520,437,479,521,522,540,558,523,541,559, - 524,542,560,525,543,561,526,544,562,527,545,563,528,546,564,529, - 547,565,530,548,566,531,549,567,532,550,568,533,551,569,534,552, - 570,535,553,571,536,554,572,537,555,573,538,556,574,539,557,575}, - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 42, 48, 37, 43, 49, 38, 44, 50, 39, 45, 51, - 40, 46, 52, 41, 47, 53, 54, 62, 70, 55, 63, 71, 56, 64, 72, 57, - 65, 73, 58, 66, 74, 59, 67, 75, 60, 68, 76, 61, 69, 77, 78, 88, - 98, 79, 89, 99, 80, 90,100, 81, 91,101, 82, 92,102, 83, 93,103, - 84, 94,104, 85, 95,105, 86, 96,106, 87, 97,107,108,120,132,109, - 121,133,110,122,134,111,123,135,112,124,136,113,125,137,114,126, - 138,115,127,139,116,128,140,117,129,141,118,130,142,119,131,143, - 144,158,172,145,159,173,146,160,174,147,161,175,148,162,176,149, - 163,177,150,164,178,151,165,179,152,166,180,153,167,181,154,168, - 182,155,169,183,156,170,184,157,171,185,186,204,222,187,205,223, - 188,206,224,189,207,225,190,208,226,191,209,227,192,210,228,193, - 211,229,194,212,230,195,213,231,196,214,232,197,215,233,198,216, - 234,199,217,235,200,218,236,201,219,237,202,220,238,203,221,239, - 240,264,288,241,265,289,242,266,290,243,267,291,244,268,292,245, - 269,293,246,270,294,247,271,295,248,272,296,249,273,297,250,274, - 298,251,275,299,252,276,300,253,277,301,254,278,302,255,279,303, - 256,280,304,257,281,305,258,282,306,259,283,307,260,284,308,261, - 285,309,262,286,310,263,287,311,312,344,376,313,345,377,314,346, - 378,315,347,379,316,348,380,317,349,381,318,350,382,319,351,383, - 320,352,384,321,353,385,322,354,386,323,355,387,324,356,388,325, - 357,389,326,358,390,327,359,391,328,360,392,329,361,393,330,362, - 394,331,363,395,332,364,396,333,365,397,334,366,398,335,367,399, - 336,368,400,337,369,401,338,370,402,339,371,403,340,372,404,341, - 373,405,342,374,406,343,375,407,408,452,496,409,453,497,410,454, - 498,411,455,499,412,456,500,413,457,501,414,458,502,415,459,503, - 416,460,504,417,461,505,418,462,506,419,463,507,420,464,508,421, - 465,509,422,466,510,423,467,511,424,468,512,425,469,513,426,470, - 514,427,471,515,428,472,516,429,473,517,430,474,518,431,475,519, - 432,476,520,433,477,521,434,478,522,435,479,523,436,480,524,437, - 481,525,438,482,526,439,483,527,440,484,528,441,485,529,442,486, - 530,443,487,531,444,488,532,445,489,533,446,490,534,447,491,535, - 448,492,536,449,493,537,450,494,538,451,495,539,540,552,564,541, - 553,565,542,554,566,543,555,567,544,556,568,545,557,569,546,558, - 570,547,559,571,548,560,572,549,561,573,550,562,574,551,563,575}, - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 42, 48, 37, 43, 49, 38, 44, 50, 39, 45, 51, - 40, 46, 52, 41, 47, 53, 54, 62, 70, 55, 63, 71, 56, 64, 72, 57, - 65, 73, 58, 66, 74, 59, 67, 75, 60, 68, 76, 61, 69, 77, 78, 88, - 98, 79, 89, 99, 80, 90,100, 81, 91,101, 82, 92,102, 83, 93,103, - 84, 94,104, 85, 95,105, 86, 96,106, 87, 97,107,108,120,132,109, - 121,133,110,122,134,111,123,135,112,124,136,113,125,137,114,126, - 138,115,127,139,116,128,140,117,129,141,118,130,142,119,131,143, - 144,158,172,145,159,173,146,160,174,147,161,175,148,162,176,149, - 163,177,150,164,178,151,165,179,152,166,180,153,167,181,154,168, - 182,155,169,183,156,170,184,157,171,185,186,204,222,187,205,223, - 188,206,224,189,207,225,190,208,226,191,209,227,192,210,228,193, - 211,229,194,212,230,195,213,231,196,214,232,197,215,233,198,216, - 234,199,217,235,200,218,236,201,219,237,202,220,238,203,221,239, - 240,264,288,241,265,289,242,266,290,243,267,291,244,268,292,245, - 269,293,246,270,294,247,271,295,248,272,296,249,273,297,250,274, - 298,251,275,299,252,276,300,253,277,301,254,278,302,255,279,303, - 256,280,304,257,281,305,258,282,306,259,283,307,260,284,308,261, - 285,309,262,286,310,263,287,311,312,342,372,313,343,373,314,344, - 374,315,345,375,316,346,376,317,347,377,318,348,378,319,349,379, - 320,350,380,321,351,381,322,352,382,323,353,383,324,354,384,325, - 355,385,326,356,386,327,357,387,328,358,388,329,359,389,330,360, - 390,331,361,391,332,362,392,333,363,393,334,364,394,335,365,395, - 336,366,396,337,367,397,338,368,398,339,369,399,340,370,400,341, - 371,401,402,442,482,403,443,483,404,444,484,405,445,485,406,446, - 486,407,447,487,408,448,488,409,449,489,410,450,490,411,451,491, - 412,452,492,413,453,493,414,454,494,415,455,495,416,456,496,417, - 457,497,418,458,498,419,459,499,420,460,500,421,461,501,422,462, - 502,423,463,503,424,464,504,425,465,505,426,466,506,427,467,507, - 428,468,508,429,469,509,430,470,510,431,471,511,432,472,512,433, - 473,513,434,474,514,435,475,515,436,476,516,437,477,517,438,478, - 518,439,479,519,440,480,520,441,481,521,522,540,558,523,541,559, - 524,542,560,525,543,561,526,544,562,527,545,563,528,546,564,529, - 547,565,530,548,566,531,549,567,532,550,568,533,551,569,534,552, - 570,535,553,571,536,554,572,537,555,573,538,556,574,539,557,575}, - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 40, 44, 37, 41, 45, 38, 42, 46, 39, 43, 47, - 48, 54, 60, 49, 55, 61, 50, 56, 62, 51, 57, 63, 52, 58, 64, 53, - 59, 65, 66, 74, 82, 67, 75, 83, 68, 76, 84, 69, 77, 85, 70, 78, - 86, 71, 79, 87, 72, 80, 88, 73, 81, 89, 90,100,110, 91,101,111, - 92,102,112, 93,103,113, 94,104,114, 95,105,115, 96,106,116, 97, - 107,117, 98,108,118, 99,109,119,120,132,144,121,133,145,122,134, - 146,123,135,147,124,136,148,125,137,149,126,138,150,127,139,151, - 128,140,152,129,141,153,130,142,154,131,143,155,156,170,184,157, - 171,185,158,172,186,159,173,187,160,174,188,161,175,189,162,176, - 190,163,177,191,164,178,192,165,179,193,166,180,194,167,181,195, - 168,182,196,169,183,197,198,216,234,199,217,235,200,218,236,201, - 219,237,202,220,238,203,221,239,204,222,240,205,223,241,206,224, - 242,207,225,243,208,226,244,209,227,245,210,228,246,211,229,247, - 212,230,248,213,231,249,214,232,250,215,233,251,252,274,296,253, - 275,297,254,276,298,255,277,299,256,278,300,257,279,301,258,280, - 302,259,281,303,260,282,304,261,283,305,262,284,306,263,285,307, - 264,286,308,265,287,309,266,288,310,267,289,311,268,290,312,269, - 291,313,270,292,314,271,293,315,272,294,316,273,295,317,318,348, - 378,319,349,379,320,350,380,321,351,381,322,352,382,323,353,383, - 324,354,384,325,355,385,326,356,386,327,357,387,328,358,388,329, - 359,389,330,360,390,331,361,391,332,362,392,333,363,393,334,364, - 394,335,365,395,336,366,396,337,367,397,338,368,398,339,369,399, - 340,370,400,341,371,401,342,372,402,343,373,403,344,374,404,345, - 375,405,346,376,406,347,377,407,408,464,520,409,465,521,410,466, - 522,411,467,523,412,468,524,413,469,525,414,470,526,415,471,527, - 416,472,528,417,473,529,418,474,530,419,475,531,420,476,532,421, - 477,533,422,478,534,423,479,535,424,480,536,425,481,537,426,482, - 538,427,483,539,428,484,540,429,485,541,430,486,542,431,487,543, - 432,488,544,433,489,545,434,490,546,435,491,547,436,492,548,437, - 493,549,438,494,550,439,495,551,440,496,552,441,497,553,442,498, - 554,443,499,555,444,500,556,445,501,557,446,502,558,447,503,559, - 448,504,560,449,505,561,450,506,562,451,507,563,452,508,564,453, - 509,565,454,510,566,455,511,567,456,512,568,457,513,569,458,514, - 570,459,515,571,460,516,572,461,517,573,462,518,574,463,519,575}, - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 40, 44, 37, 41, 45, 38, 42, 46, 39, 43, 47, - 48, 54, 60, 49, 55, 61, 50, 56, 62, 51, 57, 63, 52, 58, 64, 53, - 59, 65, 66, 72, 78, 67, 73, 79, 68, 74, 80, 69, 75, 81, 70, 76, - 82, 71, 77, 83, 84, 94,104, 85, 95,105, 86, 96,106, 87, 97,107, - 88, 98,108, 89, 99,109, 90,100,110, 91,101,111, 92,102,112, 93, - 103,113,114,126,138,115,127,139,116,128,140,117,129,141,118,130, - 142,119,131,143,120,132,144,121,133,145,122,134,146,123,135,147, - 124,136,148,125,137,149,150,164,178,151,165,179,152,166,180,153, - 167,181,154,168,182,155,169,183,156,170,184,157,171,185,158,172, - 186,159,173,187,160,174,188,161,175,189,162,176,190,163,177,191, - 192,208,224,193,209,225,194,210,226,195,211,227,196,212,228,197, - 213,229,198,214,230,199,215,231,200,216,232,201,217,233,202,218, - 234,203,219,235,204,220,236,205,221,237,206,222,238,207,223,239, - 240,260,280,241,261,281,242,262,282,243,263,283,244,264,284,245, - 265,285,246,266,286,247,267,287,248,268,288,249,269,289,250,270, - 290,251,271,291,252,272,292,253,273,293,254,274,294,255,275,295, - 256,276,296,257,277,297,258,278,298,259,279,299,300,326,352,301, - 327,353,302,328,354,303,329,355,304,330,356,305,331,357,306,332, - 358,307,333,359,308,334,360,309,335,361,310,336,362,311,337,363, - 312,338,364,313,339,365,314,340,366,315,341,367,316,342,368,317, - 343,369,318,344,370,319,345,371,320,346,372,321,347,373,322,348, - 374,323,349,375,324,350,376,325,351,377,378,444,510,379,445,511, - 380,446,512,381,447,513,382,448,514,383,449,515,384,450,516,385, - 451,517,386,452,518,387,453,519,388,454,520,389,455,521,390,456, - 522,391,457,523,392,458,524,393,459,525,394,460,526,395,461,527, - 396,462,528,397,463,529,398,464,530,399,465,531,400,466,532,401, - 467,533,402,468,534,403,469,535,404,470,536,405,471,537,406,472, - 538,407,473,539,408,474,540,409,475,541,410,476,542,411,477,543, - 412,478,544,413,479,545,414,480,546,415,481,547,416,482,548,417, - 483,549,418,484,550,419,485,551,420,486,552,421,487,553,422,488, - 554,423,489,555,424,490,556,425,491,557,426,492,558,427,493,559, - 428,494,560,429,495,561,430,496,562,431,497,563,432,498,564,433, - 499,565,434,500,566,435,501,567,436,502,568,437,503,569,438,504, - 570,439,505,571,440,506,572,441,507,573,442,508,574,443,509,575}, - { 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, 12, 16, 20, 13, - 17, 21, 14, 18, 22, 15, 19, 23, 24, 28, 32, 25, 29, 33, 26, 30, - 34, 27, 31, 35, 36, 40, 44, 37, 41, 45, 38, 42, 46, 39, 43, 47, - 48, 54, 60, 49, 55, 61, 50, 56, 62, 51, 57, 63, 52, 58, 64, 53, - 59, 65, 66, 74, 82, 67, 75, 83, 68, 76, 84, 69, 77, 85, 70, 78, - 86, 71, 79, 87, 72, 80, 88, 73, 81, 89, 90,102,114, 91,103,115, - 92,104,116, 93,105,117, 94,106,118, 95,107,119, 96,108,120, 97, - 109,121, 98,110,122, 99,111,123,100,112,124,101,113,125,126,142, - 158,127,143,159,128,144,160,129,145,161,130,146,162,131,147,163, - 132,148,164,133,149,165,134,150,166,135,151,167,136,152,168,137, - 153,169,138,154,170,139,155,171,140,156,172,141,157,173,174,194, - 214,175,195,215,176,196,216,177,197,217,178,198,218,179,199,219, - 180,200,220,181,201,221,182,202,222,183,203,223,184,204,224,185, - 205,225,186,206,226,187,207,227,188,208,228,189,209,229,190,210, - 230,191,211,231,192,212,232,193,213,233,234,260,286,235,261,287, - 236,262,288,237,263,289,238,264,290,239,265,291,240,266,292,241, - 267,293,242,268,294,243,269,295,244,270,296,245,271,297,246,272, - 298,247,273,299,248,274,300,249,275,301,250,276,302,251,277,303, - 252,278,304,253,279,305,254,280,306,255,281,307,256,282,308,257, - 283,309,258,284,310,259,285,311,312,346,380,313,347,381,314,348, - 382,315,349,383,316,350,384,317,351,385,318,352,386,319,353,387, - 320,354,388,321,355,389,322,356,390,323,357,391,324,358,392,325, - 359,393,326,360,394,327,361,395,328,362,396,329,363,397,330,364, - 398,331,365,399,332,366,400,333,367,401,334,368,402,335,369,403, - 336,370,404,337,371,405,338,372,406,339,373,407,340,374,408,341, - 375,409,342,376,410,343,377,411,344,378,412,345,379,413,414,456, - 498,415,457,499,416,458,500,417,459,501,418,460,502,419,461,503, - 420,462,504,421,463,505,422,464,506,423,465,507,424,466,508,425, - 467,509,426,468,510,427,469,511,428,470,512,429,471,513,430,472, - 514,431,473,515,432,474,516,433,475,517,434,476,518,435,477,519, - 436,478,520,437,479,521,438,480,522,439,481,523,440,482,524,441, - 483,525,442,484,526,443,485,527,444,486,528,445,487,529,446,488, - 530,447,489,531,448,490,532,449,491,533,450,492,534,451,493,535, - 452,494,536,453,495,537,454,496,538,455,497,539,540,552,564,541, - 553,565,542,554,566,543,555,567,544,556,568,545,557,569,546,558, - 570,547,559,571,548,560,572,549,561,573,550,562,574,551,563,575} - };*/ - - private static readonly float[] cs = new float[]{0.857492925712f, 0.881741997318f, 0.949628649103f, 0.983314592492f, 0.995517816065f, 0.999160558175f, 0.999899195243f, 0.999993155067f}; - - private static readonly float[] ca = new float[]{- 0.5144957554270f, - 0.4717319685650f, - 0.3133774542040f, - 0.1819131996110f, - 0.0945741925262f, - 0.0409655828852f, - 0.0141985685725f, - 0.00369997467375f}; - - /// ******************************************************** - /// - /* END OF L3TABLE */ - /// ******************************************************** - /// - - /// ******************************************************** - /// - /* L3TYPE */ - /// ******************************************************** - /// - - - /// *********************************************************** - /// - /* END OF L3TYPE */ - /// *********************************************************** - /// - - /// *********************************************************** - /// - /* INV_MDCT */ - /// *********************************************************** - /// - public static readonly float[][] win = {new float[]{- 1.6141214951e-02f, - 5.3603178919e-02f, - 1.0070713296e-01f, - 1.6280817573e-01f, - 4.9999999679e-01f, - 3.8388735032e-01f, - 6.2061144372e-01f, - 1.1659756083e+00f, - 3.8720752656e+00f, - 4.2256286556e+00f, - 1.5195289984e+00f, - 9.7416483388e-01f, - 7.3744074053e-01f, - 1.2071067773e+00f, - 5.1636156596e-01f, - 4.5426052317e-01f, - 4.0715656898e-01f, - 3.6969460527e-01f, - 3.3876269197e-01f, - 3.1242222492e-01f, - 2.8939587111e-01f, - 2.6880081906e-01f, - 5.0000000266e-01f, - 2.3251417468e-01f, - 2.1596714708e-01f, - 2.0004979098e-01f, - 1.8449493497e-01f, - 1.6905846094e-01f, - 1.5350360518e-01f, - 1.3758624925e-01f, - 1.2103922149e-01f, - 2.0710679058e-01f, - 8.4752577594e-02f, - 6.4157525656e-02f, - 4.1131172614e-02f, - 1.4790705759e-02f}, - new float[]{- 1.6141214951e-02f, - 5.3603178919e-02f, - 1.0070713296e-01f, - 1.6280817573e-01f, - 4.9999999679e-01f, - 3.8388735032e-01f, - 6.2061144372e-01f, - 1.1659756083e+00f, - 3.8720752656e+00f, - 4.2256286556e+00f, - 1.5195289984e+00f, - 9.7416483388e-01f, - 7.3744074053e-01f, - 1.2071067773e+00f, - 5.1636156596e-01f, - 4.5426052317e-01f, - 4.0715656898e-01f, - 3.6969460527e-01f, - 3.3908542600e-01f, - 3.1511810350e-01f, - 2.9642226150e-01f, - 2.8184548650e-01f, - 5.4119610000e-01f, - 2.6213228100e-01f, - 2.5387916537e-01f, - 2.3296291359e-01f, - 1.9852728987e-01f, - 1.5233534808e-01f, - 9.6496400054e-02f, - 3.3423828516e-02f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f}, new float[]{- 4.8300800645e-02f, - 1.5715656932e-01f, - 2.8325045177e-01f, - 4.2953747763e-01f, - 1.2071067795e+00f, - 8.2426483178e-01f, - 1.1451749106e+00f, - 1.7695290101e+00f, - 4.5470225061e+00f, - 3.4890531002e+00f, - 7.3296292804e-01f, - 1.5076514758e-01f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f}, new float[]{0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, 0.0000000000e+00f, - 1.5076513660e-01f, - 7.3296291107e-01f, - 3.4890530566e+00f, - 4.5470224727e+00f, - 1.7695290031e+00f, - 1.1451749092e+00f, - 8.3137738100e-01f, - 1.3065629650e+00f, - 5.4142014250e-01f, - 4.6528974900e-01f, - 4.1066990750e-01f, - 3.7004680800e-01f, - 3.3876269197e-01f, - 3.1242222492e-01f, - 2.8939587111e-01f, - 2.6880081906e-01f, - 5.0000000266e-01f, - 2.3251417468e-01f, - 2.1596714708e-01f, - 2.0004979098e-01f, -- 1.8449493497e-01f, - 1.6905846094e-01f, - 1.5350360518e-01f, - 1.3758624925e-01f, - 1.2103922149e-01f, - 2.0710679058e-01f, - 8.4752577594e-02f, - 6.4157525656e-02f, - 4.1131172614e-02f, - 1.4790705759e-02f}}; - /// *********************************************************** - /// - /* END OF INV_MDCT */ - /// *********************************************************** - /// - - //UPGRADE_NOTE: Field 'EnclosingInstance' was added to class 'Sftable' to access its enclosing instance. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1019"' - internal class Sftable - { - private void InitBlock(LayerIIIDecoder enclosingInstance) - { - this.enclosingInstance = enclosingInstance; - } - private LayerIIIDecoder enclosingInstance; - public LayerIIIDecoder Enclosing_Instance - { - get - { - return enclosingInstance; - } - - } - public int[] l; - public int[] s; - - public Sftable(LayerIIIDecoder enclosingInstance) - { - InitBlock(enclosingInstance); - l = new int[5]; - s = new int[3]; - } - - public Sftable(LayerIIIDecoder enclosingInstance, int[] thel, int[] thes) - { - InitBlock(enclosingInstance); - l = thel; - s = thes; - } - } - - public Sftable sftable; - - public static readonly int[][][] nr_of_sfb_block = {new int[][]{new int[]{6, 5, 5, 5}, new int[]{9, 9, 9, 9}, new int[]{6, 9, 9, 9}}, new int[][]{new int[]{6, 5, 7, 3}, new int[]{9, 9, 12, 6}, new int[]{6, 9, 12, 6}}, new int[][]{new int[]{11, 10, 0, 0}, new int[]{18, 18, 0, 0}, new int[]{15, 18, 0, 0}}, new int[][]{new int[]{7, 7, 7, 0}, new int[]{12, 12, 12, 0}, new int[]{6, 15, 12, 0}}, new int[][]{new int[]{6, 6, 6, 3}, new int[]{12, 9, 9, 6}, new int[]{6, 12, 9, 6}}, new int[][]{new int[]{8, 8, 5, 0}, new int[]{15, 12, 9, 0}, new int[]{6, 18, 9, 0}}}; - static LayerIIIDecoder() - { - t_43 = create_t_43(); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Manager.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Manager.cs deleted file mode 100644 index 1717e2596..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Manager.cs +++ /dev/null @@ -1,46 +0,0 @@ -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Work in progress. - /// - /// Manages a number of controls. - /// - - internal class Manager - //implements Control - { - public virtual void addControl(Control c) - { - - } - - public virtual void removeControl(Control c) - { - - } - - public virtual void removeAll() - { - - } - - // control interface delegates to a managed control - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Obuffer.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Obuffer.cs deleted file mode 100644 index 944fa738c..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Obuffer.cs +++ /dev/null @@ -1,84 +0,0 @@ -/* -* 12/12/99 Added appendSamples() method for efficiency. MDM. -* 15/02/99 ,Java Conversion by E.B ,ebsp@iname.com, JavaLayer -*----------------------------------------------------------------------------- -* obuffer.h -* -* Declarations for output buffer, includes operating system -* implementation of the virtual Obuffer. Optional routines -* enabling seeks and stops added by Jeff Tsay. -* -* -* @(#) obuffer.h 1.8, last edit: 6/15/94 16:51:56 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* Idea and first implementation for u-law output with fast downsampling by -* Jim Boucher (jboucher@flash.bu.edu) -* -* LinuxObuffer class written by -* Louis P. Kruger (lpkruger@phoenix.princeton.edu) -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Base Class for audio output. - /// - internal abstract class Obuffer - { - public const int OBUFFERSIZE = 2 * 1152; // max. 2 * 1152 samples per frame - public const int MAXCHANNELS = 2; // max. number of channels - - /// Takes a 16 Bit PCM sample. - /// - public abstract void append(int channel, short value_Renamed); - - /// Accepts 32 new PCM samples. - /// - public virtual void appendSamples(int channel, float[] f) - { - short s; - for (int i = 0; i < 32; i++) - { - append(channel, (short)clip((f[i]))); - } - } - - /// Clip Sample to 16 Bits - /// - private short clip(float sample) - { - //UPGRADE_WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' - return ((sample > 32767.0f)?(short)32767:((sample < - 32768.0f)?(short)- 32768:(short) sample)); - } - - /// Write the samples to the file or directly to the audio hardware. - /// - public abstract void write_buffer(int val); - public abstract void close(); - - /// Clears all data in the buffer (for seeking). - /// - public abstract void clear_buffer(); - - /// Notify the buffer that the user has stopped the stream. - /// - public abstract void set_stop_flag(); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/OutputChannels.cs b/Other/libs/mp3sharp/mp3sharp/decoder/OutputChannels.cs deleted file mode 100644 index e69a7b7fd..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/OutputChannels.cs +++ /dev/null @@ -1,171 +0,0 @@ -/* -* 12/12/99 Initial implementation. mdm@techie.com. -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - enum OutputChannelsEnum - { - BOTH_CHANNELS = 0, - LEFT_CHANNEL = 1, - RIGHT_CHANNEL = 2, - DOWNMIX_CHANNELS = 3 - } - - - - /// A Type-safe representation of the the supported output channel - /// constants. - /// - /// This class is immutable and, hence, is thread safe. - /// - /// - /// Mat McGowan 12/12/99 - /// @since 0.0.7 - /// - /// - internal class OutputChannels - { - /// Retrieves the code representing the desired output channels. - /// Will be one of LEFT_CHANNEL, RIGHT_CHANNEL, BOTH_CHANNELS - /// or DOWNMIX_CHANNELS. - /// - /// - /// the channel code represented by this instance. - /// - /// - virtual public int ChannelsOutputCode - { - get - { - return outputChannels; - } - - } - /// Retrieves the number of output channels represented - /// by this channel output type. - /// - /// - /// The number of output channels for this channel output - /// type. This will be 2 for BOTH_CHANNELS only, and 1 - /// for all other types. - /// - /// - virtual public int ChannelCount - { - get - { - int count = (outputChannels == BOTH_CHANNELS)?2:1; - return count; - } - - } - - /// Flag to indicate output should include both channels. - /// - public static int BOTH_CHANNELS = 0; - - /// Flag to indicate output should include the left channel only. - /// - public static int LEFT_CHANNEL = 1; - - /// Flag to indicate output should include the right channel only. - /// - public static int RIGHT_CHANNEL = 2; - - /// Flag to indicate output is mono. - /// - public static int DOWNMIX_CHANNELS = 3; - - - //UPGRADE_NOTE: Final was removed from the declaration of 'LEFT '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly OutputChannels LEFT = new OutputChannels(LEFT_CHANNEL); - //UPGRADE_NOTE: Final was removed from the declaration of 'RIGHT '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly OutputChannels RIGHT = new OutputChannels(RIGHT_CHANNEL); - //UPGRADE_NOTE: Final was removed from the declaration of 'BOTH '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly OutputChannels BOTH = new OutputChannels(BOTH_CHANNELS); - //UPGRADE_NOTE: Final was removed from the declaration of 'DOWNMIX '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - public static readonly OutputChannels DOWNMIX = new OutputChannels(DOWNMIX_CHANNELS); - - - private int outputChannels; - - /// Creates an OutputChannels instance - /// corresponding to the given channel code. - /// - /// - /// one of the OutputChannels channel code constants. - /// - /// @throws IllegalArgumentException if code is not a valid - /// channel code. - /// - /// - static public OutputChannels fromInt(int code) - { - switch (code) - { - - case (int)OutputChannelsEnum.LEFT_CHANNEL: - return LEFT; - - case (int)OutputChannelsEnum.RIGHT_CHANNEL: - return RIGHT; - - case (int)OutputChannelsEnum.BOTH_CHANNELS: - return BOTH; - - case (int)OutputChannelsEnum.DOWNMIX_CHANNELS: - return DOWNMIX; - - default: - throw new System.ArgumentException("Invalid channel code: " + code); - - } - } - - private OutputChannels(int channels) - { - outputChannels = channels; - - if (channels < 0 || channels > 3) - throw new System.ArgumentException("channels"); - } - - - - - public override bool Equals(System.Object o) - { - bool equals = false; - - if (o is OutputChannels) - { - OutputChannels oc = (OutputChannels) o; - equals = (oc.outputChannels == outputChannels); - } - - return equals; - } - - public override int GetHashCode() - { - return outputChannels; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/SampleBuffer.cs b/Other/libs/mp3sharp/mp3sharp/decoder/SampleBuffer.cs deleted file mode 100644 index c9c2e596c..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/SampleBuffer.cs +++ /dev/null @@ -1,144 +0,0 @@ -/* -* 12/12/99 Initial Version based on FileObuffer. mdm@techie.com. -* -* FileObuffer: -* 15/02/99 ,Java Conversion by E.B ,ebsp@iname.com, JavaLayer -* -*----------------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// The SampleBuffer class implements an output buffer - /// that provides storage for a fixed size block of samples. - /// - /// - /// - internal class SampleBuffer:Obuffer - { - virtual public int ChannelCount - { - get - { - return this.channels; - } - - } - virtual public int SampleFrequency - { - get - { - return this.frequency; - } - - } - virtual public short[] Buffer - { - get - { - return this.buffer; - } - - } - virtual public int BufferLength - { - get - { - return bufferp[0]; - } - - } - private short[] buffer; - private int[] bufferp; - private int channels; - private int frequency; - - /// Constructor - /// - public SampleBuffer(int sample_frequency, int number_of_channels) - { - buffer = new short[OBUFFERSIZE]; - bufferp = new int[MAXCHANNELS]; - channels = number_of_channels; - frequency = sample_frequency; - - for (int i = 0; i < number_of_channels; ++i) - bufferp[i] = (short) i; - } - - - - - - /// Takes a 16 Bit PCM sample. - /// - public override void append(int channel, short value_Renamed) - { - buffer[bufferp[channel]] = value_Renamed; - bufferp[channel] += channels; - } - - public override void appendSamples(int channel, float[] f) - { - int pos = bufferp[channel]; - - short s; - float fs; - for (int i = 0; i < 32; ) - { - fs = f[i++]; - fs = (fs > 32767.0f?32767.0f:(fs < - 32767.0f?- 32767.0f:fs)); - - //UPGRADE_WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' - s = (short) fs; - buffer[pos] = s; - pos += channels; - } - - bufferp[channel] = pos; - } - - - /// Write the samples to the file (Random Acces). - /// - public override void write_buffer(int val) - { - - //for (int i = 0; i < channels; ++i) - // bufferp[i] = (short)i; - } - - public override void close() - { - } - - /// * - /// - public override void clear_buffer() - { - for (int i = 0; i < channels; ++i) - bufferp[i] = (short) i; - } - - /// * - /// - public override void set_stop_flag() - { - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/Source.cs b/Other/libs/mp3sharp/mp3sharp/decoder/Source.cs deleted file mode 100644 index 22df45b2e..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/Source.cs +++ /dev/null @@ -1,43 +0,0 @@ -/*----------------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// Work in progress. - /// - /// Class to describe a seekable data source. - /// - /// - - internal struct Source_Fields{ - public readonly static long LENGTH_UNKNOWN = - 1; - } - internal interface Source - { - //UPGRADE_NOTE: Members of interface 'Source' were extracted into structure 'Source_Fields'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1045"' - bool Seekable - { - get; - - } - int read(sbyte[] b, int offs, int len); - bool willReadBlock(); - long length(); - long tell(); - long seek(long pos); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/SynthesisFilter.cs b/Other/libs/mp3sharp/mp3sharp/decoder/SynthesisFilter.cs deleted file mode 100644 index 1dc6ea405..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/SynthesisFilter.cs +++ /dev/null @@ -1,1658 +0,0 @@ -/* -* 04/01/00 Fixes for running under build 23xx Microsoft JVM. mdm. -* 19/12/99 Performance improvements to compute_pcm_samples(). -* Mat McGowan. mdm@techie.com. -*----------------------------------------------------------------------- -* 16/02/99 Java Conversion by E.B , ebsp@iname.com, JavaLayer -* -*----------------------------------------------------------------------- -* @(#) synthesis_filter.h 1.8, last edit: 6/15/94 16:52:00 -* @(#) Copyright (C) 1993, 1994 Tobias Bading (bading@cs.tu-berlin.de) -* @(#) Berlin University of Technology -* -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - /// A class for the synthesis filter bank. - /// This class does a fast downsampling from 32, 44.1 or 48 kHz to 8 kHz, if ULAW is defined. - /// Frequencies above 4 kHz are removed by ignoring higher subbands. - /// - class SynthesisFilter - { - private void InitBlock() - { - _tmpOut = new float[32]; - } - public virtual float[] EQ - { - set - { - this.eq = value; - - if (eq == null) - { - eq = new float[32]; - for (int i = 0; i < 32; i++) - eq[i] = 1.0f; - } - if (eq.Length < 32) - { - throw new System.ArgumentException("eq0"); - } - } - - } - private float[] v1; - private float[] v2; - private float[] actual_v; // v1 or v2 - private int actual_write_pos; // 0-15 - private float[] samples; // 32 new subband samples - private int channel; - private float scalefactor; - private float[] eq; - - /// Quality value for controlling CPU usage/quality tradeoff. - /// - /* - private int quality; - - private int v_inc; - - - - public static final int HIGH_QUALITY = 1; - public static final int MEDIUM_QUALITY = 2; - public static final int LOW_QUALITY = 4; - */ - - /// Contructor. - /// The scalefactor scales the calculated float pcm samples to short values - /// (raw pcm samples are in [-1.0, 1.0], if no violations occur). - /// - public SynthesisFilter(int channelnumber, float factor, float[] eq0) - { - InitBlock(); - if (d == null) - { - d = d_data; // load_d(); - d16 = splitArray(d, 16); - } - - v1 = new float[512]; - v2 = new float[512]; - samples = new float[32]; - channel = channelnumber; - scalefactor = factor; - EQ = eq; - //setQuality(HIGH_QUALITY); - - reset(); - } - - - /* - private void setQuality(int quality0) - { - switch (quality0) - { - case HIGH_QUALITY: - case MEDIUM_QUALITY: - case LOW_QUALITY: - v_inc = 16 * quality0; - quality = quality0; - break; - default : - throw new IllegalArgumentException("Unknown quality value"); - } - } - - public int getQuality() - { - return quality; - } - */ - - /// Reset the synthesis filter. - /// - public void reset() - { - //float[] floatp; - // float[] floatp2; - - // initialize v1[] and v2[]: - //for (floatp = v1 + 512, floatp2 = v2 + 512; floatp > v1; ) - // *--floatp = *--floatp2 = 0.0; - for (int p = 0; p < 512; p++) - v1[p] = v2[p] = 0.0f; - - // initialize samples[]: - //for (floatp = samples + 32; floatp > samples; ) - // *--floatp = 0.0; - for (int p2 = 0; p2 < 32; p2++) - samples[p2] = 0.0f; - - actual_v = v1; - actual_write_pos = 15; - } - - - /// Inject Sample. - /// - public void input_sample(float sample, int subbandnumber) - { - samples[subbandnumber] = eq[subbandnumber] * sample; - } - - public void input_samples(float[] s) - { - for (int i = 31; i >= 0; i--) - { - samples[i] = s[i] * eq[i]; - } - } - - /// Compute new values via a fast cosine transform. - /// - private void compute_new_v() - { - // p is fully initialized from x1 - //float[] p = _p; - // pp is fully initialized from p - //float[] pp = _pp; - - //float[] new_v = _new_v; - - //float[] new_v = new float[32]; // new V[0-15] and V[33-48] of Figure 3-A.2 in ISO DIS 11172-3 - //float[] p = new float[16]; - //float[] pp = new float[16]; - - /* - for (int i=31; i>=0; i--) - { - new_v[i] = 0.0f; - } - */ - - float new_v0, new_v1, new_v2, new_v3, new_v4, new_v5, new_v6, new_v7, new_v8, new_v9; - float new_v10, new_v11, new_v12, new_v13, new_v14, new_v15, new_v16, new_v17, new_v18, new_v19; - float new_v20, new_v21, new_v22, new_v23, new_v24, new_v25, new_v26, new_v27, new_v28, new_v29; - float new_v30, new_v31; - - new_v0 = new_v1 = new_v2 = new_v3 = new_v4 = new_v5 = new_v6 = new_v7 = new_v8 = new_v9 = new_v10 = new_v11 = new_v12 = new_v13 = new_v14 = new_v15 = new_v16 = new_v17 = new_v18 = new_v19 = new_v20 = new_v21 = new_v22 = new_v23 = new_v24 = new_v25 = new_v26 = new_v27 = new_v28 = new_v29 = new_v30 = new_v31 = 0.0f; - - - // float[] new_v = new float[32]; // new V[0-15] and V[33-48] of Figure 3-A.2 in ISO DIS 11172-3 - // float[] p = new float[16]; - // float[] pp = new float[16]; - - float[] s = samples; - - float s0 = s[0]; - float s1 = s[1]; - float s2 = s[2]; - float s3 = s[3]; - float s4 = s[4]; - float s5 = s[5]; - float s6 = s[6]; - float s7 = s[7]; - float s8 = s[8]; - float s9 = s[9]; - float s10 = s[10]; - float s11 = s[11]; - float s12 = s[12]; - float s13 = s[13]; - float s14 = s[14]; - float s15 = s[15]; - float s16 = s[16]; - float s17 = s[17]; - float s18 = s[18]; - float s19 = s[19]; - float s20 = s[20]; - float s21 = s[21]; - float s22 = s[22]; - float s23 = s[23]; - float s24 = s[24]; - float s25 = s[25]; - float s26 = s[26]; - float s27 = s[27]; - float s28 = s[28]; - float s29 = s[29]; - float s30 = s[30]; - float s31 = s[31]; - - float p0 = s0 + s31; - float p1 = s1 + s30; - float p2 = s2 + s29; - float p3 = s3 + s28; - float p4 = s4 + s27; - float p5 = s5 + s26; - float p6 = s6 + s25; - float p7 = s7 + s24; - float p8 = s8 + s23; - float p9 = s9 + s22; - float p10 = s10 + s21; - float p11 = s11 + s20; - float p12 = s12 + s19; - float p13 = s13 + s18; - float p14 = s14 + s17; - float p15 = s15 + s16; - - float pp0 = p0 + p15; - float pp1 = p1 + p14; - float pp2 = p2 + p13; - float pp3 = p3 + p12; - float pp4 = p4 + p11; - float pp5 = p5 + p10; - float pp6 = p6 + p9; - float pp7 = p7 + p8; - float pp8 = (p0 - p15) * cos1_32; - float pp9 = (p1 - p14) * cos3_32; - float pp10 = (p2 - p13) * cos5_32; - float pp11 = (p3 - p12) * cos7_32; - float pp12 = (p4 - p11) * cos9_32; - float pp13 = (p5 - p10) * cos11_32; - float pp14 = (p6 - p9) * cos13_32; - float pp15 = (p7 - p8) * cos15_32; - - p0 = pp0 + pp7; - p1 = pp1 + pp6; - p2 = pp2 + pp5; - p3 = pp3 + pp4; - p4 = (pp0 - pp7) * cos1_16; - p5 = (pp1 - pp6) * cos3_16; - p6 = (pp2 - pp5) * cos5_16; - p7 = (pp3 - pp4) * cos7_16; - p8 = pp8 + pp15; - p9 = pp9 + pp14; - p10 = pp10 + pp13; - p11 = pp11 + pp12; - p12 = (pp8 - pp15) * cos1_16; - p13 = (pp9 - pp14) * cos3_16; - p14 = (pp10 - pp13) * cos5_16; - p15 = (pp11 - pp12) * cos7_16; - - - pp0 = p0 + p3; - pp1 = p1 + p2; - pp2 = (p0 - p3) * cos1_8; - pp3 = (p1 - p2) * cos3_8; - pp4 = p4 + p7; - pp5 = p5 + p6; - pp6 = (p4 - p7) * cos1_8; - pp7 = (p5 - p6) * cos3_8; - pp8 = p8 + p11; - pp9 = p9 + p10; - pp10 = (p8 - p11) * cos1_8; - pp11 = (p9 - p10) * cos3_8; - pp12 = p12 + p15; - pp13 = p13 + p14; - pp14 = (p12 - p15) * cos1_8; - pp15 = (p13 - p14) * cos3_8; - - p0 = pp0 + pp1; - p1 = (pp0 - pp1) * cos1_4; - p2 = pp2 + pp3; - p3 = (pp2 - pp3) * cos1_4; - p4 = pp4 + pp5; - p5 = (pp4 - pp5) * cos1_4; - p6 = pp6 + pp7; - p7 = (pp6 - pp7) * cos1_4; - p8 = pp8 + pp9; - p9 = (pp8 - pp9) * cos1_4; - p10 = pp10 + pp11; - p11 = (pp10 - pp11) * cos1_4; - p12 = pp12 + pp13; - p13 = (pp12 - pp13) * cos1_4; - p14 = pp14 + pp15; - p15 = (pp14 - pp15) * cos1_4; - - // this is pretty insane coding - float tmp1; - new_v19 = - (new_v4 = (new_v12 = p7) + p5) - p6; - new_v27 = - p6 - p7 - p4; - new_v6 = (new_v10 = (new_v14 = p15) + p11) + p13; - new_v17 = - (new_v2 = p15 + p13 + p9) - p14; - new_v21 = (tmp1 = - p14 - p15 - p10 - p11) - p13; - new_v29 = - p14 - p15 - p12 - p8; - new_v25 = tmp1 - p12; - new_v31 = - p0; - new_v0 = p1; - new_v23 = - (new_v8 = p3) - p2; - - p0 = (s0 - s31) * cos1_64; - p1 = (s1 - s30) * cos3_64; - p2 = (s2 - s29) * cos5_64; - p3 = (s3 - s28) * cos7_64; - p4 = (s4 - s27) * cos9_64; - p5 = (s5 - s26) * cos11_64; - p6 = (s6 - s25) * cos13_64; - p7 = (s7 - s24) * cos15_64; - p8 = (s8 - s23) * cos17_64; - p9 = (s9 - s22) * cos19_64; - p10 = (s10 - s21) * cos21_64; - p11 = (s11 - s20) * cos23_64; - p12 = (s12 - s19) * cos25_64; - p13 = (s13 - s18) * cos27_64; - p14 = (s14 - s17) * cos29_64; - p15 = (s15 - s16) * cos31_64; - - - pp0 = p0 + p15; - pp1 = p1 + p14; - pp2 = p2 + p13; - pp3 = p3 + p12; - pp4 = p4 + p11; - pp5 = p5 + p10; - pp6 = p6 + p9; - pp7 = p7 + p8; - pp8 = (p0 - p15) * cos1_32; - pp9 = (p1 - p14) * cos3_32; - pp10 = (p2 - p13) * cos5_32; - pp11 = (p3 - p12) * cos7_32; - pp12 = (p4 - p11) * cos9_32; - pp13 = (p5 - p10) * cos11_32; - pp14 = (p6 - p9) * cos13_32; - pp15 = (p7 - p8) * cos15_32; - - - p0 = pp0 + pp7; - p1 = pp1 + pp6; - p2 = pp2 + pp5; - p3 = pp3 + pp4; - p4 = (pp0 - pp7) * cos1_16; - p5 = (pp1 - pp6) * cos3_16; - p6 = (pp2 - pp5) * cos5_16; - p7 = (pp3 - pp4) * cos7_16; - p8 = pp8 + pp15; - p9 = pp9 + pp14; - p10 = pp10 + pp13; - p11 = pp11 + pp12; - p12 = (pp8 - pp15) * cos1_16; - p13 = (pp9 - pp14) * cos3_16; - p14 = (pp10 - pp13) * cos5_16; - p15 = (pp11 - pp12) * cos7_16; - - - pp0 = p0 + p3; - pp1 = p1 + p2; - pp2 = (p0 - p3) * cos1_8; - pp3 = (p1 - p2) * cos3_8; - pp4 = p4 + p7; - pp5 = p5 + p6; - pp6 = (p4 - p7) * cos1_8; - pp7 = (p5 - p6) * cos3_8; - pp8 = p8 + p11; - pp9 = p9 + p10; - pp10 = (p8 - p11) * cos1_8; - pp11 = (p9 - p10) * cos3_8; - pp12 = p12 + p15; - pp13 = p13 + p14; - pp14 = (p12 - p15) * cos1_8; - pp15 = (p13 - p14) * cos3_8; - - - p0 = pp0 + pp1; - p1 = (pp0 - pp1) * cos1_4; - p2 = pp2 + pp3; - p3 = (pp2 - pp3) * cos1_4; - p4 = pp4 + pp5; - p5 = (pp4 - pp5) * cos1_4; - p6 = pp6 + pp7; - p7 = (pp6 - pp7) * cos1_4; - p8 = pp8 + pp9; - p9 = (pp8 - pp9) * cos1_4; - p10 = pp10 + pp11; - p11 = (pp10 - pp11) * cos1_4; - p12 = pp12 + pp13; - p13 = (pp12 - pp13) * cos1_4; - p14 = pp14 + pp15; - p15 = (pp14 - pp15) * cos1_4; - - - // manually doing something that a compiler should handle sucks - // coding like this is hard to read - float tmp2; - new_v5 = (new_v11 = (new_v13 = (new_v15 = p15) + p7) + p11) + p5 + p13; - new_v7 = (new_v9 = p15 + p11 + p3) + p13; - new_v16 = - (new_v1 = (tmp1 = p13 + p15 + p9) + p1) - p14; - new_v18 = - (new_v3 = tmp1 + p5 + p7) - p6 - p14; - - new_v22 = (tmp1 = - p10 - p11 - p14 - p15) - p13 - p2 - p3; - new_v20 = tmp1 - p13 - p5 - p6 - p7; - new_v24 = tmp1 - p12 - p2 - p3; - new_v26 = tmp1 - p12 - (tmp2 = p4 + p6 + p7); - new_v30 = (tmp1 = - p8 - p12 - p14 - p15) - p0; - new_v28 = tmp1 - tmp2; - - // insert V[0-15] (== new_v[0-15]) into actual v: - // float[] x2 = actual_v + actual_write_pos; - float[] dest = actual_v; - - int pos = actual_write_pos; - - dest[0 + pos] = new_v0; - dest[16 + pos] = new_v1; - dest[32 + pos] = new_v2; - dest[48 + pos] = new_v3; - dest[64 + pos] = new_v4; - dest[80 + pos] = new_v5; - dest[96 + pos] = new_v6; - dest[112 + pos] = new_v7; - dest[128 + pos] = new_v8; - dest[144 + pos] = new_v9; - dest[160 + pos] = new_v10; - dest[176 + pos] = new_v11; - dest[192 + pos] = new_v12; - dest[208 + pos] = new_v13; - dest[224 + pos] = new_v14; - dest[240 + pos] = new_v15; - - // V[16] is always 0.0: - dest[256 + pos] = 0.0f; - - // insert V[17-31] (== -new_v[15-1]) into actual v: - dest[272 + pos] = - new_v15; - dest[288 + pos] = - new_v14; - dest[304 + pos] = - new_v13; - dest[320 + pos] = - new_v12; - dest[336 + pos] = - new_v11; - dest[352 + pos] = - new_v10; - dest[368 + pos] = - new_v9; - dest[384 + pos] = - new_v8; - dest[400 + pos] = - new_v7; - dest[416 + pos] = - new_v6; - dest[432 + pos] = - new_v5; - dest[448 + pos] = - new_v4; - dest[464 + pos] = - new_v3; - dest[480 + pos] = - new_v2; - dest[496 + pos] = - new_v1; - - // insert V[32] (== -new_v[0]) into other v: - dest = (actual_v == v1)?v2:v1; - - dest[0 + pos] = - new_v0; - // insert V[33-48] (== new_v[16-31]) into other v: - dest[16 + pos] = new_v16; - dest[32 + pos] = new_v17; - dest[48 + pos] = new_v18; - dest[64 + pos] = new_v19; - dest[80 + pos] = new_v20; - dest[96 + pos] = new_v21; - dest[112 + pos] = new_v22; - dest[128 + pos] = new_v23; - dest[144 + pos] = new_v24; - dest[160 + pos] = new_v25; - dest[176 + pos] = new_v26; - dest[192 + pos] = new_v27; - dest[208 + pos] = new_v28; - dest[224 + pos] = new_v29; - dest[240 + pos] = new_v30; - dest[256 + pos] = new_v31; - - // insert V[49-63] (== new_v[30-16]) into other v: - dest[272 + pos] = new_v30; - dest[288 + pos] = new_v29; - dest[304 + pos] = new_v28; - dest[320 + pos] = new_v27; - dest[336 + pos] = new_v26; - dest[352 + pos] = new_v25; - dest[368 + pos] = new_v24; - dest[384 + pos] = new_v23; - dest[400 + pos] = new_v22; - dest[416 + pos] = new_v21; - dest[432 + pos] = new_v20; - dest[448 + pos] = new_v19; - dest[464 + pos] = new_v18; - dest[480 + pos] = new_v17; - dest[496 + pos] = new_v16; - /* - } - else - { - v1[0 + actual_write_pos] = -new_v0; - // insert V[33-48] (== new_v[16-31]) into other v: - v1[16 + actual_write_pos] = new_v16; - v1[32 + actual_write_pos] = new_v17; - v1[48 + actual_write_pos] = new_v18; - v1[64 + actual_write_pos] = new_v19; - v1[80 + actual_write_pos] = new_v20; - v1[96 + actual_write_pos] = new_v21; - v1[112 + actual_write_pos] = new_v22; - v1[128 + actual_write_pos] = new_v23; - v1[144 + actual_write_pos] = new_v24; - v1[160 + actual_write_pos] = new_v25; - v1[176 + actual_write_pos] = new_v26; - v1[192 + actual_write_pos] = new_v27; - v1[208 + actual_write_pos] = new_v28; - v1[224 + actual_write_pos] = new_v29; - v1[240 + actual_write_pos] = new_v30; - v1[256 + actual_write_pos] = new_v31; - - // insert V[49-63] (== new_v[30-16]) into other v: - v1[272 + actual_write_pos] = new_v30; - v1[288 + actual_write_pos] = new_v29; - v1[304 + actual_write_pos] = new_v28; - v1[320 + actual_write_pos] = new_v27; - v1[336 + actual_write_pos] = new_v26; - v1[352 + actual_write_pos] = new_v25; - v1[368 + actual_write_pos] = new_v24; - v1[384 + actual_write_pos] = new_v23; - v1[400 + actual_write_pos] = new_v22; - v1[416 + actual_write_pos] = new_v21; - v1[432 + actual_write_pos] = new_v20; - v1[448 + actual_write_pos] = new_v19; - v1[464 + actual_write_pos] = new_v18; - v1[480 + actual_write_pos] = new_v17; - v1[496 + actual_write_pos] = new_v16; - }*/ - } - - /// Compute new values via a fast cosine transform. - /// - private void compute_new_v_old() - { - // p is fully initialized from x1 - //float[] p = _p; - // pp is fully initialized from p - //float[] pp = _pp; - - //float[] new_v = _new_v; - - float[] new_v = new float[32]; // new V[0-15] and V[33-48] of Figure 3-A.2 in ISO DIS 11172-3 - float[] p = new float[16]; - float[] pp = new float[16]; - - - for (int i = 31; i >= 0; i--) - { - new_v[i] = 0.0f; - } - - // float[] new_v = new float[32]; // new V[0-15] and V[33-48] of Figure 3-A.2 in ISO DIS 11172-3 - // float[] p = new float[16]; - // float[] pp = new float[16]; - - float[] x1 = samples; - - p[0] = x1[0] + x1[31]; - p[1] = x1[1] + x1[30]; - p[2] = x1[2] + x1[29]; - p[3] = x1[3] + x1[28]; - p[4] = x1[4] + x1[27]; - p[5] = x1[5] + x1[26]; - p[6] = x1[6] + x1[25]; - p[7] = x1[7] + x1[24]; - p[8] = x1[8] + x1[23]; - p[9] = x1[9] + x1[22]; - p[10] = x1[10] + x1[21]; - p[11] = x1[11] + x1[20]; - p[12] = x1[12] + x1[19]; - p[13] = x1[13] + x1[18]; - p[14] = x1[14] + x1[17]; - p[15] = x1[15] + x1[16]; - - pp[0] = p[0] + p[15]; - pp[1] = p[1] + p[14]; - pp[2] = p[2] + p[13]; - pp[3] = p[3] + p[12]; - pp[4] = p[4] + p[11]; - pp[5] = p[5] + p[10]; - pp[6] = p[6] + p[9]; - pp[7] = p[7] + p[8]; - pp[8] = (p[0] - p[15]) * cos1_32; - pp[9] = (p[1] - p[14]) * cos3_32; - pp[10] = (p[2] - p[13]) * cos5_32; - pp[11] = (p[3] - p[12]) * cos7_32; - pp[12] = (p[4] - p[11]) * cos9_32; - pp[13] = (p[5] - p[10]) * cos11_32; - pp[14] = (p[6] - p[9]) * cos13_32; - pp[15] = (p[7] - p[8]) * cos15_32; - - p[0] = pp[0] + pp[7]; - p[1] = pp[1] + pp[6]; - p[2] = pp[2] + pp[5]; - p[3] = pp[3] + pp[4]; - p[4] = (pp[0] - pp[7]) * cos1_16; - p[5] = (pp[1] - pp[6]) * cos3_16; - p[6] = (pp[2] - pp[5]) * cos5_16; - p[7] = (pp[3] - pp[4]) * cos7_16; - p[8] = pp[8] + pp[15]; - p[9] = pp[9] + pp[14]; - p[10] = pp[10] + pp[13]; - p[11] = pp[11] + pp[12]; - p[12] = (pp[8] - pp[15]) * cos1_16; - p[13] = (pp[9] - pp[14]) * cos3_16; - p[14] = (pp[10] - pp[13]) * cos5_16; - p[15] = (pp[11] - pp[12]) * cos7_16; - - - pp[0] = p[0] + p[3]; - pp[1] = p[1] + p[2]; - pp[2] = (p[0] - p[3]) * cos1_8; - pp[3] = (p[1] - p[2]) * cos3_8; - pp[4] = p[4] + p[7]; - pp[5] = p[5] + p[6]; - pp[6] = (p[4] - p[7]) * cos1_8; - pp[7] = (p[5] - p[6]) * cos3_8; - pp[8] = p[8] + p[11]; - pp[9] = p[9] + p[10]; - pp[10] = (p[8] - p[11]) * cos1_8; - pp[11] = (p[9] - p[10]) * cos3_8; - pp[12] = p[12] + p[15]; - pp[13] = p[13] + p[14]; - pp[14] = (p[12] - p[15]) * cos1_8; - pp[15] = (p[13] - p[14]) * cos3_8; - - p[0] = pp[0] + pp[1]; - p[1] = (pp[0] - pp[1]) * cos1_4; - p[2] = pp[2] + pp[3]; - p[3] = (pp[2] - pp[3]) * cos1_4; - p[4] = pp[4] + pp[5]; - p[5] = (pp[4] - pp[5]) * cos1_4; - p[6] = pp[6] + pp[7]; - p[7] = (pp[6] - pp[7]) * cos1_4; - p[8] = pp[8] + pp[9]; - p[9] = (pp[8] - pp[9]) * cos1_4; - p[10] = pp[10] + pp[11]; - p[11] = (pp[10] - pp[11]) * cos1_4; - p[12] = pp[12] + pp[13]; - p[13] = (pp[12] - pp[13]) * cos1_4; - p[14] = pp[14] + pp[15]; - p[15] = (pp[14] - pp[15]) * cos1_4; - - // this is pretty insane coding - float tmp1; - new_v[36 - 17] = - (new_v[4] = (new_v[12] = p[7]) + p[5]) - p[6]; - new_v[44 - 17] = - p[6] - p[7] - p[4]; - new_v[6] = (new_v[10] = (new_v[14] = p[15]) + p[11]) + p[13]; - new_v[34 - 17] = - (new_v[2] = p[15] + p[13] + p[9]) - p[14]; - new_v[38 - 17] = (tmp1 = - p[14] - p[15] - p[10] - p[11]) - p[13]; - new_v[46 - 17] = - p[14] - p[15] - p[12] - p[8]; - new_v[42 - 17] = tmp1 - p[12]; - new_v[48 - 17] = - p[0]; - new_v[0] = p[1]; - new_v[40 - 17] = - (new_v[8] = p[3]) - p[2]; - - p[0] = (x1[0] - x1[31]) * cos1_64; - p[1] = (x1[1] - x1[30]) * cos3_64; - p[2] = (x1[2] - x1[29]) * cos5_64; - p[3] = (x1[3] - x1[28]) * cos7_64; - p[4] = (x1[4] - x1[27]) * cos9_64; - p[5] = (x1[5] - x1[26]) * cos11_64; - p[6] = (x1[6] - x1[25]) * cos13_64; - p[7] = (x1[7] - x1[24]) * cos15_64; - p[8] = (x1[8] - x1[23]) * cos17_64; - p[9] = (x1[9] - x1[22]) * cos19_64; - p[10] = (x1[10] - x1[21]) * cos21_64; - p[11] = (x1[11] - x1[20]) * cos23_64; - p[12] = (x1[12] - x1[19]) * cos25_64; - p[13] = (x1[13] - x1[18]) * cos27_64; - p[14] = (x1[14] - x1[17]) * cos29_64; - p[15] = (x1[15] - x1[16]) * cos31_64; - - - pp[0] = p[0] + p[15]; - pp[1] = p[1] + p[14]; - pp[2] = p[2] + p[13]; - pp[3] = p[3] + p[12]; - pp[4] = p[4] + p[11]; - pp[5] = p[5] + p[10]; - pp[6] = p[6] + p[9]; - pp[7] = p[7] + p[8]; - pp[8] = (p[0] - p[15]) * cos1_32; - pp[9] = (p[1] - p[14]) * cos3_32; - pp[10] = (p[2] - p[13]) * cos5_32; - pp[11] = (p[3] - p[12]) * cos7_32; - pp[12] = (p[4] - p[11]) * cos9_32; - pp[13] = (p[5] - p[10]) * cos11_32; - pp[14] = (p[6] - p[9]) * cos13_32; - pp[15] = (p[7] - p[8]) * cos15_32; - - - p[0] = pp[0] + pp[7]; - p[1] = pp[1] + pp[6]; - p[2] = pp[2] + pp[5]; - p[3] = pp[3] + pp[4]; - p[4] = (pp[0] - pp[7]) * cos1_16; - p[5] = (pp[1] - pp[6]) * cos3_16; - p[6] = (pp[2] - pp[5]) * cos5_16; - p[7] = (pp[3] - pp[4]) * cos7_16; - p[8] = pp[8] + pp[15]; - p[9] = pp[9] + pp[14]; - p[10] = pp[10] + pp[13]; - p[11] = pp[11] + pp[12]; - p[12] = (pp[8] - pp[15]) * cos1_16; - p[13] = (pp[9] - pp[14]) * cos3_16; - p[14] = (pp[10] - pp[13]) * cos5_16; - p[15] = (pp[11] - pp[12]) * cos7_16; - - - pp[0] = p[0] + p[3]; - pp[1] = p[1] + p[2]; - pp[2] = (p[0] - p[3]) * cos1_8; - pp[3] = (p[1] - p[2]) * cos3_8; - pp[4] = p[4] + p[7]; - pp[5] = p[5] + p[6]; - pp[6] = (p[4] - p[7]) * cos1_8; - pp[7] = (p[5] - p[6]) * cos3_8; - pp[8] = p[8] + p[11]; - pp[9] = p[9] + p[10]; - pp[10] = (p[8] - p[11]) * cos1_8; - pp[11] = (p[9] - p[10]) * cos3_8; - pp[12] = p[12] + p[15]; - pp[13] = p[13] + p[14]; - pp[14] = (p[12] - p[15]) * cos1_8; - pp[15] = (p[13] - p[14]) * cos3_8; - - - p[0] = pp[0] + pp[1]; - p[1] = (pp[0] - pp[1]) * cos1_4; - p[2] = pp[2] + pp[3]; - p[3] = (pp[2] - pp[3]) * cos1_4; - p[4] = pp[4] + pp[5]; - p[5] = (pp[4] - pp[5]) * cos1_4; - p[6] = pp[6] + pp[7]; - p[7] = (pp[6] - pp[7]) * cos1_4; - p[8] = pp[8] + pp[9]; - p[9] = (pp[8] - pp[9]) * cos1_4; - p[10] = pp[10] + pp[11]; - p[11] = (pp[10] - pp[11]) * cos1_4; - p[12] = pp[12] + pp[13]; - p[13] = (pp[12] - pp[13]) * cos1_4; - p[14] = pp[14] + pp[15]; - p[15] = (pp[14] - pp[15]) * cos1_4; - - - // manually doing something that a compiler should handle sucks - // coding like this is hard to read - float tmp2; - new_v[5] = (new_v[11] = (new_v[13] = (new_v[15] = p[15]) + p[7]) + p[11]) + p[5] + p[13]; - new_v[7] = (new_v[9] = p[15] + p[11] + p[3]) + p[13]; - new_v[33 - 17] = - (new_v[1] = (tmp1 = p[13] + p[15] + p[9]) + p[1]) - p[14]; - new_v[35 - 17] = - (new_v[3] = tmp1 + p[5] + p[7]) - p[6] - p[14]; - - new_v[39 - 17] = (tmp1 = - p[10] - p[11] - p[14] - p[15]) - p[13] - p[2] - p[3]; - new_v[37 - 17] = tmp1 - p[13] - p[5] - p[6] - p[7]; - new_v[41 - 17] = tmp1 - p[12] - p[2] - p[3]; - new_v[43 - 17] = tmp1 - p[12] - (tmp2 = p[4] + p[6] + p[7]); - new_v[47 - 17] = (tmp1 = - p[8] - p[12] - p[14] - p[15]) - p[0]; - new_v[45 - 17] = tmp1 - tmp2; - - // insert V[0-15] (== new_v[0-15]) into actual v: - x1 = new_v; - // float[] x2 = actual_v + actual_write_pos; - float[] dest = actual_v; - - dest[0 + actual_write_pos] = x1[0]; - dest[16 + actual_write_pos] = x1[1]; - dest[32 + actual_write_pos] = x1[2]; - dest[48 + actual_write_pos] = x1[3]; - dest[64 + actual_write_pos] = x1[4]; - dest[80 + actual_write_pos] = x1[5]; - dest[96 + actual_write_pos] = x1[6]; - dest[112 + actual_write_pos] = x1[7]; - dest[128 + actual_write_pos] = x1[8]; - dest[144 + actual_write_pos] = x1[9]; - dest[160 + actual_write_pos] = x1[10]; - dest[176 + actual_write_pos] = x1[11]; - dest[192 + actual_write_pos] = x1[12]; - dest[208 + actual_write_pos] = x1[13]; - dest[224 + actual_write_pos] = x1[14]; - dest[240 + actual_write_pos] = x1[15]; - - // V[16] is always 0.0: - dest[256 + actual_write_pos] = 0.0f; - - // insert V[17-31] (== -new_v[15-1]) into actual v: - dest[272 + actual_write_pos] = - x1[15]; - dest[288 + actual_write_pos] = - x1[14]; - dest[304 + actual_write_pos] = - x1[13]; - dest[320 + actual_write_pos] = - x1[12]; - dest[336 + actual_write_pos] = - x1[11]; - dest[352 + actual_write_pos] = - x1[10]; - dest[368 + actual_write_pos] = - x1[9]; - dest[384 + actual_write_pos] = - x1[8]; - dest[400 + actual_write_pos] = - x1[7]; - dest[416 + actual_write_pos] = - x1[6]; - dest[432 + actual_write_pos] = - x1[5]; - dest[448 + actual_write_pos] = - x1[4]; - dest[464 + actual_write_pos] = - x1[3]; - dest[480 + actual_write_pos] = - x1[2]; - dest[496 + actual_write_pos] = - x1[1]; - - // insert V[32] (== -new_v[0]) into other v: - } - - /// Compute PCM Samples. - /// - - //UPGRADE_NOTE: The initialization of '_tmpOut' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private float[] _tmpOut; - - - private void compute_pcm_samples0(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - float pcm_sample; - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - pcm_sample = (float) (((vp[0 + dvp] * dp[0]) + (vp[15 + dvp] * dp[1]) + (vp[14 + dvp] * dp[2]) + (vp[13 + dvp] * dp[3]) + (vp[12 + dvp] * dp[4]) + (vp[11 + dvp] * dp[5]) + (vp[10 + dvp] * dp[6]) + (vp[9 + dvp] * dp[7]) + (vp[8 + dvp] * dp[8]) + (vp[7 + dvp] * dp[9]) + (vp[6 + dvp] * dp[10]) + (vp[5 + dvp] * dp[11]) + (vp[4 + dvp] * dp[12]) + (vp[3 + dvp] * dp[13]) + (vp[2 + dvp] * dp[14]) + (vp[1 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples1(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[1 + dvp] * dp[0]) + (vp[0 + dvp] * dp[1]) + (vp[15 + dvp] * dp[2]) + (vp[14 + dvp] * dp[3]) + (vp[13 + dvp] * dp[4]) + (vp[12 + dvp] * dp[5]) + (vp[11 + dvp] * dp[6]) + (vp[10 + dvp] * dp[7]) + (vp[9 + dvp] * dp[8]) + (vp[8 + dvp] * dp[9]) + (vp[7 + dvp] * dp[10]) + (vp[6 + dvp] * dp[11]) + (vp[5 + dvp] * dp[12]) + (vp[4 + dvp] * dp[13]) + (vp[3 + dvp] * dp[14]) + (vp[2 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples2(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[2 + dvp] * dp[0]) + (vp[1 + dvp] * dp[1]) + (vp[0 + dvp] * dp[2]) + (vp[15 + dvp] * dp[3]) + (vp[14 + dvp] * dp[4]) + (vp[13 + dvp] * dp[5]) + (vp[12 + dvp] * dp[6]) + (vp[11 + dvp] * dp[7]) + (vp[10 + dvp] * dp[8]) + (vp[9 + dvp] * dp[9]) + (vp[8 + dvp] * dp[10]) + (vp[7 + dvp] * dp[11]) + (vp[6 + dvp] * dp[12]) + (vp[5 + dvp] * dp[13]) + (vp[4 + dvp] * dp[14]) + (vp[3 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples3(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - int idx = 0; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[3 + dvp] * dp[0]) + (vp[2 + dvp] * dp[1]) + (vp[1 + dvp] * dp[2]) + (vp[0 + dvp] * dp[3]) + (vp[15 + dvp] * dp[4]) + (vp[14 + dvp] * dp[5]) + (vp[13 + dvp] * dp[6]) + (vp[12 + dvp] * dp[7]) + (vp[11 + dvp] * dp[8]) + (vp[10 + dvp] * dp[9]) + (vp[9 + dvp] * dp[10]) + (vp[8 + dvp] * dp[11]) + (vp[7 + dvp] * dp[12]) + (vp[6 + dvp] * dp[13]) + (vp[5 + dvp] * dp[14]) + (vp[4 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples4(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[4 + dvp] * dp[0]) + (vp[3 + dvp] * dp[1]) + (vp[2 + dvp] * dp[2]) + (vp[1 + dvp] * dp[3]) + (vp[0 + dvp] * dp[4]) + (vp[15 + dvp] * dp[5]) + (vp[14 + dvp] * dp[6]) + (vp[13 + dvp] * dp[7]) + (vp[12 + dvp] * dp[8]) + (vp[11 + dvp] * dp[9]) + (vp[10 + dvp] * dp[10]) + (vp[9 + dvp] * dp[11]) + (vp[8 + dvp] * dp[12]) + (vp[7 + dvp] * dp[13]) + (vp[6 + dvp] * dp[14]) + (vp[5 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples5(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[5 + dvp] * dp[0]) + (vp[4 + dvp] * dp[1]) + (vp[3 + dvp] * dp[2]) + (vp[2 + dvp] * dp[3]) + (vp[1 + dvp] * dp[4]) + (vp[0 + dvp] * dp[5]) + (vp[15 + dvp] * dp[6]) + (vp[14 + dvp] * dp[7]) + (vp[13 + dvp] * dp[8]) + (vp[12 + dvp] * dp[9]) + (vp[11 + dvp] * dp[10]) + (vp[10 + dvp] * dp[11]) + (vp[9 + dvp] * dp[12]) + (vp[8 + dvp] * dp[13]) + (vp[7 + dvp] * dp[14]) + (vp[6 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples6(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[6 + dvp] * dp[0]) + (vp[5 + dvp] * dp[1]) + (vp[4 + dvp] * dp[2]) + (vp[3 + dvp] * dp[3]) + (vp[2 + dvp] * dp[4]) + (vp[1 + dvp] * dp[5]) + (vp[0 + dvp] * dp[6]) + (vp[15 + dvp] * dp[7]) + (vp[14 + dvp] * dp[8]) + (vp[13 + dvp] * dp[9]) + (vp[12 + dvp] * dp[10]) + (vp[11 + dvp] * dp[11]) + (vp[10 + dvp] * dp[12]) + (vp[9 + dvp] * dp[13]) + (vp[8 + dvp] * dp[14]) + (vp[7 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples7(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[7 + dvp] * dp[0]) + (vp[6 + dvp] * dp[1]) + (vp[5 + dvp] * dp[2]) + (vp[4 + dvp] * dp[3]) + (vp[3 + dvp] * dp[4]) + (vp[2 + dvp] * dp[5]) + (vp[1 + dvp] * dp[6]) + (vp[0 + dvp] * dp[7]) + (vp[15 + dvp] * dp[8]) + (vp[14 + dvp] * dp[9]) + (vp[13 + dvp] * dp[10]) + (vp[12 + dvp] * dp[11]) + (vp[11 + dvp] * dp[12]) + (vp[10 + dvp] * dp[13]) + (vp[9 + dvp] * dp[14]) + (vp[8 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples8(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[8 + dvp] * dp[0]) + (vp[7 + dvp] * dp[1]) + (vp[6 + dvp] * dp[2]) + (vp[5 + dvp] * dp[3]) + (vp[4 + dvp] * dp[4]) + (vp[3 + dvp] * dp[5]) + (vp[2 + dvp] * dp[6]) + (vp[1 + dvp] * dp[7]) + (vp[0 + dvp] * dp[8]) + (vp[15 + dvp] * dp[9]) + (vp[14 + dvp] * dp[10]) + (vp[13 + dvp] * dp[11]) + (vp[12 + dvp] * dp[12]) + (vp[11 + dvp] * dp[13]) + (vp[10 + dvp] * dp[14]) + (vp[9 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples9(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[9 + dvp] * dp[0]) + (vp[8 + dvp] * dp[1]) + (vp[7 + dvp] * dp[2]) + (vp[6 + dvp] * dp[3]) + (vp[5 + dvp] * dp[4]) + (vp[4 + dvp] * dp[5]) + (vp[3 + dvp] * dp[6]) + (vp[2 + dvp] * dp[7]) + (vp[1 + dvp] * dp[8]) + (vp[0 + dvp] * dp[9]) + (vp[15 + dvp] * dp[10]) + (vp[14 + dvp] * dp[11]) + (vp[13 + dvp] * dp[12]) + (vp[12 + dvp] * dp[13]) + (vp[11 + dvp] * dp[14]) + (vp[10 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - - private void compute_pcm_samples10(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[10 + dvp] * dp[0]) + (vp[9 + dvp] * dp[1]) + (vp[8 + dvp] * dp[2]) + (vp[7 + dvp] * dp[3]) + (vp[6 + dvp] * dp[4]) + (vp[5 + dvp] * dp[5]) + (vp[4 + dvp] * dp[6]) + (vp[3 + dvp] * dp[7]) + (vp[2 + dvp] * dp[8]) + (vp[1 + dvp] * dp[9]) + (vp[0 + dvp] * dp[10]) + (vp[15 + dvp] * dp[11]) + (vp[14 + dvp] * dp[12]) + (vp[13 + dvp] * dp[13]) + (vp[12 + dvp] * dp[14]) + (vp[11 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples11(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[11 + dvp] * dp[0]) + (vp[10 + dvp] * dp[1]) + (vp[9 + dvp] * dp[2]) + (vp[8 + dvp] * dp[3]) + (vp[7 + dvp] * dp[4]) + (vp[6 + dvp] * dp[5]) + (vp[5 + dvp] * dp[6]) + (vp[4 + dvp] * dp[7]) + (vp[3 + dvp] * dp[8]) + (vp[2 + dvp] * dp[9]) + (vp[1 + dvp] * dp[10]) + (vp[0 + dvp] * dp[11]) + (vp[15 + dvp] * dp[12]) + (vp[14 + dvp] * dp[13]) + (vp[13 + dvp] * dp[14]) + (vp[12 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples12(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[12 + dvp] * dp[0]) + (vp[11 + dvp] * dp[1]) + (vp[10 + dvp] * dp[2]) + (vp[9 + dvp] * dp[3]) + (vp[8 + dvp] * dp[4]) + (vp[7 + dvp] * dp[5]) + (vp[6 + dvp] * dp[6]) + (vp[5 + dvp] * dp[7]) + (vp[4 + dvp] * dp[8]) + (vp[3 + dvp] * dp[9]) + (vp[2 + dvp] * dp[10]) + (vp[1 + dvp] * dp[11]) + (vp[0 + dvp] * dp[12]) + (vp[15 + dvp] * dp[13]) + (vp[14 + dvp] * dp[14]) + (vp[13 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples13(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[13 + dvp] * dp[0]) + (vp[12 + dvp] * dp[1]) + (vp[11 + dvp] * dp[2]) + (vp[10 + dvp] * dp[3]) + (vp[9 + dvp] * dp[4]) + (vp[8 + dvp] * dp[5]) + (vp[7 + dvp] * dp[6]) + (vp[6 + dvp] * dp[7]) + (vp[5 + dvp] * dp[8]) + (vp[4 + dvp] * dp[9]) + (vp[3 + dvp] * dp[10]) + (vp[2 + dvp] * dp[11]) + (vp[1 + dvp] * dp[12]) + (vp[0 + dvp] * dp[13]) + (vp[15 + dvp] * dp[14]) + (vp[14 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples14(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - float pcm_sample; - - pcm_sample = (float) (((vp[14 + dvp] * dp[0]) + (vp[13 + dvp] * dp[1]) + (vp[12 + dvp] * dp[2]) + (vp[11 + dvp] * dp[3]) + (vp[10 + dvp] * dp[4]) + (vp[9 + dvp] * dp[5]) + (vp[8 + dvp] * dp[6]) + (vp[7 + dvp] * dp[7]) + (vp[6 + dvp] * dp[8]) + (vp[5 + dvp] * dp[9]) + (vp[4 + dvp] * dp[10]) + (vp[3 + dvp] * dp[11]) + (vp[2 + dvp] * dp[12]) + (vp[1 + dvp] * dp[13]) + (vp[0 + dvp] * dp[14]) + (vp[15 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - - dvp += 16; - } - // for - } - private void compute_pcm_samples15(Obuffer buffer) - { - //UPGRADE_NOTE: Final was removed from the declaration of 'vp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] vp = actual_v; - - //int inc = v_inc; - //UPGRADE_NOTE: Final was removed from the declaration of 'tmpOut '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] tmpOut = _tmpOut; - int dvp = 0; - - // fat chance of having this loop unroll - for (int i = 0; i < 32; i++) - { - float pcm_sample; - //UPGRADE_NOTE: Final was removed from the declaration of 'dp '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - float[] dp = d16[i]; - pcm_sample = (float) (((vp[15 + dvp] * dp[0]) + (vp[14 + dvp] * dp[1]) + (vp[13 + dvp] * dp[2]) + (vp[12 + dvp] * dp[3]) + (vp[11 + dvp] * dp[4]) + (vp[10 + dvp] * dp[5]) + (vp[9 + dvp] * dp[6]) + (vp[8 + dvp] * dp[7]) + (vp[7 + dvp] * dp[8]) + (vp[6 + dvp] * dp[9]) + (vp[5 + dvp] * dp[10]) + (vp[4 + dvp] * dp[11]) + (vp[3 + dvp] * dp[12]) + (vp[2 + dvp] * dp[13]) + (vp[1 + dvp] * dp[14]) + (vp[0 + dvp] * dp[15])) * scalefactor); - - tmpOut[i] = pcm_sample; - dvp += 16; - } - // for - } - - private void compute_pcm_samples(Obuffer buffer) - { - - switch (actual_write_pos) - { - - case 0: - compute_pcm_samples0(buffer); - break; - - case 1: - compute_pcm_samples1(buffer); - break; - - case 2: - compute_pcm_samples2(buffer); - break; - - case 3: - compute_pcm_samples3(buffer); - break; - - case 4: - compute_pcm_samples4(buffer); - break; - - case 5: - compute_pcm_samples5(buffer); - break; - - case 6: - compute_pcm_samples6(buffer); - break; - - case 7: - compute_pcm_samples7(buffer); - break; - - case 8: - compute_pcm_samples8(buffer); - break; - - case 9: - compute_pcm_samples9(buffer); - break; - - case 10: - compute_pcm_samples10(buffer); - break; - - case 11: - compute_pcm_samples11(buffer); - break; - - case 12: - compute_pcm_samples12(buffer); - break; - - case 13: - compute_pcm_samples13(buffer); - break; - - case 14: - compute_pcm_samples14(buffer); - break; - - case 15: - compute_pcm_samples15(buffer); - break; - } - - if (buffer != null) - { - buffer.appendSamples(channel, _tmpOut); - } - - /* - // MDM: I was considering putting in quality control for - // low-spec CPUs, but the performance gain (about 10-15%) - // did not justify the considerable drop in audio quality. - switch (inc) - { - case 16: - buffer.appendSamples(channel, tmpOut); - break; - case 32: - for (int i=0; i<16; i++) - { - buffer.append(channel, (short)tmpOut[i]); - buffer.append(channel, (short)tmpOut[i]); - } - break; - case 64: - for (int i=0; i<8; i++) - { - buffer.append(channel, (short)tmpOut[i]); - buffer.append(channel, (short)tmpOut[i]); - buffer.append(channel, (short)tmpOut[i]); - buffer.append(channel, (short)tmpOut[i]); - } - break; - - }*/ - } - - /// Calculate 32 PCM samples and put the into the Obuffer-object. - /// - - public void calculate_pcm_samples(Obuffer buffer) - { - compute_new_v(); - compute_pcm_samples(buffer); - - actual_write_pos = (actual_write_pos + 1) & 0xf; - actual_v = (actual_v == v1)?v2:v1; - - // initialize samples[]: - //for (register float *floatp = samples + 32; floatp > samples; ) - // *--floatp = 0.0f; - - // MDM: this may not be necessary. The Layer III decoder always - // outputs 32 subband samples, but I haven't checked layer I & II. - for (int p = 0; p < 32; p++) - samples[p] = 0.0f; - } - - - private const double MY_PI = 3.14159265358979323846; - //UPGRADE_NOTE: Final was removed from the declaration of 'cos1_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos1_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos3_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos3_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 3.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos5_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos5_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 5.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos7_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos7_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 7.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos9_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos9_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 9.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos11_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos11_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 11.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos13_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos13_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 13.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos15_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos15_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 15.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos17_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos17_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 17.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos19_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos19_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 19.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos21_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos21_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 21.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos23_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos23_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 23.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos25_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos25_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 25.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos27_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos27_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 27.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos29_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos29_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 29.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos31_64 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos31_64 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 31.0 / 64.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos1_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos1_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos3_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos3_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 3.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos5_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos5_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 5.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos7_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos7_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 7.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos9_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos9_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 9.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos11_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos11_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 11.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos13_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos13_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 13.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos15_32 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos15_32 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 15.0 / 32.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos1_16 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos1_16 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI / 16.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos3_16 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos3_16 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 3.0 / 16.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos5_16 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos5_16 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 5.0 / 16.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos7_16 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos7_16 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 7.0 / 16.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos1_8 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos1_8 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI / 8.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos3_8 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos3_8 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI * 3.0 / 8.0))); - //UPGRADE_NOTE: Final was removed from the declaration of 'cos1_4 '. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1003"' - private static readonly float cos1_4 = (float) (1.0 / (2.0 * System.Math.Cos(MY_PI / 4.0))); - - // Note: These values are not in the same order - // as in Annex 3-B.3 of the ISO/IEC DIS 11172-3 - // private float d[] = {0.000000000, -4.000442505}; - - private static float[] d = null; - - /// - /// d[] split into subarrays of length 16. This provides for - /// more faster access by allowing a block of 16 to be addressed - /// with constant offset. - /// * - /// - private static float[][] d16 = null; - - /// Loads the data for the d[] from the resource SFd.ser. - /// - /// the loaded values for d[]. - /// - /// - static private float[] load_d() - { - // As we can't use the Java serialized resource, we use the copy graciously provided to us below. - return null; - } - - /// Converts a 1D array into a number of smaller arrays. This is used - /// to achieve offset + constant indexing into an array. Each sub-array - /// represents a block of values of the original array. - /// - /// array to split up into blocks. - /// - /// size of the blocks to split the array - /// into. This must be an exact divisor of - /// the length of the array, or some data - /// will be lost from the main array. - /// - /// - /// An array of arrays in which each element in the returned - /// array will be of length blockSize. - /// - /// - static private float[][] splitArray(float[] array, int blockSize) - { - int size = array.Length / blockSize; - float[][] split = new float[size][]; - for (int i = 0; i < size; i++) - { - split[i] = subArray(array, i * blockSize, blockSize); - } - return split; - } - - /// Returns a subarray of an existing array. - /// - /// - /// array to retrieve a subarra from. - /// - /// offset in the array that corresponds to - /// the first index of the subarray. - /// - /// number of indeces in the subarray. - /// - /// The subarray, which may be of length 0. - /// - /// - static private float[] subArray(float[] array, int offs, int len) - { - if (offs + len > array.Length) - { - len = array.Length - offs; - } - - if (len < 0) - len = 0; - - float[] subarray = new float[len]; - for (int i = 0; i < len; i++) - { - subarray[i] = array[offs + i]; - } - - return subarray; - } - - // The original data for d[]. This data (was) loaded from a file - // to reduce the overall package size and to improve performance. - static float[] d_data = { - 0.000000000f, -0.000442505f, 0.003250122f, -0.007003784f, - 0.031082153f, -0.078628540f, 0.100311279f, -0.572036743f, - 1.144989014f, 0.572036743f, 0.100311279f, 0.078628540f, - 0.031082153f, 0.007003784f, 0.003250122f, 0.000442505f, - -0.000015259f, -0.000473022f, 0.003326416f, -0.007919312f, - 0.030517578f, -0.084182739f, 0.090927124f, -0.600219727f, - 1.144287109f, 0.543823242f, 0.108856201f, 0.073059082f, - 0.031478882f, 0.006118774f, 0.003173828f, 0.000396729f, - -0.000015259f, -0.000534058f, 0.003387451f, -0.008865356f, - 0.029785156f, -0.089706421f, 0.080688477f, -0.628295898f, - 1.142211914f, 0.515609741f, 0.116577148f, 0.067520142f, - 0.031738281f, 0.005294800f, 0.003082275f, 0.000366211f, - -0.000015259f, -0.000579834f, 0.003433228f, -0.009841919f, - 0.028884888f, -0.095169067f, 0.069595337f, -0.656219482f, - 1.138763428f, 0.487472534f, 0.123474121f, 0.061996460f, - 0.031845093f, 0.004486084f, 0.002990723f, 0.000320435f, - -0.000015259f, -0.000625610f, 0.003463745f, -0.010848999f, - 0.027801514f, -0.100540161f, 0.057617188f, -0.683914185f, - 1.133926392f, 0.459472656f, 0.129577637f, 0.056533813f, - 0.031814575f, 0.003723145f, 0.002899170f, 0.000289917f, - -0.000015259f, -0.000686646f, 0.003479004f, -0.011886597f, - 0.026535034f, -0.105819702f, 0.044784546f, -0.711318970f, - 1.127746582f, 0.431655884f, 0.134887695f, 0.051132202f, - 0.031661987f, 0.003005981f, 0.002792358f, 0.000259399f, - -0.000015259f, -0.000747681f, 0.003479004f, -0.012939453f, - 0.025085449f, -0.110946655f, 0.031082153f, -0.738372803f, - 1.120223999f, 0.404083252f, 0.139450073f, 0.045837402f, - 0.031387329f, 0.002334595f, 0.002685547f, 0.000244141f, - -0.000030518f, -0.000808716f, 0.003463745f, -0.014022827f, - 0.023422241f, -0.115921021f, 0.016510010f, -0.765029907f, - 1.111373901f, 0.376800537f, 0.143264771f, 0.040634155f, - 0.031005859f, 0.001693726f, 0.002578735f, 0.000213623f, - -0.000030518f, -0.000885010f, 0.003417969f, -0.015121460f, - 0.021575928f, -0.120697021f, 0.001068115f, -0.791213989f, - 1.101211548f, 0.349868774f, 0.146362305f, 0.035552979f, - 0.030532837f, 0.001098633f, 0.002456665f, 0.000198364f, - -0.000030518f, -0.000961304f, 0.003372192f, -0.016235352f, - 0.019531250f, -0.125259399f, -0.015228271f, -0.816864014f, - 1.089782715f, 0.323318481f, 0.148773193f, 0.030609131f, - 0.029937744f, 0.000549316f, 0.002349854f, 0.000167847f, - -0.000030518f, -0.001037598f, 0.003280640f, -0.017349243f, - 0.017257690f, -0.129562378f, -0.032379150f, -0.841949463f, - 1.077117920f, 0.297210693f, 0.150497437f, 0.025817871f, - 0.029281616f, 0.000030518f, 0.002243042f, 0.000152588f, - -0.000045776f, -0.001113892f, 0.003173828f, -0.018463135f, - 0.014801025f, -0.133590698f, -0.050354004f, -0.866363525f, - 1.063217163f, 0.271591187f, 0.151596069f, 0.021179199f, - 0.028533936f, -0.000442505f, 0.002120972f, 0.000137329f, - -0.000045776f, -0.001205444f, 0.003051758f, -0.019577026f, - 0.012115479f, -0.137298584f, -0.069168091f, -0.890090942f, - 1.048156738f, 0.246505737f, 0.152069092f, 0.016708374f, - 0.027725220f, -0.000869751f, 0.002014160f, 0.000122070f, - -0.000061035f, -0.001296997f, 0.002883911f, -0.020690918f, - 0.009231567f, -0.140670776f, -0.088775635f, -0.913055420f, - 1.031936646f, 0.221984863f, 0.151962280f, 0.012420654f, - 0.026840210f, -0.001266479f, 0.001907349f, 0.000106812f, - -0.000061035f, -0.001388550f, 0.002700806f, -0.021789551f, - 0.006134033f, -0.143676758f, -0.109161377f, -0.935195923f, - 1.014617920f, 0.198059082f, 0.151306152f, 0.008316040f, - 0.025909424f, -0.001617432f, 0.001785278f, 0.000106812f, - -0.000076294f, -0.001480103f, 0.002487183f, -0.022857666f, - 0.002822876f, -0.146255493f, -0.130310059f, -0.956481934f, - 0.996246338f, 0.174789429f, 0.150115967f, 0.004394531f, - 0.024932861f, -0.001937866f, 0.001693726f, 0.000091553f, - -0.000076294f, -0.001586914f, 0.002227783f, -0.023910522f, - -0.000686646f, -0.148422241f, -0.152206421f, -0.976852417f, - 0.976852417f, 0.152206421f, 0.148422241f, 0.000686646f, - 0.023910522f, -0.002227783f, 0.001586914f, 0.000076294f, - -0.000091553f, -0.001693726f, 0.001937866f, -0.024932861f, - -0.004394531f, -0.150115967f, -0.174789429f, -0.996246338f, - 0.956481934f, 0.130310059f, 0.146255493f, -0.002822876f, - 0.022857666f, -0.002487183f, 0.001480103f, 0.000076294f, - -0.000106812f, -0.001785278f, 0.001617432f, -0.025909424f, - -0.008316040f, -0.151306152f, -0.198059082f, -1.014617920f, - 0.935195923f, 0.109161377f, 0.143676758f, -0.006134033f, - 0.021789551f, -0.002700806f, 0.001388550f, 0.000061035f, - -0.000106812f, -0.001907349f, 0.001266479f, -0.026840210f, - -0.012420654f, -0.151962280f, -0.221984863f, -1.031936646f, - 0.913055420f, 0.088775635f, 0.140670776f, -0.009231567f, - 0.020690918f, -0.002883911f, 0.001296997f, 0.000061035f, - -0.000122070f, -0.002014160f, 0.000869751f, -0.027725220f, - -0.016708374f, -0.152069092f, -0.246505737f, -1.048156738f, - 0.890090942f, 0.069168091f, 0.137298584f, -0.012115479f, - 0.019577026f, -0.003051758f, 0.001205444f, 0.000045776f, - -0.000137329f, -0.002120972f, 0.000442505f, -0.028533936f, - -0.021179199f, -0.151596069f, -0.271591187f, -1.063217163f, - 0.866363525f, 0.050354004f, 0.133590698f, -0.014801025f, - 0.018463135f, -0.003173828f, 0.001113892f, 0.000045776f, - -0.000152588f, -0.002243042f, -0.000030518f, -0.029281616f, - -0.025817871f, -0.150497437f, -0.297210693f, -1.077117920f, - 0.841949463f, 0.032379150f, 0.129562378f, -0.017257690f, - 0.017349243f, -0.003280640f, 0.001037598f, 0.000030518f, - -0.000167847f, -0.002349854f, -0.000549316f, -0.029937744f, - -0.030609131f, -0.148773193f, -0.323318481f, -1.089782715f, - 0.816864014f, 0.015228271f, 0.125259399f, -0.019531250f, - 0.016235352f, -0.003372192f, 0.000961304f, 0.000030518f, - -0.000198364f, -0.002456665f, -0.001098633f, -0.030532837f, - -0.035552979f, -0.146362305f, -0.349868774f, -1.101211548f, - 0.791213989f, -0.001068115f, 0.120697021f, -0.021575928f, - 0.015121460f, -0.003417969f, 0.000885010f, 0.000030518f, - -0.000213623f, -0.002578735f, -0.001693726f, -0.031005859f, - -0.040634155f, -0.143264771f, -0.376800537f, -1.111373901f, - 0.765029907f, -0.016510010f, 0.115921021f, -0.023422241f, - 0.014022827f, -0.003463745f, 0.000808716f, 0.000030518f, - -0.000244141f, -0.002685547f, -0.002334595f, -0.031387329f, - -0.045837402f, -0.139450073f, -0.404083252f, -1.120223999f, - 0.738372803f, -0.031082153f, 0.110946655f, -0.025085449f, - 0.012939453f, -0.003479004f, 0.000747681f, 0.000015259f, - -0.000259399f, -0.002792358f, -0.003005981f, -0.031661987f, - -0.051132202f, -0.134887695f, -0.431655884f, -1.127746582f, - 0.711318970f, -0.044784546f, 0.105819702f, -0.026535034f, - 0.011886597f, -0.003479004f, 0.000686646f, 0.000015259f, - -0.000289917f, -0.002899170f, -0.003723145f, -0.031814575f, - -0.056533813f, -0.129577637f, -0.459472656f, -1.133926392f, - 0.683914185f, -0.057617188f, 0.100540161f, -0.027801514f, - 0.010848999f, -0.003463745f, 0.000625610f, 0.000015259f, - -0.000320435f, -0.002990723f, -0.004486084f, -0.031845093f, - -0.061996460f, -0.123474121f, -0.487472534f, -1.138763428f, - 0.656219482f, -0.069595337f, 0.095169067f, -0.028884888f, - 0.009841919f, -0.003433228f, 0.000579834f, 0.000015259f, - -0.000366211f, -0.003082275f, -0.005294800f, -0.031738281f, - -0.067520142f, -0.116577148f, -0.515609741f, -1.142211914f, - 0.628295898f, -0.080688477f, 0.089706421f, -0.029785156f, - 0.008865356f, -0.003387451f, 0.000534058f, 0.000015259f, - -0.000396729f, -0.003173828f, -0.006118774f, -0.031478882f, - -0.073059082f, -0.108856201f, -0.543823242f, -1.144287109f, - 0.600219727f, -0.090927124f, 0.084182739f, -0.030517578f, - 0.007919312f, -0.003326416f, 0.000473022f, 0.000015259f - }; - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/huffcodetab.cs b/Other/libs/mp3sharp/mp3sharp/decoder/huffcodetab.cs deleted file mode 100644 index 6eb487cd7..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/huffcodetab.cs +++ /dev/null @@ -1,316 +0,0 @@ -using Support; -/* -* 16/11/99 Renamed class, added javadoc, and changed table -* name from String to 3 chars. mdm@techie.com -* 02/15/99 Java Conversion by E.B, ebsp@iname.com, JavaLayer -* -*--------------------------------------------------------------------------- -* huffman.h -* -* Adapted from the ISO MPEG Audio Subgroup Software Simulation -* Group's public c source for its MPEG audio decoder. Miscellaneous -* changes by Jeff Tsay (ctsay@pasteur.eecs.berkeley.edu). -* -* Last modified : 04/19/97 -* -********************************************************************** -Copyright (c) 1991 MPEG/audio software simulation group, All Rights Reserved -huffman.h -********************************************************************** -********************************************************************** -* MPEG/audio coding/decoding software, work in progress * -* NOT for public distribution until verified and approved by the * -* MPEG/audio committee. For further information, please contact * -* Davis Pan, 508-493-2241, e-mail: pan@3d.enet.dec.com * -* * -* VERSION 4.1 * -* changes made since last update: * -* date programmers comment * -* 27.2.92 F.O.Witte (ITT Intermetall) * -* 8/24/93 M. Iwadare Changed for 1 pass decoding. * -* 7/14/94 J. Koller useless 'typedef' before huffcodetab * -* removed * -********************************************************************** -*---------------------------------------------------------------------------- -*/ -namespace javazoom.jl.decoder -{ - using System; - - /// Class to implements Huffman decoder. - /// - sealed class huffcodetab - { - private const int MXOFF = 250; - private const int HTN = 34; - - private char tablename0 = ' '; /* string, containing table_description */ - private char tablename1 = ' '; /* string, containing table_description */ - private char tablename2 = ' '; /* string, containing table_description */ - - private int xlen; /* max. x-index+ */ - private int ylen; /* max. y-index+ */ - private int linbits; /* number of linbits */ - private int linmax; /* max number to be stored in linbits */ - private int ref_Renamed; /* a positive value indicates a reference */ - private int[] table = null; /* pointer to array[xlen][ylen] */ - private int[] hlen = null; /* pointer to array[xlen][ylen] */ - private int[][] val = null; /* decoder tree */ - private int treelen; /* length of decoder tree */ - - private static int[][] ValTab0 = {new int[]{0, 0}}; - - private static int[][] ValTab1 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{2, 1}, new int[]{0, 16}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 17}}; - - private static int[][] ValTab2 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 33}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 2}, new int[]{0, 34}}; - - private static int[][] ValTab3 = {new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{2, 1}, new int[]{0, 16}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 33}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 2}, new int[]{0, 34}}; - - private static int[][] ValTab4 = {new int[]{0, 0}}; // dummy - - private static int[][] ValTab5 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{0, 48}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 19}, new int[]{2, 1}, new int[]{0, 49}, new int[]{2, 1}, new int[]{0, 50}, new int[]{2, 1}, new int[]{0, 35}, new int[]{0, 51}}; - - private static int[][] ValTab6 = {new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{0, 16}, new int[]{0, 17}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 33}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 2}, new int[]{0, 34}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 50}, new int[]{2, 1}, new int[]{0, 35}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 51}}; - - private static int[][] ValTab7 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{0, 33}, new int[]{18, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 34}, new int[]{0, 48}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 50}, new int[]{2, 1}, new int[]{0, 35}, new int[]{0, 4}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 65}, new int[]{2, 1}, new int[]{0, 20}, new int[]{2, 1}, new int[]{0, 66}, new int[]{0, 36}, new int[]{12, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 67}, new int[]{0, 80}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 52}, new int[]{0, 5}, new int[]{0, 81}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 21}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 53}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 83}, new int[]{0, 84}, new int[]{2, 1}, new int[]{0, 69}, new int[]{0, 85}}; - - private static int[][] ValTab8 = {new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{14, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 34}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 3}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 4}, new int[]{2, 1}, new int[]{0, 65}, new int[]{2, 1}, new int[]{0, 20}, new int[]{0, 66}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 36}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 80}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{0, 81}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 21}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 82}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 37}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 53}, new int[]{2, 1}, new int[]{0, 83}, new int[]{2, 1}, new int[]{0, 69}, new int[]{2, 1}, new int[]{0, 84}, new int[]{0, 85}}; - - private static int[][] ValTab9 = {new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{0, 16}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 17}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 33}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 2}, new int[]{0, 34}, new int[]{12, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 3}, new int[]{0, 49}, new int[]{2, 1}, new int[]{0, 19}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 65}, new int[]{0, 20}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 51}, new int[]{2, 1}, new int[]{0, 66}, new int[]{0, 36}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 4}, new int[]{0, 80}, new int[]{0, 67}, new int[]{2, 1}, new int[]{0, 52}, new int[]{0, 81}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 21}, new int[]{0, 82}, new int[]{2, 1}, new int[]{0, 37}, new int[]{0, 68}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 84}, new int[]{0, 83}, new int[]{2, 1}, new int[]{0, 53}, new int[]{2, 1}, new int[]{0, 69}, new int[]{0, 85}}; - - private static int[][] ValTab10 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{10, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{28, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{0, 48}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 50}, new int[]{2, 1}, new int[]{0, 35}, new int[]{0, 64}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 65}, new int[]{0, 20}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 4}, new int[]{0, 51}, new int[]{2, 1}, new int[]{0, 66}, new int[]{0, 36}, new int[]{28, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 80}, new int[]{0, 5}, new int[]{0, 96}, new int[]{2, 1}, new int[]{0, 97}, new int[]{0, 22}, new int[]{12, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{0, 81}, new int[]{2, 1}, new int[]{0, 21}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 38}, new int[]{0, 54}, new int[]{0, 113}, new int[]{20, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 23}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 83}, new int[]{0, 6}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 53}, new int[]{0, 69}, new int[]{0, 98}, new int[]{2, 1}, new int[]{0, 112}, new int[]{2, 1}, new int[]{0, 7}, new int[]{0, 100}, new int[]{14, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 114}, new int[]{0, 39}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 99}, new int[]{2, 1}, new int[]{0, 84}, new int[]{0, 85}, new int[]{2, 1}, new int[]{0, 70}, new int[]{0, 115}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 55}, new int[]{0, 101}, new int[]{2, 1}, new int[]{0, 86}, new int[]{0, 116}, - new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 71}, new int[]{2, 1}, new int[]{0, 102}, new int[]{0, 117}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 87}, new int[]{0, 118}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 119}}; - - private static int[][] ValTab11 = {new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{0, 18}, new int[]{24, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 33}, new int[]{2, 1}, new int[]{0, 34}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 3}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 4}, new int[]{2, 1}, new int[]{0, 65}, new int[]{0, 20}, new int[]{30, 1}, new int[]{16, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 66}, new int[]{0, 36}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 67}, new int[]{0, 80}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 52}, new int[]{0, 81}, new int[]{0, 97}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 22}, new int[]{2, 1}, new int[]{0, 6}, new int[]{0, 38}, new int[]{2, 1}, new int[]{0, 98}, new int[]{2, 1}, new int[]{0, 21}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 82}, new int[]{16, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 37}, new int[]{0, 68}, new int[]{0, 96}, new int[]{2, 1}, new int[]{0, 99}, new int[]{0, 54}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 112}, new int[]{0, 23}, new int[]{0, 113}, new int[]{16, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 7}, new int[]{0, 100}, new int[]{0, 114}, new int[]{2, 1}, new int[]{0, 39}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 83}, new int[]{0, 53}, new int[]{2, 1}, new int[]{0, 84}, new int[]{0, 69}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 70}, new int[]{0, 115}, new int[]{2, 1}, new int[]{0, 55}, new int[]{2, 1}, new int[]{0, 101}, new int[]{0, 86}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, - new int[]{0, 85}, new int[]{0, 87}, new int[]{0, 116}, new int[]{2, 1}, new int[]{0, 71}, new int[]{0, 102}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 118}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 119}}; - - private static int[][] ValTab12 = {new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{2, 1}, new int[]{0, 0}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{16, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{0, 49}, new int[]{2, 1}, new int[]{0, 19}, new int[]{2, 1}, new int[]{0, 48}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 64}, new int[]{26, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{2, 1}, new int[]{0, 65}, new int[]{0, 51}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 20}, new int[]{0, 66}, new int[]{2, 1}, new int[]{0, 36}, new int[]{2, 1}, new int[]{0, 4}, new int[]{0, 80}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{2, 1}, new int[]{0, 81}, new int[]{0, 21}, new int[]{28, 1}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{2, 1}, new int[]{0, 83}, new int[]{0, 53}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 96}, new int[]{0, 22}, new int[]{0, 97}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 98}, new int[]{0, 38}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 6}, new int[]{0, 68}, new int[]{2, 1}, new int[]{0, 84}, new int[]{0, 69}, new int[]{18, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 99}, new int[]{0, 54}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 112}, new int[]{0, 7}, new int[]{0, 113}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 23}, new int[]{0, 100}, new int[]{2, 1}, new int[]{0, 70}, new int[]{0, 114}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 39}, new int[]{2, 1}, new int[]{0, 85}, new int[]{0, 115}, new int[]{2, 1}, new int[]{0, 55}, new int[]{0, 86}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 101}, - new int[]{0, 116}, new int[]{2, 1}, new int[]{0, 71}, new int[]{0, 102}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 87}, new int[]{2, 1}, new int[]{0, 118}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 119}}; - - private static int[][] ValTab13 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 17}, new int[]{28, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{0, 48}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 49}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 19}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 4}, new int[]{0, 65}, new int[]{70, 1}, new int[]{28, 1}, new int[]{14, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 20}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 66}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 36}, new int[]{0, 80}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 81}, new int[]{0, 21}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 82}, new int[]{2, 1}, new int[]{0, 37}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 83}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 96}, new int[]{0, 6}, new int[]{2, 1}, new int[]{0, 97}, new int[]{0, 22}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 128}, new int[]{0, 8}, new int[]{0, 129}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 53}, new int[]{0, 98}, new int[]{2, 1}, new int[]{0, 38}, new int[]{0, 84}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 69}, new int[]{0, 99}, new int[]{2, 1}, new int[]{0, 54}, new int[]{0, 112}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 7}, new int[]{0, 85}, new int[]{0, 113}, new int[]{2, 1}, new int[]{0, 23}, new int[]{2, 1}, new int[]{0, 39}, new int[]{0, 55}, new int[]{72, 1}, new int[]{24, 1}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 24}, new int[]{0, 130}, new int[]{2, 1}, - new int[]{0, 40}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 100}, new int[]{0, 70}, new int[]{0, 114}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 132}, new int[]{0, 72}, new int[]{2, 1}, new int[]{0, 144}, new int[]{0, 9}, new int[]{2, 1}, new int[]{0, 145}, new int[]{0, 25}, new int[]{24, 1}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 115}, new int[]{0, 101}, new int[]{2, 1}, new int[]{0, 86}, new int[]{0, 116}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 71}, new int[]{0, 102}, new int[]{0, 131}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 56}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 87}, new int[]{2, 1}, new int[]{0, 146}, new int[]{0, 41}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 133}, new int[]{2, 1}, new int[]{0, 88}, new int[]{0, 57}, new int[]{2, 1}, new int[]{0, 147}, new int[]{2, 1}, new int[]{0, 73}, new int[]{0, 134}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 160}, new int[]{2, 1}, new int[]{0, 104}, new int[]{0, 10}, new int[]{2, 1}, new int[]{0, 161}, new int[]{0, 26}, new int[]{68, 1}, new int[]{24, 1}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 162}, new int[]{0, 42}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 149}, new int[]{0, 89}, new int[]{2, 1}, new int[]{0, 163}, new int[]{0, 58}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 74}, new int[]{0, 150}, new int[]{2, 1}, new int[]{0, 176}, new int[]{0, 11}, new int[]{2, 1}, new int[]{0, 177}, new int[]{0, 27}, new int[]{20, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 178}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 118}, new int[]{0, 119}, new int[]{0, 148}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 135}, new int[]{0, 120}, new int[]{0, 164}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 105}, new int[]{0, 165}, new int[]{0, 43}, new int[]{12, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, - 90}, new int[]{0, 136}, new int[]{0, 179}, new int[]{2, 1}, new int[]{0, 59}, new int[]{2, 1}, new int[]{0, 121}, new int[]{0, 166}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 106}, new int[]{0, 180}, new int[]{0, 192}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 152}, new int[]{0, 193}, new int[]{60, 1}, new int[]{22, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 28}, new int[]{2, 1}, new int[]{0, 137}, new int[]{0, 181}, new int[]{2, 1}, new int[]{0, 91}, new int[]{0, 194}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 44}, new int[]{0, 60}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 182}, new int[]{0, 107}, new int[]{2, 1}, new int[]{0, 196}, new int[]{0, 76}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 168}, new int[]{0, 138}, new int[]{2, 1}, new int[]{0, 208}, new int[]{0, 13}, new int[]{2, 1}, new int[]{0, 209}, new int[]{2, 1}, new int[]{0, 75}, new int[]{2, 1}, new int[]{0, 151}, new int[]{0, 167}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 195}, new int[]{2, 1}, new int[]{0, 122}, new int[]{0, 153}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 197}, new int[]{0, 92}, new int[]{0, 183}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 29}, new int[]{0, 210}, new int[]{2, 1}, new int[]{0, 45}, new int[]{2, 1}, new int[]{0, 123}, new int[]{0, 211}, new int[]{52, 1}, new int[]{28, 1}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 61}, new int[]{0, 198}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 108}, new int[]{0, 169}, new int[]{2, 1}, new int[]{0, 154}, new int[]{0, 212}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 184}, new int[]{0, 139}, new int[]{2, 1}, new int[]{0, 77}, new int[]{0, 199}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 124}, new int[]{0, 213}, new int[]{2, 1}, new int[]{0, 93}, new int[]{0, 224}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 225}, new int[]{0, 30}, new int[]{4, 1} - , new int[]{2, 1}, new int[]{0, 14}, new int[]{0, 46}, new int[]{0, 226}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 227}, new int[]{0, 109}, new int[]{2, 1}, new int[]{0, 140}, new int[]{0, 228}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 229}, new int[]{0, 186}, new int[]{0, 240}, new int[]{38, 1}, new int[]{16, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 241}, new int[]{0, 31}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 170}, new int[]{0, 155}, new int[]{0, 185}, new int[]{2, 1}, new int[]{0, 62}, new int[]{2, 1}, new int[]{0, 214}, new int[]{0, 200}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 78}, new int[]{2, 1}, new int[]{0, 215}, new int[]{0, 125}, new int[]{2, 1}, new int[]{0, 171}, new int[]{2, 1}, new int[]{0, 94}, new int[]{0, 201}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 15}, new int[]{2, 1}, new int[]{0, 156}, new int[]{0, 110}, new int[]{2, 1}, new int[]{0, 242}, new int[]{0, 47}, new int[]{32, 1}, new int[]{16, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 216}, new int[]{0, 141}, new int[]{0, 63}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 243}, new int[]{2, 1}, new int[]{0, 230}, new int[]{0, 202}, new int[]{2, 1}, new int[]{0, 244}, new int[]{0, 79}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 187}, new int[]{0, 172}, new int[]{2, 1}, new int[]{0, 231}, new int[]{0, 245}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 217}, new int[]{0, 157}, new int[]{2, 1}, new int[]{0, 95}, new int[]{0, 232}, new int[]{30, 1}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 111}, new int[]{2, 1}, new int[]{0, 246}, new int[]{0, 203}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 188}, new int[]{0, 173}, new int[]{0, 218}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 247}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 126}, new int[]{0, 127}, new int[]{0, 142}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 158}, new int[]{0, 174} - , new int[]{0, 204}, new int[]{2, 1}, new int[]{0, 248}, new int[]{0, 143}, new int[]{18, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 219}, new int[]{0, 189}, new int[]{2, 1}, new int[]{0, 234}, new int[]{0, 249}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 159}, new int[]{0, 235}, new int[]{2, 1}, new int[]{0, 190}, new int[]{2, 1}, new int[]{0, 205}, new int[]{0, 250}, new int[]{14, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 221}, new int[]{0, 236}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 233}, new int[]{0, 175}, new int[]{0, 220}, new int[]{2, 1}, new int[]{0, 206}, new int[]{0, 251}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 191}, new int[]{0, 222}, new int[]{2, 1}, new int[]{0, 207}, new int[]{0, 238}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 223}, new int[]{0, 239}, new int[]{2, 1}, new int[]{0, 255}, new int[]{2, 1}, new int[]{0, 237}, new int[]{2, 1}, new int[]{0, 253}, new int[]{2, 1}, new int[]{0, 252}, new int[]{0, 254}}; - - private static int[][] ValTab14 = {new int[]{0, 0}}; - - private static int[][] ValTab15 = {new int[]{16, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{2, 1}, new int[]{0, 16}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 17}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{50, 1}, new int[]{16, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 49}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 19}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 64}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{14, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 4}, new int[]{0, 20}, new int[]{0, 65}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 66}, new int[]{2, 1}, new int[]{0, 36}, new int[]{0, 67}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 52}, new int[]{2, 1}, new int[]{0, 80}, new int[]{0, 5}, new int[]{2, 1}, new int[]{0, 81}, new int[]{0, 21}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 83}, new int[]{0, 97}, new int[]{90, 1}, new int[]{36, 1}, new int[]{18, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 53}, new int[]{2, 1}, new int[]{0, 96}, new int[]{0, 6}, new int[]{2, 1}, new int[]{0, 22}, new int[]{0, 98}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 38}, new int[]{0, 84}, new int[]{2, 1}, new int[]{0, 69}, new int[]{0, 99}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 54}, new int[]{2, 1}, new int[]{0, 112}, new int[]{0, 7}, new int[]{2, 1}, new int[]{0, 113}, new int[]{0, 85}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 23}, new int[]{0, 100}, new int[]{2, 1}, new int[]{0, 114}, new int[]{0, 39}, new int[]{24, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 70}, new int[]{0, 115}, new int[]{2, 1}, new int[]{0, 55}, new int[]{0, 101}, new int[]{4, 1} - , new int[]{2, 1}, new int[]{0, 86}, new int[]{0, 128}, new int[]{2, 1}, new int[]{0, 8}, new int[]{0, 116}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 129}, new int[]{0, 24}, new int[]{2, 1}, new int[]{0, 130}, new int[]{0, 40}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 71}, new int[]{0, 102}, new int[]{2, 1}, new int[]{0, 131}, new int[]{0, 56}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 87}, new int[]{2, 1}, new int[]{0, 132}, new int[]{0, 72}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 144}, new int[]{0, 25}, new int[]{0, 145}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 146}, new int[]{0, 118}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 41}, new int[]{92, 1}, new int[]{36, 1}, new int[]{18, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 133}, new int[]{0, 88}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 9}, new int[]{0, 119}, new int[]{0, 147}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 57}, new int[]{0, 148}, new int[]{2, 1}, new int[]{0, 73}, new int[]{0, 134}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 104}, new int[]{2, 1}, new int[]{0, 160}, new int[]{0, 10}, new int[]{2, 1}, new int[]{0, 161}, new int[]{0, 26}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 162}, new int[]{0, 42}, new int[]{2, 1}, new int[]{0, 149}, new int[]{0, 89}, new int[]{26, 1}, new int[]{14, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 163}, new int[]{2, 1}, new int[]{0, 58}, new int[]{0, 135}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 120}, new int[]{0, 164}, new int[]{2, 1}, new int[]{0, 74}, new int[]{0, 150}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 105}, new int[]{0, 176}, new int[]{0, 177}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 27}, new int[]{0, 165}, new int[]{0, 178}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 90}, new int[]{0, 43}, new int[]{2, 1}, new int[]{0, 136}, new int[]{ - 0, 151}, new int[]{2, 1}, new int[]{0, 179}, new int[]{2, 1}, new int[]{0, 121}, new int[]{0, 59}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 106}, new int[]{0, 180}, new int[]{2, 1}, new int[]{0, 75}, new int[]{0, 193}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 152}, new int[]{0, 137}, new int[]{2, 1}, new int[]{0, 28}, new int[]{0, 181}, new int[]{80, 1}, new int[]{34, 1}, new int[]{16, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 91}, new int[]{0, 44}, new int[]{0, 194}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 11}, new int[]{0, 192}, new int[]{0, 166}, new int[]{2, 1}, new int[]{0, 167}, new int[]{0, 122}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 195}, new int[]{0, 60}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 153}, new int[]{0, 182}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 107}, new int[]{0, 196}, new int[]{2, 1}, new int[]{0, 76}, new int[]{0, 168}, new int[]{20, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 138}, new int[]{0, 197}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 208}, new int[]{0, 92}, new int[]{0, 209}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 183}, new int[]{0, 123}, new int[]{2, 1}, new int[]{0, 29}, new int[]{2, 1}, new int[]{0, 13}, new int[]{0, 45}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 210}, new int[]{0, 211}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 61}, new int[]{0, 198}, new int[]{2, 1}, new int[]{0, 108}, new int[]{0, 169}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 154}, new int[]{0, 184}, new int[]{0, 212}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 139}, new int[]{0, 77}, new int[]{2, 1}, new int[]{0, 199}, new int[]{0, 124}, new int[]{68, 1}, new int[]{34, 1}, new int[]{18, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 213}, new int[]{0, 93}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 224}, new int[]{0, 14}, new int[]{0, - 225}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 30}, new int[]{0, 226}, new int[]{2, 1}, new int[]{0, 170}, new int[]{0, 46}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 185}, new int[]{0, 155}, new int[]{2, 1}, new int[]{0, 227}, new int[]{0, 214}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 109}, new int[]{0, 62}, new int[]{2, 1}, new int[]{0, 200}, new int[]{0, 140}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 228}, new int[]{0, 78}, new int[]{2, 1}, new int[]{0, 215}, new int[]{0, 125}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 229}, new int[]{0, 186}, new int[]{2, 1}, new int[]{0, 171}, new int[]{0, 94}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 201}, new int[]{0, 156}, new int[]{2, 1}, new int[]{0, 241}, new int[]{0, 31}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 240}, new int[]{0, 110}, new int[]{0, 242}, new int[]{2, 1}, new int[]{0, 47}, new int[]{0, 230}, new int[]{38, 1}, new int[]{18, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 216}, new int[]{0, 243}, new int[]{2, 1}, new int[]{0, 63}, new int[]{0, 244}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 79}, new int[]{2, 1}, new int[]{0, 141}, new int[]{0, 217}, new int[]{2, 1}, new int[]{0, 187}, new int[]{0, 202}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 172}, new int[]{0, 231}, new int[]{2, 1}, new int[]{0, 126}, new int[]{0, 245}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 157}, new int[]{0, 95}, new int[]{2, 1}, new int[]{0, 232}, new int[]{0, 142}, new int[]{2, 1}, new int[]{0, 246}, new int[]{0, 203}, new int[]{34, 1}, new int[]{18, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 15}, new int[]{0, 174}, new int[]{0, 111}, new int[]{2, 1}, new int[]{0, 188}, new int[]{0, 218}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 173}, new int[]{0, 247}, new int[]{2, 1}, new int[]{0, 127}, new int[]{0, 233}, new int[]{8 - , 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 158}, new int[]{0, 204}, new int[]{2, 1}, new int[]{0, 248}, new int[]{0, 143}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 219}, new int[]{0, 189}, new int[]{2, 1}, new int[]{0, 234}, new int[]{0, 249}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 159}, new int[]{0, 220}, new int[]{2, 1}, new int[]{0, 205}, new int[]{0, 235}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 190}, new int[]{0, 250}, new int[]{2, 1}, new int[]{0, 175}, new int[]{0, 221}, new int[]{14, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 236}, new int[]{0, 206}, new int[]{0, 251}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 191}, new int[]{0, 237}, new int[]{2, 1}, new int[]{0, 222}, new int[]{0, 252}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 207}, new int[]{0, 253}, new int[]{0, 238}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 223}, new int[]{0, 254}, new int[]{2, 1}, new int[]{0, 239}, new int[]{0, 255}}; - - private static int[][] ValTab16 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 16}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 17}, new int[]{42, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{2, 1}, new int[]{0, 33}, new int[]{0, 18}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 34}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 3}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 4}, new int[]{0, 65}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 20}, new int[]{2, 1}, new int[]{0, 51}, new int[]{0, 66}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 36}, new int[]{0, 80}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{138, 1}, new int[]{40, 1}, new int[]{16, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 21}, new int[]{0, 81}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 53}, new int[]{0, 83}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 96}, new int[]{0, 6}, new int[]{0, 97}, new int[]{2, 1}, new int[]{0, 22}, new int[]{0, 98}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 38}, new int[]{0, 84}, new int[]{2, 1}, new int[]{0, 69}, new int[]{0, 99}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 54}, new int[]{0, 112}, new int[]{0, 113}, new int[]{40, 1}, new int[]{18, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 23}, new int[]{2, 1}, new int[]{0, 7}, new int[]{2, 1}, new int[]{0, 85}, new int[]{0, 100}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 114}, new int[]{0, 39}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 70}, new int[]{0, 101}, new int[]{0, 115}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 55} - , new int[]{2, 1}, new int[]{0, 86}, new int[]{0, 8}, new int[]{2, 1}, new int[]{0, 128}, new int[]{0, 129}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 24}, new int[]{2, 1}, new int[]{0, 116}, new int[]{0, 71}, new int[]{2, 1}, new int[]{0, 130}, new int[]{2, 1}, new int[]{0, 40}, new int[]{0, 102}, new int[]{24, 1}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 131}, new int[]{0, 56}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 132}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 72}, new int[]{0, 144}, new int[]{0, 145}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 25}, new int[]{2, 1}, new int[]{0, 9}, new int[]{0, 118}, new int[]{2, 1}, new int[]{0, 146}, new int[]{0, 41}, new int[]{14, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 133}, new int[]{0, 88}, new int[]{2, 1}, new int[]{0, 147}, new int[]{0, 57}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 160}, new int[]{0, 10}, new int[]{0, 26}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 162}, new int[]{2, 1}, new int[]{0, 103}, new int[]{2, 1}, new int[]{0, 87}, new int[]{0, 73}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 148}, new int[]{2, 1}, new int[]{0, 119}, new int[]{0, 134}, new int[]{2, 1}, new int[]{0, 161}, new int[]{2, 1}, new int[]{0, 104}, new int[]{0, 149}, new int[]{220, 1}, new int[]{126, 1}, new int[]{50, 1}, new int[]{26, 1}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 42}, new int[]{2, 1}, new int[]{0, 89}, new int[]{0, 58}, new int[]{2, 1}, new int[]{0, 163}, new int[]{2, 1}, new int[]{0, 135}, new int[]{0, 120}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 164}, new int[]{0, 74}, new int[]{2, 1}, new int[]{0, 150}, new int[]{0, 105}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 176}, new int[]{0, 11}, new int[]{0, 177}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 27}, new int[]{0, 178}, new int[]{2, 1}, new int[]{0, 43}, new int[]{2, 1}, new int[]{0, 165}, new int[]{0, 90}, new int[] - {6, 1}, new int[]{2, 1}, new int[]{0, 179}, new int[]{2, 1}, new int[]{0, 166}, new int[]{0, 106}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 180}, new int[]{0, 75}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 193}, new int[]{30, 1}, new int[]{14, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 181}, new int[]{0, 194}, new int[]{0, 44}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 167}, new int[]{0, 195}, new int[]{2, 1}, new int[]{0, 107}, new int[]{0, 196}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 29}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 136}, new int[]{0, 151}, new int[]{0, 59}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 209}, new int[]{0, 210}, new int[]{2, 1}, new int[]{0, 45}, new int[]{0, 211}, new int[]{18, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 30}, new int[]{0, 46}, new int[]{0, 226}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 121}, new int[]{0, 152}, new int[]{0, 192}, new int[]{2, 1}, new int[]{0, 28}, new int[]{2, 1}, new int[]{0, 137}, new int[]{0, 91}, new int[]{14, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 60}, new int[]{2, 1}, new int[]{0, 122}, new int[]{0, 182}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 76}, new int[]{0, 153}, new int[]{2, 1}, new int[]{0, 168}, new int[]{0, 138}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 13}, new int[]{2, 1}, new int[]{0, 197}, new int[]{0, 92}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 61}, new int[]{0, 198}, new int[]{2, 1}, new int[]{0, 108}, new int[]{0, 154}, new int[]{88, 1}, new int[]{86, 1}, new int[]{36, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 139}, new int[]{0, 77}, new int[]{2, 1}, new int[]{0, 199}, new int[]{0, 124}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 213}, new int[]{0, 93}, new int[]{2, 1}, new int[]{0, 224}, new int[]{0, 14}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 227}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 208}, new int[]{0, 183}, - new int[]{0, 123}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 169}, new int[]{0, 184}, new int[]{0, 212}, new int[]{2, 1}, new int[]{0, 225}, new int[]{2, 1}, new int[]{0, 170}, new int[]{0, 185}, new int[]{24, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 155}, new int[]{0, 214}, new int[]{0, 109}, new int[]{2, 1}, new int[]{0, 62}, new int[]{0, 200}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 140}, new int[]{0, 228}, new int[]{0, 78}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 215}, new int[]{0, 229}, new int[]{2, 1}, new int[]{0, 186}, new int[]{0, 171}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 156}, new int[]{0, 230}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 110}, new int[]{0, 216}, new int[]{2, 1}, new int[]{0, 141}, new int[]{0, 187}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 231}, new int[]{0, 157}, new int[]{2, 1}, new int[]{0, 232}, new int[]{0, 142}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 203}, new int[]{0, 188}, new int[]{0, 158}, new int[]{0, 241}, new int[]{2, 1}, new int[]{0, 31}, new int[]{2, 1}, new int[]{0, 15}, new int[]{0, 47}, new int[]{66, 1}, new int[]{56, 1}, new int[]{2, 1}, new int[]{0, 242}, new int[]{52, 1}, new int[]{50, 1}, new int[]{20, 1}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 189}, new int[]{2, 1}, new int[]{0, 94}, new int[]{2, 1}, new int[]{0, 125}, new int[]{0, 201}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 202}, new int[]{2, 1}, new int[]{0, 172}, new int[]{0, 126}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 218}, new int[]{0, 173}, new int[]{0, 204}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 174}, new int[]{2, 1}, new int[]{0, 219}, new int[]{0, 220}, new int[]{2, 1}, new int[]{0, 205}, new int[]{0, 190}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 235}, new int[]{0, 237}, new int[]{0, 238}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, - 217}, new int[]{0, 234}, new int[]{0, 233}, new int[]{2, 1}, new int[]{0, 222}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 221}, new int[]{0, 236}, new int[]{0, 206}, new int[]{0, 63}, new int[]{0, 240}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 243}, new int[]{0, 244}, new int[]{2, 1}, new int[]{0, 79}, new int[]{2, 1}, new int[]{0, 245}, new int[]{0, 95}, new int[]{10, 1}, new int[]{2, 1}, new int[]{0, 255}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 246}, new int[]{0, 111}, new int[]{2, 1}, new int[]{0, 247}, new int[]{0, 127}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 143}, new int[]{2, 1}, new int[]{0, 248}, new int[]{0, 249}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 159}, new int[]{0, 250}, new int[]{0, 175}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 251}, new int[]{0, 191}, new int[]{2, 1}, new int[]{0, 252}, new int[]{0, 207}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 253}, new int[]{0, 223}, new int[]{2, 1}, new int[]{0, 254}, new int[]{0, 239}}; - - private static int[][] ValTab24 = {new int[]{60, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{0, 16}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 17}, new int[]{14, 1}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 32}, new int[]{0, 2}, new int[]{0, 33}, new int[]{2, 1}, new int[]{0, 18}, new int[]{2, 1}, new int[]{0, 34}, new int[]{2, 1}, new int[]{0, 48}, new int[]{0, 3}, new int[]{14, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 49}, new int[]{0, 19}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 50}, new int[]{0, 35}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 64}, new int[]{0, 4}, new int[]{0, 65}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 20}, new int[]{0, 51}, new int[]{2, 1}, new int[]{0, 66}, new int[]{0, 36}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 67}, new int[]{0, 52}, new int[]{0, 81}, new int[]{6, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 80}, new int[]{0, 5}, new int[]{0, 21}, new int[]{2, 1}, new int[]{0, 82}, new int[]{0, 37}, new int[]{250, 1}, new int[]{98, 1}, new int[]{34, 1}, new int[]{18, 1}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 68}, new int[]{0, 83}, new int[]{2, 1}, new int[]{0, 53}, new int[]{2, 1}, new int[]{0, 96}, new int[]{0, 6}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 97}, new int[]{0, 22}, new int[]{2, 1}, new int[]{0, 98}, new int[]{0, 38}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 84}, new int[]{0, 69}, new int[]{2, 1}, new int[]{0, 99}, new int[]{0, 54}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 113}, new int[]{0, 85}, new int[]{2, 1}, new int[]{0, 100}, new int[]{0, 70}, new int[]{32, 1}, new int[]{14, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 114}, new int[]{2, 1}, new int[]{0, 39}, new int[]{0, 55}, new int[]{2, 1}, new int[]{0, 115}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 112}, new int[]{0, 7}, new int[]{0, 23}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, - new int[]{0, 101}, new int[]{0, 86}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 128}, new int[]{0, 8}, new int[]{0, 129}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 116}, new int[]{0, 71}, new int[]{2, 1}, new int[]{0, 24}, new int[]{0, 130}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 40}, new int[]{0, 102}, new int[]{2, 1}, new int[]{0, 131}, new int[]{0, 56}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 117}, new int[]{0, 87}, new int[]{2, 1}, new int[]{0, 132}, new int[]{0, 72}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 145}, new int[]{0, 25}, new int[]{2, 1}, new int[]{0, 146}, new int[]{0, 118}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 103}, new int[]{0, 41}, new int[]{2, 1}, new int[]{0, 133}, new int[]{0, 88}, new int[]{92, 1}, new int[]{34, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 147}, new int[]{0, 57}, new int[]{2, 1}, new int[]{0, 148}, new int[]{0, 73}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 119}, new int[]{0, 134}, new int[]{2, 1}, new int[]{0, 104}, new int[]{0, 161}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 162}, new int[]{0, 42}, new int[]{2, 1}, new int[]{0, 149}, new int[]{0, 89}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 163}, new int[]{0, 58}, new int[]{2, 1}, new int[]{0, 135}, new int[]{2, 1}, new int[]{0, 120}, new int[]{0, 74}, new int[]{22, 1}, new int[]{12, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 164}, new int[]{0, 150}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 105}, new int[]{0, 177}, new int[]{2, 1}, new int[]{0, 27}, new int[]{0, 165}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 178}, new int[]{2, 1}, new int[]{0, 90}, new int[]{0, 43}, new int[]{2, 1}, new int[]{0, 136}, new int[]{0, 179}, new int[]{16, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 144}, new int[]{2, 1}, new int[]{0, 9}, new int[]{0, 160}, new int[]{2, 1}, new int[]{0, 151}, new int[]{0, 121}, new int[] - {4, 1}, new int[]{2, 1}, new int[]{0, 166}, new int[]{0, 106}, new int[]{0, 180}, new int[]{12, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 26}, new int[]{2, 1}, new int[]{0, 10}, new int[]{0, 176}, new int[]{2, 1}, new int[]{0, 59}, new int[]{2, 1}, new int[]{0, 11}, new int[]{0, 192}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 75}, new int[]{0, 193}, new int[]{2, 1}, new int[]{0, 152}, new int[]{0, 137}, new int[]{67, 1}, new int[]{34, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 28}, new int[]{0, 181}, new int[]{2, 1}, new int[]{0, 91}, new int[]{0, 194}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 44}, new int[]{0, 167}, new int[]{2, 1}, new int[]{0, 122}, new int[]{0, 195}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 60}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 208}, new int[]{2, 1}, new int[]{0, 182}, new int[]{0, 107}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 196}, new int[]{0, 76}, new int[]{2, 1}, new int[]{0, 153}, new int[]{0, 168}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 138}, new int[]{0, 197}, new int[]{2, 1}, new int[]{0, 92}, new int[]{0, 209}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 183}, new int[]{0, 123}, new int[]{2, 1}, new int[]{0, 29}, new int[]{0, 210}, new int[]{9, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 45}, new int[]{0, 211}, new int[]{2, 1}, new int[]{0, 61}, new int[]{0, 198}, new int[]{85, 250}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 108}, new int[]{0, 169}, new int[]{2, 1}, new int[]{0, 154}, new int[]{0, 212}, new int[]{32, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 184}, new int[]{0, 139}, new int[]{2, 1}, new int[]{0, 77}, new int[]{0, 199}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 124}, new int[]{0, 213}, new int[]{2, 1}, new int[]{0, 93}, new int[]{0, 225}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 30}, new int[]{0, 226}, new int[]{2, 1 - }, new int[]{0, 170}, new int[]{0, 185}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 155}, new int[]{0, 227}, new int[]{2, 1}, new int[]{0, 214}, new int[]{0, 109}, new int[]{20, 1}, new int[]{10, 1}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 62}, new int[]{2, 1}, new int[]{0, 46}, new int[]{0, 78}, new int[]{2, 1}, new int[]{0, 200}, new int[]{0, 140}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 228}, new int[]{0, 215}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 125}, new int[]{0, 171}, new int[]{0, 229}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 186}, new int[]{0, 94}, new int[]{2, 1}, new int[]{0, 201}, new int[]{2, 1}, new int[]{0, 156}, new int[]{0, 110}, new int[]{8, 1}, new int[]{2, 1}, new int[]{0, 230}, new int[]{2, 1}, new int[]{0, 13}, new int[]{2, 1}, new int[]{0, 224}, new int[]{0, 14}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 216}, new int[]{0, 141}, new int[]{2, 1}, new int[]{0, 187}, new int[]{0, 202}, new int[]{74, 1}, new int[]{2, 1}, new int[]{0, 255}, new int[]{64, 1}, new int[]{58, 1}, new int[]{32, 1}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 172}, new int[]{0, 231}, new int[]{2, 1}, new int[]{0, 126}, new int[]{0, 217}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 157}, new int[]{0, 232}, new int[]{2, 1}, new int[]{0, 142}, new int[]{0, 203}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 188}, new int[]{0, 218}, new int[]{2, 1}, new int[]{0, 173}, new int[]{0, 233}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 158}, new int[]{0, 204}, new int[]{2, 1}, new int[]{0, 219}, new int[]{0, 189}, new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 234}, new int[]{0, 174}, new int[]{2, 1}, new int[]{0, 220}, new int[]{0, 205}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 235}, new int[]{0, 190}, new int[]{2, 1}, new int[]{0, 221}, new int[]{0, 236}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 206}, new int[]{0, 237}, new int[] - {2, 1}, new int[]{0, 222}, new int[]{0, 238}, new int[]{0, 15}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 240}, new int[]{0, 31}, new int[]{0, 241}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 242}, new int[]{0, 47}, new int[]{2, 1}, new int[]{0, 243}, new int[]{0, 63}, new int[]{18, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 244}, new int[]{0, 79}, new int[]{2, 1}, new int[]{0, 245}, new int[]{0, 95}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 246}, new int[]{0, 111}, new int[]{2, 1}, new int[]{0, 247}, new int[]{2, 1}, new int[]{0, 127}, new int[]{0, 143}, new int[]{10, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 248}, new int[]{0, 249}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 159}, new int[]{0, 175}, new int[]{0, 250}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 251}, new int[]{0, 191}, new int[]{2, 1}, new int[]{0, 252}, new int[]{0, 207}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 253}, new int[]{0, 223}, new int[]{2, 1}, new int[]{0, 254}, new int[]{0, 239}}; - - private static int[][] ValTab32 = {new int[]{2, 1}, new int[]{0, 0}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 8}, new int[]{0, 4}, new int[]{2, 1}, new int[]{0, 1}, new int[]{0, 2}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 10}, new int[]{2, 1}, new int[]{0, 3}, new int[]{0, 6}, new int[]{6, 1}, new int[]{2, 1}, new int[]{0, 9}, new int[]{2, 1}, new int[]{0, 5}, new int[]{0, 7}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 14}, new int[]{0, 13}, new int[]{2, 1}, new int[]{0, 15}, new int[]{0, 11}}; - - private static int[][] ValTab33 = {new int[]{16, 1}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 0}, new int[]{0, 1}, new int[]{2, 1}, new int[]{0, 2}, new int[]{0, 3}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 4}, new int[]{0, 5}, new int[]{2, 1}, new int[]{0, 6}, new int[]{0, 7}, new int[]{8, 1}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 8}, new int[]{0, 9}, new int[]{2, 1}, new int[]{0, 10}, new int[]{0, 11}, new int[]{4, 1}, new int[]{2, 1}, new int[]{0, 12}, new int[]{0, 13}, new int[]{2, 1}, new int[]{0, 14}, new int[]{0, 15}}; - - - public static huffcodetab[] ht = null; /* Simulate extern struct */ - - private static int[] bitbuf; - - /// Big Constructor : Computes all Huffman Tables. - /// - private huffcodetab(System.String S, int XLEN, int YLEN, int LINBITS, int LINMAX, int REF, int[] TABLE, int[] HLEN, int[][] VAL, int TREELEN) - { - tablename0 = S[0]; - tablename1 = S[1]; - tablename2 = S[2]; - xlen = XLEN; - ylen = YLEN; - linbits = LINBITS; - linmax = LINMAX; - ref_Renamed = REF; - table = TABLE; - hlen = HLEN; - val = VAL; - treelen = TREELEN; - } - - - - /// Do the huffman-decoding. - /// note! for counta,countb -the 4 bit value is returned in y, - /// discard x. - /// - public static int huffman_decoder(huffcodetab h, int[] x, int[] y, int[] v, int[] w, BitReserve br) - { - // array of all huffcodtable headers - // 0..31 Huffman code table 0..31 - // 32,33 count1-tables - - int dmask = 1 << ((4 * 8) - 1); - int hs = 4 * 8; - int level; - int point = 0; - int error = 1; - level = dmask; - - if (h.val == null) - return 2; - - /* table 0 needs no bits */ - if (h.treelen == 0) - { - x[0] = y[0] = 0; - return 0; - } - - /* Lookup in Huffman table. */ - - /*int bitsAvailable = 0; - int bitIndex = 0; - - int bits[] = bitbuf;*/ - do - { - if (h.val[point][0] == 0) - { - /*end of tree*/ - x[0] = SupportClass.URShift(h.val[point][1], 4); - y[0] = h.val[point][1] & 0xf; - error = 0; - break; - } - - // hget1bit() is called thousands of times, and so needs to be - // ultra fast. - /* - if (bitIndex==bitsAvailable) - { - bitsAvailable = br.readBits(bits, 32); - bitIndex = 0; - } - */ - //if (bits[bitIndex++]!=0) - if (br.hget1bit() != 0) - { - while (h.val[point][1] >= MXOFF) - point += h.val[point][1]; - point += h.val[point][1]; - } - else - { - while (h.val[point][0] >= MXOFF) - point += h.val[point][0]; - point += h.val[point][0]; - } - level = SupportClass.URShift(level, 1); - // MDM: ht[0] is always 0; - } - while ((level != 0) || (point < 0)); - - // put back any bits not consumed - /* - int unread = (bitsAvailable-bitIndex); - if (unread>0) - br.rewindNbits(unread); - */ - /* Process sign encodings for quadruples tables. */ - // System.out.println(h.tablename); - if (h.tablename0 == '3' && (h.tablename1 == '2' || h.tablename1 == '3')) - { - v[0] = (y[0] >> 3) & 1; - w[0] = (y[0] >> 2) & 1; - x[0] = (y[0] >> 1) & 1; - y[0] = y[0] & 1; - - /* v, w, x and y are reversed in the bitstream. - switch them around to make test bistream work. */ - - if (v[0] != 0) - if (br.hget1bit() != 0) - v[0] = - v[0]; - if (w[0] != 0) - if (br.hget1bit() != 0) - w[0] = - w[0]; - if (x[0] != 0) - if (br.hget1bit() != 0) - x[0] = - x[0]; - if (y[0] != 0) - if (br.hget1bit() != 0) - y[0] = - y[0]; - } - else - { - // Process sign and escape encodings for dual tables. - // x and y are reversed in the test bitstream. - // Reverse x and y here to make test bitstream work. - - if (h.linbits != 0) - if ((h.xlen - 1) == x[0]) - x[0] += br.hgetbits(h.linbits); - if (x[0] != 0) - if (br.hget1bit() != 0) - x[0] = - x[0]; - if (h.linbits != 0) - if ((h.ylen - 1) == y[0]) - y[0] += br.hgetbits(h.linbits); - if (y[0] != 0) - if (br.hget1bit() != 0) - y[0] = - y[0]; - } - return error; - } - - public static void inithuff() - { - - if (ht != null) - return ; - - ht = new huffcodetab[HTN]; - ht[0] = new huffcodetab("0 ", 0, 0, 0, 0, - 1, null, null, ValTab0, 0); - ht[1] = new huffcodetab("1 ", 2, 2, 0, 0, - 1, null, null, ValTab1, 7); - ht[2] = new huffcodetab("2 ", 3, 3, 0, 0, - 1, null, null, ValTab2, 17); - ht[3] = new huffcodetab("3 ", 3, 3, 0, 0, - 1, null, null, ValTab3, 17); - ht[4] = new huffcodetab("4 ", 0, 0, 0, 0, - 1, null, null, ValTab4, 0); - ht[5] = new huffcodetab("5 ", 4, 4, 0, 0, - 1, null, null, ValTab5, 31); - ht[6] = new huffcodetab("6 ", 4, 4, 0, 0, - 1, null, null, ValTab6, 31); - ht[7] = new huffcodetab("7 ", 6, 6, 0, 0, - 1, null, null, ValTab7, 71); - ht[8] = new huffcodetab("8 ", 6, 6, 0, 0, - 1, null, null, ValTab8, 71); - ht[9] = new huffcodetab("9 ", 6, 6, 0, 0, - 1, null, null, ValTab9, 71); - ht[10] = new huffcodetab("10 ", 8, 8, 0, 0, - 1, null, null, ValTab10, 127); - ht[11] = new huffcodetab("11 ", 8, 8, 0, 0, - 1, null, null, ValTab11, 127); - ht[12] = new huffcodetab("12 ", 8, 8, 0, 0, - 1, null, null, ValTab12, 127); - ht[13] = new huffcodetab("13 ", 16, 16, 0, 0, - 1, null, null, ValTab13, 511); - ht[14] = new huffcodetab("14 ", 0, 0, 0, 0, - 1, null, null, ValTab14, 0); - ht[15] = new huffcodetab("15 ", 16, 16, 0, 0, - 1, null, null, ValTab15, 511); - ht[16] = new huffcodetab("16 ", 16, 16, 1, 1, - 1, null, null, ValTab16, 511); - ht[17] = new huffcodetab("17 ", 16, 16, 2, 3, 16, null, null, ValTab16, 511); - ht[18] = new huffcodetab("18 ", 16, 16, 3, 7, 16, null, null, ValTab16, 511); - ht[19] = new huffcodetab("19 ", 16, 16, 4, 15, 16, null, null, ValTab16, 511); - ht[20] = new huffcodetab("20 ", 16, 16, 6, 63, 16, null, null, ValTab16, 511); - ht[21] = new huffcodetab("21 ", 16, 16, 8, 255, 16, null, null, ValTab16, 511); - ht[22] = new huffcodetab("22 ", 16, 16, 10, 1023, 16, null, null, ValTab16, 511); - ht[23] = new huffcodetab("23 ", 16, 16, 13, 8191, 16, null, null, ValTab16, 511); - ht[24] = new huffcodetab("24 ", 16, 16, 4, 15, - 1, null, null, ValTab24, 512); - ht[25] = new huffcodetab("25 ", 16, 16, 5, 31, 24, null, null, ValTab24, 512); - ht[26] = new huffcodetab("26 ", 16, 16, 6, 63, 24, null, null, ValTab24, 512); - ht[27] = new huffcodetab("27 ", 16, 16, 7, 127, 24, null, null, ValTab24, 512); - ht[28] = new huffcodetab("28 ", 16, 16, 8, 255, 24, null, null, ValTab24, 512); - ht[29] = new huffcodetab("29 ", 16, 16, 9, 511, 24, null, null, ValTab24, 512); - ht[30] = new huffcodetab("30 ", 16, 16, 11, 2047, 24, null, null, ValTab24, 512); - ht[31] = new huffcodetab("31 ", 16, 16, 13, 8191, 24, null, null, ValTab24, 512); - ht[32] = new huffcodetab("32 ", 1, 16, 0, 0, - 1, null, null, ValTab32, 31); - ht[33] = new huffcodetab("33 ", 1, 16, 0, 0, - 1, null, null, ValTab33, 31); - } - static huffcodetab() - { - bitbuf = new int[32]; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/decoder/readme.txt b/Other/libs/mp3sharp/mp3sharp/decoder/readme.txt deleted file mode 100644 index 7a765ec7d..000000000 --- a/Other/libs/mp3sharp/mp3sharp/decoder/readme.txt +++ /dev/null @@ -1,15 +0,0 @@ - -TODO: - - -Implement high-level Player and Converter classes. - -Add MP1 and MP2 support and test. - -Add option to run each "stage" on own thread. -E.g. read & parse input, decode subbands, subband synthesis, audio output. - -Retrofit seek support (temporarily removed when reworking classes.) - - -Document and give example code. \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/AudioDevice.cs b/Other/libs/mp3sharp/mp3sharp/player/AudioDevice.cs deleted file mode 100644 index d1d37e49c..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/AudioDevice.cs +++ /dev/null @@ -1,105 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - /// The AudioDevice interface provides an abstraction for - /// a device capable of sounding audio samples. Samples are written to - /// the device wia the {@link write() write()} method. The device assumes - /// that these samples are signed 16-bit samples taken at the output frequency - /// of the decoder. If the decoder outputs more than one channel, the samples for - /// each channel are assumed to appear consecutively, with the lower numbered - /// channels preceeding higher-numbered channels. E.g. if there are two - /// channels, the samples will appear in this order: - ///

-	/// 
-	/// l0, r0, l1, r1, l2, r2...
-	/// 
-	/// where 
-	/// lx indicates the xth sample on channel 0
-	/// rx indicates the xth sample on channel 1
-	/// 
- /// - /// @since 0.0.8 - ///
- /// Mat McGowan - /// - /// - public interface AudioDevice - { - /// Retrieves the open state of this audio device. - /// - /// - /// true if this audio device is open and playing - /// audio samples, or false otherwise. - /// - /// - bool Open - { - get; - - } - /// Retrieves the current playback position in milliseconds. - /// - int Position - { - get; - - } - /// Prepares the AudioDevice for playback of audio samples. - /// - /// decoder that will be providing the audio - /// samples. - /// - /// If the audio device is already open, this method returns silently. - /// - /// - /// - void open(Decoder decoder); - /// Writes a number of samples to this AudioDevice. - /// - /// - /// array of signed 16-bit samples to write - /// to the audio device. - /// - /// offset of the first sample. - /// - /// number of samples to write. - /// - /// This method may return prior to the samples actually being played - /// by the audio device. - /// - /// - void write(short[] samples, int offs, int len); - /// Closes this audio device. Any currently playing audio is stopped - /// as soon as possible. Any previously written audio data that has not been heard - /// is discarded. - /// - /// The implementation should ensure that any threads currently blocking - /// on the device (e.g. during a write or flush - /// operation should be unblocked by this method. - /// - void close(); - /// Blocks until all audio samples previously written to this audio device have - /// been heard. - /// - void flush(); - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceBase.cs b/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceBase.cs deleted file mode 100644 index 407e6a253..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceBase.cs +++ /dev/null @@ -1,201 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - /// The AudioDeviceBase class provides a simple thread-safe - /// implementation of the AudioDevice interface. - /// Template methods are provided for subclasses to override and - /// in doing so provide the implementation for the main operations - /// of the AudioDevice interface. - /// - /// @since 0.0.8 - /// - /// Mat McGowan - /// - /// - /* - * REVIEW: It is desirable to be able to use the decoder whe - * in the implementation of open(), but the decoder - * has not yet read a frame, and so much of the - * desired information (sample rate, channels etc.) - * are not available. - */ - public abstract class AudioDeviceBase : AudioDevice - { - /// Sets the open state for this audio device. - /// - virtual protected internal bool Open - { - set - { - this.open_Renamed_Field = value; - } - - } - /// Determines if this audio device is open or not. - /// - /// - /// true if the audio device is open, - /// false if it is not. - /// - /// - //UPGRADE_NOTE: Synchronized keyword was removed from method 'isOpen'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - virtual public bool IsOpen - { - get - { - lock (this) - { - return open_Renamed_Field; - } - } - - } - /// Retrieves the decoder that provides audio data to this - /// audio device. - /// - /// - /// The associated decoder. - /// - /// - virtual protected internal Decoder Decoder - { - get - { - return decoder; - } - - } - private bool open_Renamed_Field = false; - - private Decoder decoder = null; - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'open'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - /// Opens this audio device. - /// - /// - /// decoder that will provide audio data - /// to this audio device. - /// - /// - public virtual void open(Decoder decoder) - { - lock (this) - { - if (!Open) - { - this.decoder = decoder; - openImpl(); - Open = true; - } - } - } - - /// Template method to provide the - /// implementation for the opening of the audio device. - /// - protected internal virtual void openImpl() - { - } - - - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'close'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - /// Closes this audio device. If the device is currently playing - /// audio, playback is stopped immediately without flushing - /// any buffered audio data. - /// - public virtual void close() - { - lock (this) - { - if (Open) - { - closeImpl(); - Open = false; - decoder = null; - } - } - } - - /// Template method to provide the implementation for - /// closing the audio device. - /// - protected internal virtual void closeImpl() - { - } - - /// Writes audio data to this audio device. Audio data is - /// assumed to be in the output format of the decoder. This - /// method may return before the data has actually been sounded - /// by the device if the device buffers audio samples. - /// - /// - /// samples to write to the audio device. - /// - /// offset into the array of the first sample to write. - /// - /// number of samples from the array to write. - /// @throws JavaLayerException if the audio data could not be - /// written to the audio device. - /// If the audio device is not open, this method does nthing. - /// - /// - public virtual void write(short[] samples, int offs, int len) - { - if (Open) - { - writeImpl(samples, offs, len); - } - } - - /// Template method to provide the implementation for - /// writing audio samples to the audio device. - /// - /// - /// write() - /// - /// - protected internal virtual void writeImpl(short[] samples, int offs, int len) - { - } - - /// Waits for any buffered audio samples to be played by the - /// audio device. This method should only be called prior - /// to closing the device. - /// - public virtual void flush() - { - if (Open) - { - flushImpl(); - } - } - - /// Template method to provide the implementation for - /// flushing any buffered audio data. - /// - protected internal virtual void flushImpl() - { - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceFactory.cs b/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceFactory.cs deleted file mode 100644 index 4b96a37ec..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/AudioDeviceFactory.cs +++ /dev/null @@ -1,92 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - /// An AudioDeviceFactory class is responsible for creating - /// a specific AudioDevice implementation. A factory implementation - /// can be as simple or complex as desired and may support just one implementation - /// or may return several implementations depending upon the execution - /// environment. - ///

- /// When implementing a factory that provides an AudioDevice that uses - /// class that may not be present, the factory should dynamically link to any - /// specific implementation classes required to instantiate or test the audio - /// implementation. This is so that the application as a whole - /// can run without these classes being present. The audio - /// device implementation, however, will usually statically link to the classes - /// required. (See the JavaSound deivce and factory for an example - /// of this.) - /// - ///

- /// FactoryRegistry - /// - /// @since 0.0.8 - /// - /// Mat McGowan - /// - /// - public abstract class AudioDeviceFactory - { - /// Creates a new AudioDevice. - /// - /// - /// a new instance of a specific class of AudioDevice. - /// @throws JavaLayerException if an instance of AudioDevice could not - /// be created. - /// - /// - public abstract AudioDevice createAudioDevice(); - - //UPGRADE_ISSUE: Class 'java.lang.ClassLoader' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javalangClassLoader"' - /// Creates an instance of an AudioDevice implementation. - /// - /// ClassLoader to use to - /// load the named class, or null to use the - /// system class loader. - /// - /// name of the class to load. - /// - /// A newly-created instance of the audio device class. - /// - /// - protected internal virtual AudioDevice instantiate(ClassLoader loader, System.String name) - { - AudioDevice dev = null; - - System.Type cls = null; - if (loader == null) - { - //UPGRADE_TODO: Format of parameters of method 'java.lang.Class.forName' are different in the equivalent in .NET. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1092"' - cls = System.Type.GetType(name); - } - else - { - //UPGRADE_ISSUE: Method 'java.lang.ClassLoader.loadClass' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javalangClassLoader"' - cls = loader.loadClass(name); - } - - System.Object o = SupportClass.CreateNewInstance(cls); - dev = (AudioDevice) o; - - return dev; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/FactoryRegistry.cs b/Other/libs/mp3sharp/mp3sharp/player/FactoryRegistry.cs deleted file mode 100644 index 18624792b..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/FactoryRegistry.cs +++ /dev/null @@ -1,144 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - /// The FactoryRegistry class stores the factories - /// for all the audio device implementations available in the system. - ///

- /// Instances of this class are thread-safe. - /// - /// @since 0.0.8 - ///

- /// Mat McGowan - /// - /// - - public class FactoryRegistry:AudioDeviceFactory - { - public FactoryRegistry() - { - InitBlock(); - } - private void InitBlock() - { - factories = new System.Collections.Hashtable(); - } - virtual protected internal AudioDeviceFactory[] FactoriesPriority - { - get - { - AudioDeviceFactory[] fa = null; - lock (factories) - { - int size = factories.Count; - if (size != 0) - { - fa = new AudioDeviceFactory[size]; - int idx = 0; - System.Collections.IEnumerator e = factories.GetEnumerator(); - //UPGRADE_TODO: Method 'java.util.Enumeration.hasMoreElements' was converted to 'System.Collections.IEnumerator.MoveNext' which has a different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1073"' - while (e.MoveNext()) - { - //UPGRADE_TODO: Method 'java.util.Enumeration.nextElement' was converted to 'System.Collections.IEnumerator.Current' which has a different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1073"' - AudioDeviceFactory factory = (AudioDeviceFactory) e.Current; - fa[idx++] = factory; - } - } - } - return fa; - } - - } - private static FactoryRegistry instance = null; - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'systemRegistry'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - static public FactoryRegistry systemRegistry() - { - lock (typeof(javazoom.jl.player.FactoryRegistry)) - { - if (instance == null) - { - instance = new FactoryRegistry(); - instance.registerDefaultFactories(); - } - return instance; - } - } - - - //UPGRADE_NOTE: The initialization of 'factories' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - protected internal System.Collections.Hashtable factories; - - /// Registers an AudioDeviceFactory instance - /// with this registry. - /// - public virtual void addFactory(AudioDeviceFactory factory) - { - SupportClass.PutElement(factories, factory.GetType(), factory); - } - - public virtual void removeFactoryType(System.Type cls) - { - SupportClass.HashtableRemove(factories, cls); - } - - public virtual void removeFactory(AudioDeviceFactory factory) - { - SupportClass.HashtableRemove(factories, factory.GetType()); - } - - public override AudioDevice createAudioDevice() - { - AudioDevice device = null; - AudioDeviceFactory[] factories = FactoriesPriority; - - if (factories == null) - throw new JavaLayerException(this + ": no factories registered"); - - JavaLayerException lastEx = null; - for (int i = 0; (device == null) && (i < factories.Length); i++) - { - try - { - device = factories[i].createAudioDevice(); - } - catch (JavaLayerException ex) - { - lastEx = ex; - } - } - - if (device == null && lastEx != null) - { - throw new JavaLayerException("Cannot create AudioDevice", lastEx); - } - - return device; - } - - - - protected internal virtual void registerDefaultFactories() - { - addFactory(new JavaSoundAudioDeviceFactory()); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDevice.cs b/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDevice.cs deleted file mode 100644 index 48f17e45b..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDevice.cs +++ /dev/null @@ -1,219 +0,0 @@ -/* -* 06/04/01 Too fast playback fixed. mdm@techie.com -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - using javax.sound.sampled; - /// The JavaSoundAudioDevice implements an audio - /// device by using the JavaSound API. - /// * - /// @since 0.0.8 - /// - /// Mat McGowan - /// - /// - public class JavaSoundAudioDevice:AudioDeviceBase - { - public JavaSoundAudioDevice() - { - InitBlock(); - } - private void InitBlock() - { - byteBuf = new sbyte[1024]; - } - virtual protected internal AudioFormat AudioFormat - { - get - { - if (fmt == null) - { - Decoder decoder = Decoder; - fmt = new AudioFormat(decoder.OutputFrequency, 16, decoder.OutputChannels, true, false); - } - return fmt; - } - - set - { - fmt = value; - } - - } - virtual protected internal DataLine.Info SourceLineInfo - { - get - { - AudioFormat fmt = AudioFormat; - DataLine.Info info = new DataLine.Info(typeof(SourceDataLine), fmt, 4000); - return info; - } - - } - override public int Position - { - get - { - int pos = 0; - if (source != null) - { - pos = (int) (source.MicrosecondPosition / 1000); - } - return pos; - } - - } - private SourceDataLine source = null; - - private AudioFormat fmt = null; - - //UPGRADE_NOTE: The initialization of 'byteBuf' was moved to method 'InitBlock'. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1005"' - private sbyte[] byteBuf; - - - - - public virtual void open(AudioFormat fmt) - { - if (!Open) - { - setAudioFormat(fmt); - openImpl(); - Open = true; - } - } - - protected internal override void openImpl() - { - } - - - // createSource fix. - protected internal virtual void createSource() - { - //UPGRADE_NOTE: Exception 'java.lang.Throwable' was converted to 'System.Exception' which has different behavior. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1100"' - System.Exception t = null; - try - { - Line line = AudioSystem.getLine(SourceLineInfo); - if (line is SourceDataLine) - { - source = (SourceDataLine) line; - source.open(fmt, millisecondsToBytes(fmt, 2000)); - /* - if (source.isControlSupported(FloatControl.Type.MASTER_GAIN)) - { - FloatControl c = (FloatControl)source.getControl(FloatControl.Type.MASTER_GAIN); - c.setValue(c.getMaximum()); - }*/ - source.start(); - } - } - catch (System.SystemException ex) - { - t = ex; - } - catch (System.ApplicationException ex) - { - t = ex; - } - catch (LineUnavailableException ex) - { - t = ex; - } - if (source == null) - throw new JavaLayerException("cannot obtain source audio line", t); - } - - public virtual int millisecondsToBytes(AudioFormat fmt, int time) - { - //UPGRADE_WARNING: Narrowing conversions may produce unexpected results in C#. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1042"' - return (int) (time * (fmt.SampleRate * fmt.Channels * fmt.SampleSizeInBits) / 8000.0); - } - - protected internal override void closeImpl() - { - if (source != null) - { - source.close(); - } - } - - protected internal override void writeImpl(short[] samples, int offs, int len) - { - if (source == null) - createSource(); - - sbyte[] b = toByteArray(samples, offs, len); - source.write(b, 0, len * 2); - } - - protected internal virtual sbyte[] getByteArray(int length) - { - if (byteBuf.Length < length) - { - byteBuf = new sbyte[length + 1024]; - } - return byteBuf; - } - - protected internal virtual sbyte[] toByteArray(short[] samples, int offs, int len) - { - sbyte[] b = getByteArray(len * 2); - int idx = 0; - short s; - while (len-- > 0) - { - s = samples[offs++]; - b[idx++] = (sbyte) s; - b[idx++] = (sbyte) (SupportClass.URShift(s, 8)); - } - return b; - } - - protected internal override void flushImpl() - { - if (source != null) - { - source.drain(); - } - } - - - /// Runs a short test by playing a short silent sound. - /// - public virtual void test() - { - try - { - open(new AudioFormat(22050, 16, 1, true, false)); - short[] data = new short[22050 / 10]; - write(data, 0, data.Length); - flush(); - close(); - } - catch (System.SystemException ex) - { - throw new JavaLayerException("Device test failed: " + ex); - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDeviceFactory.cs b/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDeviceFactory.cs deleted file mode 100644 index 86f3024d8..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/JavaSoundAudioDeviceFactory.cs +++ /dev/null @@ -1,87 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using JavaLayerException = javazoom.jl.decoder.JavaLayerException; - /// This class is responsible for creating instances of the - /// JavaSoundAudioDevice. The audio device implementation is loaded - /// and tested dynamically as not all systems will have support - /// for JavaSound, or they may have the incorrect version. - /// - - public class JavaSoundAudioDeviceFactory:AudioDeviceFactory - { - private bool tested = false; - - private const System.String DEVICE_CLASS_NAME = "javazoom.jl.player.JavaSoundAudioDevice"; - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'createAudioDevice'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - public override AudioDevice createAudioDevice() - { - lock (this) - { - if (!tested) - { - testAudioDevice(); - tested = true; - } - - try - { - return createAudioDeviceImpl(); - } - catch (System.Exception ex) - { - throw new JavaLayerException("unable to create JavaSound device: " + ex); - } - catch (System.ApplicationException ex) - { - throw new JavaLayerException("unable to create JavaSound device: " + ex); - } - } - } - - protected internal virtual JavaSoundAudioDevice createAudioDeviceImpl() - { - //UPGRADE_ISSUE: Class 'java.lang.ClassLoader' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javalangClassLoader"' - //UPGRADE_ISSUE: Method 'java.lang.Class.getClassLoader' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javalangClassgetClassLoader"' - ClassLoader loader = GetType().getClassLoader(); - try - { - JavaSoundAudioDevice dev = (JavaSoundAudioDevice) instantiate(loader, DEVICE_CLASS_NAME); - return dev; - } - catch (System.Exception ex) - { - throw new JavaLayerException("Cannot create JavaSound device", ex); - } - catch (System.ApplicationException ex) - { - throw new JavaLayerException("Cannot create JavaSound device", ex); - } - } - - public virtual void testAudioDevice() - { - JavaSoundAudioDevice dev = createAudioDeviceImpl(); - dev.test(); - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/NullAudioDevice.cs b/Other/libs/mp3sharp/mp3sharp/player/NullAudioDevice.cs deleted file mode 100644 index ff02b311a..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/NullAudioDevice.cs +++ /dev/null @@ -1,43 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - - /// The NullAudioDevice implements a silent, no-op - /// audio device. This is useful for testing purposes. - /// - /// @since 0.0.8 - /// - /// Mat McGowan - /// - /// - public class NullAudioDevice:AudioDeviceBase - { - override public int Position - { - get - { - return 0; - } - - } - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/Player.cs b/Other/libs/mp3sharp/mp3sharp/player/Player.cs deleted file mode 100644 index 56fab77e7..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/Player.cs +++ /dev/null @@ -1,251 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using javazoom.jl.decoder; - /// The Player class implements a simple player for playback - /// of an MPEG audio stream. - /// - /// - /// Mat McGowan - /// @since 0.0.8 - /// - /// - - // REVIEW: the audio device should not be opened until the - // first MPEG audio frame has been decoded. - public class Player - { - /// Returns the completed status of this player. - /// - /// - /// true if all available MPEG audio frames have been - /// decoded, or false otherwise. - /// - /// - //UPGRADE_NOTE: Synchronized keyword was removed from method 'isComplete'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - virtual public bool Complete - { - get - { - lock (this) - { - return complete; - } - } - - } - /// Retrieves the position in milliseconds of the current audio - /// sample being played. This method delegates to the - /// AudioDevice that is used by this player to sound - /// the decoded audio samples. - /// - virtual public int Position - { - get - { - int position = lastPosition; - - AudioDevice out_Renamed = audio; - if (out_Renamed != null) - { - position = out_Renamed.Position; - } - return position; - } - - } - /// The current frame number. - /// - private int frame = 0; - - /// The MPEG audio bitstream. - /// - // javac blank final bug. - /*final*/ private Bitstream bitstream; - - /// The MPEG audio decoder. - /// - /*final*/ private Decoder decoder; - - /// The AudioDevice the audio samples are written to. - /// - private AudioDevice audio; - - /// Has the player been closed? - /// - private bool closed = false; - - /// Has the player played back all frames from the stream? - /// - private bool complete = false; - - private int lastPosition = 0; - - /// Creates a new Player instance. - /// - public Player(System.IO.Stream stream):this(stream, null) - { - } - - public Player(System.IO.Stream stream, AudioDevice device) - { - bitstream = new Bitstream(stream); - decoder = new Decoder(); - - if (device != null) - { - audio = device; - } - else - { - FactoryRegistry r = FactoryRegistry.systemRegistry(); - audio = r.createAudioDevice(); - } - audio.open(decoder); - } - - public virtual void play() - { - play(System.Int32.MaxValue); - } - - /// Plays a number of MPEG audio frames. - /// - /// - /// number of frames to play. - /// - /// true if the last frame was played, or false if there are - /// more frames. - /// - /// - public virtual bool play(int frames) - { - bool ret = true; - - while (frames-- > 0 && ret) - { - ret = decodeFrame(); - } - - if (!ret) - { - // last frame, ensure all data flushed to the audio device. - AudioDevice out_Renamed = audio; - if (out_Renamed != null) - { - out_Renamed.flush(); - lock (this) - { - complete = (!closed); - close(); - } - } - } - return ret; - } - - //UPGRADE_NOTE: Synchronized keyword was removed from method 'close'. Lock expression was added. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1027"' - /// Cloases this player. Any audio currently playing is stopped - /// immediately. - /// - public virtual void close() - { - lock (this) - { - AudioDevice out_Renamed = audio; - if (out_Renamed != null) - { - closed = true; - audio = null; - // this may fail, so ensure object state is set up before - // calling this method. - out_Renamed.close(); - lastPosition = out_Renamed.Position; - try - { - bitstream.close(); - } - catch (BitstreamException ex) - { - } - } - } - } - - - - /// Decodes a single frame. - /// - /// - /// true if there are no more frames to decode, false otherwise. - /// - /// - protected internal virtual bool decodeFrame() - { - try - { - AudioDevice out_Renamed = audio; - if (out_Renamed == null) - return false; - - Header h = bitstream.readFrame(); - - if (h == null) - return false; - - // sample buffer set when decoder constructed - SampleBuffer output = (SampleBuffer) decoder.decodeFrame(h, bitstream); - - lock (this) - { - out_Renamed = audio; - if (out_Renamed != null) - { - out_Renamed.write(output.Buffer, 0, output.BufferLength); - } - } - - bitstream.closeFrame(); - } - catch (System.SystemException ex) - { - throw new JavaLayerException("Exception decoding audio frame", ex); - } - /* - catch (IOException ex) - { - System.out.println("exception decoding audio frame: "+ex); - return false; - } - catch (BitstreamException bitex) - { - System.out.println("exception decoding audio frame: "+bitex); - return false; - } - catch (DecoderException decex) - { - System.out.println("exception decoding audio frame: "+decex); - return false; - }*/ - return true; - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/PlayerApplet.cs b/Other/libs/mp3sharp/mp3sharp/player/PlayerApplet.cs deleted file mode 100644 index 513d2c5e6..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/PlayerApplet.cs +++ /dev/null @@ -1,308 +0,0 @@ -/* -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using System.Collections; - using System.ComponentModel; - using System.Drawing; - using System.Data; - using System.Windows.Forms; - using javazoom.jl.decoder; - /// A simple applet that plays an MPEG audio file. - /// The URL (relative to the document base) - /// is passed as the "audioURL" parameter. - /// - /// - /// Mat McGowan - /// @since 0.0.8 - /// - /// - public class PlayerApplet:System.Windows.Forms.UserControl, IThreadRunnable - { - public PlayerApplet() - { - init(); - } - /// Retrieves the AudioDevice instance that will - /// be used to sound the audio data. - /// - /// - /// an audio device instance that will be used to - /// sound the audio stream. - /// - /// - virtual protected internal AudioDevice AudioDevice - { - get - { - return FactoryRegistry.systemRegistry().createAudioDevice(); - } - - } - /// Retrieves the InputStream that provides the MPEG audio - /// stream data. - /// - /// - /// an InputStream from which the MPEG audio data - /// is read, or null if an error occurs. - /// - /// - virtual protected internal System.IO.Stream AudioStream - { - get - { - System.IO.Stream @in = null; - - try - { - System.Uri url = AudioURL; - if (url != null) - @in = System.Net.WebRequest.Create(url).GetResponse().GetResponseStream(); - } - catch (System.IO.IOException ex) - { - System.Console.Error.WriteLine(ex); - } - return @in; - } - - } - virtual protected internal System.String AudioFileName - { - get - { - System.String urlString = fileName; - if (urlString == null) - { - //UPGRADE_ISSUE: Method 'java.applet.Applet.getParameter' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javaappletAppletgetParameter_javalangString"' - //UPGRADE_TODO: Applet parameter was not converted because it requires a string literal as parameter name. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1167"' - urlString = getParameter(AUDIO_PARAMETER); - } - return urlString; - } - - } - virtual protected internal System.Uri AudioURL - { - get - { - System.String urlString = AudioFileName; - System.Uri url = null; - if (urlString != null) - { - try - { - //UPGRADE_TODO: Class 'java.net.URL' was converted to a 'System.Uri' which does not throw an exception if a URL specifies an unknown protocol. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1132"' - //UPGRADE_ISSUE: Method 'java.applet.Applet.getDocumentBase' was not converted. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1000_javaappletAppletgetDocumentBase"' - url = new System.Uri(getDocumentBase(), urlString); - } - catch (System.Exception ex) - { - System.Console.Error.WriteLine(ex); - } - } - return url; - } - - } - /// Sets the URL of the audio stream to play. - /// - virtual public System.String FileName - { - get - { - return fileName; - } - - set - { - fileName = value; - } - - } - private bool isActiveVar = true; - public bool isActive() - { - return isActiveVar; - } - private void javazoom.jl.player.PlayerApplet_StartEventHandler(System.Object sender, System.EventArgs e) - { - start(); - } - private void javazoom.jl.player.PlayerApplet_StopEventHandler(System.Object sender, System.EventArgs e) - { - stop(); - } - public String getParameter(System.String paramName) - { - switch (paramName) - { - - default: - return null; - - } - } - public const System.String AUDIO_PARAMETER = "audioURL"; - - /// The Player used to play the MPEG audio file. - /// - private Player player = null; - - /// The thread that runs the player. - /// - private SupportClass.ThreadClass playerThread = null; - - private System.String fileName = null; - - - - - - - - - /// Stops the audio player. If the player is already stopped - /// this method is a no-op. - /// - protected internal virtual void stopPlayer() - { - if (player != null) - { - player.close(); - player = null; - playerThread = null; - } - } - - /// Decompresses audio data from an InputStream and plays it - /// back through an AudioDevice. The playback is run on a newly - /// created thread. - /// - /// - /// InputStream that provides the MPEG audio data. - /// - /// AudioDevice to use to sound the decompressed data. - /// - /// @throws JavaLayerException if there was a problem decoding - /// or playing the audio data. - /// - /// - protected internal virtual void play(System.IO.Stream @in, AudioDevice dev) - { - stopPlayer(); - - if (@in != null && dev != null) - { - player = new Player(@in, dev); - playerThread = createPlayerThread(); - playerThread.Start(); - } - } - - /// Creates a new thread used to run the audio player. - /// - /// A new Thread that, once started, runs the audio player. - /// - /// - protected internal virtual SupportClass.ThreadClass createPlayerThread() - { - SupportClass.ThreadClass temp_Thread; - temp_Thread = new SupportClass.ThreadClass(new System.Threading.ThreadStart(this.Run)); - temp_Thread.Name = "Audio player thread"; - return temp_Thread; - } - - //UPGRADE_TODO: The equivalent of method 'java.applet.Applet.init' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - /// Initializes this applet. - /// - public void init() - { - this.BackColor = Color.LightGray; - this.Load += new System.EventHandler(this.javazoom.jl.player.PlayerApplet_StartEventHandler); - this.Disposed += new System.EventHandler(this.javazoom.jl.player.PlayerApplet_StopEventHandler); - } - - //UPGRADE_TODO: The equivalent of method 'java.applet.Applet.start' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - /// Starts this applet. An input stream and audio device - /// are created and passed to the play() method. - /// - public void start() - { - isActiveVar = true; - System.String name = AudioFileName; - try - { - System.IO.Stream @in = AudioStream; - AudioDevice dev = AudioDevice; - play(@in, dev); - } - catch (JavaLayerException ex) - { - lock (System.Console.Error) - { - System.Console.Error.WriteLine("Unable to play " + name); - SupportClass.WriteStackTrace(ex, System.Console.Error); - } - } - } - - //UPGRADE_TODO: The equivalent of method 'java.applet.Applet.stop' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - /// Stops this applet. If audio is currently playing, it is - /// stopped. - /// - public void stop() - { - try - { - stopPlayer(); - } - catch (JavaLayerException ex) - { - System.Console.Error.WriteLine(ex); - } - isActiveVar = false; - } - - //UPGRADE_TODO: This function is not marked as virtual in the base class. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca5000"' - public void Dispose() - { - } - - //UPGRADE_TODO: The equivalent of method 'java.lang.Runnable.run' is not an override method. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1143"' - /// The run method for the audio player thread. Simply calls - /// play() on the player to play the entire stream. - /// - public void Run() - { - if (player != null) - { - try - { - player.play(); - } - catch (JavaLayerException ex) - { - System.Console.Error.WriteLine("Problem playing audio: " + ex); - } - } - } - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/player/jlp.cs b/Other/libs/mp3sharp/mp3sharp/player/jlp.cs deleted file mode 100644 index c0bc2f728..000000000 --- a/Other/libs/mp3sharp/mp3sharp/player/jlp.cs +++ /dev/null @@ -1,177 +0,0 @@ -/* -* 06/04/01 Streaming support added. ebsp@iname.com -* 29/01/00 Initial version. mdm@techie.com -/*----------------------------------------------------------------------- -* This program is free software; you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation; either version 2 of the License, or -* (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program; if not, write to the Free Software -* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -*---------------------------------------------------------------------- -*/ -namespace javazoom.jl.player -{ - using System; - using JavaLayerException = javazoom.jl.decoder.JavaLayerException; - /// The jlp class implements a simple command-line - /// player for MPEG audio files. - /// * - /// - /// Mat McGowan (mdm@techie.com) - /// - /// - public class jlp - { - /// Playing file from URL (Streaming). - /// - virtual protected internal System.IO.Stream URLInputStream - { - get - { - - //UPGRADE_TODO: Class 'java.net.URL' was converted to a 'System.Uri' which does not throw an exception if a URL specifies an unknown protocol. 'ms-help://MS.VSCC.2003/commoner/redir/redirect.htm?keyword="jlca1132"' - System.Uri url = new System.Uri(fFilename); - System.IO.Stream fin = System.Net.WebRequest.Create(url).GetResponse().GetResponseStream(); - System.IO.BufferedStream bin = new System.IO.BufferedStream(fin); - return bin; - } - - } - /// Playing file from FileInputStream. - /// - virtual protected internal System.IO.Stream InputStream - { - get - { - System.IO.FileStream fin = new System.IO.FileStream(fFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read); - System.IO.BufferedStream bin = new System.IO.BufferedStream(fin); - return bin; - } - - } - virtual protected internal AudioDevice AudioDevice - { - get - { - return FactoryRegistry.systemRegistry().createAudioDevice(); - } - - } - private System.String fFilename = null; - private bool remote = false; - - [STAThread] - public static void Main(System.String[] args) - { - int retval = 0; - try - { - jlp player = createInstance(args); - if (player != null) - player.play(); - } - catch (System.Exception ex) - { - System.Console.Error.WriteLine(ex); - SupportClass.WriteStackTrace(ex, System.Console.Error); - retval = 1; - } - System.Environment.Exit(retval); - } - - static public jlp createInstance(System.String[] args) - { - jlp player = new jlp(); - if (!player.parseArgs(args)) - player = null; - return player; - } - - private jlp() - { - } - - public jlp(System.String filename) - { - init(filename); - } - - protected internal virtual void init(System.String filename) - { - fFilename = filename; - } - - protected internal virtual bool parseArgs(System.String[] args) - { - bool parsed = false; - if (args.Length == 1) - { - init(args[0]); - parsed = true; - remote = false; - } - else if (args.Length == 2) - { - if (!(args[0].Equals("-url"))) - { - showUsage(); - } - else - { - init(args[1]); - parsed = true; - remote = true; - } - } - else - { - showUsage(); - } - return parsed; - } - - public virtual void showUsage() - { - System.Console.Out.WriteLine("Usage: jlp [-url] "); - System.Console.Out.WriteLine(""); - System.Console.Out.WriteLine(" e.g. : java javazoom.jl.player.jlp localfile.mp3"); - System.Console.Out.WriteLine(" java javazoom.jl.player.jlp -url http://www.server.com/remotefile.mp3"); - System.Console.Out.WriteLine(" java javazoom.jl.player.jlp -url http://www.shoutcastserver.com:8000"); - } - - public virtual void play() - { - try - { - System.Console.Out.WriteLine("playing " + fFilename + "..."); - System.IO.Stream in_Renamed = null; - if (remote == true) - in_Renamed = URLInputStream; - else - in_Renamed = InputStream; - AudioDevice dev = AudioDevice; - Player player = new Player(in_Renamed, dev); - player.play(); - } - catch (System.IO.IOException ex) - { - throw new JavaLayerException("Problem playing file " + fFilename, ex); - } - catch (System.Exception ex) - { - throw new JavaLayerException("Problem playing file " + fFilename, ex); - } - } - - - - } -} \ No newline at end of file diff --git a/Other/libs/mp3sharp/mp3sharp/readme.txt b/Other/libs/mp3sharp/mp3sharp/readme.txt deleted file mode 100644 index 4eaa79f66..000000000 --- a/Other/libs/mp3sharp/mp3sharp/readme.txt +++ /dev/null @@ -1,47 +0,0 @@ -MP3Sharp: JavaLayer C# Port -Robert Burke, 25 Feb 04 -rob@mle.ie - -Right now it's lacking polish, but here's a C# port of JavaLayer, -an MP3 decoder for Java written by the JavaZoom team. Hopefully -it will be useful to other people who want to decode Mp3s in -native C#. I've tested it with a variety of MP3s (Constant and -Variable Bitrate, Stereo and Mono, etc. etc.) and - props to the -JavaZoom team! - it seems to do the trick. - -There's some sample code in the enclosed (VS.NET2003) Solution that -should hopefully set you on the right path. - -I used Beta2 of the Java Language Conversion Assistant as a starting -point for this project, and spent the rest of the day cleaning up -after it. There were some bizarre bit-shifting bugs introduced by -the JLCA that I corrected. I also removed JavaZoom's dependency on -serialized files. - -Honestly, this was a half-day project, so please forgive me for the -state of the code. I came back to this a year on and spent another -half-day writing the System.IO.Stream-derived interface to it, -and an example of streaming MP3 audio using Managed DirectSound. - -But I welcome comments, requests, suggestions and contributions: -rob@mle.ie - --------- - -Update 1 Sep 04 -rob@mle.ie - -Version 1.4 released. With kind thanks to tedHedd (tekhedd@byteheaven.net) -the code is now significantly optimized. I cleaned up the interface a -little and so now if you just use the Mp3Sharp DLL it should get the job done. - - --------- - - -Quickstart: - -See Sample.cs. Altough this assembly exposes a bunch of classes, -the only one you really want to use is Mp3Sharp.Mp3Stream. - - diff --git a/Other/tools/FSO.Packager/CoreImageLoader.cs b/Other/tools/FSO.Packager/CoreImageLoader.cs new file mode 100644 index 000000000..86d39f607 --- /dev/null +++ b/Other/tools/FSO.Packager/CoreImageLoader.cs @@ -0,0 +1,45 @@ +using FSO.Content.Model; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace FSO.Server.Core +{ + public class CoreImageLoader + { + public static TexBitmap SoftImageFetch(Stream stream, AbstractTextureRef texRef) + { + Image result = null; + try + { + result = Image.Load(stream); + } + catch (Exception) + { + return new TexBitmap() { Data = new byte[0] }; + } + stream.Close(); + + if (result == null) return null; + + // Get pixel data + var data = new byte[result.Width * result.Height * 4]; + result.CopyPixelDataTo(data); + + // Swap red and blue channels + for (int i = 0; i < data.Length; i += 4) + { + var temp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = temp; + } + + return new TexBitmap + { + Data = data, + Width = result.Width, + Height = result.Height, + PixelSize = 4 + }; + } + } +} diff --git a/Other/tools/FSO.Packager/FSO.Packager.csproj b/Other/tools/FSO.Packager/FSO.Packager.csproj new file mode 100644 index 000000000..604bcd70c --- /dev/null +++ b/Other/tools/FSO.Packager/FSO.Packager.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + + diff --git a/Other/tools/FSO.Packager/FSO.Packager.slnx b/Other/tools/FSO.Packager/FSO.Packager.slnx new file mode 100644 index 000000000..345cb4814 --- /dev/null +++ b/Other/tools/FSO.Packager/FSO.Packager.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/Other/tools/FSO.Packager/ITool.cs b/Other/tools/FSO.Packager/ITool.cs new file mode 100644 index 000000000..3cbb25347 --- /dev/null +++ b/Other/tools/FSO.Packager/ITool.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Packager +{ + internal interface ITool + { + int Run(); + } +} diff --git a/Other/tools/FSO.Packager/Program.cs b/Other/tools/FSO.Packager/Program.cs new file mode 100644 index 000000000..315ffc6bd --- /dev/null +++ b/Other/tools/FSO.Packager/Program.cs @@ -0,0 +1,68 @@ +using CommandLine; +using FSO.Server.Core; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; + +namespace FSO.Packager +{ + internal class Program + { + public static Tuple BitmapReader(Stream str) + { + using var image = Image.Load(str); + int width = image.Width; + int height = image.Height; + + var data = new byte[width * height * 4]; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int i = (y * width + x) * 4; + Rgba32 px = image[x, y]; + data[i] = px.R; + data[i + 1] = px.G; + data[i + 2] = px.B; + data[i + 3] = px.A; + } + } + + return new Tuple(data, width, height); + } + + static void Main(string[] args) + { + Content.Model.AbstractTextureRef.ImageFetchFunction = CoreImageLoader.SoftImageFetch; + FSO.Files.ImageLoaderHelpers.BitmapFunction = BitmapReader; + + ITool? tool = null; + + int result = Parser.Default.ParseArguments(args) + .MapResult( + (PackageRemeshesOptions opts) => + { + tool = new ToolPackageRemeshes(opts); + return 0; + }, + (ReleaseRemeshesOptions opts) => + { + tool = new ToolReleaseRemeshes(opts); + return 0; + }, + (DummyOptions opts) => + { + return 0; + }, + errs => 1 + ); + + if (result == 1 || tool == null) + { + Environment.Exit(1); + } + + tool.Run(); + } + } +} diff --git a/Other/tools/FSO.Packager/ProgramOptions.cs b/Other/tools/FSO.Packager/ProgramOptions.cs new file mode 100644 index 000000000..0824230f0 --- /dev/null +++ b/Other/tools/FSO.Packager/ProgramOptions.cs @@ -0,0 +1,35 @@ +using CommandLine; + +namespace FSO.Packager +{ + [Verb("package-remeshes", HelpText = "Package remeshes in the FSO.Remeshes format")] + public class PackageRemeshesOptions + { + [Value(0, Required = true, MetaName = "Source Directory")] + public required string SourceDirectory { get; set; } + + [Option('l', "legacy", Default = false, HelpText = "Generate legacy packages")] + public bool Legacy { get; set; } + + [Option('g', "games", Default = "freeso,simitone", HelpText = "Specify games to generate packages for, comma separated")] + public required string Games { get; set; } + + [Option('o', "out", Default = "dist/", HelpText = "Directory to output packages to")] + public required string OutDirectory { get; set; } + } + + [Verb("release-remeshes", HelpText = "Weites version information to remesh packages, and generates manifest json that can be used with the FreeSO updater")] + public class ReleaseRemeshesOptions + { + [Value(0, Required = true, MetaName = "Source Directory")] + public required string SourceDirectory { get; set; } + + [Option('g', "games", Default = "freeso,simitone", HelpText = "Specify games to generate packages for, comma separated")] + public required string Games { get; set; } + } + + [Verb("dummy", HelpText = "Verb that does nothing")] + public class DummyOptions + { + } +} diff --git a/Other/tools/FSO.Packager/ToolPackageRemeshes.cs b/Other/tools/FSO.Packager/ToolPackageRemeshes.cs new file mode 100644 index 000000000..ee128427d --- /dev/null +++ b/Other/tools/FSO.Packager/ToolPackageRemeshes.cs @@ -0,0 +1,596 @@ +using FSO.Common.Utils; +using FSO.Common.WorldGeometry.Paths; +using FSO.Files; +using FSO.Files.Formats.DBPF; +using FSO.Files.Formats.IFF.Chunks; +using FSO.Files.RC; +using Newtonsoft.Json; +using System.IO.Compression; +using System.Runtime.InteropServices; + +namespace FSO.Packager +{ + internal class GamePackager + { + private class AuthorMetadataJson + { + [JsonProperty("name")] + public required string Name { get; set; } + + [JsonProperty("thread")] + public string? Thread { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + } + + private class GroupMetadataJson + { + [JsonProperty("name")] + public required string Name { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("game")] + public required string Game { get; set; } = "freeso,simitone"; + + [JsonProperty("priority")] + public required int Priority { get; set; } = 0; + } + + private class RemeshAliasSplitJson + { + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("to")] + public required string To { get; set; } + + [JsonProperty("dgrpFrom")] + public required int DgrpFrom { get; set; } + + [JsonProperty("dgrpTo")] + public required int DgrpTo { get; set; } + + [JsonProperty("range")] + public required int Range { get; set; } + } + + private class RemeshAliasJson + { + [JsonProperty("from")] + public required string From { get; set; } + + [JsonProperty("to")] + public string? To { get; set; } + + [JsonProperty("split")] + public RemeshAliasSplitJson[]? Split { get; set; } + } + + private class PackageMetadataJson + { + [JsonProperty("name")] + public required string Name { get; set; } + + [JsonProperty("id")] + public required string ID { get; set; } + + [JsonProperty("description")] + public string? Description { get; set; } + + [JsonProperty("url")] + public string? Url { get; set; } + + [JsonProperty("alias")] + public Dictionary? Alias { get; set; } + } + + private readonly PackageRemeshesOptions Options; + private readonly string Game; + + private readonly DBPFFile CompressedPackage; + private readonly DBPFFile UncompressedPackage; + private readonly DBPFFile CreditsPackage; + + private readonly Dictionary CompressedIDs; + private readonly Dictionary UncompressedIDs; + + private readonly FSO3DDirectory DirectoryChunk; + private readonly FSO3DCredits Credits; + + private ZipArchive? LegacyPackage; + + private Dictionary Aliases = []; + + public GamePackager(PackageRemeshesOptions options, string game) + { + Options = options; + Game = game; + + CompressedPackage = new DBPFFile(); + UncompressedPackage = new DBPFFile(); + CreditsPackage = new DBPFFile(); + + CompressedIDs = []; + UncompressedIDs = []; + + DirectoryChunk = new FSO3DDirectory() + { + Entries = [] + }; + + Credits = new FSO3DCredits() + { + Authors = [] + }; + } + + private void AddFile(DBPFFile file, uint id, DBPFTypeID type, Action fileWriter) + { + byte[] data; + + using (var mem = new MemoryStream()) + { + fileWriter(mem); + + data = mem.ToArray(); + } + + // Add an entry to the file. + file.AddOrReplace(((ulong)id << 32) | (ulong)type, DBPFGroupID.RemeshPackage, data); + } + + private uint AddFile(DBPFFile file, Dictionary lastIds, DBPFTypeID type, Action fileWriter) + { + // Get a free ID for the file + if (!lastIds.TryGetValue((uint)type, out uint lastId)) + { + lastId = uint.MaxValue; + } + + uint id = lastId + 1; + lastIds[(uint)type] = id; + + AddFile(file, id, type, fileWriter); + + return id; + } + + private void Warning(string message) + { + Console.WriteLine($" WARNING: {message}"); + } + + private void CompressTextureTo(string srcPath, Stream dstStream) + { + // Load image from path + + var data = ImageLoader.DataFromStream(null, File.OpenRead(srcPath)).Value.Data.Value; + + if ((data.Width % 4) != 0 || (data.Height % 4) != 0) + { + Warning($"{Path.GetFileName(srcPath)} does not align to the 4x4 block size and will force runtime UV scaling"); + } + + // If it has alpha, compress to DXT5 + // if not, compress to DXT1 + + var colorData = data.Data; + bool hasAlpha = colorData.Any(col => col.A != 255); + + var mtex2 = new MTX2() + { + Width = data.Width, + Height = data.Height, + Compression = MTX2CompressionType.GZip, + }; + + if (hasAlpha) + { + var dxt5Data = TextureUtils.GenerateDXT5WithMips(data.Width, data.Height, colorData); + mtex2.SetData(MTX2Format.DXT5, dxt5Data); + } + else + { + var dxt1Data = TextureUtils.GenerateDXT1WithMips(data.Width, data.Height, colorData); + mtex2.SetData(MTX2Format.DXT1, dxt1Data); + } + + mtex2.Write(null, dstStream); + } + + private int DirectoryID = 0; + + private FSO3DDirectoryEntry GetDirectoryEntry(string name) + { + if (!DirectoryChunk.Entries.TryGetValue(name, out var entry)) + { + entry = new FSO3DDirectoryEntry() + { + ID = (DirectoryID++), + Filename = name, + Meshes = [], + Textures = [], + }; + + DirectoryChunk.Entries[name] = entry; + } + + return entry; + } + + private T ReadMetadata(string path, string typeName) + { + try + { + var metadataJSON = File.ReadAllText(path); + + return JsonConvert.DeserializeObject(metadataJSON) ?? throw new Exception("Metadata cannot be null"); + } + catch (Exception e) + { + Console.WriteLine($"Failed to read metadata JSON for {typeName} {Path.GetFileName(Path.GetDirectoryName(path))}. Invalid or missing?"); + + throw; + } + } + + private FSO3DCreditsGroup? ProcessGroup(string dir) + { + var metadata = ReadMetadata(Path.Join(dir, "metadata.json"), "group"); + + var games = metadata.Game ?? "freeso,simitone"; + var gameList = games.Split(','); + + if (!gameList.Contains(Game)) + { + // This remesh isn't for this game. + return null; + } + + var credits = new FSO3DCreditsGroup() + { + Metadata = new FSO3DGroupMetadata() + { + Name = metadata.Name, + Description = metadata.Description ?? "" + }, + Files = [] + }; + + var files = Directory.GetFiles(dir); + + foreach (var file in files) + { + bool isMesh = Path.GetExtension(file) == ".fsom"; + bool isPng = !isMesh && Path.GetExtension(file) == ".png"; + if (isMesh || isPng) + { + var cType = isPng ? DBPFTypeID.MTX2 : DBPFTypeID.FSOM; + var uType = isPng ? DBPFTypeID.MTEX : DBPFTypeID.FSOM; + + // Try parse the id at the end of the filename. + string name = Path.GetFileNameWithoutExtension(file); + int lastUnderscore = name.LastIndexOf('_'); + + if (lastUnderscore == -1 || !ushort.TryParse(name.AsSpan(lastUnderscore + 1), out ushort chunkId)) + { + throw new InvalidDataException($"Remesh file {file} doesn't have a valid format (expected resource id after underscore)."); + } + + string directoryName = name[..lastUnderscore]; + + if (isPng) + { + if (!directoryName.EndsWith("_TEX")) + { + throw new InvalidDataException($"Remesh texture {file} must have TEX_ before the texture ID."); + } + + directoryName = directoryName[..^4]; + } + + directoryName = directoryName.ToLowerInvariant(); + + string legacyName = Path.GetFileName(file); + + if (Aliases.TryGetValue(directoryName, out var alias)) + { + if (alias.To != null) + { + directoryName = alias.To; + + legacyName = alias.To + legacyName.Substring(alias.From.Length); + } + else if (alias.Split != null) + { + foreach (var split in alias.Split) + { + if (chunkId >= split.DgrpFrom && chunkId < split.DgrpFrom + split.Range) + { + directoryName = split.To; + + if (isMesh) + { + chunkId = (ushort)((chunkId - split.DgrpFrom) + split.DgrpTo); + legacyName = $"{directoryName}_{chunkId}.fsom"; + } + else + { + legacyName = $"{directoryName}_TEX_{chunkId}.png"; + } + } + } + } + } + + // Add resource + + uint fileId = AddFile(CompressedPackage, CompressedIDs, cType, (stream) => + { + if (cType == DBPFTypeID.FSOM) + { + using var fileStream = File.OpenRead(file); + fileStream.CopyTo(stream); + } + else + { + CompressTextureTo(file, stream); + } + }); + + // We expect this ID to be the same... + uint fileId2 = AddFile(UncompressedPackage, UncompressedIDs, cType, (stream) => + { + using var fileStream = File.OpenRead(file); + fileStream.CopyTo(stream); + }); + + var entry = GetDirectoryEntry(directoryName); + var ref3d = new FSO3DRef(chunkId, fileId, (uint)cType); + + if (isPng) + { + entry.Textures[chunkId] = ref3d; + } + else + { + entry.Meshes[chunkId] = ref3d; + } + + credits.Files.Add(ref3d); + + LegacyPackage?.CreateEntryFromFile(file, legacyName, CompressionLevel.SmallestSize); + } + } + + return credits; + } + + private void ProcessContributor(string dir) + { + var metadata = ReadMetadata(Path.Join(dir, "metadata.json"), "author"); + + var author = new FSO3DCreditsAuthor() + { + Metadata = new FSO3DAuthorMetadata() + { + Name = metadata.Name, + Description = metadata.Description ?? "" + }, + Groups = [] + }; + + Credits.Authors.Add(author); + + var groupDirs = Directory.GetDirectories(dir); + + foreach (var groupDir in groupDirs) + { + var group = ProcessGroup(groupDir); + + if (group != null) + { + author.Groups.Add(group); + } + } + } + + private void AddDirectoryChunk(DBPFFile file) + { + AddFile(file, 0, DBPFTypeID.FSO3DDirectory, (stream) => DirectoryChunk.Write(stream)); + } + + private void AddCreditsChunk(DBPFFile file) + { + AddFile(file, 0, DBPFTypeID.FSO3DCredits, (stream) => Credits.Write(stream)); + } + + private FSO3DRef ReplaceType(FSO3DRef item, DBPFTypeID from, DBPFTypeID to) + { + if ((DBPFTypeID)item.TypeID == from) + { + return new FSO3DRef(item.ID, item.FileID, (uint)to); + } + + return item; + } + + private void ReplaceTypes(DBPFTypeID from, DBPFTypeID to) + { + // In directory + foreach (var entry in DirectoryChunk.Entries.Values) + { + foreach (var key in entry.Meshes.Keys) + { + entry.Meshes[key] = ReplaceType(entry.Meshes[key], from, to); + } + + foreach (var key in entry.Textures.Keys) + { + entry.Textures[key] = ReplaceType(entry.Textures[key], from, to); + } + } + + foreach (var author in Credits.Authors) + { + foreach (var group in author.Groups) + { + var files = CollectionsMarshal.AsSpan(group.Files); + for (int i = 0; i < files.Length; i++) + { + ref var item = ref files[i]; + + item = ReplaceType(item, from, to); + } + } + } + } + + public int Run() + { + Console.WriteLine($"Packaging remeshes for game '{Game}'."); + + if (Options.Legacy) + { + var path = Path.Combine(Options.OutDirectory, $"{Game}-remeshes-legacy.zip"); + if (File.Exists(path)) + { + File.Delete(path); + } + + LegacyPackage = ZipFile.Open(path, ZipArchiveMode.Create); + } + + var metadata = ReadMetadata(Path.Join(Options.SourceDirectory, "metadata.json"), "root package"); + + Credits.Metadata = new FSO3DPackageMetadata() + { + Name = metadata.Name, + Description = metadata.Description ?? "", + Url = metadata.Url ?? "", + ID = metadata.ID, + }; + + if (metadata.Alias != null && metadata.Alias.TryGetValue(Game, out var aliases)) + { + if (aliases != null) + { + foreach (var alias in aliases) + { + Aliases[alias.From] = alias; + } + } + } + + // Rough DBPF File structure + + // Multiple items + // FSOM: Remesh models + // FTEX: Remesh PNG textures + // FTX2: Remesh compressed textures + + // Single item (id 0) + // FSO3DDirectory Remesh Directory (list of entries): + // - UID (for ref from credits) + // - Target filename (eg. chairconnectingtheater_iff) + // - List of FSOM + // - (dgrp num, FSOM id) + // - List of FTEX + // - (tex num, FTEX/FTX2 id, type id) + // FSO3DCredits Credits + // - Root metadata + // - List of strings + // - List of remeshers + // - Remesher metadata + // - List of mesh packages + // - Package metadata + // - List of entries in the Remesh Directory (UID, fsom #, tex #) + + // Scan the remeshes + Console.WriteLine($" - Adding files..."); + + var contributorDirs = Directory.GetDirectories(Options.SourceDirectory); + foreach (var contributorDir in contributorDirs) + { + ProcessContributor(contributorDir); + } + + Console.WriteLine($" - Finalizing packages..."); + + AddDirectoryChunk(CompressedPackage); + + Credits.Metadata.Format = FSO3DPackageTextureFormat.Dxt; + AddCreditsChunk(CompressedPackage); + + Credits.Metadata.Format = FSO3DPackageTextureFormat.Credits; + AddCreditsChunk(CreditsPackage); + + // For the uncompressed package, rewrite the texture types to all use MTEX + ReplaceTypes(DBPFTypeID.MTX2, DBPFTypeID.MTEX); + AddDirectoryChunk(UncompressedPackage); + Credits.Metadata.Format = FSO3DPackageTextureFormat.Png; + AddCreditsChunk(UncompressedPackage); + + Console.WriteLine($" - Writing packages..."); + + using (var file = File.Open(Path.Combine(Options.OutDirectory, $"{Game}-remeshes-dxt.dat"), FileMode.Create)) + { + CompressedPackage.Write(file); + } + + using (var file = File.Open(Path.Combine(Options.OutDirectory, $"{Game}-remeshes-png.dat"), FileMode.Create)) + { + UncompressedPackage.Write(file); + } + + using (var file = File.Open(Path.Combine(Options.OutDirectory, $"{Game}-remeshes-credits.dat"), FileMode.Create)) + { + CreditsPackage.Write(file); + } + + Console.WriteLine($" - Done!"); + + LegacyPackage?.Dispose(); + + return 0; + } + } + + internal class ToolPackageRemeshes : ITool + { + + private readonly PackageRemeshesOptions Options; + + public ToolPackageRemeshes(PackageRemeshesOptions opts) + { + Options = opts; + } + + private void GeneratePackage(string game) + { + var packager = new GamePackager(Options, game); + packager.Run(); + } + + public int Run() + { + var games = Options.Games.Split(','); + + foreach (var game in games) + { + GeneratePackage(game); + } + + return 0; + } + } +} diff --git a/Other/tools/FSO.Packager/ToolReleaseRemeshes.cs b/Other/tools/FSO.Packager/ToolReleaseRemeshes.cs new file mode 100644 index 000000000..80f3125d8 --- /dev/null +++ b/Other/tools/FSO.Packager/ToolReleaseRemeshes.cs @@ -0,0 +1,201 @@ +using FSO.Files.Formats.DBPF; +using FSO.Files.FSO; +using FSO.Files.RC; +using Newtonsoft.Json; +using System.Security.Cryptography; + +namespace FSO.Packager +{ + internal class ToolReleaseRemeshes : ITool + { + private readonly ReleaseRemeshesOptions Options; + private RSA? Crypto; + private string ReleaseAssetsBase = ""; + private FSORemeshChannel RemeshChannel = new(); + + + public ToolReleaseRemeshes(ReleaseRemeshesOptions opts) + { + Options = opts; + } + + private FSORemeshFile GetRemeshFile(string path, string url) + { + // Build a zip from the input directory. + + var data = File.ReadAllBytes(path); + var shaHash = SHA256.HashData(data); + + return new FSORemeshFile() + { + url = url, + size = data.Length, + hash = Convert.ToBase64String(shaHash), + signature = Crypto != null ? Convert.ToBase64String(Crypto.SignHash(shaHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) : "", + }; + } + + private static RSA TryGetCrypto(string privateKey) + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(privateKey.Replace('^', '\n')); + + return rsa; + } + catch (Exception) + { + return null; + } + } + + private static string GetFormatString(FSO3DPackageTextureFormat format) + { + return format switch + { + FSO3DPackageTextureFormat.Credits => "credits", + FSO3DPackageTextureFormat.Dxt => "dxt", + FSO3DPackageTextureFormat.Png => "png", + _ => throw new Exception("Unknown format") + }; + } + + private void AddToChannel(FSORemeshFile file, FSO3DPackageMetadata meta) + { + RemeshChannel.name = meta.Name; + RemeshChannel.description = meta.Description; + RemeshChannel.url = meta.Url; + + switch (meta.Format) + { + case FSO3DPackageTextureFormat.Dxt: + RemeshChannel.dxt = file; + break; + + case FSO3DPackageTextureFormat.Png: + RemeshChannel.png = file; + break; + } + } + + private void ProcessRemesh(string game, FSO3DPackageTextureFormat format) + { + string formatString = GetFormatString(format); + string filename = $"{game}-remeshes-{formatString}.dat"; + + var path = Path.Combine(Options.SourceDirectory, filename); + + if (File.Exists(path)) + { + var dbpf = new DBPFFile(path); + + var creditsData = dbpf.GetItemByID(DBPFTypeID.FSO3DCredits, 0); + + var credits = new FSO3DCredits(); + using var creditsStream = new MemoryStream(creditsData); + credits.Read(creditsStream); + + // Add version related fields + var meta = credits.Metadata; + meta.Format = format; + meta.ChannelName = RemeshChannel.channel ?? ""; + meta.Version = RemeshChannel.version; + meta.PublicKey = RemeshChannel.publicKey ?? ""; + + using var creditsOut = new MemoryStream(); + credits.Write(creditsOut); + + dbpf.AddOrReplace(0, DBPFTypeID.FSO3DCredits, DBPFGroupID.RemeshPackage, creditsOut.ToArray()); + + // Save the modified file. + using (var mem = new MemoryStream()) + { + dbpf.Write(mem); + dbpf.Dispose(); + + File.WriteAllBytes(path, mem.ToArray()); + } + + if (format != FSO3DPackageTextureFormat.Credits) + { + // Generate metadata for the updater + + var metaObj = GetRemeshFile(path, ReleaseAssetsBase + filename); + + AddToChannel(metaObj, meta); + } + } + } + + private void ProcessGame(string game) + { + RemeshChannel = new() + { + publicKey = RemeshChannel.publicKey, + channel = RemeshChannel.channel, + version = RemeshChannel.version + }; + + Console.WriteLine($"Processing packages for {game}."); + ProcessRemesh(game, FSO3DPackageTextureFormat.Dxt); + ProcessRemesh(game, FSO3DPackageTextureFormat.Png); + ProcessRemesh(game, FSO3DPackageTextureFormat.Credits); + + // Output metadata for this game to the source directory + + string metaFilename = $"{game}-remeshes.json"; + var metaPath = Path.Combine(Options.SourceDirectory, metaFilename); + + var result = JsonConvert.SerializeObject(RemeshChannel); + File.WriteAllText(metaPath, result); + + Console.WriteLine($"Output version metadata for {game}!"); + } + + public int Run() + { + var games = Options.Games.Split(','); + + string versionString = Environment.GetEnvironmentVariable("FSO_REMESH_VERSION") ?? ""; + string assetsBaseString = Environment.GetEnvironmentVariable("FSO_REMESH_ASSETS_BASE") ?? "https://github.com/riperiperi/FSO.Remeshes/releases/download/"; + ReleaseAssetsBase = assetsBaseString + versionString + "/"; + + string publicKey = Environment.GetEnvironmentVariable("FSO_UPDATE_PUBLIC_KEY") ?? ""; + string privateKey = Environment.GetEnvironmentVariable("FSO_UPDATE_PRIVATE_KEY") ?? ""; + + RSA? crypto = null; + + if (publicKey.Length > 0 && privateKey.Length > 0) + { + crypto = TryGetCrypto(privateKey); + } + + Crypto = crypto; + + var versionSplit = versionString.Split('.', 2); + + if (!int.TryParse(versionSplit[1], out var version)) + { + Console.WriteLine($"Incorrect format for version. (got {versionString}, should be like prod.1)"); + return 1; + } + + RemeshChannel.publicKey = crypto != null ? publicKey : ""; + RemeshChannel.channel = versionSplit[0]; + RemeshChannel.version = version; + + Console.WriteLine(crypto == null ? "Packaging without signatures." : "Public/private key detected - update zips will be signed."); + + foreach (var game in games) + { + ProcessGame(game); + } + + Console.WriteLine("Done!"); + + return 0; + } + } +} diff --git a/Other/tools/FSO.UpdateBuilder/ConventionalCommits.cs b/Other/tools/FSO.UpdateBuilder/ConventionalCommits.cs new file mode 100644 index 000000000..4b4fbde72 --- /dev/null +++ b/Other/tools/FSO.UpdateBuilder/ConventionalCommits.cs @@ -0,0 +1,109 @@ +using LibGit2Sharp; +using System.Text; +using System.Text.RegularExpressions; + +namespace FSO.UpdateBuilder +{ + internal enum ConventionalCommitsBump + { + Patch = 0, + Minor, + Major + } + + internal static class ConventionalCommits + { + private static Regex ConventionalCommitsRegex = new Regex("^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: (([\\w .,!&/~()-])+)([\\s\\S]*)"); + + private static bool SkipType(string type) + { + switch (type) + { + case "build": + case "chore": + case "ci": + case "docs": + case "style": + case "test": + return true; + } + + return false; + } + + private static ConventionalCommitsBump TypeBump(string type, string breaking) + { + ConventionalCommitsBump bump = type switch + { + "feat" => ConventionalCommitsBump.Minor, + _ => ConventionalCommitsBump.Patch + }; + + if (breaking == "!") + { + bump += 1; + } + + return bump; + } + + public static void TestParse(string msg) + { + var results = ConventionalCommitsRegex.Match(msg); + } + + public static bool AddToChangelog(StringBuilder changelog, ref ConventionalCommitsBump bump, Commit commit) + { + var results = ConventionalCommitsRegex.Match(commit.Message); + + var lines = commit.Message.Split('\n'); + + if (results.Success) + { + var type = results.Groups[1].Value; + var scope = results.Groups[2].Value; + var breaking = results.Groups[3].Value; + var message = results.Groups[4].Value; + var description = results.Groups[6].Value; + + if (SkipType(type)) + { + return false; + } + + ConventionalCommitsBump newBump = TypeBump(type, breaking); + + if (newBump > bump) + { + bump = newBump; + } + } + else + { + // Not a conventional commit. Doesn't really do anything special. + // If it's similar to a merge commit, ignore it. + + if (lines[0].StartsWith("Merge branch ")) + { + return false; + } + } + + changelog.AppendLine($"- {lines[0]}"); + + for (int i = 1; i < lines.Length; i++) + { + var line = lines[i].Trim(); + + if (line.Length == 0) + { + continue; + } + + changelog.AppendLine($" {line}"); + } + + return true; + } + } +} diff --git a/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.csproj b/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.csproj new file mode 100644 index 000000000..3f1a99515 --- /dev/null +++ b/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.csproj @@ -0,0 +1,20 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + diff --git a/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.slnx b/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.slnx new file mode 100644 index 000000000..0ba1d273f --- /dev/null +++ b/Other/tools/FSO.UpdateBuilder/FSO.UpdateBuilder.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/Other/tools/FSO.UpdateBuilder/ParsedVersion.cs b/Other/tools/FSO.UpdateBuilder/ParsedVersion.cs new file mode 100644 index 000000000..b6f43e373 --- /dev/null +++ b/Other/tools/FSO.UpdateBuilder/ParsedVersion.cs @@ -0,0 +1,84 @@ +namespace FSO.UpdateBuilder +{ + struct ParsedVersion(int major, int minor, int patch, string suffix) + { + public readonly int Major = major; + public readonly int Minor = minor; + public readonly int Patch = patch; + public readonly string Suffix = suffix; + + public readonly ParsedVersion Next(int majorTarget, ConventionalCommitsBump bump) + { + if (majorTarget > Major) + { + return new ParsedVersion(majorTarget, 0, 0, Suffix); + } + else if (bump == ConventionalCommitsBump.Major) + { + return new ParsedVersion(Major + 1, 0, 0, Suffix); + } + else if (bump == ConventionalCommitsBump.Minor) + { + return new ParsedVersion(Major, Minor + 1, 0, Suffix); + } + else + { + return new ParsedVersion(Major, Minor, Patch + 1, Suffix); + } + } + + public readonly ParsedVersion WithSuffix(string suffix) + { + return new ParsedVersion(Major, Minor, Patch, suffix); + } + + public static ParsedVersion? Parse(string text) + { + // Format v1.2.3 or v1.2.3-suffix + + if (text[0] == 'v') + { + var dotSplit = text.Substring(1).Split('.'); + + if (dotSplit.Length == 3) + { + if (int.TryParse(dotSplit[0], out int major) && int.TryParse(dotSplit[1], out int minor)) + { + string patchString = dotSplit[2]; + int dashIndex = patchString.IndexOf('-'); + + int patch; + string suffix; + if (dashIndex == -1) + { + if (!int.TryParse(patchString, out patch)) + { + return null; + } + + suffix = ""; + } + else + { + if (!int.TryParse(patchString.Substring(0, dashIndex), out patch)) + { + return null; + } + + suffix = patchString.Substring(dashIndex + 1); + } + + return new ParsedVersion(major, minor, patch, suffix); + } + } + } + + return null; + } + + public readonly override string ToString() + { + return Suffix.Length > 0 ? $"v{Major}.{Minor}.{Patch}-{Suffix}" : $"v{Major}.{Minor}.{Patch}"; + } + } +} diff --git a/Other/tools/FSO.UpdateBuilder/Program.cs b/Other/tools/FSO.UpdateBuilder/Program.cs new file mode 100644 index 000000000..443b191c5 --- /dev/null +++ b/Other/tools/FSO.UpdateBuilder/Program.cs @@ -0,0 +1,562 @@ +using FSO.Common; +using FSO.Files.FSO; +using FSO.Files.Utils; +using Newtonsoft.Json; +using Octokit; +using System.Diagnostics; +using System.IO.Compression; +using System.Net.Http.Json; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; + +namespace FSO.UpdateBuilder +{ + internal class DeltaJson + { + public FileDiff[] Diffs { get; set; } = []; + } + + internal class Program + { + private static Regex AssetUrlUntagged = new Regex("/untagged-[0-9a-f]+/"); + + private static LibGit2Sharp.Commit? GetReleaseCommit(Release lastRelease, LibGit2Sharp.Repository gitRepo) + { + var lastTag = gitRepo.Tags.FirstOrDefault(tag => tag.FriendlyName == lastRelease.TagName); + + if (lastTag != null && lastTag.PeeledTarget is LibGit2Sharp.Commit commit) + { + return commit; + } + + return null; + } + + private static async Task DownloadLastBuild(HttpClient http, FSOUpdateFile file, string workingDirectory, string platform, string[] targets, Func correctAssetUrl) + { + if (!targets.Contains(platform) || file == null) + { + return false; + } + + string targetDirectory = Path.Combine(workingDirectory, $"{platform}-old"); + + var url = correctAssetUrl(file.zip); + try + { + Console.WriteLine($"Trying to download old {platform} version from {url}"); + var fileRequest = await http.GetAsync(url); + + if (!fileRequest.IsSuccessStatusCode) + { + return false; + } + + using var zipStream = await fileRequest.Content.ReadAsStreamAsync(); + + ZipFile.ExtractToDirectory(zipStream, targetDirectory); + + Console.WriteLine($"Downloaded old {platform} version."); + return true; + } + catch (Exception e) + { + Console.WriteLine($"Download failed: {url}"); + return false; + } + } + + private static async Task FolderToZip(GitHubClient client, Release release, string target, string versionString, string zipQualifier, string directory, RSA? crypto) + { + // Build a zip from the input directory. + + using var mem = new MemoryStream(); + ZipFile.CreateFromDirectory(directory, mem, CompressionLevel.Optimal, false); + + mem.Position = 0; + + var asset = await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload() + { + FileName = $"{zipQualifier}-{target}-{versionString}.zip", + ContentType = "application/zip", + RawData = new MemoryStream(mem.ToArray()), + }); + + var hash = SHA256.Create(); + + mem.Position = 0; + var shaHash = SHA256.HashData(mem); + + return new FSOUpdateFile() + { + zip = FixAssetUrl(asset.BrowserDownloadUrl, versionString), + size = (int)mem.Length, + hash = Convert.ToBase64String(shaHash), + signature = crypto != null ? Convert.ToBase64String(crypto.SignHash(shaHash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) : "", + }; + } + + private static RSA TryGetCrypto(string privateKey) + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(privateKey.Replace('^', '\n')); + + return rsa; + } + catch (Exception) + { + return null; + } + } + + private static string FixAssetUrl(string url, string version) + { + return AssetUrlUntagged.Replace(url, $"/{version}/"); + } + + private static void ExecuteZshScript(string command) + { + var info = new ProcessStartInfo(); + info.FileName = "/bin/zsh"; + info.Arguments = command; + info.UseShellExecute = false; + info.CreateNoWindow = true; + + using var process = Process.Start(info); + + process.WaitForExit(); + } + + static async Task Main(string[] args) + { + if (args[0] == "--windowsMsi") + { + await WindowsMsiPackager(); + + return; + } + + string workingDirectory = args[0] ?? "./"; + Console.WriteLine($"FreeSO Update Packager (working directory: {workingDirectory})"); + Console.WriteLine("=============================================================="); + Console.WriteLine(""); + + Console.WriteLine("Initializing GitHub Client"); + var client = new GitHubClient(new ProductHeaderValue("freeso-ci")); + var rawToken = Environment.GetEnvironmentVariable("GH_TOKEN"); + var tokenAuth = new Octokit.Credentials(rawToken); + client.Credentials = tokenAuth; + + string repoString = Environment.GetEnvironmentVariable("FSO_UPDATE_GITHUB_REPO") ?? "riperiperi/FreeSO"; + string[] splitRepo = repoString.Split('/'); + string authorName = splitRepo[0]; + string repoName = splitRepo[1]; + string primaryBranchName = Environment.GetEnvironmentVariable("FSO_UPDATE_PRIMARY_BRANCH") ?? "master"; + + string releaseChannel = Environment.GetEnvironmentVariable("FSO_UPDATE_RELEASE_CHANNEL") ?? "FreeSO Archive"; + string releaseSuffix = Environment.GetEnvironmentVariable("FSO_UPDATE_RELEASE_SUFFIX") ?? ""; + string prereleaseChannel = Environment.GetEnvironmentVariable("FSO_UPDATE_PRERELEASE_CHANNEL") ?? "FreeSO Archive Beta"; + string prereleaseSuffix = Environment.GetEnvironmentVariable("FSO_UPDATE_PRERELEASE_SUFFIX") ?? "beta"; + string channelUrl = Environment.GetEnvironmentVariable("FSO_UPDATE_CHANNEL_URL") ?? ""; + + string initialVersion = Environment.GetEnvironmentVariable("FSO_UPDATE_INITIAL_VERSION") ?? "v0.1.0"; + string targetsString = Environment.GetEnvironmentVariable("FSO_UPDATE_TARGETS") ?? "windows"; + + string publicKey = Environment.GetEnvironmentVariable("FSO_UPDATE_PUBLIC_KEY") ?? ""; + string privateKey = Environment.GetEnvironmentVariable("FSO_UPDATE_PRIVATE_KEY") ?? ""; + + RSA? crypto = null; + + if (publicKey.Length > 0 && privateKey.Length > 0) + { + crypto = TryGetCrypto(privateKey); + } + + Console.WriteLine(crypto == null ? "Packaging without signatures." : "Public/private key detected - update zips will be signed."); + + string[] targets = targetsString.Split(','); + + var baseVersion = ParsedVersion.Parse(initialVersion); + + if (baseVersion == null) + { + Console.WriteLine("FSO_UPDATE_INITIAL_VERSION isn't in the right format. (should be similar to v1.2.3)"); + return; + } + + int majorTarget = baseVersion.Value.Major; + + Console.WriteLine("Fetching last release..."); + + var releases = await client.Repository.Release.GetAll(authorName, repoName); + + using LibGit2Sharp.Repository gitRepo = new LibGit2Sharp.Repository(Path.Combine(workingDirectory, "../")); + + var branches = gitRepo.Branches; + var activeBranch = branches.First(x => x.IsCurrentRepositoryHead); + + bool isPrerelease = activeBranch.FriendlyName != primaryBranchName; + + baseVersion = baseVersion.Value.WithSuffix(isPrerelease ? prereleaseSuffix : releaseSuffix); + + // Determine what the last release was for the current channel. (prerelease or otherwise) + + var pastReleases = releases.Where(x => x.Prerelease == isPrerelease).OrderByDescending(x => x.CreatedAt); + var lastRelease = releases.FirstOrDefault(); + + ConventionalCommitsBump bump = ConventionalCommitsBump.Patch; + var changelog = new StringBuilder(); + + bool windowsDelta = false; + bool macDelta = false; + bool linuxDelta = false; + + ParsedVersion newVersion; + string? lastVersionString = null; + + if (lastRelease != null) + { + // Determine the last published version + lastVersionString = lastRelease.TagName; + var lastVersion = ParsedVersion.Parse(lastVersionString); + + // Try and construct the changelog. + var lastCommit = GetReleaseCommit(lastRelease, gitRepo); + if (lastCommit != null && lastVersion != null) + { + Console.WriteLine($"Found previous version: {lastVersion}"); + Console.WriteLine("Building changelog..."); + var commitBranches = branches.Where(branch => branch.Commits.Any(x => x.Id == lastCommit.Id)); + + // If the current branch contains the last ref, prefer it. Otherwise just select the first owner of that ref that we find. + var commitBranch = commitBranches.Any(x => x.FriendlyName == activeBranch.FriendlyName) ? activeBranch : commitBranches.First(); + + // If the branches are different, find the latest commit that both share. + List newCommits = []; + if (commitBranch != activeBranch) + { + changelog.AppendLine($"Switched from branch {commitBranch.FriendlyName} to {activeBranch.FriendlyName} - changes in the source branch may have been reverted."); + + if (lastVersion.Value.Minor == 0) + { + bump = ConventionalCommitsBump.Major; // The last release had a breaking change, so undoing it will cause another. + } + else if (lastVersion.Value.Patch == 0) + { + bump = ConventionalCommitsBump.Minor; // Same, but for minor. + } + + var latestShared = activeBranch.Commits.FirstOrDefault(a => commitBranch.Commits.Any(b => a.Id == b.Id)); + + if (latestShared != null) + { + foreach (var commit in activeBranch.Commits) + { + if (commit.Id == latestShared.Id) + { + break; + } + + newCommits.Add(commit); + } + } + else + { + changelog.AppendLine($"Couldn't find a shared commit between the branches."); + } + } + else + { + foreach (var commit in activeBranch.Commits) + { + if (commit.Id == lastCommit.Id) + { + break; + } + + newCommits.Add(commit); + } + } + + // From the commit list, try parse each commit message with conventional commits format, add it to the changelog. + // If there are any breaking changes (eg. feat!:) then do a minor bump instead of patch. + + Console.WriteLine($"Found {newCommits.Count} commits since the last update."); + + changelog.AppendLine(""); + + foreach (var commit in newCommits) + { + ConventionalCommits.AddToChangelog(changelog, ref bump, commit); + } + + newVersion = lastVersion.Value.Next(majorTarget, bump); + + Console.WriteLine($"Downloading client assets for {lastVersion} to create delta..."); + // Download and extract the assets + var manifestAsset = lastRelease.Assets.FirstOrDefault(x => x.Name == $"manifest-{lastVersion.Value}.json"); + var http = new HttpClient(); + http.DefaultRequestHeaders.Accept.Clear(); + http.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream")); + http.DefaultRequestHeaders.Add("User-Agent", "freeso-ci"); + http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", rawToken); + + if (manifestAsset != null) + { + Console.WriteLine($"Downloading manifest from {FixAssetUrl(manifestAsset.Url, lastVersionString)}..."); + var manifestResponse = await http.GetAsync(FixAssetUrl(manifestAsset.Url, lastVersionString)); + if (manifestResponse.IsSuccessStatusCode) + { + var content = await manifestResponse.Content.ReadFromJsonAsync(); + + string correctAssetUrl(string url) + { + url = FixAssetUrl(url, lastVersionString); + + // If the asset URL is on github, it might be private, in which case we want to access it via the API instead. + var matchingAsset = lastRelease.Assets.FirstOrDefault(x => FixAssetUrl(x.BrowserDownloadUrl, lastVersionString) == url); + + return matchingAsset?.Url ?? url; + } + + if (content != null) + { + windowsDelta = await DownloadLastBuild(http, content.full.windows, workingDirectory, "windows", targets, correctAssetUrl); + macDelta = await DownloadLastBuild(http, content.full.mac, workingDirectory, "mac", targets, correctAssetUrl); + linuxDelta = await DownloadLastBuild(http, content.full.linux, workingDirectory, "linux", targets, correctAssetUrl); + } + else + { + Console.WriteLine($"Manifest failed to parse."); + } + } + else + { + Console.WriteLine($"Couldn't download manifest: {manifestResponse.StatusCode} {manifestResponse.ReasonPhrase} {await manifestResponse.Content.ReadAsStringAsync()}"); + } + } + else + { + Console.WriteLine($"Manifest asset was missing..."); + } + } + else + { + Console.WriteLine($"Failed to identify last release commit. (was it made manually?)"); + changelog.AppendLine("Changelog unavailable."); + + lastVersionString = null; + newVersion = baseVersion.Value; + } + } + else + { + // There's no delta for this release. Use the initial version. + Console.WriteLine($"Starting a new release channel with initial version {baseVersion.Value}."); + changelog.AppendLine("New release channel."); + + newVersion = baseVersion.Value; + } + + string versionString = newVersion.ToString(); + string channel = isPrerelease ? prereleaseChannel : releaseChannel; + string changelogString = changelog.ToString(); + + Console.WriteLine($"Creating release for new version {versionString}."); + + // Build the version.json + FSOVersionInfo info = new() + { + id = versionString, + publicKey = publicKey, + channel = channel, + channelUrl = channelUrl + }; + + var infoText = JsonConvert.SerializeObject(info); + + // Create the release on GitHub + + bool anyDelta = windowsDelta || macDelta || linuxDelta; + + var release = await client.Repository.Release.Create(authorName, repoName, new NewRelease(versionString) + { + Name = $"{versionString} ({channel})", + Body = $"Changelog:\n\n{changelogString}", + Prerelease = isPrerelease, + Draft = true, + TargetCommitish = activeBranch.Commits.First().Sha, + }); + + var manifest = new FSOUpdateMetadataStandalone() + { + id = versionString, + channel = channel, + publicKey = publicKey, + lastid = lastVersionString, + date = (uint)DateTimeOffset.UtcNow.ToUnixTimeSeconds(), + server = new FSOUpdateCrossPlatformFile(), + full = new FSOUpdateCrossPlatformFile(), + delta = anyDelta ? new FSOUpdateCrossPlatformFile() : null, + changelog = changelogString, + }; + + Console.WriteLine($"Generating and uploading assets for release targets: {string.Join(", ", targets)}"); + + foreach (var target in targets) + { + Console.WriteLine($"- {target}"); + string clientPath = Path.Combine(workingDirectory, $"{target}"); + string serverPath = Path.Combine(workingDirectory, $"{target}-server"); + string originalClientPath = clientPath; + + if (target == "mac") + { + // Full zip and delta zip come from inside the bundle on macos. + clientPath = Path.Combine(clientPath, "FreeSO.app/Contents/MacOS"); + } + + // Insert version.json into the build. + File.WriteAllText(Path.Combine(clientPath, "version.json"), infoText); + File.WriteAllText(Path.Combine(serverPath, "version.json"), infoText); + + // Build and upload client/server zips (with encrypted SHA-256 hash) + Console.WriteLine(" - Client Full Zip..."); + FSOUpdateFile clientInfo = await FolderToZip(client, release, target, versionString, "client", clientPath, crypto); + Console.WriteLine(" - Server Full Zip..."); + FSOUpdateFile serverInfo = await FolderToZip(client, release, target, versionString, "server", serverPath, crypto); + + manifest.full.SetPlatform(target, clientInfo); + manifest.server.SetPlatform(target, serverInfo); + + // If this target can build a client delta, do that here. + + if ((target == "windows" && windowsDelta) || (target == "mac" && macDelta) || (target == "linux" && linuxDelta)) + { + Console.WriteLine(" - Client Delta:"); + Console.WriteLine(" Calculating diff..."); + var diffs = DiffGenerator.GetDiffs( + Path.GetFullPath(Path.Combine(workingDirectory, $"{target}-old")), + Path.GetFullPath(clientPath)); + + FileDiff[] toZip = [..diffs.Where(x => x.DiffType == FileDiffType.Add || x.DiffType == FileDiffType.Modify)]; + //build diff folder + string deltaDir = Path.Combine(workingDirectory, $"{target}-delta"); + Directory.CreateDirectory(deltaDir); + Console.WriteLine($" Adding {toZip.Length} new or modified files..."); + foreach (var diff in toZip) + { + Directory.CreateDirectory(Path.GetDirectoryName(Path.Combine(deltaDir, diff.Path))!); + System.IO.File.Copy(Path.Combine(clientPath, diff.Path), Path.Combine(deltaDir, diff.Path)); + } + + var deltaJson = new DeltaJson() + { + Diffs = [..diffs] + }; + + File.WriteAllText(Path.Combine(deltaDir, "delta.json"), JsonConvert.SerializeObject(deltaJson)); + + Console.WriteLine($" Building delta zip..."); + FSOUpdateFile deltaInfo = await FolderToZip(client, release, target, versionString, "client-delta", deltaDir, crypto); + + manifest.delta!.SetPlatform(target, deltaInfo); + } + + if (target == "mac") + { + var appPath = Path.GetFullPath(Path.Combine(originalClientPath, "FreeSO.app")); + var dmgPath = Path.GetFullPath(Path.Combine(workingDirectory, "FreeSO.dmg")); + + if (Directory.Exists(appPath)) + { + ExecuteZshScript($"-c \"create-dmg {appPath} --no-code-sign --overwrite --no-version-in-filename && mv FreeSO.dmg {dmgPath}\""); + + await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload() + { + FileName = $"installer-{target}-{versionString}.dmg", + ContentType = "application/x-apple-diskimage", + RawData = File.OpenRead(dmgPath), + }); + } + } + else if (target == "windows") + { + var msiPath = Path.GetFullPath(Path.Combine(workingDirectory, "FSO.Installer.Windows.msi")); + + if (File.Exists(msiPath)) + { + await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload() + { + FileName = $"installer-{target}-{versionString}.msi", + ContentType = "application/octet-stream", + RawData = File.OpenRead(msiPath), + }); + } + } + } + + + Console.WriteLine($"Finished building update! Uploading final manifest."); + + // Finally, upload the final manifest. This will get added to the update list by the update API. + + var manifestData = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(manifest)); + + await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload() + { + FileName = $"manifest-{versionString}.json", + ContentType = "application/json", + RawData = new MemoryStream(manifestData), + }); + + Console.WriteLine($"Undrafting release..."); + + await client.Repository.Release.Edit(authorName, repoName, release.Id, new ReleaseUpdate() + { + Draft = false, + MakeLatest = isPrerelease ? null : MakeLatestQualifier.True, + }); + + Console.WriteLine($"Done."); + } + + private static async Task WindowsMsiPackager() + { + Console.WriteLine($"FreeSO Msi Packager"); + Console.WriteLine("=============================================================="); + Console.WriteLine(""); + + Console.WriteLine("Initializing GitHub Client"); + var client = new GitHubClient(new ProductHeaderValue("freeso-ci")); + var rawToken = Environment.GetEnvironmentVariable("GH_TOKEN"); + var tokenAuth = new Octokit.Credentials(rawToken); + client.Credentials = tokenAuth; + + string repoString = Environment.GetEnvironmentVariable("FSO_UPDATE_GITHUB_REPO") ?? "riperiperi/FreeSO"; + string[] splitRepo = repoString.Split('/'); + string authorName = splitRepo[0]; + string repoName = splitRepo[1]; + + // We want to add the installer MSI to the existing release. + // We can get the release tag from the version manifest. + + var version = FSOVersionInfo.FromJson(File.ReadAllText("../../../Artifacts/Client/version.json")); + + var release = await client.Repository.Release.Get(authorName, repoName, version.id); + + await client.Repository.Release.UploadAsset(release, new ReleaseAssetUpload() + { + FileName = $"installer-windows-{version.id}.msi", + ContentType = "application/octet-stream", + RawData = File.OpenRead("../../../Artifacts/Installer/en-US/FSO.Installer.Windows.msi"), + }); + + Console.WriteLine($"Done."); + } + } +} diff --git a/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.csproj b/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.csproj new file mode 100644 index 000000000..08ad74680 --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + diff --git a/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.slnx b/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.slnx new file mode 100644 index 000000000..d7353ab4b --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/FSO.UpdateWorker.slnx @@ -0,0 +1,4 @@ + + + + diff --git a/Other/tools/FSO.UpdateWorker/InstallerCache.cs b/Other/tools/FSO.UpdateWorker/InstallerCache.cs new file mode 100644 index 000000000..e941ba725 --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/InstallerCache.cs @@ -0,0 +1,147 @@ +using Octokit; +using System.Text.Json; + +namespace FSO.UpdateWorker +{ + internal class InstallerCache + { + private static Dictionary PlatformNames = new() + { + { "windows", "Windows (x64)" }, + { "mac", "macOS (ARM)" }, + { "linux", "Linux (x64)" }, + }; + + private readonly ManifestCache Manifests; + private readonly string[] Platforms; + private InstallerManifestResponse Response = new(); + + public InstallerCache(ManifestCache manifests, string[] platforms) + { + Manifests = manifests; + Platforms = platforms; + } + + private static InstallerFile MakeFile(ReleaseAsset asset) + { + return new InstallerFile() + { + url = asset.BrowserDownloadUrl, + size = asset.Size, + }; + } + + private async Task GetResponse(Release release) + { + if (release.Draft) + { + return null; + } + + // Should have installer URLs for all of the chosen platforms. + + var result = new InstallerManifestChannel() + { + version = release.TagName, + releaseUrl = release.HtmlUrl + }; + + foreach (var platform in Platforms) + { + var zipAsset = release.Assets.FirstOrDefault(x => x.Name.StartsWith($"client-{platform}-")); + var installerAsset = release.Assets.FirstOrDefault(x => x.Name.StartsWith($"installer-{platform}-")); + var serverAsset = release.Assets.FirstOrDefault(x => x.Name.StartsWith($"server-{platform}-")); + + if (zipAsset == null || (installerAsset == null && platform != "linux")) + { + return null; + } + + if (!PlatformNames.TryGetValue(platform, out string? name)) + { + name = "Unknown"; + } + + var platformAssets = new InstallerPlatform() + { + name = name, + zip = MakeFile(zipAsset), + installer = installerAsset == null ? null : MakeFile(installerAsset), + server = serverAsset == null ? null : MakeFile(serverAsset) + }; + + switch (platform) + { + case "windows": + result.windows = platformAssets; + break; + case "mac": + result.mac = platformAssets; + break; + case "linux": + result.linux = platformAssets; + break; + } + } + + // Finally, fetch the channel for this release from its manifest. If it doesn't match the channel for this installer manifest, ignore it. + + var manifest = await Manifests.GetMetadata(release); + + if (manifest == null) + { + return null; + } + + result.channel = manifest.channel; + + return result; + } + + public async Task ProcessLatest(List releases) + { + var processedChannels = new HashSet(); + + bool anyChanged = false; + + foreach (var release in releases) + { + // Select the first release for each channel that satisfies all of the criteria. + + var newResponse = await GetResponse(release); + + if (newResponse != null && !processedChannels.Contains(newResponse.channel)) + { + processedChannels.Add(newResponse.channel); + + var existingIndex = Array.FindIndex(Response.channels, x => x.channel == newResponse.channel); + + if (existingIndex == -1) + { + Response.channels = [.. Response.channels, newResponse]; + anyChanged = true; + } + else + { + var existing = Response.channels[existingIndex]; + + if (!existing.Equals(newResponse)) + { + Response.channels[existingIndex] = newResponse; + + anyChanged = true; + } + } + } + } + + return anyChanged; + } + + public void SaveResponse(string path) + { + Console.WriteLine($"Saving updated installer manifest to {path}"); + File.WriteAllText(path, JsonSerializer.Serialize(Response)); + } + } +} diff --git a/Other/tools/FSO.UpdateWorker/InstallerManifestResponse.cs b/Other/tools/FSO.UpdateWorker/InstallerManifestResponse.cs new file mode 100644 index 000000000..a00159d5a --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/InstallerManifestResponse.cs @@ -0,0 +1,89 @@ +namespace FSO.UpdateWorker +{ + internal class InstallerFile : IEquatable + { + public string url { get; set; } = ""; + public int size { get; set; } + + public bool Equals(InstallerFile? other) + { + return other != null && + url == other.url && + size == other.size; + } + + public override bool Equals(object? obj) + { + return obj is InstallerFile file && Equals(file); + } + + public override int GetHashCode() + { + return HashCode.Combine(url, size); + } + } + + internal class InstallerPlatform : IEquatable + { + public string name { get; set; } = "Unknown"; + + public InstallerFile? installer { get; set; } + public InstallerFile? zip { get; set; } + public InstallerFile? server { get; set; } + + public bool Equals(InstallerPlatform? other) + { + return other != null && + name == other.name && + (installer == other.installer || (installer?.Equals(other.installer) ?? false)) && + (zip == other.zip || (zip?.Equals(other.zip) ?? false)) && + (server == other.server || (server?.Equals(other.server) ?? false)); + } + + public override bool Equals(object? obj) + { + return obj is InstallerPlatform platform && Equals(platform); + } + + public override int GetHashCode() + { + return HashCode.Combine(name, installer, server); + } + } + + internal class InstallerManifestChannel : IEquatable + { + public string channel { get; set; } = ""; + public string version { get; set; } = ""; + public string releaseUrl { get; set; } = ""; + public InstallerPlatform? windows { get; set; } + public InstallerPlatform? linux { get; set; } + public InstallerPlatform? mac { get; set; } + + public bool Equals(InstallerManifestChannel? other) + { + return other != null && + channel == other.channel && + version == other.version && + releaseUrl == other.releaseUrl && + (windows == other.windows || (windows?.Equals(other.windows) ?? false)) && + (mac == other.mac || (mac?.Equals(other.mac) ?? false)) && + (linux == other.linux || (windows?.Equals(other.linux) ?? false)); + } + + public override bool Equals(object? obj) + { + return obj is InstallerManifestChannel other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(channel, version, releaseUrl, windows, linux, mac); + } + } + + internal class InstallerManifestResponse + { + public InstallerManifestChannel[] channels { get; set; } = []; + } +} diff --git a/Other/tools/FSO.UpdateWorker/ManifestCache.cs b/Other/tools/FSO.UpdateWorker/ManifestCache.cs new file mode 100644 index 000000000..c6410ec7a --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/ManifestCache.cs @@ -0,0 +1,70 @@ +using FSO.Files.FSO; +using Octokit; +using System.Collections.Concurrent; +using System.Net.Http.Json; + +namespace FSO.UpdateWorker +{ + internal class ManifestCache + { + private ConcurrentDictionary Manifests = []; + private readonly HttpClient Http; + + public ManifestCache(HttpClient http) + { + Http = http; + } + + public async Task GetMetadata(string url, string name) + { + if (Manifests.TryGetValue(url, out var data)) + { + return data; + } + + try + { + // Assuming that we have permissions here. + data = await Http.GetFromJsonAsync(url); + + if (data?.id != null) + { + Manifests[url] = data; + return data; + } + else + { + Console.WriteLine($"Couldn't parse update JSON for {name}, skipping."); + } + } + catch + { + // Nothing happens - this asset just gets skipped. + Console.WriteLine($"Couldn't load update info for {name}, skipping."); + } + + return null; + } + + public async Task GetMetadata(ReleaseAsset? manifestAsset, string name) + { + if (manifestAsset != null) + { + return await GetMetadata(manifestAsset.Url, name); + } + else + { + Console.WriteLine($"Couldn't find manifest asset for {name}, skipping."); + } + + return null; + } + + public async Task GetMetadata(Release release) + { + var manifestAsset = release.Assets.FirstOrDefault(x => x.Name == $"manifest-{release.TagName}.json"); + + return await GetMetadata(manifestAsset, release.TagName); + } + } +} diff --git a/Other/tools/FSO.UpdateWorker/Program.cs b/Other/tools/FSO.UpdateWorker/Program.cs new file mode 100644 index 000000000..73c03f7f4 --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/Program.cs @@ -0,0 +1,104 @@ +using Octokit; +using System.Text.Json; + +namespace FSO.UpdateWorker +{ + internal class Program + { + private const int CheckFrequency = 1000 * 60 * 2; // 4 minutes + + static async Task Main(string[] args) + { + UpdateWorkerConfig config = new(); + + try + { + if (File.Exists("config.json")) + { + config = JsonSerializer.Deserialize(File.ReadAllText("config.json")) ?? new(); + } + } + catch (Exception) + { + config = new(); + } + + string authorName = config.authorName; + string repoName = config.repoName; + var client = new GitHubClient(new ProductHeaderValue("freeso-updates")); + + var http = new HttpClient(); + http.DefaultRequestHeaders.Accept.Clear(); + http.DefaultRequestHeaders.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/octet-stream")); + http.DefaultRequestHeaders.Add("User-Agent", "freeso-ci"); + if (config.githubToken != null) + { + client.Credentials = new Credentials(config.githubToken); + http.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", config.githubToken); + } + + var manifests = new ManifestCache(http); + + var cache = new ReleaseCache(client, http, manifests); + var installer = new InstallerCache(manifests, config.installerPlatforms); + + if (!config.clearCache) + { + cache.LoadCache(config.targetPath); + } + + while (true) + { + try + { + List releases = [.. await client.Repository.Release.GetAll(authorName, repoName)]; + + bool hasChange = await cache.AddReleases(releases); + + var remeshReleases = await client.Repository.Release.GetAll(config.remeshAuthorName, config.remeshRepoName); + + hasChange |= await cache.AddRemeshes([.. remeshReleases], config); + + if (hasChange) + { + cache.SaveResponse(config.targetPath); + } + + if (config.installerTargetPath != null && await installer.ProcessLatest(releases)) + { + installer.SaveResponse(config.installerTargetPath); + } + + Thread.Sleep(CheckFrequency); + + /* + * This only works when for a non-prerelease branch + while (true) + { + Thread.Sleep(CheckFrequency); + + try + { + var latest = await client.Repository.Release.GetLatest(authorName, repoName); + + if (latest.Id != releases.FirstOrDefault()?.Id) + { + // Get the full release list if something changed. + break; + } + } + catch + { + // Try again later. + } + } + */ + } + catch + { + Thread.Sleep(CheckFrequency); + } + } + } + } +} diff --git a/Other/tools/FSO.UpdateWorker/ReleaseCache.cs b/Other/tools/FSO.UpdateWorker/ReleaseCache.cs new file mode 100644 index 000000000..cb4790ee7 --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/ReleaseCache.cs @@ -0,0 +1,202 @@ +using FSO.Files.FSO; +using Octokit; +using System.Net.Http.Json; +using System.Text.Json; + +namespace FSO.UpdateWorker +{ + internal class ReleaseCache + { + private readonly GitHubClient Client; + private readonly HttpClient Http; + private readonly ManifestCache Manifests; + + private FSOUpdateResponse Response = new FSOUpdateResponse(); + private HashSet SeenTags = []; + + public ReleaseCache(GitHubClient client, HttpClient http, ManifestCache manifests) + { + Client = client; + Http = http; + Manifests = manifests; + } + + public async Task AddRemeshes(List releases, UpdateWorkerConfig config) + { + bool changed = false; + + foreach (var channelName in config.remeshChannels) + { + var latest = releases.FirstOrDefault(x => x.TagName.StartsWith($"{channelName}.")); + + if (latest == null) + { + continue; + } + + string[] split = latest.TagName.Split('.'); + + if (split.Length != 2 || !int.TryParse(split[1], out int version)) + { + continue; + } + + var existing = Response.remeshes.FirstOrDefault(x => x.channel == channelName); + + if (existing != null) + { + // Only update if the version has increased. + + if (version <= existing.version) + { + continue; + } + } + + // Get the remesh's manifest and try to add it to the Response + + var manifestAsset = latest.Assets.FirstOrDefault(x => x.Name == $"freeso-remeshes.json"); + + try + { + if (manifestAsset != null) + { + // Assuming that we have permissions here. + var assetUrl = manifestAsset.Url; + + var data = await Http.GetFromJsonAsync(assetUrl); + + if (data?.channel != channelName) + { + Console.WriteLine($"Couldn't parse remesh JSON for {latest.TagName}, skipping."); + } + + Response.remeshes = [.. Response.remeshes.Where(x => x.channel != channelName), data]; + + if (config.autoRemeshChannel == channelName) + { + Response.autoRemeshChannel = channelName; + } + + Console.WriteLine($"Updating remesh channel '{channelName}'"); + + changed = true; + } + else + { + Console.WriteLine($"Couldn't find manifest asset for {latest.TagName}, skipping."); + } + } + catch + { + // Nothing happens - this asset just gets skipped. + Console.WriteLine($"Couldn't load remesh info for {latest.TagName}, skipping."); + } + } + + return changed; + } + + public async Task AddReleases(List releases) + { + bool addedAny = false; + + foreach (var release in releases.OrderBy(x => x.CreatedAt)) // Oldest first + { + if (!SeenTags.Contains(release.TagName)) + { + if (!await AddRelease(release)) + { + // It's not ready yet - try this one again later. + continue; + } + + addedAny = true; + SeenTags.Add(release.TagName); + } + } + + return addedAny; + } + + private async Task AddRelease(Release release) + { + if (release.Draft) + { + // Not ready yet. + return false; + } + + // Get the release's manifest and try to add it to the Response + var manifest = await Manifests.GetMetadata(release); + + if (manifest != null) + { + AddReleaseManifest(manifest); + } + + return true; + } + + private FSOUpdateChannel GetOrAddChannel(FSOUpdateMetadataStandalone standalone) + { + var existing = Response.channels.FirstOrDefault(x => x.channel == standalone.channel); + + if (existing == null) + { + Console.WriteLine($"Adding new channel '{standalone.channel}'"); + existing = new FSOUpdateChannel() + { + channel = standalone.channel, + publicKey = standalone.publicKey, + }; + + Response.channels = [.. Response.channels, existing]; + } + + existing.publicKey = standalone.publicKey; + + return existing; + } + + private void AddReleaseManifest(FSOUpdateMetadataStandalone standalone) + { + var channel = GetOrAddChannel(standalone); + + List updates = [standalone.Clone(), ..channel.updates]; + + Console.WriteLine($"Adding new update '{standalone.id}'"); + channel.updates = [.. updates.OrderByDescending(x => x.date)]; + } + + public void SaveResponse(string path) + { + Console.WriteLine($"Saving updated releases to {path}"); + File.WriteAllText(path, JsonSerializer.Serialize(Response)); + } + + public void LoadCache(string path) + { + try + { + var text = File.ReadAllText(path); + + Response = JsonSerializer.Deserialize(text) ?? Response; + + foreach (var channel in Response.channels) + { + foreach (var update in channel.updates) + { + SeenTags.Add(update.id); + } + } + + Console.WriteLine($"Loaded cache from {path} with {SeenTags.Count} items."); + } + catch + { + Console.WriteLine($"Cache at {path} could not be loaded."); + } + } + } +} diff --git a/Other/tools/FSO.UpdateWorker/UpdateWorkerConfig.cs b/Other/tools/FSO.UpdateWorker/UpdateWorkerConfig.cs new file mode 100644 index 000000000..6f873b4fd --- /dev/null +++ b/Other/tools/FSO.UpdateWorker/UpdateWorkerConfig.cs @@ -0,0 +1,17 @@ +namespace FSO.UpdateWorker +{ + internal class UpdateWorkerConfig + { + public string authorName { get; set; } = "riperiperi"; + public string repoName { get; set; } = "FreeSO"; + public string[] installerPlatforms { get; set; } = ["windows", "mac"]; + public string remeshAuthorName { get; set; } = "riperiperi"; + public string remeshRepoName { get; set; } = "FSO.Remeshes"; + public string targetPath { get; set; } = "update.json"; + public string? installerTargetPath { get; set; } = "installer.json"; + public string[] remeshChannels { get; set; } = ["prod"]; + public string autoRemeshChannel { get; set; } = "prod"; + public string? githubToken { get; set; } + public bool clearCache { get; set; } + } +} diff --git a/README.md b/README.md index 936ed205d..77a6ff69b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ -![image](http://freeso.org/wp-content/uploads/2016/03/freeso-logo.png?1) +![image](https://freeso.org/staticfso/freeso.svg) -A full reimplementation of The Sims Online, using Monogame. While FreeSO aims to be faithful to the original game, it includes many quality of life changes such as hardware rendering, custom dynamic lighting, hi-res output and >2 floor houses. While there used to be an official FreeSO server, FreeSO is now a technology base for other The Sims Online servers to build upon. Please see the https://freeso.org blog for more information. In the future, a client specifically suited to exploring the original FreeSO server alone or with friends will be made available in a different repository. +A full reimplementation of The Sims Online, using Monogame. While FreeSO aims to be faithful to the original game, it includes many quality of life changes such as hardware rendering, custom dynamic lighting, hi-res output and >2 floor houses. There are also some huge gameplay additions such as Neighbourhoods, toggleable 1st/3rd person controls, open lot borders and more. + +While there used to be an official FreeSO server, FreeSO is now both a standalone application that allows players to self-host and join instances of the FreeSO server, and a technology base for other The Sims Online servers to build upon. Please see the https://freeso.org blog for more information. FreeSO currently depends on the original game files (objects, avatars, ui) to function, which are available for download from EA servers. FreeSO is simply a game engine, and does not contain any copyrighted material in and of itself. -![image](http://freeso.org/wp-content/uploads/2017/05/band.png) +![image](Documentation/media/band.png) # The Sims 1 via Simitone @@ -14,10 +16,12 @@ The content system, HIT VM and SimAntics VM included within this repo support bo # 3D Mode -![image](https://cdn.discordapp.com/attachments/355135351234494464/355396364349210625/unknown.png) +![image](Documentation/media/3d.png) The FreeSO engine additionally supports a 3D mode, which allows you to see the game from a different perspective. 3D meshes are reconstructed at runtime from the z-buffers included with object sprites. FreeSO also generates 3D geometry for walls and floors at runtime, and switches to an alternate camera with different controls when the mode is enabled. +A large selection of objects from the game have specially crafted 3D models created by the community, as the generated 3D meshes can be garbled due to small details not encoding well into sprite form, and gaps between multitile parts. These are maintained separately at the [FSO.Remeshes](https://github.com/riperiperi/FSO.Remeshes) repository. + The mode can be enabled via the launch parameter `-3d`. See the blog for more information. (http://freeso.org/the-impossible/) # Volcanic @@ -30,6 +34,8 @@ Volcanic is an extension of FreeSO that allows users to view, modify and save ga # Contributing You can contribute to FreeSO by testing cutting edge features in the latest releases, filing bugs, and joining in the discussion on our forums! +FreeSO is largely complete - we only expect to see limited changes for bugfixes, extended support or a select few features that would improve the existing game experience. If you wish to make a large scale change, you should ask on Discord whether it's something that would be accepted or not. + * [Getting Started](https://github.com/riperiperi/FreeSO/wiki) * [Project Structure](https://github.com/riperiperi/FreeSO/wiki/Project-structure) * [Coding Standards](https://github.com/riperiperi/FreeSO/wiki/Coding-standards) @@ -42,8 +48,13 @@ You can contribute to FreeSO by testing cutting edge features in the latest rele Looking for something to do? Check out the issues tagged as [help wanted](https://github.com/riperiperi/FreeSO/labels/help%20wanted) to get started. ## Prerequisites -* [Visual Studio 2019](https://visualstudio.microsoft.com/vs/) -* [MonoGame](http://www.monogame.net): 3.5 for the iOS and Android VS2015 project types. (optional) +* [Visual Studio Community](https://visualstudio.microsoft.com/vs/): With .NET 9.0 +* [MonoGame](http://www.monogame.net): 3.8.5 + +## AI +**This repository does not accept AI assisted contributions in any form.** + +FreeSO is a passion project born of the dedication and creativity of real people, each of whom has a storied history of playing the game, getting inspired by it, learning new skills to contribute and interacting with the community. Firing vague instructions at a prompt to make changes for changes sake is _not_ the kind of dedication that makes a project like this. # License > This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. diff --git a/TSOClient/.config/dotnet-tools.json b/TSOClient/.config/dotnet-tools.json new file mode 100644 index 000000000..852a41cd5 --- /dev/null +++ b/TSOClient/.config/dotnet-tools.json @@ -0,0 +1,41 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-mgcb": { + "version": "3.8.5", + "commands": [ + "mgcb" + ], + "rollForward": false + }, + "dotnet-mgcb-editor": { + "version": "3.8.4", + "commands": [ + "mgcb-editor" + ], + "rollForward": false + }, + "dotnet-mgcb-editor-linux": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-linux" + ], + "rollForward": false + }, + "dotnet-mgcb-editor-windows": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-windows" + ], + "rollForward": false + }, + "dotnet-mgcb-editor-mac": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-mac" + ], + "rollForward": false + } + } +} diff --git a/TSOClient/FSO.Common.DatabaseService/FSO.Common.DatabaseService.csproj b/TSOClient/FSO.Common.DatabaseService/FSO.Common.DatabaseService.csproj index 347b3d2c2..1b6098917 100644 --- a/TSOClient/FSO.Common.DatabaseService/FSO.Common.DatabaseService.csproj +++ b/TSOClient/FSO.Common.DatabaseService/FSO.Common.DatabaseService.csproj @@ -1,117 +1,24 @@ - - - + + - Debug - AnyCPU - {C051793D-1A9C-4554-9BB8-BAFDC01A096A} Library - Properties FSO.Common.DatabaseService FSO.Common.DatabaseService - v4.5 512 - + net9.0 + enable + disable + True - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - - {329e0aee-7871-40a7-b5af-8c0d0086ef71} - FSO.Server.Clients - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - + + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Common.DatabaseService/Properties/AssemblyInfo.cs b/TSOClient/FSO.Common.DatabaseService/Properties/AssemblyInfo.cs deleted file mode 100644 index bb3d6136f..000000000 --- a/TSOClient/FSO.Common.DatabaseService/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Common.DatabaseService")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Common.DatabaseService")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("c051793d-1a9c-4554-9bb8-bafdc01a096a")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Common.DatabaseService/app.config b/TSOClient/FSO.Common.DatabaseService/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Common.DatabaseService/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Common.DatabaseService/packages.config b/TSOClient/FSO.Common.DatabaseService/packages.config deleted file mode 100644 index 9477ab48a..000000000 --- a/TSOClient/FSO.Common.DatabaseService/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Common.Domain/FSO.Common.Domain.csproj b/TSOClient/FSO.Common.Domain/FSO.Common.Domain.csproj index 46312fbd9..e46508a14 100644 --- a/TSOClient/FSO.Common.Domain/FSO.Common.Domain.csproj +++ b/TSOClient/FSO.Common.Domain/FSO.Common.Domain.csproj @@ -1,105 +1,24 @@ - - - + + - Debug - AnyCPU - {9848FAF5-444A-48CC-A26A-8115D8C4FB52} + net9.0 + enable + disable Library - Properties FSO.Common.Domain FSO.Common.Domain - v4.5 512 - + True - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - - - - - - - - - - - - - - - - - - - - - - + - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - + + + - - + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.DataService/Framework/IServerNFSProvider.cs b/TSOClient/FSO.Common.Domain/IServerNFSProvider.cs similarity index 52% rename from TSOClient/FSO.Server.DataService/Framework/IServerNFSProvider.cs rename to TSOClient/FSO.Common.Domain/IServerNFSProvider.cs index fcad3a27a..3ef0379c4 100644 --- a/TSOClient/FSO.Server.DataService/Framework/IServerNFSProvider.cs +++ b/TSOClient/FSO.Common.Domain/IServerNFSProvider.cs @@ -1,7 +1,8 @@ -namespace FSO.Common.DataService.Framework +namespace FSO.Common.Domain { public interface IServerNFSProvider { string GetBaseDirectory(); + string GetShardMapDirectory(int shardId); } } diff --git a/TSOClient/FSO.Common.Domain/Properties/AssemblyInfo.cs b/TSOClient/FSO.Common.Domain/Properties/AssemblyInfo.cs deleted file mode 100644 index 4b2e6937d..000000000 --- a/TSOClient/FSO.Common.Domain/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Common.Domain")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Common.Domain")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9848faf5-444a-48cc-a26a-8115d8c4fb52")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Common.Domain/Realestate/CityMapUtils.cs b/TSOClient/FSO.Common.Domain/Realestate/CityMapUtils.cs new file mode 100644 index 000000000..18a0496c6 --- /dev/null +++ b/TSOClient/FSO.Common.Domain/Realestate/CityMapUtils.cs @@ -0,0 +1,876 @@ +using FSO.Content.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using System.Runtime.CompilerServices; + +namespace FSO.Common.Domain.Realestate +{ + public enum RoadSegs : byte + { + BottomLeft = 1, + BottomRight = 2, + TopRight = 4, + TopLeft = 8, + + Left = 16, + Bottom = 32, + Right = 64, + Top = 128, + + AllCorners = Bottom | Right | Top | Left + } + + public static class CityMapUtils + { + private static readonly Point[] WLStartOff = { + + // Look at this way up <---- + // Starting at % line, going cw. Middle is (0,0), and below it is the tile (0,0).. + // + // /\ + // / \ +x + // /\ %\ + // / \% \ + // \ /\ / + // \/ \/ + // \ / +y + // \/ + + new(0, 0), + new(0, 0), + new(-1, 0), + new(0, -1), + }; + + private static readonly RoadSegs[] WLMainSeg = + { + RoadSegs.TopLeft, + RoadSegs.BottomLeft, + RoadSegs.TopLeft, + RoadSegs.BottomLeft, + }; + + private static readonly Point[] WLSubOff = + { + new(0, -1), + new(-1, 0), + new(0, -1), + new(-1, 0), + }; + + private static readonly RoadSegs[] WLSubSeg = + { + RoadSegs.BottomRight, + RoadSegs.TopRight, + RoadSegs.BottomRight, + RoadSegs.TopRight, + }; + + + private static readonly Point[] WLStep = + { + new(1, 0), + new(0, 1), + new(-1, 0), + new(0, -1), + }; + + private static readonly ((RoadSegs line, RoadSegs corner), (RoadSegs line2, RoadSegs corner2))[] AdjEdgeToCorner = + [ + ( // positive x + (RoadSegs.BottomRight, RoadSegs.Right), + (RoadSegs.TopLeft, RoadSegs.Top) + ), + ( // positive y + (RoadSegs.BottomLeft, RoadSegs.Bottom), + (RoadSegs.TopRight, RoadSegs.Right) + ), + ( // negative x + (RoadSegs.TopLeft, RoadSegs.Left), + (RoadSegs.BottomRight, RoadSegs.Bottom) + ), + ( // negative y + (RoadSegs.TopRight, RoadSegs.Top), + (RoadSegs.BottomLeft, RoadSegs.Left) + ) + ]; + + private const int RandomSeed = 123456789; + + [ThreadStatic] + private static CityEditBitmap ReservedBitmap; + private readonly static byte[] Noise; + + static CityMapUtils() + { + byte[] noise = new byte[512 * 512]; + + var rand = new Random(RandomSeed); + + rand.NextBytes(noise); + + Noise = noise; + } + + public static byte[] GetRawNoise() + { + return Noise; + } + + public static void GetSpraypaintNoise(byte[] target, uint seed) + { + var index = (int)(seed % Noise.Length); + + if (index > 0) + { + var sliceSize = Noise.Length - index; + (Noise.AsSpan(index)).CopyTo(target.AsSpan(0, sliceSize)); + (Noise.AsSpan(0, index)).CopyTo(target.AsSpan(sliceSize)); + } + else + { + Noise.CopyTo(target, 0); + } + } + + private static CityEditBitmap GetReservedBitmapBase(CityMap map) + { + CityEditBitmap bitmap; + if (ReservedBitmap == null || ReservedBitmap.Width != map.Width || ReservedBitmap.Height != map.Height) + { + bitmap = new CityEditBitmap(map.Width, map.Height); + + ReservedBitmap = bitmap; + } + else + { + bitmap = ReservedBitmap; + bitmap.Clear(); + } + + return bitmap; + } + + private static CityEditBitmap GetReservedBitmap(CityMap map, CityEditBase command) + { + CityEditBitmap bitmap = GetReservedBitmapBase(map); + + if (command.ReservedLocations != null) + { + foreach (var location in command.ReservedLocations) + { + var pt = ReservedLocationToPoint(location); + + if (pt.X >= 0 && pt.Y >= 0 && pt.X < map.Width && pt.Y < map.Height) + { + bitmap.Set(pt.X, pt.Y); + } + } + } + + return bitmap; + } + + private static CityEditBitmap GetReservedBitmapAlt(CityMap map, CityEditBase command) + { + CityEditBitmap bitmap = GetReservedBitmapBase(map); + + if (command.ReservedLocations != null) + { + foreach (var location in command.ReservedLocations) + { + var pt = ReservedLocationToPoint(location); + + if (pt.X >= 0 && pt.Y >= 0 && pt.X < map.Width && pt.Y < map.Height) + { + bitmap.Set(pt.X, pt.Y); + + // Reserved tiles lock all four corners. + + if (pt.X + 1 < map.Width) + { + bitmap.Set(pt.X + 1, pt.Y); + + if (pt.Y + 1 < map.Height) + { + bitmap.Set(pt.X + 1, pt.Y + 1); + } + } + + if (pt.Y + 1 < map.Height) + { + bitmap.Set(pt.X, pt.Y + 1); + } + } + } + } + + return bitmap; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool InBounds(Point tile) + { + // This will break when the coords are past short range, but that can't happen with these commands. + return MapCoordinates.InBounds((ushort)tile.X, (ushort)tile.Y, 0); + } + + public static bool ValidateCommand(CityMap map, CityEditBase command) + { + return command switch + { + CityEditRoad road => ValidateRoad(map, road), + _ => true + }; + } + + public static bool ApplyCommand(CityMap map, CityEditBase command, HashSet reservedTiles = null, HashSet toUpdate = null, bool forUndo = false) + { + return command switch + { + CityEditRoad road => ApplyRoad(map, road, reservedTiles, toUpdate), + CityEditPaint paint => ApplyPaint(map, paint, reservedTiles, toUpdate, forUndo), + CityEditAltitude alt => ApplyAltitude(map, alt, reservedTiles, toUpdate, forUndo), + CityEditForest forest => ApplyForest(map, forest), // Forest doesn't update its tiles. + _ => false + }; + } + + public static Rectangle? GetBounds(CityMap map, CityEditBase command) + { + return command switch + { + CityEditRoad road => GetRoadBounds(road, true), + CityEditPaint paint => GetBitmapBounds(map, paint.Bitmap), + CityEditAltitude alt => GetBitmapBounds(map, alt.Bitmap), + CityEditForest forest => GetBitmapBounds(map, forest.Bitmap), + _ => null + }; + } + + private static Rectangle? GetBitmapBounds(CityMap map, CityEditBitmap bitmap) + { + if (bitmap == null) + { + return null; + } + + CityEditBitmap trimmed = (bitmap.Width == map.Width && bitmap.Height == map.Height) ? bitmap.Trim() : bitmap; + + if (trimmed == null) + { + return null; + } + + return new Rectangle(trimmed.X, trimmed.Y, trimmed.Width, trimmed.Height); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Point ReservedLocationToPoint(uint location) + { + var coords = MapCoordinates.Unpack(location); + + return new Point(coords.X, coords.Y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void UpdateMinMax(Point pt, ref int minX, ref int minY, ref int maxX, ref int maxY) + { + if (pt.X < minX) minX = pt.X; + if (pt.Y < minY) minY = pt.Y; + if (pt.X + 1 > maxX) maxX = pt.X + 1; + if (pt.Y + 1 > maxY) maxY = pt.Y + 1; + } + + private static Rectangle GetRoadBounds(CityEditRoad road, bool corners) + { + bool xDir = (road.Direction % 2) == 0; + var direction = road.Direction; + + Point step = WLStep[direction]; + Point start = new(road.StartX, road.StartY); + int length = road.Length; + + Point subOff = WLSubOff[direction]; // Direction to place the sub segment of the wall + + if (corners) + { + start -= step; + length += 2; + } + + start += WLStartOff[direction]; + + Point end = start + new Point(step.X * length, step.Y * length); + + int minX = start.X; + int minY = start.Y; + int maxX = start.X + 1; + int maxY = start.Y + 1; + + UpdateMinMax(start + subOff, ref minX, ref minY, ref maxX, ref maxY); + + UpdateMinMax(end, ref minX, ref minY, ref maxX, ref maxY); + UpdateMinMax(end + subOff, ref minX, ref minY, ref maxX, ref maxY); + + return new Rectangle(minX, minY, maxX - minX, maxY - minY); + } + + public static bool ValidateRoad(CityMap map, CityEditRoad road) + { + // Does the bound go outside the map? + var innerBounds = GetRoadBounds(road, false); + + if (innerBounds.X < 0 || innerBounds.Y < 0 || innerBounds.Bottom > map.Height || innerBounds.Right > map.Width) + { + return false; + } + + + // Is any reserved tile overlapping the road bounds? + var bounds = GetRoadBounds(road, true); + + if (road.ReservedLocations != null) + { + foreach (uint location in road.ReservedLocations) + { + if (bounds.Contains(ReservedLocationToPoint(location))) + { + return false; + } + } + } + + return true; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int GetOffset(CityMap map, Point tile) + { + return tile.Y * map.Width + tile.X; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ApplyCornerRule(in (RoadSegs line, RoadSegs corner) rule, in byte adjRoad, ref byte road) + { + byte lineB = (byte)rule.line; + + if ((road & lineB) == 0 && (adjRoad & lineB) != 0) + { + road |= (byte)rule.corner; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RecalculateCorner(CityMap map, byte[] roads, Point tile) + { + // Corner presence is dictated by the presence of a road segment on an adjacent tile + // and its subsequent absence on this tile. Only segments perpendicular to the tile edge create corners. + + if (!InBounds(tile)) return; + + ref byte road = ref roads[GetOffset(map, tile)]; + + // Clear corners, add them on top as we find them. + road &= (byte)~RoadSegs.AllCorners; + + for (int i = 0; i < 4; i++) + { + var adj = tile + WLStep[i]; + if (InBounds(adj)) + { + byte adjRoad = roads[GetOffset(map, adj)]; + + var (rule1, rule2) = AdjEdgeToCorner[i]; + + ApplyCornerRule(in rule1, in adjRoad, ref road); + ApplyCornerRule(in rule2, in adjRoad, ref road); + } + } + + // Finally, do some cleanup for invalid corners + + var roadSegs = (RoadSegs)road; + + if (roadSegs.HasFlag(RoadSegs.BottomRight)) + { + road &= (byte)~(RoadSegs.Bottom | RoadSegs.Right); + } + + if (roadSegs.HasFlag(RoadSegs.TopRight)) + { + road &= (byte)~(RoadSegs.Top | RoadSegs.Right); + } + + if (roadSegs.HasFlag(RoadSegs.BottomLeft)) + { + road &= (byte)~(RoadSegs.Bottom | RoadSegs.Left); + } + + if (roadSegs.HasFlag(RoadSegs.TopLeft)) + { + road &= (byte)~(RoadSegs.Top | RoadSegs.Left); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint GetMapCoord(Point pos) + { + return MapCoordinates.Pack((ushort)pos.X, (ushort)pos.Y); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RegisterUpdate(HashSet reservedTiles, HashSet toUpdate, uint id) + { + if (reservedTiles.Contains(id)) + { + toUpdate.Add(id); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RegisterRoadUpdates(HashSet reservedTiles, HashSet toUpdate, Point pos) + { + // Road modifications only update the lot they're on top of. + + if (reservedTiles != null) + { + uint id = GetMapCoord(pos); + + RegisterUpdate(reservedTiles, toUpdate, id); + } + } + + public static bool ApplyRoad(CityMap map, CityEditRoad road, HashSet reservedTiles, HashSet toUpdate) + { + byte[] roads = map.GetRawRoads(); + + // Step 1: place edges + + int direction = road.Direction; + Console.WriteLine(direction); + int length = road.Length; + Point startPos = new Point(road.StartX, road.StartY); + + Point step = WLStep[direction]; // Direction to move each length unit. + Point subOff = WLSubOff[direction]; // Direction to place the sub segment of the wall + + byte mainSeg = (byte)WLMainSeg[direction]; + byte subSeg = (byte)WLSubSeg[direction]; + + bool erase = road.Delete; + + Point pos = startPos + WLStartOff[direction]; + + for (int i = 0; i < length; i++) + { + Point subPos = pos + subOff; + + if (erase) + { + roads[GetOffset(map, pos)] &= (byte)~mainSeg; + roads[GetOffset(map, subPos)] &= (byte)~subSeg; + } + else + { + roads[GetOffset(map, pos)] |= mainSeg; + roads[GetOffset(map, subPos)] |= subSeg; + } + + pos += step; + } + + // Step 2: recalculate corners (extends 1 further out into the road direction on both sides) + int cornerLength = length + 2; + Point cornerPos = startPos + WLStartOff[direction] - step; + + for (int i = 0; i < cornerLength; i++) + { + Point subPos = cornerPos + subOff; + + RecalculateCorner(map, roads, cornerPos); + RecalculateCorner(map, roads, subPos); + + // These still trigger updates even when the road isn't updated + RegisterRoadUpdates(reservedTiles, toUpdate, cornerPos); + RegisterRoadUpdates(reservedTiles, toUpdate, subPos); + + cornerPos += step; + } + + map.SetDirty(CityMapAspects.Road); + + return true; + } + + private static Span GetPaintAspect(CityMap map, CityEditPaintType type) + { + return type switch + { + CityEditPaintType.TerrainType => System.Runtime.InteropServices.MemoryMarshal.Cast(map.TerrainType), + CityEditPaintType.ForestType => System.Runtime.InteropServices.MemoryMarshal.Cast(map.ForestTypeData), + CityEditPaintType.ForestDensity => map.ForestDensityData, + _ => null + }; + } + + private static CityMapAspects GetPaintDirtyAspect(CityEditPaintType type) + { + return type switch + { + CityEditPaintType.TerrainType => CityMapAspects.TerrainType, + CityEditPaintType.ForestType => CityMapAspects.Forest, + CityEditPaintType.ForestDensity => CityMapAspects.Forest, + _ => CityMapAspects.None + }; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RegisterTerrainUpdates(HashSet reservedTiles, HashSet toUpdate, Point pos, bool forUndo) + { + // Terrain modifications update all adjacent tiles, as it could affect the blend colour of the lot. + + if (reservedTiles != null) + { + uint id = GetMapCoord(pos); + uint skip = 1u << 16; + + RegisterUpdate(reservedTiles, toUpdate, id); + + if (!forUndo) + { + RegisterUpdate(reservedTiles, toUpdate, id - 1); + RegisterUpdate(reservedTiles, toUpdate, id + 1); + + RegisterUpdate(reservedTiles, toUpdate, (id - 1) - skip); + RegisterUpdate(reservedTiles, toUpdate, id - skip); + RegisterUpdate(reservedTiles, toUpdate, id + 1 - skip); + + RegisterUpdate(reservedTiles, toUpdate, (id - 1) + skip); + RegisterUpdate(reservedTiles, toUpdate, id + skip); + RegisterUpdate(reservedTiles, toUpdate, id + 1 + skip); + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool ReservedOrOob(CityEditBitmap reserved, int x, int y) + { + return !MapCoordinates.InBounds((ushort)x, (ushort)y) || reserved.IsSet(x, y); + } + + public static bool ApplyPaint(CityMap map, CityEditPaint paint, HashSet reservedTiles, HashSet toUpdate, bool forUndo) + { + var reserved = GetReservedBitmap(map, paint); + var bitmap = paint.Bitmap; + var value = paint.Value; + Span aspect = GetPaintAspect(map, paint.Type); + + bool isTerrainType = paint.Type == CityEditPaintType.TerrainType; + ForestType[] forestType = map.GetRawForestType(); + byte[] forestDensity = map.GetRawForestDensity(); + + bool anyChanged = false; + foreach (var line in bitmap.GetSetLines()) + { + int x = line.x + bitmap.X; + int y = line.y + bitmap.Y; + int mapIndex = (y * map.Width) + x; + + for (int i = 0; i < line.count; i++) + { + if (!ReservedOrOob(reserved, x, y)) + { + ref var existing = ref aspect[mapIndex]; + + if (value != existing) + { + anyChanged = true; + if (isTerrainType) + { + RegisterTerrainUpdates(reservedTiles, toUpdate, new Point(x, y), forUndo); + + if (value == (byte)TerrainType.WATER) + { + forestType[mapIndex] = ForestType.NULL; + forestDensity[mapIndex] = 0; + } + } + + existing = value; + } + } + + mapIndex++; + x++; + } + } + + if (anyChanged) + { + map.SetDirty(GetPaintDirtyAspect(paint.Type)); + } + + return true; + } + + public static bool ApplyForest(CityMap map, CityEditForest paint) + { + var reserved = GetReservedBitmap(map, paint); + var erasing = paint.Erasing; + var bitmap = paint.Bitmap; + var intensity = paint.Intensities; + var newType = (ForestType)paint.ForestType; + + var forestType = map.ForestTypeData; + var forestDensity = map.ForestDensityData; + var terrainType = map.TerrainType; + + byte maxDensity = 4; + + bool anyChanged = false; + foreach (var line in bitmap.GetSetLines()) + { + int deltaIndex = (line.y * bitmap.Width) + line.x; + + int x = line.x + bitmap.X; + int y = line.y + bitmap.Y; + int mapIndex = (y * map.Width) + x; + + for (int i = 0; i < line.count; i++) + { + if (!ReservedOrOob(reserved, x++, y)) + { + ref var existingTerrain = ref terrainType[mapIndex]; + ref var existingType = ref forestType[mapIndex]; + ref var existingDensity = ref forestDensity[mapIndex]; + var newDensity = (byte)Math.Min(Math.Min(maxDensity, intensity[deltaIndex]) * 64, 255); + + if (erasing) + { + anyChanged = true; + if (newDensity >= existingDensity) + { + existingDensity = 0; + existingType = ForestType.NULL; + } + else + { + existingDensity -= newDensity; + } + } + else + { + if (newDensity >= existingDensity && existingTerrain != TerrainType.WATER) + { + anyChanged = true; + existingDensity = newDensity; + existingType = newType; + } + } + } + + deltaIndex++; + mapIndex++; + } + } + + if (anyChanged) + { + map.SetDirty(CityMapAspects.Forest); + } + + return true; + } + + private static bool OverThreshold(int value, int min, int blend, int index) + { + if (value >= min) + { + if (value < min + blend) + { + // Use the noise to determine if the threshold is met. + var tileNoise = Noise[index]; + var pct = ((value - min) * 255) / blend; + + return pct > tileNoise; + } + else + { + return true; + } + } + + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void RegisterAltitudeUpdates(HashSet reservedTiles, HashSet toUpdate, Point pos) + { + // Altitude modifications happen on the top left vertex of a lot, + // So they also affect the lots up and to the left (including diagonal + + if (reservedTiles != null) + { + uint id = GetMapCoord(pos); + uint skip = 1u << 16; + + RegisterUpdate(reservedTiles, toUpdate, id - 1); + RegisterUpdate(reservedTiles, toUpdate, id); + + RegisterUpdate(reservedTiles, toUpdate, (id - 1) - skip); + RegisterUpdate(reservedTiles, toUpdate, id - skip); + } + } + + public static bool ApplyAltitude(CityMap map, CityEditAltitude altEdit, HashSet reservedTiles, HashSet toUpdate, bool forUndo) + { + var reserved = GetReservedBitmapAlt(map, altEdit); + var bitmap = altEdit.Bitmap; + var deltas = altEdit.AltitudeDeltas; + var auto = altEdit.AutoTerrainType; + byte[] altitudes = map.GetRawElevation(); + + if (bitmap != null) + { + bool anyData = false; + int height = bitmap.Height; + foreach (var line in bitmap.GetSetLines()) + { + int deltaIndex = (line.y * bitmap.Width) + line.x; + + int x = line.x + bitmap.X; + int y = line.y + bitmap.Y; + int mapIndex = (y * map.Width) + x; + + for (int i = 0; i < line.count; i++) + { + if (!ReservedOrOob(reserved, x, y)) + { + RegisterAltitudeUpdates(reservedTiles, toUpdate, new Point(x, y)); + anyData = true; + ref var alt = ref altitudes[mapIndex]; + alt = (byte)Math.Clamp(alt + deltas[deltaIndex], 0, 255); + } + + mapIndex++; + deltaIndex++; + x++; + } + } + + if (anyData) + { + map.SetDirty(CityMapAspects.Elevation); + } + + if (auto) + { + int AltScale = 4; + + // Any modified tile is changed to non-water. + // Starting with sand, at each minimum height we start selecting another tile. + int AutoGrassMin = 2 * AltScale - 2; + int AutoRockMin = 100 * AltScale; + int AutoSnowMin = 190 * AltScale; + + // The blend region introduces some dithering when transitioning between terrain type regions. + // For example, after AutoRockMin we gradually introduce more rock until AutoRockMin + AutoRockBlend, where it becomes all rock. + int AutoGrassBlend = 6; + int AutoRockBlend = 50 * AltScale; + int AutoSnowBlend = 20 * AltScale; + + // When a tile is too steep, it automatically becomes rock. + int AutoRockSteepness = 8; + int AutoRockSteepnessBlend = 2; + + TerrainType[] type = map.GetRawTerrain(); + anyData = false; + + foreach (var line in bitmap.GetSetLines()) + { + int deltaIndex = (line.y * bitmap.Width) + line.x; + + int x = line.x + bitmap.X; + int y = line.y + bitmap.Y; + int mapIndex = (y * map.Width) + x; + + int lineX = line.x; + int nextLineY = line.y + 1; + bool hasNextLine = line.y + 1 < height; + + for (int i = 0; i < line.count; i++) + { + if (i < line.count - 1 && hasNextLine && bitmap.IsSet(lineX + 1, nextLineY) && !ReservedOrOob(reserved, x, y)) + { + var alt1 = altitudes[mapIndex]; + var alt2 = altitudes[mapIndex + map.Width]; + var alt3 = altitudes[mapIndex + 1]; + var alt4 = altitudes[mapIndex + 1 + map.Width]; + + var avg4 = alt1 + alt2 + alt3 + alt4; + var min = Math.Min(alt1, Math.Min(alt2, Math.Min(alt3, alt4))); + var max = Math.Max(alt1, Math.Max(alt2, Math.Max(alt3, alt4))); + + var delta = max - min; + + ref TerrainType existingType = ref type[mapIndex]; + + TerrainType tileType; + + if (OverThreshold(delta, AutoRockSteepness, AutoRockSteepnessBlend, mapIndex)) + { + tileType = TerrainType.ROCK; + } + else + { + if (OverThreshold(avg4, AutoGrassMin, AutoGrassBlend, mapIndex)) + { + avg4 += delta * 8; + if (OverThreshold(avg4, AutoRockMin, AutoRockBlend, mapIndex)) + { + if (OverThreshold(avg4, AutoSnowMin, AutoSnowBlend, mapIndex)) + { + tileType = TerrainType.SNOW; + } + else + { + tileType = TerrainType.ROCK; + } + } + else + { + tileType = TerrainType.GRASS; + } + } + else + { + tileType = TerrainType.SAND; + } + } + + if (existingType != tileType) + { + anyData = true; + RegisterTerrainUpdates(reservedTiles, toUpdate, new Point(x, y), forUndo); + existingType = tileType; + } + } + + mapIndex++; + lineX++; + x++; + } + } + + if (anyData) + { + map.SetDirty(CityMapAspects.TerrainType); + } + } + } + + return true; + } + } +} diff --git a/TSOClient/FSO.Common.Domain/Realestate/CityUndoStack.cs b/TSOClient/FSO.Common.Domain/Realestate/CityUndoStack.cs new file mode 100644 index 000000000..6e9e38cbd --- /dev/null +++ b/TSOClient/FSO.Common.Domain/Realestate/CityUndoStack.cs @@ -0,0 +1,89 @@ +using FSO.Server.Protocol.Electron.Model.CityEditCommands; + +namespace FSO.Common.Domain.Realestate +{ + public class CityUndoStack + { + private uint AvatarID = uint.MaxValue; + private readonly List UndoStack = []; + private readonly Stack RedoStack = []; + private readonly HashSet ExpectedRedo = []; + + public event Action UndoChanged; + + public void WatchAvatar(uint avatarID, List history) + { + AvatarID = avatarID; + + ExpectedRedo.Clear(); + UndoStack.Clear(); + RedoStack.Clear(); + + foreach (var item in history) + { + AddCommand(item); + } + } + + public bool CanUndo() + { + return UndoStack.Count > 0; + } + + public bool CanRedo() + { + return RedoStack.Count > 0; + } + + public int? Undo() + { + if (!CanUndo()) + { + return null; + } + + // Tell the city we want to undo the last command. + return UndoStack.Last().UserModId; + } + + public CityEditBase Redo() + { + if (!CanRedo()) + { + return null; + } + + // Resubmit the command. + var redo = RedoStack.Pop(); + + ExpectedRedo.Add(redo.UserModId); + + return redo; + } + + public void AddCommand(CityEditBase command) + { + if (command.AvatarId == AvatarID) + { + if (!ExpectedRedo.Contains(command.UserModId)) + { + RedoStack.Clear(); + } + UndoStack.Add(command); + UndoChanged?.Invoke(); + } + } + + public void HandleUndo(CityEditBase command) + { + // If this undo is ours, put the undo command on the redo stack, and remove it from the undo stack. + + if (command.AvatarId == AvatarID) + { + UndoStack.RemoveAt(UndoStack.Count - 1); + RedoStack.Push(command); + UndoChanged?.Invoke(); + } + } + } +} diff --git a/TSOClient/FSO.Common.Domain/Realestate/IRealestateDomain.cs b/TSOClient/FSO.Common.Domain/Realestate/IRealestateDomain.cs index 17ebc3546..d9be6135f 100644 --- a/TSOClient/FSO.Common.Domain/Realestate/IRealestateDomain.cs +++ b/TSOClient/FSO.Common.Domain/Realestate/IRealestateDomain.cs @@ -7,5 +7,6 @@ public interface IRealestateDomain IShardRealestateDomain GetByShard(int shardId); bool ValidateLotName(string name); + void Reset(); } } diff --git a/TSOClient/FSO.Common.Domain/Realestate/IShardRealestateDomain.cs b/TSOClient/FSO.Common.Domain/Realestate/IShardRealestateDomain.cs index 986457e62..baf80076e 100644 --- a/TSOClient/FSO.Common.Domain/Realestate/IShardRealestateDomain.cs +++ b/TSOClient/FSO.Common.Domain/Realestate/IShardRealestateDomain.cs @@ -1,12 +1,26 @@ -using FSO.Content.Model; +using FSO.Common.Domain.Realestate; +using FSO.Content.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; namespace FSO.Common.Domain.RealestateDomain { public interface IShardRealestateDomain { + int ID { get; } + bool Dynamic { get; } + CityUndoStack UndoStack { get; } + event Action OnMapChange; int GetPurchasePrice(ushort x, ushort y); + bool IsOpenable(ushort x, ushort y); bool IsPurchasable(ushort x, ushort y); int GetSlope(ushort x, ushort y); CityMap GetMap(); + CityInitResponse GetInit(); + int AppendCommand(CityEditBase command, HashSet reservedTiles = null, HashSet toUpdate = null); + bool SetMyTempCommand(CityEditBase command); + bool HandleUserCommand(CityUpdateCommand command, HashSet reservedTiles = null, HashSet toUpdate = null, HashSet blockedTiles = null); + void TrackUndo(uint avatarId); } -} +} \ No newline at end of file diff --git a/TSOClient/FSO.Common.Domain/Realestate/MapCoordinates.cs b/TSOClient/FSO.Common.Domain/Realestate/MapCoordinates.cs index c6d6e2453..9e1047d88 100644 --- a/TSOClient/FSO.Common.Domain/Realestate/MapCoordinates.cs +++ b/TSOClient/FSO.Common.Domain/Realestate/MapCoordinates.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using System.Runtime.CompilerServices; namespace FSO.Common.Domain.Realestate { @@ -18,29 +19,19 @@ public static MapCoordinate Offset(MapCoordinate coord, int offsetX, int offsetY return new MapCoordinate((ushort)(coord.X - offsetY), (ushort)(coord.Y + offsetX)); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InBounds(ushort x, ushort y){ return InBounds(x, y, 0); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool InBounds(ushort x, ushort y, ushort padding) { if (y < padding) { return false; } if (y > (511 - padding)) { return false; } - - var xStart = 0; - var xEnd = 0; - if (y < 306){ - xStart = 306 - y; - }else{ - xStart = y - 306; - } - - if (y < 205){ - xEnd = 307 + y; - }else{ - xEnd = 512 - (y - 205); - } + int xStart = y < 306 ? 306 - y : (y - 306); + int xEnd = y < 205 ? 307 + y : (512 - (y - 205)); if (x < xStart + padding) { return false; } if (x > xEnd - padding) { return false; } @@ -69,6 +60,12 @@ public MapCoordinate(ushort x, ushort y) Y = y; } + public MapCoordinate(Point point) + { + X = (ushort)point.X; + Y = (ushort)point.Y; + } + public ushort X; public ushort Y; @@ -76,5 +73,10 @@ public Vector2 ToVector2() { return new Vector2(X, Y); } + + public Point ToPoint() + { + return new Point(X, Y); + } } } diff --git a/TSOClient/FSO.Common.Domain/Realestate/RealestateDomain.cs b/TSOClient/FSO.Common.Domain/Realestate/RealestateDomain.cs index 6c5533118..812ae72f8 100644 --- a/TSOClient/FSO.Common.Domain/Realestate/RealestateDomain.cs +++ b/TSOClient/FSO.Common.Domain/Realestate/RealestateDomain.cs @@ -2,8 +2,9 @@ using FSO.Common.Domain.Shards; using FSO.Content.Model; using FSO.Server.Protocol.CitySelector; -using System; -using System.Collections.Generic; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; using System.Text.RegularExpressions; namespace FSO.Common.Domain.Realestate @@ -27,8 +28,9 @@ public RealestateDomain(IShardsDomain shards, FSO.Content.Content content) _Shards = shards; _Content = content; _ByShard = new Dictionary(); - - foreach(var item in shards.All){ + + foreach (var item in shards.All) + { GetByShard(item.Id); } } @@ -43,7 +45,8 @@ public IShardRealestateDomain GetByShard(int shardId) } var shard = _Shards.GetById(shardId); - var item = new ShardRealestateDomain(shard, this._Content.CityMaps.Get(shard.Map)); + var map = _Shards.GetMapForId(shardId); + var item = new ShardRealestateDomain(shard, map); _ByShard.Add(shardId, item); return item; } @@ -63,6 +66,14 @@ public bool ValidateLotName(string name) } return true; } + + public void Reset() + { + lock (_ByShard) + { + _ByShard.Clear(); + } + } } public class ShardRealestateDomain : IShardRealestateDomain @@ -70,22 +81,84 @@ public class ShardRealestateDomain : IShardRealestateDomain private LotPricingStrategy _Pricing; private CityMap _Map; + public int ID { get; private set; } + public bool Dynamic => true; + public CityUndoStack UndoStack { get; private set; } = new CityUndoStack(); + private CityMap _BaseMap; + private CityMap _PreTempMap; + private CityMap _UndoWorkingMap; + private Rectangle? _TempChangeBounds; + + private List _Commands = []; + private CityEditBase _MyTempCommand; + private List _TempCommands = []; + + private Task CompressedBaseData; + + public event Action OnMapChange; + public ShardRealestateDomain(ShardStatusItem shard, CityMap map) { _Map = map; + ID = shard.Id; + if (Dynamic) + { + _Map = new(map); + _BaseMap = new(map); + + CompressedBaseData = Task.Run(CompressMap); + } //TODO: Hardcore _Pricing = new BasicLotPricingStrategy(); } + private CityMap GetUndoWorkingMap() + { + if (_UndoWorkingMap == null) + { + _UndoWorkingMap = new CityMap(_Map); + } + else + { + _UndoWorkingMap.Set(_Map); + _UndoWorkingMap.ConsumeDirty(); + } + + return _UndoWorkingMap; + } + public int GetPurchasePrice(ushort x, ushort y) { return _Pricing.GetPrice(_Map, x, y); } + public bool IsOpenable(ushort x, ushort y) + { + // Can't open lots out of bounds. + if (!MapCoordinates.InBounds(x, y, 1)) + { + //Out of bounds! + return false; + } + + // All-water lots have nowhere for players to stand. + var terrain = _Map.GetTerrain(x, y); + if (terrain == TerrainType.WATER) { + // Only openable if any side of the terrain has a road. + // TODO: When the terrain restore supports putting the mailbox on corners, allow those too. + + var road = _Map.GetRoad(x, y); + return (road & 0xF) != 0; + } + + return true; + } + public bool IsPurchasable(ushort x, ushort y) { //Cant buy lots on the very edge - if(!MapCoordinates.InBounds(x, y, 1)){ + if (!MapCoordinates.InBounds(x, y, 1)) + { //Out of bounds! return false; } @@ -122,5 +195,264 @@ public CityMap GetMap() { return _Map; } + + private byte[] CompressMap() + { + return _BaseMap.Save().Write(); + } + + private byte[] GetCompressedBaseData() + { + return CompressedBaseData.Result; + } + + public CityInitResponse GetInit() + { + return new CityInitResponse() + { + CityData = GetCompressedBaseData(), + Commands = [.. _Commands.Select(x => new CityEditCommand(x))] + }; + } + + public int AppendCommand(CityEditBase command, HashSet reservedTiles = null, HashSet toUpdate = null) + { + if (_TempChangeBounds != null) + { + // Undo any temp changes so we can apply the command for real + _Map.Set(_PreTempMap); + } + + // When a command appears for real, remove it from the temp command set. + _TempCommands.RemoveAll(x => x.AvatarId == command.AvatarId && x.UserModId == command.UserModId); + + if (!CityMapUtils.ValidateCommand(_Map, command)) + { + return -1; + } + + UndoStack.AddCommand(command); + + int index = _Commands.Count; + + _Commands.Add(command); + + CityMapUtils.ApplyCommand(_Map, command, reservedTiles, toUpdate); + + if (OnMapChange != null) + { + var bound = CityMapUtils.GetBounds(_Map, command); + + if (bound != null) + { + _PreTempMap?.Set(_Map); + ApplyTempCommands(bound); + } + } + + return index; + } + + /// + /// Set the temp command for this client (modifications the client is performing). + /// + /// + /// True when all temp commands are valid + public bool SetMyTempCommand(CityEditBase command) + { + bool redraw = true; + if (command == null) + { + redraw = _TempCommands.Remove(_MyTempCommand); + } + else + { + if (_MyTempCommand != null) + { + SetMyTempCommand(null); + } + + var matching = _TempCommands.FindIndex(x => x.AvatarId == command.AvatarId && x.UserModId == command.UserModId); + + if (matching != -1) + { + _TempCommands[matching] = command; + } + else + { + _TempCommands.Add(command); + } + } + + _MyTempCommand = command; + + if (redraw) + { + return ApplyTempCommands(); + } + + return true; + } + + private void RedrawAll() + { + _Map.Set(_BaseMap); + + Rectangle? tempBounds = null; + foreach (var cmd in _Commands) + { + if (CityMapUtils.ApplyCommand(_Map, cmd)) + { + var modBounds = CityMapUtils.GetBounds(_Map, cmd); + + tempBounds = Union(tempBounds, modBounds); + } + } + + // TODO: combine all bounds before with all bounds now to get the range to invalidate. + + _PreTempMap?.Set(_Map); + ApplyTempCommands(new Rectangle(0, 0, 512, 512)); + } + + public bool HandleUserCommand(CityUpdateCommand command, HashSet reservedTiles = null, HashSet toUpdate = null, HashSet blockedTiles = null) + { + switch (command.Mode) + { + case CityUpdateCommandMode.Undo: + // Find the command with the given owner and ID, and undo it. + var toUndo = _Commands.FindIndex(cmd => cmd.AvatarId == command.AvatarID && cmd.UserModId == command.TargetUID); + + if (toUndo != -1) + { + UndoStack.HandleUndo(_Commands[toUndo]); + + // Replay the commands til we get to the undo command + + _Map.Set(_BaseMap); + + CityMapAspects undoAspects = CityMapAspects.None; + Rectangle? undoBounds = null; + for (int i = 0; i < _Commands.Count; i++) + { + var cmd = _Commands[i]; + bool isUndo = i == toUndo; + var map = isUndo ? GetUndoWorkingMap() : _Map; + + if (CityMapUtils.ApplyCommand(map, cmd, isUndo ? reservedTiles : null, isUndo ? toUpdate : null, isUndo)) + { + var modBounds = CityMapUtils.GetBounds(_Map, cmd); + + if (isUndo) + { + undoBounds = Union(undoBounds, modBounds); + } + } + + if (isUndo) + { + // Determine if we're meant to skip this command or not... + if (blockedTiles != null && blockedTiles.Count > 0 && toUpdate.Count > 0) + { + var intersect = blockedTiles.Intersect(toUpdate); + + if (intersect.Any()) + { + // We can't undo this command without modifying a blocked tile. + // Clear the undo and replay the remaining commands + // (including the one we tried to undo, by deliberately not incrementing i) + toUndo = -1; + i--; + continue; + } + } + + undoAspects |= map.ConsumeDirty(); + } + } + + _PreTempMap?.Set(_Map); + ApplyTempCommands(toUndo == -1 ? null : undoBounds); + + if (toUndo == -1) + { + return false; + } + else + { + _Commands.RemoveAt(toUndo); + _Map.SetDirty(undoAspects); + } + + return true; + } + break; + } + + return false; + } + + private Rectangle? Union(Rectangle? first, Rectangle? second) + { + if (!first.HasValue) + { + return second; + } + else if (!second.HasValue) + { + return first; + } + else + { + return Rectangle.Union(first.Value, second.Value); + } + } + + public bool ApplyTempCommands(Rectangle? bounds = null) + { + // If we don't have a pre-temp copy, make it now. + if (_PreTempMap == null && _TempCommands.Count > 0) + { + _PreTempMap = new(_Map); + } + + // If there were previous temp changes, roll them back so we can apply the new ones + if (_TempChangeBounds != null) + { + bounds = Union(bounds, _TempChangeBounds); + _Map.Set(_PreTempMap); + } + + bool allTempValid = true; + + Rectangle? tempBounds = null; + foreach (var temp in _TempCommands) + { + bool valid = CityMapUtils.ValidateCommand(_Map, temp); + if (valid && CityMapUtils.ApplyCommand(_Map, temp)) + { + var modBounds = CityMapUtils.GetBounds(_Map, temp); + + tempBounds = Union(tempBounds, modBounds); + } + + allTempValid = allTempValid && valid; + } + + bounds = Union(tempBounds, bounds); + if (bounds != null) + { + OnMapChange?.Invoke(bounds.Value); + } + + _TempChangeBounds = tempBounds; + + return allTempValid; + } + + public void TrackUndo(uint avatarId) + { + UndoStack.WatchAvatar(avatarId, _Commands); + } } -} +} \ No newline at end of file diff --git a/TSOClient/FSO.Common.Domain/Shards/ClientShards.cs b/TSOClient/FSO.Common.Domain/Shards/ClientShards.cs index 646510617..989bbe072 100644 --- a/TSOClient/FSO.Common.Domain/Shards/ClientShards.cs +++ b/TSOClient/FSO.Common.Domain/Shards/ClientShards.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Linq; +using FSO.Content.Model; using FSO.Server.Protocol.CitySelector; namespace FSO.Common.Domain.Shards { public class ClientShards : IShardsDomain { + private Dictionary CustomMapsByShard = []; public int? CurrentShard { get; set; } public List All @@ -22,5 +22,22 @@ public ShardStatusItem GetByName(string name) { return All.FirstOrDefault(x => x.Name == name); } + + public void SetShardMapBase(int id, CityMapMarshal marshal) + { + CustomMapsByShard[id] = new CityMap(marshal); + } + + public CityMap GetMapForId(int id) + { + var shard = GetById(id); + + if (shard.Map.StartsWith("dynamic")) + { + return CustomMapsByShard[id]; + } + + return FSO.Content.Content.Get().CityMaps.Get(shard.Map); + } } } diff --git a/TSOClient/FSO.Common.Domain/Shards/IShardsDomain.cs b/TSOClient/FSO.Common.Domain/Shards/IShardsDomain.cs index 67ca6dca9..adabdd42d 100644 --- a/TSOClient/FSO.Common.Domain/Shards/IShardsDomain.cs +++ b/TSOClient/FSO.Common.Domain/Shards/IShardsDomain.cs @@ -1,4 +1,5 @@ -using FSO.Server.Protocol.CitySelector; +using FSO.Content.Model; +using FSO.Server.Protocol.CitySelector; using System.Collections.Generic; namespace FSO.Common.Domain.Shards @@ -8,6 +9,7 @@ public interface IShardsDomain List All { get; } ShardStatusItem GetById(int id); ShardStatusItem GetByName(string name); + CityMap GetMapForId(int id); int? CurrentShard { get; } } } diff --git a/TSOClient/FSO.Common.Domain/app.config b/TSOClient/FSO.Common.Domain/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Common.Domain/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Common.Domain/packages.config b/TSOClient/FSO.Common.Domain/packages.config deleted file mode 100644 index 42e065dc9..000000000 --- a/TSOClient/FSO.Common.Domain/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/Audio/fsosamples.dat b/TSOClient/FSO.Content.TSO/Content/Audio/fsosamples.dat new file mode 100644 index 000000000..99e16d98a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Audio/fsosamples.dat differ diff --git a/TSOClient/FSO.Content.TSO/Content/Avatar/Animations/a2o-kart-go-out.b1df3f4100000007.anim b/TSOClient/FSO.Content.TSO/Content/Avatar/Animations/a2o-kart-go-out.b1df3f4100000007.anim index 2212a1823..827233882 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Avatar/Animations/a2o-kart-go-out.b1df3f4100000007.anim and b/TSOClient/FSO.Content.TSO/Content/Avatar/Animations/a2o-kart-go-out.b1df3f4100000007.anim differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0100/info.cst b/TSOClient/FSO.Content.TSO/Content/Cities/city_0100/info.cst new file mode 100644 index 000000000..cafb97eac --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/Cities/city_0100/info.cst @@ -0,0 +1,2 @@ +1 ^Sunrise Crater^ +2 ^After the Test Center disaster of 2007, the city remained barren and lifeless for a whole decade, with onlookers merely biding their time for better days. However, not everyone was satisfied with standing on the sidelines, and many attempts were made to restore the city to its former glory. Years of redevelopment finally culminated in the bustling city you see today, with tight-knit neighbourhoods and a well connected road network. Monsoons, heatwaves, snowstorms, language inhibiting brain diseases... Sunrise Crater thrived through it all. Just don't mention the 8th December 2024 to anyone.^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/elevation.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/elevation.png new file mode 100644 index 000000000..05195c6ff Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/elevation.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/forestdensity.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/forestdensity.png new file mode 100644 index 000000000..471dc9b5b Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/forestdensity.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/foresttype.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/foresttype.png new file mode 100644 index 000000000..f00a05453 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/foresttype.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/info.cst b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/info.cst new file mode 100644 index 000000000..4cecd667c --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/info.cst @@ -0,0 +1,2 @@ +1 ^Flat Grass^ +2 ^Want a blank canvas to build whatever you want? Flat grass is about as blank as you can get - get started using the City Builder or go straight to building property.^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/roadmap.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/roadmap.png new file mode 100644 index 000000000..471dc9b5b Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/roadmap.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/terraintype.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/terraintype.png new file mode 100644 index 000000000..22ec6c475 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/terraintype.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/thumbnail.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/thumbnail.png new file mode 100644 index 000000000..189550163 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/thumbnail.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/vertexcolor.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/vertexcolor.png new file mode 100644 index 000000000..e7a5d4349 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0900/vertexcolor.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/elevation.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/elevation.png new file mode 100644 index 000000000..05195c6ff Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/elevation.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/forestdensity.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/forestdensity.png new file mode 100644 index 000000000..471dc9b5b Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/forestdensity.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/foresttype.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/foresttype.png new file mode 100644 index 000000000..28c10d2ce Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/foresttype.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/info.cst b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/info.cst new file mode 100644 index 000000000..a72c9fe75 --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/info.cst @@ -0,0 +1,2 @@ +1 ^Empty Ocean^ +2 ^Want a blank canvas to build whatever you want? An empty ocean is unreasonably blank - place some buildable ground using the City Builder... or stay in city view forever.^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/roadmap.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/roadmap.png new file mode 100644 index 000000000..471dc9b5b Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/roadmap.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/terraintype.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/terraintype.png new file mode 100644 index 000000000..7d0d90fa4 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/terraintype.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/thumbnail.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/thumbnail.png new file mode 100644 index 000000000..7d9fd4ec2 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/thumbnail.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/vertexcolor.png b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/vertexcolor.png new file mode 100644 index 000000000..c47f2b357 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Cities/city_0901/vertexcolor.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/FSODataDefinition.dat b/TSOClient/FSO.Content.TSO/Content/FSODataDefinition.dat index 574e6472c..9cd51dd08 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/FSODataDefinition.dat and b/TSOClient/FSO.Content.TSO/Content/FSODataDefinition.dat differ diff --git a/TSOClient/FSO.Content.TSO/Content/Objects/Halloween_Station.iff b/TSOClient/FSO.Content.TSO/Content/Objects/Halloween_Station.iff index 7ece3e373..3b2f3d386 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Objects/Halloween_Station.iff and b/TSOClient/FSO.Content.TSO/Content/Objects/Halloween_Station.iff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Objects/amogus.iff b/TSOClient/FSO.Content.TSO/Content/Objects/amogus.iff index f304f76ce..e7ff3f34c 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Objects/amogus.iff and b/TSOClient/FSO.Content.TSO/Content/Objects/amogus.iff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Objects/catalog_downloads.xml b/TSOClient/FSO.Content.TSO/Content/Objects/catalog_downloads.xml index be6cfd6dc..7d9482a3c 100644 --- a/TSOClient/FSO.Content.TSO/Content/Objects/catalog_downloads.xml +++ b/TSOClient/FSO.Content.TSO/Content/Objects/catalog_downloads.xml @@ -1,15 +1,24 @@ - + + + -

-

-

-

-

-

-

-

-

+

+

+

+

+

+

+

+

+

-

-

-

-

+

+

+

+

@@ -87,25 +96,25 @@ -

+

-

-

-

-

-

-

-

-

-

+

+

+

+

+

+

+

+

+

-

-

-

-

-

-

+

+

+

+

+

+

-

+

-

-

-

-

-

-

-

-

- -

-

-

+

+

+

+

+

+

+

+

+ +

+

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/computers5_break.piff b/TSOClient/FSO.Content.TSO/Content/Patch/computers5_break.piff new file mode 100644 index 000000000..32569fb38 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Patch/computers5_break.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/global.piff b/TSOClient/FSO.Content.TSO/Content/Patch/global.piff index 32e7f90fa..3e8da826e 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Patch/global.piff and b/TSOClient/FSO.Content.TSO/Content/Patch/global.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/jukebox.piff b/TSOClient/FSO.Content.TSO/Content/Patch/jukebox.piff new file mode 100644 index 000000000..8767c0313 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Patch/jukebox.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-controller_uninit_local.piff b/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-controller_uninit_local.piff index f8804249b..3b8b577e2 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-controller_uninit_local.piff and b/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-controller_uninit_local.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-ticketholder.piff b/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-ticketholder.piff new file mode 100644 index 000000000..e111e4b2f Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/Patch/oj-rest-ticketholder.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/Patch/skillobjects_10xmultiplier.piff b/TSOClient/FSO.Content.TSO/Content/Patch/skillobjects_10xmultiplier.piff index c29b77646..af71a58af 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/Patch/skillobjects_10xmultiplier.piff and b/TSOClient/FSO.Content.TSO/Content/Patch/skillobjects_10xmultiplier.piff differ diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uiscripts/archivepersonselection1024.uis b/TSOClient/FSO.Content.TSO/Content/UI/uiscripts/archivepersonselection1024.uis new file mode 100644 index 000000000..52686bb2e --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uiscripts/archivepersonselection1024.uis @@ -0,0 +1,247 @@ +# UI properties for Archive Person Selection window + + + + # Images --------------------------------------------------------------------------------------- + + # ./uigraphics/personselection/1024-768frame.bmp + + #./uigraphics/personselection/person_select_tabsback.bmp + #./uigraphics/personselection/person_select_descriptionback.bmp + #./uigraphics/personselection/person_select_descriptiontab.bmp + #./uigraphics/personselection/person_select_descriptiontabbtn.bmp + #./uigraphics/personselection/person_select_entertabbtn.bmp + #./uigraphics/personselection/person_select_icontab.bmp + #./uigraphics/personselection/person_select_iconsindents.bmp + #./uigraphics/personselection/person_select_exitBtn.bmp"> + #./uigraphics/personselection/person_select_simcreatebtn.bmp + #./uigraphics/personselection/person_select_simselectbtn.bmp + #./uigraphics/gizmo/gizmo_scrollbarimg.bmp + #./uigraphics/personselection/person_select_arrowdownbtn.bmp + #./uigraphics/personselection/person_select_arrowupbtn.bmp + #./uigraphics/personselection/cas-sas-templatehouse.bmp + #./uigraphics/personselection/cas-sas-templatecity.bmp + #./uigraphics/personselection/person_select_cityhouseiconalpha.tga + + #./uigraphics/personselection/cas-sas-creditsbtn.bmp + #./uigraphics/personselection/cas-sas-creditsindent.bmp + #./uigraphics/personselection/person_select_cityhouseiconbusy.tga + + + #./uigraphics/personselection/person_edit_acceptbtn.bmp + + # Strings -------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # Images --------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # Buttons -------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + # Text Labels -------------------------------------------------------------------------------------------- + + + + + + + + # description + + + + + + + + + + # people 3d views ----------------------------------------------------------------------------------------------------- + + + + + + + + # Scrollbars ----------------------------------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + # Title Text ------------------------------------------------------------------------------------------------------------ + + + + # City Name Labels ------------------------------------------------------------------------------------------------------ + + + + + + + + + + # Credits Button --------------------------------------------------------------------------------------------------------- + + + + + # CAS Button ----------------------------------------------------------------------------------------------------------- + + + + diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f100_genericstrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f100_genericstrings.cst index dc8490315..365c25553 100644 --- a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f100_genericstrings.cst +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f100_genericstrings.cst @@ -4,4 +4,9 @@ 4 ^Reconnecting to City Server... (%s/10)^ 5 ^Reconnecting to Lot Server... (%s/10)^ 6 ^3D Meshes Generating... (%s in queue)^ -7 ^Direct Control (F10)^ \ No newline at end of file +7 ^Direct Control (F10)^ +8 ^Downloading City Data...^ +9 ^Downloaded City Data! Finishing up...^ +10 ^Overall Progress^ +11 ^Current Task^ +12 ^Downloading 3D Meshes... (%s%)^ diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f101_updaterstrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f101_updaterstrings.cst index a0f620bbf..f926ade07 100644 --- a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f101_updaterstrings.cst +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f101_updaterstrings.cst @@ -30,7 +30,7 @@ Remember that you should only install updates and content packs from servers you (you can ignore this by pressing the cheat combo from TS1, but it is DEV ONLY as your game will likely just crash.)^ 16 ^A patch is available to convert your TSO version into the correct one. It will be applied if you click OK. (only perform this operation once)^ -17 ^Your version could not be identified, so the FreeSO client does not know what to do with it. Please visit http://freeso.org for the correct way to download TSO files. (usually via a launcher)^ +17 ^Your version could not be identified, so the FreeSO client does not know what to do with it. Please visit https://freeso.org for the correct way to download TSO files. (usually via a launcher)^ 18 ^Patching TSO Files...^ 19 ^Fatal Error^ @@ -54,11 +54,82 @@ Remember that you should only install updates and content packs from servers you 26 ^(current) ^ 27 ^Obtaining Update Info...^ -28 ^The file %s could not be downloaded or was empty/corrupted. You will now return to the login screen.^ +28 ^The file %s could not be downloaded or was empty/corrupted.^ 29 ^ Manifest^ 30 ^Updater failed!^ 31 ^The updater could not be opened. If you have anti-virus software, try disabling real-time protection or adding an exception for FreeSO.exe and update.exe. If you still experience problems, open up update.exe from the FreeSO folder manually. (as administrator if you have installed in Program Files) Error Message: -%s^ \ No newline at end of file +%s^ + +// Updater V2 (freeso archive) + +32 ^The %s update server couldn't be contacted.^ +33 ^%s is up to date!^ +34 ^An update is available for %s: (%s)^ + +35 ^Ignore^ +36 ^Download & Install (%s)^ +37 ^Continue^ + +40 ^This server is using a different version of FreeSO that isn't a direct update: + +Channel: %s +Update: %s + +^ + +41 ^This server's version is available for download. Do you want to update?^ +42 ^The update download couldn't be located - you may need to download this game version manually from the server host.^ +43 ^This server's version is available, but is older than your current game version. Do you want to downgrade?^ +44 ^Cancel^ + +50 ^[color=red][s]This update is NOT signed by FreeSO.[/s][/color] +Make sure that you fully trust this server before updating to this game version.^ +51 ^This update is NOT from the same channel as your client, but is signed. You could be switching to a beta or experimental client, so make sure to read the changelog for important information.^ + +52 ^Unverified Game Version^ +53 ^Confirm update to this unverified game version? You may need to reinstall FreeSO to revert it.^ + +54 ^The update hashes could not be verified - the update server may be configured incorrectly.^ +55 ^Auto Updater^ + +56 ^[color=red][s]This update's signature does NOT match your client.[/s][/color] +Make sure that you fully trust this server before updating to this game version.^ +57 ^[color=red][s]This update is not signed.[/s][/color] +Make sure that you fully trust this server before updating to this game version.^ +58 ^This update is on a different server from your current update, but still has a matching signature.^ + +59 ^Up to date^ +60 ^New update available! (%s)^ +61 ^A new update is available for download (%s). Do you want to update?^ +62 ^Fetching update data...^ +63 ^Update check failed^ +64 ^Couldn't reach the update server at: + +%s + +Check your internet connection, or for any information from the server owner.^ +65 ^You're currently up to date on the following update channel: + +Channel: %s +Update: %s + +FreeSO will check for updates when it starts, or if a server you're connecting to has a different game version.^ + +66 ^3D Mode Remesh Update^ +67 ^The FreeSO remesh package has received an update. The remesh package contains replacement 3D models and textures for objects from The Sims Online to improve the visuals in 3D mode. + +Would you like to download it now?^ + +68 ^Download 3D Mode Remesh Package^ +69 ^The FreeSO remesh package contains replacement 3D models and textures for objects from The Sims Online to improve the visuals in 3D mode. + +Would you like to download it now?^ + +70 ^Your version of the FreeSO remesh package is up to date.^ +71 ^Downloading 3D Mode Remeshes...^ + +72 ^Development Version^ +73 ^This version of the client doesn't have an update channel.^ diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f107_custombuildstrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f107_custombuildstrings.cst index 6a8688d60..ca153fd3f 100644 --- a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f107_custombuildstrings.cst +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f107_custombuildstrings.cst @@ -1,6 +1,7 @@ 1 ^Terrain Tool^ 2 ^Flatten Tool^ 3 ^Grass Tool^ +4 ^Debug Objects (dangerous!)^ 101 ^Use this tool to create interesting landscapes, which will impress visitors and make the local council mad. Click on the corner of a tile and drag to change its height. This may raise additional tiles around the initial target to keep terrain from becoming too steep. diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f128_archivestrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f128_archivestrings.cst new file mode 100644 index 000000000..bd51a1753 --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f128_archivestrings.cst @@ -0,0 +1,295 @@ +1 ^Download Required^ +2 ^The selected save "%s" has missing data that can be downloaded from %s. This download is approximately %s, and extracts to be %s. Would you like to download the save data?^ +3 ^No Data^ +4 ^The selected save is missing its data, so the server cannot be started.^ +5 ^Downloading Archive Data^ +6 ^Corrupt Data^ +7 ^Extracting Data^ +8 ^The archive data is corrupted, so the server cannot be started.^ +9 ^Delete data^ +10 ^Download Failed^ +11 ^Unable to download the archive data. Check your internet connection and try again, or contact the provider of the archive data.^ +12 ^Extracting %s... (%s/%s)^ +13 ^Scanning %s... Might take some time. (%s files found)^ +14 ^UPnP^ +15 ^Attempting to forward ports with UPnP...^ +16 ^UPnP Failed^ +17 ^Failed to forward ports with UPnP! Either enable UPnP on your router, or disable the UPnP setting and set the server ports manually. + +For more information, see https://freeso.org/port-forwarding .^ +18 ^Disable UPnP to manually set ports.^ +19 ^Server Information^ +20 ^You're connected to a server hosted by this game client.^ +21 ^You're connected to a server hosted by another game client.^ +22 ^You're connected to a dedicated server.^ +23 ^You're offline. + +To connect with other players, select "Host Server" on the menu and uncheck "Offline Mode".^ +24 ^If the game client hosting this server closes, everyone will disconnect!^ +25 ^!! View Server IP !!^ +26 ^Public IP:^ +27 ^(fetching public ip...)^ +28 ^Private IPs:^ +29 ^(local network)^ +30 ^(VPN)^ +31 ^(Hamachi)^ +32 ^(ZeroTier)^ +33 ^Copy^ +34 ^Copied to clipboard!^ +35 ^(could not determine IP - are you offline?)^ + +36 ^Export Config^ +37 ^Exports a config.json file that can be used by FSO.Server.Core to run a standalone version of the Archive server. + +If you use this on a different system, you will need to copy the archive data (usually in Content/), TSO game data and possibly adjust paths in the config.json.^ +38 ^Use absolute path for archive data^ +39 ^Use absolute path for TSO game data^ +40 ^Export^ +41 ^Export Error^ +42 ^Couldn't export config to "%s". Make sure FreeSO has the permission to write to the specified location.^ +43 ^Config successfully exported to "%s".^ +44 ^Path:^ +45 ^Config successfully exported to "%s". + +This path has been copied to your clipboard.^ +46 ^Can't export configuration without preparing the archive data first. Try running the server within the game client first.^ +47 ^Success^ + +48 ^User List^ +49 ^Username^ +50 ^Avatar Count^ +51 ^Status^ +52 ^View Avatars^ +53 ^Show IP^ +54 ^Ban User^ +55 ^Unban User^ + +56 ^IP Bans^ + +57 ^Avatars owned by %s^ +58 ^Name^ +59 ^Lot^ + +60 ^IP^ +61 ^Associated Accounts^ +62 ^Unban^ +63 ^Ban IP^ + +64 ^Search: ^ +65 ^Delete Avatar^ +66 ^Delete User^ + +67 ^Enter an IPv4 address to ban.^ +68 ^Are you sure you want to unban IP %s? This will also unban any user accounts that last used this IP.^ + +69 ^Are you sure you want to delete the sim "%s"? This will remove everything that they own, and might also delete properties if their removal leaves no roommates.^ + +70 ^User "%s"'s IP is %s. + +IP has been copied to your clipboard.^ +71 ^Are you sure you want to unban the user "%s"? This will also unban the last IP that used this account.^ +72 ^Are you sure you want to ban the user "%s"? This will also ban the last IP that used this account.^ +73 ^Are you sure you want to delete the user "%s"? This will transfer any avatars they own to the archive user.^ + +74 ^Transfer to User^ +75 ^Select a user to transfer the sim %s to.^ +76 ^Transfer^ +77 ^Are you sure you want to transfer the sim %s to %s? This may affect some user generated content belonging to this avatar.^ + +78 ^An unknown error occurred.^ + +79 ^Start^ + +80 ^Banned^ +81 ^Your user account/IP has been banned from this server. Contact the host to have the ban removed.^ +82 ^Invalid Name^ +83 ^Your display name should not be empty, at most 64 characters, and not contain certain special characters.^ +84 ^Display Name Taken^ +85 ^The provided display name is already in use. Please choose another one. + +If you're the user of this display name, you should either copy the configuration ini files from the installation you previously used to log into this server, or join with another display name and get the server owner to migrate any avatars to your new account.^ +86 ^Invalid Client ID^ +87 ^The server couldn't read your client ID. This may be a problem with the server or client key configuration.^ +88 ^Encryption Failure^ +89 ^The client and server were unable to exchange authentication keys. The public/private key configuration on the server may be incorrect.^ +90 ^Encryption Failure^ +91 ^Your client didn't respond correctly to the encryption challenge. Not sure how you'd get here.^ +92 ^Verification Rejected^ +93 ^An admin or moderator has rejected your verification request.^ + +96 ^This client is already hosting a server. Please select an option:^ +97 ^User Management^ +98 ^Close Server^ +99 ^Join Server^ + +100 ^Unable to select Avatar^ +101 ^The specified avatar was not found. Maybe it was just deleted?^ +102 ^You don't have permission to play as this avatar.^ +103 ^This avatar is currently in use by a client with the same ID (likely yourself), and the server was unable to fully disconnect the avatar to give it to this client. Please try again later.^ +104 ^This avatar is currently in use by another user. Please try again later.^ +105 ^An unknown error occurred while selecting that avatar.^ + +110 ^Enable Discord Game Invites^ +111 ^Invites are now enabled. Sending a Discord game invite to someone will supply them with the public IP of this server, so be careful who you send an invite to. +You can send an invite from the + menu that appears next to the chat input.^ +112 ^Discord Game Invites are Enabled^ +113 ^Unable to enable invites. This could be due to an issue with discord RPC, or the hostname is too complex.^ +114 ^This invite is for a FreeSO-based server, and you're in archive mode. Please find the appropriate client for the server you're connecting to.^ +115 ^This invite is for a different game server than the one you're currently on, and the person who sent the invite has not enabled server invites.^ +116 ^This invite is for a different game server than the one you're currently on. Would you like to switch servers?^ +117 ^Joining a server from Discord^ +118 ^Getting server status... Please wait.^ +119 ^Unable to enable invites, as the address for this server could not be determined.^ +120 ^This invite to a specific property needs you to be on the same game server as the inviter, and they haven't enabled server invites.^ + +121 ^The city editor allows any player with administrator rights to modify the city while it's open, including terraforming and drawing roads. Changes to the city terrain are included with the archive save. You can see changes from other players in realtime, so feel free to collaborate with friends. + +Enabling the city editor and starting the server will convert the save to use a Dynamic city, meaning that clients will download the city map from the server when they join rather than load it from their game files. This may drastically change the appearance of cities that have artistic changes to their vertex color images (as these are now generated by FreeSO), such as Fancey Fields and Dragon's Cove. This will persist even when the city editor is disabled, so make sure you're fine with it first.^ + +122 ^FreeSO Credits^ +123 ^Switch to The Sims Online original credits^ +124 ^Switch to FreeSO credits^ +125 ^View FreeSO/The Sims Online Credits^ + +126 ^FreeSO inherits The Sims Online's gameplay, which was a classic MMO with a glacial pace of progression intended to make it hard to reach "the end". Archive mode has a different focus - pop-up gameplay sessions with small groups of people with much quicker gameplay progression. Of course, you can still configure things back to the original tuning if you want to replicate the MMO experience, or you have a VERY large group of players (in this case, maybe a full scale server would be more appropriate). + +- "Skill speed" affects how quickly skills are gained. This affects the base rate - the percentage will appear the same. Note that skilling times still scale nonlinearly. + +- "Payout multiplier" affects simoleon payouts from single/group job objects, as well as payouts from the Restaurant, Robot Factory and Club jobs. + +- "Singleplayer penalty" affects how much slower skilling or making money by yourself is. TSO encouraged groups of at least 12 on skill/money lots by decreasing payouts and skill speeds below that. 100% is the full penalty that TSO applied, whereas 0% will make it equivalent to the player cap being reached. Note that penalties for being on the wrong lot type or not having many skilling stations still apply.^ + +127 ^Help^ +128 ^Reset^ + +129 ^Reset to the original TSO tuning, or recommended FreeSO Archive tuning?^ +130 ^The Sims Online^ +131 ^FreeSO Archive^ + +132 ^Server Type^ +133 ^Name^ +134 ^Version^ +135 ^Players^ +136 ^Forget^ +137 ^Copy IP^ +138 ^(offline)^ +139 ^Refresh^ + +140 ^Archive Server^ +141 ^FreeSO Server^ +142 ^Discord Server^ + +143 ^Display Name^ +144 ^Enter a display name to use on archive servers. When joining certain servers, you might need an admin to verify you, so use a display name that makes it clear who you are.^ +145 ^Display name:^ +146 ^Change Display Name^ + +147 ^Add Server^ +148 ^Enter the IP address or URL of the server. You may need to include the port.^ +149 ^Found server: %s (%s). +Add to the server list?^ +150 ^Contacting server...^ +151 ^Failed to contact the server.^ +152 ^Unable to join a server from a Discord invite. Make sure the host has either enabled UPnP or forwarded the server ports manually.^ + +153 ^FreeSO has detected that you're short on disk space on the installation drive (%s needed, %s available). It's likely that it will run out of disk space while downloading and extracting this archive save. Are you sure you want to continue?^ +154 ^Low Disk Space^ + + +200 ^Offline Mode^ +201 ^Use UPnP^ +202 ^Require user verification^ +203 ^City editor^ +204 ^Free roam^ +205 ^Debug interactions^ +206 ^Allow lot creation^ +207 ^Allow character creation^ +208 ^Lock archived characters^ +209 ^Hide display names^ + +211 ^UPnP attempts to automatically forward ports on your router to allow public access to your game server. Some routers have this disabled by default or simply don't support it, in which case you'll need to uncheck this option and manually forward the ports.^ +212 ^Without user verification, any user with the server IP can connect and join the city - authentication is automatic. Users can be banned by client and IP, but new users are always given the benefit of the doubt. + +When this option is enabled, new users will require verification from an admin or mod before they can interact with the game server. You can verify users from the User List ingame, which appears at the bottom left in the UCP. The button will start flashing if there are any pending verifications.^ +214 ^The original game server only allowed offline lots to be opened by their owner or roommates, and empty lots couldn't be joined at all. When this option is set to true, any player can join any tile on the map. + +This option will also allow players to travel seamlessly between properties by clicking on adjacent lots, or walking over the property boundary in direct control mode. Any sims present on adjacent lots also become visible.^ +215 ^When enabled, the archive server gives admins the ability to spawn all debug objects from build mode and use debug interactions. These objects/interactions allow users to cheat money/skill, but can potentially cause object errors crash the game. If you want a 'safe' selection of debug objects for all players, you can enable the 'debug' catalog in the events configuration. +These interactions can optionally be enabled for moderators and all other users.^ +218 ^The archive server allows players to enter the game as any character that has been archived, or create their own. When this option is set to true, only admins and mods can enter the game as archived characters - normal users must create their own.^ + + +220 ^Mods^ +221 ^All users^ + + +230 ^Server name:^ +231 ^Export Config^ +232 ^Users^ +233 ^Ports^ +234 ^Events^ +235 ^Cheats^ + +240 ^Quick Start^ +241 ^Host Server^ +242 ^Host or join a server to get started.^ +243 ^Quick Start will begin a singleplayer session of the last used archive data.^ + +250 ^Custom Ports^ +251 ^Choose TCP ports for the server. For public access, these ports should be forwarded in your router settings.^ +252 ^City: ^ +253 ^Lot: ^ + +260 ^Archive Server^ +261 ^Starting archive server. Please wait...^ +262 ^Safely shutting down archive server before closing.^ + +270 ^User List^ +271 ^Kick %s^ +272 ^Are you sure you want to kick %s from the server?^ +273 ^Ban %s^ +274 ^Are you sure you want to ban %s from the server? They won't be able to rejoin from the same client or IP, until they are manually unbanned from the users list.^ +275 ^Are you sure you want to ban %s from the server? They won't be able to apply for verification from the same client or IP, until they are manually unbanned from the users list.^ +276 ^User^ +277 ^Moderator^ +278 ^Administrator^ +279 ^Are you sure you want change %s from %s to %s?^ +280 ^Approve^ +281 ^Reject^ +282 ^Ban^ +283 ^Make Admin^ +284 ^Make Moderator^ +285 ^Revoke Admin/Mod^ +286 ^Kick^ +287 ^User List (%s)^ + +300 ^Skill/Money Cheats^ +301 ^Increase the speed of skilling or the payout of money objects. +Some objects may not correctly report multiplied payouts - keep an eye on your balance.^ +302 ^Starting funds: ^ +303 ^Skill speed: ^ +304 ^Payout multiplier: ^ +305 ^Singleplayer Penalty: ^ +306 ^Speedy Job Progression^ +307 ^One full day of work at any job promotes you to the next level, as long as you have the skill. No friend requirements.^ + +310 ^Events^ +311 ^Event schedule:^ +312 ^Timed^ +313 ^Manual^ +314 ^Clear^ +315 ^Simulate Timed^ + +320 ^Save name:^ +321 ^New Save^ +322 ^Description:^ +323 ^Unknown City^ +324 ^This city data is missing info.cst, so it doesn't have a name or description.^ +325 ^Unknown error^ +326 ^Failed to create the save file from template.^ + +330 ^Loading...^ +331 ^Waiting for verification...^ +332 ^Search^ +333 ^Name^ +334 ^Lot^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f129_servermessages.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f129_servermessages.cst new file mode 100644 index 000000000..ac4cd3ef0 --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f129_servermessages.cst @@ -0,0 +1,2 @@ +1 ^Banned^ +2 ^Your user account/IP has been banned from this server. Contact the host to have the ban removed.^ diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f130_cityeditorstrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f130_cityeditorstrings.cst new file mode 100644 index 000000000..dcae0e20f --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f130_cityeditorstrings.cst @@ -0,0 +1,43 @@ +1 ^Edit City^ + +2 ^Elevation^ +3 ^Terrain Type^ +4 ^Roads^ +5 ^Trees^ + +6 ^Undo^ +7 ^Redo^ +8 ^Update City Thumbnail^ +9 ^Lock Lot Terrain^ +10 ^Brush Size^ +11 ^Intensity^ +12 ^Spray Brush^ +13 ^Invert brush (or hold ctrl)^ +14 ^(inverted)^ +15 ^Close^ +16 ^City Editor^ +17 ^Click and drag to draw roads.^ +18 ^Unlock Lot Terrain^ +19 ^Close^ + +20 ^Auto Terrain Type^ +21 ^Flatten^ +22 ^Natural Edge^ + +30 ^Grass^ +31 ^Water^ +32 ^Rock^ +33 ^Snow^ +34 ^Sand^ + +40 ^Heavy Forest^ +41 ^Light Forest^ +42 ^Cacti^ +43 ^Palm^ + +50 ^Modified by %s^ +51 ^Rename City^ +52 ^Please enter a new name for the city. This name can be changed by anyone with access to the City Editor, at any time.^ +53 ^Can't modify open lots or out of bounds tiles.^ +54 ^Cannot undo as terrain around open lots would be changed.^ +55 ^Can't modify lot terrain when it's locked.^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f131_downloaderstrings.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f131_downloaderstrings.cst new file mode 100644 index 000000000..0cfad201a --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f131_downloaderstrings.cst @@ -0,0 +1,35 @@ +1 ^Download The Sims Online^ +2 ^Welcome to FreeSO! + +The Sims Online's game files are required to play, but were not found on this computer. You can download and install The Sims Online automatically from archive.org. This requires around 2.8GB of space, though only 1.6 GB will be used at the end of installation. + +If TSO is installed on this computer, copy the path that contains TSOClient into the box below.^ +3 ^Path:^ +4 ^Download URL:^ +5 ^Quit^ +6 ^Download The Sims Online^ + +7 ^Downloading The Sims Online (1/3)^ +8 ^Extracting The Sims Online (2/3)^ +9 ^Installing The Sims Online (3/3)^ +10 ^(%s/%s)^ + +11 ^The Sims Online could not be downloaded. Ensure you have a stable internet connection and try again.^ +12 ^The Sims Online could not be extracted. The downloaded file is likely corrupted - ensure you have a stable internet connection and try again. Exception: %s^ +13 ^The Sims Online could not be installed. Exception: %s^ +14 ^The Sims Online has been successfully installed! The game will now restart.^ +15 ^OK^ + +16 ^Couldn't write to the target directory. If you want to install The Sims Online in a protected directory such as Program Files, you need to run the game as Administrator.^ +17 ^Ran out of disk space. You should free up at least 3GB of disk space before installing The Sims Online, or install to a another drive that has enough free space.^ +18 ^FreeSO has detected that you're short on disk space on the target installation drive (under 3GB). It's likely that it will run out of disk space while installing the game. Are you sure you want to continue?^ +19 ^The Sims Online was detected in the specified folder. Do you want to use this installation for FreeSO?^ +20 ^Update Registry^ +21 ^FreeSO Only^ +22 ^Yes^ +23 ^Redownload^ +24 ^Cancel^ +25 ^The Sims Online Located^ + +26 ^Use Existing Installation^ +27 ^No^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f132_jobnames.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f132_jobnames.cst new file mode 100644 index 000000000..bcebfe5eb --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f132_jobnames.cst @@ -0,0 +1,41 @@ +0 ^Heckhol Industrial^ +1 ^Heckhol Industrial^ +2 ^Heckhol Industrial^ +3 ^Futurematic Automatons^ +4 ^Futurematic Automatons^ +5 ^Futurematic Automatons^ +6 ^Futurematic Automatons^ +7 ^Technocracy Technologies^ +8 ^Technocracy Technologies^ +9 ^Technocracy Technologies^ +10 ^Technocracy Technologies^ + +100 ^Nate's Lunchatorium^ +101 ^Nate's Lunchatorium^ +102 ^Nate's Lunchatorium^ +103 ^McDingus's^ +104 ^McDingus's^ +105 ^McDingus's^ +106 ^McDingus's^ +107 ^Walt's^ +108 ^Walt's^ +109 ^Walt's^ +110 ^Walt's^ + +200 ^Trader Jimmy's Tiki Lounge^ +201 ^Trader Jimmy's Tiki Lounge^ +202 ^Trader Jimmy's Tiki Lounge^ +203 ^Luxopolis^ +204 ^Luxopolis^ +205 ^Luxopolis^ +206 ^Luxopolis^ +207 ^209 RSP^ +208 ^209 RSP^ +209 ^209 RSP^ +210 ^209 RSP^ + +1000 ^%s Job +--- +%s (Level %s) +%s +%s^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f200_freesocredits.cst b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f200_freesocredits.cst new file mode 100644 index 000000000..0a8b6f7ae --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/UI/uitext/english.dir/_f200_freesocredits.cst @@ -0,0 +1,725 @@ +// FreeSO Credits String Set (using an extension of the TSO credits) + +// Scroll speed - ( ~ 10 slow - 100 fast ) + +^21^ + +// Line format: +// +// NewLine - starts a new line with the following arguments: +// +// |line height|font size|font color +// +// LineEntry - adds a text to be displayed on the line with arguments: +// +// |Center, Left, Right or integer to specify left offset|text|optional underline color +// +// ObjectFile - Adds credits for an object file. It finds all guids with a valid CTSS and adds a line for each. +// +// |Filename|Optional comma separated GUID whitelist (empty means all, "x" means none)|Optional comma separated list of object names (to add to the end) +// +// RemeshPackage - Automatically generates remesh package credits from the installed remesh pack. (no arguments) + +// ------------------------------- FreeSO ---------------------------------- + +^NewLine|25|13|247,232,145^ +^LineEntry|Center|FreeSO|247,232,145^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Project Lead|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|riperiperi^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Client & Server Framework|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|ddfczm^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Art|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|S1ndle^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|SimAntics Plugins|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|TheArchitectFreeSO^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|FreeSO Launcher|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|ItsSim^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|.NET 9 Port|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|SegerEnd^ +^NewLine|25|10|180,210,226^ + +// -- code contributors from github -- + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Code Contributors|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|Andrew Knoll^ +^LineEntry|Center|Cowplant-Simmer-Collin^ +^LineEntry|Right|thertzelle^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|LazyDuchess^ +^LineEntry|Center|JDrocks450^ +^LineEntry|Right|dotequals^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|alexjyong^ +^LineEntry|Center|francot514^ +^LineEntry|Right|fHachenberg^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|Cosmatevs^ +^LineEntry|Center|katemi^ +^LineEntry|Right|inb40^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|simptomo^ +^LineEntry|Center|AstroPsynapse^ +^LineEntry|Right|Bxil^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|Itja^ +^LineEntry|Center|Bengrs^ + +^NewLine|10|10|180,210,226^ + +// ------------------------------- Objects ---------------------------------- + +^NewLine|25|10|180,210,226^ + +^NewLine|25|13|247,232,145^ +^LineEntry|Center|OBJECTS|247,232,145^ +^NewLine|5|7|180,210,226^ + + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|riperiperi|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_lot_link.iff^ +^ObjectFile|fso_hween_skeledeco.iff^ +^ObjectFile|thinking.iff^ +^ObjectFile|ballflood.iff|x|April Fools Ball Flood^ +^ObjectFile|fso_cat_test.iff|x|Controllable Cat^ +^ObjectFile|fso_snowball_controller.iff|x|Snowball Fight Controller^ +^ObjectFile|fso_firewater.iff|x|April Fools Pool Fire^ +^ObjectFile|fso_surrounding_link.iff|x|Free Roam Goto Target^ +^ObjectFile|fso_event_tally.iff|x|Event Tally Digit^ +^ObjectFile|auto_candle.iff|x|Handheld Candle Spawner^ +^ObjectFile|fso_af2020_hat.iff|x|April Fools Hat Tower^ +^ObjectFile|fso_awards_lots.iff|x|2018 AFAs Lot Awards^ +^ObjectFile|Anni2018.iff|x|FreeSO.ml Annivarsary Sculpture^ + +^ObjectFile|npc_fso_grimevent.iff|x|Halloween NPCs,Accessories by S1ndle and Andrew^ +^ObjectFile|npc_fso_halloween2022.iff|x|Halloween 2022 Event^ +^ObjectFile|npc_fso_halloween2024.iff|x|Halloween 2024 Event^ +^ObjectFile|npc_fso_pizzaman.iff|x|Pizzaman NPC^ +^ObjectFile|npc_fso_santa.iff|x|Santa NPC^ +^ObjectFile|npc_fso_skeleton.iff|x|Jack-o-Lantern Skeleton NPC^ +^ObjectFile|npc_fso_snowball.iff|x|Christmas NPCs,Accessories by S1ndle^ +^ObjectFile|npc_fso_soccer.iff|x|Soccer Skeleton NPC^ +^ObjectFile|npc_fso_trapdoor.iff|x|Trapdoor Skeleton NPC^ +^ObjectFile|npc_fso_witch.iff|x|Madge Cal NPC^ + +^ObjectFile|fso_vehicle_controller.iff|x|Drivable Vehicle Controller^ +^ObjectFile|fso_water_balloon_controller.iff|x|Water Balloon Game Controller^ + +^NewLine|18|10|180,210,226^ +^LineEntry|Center|Various object patches for overfill, upgrades and bugfixes.^ +^NewLine|8|10|180,210,226^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|S1ndle (art) & riperiperi (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|event_treasure.iff^ +^ObjectFile|trapdoor_trap.iff^ +^ObjectFile|cage_trap.iff^ +^ObjectFile|floor_trap.iff^ +^ObjectFile|fso_bulletin_board.iff^ +^ObjectFile|fso_candy_dispense.iff^ +^ObjectFile|fso_candy_token.iff|x|Halloween Candy Token^ +^ObjectFile|fso_candy_well.iff|x|Mr Tickles' Well^ +^ObjectFile|fso_halloween_flag.iff^ +^ObjectFile|fso_hween_signs.iff^ +^ObjectFile|fso_halloween_team_token.iff|x|Halloween Team Token^ +^ObjectFile|fso_souls_dispenser.iff|x|Soul Scavenger Hunt Controller^ +^ObjectFile|fso_souls_token.iff|x|Halloween Souls Token^ +^ObjectFile|fso_drivable_broomstick.iff^ +^ObjectFile|fso_drivable_sleigh.iff^ +^ObjectFile|fso_event_prizebooth.iff|x|Event Prize Booth^ +^ObjectFile|fso_snowball.iff|x|Snow Pile,Snowball,Snowball Game Controller^ +^ObjectFile|fso_snowball_event_present.iff|x|Big Present^ +^ObjectFile|fso_christmas_flag.iff^ +^ObjectFile|fso_christmas_team_token.iff|x|Elves vs. Reindeer Token^ +^ObjectFile|fso_vehicle_key_token.iff|x|Vehicle Key^ +^ObjectFile|fso_water_balloon.iff^ +^ObjectFile|fso_rc_car.iff^ +^ObjectFile|fso_escape_room.iff^ +^ObjectFile|fso_game_door.iff^ +^ObjectFile|fso_christmas_bob.iff|x|Snowball Team Plumbbob^ +^ObjectFile|fso_event_immortal.iff^ +^ObjectFile|fso_awards_lotspecial_2022.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|S1ndle (art) & Raeven (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_holiday_EggNog.iff^ +^ObjectFile|deco_daddi_logo.iff^ +^ObjectFile|deco_momi_logo.iff^ +^ObjectFile|lighted_daddi_logo.iff^ +^ObjectFile|lighted_momi_logo.iff^ +^ObjectFile|fso_autumn_portrait.iff^ +^ObjectFile|fso-autumn-witchcauldron.iff^ +^ObjectFile|fso_pumpkinpatch.iff^ +^ObjectFile|food_s1_xmaspudding.iff|x|Christmas Pudding^ +^ObjectFile|painting_s1_AFA2018.iff^ +^ObjectFile|painting_s1_jackpotPoster.iff^ +^ObjectFile|fso_holiday_cookies.iff^ +^ObjectFile|fso_communitygrill.iff^ +^ObjectFile|fso_community_buffettable.iff^ +^ObjectFile|fso_hottub_ballpit.iff^ +^ObjectFile|fso_winter2019_lawnOrnament.iff^ +^ObjectFile|fso_winter2019_stepstones.iff^ +^ObjectFile|fsoDecor-miniMysteryChest.iff^ +^ObjectFile|Casino_2-Tile_Bar_CC.iff^ +^ObjectFile|Casino_Area-3x3_CC.iff^ +^ObjectFile|Casino_Cabinet-Drawer_CC.iff^ +^ObjectFile|Casino_Column_CC.iff^ +^ObjectFile|Casino_End_Display_CC.iff^ +^ObjectFile|Casino_Island_CC.iff^ +^ObjectFile|Casino_Lamps_Dice_3_CC.iff^ +^ObjectFile|Casino_Neon_Wall_CC.iff^ +^ObjectFile|Casino_Runner-1x4_CC.iff^ +^ObjectFile|decoration_taxicab_blue.iff^ +^ObjectFile|decoration_taxicab_yellow.iff^ +^ObjectFile|fso_food_icecreamPlatter.iff^ +^ObjectFile|fso_autumn_MadgeCal_chest.iff^ +^ObjectFile|fso_party-lightstring.iff^ +^ObjectFile|fso_holiday_festivegreenery.iff^ +^ObjectFile|fso_holiday_tipjar.iff^ +^ObjectFile|fso_MrTickles_ThanksYou.iff^ +^ObjectFile|Chair_fso_Bouncy_Beach_Ball.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|S1ndle|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|ToiletHolder.iff^ +^ObjectFile|ToiletStack.iff^ +^ObjectFile|fso_summer_condiments.iff^ +^ObjectFile|fso_decor_2-in-1_Racing_Banner.iff^ +^ObjectFile|fso_endstatue.iff^ +^ObjectFile|fso-halloween-deco-candy-pile.iff^ +^ObjectFile|fso_hballoon_2018.iff^ +^ObjectFile|fso_award_stand.iff^ +^ObjectFile|fso_deco_easteregg.iff^ +^ObjectFile|fso_holiday_mistletoe.iff^ +^ObjectFile|mailbox.spf.piff|x|Halloween Event Mailbox^ +^ObjectFile|leavelot.spf.piff|x|Halloween Event Payphone^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|S1ndle (art) & The Architect (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|slotmachine04_Xtreasure.iff^ +^ObjectFile|slotmachine05_Jackpot.iff^ +^ObjectFile|fso_casino_holdem.iff^ +^ObjectFile|Casino_Sign_CC.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Dica (art) & dotequals (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|momistation.iff^ +^ObjectFile|fso_zombie_spawner.iff^ + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Dica (art) & riperiperi (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|3TileClock.iff^ +^ObjectFile|fso_event_jams.iff|x|Jams vs. Money Token,Uses Dica's Jams Remesh^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Dica (art) & Raeven (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +// S1ndle put the graphics in this iff? +^ObjectFile|fso_winter2019_cocoa.iff^ +^ObjectFile|drain-small.iff^ +^ObjectFile|fso_dead_tree.iff^ +^ObjectFile|fso_autumn_lanterns.iff^ +^ObjectFile|fso_decor-centerpiece_dica.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|jwofles (art) & Raeven (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_decor_poolfloat-duck.iff^ +^ObjectFile|fso_decor_poolfloat-ring.iff^ +^ObjectFile|fso_holiday_craftbench.iff^ +^ObjectFile|fso_holiday_craftnotions.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|dotequals|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_zombie.iff|x|Halloween Zombie NPC^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Toddy|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso-8bit-doormat.iff^ +^ObjectFile|fso-8bit-pride-box.iff^ +^ObjectFile|fso-8bit-pride-cloudlamp.iff^ +^ObjectFile|fso-8bit-pride-counter.iff^ +^ObjectFile|fso-8bit-pride-mirror.iff^ +^ObjectFile|fso-8bit-pride-table.iff^ +^ObjectFile|fso-decor-toddy-bauble.iff^ +^ObjectFile|fso-decor-toddy-Burlap.iff^ +^ObjectFile|fso-decor-toddy-Grmas.iff^ +^ObjectFile|fso-decor-toddy-Red_Bow.iff^ +^ObjectFile|fso-decor-toddy-Wall_Bow.iff^ +^ObjectFile|fso-winter-toddy-baublechest.iff^ +^ObjectFile|fso-winter-toddy-christmas-truck.iff^ +^ObjectFile|fso-winter-toddy-nutcrackertipjar.iff^ +^ObjectFile|fso-gummy-bear.iff^ +^ObjectFile|fso_decor_toddy_sunflowerpot.iff^ +^ObjectFile|fso-summer-pink-limo.iff^ +^ObjectFile|fso-summer-biscuit-rugs.iff^ +^ObjectFile|fso-summer-candy-border.iff^ +^ObjectFile|fso-summer-cookiecakestand-table.iff^ +^ObjectFile|fso-summer-gummybear-tipjar.iff^ +^ObjectFile|fso-summer-heart-lamp-wall.iff^ +^ObjectFile|fso-summer-ice-cream-shrub.iff^ +^ObjectFile|fso-summer-icecream-truck.iff^ +^ObjectFile|fso-summer-lollipop-lamp.iff^ +^ObjectFile|fso-summer-macaron-column.iff^ +^ObjectFile|fso-summer-marshmallow-stool.iff^ +^ObjectFile|fso-summer-milkcarton-tipjar.iff^ +^ObjectFile|fso-summer-star-lamp.iff^ +^ObjectFile|fso_summer-cotton-candy-tree.iff^ +^ObjectFile|fso-spring-butterfly-wall.iff^ +^ObjectFile|fso-spring-fairy-chest.iff^ +^ObjectFile|fso-spring-fairy-statue.iff^ +^ObjectFile|fso-spring-fireflies-light.iff^ +^ObjectFile|fso-spring-floor-lamp.iff^ +^ObjectFile|fso-spring-flower-lamp.iff^ +^ObjectFile|fso-spring-forest-map.iff^ +^ObjectFile|fso-spring-leaf-sign-board.iff^ +^ObjectFile|fso-spring-mushroom-bush.iff^ +^ObjectFile|fso-spring-mushroom-lamp.iff^ +^ObjectFile|fso-spring-mushroom-stool.iff^ +^ObjectFile|fso-spring-mushroom-table.iff^ +^ObjectFile|fso-spring-rock-fence.iff^ +^ObjectFile|fso-spring-scale.iff^ +^ObjectFile|fso-spring-wooden-stepping-stones.iff^ +^ObjectFile|fso-halloween-vultures.iff^ +^ObjectFile|fso-halloween-mushroom-stool.iff^ +^ObjectFile|fso-halloween-mushroom-table.iff^ +^ObjectFile|fso-halloween-skeletonlamp.iff^ +^ObjectFile|fso-halloween-pumpkinfences.iff^ +^ObjectFile|fso-halloween-pumpkin-pot.iff^ +^ObjectFile|fso-halloween-skeleton-ceilinglamp.iff^ +^ObjectFile|fso-halloween-skeletoncandelabra.iff^ +^ObjectFile|fso-halloween-candleborder.iff^ +^ObjectFile|fso-halloween-mirror.iff^ +^ObjectFile|fso-halloween-plant-tipjar.iff^ +^ObjectFile|fso-halloween-truck.iff^ +^ObjectFile|mailbox.spf.piff|x|Summer Event Mailbox^ +^ObjectFile|leavelot.spf.piff|x|Summer Event Payphone^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Toddy (art) & riperiperi (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_music_box.iff^ +^ObjectFile|amogus.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Toddy (art) & Raeven (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fsoFood_HalloweenCake.iff^ +^ObjectFile|fsoFood_HalloweenCupcake.iff^ +^ObjectFile|fso-summer-gumball-float.iff^ +^ObjectFile|fso_table_toddy_ScarefestDinTable.iff^ +^ObjectFile|fso-halloween-scarefest-squaretable.iff^ +^ObjectFile|fso_holiday_toddy_ScarefestSnackTable.iff^ +^ObjectFile|fso_holiday_toddy_SpooktacularTipJar.iff^ +^ObjectFile|fso_chair_toddy-ScarefestDinChair.iff^ +^ObjectFile|fso_event_luckyboxmachine.iff^ +^ObjectFile|fso-summer-massivecake.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Raeven|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|npc_CaretakerAvery.iff|x|Caretaker Avery NPC^ +^ObjectFile|fso_GreenbeardBarrelCollection.iff^ +^ObjectFile|fso_decor_clutter_bones.iff^ +^ObjectFile|TSOPorts/*.piff|x|Fixed unused but included TS1 objects to work in TSO!,(there are a lot of these)^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Heyty|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|heyty_bookcase.iff^ +^ObjectFile|heyty_bookpile.iff^ +^ObjectFile|heyty_coffinjar.iff^ +^ObjectFile|heyty_crystalball.iff^ +^ObjectFile|heyty_old_mop.iff^ +^ObjectFile|heyty_sign.iff^ +^ObjectFile|heyty_sign_tiny.iff^ +^ObjectFile|heyty_vanityitems.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Heyty (art) & Daat (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|cursebook_set_permission.iff^ +^ObjectFile|curse_book_fx.iff|x|Curse Book Special FX^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Mixa97sr|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|Halloween_Station.iff^ +^ObjectFile|Christmas_Station.iff^ +^ObjectFile|GhostLampFloating.iff^ +^ObjectFile|lamp_cicd_snowman.iff^ +^ObjectFile|Lamp_m97s_ChristmasCandle.iff^ +^ObjectFile|fso_holiday_ceiling_lights.iff^ +^ObjectFile|fso_holiday_stringlights.iff^ +^ObjectFile|poolslide.piff|x|Replacement Pool Slide Animation^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Mars (art) & Raeven (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_skill_body_woodChopLog.iff^ +^ObjectFile|fso-mars_spiral-wood-decor.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Andrew Knoll|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso_1000day_prize.iff^ +^ObjectFile|fso_alarm_light.iff^ +^ObjectFile|fso_killer_crypt.iff^ +^ObjectFile|fso_magic_book.iff^ +^ObjectFile|fso-outside-shower.iff^ +^ObjectFile|fso_face_mask_sign.iff^ +^ObjectFile|fso_flags_pride.iff^ +^ObjectFile|sunbathing_towel.iff^ +^ObjectFile|fso_hand_sanitizer.iff^ +^ObjectFile|spring_ceiling_high_pole.iff^ +^ObjectFile|fso_hearts_on_heads_spawner.iff|x|Hearts on Heads^ +^ObjectFile|fso_global_accessory_controller.iff|x|Global Accessory Controller^ +^ObjectFile|fso_hide_and_search_controller.iff|x|Book of Hiding Controller^ +^ObjectFile|fso_duck_trophy.iff|x|Duck Trophy^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Andrew Knoll & jwofles|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|summer_accessories_rack.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Andrew Knoll (art) & The Architect (script)|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|buzzer_game_host.iff^ +^ObjectFile|buzzer_game_podium.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|BrandonSJ96|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|rugs_bsj96_christmas2019.iff^ +^ObjectFile|decor_bsj96_christmasstockings.iff^ +^ObjectFile|diningtable_bsj96_coffin.iff^ +^ObjectFile|bsj96_Table_End_Vacation_CC.iff^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|The Architect|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|roulette-croupiers-fso.iff|x|Roulette Croupier NPC^ +^ObjectFile|blackjack_dealers-fso.iff|x|Blackjack Dealer NPC^ +^ObjectFile|fso_casino_holdem_NPCdealers.iff|x|Casino Hold'em NPC^ +^ObjectFile|blackjack.piff|x|Blackjack Table^ + + +^NewLine|10|12|210,240,250^ +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Collin|210,240,250^ +^NewLine|5|7|180,210,226^ + +^ObjectFile|fso-decor-big-rhysie.iff^ +^ObjectFile|FreeSO_Award_2018_3.iff|x|The Rhysie^ + +^NewLine|23|12|210,240,250^ +^NewLine|13|9|255,255,255^ +^LineEntry|Center|Thanks to Fawn & S1ndle for writing lots of catalog/dialog text!^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|"TSOPorts/" catalog entries were written by forum members.^ + +^NewLine|25|10|180,210,226^ + +// ------------------------------- FreeSO Server ---------------------------------- + +^NewLine|25|13|247,232,145^ +^LineEntry|Center|FreeSO SERVER|247,232,145^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Community Manager|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Vekas^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Event Planning & Content|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|S1ndle^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Amoreena^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Hira^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Fawn^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Toddy^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Andrew Knoll^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Sassylas^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|SimAntics Guru|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Raeven^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Moderation Tooling|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|ItsSim^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Moderation & Team Support|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Nodster^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|tsomatt^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Nahte^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|jwofles^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|JDRocks450^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Hosting & Administration|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Maria & ItsSim [2017, 2018]^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Travis [2017, 2020-2021]^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|MrRobbles [2018-2020]^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|riperiperi [2021-2024]^ +^NewLine|25|10|180,210,226^ + +// ------------------------------- Project Dollhouse ---------------------------------- + +^NewLine|25|10|180,210,226^ + +^NewLine|25|13|247,232,145^ +^LineEntry|Center|PROJECT DOLLHOUSE|247,232,145^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Project Lead|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|Afr0^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|SimAntics & World|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|11|180,210,226^ +^LineEntry|Center|riperiperi^ +^NewLine|25|11|180,210,226^ +^LineEntry|Center|ddfczm^ +^NewLine|25|10|180,210,226^ + +^NewLine|25|12|210,240,250^ +^LineEntry|Center|Code Contributors|210,240,250^ +^NewLine|5|7|180,210,226^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|Fatbag^ +^LineEntry|Center|tonytins^ +^LineEntry|Right|xezno^ + +^NewLine|25|10|180,210,226^ +^LineEntry|Left|DarkKostas^ +^LineEntry|Center|HeyItsTigR^ + +^NewLine|10|10|180,210,226^ + +// ------------------------------- Special Thanks ---------------------------------- + +^NewLine|25|10|180,210,226^ + +^NewLine|25|13|247,232,145^ +^LineEntry|Center|SPECIAL THANKS FROM RHYS|247,232,145^ +^NewLine|5|7|180,210,226^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|The FreeSO Team (again!),^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|for putting up with everything that me and the community threw at you^ + +^NewLine|25|9|255,255,255^ +^LineEntry|Center|Maria & Sim, for putting in a lot of administration effort & funding when I couldn't^ + +^NewLine|25|9|255,255,255^ +^LineEntry|Center|MrRobbles, for providing awesome dedicated server hosting for a few years^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|All of the Maxis team, for taking risks on making games like The Sims,^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|and changing the course of my life^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|The Sims Online team, for making a game that's easy to become obsessed with^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|(check out their credits, too!)^ + +^NewLine|25|9|255,255,255^ +^LineEntry|Center|SimTech, for their invaluable TS1 documentation^ + +^NewLine|25|9|255,255,255^ +^LineEntry|Center|Fatbag, for a lot of TSO specific file format reversing that made FreeSO possible^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|Project Dollhouse & TSOR teams,^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|whose efforts and inspiration carried forward into FreeSO^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|FreeSO.ml staff and players (including the Maria Trio),^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|for giving me the motivation to continue development^ + +^NewLine|13|9|255,255,255^ +^LineEntry|Center|Everyone else who spammed refresh on the FreeSO forums,^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|before Discord ruined everything^ + +^NewLine|25|9|255,255,255^ +^NewLine|25|9|255,255,255^ +^LineEntry|Center|pisarz, for stealing my food^ + +^NewLine|50|8|255,255,255^ + +// ------------------------------- 3D Remesh Package ---------------------------------- + +^RemeshPackage^ + +^NewLine|25|10|180,210,226^ \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/Content/setup.png b/TSOClient/FSO.Content.TSO/Content/setup.png index 7c0f044a9..47076babb 100644 Binary files a/TSOClient/FSO.Content.TSO/Content/setup.png and b/TSOClient/FSO.Content.TSO/Content/setup.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_burgermenu.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_burgermenu.png new file mode 100644 index 000000000..1fdc75936 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_burgermenu.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_casbutton.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_casbutton.png new file mode 100644 index 000000000..ed20e0420 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_casbutton.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_cat_debug.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_cat_debug.png new file mode 100644 index 000000000..602ccdec3 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_cat_debug.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_clientsbtn.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_clientsbtn.png new file mode 100644 index 000000000..76f970f22 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_clientsbtn.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_combobox.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_combobox.png new file mode 100644 index 000000000..e3312b7f9 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_combobox.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configcheats.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configcheats.png new file mode 100644 index 000000000..d1d758202 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configcheats.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configevents.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configevents.png new file mode 100644 index 000000000..0ba6f5628 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configevents.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configexport.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configexport.png new file mode 100644 index 000000000..03275cc3a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configexport.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configports.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configports.png new file mode 100644 index 000000000..03728eefe Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configports.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configusers.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configusers.png new file mode 100644 index 000000000..5f962da37 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_configusers.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discord.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discord.png new file mode 100644 index 000000000..ba8a250ae Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discord.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discordserver.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discordserver.png new file mode 100644 index 000000000..4a6d74cd0 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_discordserver.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_edit.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_edit.png new file mode 100644 index 000000000..16bcd7d03 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_edit.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_hostbtn.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_hostbtn.png new file mode 100644 index 000000000..f3627085b Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_hostbtn.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_joinbtn.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_joinbtn.png new file mode 100644 index 000000000..d79a0dde8 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_joinbtn.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_1x.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_1x.png new file mode 100644 index 000000000..8da2709ea Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_1x.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_2x.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_2x.png new file mode 100644 index 000000000..1156f6354 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_logo_2x.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_quickstartbtn.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_quickstartbtn.png new file mode 100644 index 000000000..9cca0b5dd Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_quickstartbtn.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_sasbg.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_sasbg.png new file mode 100644 index 000000000..f9a459920 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_sasbg.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simowned.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simowned.png new file mode 100644 index 000000000..fd1745e0a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simowned.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simrecent.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simrecent.png new file mode 100644 index 000000000..8b6ac3e1c Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simrecent.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simshared.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simshared.png new file mode 100644 index 000000000..322c4d2bb Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simshared.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simuser.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simuser.png new file mode 100644 index 000000000..a66466cd2 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_simuser.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_tab.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_tab.png new file mode 100644 index 000000000..de3275a8a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_tab.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_translist.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_translist.png new file mode 100644 index 000000000..6fdbbcf77 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_translist.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_useradmin.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_useradmin.png new file mode 100644 index 000000000..5afe739b8 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_useradmin.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_usermod.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_usermod.png new file mode 100644 index 000000000..7cbe3393a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_usermod.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_userverify.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_userverify.png new file mode 100644 index 000000000..927ac6851 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/archive/archive_userverify.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_bg.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_bg.png new file mode 100644 index 000000000..d0f487355 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_bg.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_camera.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_camera.png new file mode 100644 index 000000000..bffcaa46d Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_camera.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_catbutton.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_catbutton.png new file mode 100644 index 000000000..c1150a159 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_catbutton.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_anchor.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_anchor.png new file mode 100644 index 000000000..1ba9e1195 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_anchor.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_base.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_base.png new file mode 100644 index 000000000..5b73c0861 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_base.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_road.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_road.png new file mode 100644 index 000000000..60dfba7e3 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_road.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_roaddel.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_roaddel.png new file mode 100644 index 000000000..86ea3bc80 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_roaddel.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_sel.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_sel.png new file mode 100644 index 000000000..c9e063c10 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_cursor_sel.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_elevation.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_elevation.png new file mode 100644 index 000000000..ceb7db6f3 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_elevation.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_forests.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_forests.png new file mode 100644 index 000000000..4273d82a0 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_forests.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_locked.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_locked.png new file mode 100644 index 000000000..3728a789f Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_locked.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_redo.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_redo.png new file mode 100644 index 000000000..ea32c3c20 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_redo.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_road.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_road.png new file mode 100644 index 000000000..fb3c2fc66 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_road.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_slider.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_slider.png new file mode 100644 index 000000000..55cf20398 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_slider.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_spike.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_spike.png new file mode 100644 index 000000000..d59fa0a59 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_spike.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab1.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab1.png new file mode 100644 index 000000000..8898abbfe Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab1.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab2.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab2.png new file mode 100644 index 000000000..d14bcd194 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab2.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab3.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab3.png new file mode 100644 index 000000000..a53f875d2 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab3.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab4.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab4.png new file mode 100644 index 000000000..d371835b5 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tab4.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_toggle.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_toggle.png new file mode 100644 index 000000000..418ad8f95 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_toggle.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_auto.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_auto.png new file mode 100644 index 000000000..6920c9f95 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_auto.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_cacti.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_cacti.png new file mode 100644 index 000000000..7c88f4037 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_cacti.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_flat.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_flat.png new file mode 100644 index 000000000..38bd2956c Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_flat.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_grass.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_grass.png new file mode 100644 index 000000000..b1e142377 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_grass.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_heavy.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_heavy.png new file mode 100644 index 000000000..28d6ca0ec Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_heavy.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_light.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_light.png new file mode 100644 index 000000000..8f255f725 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_light.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_palm.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_palm.png new file mode 100644 index 000000000..9878273bc Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_palm.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rock.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rock.png new file mode 100644 index 000000000..b060f3c4d Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rock.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rough.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rough.png new file mode 100644 index 000000000..c1150a159 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_rough.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_sand.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_sand.png new file mode 100644 index 000000000..4c1d92d80 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_sand.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_snow.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_snow.png new file mode 100644 index 000000000..3a72c3112 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_snow.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_spray.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_spray.png new file mode 100644 index 000000000..a84ebaba8 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_spray.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_water.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_water.png new file mode 100644 index 000000000..3a71b4d0d Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_tool_water.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_ttype.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_ttype.png new file mode 100644 index 000000000..889de0f9a Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_ttype.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_undo.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_undo.png new file mode 100644 index 000000000..eea0a702c Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_undo.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_unlocked.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_unlocked.png new file mode 100644 index 000000000..1fd0ac5a0 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/cityedit/cityedit_unlocked.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsobutton.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsobutton.png new file mode 100644 index 000000000..de40b031f Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsobutton.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsologo.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsologo.png new file mode 100644 index 000000000..a0bb76152 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_fsologo.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_tsobutton.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_tsobutton.png new file mode 100644 index 000000000..50dc45ae1 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/credits/credits_tsobutton.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e500000002.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e500000002.png new file mode 100644 index 000000000..602e4d3dc Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e500000002.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e600000002.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e600000002.png new file mode 100644 index 000000000..f318beac0 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000000e600000002.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000001e700000001.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000001e700000001.png new file mode 100644 index 000000000..d4aa7fae7 Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000001e700000001.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a400000001.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a400000001.png new file mode 100644 index 000000000..28739b2ff Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a400000001.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a500000001.png b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a500000001.png new file mode 100644 index 000000000..eddd92e4d Binary files /dev/null and b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/0x000007a500000001.png differ diff --git a/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/readme.txt b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/readme.txt new file mode 100644 index 000000000..76ddf4793 --- /dev/null +++ b/TSOClient/FSO.Content.TSO/Content/uigraphics/fallback/readme.txt @@ -0,0 +1 @@ +These files are manual recreations of the core TSO UI graphics, used for when TSO is not installed and the game is downloading/extracting the files. \ No newline at end of file diff --git a/TSOClient/FSO.Content.TSO/FSO.Content.TSO.csproj b/TSOClient/FSO.Content.TSO/FSO.Content.TSO.csproj index 64ac0b1f8..27c4a72b5 100644 --- a/TSOClient/FSO.Content.TSO/FSO.Content.TSO.csproj +++ b/TSOClient/FSO.Content.TSO/FSO.Content.TSO.csproj @@ -1,59 +1,21 @@ - - - + + - Debug - AnyCPU - b5b2c04d-b8e4-47c7-9731-48e30fd5f70d + net9.0 + enable + disable Library - Properties FSO.Content.TSO FSO.Content.TSO - v4.7.2 512 true - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - + PreserveNewest - - - + + diff --git a/TSOClient/FSO.Content.TSO/Properties/AssemblyInfo.cs b/TSOClient/FSO.Content.TSO/Properties/AssemblyInfo.cs deleted file mode 100644 index 5ac655701..000000000 --- a/TSOClient/FSO.Content.TSO/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Content.TSO")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Content.TSO")] -[assembly: AssemblyCopyright("Copyright © 2024")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b5b2c04d-b8e4-47c7-9731-48e30fd5f70d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.IDE/AboutWindow.Designer.cs b/TSOClient/FSO.IDE/AboutWindow.Designer.cs index 012d545be..52b3b7623 100644 --- a/TSOClient/FSO.IDE/AboutWindow.Designer.cs +++ b/TSOClient/FSO.IDE/AboutWindow.Designer.cs @@ -98,8 +98,8 @@ private void InitializeComponent() // // AboutWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(550, 257); this.Controls.Add(this.label5); this.Controls.Add(this.label4); diff --git a/TSOClient/FSO.IDE/App.config b/TSOClient/FSO.IDE/App.config deleted file mode 100644 index cf8f9dc06..000000000 --- a/TSOClient/FSO.IDE/App.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.Designer.cs b/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.Designer.cs index 33cc5f174..4bafd74e9 100644 --- a/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.Designer.cs +++ b/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.Designer.cs @@ -29,163 +29,163 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AddAppearanceWindow)); - this.AddAsLabel = new System.Windows.Forms.Label(); - this.InfoLabel = new System.Windows.Forms.Label(); - this.NameEntry = new System.Windows.Forms.TextBox(); - this.AppearanceRadio = new System.Windows.Forms.RadioButton(); - this.OutfitRadio = new System.Windows.Forms.RadioButton(); - this.HandgroupRadio = new System.Windows.Forms.RadioButton(); - this.NameLabel = new System.Windows.Forms.Label(); - this.HandgroupCombo = new System.Windows.Forms.ComboBox(); - this.HandgroupLabel = new System.Windows.Forms.Label(); - this.SummaryText = new System.Windows.Forms.TextBox(); - this.ImportButton = new System.Windows.Forms.Button(); - this.HeadRadio = new System.Windows.Forms.RadioButton(); - this.SuspendLayout(); + AddAsLabel = new Label(); + InfoLabel = new Label(); + NameEntry = new TextBox(); + AppearanceRadio = new RadioButton(); + OutfitRadio = new RadioButton(); + HandgroupRadio = new RadioButton(); + NameLabel = new Label(); + HandgroupCombo = new ComboBox(); + HandgroupLabel = new Label(); + SummaryText = new TextBox(); + ImportButton = new Button(); + HeadRadio = new RadioButton(); + SuspendLayout(); // // AddAsLabel // - this.AddAsLabel.AutoSize = true; - this.AddAsLabel.Location = new System.Drawing.Point(12, 89); - this.AddAsLabel.Name = "AddAsLabel"; - this.AddAsLabel.Size = new System.Drawing.Size(43, 13); - this.AddAsLabel.TabIndex = 0; - this.AddAsLabel.Text = "Add as:"; + AddAsLabel.AutoSize = true; + AddAsLabel.Location = new Point(12, 89); + AddAsLabel.Name = "AddAsLabel"; + AddAsLabel.Size = new Size(45, 13); + AddAsLabel.TabIndex = 0; + AddAsLabel.Text = "Add as:"; // // InfoLabel // - this.InfoLabel.Location = new System.Drawing.Point(12, 9); - this.InfoLabel.Name = "InfoLabel"; - this.InfoLabel.Size = new System.Drawing.Size(396, 75); - this.InfoLabel.TabIndex = 1; - this.InfoLabel.Text = resources.GetString("InfoLabel.Text"); + InfoLabel.Location = new Point(12, 9); + InfoLabel.Name = "InfoLabel"; + InfoLabel.Size = new Size(396, 75); + InfoLabel.TabIndex = 1; + InfoLabel.Text = resources.GetString("InfoLabel.Text"); // // NameEntry // - this.NameEntry.Location = new System.Drawing.Point(15, 251); - this.NameEntry.Name = "NameEntry"; - this.NameEntry.Size = new System.Drawing.Size(213, 20); - this.NameEntry.TabIndex = 2; - this.NameEntry.TextChanged += new System.EventHandler(this.NameEntry_TextChanged); + NameEntry.Location = new Point(15, 251); + NameEntry.Name = "NameEntry"; + NameEntry.Size = new Size(213, 22); + NameEntry.TabIndex = 2; + NameEntry.TextChanged += NameEntry_TextChanged; // // AppearanceRadio // - this.AppearanceRadio.AutoSize = true; - this.AppearanceRadio.Checked = true; - this.AppearanceRadio.Location = new System.Drawing.Point(64, 87); - this.AppearanceRadio.Name = "AppearanceRadio"; - this.AppearanceRadio.Size = new System.Drawing.Size(83, 17); - this.AppearanceRadio.TabIndex = 3; - this.AppearanceRadio.TabStop = true; - this.AppearanceRadio.Text = "Appearance"; - this.AppearanceRadio.UseVisualStyleBackColor = true; - this.AppearanceRadio.CheckedChanged += new System.EventHandler(this.AppearanceRadio_CheckedChanged); + AppearanceRadio.AutoSize = true; + AppearanceRadio.Checked = true; + AppearanceRadio.Location = new Point(64, 87); + AppearanceRadio.Name = "AppearanceRadio"; + AppearanceRadio.Size = new Size(86, 17); + AppearanceRadio.TabIndex = 3; + AppearanceRadio.TabStop = true; + AppearanceRadio.Text = "Appearance"; + AppearanceRadio.UseVisualStyleBackColor = true; + AppearanceRadio.CheckedChanged += AppearanceRadio_CheckedChanged; // // OutfitRadio // - this.OutfitRadio.AutoSize = true; - this.OutfitRadio.Location = new System.Drawing.Point(153, 87); - this.OutfitRadio.Name = "OutfitRadio"; - this.OutfitRadio.Size = new System.Drawing.Size(50, 17); - this.OutfitRadio.TabIndex = 4; - this.OutfitRadio.Text = "Outfit"; - this.OutfitRadio.UseVisualStyleBackColor = true; - this.OutfitRadio.CheckedChanged += new System.EventHandler(this.OutfitRadio_CheckedChanged); + OutfitRadio.AutoSize = true; + OutfitRadio.Location = new Point(153, 87); + OutfitRadio.Name = "OutfitRadio"; + OutfitRadio.Size = new Size(56, 17); + OutfitRadio.TabIndex = 4; + OutfitRadio.Text = "Outfit"; + OutfitRadio.UseVisualStyleBackColor = true; + OutfitRadio.CheckedChanged += OutfitRadio_CheckedChanged; // // HandgroupRadio // - this.HandgroupRadio.AutoSize = true; - this.HandgroupRadio.Location = new System.Drawing.Point(266, 87); - this.HandgroupRadio.Name = "HandgroupRadio"; - this.HandgroupRadio.Size = new System.Drawing.Size(78, 17); - this.HandgroupRadio.TabIndex = 5; - this.HandgroupRadio.Text = "Handgroup"; - this.HandgroupRadio.UseVisualStyleBackColor = true; - this.HandgroupRadio.CheckedChanged += new System.EventHandler(this.HandgroupRadio_CheckedChanged); + HandgroupRadio.AutoSize = true; + HandgroupRadio.Location = new Point(266, 87); + HandgroupRadio.Name = "HandgroupRadio"; + HandgroupRadio.Size = new Size(85, 17); + HandgroupRadio.TabIndex = 5; + HandgroupRadio.Text = "Handgroup"; + HandgroupRadio.UseVisualStyleBackColor = true; + HandgroupRadio.CheckedChanged += HandgroupRadio_CheckedChanged; // // NameLabel // - this.NameLabel.AutoSize = true; - this.NameLabel.Location = new System.Drawing.Point(12, 235); - this.NameLabel.Name = "NameLabel"; - this.NameLabel.Size = new System.Drawing.Size(38, 13); - this.NameLabel.TabIndex = 6; - this.NameLabel.Text = "Name:"; + NameLabel.AutoSize = true; + NameLabel.Location = new Point(12, 235); + NameLabel.Name = "NameLabel"; + NameLabel.Size = new Size(39, 13); + NameLabel.TabIndex = 6; + NameLabel.Text = "Name:"; // // HandgroupCombo // - this.HandgroupCombo.FormattingEnabled = true; - this.HandgroupCombo.Location = new System.Drawing.Point(248, 250); - this.HandgroupCombo.Name = "HandgroupCombo"; - this.HandgroupCombo.Size = new System.Drawing.Size(160, 21); - this.HandgroupCombo.TabIndex = 7; + HandgroupCombo.FormattingEnabled = true; + HandgroupCombo.Location = new Point(248, 250); + HandgroupCombo.Name = "HandgroupCombo"; + HandgroupCombo.Size = new Size(160, 21); + HandgroupCombo.TabIndex = 7; // // HandgroupLabel // - this.HandgroupLabel.AutoSize = true; - this.HandgroupLabel.Location = new System.Drawing.Point(245, 234); - this.HandgroupLabel.Name = "HandgroupLabel"; - this.HandgroupLabel.Size = new System.Drawing.Size(63, 13); - this.HandgroupLabel.TabIndex = 8; - this.HandgroupLabel.Text = "Handgroup:"; + HandgroupLabel.AutoSize = true; + HandgroupLabel.Location = new Point(245, 234); + HandgroupLabel.Name = "HandgroupLabel"; + HandgroupLabel.Size = new Size(70, 13); + HandgroupLabel.TabIndex = 8; + HandgroupLabel.Text = "Handgroup:"; // // SummaryText // - this.SummaryText.Location = new System.Drawing.Point(15, 110); - this.SummaryText.Multiline = true; - this.SummaryText.Name = "SummaryText"; - this.SummaryText.ReadOnly = true; - this.SummaryText.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; - this.SummaryText.Size = new System.Drawing.Size(393, 117); - this.SummaryText.TabIndex = 9; + SummaryText.Location = new Point(15, 110); + SummaryText.Multiline = true; + SummaryText.Name = "SummaryText"; + SummaryText.ReadOnly = true; + SummaryText.ScrollBars = ScrollBars.Vertical; + SummaryText.Size = new Size(393, 117); + SummaryText.TabIndex = 9; // // ImportButton // - this.ImportButton.Location = new System.Drawing.Point(333, 277); - this.ImportButton.Name = "ImportButton"; - this.ImportButton.Size = new System.Drawing.Size(75, 23); - this.ImportButton.TabIndex = 10; - this.ImportButton.Text = "Import"; - this.ImportButton.UseVisualStyleBackColor = true; - this.ImportButton.Click += new System.EventHandler(this.ImportButton_Click); + ImportButton.Location = new Point(333, 277); + ImportButton.Name = "ImportButton"; + ImportButton.Size = new Size(75, 23); + ImportButton.TabIndex = 10; + ImportButton.Text = "Import"; + ImportButton.UseVisualStyleBackColor = true; + ImportButton.Click += ImportButton_Click; // // HeadRadio // - this.HeadRadio.AutoSize = true; - this.HeadRadio.Location = new System.Drawing.Point(209, 87); - this.HeadRadio.Name = "HeadRadio"; - this.HeadRadio.Size = new System.Drawing.Size(51, 17); - this.HeadRadio.TabIndex = 11; - this.HeadRadio.TabStop = true; - this.HeadRadio.Text = "Head"; - this.HeadRadio.UseVisualStyleBackColor = true; - this.HeadRadio.CheckedChanged += new System.EventHandler(this.HeadRadio_CheckedChanged); + HeadRadio.AutoSize = true; + HeadRadio.Location = new Point(209, 87); + HeadRadio.Name = "HeadRadio"; + HeadRadio.Size = new Size(52, 17); + HeadRadio.TabIndex = 11; + HeadRadio.TabStop = true; + HeadRadio.Text = "Head"; + HeadRadio.UseVisualStyleBackColor = true; + HeadRadio.CheckedChanged += HeadRadio_CheckedChanged; // // AddAppearanceWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(420, 308); - this.Controls.Add(this.HeadRadio); - this.Controls.Add(this.ImportButton); - this.Controls.Add(this.SummaryText); - this.Controls.Add(this.HandgroupLabel); - this.Controls.Add(this.HandgroupCombo); - this.Controls.Add(this.NameLabel); - this.Controls.Add(this.HandgroupRadio); - this.Controls.Add(this.OutfitRadio); - this.Controls.Add(this.AppearanceRadio); - this.Controls.Add(this.NameEntry); - this.Controls.Add(this.InfoLabel); - this.Controls.Add(this.AddAsLabel); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MinimizeBox = false; - this.Name = "AddAppearanceWindow"; - this.Text = "Import Meshes..."; - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(420, 308); + Controls.Add(HeadRadio); + Controls.Add(ImportButton); + Controls.Add(SummaryText); + Controls.Add(HandgroupLabel); + Controls.Add(HandgroupCombo); + Controls.Add(NameLabel); + Controls.Add(HandgroupRadio); + Controls.Add(OutfitRadio); + Controls.Add(AppearanceRadio); + Controls.Add(NameEntry); + Controls.Add(InfoLabel); + Controls.Add(AddAsLabel); + FormBorderStyle = FormBorderStyle.FixedDialog; + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + MinimizeBox = false; + Name = "AddAppearanceWindow"; + Text = "Import Meshes..."; + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.resx b/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.resx index c28753615..714b16cff 100644 --- a/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.resx +++ b/TSOClient/FSO.IDE/AvatarUtils/AddAppearanceWindow.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/BHAVEditor.Designer.cs b/TSOClient/FSO.IDE/BHAVEditor.Designer.cs index c13dca7cf..2cca1fb28 100644 --- a/TSOClient/FSO.IDE/BHAVEditor.Designer.cs +++ b/TSOClient/FSO.IDE/BHAVEditor.Designer.cs @@ -32,679 +32,644 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(BHAVEditor)); - System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem("CT - Notify Current Object Social Occurred"); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveToFilebhavToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.loadFromFileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.openParentResourceToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.editToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.undoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.redoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.copyToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.pasteStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator(); - this.removeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.setFirstToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.snapPrimitivesToGridToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.insertToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.trueToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.falseToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.labelToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.commentToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.MainTable = new System.Windows.Forms.TableLayoutPanel(); - this.splitContainer1 = new System.Windows.Forms.SplitContainer(); - this.PrimitivesGroup = new System.Windows.Forms.GroupBox(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.SearchBox = new System.Windows.Forms.TextBox(); - this.PrimitiveList = new System.Windows.Forms.ListBox(); - this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); - this.DebugBtn = new System.Windows.Forms.Button(); - this.SimBtn = new System.Windows.Forms.Button(); - this.ObjectBtn = new System.Windows.Forms.Button(); - this.PositionBtn = new System.Windows.Forms.Button(); - this.MathBtn = new System.Windows.Forms.Button(); - this.ControlBtn = new System.Windows.Forms.Button(); - this.LooksBtn = new System.Windows.Forms.Button(); - this.SubroutineBtn = new System.Windows.Forms.Button(); - this.TSOBtn = new System.Windows.Forms.Button(); - this.AllBtn = new System.Windows.Forms.Button(); - this.OperandGroup = new System.Windows.Forms.GroupBox(); - this.OperandScroller = new System.Windows.Forms.FlowLayoutPanel(); - this.OperandEditTable = new System.Windows.Forms.TableLayoutPanel(); - this.EditorControl = new FSO.IDE.EditorComponent.BHAVViewControl(); - this.DebugTable = new System.Windows.Forms.TableLayoutPanel(); - this.ObjectDataGrid = new System.Windows.Forms.PropertyGrid(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.StackView = new System.Windows.Forms.ListView(); - this.StackTreeNameCol = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.StackSourceCol = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); - this.menuStrip1.SuspendLayout(); - this.MainTable.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit(); - this.splitContainer1.Panel1.SuspendLayout(); - this.splitContainer1.Panel2.SuspendLayout(); - this.splitContainer1.SuspendLayout(); - this.PrimitivesGroup.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.tableLayoutPanel2.SuspendLayout(); - this.OperandGroup.SuspendLayout(); - this.OperandScroller.SuspendLayout(); - this.DebugTable.SuspendLayout(); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); + ListViewItem listViewItem1 = new ListViewItem("CT - Notify Current Object Social Occurred"); + menuStrip1 = new MenuStrip(); + fileToolStripMenuItem = new ToolStripMenuItem(); + saveToFilebhavToolStripMenuItem = new ToolStripMenuItem(); + loadFromFileToolStripMenuItem = new ToolStripMenuItem(); + openParentResourceToolStripMenuItem = new ToolStripMenuItem(); + editToolStripMenuItem = new ToolStripMenuItem(); + undoToolStripMenuItem = new ToolStripMenuItem(); + redoToolStripMenuItem = new ToolStripMenuItem(); + toolStripSeparator1 = new ToolStripSeparator(); + copyToolStripMenuItem = new ToolStripMenuItem(); + pasteStripMenuItem = new ToolStripMenuItem(); + toolStripSeparator2 = new ToolStripSeparator(); + removeToolStripMenuItem = new ToolStripMenuItem(); + setFirstToolStripMenuItem = new ToolStripMenuItem(); + viewToolStripMenuItem = new ToolStripMenuItem(); + snapPrimitivesToGridToolStripMenuItem = new ToolStripMenuItem(); + insertToolStripMenuItem = new ToolStripMenuItem(); + trueToolStripMenuItem = new ToolStripMenuItem(); + falseToolStripMenuItem = new ToolStripMenuItem(); + labelToolStripMenuItem = new ToolStripMenuItem(); + commentToolStripMenuItem = new ToolStripMenuItem(); + MainTable = new TableLayoutPanel(); + splitContainer1 = new SplitContainer(); + PrimitivesGroup = new GroupBox(); + pictureBox1 = new PictureBox(); + SearchBox = new TextBox(); + PrimitiveList = new ListBox(); + tableLayoutPanel2 = new TableLayoutPanel(); + DebugBtn = new Button(); + SimBtn = new Button(); + ObjectBtn = new Button(); + PositionBtn = new Button(); + MathBtn = new Button(); + ControlBtn = new Button(); + LooksBtn = new Button(); + SubroutineBtn = new Button(); + TSOBtn = new Button(); + AllBtn = new Button(); + OperandGroup = new GroupBox(); + OperandScroller = new FlowLayoutPanel(); + OperandEditTable = new TableLayoutPanel(); + EditorControl = new FSO.IDE.EditorComponent.BHAVViewControl(); + DebugTable = new TableLayoutPanel(); + ObjectDataGrid = new PropertyGrid(); + groupBox1 = new GroupBox(); + StackView = new ListView(); + StackTreeNameCol = new ColumnHeader(); + StackSourceCol = new ColumnHeader(); + menuStrip1.SuspendLayout(); + MainTable.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)splitContainer1).BeginInit(); + splitContainer1.Panel1.SuspendLayout(); + splitContainer1.Panel2.SuspendLayout(); + splitContainer1.SuspendLayout(); + PrimitivesGroup.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).BeginInit(); + tableLayoutPanel2.SuspendLayout(); + OperandGroup.SuspendLayout(); + OperandScroller.SuspendLayout(); + DebugTable.SuspendLayout(); + groupBox1.SuspendLayout(); + SuspendLayout(); // // menuStrip1 // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem, - this.editToolStripMenuItem, - this.viewToolStripMenuItem, - this.insertToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(1014, 24); - this.menuStrip1.TabIndex = 1; - this.menuStrip1.Text = "menuStrip1"; + menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, editToolStripMenuItem, viewToolStripMenuItem, insertToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(1014, 24); + menuStrip1.TabIndex = 1; + menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.saveToFilebhavToolStripMenuItem, - this.loadFromFileToolStripMenuItem, - this.openParentResourceToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); - this.fileToolStripMenuItem.Text = "File"; + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { saveToFilebhavToolStripMenuItem, loadFromFileToolStripMenuItem, openParentResourceToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new Size(37, 20); + fileToolStripMenuItem.Text = "File"; // // saveToFilebhavToolStripMenuItem // - this.saveToFilebhavToolStripMenuItem.Enabled = false; - this.saveToFilebhavToolStripMenuItem.Name = "saveToFilebhavToolStripMenuItem"; - this.saveToFilebhavToolStripMenuItem.Size = new System.Drawing.Size(191, 22); - this.saveToFilebhavToolStripMenuItem.Text = "Save to File (.bhav)"; + saveToFilebhavToolStripMenuItem.Enabled = false; + saveToFilebhavToolStripMenuItem.Name = "saveToFilebhavToolStripMenuItem"; + saveToFilebhavToolStripMenuItem.Size = new Size(191, 22); + saveToFilebhavToolStripMenuItem.Text = "Save to File (.bhav)"; // // loadFromFileToolStripMenuItem // - this.loadFromFileToolStripMenuItem.Enabled = false; - this.loadFromFileToolStripMenuItem.Name = "loadFromFileToolStripMenuItem"; - this.loadFromFileToolStripMenuItem.Size = new System.Drawing.Size(191, 22); - this.loadFromFileToolStripMenuItem.Text = "Load from File"; + loadFromFileToolStripMenuItem.Enabled = false; + loadFromFileToolStripMenuItem.Name = "loadFromFileToolStripMenuItem"; + loadFromFileToolStripMenuItem.Size = new Size(191, 22); + loadFromFileToolStripMenuItem.Text = "Load from File"; // // openParentResourceToolStripMenuItem // - this.openParentResourceToolStripMenuItem.Name = "openParentResourceToolStripMenuItem"; - this.openParentResourceToolStripMenuItem.Size = new System.Drawing.Size(191, 22); - this.openParentResourceToolStripMenuItem.Text = "Open Parent Resource"; - this.openParentResourceToolStripMenuItem.Click += new System.EventHandler(this.openParentResourceToolStripMenuItem_Click); + openParentResourceToolStripMenuItem.Name = "openParentResourceToolStripMenuItem"; + openParentResourceToolStripMenuItem.Size = new Size(191, 22); + openParentResourceToolStripMenuItem.Text = "Open Parent Resource"; + openParentResourceToolStripMenuItem.Click += openParentResourceToolStripMenuItem_Click; // // editToolStripMenuItem // - this.editToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.undoToolStripMenuItem, - this.redoToolStripMenuItem, - this.toolStripSeparator1, - this.copyToolStripMenuItem, - this.pasteStripMenuItem, - this.toolStripSeparator2, - this.removeToolStripMenuItem, - this.setFirstToolStripMenuItem}); - this.editToolStripMenuItem.Name = "editToolStripMenuItem"; - this.editToolStripMenuItem.Size = new System.Drawing.Size(39, 20); - this.editToolStripMenuItem.Text = "Edit"; + editToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { undoToolStripMenuItem, redoToolStripMenuItem, toolStripSeparator1, copyToolStripMenuItem, pasteStripMenuItem, toolStripSeparator2, removeToolStripMenuItem, setFirstToolStripMenuItem }); + editToolStripMenuItem.Name = "editToolStripMenuItem"; + editToolStripMenuItem.Size = new Size(39, 20); + editToolStripMenuItem.Text = "Edit"; // // undoToolStripMenuItem // - this.undoToolStripMenuItem.Name = "undoToolStripMenuItem"; - this.undoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Z))); - this.undoToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.undoToolStripMenuItem.Text = "Undo"; - this.undoToolStripMenuItem.Click += new System.EventHandler(this.undoToolStripMenuItem_Click); + undoToolStripMenuItem.Name = "undoToolStripMenuItem"; + undoToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.Z; + undoToolStripMenuItem.Size = new Size(169, 22); + undoToolStripMenuItem.Text = "Undo"; + undoToolStripMenuItem.Click += undoToolStripMenuItem_Click; // // redoToolStripMenuItem // - this.redoToolStripMenuItem.Name = "redoToolStripMenuItem"; - this.redoToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Y))); - this.redoToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.redoToolStripMenuItem.Text = "Redo"; - this.redoToolStripMenuItem.Click += new System.EventHandler(this.redoToolStripMenuItem_Click); + redoToolStripMenuItem.Name = "redoToolStripMenuItem"; + redoToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.Y; + redoToolStripMenuItem.Size = new Size(169, 22); + redoToolStripMenuItem.Text = "Redo"; + redoToolStripMenuItem.Click += redoToolStripMenuItem_Click; // // toolStripSeparator1 // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(177, 6); + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new Size(166, 6); // // copyToolStripMenuItem // - this.copyToolStripMenuItem.Name = "copyToolStripMenuItem"; - this.copyToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.C))); - this.copyToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.copyToolStripMenuItem.Text = "Copy"; - this.copyToolStripMenuItem.Click += new System.EventHandler(this.copyToolStripMenuItem_Click); + copyToolStripMenuItem.Name = "copyToolStripMenuItem"; + copyToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.C; + copyToolStripMenuItem.Size = new Size(169, 22); + copyToolStripMenuItem.Text = "Copy"; + copyToolStripMenuItem.Click += copyToolStripMenuItem_Click; // // pasteStripMenuItem // - this.pasteStripMenuItem.Name = "pasteStripMenuItem"; - this.pasteStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.V))); - this.pasteStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.pasteStripMenuItem.Text = "Paste"; - this.pasteStripMenuItem.Click += new System.EventHandler(this.pasteToolStripMenuItem_Click); + pasteStripMenuItem.Name = "pasteStripMenuItem"; + pasteStripMenuItem.ShortcutKeys = Keys.Control | Keys.V; + pasteStripMenuItem.Size = new Size(169, 22); + pasteStripMenuItem.Text = "Paste"; + pasteStripMenuItem.Click += pasteToolStripMenuItem_Click; // // toolStripSeparator2 // - this.toolStripSeparator2.Name = "toolStripSeparator2"; - this.toolStripSeparator2.Size = new System.Drawing.Size(177, 6); + toolStripSeparator2.Name = "toolStripSeparator2"; + toolStripSeparator2.Size = new Size(166, 6); // // removeToolStripMenuItem // - this.removeToolStripMenuItem.Name = "removeToolStripMenuItem"; - this.removeToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Delete; - this.removeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.removeToolStripMenuItem.Text = "Remove"; - this.removeToolStripMenuItem.Click += new System.EventHandler(this.removeToolStripMenuItem_Click); + removeToolStripMenuItem.Name = "removeToolStripMenuItem"; + removeToolStripMenuItem.ShortcutKeys = Keys.Delete; + removeToolStripMenuItem.Size = new Size(169, 22); + removeToolStripMenuItem.Text = "Remove"; + removeToolStripMenuItem.Click += removeToolStripMenuItem_Click; // // setFirstToolStripMenuItem // - this.setFirstToolStripMenuItem.Name = "setFirstToolStripMenuItem"; - this.setFirstToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.D1))); - this.setFirstToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.setFirstToolStripMenuItem.Text = "Set as First"; - this.setFirstToolStripMenuItem.Click += new System.EventHandler(this.setFirstToolStripMenuItem_Click); + setFirstToolStripMenuItem.Name = "setFirstToolStripMenuItem"; + setFirstToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.D1; + setFirstToolStripMenuItem.Size = new Size(169, 22); + setFirstToolStripMenuItem.Text = "Set as First"; + setFirstToolStripMenuItem.Click += setFirstToolStripMenuItem_Click; // // viewToolStripMenuItem // - this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.snapPrimitivesToGridToolStripMenuItem}); - this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; - this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); - this.viewToolStripMenuItem.Text = "View"; + viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { snapPrimitivesToGridToolStripMenuItem }); + viewToolStripMenuItem.Name = "viewToolStripMenuItem"; + viewToolStripMenuItem.Size = new Size(44, 20); + viewToolStripMenuItem.Text = "View"; // // snapPrimitivesToGridToolStripMenuItem // - this.snapPrimitivesToGridToolStripMenuItem.Name = "snapPrimitivesToGridToolStripMenuItem"; - this.snapPrimitivesToGridToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.S))); - this.snapPrimitivesToGridToolStripMenuItem.Size = new System.Drawing.Size(235, 22); - this.snapPrimitivesToGridToolStripMenuItem.Text = "Snap Primitives To Grid"; - this.snapPrimitivesToGridToolStripMenuItem.Click += new System.EventHandler(this.SnapPrimitivesToGridToolStripMenuItem_Click); + snapPrimitivesToGridToolStripMenuItem.Name = "snapPrimitivesToGridToolStripMenuItem"; + snapPrimitivesToGridToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.S; + snapPrimitivesToGridToolStripMenuItem.Size = new Size(236, 22); + snapPrimitivesToGridToolStripMenuItem.Text = "Snap Primitives To Grid"; + snapPrimitivesToGridToolStripMenuItem.Click += SnapPrimitivesToGridToolStripMenuItem_Click; // // insertToolStripMenuItem // - this.insertToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.trueToolStripMenuItem, - this.falseToolStripMenuItem, - this.labelToolStripMenuItem, - this.commentToolStripMenuItem}); - this.insertToolStripMenuItem.Name = "insertToolStripMenuItem"; - this.insertToolStripMenuItem.Size = new System.Drawing.Size(48, 20); - this.insertToolStripMenuItem.Text = "Insert"; + insertToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { trueToolStripMenuItem, falseToolStripMenuItem, labelToolStripMenuItem, commentToolStripMenuItem }); + insertToolStripMenuItem.Name = "insertToolStripMenuItem"; + insertToolStripMenuItem.Size = new Size(48, 20); + insertToolStripMenuItem.Text = "Insert"; // // trueToolStripMenuItem // - this.trueToolStripMenuItem.Name = "trueToolStripMenuItem"; - this.trueToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.T))); - this.trueToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.trueToolStripMenuItem.Text = "True"; - this.trueToolStripMenuItem.Click += new System.EventHandler(this.trueToolStripMenuItem_Click); + trueToolStripMenuItem.Name = "trueToolStripMenuItem"; + trueToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.T; + trueToolStripMenuItem.Size = new Size(180, 22); + trueToolStripMenuItem.Text = "True"; + trueToolStripMenuItem.Click += trueToolStripMenuItem_Click; // // falseToolStripMenuItem // - this.falseToolStripMenuItem.Name = "falseToolStripMenuItem"; - this.falseToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.F))); - this.falseToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.falseToolStripMenuItem.Text = "False"; - this.falseToolStripMenuItem.Click += new System.EventHandler(this.falseToolStripMenuItem_Click); + falseToolStripMenuItem.Name = "falseToolStripMenuItem"; + falseToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.F; + falseToolStripMenuItem.Size = new Size(180, 22); + falseToolStripMenuItem.Text = "False"; + falseToolStripMenuItem.Click += falseToolStripMenuItem_Click; // // labelToolStripMenuItem // - this.labelToolStripMenuItem.Name = "labelToolStripMenuItem"; - this.labelToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.L))); - this.labelToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.labelToolStripMenuItem.Text = "Label"; - this.labelToolStripMenuItem.Click += new System.EventHandler(this.labelToolStripMenuItem_Click); + labelToolStripMenuItem.Name = "labelToolStripMenuItem"; + labelToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.L; + labelToolStripMenuItem.Size = new Size(180, 22); + labelToolStripMenuItem.Text = "Label"; + labelToolStripMenuItem.Click += labelToolStripMenuItem_Click; // // commentToolStripMenuItem // - this.commentToolStripMenuItem.Name = "commentToolStripMenuItem"; - this.commentToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.OemQuestion))); - this.commentToolStripMenuItem.ShowShortcutKeys = false; - this.commentToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.commentToolStripMenuItem.Text = "Comment Ctrl+/"; - this.commentToolStripMenuItem.Click += new System.EventHandler(this.commentToolStripMenuItem_Click); + commentToolStripMenuItem.Name = "commentToolStripMenuItem"; + commentToolStripMenuItem.ShortcutKeys = Keys.Control | Keys.Oem2; + commentToolStripMenuItem.ShowShortcutKeys = false; + commentToolStripMenuItem.Size = new Size(180, 22); + commentToolStripMenuItem.Text = "Comment Ctrl+/"; + commentToolStripMenuItem.Click += commentToolStripMenuItem_Click; // // MainTable // - this.MainTable.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.MainTable.ColumnCount = 3; - this.MainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 260F)); - this.MainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.MainTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 260F)); - this.MainTable.Controls.Add(this.splitContainer1, 0, 0); - this.MainTable.Controls.Add(this.EditorControl, 1, 0); - this.MainTable.Controls.Add(this.DebugTable, 2, 0); - this.MainTable.Location = new System.Drawing.Point(0, 27); - this.MainTable.Name = "MainTable"; - this.MainTable.RowCount = 1; - this.MainTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.MainTable.Size = new System.Drawing.Size(1014, 569); - this.MainTable.TabIndex = 2; + MainTable.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + MainTable.ColumnCount = 3; + MainTable.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 260F)); + MainTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + MainTable.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 260F)); + MainTable.Controls.Add(splitContainer1, 0, 0); + MainTable.Controls.Add(EditorControl, 1, 0); + MainTable.Controls.Add(DebugTable, 2, 0); + MainTable.Location = new Point(0, 27); + MainTable.Name = "MainTable"; + MainTable.RowCount = 1; + MainTable.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + MainTable.Size = new Size(1014, 569); + MainTable.TabIndex = 2; // // splitContainer1 // - this.splitContainer1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.splitContainer1.Location = new System.Drawing.Point(3, 3); - this.splitContainer1.Name = "splitContainer1"; - this.splitContainer1.Orientation = System.Windows.Forms.Orientation.Horizontal; + splitContainer1.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + splitContainer1.Location = new Point(3, 3); + splitContainer1.Name = "splitContainer1"; + splitContainer1.Orientation = Orientation.Horizontal; // // splitContainer1.Panel1 // - this.splitContainer1.Panel1.Controls.Add(this.PrimitivesGroup); + splitContainer1.Panel1.Controls.Add(PrimitivesGroup); // // splitContainer1.Panel2 // - this.splitContainer1.Panel2.Controls.Add(this.OperandGroup); - this.splitContainer1.Size = new System.Drawing.Size(254, 563); - this.splitContainer1.SplitterDistance = 314; - this.splitContainer1.TabIndex = 2; + splitContainer1.Panel2.Controls.Add(OperandGroup); + splitContainer1.Size = new Size(254, 563); + splitContainer1.SplitterDistance = 314; + splitContainer1.TabIndex = 2; // // PrimitivesGroup // - this.PrimitivesGroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.PrimitivesGroup.Controls.Add(this.pictureBox1); - this.PrimitivesGroup.Controls.Add(this.SearchBox); - this.PrimitivesGroup.Controls.Add(this.PrimitiveList); - this.PrimitivesGroup.Controls.Add(this.tableLayoutPanel2); - this.PrimitivesGroup.Location = new System.Drawing.Point(3, -1); - this.PrimitivesGroup.Name = "PrimitivesGroup"; - this.PrimitivesGroup.Size = new System.Drawing.Size(248, 312); - this.PrimitivesGroup.TabIndex = 5; - this.PrimitivesGroup.TabStop = false; - this.PrimitivesGroup.Text = "Primitives"; + PrimitivesGroup.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + PrimitivesGroup.Controls.Add(pictureBox1); + PrimitivesGroup.Controls.Add(SearchBox); + PrimitivesGroup.Controls.Add(PrimitiveList); + PrimitivesGroup.Controls.Add(tableLayoutPanel2); + PrimitivesGroup.Location = new Point(3, -1); + PrimitivesGroup.Name = "PrimitivesGroup"; + PrimitivesGroup.Size = new Size(248, 312); + PrimitivesGroup.TabIndex = 5; + PrimitivesGroup.TabStop = false; + PrimitivesGroup.Text = "Primitives"; // // pictureBox1 // - this.pictureBox1.Image = global::FSO.IDE.Properties.Resources.search; - this.pictureBox1.Location = new System.Drawing.Point(11, 142); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(18, 19); - this.pictureBox1.TabIndex = 3; - this.pictureBox1.TabStop = false; + pictureBox1.Image = Properties.Resources.search; + pictureBox1.Location = new Point(11, 142); + pictureBox1.Name = "pictureBox1"; + pictureBox1.Size = new Size(18, 19); + pictureBox1.TabIndex = 3; + pictureBox1.TabStop = false; // // SearchBox // - this.SearchBox.Location = new System.Drawing.Point(32, 141); - this.SearchBox.Name = "SearchBox"; - this.SearchBox.Size = new System.Drawing.Size(208, 20); - this.SearchBox.TabIndex = 4; - this.SearchBox.TextChanged += new System.EventHandler(this.SearchBox_TextChanged); + SearchBox.Location = new Point(32, 141); + SearchBox.Name = "SearchBox"; + SearchBox.Size = new Size(208, 22); + SearchBox.TabIndex = 4; + SearchBox.TextChanged += SearchBox_TextChanged; // // PrimitiveList // - this.PrimitiveList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.PrimitiveList.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.PrimitiveList.FormattingEnabled = true; - this.PrimitiveList.Location = new System.Drawing.Point(10, 166); - this.PrimitiveList.Name = "PrimitiveList"; - this.PrimitiveList.Size = new System.Drawing.Size(230, 132); - this.PrimitiveList.TabIndex = 3; - this.PrimitiveList.SelectedIndexChanged += new System.EventHandler(this.listBox1_SelectedIndexChanged); + PrimitiveList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + PrimitiveList.BorderStyle = BorderStyle.FixedSingle; + PrimitiveList.FormattingEnabled = true; + PrimitiveList.Location = new Point(10, 166); + PrimitiveList.Name = "PrimitiveList"; + PrimitiveList.Size = new Size(230, 132); + PrimitiveList.TabIndex = 3; + PrimitiveList.SelectedIndexChanged += listBox1_SelectedIndexChanged; // // tableLayoutPanel2 // - this.tableLayoutPanel2.ColumnCount = 2; - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.tableLayoutPanel2.Controls.Add(this.DebugBtn, 0, 3); - this.tableLayoutPanel2.Controls.Add(this.SimBtn, 1, 2); - this.tableLayoutPanel2.Controls.Add(this.ObjectBtn, 0, 2); - this.tableLayoutPanel2.Controls.Add(this.PositionBtn, 1, 1); - this.tableLayoutPanel2.Controls.Add(this.MathBtn, 1, 0); - this.tableLayoutPanel2.Controls.Add(this.ControlBtn, 0, 0); - this.tableLayoutPanel2.Controls.Add(this.LooksBtn, 0, 1); - this.tableLayoutPanel2.Controls.Add(this.SubroutineBtn, 0, 4); - this.tableLayoutPanel2.Controls.Add(this.TSOBtn, 1, 3); - this.tableLayoutPanel2.Controls.Add(this.AllBtn, 1, 4); - this.tableLayoutPanel2.Location = new System.Drawing.Point(7, 18); - this.tableLayoutPanel2.Name = "tableLayoutPanel2"; - this.tableLayoutPanel2.RowCount = 5; - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 20F)); - this.tableLayoutPanel2.Size = new System.Drawing.Size(236, 118); - this.tableLayoutPanel2.TabIndex = 2; + tableLayoutPanel2.ColumnCount = 2; + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + tableLayoutPanel2.Controls.Add(DebugBtn, 0, 3); + tableLayoutPanel2.Controls.Add(SimBtn, 1, 2); + tableLayoutPanel2.Controls.Add(ObjectBtn, 0, 2); + tableLayoutPanel2.Controls.Add(PositionBtn, 1, 1); + tableLayoutPanel2.Controls.Add(MathBtn, 1, 0); + tableLayoutPanel2.Controls.Add(ControlBtn, 0, 0); + tableLayoutPanel2.Controls.Add(LooksBtn, 0, 1); + tableLayoutPanel2.Controls.Add(SubroutineBtn, 0, 4); + tableLayoutPanel2.Controls.Add(TSOBtn, 1, 3); + tableLayoutPanel2.Controls.Add(AllBtn, 1, 4); + tableLayoutPanel2.Location = new Point(7, 18); + tableLayoutPanel2.Name = "tableLayoutPanel2"; + tableLayoutPanel2.RowCount = 5; + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 20F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 20F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 20F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 20F)); + tableLayoutPanel2.RowStyles.Add(new RowStyle(SizeType.Percent, 20F)); + tableLayoutPanel2.Size = new Size(236, 118); + tableLayoutPanel2.TabIndex = 2; // // DebugBtn // - this.DebugBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(115)))), ((int)(((byte)(115))))); - this.DebugBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("DebugBtn.BackgroundImage"))); - this.DebugBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.DebugBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.DebugBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.DebugBtn.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(102)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.DebugBtn.Location = new System.Drawing.Point(1, 70); - this.DebugBtn.Margin = new System.Windows.Forms.Padding(1); - this.DebugBtn.Name = "DebugBtn"; - this.DebugBtn.Size = new System.Drawing.Size(112, 20); - this.DebugBtn.TabIndex = 7; - this.DebugBtn.Text = "Debug"; - this.DebugBtn.UseVisualStyleBackColor = false; + DebugBtn.BackColor = Color.FromArgb(255, 115, 115); + DebugBtn.BackgroundImage = (Image)resources.GetObject("DebugBtn.BackgroundImage"); + DebugBtn.FlatAppearance.BorderColor = Color.White; + DebugBtn.FlatStyle = FlatStyle.Popup; + DebugBtn.Font = new Font("Segoe UI", 8.25F); + DebugBtn.ForeColor = Color.FromArgb(102, 0, 0); + DebugBtn.Location = new Point(1, 70); + DebugBtn.Margin = new Padding(1); + DebugBtn.Name = "DebugBtn"; + DebugBtn.Size = new Size(112, 20); + DebugBtn.TabIndex = 7; + DebugBtn.Text = "Debug"; + DebugBtn.UseVisualStyleBackColor = false; // // SimBtn // - this.SimBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(151)))), ((int)(((byte)(253))))); - this.SimBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("SimBtn.BackgroundImage"))); - this.SimBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.SimBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.SimBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.SimBtn.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(105)))), ((int)(((byte)(0)))), ((int)(((byte)(140))))); - this.SimBtn.Location = new System.Drawing.Point(122, 47); - this.SimBtn.Margin = new System.Windows.Forms.Padding(4, 1, 1, 1); - this.SimBtn.Name = "SimBtn"; - this.SimBtn.Size = new System.Drawing.Size(112, 20); - this.SimBtn.TabIndex = 6; - this.SimBtn.Text = "Sim"; - this.SimBtn.UseVisualStyleBackColor = false; + SimBtn.BackColor = Color.FromArgb(255, 151, 253); + SimBtn.BackgroundImage = (Image)resources.GetObject("SimBtn.BackgroundImage"); + SimBtn.FlatAppearance.BorderColor = Color.White; + SimBtn.FlatStyle = FlatStyle.Popup; + SimBtn.Font = new Font("Segoe UI", 8.25F); + SimBtn.ForeColor = Color.FromArgb(105, 0, 140); + SimBtn.Location = new Point(122, 47); + SimBtn.Margin = new Padding(4, 1, 1, 1); + SimBtn.Name = "SimBtn"; + SimBtn.Size = new Size(112, 20); + SimBtn.TabIndex = 6; + SimBtn.Text = "Sim"; + SimBtn.UseVisualStyleBackColor = false; // // ObjectBtn // - this.ObjectBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(105)))), ((int)(((byte)(0)))), ((int)(((byte)(140))))); - this.ObjectBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("ObjectBtn.BackgroundImage"))); - this.ObjectBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.ObjectBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.ObjectBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ObjectBtn.ForeColor = System.Drawing.Color.White; - this.ObjectBtn.Location = new System.Drawing.Point(1, 47); - this.ObjectBtn.Margin = new System.Windows.Forms.Padding(1); - this.ObjectBtn.Name = "ObjectBtn"; - this.ObjectBtn.Size = new System.Drawing.Size(112, 20); - this.ObjectBtn.TabIndex = 5; - this.ObjectBtn.Text = "Object"; - this.ObjectBtn.UseVisualStyleBackColor = false; + ObjectBtn.BackColor = Color.FromArgb(105, 0, 140); + ObjectBtn.BackgroundImage = (Image)resources.GetObject("ObjectBtn.BackgroundImage"); + ObjectBtn.FlatAppearance.BorderColor = Color.White; + ObjectBtn.FlatStyle = FlatStyle.Popup; + ObjectBtn.Font = new Font("Segoe UI", 8.25F); + ObjectBtn.ForeColor = Color.White; + ObjectBtn.Location = new Point(1, 47); + ObjectBtn.Margin = new Padding(1); + ObjectBtn.Name = "ObjectBtn"; + ObjectBtn.Size = new Size(112, 20); + ObjectBtn.TabIndex = 5; + ObjectBtn.Text = "Object"; + ObjectBtn.UseVisualStyleBackColor = false; // // PositionBtn // - this.PositionBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(89)))), ((int)(((byte)(178))))); - this.PositionBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg10; - this.PositionBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.PositionBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.PositionBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.PositionBtn.ForeColor = System.Drawing.Color.White; - this.PositionBtn.Location = new System.Drawing.Point(122, 24); - this.PositionBtn.Margin = new System.Windows.Forms.Padding(4, 1, 1, 1); - this.PositionBtn.Name = "PositionBtn"; - this.PositionBtn.Size = new System.Drawing.Size(112, 20); - this.PositionBtn.TabIndex = 4; - this.PositionBtn.Text = "Position"; - this.PositionBtn.UseVisualStyleBackColor = false; + PositionBtn.BackColor = Color.FromArgb(0, 89, 178); + PositionBtn.BackgroundImage = Properties.Resources.diagbg10; + PositionBtn.FlatAppearance.BorderColor = Color.White; + PositionBtn.FlatStyle = FlatStyle.Popup; + PositionBtn.Font = new Font("Segoe UI", 8.25F); + PositionBtn.ForeColor = Color.White; + PositionBtn.Location = new Point(122, 24); + PositionBtn.Margin = new Padding(4, 1, 1, 1); + PositionBtn.Name = "PositionBtn"; + PositionBtn.Size = new Size(112, 20); + PositionBtn.TabIndex = 4; + PositionBtn.Text = "Position"; + PositionBtn.UseVisualStyleBackColor = false; // // MathBtn // - this.MathBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(70)))), ((int)(((byte)(140)))), ((int)(((byte)(0))))); - this.MathBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg10; - this.MathBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.MathBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.MathBtn.ForeColor = System.Drawing.Color.White; - this.MathBtn.Location = new System.Drawing.Point(122, 1); - this.MathBtn.Margin = new System.Windows.Forms.Padding(4, 1, 1, 1); - this.MathBtn.Name = "MathBtn"; - this.MathBtn.Size = new System.Drawing.Size(112, 20); - this.MathBtn.TabIndex = 2; - this.MathBtn.Text = "Math"; - this.MathBtn.UseVisualStyleBackColor = false; + MathBtn.BackColor = Color.FromArgb(70, 140, 0); + MathBtn.BackgroundImage = Properties.Resources.diagbg10; + MathBtn.FlatStyle = FlatStyle.Popup; + MathBtn.Font = new Font("Segoe UI", 8.25F); + MathBtn.ForeColor = Color.White; + MathBtn.Location = new Point(122, 1); + MathBtn.Margin = new Padding(4, 1, 1, 1); + MathBtn.Name = "MathBtn"; + MathBtn.Size = new Size(112, 20); + MathBtn.TabIndex = 2; + MathBtn.Text = "Math"; + MathBtn.UseVisualStyleBackColor = false; // // ControlBtn // - this.ControlBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(255)))), ((int)(((byte)(191)))), ((int)(((byte)(0))))); - this.ControlBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg20; - this.ControlBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.ControlBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.ControlBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ControlBtn.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(102)))), ((int)(((byte)(76)))), ((int)(((byte)(0))))); - this.ControlBtn.Location = new System.Drawing.Point(1, 1); - this.ControlBtn.Margin = new System.Windows.Forms.Padding(1); - this.ControlBtn.Name = "ControlBtn"; - this.ControlBtn.Size = new System.Drawing.Size(112, 20); - this.ControlBtn.TabIndex = 1; - this.ControlBtn.Text = "Control"; - this.ControlBtn.UseVisualStyleBackColor = false; + ControlBtn.BackColor = Color.FromArgb(255, 191, 0); + ControlBtn.BackgroundImage = Properties.Resources.diagbg20; + ControlBtn.FlatAppearance.BorderColor = Color.White; + ControlBtn.FlatStyle = FlatStyle.Popup; + ControlBtn.Font = new Font("Segoe UI", 8.25F); + ControlBtn.ForeColor = Color.FromArgb(102, 76, 0); + ControlBtn.Location = new Point(1, 1); + ControlBtn.Margin = new Padding(1); + ControlBtn.Name = "ControlBtn"; + ControlBtn.Size = new Size(112, 20); + ControlBtn.TabIndex = 1; + ControlBtn.Text = "Control"; + ControlBtn.UseVisualStyleBackColor = false; // // LooksBtn // - this.LooksBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(115)))), ((int)(((byte)(220)))), ((int)(((byte)(255))))); - this.LooksBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg20; - this.LooksBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.LooksBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.LooksBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.LooksBtn.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(105)))), ((int)(((byte)(140))))); - this.LooksBtn.Location = new System.Drawing.Point(1, 24); - this.LooksBtn.Margin = new System.Windows.Forms.Padding(1); - this.LooksBtn.Name = "LooksBtn"; - this.LooksBtn.Size = new System.Drawing.Size(112, 20); - this.LooksBtn.TabIndex = 3; - this.LooksBtn.Text = "Looks"; - this.LooksBtn.UseVisualStyleBackColor = false; + LooksBtn.BackColor = Color.FromArgb(115, 220, 255); + LooksBtn.BackgroundImage = Properties.Resources.diagbg20; + LooksBtn.FlatAppearance.BorderColor = Color.White; + LooksBtn.FlatStyle = FlatStyle.Popup; + LooksBtn.Font = new Font("Segoe UI", 8.25F); + LooksBtn.ForeColor = Color.FromArgb(0, 105, 140); + LooksBtn.Location = new Point(1, 24); + LooksBtn.Margin = new Padding(1); + LooksBtn.Name = "LooksBtn"; + LooksBtn.Size = new Size(112, 20); + LooksBtn.TabIndex = 3; + LooksBtn.Text = "Looks"; + LooksBtn.UseVisualStyleBackColor = false; // // SubroutineBtn // - this.SubroutineBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg; - this.SubroutineBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.SubroutineBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.SubroutineBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.SubroutineBtn.Location = new System.Drawing.Point(1, 93); - this.SubroutineBtn.Margin = new System.Windows.Forms.Padding(1); - this.SubroutineBtn.Name = "SubroutineBtn"; - this.SubroutineBtn.Size = new System.Drawing.Size(112, 20); - this.SubroutineBtn.TabIndex = 9; - this.SubroutineBtn.Text = "Subroutine"; - this.SubroutineBtn.UseVisualStyleBackColor = true; + SubroutineBtn.BackgroundImage = Properties.Resources.diagbg; + SubroutineBtn.FlatAppearance.BorderColor = Color.White; + SubroutineBtn.FlatStyle = FlatStyle.Popup; + SubroutineBtn.Font = new Font("Segoe UI", 8.25F); + SubroutineBtn.Location = new Point(1, 93); + SubroutineBtn.Margin = new Padding(1); + SubroutineBtn.Name = "SubroutineBtn"; + SubroutineBtn.Size = new Size(112, 20); + SubroutineBtn.TabIndex = 9; + SubroutineBtn.Text = "Subroutine"; + SubroutineBtn.UseVisualStyleBackColor = true; // // TSOBtn // - this.TSOBtn.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(140)))), ((int)(((byte)(0)))), ((int)(((byte)(0))))); - this.TSOBtn.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("TSOBtn.BackgroundImage"))); - this.TSOBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.TSOBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.TSOBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.TSOBtn.ForeColor = System.Drawing.Color.White; - this.TSOBtn.Location = new System.Drawing.Point(122, 70); - this.TSOBtn.Margin = new System.Windows.Forms.Padding(4, 1, 1, 1); - this.TSOBtn.Name = "TSOBtn"; - this.TSOBtn.Size = new System.Drawing.Size(112, 20); - this.TSOBtn.TabIndex = 8; - this.TSOBtn.Text = "TSO"; - this.TSOBtn.UseVisualStyleBackColor = false; + TSOBtn.BackColor = Color.FromArgb(140, 0, 0); + TSOBtn.BackgroundImage = (Image)resources.GetObject("TSOBtn.BackgroundImage"); + TSOBtn.FlatAppearance.BorderColor = Color.White; + TSOBtn.FlatStyle = FlatStyle.Popup; + TSOBtn.Font = new Font("Segoe UI", 8.25F); + TSOBtn.ForeColor = Color.White; + TSOBtn.Location = new Point(122, 70); + TSOBtn.Margin = new Padding(4, 1, 1, 1); + TSOBtn.Name = "TSOBtn"; + TSOBtn.Size = new Size(112, 20); + TSOBtn.TabIndex = 8; + TSOBtn.Text = "TSO"; + TSOBtn.UseVisualStyleBackColor = false; // // AllBtn // - this.AllBtn.BackColor = System.Drawing.Color.Black; - this.AllBtn.BackgroundImage = global::FSO.IDE.Properties.Resources.diagbg20; - this.AllBtn.FlatAppearance.BorderColor = System.Drawing.Color.White; - this.AllBtn.FlatStyle = System.Windows.Forms.FlatStyle.Popup; - this.AllBtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.AllBtn.ForeColor = System.Drawing.Color.White; - this.AllBtn.Location = new System.Drawing.Point(122, 93); - this.AllBtn.Margin = new System.Windows.Forms.Padding(4, 1, 1, 1); - this.AllBtn.Name = "AllBtn"; - this.AllBtn.Size = new System.Drawing.Size(112, 20); - this.AllBtn.TabIndex = 10; - this.AllBtn.Text = "All"; - this.AllBtn.UseVisualStyleBackColor = false; + AllBtn.BackColor = Color.Black; + AllBtn.BackgroundImage = Properties.Resources.diagbg20; + AllBtn.FlatAppearance.BorderColor = Color.White; + AllBtn.FlatStyle = FlatStyle.Popup; + AllBtn.Font = new Font("Segoe UI", 8.25F); + AllBtn.ForeColor = Color.White; + AllBtn.Location = new Point(122, 93); + AllBtn.Margin = new Padding(4, 1, 1, 1); + AllBtn.Name = "AllBtn"; + AllBtn.Size = new Size(112, 20); + AllBtn.TabIndex = 10; + AllBtn.Text = "All"; + AllBtn.UseVisualStyleBackColor = false; // // OperandGroup // - this.OperandGroup.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.OperandGroup.Controls.Add(this.OperandScroller); - this.OperandGroup.Location = new System.Drawing.Point(3, 0); - this.OperandGroup.Name = "OperandGroup"; - this.OperandGroup.Size = new System.Drawing.Size(248, 239); - this.OperandGroup.TabIndex = 5; - this.OperandGroup.TabStop = false; - this.OperandGroup.Text = "Operand"; + OperandGroup.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + OperandGroup.Controls.Add(OperandScroller); + OperandGroup.Location = new Point(3, 0); + OperandGroup.Name = "OperandGroup"; + OperandGroup.Size = new Size(248, 239); + OperandGroup.TabIndex = 5; + OperandGroup.TabStop = false; + OperandGroup.Text = "Operand"; // // OperandScroller // - this.OperandScroller.AutoScroll = true; - this.OperandScroller.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.OperandScroller.Controls.Add(this.OperandEditTable); - this.OperandScroller.Dock = System.Windows.Forms.DockStyle.Fill; - this.OperandScroller.FlowDirection = System.Windows.Forms.FlowDirection.TopDown; - this.OperandScroller.Location = new System.Drawing.Point(3, 16); - this.OperandScroller.Name = "OperandScroller"; - this.OperandScroller.Size = new System.Drawing.Size(242, 220); - this.OperandScroller.TabIndex = 6; - this.OperandScroller.WrapContents = false; - this.OperandScroller.Resize += new System.EventHandler(this.OperandScroller_Resize); + OperandScroller.AutoScroll = true; + OperandScroller.AutoSizeMode = AutoSizeMode.GrowAndShrink; + OperandScroller.Controls.Add(OperandEditTable); + OperandScroller.Dock = DockStyle.Fill; + OperandScroller.FlowDirection = FlowDirection.TopDown; + OperandScroller.Location = new Point(3, 18); + OperandScroller.Name = "OperandScroller"; + OperandScroller.Size = new Size(242, 218); + OperandScroller.TabIndex = 6; + OperandScroller.WrapContents = false; + OperandScroller.Resize += OperandScroller_Resize; // // OperandEditTable // - this.OperandEditTable.AutoSize = true; - this.OperandEditTable.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.OperandEditTable.BackgroundImageLayout = System.Windows.Forms.ImageLayout.None; - this.OperandEditTable.ColumnCount = 1; - this.OperandEditTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.OperandEditTable.Dock = System.Windows.Forms.DockStyle.Fill; - this.OperandEditTable.Location = new System.Drawing.Point(0, 0); - this.OperandEditTable.Margin = new System.Windows.Forms.Padding(0); - this.OperandEditTable.MaximumSize = new System.Drawing.Size(236, 0); - this.OperandEditTable.Name = "OperandEditTable"; - this.OperandEditTable.RowCount = 1; - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 1F)); - this.OperandEditTable.Size = new System.Drawing.Size(0, 0); - this.OperandEditTable.TabIndex = 8; + OperandEditTable.AutoSize = true; + OperandEditTable.AutoSizeMode = AutoSizeMode.GrowAndShrink; + OperandEditTable.BackgroundImageLayout = ImageLayout.None; + OperandEditTable.ColumnCount = 1; + OperandEditTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + OperandEditTable.Dock = DockStyle.Fill; + OperandEditTable.Location = new Point(0, 0); + OperandEditTable.Margin = new Padding(0); + OperandEditTable.MaximumSize = new Size(236, 0); + OperandEditTable.Name = "OperandEditTable"; + OperandEditTable.RowCount = 1; + OperandEditTable.RowStyles.Add(new RowStyle()); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 1F)); + OperandEditTable.Size = new Size(0, 0); + OperandEditTable.TabIndex = 8; // // EditorControl // - this.EditorControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.EditorControl.Location = new System.Drawing.Point(260, 0); - this.EditorControl.Margin = new System.Windows.Forms.Padding(0); - this.EditorControl.Name = "EditorControl"; - this.EditorControl.Size = new System.Drawing.Size(494, 569); - this.EditorControl.TabIndex = 0; + EditorControl.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + EditorControl.Location = new Point(260, 0); + EditorControl.Margin = new Padding(0); + EditorControl.Name = "EditorControl"; + EditorControl.Size = new Size(494, 569); + EditorControl.TabIndex = 0; // // DebugTable // - this.DebugTable.ColumnCount = 1; - this.DebugTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.DebugTable.Controls.Add(this.ObjectDataGrid, 0, 1); - this.DebugTable.Controls.Add(this.groupBox1, 0, 0); - this.DebugTable.Dock = System.Windows.Forms.DockStyle.Fill; - this.DebugTable.Location = new System.Drawing.Point(757, 3); - this.DebugTable.Name = "DebugTable"; - this.DebugTable.RowCount = 2; - this.DebugTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 200F)); - this.DebugTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.DebugTable.Size = new System.Drawing.Size(254, 563); - this.DebugTable.TabIndex = 3; + DebugTable.ColumnCount = 1; + DebugTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100F)); + DebugTable.Controls.Add(ObjectDataGrid, 0, 1); + DebugTable.Controls.Add(groupBox1, 0, 0); + DebugTable.Dock = DockStyle.Fill; + DebugTable.Location = new Point(757, 3); + DebugTable.Name = "DebugTable"; + DebugTable.RowCount = 2; + DebugTable.RowStyles.Add(new RowStyle(SizeType.Absolute, 200F)); + DebugTable.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + DebugTable.Size = new Size(254, 563); + DebugTable.TabIndex = 3; // // ObjectDataGrid // - this.ObjectDataGrid.CategoryForeColor = System.Drawing.SystemColors.InactiveCaptionText; - this.ObjectDataGrid.Dock = System.Windows.Forms.DockStyle.Fill; - this.ObjectDataGrid.Location = new System.Drawing.Point(3, 203); - this.ObjectDataGrid.Name = "ObjectDataGrid"; - this.ObjectDataGrid.PropertySort = System.Windows.Forms.PropertySort.Categorized; - this.ObjectDataGrid.Size = new System.Drawing.Size(248, 357); - this.ObjectDataGrid.TabIndex = 0; - this.ObjectDataGrid.ToolbarVisible = false; + ObjectDataGrid.BackColor = SystemColors.Control; + ObjectDataGrid.CategoryForeColor = SystemColors.InactiveCaptionText; + ObjectDataGrid.Dock = DockStyle.Fill; + ObjectDataGrid.Location = new Point(3, 203); + ObjectDataGrid.Name = "ObjectDataGrid"; + ObjectDataGrid.PropertySort = PropertySort.Categorized; + ObjectDataGrid.Size = new Size(248, 357); + ObjectDataGrid.TabIndex = 0; + ObjectDataGrid.ToolbarVisible = false; // // groupBox1 // - this.groupBox1.Controls.Add(this.StackView); - this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill; - this.groupBox1.Location = new System.Drawing.Point(3, 3); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(248, 194); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Stack"; + groupBox1.Controls.Add(StackView); + groupBox1.Dock = DockStyle.Fill; + groupBox1.Location = new Point(3, 3); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(248, 194); + groupBox1.TabIndex = 1; + groupBox1.TabStop = false; + groupBox1.Text = "Stack"; // // StackView // - this.StackView.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.StackTreeNameCol, - this.StackSourceCol}); - this.StackView.Dock = System.Windows.Forms.DockStyle.Fill; - this.StackView.HideSelection = false; - this.StackView.Items.AddRange(new System.Windows.Forms.ListViewItem[] { - listViewItem1}); - this.StackView.Location = new System.Drawing.Point(3, 16); - this.StackView.Margin = new System.Windows.Forms.Padding(6); - this.StackView.MultiSelect = false; - this.StackView.Name = "StackView"; - this.StackView.Size = new System.Drawing.Size(242, 175); - this.StackView.TabIndex = 0; - this.StackView.UseCompatibleStateImageBehavior = false; - this.StackView.View = System.Windows.Forms.View.Details; - this.StackView.SelectedIndexChanged += new System.EventHandler(this.StackView_SelectedIndexChanged); + StackView.Columns.AddRange(new ColumnHeader[] { StackTreeNameCol, StackSourceCol }); + StackView.Dock = DockStyle.Fill; + StackView.Items.AddRange(new ListViewItem[] { listViewItem1 }); + StackView.Location = new Point(3, 18); + StackView.Margin = new Padding(6); + StackView.MultiSelect = false; + StackView.Name = "StackView"; + StackView.Size = new Size(242, 173); + StackView.TabIndex = 0; + StackView.UseCompatibleStateImageBehavior = false; + StackView.View = View.Details; + StackView.SelectedIndexChanged += StackView_SelectedIndexChanged; // // StackTreeNameCol // - this.StackTreeNameCol.Text = "Tree Name"; - this.StackTreeNameCol.Width = 150; + StackTreeNameCol.Text = "Tree Name"; + StackTreeNameCol.Width = 150; // // StackSourceCol // - this.StackSourceCol.Text = "Source"; - this.StackSourceCol.Width = 88; + StackSourceCol.Text = "Source"; + StackSourceCol.Width = 88; // // BHAVEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1014, 592); - this.Controls.Add(this.MainTable); - this.Controls.Add(this.menuStrip1); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.menuStrip1; - this.Name = "BHAVEditor"; - this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; - this.Text = "BHAV Editor"; - this.Activated += new System.EventHandler(this.BHAVEditor_Activated); - this.Deactivate += new System.EventHandler(this.BHAVEditor_Deactivate); - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.BHAVEditor_FormClosing); - this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.BHAVEditor_KeyDown); - this.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.BHAVEditor_KeyPress); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.MainTable.ResumeLayout(false); - this.splitContainer1.Panel1.ResumeLayout(false); - this.splitContainer1.Panel2.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).EndInit(); - this.splitContainer1.ResumeLayout(false); - this.PrimitivesGroup.ResumeLayout(false); - this.PrimitivesGroup.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.tableLayoutPanel2.ResumeLayout(false); - this.OperandGroup.ResumeLayout(false); - this.OperandScroller.ResumeLayout(false); - this.OperandScroller.PerformLayout(); - this.DebugTable.ResumeLayout(false); - this.groupBox1.ResumeLayout(false); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(1014, 592); + Controls.Add(MainTable); + Controls.Add(menuStrip1); + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + Name = "BHAVEditor"; + StartPosition = FormStartPosition.CenterScreen; + Text = "BHAV Editor"; + Activated += BHAVEditor_Activated; + Deactivate += BHAVEditor_Deactivate; + FormClosing += BHAVEditor_FormClosing; + KeyDown += BHAVEditor_KeyDown; + KeyPress += BHAVEditor_KeyPress; + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + MainTable.ResumeLayout(false); + splitContainer1.Panel1.ResumeLayout(false); + splitContainer1.Panel2.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)splitContainer1).EndInit(); + splitContainer1.ResumeLayout(false); + PrimitivesGroup.ResumeLayout(false); + PrimitivesGroup.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)pictureBox1).EndInit(); + tableLayoutPanel2.ResumeLayout(false); + OperandGroup.ResumeLayout(false); + OperandScroller.ResumeLayout(false); + OperandScroller.PerformLayout(); + DebugTable.ResumeLayout(false); + groupBox1.ResumeLayout(false); + ResumeLayout(false); + PerformLayout(); - } + } #endregion private System.Windows.Forms.MenuStrip menuStrip1; diff --git a/TSOClient/FSO.IDE/BHAVEditor.cs b/TSOClient/FSO.IDE/BHAVEditor.cs index 20a7b01c8..1db9fa577 100644 --- a/TSOClient/FSO.IDE/BHAVEditor.cs +++ b/TSOClient/FSO.IDE/BHAVEditor.cs @@ -23,18 +23,18 @@ namespace FSO.IDE public partial class BHAVEditor : Form { private Dictionary ButtonGroups; - private Dictionary ButtonColors; - private Dictionary ButtonSelectedText = new Dictionary() - { - {PrimitiveGroup.Subroutine, Color.White}, - {PrimitiveGroup.Control, Color.FromArgb(0x22,0x22,0x22)}, - {PrimitiveGroup.Debug, Color.FromArgb(0x40,0x00,0x00)}, - {PrimitiveGroup.Math, Color.FromArgb(0x00,0x66,0x33)}, - {PrimitiveGroup.Sim, Color.FromArgb(0x4C,0x00,0x66)}, - {PrimitiveGroup.Object, Color.FromArgb(0x4C,0x00,0x66)}, - {PrimitiveGroup.Looks, Color.FromArgb(0x00,0x33,0x66)}, - {PrimitiveGroup.Position, Color.FromArgb(0x00,0x20,0x40)}, - {PrimitiveGroup.TSO, Color.FromArgb(0x3F,0x00,0x00)}, + private Dictionary ButtonColors; + private Dictionary ButtonSelectedText = new Dictionary() + { + {PrimitiveGroup.Subroutine, System.Drawing.Color.White}, + {PrimitiveGroup.Control, System.Drawing.Color.FromArgb(0x22,0x22,0x22)}, + {PrimitiveGroup.Debug, System.Drawing.Color.FromArgb(0x40,0x00,0x00)}, + {PrimitiveGroup.Math, System.Drawing.Color.FromArgb(0x00,0x66,0x33)}, + {PrimitiveGroup.Sim, System.Drawing.Color.FromArgb(0x4C,0x00,0x66)}, + {PrimitiveGroup.Object, System.Drawing.Color.FromArgb(0x4C,0x00,0x66)}, + {PrimitiveGroup.Looks, System.Drawing.Color.FromArgb(0x00,0x33,0x66)}, + {PrimitiveGroup.Position, System.Drawing.Color.FromArgb(0x00,0x20,0x40)}, + {PrimitiveGroup.TSO, System.Drawing.Color.FromArgb(0x3F,0x00,0x00)}, }; private PrimitiveGroup SelectedGroup = PrimitiveGroup.Control; @@ -71,7 +71,7 @@ public BHAVEditor() {AllBtn, PrimitiveGroup.All } }; - ButtonColors = new Dictionary(); + ButtonColors = new Dictionary(); foreach (var btn in ButtonGroups) { ButtonColors.Add(btn.Value, btn.Key.BackColor); @@ -119,7 +119,7 @@ private void UpdateStack() if (stack[i] is VMRoutingFrame) { item.Tag = "route"; - item.ForeColor = Color.Gray; + item.ForeColor = System.Drawing.Color.Gray; } else lastFrame = i; StackView.Items.Add(item); @@ -205,7 +205,7 @@ private void PrimGroupChange(object sender, EventArgs e) foreach (var cbtn in ButtonGroups) { var col = ButtonColors[cbtn.Value]; - if (cbtn.Key == btn) cbtn.Key.BackColor = Color.FromArgb((col.R * 128) / 255 + 127, (col.G * 128) / 255 + 127, (col.B * 128) / 255 + 127); + if (cbtn.Key == btn) cbtn.Key.BackColor = System.Drawing.Color.FromArgb((col.R * 128) / 255 + 127, (col.G * 128) / 255 + 127, (col.B * 128) / 255 + 127); else cbtn.Key.BackColor = col; } diff --git a/TSOClient/FSO.IDE/BHAVEditor.resx b/TSOClient/FSO.IDE/BHAVEditor.resx index 4beb9449d..696ca1ee7 100644 --- a/TSOClient/FSO.IDE/BHAVEditor.resx +++ b/TSOClient/FSO.IDE/BHAVEditor.resx @@ -1,17 +1,17 @@  - @@ -124,73 +124,69 @@ iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAK - 6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAKJSURBVHhe - zdtRcuowFATR7Cn7XxsvoV4oe9wYSb6S++NQMMFSF//5ejwe1b7FqPcSHC+gaAvqvQzHQRRtQb0lcBxA - 0RbUWwbHThRtQb2lcOxA0RbUWw7HRhRtQb1T4NiAoi2odxocP6BoC+qdCscTFG1BvdPh+AZFW1DvEjgC - irag3mVwDBRtQb1L4bhB0RbUuxyO/1G0BfXeAscfFG1BvbehkaItqPdWOVC0RbYqbD9QtMW2U+XvDUVb - 7IJtfl8o2uIQbPP7QuEGh1ih7+eLUIYaPVtfb0Qy1OjVu/sgkKFGu+bDcKMMNTp043iDDDWibsUPmKFG - 1P10+scFMtSIul8+fmGiDDWi7p2mL02QoUbUfdD8xUIZakTdqOvLBTLUiLrf6n7gggw1ou5TQw8NyFAj - 6v5o+MEOGWpE3U0uPdwgQ42ou9nlA05kqBF1dyk5BGSoEXV3KztoI0ONqHtI6WE/MtSIuodVHpihRtR9 - SdWhGWpE3ZdVHJyhRtRd4urhGWpE3WWuXJChRtRdavSSDDWi7nIjF2WoEXVP0XtZhhpR9zQ9F2aoEXVP - 1XpphhpR93QtF2eoEXUv8enyDDWi7mXOAjLUiLqXeheRoUbUvRyFZKhRNt8mYzLUaNt7u21Qhhrt4g3+ - ojLU6BBvQKFGGG9AsTYYLnH4XzkbirZ4NmawCUVbvDq3wSYUbbFr3X2QoGiLQ+9huBlFW1Cv6gekaAvq - fcLxBhRtQb0vOC5G0RbUu4PjQhRtQb0HOC5C0RbUi3BcgKItqPctHCejaAvqPYXjRBRtQb0f4TgJRVtQ - bxMcJ6BoC+pthmMxirag3i44FqJoC+rthmMRirag3iE4FqBoC+od9Pj6B2BGJxRrmEzDAAAAAElFTkSu - QmCC + 6QAACukB/XXO0wAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJ1SURBVHhe + zdtLbsJAAATR3Cn3PxtRrAThamPmP7V4KDT2uGCfr8fj8TXYtxhbu8XQicEmbB0ihg4MNmHrMDE0YrAJ + W4eKoQGDTdg6XAyVGGzC1iliqMBgE7ZOE0MhBpuwdaoYCjDYhK3TxfABg03YukQMNxhswtZlYniDwSZs + XSqGCww2YetyMQCDTdi6RQwvGGzC1m1i+MNgE7ZuFcNFsAlbt+PAYBO2Kry+YbBJhFv8/8Fgk4g2+X1h + sEkE2/y+MNoiYoW+jxchhhodrc8/RBhq9Ow9vRFgqNGpOYaNGGrEZs0PyFAjNh/efrAQQ43Y/HT74QIM + NWLzyccLJmKoEZtD0UUTMNSIzZeKLxyIoUZsfqvq4gEYasTmW9U3dGCoEZs/arqpAUON2Fyk+cYKDDVi + c7Gumwsw1IjNVboPuMFQIzZXG3LIBYYasbnJsINeMNSIzc2GHnYRasTmLiMPZKgRm7uNOpShRmweYsTB + DDVi8zC9hzPUiM1D9TyAoUZsHq71IQw1YvMULQ9iqBGbp6l9GEON2DxVzQMZasTm6UofylAjNi9R8mCG + GrF5mU8PZ6gRm5e6C2CoEZuXexfBUCM2b3EVwlAjNm/DGIYaxZfY6TWIoUbxBXb7j2KoUcQbMNIqwi0Y + ahTRIvG/cjYMNjkaGWzCYJNnJ6MtGGxyamW4AYNN2Kr7ARlswtZDDBsx2IStTzFswmATtp7EsAGDTdga + YliMwSZsvRTDQgw2YetbMSzCYBO23ophAQabsPWjGCZjsAlbi8QwEYNN2FoshkkYbMLWKjFMwGATtlaL + YTAGm7C1SQwDMdiErc1iGITBJmzt8gNgRicU5eAqQQAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAK - 6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAKJSURBVHhe - zdtRcuowFATR7Cn7XxsvoV4oe9wYSb6S++NQMMFSF//5ejwe1b7FqPcSHC+gaAvqvQzHQRRtQb0lcBxA - 0RbUWwbHThRtQb2lcOxA0RbUWw7HRhRtQb1T4NiAoi2odxocP6BoC+qdCscTFG1BvdPh+AZFW1DvEjgC - irag3mVwDBRtQb1L4bhB0RbUuxyO/1G0BfXeAscfFG1BvbehkaItqPdWOVC0RbYqbD9QtMW2U+XvDUVb - 7IJtfl8o2uIQbPP7QuEGh1ih7+eLUIYaPVtfb0Qy1OjVu/sgkKFGu+bDcKMMNTp043iDDDWibsUPmKFG - 1P10+scFMtSIul8+fmGiDDWi7p2mL02QoUbUfdD8xUIZakTdqOvLBTLUiLrf6n7gggw1ou5TQw8NyFAj - 6v5o+MEOGWpE3U0uPdwgQ42ou9nlA05kqBF1dyk5BGSoEXV3KztoI0ONqHtI6WE/MtSIuodVHpihRtR9 - SdWhGWpE3ZdVHJyhRtRd4urhGWpE3WWuXJChRtRdavSSDDWi7nIjF2WoEXVP0XtZhhpR9zQ9F2aoEXVP - 1XpphhpR93QtF2eoEXUv8enyDDWi7mXOAjLUiLqXeheRoUbUvRyFZKhRNt8mYzLUaNt7u21Qhhrt4g3+ - ojLU6BBvQKFGGG9AsTYYLnH4XzkbirZ4NmawCUVbvDq3wSYUbbFr3X2QoGiLQ+9huBlFW1Cv6gekaAvq - fcLxBhRtQb0vOC5G0RbUu4PjQhRtQb0HOC5C0RbUi3BcgKItqPctHCejaAvqPYXjRBRtQb0f4TgJRVtQ - bxMcJ6BoC+pthmMxirag3i44FqJoC+rthmMRirag3iE4FqBoC+od9Pj6B2BGJxRrmEzDAAAAAElFTkSu - QmCC + 6QAACukB/XXO0wAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJ1SURBVHhe + zdtLbsJAAATR3Cn3PxtRrAThamPmP7V4KDT2uGCfr8fj8TXYtxhbu8XQicEmbB0ihg4MNmHrMDE0YrAJ + W4eKoQGDTdg6XAyVGGzC1iliqMBgE7ZOE0MhBpuwdaoYCjDYhK3TxfABg03YukQMNxhswtZlYniDwSZs + XSqGCww2YetyMQCDTdi6RQwvGGzC1m1i+MNgE7ZuFcNFsAlbt+PAYBO2Kry+YbBJhFv8/8Fgk4g2+X1h + sEkE2/y+MNoiYoW+jxchhhodrc8/RBhq9Ow9vRFgqNGpOYaNGGrEZs0PyFAjNh/efrAQQ43Y/HT74QIM + NWLzyccLJmKoEZtD0UUTMNSIzZeKLxyIoUZsfqvq4gEYasTmW9U3dGCoEZs/arqpAUON2Fyk+cYKDDVi + c7Gumwsw1IjNVboPuMFQIzZXG3LIBYYasbnJsINeMNSIzc2GHnYRasTmLiMPZKgRm7uNOpShRmweYsTB + DDVi8zC9hzPUiM1D9TyAoUZsHq71IQw1YvMULQ9iqBGbp6l9GEON2DxVzQMZasTm6UofylAjNi9R8mCG + GrF5mU8PZ6gRm5e6C2CoEZuXexfBUCM2b3EVwlAjNm/DGIYaxZfY6TWIoUbxBXb7j2KoUcQbMNIqwi0Y + ahTRIvG/cjYMNjkaGWzCYJNnJ6MtGGxyamW4AYNN2Kr7ARlswtZDDBsx2IStTzFswmATtp7EsAGDTdga + YliMwSZsvRTDQgw2YetbMSzCYBO23ophAQabsPWjGCZjsAlbi8QwEYNN2FoshkkYbMLWKjFMwGATtlaL + YTAGm7C1SQwDMdiErc1iGITBJmzt8gNgRicU5eAqQQAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAK - 6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAKJSURBVHhe - zdtRcuowFATRrCP73ycvoV4oe9wYSb6S++NQMMFSF//5ejwe1b7FqPcSHC+gaAvqvQzHQRRtQb0lcBxA - 0RbUWwbHThRtQb2lcOxA0RbUWw7HRhRtQb1T4NiAoi2odxocP6BoC+qdCscTFG1BvdPh+AZFW1DvEjgC - irag3mVwDBRtQb1L4bhB0RbUuxyO/1G0BfXeAscfFG1BvbehkaItqPdWOVC0RbYqbD9QtMW2U+XvDUVb - 7IJtfl8o2uIQbPP7QuEGh1ih7+eLUIYaPVtfb0Qy1OjVu/sgkKFGu+bDcKMMNTp043iDDDWibsUPmKFG - 1P10+scFMtSIul8+fmGiDDWi7p2mL02QoUbUfdD8xUIZakTdqOvLBTLUiLrf6n7gggw1ou5TQw8NyFAj - 6v5o+MEOGWpE3U0uPdwgQ42ou9nlA05kqBF1dyk5BGSoEXV3KztoI0ONqHtI6WE/MtSIuodVHpihRtR9 - SdWhGWpE3ZdVHJyhRtRd4urhGWpE3WWuXJChRtRdavSSDDWi7nIjF2WoEXVP0XtZhhpR9zQ9F2aoEXVP - 1XpphhpR93QtF2eoEXUv8enyDDWi7mXOAjLUiLqXeheRoUbUvRyFZKhRNt8mYzLUaNt7u21Qhhrt4g3+ - ojLU6BBvQKFGGG9AsTYYLnH4XzkbirZ4NmawCUVbvDq3wSYUbbFr3X2QoGiLQ+9huBlFW1Cv6gekaAvq - fcLxBhRtQb0vOC5G0RbUu4PjQhRtQb0HOC5C0RbUi3BcgKItqPctHCejaAvqPYXjRBRtQb0f4TgJRVtQ - bxMcJ6BoC+pthmMxirag3i44FqJoC+rthmMRirag3iE4FqBoC+od9Pj6B65C9kZWwNPtAAAAAElFTkSu - QmCC + 6QAACukB/XXO0wAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJ1SURBVHhe + zdtLbsJAAATRnCP3vydRrAThamPmP7V4KDT2uGCfr8fj8TXYtxhbu8XQicEmbB0ihg4MNmHrMDE0YrAJ + W4eKoQGDTdg6XAyVGGzC1iliqMBgE7ZOE0MhBpuwdaoYCjDYhK3TxfABg03YukQMNxhswtZlYniDwSZs + XSqGCww2YetyMQCDTdi6RQwvGGzC1m1i+MNgE7ZuFcNFsAlbt+PAYBO2Kry+YbBJhFv8/8Fgk4g2+X1h + sEkE2/y+MNoiYoW+jxchhhodrc8/RBhq9Ow9vRFgqNGpOYaNGGrEZs0PyFAjNh/efrAQQ43Y/HT74QIM + NWLzyccLJmKoEZtD0UUTMNSIzZeKLxyIoUZsfqvq4gEYasTmW9U3dGCoEZs/arqpAUON2Fyk+cYKDDVi + c7Gumwsw1IjNVboPuMFQIzZXG3LIBYYasbnJsINeMNSIzc2GHnYRasTmLiMPZKgRm7uNOpShRmweYsTB + DDVi8zC9hzPUiM1D9TyAoUZsHq71IQw1YvMULQ9iqBGbp6l9GEON2DxVzQMZasTm6UofylAjNi9R8mCG + GrF5mU8PZ6gRm5e6C2CoEZuXexfBUCM2b3EVwlAjNm/DGIYaxZfY6TWIoUbxBXb7j2KoUcQbMNIqwi0Y + ahTRIvG/cjYMNjkaGWzCYJNnJ6MtGGxyamW4AYNN2Kr7ARlswtZDDBsx2IStTzFswmATtp7EsAGDTdga + YliMwSZsvRTDQgw2YetbMSzCYBO23ophAQabsPWjGCZjsAlbi8QwEYNN2FoshkkYbMLWKjFMwGATtlaL + YTAGm7C1SQwDMdiErc1iGITBJmzt8gOuQvZGIglqfwAAAABJRU5ErkJggg== iVBORw0KGgoAAAANSUhEUgAAAFAAAABQCAYAAACOEfKtAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAK - 6wAACusBgosNWgAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAKJSURBVHhe - zdtRcuowFATRrCP73ycvoV4oe9wYSb6S++NQMMFSF//5ejwe1b7FqPcSHC+gaAvqvQzHQRRtQb0lcBxA - 0RbUWwbHThRtQb2lcOxA0RbUWw7HRhRtQb1T4NiAoi2odxocP6BoC+qdCscTFG1BvdPh+AZFW1DvEjgC - irag3mVwDBRtQb1L4bhB0RbUuxyO/1G0BfXeAscfFG1BvbehkaItqPdWOVC0RbYqbD9QtMW2U+XvDUVb - 7IJtfl8o2uIQbPP7QuEGh1ih7+eLUIYaPVtfb0Qy1OjVu/sgkKFGu+bDcKMMNTp043iDDDWibsUPmKFG - 1P10+scFMtSIul8+fmGiDDWi7p2mL02QoUbUfdD8xUIZakTdqOvLBTLUiLrf6n7gggw1ou5TQw8NyFAj - 6v5o+MEOGWpE3U0uPdwgQ42ou9nlA05kqBF1dyk5BGSoEXV3KztoI0ONqHtI6WE/MtSIuodVHpihRtR9 - SdWhGWpE3ZdVHJyhRtRd4urhGWpE3WWuXJChRtRdavSSDDWi7nIjF2WoEXVP0XtZhhpR9zQ9F2aoEXVP - 1XpphhpR93QtF2eoEXUv8enyDDWi7mXOAjLUiLqXeheRoUbUvRyFZKhRNt8mYzLUaNt7u21Qhhrt4g3+ - ojLU6BBvQKFGGG9AsTYYLnH4XzkbirZ4NmawCUVbvDq3wSYUbbFr3X2QoGiLQ+9huBlFW1Cv6gekaAvq - fcLxBhRtQb0vOC5G0RbUu4PjQhRtQb0HOC5C0RbUi3BcgKItqPctHCejaAvqPYXjRBRtQb0f4TgJRVtQ - bxMcJ6BoC+pthmMxirag3i44FqJoC+rthmMRirag3iE4FqBoC+od9Pj6B65C9kZWwNPtAAAAAElFTkSu - QmCC + 6QAACukB/XXO0wAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAJ1SURBVHhe + zdtLbsJAAATRnCP3vydRrAThamPmP7V4KDT2uGCfr8fj8TXYtxhbu8XQicEmbB0ihg4MNmHrMDE0YrAJ + W4eKoQGDTdg6XAyVGGzC1iliqMBgE7ZOE0MhBpuwdaoYCjDYhK3TxfABg03YukQMNxhswtZlYniDwSZs + XSqGCww2YetyMQCDTdi6RQwvGGzC1m1i+MNgE7ZuFcNFsAlbt+PAYBO2Kry+YbBJhFv8/8Fgk4g2+X1h + sEkE2/y+MNoiYoW+jxchhhodrc8/RBhq9Ow9vRFgqNGpOYaNGGrEZs0PyFAjNh/efrAQQ43Y/HT74QIM + NWLzyccLJmKoEZtD0UUTMNSIzZeKLxyIoUZsfqvq4gEYasTmW9U3dGCoEZs/arqpAUON2Fyk+cYKDDVi + c7Gumwsw1IjNVboPuMFQIzZXG3LIBYYasbnJsINeMNSIzc2GHnYRasTmLiMPZKgRm7uNOpShRmweYsTB + DDVi8zC9hzPUiM1D9TyAoUZsHq71IQw1YvMULQ9iqBGbp6l9GEON2DxVzQMZasTm6UofylAjNi9R8mCG + GrF5mU8PZ6gRm5e6C2CoEZuXexfBUCM2b3EVwlAjNm/DGIYaxZfY6TWIoUbxBXb7j2KoUcQbMNIqwi0Y + ahTRIvG/cjYMNjkaGWzCYJNnJ6MtGGxyamW4AYNN2Kr7ARlswtZDDBsx2IStTzFswmATtp7EsAGDTdga + YliMwSZsvRTDQgw2YetbMSzCYBO23ophAQabsPWjGCZjsAlbi8QwEYNN2FoshkkYbMLWKjFMwGATtlaL + YTAGm7C1SQwDMdiErc1iGITBJmzt8gOuQvZGIglqfwAAAABJRU5ErkJggg== diff --git a/TSOClient/FSO.IDE/ClassDiagram1.cd b/TSOClient/FSO.IDE/ClassDiagram1.cd deleted file mode 100644 index d61921840..000000000 --- a/TSOClient/FSO.IDE/ClassDiagram1.cd +++ /dev/null @@ -1,977 +0,0 @@ - - - - - - AAAAAAAAACAAABAAAACAAAACAAAAAAAAAAAAAABAAAA= - AboutWindow.cs - - - - - - BZBEEAE0ACVAE6CwQVLkSoziCIAOARYCIWAI5FLTAcA= - BHAVEditor.cs - - - - - - AAAAIAAEACBAFAAIAiCAABDGwAEgACAAEAAAgAAAAAA= - EntityInspector.cs - - - - - - EAAAAAQAAAAAgAAAAAAAAAQAAAAAAAAAAAAAAAAACgA= - EntityInspector.cs - - - - - - AADAIAAAgAACgAAAQAABANAAABAAAAABIAAAAEBCAQA= - FSOUIControl.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAgAAAAgAAAAAAAAAAEAgAA= - IDETester.cs - - - - - - - C4EQFMECYCBEACEBAACzEMGCggYgQAARUmEQOIMUgAA= - MainWindow.cs - - - - - - AAAAAAAAIAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAA= - MainWindow.cs - - - - - - AkEAAABAMCAgADAAAADAEABGwAQAAAAAQIAAAAAAEAA= - ObjectBrowser.cs - - - - - - ACKCIQgAAiABQGEDAACAEABCJAEgAAAEYXAAAVBAQQA= - ObjectWindow.cs - - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAA= - Program.cs - - - - - - AAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Program.cs - - - - - - AAAAAAAAACAAAAAAAACAAAACAAAAIAAAAACAAAAAAAA= - Common\AvatarAnimatorControl.cs - - - - - - AAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - Common\ExternalWorld.cs - - - - - - AAAAAAAAACAAAAAAAAGAAAACAEAAAAAAAACQAAAAQAA= - Common\InteractiveDGRPControl.cs - - - - - - ABACAAAAAGAAABACAACAAAACAAAAAAAAAAAABQAQAAA= - Common\NewIffDialog.cs - - - - - - AAACAAAAACAEABAAAACAAAACAAAAACCAAEAABARQQQA= - Common\NewObjectDialog.cs - - - - - - AAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAA= - Common\ObjectRegistry.cs - - - - - - AABAAEAAAAAAQAAEAAAEAAQAAAAAQAIAAAAAAAABABA= - Common\ObjectRegistry.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAAAAAAAA= - Common\ObjThumbnailControl.cs - - - - - - AAQAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAgAAA= - Common\UIAvatarAnimator.cs - - - - - - AARBAAAAAAAAAgAAAEECAACAAAAAAQAAAAAwAAQAQAA= - Common\UIInteractiveDGRP.cs - - - - - - AARAAAAAAAAAAgAAgEACAAAAAAAAAQAAAgAgAAQAAAA= - Common\UIThumbnailRenderer.cs - - - - - - AAAAAAAAAAAAABAAQAAAABAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\BHAVViewControl.cs - - - - - - AAEAAAAAAADAQEFAJCAEABAACACAAAAAAAAjAAEABAg= - EditorComponent\EditorResource.cs - - - - - - AgAAAAQAAAEACAAhBCCABACCAAAgCACgQCAgCAEBQAA= - EditorComponent\EditorScope.cs - - - - - - AAAAAAAAAAAgAAAEAAAAAAQAAAAAQAAAAAAgAAAAABA= - EditorComponent\EditorScope.cs - - - - - - AAAAAAAAAAAAAQAAAAAAAAACAAABBAAAAAAgAAAAAAA= - EditorComponent\PrimitiveRegistry.cs - - - - - - AABAAAUAACAAAhCAFBSAAAACIAAEACACgAAAEMBAUQA= - EditorComponent\VarAnimSelect.cs - - - - - - BBAQAAAAACAAAAEAAACAAAACAAAAAAAAAAAABAAEQAA= - EditorComponent\VarObjectSelect.cs - - - - - - ALICAEEAACIAADASAASDgRACAkQAFAgAJMAQRAAQCKI= - EditorComponent\VarScopeSelect.cs - - - - - - AAAAAEAAAAAggAAEAAAAAAQAAAAAQAIAAAAAAAAAABA= - EditorComponent\VarScopeSelect.cs - - - - - - AAAAAAAAAAAABAQAAAAAAAAAAAABAAAAAAAAAAAAAAA= - EditorComponent\Commands\AddPrimCommand.cs - - - - - - AAAAAAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Commands\BHAVCommand.cs - - - - - - AAAAAAACAAEABAQAAABAACAAAAAAAAAAAAIAAAAAEAE= - EditorComponent\Commands\ChangeBHAVCommand.cs - - - - - - AAAAAAAABEAABAQAEACAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Commands\ChangePointerCommand.cs - - - - - - AAAAAAAAAAAABAQAAAAQAAAAAAAAAAAABAgAAAAAAAA= - EditorComponent\Commands\OpModifyCommand.cs - - - - - - AAAAAAAQAAAABAQAgAAAAAAAAAAAAAAAAAAAAIAAAAA= - EditorComponent\Commands\RemovePrimCommand.cs - - - - - - AAAAAAAQAAAABAQAkAAAAAAAIAIAAAAAAAAAAIAAAAA= - EditorComponent\Commands\SetFirstCommand.cs - - - - - - AAAAAAAABAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Commands\ToggleBreakpointCommand.cs - - - - - - AAAACAAACAAAACAgQAAAAAAAABAEIAAAAERAAAAACAE= - EditorComponent\DataView\PropGridVMData.cs - - - - - - - AAAAAAAQJAEwggAwQAAAAAAAAAAAEAQEAQACAAAACAE= - EditorComponent\DataView\VMDataPropertyDescriptor.cs - - - - - - AAAAAAAAAAAAgAQgAAAAAAAAAAAAAAAAAQAgAAAAAAE= - EditorComponent\DataView\VMModifyDataCommand.cs - - - - - - AAAAAAAAAAAAgAAEAAAAAAQAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Model\InstructionIDNamePair.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAA= - EditorComponent\Model\PGroupStyles.cs - - - - - - AAAAAAAAAAAAAEAAAAABAAAAAAAAAAAAAAAgAAAAEAA= - EditorComponent\Model\PGroupStyles.cs - - - - - - AAAAAFAAQAAAAAAAAAAAAAACAAAAAAAAAAgEACEAAAA= - EditorComponent\Model\PrimitiveDescriptor.cs - - - - - - AAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\DataProviders\OpDataProvider.cs - - - - - - AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\DataProviders\OpNamedPropertyProvider.cs - - - - - - AAAAAAAAABEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\DataProviders\OpNamedPropertyProvider.cs - - - - - - AAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\DataProviders\OpTextProvider.cs - - - - - - AAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAACAAAAAA= - EditorComponent\OperandForms\DataProviders\OpTextProvider.cs - - - - - - AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\DataProviders\OpValueBoundsProvider.cs - - - - - - AAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgA= - EditorComponent\OperandForms\DataProviders\OpValueBoundsProvider.cs - - - - - - AiAAAAABACAAACCAAACAAIACAAAAIAIAgEAAAAEAAAA= - EditorComponent\OperandForms\OpAnimControl.cs - - - - - - - AAAAgAAAACAAACCAAACAAoACAQAAAAIAgAAAAQkACAA= - EditorComponent\OperandForms\OpComboControl.cs - - - - - - - ACAABAAAACACACCAAACAAgACAIAAAAYAAAAAAAEAAAA= - EditorComponent\OperandForms\OpFlagsControl.cs - - - - - - - AAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAQAAAAA= - EditorComponent\OperandForms\OpFlagsControl.cs - - - - - - AAAAAAAAAAAAACAAAAAAAAAAAAAAAAIgAAAAAAEAAAA= - EditorComponent\OperandForms\OpLabelControl.cs - - - - - - - AgAAAAAAAiAAACCAAACAAIACAAAAIAIAgEAAAAEAAAA= - EditorComponent\OperandForms\OpObjectControl.cs - - - - - - - AgAAAAAAACAAACCAAACAAIACAAAAIAMAgEAAAAEgAAA= - EditorComponent\OperandForms\OpScopeControl.cs - - - - - - - AACAAAAAACAAAAAAAACAAAACAAAEAAAAAAAAAAAAAAA= - EditorComponent\OperandForms\OpUnknownControl.cs - - - - - - AAAAgAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAA= - EditorComponent\OperandForms\OpUtils.cs - - - - - - EAAAAEAgACAAACCAAACAAoACAAAAAAIAgAAAAQEAAAA= - EditorComponent\OperandForms\OpValueControl.cs - - - - - - - AAAAAFAAAAAAAAAAAAAAAACCAAAAAAAEAAgAACAAAAA= - EditorComponent\Primitives\AnimateSimDescriptor.cs - - - - - - AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Primitives\AnimateSimDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\BreakpointDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\CreateObjectInstanceDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\DropDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\DropOntoDescriptor.cs - - - - - - gAAAAFAAAAAAAAAAAAAAAAADAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\ExpressionDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\FindBestObjectForFunctionDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\FindLocationForDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\GetDirectionToDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\GetDistanceToDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\GotoRelativePositionDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\GotoRoutingSlotDescriptor.cs - - - - - - AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Primitives\GotoRoutingSlotDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\GrabDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\IdleForInputDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\LookTowardsDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\NotifyOutOfIdleDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\PlaySoundEventDescriptor.cs - - - - - - AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Primitives\PlaySoundEventDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\RandomNumberDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\RefreshDescriptor.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAA= - EditorComponent\Primitives\RelationshipDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\RelationshipDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\RemoveObjectInstanceDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\RunFunctionalTreeDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\SetMotiveChangeDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\SetToNextDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\SleepDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\SnapDescriptor.cs - - - - - - AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Primitives\SnapDescriptor.cs - - - - - - AAAAAFAAQAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\SubroutineDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAACAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\TestObjectTypeDescriptor.cs - - - - - - AAAAAFAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAACAAAAA= - EditorComponent\Primitives\UnknownPrimitiveDescriptor.cs - - - - - - ABQBEAgAAABAhqIEQEAAAAABEAAAIAIgAAYAQAAAAiI= - EditorComponent\UI\BHAVContainer.cs - - - - - - EISkAAAADEBAQkCQBAAAAEQACIAIAAAARQ5BCYEAADA= - EditorComponent\UI\PrimitiveBox.cs - - - - - - AASAAAAAAABAAgIwAiAAAAAAAAAACAAEAUQAAAAAAgA= - EditorComponent\UI\PrimitiveNode.cs - - - - - - AUQCggRkgLAAIgAoAAAAQAgBAAQAIggAYVABACgAgMI= - EditorComponent\UI\UIBHAVEditor.cs - - - - - - AAAAAAAAAAAAAACAIAAAAAAACAAAAAAAAAAQAAAgAAQ= - Managers\BHAVEditManager.cs - - - - - - AAAAAAAAgAAAAAAAAAAAAAAAQAAAgAAAAAAAAAAAAAA= - Managers\IffEditManager.cs - - - - - - AAAAAAAAAAAAABAAAAABEAAAAQAAAAAAAiAAAAAASIA= - - - - - - AAAAAAAAAAAAAAAAAAAAIAAAAAABAAAAAAAAAAAAAAA= - - - - - - uqaUEgIiGChQAhBBENiCkHD2AEAQMJwBBOIAMZlKGCQ= - ResourceBrowser\DGRPEditor.cs - - - - - - ABACAAAAACBCAAAAAACAAAACAAAAAAAAAAAIBAAQAAA= - ResourceBrowser\GenericTextInput.cs - - - - - - AAAAAAAACCAAAAAAAACAAAACAAAYAAAAEAAAAAAAAAA= - ResourceBrowser\GUIDChange.cs - - - - - - AFACAEAAACAAAAgAAACAAAACAAAACAAAAAACBAAQAQA= - ResourceBrowser\IffNameDialog.cs - - - - - - ABgQQBAARCEAQSAAAgCBFAEDQAEAABAAGQCiARBDVQo= - ResourceBrowser\IFFResComponent.cs - - - - - - AAAAAAAAAAAAgAAEAAAAAAQAAAAAAAAAAAAAAAAAAAA= - ResourceBrowser\IFFResComponent.cs - - - - - - AAAAAAAAAiAAEAAAAACAAAACAAEAAAAQAAAAAAAAAAA= - ResourceBrowser\IffResourceViewer.cs - - - - - - - /2tAqWRDKLAgATbfgAHIWDdChMAolzWChEATYKxJETM= - ResourceBrowser\OBJDEditor.cs - - - - - - AAAAAAAAAAAAAAAEAAAAAAQAAAQAAAAAAAAgAAAAAAA= - ResourceBrowser\OBJDEditor.cs - - - - - - AAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAQAAAAA= - ResourceBrowser\OBJDEditor.cs - - - - - - BKAAAQAAAKAACAEAAACAAACCAAAAAAAgAAAAAAAAAAA= - ResourceBrowser\OBJDSelectorControl.cs - - - - - - AAAAAAAAAAAAAAAEAAAAAAQAAACAAAAAAAAAAAAAQAA= - ResourceBrowser\OBJDSelectorControl.cs - - - - - - BQEQGAQQACBAAACEAACAKEQCAAQAAoABkAAMABAgIAA= - ResourceBrowser\OBJfEditor.cs - - - - - - AAAAAAAAACEAAAAAAACAAAACAAAAAAAQAAAAAAAAAAA= - ResourceBrowser\SelectorDialogs\SPR2SelectorDialog.cs - - - - - - BABAAAAAICAAIAEAAACAAAKCAACEAAAAQEAAAAJAAAA= - ResourceBrowser\SelectTreeDialog.cs - - - - - - B0AQAAAQBCCEIJAAAADBAAECEIEAgEDJgEQCGBgBACA= - ResourceBrowser\ResourceEditors\BHAVResourceControl.cs - - - - - - - AQCQQwUIIKAsIgEACCCRQAAGiAGAQAABABMAIIBAAAA= - ResourceBrowser\ResourceEditors\SPR2ResourceControl.cs - - - - - - - FYBAAABIAKAEABCAQDCkAAxCQAAAAgkFAAAAAhAAQMA= - ResourceBrowser\ResourceEditors\STRResourceControl.cs - - - - - - - CaCUgAQIIGVEQQEm0IDggxKyBxAMAG0F9OBARCzhvE4= - ResourceBrowser\ResourceEditors\TTABResourceControl.cs - - - - - - - AcAAAAAAACEEAAAAIACAEAACAAAAAAABACAAAAAAAAA= - ResourceBrowser\ResourceEditors\UnknownResourceControl.cs - - - - - - - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAA= - EditorComponent\OperandForms\IOpControl.cs - - - - - - AAAAAAAAAAAAAABAAAAAAAAAAAEEAAAAAAAAAACAAAA= - Managers\IffEditManager.cs - - - - - - AQAAAAAAAAAEAAAAAAAAAAAAAAAAAAABAAAAAAAAAAA= - ResourceBrowser\ResourceEditors\IResourceControl.cs - - - - - - AAAAAAAAAAAACAAAAAAAAAAAEAAAAAAAAAAAAAABAAA= - EditorComponent\EditorScope.cs - - - - - - BAAAAAAAABAAEgAAAAAAAAAAAAAAAAAAAAAAAAABAAA= - EditorComponent\VarScopeSelect.cs - - - - - - AAAAAAACAAAIAAAAAAAAAAAQAADQAABAAAAAiAABAAA= - EditorComponent\DataView\VMExtDataType.cs - - - - - - AABAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\Model\PrimitiveDescriptor.cs - - - - - - AAAAAAAEAAAAAIAgAAAEAAgAACAAAAAAAIAQAAIAAJA= - EditorComponent\Model\PrimitiveGroup.cs - - - - - - AAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAgAAIAAAAA= - EditorComponent\UI\PrimitiveBox.cs - - - - - - AABAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAA= - EditorComponent\UI\PrimitiveNode.cs - - - - - - AAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAA= - EditorComponent\UI\BHAVContainer.cs - - - - \ No newline at end of file diff --git a/TSOClient/FSO.IDE/Common/AvatarAnimatorControl.Designer.cs b/TSOClient/FSO.IDE/Common/AvatarAnimatorControl.Designer.cs index 3e582d715..b3f8a4b56 100644 --- a/TSOClient/FSO.IDE/Common/AvatarAnimatorControl.Designer.cs +++ b/TSOClient/FSO.IDE/Common/AvatarAnimatorControl.Designer.cs @@ -29,7 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { components = new System.ComponentModel.Container(); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; } #endregion diff --git a/TSOClient/FSO.IDE/Common/Debug/UI3DDGRP.cs b/TSOClient/FSO.IDE/Common/Debug/UI3DDGRP.cs index 4e87817d0..cb7057ad9 100644 --- a/TSOClient/FSO.IDE/Common/Debug/UI3DDGRP.cs +++ b/TSOClient/FSO.IDE/Common/Debug/UI3DDGRP.cs @@ -12,6 +12,8 @@ using FSO.Common.Rendering.Framework.Shapes; using FSO.LotView.Debug; +using Color = Microsoft.Xna.Framework.Color; + namespace FSO.IDE.Common.Debug { public class UI3DDGRP : UIInteractiveDGRP @@ -44,7 +46,7 @@ public override void Removed() private float RotationX; private float RotationY; private bool MouseDown; - private Point LastMouse; + private Microsoft.Xna.Framework.Point LastMouse; public override void Update(UpdateState state) { Scene.Update(state); diff --git a/TSOClient/FSO.IDE/Common/Debug3DControl.Designer.cs b/TSOClient/FSO.IDE/Common/Debug3DControl.Designer.cs index bd3d0dc85..4ee9b0805 100644 --- a/TSOClient/FSO.IDE/Common/Debug3DControl.Designer.cs +++ b/TSOClient/FSO.IDE/Common/Debug3DControl.Designer.cs @@ -29,7 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { components = new System.ComponentModel.Container(); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; } #endregion diff --git a/TSOClient/FSO.IDE/Common/Debug3DControl.cs b/TSOClient/FSO.IDE/Common/Debug3DControl.cs index fbb7beabc..07fe9c5c3 100644 --- a/TSOClient/FSO.IDE/Common/Debug3DControl.cs +++ b/TSOClient/FSO.IDE/Common/Debug3DControl.cs @@ -1,7 +1,8 @@ -using FSO.Client.UI.Framework; -using FSO.Client; -using FSO.IDE.Common.Debug; +using FSO.Client; +using FSO.Client.UI.Framework; +using FSO.Common.Utils; using FSO.Files.RC; +using FSO.IDE.Common.Debug; namespace FSO.IDE.Common { @@ -31,43 +32,43 @@ public void ShowObject(uint GUID) else { //reuse existing - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.SetGUID(GUID); - } + }); } } public void ChangeWorld(int rotation, int zoom) { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.ChangeWorld(rotation, zoom); - } + }); } public void ChangeGraphic(int gfx) { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.ChangeGraphic(gfx); - } + }); } public void ForceUpdate() { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.ForceUpdate(); - } + }); } public void SetDynamic(int i) { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.SetDynamic(i); - } + }); } } } diff --git a/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.Designer.cs b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.Designer.cs index c470238cc..ef36510e6 100644 --- a/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.Designer.cs +++ b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.Designer.cs @@ -29,7 +29,7 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { components = new System.ComponentModel.Container(); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; } #endregion diff --git a/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.cs b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.cs index 87bbce44e..4c5a0c7de 100644 --- a/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.cs +++ b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.cs @@ -1,5 +1,6 @@ -using FSO.Client.UI.Framework; -using FSO.Client; +using FSO.Client; +using FSO.Client.UI.Framework; +using FSO.Common.Utils; using FSO.SimAntics; using FSO.SimAntics.Entities; @@ -40,43 +41,43 @@ public void ShowObject(uint GUID) else { //reuse existing - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.SetGUID(GUID); - } + }); } } public void ChangeWorld(int rotation, int zoom) { - lock(FSOUI) + GameThread.InUpdate(() => { Renderer.ChangeWorld(rotation, zoom); - } + }); } public void ChangeGraphic(int gfx) { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.ChangeGraphic(gfx); - } + }); } public void ForceUpdate() { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.ForceUpdate(); - } + }); } public void SetDynamic(int i) { - lock (FSOUI) + GameThread.InUpdate(() => { Renderer.SetDynamic(i); - } + }); } } } diff --git a/TSOClient/FSO.UI/Debug/TSOEdith.resx b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.resx similarity index 97% rename from TSOClient/FSO.UI/Debug/TSOEdith.resx rename to TSOClient/FSO.IDE/Common/InteractiveDGRPControl.resx index 19dc0dd8b..1af7de150 100644 --- a/TSOClient/FSO.UI/Debug/TSOEdith.resx +++ b/TSOClient/FSO.IDE/Common/InteractiveDGRPControl.resx @@ -1,4 +1,4 @@ - + diff --git a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.Designer.cs b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.Designer.cs index 07a699a76..283baf404 100644 --- a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.Designer.cs @@ -28,240 +28,231 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Avatar_ID"); - System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Avatar", new System.Windows.Forms.TreeNode[] { - treeNode1}); + TreeNode treeNode1 = new TreeNode("Avatar_ID"); + TreeNode treeNode2 = new TreeNode("Avatar", new TreeNode[] { treeNode1 }); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(TSODataDefinitionEditor)); - this.PropGrid = new System.Windows.Forms.PropertyGrid(); - this.DataViewTabs = new System.Windows.Forms.TabControl(); - this.Struct1Tab = new System.Windows.Forms.TabPage(); - this.TreeView1S = new System.Windows.Forms.TreeView(); - this.Struct2Tab = new System.Windows.Forms.TabPage(); - this.TreeView2S = new System.Windows.Forms.TreeView(); - this.StructDTab = new System.Windows.Forms.TabPage(); - this.TreeViewDS = new System.Windows.Forms.TreeView(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.loadToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.activateIngameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.NewRoot = new System.Windows.Forms.Button(); - this.Delete = new System.Windows.Forms.Button(); - this.NewChild = new System.Windows.Forms.Button(); - this.saveAsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.DataViewTabs.SuspendLayout(); - this.Struct1Tab.SuspendLayout(); - this.Struct2Tab.SuspendLayout(); - this.StructDTab.SuspendLayout(); - this.menuStrip1.SuspendLayout(); - this.SuspendLayout(); + PropGrid = new PropertyGrid(); + DataViewTabs = new TabControl(); + Struct1Tab = new TabPage(); + TreeView1S = new TreeView(); + Struct2Tab = new TabPage(); + TreeView2S = new TreeView(); + StructDTab = new TabPage(); + TreeViewDS = new TreeView(); + menuStrip1 = new MenuStrip(); + fileToolStripMenuItem = new ToolStripMenuItem(); + loadToolStripMenuItem = new ToolStripMenuItem(); + saveToolStripMenuItem = new ToolStripMenuItem(); + saveAsToolStripMenuItem = new ToolStripMenuItem(); + activateIngameToolStripMenuItem = new ToolStripMenuItem(); + NewRoot = new Button(); + Delete = new Button(); + NewChild = new Button(); + DataViewTabs.SuspendLayout(); + Struct1Tab.SuspendLayout(); + Struct2Tab.SuspendLayout(); + StructDTab.SuspendLayout(); + menuStrip1.SuspendLayout(); + SuspendLayout(); // // PropGrid // - this.PropGrid.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Right))); - this.PropGrid.Location = new System.Drawing.Point(458, 27); - this.PropGrid.Name = "PropGrid"; - this.PropGrid.Size = new System.Drawing.Size(331, 382); - this.PropGrid.TabIndex = 0; - this.PropGrid.PropertyValueChanged += new System.Windows.Forms.PropertyValueChangedEventHandler(this.PropGrid_PropertyValueChanged); + PropGrid.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right; + PropGrid.BackColor = SystemColors.Control; + PropGrid.Location = new Point(458, 27); + PropGrid.Name = "PropGrid"; + PropGrid.Size = new Size(331, 382); + PropGrid.TabIndex = 0; + PropGrid.PropertyValueChanged += PropGrid_PropertyValueChanged; // // DataViewTabs // - this.DataViewTabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.DataViewTabs.Controls.Add(this.Struct1Tab); - this.DataViewTabs.Controls.Add(this.Struct2Tab); - this.DataViewTabs.Controls.Add(this.StructDTab); - this.DataViewTabs.Location = new System.Drawing.Point(12, 27); - this.DataViewTabs.Name = "DataViewTabs"; - this.DataViewTabs.SelectedIndex = 0; - this.DataViewTabs.Size = new System.Drawing.Size(440, 411); - this.DataViewTabs.TabIndex = 1; + DataViewTabs.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + DataViewTabs.Controls.Add(Struct1Tab); + DataViewTabs.Controls.Add(Struct2Tab); + DataViewTabs.Controls.Add(StructDTab); + DataViewTabs.Location = new Point(12, 27); + DataViewTabs.Name = "DataViewTabs"; + DataViewTabs.SelectedIndex = 0; + DataViewTabs.Size = new Size(440, 411); + DataViewTabs.TabIndex = 1; // // Struct1Tab // - this.Struct1Tab.Controls.Add(this.TreeView1S); - this.Struct1Tab.Location = new System.Drawing.Point(4, 22); - this.Struct1Tab.Name = "Struct1Tab"; - this.Struct1Tab.Padding = new System.Windows.Forms.Padding(3); - this.Struct1Tab.Size = new System.Drawing.Size(432, 385); - this.Struct1Tab.TabIndex = 0; - this.Struct1Tab.Text = "1st Level"; - this.Struct1Tab.UseVisualStyleBackColor = true; + Struct1Tab.Controls.Add(TreeView1S); + Struct1Tab.Location = new Point(4, 22); + Struct1Tab.Name = "Struct1Tab"; + Struct1Tab.Padding = new Padding(3); + Struct1Tab.Size = new Size(432, 385); + Struct1Tab.TabIndex = 0; + Struct1Tab.Text = "1st Level"; + Struct1Tab.UseVisualStyleBackColor = true; // // TreeView1S // - this.TreeView1S.Dock = System.Windows.Forms.DockStyle.Fill; - this.TreeView1S.FullRowSelect = true; - this.TreeView1S.HideSelection = false; - this.TreeView1S.Indent = 10; - this.TreeView1S.Location = new System.Drawing.Point(3, 3); - this.TreeView1S.Name = "TreeView1S"; + TreeView1S.Dock = DockStyle.Fill; + TreeView1S.FullRowSelect = true; + TreeView1S.HideSelection = false; + TreeView1S.Indent = 10; + TreeView1S.Location = new Point(3, 3); + TreeView1S.Name = "TreeView1S"; treeNode1.Name = "Node1"; treeNode1.Text = "Avatar_ID"; treeNode2.Name = "Node0"; treeNode2.Text = "Avatar"; - this.TreeView1S.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { - treeNode2}); - this.TreeView1S.ShowNodeToolTips = true; - this.TreeView1S.Size = new System.Drawing.Size(426, 379); - this.TreeView1S.TabIndex = 0; - this.TreeView1S.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TreeView1S_AfterSelect); + TreeView1S.Nodes.AddRange(new TreeNode[] { treeNode2 }); + TreeView1S.ShowNodeToolTips = true; + TreeView1S.Size = new Size(426, 379); + TreeView1S.TabIndex = 0; + TreeView1S.AfterSelect += TreeView1S_AfterSelect; // // Struct2Tab // - this.Struct2Tab.Controls.Add(this.TreeView2S); - this.Struct2Tab.Location = new System.Drawing.Point(4, 22); - this.Struct2Tab.Name = "Struct2Tab"; - this.Struct2Tab.Padding = new System.Windows.Forms.Padding(3); - this.Struct2Tab.Size = new System.Drawing.Size(532, 385); - this.Struct2Tab.TabIndex = 1; - this.Struct2Tab.Text = "2nd Level"; - this.Struct2Tab.UseVisualStyleBackColor = true; + Struct2Tab.Controls.Add(TreeView2S); + Struct2Tab.Location = new Point(4, 22); + Struct2Tab.Name = "Struct2Tab"; + Struct2Tab.Padding = new Padding(3); + Struct2Tab.Size = new Size(432, 385); + Struct2Tab.TabIndex = 1; + Struct2Tab.Text = "2nd Level"; + Struct2Tab.UseVisualStyleBackColor = true; // // TreeView2S // - this.TreeView2S.Dock = System.Windows.Forms.DockStyle.Fill; - this.TreeView2S.FullRowSelect = true; - this.TreeView2S.HideSelection = false; - this.TreeView2S.Indent = 10; - this.TreeView2S.Location = new System.Drawing.Point(3, 3); - this.TreeView2S.Name = "TreeView2S"; - this.TreeView2S.Size = new System.Drawing.Size(526, 379); - this.TreeView2S.TabIndex = 0; - this.TreeView2S.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TreeView2S_AfterSelect); + TreeView2S.Dock = DockStyle.Fill; + TreeView2S.FullRowSelect = true; + TreeView2S.HideSelection = false; + TreeView2S.Indent = 10; + TreeView2S.Location = new Point(3, 3); + TreeView2S.Name = "TreeView2S"; + TreeView2S.Size = new Size(426, 379); + TreeView2S.TabIndex = 0; + TreeView2S.AfterSelect += TreeView2S_AfterSelect; // // StructDTab // - this.StructDTab.Controls.Add(this.TreeViewDS); - this.StructDTab.Location = new System.Drawing.Point(4, 22); - this.StructDTab.Name = "StructDTab"; - this.StructDTab.Padding = new System.Windows.Forms.Padding(3); - this.StructDTab.Size = new System.Drawing.Size(532, 385); - this.StructDTab.TabIndex = 2; - this.StructDTab.Text = "Derived"; - this.StructDTab.UseVisualStyleBackColor = true; + StructDTab.Controls.Add(TreeViewDS); + StructDTab.Location = new Point(4, 22); + StructDTab.Name = "StructDTab"; + StructDTab.Padding = new Padding(3); + StructDTab.Size = new Size(432, 385); + StructDTab.TabIndex = 2; + StructDTab.Text = "Derived"; + StructDTab.UseVisualStyleBackColor = true; // // TreeViewDS // - this.TreeViewDS.Dock = System.Windows.Forms.DockStyle.Fill; - this.TreeViewDS.FullRowSelect = true; - this.TreeViewDS.HideSelection = false; - this.TreeViewDS.Indent = 10; - this.TreeViewDS.Location = new System.Drawing.Point(3, 3); - this.TreeViewDS.Name = "TreeViewDS"; - this.TreeViewDS.Size = new System.Drawing.Size(526, 379); - this.TreeViewDS.TabIndex = 0; - this.TreeViewDS.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.TreeViewDS_AfterSelect); + TreeViewDS.Dock = DockStyle.Fill; + TreeViewDS.FullRowSelect = true; + TreeViewDS.HideSelection = false; + TreeViewDS.Indent = 10; + TreeViewDS.Location = new Point(3, 3); + TreeViewDS.Name = "TreeViewDS"; + TreeViewDS.Size = new Size(426, 379); + TreeViewDS.TabIndex = 0; + TreeViewDS.AfterSelect += TreeViewDS_AfterSelect; // // menuStrip1 // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(801, 24); - this.menuStrip1.TabIndex = 2; - this.menuStrip1.Text = "menuStrip1"; + menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(801, 24); + menuStrip1.TabIndex = 2; + menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.loadToolStripMenuItem, - this.saveToolStripMenuItem, - this.saveAsToolStripMenuItem, - this.activateIngameToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); - this.fileToolStripMenuItem.Text = "File"; + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { loadToolStripMenuItem, saveToolStripMenuItem, saveAsToolStripMenuItem, activateIngameToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new Size(37, 20); + fileToolStripMenuItem.Text = "File"; + // + // loadToolStripMenuItem + // + loadToolStripMenuItem.Name = "loadToolStripMenuItem"; + loadToolStripMenuItem.Size = new Size(160, 22); + loadToolStripMenuItem.Text = "Load"; + loadToolStripMenuItem.Click += loadToolStripMenuItem_Click; // // saveToolStripMenuItem // - this.saveToolStripMenuItem.Name = "saveToolStripMenuItem"; - this.saveToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.saveToolStripMenuItem.Text = "Save"; - this.saveToolStripMenuItem.Click += new System.EventHandler(this.saveToolStripMenuItem_Click); + saveToolStripMenuItem.Name = "saveToolStripMenuItem"; + saveToolStripMenuItem.Size = new Size(160, 22); + saveToolStripMenuItem.Text = "Save"; + saveToolStripMenuItem.Click += saveToolStripMenuItem_Click; // - // loadToolStripMenuItem + // saveAsToolStripMenuItem // - this.loadToolStripMenuItem.Name = "loadToolStripMenuItem"; - this.loadToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.loadToolStripMenuItem.Text = "Load"; - this.loadToolStripMenuItem.Click += new System.EventHandler(this.loadToolStripMenuItem_Click); + saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; + saveAsToolStripMenuItem.Size = new Size(160, 22); + saveAsToolStripMenuItem.Text = "Save As..."; + saveAsToolStripMenuItem.Click += saveAsToolStripMenuItem_Click; // // activateIngameToolStripMenuItem // - this.activateIngameToolStripMenuItem.Name = "activateIngameToolStripMenuItem"; - this.activateIngameToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.activateIngameToolStripMenuItem.Text = "Activate Ingame"; - this.activateIngameToolStripMenuItem.Click += new System.EventHandler(this.activateIngameToolStripMenuItem_Click); + activateIngameToolStripMenuItem.Name = "activateIngameToolStripMenuItem"; + activateIngameToolStripMenuItem.Size = new Size(160, 22); + activateIngameToolStripMenuItem.Text = "Activate Ingame"; + activateIngameToolStripMenuItem.Click += activateIngameToolStripMenuItem_Click; // // NewRoot // - this.NewRoot.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.NewRoot.Location = new System.Drawing.Point(458, 415); - this.NewRoot.Name = "NewRoot"; - this.NewRoot.Size = new System.Drawing.Size(75, 23); - this.NewRoot.TabIndex = 3; - this.NewRoot.Text = "New Root"; - this.NewRoot.UseVisualStyleBackColor = true; - this.NewRoot.Click += new System.EventHandler(this.NewRoot_Click); + NewRoot.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + NewRoot.Location = new Point(458, 415); + NewRoot.Name = "NewRoot"; + NewRoot.Size = new Size(75, 23); + NewRoot.TabIndex = 3; + NewRoot.Text = "New Root"; + NewRoot.UseVisualStyleBackColor = true; + NewRoot.Click += NewRoot_Click; // // Delete // - this.Delete.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.Delete.Location = new System.Drawing.Point(714, 415); - this.Delete.Name = "Delete"; - this.Delete.Size = new System.Drawing.Size(75, 23); - this.Delete.TabIndex = 4; - this.Delete.Text = "Delete"; - this.Delete.UseVisualStyleBackColor = true; - this.Delete.Click += new System.EventHandler(this.Delete_Click); + Delete.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + Delete.Location = new Point(714, 415); + Delete.Name = "Delete"; + Delete.Size = new Size(75, 23); + Delete.TabIndex = 4; + Delete.Text = "Delete"; + Delete.UseVisualStyleBackColor = true; + Delete.Click += Delete_Click; // // NewChild // - this.NewChild.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.NewChild.Location = new System.Drawing.Point(539, 415); - this.NewChild.Name = "NewChild"; - this.NewChild.Size = new System.Drawing.Size(75, 23); - this.NewChild.TabIndex = 5; - this.NewChild.Text = "New Child"; - this.NewChild.UseVisualStyleBackColor = true; - this.NewChild.Click += new System.EventHandler(this.NewChild_Click); - // - // saveAsToolStripMenuItem - // - this.saveAsToolStripMenuItem.Name = "saveAsToolStripMenuItem"; - this.saveAsToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.saveAsToolStripMenuItem.Text = "Save As..."; - this.saveAsToolStripMenuItem.Click += new System.EventHandler(this.saveAsToolStripMenuItem_Click); + NewChild.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + NewChild.Location = new Point(539, 415); + NewChild.Name = "NewChild"; + NewChild.Size = new Size(75, 23); + NewChild.TabIndex = 5; + NewChild.Text = "New Child"; + NewChild.UseVisualStyleBackColor = true; + NewChild.Click += NewChild_Click; // // TSODataDefinitionEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(801, 450); - this.Controls.Add(this.NewChild); - this.Controls.Add(this.Delete); - this.Controls.Add(this.NewRoot); - this.Controls.Add(this.DataViewTabs); - this.Controls.Add(this.PropGrid); - this.Controls.Add(this.menuStrip1); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.menuStrip1; - this.Name = "TSODataDefinitionEditor"; - this.Text = "Data Service Editor"; - this.Load += new System.EventHandler(this.TSODataDefinitionEditor_Load); - this.DataViewTabs.ResumeLayout(false); - this.Struct1Tab.ResumeLayout(false); - this.Struct2Tab.ResumeLayout(false); - this.StructDTab.ResumeLayout(false); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(801, 450); + Controls.Add(NewChild); + Controls.Add(Delete); + Controls.Add(NewRoot); + Controls.Add(DataViewTabs); + Controls.Add(PropGrid); + Controls.Add(menuStrip1); + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + Name = "TSODataDefinitionEditor"; + Text = "Data Service Editor"; + Load += TSODataDefinitionEditor_Load; + DataViewTabs.ResumeLayout(false); + Struct1Tab.ResumeLayout(false); + Struct2Tab.ResumeLayout(false); + StructDTab.ResumeLayout(false); + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.cs b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.cs index 4d019ee36..bd0d36026 100644 --- a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.cs +++ b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.cs @@ -7,6 +7,7 @@ using System.Windows.Forms; using FSO.Files.Formats.tsodata; +using System.ComponentModel; namespace FSO.IDE.ContentEditors { @@ -14,6 +15,8 @@ public partial class TSODataDefinitionEditor : Form { public TSODataDefinition Data; private object _CurrentSelection; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public object CurrentSelection { get diff --git a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.resx b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.resx index ea07ded6c..1922e3900 100644 --- a/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.resx +++ b/TSOClient/FSO.IDE/ContentEditors/TSODataDefinitionEditor.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/EditorComponent/Commands/UpdateBoxPosCommand.cs b/TSOClient/FSO.IDE/EditorComponent/Commands/UpdateBoxPosCommand.cs index 5eb73edee..2f09294d6 100644 --- a/TSOClient/FSO.IDE/EditorComponent/Commands/UpdateBoxPosCommand.cs +++ b/TSOClient/FSO.IDE/EditorComponent/Commands/UpdateBoxPosCommand.cs @@ -2,6 +2,8 @@ using FSO.IDE.EditorComponent.UI; using Microsoft.Xna.Framework; +using Point = Microsoft.Xna.Framework.Point; + namespace FSO.IDE.EditorComponent.Commands { public class UpdateBoxPosCommand : BHAVCommand diff --git a/TSOClient/FSO.IDE/EditorComponent/EditorResource.cs b/TSOClient/FSO.IDE/EditorComponent/EditorResource.cs index e395c99bf..644802fdb 100644 --- a/TSOClient/FSO.IDE/EditorComponent/EditorResource.cs +++ b/TSOClient/FSO.IDE/EditorComponent/EditorResource.cs @@ -6,6 +6,8 @@ using Microsoft.Xna.Framework.Graphics; using System.IO; +using Color = Microsoft.Xna.Framework.Color; + namespace FSO.IDE.EditorComponent { public class EditorResource diff --git a/TSOClient/FSO.IDE/EditorComponent/EditorScope.cs b/TSOClient/FSO.IDE/EditorComponent/EditorScope.cs index 02274989d..12d5be257 100644 --- a/TSOClient/FSO.IDE/EditorComponent/EditorScope.cs +++ b/TSOClient/FSO.IDE/EditorComponent/EditorScope.cs @@ -338,7 +338,7 @@ public string GetTuningVariableLabel(ushort data) { if (labels != null && keyID < labels.Entries.Length) { - return labels.Entries[keyID].Label + " #" + keyID; + return bcon.ChunkLabel + ": " + labels.Entries[keyID].Label + " #" + keyID; } return bcon.ChunkLabel + " #" + keyID; } @@ -351,12 +351,12 @@ public string GetTuningVariableLabel(ushort data) break; case 2: bcon = Globals.Resource.Get((ushort)(tableID + 256)); - labels = Object.Resource.Get((ushort)(tableID + 256)); + labels = Globals.Resource.Get((ushort)(tableID + 256)); if (bcon != null) { if (labels != null && keyID < labels.Entries.Length) { - return labels.Entries[keyID].Label + " #" + keyID; + return bcon.ChunkLabel + ": " + labels.Entries[keyID].Label + " #" + keyID; } return bcon.ChunkLabel + " #" + keyID; } diff --git a/TSOClient/FSO.IDE/EditorComponent/Model/PGroupStyles.cs b/TSOClient/FSO.IDE/EditorComponent/Model/PGroupStyles.cs index b9a2d9954..a588ba7c4 100644 --- a/TSOClient/FSO.IDE/EditorComponent/Model/PGroupStyles.cs +++ b/TSOClient/FSO.IDE/EditorComponent/Model/PGroupStyles.cs @@ -1,6 +1,8 @@ using Microsoft.Xna.Framework; using System.Collections.Generic; +using Color = Microsoft.Xna.Framework.Color; + namespace FSO.IDE.EditorComponent.Model { public static class PGroupStyles diff --git a/TSOClient/FSO.IDE/EditorComponent/OperandForms/OpUnknownControl.Designer.cs b/TSOClient/FSO.IDE/EditorComponent/OperandForms/OpUnknownControl.Designer.cs index 9ac29db22..7a17e864c 100644 --- a/TSOClient/FSO.IDE/EditorComponent/OperandForms/OpUnknownControl.Designer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/OperandForms/OpUnknownControl.Designer.cs @@ -44,8 +44,8 @@ private void InitializeComponent() // // OpUnknownControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.AutoSize = true; this.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; this.Controls.Add(this.UnkLabel); diff --git a/TSOClient/FSO.IDE/EditorComponent/UI/BHAVContainer.cs b/TSOClient/FSO.IDE/EditorComponent/UI/BHAVContainer.cs index c0fabbc8a..8a2ef751f 100644 --- a/TSOClient/FSO.IDE/EditorComponent/UI/BHAVContainer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/UI/BHAVContainer.cs @@ -7,6 +7,9 @@ using System.Collections.Generic; using System.Linq; +using Color = Microsoft.Xna.Framework.Color; +using Rectangle = Microsoft.Xna.Framework.Rectangle; + namespace FSO.IDE.EditorComponent.UI { public class BHAVContainer : UIContainer diff --git a/TSOClient/FSO.IDE/EditorComponent/UI/CommentContainer.cs b/TSOClient/FSO.IDE/EditorComponent/UI/CommentContainer.cs index 40ebf697d..1c90b9d18 100644 --- a/TSOClient/FSO.IDE/EditorComponent/UI/CommentContainer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/UI/CommentContainer.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; +using Color = Microsoft.Xna.Framework.Color; + namespace FSO.IDE.EditorComponent.UI { public class CommentContainer : UIContainer @@ -109,7 +111,7 @@ public void ToggleHidden(bool hidden) { "InvalidationDummy", 1 }, }, TweenQuad.EaseOut); if (Collapsed) { - AddCommentListener = ListenForMouse(new Rectangle(-5, -30, 35, 35), MouseEvent); + AddCommentListener = ListenForMouse(new Microsoft.Xna.Framework.Rectangle(-5, -30, 35, 35), MouseEvent); } } } @@ -170,7 +172,7 @@ public void ToggleCollapsed(bool collapsed) ClickLabel.Caption = (CommentEmpty) ? "+" : ".."; ClickLabel.Visible = Collapsed; TextEdit.Visible = !Collapsed; - if (Collapsed) SetSize(new Rectangle()); + if (Collapsed) SetSize(new Microsoft.Xna.Framework.Rectangle()); else ResizeBasedOnTextEdit(); } @@ -203,7 +205,7 @@ public void ResizeBasedOnTextEdit() SetSize(bounds); } - public void SetSize(Rectangle rect) + public void SetSize(Microsoft.Xna.Framework.Rectangle rect) { if (BgTween != null) BgTween.Complete(); var margin = 7; diff --git a/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveBox.cs b/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveBox.cs index 51fc4d15b..aa384f993 100644 --- a/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveBox.cs +++ b/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveBox.cs @@ -13,6 +13,9 @@ using FSO.IDE.EditorComponent.Primitives; using FSO.IDE.EditorComponent.Commands; using Microsoft.Xna.Framework.Input; +using ButtonState = Microsoft.Xna.Framework.Input.ButtonState; + +using Color = Microsoft.Xna.Framework.Color; namespace FSO.IDE.EditorComponent.UI { @@ -106,7 +109,7 @@ public PrimitiveBox(BHAVInstruction inst, BHAVContainer master) TreeBox = new TREEBox(null); Master = master; Instruction = inst; - HitTest = ListenForMouse(new Rectangle(0, 0, Width, Height), new UIMouseEvent(MouseEvents)); + HitTest = ListenForMouse(new Microsoft.Xna.Framework.Rectangle(0, 0, Width, Height), new UIMouseEvent(MouseEvents)); PreparePrimitive(); } @@ -116,7 +119,7 @@ public PrimitiveBox(TREEBox box, BHAVContainer master) Master = master; Nodes = new PrimitiveNode[0]; ApplyBoxPosition(); - HitTest = ListenForMouse(new Rectangle(0, 0, Width, Height), new UIMouseEvent(MouseEvents)); + HitTest = ListenForMouse(new Microsoft.Xna.Framework.Rectangle(0, 0, Width, Height), new UIMouseEvent(MouseEvents)); Texture2D sliceTex = null; switch (Type) { @@ -358,7 +361,7 @@ public void UpdateDisplay() BodyText = Descriptor.GetBody(Master.Scope); BodyTextStyle.Color = Style.Body; - BodyTextLabels = TextRenderer.ComputeText(BodyText, new TextRendererOptions + BodyTextLabels = Client.UI.Framework.TextRenderer.ComputeText(BodyText, new TextRendererOptions { Alignment = TextAlignment.Center, MaxWidth = 300, @@ -398,7 +401,7 @@ public void UpdateDisplay() } } - private void DrawSliceShadow(UISpriteBatch batch, Color color, Vector2 offset) + private void DrawSliceShadow(UISpriteBatch batch, Microsoft.Xna.Framework.Color color, Vector2 offset) { var blend = SliceBg.BlendColor; color.A = (byte)(blend.A * (color.A / 255f)); @@ -419,7 +422,7 @@ public void ShadDraw(UISpriteBatch batch) DrawSliceShadow(batch, new Color(0, 0, 0, 51), new Vector2(5)); } else if (Style == null || Style.Background.A > 200) DrawLocalTexture(batch, res.WhiteTex, null, new Vector2(5,5), new Vector2(Width, Height), ShadCol); - else DrawTiledTexture(batch, res.DiagTile, new Rectangle(5, 5, Width, Height), ShadCol); + else DrawTiledTexture(batch, res.DiagTile, new Microsoft.Xna.Framework.Rectangle(5, 5, Width, Height), ShadCol); if (Type == TREEBoxType.Primitive) { @@ -453,7 +456,7 @@ public override void CalculateMatrix() if (Type == TREEBoxType.Primitive) { - BodyTextLabels = TextRenderer.ComputeText(BodyText, new TextRendererOptions + BodyTextLabels = Client.UI.Framework.TextRenderer.ComputeText(BodyText, new TextRendererOptions { Alignment = TextAlignment.Center, MaxWidth = 300, @@ -507,14 +510,14 @@ public override void Draw(UISpriteBatch batch) { if (Style.Background.A > 200) DrawLocalTexture(batch, res.WhiteTex, null, new Vector2(), new Vector2(Width, Height), Master.Selected.Contains(this) ? Color.Red : Color.White); //white outline DrawLocalTexture(batch, res.WhiteTex, null, new Vector2(1, 1), new Vector2(Width - 2, Height - 2), Style.Background); //background - DrawTiledTexture(batch, res.DiagTile, new Rectangle(1, 1, Width - 2, Height - 2), Color.White * Style.DiagBrightness); + DrawTiledTexture(batch, res.DiagTile, new Microsoft.Xna.Framework.Rectangle(1, 1, Width - 2, Height - 2), Color.White * Style.DiagBrightness); DrawLocalTexture(batch, res.WhiteTex, null, new Vector2(1, 1), new Vector2(Width - 2, 20), Color.White * 0.66f); //title bg } Index?.Draw(batch); Title?.Draw(batch); TextEdit?.Draw(batch); - if (BodyTextLabels != null) TextRenderer.DrawText(BodyTextLabels.DrawingCommands, this, batch); + if (BodyTextLabels != null) Client.UI.Framework.TextRenderer.DrawText(BodyTextLabels.DrawingCommands, this, batch); int topInd = 0; if (Instruction?.Breakpoint == true) @@ -647,7 +650,7 @@ public Vector2 SnapToNearbyPrims(UpdateState state, Vector2 defaultPosition, Vec { if (prim == this) continue; - Rectangle r = new Rectangle((int)(prim.X - hitboxMarginX), + Microsoft.Xna.Framework.Rectangle r = new Microsoft.Xna.Framework.Rectangle((int)(prim.X - hitboxMarginX), (int)(prim.Y - hitboxMarginY), (int)(prim.Width + hitboxMarginX * 2), (int)(prim.Height + hitboxMarginY * 2)); // create a hitbox around the prim to test if the mouse is inside it diff --git a/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveNode.cs b/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveNode.cs index 869f2eed9..849e4fd56 100644 --- a/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveNode.cs +++ b/TSOClient/FSO.IDE/EditorComponent/UI/PrimitiveNode.cs @@ -7,6 +7,9 @@ using FSO.Common.Rendering.Framework.IO; using FSO.IDE.EditorComponent.Commands; +using Color = Microsoft.Xna.Framework.Color; +using Rectangle = Microsoft.Xna.Framework.Rectangle; + namespace FSO.IDE.EditorComponent.UI { public class PrimitiveNode : UIContainer diff --git a/TSOClient/FSO.IDE/EditorComponent/UI/UIBHAVEditor.cs b/TSOClient/FSO.IDE/EditorComponent/UI/UIBHAVEditor.cs index f7c0acc60..9537df7f1 100644 --- a/TSOClient/FSO.IDE/EditorComponent/UI/UIBHAVEditor.cs +++ b/TSOClient/FSO.IDE/EditorComponent/UI/UIBHAVEditor.cs @@ -15,6 +15,9 @@ using FSO.IDE.EditorComponent.DataView; using FSO.Client; using FSO.Common.Utils; +using ButtonState = Microsoft.Xna.Framework.Input.ButtonState; + +using Color = Microsoft.Xna.Framework.Color; namespace FSO.IDE.EditorComponent.UI { @@ -618,7 +621,7 @@ private void DrawLine(Texture2D Fill, Vector2 Start, Vector2 End, SpriteBatch sp End.Y += lineWidth / 2; double length = Math.Sqrt(Math.Pow(End.X - Start.X, 2) + Math.Pow(End.Y - Start.Y, 2)); float direction = (float)Math.Atan2(End.Y - Start.Y, End.X - Start.X); - spriteBatch.Draw(Fill, new Rectangle((int)Start.X, (int)Start.Y - (int)(lineWidth / 2), (int)length, lineWidth), null, tint, direction, new Vector2(0, 0.5f), SpriteEffects.None, 0); // + spriteBatch.Draw(Fill, new Microsoft.Xna.Framework.Rectangle((int)Start.X, (int)Start.Y - (int)(lineWidth / 2), (int)length, lineWidth), null, tint, direction, new Vector2(0, 0.5f), SpriteEffects.None, 0); // } } diff --git a/TSOClient/FSO.IDE/EditorComponent/VarAnimSelect.Designer.cs b/TSOClient/FSO.IDE/EditorComponent/VarAnimSelect.Designer.cs index 2f71fec25..c0c73a736 100644 --- a/TSOClient/FSO.IDE/EditorComponent/VarAnimSelect.Designer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/VarAnimSelect.Designer.cs @@ -169,8 +169,8 @@ private void InitializeComponent() // // VarAnimSelect // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(584, 461); this.Controls.Add(this.glTFImportButton); this.Controls.Add(this.FBXButton); diff --git a/TSOClient/FSO.IDE/EditorComponent/VarObjectSelect.Designer.cs b/TSOClient/FSO.IDE/EditorComponent/VarObjectSelect.Designer.cs index 8b50143ab..98f2b4183 100644 --- a/TSOClient/FSO.IDE/EditorComponent/VarObjectSelect.Designer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/VarObjectSelect.Designer.cs @@ -68,8 +68,8 @@ private void InitializeComponent() // // VarObjectSelect // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(493, 383); this.Controls.Add(this.CancelButton); this.Controls.Add(this.SelectButton); diff --git a/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.Designer.cs b/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.Designer.cs index dea630b4e..d7d7d3b3d 100644 --- a/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.Designer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.Designer.cs @@ -191,8 +191,8 @@ private void InitializeComponent() // // VarScopeSelect // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(393, 303); this.Controls.Add(this.DataDesc); this.Controls.Add(this.DataValue); diff --git a/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.cs b/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.cs index 411adc56a..960bc57f5 100644 --- a/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.cs +++ b/TSOClient/FSO.IDE/EditorComponent/VarScopeSelect.cs @@ -4,6 +4,7 @@ using System.Data; using System.Linq; using System.Windows.Forms; +using System.ComponentModel; namespace FSO.IDE.EditorComponent { @@ -155,6 +156,8 @@ public byte SelectedSource return SelectedDef.ID; } } + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public short SelectedData { set { DataValue.Value = value; } get { return (short)DataValue.Value; } diff --git a/TSOClient/FSO.IDE/EditorComponent/VarSoundSelect.Designer.cs b/TSOClient/FSO.IDE/EditorComponent/VarSoundSelect.Designer.cs index 20a851bd4..556e64ac2 100644 --- a/TSOClient/FSO.IDE/EditorComponent/VarSoundSelect.Designer.cs +++ b/TSOClient/FSO.IDE/EditorComponent/VarSoundSelect.Designer.cs @@ -139,8 +139,8 @@ private void InitializeComponent() // // VarSoundSelect // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(422, 461); this.Controls.Add(this.RemoveButton); this.Controls.Add(this.MyList); diff --git a/TSOClient/FSO.IDE/EntityInspector.Designer.cs b/TSOClient/FSO.IDE/EntityInspector.Designer.cs index 2cee9e5d8..e9a3fc4cb 100644 --- a/TSOClient/FSO.IDE/EntityInspector.Designer.cs +++ b/TSOClient/FSO.IDE/EntityInspector.Designer.cs @@ -130,8 +130,8 @@ private void InitializeComponent() // // EntityInspector // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.OpenResource); this.Controls.Add(this.DeleteButton); this.Controls.Add(this.TracerButton); diff --git a/TSOClient/FSO.IDE/FSO.IDE.csproj b/TSOClient/FSO.IDE/FSO.IDE.csproj index 787f3eee8..f02e38be0 100644 --- a/TSOClient/FSO.IDE/FSO.IDE.csproj +++ b/TSOClient/FSO.IDE/FSO.IDE.csproj @@ -1,817 +1,41 @@ - - - + + - Debug - AnyCPU - {5DEB20EB-1EB7-48F9-922C-463ABAE56E63} + net9.0-windows + enable + disable WinExe - Properties FSO.IDE Volcanic - v4.6.1 512 - true - - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - - AnyCPU - true - full - false - bin\Debug\ - TRACE;DEBUG - prompt - 4 - false - True - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - True - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 3 - false - true - True - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - false - true - True - - + true + true + false + false + partial IDE.ico + true + true + Segoe UI, 8.25pt + true + false + true - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - True - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - True - + - - - ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - ..\packages\System.Memory.4.5.3\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - + + + + - - Form - - - AboutWindow.cs - - - Form - - - AddAppearanceWindow.cs - - - - - UserControl - - - AvatarAnimatorControl.cs - - - UserControl - - - InteractiveDGRPControl.cs - - - - Form - - - NewIffDialog.cs - - - Form - - - NewObjectDialog.cs - - - - UserControl - - - - - - - UserControl - - - Debug3DControl.cs - - - Form - - - AOTGenerator.cs - - - Form - - - AvatarTool.cs - - - Form - - - TSODataDefinitionEditor.cs - - - - UserControl - - - - - - - - - - - - - - - - - - - - - - - UserControl - - - OpSoundControl.cs - - - UserControl - - - OpAnimControl.cs - - - UserControl - - - OpScopeControl.cs - - - UserControl - - - OpObjectControl.cs - - - Component - - - OpFlagsControl.cs - - - Component - - - - UserControl - - - OpValueControl.cs - - - UserControl - - - OpComboControl.cs - - - UserControl - - - OpUnknownControl.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Form - - - VarSoundSelect.cs - - - Form - - - VarAnimSelect.cs - - - Form - - - VarObjectSelect.cs - - - Form - - - VarScopeSelect.cs - - - UserControl - - - EntityInspector.cs - - - UserControl - - - - - - - - - - - - - - - - - - Form - - - BHAVEditor.cs - - - Form - - - MainWindow.cs - - - - - UserControl - - - ObjectBrowser.cs - - - Form - - - ObjectWindow.cs - - - - - UserControl - - - DGRPEditor.cs - - - UserControl - - - FSOMEditor.cs - - - Form - - - GenericTextInput.cs - - - Form - - - GUIDChange.cs - - - Form - - - IffNameDialog.cs - - - Form - - - IffResourceViewer.cs - - - UserControl - - - IFFResComponent.cs - - - UserControl - - - OTFResourceControl.cs - - - UserControl - - - PIFFEditor.cs - - - UserControl - - - UpgradeEditor.cs - - - UserControl - - - XMLEntryEditor.cs - - - UserControl - - - OBJDEditor.cs - - - UserControl - - - OBJDSelectorControl.cs - - - UserControl - - - OBJfEditor.cs - - - UserControl - - - BHAVResourceControl.cs - - - - UserControl - - - SLOTResourceControl.cs - - - UserControl - - - SPR2ResourceControl.cs - - - UserControl - - - BCONResourceControl.cs - - - UserControl - - - TTABResourceControl.cs - - - UserControl - - - STRResourceControl.cs - - - UserControl - - - UnknownResourceControl.cs - - - Form - - - SPR2SelectorDialog.cs - - - Form - - - SelectTreeDialog.cs - - - - Form - - - FieldEncodingFormatTracker.cs - - - - - - Form - - - HouseSpy.cs - - - - AboutWindow.cs - - - AddAppearanceWindow.cs - - - NewIffDialog.cs - - - NewObjectDialog.cs - - - AOTGenerator.cs - - - AvatarTool.cs - - - TSODataDefinitionEditor.cs - - - BHAVViewControl.cs - - - OpSoundControl.cs - - - OpAnimControl.cs - - - OpScopeControl.cs - - - OpObjectControl.cs - - - OpFlagsControl.cs - - - OpValueControl.cs - - - OpComboControl.cs - - - OpUnknownControl.cs - - - VarSoundSelect.cs - - - VarAnimSelect.cs - - - VarObjectSelect.cs - - - VarScopeSelect.cs - - - EntityInspector.cs - - - FSOUIControl.cs - - - MainWindow.cs - - - ObjectBrowser.cs - - - ObjectWindow.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - BHAVEditor.cs - - - DGRPEditor.cs - - - FSOMEditor.cs - - - GenericTextInput.cs - - - GUIDChange.cs - - - IffNameDialog.cs - - - IFFResComponent.cs - - - IffResourceViewer.cs - - - PIFFEditor.cs - - - OTFResourceControl.cs - - - UpgradeEditor.cs - - - XMLEntryEditor.cs - - - OBJDEditor.cs - - - OBJDSelectorControl.cs - - - OBJfEditor.cs - - - BHAVResourceControl.cs - - - SLOTResourceControl.cs - - - SPR2ResourceControl.cs - - - BCONResourceControl.cs - - - TTABResourceControl.cs - - - STRResourceControl.cs - - - UnknownResourceControl.cs - - - SPR2SelectorDialog.cs - - - SelectTreeDialog.cs - - - FieldEncodingFormatTracker.cs - - - HouseSpy.cs - SettingsSingleFileGenerator Settings.Designer.cs - - True - Settings.settings - True - - - - - - - - {37812a22-91f3-4220-891e-5c26da64a975} - SimplePaletteQuantizer - - - {834cab58-648d-47cc-ac6f-d01c08c809a4} - Mp3Sharp - - - {56f4bd87-2404-4263-80d5-6fa2161eb0a4} - TargaImage - - - {4e43ce64-343f-4c53-a055-bbf0f4986a16} - FSO.Patcher - - - {b3de74c1-b7a1-4773-bd36-993988b23527} - FSO.SimAntics.JIT.Roslyn - False - - - {b8ab3711-7b4f-4126-9bf3-4ddde9475b74} - FSO.SimAntics.JIT - - - {73e2ad5b-720b-4ef3-9b7c-55931d0ec693} - FSO.UI - - - {39201960-f96f-4039-84b1-1331d5dde3c2} - FSO.Windows - - - {635e68fa-3905-4943-b4f5-d463a8c02e87} - FSO.Client - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {5eddefd2-c850-49c1-812d-ddeff09125ef} - FSO.SimAntics - - - {072781d8-51ec-4143-9cae-daf50177d3ad} - FSO.HIT - - - {fd7957f7-a1e0-4d00-8f6c-3fa555eaa163} - FSO.Vitaboy.Engine - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - - - {b1a6e4c2-e080-4c34-a604-d11b5296a9b8} - FSO.LotView - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 4.5 - true - + @@ -903,23 +127,69 @@ PreserveNewest - - - 3.6.0.1625 - - - 12.0.2 - - - 1.0.0-alpha0011 - - - - + + + + + + + + + + + + + - --> - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TSOClient/FSO.IDE/FSOUIControl.cs b/TSOClient/FSO.IDE/FSOUIControl.cs index ddc88cdb6..75a3962e5 100644 --- a/TSOClient/FSO.IDE/FSOUIControl.cs +++ b/TSOClient/FSO.IDE/FSOUIControl.cs @@ -98,7 +98,7 @@ private void FSOUIFrame() Framebuffer = new Bitmap(FSOUI.Width, FSOUI.Height, PixelFormat.Format32bppArgb); } - var bmpData = Framebuffer.LockBits(new Rectangle(0, 0, Framebuffer.Width, Framebuffer.Height), ImageLockMode.WriteOnly, Framebuffer.PixelFormat); + var bmpData = Framebuffer.LockBits(new System.Drawing.Rectangle(0, 0, Framebuffer.Width, Framebuffer.Height), ImageLockMode.WriteOnly, Framebuffer.PixelFormat); IntPtr ptr = bmpData.Scan0; Marshal.Copy(FSOUI.RawImage, 0, ptr, bmpData.Stride * bmpData.Height); @@ -187,7 +187,7 @@ protected override void OnPaint(PaintEventArgs e) { lock (FrameLock) { - if (Framebuffer != null) e.Graphics.DrawImage(Framebuffer, new Point()); + if (Framebuffer != null) e.Graphics.DrawImage(Framebuffer, new System.Drawing.Point()); } } if (FSOUI != null) FSOUI.NeedFrames = 5; diff --git a/TSOClient/FSO.IDE/MainWindow.Designer.cs b/TSOClient/FSO.IDE/MainWindow.Designer.cs index cc2953d18..9aa9a8697 100644 --- a/TSOClient/FSO.IDE/MainWindow.Designer.cs +++ b/TSOClient/FSO.IDE/MainWindow.Designer.cs @@ -28,392 +28,372 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("(BHAV #4000) Init"); - System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("(SPR2 #254) Fish Sprite"); - System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("(DGRP #100) Dead 1"); - System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("(DGRP #101) Dead 2"); - System.Windows.Forms.TreeNode treeNode5 = new System.Windows.Forms.TreeNode("aquarium.iff", new System.Windows.Forms.TreeNode[] { - treeNode1, - treeNode2, - treeNode3, - treeNode4}); - System.Windows.Forms.TreeNode treeNode6 = new System.Windows.Forms.TreeNode("(BHAV #4023) Interaction - Read Inscription"); - System.Windows.Forms.TreeNode treeNode7 = new System.Windows.Forms.TreeNode("(CTSS #223) Plaque CTSS"); - System.Windows.Forms.TreeNode treeNode8 = new System.Windows.Forms.TreeNode("Content/Objects/objPlaque.iff", new System.Windows.Forms.TreeNode[] { - treeNode6, - treeNode7}); + TreeNode treeNode1 = new TreeNode("(BHAV #4000) Init"); + TreeNode treeNode2 = new TreeNode("(SPR2 #254) Fish Sprite"); + TreeNode treeNode3 = new TreeNode("(DGRP #100) Dead 1"); + TreeNode treeNode4 = new TreeNode("(DGRP #101) Dead 2"); + TreeNode treeNode5 = new TreeNode("aquarium.iff", new TreeNode[] { treeNode1, treeNode2, treeNode3, treeNode4 }); + TreeNode treeNode6 = new TreeNode("(BHAV #4023) Interaction - Read Inscription"); + TreeNode treeNode7 = new TreeNode("(CTSS #223) Plaque CTSS"); + TreeNode treeNode8 = new TreeNode("Content/Objects/objPlaque.iff", new TreeNode[] { treeNode6, treeNode7 }); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow)); - this.CreateButton = new System.Windows.Forms.Button(); - this.EditButton = new System.Windows.Forms.Button(); - this.CloneButton = new System.Windows.Forms.Button(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.newToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.objectToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.semiGlobalToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.dataServiceEditorToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.simAnticsAOTToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveGlobalscsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.avatarToolToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.openExternalIffToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.fieldEncodingReverserToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.windowToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.hideAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator(); - this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.aboutToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.UtilityTabs = new System.Windows.Forms.TabControl(); - this.OverviewTab = new System.Windows.Forms.TabPage(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.ChunkSelection = new System.Windows.Forms.Label(); - this.ChunkDiscard = new System.Windows.Forms.Button(); - this.AllTable = new System.Windows.Forms.TableLayoutPanel(); - this.SaveAll = new System.Windows.Forms.Button(); - this.DiscardAll = new System.Windows.Forms.Button(); - this.groupBox2 = new System.Windows.Forms.GroupBox(); - this.IffSelection = new System.Windows.Forms.Label(); - this.IffSave = new System.Windows.Forms.Button(); - this.IffDiscard = new System.Windows.Forms.Button(); - this.ChangesLabel = new System.Windows.Forms.Label(); - this.ChangesView = new System.Windows.Forms.TreeView(); - this.BrowserTab = new System.Windows.Forms.TabPage(); - this.NewOBJButton = new System.Windows.Forms.Button(); - this.Browser = new FSO.IDE.ObjectBrowser(); - this.InspectorTab = new System.Windows.Forms.TabPage(); - this.entityInspector1 = new FSO.IDE.EntityInspector(); - this.houseSpyTS1ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.menuStrip1.SuspendLayout(); - this.UtilityTabs.SuspendLayout(); - this.OverviewTab.SuspendLayout(); - this.groupBox1.SuspendLayout(); - this.AllTable.SuspendLayout(); - this.groupBox2.SuspendLayout(); - this.BrowserTab.SuspendLayout(); - this.InspectorTab.SuspendLayout(); - this.SuspendLayout(); + CreateButton = new Button(); + EditButton = new Button(); + CloneButton = new Button(); + menuStrip1 = new MenuStrip(); + newToolStripMenuItem = new ToolStripMenuItem(); + objectToolStripMenuItem = new ToolStripMenuItem(); + semiGlobalToolStripMenuItem = new ToolStripMenuItem(); + toolsToolStripMenuItem = new ToolStripMenuItem(); + dataServiceEditorToolStripMenuItem = new ToolStripMenuItem(); + simAnticsAOTToolStripMenuItem = new ToolStripMenuItem(); + saveGlobalscsToolStripMenuItem = new ToolStripMenuItem(); + avatarToolToolStripMenuItem = new ToolStripMenuItem(); + openExternalIffToolStripMenuItem = new ToolStripMenuItem(); + fieldEncodingReverserToolStripMenuItem = new ToolStripMenuItem(); + houseSpyTS1ToolStripMenuItem = new ToolStripMenuItem(); + windowToolStripMenuItem = new ToolStripMenuItem(); + hideAllToolStripMenuItem = new ToolStripMenuItem(); + toolStripSeparator1 = new ToolStripSeparator(); + helpToolStripMenuItem = new ToolStripMenuItem(); + aboutToolStripMenuItem = new ToolStripMenuItem(); + UtilityTabs = new TabControl(); + OverviewTab = new TabPage(); + groupBox1 = new GroupBox(); + ChunkSelection = new Label(); + ChunkDiscard = new Button(); + AllTable = new TableLayoutPanel(); + SaveAll = new Button(); + DiscardAll = new Button(); + groupBox2 = new GroupBox(); + IffSelection = new Label(); + IffSave = new Button(); + IffDiscard = new Button(); + ChangesLabel = new Label(); + ChangesView = new TreeView(); + BrowserTab = new TabPage(); + NewOBJButton = new Button(); + Browser = new ObjectBrowser(); + InspectorTab = new TabPage(); + entityInspector1 = new EntityInspector(); + menuStrip1.SuspendLayout(); + UtilityTabs.SuspendLayout(); + OverviewTab.SuspendLayout(); + groupBox1.SuspendLayout(); + AllTable.SuspendLayout(); + groupBox2.SuspendLayout(); + BrowserTab.SuspendLayout(); + InspectorTab.SuspendLayout(); + SuspendLayout(); // // CreateButton // - this.CreateButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CreateButton.Location = new System.Drawing.Point(531, 352); - this.CreateButton.Name = "CreateButton"; - this.CreateButton.Size = new System.Drawing.Size(186, 23); - this.CreateButton.TabIndex = 21; - this.CreateButton.Text = "Create New Object Instance"; - this.CreateButton.UseVisualStyleBackColor = true; - this.CreateButton.Click += new System.EventHandler(this.CreateButton_Click); + CreateButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + CreateButton.Location = new Point(531, 352); + CreateButton.Name = "CreateButton"; + CreateButton.Size = new Size(186, 23); + CreateButton.TabIndex = 21; + CreateButton.Text = "Create New Object Instance"; + CreateButton.UseVisualStyleBackColor = true; + CreateButton.Click += CreateButton_Click; // // EditButton // - this.EditButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.EditButton.Location = new System.Drawing.Point(531, 323); - this.EditButton.Name = "EditButton"; - this.EditButton.Size = new System.Drawing.Size(186, 23); - this.EditButton.TabIndex = 20; - this.EditButton.Text = "Edit Object"; - this.EditButton.UseVisualStyleBackColor = true; - this.EditButton.Click += new System.EventHandler(this.button2_Click); + EditButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + EditButton.Location = new Point(531, 323); + EditButton.Name = "EditButton"; + EditButton.Size = new Size(186, 23); + EditButton.TabIndex = 20; + EditButton.Text = "Edit Object"; + EditButton.UseVisualStyleBackColor = true; + EditButton.Click += button2_Click; // // CloneButton // - this.CloneButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CloneButton.Enabled = false; - this.CloneButton.Location = new System.Drawing.Point(531, 410); - this.CloneButton.Name = "CloneButton"; - this.CloneButton.Size = new System.Drawing.Size(186, 23); - this.CloneButton.TabIndex = 19; - this.CloneButton.Text = "Clone Object (.piff)"; - this.CloneButton.UseVisualStyleBackColor = true; - this.CloneButton.Click += new System.EventHandler(this.button1_Click); + CloneButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + CloneButton.Enabled = false; + CloneButton.Location = new Point(531, 410); + CloneButton.Name = "CloneButton"; + CloneButton.Size = new Size(186, 23); + CloneButton.TabIndex = 19; + CloneButton.Text = "Clone Object (.piff)"; + CloneButton.UseVisualStyleBackColor = true; + CloneButton.Click += button1_Click; // // menuStrip1 // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.newToolStripMenuItem, - this.toolsToolStripMenuItem, - this.windowToolStripMenuItem, - this.helpToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(762, 24); - this.menuStrip1.TabIndex = 22; - this.menuStrip1.Text = "menuStrip1"; + menuStrip1.Items.AddRange(new ToolStripItem[] { newToolStripMenuItem, toolsToolStripMenuItem, windowToolStripMenuItem, helpToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(762, 24); + menuStrip1.TabIndex = 22; + menuStrip1.Text = "menuStrip1"; // // newToolStripMenuItem // - this.newToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.objectToolStripMenuItem, - this.semiGlobalToolStripMenuItem}); - this.newToolStripMenuItem.Name = "newToolStripMenuItem"; - this.newToolStripMenuItem.Size = new System.Drawing.Size(43, 20); - this.newToolStripMenuItem.Text = "New"; + newToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { objectToolStripMenuItem, semiGlobalToolStripMenuItem }); + newToolStripMenuItem.Name = "newToolStripMenuItem"; + newToolStripMenuItem.Size = new Size(43, 20); + newToolStripMenuItem.Text = "New"; // // objectToolStripMenuItem // - this.objectToolStripMenuItem.Name = "objectToolStripMenuItem"; - this.objectToolStripMenuItem.Size = new System.Drawing.Size(139, 22); - this.objectToolStripMenuItem.Text = "Object"; - this.objectToolStripMenuItem.Click += new System.EventHandler(this.NewOBJButton_Click); + objectToolStripMenuItem.Name = "objectToolStripMenuItem"; + objectToolStripMenuItem.Size = new Size(139, 22); + objectToolStripMenuItem.Text = "Object"; + objectToolStripMenuItem.Click += NewOBJButton_Click; // // semiGlobalToolStripMenuItem // - this.semiGlobalToolStripMenuItem.Name = "semiGlobalToolStripMenuItem"; - this.semiGlobalToolStripMenuItem.Size = new System.Drawing.Size(139, 22); - this.semiGlobalToolStripMenuItem.Text = "Semi-Global"; - this.semiGlobalToolStripMenuItem.Click += new System.EventHandler(this.semiGlobalToolStripMenuItem_Click); + semiGlobalToolStripMenuItem.Name = "semiGlobalToolStripMenuItem"; + semiGlobalToolStripMenuItem.Size = new Size(139, 22); + semiGlobalToolStripMenuItem.Text = "Semi-Global"; + semiGlobalToolStripMenuItem.Click += semiGlobalToolStripMenuItem_Click; // // toolsToolStripMenuItem // - this.toolsToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.dataServiceEditorToolStripMenuItem, - this.simAnticsAOTToolStripMenuItem, - this.avatarToolToolStripMenuItem, - this.openExternalIffToolStripMenuItem, - this.fieldEncodingReverserToolStripMenuItem, - this.houseSpyTS1ToolStripMenuItem}); - this.toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; - this.toolsToolStripMenuItem.Size = new System.Drawing.Size(46, 20); - this.toolsToolStripMenuItem.Text = "Tools"; + toolsToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { dataServiceEditorToolStripMenuItem, simAnticsAOTToolStripMenuItem, avatarToolToolStripMenuItem, openExternalIffToolStripMenuItem, fieldEncodingReverserToolStripMenuItem, houseSpyTS1ToolStripMenuItem }); + toolsToolStripMenuItem.Name = "toolsToolStripMenuItem"; + toolsToolStripMenuItem.Size = new Size(47, 20); + toolsToolStripMenuItem.Text = "Tools"; // // dataServiceEditorToolStripMenuItem // - this.dataServiceEditorToolStripMenuItem.Name = "dataServiceEditorToolStripMenuItem"; - this.dataServiceEditorToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.dataServiceEditorToolStripMenuItem.Text = "Data Service Editor"; - this.dataServiceEditorToolStripMenuItem.Click += new System.EventHandler(this.dataServiceEditorToolStripMenuItem_Click); + dataServiceEditorToolStripMenuItem.Name = "dataServiceEditorToolStripMenuItem"; + dataServiceEditorToolStripMenuItem.Size = new Size(199, 22); + dataServiceEditorToolStripMenuItem.Text = "Data Service Editor"; + dataServiceEditorToolStripMenuItem.Click += dataServiceEditorToolStripMenuItem_Click; // // simAnticsAOTToolStripMenuItem // - this.simAnticsAOTToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.saveGlobalscsToolStripMenuItem}); - this.simAnticsAOTToolStripMenuItem.Name = "simAnticsAOTToolStripMenuItem"; - this.simAnticsAOTToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.simAnticsAOTToolStripMenuItem.Text = "SimAntics AOT"; + simAnticsAOTToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { saveGlobalscsToolStripMenuItem }); + simAnticsAOTToolStripMenuItem.Name = "simAnticsAOTToolStripMenuItem"; + simAnticsAOTToolStripMenuItem.Size = new Size(199, 22); + simAnticsAOTToolStripMenuItem.Text = "SimAntics AOT"; // // saveGlobalscsToolStripMenuItem // - this.saveGlobalscsToolStripMenuItem.Name = "saveGlobalscsToolStripMenuItem"; - this.saveGlobalscsToolStripMenuItem.Size = new System.Drawing.Size(215, 22); - this.saveGlobalscsToolStripMenuItem.Text = "Generate AOT Sources (.cs)"; - this.saveGlobalscsToolStripMenuItem.Click += new System.EventHandler(this.saveGlobalscsToolStripMenuItem_Click); + saveGlobalscsToolStripMenuItem.Name = "saveGlobalscsToolStripMenuItem"; + saveGlobalscsToolStripMenuItem.Size = new Size(216, 22); + saveGlobalscsToolStripMenuItem.Text = "Generate AOT Sources (.cs)"; + saveGlobalscsToolStripMenuItem.Click += saveGlobalscsToolStripMenuItem_Click; // // avatarToolToolStripMenuItem // - this.avatarToolToolStripMenuItem.Name = "avatarToolToolStripMenuItem"; - this.avatarToolToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.avatarToolToolStripMenuItem.Text = "Avatar Tool"; - this.avatarToolToolStripMenuItem.Click += new System.EventHandler(this.avatarToolToolStripMenuItem_Click); + avatarToolToolStripMenuItem.Name = "avatarToolToolStripMenuItem"; + avatarToolToolStripMenuItem.Size = new Size(199, 22); + avatarToolToolStripMenuItem.Text = "Avatar Tool"; + avatarToolToolStripMenuItem.Click += avatarToolToolStripMenuItem_Click; // // openExternalIffToolStripMenuItem // - this.openExternalIffToolStripMenuItem.Name = "openExternalIffToolStripMenuItem"; - this.openExternalIffToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.openExternalIffToolStripMenuItem.Text = "Open External Iff..."; - this.openExternalIffToolStripMenuItem.Click += new System.EventHandler(this.openExternalIffToolStripMenuItem_Click); + openExternalIffToolStripMenuItem.Name = "openExternalIffToolStripMenuItem"; + openExternalIffToolStripMenuItem.Size = new Size(199, 22); + openExternalIffToolStripMenuItem.Text = "Open External Iff..."; + openExternalIffToolStripMenuItem.Click += openExternalIffToolStripMenuItem_Click; // // fieldEncodingReverserToolStripMenuItem // - this.fieldEncodingReverserToolStripMenuItem.Name = "fieldEncodingReverserToolStripMenuItem"; - this.fieldEncodingReverserToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.fieldEncodingReverserToolStripMenuItem.Text = "Field Encoding Reverser"; - this.fieldEncodingReverserToolStripMenuItem.Click += new System.EventHandler(this.fieldEncodingReverserToolStripMenuItem_Click); + fieldEncodingReverserToolStripMenuItem.Name = "fieldEncodingReverserToolStripMenuItem"; + fieldEncodingReverserToolStripMenuItem.Size = new Size(199, 22); + fieldEncodingReverserToolStripMenuItem.Text = "Field Encoding Reverser"; + fieldEncodingReverserToolStripMenuItem.Click += fieldEncodingReverserToolStripMenuItem_Click; + // + // houseSpyTS1ToolStripMenuItem + // + houseSpyTS1ToolStripMenuItem.Name = "houseSpyTS1ToolStripMenuItem"; + houseSpyTS1ToolStripMenuItem.Size = new Size(199, 22); + houseSpyTS1ToolStripMenuItem.Text = "House Spy (TS1)"; + houseSpyTS1ToolStripMenuItem.Click += houseSpyTS1ToolStripMenuItem_Click; // // windowToolStripMenuItem // - this.windowToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.hideAllToolStripMenuItem, - this.toolStripSeparator1}); - this.windowToolStripMenuItem.Name = "windowToolStripMenuItem"; - this.windowToolStripMenuItem.Size = new System.Drawing.Size(63, 20); - this.windowToolStripMenuItem.Text = "Window"; - this.windowToolStripMenuItem.DropDownOpening += new System.EventHandler(this.windowToolStripMenuItem_DropDownOpening); + windowToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { hideAllToolStripMenuItem, toolStripSeparator1 }); + windowToolStripMenuItem.Name = "windowToolStripMenuItem"; + windowToolStripMenuItem.Size = new Size(63, 20); + windowToolStripMenuItem.Text = "Window"; + windowToolStripMenuItem.DropDownOpening += windowToolStripMenuItem_DropDownOpening; // // hideAllToolStripMenuItem // - this.hideAllToolStripMenuItem.Name = "hideAllToolStripMenuItem"; - this.hideAllToolStripMenuItem.Size = new System.Drawing.Size(116, 22); - this.hideAllToolStripMenuItem.Text = "Hide All"; - this.hideAllToolStripMenuItem.Click += new System.EventHandler(this.hideAllToolStripMenuItem_Click); + hideAllToolStripMenuItem.Name = "hideAllToolStripMenuItem"; + hideAllToolStripMenuItem.Size = new Size(116, 22); + hideAllToolStripMenuItem.Text = "Hide All"; + hideAllToolStripMenuItem.Click += hideAllToolStripMenuItem_Click; // // toolStripSeparator1 // - this.toolStripSeparator1.Name = "toolStripSeparator1"; - this.toolStripSeparator1.Size = new System.Drawing.Size(113, 6); + toolStripSeparator1.Name = "toolStripSeparator1"; + toolStripSeparator1.Size = new Size(113, 6); // // helpToolStripMenuItem // - this.helpToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.aboutToolStripMenuItem}); - this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; - this.helpToolStripMenuItem.Size = new System.Drawing.Size(44, 20); - this.helpToolStripMenuItem.Text = "Help"; + helpToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { aboutToolStripMenuItem }); + helpToolStripMenuItem.Name = "helpToolStripMenuItem"; + helpToolStripMenuItem.Size = new Size(44, 20); + helpToolStripMenuItem.Text = "Help"; // // aboutToolStripMenuItem // - this.aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; - this.aboutToolStripMenuItem.Size = new System.Drawing.Size(107, 22); - this.aboutToolStripMenuItem.Text = "About"; - this.aboutToolStripMenuItem.Click += new System.EventHandler(this.aboutToolStripMenuItem_Click); + aboutToolStripMenuItem.Name = "aboutToolStripMenuItem"; + aboutToolStripMenuItem.Size = new Size(107, 22); + aboutToolStripMenuItem.Text = "About"; + aboutToolStripMenuItem.Click += aboutToolStripMenuItem_Click; // // UtilityTabs // - this.UtilityTabs.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.UtilityTabs.Controls.Add(this.OverviewTab); - this.UtilityTabs.Controls.Add(this.BrowserTab); - this.UtilityTabs.Controls.Add(this.InspectorTab); - this.UtilityTabs.Location = new System.Drawing.Point(12, 27); - this.UtilityTabs.Name = "UtilityTabs"; - this.UtilityTabs.SelectedIndex = 0; - this.UtilityTabs.Size = new System.Drawing.Size(738, 484); - this.UtilityTabs.TabIndex = 23; + UtilityTabs.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + UtilityTabs.Controls.Add(OverviewTab); + UtilityTabs.Controls.Add(BrowserTab); + UtilityTabs.Controls.Add(InspectorTab); + UtilityTabs.Location = new Point(12, 27); + UtilityTabs.Name = "UtilityTabs"; + UtilityTabs.SelectedIndex = 0; + UtilityTabs.Size = new Size(738, 484); + UtilityTabs.TabIndex = 23; // // OverviewTab // - this.OverviewTab.Controls.Add(this.groupBox1); - this.OverviewTab.Controls.Add(this.AllTable); - this.OverviewTab.Controls.Add(this.groupBox2); - this.OverviewTab.Controls.Add(this.ChangesLabel); - this.OverviewTab.Controls.Add(this.ChangesView); - this.OverviewTab.Location = new System.Drawing.Point(4, 22); - this.OverviewTab.Name = "OverviewTab"; - this.OverviewTab.Padding = new System.Windows.Forms.Padding(3); - this.OverviewTab.Size = new System.Drawing.Size(730, 458); - this.OverviewTab.TabIndex = 2; - this.OverviewTab.Text = "Resources"; - this.OverviewTab.UseVisualStyleBackColor = true; + OverviewTab.Controls.Add(groupBox1); + OverviewTab.Controls.Add(AllTable); + OverviewTab.Controls.Add(groupBox2); + OverviewTab.Controls.Add(ChangesLabel); + OverviewTab.Controls.Add(ChangesView); + OverviewTab.Location = new Point(4, 22); + OverviewTab.Name = "OverviewTab"; + OverviewTab.Padding = new Padding(3); + OverviewTab.Size = new Size(730, 458); + OverviewTab.TabIndex = 2; + OverviewTab.Text = "Resources"; + OverviewTab.UseVisualStyleBackColor = true; // // groupBox1 // - this.groupBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox1.Controls.Add(this.ChunkSelection); - this.groupBox1.Controls.Add(this.ChunkDiscard); - this.groupBox1.Location = new System.Drawing.Point(592, 143); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(132, 66); - this.groupBox1.TabIndex = 30; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Chunk"; + groupBox1.Anchor = AnchorStyles.Top | AnchorStyles.Right; + groupBox1.Controls.Add(ChunkSelection); + groupBox1.Controls.Add(ChunkDiscard); + groupBox1.Location = new Point(592, 143); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(132, 66); + groupBox1.TabIndex = 30; + groupBox1.TabStop = false; + groupBox1.Text = "Chunk"; // // ChunkSelection // - this.ChunkSelection.Location = new System.Drawing.Point(6, 16); - this.ChunkSelection.Name = "ChunkSelection"; - this.ChunkSelection.Size = new System.Drawing.Size(120, 16); - this.ChunkSelection.TabIndex = 3; - this.ChunkSelection.Text = "6 in selection."; + ChunkSelection.Location = new Point(6, 16); + ChunkSelection.Name = "ChunkSelection"; + ChunkSelection.Size = new Size(120, 16); + ChunkSelection.TabIndex = 3; + ChunkSelection.Text = "6 in selection."; // // ChunkDiscard // - this.ChunkDiscard.Location = new System.Drawing.Point(6, 35); - this.ChunkDiscard.Name = "ChunkDiscard"; - this.ChunkDiscard.Size = new System.Drawing.Size(120, 23); - this.ChunkDiscard.TabIndex = 1; - this.ChunkDiscard.Text = "Discard Changes"; - this.ChunkDiscard.UseVisualStyleBackColor = true; - this.ChunkDiscard.Click += new System.EventHandler(this.ChunkDiscard_Click); + ChunkDiscard.Location = new Point(6, 35); + ChunkDiscard.Name = "ChunkDiscard"; + ChunkDiscard.Size = new Size(120, 23); + ChunkDiscard.TabIndex = 1; + ChunkDiscard.Text = "Discard Changes"; + ChunkDiscard.UseVisualStyleBackColor = true; + ChunkDiscard.Click += ChunkDiscard_Click; // // AllTable // - this.AllTable.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.AllTable.ColumnCount = 2; - this.AllTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.AllTable.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F)); - this.AllTable.Controls.Add(this.SaveAll, 0, 0); - this.AllTable.Controls.Add(this.DiscardAll, 1, 0); - this.AllTable.Location = new System.Drawing.Point(6, 3); - this.AllTable.Margin = new System.Windows.Forms.Padding(0); - this.AllTable.Name = "AllTable"; - this.AllTable.RowCount = 1; - this.AllTable.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.AllTable.Size = new System.Drawing.Size(580, 35); - this.AllTable.TabIndex = 24; + AllTable.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + AllTable.ColumnCount = 2; + AllTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + AllTable.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F)); + AllTable.Controls.Add(SaveAll, 0, 0); + AllTable.Controls.Add(DiscardAll, 1, 0); + AllTable.Location = new Point(6, 3); + AllTable.Margin = new Padding(0); + AllTable.Name = "AllTable"; + AllTable.RowCount = 1; + AllTable.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + AllTable.Size = new Size(580, 35); + AllTable.TabIndex = 24; // // SaveAll // - this.SaveAll.Dock = System.Windows.Forms.DockStyle.Fill; - this.SaveAll.Location = new System.Drawing.Point(3, 3); - this.SaveAll.Name = "SaveAll"; - this.SaveAll.Size = new System.Drawing.Size(284, 29); - this.SaveAll.TabIndex = 26; - this.SaveAll.Text = "Save All"; - this.SaveAll.UseVisualStyleBackColor = true; - this.SaveAll.Click += new System.EventHandler(this.SaveAll_Click); + SaveAll.Dock = DockStyle.Fill; + SaveAll.Location = new Point(3, 3); + SaveAll.Name = "SaveAll"; + SaveAll.Size = new Size(284, 29); + SaveAll.TabIndex = 26; + SaveAll.Text = "Save All"; + SaveAll.UseVisualStyleBackColor = true; + SaveAll.Click += SaveAll_Click; // // DiscardAll // - this.DiscardAll.Dock = System.Windows.Forms.DockStyle.Fill; - this.DiscardAll.Location = new System.Drawing.Point(293, 3); - this.DiscardAll.Name = "DiscardAll"; - this.DiscardAll.Size = new System.Drawing.Size(284, 29); - this.DiscardAll.TabIndex = 26; - this.DiscardAll.Text = "Discard All"; - this.DiscardAll.UseVisualStyleBackColor = true; - this.DiscardAll.Click += new System.EventHandler(this.DiscardAll_Click); + DiscardAll.Dock = DockStyle.Fill; + DiscardAll.Location = new Point(293, 3); + DiscardAll.Name = "DiscardAll"; + DiscardAll.Size = new Size(284, 29); + DiscardAll.TabIndex = 26; + DiscardAll.Text = "Discard All"; + DiscardAll.UseVisualStyleBackColor = true; + DiscardAll.Click += DiscardAll_Click; // // groupBox2 // - this.groupBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.groupBox2.Controls.Add(this.IffSelection); - this.groupBox2.Controls.Add(this.IffSave); - this.groupBox2.Controls.Add(this.IffDiscard); - this.groupBox2.Location = new System.Drawing.Point(592, 41); - this.groupBox2.Name = "groupBox2"; - this.groupBox2.Size = new System.Drawing.Size(132, 96); - this.groupBox2.TabIndex = 29; - this.groupBox2.TabStop = false; - this.groupBox2.Text = "Iff"; + groupBox2.Anchor = AnchorStyles.Top | AnchorStyles.Right; + groupBox2.Controls.Add(IffSelection); + groupBox2.Controls.Add(IffSave); + groupBox2.Controls.Add(IffDiscard); + groupBox2.Location = new Point(592, 41); + groupBox2.Name = "groupBox2"; + groupBox2.Size = new Size(132, 96); + groupBox2.TabIndex = 29; + groupBox2.TabStop = false; + groupBox2.Text = "Iff"; // // IffSelection // - this.IffSelection.ForeColor = System.Drawing.SystemColors.ControlText; - this.IffSelection.Location = new System.Drawing.Point(6, 16); - this.IffSelection.Name = "IffSelection"; - this.IffSelection.Size = new System.Drawing.Size(120, 16); - this.IffSelection.TabIndex = 3; - this.IffSelection.Text = "2 files selected."; + IffSelection.ForeColor = SystemColors.ControlText; + IffSelection.Location = new Point(6, 16); + IffSelection.Name = "IffSelection"; + IffSelection.Size = new Size(120, 16); + IffSelection.TabIndex = 3; + IffSelection.Text = "2 files selected."; // // IffSave // - this.IffSave.Location = new System.Drawing.Point(6, 35); - this.IffSave.Name = "IffSave"; - this.IffSave.Size = new System.Drawing.Size(120, 23); - this.IffSave.TabIndex = 2; - this.IffSave.Text = "Save Changes"; - this.IffSave.UseVisualStyleBackColor = true; - this.IffSave.Click += new System.EventHandler(this.IffSave_Click); + IffSave.Location = new Point(6, 35); + IffSave.Name = "IffSave"; + IffSave.Size = new Size(120, 23); + IffSave.TabIndex = 2; + IffSave.Text = "Save Changes"; + IffSave.UseVisualStyleBackColor = true; + IffSave.Click += IffSave_Click; // // IffDiscard // - this.IffDiscard.Location = new System.Drawing.Point(6, 64); - this.IffDiscard.Name = "IffDiscard"; - this.IffDiscard.Size = new System.Drawing.Size(120, 23); - this.IffDiscard.TabIndex = 1; - this.IffDiscard.Text = "Discard Changes"; - this.IffDiscard.UseVisualStyleBackColor = true; - this.IffDiscard.Click += new System.EventHandler(this.IffDiscard_Click); + IffDiscard.Location = new Point(6, 64); + IffDiscard.Name = "IffDiscard"; + IffDiscard.Size = new Size(120, 23); + IffDiscard.TabIndex = 1; + IffDiscard.Text = "Discard Changes"; + IffDiscard.UseVisualStyleBackColor = true; + IffDiscard.Click += IffDiscard_Click; // // ChangesLabel // - this.ChangesLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.ChangesLabel.Location = new System.Drawing.Point(6, 442); - this.ChangesLabel.Name = "ChangesLabel"; - this.ChangesLabel.Size = new System.Drawing.Size(370, 16); - this.ChangesLabel.TabIndex = 27; - this.ChangesLabel.Text = "Changed 6 chunks in 2 files."; + ChangesLabel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + ChangesLabel.Location = new Point(6, 442); + ChangesLabel.Name = "ChangesLabel"; + ChangesLabel.Size = new Size(370, 16); + ChangesLabel.TabIndex = 27; + ChangesLabel.Text = "Changed 6 chunks in 2 files."; // // ChangesView // - this.ChangesView.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.ChangesView.CheckBoxes = true; - this.ChangesView.FullRowSelect = true; - this.ChangesView.Indent = 10; - this.ChangesView.Location = new System.Drawing.Point(6, 41); - this.ChangesView.Name = "ChangesView"; + ChangesView.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ChangesView.CheckBoxes = true; + ChangesView.FullRowSelect = true; + ChangesView.Indent = 10; + ChangesView.Location = new Point(6, 41); + ChangesView.Name = "ChangesView"; treeNode1.Name = "Node1"; treeNode1.Text = "(BHAV #4000) Init"; treeNode2.Name = "Node2"; @@ -430,102 +410,91 @@ private void InitializeComponent() treeNode7.Text = "(CTSS #223) Plaque CTSS"; treeNode8.Name = "Node6"; treeNode8.Text = "Content/Objects/objPlaque.iff"; - this.ChangesView.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { - treeNode5, - treeNode8}); - this.ChangesView.ShowRootLines = false; - this.ChangesView.Size = new System.Drawing.Size(580, 398); - this.ChangesView.TabIndex = 24; - this.ChangesView.AfterCheck += new System.Windows.Forms.TreeViewEventHandler(this.ChangesView_AfterCheck); + ChangesView.Nodes.AddRange(new TreeNode[] { treeNode5, treeNode8 }); + ChangesView.ShowRootLines = false; + ChangesView.Size = new Size(580, 398); + ChangesView.TabIndex = 24; + ChangesView.AfterCheck += ChangesView_AfterCheck; // // BrowserTab // - this.BrowserTab.Controls.Add(this.NewOBJButton); - this.BrowserTab.Controls.Add(this.CreateButton); - this.BrowserTab.Controls.Add(this.CloneButton); - this.BrowserTab.Controls.Add(this.EditButton); - this.BrowserTab.Controls.Add(this.Browser); - this.BrowserTab.Location = new System.Drawing.Point(4, 22); - this.BrowserTab.Name = "BrowserTab"; - this.BrowserTab.Padding = new System.Windows.Forms.Padding(3); - this.BrowserTab.Size = new System.Drawing.Size(730, 458); - this.BrowserTab.TabIndex = 0; - this.BrowserTab.Text = "Object Browser"; - this.BrowserTab.UseVisualStyleBackColor = true; + BrowserTab.Controls.Add(NewOBJButton); + BrowserTab.Controls.Add(CreateButton); + BrowserTab.Controls.Add(CloneButton); + BrowserTab.Controls.Add(EditButton); + BrowserTab.Controls.Add(Browser); + BrowserTab.Location = new Point(4, 22); + BrowserTab.Name = "BrowserTab"; + BrowserTab.Padding = new Padding(3); + BrowserTab.Size = new Size(730, 458); + BrowserTab.TabIndex = 0; + BrowserTab.Text = "Object Browser"; + BrowserTab.UseVisualStyleBackColor = true; // // NewOBJButton // - this.NewOBJButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.NewOBJButton.Location = new System.Drawing.Point(531, 381); - this.NewOBJButton.Name = "NewOBJButton"; - this.NewOBJButton.Size = new System.Drawing.Size(186, 23); - this.NewOBJButton.TabIndex = 22; - this.NewOBJButton.Text = "Create New Object"; - this.NewOBJButton.UseVisualStyleBackColor = true; - this.NewOBJButton.Click += new System.EventHandler(this.NewOBJButton_Click); + NewOBJButton.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + NewOBJButton.Location = new Point(531, 381); + NewOBJButton.Name = "NewOBJButton"; + NewOBJButton.Size = new Size(186, 23); + NewOBJButton.TabIndex = 22; + NewOBJButton.Text = "Create New Object"; + NewOBJButton.UseVisualStyleBackColor = true; + NewOBJButton.Click += NewOBJButton_Click; // // Browser // - this.Browser.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.Browser.Location = new System.Drawing.Point(3, 3); - this.Browser.Name = "Browser"; - this.Browser.Size = new System.Drawing.Size(724, 452); - this.Browser.TabIndex = 0; + Browser.Dock = DockStyle.Fill; + Browser.Location = new Point(3, 3); + Browser.Margin = new Padding(4, 3, 4, 3); + Browser.Name = "Browser"; + Browser.Size = new Size(724, 452); + Browser.TabIndex = 0; // // InspectorTab // - this.InspectorTab.Controls.Add(this.entityInspector1); - this.InspectorTab.Location = new System.Drawing.Point(4, 22); - this.InspectorTab.Name = "InspectorTab"; - this.InspectorTab.Padding = new System.Windows.Forms.Padding(3); - this.InspectorTab.Size = new System.Drawing.Size(730, 458); - this.InspectorTab.TabIndex = 1; - this.InspectorTab.Text = "VMEntity Inspector"; - this.InspectorTab.UseVisualStyleBackColor = true; + InspectorTab.Controls.Add(entityInspector1); + InspectorTab.Location = new Point(4, 22); + InspectorTab.Name = "InspectorTab"; + InspectorTab.Padding = new Padding(3); + InspectorTab.Size = new Size(730, 458); + InspectorTab.TabIndex = 1; + InspectorTab.Text = "VMEntity Inspector"; + InspectorTab.UseVisualStyleBackColor = true; // // entityInspector1 // - this.entityInspector1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.entityInspector1.Location = new System.Drawing.Point(3, 3); - this.entityInspector1.Name = "entityInspector1"; - this.entityInspector1.Size = new System.Drawing.Size(724, 452); - this.entityInspector1.TabIndex = 0; - // - // houseSpyTS1ToolStripMenuItem - // - this.houseSpyTS1ToolStripMenuItem.Name = "houseSpyTS1ToolStripMenuItem"; - this.houseSpyTS1ToolStripMenuItem.Size = new System.Drawing.Size(199, 22); - this.houseSpyTS1ToolStripMenuItem.Text = "House Spy (TS1)"; - this.houseSpyTS1ToolStripMenuItem.Click += new System.EventHandler(this.houseSpyTS1ToolStripMenuItem_Click); + entityInspector1.Dock = DockStyle.Fill; + entityInspector1.Location = new Point(3, 3); + entityInspector1.Margin = new Padding(4, 3, 4, 3); + entityInspector1.Name = "entityInspector1"; + entityInspector1.Size = new Size(724, 452); + entityInspector1.TabIndex = 0; // // MainWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(762, 523); - this.Controls.Add(this.UtilityTabs); - this.Controls.Add(this.menuStrip1); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.menuStrip1; - this.Name = "MainWindow"; - this.Text = "Volcanic"; - this.Activated += new System.EventHandler(this.MainWindow_Activated); - this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainWindow_FormClosed); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.UtilityTabs.ResumeLayout(false); - this.OverviewTab.ResumeLayout(false); - this.groupBox1.ResumeLayout(false); - this.AllTable.ResumeLayout(false); - this.groupBox2.ResumeLayout(false); - this.BrowserTab.ResumeLayout(false); - this.InspectorTab.ResumeLayout(false); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(762, 523); + Controls.Add(UtilityTabs); + Controls.Add(menuStrip1); + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + Name = "MainWindow"; + Text = "Volcanic"; + Activated += MainWindow_Activated; + FormClosed += MainWindow_FormClosed; + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + UtilityTabs.ResumeLayout(false); + OverviewTab.ResumeLayout(false); + groupBox1.ResumeLayout(false); + AllTable.ResumeLayout(false); + groupBox2.ResumeLayout(false); + BrowserTab.ResumeLayout(false); + InspectorTab.ResumeLayout(false); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/MainWindow.resx b/TSOClient/FSO.IDE/MainWindow.resx index a9ae3ed73..9a9b88909 100644 --- a/TSOClient/FSO.IDE/MainWindow.resx +++ b/TSOClient/FSO.IDE/MainWindow.resx @@ -121,7 +121,7 @@ 17, 17 - 29 + 36 diff --git a/TSOClient/FSO.IDE/ObjectBrowser.Designer.cs b/TSOClient/FSO.IDE/ObjectBrowser.Designer.cs index 45e3ede82..ce6d2ec62 100644 --- a/TSOClient/FSO.IDE/ObjectBrowser.Designer.cs +++ b/TSOClient/FSO.IDE/ObjectBrowser.Designer.cs @@ -28,58 +28,47 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Accessory Rack - Cheap"); - System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Accessory Rack - Expensive"); - System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("Accessory Rack - Moderate"); - System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("accessoryrack", new System.Windows.Forms.TreeNode[] { - treeNode1, - treeNode2, - treeNode3}); - System.Windows.Forms.TreeNode treeNode5 = new System.Windows.Forms.TreeNode("Puzzle - 2 Person Portal - North"); - System.Windows.Forms.TreeNode treeNode6 = new System.Windows.Forms.TreeNode("Puzzle - 2 Person Portal - South"); - System.Windows.Forms.TreeNode treeNode7 = new System.Windows.Forms.TreeNode("Puzzle - 2 Person Portal - Tunnel"); - System.Windows.Forms.TreeNode treeNode8 = new System.Windows.Forms.TreeNode("Puzzle - 2 Person Portal", new System.Windows.Forms.TreeNode[] { - treeNode5, - treeNode6, - treeNode7}); - System.Windows.Forms.TreeNode treeNode9 = new System.Windows.Forms.TreeNode("2 Person Portal Controller"); - System.Windows.Forms.TreeNode treeNode10 = new System.Windows.Forms.TreeNode("2personpuzzle", new System.Windows.Forms.TreeNode[] { - treeNode8, - treeNode9}); - this.ObjectSearch = new System.Windows.Forms.TextBox(); - this.ObjectTree = new System.Windows.Forms.TreeView(); - this.ObjNameLabel = new System.Windows.Forms.Label(); - this.ObjDescLabel = new System.Windows.Forms.Label(); - this.SearchButton = new System.Windows.Forms.Button(); - this.SearchDescribe = new System.Windows.Forms.Label(); - this.ObjMultitileLabel = new System.Windows.Forms.Label(); - this.ObjThumbnail = new FSO.IDE.Common.ObjThumbnailControl(); - this.SuspendLayout(); + TreeNode treeNode1 = new TreeNode("Accessory Rack - Cheap"); + TreeNode treeNode2 = new TreeNode("Accessory Rack - Expensive"); + TreeNode treeNode3 = new TreeNode("Accessory Rack - Moderate"); + TreeNode treeNode4 = new TreeNode("accessoryrack", new TreeNode[] { treeNode1, treeNode2, treeNode3 }); + TreeNode treeNode5 = new TreeNode("Puzzle - 2 Person Portal - North"); + TreeNode treeNode6 = new TreeNode("Puzzle - 2 Person Portal - South"); + TreeNode treeNode7 = new TreeNode("Puzzle - 2 Person Portal - Tunnel"); + TreeNode treeNode8 = new TreeNode("Puzzle - 2 Person Portal", new TreeNode[] { treeNode5, treeNode6, treeNode7 }); + TreeNode treeNode9 = new TreeNode("2 Person Portal Controller"); + TreeNode treeNode10 = new TreeNode("2personpuzzle", new TreeNode[] { treeNode8, treeNode9 }); + ObjectSearch = new TextBox(); + ObjectTree = new TreeView(); + ObjNameLabel = new Label(); + ObjDescLabel = new Label(); + SearchButton = new Button(); + SearchDescribe = new Label(); + ObjMultitileLabel = new Label(); + ObjThumbnail = new FSO.IDE.Common.ObjThumbnailControl(); + SuspendLayout(); // // ObjectSearch // - this.ObjectSearch.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.ObjectSearch.Location = new System.Drawing.Point(12, 13); - this.ObjectSearch.Name = "ObjectSearch"; - this.ObjectSearch.Size = new System.Drawing.Size(210, 20); - this.ObjectSearch.TabIndex = 7; - this.ObjectSearch.TextChanged += new System.EventHandler(this.ObjectSearch_TextChanged); - this.ObjectSearch.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ObjectSearch_KeyDown); + ObjectSearch.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + ObjectSearch.Location = new Point(12, 13); + ObjectSearch.Name = "ObjectSearch"; + ObjectSearch.Size = new Size(210, 22); + ObjectSearch.TabIndex = 7; + ObjectSearch.TextChanged += ObjectSearch_TextChanged; + ObjectSearch.KeyDown += ObjectSearch_KeyDown; // // ObjectTree // - this.ObjectTree.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.ObjectTree.FullRowSelect = true; - this.ObjectTree.HideSelection = false; - this.ObjectTree.HotTracking = true; - this.ObjectTree.ImeMode = System.Windows.Forms.ImeMode.Off; - this.ObjectTree.Indent = 15; - this.ObjectTree.ItemHeight = 16; - this.ObjectTree.Location = new System.Drawing.Point(12, 39); - this.ObjectTree.Name = "ObjectTree"; + ObjectTree.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + ObjectTree.FullRowSelect = true; + ObjectTree.HideSelection = false; + ObjectTree.HotTracking = true; + ObjectTree.ImeMode = ImeMode.Off; + ObjectTree.Indent = 15; + ObjectTree.ItemHeight = 16; + ObjectTree.Location = new Point(12, 39); + ObjectTree.Name = "ObjectTree"; treeNode1.Name = "Node2"; treeNode1.Text = "Accessory Rack - Cheap"; treeNode2.Name = "Node3"; @@ -100,93 +89,91 @@ private void InitializeComponent() treeNode9.Text = "2 Person Portal Controller"; treeNode10.Name = "Node5"; treeNode10.Text = "2personpuzzle"; - this.ObjectTree.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { - treeNode4, - treeNode10}); - this.ObjectTree.RightToLeft = System.Windows.Forms.RightToLeft.No; - this.ObjectTree.ShowRootLines = false; - this.ObjectTree.Size = new System.Drawing.Size(272, 315); - this.ObjectTree.TabIndex = 9; - this.ObjectTree.TabStop = false; - this.ObjectTree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.ObjectTree_AfterSelect); + ObjectTree.Nodes.AddRange(new TreeNode[] { treeNode4, treeNode10 }); + ObjectTree.RightToLeft = RightToLeft.No; + ObjectTree.ShowRootLines = false; + ObjectTree.Size = new Size(272, 315); + ObjectTree.TabIndex = 9; + ObjectTree.TabStop = false; + ObjectTree.AfterSelect += ObjectTree_AfterSelect; // // ObjNameLabel // - this.ObjNameLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ObjNameLabel.AutoEllipsis = true; - this.ObjNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ObjNameLabel.Location = new System.Drawing.Point(294, 204); - this.ObjNameLabel.Name = "ObjNameLabel"; - this.ObjNameLabel.Size = new System.Drawing.Size(186, 17); - this.ObjNameLabel.TabIndex = 12; - this.ObjNameLabel.Text = "Accessory Rack - Cheap"; - this.ObjNameLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + ObjNameLabel.Anchor = AnchorStyles.Top | AnchorStyles.Right; + ObjNameLabel.AutoEllipsis = true; + ObjNameLabel.Font = new Font("Microsoft Sans Serif", 9.75F, FontStyle.Regular, GraphicsUnit.Point, 0); + ObjNameLabel.Location = new Point(294, 204); + ObjNameLabel.Name = "ObjNameLabel"; + ObjNameLabel.Size = new Size(186, 17); + ObjNameLabel.TabIndex = 12; + ObjNameLabel.Text = "Accessory Rack - Cheap"; + ObjNameLabel.TextAlign = ContentAlignment.TopCenter; // // ObjDescLabel // - this.ObjDescLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ObjDescLabel.Location = new System.Drawing.Point(294, 222); - this.ObjDescLabel.Name = "ObjDescLabel"; - this.ObjDescLabel.Size = new System.Drawing.Size(186, 17); - this.ObjDescLabel.TabIndex = 14; - this.ObjDescLabel.Text = "§2000 - Job Object"; - this.ObjDescLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + ObjDescLabel.Anchor = AnchorStyles.Top | AnchorStyles.Right; + ObjDescLabel.Location = new Point(294, 222); + ObjDescLabel.Name = "ObjDescLabel"; + ObjDescLabel.Size = new Size(186, 17); + ObjDescLabel.TabIndex = 14; + ObjDescLabel.Text = "§2000 - Job Object"; + ObjDescLabel.TextAlign = ContentAlignment.TopCenter; // // SearchButton // - this.SearchButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.SearchButton.Location = new System.Drawing.Point(228, 11); - this.SearchButton.Name = "SearchButton"; - this.SearchButton.Size = new System.Drawing.Size(56, 23); - this.SearchButton.TabIndex = 15; - this.SearchButton.Text = "Search"; - this.SearchButton.UseVisualStyleBackColor = true; - this.SearchButton.Click += new System.EventHandler(this.SearchButton_Click); + SearchButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; + SearchButton.Location = new Point(228, 11); + SearchButton.Name = "SearchButton"; + SearchButton.Size = new Size(56, 23); + SearchButton.TabIndex = 15; + SearchButton.Text = "Search"; + SearchButton.UseVisualStyleBackColor = true; + SearchButton.Click += SearchButton_Click; // // SearchDescribe // - this.SearchDescribe.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.SearchDescribe.Location = new System.Drawing.Point(12, 357); - this.SearchDescribe.Name = "SearchDescribe"; - this.SearchDescribe.Size = new System.Drawing.Size(234, 23); - this.SearchDescribe.TabIndex = 16; - this.SearchDescribe.Text = "Showing all objects."; + SearchDescribe.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + SearchDescribe.Location = new Point(12, 357); + SearchDescribe.Name = "SearchDescribe"; + SearchDescribe.Size = new Size(234, 23); + SearchDescribe.TabIndex = 16; + SearchDescribe.Text = "Showing all objects."; // // ObjMultitileLabel // - this.ObjMultitileLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ObjMultitileLabel.Location = new System.Drawing.Point(294, 237); - this.ObjMultitileLabel.Name = "ObjMultitileLabel"; - this.ObjMultitileLabel.Size = new System.Drawing.Size(186, 17); - this.ObjMultitileLabel.TabIndex = 17; - this.ObjMultitileLabel.Text = "Multitile Master Object"; - this.ObjMultitileLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + ObjMultitileLabel.Anchor = AnchorStyles.Top | AnchorStyles.Right; + ObjMultitileLabel.Location = new Point(294, 237); + ObjMultitileLabel.Name = "ObjMultitileLabel"; + ObjMultitileLabel.Size = new Size(186, 17); + ObjMultitileLabel.TabIndex = 17; + ObjMultitileLabel.Text = "Multitile Master Object"; + ObjMultitileLabel.TextAlign = ContentAlignment.TopCenter; // // ObjThumbnail // - this.ObjThumbnail.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ObjThumbnail.Location = new System.Drawing.Point(294, 13); - this.ObjThumbnail.Name = "ObjThumbnail"; - this.ObjThumbnail.Size = new System.Drawing.Size(186, 186); - this.ObjThumbnail.TabIndex = 19; + ObjThumbnail.Anchor = AnchorStyles.Top | AnchorStyles.Right; + ObjThumbnail.Location = new Point(294, 13); + ObjThumbnail.Name = "ObjThumbnail"; + ObjThumbnail.Size = new Size(186, 186); + ObjThumbnail.TabIndex = 19; // // ObjectBrowser // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.ObjThumbnail); - this.Controls.Add(this.ObjMultitileLabel); - this.Controls.Add(this.SearchDescribe); - this.Controls.Add(this.SearchButton); - this.Controls.Add(this.ObjDescLabel); - this.Controls.Add(this.ObjNameLabel); - this.Controls.Add(this.ObjectSearch); - this.Controls.Add(this.ObjectTree); - this.Name = "ObjectBrowser"; - this.Size = new System.Drawing.Size(492, 382); - this.Load += new System.EventHandler(this.ObjectBrowser_Load); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + Controls.Add(ObjThumbnail); + Controls.Add(ObjMultitileLabel); + Controls.Add(SearchDescribe); + Controls.Add(SearchButton); + Controls.Add(ObjDescLabel); + Controls.Add(ObjNameLabel); + Controls.Add(ObjectSearch); + Controls.Add(ObjectTree); + Name = "ObjectBrowser"; + Size = new Size(492, 382); + Load += ObjectBrowser_Load; + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/ObjectBrowser.resx b/TSOClient/FSO.IDE/ObjectBrowser.resx index 1af7de150..8b2ff64a1 100644 --- a/TSOClient/FSO.IDE/ObjectBrowser.resx +++ b/TSOClient/FSO.IDE/ObjectBrowser.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/ObjectWindow.Designer.cs b/TSOClient/FSO.IDE/ObjectWindow.Designer.cs index 3106bb686..fdb31484d 100644 --- a/TSOClient/FSO.IDE/ObjectWindow.Designer.cs +++ b/TSOClient/FSO.IDE/ObjectWindow.Designer.cs @@ -29,338 +29,341 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ObjectWindow)); - this.ObjCombo = new System.Windows.Forms.ComboBox(); - this.SemiGlobalButton = new System.Windows.Forms.Button(); - this.ObjMultitileLabel = new System.Windows.Forms.Label(); - this.ObjDescLabel = new System.Windows.Forms.Label(); - this.ObjNameLabel = new System.Windows.Forms.Label(); - this.GlobalButton = new System.Windows.Forms.Button(); - this.SGChangeButton = new System.Windows.Forms.Button(); - this.AppearanceTab = new System.Windows.Forms.TabPage(); - this.DrawgroupEdit = new FSO.IDE.ResourceBrowser.DGRPEditor(); - this.tabPage3 = new System.Windows.Forms.TabPage(); - this.FuncEditor = new FSO.IDE.ResourceBrowser.OBJfEditor(); - this.tabPage2 = new System.Windows.Forms.TabPage(); - this.IffResView = new FSO.IDE.ResourceBrowser.IFFResComponent(); - this.DefinitionTab = new System.Windows.Forms.TabPage(); - this.DefinitionEditor = new FSO.IDE.ResourceBrowser.OBJDEditor(); - this.objPages = new System.Windows.Forms.TabControl(); - this.Debug3D = new System.Windows.Forms.TabPage(); - this.FSOMEdit = new FSO.IDE.ResourceBrowser.FSOMEditor(); - this.XMLEntryTab = new System.Windows.Forms.TabPage(); - this.XMLEdit = new FSO.IDE.ResourceBrowser.XMLEntryEditor(); - this.PatchTab = new System.Windows.Forms.TabPage(); - this.PIFFEditor = new FSO.IDE.ResourceBrowser.PIFFEditor(); - this.NewOBJD = new System.Windows.Forms.Button(); - this.DeleteOBJD = new System.Windows.Forms.Button(); - this.ObjThumb = new FSO.IDE.Common.ObjThumbnailControl(); - this.UpgradeTab = new System.Windows.Forms.TabPage(); - this.UpgradeEditor = new FSO.IDE.ResourceBrowser.UpgradeEditor(); - this.AppearanceTab.SuspendLayout(); - this.tabPage3.SuspendLayout(); - this.tabPage2.SuspendLayout(); - this.DefinitionTab.SuspendLayout(); - this.objPages.SuspendLayout(); - this.Debug3D.SuspendLayout(); - this.XMLEntryTab.SuspendLayout(); - this.PatchTab.SuspendLayout(); - this.UpgradeTab.SuspendLayout(); - this.SuspendLayout(); + ObjCombo = new ComboBox(); + SemiGlobalButton = new Button(); + ObjMultitileLabel = new Label(); + ObjDescLabel = new Label(); + ObjNameLabel = new Label(); + GlobalButton = new Button(); + SGChangeButton = new Button(); + AppearanceTab = new TabPage(); + DrawgroupEdit = new FSO.IDE.ResourceBrowser.DGRPEditor(); + tabPage3 = new TabPage(); + FuncEditor = new FSO.IDE.ResourceBrowser.OBJfEditor(); + tabPage2 = new TabPage(); + IffResView = new FSO.IDE.ResourceBrowser.IFFResComponent(); + DefinitionTab = new TabPage(); + DefinitionEditor = new FSO.IDE.ResourceBrowser.OBJDEditor(); + objPages = new TabControl(); + Debug3D = new TabPage(); + FSOMEdit = new FSO.IDE.ResourceBrowser.FSOMEditor(); + XMLEntryTab = new TabPage(); + XMLEdit = new FSO.IDE.ResourceBrowser.XMLEntryEditor(); + UpgradeTab = new TabPage(); + UpgradeEditor = new FSO.IDE.ResourceBrowser.UpgradeEditor(); + PatchTab = new TabPage(); + PIFFEditor = new FSO.IDE.ResourceBrowser.PIFFEditor(); + NewOBJD = new Button(); + DeleteOBJD = new Button(); + ObjThumb = new FSO.IDE.Common.ObjThumbnailControl(); + AppearanceTab.SuspendLayout(); + tabPage3.SuspendLayout(); + tabPage2.SuspendLayout(); + DefinitionTab.SuspendLayout(); + objPages.SuspendLayout(); + Debug3D.SuspendLayout(); + XMLEntryTab.SuspendLayout(); + UpgradeTab.SuspendLayout(); + PatchTab.SuspendLayout(); + SuspendLayout(); // // ObjCombo // - this.ObjCombo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.ObjCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.ObjCombo.FormattingEnabled = true; - this.ObjCombo.Location = new System.Drawing.Point(469, 12); - this.ObjCombo.Name = "ObjCombo"; - this.ObjCombo.Size = new System.Drawing.Size(304, 21); - this.ObjCombo.TabIndex = 2; - this.ObjCombo.SelectedIndexChanged += new System.EventHandler(this.ObjCombo_SelectedIndexChanged); + ObjCombo.Anchor = AnchorStyles.Top | AnchorStyles.Right; + ObjCombo.DropDownStyle = ComboBoxStyle.DropDownList; + ObjCombo.FormattingEnabled = true; + ObjCombo.Location = new Point(469, 12); + ObjCombo.Name = "ObjCombo"; + ObjCombo.Size = new Size(304, 21); + ObjCombo.TabIndex = 2; + ObjCombo.SelectedIndexChanged += ObjCombo_SelectedIndexChanged; // // SemiGlobalButton // - this.SemiGlobalButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.SemiGlobalButton.Location = new System.Drawing.Point(468, 37); - this.SemiGlobalButton.Name = "SemiGlobalButton"; - this.SemiGlobalButton.Size = new System.Drawing.Size(171, 23); - this.SemiGlobalButton.TabIndex = 3; - this.SemiGlobalButton.Text = "Semi-Global (doorglobals)"; - this.SemiGlobalButton.UseVisualStyleBackColor = true; - this.SemiGlobalButton.Click += new System.EventHandler(this.SemiGlobalButton_Click); + SemiGlobalButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; + SemiGlobalButton.Location = new Point(468, 37); + SemiGlobalButton.Name = "SemiGlobalButton"; + SemiGlobalButton.Size = new Size(171, 23); + SemiGlobalButton.TabIndex = 3; + SemiGlobalButton.Text = "Semi-Global (doorglobals)"; + SemiGlobalButton.UseVisualStyleBackColor = true; + SemiGlobalButton.Click += SemiGlobalButton_Click; // // ObjMultitileLabel // - this.ObjMultitileLabel.Location = new System.Drawing.Point(61, 45); - this.ObjMultitileLabel.Name = "ObjMultitileLabel"; - this.ObjMultitileLabel.Size = new System.Drawing.Size(186, 17); - this.ObjMultitileLabel.TabIndex = 20; - this.ObjMultitileLabel.Text = "Multitile Master Object"; + ObjMultitileLabel.Location = new Point(61, 45); + ObjMultitileLabel.Name = "ObjMultitileLabel"; + ObjMultitileLabel.Size = new Size(186, 17); + ObjMultitileLabel.TabIndex = 20; + ObjMultitileLabel.Text = "Multitile Master Object"; // // ObjDescLabel // - this.ObjDescLabel.Location = new System.Drawing.Point(61, 30); - this.ObjDescLabel.Name = "ObjDescLabel"; - this.ObjDescLabel.Size = new System.Drawing.Size(186, 17); - this.ObjDescLabel.TabIndex = 19; - this.ObjDescLabel.Text = "§2000 - Job Object"; + ObjDescLabel.Location = new Point(61, 30); + ObjDescLabel.Name = "ObjDescLabel"; + ObjDescLabel.Size = new Size(186, 17); + ObjDescLabel.TabIndex = 19; + ObjDescLabel.Text = "§2000 - Job Object"; // // ObjNameLabel // - this.ObjNameLabel.AutoEllipsis = true; - this.ObjNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.ObjNameLabel.Location = new System.Drawing.Point(61, 12); - this.ObjNameLabel.Name = "ObjNameLabel"; - this.ObjNameLabel.Size = new System.Drawing.Size(288, 17); - this.ObjNameLabel.TabIndex = 18; - this.ObjNameLabel.Text = "Accessory Rack - Cheap"; + ObjNameLabel.AutoEllipsis = true; + ObjNameLabel.Font = new Font("Microsoft Sans Serif", 9.75F, FontStyle.Regular, GraphicsUnit.Point, 0); + ObjNameLabel.Location = new Point(61, 12); + ObjNameLabel.Name = "ObjNameLabel"; + ObjNameLabel.Size = new Size(288, 17); + ObjNameLabel.TabIndex = 18; + ObjNameLabel.Text = "Accessory Rack - Cheap"; // // GlobalButton // - this.GlobalButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.GlobalButton.Location = new System.Drawing.Point(698, 37); - this.GlobalButton.Name = "GlobalButton"; - this.GlobalButton.Size = new System.Drawing.Size(75, 23); - this.GlobalButton.TabIndex = 21; - this.GlobalButton.Text = "Global"; - this.GlobalButton.UseVisualStyleBackColor = true; - this.GlobalButton.Click += new System.EventHandler(this.GlobalButton_Click); + GlobalButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; + GlobalButton.Location = new Point(698, 37); + GlobalButton.Name = "GlobalButton"; + GlobalButton.Size = new Size(75, 23); + GlobalButton.TabIndex = 21; + GlobalButton.Text = "Global"; + GlobalButton.UseVisualStyleBackColor = true; + GlobalButton.Click += GlobalButton_Click; // // SGChangeButton // - this.SGChangeButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.SGChangeButton.Location = new System.Drawing.Point(640, 37); - this.SGChangeButton.Name = "SGChangeButton"; - this.SGChangeButton.Size = new System.Drawing.Size(52, 23); - this.SGChangeButton.TabIndex = 22; - this.SGChangeButton.Text = "Change"; - this.SGChangeButton.UseVisualStyleBackColor = true; - this.SGChangeButton.Click += new System.EventHandler(this.SGChangeButton_Click); + SGChangeButton.Anchor = AnchorStyles.Top | AnchorStyles.Right; + SGChangeButton.Location = new Point(640, 37); + SGChangeButton.Name = "SGChangeButton"; + SGChangeButton.Size = new Size(52, 23); + SGChangeButton.TabIndex = 22; + SGChangeButton.Text = "Change"; + SGChangeButton.UseVisualStyleBackColor = true; + SGChangeButton.Click += SGChangeButton_Click; // // AppearanceTab // - this.AppearanceTab.Controls.Add(this.DrawgroupEdit); - this.AppearanceTab.Location = new System.Drawing.Point(4, 22); - this.AppearanceTab.Name = "AppearanceTab"; - this.AppearanceTab.Size = new System.Drawing.Size(762, 459); - this.AppearanceTab.TabIndex = 4; - this.AppearanceTab.Text = "Appearance"; - this.AppearanceTab.UseVisualStyleBackColor = true; + AppearanceTab.Controls.Add(DrawgroupEdit); + AppearanceTab.Location = new Point(4, 24); + AppearanceTab.Name = "AppearanceTab"; + AppearanceTab.Size = new Size(192, 72); + AppearanceTab.TabIndex = 4; + AppearanceTab.Text = "Appearance"; + AppearanceTab.UseVisualStyleBackColor = true; // // DrawgroupEdit // - this.DrawgroupEdit.Dock = System.Windows.Forms.DockStyle.Fill; - this.DrawgroupEdit.Location = new System.Drawing.Point(0, 0); - this.DrawgroupEdit.Name = "DrawgroupEdit"; - this.DrawgroupEdit.Size = new System.Drawing.Size(762, 459); - this.DrawgroupEdit.TabIndex = 0; + DrawgroupEdit.Dock = DockStyle.Fill; + DrawgroupEdit.Location = new Point(0, 0); + DrawgroupEdit.Margin = new Padding(4, 3, 4, 3); + DrawgroupEdit.Name = "DrawgroupEdit"; + DrawgroupEdit.Size = new Size(192, 72); + DrawgroupEdit.TabIndex = 0; // // tabPage3 // - this.tabPage3.Controls.Add(this.FuncEditor); - this.tabPage3.Location = new System.Drawing.Point(4, 22); - this.tabPage3.Name = "tabPage3"; - this.tabPage3.Size = new System.Drawing.Size(762, 459); - this.tabPage3.TabIndex = 2; - this.tabPage3.Text = "Entry Points"; - this.tabPage3.UseVisualStyleBackColor = true; + tabPage3.Controls.Add(FuncEditor); + tabPage3.Location = new Point(4, 24); + tabPage3.Name = "tabPage3"; + tabPage3.Size = new Size(192, 72); + tabPage3.TabIndex = 2; + tabPage3.Text = "Entry Points"; + tabPage3.UseVisualStyleBackColor = true; // // FuncEditor // - this.FuncEditor.Dock = System.Windows.Forms.DockStyle.Fill; - this.FuncEditor.Location = new System.Drawing.Point(0, 0); - this.FuncEditor.Margin = new System.Windows.Forms.Padding(0); - this.FuncEditor.Name = "FuncEditor"; - this.FuncEditor.Size = new System.Drawing.Size(762, 459); - this.FuncEditor.TabIndex = 0; + FuncEditor.Dock = DockStyle.Fill; + FuncEditor.Location = new Point(0, 0); + FuncEditor.Margin = new Padding(0); + FuncEditor.Name = "FuncEditor"; + FuncEditor.Size = new Size(192, 72); + FuncEditor.TabIndex = 0; // // tabPage2 // - this.tabPage2.Controls.Add(this.IffResView); - this.tabPage2.Location = new System.Drawing.Point(4, 22); - this.tabPage2.Name = "tabPage2"; - this.tabPage2.Size = new System.Drawing.Size(762, 459); - this.tabPage2.TabIndex = 1; - this.tabPage2.Text = "Trees and Resources"; - this.tabPage2.UseVisualStyleBackColor = true; + tabPage2.Controls.Add(IffResView); + tabPage2.Location = new Point(4, 24); + tabPage2.Name = "tabPage2"; + tabPage2.Size = new Size(192, 72); + tabPage2.TabIndex = 1; + tabPage2.Text = "Trees and Resources"; + tabPage2.UseVisualStyleBackColor = true; // // IffResView // - this.IffResView.Dock = System.Windows.Forms.DockStyle.Fill; - this.IffResView.Location = new System.Drawing.Point(0, 0); - this.IffResView.Margin = new System.Windows.Forms.Padding(0); - this.IffResView.Name = "IffResView"; - this.IffResView.Size = new System.Drawing.Size(762, 459); - this.IffResView.TabIndex = 0; + IffResView.Dock = DockStyle.Fill; + IffResView.Location = new Point(0, 0); + IffResView.Margin = new Padding(0); + IffResView.Name = "IffResView"; + IffResView.Size = new Size(192, 72); + IffResView.TabIndex = 0; // // DefinitionTab // - this.DefinitionTab.Controls.Add(this.DefinitionEditor); - this.DefinitionTab.Location = new System.Drawing.Point(4, 22); - this.DefinitionTab.Name = "DefinitionTab"; - this.DefinitionTab.Padding = new System.Windows.Forms.Padding(3); - this.DefinitionTab.Size = new System.Drawing.Size(762, 459); - this.DefinitionTab.TabIndex = 0; - this.DefinitionTab.Text = "Object"; - this.DefinitionTab.UseVisualStyleBackColor = true; + DefinitionTab.Controls.Add(DefinitionEditor); + DefinitionTab.Location = new Point(4, 22); + DefinitionTab.Name = "DefinitionTab"; + DefinitionTab.Padding = new Padding(3); + DefinitionTab.Size = new Size(762, 459); + DefinitionTab.TabIndex = 0; + DefinitionTab.Text = "Object"; + DefinitionTab.UseVisualStyleBackColor = true; // // DefinitionEditor // - this.DefinitionEditor.Location = new System.Drawing.Point(0, 0); - this.DefinitionEditor.Name = "DefinitionEditor"; - this.DefinitionEditor.Size = new System.Drawing.Size(762, 459); - this.DefinitionEditor.TabIndex = 0; + DefinitionEditor.Location = new Point(0, 0); + DefinitionEditor.Margin = new Padding(4, 3, 4, 3); + DefinitionEditor.Name = "DefinitionEditor"; + DefinitionEditor.Size = new Size(762, 459); + DefinitionEditor.TabIndex = 0; // // objPages // - this.objPages.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.objPages.Controls.Add(this.DefinitionTab); - this.objPages.Controls.Add(this.tabPage2); - this.objPages.Controls.Add(this.tabPage3); - this.objPages.Controls.Add(this.AppearanceTab); - this.objPages.Controls.Add(this.Debug3D); - this.objPages.Controls.Add(this.XMLEntryTab); - this.objPages.Controls.Add(this.UpgradeTab); - this.objPages.Controls.Add(this.PatchTab); - this.objPages.Location = new System.Drawing.Point(7, 68); - this.objPages.Name = "objPages"; - this.objPages.SelectedIndex = 0; - this.objPages.Size = new System.Drawing.Size(770, 485); - this.objPages.TabIndex = 0; + objPages.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + objPages.Controls.Add(DefinitionTab); + objPages.Controls.Add(tabPage2); + objPages.Controls.Add(tabPage3); + objPages.Controls.Add(AppearanceTab); + objPages.Controls.Add(Debug3D); + objPages.Controls.Add(XMLEntryTab); + objPages.Controls.Add(UpgradeTab); + objPages.Controls.Add(PatchTab); + objPages.Location = new Point(7, 68); + objPages.Name = "objPages"; + objPages.SelectedIndex = 0; + objPages.Size = new Size(770, 485); + objPages.TabIndex = 0; // // Debug3D // - this.Debug3D.Controls.Add(this.FSOMEdit); - this.Debug3D.Location = new System.Drawing.Point(4, 22); - this.Debug3D.Name = "Debug3D"; - this.Debug3D.Padding = new System.Windows.Forms.Padding(3); - this.Debug3D.Size = new System.Drawing.Size(762, 459); - this.Debug3D.TabIndex = 5; - this.Debug3D.Text = "3D Mode"; - this.Debug3D.UseVisualStyleBackColor = true; + Debug3D.Controls.Add(FSOMEdit); + Debug3D.Location = new Point(4, 24); + Debug3D.Name = "Debug3D"; + Debug3D.Padding = new Padding(3); + Debug3D.Size = new Size(192, 72); + Debug3D.TabIndex = 5; + Debug3D.Text = "3D Mode"; + Debug3D.UseVisualStyleBackColor = true; // // FSOMEdit // - this.FSOMEdit.Location = new System.Drawing.Point(0, 0); - this.FSOMEdit.Margin = new System.Windows.Forms.Padding(0); - this.FSOMEdit.Name = "FSOMEdit"; - this.FSOMEdit.Size = new System.Drawing.Size(762, 459); - this.FSOMEdit.TabIndex = 0; + FSOMEdit.Location = new Point(0, 0); + FSOMEdit.Margin = new Padding(0); + FSOMEdit.Name = "FSOMEdit"; + FSOMEdit.Size = new Size(762, 459); + FSOMEdit.TabIndex = 0; // // XMLEntryTab // - this.XMLEntryTab.Controls.Add(this.XMLEdit); - this.XMLEntryTab.Location = new System.Drawing.Point(4, 22); - this.XMLEntryTab.Name = "XMLEntryTab"; - this.XMLEntryTab.Size = new System.Drawing.Size(762, 459); - this.XMLEntryTab.TabIndex = 6; - this.XMLEntryTab.Text = "XML Entry"; - this.XMLEntryTab.UseVisualStyleBackColor = true; + XMLEntryTab.Controls.Add(XMLEdit); + XMLEntryTab.Location = new Point(4, 24); + XMLEntryTab.Name = "XMLEntryTab"; + XMLEntryTab.Size = new Size(192, 72); + XMLEntryTab.TabIndex = 6; + XMLEntryTab.Text = "XML Entry"; + XMLEntryTab.UseVisualStyleBackColor = true; // // XMLEdit // - this.XMLEdit.Location = new System.Drawing.Point(-1, 0); - this.XMLEdit.Name = "XMLEdit"; - this.XMLEdit.Size = new System.Drawing.Size(762, 459); - this.XMLEdit.TabIndex = 0; + XMLEdit.Location = new Point(-1, 0); + XMLEdit.Margin = new Padding(4, 3, 4, 3); + XMLEdit.Name = "XMLEdit"; + XMLEdit.Size = new Size(762, 459); + XMLEdit.TabIndex = 0; + // + // UpgradeTab + // + UpgradeTab.Controls.Add(UpgradeEditor); + UpgradeTab.Location = new Point(4, 24); + UpgradeTab.Name = "UpgradeTab"; + UpgradeTab.Size = new Size(192, 72); + UpgradeTab.TabIndex = 8; + UpgradeTab.Text = "Upgrades"; + UpgradeTab.UseVisualStyleBackColor = true; + // + // UpgradeEditor + // + UpgradeEditor.Dock = DockStyle.Fill; + UpgradeEditor.Location = new Point(0, 0); + UpgradeEditor.Margin = new Padding(4, 3, 4, 3); + UpgradeEditor.Name = "UpgradeEditor"; + UpgradeEditor.Size = new Size(192, 72); + UpgradeEditor.TabIndex = 0; // // PatchTab // - this.PatchTab.Controls.Add(this.PIFFEditor); - this.PatchTab.Location = new System.Drawing.Point(4, 22); - this.PatchTab.Name = "PatchTab"; - this.PatchTab.Size = new System.Drawing.Size(762, 459); - this.PatchTab.TabIndex = 7; - this.PatchTab.Text = "Patch Info"; - this.PatchTab.UseVisualStyleBackColor = true; + PatchTab.Controls.Add(PIFFEditor); + PatchTab.Location = new Point(4, 24); + PatchTab.Name = "PatchTab"; + PatchTab.Size = new Size(192, 72); + PatchTab.TabIndex = 7; + PatchTab.Text = "Patch Info"; + PatchTab.UseVisualStyleBackColor = true; // // PIFFEditor // - this.PIFFEditor.Dock = System.Windows.Forms.DockStyle.Fill; - this.PIFFEditor.Location = new System.Drawing.Point(0, 0); - this.PIFFEditor.Name = "PIFFEditor"; - this.PIFFEditor.Size = new System.Drawing.Size(762, 459); - this.PIFFEditor.TabIndex = 0; + PIFFEditor.Dock = DockStyle.Fill; + PIFFEditor.Location = new Point(0, 0); + PIFFEditor.Margin = new Padding(4, 3, 4, 3); + PIFFEditor.Name = "PIFFEditor"; + PIFFEditor.Size = new Size(192, 72); + PIFFEditor.TabIndex = 0; // // NewOBJD // - this.NewOBJD.Location = new System.Drawing.Point(416, 11); - this.NewOBJD.Name = "NewOBJD"; - this.NewOBJD.Size = new System.Drawing.Size(47, 23); - this.NewOBJD.TabIndex = 24; - this.NewOBJD.Text = "New"; - this.NewOBJD.UseVisualStyleBackColor = true; - this.NewOBJD.Click += new System.EventHandler(this.NewOBJD_Click); + NewOBJD.Location = new Point(416, 11); + NewOBJD.Name = "NewOBJD"; + NewOBJD.Size = new Size(47, 23); + NewOBJD.TabIndex = 24; + NewOBJD.Text = "New"; + NewOBJD.UseVisualStyleBackColor = true; + NewOBJD.Click += NewOBJD_Click; // // DeleteOBJD // - this.DeleteOBJD.Location = new System.Drawing.Point(355, 11); - this.DeleteOBJD.Name = "DeleteOBJD"; - this.DeleteOBJD.Size = new System.Drawing.Size(55, 23); - this.DeleteOBJD.TabIndex = 25; - this.DeleteOBJD.Text = "Delete"; - this.DeleteOBJD.UseVisualStyleBackColor = true; - this.DeleteOBJD.Click += new System.EventHandler(this.DeleteOBJD_Click); + DeleteOBJD.Location = new Point(355, 11); + DeleteOBJD.Name = "DeleteOBJD"; + DeleteOBJD.Size = new Size(55, 23); + DeleteOBJD.TabIndex = 25; + DeleteOBJD.Text = "Delete"; + DeleteOBJD.UseVisualStyleBackColor = true; + DeleteOBJD.Click += DeleteOBJD_Click; // // ObjThumb // - this.ObjThumb.Location = new System.Drawing.Point(7, 12); - this.ObjThumb.Name = "ObjThumb"; - this.ObjThumb.Size = new System.Drawing.Size(48, 48); - this.ObjThumb.TabIndex = 23; - // - // UpgradeTab - // - this.UpgradeTab.Controls.Add(this.UpgradeEditor); - this.UpgradeTab.Location = new System.Drawing.Point(4, 22); - this.UpgradeTab.Name = "UpgradeTab"; - this.UpgradeTab.Size = new System.Drawing.Size(762, 459); - this.UpgradeTab.TabIndex = 8; - this.UpgradeTab.Text = "Upgrades"; - this.UpgradeTab.UseVisualStyleBackColor = true; - // - // UpgradeEditor - // - this.UpgradeEditor.Dock = System.Windows.Forms.DockStyle.Fill; - this.UpgradeEditor.Location = new System.Drawing.Point(0, 0); - this.UpgradeEditor.Name = "UpgradeEditor"; - this.UpgradeEditor.Size = new System.Drawing.Size(762, 459); - this.UpgradeEditor.TabIndex = 0; + ObjThumb.Location = new Point(7, 12); + ObjThumb.Name = "ObjThumb"; + ObjThumb.Size = new Size(48, 48); + ObjThumb.TabIndex = 23; // // ObjectWindow // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(784, 561); - this.Controls.Add(this.DeleteOBJD); - this.Controls.Add(this.NewOBJD); - this.Controls.Add(this.ObjThumb); - this.Controls.Add(this.SGChangeButton); - this.Controls.Add(this.GlobalButton); - this.Controls.Add(this.ObjMultitileLabel); - this.Controls.Add(this.ObjDescLabel); - this.Controls.Add(this.ObjNameLabel); - this.Controls.Add(this.SemiGlobalButton); - this.Controls.Add(this.ObjCombo); - this.Controls.Add(this.objPages); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.Name = "ObjectWindow"; - this.Text = "Edit Object - accessoryrack"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.ObjectWindow_FormClosing); - this.AppearanceTab.ResumeLayout(false); - this.tabPage3.ResumeLayout(false); - this.tabPage2.ResumeLayout(false); - this.DefinitionTab.ResumeLayout(false); - this.objPages.ResumeLayout(false); - this.Debug3D.ResumeLayout(false); - this.XMLEntryTab.ResumeLayout(false); - this.PatchTab.ResumeLayout(false); - this.UpgradeTab.ResumeLayout(false); - this.ResumeLayout(false); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(784, 561); + Controls.Add(DeleteOBJD); + Controls.Add(NewOBJD); + Controls.Add(ObjThumb); + Controls.Add(SGChangeButton); + Controls.Add(GlobalButton); + Controls.Add(ObjMultitileLabel); + Controls.Add(ObjDescLabel); + Controls.Add(ObjNameLabel); + Controls.Add(SemiGlobalButton); + Controls.Add(ObjCombo); + Controls.Add(objPages); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + Name = "ObjectWindow"; + Text = "Edit Object - accessoryrack"; + FormClosing += ObjectWindow_FormClosing; + AppearanceTab.ResumeLayout(false); + tabPage3.ResumeLayout(false); + tabPage2.ResumeLayout(false); + DefinitionTab.ResumeLayout(false); + objPages.ResumeLayout(false); + Debug3D.ResumeLayout(false); + XMLEntryTab.ResumeLayout(false); + UpgradeTab.ResumeLayout(false); + PatchTab.ResumeLayout(false); + ResumeLayout(false); } diff --git a/TSOClient/FSO.IDE/ObjectWindow.resx b/TSOClient/FSO.IDE/ObjectWindow.resx index 21e397da9..62b6bd5cd 100644 --- a/TSOClient/FSO.IDE/ObjectWindow.resx +++ b/TSOClient/FSO.IDE/ObjectWindow.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/Program.cs b/TSOClient/FSO.IDE/Program.cs index cbe59fd05..7b2438c54 100644 --- a/TSOClient/FSO.IDE/Program.cs +++ b/TSOClient/FSO.IDE/Program.cs @@ -68,6 +68,8 @@ public void InitVolcanic(string[] args) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); + Application.SetDefaultFont(new Font(new FontFamily("Segoe UI"), 8.25f)); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); Files.Formats.IFF.Chunks.SPR2FrameEncoder.QuantizeFrame = SpriteEncoderUtils.QuantizeFrame; FSO.Files.Formats.IFF.IffFile.RETAIN_CHUNK_DATA = true; FSO.SimAntics.VM.SignalBreaks = true; diff --git a/TSOClient/FSO.IDE/Properties/AssemblyInfo.cs b/TSOClient/FSO.IDE/Properties/AssemblyInfo.cs deleted file mode 100644 index fec409ffa..000000000 --- a/TSOClient/FSO.IDE/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.IDE")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.IDE")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("5deb20eb-1eb7-48f9-922c-463abae56e63")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.IDE/Properties/launchSettings.json b/TSOClient/FSO.IDE/Properties/launchSettings.json new file mode 100644 index 000000000..8cf40256a --- /dev/null +++ b/TSOClient/FSO.IDE/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "FSO.IDE": { + "commandName": "Project" + }, + "FSO.IDE 3D": { + "commandName": "Project", + "commandLineArgs": "-3d" + } + } +} \ No newline at end of file diff --git a/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.Designer.cs index 64e21d4e7..097f5a3b8 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.Designer.cs @@ -663,8 +663,8 @@ private void InitializeComponent() // // DGRPEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.AutoRot); this.Controls.Add(this.AutoZoom); this.Controls.Add(this.groupBox1); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.cs b/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.cs index 4c38c5d3f..afdda5450 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/DGRPEditor.cs @@ -88,13 +88,13 @@ public void UpdateDGRPList(bool selectBase) if (relativeIndex == 0) { baseG = DGRPList.Items.Count; - listItem.BackColor = Color.LightSeaGreen; + listItem.BackColor = System.Drawing.Color.LightSeaGreen; } else if (relativeIndex == ActiveObject.OBJ.NumGraphics - 1) - listItem.BackColor = Color.LightSalmon; + listItem.BackColor = System.Drawing.Color.LightSalmon; if (notUsed) - listItem.BackColor = Color.LightGray; + listItem.BackColor = System.Drawing.Color.LightGray; else lastG = DGRPList.Items.Count; diff --git a/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.Designer.cs index 5e88281f8..67fa8f2d3 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.Designer.cs @@ -340,8 +340,8 @@ private void InitializeComponent() // // FSOMEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.IffCheck); this.Controls.Add(this.Debug3D); this.Controls.Add(this.DGRPBox); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.cs b/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.cs index b7cb1ec65..5d3238bce 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/FSOMEditor.cs @@ -100,13 +100,13 @@ public void UpdateDGRPList(bool selectBase) if (relativeIndex == 0) { baseG = DGRPList.Items.Count; - listItem.BackColor = Color.LightSeaGreen; + listItem.BackColor = System.Drawing.Color.LightSeaGreen; } else if (relativeIndex == ActiveObject.OBJ.NumGraphics - 1) - listItem.BackColor = Color.LightSalmon; + listItem.BackColor = System.Drawing.Color.LightSalmon; if (notUsed) - listItem.BackColor = Color.LightGray; + listItem.BackColor = System.Drawing.Color.LightGray; else lastG = DGRPList.Items.Count; diff --git a/TSOClient/FSO.IDE/ResourceBrowser/GUIDChange.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/GUIDChange.Designer.cs index 33a677318..48f76b9f8 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/GUIDChange.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/GUIDChange.Designer.cs @@ -72,8 +72,8 @@ private void InitializeComponent() // // GUIDChange // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(282, 145); this.ControlBox = false; this.Controls.Add(this.button2); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/GenericTextInput.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/GenericTextInput.Designer.cs index cd2fb06a1..a7372693e 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/GenericTextInput.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/GenericTextInput.Designer.cs @@ -72,8 +72,8 @@ private void InitializeComponent() // // GenericTextInput // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(283, 87); this.ControlBox = false; this.Controls.Add(this.DescLabel); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.Designer.cs index 36dd8aae7..e1df231b8 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.Designer.cs @@ -158,8 +158,8 @@ private void InitializeComponent() // // IFFResComponent // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.ResControlPanel); this.Controls.Add(this.ResTypeCombo); this.Controls.Add(this.RenameRes); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.cs b/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.cs index 60c72747d..d7dc6165c 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/IFFResComponent.cs @@ -75,9 +75,9 @@ public partial class IFFResComponent : UserControl new OBJDSelector[] { } }; - private ContextMenu ResRightClick; - private MenuItem ResRCAlpha; - private MenuItem ResRCShowID; + private ContextMenuStrip ResRightClick; + private ToolStripMenuItem ResRCAlpha; + private ToolStripMenuItem ResRCShowID; private List VisibleChunks; private OBJDSelector[] ActiveSelectors; @@ -88,15 +88,15 @@ public IFFResComponent() { InitializeComponent(); - ResRightClick = new ContextMenu(); - ResRCAlpha = new MenuItem() { Text = "Alphabetical Order", Index = 0, Checked = true }; + ResRightClick = new ContextMenuStrip(); + ResRCAlpha = new ToolStripMenuItem() { Text = "Alphabetical Order", MergeIndex = 0, Checked = true }; ResRCAlpha.Click += ResRCAlpha_Select; - ResRCShowID = new MenuItem() { Text = "Show IDs", Index = 1, Checked = true }; + ResRCShowID = new ToolStripMenuItem() { Text = "Show IDs", MergeIndex = 1, Checked = true }; ResRCShowID.Click += ResRCShowID_Select; - ResRightClick.MenuItems.AddRange(new MenuItem[]{ ResRCAlpha, ResRCShowID }); - ResList.ContextMenu = ResRightClick; + ResRightClick.Items.AddRange(new ToolStripItem[] { ResRCAlpha, ResRCShowID }); + ResList.ContextMenuStrip = ResRightClick; ResList.DrawMode = DrawMode.OwnerDrawFixed; ResList.DrawItem += ResList_DrawItem; } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/IffNameDialog.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/IffNameDialog.Designer.cs index 62a3f66ec..7230dbd82 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/IffNameDialog.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/IffNameDialog.Designer.cs @@ -106,8 +106,8 @@ private void InitializeComponent() // // IffNameDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(320, 108); this.ControlBox = false; this.Controls.Add(this.ChunkIDEntry); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.Designer.cs index 105e44312..14a978aad 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.Designer.cs @@ -29,103 +29,98 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(IffResourceViewer)); - this.iffRes = new FSO.IDE.ResourceBrowser.IFFResComponent(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.viewToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.resourcesToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.patchesPIFFToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.saveIFFToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.piffEditor = new FSO.IDE.ResourceBrowser.PIFFEditor(); - this.menuStrip1.SuspendLayout(); - this.SuspendLayout(); + iffRes = new IFFResComponent(); + menuStrip1 = new MenuStrip(); + fileToolStripMenuItem = new ToolStripMenuItem(); + saveIFFToolStripMenuItem = new ToolStripMenuItem(); + viewToolStripMenuItem = new ToolStripMenuItem(); + resourcesToolStripMenuItem = new ToolStripMenuItem(); + patchesPIFFToolStripMenuItem = new ToolStripMenuItem(); + piffEditor = new PIFFEditor(); + menuStrip1.SuspendLayout(); + SuspendLayout(); // // iffRes // - this.iffRes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.iffRes.Location = new System.Drawing.Point(3, 28); - this.iffRes.Name = "iffRes"; - this.iffRes.Size = new System.Drawing.Size(762, 459); - this.iffRes.TabIndex = 0; + iffRes.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + iffRes.Location = new Point(3, 28); + iffRes.Margin = new Padding(4, 3, 4, 3); + iffRes.Name = "iffRes"; + iffRes.Size = new Size(762, 459); + iffRes.TabIndex = 0; // // menuStrip1 // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem, - this.viewToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(768, 24); - this.menuStrip1.TabIndex = 1; - this.menuStrip1.Text = "menuStrip1"; + menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem, viewToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(768, 24); + menuStrip1.TabIndex = 1; + menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.saveIFFToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); - this.fileToolStripMenuItem.Text = "File"; + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { saveIFFToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new Size(37, 20); + fileToolStripMenuItem.Text = "File"; + // + // saveIFFToolStripMenuItem + // + saveIFFToolStripMenuItem.Name = "saveIFFToolStripMenuItem"; + saveIFFToolStripMenuItem.Size = new Size(116, 22); + saveIFFToolStripMenuItem.Text = "Save IFF"; // // viewToolStripMenuItem // - this.viewToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.resourcesToolStripMenuItem, - this.patchesPIFFToolStripMenuItem}); - this.viewToolStripMenuItem.Name = "viewToolStripMenuItem"; - this.viewToolStripMenuItem.Size = new System.Drawing.Size(44, 20); - this.viewToolStripMenuItem.Text = "View"; + viewToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { resourcesToolStripMenuItem, patchesPIFFToolStripMenuItem }); + viewToolStripMenuItem.Name = "viewToolStripMenuItem"; + viewToolStripMenuItem.Size = new Size(44, 20); + viewToolStripMenuItem.Text = "View"; // // resourcesToolStripMenuItem // - this.resourcesToolStripMenuItem.Name = "resourcesToolStripMenuItem"; - this.resourcesToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.resourcesToolStripMenuItem.Text = "Resources"; - this.resourcesToolStripMenuItem.Click += new System.EventHandler(this.resourcesToolStripMenuItem_Click); + resourcesToolStripMenuItem.Name = "resourcesToolStripMenuItem"; + resourcesToolStripMenuItem.Size = new Size(148, 22); + resourcesToolStripMenuItem.Text = "Resources"; + resourcesToolStripMenuItem.Click += resourcesToolStripMenuItem_Click; // // patchesPIFFToolStripMenuItem // - this.patchesPIFFToolStripMenuItem.Name = "patchesPIFFToolStripMenuItem"; - this.patchesPIFFToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.patchesPIFFToolStripMenuItem.Text = "Patches (PIFF)"; - this.patchesPIFFToolStripMenuItem.Click += new System.EventHandler(this.patchesPIFFToolStripMenuItem_Click); - // - // saveIFFToolStripMenuItem - // - this.saveIFFToolStripMenuItem.Name = "saveIFFToolStripMenuItem"; - this.saveIFFToolStripMenuItem.Size = new System.Drawing.Size(180, 22); - this.saveIFFToolStripMenuItem.Text = "Save IFF"; + patchesPIFFToolStripMenuItem.Name = "patchesPIFFToolStripMenuItem"; + patchesPIFFToolStripMenuItem.Size = new Size(148, 22); + patchesPIFFToolStripMenuItem.Text = "Patches (PIFF)"; + patchesPIFFToolStripMenuItem.Click += patchesPIFFToolStripMenuItem_Click; // // piffEditor // - this.piffEditor.Location = new System.Drawing.Point(3, 27); - this.piffEditor.Name = "piffEditor"; - this.piffEditor.Size = new System.Drawing.Size(762, 459); - this.piffEditor.TabIndex = 2; - this.piffEditor.Visible = false; + piffEditor.Location = new Point(3, 27); + piffEditor.Margin = new Padding(4, 3, 4, 3); + piffEditor.Name = "piffEditor"; + piffEditor.Size = new Size(762, 459); + piffEditor.TabIndex = 2; + piffEditor.Visible = false; // // IffResourceViewer // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(768, 489); - this.Controls.Add(this.piffEditor); - this.Controls.Add(this.iffRes); - this.Controls.Add(this.menuStrip1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.menuStrip1; - this.MaximizeBox = false; - this.MinimumSize = new System.Drawing.Size(784, 504); - this.Name = "IffResourceViewer"; - this.Text = "Edit Iff - globals"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.IffResourceViewer_FormClosing); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(768, 489); + Controls.Add(piffEditor); + Controls.Add(iffRes); + Controls.Add(menuStrip1); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + MaximizeBox = false; + MinimumSize = new Size(784, 504); + Name = "IffResourceViewer"; + Text = "Edit Iff - globals"; + FormClosing += IffResourceViewer_FormClosing; + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.resx b/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.resx index ea07ded6c..1922e3900 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.resx +++ b/TSOClient/FSO.IDE/ResourceBrowser/IffResourceViewer.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs index 4f5aa7f8e..756cd70e9 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.Designer.cs @@ -28,1277 +28,1186 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.ThumbnailBox = new System.Windows.Forms.GroupBox(); - this.ThumbSave = new System.Windows.Forms.Button(); - this.ImportButton = new System.Windows.Forms.Button(); - this.RegenThumb = new System.Windows.Forms.Button(); - this.ThumbnailPic = new System.Windows.Forms.PictureBox(); - this.VisualBox = new System.Windows.Forms.GroupBox(); - this.DeprLimit = new System.Windows.Forms.NumericUpDown(); - this.label18 = new System.Windows.Forms.Label(); - this.DeprDaily = new System.Windows.Forms.NumericUpDown(); - this.label16 = new System.Windows.Forms.Label(); - this.DeprInitial = new System.Windows.Forms.NumericUpDown(); - this.label17 = new System.Windows.Forms.Label(); - this.DepreciationLabel = new System.Windows.Forms.Label(); - this.ShadowEntry = new System.Windows.Forms.NumericUpDown(); - this.label15 = new System.Windows.Forms.Label(); - this.ShadowType = new System.Windows.Forms.ComboBox(); - this.pictureBox2 = new System.Windows.Forms.PictureBox(); - this.comboBox4 = new System.Windows.Forms.ComboBox(); - this.label14 = new System.Windows.Forms.Label(); - this.MotiveBox = new System.Windows.Forms.GroupBox(); - this.SklCharisma = new System.Windows.Forms.CheckBox(); - this.SklCreativity = new System.Windows.Forms.CheckBox(); - this.SklBody = new System.Windows.Forms.CheckBox(); - this.SklLogic = new System.Windows.Forms.CheckBox(); - this.SklMechanical = new System.Windows.Forms.CheckBox(); - this.SklCooking = new System.Windows.Forms.CheckBox(); - this.label10 = new System.Windows.Forms.Label(); - this.MotiveRoom = new System.Windows.Forms.NumericUpDown(); - this.label11 = new System.Windows.Forms.Label(); - this.MotiveFun = new System.Windows.Forms.NumericUpDown(); - this.label12 = new System.Windows.Forms.Label(); - this.MotiveEnergy = new System.Windows.Forms.NumericUpDown(); - this.label13 = new System.Windows.Forms.Label(); - this.MotiveBladder = new System.Windows.Forms.NumericUpDown(); - this.label8 = new System.Windows.Forms.Label(); - this.MotiveHygiene = new System.Windows.Forms.NumericUpDown(); - this.label9 = new System.Windows.Forms.Label(); - this.MotiveComfort = new System.Windows.Forms.NumericUpDown(); - this.label2 = new System.Windows.Forms.Label(); - this.MotiveHunger = new System.Windows.Forms.NumericUpDown(); - this.label5 = new System.Windows.Forms.Label(); - this.GlobalSim = new System.Windows.Forms.CheckBox(); - this.VersionLabel = new System.Windows.Forms.Label(); - this.VersionEntry = new System.Windows.Forms.NumericUpDown(); - this.TypeCombo = new System.Windows.Forms.ComboBox(); - this.TypeLabel = new System.Windows.Forms.Label(); - this.CatalogBox = new System.Windows.Forms.GroupBox(); - this.CatCommunity = new System.Windows.Forms.CheckBox(); - this.CatResidence = new System.Windows.Forms.CheckBox(); - this.CatEntertainment = new System.Windows.Forms.CheckBox(); - this.CatGames = new System.Windows.Forms.CheckBox(); - this.CatWelcome = new System.Windows.Forms.CheckBox(); - this.CatSkills = new System.Windows.Forms.CheckBox(); - this.CatShopping = new System.Windows.Forms.CheckBox(); - this.CatServices = new System.Windows.Forms.CheckBox(); - this.CatRomance = new System.Windows.Forms.CheckBox(); - this.CatOffbeat = new System.Windows.Forms.CheckBox(); - this.CatMoney = new System.Windows.Forms.CheckBox(); - this.LotCatLabel = new System.Windows.Forms.Label(); - this.CTSSButton = new System.Windows.Forms.Button(); - this.CTSSIDLabel = new System.Windows.Forms.Label(); - this.CatalogNameLabel = new System.Windows.Forms.Label(); - this.label7 = new System.Windows.Forms.Label(); - this.comboBox3 = new System.Windows.Forms.ComboBox(); - this.label6 = new System.Windows.Forms.Label(); - this.BuyCategory = new System.Windows.Forms.ComboBox(); - this.SalePrice = new System.Windows.Forms.NumericUpDown(); - this.PriceLabel = new System.Windows.Forms.Label(); - this.ObjectView = new FSO.IDE.Common.InteractiveDGRPControl(); - this.PhysicalBox = new System.Windows.Forms.GroupBox(); - this.FrontDirLabel = new System.Windows.Forms.Label(); - this.FrontDir = new System.Windows.Forms.ComboBox(); - this.FootprintSouth = new System.Windows.Forms.NumericUpDown(); - this.FootprintNorth = new System.Windows.Forms.NumericUpDown(); - this.FootprintWest = new System.Windows.Forms.NumericUpDown(); - this.FootprintLabel = new System.Windows.Forms.Label(); - this.TileWidth = new System.Windows.Forms.NumericUpDown(); - this.TileWidthLabel = new System.Windows.Forms.Label(); - this.FootprintEast = new System.Windows.Forms.NumericUpDown(); - this.MultitileBox = new System.Windows.Forms.GroupBox(); - this.InteractionGroupLabel = new System.Windows.Forms.Label(); - this.InteractionGroup = new System.Windows.Forms.NumericUpDown(); - this.MasterMultitile = new System.Windows.Forms.Button(); - this.LeadMultitile = new System.Windows.Forms.Button(); - this.NewMultitile = new System.Windows.Forms.Button(); - this.MultiGroupCombo = new System.Windows.Forms.ComboBox(); - this.label3 = new System.Windows.Forms.Label(); - this.MultitileList = new System.Windows.Forms.ListBox(); - this.XOffset = new System.Windows.Forms.NumericUpDown(); - this.OffsetXLabel = new System.Windows.Forms.Label(); - this.LevelOffset = new System.Windows.Forms.NumericUpDown(); - this.LevelLabel = new System.Windows.Forms.Label(); - this.YOffset = new System.Windows.Forms.NumericUpDown(); - this.OffsetYLabel = new System.Windows.Forms.Label(); - this.GUIDLabel = new System.Windows.Forms.Label(); - this.NameLabel = new System.Windows.Forms.Label(); - this.NameEntry = new System.Windows.Forms.TextBox(); - this.GUIDButton = new System.Windows.Forms.Button(); - this.ThumbnailBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.ThumbnailPic)).BeginInit(); - this.VisualBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.DeprLimit)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.DeprDaily)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.DeprInitial)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.ShadowEntry)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit(); - this.MotiveBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveRoom)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveFun)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveEnergy)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveBladder)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveHygiene)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveComfort)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveHunger)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.VersionEntry)).BeginInit(); - this.CatalogBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.SalePrice)).BeginInit(); - this.PhysicalBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintSouth)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintNorth)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintWest)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.TileWidth)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintEast)).BeginInit(); - this.MultitileBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.InteractionGroup)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.XOffset)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.LevelOffset)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.YOffset)).BeginInit(); - this.SuspendLayout(); + ThumbnailBox = new GroupBox(); + ThumbSave = new Button(); + ImportButton = new Button(); + RegenThumb = new Button(); + ThumbnailPic = new PictureBox(); + VisualBox = new GroupBox(); + DeprLimit = new NumericUpDown(); + label18 = new Label(); + DeprDaily = new NumericUpDown(); + label16 = new Label(); + DeprInitial = new NumericUpDown(); + label17 = new Label(); + DepreciationLabel = new Label(); + ShadowEntry = new NumericUpDown(); + label15 = new Label(); + ShadowType = new ComboBox(); + pictureBox2 = new PictureBox(); + comboBox4 = new ComboBox(); + label14 = new Label(); + MotiveBox = new GroupBox(); + SklCharisma = new CheckBox(); + SklCreativity = new CheckBox(); + SklBody = new CheckBox(); + SklLogic = new CheckBox(); + SklMechanical = new CheckBox(); + SklCooking = new CheckBox(); + label10 = new Label(); + MotiveRoom = new NumericUpDown(); + label11 = new Label(); + MotiveFun = new NumericUpDown(); + label12 = new Label(); + MotiveEnergy = new NumericUpDown(); + label13 = new Label(); + MotiveBladder = new NumericUpDown(); + label8 = new Label(); + MotiveHygiene = new NumericUpDown(); + label9 = new Label(); + MotiveComfort = new NumericUpDown(); + label2 = new Label(); + MotiveHunger = new NumericUpDown(); + label5 = new Label(); + GlobalSim = new CheckBox(); + VersionLabel = new Label(); + VersionEntry = new NumericUpDown(); + TypeCombo = new ComboBox(); + TypeLabel = new Label(); + CatalogBox = new GroupBox(); + CatCommunity = new CheckBox(); + CatResidence = new CheckBox(); + CatEntertainment = new CheckBox(); + CatGames = new CheckBox(); + CatWelcome = new CheckBox(); + CatSkills = new CheckBox(); + CatShopping = new CheckBox(); + CatServices = new CheckBox(); + CatRomance = new CheckBox(); + CatOffbeat = new CheckBox(); + CatMoney = new CheckBox(); + LotCatLabel = new Label(); + CTSSButton = new Button(); + CTSSIDLabel = new Label(); + CatalogNameLabel = new Label(); + label7 = new Label(); + comboBox3 = new ComboBox(); + label6 = new Label(); + BuyCategory = new ComboBox(); + SalePrice = new NumericUpDown(); + PriceLabel = new Label(); + ObjectView = new FSO.IDE.Common.InteractiveDGRPControl(); + PhysicalBox = new GroupBox(); + FrontDirLabel = new Label(); + FrontDir = new ComboBox(); + FootprintSouth = new NumericUpDown(); + FootprintNorth = new NumericUpDown(); + FootprintWest = new NumericUpDown(); + FootprintLabel = new Label(); + TileWidth = new NumericUpDown(); + TileWidthLabel = new Label(); + FootprintEast = new NumericUpDown(); + MultitileBox = new GroupBox(); + InteractionGroupLabel = new Label(); + InteractionGroup = new NumericUpDown(); + MasterMultitile = new Button(); + LeadMultitile = new Button(); + NewMultitile = new Button(); + MultiGroupCombo = new ComboBox(); + label3 = new Label(); + MultitileList = new ListBox(); + XOffset = new NumericUpDown(); + OffsetXLabel = new Label(); + LevelOffset = new NumericUpDown(); + LevelLabel = new Label(); + YOffset = new NumericUpDown(); + OffsetYLabel = new Label(); + GUIDLabel = new Label(); + NameLabel = new Label(); + NameEntry = new TextBox(); + GUIDButton = new Button(); + ThumbnailBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)ThumbnailPic).BeginInit(); + VisualBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)DeprLimit).BeginInit(); + ((System.ComponentModel.ISupportInitialize)DeprDaily).BeginInit(); + ((System.ComponentModel.ISupportInitialize)DeprInitial).BeginInit(); + ((System.ComponentModel.ISupportInitialize)ShadowEntry).BeginInit(); + ((System.ComponentModel.ISupportInitialize)pictureBox2).BeginInit(); + MotiveBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)MotiveRoom).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveFun).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveEnergy).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveBladder).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveHygiene).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveComfort).BeginInit(); + ((System.ComponentModel.ISupportInitialize)MotiveHunger).BeginInit(); + ((System.ComponentModel.ISupportInitialize)VersionEntry).BeginInit(); + CatalogBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)SalePrice).BeginInit(); + PhysicalBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)FootprintSouth).BeginInit(); + ((System.ComponentModel.ISupportInitialize)FootprintNorth).BeginInit(); + ((System.ComponentModel.ISupportInitialize)FootprintWest).BeginInit(); + ((System.ComponentModel.ISupportInitialize)TileWidth).BeginInit(); + ((System.ComponentModel.ISupportInitialize)FootprintEast).BeginInit(); + MultitileBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)InteractionGroup).BeginInit(); + ((System.ComponentModel.ISupportInitialize)XOffset).BeginInit(); + ((System.ComponentModel.ISupportInitialize)LevelOffset).BeginInit(); + ((System.ComponentModel.ISupportInitialize)YOffset).BeginInit(); + SuspendLayout(); // // ThumbnailBox // - this.ThumbnailBox.Controls.Add(this.ThumbSave); - this.ThumbnailBox.Controls.Add(this.ImportButton); - this.ThumbnailBox.Controls.Add(this.RegenThumb); - this.ThumbnailBox.Controls.Add(this.ThumbnailPic); - this.ThumbnailBox.Location = new System.Drawing.Point(656, 335); - this.ThumbnailBox.Name = "ThumbnailBox"; - this.ThumbnailBox.Size = new System.Drawing.Size(100, 118); - this.ThumbnailBox.TabIndex = 67; - this.ThumbnailBox.TabStop = false; - this.ThumbnailBox.Text = "Thumbnail"; + ThumbnailBox.Controls.Add(ThumbSave); + ThumbnailBox.Controls.Add(ImportButton); + ThumbnailBox.Controls.Add(RegenThumb); + ThumbnailBox.Controls.Add(ThumbnailPic); + ThumbnailBox.Location = new Point(656, 335); + ThumbnailBox.Name = "ThumbnailBox"; + ThumbnailBox.Size = new Size(100, 118); + ThumbnailBox.TabIndex = 67; + ThumbnailBox.TabStop = false; + ThumbnailBox.Text = "Thumbnail"; // // ThumbSave // - this.ThumbSave.Enabled = false; - this.ThumbSave.Location = new System.Drawing.Point(49, 87); - this.ThumbSave.Name = "ThumbSave"; - this.ThumbSave.Size = new System.Drawing.Size(43, 23); - this.ThumbSave.TabIndex = 3; - this.ThumbSave.Text = "Save"; - this.ThumbSave.UseVisualStyleBackColor = true; - this.ThumbSave.Click += new System.EventHandler(this.ThumbSave_Click); + ThumbSave.Enabled = false; + ThumbSave.Location = new Point(49, 87); + ThumbSave.Name = "ThumbSave"; + ThumbSave.Size = new Size(43, 23); + ThumbSave.TabIndex = 3; + ThumbSave.Text = "Save"; + ThumbSave.UseVisualStyleBackColor = true; + ThumbSave.Click += ThumbSave_Click; // // ImportButton // - this.ImportButton.Location = new System.Drawing.Point(6, 87); - this.ImportButton.Name = "ImportButton"; - this.ImportButton.Size = new System.Drawing.Size(43, 23); - this.ImportButton.TabIndex = 2; - this.ImportButton.Text = "Open"; - this.ImportButton.UseVisualStyleBackColor = true; - this.ImportButton.Click += new System.EventHandler(this.ImportButton_Click); + ImportButton.Location = new Point(6, 87); + ImportButton.Name = "ImportButton"; + ImportButton.Size = new Size(43, 23); + ImportButton.TabIndex = 2; + ImportButton.Text = "Open"; + ImportButton.UseVisualStyleBackColor = true; + ImportButton.Click += ImportButton_Click; // // RegenThumb // - this.RegenThumb.Location = new System.Drawing.Point(6, 61); - this.RegenThumb.Name = "RegenThumb"; - this.RegenThumb.Size = new System.Drawing.Size(86, 23); - this.RegenThumb.TabIndex = 1; - this.RegenThumb.Text = "Regenerate"; - this.RegenThumb.UseVisualStyleBackColor = true; - this.RegenThumb.Click += new System.EventHandler(this.RegenThumb_Click); + RegenThumb.Location = new Point(6, 61); + RegenThumb.Name = "RegenThumb"; + RegenThumb.Size = new Size(86, 23); + RegenThumb.TabIndex = 1; + RegenThumb.Text = "Regenerate"; + RegenThumb.UseVisualStyleBackColor = true; + RegenThumb.Click += RegenThumb_Click; // // ThumbnailPic // - this.ThumbnailPic.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.ThumbnailPic.Enabled = false; - this.ThumbnailPic.Location = new System.Drawing.Point(11, 16); - this.ThumbnailPic.Name = "ThumbnailPic"; - this.ThumbnailPic.Size = new System.Drawing.Size(76, 39); - this.ThumbnailPic.TabIndex = 0; - this.ThumbnailPic.TabStop = false; + ThumbnailPic.BorderStyle = BorderStyle.FixedSingle; + ThumbnailPic.Enabled = false; + ThumbnailPic.Location = new Point(11, 16); + ThumbnailPic.Name = "ThumbnailPic"; + ThumbnailPic.Size = new Size(76, 39); + ThumbnailPic.SizeMode = PictureBoxSizeMode.Zoom; + ThumbnailPic.TabIndex = 0; + ThumbnailPic.TabStop = false; // // VisualBox // - this.VisualBox.Controls.Add(this.DeprLimit); - this.VisualBox.Controls.Add(this.label18); - this.VisualBox.Controls.Add(this.DeprDaily); - this.VisualBox.Controls.Add(this.label16); - this.VisualBox.Controls.Add(this.DeprInitial); - this.VisualBox.Controls.Add(this.label17); - this.VisualBox.Controls.Add(this.DepreciationLabel); - this.VisualBox.Controls.Add(this.ShadowEntry); - this.VisualBox.Controls.Add(this.label15); - this.VisualBox.Controls.Add(this.ShadowType); - this.VisualBox.Controls.Add(this.pictureBox2); - this.VisualBox.Controls.Add(this.comboBox4); - this.VisualBox.Controls.Add(this.label14); - this.VisualBox.Location = new System.Drawing.Point(392, 335); - this.VisualBox.Name = "VisualBox"; - this.VisualBox.Size = new System.Drawing.Size(258, 118); - this.VisualBox.TabIndex = 65; - this.VisualBox.TabStop = false; - this.VisualBox.Text = "Other"; + VisualBox.Controls.Add(DeprLimit); + VisualBox.Controls.Add(label18); + VisualBox.Controls.Add(DeprDaily); + VisualBox.Controls.Add(label16); + VisualBox.Controls.Add(DeprInitial); + VisualBox.Controls.Add(label17); + VisualBox.Controls.Add(DepreciationLabel); + VisualBox.Controls.Add(ShadowEntry); + VisualBox.Controls.Add(label15); + VisualBox.Controls.Add(ShadowType); + VisualBox.Controls.Add(pictureBox2); + VisualBox.Controls.Add(comboBox4); + VisualBox.Controls.Add(label14); + VisualBox.Location = new Point(392, 335); + VisualBox.Name = "VisualBox"; + VisualBox.Size = new Size(258, 118); + VisualBox.TabIndex = 65; + VisualBox.TabStop = false; + VisualBox.Text = "Other"; // // DeprLimit // - this.DeprLimit.Location = new System.Drawing.Point(111, 91); - this.DeprLimit.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.DeprLimit.Name = "DeprLimit"; - this.DeprLimit.Size = new System.Drawing.Size(47, 20); - this.DeprLimit.TabIndex = 77; + DeprLimit.Location = new Point(111, 91); + DeprLimit.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + DeprLimit.Name = "DeprLimit"; + DeprLimit.Size = new Size(47, 22); + DeprLimit.TabIndex = 77; // // label18 // - this.label18.Location = new System.Drawing.Point(111, 75); - this.label18.Name = "label18"; - this.label18.Size = new System.Drawing.Size(47, 13); - this.label18.TabIndex = 76; - this.label18.Text = "Limit"; - this.label18.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label18.Location = new Point(111, 75); + label18.Name = "label18"; + label18.Size = new Size(47, 13); + label18.TabIndex = 76; + label18.Text = "Limit"; + label18.TextAlign = ContentAlignment.TopCenter; // // DeprDaily // - this.DeprDaily.Location = new System.Drawing.Point(60, 91); - this.DeprDaily.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.DeprDaily.Name = "DeprDaily"; - this.DeprDaily.Size = new System.Drawing.Size(47, 20); - this.DeprDaily.TabIndex = 75; + DeprDaily.Location = new Point(60, 91); + DeprDaily.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + DeprDaily.Name = "DeprDaily"; + DeprDaily.Size = new Size(47, 22); + DeprDaily.TabIndex = 75; // // label16 // - this.label16.Location = new System.Drawing.Point(60, 75); - this.label16.Name = "label16"; - this.label16.Size = new System.Drawing.Size(47, 13); - this.label16.TabIndex = 74; - this.label16.Text = "Daily"; - this.label16.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label16.Location = new Point(60, 75); + label16.Name = "label16"; + label16.Size = new Size(47, 13); + label16.TabIndex = 74; + label16.Text = "Daily"; + label16.TextAlign = ContentAlignment.TopCenter; // // DeprInitial // - this.DeprInitial.Location = new System.Drawing.Point(9, 91); - this.DeprInitial.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.DeprInitial.Name = "DeprInitial"; - this.DeprInitial.Size = new System.Drawing.Size(47, 20); - this.DeprInitial.TabIndex = 73; + DeprInitial.Location = new Point(9, 91); + DeprInitial.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + DeprInitial.Name = "DeprInitial"; + DeprInitial.Size = new Size(47, 22); + DeprInitial.TabIndex = 73; // // label17 // - this.label17.Location = new System.Drawing.Point(9, 75); - this.label17.Name = "label17"; - this.label17.Size = new System.Drawing.Size(47, 13); - this.label17.TabIndex = 72; - this.label17.Text = "Initial"; - this.label17.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label17.Location = new Point(9, 75); + label17.Name = "label17"; + label17.Size = new Size(47, 13); + label17.TabIndex = 72; + label17.Text = "Initial"; + label17.TextAlign = ContentAlignment.TopCenter; // // DepreciationLabel // - this.DepreciationLabel.Location = new System.Drawing.Point(9, 61); - this.DepreciationLabel.Name = "DepreciationLabel"; - this.DepreciationLabel.Size = new System.Drawing.Size(149, 18); - this.DepreciationLabel.TabIndex = 71; - this.DepreciationLabel.Text = "Depreciation"; - this.DepreciationLabel.TextAlign = System.Drawing.ContentAlignment.TopCenter; + DepreciationLabel.Location = new Point(9, 61); + DepreciationLabel.Name = "DepreciationLabel"; + DepreciationLabel.Size = new Size(149, 18); + DepreciationLabel.TabIndex = 71; + DepreciationLabel.Text = "Depreciation"; + DepreciationLabel.TextAlign = ContentAlignment.TopCenter; // // ShadowEntry // - this.ShadowEntry.Location = new System.Drawing.Point(10, 32); - this.ShadowEntry.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.ShadowEntry.Name = "ShadowEntry"; - this.ShadowEntry.Size = new System.Drawing.Size(63, 20); - this.ShadowEntry.TabIndex = 70; + ShadowEntry.Location = new Point(10, 32); + ShadowEntry.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + ShadowEntry.Name = "ShadowEntry"; + ShadowEntry.Size = new Size(63, 22); + ShadowEntry.TabIndex = 70; // // label15 // - this.label15.AutoSize = true; - this.label15.Location = new System.Drawing.Point(6, 16); - this.label15.Name = "label15"; - this.label15.Size = new System.Drawing.Size(101, 13); - this.label15.TabIndex = 69; - this.label15.Text = "Shadow Brightness:"; + label15.AutoSize = true; + label15.Location = new Point(6, 16); + label15.Name = "label15"; + label15.Size = new Size(110, 13); + label15.TabIndex = 69; + label15.Text = "Shadow Brightness:"; // // ShadowType // - this.ShadowType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.ShadowType.FormattingEnabled = true; - this.ShadowType.Location = new System.Drawing.Point(79, 32); - this.ShadowType.Name = "ShadowType"; - this.ShadowType.Size = new System.Drawing.Size(58, 21); - this.ShadowType.TabIndex = 68; + ShadowType.DropDownStyle = ComboBoxStyle.DropDownList; + ShadowType.FormattingEnabled = true; + ShadowType.Location = new Point(79, 32); + ShadowType.Name = "ShadowType"; + ShadowType.Size = new Size(58, 21); + ShadowType.TabIndex = 68; // // pictureBox2 // - this.pictureBox2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.pictureBox2.Enabled = false; - this.pictureBox2.Location = new System.Drawing.Point(177, 24); - this.pictureBox2.Name = "pictureBox2"; - this.pictureBox2.Size = new System.Drawing.Size(64, 64); - this.pictureBox2.TabIndex = 2; - this.pictureBox2.TabStop = false; + pictureBox2.BorderStyle = BorderStyle.FixedSingle; + pictureBox2.Enabled = false; + pictureBox2.Location = new Point(177, 24); + pictureBox2.Name = "pictureBox2"; + pictureBox2.Size = new Size(64, 64); + pictureBox2.TabIndex = 2; + pictureBox2.TabStop = false; // // comboBox4 // - this.comboBox4.Enabled = false; - this.comboBox4.FormattingEnabled = true; - this.comboBox4.Location = new System.Drawing.Point(166, 92); - this.comboBox4.Name = "comboBox4"; - this.comboBox4.Size = new System.Drawing.Size(86, 21); - this.comboBox4.TabIndex = 1; - this.comboBox4.Text = "None"; + comboBox4.Enabled = false; + comboBox4.FormattingEnabled = true; + comboBox4.Location = new Point(166, 92); + comboBox4.Name = "comboBox4"; + comboBox4.Size = new Size(86, 21); + comboBox4.TabIndex = 1; + comboBox4.Text = "None"; // // label14 // - this.label14.Location = new System.Drawing.Point(166, 10); - this.label14.Name = "label14"; - this.label14.Size = new System.Drawing.Size(86, 15); - this.label14.TabIndex = 0; - this.label14.Text = "Wall Style:"; - this.label14.TextAlign = System.Drawing.ContentAlignment.TopCenter; + label14.Location = new Point(166, 10); + label14.Name = "label14"; + label14.Size = new Size(86, 15); + label14.TabIndex = 0; + label14.Text = "Wall Style:"; + label14.TextAlign = ContentAlignment.TopCenter; // // MotiveBox // - this.MotiveBox.Controls.Add(this.SklCharisma); - this.MotiveBox.Controls.Add(this.SklCreativity); - this.MotiveBox.Controls.Add(this.SklBody); - this.MotiveBox.Controls.Add(this.SklLogic); - this.MotiveBox.Controls.Add(this.SklMechanical); - this.MotiveBox.Controls.Add(this.SklCooking); - this.MotiveBox.Controls.Add(this.label10); - this.MotiveBox.Controls.Add(this.MotiveRoom); - this.MotiveBox.Controls.Add(this.label11); - this.MotiveBox.Controls.Add(this.MotiveFun); - this.MotiveBox.Controls.Add(this.label12); - this.MotiveBox.Controls.Add(this.MotiveEnergy); - this.MotiveBox.Controls.Add(this.label13); - this.MotiveBox.Controls.Add(this.MotiveBladder); - this.MotiveBox.Controls.Add(this.label8); - this.MotiveBox.Controls.Add(this.MotiveHygiene); - this.MotiveBox.Controls.Add(this.label9); - this.MotiveBox.Controls.Add(this.MotiveComfort); - this.MotiveBox.Controls.Add(this.label2); - this.MotiveBox.Controls.Add(this.MotiveHunger); - this.MotiveBox.Controls.Add(this.label5); - this.MotiveBox.Location = new System.Drawing.Point(392, 220); - this.MotiveBox.Name = "MotiveBox"; - this.MotiveBox.Size = new System.Drawing.Size(364, 109); - this.MotiveBox.TabIndex = 66; - this.MotiveBox.TabStop = false; - this.MotiveBox.Text = "Motive Ratings"; + MotiveBox.Controls.Add(SklCharisma); + MotiveBox.Controls.Add(SklCreativity); + MotiveBox.Controls.Add(SklBody); + MotiveBox.Controls.Add(SklLogic); + MotiveBox.Controls.Add(SklMechanical); + MotiveBox.Controls.Add(SklCooking); + MotiveBox.Controls.Add(label10); + MotiveBox.Controls.Add(MotiveRoom); + MotiveBox.Controls.Add(label11); + MotiveBox.Controls.Add(MotiveFun); + MotiveBox.Controls.Add(label12); + MotiveBox.Controls.Add(MotiveEnergy); + MotiveBox.Controls.Add(label13); + MotiveBox.Controls.Add(MotiveBladder); + MotiveBox.Controls.Add(label8); + MotiveBox.Controls.Add(MotiveHygiene); + MotiveBox.Controls.Add(label9); + MotiveBox.Controls.Add(MotiveComfort); + MotiveBox.Controls.Add(label2); + MotiveBox.Controls.Add(MotiveHunger); + MotiveBox.Controls.Add(label5); + MotiveBox.Location = new Point(392, 220); + MotiveBox.Name = "MotiveBox"; + MotiveBox.Size = new Size(364, 109); + MotiveBox.TabIndex = 66; + MotiveBox.TabStop = false; + MotiveBox.Text = "Motive Ratings"; // // SklCharisma // - this.SklCharisma.AutoSize = true; - this.SklCharisma.Location = new System.Drawing.Point(244, 89); - this.SklCharisma.Margin = new System.Windows.Forms.Padding(0); - this.SklCharisma.Name = "SklCharisma"; - this.SklCharisma.Size = new System.Drawing.Size(69, 17); - this.SklCharisma.TabIndex = 67; - this.SklCharisma.Text = "Charisma"; - this.SklCharisma.UseVisualStyleBackColor = true; + SklCharisma.AutoSize = true; + SklCharisma.Location = new Point(244, 89); + SklCharisma.Margin = new Padding(0); + SklCharisma.Name = "SklCharisma"; + SklCharisma.Size = new Size(73, 17); + SklCharisma.TabIndex = 67; + SklCharisma.Text = "Charisma"; + SklCharisma.UseVisualStyleBackColor = true; // // SklCreativity // - this.SklCreativity.AutoSize = true; - this.SklCreativity.Location = new System.Drawing.Point(244, 73); - this.SklCreativity.Margin = new System.Windows.Forms.Padding(0); - this.SklCreativity.Name = "SklCreativity"; - this.SklCreativity.Size = new System.Drawing.Size(69, 17); - this.SklCreativity.TabIndex = 66; - this.SklCreativity.Text = "Creativity"; - this.SklCreativity.UseVisualStyleBackColor = true; + SklCreativity.AutoSize = true; + SklCreativity.Location = new Point(244, 73); + SklCreativity.Margin = new Padding(0); + SklCreativity.Name = "SklCreativity"; + SklCreativity.Size = new Size(73, 17); + SklCreativity.TabIndex = 66; + SklCreativity.Text = "Creativity"; + SklCreativity.UseVisualStyleBackColor = true; // // SklBody // - this.SklBody.AutoSize = true; - this.SklBody.Location = new System.Drawing.Point(306, 57); - this.SklBody.Margin = new System.Windows.Forms.Padding(0); - this.SklBody.Name = "SklBody"; - this.SklBody.Size = new System.Drawing.Size(50, 17); - this.SklBody.TabIndex = 65; - this.SklBody.Text = "Body"; - this.SklBody.UseVisualStyleBackColor = true; + SklBody.AutoSize = true; + SklBody.Location = new Point(306, 57); + SklBody.Margin = new Padding(0); + SklBody.Name = "SklBody"; + SklBody.Size = new Size(52, 17); + SklBody.TabIndex = 65; + SklBody.Text = "Body"; + SklBody.UseVisualStyleBackColor = true; // // SklLogic // - this.SklLogic.AutoSize = true; - this.SklLogic.Location = new System.Drawing.Point(244, 57); - this.SklLogic.Margin = new System.Windows.Forms.Padding(0); - this.SklLogic.Name = "SklLogic"; - this.SklLogic.Size = new System.Drawing.Size(52, 17); - this.SklLogic.TabIndex = 64; - this.SklLogic.Text = "Logic"; - this.SklLogic.UseVisualStyleBackColor = true; + SklLogic.AutoSize = true; + SklLogic.Location = new Point(244, 57); + SklLogic.Margin = new Padding(0); + SklLogic.Name = "SklLogic"; + SklLogic.Size = new Size(53, 17); + SklLogic.TabIndex = 64; + SklLogic.Text = "Logic"; + SklLogic.UseVisualStyleBackColor = true; // // SklMechanical // - this.SklMechanical.AutoSize = true; - this.SklMechanical.Location = new System.Drawing.Point(244, 41); - this.SklMechanical.Margin = new System.Windows.Forms.Padding(0); - this.SklMechanical.Name = "SklMechanical"; - this.SklMechanical.Size = new System.Drawing.Size(81, 17); - this.SklMechanical.TabIndex = 63; - this.SklMechanical.Text = "Mechanical"; - this.SklMechanical.UseVisualStyleBackColor = true; + SklMechanical.AutoSize = true; + SklMechanical.Location = new Point(244, 41); + SklMechanical.Margin = new Padding(0); + SklMechanical.Name = "SklMechanical"; + SklMechanical.Size = new Size(84, 17); + SklMechanical.TabIndex = 63; + SklMechanical.Text = "Mechanical"; + SklMechanical.UseVisualStyleBackColor = true; // // SklCooking // - this.SklCooking.AutoSize = true; - this.SklCooking.Location = new System.Drawing.Point(244, 25); - this.SklCooking.Margin = new System.Windows.Forms.Padding(0); - this.SklCooking.Name = "SklCooking"; - this.SklCooking.Size = new System.Drawing.Size(65, 17); - this.SklCooking.TabIndex = 47; - this.SklCooking.Text = "Cooking"; - this.SklCooking.UseVisualStyleBackColor = true; + SklCooking.AutoSize = true; + SklCooking.Location = new Point(244, 25); + SklCooking.Margin = new Padding(0); + SklCooking.Name = "SklCooking"; + SklCooking.Size = new Size(70, 17); + SklCooking.TabIndex = 47; + SklCooking.Text = "Cooking"; + SklCooking.UseVisualStyleBackColor = true; // // label10 // - this.label10.AutoSize = true; - this.label10.Location = new System.Drawing.Point(241, 10); - this.label10.Name = "label10"; - this.label10.Size = new System.Drawing.Size(57, 13); - this.label10.TabIndex = 62; - this.label10.Text = "Skill Flags:"; + label10.AutoSize = true; + label10.Location = new Point(241, 10); + label10.Name = "label10"; + label10.Size = new Size(61, 13); + label10.TabIndex = 62; + label10.Text = "Skill Flags:"; // // MotiveRoom // - this.MotiveRoom.Location = new System.Drawing.Point(178, 62); - this.MotiveRoom.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveRoom.Name = "MotiveRoom"; - this.MotiveRoom.Size = new System.Drawing.Size(51, 20); - this.MotiveRoom.TabIndex = 61; + MotiveRoom.Location = new Point(178, 62); + MotiveRoom.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveRoom.Name = "MotiveRoom"; + MotiveRoom.Size = new Size(51, 22); + MotiveRoom.TabIndex = 61; // // label11 // - this.label11.AutoSize = true; - this.label11.Location = new System.Drawing.Point(127, 64); - this.label11.Name = "label11"; - this.label11.Size = new System.Drawing.Size(38, 13); - this.label11.TabIndex = 60; - this.label11.Text = "Room:"; + label11.AutoSize = true; + label11.Location = new Point(127, 64); + label11.Name = "label11"; + label11.Size = new Size(40, 13); + label11.TabIndex = 60; + label11.Text = "Room:"; // // MotiveFun // - this.MotiveFun.Location = new System.Drawing.Point(178, 40); - this.MotiveFun.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveFun.Name = "MotiveFun"; - this.MotiveFun.Size = new System.Drawing.Size(51, 20); - this.MotiveFun.TabIndex = 59; + MotiveFun.Location = new Point(178, 40); + MotiveFun.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveFun.Name = "MotiveFun"; + MotiveFun.Size = new Size(51, 22); + MotiveFun.TabIndex = 59; // // label12 // - this.label12.AutoSize = true; - this.label12.Location = new System.Drawing.Point(127, 42); - this.label12.Name = "label12"; - this.label12.Size = new System.Drawing.Size(28, 13); - this.label12.TabIndex = 58; - this.label12.Text = "Fun:"; + label12.AutoSize = true; + label12.Location = new Point(127, 42); + label12.Name = "label12"; + label12.Size = new Size(30, 13); + label12.TabIndex = 58; + label12.Text = "Fun:"; // // MotiveEnergy // - this.MotiveEnergy.Location = new System.Drawing.Point(178, 18); - this.MotiveEnergy.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveEnergy.Name = "MotiveEnergy"; - this.MotiveEnergy.Size = new System.Drawing.Size(51, 20); - this.MotiveEnergy.TabIndex = 57; + MotiveEnergy.Location = new Point(178, 18); + MotiveEnergy.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveEnergy.Name = "MotiveEnergy"; + MotiveEnergy.Size = new Size(51, 22); + MotiveEnergy.TabIndex = 57; // // label13 // - this.label13.AutoSize = true; - this.label13.Location = new System.Drawing.Point(127, 20); - this.label13.Name = "label13"; - this.label13.Size = new System.Drawing.Size(43, 13); - this.label13.TabIndex = 56; - this.label13.Text = "Energy:"; + label13.AutoSize = true; + label13.Location = new Point(127, 20); + label13.Name = "label13"; + label13.Size = new Size(45, 13); + label13.TabIndex = 56; + label13.Text = "Energy:"; // // MotiveBladder // - this.MotiveBladder.Location = new System.Drawing.Point(63, 84); - this.MotiveBladder.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveBladder.Name = "MotiveBladder"; - this.MotiveBladder.Size = new System.Drawing.Size(51, 20); - this.MotiveBladder.TabIndex = 55; + MotiveBladder.Location = new Point(63, 84); + MotiveBladder.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveBladder.Name = "MotiveBladder"; + MotiveBladder.Size = new Size(51, 22); + MotiveBladder.TabIndex = 55; // // label8 // - this.label8.AutoSize = true; - this.label8.Location = new System.Drawing.Point(7, 86); - this.label8.Name = "label8"; - this.label8.Size = new System.Drawing.Size(46, 13); - this.label8.TabIndex = 54; - this.label8.Text = "Bladder:"; + label8.AutoSize = true; + label8.Location = new Point(7, 86); + label8.Name = "label8"; + label8.Size = new Size(50, 13); + label8.TabIndex = 54; + label8.Text = "Bladder:"; // // MotiveHygiene // - this.MotiveHygiene.Location = new System.Drawing.Point(63, 62); - this.MotiveHygiene.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveHygiene.Name = "MotiveHygiene"; - this.MotiveHygiene.Size = new System.Drawing.Size(51, 20); - this.MotiveHygiene.TabIndex = 53; + MotiveHygiene.Location = new Point(63, 62); + MotiveHygiene.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveHygiene.Name = "MotiveHygiene"; + MotiveHygiene.Size = new Size(51, 22); + MotiveHygiene.TabIndex = 53; // // label9 // - this.label9.AutoSize = true; - this.label9.Location = new System.Drawing.Point(7, 64); - this.label9.Name = "label9"; - this.label9.Size = new System.Drawing.Size(49, 13); - this.label9.TabIndex = 52; - this.label9.Text = "Hygiene:"; + label9.AutoSize = true; + label9.Location = new Point(7, 64); + label9.Name = "label9"; + label9.Size = new Size(52, 13); + label9.TabIndex = 52; + label9.Text = "Hygiene:"; // // MotiveComfort // - this.MotiveComfort.Location = new System.Drawing.Point(63, 40); - this.MotiveComfort.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveComfort.Name = "MotiveComfort"; - this.MotiveComfort.Size = new System.Drawing.Size(51, 20); - this.MotiveComfort.TabIndex = 51; + MotiveComfort.Location = new Point(63, 40); + MotiveComfort.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveComfort.Name = "MotiveComfort"; + MotiveComfort.Size = new Size(51, 22); + MotiveComfort.TabIndex = 51; // // label2 // - this.label2.AutoSize = true; - this.label2.Location = new System.Drawing.Point(7, 42); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(46, 13); - this.label2.TabIndex = 50; - this.label2.Text = "Comfort:"; + label2.AutoSize = true; + label2.Location = new Point(7, 42); + label2.Name = "label2"; + label2.Size = new Size(52, 13); + label2.TabIndex = 50; + label2.Text = "Comfort:"; // // MotiveHunger // - this.MotiveHunger.Location = new System.Drawing.Point(63, 18); - this.MotiveHunger.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.MotiveHunger.Name = "MotiveHunger"; - this.MotiveHunger.Size = new System.Drawing.Size(51, 20); - this.MotiveHunger.TabIndex = 49; + MotiveHunger.Location = new Point(63, 18); + MotiveHunger.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + MotiveHunger.Name = "MotiveHunger"; + MotiveHunger.Size = new Size(51, 22); + MotiveHunger.TabIndex = 49; // // label5 // - this.label5.AutoSize = true; - this.label5.Location = new System.Drawing.Point(7, 20); - this.label5.Name = "label5"; - this.label5.Size = new System.Drawing.Size(45, 13); - this.label5.TabIndex = 48; - this.label5.Text = "Hunger:"; + label5.AutoSize = true; + label5.Location = new Point(7, 20); + label5.Name = "label5"; + label5.Size = new Size(49, 13); + label5.TabIndex = 48; + label5.Text = "Hunger:"; // // GlobalSim // - this.GlobalSim.Location = new System.Drawing.Point(511, 14); - this.GlobalSim.Name = "GlobalSim"; - this.GlobalSim.Size = new System.Drawing.Size(86, 36); - this.GlobalSim.TabIndex = 64; - this.GlobalSim.Text = "Global Sim Object?"; - this.GlobalSim.UseVisualStyleBackColor = true; + GlobalSim.Location = new Point(511, 14); + GlobalSim.Name = "GlobalSim"; + GlobalSim.Size = new Size(86, 36); + GlobalSim.TabIndex = 64; + GlobalSim.Text = "Global Sim Object?"; + GlobalSim.UseVisualStyleBackColor = true; // // VersionLabel // - this.VersionLabel.AutoSize = true; - this.VersionLabel.Location = new System.Drawing.Point(694, 6); - this.VersionLabel.Name = "VersionLabel"; - this.VersionLabel.Size = new System.Drawing.Size(42, 13); - this.VersionLabel.TabIndex = 63; - this.VersionLabel.Text = "Version"; + VersionLabel.AutoSize = true; + VersionLabel.Location = new Point(694, 6); + VersionLabel.Name = "VersionLabel"; + VersionLabel.Size = new Size(45, 13); + VersionLabel.TabIndex = 63; + VersionLabel.Text = "Version"; // // VersionEntry // - this.VersionEntry.Location = new System.Drawing.Point(697, 22); - this.VersionEntry.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.VersionEntry.Name = "VersionEntry"; - this.VersionEntry.Size = new System.Drawing.Size(59, 20); - this.VersionEntry.TabIndex = 62; + VersionEntry.Location = new Point(697, 22); + VersionEntry.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + VersionEntry.Name = "VersionEntry"; + VersionEntry.Size = new Size(59, 22); + VersionEntry.TabIndex = 62; // // TypeCombo // - this.TypeCombo.FormattingEnabled = true; - this.TypeCombo.Location = new System.Drawing.Point(614, 21); - this.TypeCombo.Name = "TypeCombo"; - this.TypeCombo.Size = new System.Drawing.Size(77, 21); - this.TypeCombo.TabIndex = 61; + TypeCombo.FormattingEnabled = true; + TypeCombo.Location = new Point(614, 21); + TypeCombo.Name = "TypeCombo"; + TypeCombo.Size = new Size(77, 21); + TypeCombo.TabIndex = 61; // // TypeLabel // - this.TypeLabel.AutoSize = true; - this.TypeLabel.Location = new System.Drawing.Point(611, 6); - this.TypeLabel.Name = "TypeLabel"; - this.TypeLabel.Size = new System.Drawing.Size(31, 13); - this.TypeLabel.TabIndex = 60; - this.TypeLabel.Text = "Type"; + TypeLabel.AutoSize = true; + TypeLabel.Location = new Point(611, 6); + TypeLabel.Name = "TypeLabel"; + TypeLabel.Size = new Size(29, 13); + TypeLabel.TabIndex = 60; + TypeLabel.Text = "Type"; // // CatalogBox // - this.CatalogBox.Controls.Add(this.CatCommunity); - this.CatalogBox.Controls.Add(this.CatResidence); - this.CatalogBox.Controls.Add(this.CatEntertainment); - this.CatalogBox.Controls.Add(this.CatGames); - this.CatalogBox.Controls.Add(this.CatWelcome); - this.CatalogBox.Controls.Add(this.CatSkills); - this.CatalogBox.Controls.Add(this.CatShopping); - this.CatalogBox.Controls.Add(this.CatServices); - this.CatalogBox.Controls.Add(this.CatRomance); - this.CatalogBox.Controls.Add(this.CatOffbeat); - this.CatalogBox.Controls.Add(this.CatMoney); - this.CatalogBox.Controls.Add(this.LotCatLabel); - this.CatalogBox.Controls.Add(this.CTSSButton); - this.CatalogBox.Controls.Add(this.CTSSIDLabel); - this.CatalogBox.Controls.Add(this.CatalogNameLabel); - this.CatalogBox.Controls.Add(this.label7); - this.CatalogBox.Controls.Add(this.comboBox3); - this.CatalogBox.Controls.Add(this.label6); - this.CatalogBox.Controls.Add(this.BuyCategory); - this.CatalogBox.Controls.Add(this.SalePrice); - this.CatalogBox.Controls.Add(this.PriceLabel); - this.CatalogBox.Location = new System.Drawing.Point(392, 48); - this.CatalogBox.Name = "CatalogBox"; - this.CatalogBox.Size = new System.Drawing.Size(364, 166); - this.CatalogBox.TabIndex = 59; - this.CatalogBox.TabStop = false; - this.CatalogBox.Text = "Catalog"; + CatalogBox.Controls.Add(CatCommunity); + CatalogBox.Controls.Add(CatResidence); + CatalogBox.Controls.Add(CatEntertainment); + CatalogBox.Controls.Add(CatGames); + CatalogBox.Controls.Add(CatWelcome); + CatalogBox.Controls.Add(CatSkills); + CatalogBox.Controls.Add(CatShopping); + CatalogBox.Controls.Add(CatServices); + CatalogBox.Controls.Add(CatRomance); + CatalogBox.Controls.Add(CatOffbeat); + CatalogBox.Controls.Add(CatMoney); + CatalogBox.Controls.Add(LotCatLabel); + CatalogBox.Controls.Add(CTSSButton); + CatalogBox.Controls.Add(CTSSIDLabel); + CatalogBox.Controls.Add(CatalogNameLabel); + CatalogBox.Controls.Add(label7); + CatalogBox.Controls.Add(comboBox3); + CatalogBox.Controls.Add(label6); + CatalogBox.Controls.Add(BuyCategory); + CatalogBox.Controls.Add(SalePrice); + CatalogBox.Controls.Add(PriceLabel); + CatalogBox.Location = new Point(392, 48); + CatalogBox.Name = "CatalogBox"; + CatalogBox.Size = new Size(364, 166); + CatalogBox.TabIndex = 59; + CatalogBox.TabStop = false; + CatalogBox.Text = "Catalog"; // // CatCommunity // - this.CatCommunity.AutoSize = true; - this.CatCommunity.Location = new System.Drawing.Point(94, 107); - this.CatCommunity.Name = "CatCommunity"; - this.CatCommunity.Size = new System.Drawing.Size(134, 17); - this.CatCommunity.TabIndex = 47; - this.CatCommunity.Text = "Community (Town Hall)"; - this.CatCommunity.UseVisualStyleBackColor = true; + CatCommunity.AutoSize = true; + CatCommunity.Location = new Point(94, 107); + CatCommunity.Name = "CatCommunity"; + CatCommunity.Size = new Size(143, 17); + CatCommunity.TabIndex = 47; + CatCommunity.Text = "Community (Town Hall)"; + CatCommunity.UseVisualStyleBackColor = true; // // CatResidence // - this.CatResidence.AutoSize = true; - this.CatResidence.Location = new System.Drawing.Point(289, 143); - this.CatResidence.Margin = new System.Windows.Forms.Padding(0); - this.CatResidence.Name = "CatResidence"; - this.CatResidence.Size = new System.Drawing.Size(77, 17); - this.CatResidence.TabIndex = 46; - this.CatResidence.Text = "Residence"; - this.CatResidence.UseVisualStyleBackColor = true; + CatResidence.AutoSize = true; + CatResidence.Location = new Point(289, 143); + CatResidence.Margin = new Padding(0); + CatResidence.Name = "CatResidence"; + CatResidence.Size = new Size(78, 17); + CatResidence.TabIndex = 46; + CatResidence.Text = "Residence"; + CatResidence.UseVisualStyleBackColor = true; // // CatEntertainment // - this.CatEntertainment.AutoSize = true; - this.CatEntertainment.Location = new System.Drawing.Point(196, 143); - this.CatEntertainment.Margin = new System.Windows.Forms.Padding(0); - this.CatEntertainment.Name = "CatEntertainment"; - this.CatEntertainment.Size = new System.Drawing.Size(91, 17); - this.CatEntertainment.TabIndex = 45; - this.CatEntertainment.Text = "Entertainment"; - this.CatEntertainment.UseVisualStyleBackColor = true; + CatEntertainment.AutoSize = true; + CatEntertainment.Location = new Point(196, 143); + CatEntertainment.Margin = new Padding(0); + CatEntertainment.Name = "CatEntertainment"; + CatEntertainment.Size = new Size(99, 17); + CatEntertainment.TabIndex = 45; + CatEntertainment.Text = "Entertainment"; + CatEntertainment.UseVisualStyleBackColor = true; // // CatGames // - this.CatGames.AutoSize = true; - this.CatGames.Location = new System.Drawing.Point(135, 143); - this.CatGames.Margin = new System.Windows.Forms.Padding(0); - this.CatGames.Name = "CatGames"; - this.CatGames.Size = new System.Drawing.Size(59, 17); - this.CatGames.TabIndex = 44; - this.CatGames.Text = "Games"; - this.CatGames.UseVisualStyleBackColor = true; + CatGames.AutoSize = true; + CatGames.Location = new Point(135, 143); + CatGames.Margin = new Padding(0); + CatGames.Name = "CatGames"; + CatGames.Size = new Size(60, 17); + CatGames.TabIndex = 44; + CatGames.Text = "Games"; + CatGames.UseVisualStyleBackColor = true; // // CatWelcome // - this.CatWelcome.AutoSize = true; - this.CatWelcome.Location = new System.Drawing.Point(62, 143); - this.CatWelcome.Margin = new System.Windows.Forms.Padding(0); - this.CatWelcome.Name = "CatWelcome"; - this.CatWelcome.Size = new System.Drawing.Size(71, 17); - this.CatWelcome.TabIndex = 43; - this.CatWelcome.Text = "Welcome"; - this.CatWelcome.UseVisualStyleBackColor = true; + CatWelcome.AutoSize = true; + CatWelcome.Location = new Point(62, 143); + CatWelcome.Margin = new Padding(0); + CatWelcome.Name = "CatWelcome"; + CatWelcome.Size = new Size(73, 17); + CatWelcome.TabIndex = 43; + CatWelcome.Text = "Welcome"; + CatWelcome.UseVisualStyleBackColor = true; // // CatSkills // - this.CatSkills.AutoSize = true; - this.CatSkills.Location = new System.Drawing.Point(10, 143); - this.CatSkills.Margin = new System.Windows.Forms.Padding(0); - this.CatSkills.Name = "CatSkills"; - this.CatSkills.Size = new System.Drawing.Size(50, 17); - this.CatSkills.TabIndex = 42; - this.CatSkills.Text = "Skills"; - this.CatSkills.UseVisualStyleBackColor = true; + CatSkills.AutoSize = true; + CatSkills.Location = new Point(10, 143); + CatSkills.Margin = new Padding(0); + CatSkills.Name = "CatSkills"; + CatSkills.Size = new Size(52, 17); + CatSkills.TabIndex = 42; + CatSkills.Text = "Skills"; + CatSkills.UseVisualStyleBackColor = true; // // CatShopping // - this.CatShopping.AutoSize = true; - this.CatShopping.Location = new System.Drawing.Point(289, 126); - this.CatShopping.Margin = new System.Windows.Forms.Padding(0); - this.CatShopping.Name = "CatShopping"; - this.CatShopping.Size = new System.Drawing.Size(71, 17); - this.CatShopping.TabIndex = 41; - this.CatShopping.Text = "Shopping"; - this.CatShopping.UseVisualStyleBackColor = true; + CatShopping.AutoSize = true; + CatShopping.Location = new Point(289, 126); + CatShopping.Margin = new Padding(0); + CatShopping.Name = "CatShopping"; + CatShopping.Size = new Size(77, 17); + CatShopping.TabIndex = 41; + CatShopping.Text = "Shopping"; + CatShopping.UseVisualStyleBackColor = true; // // CatServices // - this.CatServices.AutoSize = true; - this.CatServices.Location = new System.Drawing.Point(219, 126); - this.CatServices.Margin = new System.Windows.Forms.Padding(0); - this.CatServices.Name = "CatServices"; - this.CatServices.Size = new System.Drawing.Size(67, 17); - this.CatServices.TabIndex = 40; - this.CatServices.Text = "Services"; - this.CatServices.UseVisualStyleBackColor = true; + CatServices.AutoSize = true; + CatServices.Location = new Point(219, 126); + CatServices.Margin = new Padding(0); + CatServices.Name = "CatServices"; + CatServices.Size = new Size(66, 17); + CatServices.TabIndex = 40; + CatServices.Text = "Services"; + CatServices.UseVisualStyleBackColor = true; // // CatRomance // - this.CatRomance.AutoSize = true; - this.CatRomance.Location = new System.Drawing.Point(143, 126); - this.CatRomance.Margin = new System.Windows.Forms.Padding(0); - this.CatRomance.Name = "CatRomance"; - this.CatRomance.Size = new System.Drawing.Size(72, 17); - this.CatRomance.TabIndex = 39; - this.CatRomance.Text = "Romance"; - this.CatRomance.UseVisualStyleBackColor = true; + CatRomance.AutoSize = true; + CatRomance.Location = new Point(143, 126); + CatRomance.Margin = new Padding(0); + CatRomance.Name = "CatRomance"; + CatRomance.Size = new Size(73, 17); + CatRomance.TabIndex = 39; + CatRomance.Text = "Romance"; + CatRomance.UseVisualStyleBackColor = true; // // CatOffbeat // - this.CatOffbeat.AutoSize = true; - this.CatOffbeat.Location = new System.Drawing.Point(76, 126); - this.CatOffbeat.Margin = new System.Windows.Forms.Padding(0); - this.CatOffbeat.Name = "CatOffbeat"; - this.CatOffbeat.Size = new System.Drawing.Size(61, 17); - this.CatOffbeat.TabIndex = 38; - this.CatOffbeat.Text = "Offbeat"; - this.CatOffbeat.UseVisualStyleBackColor = true; + CatOffbeat.AutoSize = true; + CatOffbeat.Location = new Point(76, 126); + CatOffbeat.Margin = new Padding(0); + CatOffbeat.Name = "CatOffbeat"; + CatOffbeat.Size = new Size(66, 17); + CatOffbeat.TabIndex = 38; + CatOffbeat.Text = "Offbeat"; + CatOffbeat.UseVisualStyleBackColor = true; // // CatMoney // - this.CatMoney.AutoSize = true; - this.CatMoney.Location = new System.Drawing.Point(10, 126); - this.CatMoney.Margin = new System.Windows.Forms.Padding(0); - this.CatMoney.Name = "CatMoney"; - this.CatMoney.Size = new System.Drawing.Size(58, 17); - this.CatMoney.TabIndex = 37; - this.CatMoney.Text = "Money"; - this.CatMoney.UseVisualStyleBackColor = true; + CatMoney.AutoSize = true; + CatMoney.Location = new Point(10, 126); + CatMoney.Margin = new Padding(0); + CatMoney.Name = "CatMoney"; + CatMoney.Size = new Size(61, 17); + CatMoney.TabIndex = 37; + CatMoney.Text = "Money"; + CatMoney.UseVisualStyleBackColor = true; // // LotCatLabel // - this.LotCatLabel.AutoSize = true; - this.LotCatLabel.Location = new System.Drawing.Point(10, 107); - this.LotCatLabel.Name = "LotCatLabel"; - this.LotCatLabel.Size = new System.Drawing.Size(78, 13); - this.LotCatLabel.TabIndex = 36; - this.LotCatLabel.Text = "Lot Categories:"; + LotCatLabel.AutoSize = true; + LotCatLabel.Location = new Point(10, 107); + LotCatLabel.Name = "LotCatLabel"; + LotCatLabel.Size = new Size(84, 13); + LotCatLabel.TabIndex = 36; + LotCatLabel.Text = "Lot Categories:"; // // CTSSButton // - this.CTSSButton.Location = new System.Drawing.Point(274, 19); - this.CTSSButton.Name = "CTSSButton"; - this.CTSSButton.Size = new System.Drawing.Size(84, 31); - this.CTSSButton.TabIndex = 35; - this.CTSSButton.Text = "Modify CTSS"; - this.CTSSButton.UseVisualStyleBackColor = true; - this.CTSSButton.Click += new System.EventHandler(this.CTSSButton_Click); + CTSSButton.Location = new Point(274, 19); + CTSSButton.Name = "CTSSButton"; + CTSSButton.Size = new Size(84, 31); + CTSSButton.TabIndex = 35; + CTSSButton.Text = "Modify CTSS"; + CTSSButton.UseVisualStyleBackColor = true; + CTSSButton.Click += CTSSButton_Click; // // CTSSIDLabel // - this.CTSSIDLabel.AutoSize = true; - this.CTSSIDLabel.Location = new System.Drawing.Point(10, 40); - this.CTSSIDLabel.Name = "CTSSIDLabel"; - this.CTSSIDLabel.Size = new System.Drawing.Size(63, 13); - this.CTSSIDLabel.TabIndex = 34; - this.CTSSIDLabel.Text = "(CTSS #24)"; + CTSSIDLabel.AutoSize = true; + CTSSIDLabel.Location = new Point(10, 40); + CTSSIDLabel.Name = "CTSSIDLabel"; + CTSSIDLabel.Size = new Size(59, 13); + CTSSIDLabel.TabIndex = 34; + CTSSIDLabel.Text = "(CTSS #24)"; // // CatalogNameLabel // - this.CatalogNameLabel.AutoEllipsis = true; - this.CatalogNameLabel.Font = new System.Drawing.Font("Microsoft Sans Serif", 11.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.CatalogNameLabel.Location = new System.Drawing.Point(10, 19); - this.CatalogNameLabel.Name = "CatalogNameLabel"; - this.CatalogNameLabel.Size = new System.Drawing.Size(258, 21); - this.CatalogNameLabel.TabIndex = 33; - this.CatalogNameLabel.Text = "Object De La Extravagant Name"; + CatalogNameLabel.AutoEllipsis = true; + CatalogNameLabel.Font = new Font("Microsoft Sans Serif", 11.25F, FontStyle.Regular, GraphicsUnit.Point, 0); + CatalogNameLabel.Location = new Point(10, 19); + CatalogNameLabel.Name = "CatalogNameLabel"; + CatalogNameLabel.Size = new Size(258, 21); + CatalogNameLabel.TabIndex = 33; + CatalogNameLabel.Text = "Object De La Extravagant Name"; // // label7 // - this.label7.AutoSize = true; - this.label7.Location = new System.Drawing.Point(145, 62); - this.label7.Name = "label7"; - this.label7.Size = new System.Drawing.Size(105, 13); - this.label7.TabIndex = 32; - this.label7.Text = "Build Mode Category"; + label7.AutoSize = true; + label7.Location = new Point(145, 62); + label7.Name = "label7"; + label7.Size = new Size(116, 13); + label7.TabIndex = 32; + label7.Text = "Build Mode Category"; // // comboBox3 // - this.comboBox3.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.comboBox3.Enabled = false; - this.comboBox3.FormattingEnabled = true; - this.comboBox3.Location = new System.Drawing.Point(145, 77); - this.comboBox3.Name = "comboBox3"; - this.comboBox3.Size = new System.Drawing.Size(121, 21); - this.comboBox3.TabIndex = 31; + comboBox3.DropDownStyle = ComboBoxStyle.DropDownList; + comboBox3.Enabled = false; + comboBox3.FormattingEnabled = true; + comboBox3.Location = new Point(145, 77); + comboBox3.Name = "comboBox3"; + comboBox3.Size = new Size(121, 21); + comboBox3.TabIndex = 31; // // label6 // - this.label6.AutoSize = true; - this.label6.Location = new System.Drawing.Point(10, 62); - this.label6.Name = "label6"; - this.label6.Size = new System.Drawing.Size(100, 13); - this.label6.TabIndex = 30; - this.label6.Text = "Buy Mode Category"; + label6.AutoSize = true; + label6.Location = new Point(10, 62); + label6.Name = "label6"; + label6.Size = new Size(108, 13); + label6.TabIndex = 30; + label6.Text = "Buy Mode Category"; // // BuyCategory // - this.BuyCategory.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.BuyCategory.Enabled = false; - this.BuyCategory.FormattingEnabled = true; - this.BuyCategory.Location = new System.Drawing.Point(10, 77); - this.BuyCategory.Name = "BuyCategory"; - this.BuyCategory.Size = new System.Drawing.Size(121, 21); - this.BuyCategory.TabIndex = 29; + BuyCategory.DropDownStyle = ComboBoxStyle.DropDownList; + BuyCategory.Enabled = false; + BuyCategory.FormattingEnabled = true; + BuyCategory.Location = new Point(10, 77); + BuyCategory.Name = "BuyCategory"; + BuyCategory.Size = new Size(121, 21); + BuyCategory.TabIndex = 29; // // SalePrice // - this.SalePrice.Location = new System.Drawing.Point(303, 78); - this.SalePrice.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.SalePrice.Name = "SalePrice"; - this.SalePrice.Size = new System.Drawing.Size(55, 20); - this.SalePrice.TabIndex = 28; + SalePrice.Location = new Point(303, 78); + SalePrice.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + SalePrice.Name = "SalePrice"; + SalePrice.Size = new Size(55, 22); + SalePrice.TabIndex = 28; // // PriceLabel // - this.PriceLabel.AutoSize = true; - this.PriceLabel.Location = new System.Drawing.Point(303, 62); - this.PriceLabel.Name = "PriceLabel"; - this.PriceLabel.Size = new System.Drawing.Size(55, 13); - this.PriceLabel.TabIndex = 27; - this.PriceLabel.Text = "Sale Price"; + PriceLabel.AutoSize = true; + PriceLabel.Location = new Point(303, 62); + PriceLabel.Name = "PriceLabel"; + PriceLabel.Size = new Size(55, 13); + PriceLabel.TabIndex = 27; + PriceLabel.Text = "Sale Price"; // // ObjectView // - this.ObjectView.Location = new System.Drawing.Point(6, 6); - this.ObjectView.Name = "ObjectView"; - this.ObjectView.Size = new System.Drawing.Size(160, 447); - this.ObjectView.TabIndex = 58; + ObjectView.Location = new Point(6, 6); + ObjectView.Name = "ObjectView"; + ObjectView.Size = new Size(160, 447); + ObjectView.TabIndex = 58; // // PhysicalBox // - this.PhysicalBox.Controls.Add(this.FrontDirLabel); - this.PhysicalBox.Controls.Add(this.FrontDir); - this.PhysicalBox.Controls.Add(this.FootprintSouth); - this.PhysicalBox.Controls.Add(this.FootprintNorth); - this.PhysicalBox.Controls.Add(this.FootprintWest); - this.PhysicalBox.Controls.Add(this.FootprintLabel); - this.PhysicalBox.Controls.Add(this.TileWidth); - this.PhysicalBox.Controls.Add(this.TileWidthLabel); - this.PhysicalBox.Controls.Add(this.FootprintEast); - this.PhysicalBox.Location = new System.Drawing.Point(175, 335); - this.PhysicalBox.Name = "PhysicalBox"; - this.PhysicalBox.Size = new System.Drawing.Size(206, 118); - this.PhysicalBox.TabIndex = 57; - this.PhysicalBox.TabStop = false; - this.PhysicalBox.Text = "Physical"; + PhysicalBox.Controls.Add(FrontDirLabel); + PhysicalBox.Controls.Add(FrontDir); + PhysicalBox.Controls.Add(FootprintSouth); + PhysicalBox.Controls.Add(FootprintNorth); + PhysicalBox.Controls.Add(FootprintWest); + PhysicalBox.Controls.Add(FootprintLabel); + PhysicalBox.Controls.Add(TileWidth); + PhysicalBox.Controls.Add(TileWidthLabel); + PhysicalBox.Controls.Add(FootprintEast); + PhysicalBox.Location = new Point(175, 335); + PhysicalBox.Name = "PhysicalBox"; + PhysicalBox.Size = new Size(206, 118); + PhysicalBox.TabIndex = 57; + PhysicalBox.TabStop = false; + PhysicalBox.Text = "Physical"; // // FrontDirLabel // - this.FrontDirLabel.AutoSize = true; - this.FrontDirLabel.Location = new System.Drawing.Point(142, 66); - this.FrontDirLabel.Name = "FrontDirLabel"; - this.FrontDirLabel.Size = new System.Drawing.Size(50, 13); - this.FrontDirLabel.TabIndex = 30; - this.FrontDirLabel.Text = "Front Dir:"; + FrontDirLabel.AutoSize = true; + FrontDirLabel.Location = new Point(142, 66); + FrontDirLabel.Name = "FrontDirLabel"; + FrontDirLabel.Size = new Size(56, 13); + FrontDirLabel.TabIndex = 30; + FrontDirLabel.Text = "Front Dir:"; // // FrontDir // - this.FrontDir.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.FrontDir.FormattingEnabled = true; - this.FrontDir.Location = new System.Drawing.Point(145, 82); - this.FrontDir.Name = "FrontDir"; - this.FrontDir.Size = new System.Drawing.Size(55, 21); - this.FrontDir.TabIndex = 29; + FrontDir.DropDownStyle = ComboBoxStyle.DropDownList; + FrontDir.FormattingEnabled = true; + FrontDir.Location = new Point(145, 82); + FrontDir.Name = "FrontDir"; + FrontDir.Size = new Size(55, 21); + FrontDir.TabIndex = 29; // // FootprintSouth // - this.FootprintSouth.Location = new System.Drawing.Point(42, 90); - this.FootprintSouth.Maximum = new decimal(new int[] { - 15, - 0, - 0, - 0}); - this.FootprintSouth.Name = "FootprintSouth"; - this.FootprintSouth.Size = new System.Drawing.Size(55, 20); - this.FootprintSouth.TabIndex = 28; + FootprintSouth.Location = new Point(42, 90); + FootprintSouth.Maximum = new decimal(new int[] { 15, 0, 0, 0 }); + FootprintSouth.Name = "FootprintSouth"; + FootprintSouth.Size = new Size(55, 22); + FootprintSouth.TabIndex = 28; // // FootprintNorth // - this.FootprintNorth.Location = new System.Drawing.Point(42, 36); - this.FootprintNorth.Maximum = new decimal(new int[] { - 15, - 0, - 0, - 0}); - this.FootprintNorth.Name = "FootprintNorth"; - this.FootprintNorth.Size = new System.Drawing.Size(55, 20); - this.FootprintNorth.TabIndex = 27; + FootprintNorth.Location = new Point(42, 36); + FootprintNorth.Maximum = new decimal(new int[] { 15, 0, 0, 0 }); + FootprintNorth.Name = "FootprintNorth"; + FootprintNorth.Size = new Size(55, 22); + FootprintNorth.TabIndex = 27; // // FootprintWest // - this.FootprintWest.Location = new System.Drawing.Point(8, 63); - this.FootprintWest.Maximum = new decimal(new int[] { - 15, - 0, - 0, - 0}); - this.FootprintWest.Name = "FootprintWest"; - this.FootprintWest.Size = new System.Drawing.Size(55, 20); - this.FootprintWest.TabIndex = 26; + FootprintWest.Location = new Point(8, 63); + FootprintWest.Maximum = new decimal(new int[] { 15, 0, 0, 0 }); + FootprintWest.Name = "FootprintWest"; + FootprintWest.Size = new Size(55, 22); + FootprintWest.TabIndex = 26; // // FootprintLabel // - this.FootprintLabel.AutoSize = true; - this.FootprintLabel.Location = new System.Drawing.Point(11, 19); - this.FootprintLabel.Name = "FootprintLabel"; - this.FootprintLabel.Size = new System.Drawing.Size(118, 13); - this.FootprintLabel.TabIndex = 25; - this.FootprintLabel.Text = "Collision Footprint Inset:"; + FootprintLabel.AutoSize = true; + FootprintLabel.Location = new Point(11, 19); + FootprintLabel.Name = "FootprintLabel"; + FootprintLabel.Size = new Size(135, 13); + FootprintLabel.TabIndex = 25; + FootprintLabel.Text = "Collision Footprint Inset:"; // // TileWidth // - this.TileWidth.Location = new System.Drawing.Point(145, 38); - this.TileWidth.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.TileWidth.Name = "TileWidth"; - this.TileWidth.Size = new System.Drawing.Size(55, 20); - this.TileWidth.TabIndex = 3; + TileWidth.Location = new Point(145, 38); + TileWidth.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + TileWidth.Name = "TileWidth"; + TileWidth.Size = new Size(55, 22); + TileWidth.TabIndex = 3; // // TileWidthLabel // - this.TileWidthLabel.AutoSize = true; - this.TileWidthLabel.Location = new System.Drawing.Point(142, 22); - this.TileWidthLabel.Name = "TileWidthLabel"; - this.TileWidthLabel.Size = new System.Drawing.Size(58, 13); - this.TileWidthLabel.TabIndex = 2; - this.TileWidthLabel.Text = "Tile Width:"; + TileWidthLabel.AutoSize = true; + TileWidthLabel.Location = new Point(142, 22); + TileWidthLabel.Name = "TileWidthLabel"; + TileWidthLabel.Size = new Size(62, 13); + TileWidthLabel.TabIndex = 2; + TileWidthLabel.Text = "Tile Width:"; // // FootprintEast // - this.FootprintEast.Location = new System.Drawing.Point(76, 63); - this.FootprintEast.Maximum = new decimal(new int[] { - 15, - 0, - 0, - 0}); - this.FootprintEast.Name = "FootprintEast"; - this.FootprintEast.Size = new System.Drawing.Size(55, 20); - this.FootprintEast.TabIndex = 1; + FootprintEast.Location = new Point(76, 63); + FootprintEast.Maximum = new decimal(new int[] { 15, 0, 0, 0 }); + FootprintEast.Name = "FootprintEast"; + FootprintEast.Size = new Size(55, 22); + FootprintEast.TabIndex = 1; // // MultitileBox // - this.MultitileBox.Controls.Add(this.InteractionGroupLabel); - this.MultitileBox.Controls.Add(this.InteractionGroup); - this.MultitileBox.Controls.Add(this.MasterMultitile); - this.MultitileBox.Controls.Add(this.LeadMultitile); - this.MultitileBox.Controls.Add(this.NewMultitile); - this.MultitileBox.Controls.Add(this.MultiGroupCombo); - this.MultitileBox.Controls.Add(this.label3); - this.MultitileBox.Controls.Add(this.MultitileList); - this.MultitileBox.Controls.Add(this.XOffset); - this.MultitileBox.Controls.Add(this.OffsetXLabel); - this.MultitileBox.Controls.Add(this.LevelOffset); - this.MultitileBox.Controls.Add(this.LevelLabel); - this.MultitileBox.Controls.Add(this.YOffset); - this.MultitileBox.Controls.Add(this.OffsetYLabel); - this.MultitileBox.Location = new System.Drawing.Point(175, 48); - this.MultitileBox.Name = "MultitileBox"; - this.MultitileBox.Size = new System.Drawing.Size(206, 281); - this.MultitileBox.TabIndex = 56; - this.MultitileBox.TabStop = false; - this.MultitileBox.Text = "Multitile"; + MultitileBox.Controls.Add(InteractionGroupLabel); + MultitileBox.Controls.Add(InteractionGroup); + MultitileBox.Controls.Add(MasterMultitile); + MultitileBox.Controls.Add(LeadMultitile); + MultitileBox.Controls.Add(NewMultitile); + MultitileBox.Controls.Add(MultiGroupCombo); + MultitileBox.Controls.Add(label3); + MultitileBox.Controls.Add(MultitileList); + MultitileBox.Controls.Add(XOffset); + MultitileBox.Controls.Add(OffsetXLabel); + MultitileBox.Controls.Add(LevelOffset); + MultitileBox.Controls.Add(LevelLabel); + MultitileBox.Controls.Add(YOffset); + MultitileBox.Controls.Add(OffsetYLabel); + MultitileBox.Location = new Point(175, 48); + MultitileBox.Name = "MultitileBox"; + MultitileBox.Size = new Size(206, 281); + MultitileBox.TabIndex = 56; + MultitileBox.TabStop = false; + MultitileBox.Text = "Multitile"; // // InteractionGroupLabel // - this.InteractionGroupLabel.AutoSize = true; - this.InteractionGroupLabel.Location = new System.Drawing.Point(8, 90); - this.InteractionGroupLabel.Name = "InteractionGroupLabel"; - this.InteractionGroupLabel.Size = new System.Drawing.Size(92, 13); - this.InteractionGroupLabel.TabIndex = 33; - this.InteractionGroupLabel.Text = "Interaction Group:"; + InteractionGroupLabel.AutoSize = true; + InteractionGroupLabel.Location = new Point(8, 90); + InteractionGroupLabel.Name = "InteractionGroupLabel"; + InteractionGroupLabel.Size = new Size(102, 13); + InteractionGroupLabel.TabIndex = 33; + InteractionGroupLabel.Text = "Interaction Group:"; // // InteractionGroup // - this.InteractionGroup.Location = new System.Drawing.Point(103, 88); - this.InteractionGroup.Maximum = new decimal(new int[] { - 32767, - 0, - 0, - 0}); - this.InteractionGroup.Minimum = new decimal(new int[] { - 32768, - 0, - 0, - -2147483648}); - this.InteractionGroup.Name = "InteractionGroup"; - this.InteractionGroup.Size = new System.Drawing.Size(95, 20); - this.InteractionGroup.TabIndex = 32; + InteractionGroup.Location = new Point(103, 88); + InteractionGroup.Maximum = new decimal(new int[] { 32767, 0, 0, 0 }); + InteractionGroup.Minimum = new decimal(new int[] { 32768, 0, 0, int.MinValue }); + InteractionGroup.Name = "InteractionGroup"; + InteractionGroup.Size = new Size(95, 22); + InteractionGroup.TabIndex = 32; // // MasterMultitile // - this.MasterMultitile.Location = new System.Drawing.Point(8, 248); - this.MasterMultitile.Name = "MasterMultitile"; - this.MasterMultitile.Size = new System.Drawing.Size(89, 23); - this.MasterMultitile.TabIndex = 31; - this.MasterMultitile.Text = "Make Master"; - this.MasterMultitile.UseVisualStyleBackColor = true; + MasterMultitile.Location = new Point(8, 248); + MasterMultitile.Name = "MasterMultitile"; + MasterMultitile.Size = new Size(89, 23); + MasterMultitile.TabIndex = 31; + MasterMultitile.Text = "Make Master"; + MasterMultitile.UseVisualStyleBackColor = true; // // LeadMultitile // - this.LeadMultitile.Location = new System.Drawing.Point(109, 248); - this.LeadMultitile.Name = "LeadMultitile"; - this.LeadMultitile.Size = new System.Drawing.Size(89, 23); - this.LeadMultitile.TabIndex = 30; - this.LeadMultitile.Text = "Make Lead"; - this.LeadMultitile.UseVisualStyleBackColor = true; - this.LeadMultitile.Click += new System.EventHandler(this.LeadMultitile_Click); + LeadMultitile.Location = new Point(109, 248); + LeadMultitile.Name = "LeadMultitile"; + LeadMultitile.Size = new Size(89, 23); + LeadMultitile.TabIndex = 30; + LeadMultitile.Text = "Make Lead"; + LeadMultitile.UseVisualStyleBackColor = true; + LeadMultitile.Click += LeadMultitile_Click; // // NewMultitile // - this.NewMultitile.Location = new System.Drawing.Point(135, 17); - this.NewMultitile.Name = "NewMultitile"; - this.NewMultitile.Size = new System.Drawing.Size(65, 23); - this.NewMultitile.TabIndex = 29; - this.NewMultitile.Text = "New"; - this.NewMultitile.UseVisualStyleBackColor = true; - this.NewMultitile.Click += new System.EventHandler(this.NewMultitile_Click); + NewMultitile.Location = new Point(135, 17); + NewMultitile.Name = "NewMultitile"; + NewMultitile.Size = new Size(65, 23); + NewMultitile.TabIndex = 29; + NewMultitile.Text = "New"; + NewMultitile.UseVisualStyleBackColor = true; + NewMultitile.Click += NewMultitile_Click; // // MultiGroupCombo // - this.MultiGroupCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.MultiGroupCombo.FormattingEnabled = true; - this.MultiGroupCombo.Location = new System.Drawing.Point(8, 19); - this.MultiGroupCombo.Name = "MultiGroupCombo"; - this.MultiGroupCombo.Size = new System.Drawing.Size(121, 21); - this.MultiGroupCombo.TabIndex = 28; + MultiGroupCombo.DropDownStyle = ComboBoxStyle.DropDownList; + MultiGroupCombo.FormattingEnabled = true; + MultiGroupCombo.Location = new Point(8, 19); + MultiGroupCombo.Name = "MultiGroupCombo"; + MultiGroupCombo.Size = new Size(121, 21); + MultiGroupCombo.TabIndex = 28; // // label3 // - this.label3.AutoSize = true; - this.label3.Location = new System.Drawing.Point(8, 111); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(43, 13); - this.label3.TabIndex = 27; - this.label3.Text = "Objects"; + label3.AutoSize = true; + label3.Location = new Point(8, 111); + label3.Name = "label3"; + label3.Size = new Size(46, 13); + label3.TabIndex = 27; + label3.Text = "Objects"; // // MultitileList // - this.MultitileList.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed; - this.MultitileList.FormattingEnabled = true; - this.MultitileList.IntegralHeight = false; - this.MultitileList.Location = new System.Drawing.Point(8, 126); - this.MultitileList.Name = "MultitileList"; - this.MultitileList.Size = new System.Drawing.Size(190, 118); - this.MultitileList.TabIndex = 15; + MultitileList.DrawMode = DrawMode.OwnerDrawFixed; + MultitileList.FormattingEnabled = true; + MultitileList.IntegralHeight = false; + MultitileList.Location = new Point(8, 126); + MultitileList.Name = "MultitileList"; + MultitileList.Size = new Size(190, 118); + MultitileList.TabIndex = 15; // // XOffset // - this.XOffset.Location = new System.Drawing.Point(8, 62); - this.XOffset.Maximum = new decimal(new int[] { - 127, - 0, - 0, - 0}); - this.XOffset.Name = "XOffset"; - this.XOffset.Size = new System.Drawing.Size(55, 20); - this.XOffset.TabIndex = 26; - this.XOffset.ValueChanged += new System.EventHandler(this.XOffset_ValueChanged); + XOffset.Location = new Point(8, 62); + XOffset.Maximum = new decimal(new int[] { 127, 0, 0, 0 }); + XOffset.Name = "XOffset"; + XOffset.Size = new Size(55, 22); + XOffset.TabIndex = 26; + XOffset.ValueChanged += XOffset_ValueChanged; // // OffsetXLabel // - this.OffsetXLabel.AutoSize = true; - this.OffsetXLabel.Location = new System.Drawing.Point(8, 46); - this.OffsetXLabel.Name = "OffsetXLabel"; - this.OffsetXLabel.Size = new System.Drawing.Size(48, 13); - this.OffsetXLabel.TabIndex = 25; - this.OffsetXLabel.Text = "Offest X:"; + OffsetXLabel.AutoSize = true; + OffsetXLabel.Location = new Point(8, 46); + OffsetXLabel.Name = "OffsetXLabel"; + OffsetXLabel.Size = new Size(51, 13); + OffsetXLabel.TabIndex = 25; + OffsetXLabel.Text = "Offest X:"; // // LevelOffset // - this.LevelOffset.Location = new System.Drawing.Point(143, 62); - this.LevelOffset.Maximum = new decimal(new int[] { - 65535, - 0, - 0, - 0}); - this.LevelOffset.Name = "LevelOffset"; - this.LevelOffset.Size = new System.Drawing.Size(55, 20); - this.LevelOffset.TabIndex = 3; + LevelOffset.Location = new Point(143, 62); + LevelOffset.Maximum = new decimal(new int[] { 65535, 0, 0, 0 }); + LevelOffset.Name = "LevelOffset"; + LevelOffset.Size = new Size(55, 22); + LevelOffset.TabIndex = 3; // // LevelLabel // - this.LevelLabel.AutoSize = true; - this.LevelLabel.Location = new System.Drawing.Point(143, 46); - this.LevelLabel.Name = "LevelLabel"; - this.LevelLabel.Size = new System.Drawing.Size(36, 13); - this.LevelLabel.TabIndex = 2; - this.LevelLabel.Text = "Level:"; + LevelLabel.AutoSize = true; + LevelLabel.Location = new Point(143, 46); + LevelLabel.Name = "LevelLabel"; + LevelLabel.Size = new Size(35, 13); + LevelLabel.TabIndex = 2; + LevelLabel.Text = "Level:"; // // YOffset // - this.YOffset.Location = new System.Drawing.Point(75, 62); - this.YOffset.Maximum = new decimal(new int[] { - 127, - 0, - 0, - 0}); - this.YOffset.Name = "YOffset"; - this.YOffset.Size = new System.Drawing.Size(55, 20); - this.YOffset.TabIndex = 1; - this.YOffset.ValueChanged += new System.EventHandler(this.YOffset_ValueChanged); + YOffset.Location = new Point(75, 62); + YOffset.Maximum = new decimal(new int[] { 127, 0, 0, 0 }); + YOffset.Name = "YOffset"; + YOffset.Size = new Size(55, 22); + YOffset.TabIndex = 1; + YOffset.ValueChanged += YOffset_ValueChanged; // // OffsetYLabel // - this.OffsetYLabel.AutoSize = true; - this.OffsetYLabel.Location = new System.Drawing.Point(75, 46); - this.OffsetYLabel.Name = "OffsetYLabel"; - this.OffsetYLabel.Size = new System.Drawing.Size(48, 13); - this.OffsetYLabel.TabIndex = 0; - this.OffsetYLabel.Text = "Offest Y:"; + OffsetYLabel.AutoSize = true; + OffsetYLabel.Location = new Point(75, 46); + OffsetYLabel.Name = "OffsetYLabel"; + OffsetYLabel.Size = new Size(50, 13); + OffsetYLabel.TabIndex = 0; + OffsetYLabel.Text = "Offest Y:"; // // GUIDLabel // - this.GUIDLabel.AutoSize = true; - this.GUIDLabel.Location = new System.Drawing.Point(402, 6); - this.GUIDLabel.Name = "GUIDLabel"; - this.GUIDLabel.Size = new System.Drawing.Size(34, 13); - this.GUIDLabel.TabIndex = 55; - this.GUIDLabel.Text = "GUID"; + GUIDLabel.AutoSize = true; + GUIDLabel.Location = new Point(402, 6); + GUIDLabel.Name = "GUIDLabel"; + GUIDLabel.Size = new Size(34, 13); + GUIDLabel.TabIndex = 55; + GUIDLabel.Text = "GUID"; // // NameLabel // - this.NameLabel.AutoSize = true; - this.NameLabel.Location = new System.Drawing.Point(172, 6); - this.NameLabel.Name = "NameLabel"; - this.NameLabel.Size = new System.Drawing.Size(35, 13); - this.NameLabel.TabIndex = 53; - this.NameLabel.Text = "Name"; + NameLabel.AutoSize = true; + NameLabel.Location = new Point(172, 6); + NameLabel.Name = "NameLabel"; + NameLabel.Size = new Size(36, 13); + NameLabel.TabIndex = 53; + NameLabel.Text = "Name"; // // NameEntry // - this.NameEntry.Location = new System.Drawing.Point(175, 22); - this.NameEntry.Name = "NameEntry"; - this.NameEntry.Size = new System.Drawing.Size(208, 20); - this.NameEntry.TabIndex = 52; - this.NameEntry.Text = "Accessory Rack - Cheap"; - this.NameEntry.TextChanged += new System.EventHandler(this.NameEntry_TextChanged); + NameEntry.Location = new Point(175, 22); + NameEntry.Name = "NameEntry"; + NameEntry.Size = new Size(208, 22); + NameEntry.TabIndex = 52; + NameEntry.Text = "Accessory Rack - Cheap"; + NameEntry.TextChanged += NameEntry_TextChanged; // // GUIDButton // - this.GUIDButton.Location = new System.Drawing.Point(401, 20); - this.GUIDButton.Name = "GUIDButton"; - this.GUIDButton.Size = new System.Drawing.Size(85, 23); - this.GUIDButton.TabIndex = 78; - this.GUIDButton.Text = "0xDEADBEEF"; - this.GUIDButton.UseVisualStyleBackColor = true; - this.GUIDButton.MouseDown += new System.Windows.Forms.MouseEventHandler(this.GUIDButton_MouseDown); + GUIDButton.Location = new Point(401, 20); + GUIDButton.Name = "GUIDButton"; + GUIDButton.Size = new Size(85, 23); + GUIDButton.TabIndex = 78; + GUIDButton.Text = "0xDEADBEEF"; + GUIDButton.UseVisualStyleBackColor = true; + GUIDButton.MouseDown += GUIDButton_MouseDown; // // OBJDEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.GUIDButton); - this.Controls.Add(this.ThumbnailBox); - this.Controls.Add(this.VisualBox); - this.Controls.Add(this.MotiveBox); - this.Controls.Add(this.GlobalSim); - this.Controls.Add(this.VersionLabel); - this.Controls.Add(this.VersionEntry); - this.Controls.Add(this.TypeCombo); - this.Controls.Add(this.TypeLabel); - this.Controls.Add(this.CatalogBox); - this.Controls.Add(this.ObjectView); - this.Controls.Add(this.PhysicalBox); - this.Controls.Add(this.MultitileBox); - this.Controls.Add(this.GUIDLabel); - this.Controls.Add(this.NameLabel); - this.Controls.Add(this.NameEntry); - this.Name = "OBJDEditor"; - this.Size = new System.Drawing.Size(762, 459); - this.ThumbnailBox.ResumeLayout(false); - ((System.ComponentModel.ISupportInitialize)(this.ThumbnailPic)).EndInit(); - this.VisualBox.ResumeLayout(false); - this.VisualBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.DeprLimit)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.DeprDaily)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.DeprInitial)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.ShadowEntry)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit(); - this.MotiveBox.ResumeLayout(false); - this.MotiveBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveRoom)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveFun)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveEnergy)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveBladder)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveHygiene)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveComfort)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.MotiveHunger)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.VersionEntry)).EndInit(); - this.CatalogBox.ResumeLayout(false); - this.CatalogBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.SalePrice)).EndInit(); - this.PhysicalBox.ResumeLayout(false); - this.PhysicalBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintSouth)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintNorth)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintWest)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.TileWidth)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.FootprintEast)).EndInit(); - this.MultitileBox.ResumeLayout(false); - this.MultitileBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.InteractionGroup)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.XOffset)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.LevelOffset)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.YOffset)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + Controls.Add(GUIDButton); + Controls.Add(ThumbnailBox); + Controls.Add(VisualBox); + Controls.Add(MotiveBox); + Controls.Add(GlobalSim); + Controls.Add(VersionLabel); + Controls.Add(VersionEntry); + Controls.Add(TypeCombo); + Controls.Add(TypeLabel); + Controls.Add(CatalogBox); + Controls.Add(ObjectView); + Controls.Add(PhysicalBox); + Controls.Add(MultitileBox); + Controls.Add(GUIDLabel); + Controls.Add(NameLabel); + Controls.Add(NameEntry); + Name = "OBJDEditor"; + Size = new Size(762, 459); + ThumbnailBox.ResumeLayout(false); + ((System.ComponentModel.ISupportInitialize)ThumbnailPic).EndInit(); + VisualBox.ResumeLayout(false); + VisualBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)DeprLimit).EndInit(); + ((System.ComponentModel.ISupportInitialize)DeprDaily).EndInit(); + ((System.ComponentModel.ISupportInitialize)DeprInitial).EndInit(); + ((System.ComponentModel.ISupportInitialize)ShadowEntry).EndInit(); + ((System.ComponentModel.ISupportInitialize)pictureBox2).EndInit(); + MotiveBox.ResumeLayout(false); + MotiveBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)MotiveRoom).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveFun).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveEnergy).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveBladder).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveHygiene).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveComfort).EndInit(); + ((System.ComponentModel.ISupportInitialize)MotiveHunger).EndInit(); + ((System.ComponentModel.ISupportInitialize)VersionEntry).EndInit(); + CatalogBox.ResumeLayout(false); + CatalogBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)SalePrice).EndInit(); + PhysicalBox.ResumeLayout(false); + PhysicalBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)FootprintSouth).EndInit(); + ((System.ComponentModel.ISupportInitialize)FootprintNorth).EndInit(); + ((System.ComponentModel.ISupportInitialize)FootprintWest).EndInit(); + ((System.ComponentModel.ISupportInitialize)TileWidth).EndInit(); + ((System.ComponentModel.ISupportInitialize)FootprintEast).EndInit(); + MultitileBox.ResumeLayout(false); + MultitileBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)InteractionGroup).EndInit(); + ((System.ComponentModel.ISupportInitialize)XOffset).EndInit(); + ((System.ComponentModel.ISupportInitialize)LevelOffset).EndInit(); + ((System.ComponentModel.ISupportInitialize)YOffset).EndInit(); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.resx b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.resx index 1af7de150..8b2ff64a1 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.resx +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJDEditor.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJDSelectorControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJDSelectorControl.Designer.cs index cd5fc33c3..b9d1d1fb5 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJDSelectorControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJDSelectorControl.Designer.cs @@ -57,8 +57,8 @@ private void InitializeComponent() // // OBJDSelectorControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.SelectCombo); this.Controls.Add(this.SelectButton); this.Name = "OBJDSelectorControl"; diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.Designer.cs index f9a29df0f..08118a181 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.Designer.cs @@ -146,8 +146,8 @@ private void InitializeComponent() // // OBJfEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.DescLabel); this.Controls.Add(this.FilterCheck); this.Controls.Add(this.DescTitle); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.cs b/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.cs index 47da560c1..7660a56e6 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/OBJfEditor.cs @@ -129,7 +129,7 @@ public void RefreshView() (func.ActionFunction == 0)?"":((action == null)?("#"+func.ActionFunction):action.ChunkLabel), (func.ConditionFunction == 0)?"":((check == null)?("#"+func.ConditionFunction):check.ChunkLabel), }); - if (i < 2 && func.ActionFunction == 0) item.BackColor = Color.IndianRed; + if (i < 2 && func.ActionFunction == 0) item.BackColor = System.Drawing.Color.IndianRed; itemToOffset.Add(item, i++); FunctionList.Items.Add(item); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.Designer.cs index d273ad1c9..681af36e5 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.Designer.cs @@ -217,8 +217,8 @@ private void InitializeComponent() // // PIFFEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.PIFFBox); this.Controls.Add(this.PIFFComment); this.Controls.Add(this.FileCommentsLabel); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.cs~RF9522a17.TMP b/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.cs~RF9522a17.TMP deleted file mode 100644 index 34516813c..000000000 --- a/TSOClient/FSO.IDE/ResourceBrowser/PIFFEditor.cs~RF9522a17.TMP +++ /dev/null @@ -1,375 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows.Forms; -using FSO.Content; -using FSO.Files.Formats.IFF; -using FSO.Files.Formats.IFF.Chunks; -using System.Reflection; -using System.IO; -using System.Collections; - -namespace FSO.IDE.ResourceBrowser -{ - public partial class PIFFEditor : UserControl - { - public static HashSet IgnoreProps = new HashSet - { - "RuntimeInfo", - "Function" - }; - public GameObject ActiveObj; - public IffFile ActiveIff; - - public int ActivePIFFIndex = -1; - - public IffFile[] ActivePIFFs = new IffFile[]{ - null, //PIFF - null, //SPF - null //STR - }; - - public IffFile ActivePIFF; - public PIFF ActivePIFFChunk; - - public PIFFListItem ActiveItem; - public PIFFEditor() - { - InitializeComponent(); - } - - public void SetActiveObject(GameObject obj) - { - ActiveObj = obj; - ActiveIff = obj.Resource.MainIff; - var piffs = ActiveIff.RuntimeInfo.Patches; - - ActivePIFF = piffs.FirstOrDefault(); - ActivePIFFs[0] = obj.Resource.Iff.RuntimeInfo.Patches.FirstOrDefault(x => !x.Filename.EndsWith(".str.piff")); - ActivePIFFs[1] = obj.Resource.Sprites.RuntimeInfo.Patches.FirstOrDefault(); - ActivePIFFs[2] = obj.Resource.Iff.RuntimeInfo.Patches.FirstOrDefault(x => !x.Filename.EndsWith(".piff")); - - PIFFButton.Enabled = ActivePIFFs[0] != null; - PIFFButton.Enabled = ActivePIFFs[1] != null; - PIFFButton.Enabled = ActivePIFFs[2] != null; - - SelectPIFF(Array.FindIndex(ActivePIFFs, x => x != null)); - - Render(); - EntryList.SelectedItem = null; - } - - public void SelectPIFF(int index) - { - if (index == -1) - { - - } - } - - public void RenderEmpty() - { - PIFFBox.Enabled = false; - } - - public void Render() - { - EntryList.Items.Clear(); - - PIFFName.Enabled = ActivePIFF != null; - PIFFComment.Enabled = ActivePIFF != null; - if (ActivePIFF != null) - { - var piffs = ActivePIFF.List(); - if ((piffs?.Count ?? 0) == 0) { - RenderEmpty(); return; - } - var piff = piffs[0]; - ActivePIFFChunk = piff; - - PIFFName.Text = ActivePIFF.Filename; - PIFFComment.Text = piff.Comment; - - PIFFBox.Enabled = true; - - foreach (var entry in piff.Entries) - { - EntryList.Items.Add(new PIFFListItem(entry, ActiveIff)); - } - } else - { - PIFFName.Text = "None"; - PIFFComment.Text = "Make changes and save then using the volcanic main window to view or edit the PIFF."; - } - } - - private void EntryList_SelectedIndexChanged(object sender, EventArgs e) - { - ActiveItem = EntryList.SelectedItem as PIFFListItem; - - if (ActiveItem == null) - { - EntryComment.Text = ""; - EntrySummary.Text = ""; - EntryComment.Enabled = false; - } - else - { - var entry = ActiveItem.Entry; - EntryComment.Text = entry.Comment; - EntrySummary.Text = GetEntrySummary(); - EntryComment.Enabled = true; - } - } - - private string GetEntrySummary() - { - var to = ActiveItem.Replaced; - if (to == null || to.OriginalData == null) return "Change summary not available."; - - Type chunkClass = IffFile.CHUNK_TYPES[to.ChunkType]; - IffChunk newChunk = (IffChunk)Activator.CreateInstance(chunkClass); - newChunk.ChunkLabel = to.OriginalLabel; - newChunk.ChunkID = to.OriginalID; - newChunk.OriginalID = to.OriginalID; - newChunk.OriginalData = to.OriginalData; - newChunk.OriginalLabel = to.OriginalLabel; - using (var str = new MemoryStream(to.OriginalData)) { - newChunk.Read(to.ChunkParent, str); - } - var from = newChunk; - //instruction mode - load original and new as Routine before we compare fields. (to get operands) - - //default mode - load original and use reflection to compare fields. - - var builder = new StringBuilder(); - if (from is BHAV) - { - var froutine = SimAntics.Engine.VMTranslator.INSTANCE.Assemble(from as BHAV); - var troutine = SimAntics.Engine.VMTranslator.INSTANCE.Assemble(to as BHAV); - CompareObject("", froutine, troutine, builder); - } else - { - CompareObject("", from, to, builder); - } - - return builder.ToString(); - } - - private void PrintObject(object obj, StringBuilder builder) - { - if (builder == null) return; - var fromType = obj.GetType(); - if (fromType.IsPrimitive || fromType.Equals(typeof(string))) - { - builder.Append(obj.ToString()); - return; - } - var fromProps = fromType.GetProperties(); - var fromMembers = fromType.GetFields(); - - builder.Append("{ "); - - foreach (var prop in fromProps) - { - builder.Append(prop.Name + ": " + (prop.GetValue(obj)?.ToString() ?? "null") + ", "); - } - - foreach (var prop in fromMembers) - { - builder.Append(prop.Name + ": " + (prop.GetValue(obj)?.ToString() ?? "null") + ", "); - } - - builder.Append("}"); - } - - private bool CompareObject(string depth, object from, object to, StringBuilder builder) - { - return CompareObject(depth, from, to, builder, new HashSet()); - } - - private bool CompareObject(string depth, object from, object to, StringBuilder builder, HashSet visitedFrom) - { - var fromType = from.GetType(); - if (fromType.IsPrimitive || fromType.Equals(typeof(string))) - { - if (!(from?.Equals(to) ?? false)) - { - builder?.Append($"{depth} set from "); - PrintObject(from, builder); - builder?.Append(" to "); - PrintObject(to, builder); - builder?.AppendLine(); - return true; - } - return false; - } - if (visitedFrom.Contains(from)) return false; - if (from is IList && to is IList) - { - return CompareList(depth, from as IList, to as IList, builder); - } - var fromProps = fromType.GetProperties(); - var fromMembers = fromType.GetFields(); - - var combined = Enumerable.Concat(fromProps, fromMembers); - var changed = false; - - foreach (var cprop in combined) - { - if (cprop.Name.StartsWith("Chunk")) continue; - if (IgnoreProps.Contains(cprop.Name)) continue; - object fromVal = (cprop as PropertyInfo)?.GetValue(from) ?? (cprop as FieldInfo)?.GetValue(from); - object toVal = (cprop as PropertyInfo)?.GetValue(to) ?? (cprop as FieldInfo)?.GetValue(to); - if (fromVal != null && toVal != null) - { - if (fromVal.GetType() != toVal.GetType()) - { - builder?.Append($"{depth} set from "); - PrintObject(fromVal, builder); - builder?.Append(" to "); - PrintObject(toVal, builder); - builder.AppendLine(); - changed = true; - } - else - { - //types are equal. - if (from.Equals(to)) - { - - } - else - { - CompareObject(depth + cprop.Name + ".", fromVal, toVal, builder, visitedFrom); - } - } - } - else if (fromVal != null) - { - builder?.Append($"{depth} set to NULL from: "); - PrintObject(fromVal, builder); - builder?.AppendLine(); - changed = true; - } - else if (toVal != null) - { - builder?.Append($"{depth} set from NULL: "); - PrintObject(toVal, builder); - builder?.AppendLine(); - changed = true; - } - } - return changed; - } - - private bool CompareList(string depth, IList from, IList to, StringBuilder builder) - { - if (depth.Length > 0) depth = depth.Substring(0, depth.Length - 1); - var fromCount = from.Count; - var toCount = to.Count; - - var changed = false; - var shared = Math.Min(fromCount, toCount); - var i = 0; - foreach (var item in from) - { - var toItem = to[i]; - //compare items - if (CompareObject(depth + "[" + i + "].", item, toItem, builder)) - { - changed = true; - } - if (++i >= shared) break; - } - - if (from.Count > to.Count) - { - //removed last in from - for (int j=shared; j chunk.ChunkType == entry.Type && chunk.OriginalID == entry.ChunkID); - if (Replaced == null) Name = "UNKNOWN"; - else Name = Replaced.OriginalLabel; - } - } - - public override string ToString() - { - return $"({Entry.Type} {Entry.ChunkID}) {Name} - {Entry.EntryType.ToString()}"; - } - } -} diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BCONResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BCONResourceControl.Designer.cs index a6a3d1712..c682809ec 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BCONResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BCONResourceControl.Designer.cs @@ -207,8 +207,8 @@ private void InitializeComponent() // // BCONResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.ValueLabel); this.Controls.Add(this.NameLabel); this.Controls.Add(this.ValueBox); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BHAVResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BHAVResourceControl.Designer.cs index 55c992331..d0c841df3 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BHAVResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/BHAVResourceControl.Designer.cs @@ -327,8 +327,8 @@ private void InitializeComponent() // // BHAVResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.StackObjName); this.Controls.Add(this.StackChangeBtn); this.Controls.Add(this.label3); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/OTFResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/OTFResourceControl.Designer.cs index 35097935d..6652a69d6 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/OTFResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/OTFResourceControl.Designer.cs @@ -53,8 +53,8 @@ private void InitializeComponent() // // OTFResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.label1); this.Controls.Add(this.XMLDisplay); this.Name = "OTFResourceControl"; diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SLOTResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SLOTResourceControl.Designer.cs index ac0d56dbd..ced1a015c 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SLOTResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SLOTResourceControl.Designer.cs @@ -685,8 +685,8 @@ private void InitializeComponent() // // SLOTResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.EqualProximityScoreCheck); this.Controls.Add(this.MaxSizeLabel); this.Controls.Add(this.MaxSizeEntry); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.Designer.cs index e92c3a2aa..e85a24fd3 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.Designer.cs @@ -28,218 +28,218 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.ModeCombo = new System.Windows.Forms.ComboBox(); - this.PreviewLabel = new System.Windows.Forms.Label(); - this.SPRBox3 = new System.Windows.Forms.PictureBox(); - this.SPRBox2 = new System.Windows.Forms.PictureBox(); - this.SPRBox1 = new System.Windows.Forms.PictureBox(); - this.FrameList = new System.Windows.Forms.ListBox(); - this.FramesLabel = new System.Windows.Forms.Label(); - this.NewButton = new System.Windows.Forms.Button(); - this.ImportButton = new System.Windows.Forms.Button(); - this.ExportButton = new System.Windows.Forms.Button(); - this.DeleteButton = new System.Windows.Forms.Button(); - this.ExportAll = new System.Windows.Forms.Button(); - this.ImportAll = new System.Windows.Forms.Button(); - this.AutoZooms = new System.Windows.Forms.CheckBox(); - this.SPRSelector = new FSO.IDE.ResourceBrowser.OBJDSelectorControl(); - this.SheetImport = new System.Windows.Forms.Button(); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox3)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox2)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox1)).BeginInit(); - this.SuspendLayout(); + ModeCombo = new ComboBox(); + PreviewLabel = new Label(); + SPRBox3 = new PictureBox(); + SPRBox2 = new PictureBox(); + SPRBox1 = new PictureBox(); + FrameList = new ListBox(); + FramesLabel = new Label(); + NewButton = new Button(); + ImportButton = new Button(); + ExportButton = new Button(); + DeleteButton = new Button(); + ExportAll = new Button(); + ImportAll = new Button(); + AutoZooms = new CheckBox(); + SPRSelector = new OBJDSelectorControl(); + SheetImport = new Button(); + ((System.ComponentModel.ISupportInitialize)SPRBox3).BeginInit(); + ((System.ComponentModel.ISupportInitialize)SPRBox2).BeginInit(); + ((System.ComponentModel.ISupportInitialize)SPRBox1).BeginInit(); + SuspendLayout(); // // ModeCombo // - this.ModeCombo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.ModeCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this.ModeCombo.FormattingEnabled = true; - this.ModeCombo.Items.AddRange(new object[] { - "Color", - "Alpha", - "Z-Buffer"}); - this.ModeCombo.Location = new System.Drawing.Point(3, 431); - this.ModeCombo.Name = "ModeCombo"; - this.ModeCombo.Size = new System.Drawing.Size(250, 21); - this.ModeCombo.TabIndex = 3; - this.ModeCombo.SelectedIndexChanged += new System.EventHandler(this.comboBox1_SelectedIndexChanged); + ModeCombo.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + ModeCombo.DropDownStyle = ComboBoxStyle.DropDownList; + ModeCombo.FormattingEnabled = true; + ModeCombo.Items.AddRange(new object[] { "Color", "Alpha", "Z-Buffer" }); + ModeCombo.Location = new Point(3, 431); + ModeCombo.Name = "ModeCombo"; + ModeCombo.Size = new Size(250, 21); + ModeCombo.TabIndex = 3; + ModeCombo.SelectedIndexChanged += comboBox1_SelectedIndexChanged; // // PreviewLabel // - this.PreviewLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.PreviewLabel.AutoSize = true; - this.PreviewLabel.Location = new System.Drawing.Point(3, 21); - this.PreviewLabel.Name = "PreviewLabel"; - this.PreviewLabel.Size = new System.Drawing.Size(48, 13); - this.PreviewLabel.TabIndex = 4; - this.PreviewLabel.Text = "Preview:"; + PreviewLabel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + PreviewLabel.AutoSize = true; + PreviewLabel.Location = new Point(3, 21); + PreviewLabel.Name = "PreviewLabel"; + PreviewLabel.Size = new Size(49, 13); + PreviewLabel.TabIndex = 4; + PreviewLabel.Text = "Preview:"; // // SPRBox3 // - this.SPRBox3.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.SPRBox3.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.SPRBox3.Location = new System.Drawing.Point(219, 325); - this.SPRBox3.Name = "SPRBox3"; - this.SPRBox3.Size = new System.Drawing.Size(34, 100); - this.SPRBox3.TabIndex = 2; - this.SPRBox3.TabStop = false; + SPRBox3.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + SPRBox3.BorderStyle = BorderStyle.FixedSingle; + SPRBox3.Location = new Point(219, 325); + SPRBox3.Name = "SPRBox3"; + SPRBox3.Size = new Size(34, 100); + SPRBox3.SizeMode = PictureBoxSizeMode.Zoom; + SPRBox3.TabIndex = 2; + SPRBox3.TabStop = false; // // SPRBox2 // - this.SPRBox2.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.SPRBox2.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.SPRBox2.Location = new System.Drawing.Point(145, 229); - this.SPRBox2.Name = "SPRBox2"; - this.SPRBox2.Size = new System.Drawing.Size(68, 196); - this.SPRBox2.TabIndex = 1; - this.SPRBox2.TabStop = false; + SPRBox2.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + SPRBox2.BorderStyle = BorderStyle.FixedSingle; + SPRBox2.Location = new Point(145, 229); + SPRBox2.Name = "SPRBox2"; + SPRBox2.Size = new Size(68, 196); + SPRBox2.SizeMode = PictureBoxSizeMode.Zoom; + SPRBox2.TabIndex = 1; + SPRBox2.TabStop = false; // // SPRBox1 // - this.SPRBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.SPRBox1.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; - this.SPRBox1.Location = new System.Drawing.Point(3, 37); - this.SPRBox1.Name = "SPRBox1"; - this.SPRBox1.Size = new System.Drawing.Size(136, 388); - this.SPRBox1.TabIndex = 0; - this.SPRBox1.TabStop = false; + SPRBox1.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + SPRBox1.BorderStyle = BorderStyle.FixedSingle; + SPRBox1.Location = new Point(3, 37); + SPRBox1.Name = "SPRBox1"; + SPRBox1.Size = new Size(136, 388); + SPRBox1.SizeMode = PictureBoxSizeMode.Zoom; + SPRBox1.TabIndex = 0; + SPRBox1.TabStop = false; // // FrameList // - this.FrameList.FormattingEnabled = true; - this.FrameList.IntegralHeight = false; - this.FrameList.Location = new System.Drawing.Point(269, 37); - this.FrameList.Name = "FrameList"; - this.FrameList.Size = new System.Drawing.Size(149, 305); - this.FrameList.TabIndex = 5; - this.FrameList.SelectedIndexChanged += new System.EventHandler(this.FrameList_SelectedIndexChanged); + FrameList.FormattingEnabled = true; + FrameList.IntegralHeight = false; + FrameList.Location = new Point(269, 37); + FrameList.Name = "FrameList"; + FrameList.Size = new Size(149, 305); + FrameList.TabIndex = 5; + FrameList.SelectedIndexChanged += FrameList_SelectedIndexChanged; // // FramesLabel // - this.FramesLabel.AutoSize = true; - this.FramesLabel.Location = new System.Drawing.Point(266, 21); - this.FramesLabel.Name = "FramesLabel"; - this.FramesLabel.Size = new System.Drawing.Size(55, 13); - this.FramesLabel.TabIndex = 6; - this.FramesLabel.Text = "Rotations:"; + FramesLabel.AutoSize = true; + FramesLabel.Location = new Point(266, 21); + FramesLabel.Name = "FramesLabel"; + FramesLabel.Size = new Size(60, 13); + FramesLabel.TabIndex = 6; + FramesLabel.Text = "Rotations:"; // // NewButton // - this.NewButton.Location = new System.Drawing.Point(424, 37); - this.NewButton.Name = "NewButton"; - this.NewButton.Size = new System.Drawing.Size(75, 23); - this.NewButton.TabIndex = 7; - this.NewButton.Text = "New"; - this.NewButton.UseVisualStyleBackColor = true; - this.NewButton.Click += new System.EventHandler(this.NewButton_Click); + NewButton.Location = new Point(424, 37); + NewButton.Name = "NewButton"; + NewButton.Size = new Size(75, 23); + NewButton.TabIndex = 7; + NewButton.Text = "New"; + NewButton.UseVisualStyleBackColor = true; + NewButton.Click += NewButton_Click; // // ImportButton // - this.ImportButton.Location = new System.Drawing.Point(424, 66); - this.ImportButton.Name = "ImportButton"; - this.ImportButton.Size = new System.Drawing.Size(75, 23); - this.ImportButton.TabIndex = 8; - this.ImportButton.Text = "Import"; - this.ImportButton.UseVisualStyleBackColor = true; - this.ImportButton.Click += new System.EventHandler(this.ImportButton_Click); + ImportButton.Location = new Point(424, 66); + ImportButton.Name = "ImportButton"; + ImportButton.Size = new Size(75, 23); + ImportButton.TabIndex = 8; + ImportButton.Text = "Import"; + ImportButton.UseVisualStyleBackColor = true; + ImportButton.Click += ImportButton_Click; // // ExportButton // - this.ExportButton.Location = new System.Drawing.Point(424, 95); - this.ExportButton.Name = "ExportButton"; - this.ExportButton.Size = new System.Drawing.Size(75, 23); - this.ExportButton.TabIndex = 9; - this.ExportButton.Text = "Export"; - this.ExportButton.UseVisualStyleBackColor = true; - this.ExportButton.Click += new System.EventHandler(this.ExportButton_Click); + ExportButton.Location = new Point(424, 95); + ExportButton.Name = "ExportButton"; + ExportButton.Size = new Size(75, 23); + ExportButton.TabIndex = 9; + ExportButton.Text = "Export"; + ExportButton.UseVisualStyleBackColor = true; + ExportButton.Click += ExportButton_Click; // // DeleteButton // - this.DeleteButton.Location = new System.Drawing.Point(424, 124); - this.DeleteButton.Name = "DeleteButton"; - this.DeleteButton.Size = new System.Drawing.Size(75, 23); - this.DeleteButton.TabIndex = 10; - this.DeleteButton.Text = "Delete"; - this.DeleteButton.UseVisualStyleBackColor = true; - this.DeleteButton.Click += new System.EventHandler(this.DeleteButton_Click); + DeleteButton.Location = new Point(424, 124); + DeleteButton.Name = "DeleteButton"; + DeleteButton.Size = new Size(75, 23); + DeleteButton.TabIndex = 10; + DeleteButton.Text = "Delete"; + DeleteButton.UseVisualStyleBackColor = true; + DeleteButton.Click += DeleteButton_Click; // // ExportAll // - this.ExportAll.Location = new System.Drawing.Point(269, 377); - this.ExportAll.Name = "ExportAll"; - this.ExportAll.Size = new System.Drawing.Size(149, 23); - this.ExportAll.TabIndex = 11; - this.ExportAll.Text = "Export All"; - this.ExportAll.UseVisualStyleBackColor = true; - this.ExportAll.Click += new System.EventHandler(this.ExportAll_Click); + ExportAll.Location = new Point(269, 377); + ExportAll.Name = "ExportAll"; + ExportAll.Size = new Size(149, 23); + ExportAll.TabIndex = 11; + ExportAll.Text = "Export All"; + ExportAll.UseVisualStyleBackColor = true; + ExportAll.Click += ExportAll_Click; // // ImportAll // - this.ImportAll.Location = new System.Drawing.Point(269, 348); - this.ImportAll.Name = "ImportAll"; - this.ImportAll.Size = new System.Drawing.Size(149, 23); - this.ImportAll.TabIndex = 12; - this.ImportAll.Text = "Import All"; - this.ImportAll.UseVisualStyleBackColor = true; - this.ImportAll.Click += new System.EventHandler(this.ImportAll_Click); + ImportAll.Location = new Point(269, 348); + ImportAll.Name = "ImportAll"; + ImportAll.Size = new Size(149, 23); + ImportAll.TabIndex = 12; + ImportAll.Text = "Import All"; + ImportAll.UseVisualStyleBackColor = true; + ImportAll.Click += ImportAll_Click; // // AutoZooms // - this.AutoZooms.AutoSize = true; - this.AutoZooms.Checked = true; - this.AutoZooms.CheckState = System.Windows.Forms.CheckState.Checked; - this.AutoZooms.Location = new System.Drawing.Point(250, 0); - this.AutoZooms.Name = "AutoZooms"; - this.AutoZooms.Size = new System.Drawing.Size(249, 17); - this.AutoZooms.TabIndex = 13; - this.AutoZooms.Text = "Automatically Generate Medium and Far Zooms"; - this.AutoZooms.UseVisualStyleBackColor = true; - this.AutoZooms.CheckedChanged += new System.EventHandler(this.AutoZooms_CheckedChanged); + AutoZooms.AutoSize = true; + AutoZooms.Checked = true; + AutoZooms.CheckState = CheckState.Checked; + AutoZooms.Location = new Point(250, 0); + AutoZooms.Name = "AutoZooms"; + AutoZooms.Size = new Size(269, 17); + AutoZooms.TabIndex = 13; + AutoZooms.Text = "Automatically Generate Medium and Far Zooms"; + AutoZooms.UseVisualStyleBackColor = true; + AutoZooms.CheckedChanged += AutoZooms_CheckedChanged; // // SPRSelector // - this.SPRSelector.Location = new System.Drawing.Point(269, 406); - this.SPRSelector.Name = "SPRSelector"; - this.SPRSelector.Size = new System.Drawing.Size(230, 46); - this.SPRSelector.TabIndex = 14; + SPRSelector.Location = new Point(269, 406); + SPRSelector.Name = "SPRSelector"; + SPRSelector.Size = new Size(230, 46); + SPRSelector.TabIndex = 14; // // SheetImport // - this.SheetImport.Location = new System.Drawing.Point(424, 348); - this.SheetImport.Name = "SheetImport"; - this.SheetImport.Size = new System.Drawing.Size(75, 52); - this.SheetImport.TabIndex = 15; - this.SheetImport.Text = "Import from TGA Sheet"; - this.SheetImport.UseVisualStyleBackColor = true; - this.SheetImport.Click += new System.EventHandler(this.SheetImport_Click); + SheetImport.Location = new Point(424, 348); + SheetImport.Name = "SheetImport"; + SheetImport.Size = new Size(75, 52); + SheetImport.TabIndex = 15; + SheetImport.Text = "Import from TGA Sheet"; + SheetImport.UseVisualStyleBackColor = true; + SheetImport.Click += SheetImport_Click; // // SPR2ResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.SheetImport); - this.Controls.Add(this.SPRSelector); - this.Controls.Add(this.AutoZooms); - this.Controls.Add(this.ImportAll); - this.Controls.Add(this.ExportAll); - this.Controls.Add(this.DeleteButton); - this.Controls.Add(this.ExportButton); - this.Controls.Add(this.ImportButton); - this.Controls.Add(this.NewButton); - this.Controls.Add(this.FramesLabel); - this.Controls.Add(this.FrameList); - this.Controls.Add(this.PreviewLabel); - this.Controls.Add(this.ModeCombo); - this.Controls.Add(this.SPRBox3); - this.Controls.Add(this.SPRBox2); - this.Controls.Add(this.SPRBox1); - this.Name = "SPR2ResourceControl"; - this.Size = new System.Drawing.Size(502, 455); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox3)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox2)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.SPRBox1)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + Controls.Add(SheetImport); + Controls.Add(SPRSelector); + Controls.Add(AutoZooms); + Controls.Add(ImportAll); + Controls.Add(ExportAll); + Controls.Add(DeleteButton); + Controls.Add(ExportButton); + Controls.Add(ImportButton); + Controls.Add(NewButton); + Controls.Add(FramesLabel); + Controls.Add(FrameList); + Controls.Add(PreviewLabel); + Controls.Add(ModeCombo); + Controls.Add(SPRBox3); + Controls.Add(SPRBox2); + Controls.Add(SPRBox1); + Name = "SPR2ResourceControl"; + Size = new Size(502, 455); + ((System.ComponentModel.ISupportInitialize)SPRBox3).EndInit(); + ((System.ComponentModel.ISupportInitialize)SPRBox2).EndInit(); + ((System.ComponentModel.ISupportInitialize)SPRBox1).EndInit(); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.resx b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.resx index 1af7de150..8b2ff64a1 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.resx +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/SPR2ResourceControl.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/STRResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/STRResourceControl.Designer.cs index 023084ade..0544e47ab 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/STRResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/STRResourceControl.Designer.cs @@ -187,8 +187,8 @@ private void InitializeComponent() // // STRResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.CommentBox); this.Controls.Add(this.CommentLabel); this.Controls.Add(this.Selector); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/TTABResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/TTABResourceControl.Designer.cs index 9656cc12c..9bbe1785f 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/TTABResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/TTABResourceControl.Designer.cs @@ -829,8 +829,8 @@ private void InitializeComponent() // // TTABResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.Selector); this.Controls.Add(this.SearchIcon); this.Controls.Add(this.SearchBox); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/UnknownResourceControl.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/UnknownResourceControl.Designer.cs index ee591c4b4..0122c4bc9 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/UnknownResourceControl.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/ResourceEditors/UnknownResourceControl.Designer.cs @@ -67,8 +67,8 @@ private void InitializeComponent() // // UnknownResourceControl // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.groupBox1); this.Margin = new System.Windows.Forms.Padding(0); this.Name = "UnknownResourceControl"; diff --git a/TSOClient/FSO.IDE/ResourceBrowser/SelectTreeDialog.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/SelectTreeDialog.Designer.cs index 14582fdc9..20b9a7ec5 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/SelectTreeDialog.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/SelectTreeDialog.Designer.cs @@ -83,8 +83,8 @@ private void InitializeComponent() // // SelectTreeDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(254, 406); this.Controls.Add(this.SelectButton); this.Controls.Add(this.pictureBox1); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.Designer.cs index b1494659d..75550164a 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.Designer.cs @@ -29,32 +29,31 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SPR2SelectorDialog)); - this.iffRes = new FSO.IDE.ResourceBrowser.IFFResComponent(); - this.SuspendLayout(); + iffRes = new IFFResComponent(); + SuspendLayout(); // // iffRes // - this.iffRes.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.iffRes.Location = new System.Drawing.Point(3, 3); - this.iffRes.Name = "iffRes"; - this.iffRes.Size = new System.Drawing.Size(762, 459); - this.iffRes.TabIndex = 0; + iffRes.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + iffRes.Location = new Point(3, 3); + iffRes.Margin = new Padding(4, 3, 4, 3); + iffRes.Name = "iffRes"; + iffRes.Size = new Size(762, 459); + iffRes.TabIndex = 0; // // SPR2SelectorDialog // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(768, 465); - this.Controls.Add(this.iffRes); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle; - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MaximizeBox = false; - this.MinimumSize = new System.Drawing.Size(784, 504); - this.Name = "SPR2SelectorDialog"; - this.Text = "Select SPR2..."; - this.ResumeLayout(false); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(768, 465); + Controls.Add(iffRes); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + MinimumSize = new Size(784, 504); + Name = "SPR2SelectorDialog"; + Text = "Select SPR2..."; + ResumeLayout(false); } diff --git a/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.resx b/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.resx index 21e397da9..62b6bd5cd 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.resx +++ b/TSOClient/FSO.IDE/ResourceBrowser/SelectorDialogs/SPR2SelectorDialog.resx @@ -1,17 +1,17 @@  - diff --git a/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.Designer.cs index 902f1c0a0..8f1ef3f12 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.Designer.cs @@ -706,8 +706,8 @@ private void InitializeComponent() // // UpgradeEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.PasteButton); this.Controls.Add(this.CopyButton); this.Controls.Add(this.SaveButton); diff --git a/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.cs b/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.cs index 650f09d4a..17ee31737 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/UpgradeEditor.cs @@ -332,7 +332,7 @@ private void UpdateSubsList() else { UpgradeContainer.Parent = LevelsTabControl.SelectedTab; - UpgradeContainer.BackColor = Color.White; + UpgradeContainer.BackColor = System.Drawing.Color.White; int parsedLevelName; if (int.TryParse(ActiveLevel.Name, out parsedLevelName)) diff --git a/TSOClient/FSO.IDE/ResourceBrowser/XMLEntryEditor.Designer.cs b/TSOClient/FSO.IDE/ResourceBrowser/XMLEntryEditor.Designer.cs index d0a28e95a..1acc279f5 100644 --- a/TSOClient/FSO.IDE/ResourceBrowser/XMLEntryEditor.Designer.cs +++ b/TSOClient/FSO.IDE/ResourceBrowser/XMLEntryEditor.Designer.cs @@ -191,8 +191,8 @@ private void InitializeComponent() // // XMLEntryEditor // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.CommentCheckbox); this.Controls.Add(this.CopiedLabel); this.Controls.Add(this.SalePriceUpDown); diff --git a/TSOClient/FSO.IDE/Utils/AppearanceGenerator.cs b/TSOClient/FSO.IDE/Utils/AppearanceGenerator.cs index d97f1e0f1..a97173f7b 100644 --- a/TSOClient/FSO.IDE/Utils/AppearanceGenerator.cs +++ b/TSOClient/FSO.IDE/Utils/AppearanceGenerator.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Binding = FSO.Vitaboy.Binding; namespace FSO.IDE.Utils { @@ -72,7 +73,7 @@ public ulong GenerateAppearanceTSO(List meshes, string name, bo }; }); - var apr = new Appearance() + var apr = new Vitaboy.Appearance() { Bindings = bindings.ToArray(), Name = name, @@ -85,7 +86,7 @@ public ulong GenerateAppearanceTSO(List meshes, string name, bo apr.Write(mem); appearanceData = mem.ToArray(); } - var aprID = (Content.Content.Get().AvatarAppearances as TSOAvatarContentProvider).CreateFile(name + ".apr", apr, appearanceData, runtime); + var aprID = (Content.Content.Get().AvatarAppearances as TSOAvatarContentProvider).CreateFile(name + ".apr", apr, appearanceData, runtime); return aprID; } diff --git a/TSOClient/FSO.IDE/Utils/FormatReverse/FieldEncodingFormatTracker.Designer.cs b/TSOClient/FSO.IDE/Utils/FormatReverse/FieldEncodingFormatTracker.Designer.cs index 6d5becadc..61bcf2b2b 100644 --- a/TSOClient/FSO.IDE/Utils/FormatReverse/FieldEncodingFormatTracker.Designer.cs +++ b/TSOClient/FSO.IDE/Utils/FormatReverse/FieldEncodingFormatTracker.Designer.cs @@ -264,8 +264,8 @@ private void InitializeComponent() // // FieldEncodingFormatTracker // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(468, 356); this.Controls.Add(this.UnknownButton); this.Controls.Add(this.BitView); diff --git a/TSOClient/FSO.IDE/Utils/HouseSpy.Designer.cs b/TSOClient/FSO.IDE/Utils/HouseSpy.Designer.cs index 5f8d95e34..74c587985 100644 --- a/TSOClient/FSO.IDE/Utils/HouseSpy.Designer.cs +++ b/TSOClient/FSO.IDE/Utils/HouseSpy.Designer.cs @@ -28,375 +28,337 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem(new string[] { - "0", - "65536", - "63356", - "65536", - "65536", - "99", - "0,0,0,0", - "100", - "8192", - "0.3", - "99"}, -1); + ListViewItem listViewItem1 = new ListViewItem(new string[] { "0", "65536", "63356", "65536", "65536", "99", "0,0,0,0", "100", "8192", "0.3", "99" }, -1); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(HouseSpy)); - this.objectList = new System.Windows.Forms.ListBox(); - this.peopleLabel = new System.Windows.Forms.Label(); - this.menuStrip1 = new System.Windows.Forms.MenuStrip(); - this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.openToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); - this.personBox = new System.Windows.Forms.GroupBox(); - this.positionLabel = new System.Windows.Forms.Label(); - this.unknownsLabel = new System.Windows.Forms.Label(); - this.animationLabel = new System.Windows.Forms.Label(); - this.accessoriesLabel = new System.Windows.Forms.Label(); - this.accessoriesList = new System.Windows.Forms.ListBox(); - this.useCountLabel = new System.Windows.Forms.Label(); - this.useCountList = new System.Windows.Forms.ListView(); - this.useIDColumn = new System.Windows.Forms.ColumnHeader(); - this.useStackColumn = new System.Windows.Forms.ColumnHeader(); - this.useFlagColumn = new System.Windows.Forms.ColumnHeader(); - this.motiveChangeLabel = new System.Windows.Forms.Label(); - this.motiveChangeList = new System.Windows.Forms.ListView(); - this.motiveIDColumn = new System.Windows.Forms.ColumnHeader(); - this.motiveDeltaColumn = new System.Windows.Forms.ColumnHeader(); - this.motiveStopColumn = new System.Windows.Forms.ColumnHeader(); - this.queueList = new System.Windows.Forms.ListView(); - this.queueNullHeader = new System.Windows.Forms.ColumnHeader(); - this.queueUIDHeader = new System.Windows.Forms.ColumnHeader(); - this.queueCallerHeader = new System.Windows.Forms.ColumnHeader(); - this.queueTargetHeader = new System.Windows.Forms.ColumnHeader(); - this.queueIconHeader = new System.Windows.Forms.ColumnHeader(); - this.queueTTAHeader = new System.Windows.Forms.ColumnHeader(); - this.queueArgsHeader = new System.Windows.Forms.ColumnHeader(); - this.queuePriorityHeader = new System.Windows.Forms.ColumnHeader(); - this.queueTreeHeader = new System.Windows.Forms.ColumnHeader(); - this.queueAttenuationHeader = new System.Windows.Forms.ColumnHeader(); - this.queueFlagsHeader = new System.Windows.Forms.ColumnHeader(); - this.floatsLabel = new System.Windows.Forms.Label(); - this.floatsList = new System.Windows.Forms.ListBox(); - this.updatedLabel = new System.Windows.Forms.Label(); - this.menuStrip1.SuspendLayout(); - this.personBox.SuspendLayout(); - this.SuspendLayout(); + objectList = new ListBox(); + peopleLabel = new Label(); + menuStrip1 = new MenuStrip(); + fileToolStripMenuItem = new ToolStripMenuItem(); + openToolStripMenuItem = new ToolStripMenuItem(); + personBox = new GroupBox(); + positionLabel = new Label(); + unknownsLabel = new Label(); + animationLabel = new Label(); + accessoriesLabel = new Label(); + accessoriesList = new ListBox(); + useCountLabel = new Label(); + useCountList = new ListView(); + useIDColumn = new ColumnHeader(); + useStackColumn = new ColumnHeader(); + useFlagColumn = new ColumnHeader(); + motiveChangeLabel = new Label(); + motiveChangeList = new ListView(); + motiveIDColumn = new ColumnHeader(); + motiveDeltaColumn = new ColumnHeader(); + motiveStopColumn = new ColumnHeader(); + queueList = new ListView(); + queueNullHeader = new ColumnHeader(); + queueUIDHeader = new ColumnHeader(); + queueCallerHeader = new ColumnHeader(); + queueTargetHeader = new ColumnHeader(); + queueIconHeader = new ColumnHeader(); + queueTTAHeader = new ColumnHeader(); + queueArgsHeader = new ColumnHeader(); + queuePriorityHeader = new ColumnHeader(); + queueTreeHeader = new ColumnHeader(); + queueAttenuationHeader = new ColumnHeader(); + queueFlagsHeader = new ColumnHeader(); + floatsLabel = new Label(); + floatsList = new ListBox(); + updatedLabel = new Label(); + menuStrip1.SuspendLayout(); + personBox.SuspendLayout(); + SuspendLayout(); // // objectList // - this.objectList.FormattingEnabled = true; - this.objectList.Location = new System.Drawing.Point(12, 49); - this.objectList.Name = "objectList"; - this.objectList.Size = new System.Drawing.Size(137, 108); - this.objectList.TabIndex = 0; - this.objectList.SelectedIndexChanged += new System.EventHandler(this.objectList_SelectedIndexChanged); + objectList.FormattingEnabled = true; + objectList.Location = new Point(12, 49); + objectList.Name = "objectList"; + objectList.Size = new Size(137, 108); + objectList.TabIndex = 0; + objectList.SelectedIndexChanged += objectList_SelectedIndexChanged; // // peopleLabel // - this.peopleLabel.AutoSize = true; - this.peopleLabel.Location = new System.Drawing.Point(12, 33); - this.peopleLabel.Name = "peopleLabel"; - this.peopleLabel.Size = new System.Drawing.Size(40, 13); - this.peopleLabel.TabIndex = 1; - this.peopleLabel.Text = "People"; + peopleLabel.AutoSize = true; + peopleLabel.Location = new Point(12, 33); + peopleLabel.Name = "peopleLabel"; + peopleLabel.Size = new Size(42, 13); + peopleLabel.TabIndex = 1; + peopleLabel.Text = "People"; // // menuStrip1 // - this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.fileToolStripMenuItem}); - this.menuStrip1.Location = new System.Drawing.Point(0, 0); - this.menuStrip1.Name = "menuStrip1"; - this.menuStrip1.Size = new System.Drawing.Size(756, 24); - this.menuStrip1.TabIndex = 2; - this.menuStrip1.Text = "menuStrip1"; + menuStrip1.Items.AddRange(new ToolStripItem[] { fileToolStripMenuItem }); + menuStrip1.Location = new Point(0, 0); + menuStrip1.Name = "menuStrip1"; + menuStrip1.Size = new Size(756, 24); + menuStrip1.TabIndex = 2; + menuStrip1.Text = "menuStrip1"; // // fileToolStripMenuItem // - this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.openToolStripMenuItem}); - this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; - this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); - this.fileToolStripMenuItem.Text = "File"; + fileToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[] { openToolStripMenuItem }); + fileToolStripMenuItem.Name = "fileToolStripMenuItem"; + fileToolStripMenuItem.Size = new Size(37, 20); + fileToolStripMenuItem.Text = "File"; // // openToolStripMenuItem // - this.openToolStripMenuItem.Name = "openToolStripMenuItem"; - this.openToolStripMenuItem.Size = new System.Drawing.Size(103, 22); - this.openToolStripMenuItem.Text = "Open"; - this.openToolStripMenuItem.Click += new System.EventHandler(this.openToolStripMenuItem_Click); + openToolStripMenuItem.Name = "openToolStripMenuItem"; + openToolStripMenuItem.Size = new Size(103, 22); + openToolStripMenuItem.Text = "Open"; + openToolStripMenuItem.Click += openToolStripMenuItem_Click; // // personBox // - this.personBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.personBox.Controls.Add(this.positionLabel); - this.personBox.Controls.Add(this.unknownsLabel); - this.personBox.Controls.Add(this.animationLabel); - this.personBox.Controls.Add(this.accessoriesLabel); - this.personBox.Controls.Add(this.accessoriesList); - this.personBox.Controls.Add(this.useCountLabel); - this.personBox.Controls.Add(this.useCountList); - this.personBox.Controls.Add(this.motiveChangeLabel); - this.personBox.Controls.Add(this.motiveChangeList); - this.personBox.Controls.Add(this.queueList); - this.personBox.Controls.Add(this.floatsLabel); - this.personBox.Controls.Add(this.floatsList); - this.personBox.Location = new System.Drawing.Point(155, 33); - this.personBox.Name = "personBox"; - this.personBox.Size = new System.Drawing.Size(589, 356); - this.personBox.TabIndex = 3; - this.personBox.TabStop = false; - this.personBox.Text = "Person"; + personBox.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + personBox.Controls.Add(positionLabel); + personBox.Controls.Add(unknownsLabel); + personBox.Controls.Add(animationLabel); + personBox.Controls.Add(accessoriesLabel); + personBox.Controls.Add(accessoriesList); + personBox.Controls.Add(useCountLabel); + personBox.Controls.Add(useCountList); + personBox.Controls.Add(motiveChangeLabel); + personBox.Controls.Add(motiveChangeList); + personBox.Controls.Add(queueList); + personBox.Controls.Add(floatsLabel); + personBox.Controls.Add(floatsList); + personBox.Location = new Point(155, 33); + personBox.Name = "personBox"; + personBox.Size = new Size(589, 356); + personBox.TabIndex = 3; + personBox.TabStop = false; + personBox.Text = "Person"; // // positionLabel // - this.positionLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); - this.positionLabel.Location = new System.Drawing.Point(477, 16); - this.positionLabel.Name = "positionLabel"; - this.positionLabel.Size = new System.Drawing.Size(106, 18); - this.positionLabel.TabIndex = 0; - this.positionLabel.Text = "X: 0, Y: 0"; - this.positionLabel.TextAlign = System.Drawing.ContentAlignment.TopRight; + positionLabel.Anchor = AnchorStyles.Top | AnchorStyles.Right; + positionLabel.Location = new Point(477, 16); + positionLabel.Name = "positionLabel"; + positionLabel.Size = new Size(106, 18); + positionLabel.TabIndex = 0; + positionLabel.Text = "X: 0, Y: 0"; + positionLabel.TextAlign = ContentAlignment.TopRight; // // unknownsLabel // - this.unknownsLabel.Location = new System.Drawing.Point(106, 69); - this.unknownsLabel.Name = "unknownsLabel"; - this.unknownsLabel.Size = new System.Drawing.Size(373, 32); - this.unknownsLabel.TabIndex = 4; - this.unknownsLabel.Text = "Unknown1: 65536, Unknown2: 65536, UnknownValue: 65536\r\nRoutingFrameCount: 1"; + unknownsLabel.Location = new Point(106, 69); + unknownsLabel.Name = "unknownsLabel"; + unknownsLabel.Size = new Size(373, 32); + unknownsLabel.TabIndex = 4; + unknownsLabel.Text = "Unknown1: 65536, Unknown2: 65536, UnknownValue: 65536\r\nRoutingFrameCount: 1"; // // animationLabel // - this.animationLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.animationLabel.Location = new System.Drawing.Point(106, 16); - this.animationLabel.Name = "animationLabel"; - this.animationLabel.Size = new System.Drawing.Size(420, 42); - this.animationLabel.TabIndex = 11; - this.animationLabel.Text = "Animation: a2o-idle-neutral-lhips-look-1c;1;1000;240;1000;0;1;1\r\nBase: a2o-standi" + - "ng-loop;-10;1000;70;1000;1;1;1\r\nCarry: a2o-rarm-carry-loop;10;0;1000;1000;0;1;1"; + animationLabel.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + animationLabel.Location = new Point(106, 16); + animationLabel.Name = "animationLabel"; + animationLabel.Size = new Size(420, 42); + animationLabel.TabIndex = 11; + animationLabel.Text = "Animation: a2o-idle-neutral-lhips-look-1c;1;1000;240;1000;0;1;1\r\nBase: a2o-standing-loop;-10;1000;70;1000;1;1;1\r\nCarry: a2o-rarm-carry-loop;10;0;1000;1000;0;1;1"; // // accessoriesLabel // - this.accessoriesLabel.AutoSize = true; - this.accessoriesLabel.Location = new System.Drawing.Point(477, 110); - this.accessoriesLabel.Name = "accessoriesLabel"; - this.accessoriesLabel.Size = new System.Drawing.Size(64, 13); - this.accessoriesLabel.TabIndex = 10; - this.accessoriesLabel.Text = "Accessories"; + accessoriesLabel.AutoSize = true; + accessoriesLabel.Location = new Point(477, 110); + accessoriesLabel.Name = "accessoriesLabel"; + accessoriesLabel.Size = new Size(65, 13); + accessoriesLabel.TabIndex = 10; + accessoriesLabel.Text = "Accessories"; // // accessoriesList // - this.accessoriesList.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.accessoriesList.FormattingEnabled = true; - this.accessoriesList.Location = new System.Drawing.Point(477, 128); - this.accessoriesList.Name = "accessoriesList"; - this.accessoriesList.Size = new System.Drawing.Size(106, 95); - this.accessoriesList.TabIndex = 9; + accessoriesList.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + accessoriesList.FormattingEnabled = true; + accessoriesList.Location = new Point(477, 128); + accessoriesList.Name = "accessoriesList"; + accessoriesList.Size = new Size(106, 95); + accessoriesList.TabIndex = 9; // // useCountLabel // - this.useCountLabel.AutoSize = true; - this.useCountLabel.Location = new System.Drawing.Point(104, 110); - this.useCountLabel.Name = "useCountLabel"; - this.useCountLabel.Size = new System.Drawing.Size(62, 13); - this.useCountLabel.TabIndex = 8; - this.useCountLabel.Text = "Use Counts"; + useCountLabel.AutoSize = true; + useCountLabel.Location = new Point(104, 110); + useCountLabel.Name = "useCountLabel"; + useCountLabel.Size = new Size(66, 13); + useCountLabel.TabIndex = 8; + useCountLabel.Text = "Use Counts"; // // useCountList // - this.useCountList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.useIDColumn, - this.useStackColumn, - this.useFlagColumn}); - this.useCountList.Location = new System.Drawing.Point(104, 126); - this.useCountList.Name = "useCountList"; - this.useCountList.Size = new System.Drawing.Size(199, 97); - this.useCountList.TabIndex = 7; - this.useCountList.UseCompatibleStateImageBehavior = false; - this.useCountList.View = System.Windows.Forms.View.Details; + useCountList.Columns.AddRange(new ColumnHeader[] { useIDColumn, useStackColumn, useFlagColumn }); + useCountList.Location = new Point(104, 126); + useCountList.Name = "useCountList"; + useCountList.Size = new Size(199, 97); + useCountList.TabIndex = 7; + useCountList.UseCompatibleStateImageBehavior = false; + useCountList.View = View.Details; // // useIDColumn // - this.useIDColumn.Text = "Object"; - this.useIDColumn.Width = 100; + useIDColumn.Text = "Object"; + useIDColumn.Width = 100; // // useStackColumn // - this.useStackColumn.Text = "Stack"; - this.useStackColumn.Width = 43; + useStackColumn.Text = "Stack"; + useStackColumn.Width = 43; // // useFlagColumn // - this.useFlagColumn.Text = "Flag"; - this.useFlagColumn.Width = 43; + useFlagColumn.Text = "Flag"; + useFlagColumn.Width = 43; // // motiveChangeLabel // - this.motiveChangeLabel.AutoSize = true; - this.motiveChangeLabel.Location = new System.Drawing.Point(309, 112); - this.motiveChangeLabel.Name = "motiveChangeLabel"; - this.motiveChangeLabel.Size = new System.Drawing.Size(84, 13); - this.motiveChangeLabel.TabIndex = 6; - this.motiveChangeLabel.Text = "Motive Changes"; + motiveChangeLabel.AutoSize = true; + motiveChangeLabel.Location = new Point(309, 112); + motiveChangeLabel.Name = "motiveChangeLabel"; + motiveChangeLabel.Size = new Size(90, 13); + motiveChangeLabel.TabIndex = 6; + motiveChangeLabel.Text = "Motive Changes"; // // motiveChangeList // - this.motiveChangeList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.motiveIDColumn, - this.motiveDeltaColumn, - this.motiveStopColumn}); - this.motiveChangeList.Location = new System.Drawing.Point(309, 128); - this.motiveChangeList.Name = "motiveChangeList"; - this.motiveChangeList.Size = new System.Drawing.Size(162, 97); - this.motiveChangeList.TabIndex = 5; - this.motiveChangeList.UseCompatibleStateImageBehavior = false; - this.motiveChangeList.View = System.Windows.Forms.View.Details; + motiveChangeList.Columns.AddRange(new ColumnHeader[] { motiveIDColumn, motiveDeltaColumn, motiveStopColumn }); + motiveChangeList.Location = new Point(309, 128); + motiveChangeList.Name = "motiveChangeList"; + motiveChangeList.Size = new Size(162, 97); + motiveChangeList.TabIndex = 5; + motiveChangeList.UseCompatibleStateImageBehavior = false; + motiveChangeList.View = View.Details; // // motiveIDColumn // - this.motiveIDColumn.Text = "Motive"; + motiveIDColumn.Text = "Motive"; // // motiveDeltaColumn // - this.motiveDeltaColumn.Text = "Delta"; - this.motiveDeltaColumn.Width = 40; + motiveDeltaColumn.Text = "Delta"; + motiveDeltaColumn.Width = 40; // // motiveStopColumn // - this.motiveStopColumn.Text = "Stop"; - this.motiveStopColumn.Width = 40; + motiveStopColumn.Text = "Stop"; + motiveStopColumn.Width = 40; // // queueList // - this.queueList.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.queueList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { - this.queueNullHeader, - this.queueUIDHeader, - this.queueCallerHeader, - this.queueTargetHeader, - this.queueIconHeader, - this.queueTTAHeader, - this.queueArgsHeader, - this.queuePriorityHeader, - this.queueTreeHeader, - this.queueAttenuationHeader, - this.queueFlagsHeader}); - this.queueList.Items.AddRange(new System.Windows.Forms.ListViewItem[] { - listViewItem1}); - this.queueList.Location = new System.Drawing.Point(6, 237); - this.queueList.Name = "queueList"; - this.queueList.Size = new System.Drawing.Size(577, 113); - this.queueList.TabIndex = 3; - this.queueList.UseCompatibleStateImageBehavior = false; - this.queueList.View = System.Windows.Forms.View.Details; + queueList.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + queueList.Columns.AddRange(new ColumnHeader[] { queueNullHeader, queueUIDHeader, queueCallerHeader, queueTargetHeader, queueIconHeader, queueTTAHeader, queueArgsHeader, queuePriorityHeader, queueTreeHeader, queueAttenuationHeader, queueFlagsHeader }); + queueList.Items.AddRange(new ListViewItem[] { listViewItem1 }); + queueList.Location = new Point(6, 237); + queueList.Name = "queueList"; + queueList.Size = new Size(577, 113); + queueList.TabIndex = 3; + queueList.UseCompatibleStateImageBehavior = false; + queueList.View = View.Details; // // queueNullHeader // - this.queueNullHeader.Text = "?"; - this.queueNullHeader.Width = 20; + queueNullHeader.Text = "?"; + queueNullHeader.Width = 20; // // queueUIDHeader // - this.queueUIDHeader.Text = "UID"; - this.queueUIDHeader.Width = 43; + queueUIDHeader.Text = "UID"; + queueUIDHeader.Width = 43; // // queueCallerHeader // - this.queueCallerHeader.Text = "Caller"; - this.queueCallerHeader.Width = 43; + queueCallerHeader.Text = "Caller"; + queueCallerHeader.Width = 43; // // queueTargetHeader // - this.queueTargetHeader.Text = "Target"; - this.queueTargetHeader.Width = 110; + queueTargetHeader.Text = "Target"; + queueTargetHeader.Width = 110; // // queueIconHeader // - this.queueIconHeader.Text = "Icon"; - this.queueIconHeader.Width = 43; + queueIconHeader.Text = "Icon"; + queueIconHeader.Width = 43; // // queueTTAHeader // - this.queueTTAHeader.Text = "TTA#"; - this.queueTTAHeader.Width = 43; + queueTTAHeader.Text = "TTA#"; + queueTTAHeader.Width = 43; // // queueArgsHeader // - this.queueArgsHeader.Text = "Args"; + queueArgsHeader.Text = "Args"; // // queuePriorityHeader // - this.queuePriorityHeader.Text = "Priority"; - this.queuePriorityHeader.Width = 43; + queuePriorityHeader.Text = "Priority"; + queuePriorityHeader.Width = 43; // // queueTreeHeader // - this.queueTreeHeader.Text = "Tree#"; - this.queueTreeHeader.Width = 43; + queueTreeHeader.Text = "Tree#"; + queueTreeHeader.Width = 43; // // queueAttenuationHeader // - this.queueAttenuationHeader.Text = "Attenuation"; - this.queueAttenuationHeader.Width = 66; + queueAttenuationHeader.Text = "Attenuation"; + queueAttenuationHeader.Width = 66; // // queueFlagsHeader // - this.queueFlagsHeader.Text = "Flags"; - this.queueFlagsHeader.Width = 43; + queueFlagsHeader.Text = "Flags"; + queueFlagsHeader.Width = 43; // // floatsLabel // - this.floatsLabel.AutoSize = true; - this.floatsLabel.Location = new System.Drawing.Point(65, 21); - this.floatsLabel.Name = "floatsLabel"; - this.floatsLabel.Size = new System.Drawing.Size(35, 13); - this.floatsLabel.TabIndex = 2; - this.floatsLabel.Text = "Floats"; + floatsLabel.AutoSize = true; + floatsLabel.Location = new Point(65, 21); + floatsLabel.Name = "floatsLabel"; + floatsLabel.Size = new Size(38, 13); + floatsLabel.TabIndex = 2; + floatsLabel.Text = "Floats"; // // floatsList // - this.floatsList.FormattingEnabled = true; - this.floatsList.Location = new System.Drawing.Point(6, 37); - this.floatsList.Name = "floatsList"; - this.floatsList.Size = new System.Drawing.Size(92, 186); - this.floatsList.TabIndex = 1; + floatsList.FormattingEnabled = true; + floatsList.Location = new Point(6, 37); + floatsList.Name = "floatsList"; + floatsList.Size = new Size(92, 186); + floatsList.TabIndex = 1; // // updatedLabel // - this.updatedLabel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); - this.updatedLabel.AutoSize = true; - this.updatedLabel.Location = new System.Drawing.Point(12, 379); - this.updatedLabel.Name = "updatedLabel"; - this.updatedLabel.Size = new System.Drawing.Size(119, 13); - this.updatedLabel.TabIndex = 4; - this.updatedLabel.Text = "Last Updated: 21:14:00"; + updatedLabel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left; + updatedLabel.AutoSize = true; + updatedLabel.Location = new Point(12, 379); + updatedLabel.Name = "updatedLabel"; + updatedLabel.Size = new Size(123, 13); + updatedLabel.TabIndex = 4; + updatedLabel.Text = "Last Updated: 21:14:00"; // // HouseSpy // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(756, 401); - this.Controls.Add(this.updatedLabel); - this.Controls.Add(this.personBox); - this.Controls.Add(this.peopleLabel); - this.Controls.Add(this.objectList); - this.Controls.Add(this.menuStrip1); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MainMenuStrip = this.menuStrip1; - this.Name = "HouseSpy"; - this.Text = "House Spy"; - this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.HouseSpy_FormClosing); - this.menuStrip1.ResumeLayout(false); - this.menuStrip1.PerformLayout(); - this.personBox.ResumeLayout(false); - this.personBox.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); + AutoScaleDimensions = new SizeF(96F, 96F); + AutoScaleMode = AutoScaleMode.Dpi; + ClientSize = new Size(756, 401); + Controls.Add(updatedLabel); + Controls.Add(personBox); + Controls.Add(peopleLabel); + Controls.Add(objectList); + Controls.Add(menuStrip1); + Icon = (Icon)resources.GetObject("$this.Icon"); + MainMenuStrip = menuStrip1; + Name = "HouseSpy"; + Text = "House Spy"; + FormClosing += HouseSpy_FormClosing; + menuStrip1.ResumeLayout(false); + menuStrip1.PerformLayout(); + personBox.ResumeLayout(false); + personBox.PerformLayout(); + ResumeLayout(false); + PerformLayout(); } diff --git a/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.slnx b/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.slnx new file mode 100644 index 000000000..e556022d3 --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.slnx @@ -0,0 +1,6 @@ + + + + + + diff --git a/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.wixproj b/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.wixproj new file mode 100644 index 000000000..394c185dc --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/FSO.Installer.Windows.wixproj @@ -0,0 +1,10 @@ + + + wix7 + + + + + + + \ No newline at end of file diff --git a/TSOClient/FSO.Installer.Windows/Folders.wxs b/TSOClient/FSO.Installer.Windows/Folders.wxs new file mode 100644 index 000000000..1278bb610 --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/Folders.wxs @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/TSOClient/FSO.Installer.Windows/FreeSOClient.wxs b/TSOClient/FSO.Installer.Windows/FreeSOClient.wxs new file mode 100644 index 000000000..c2bff1c0f --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/FreeSOClient.wxs @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TSOClient/FSO.Installer.Windows/Package.en-us.wxl b/TSOClient/FSO.Installer.Windows/Package.en-us.wxl new file mode 100644 index 000000000..7fa02fa55 --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/Package.en-us.wxl @@ -0,0 +1,8 @@ + + + + + + diff --git a/TSOClient/FSO.Installer.Windows/Package.wxs b/TSOClient/FSO.Installer.Windows/Package.wxs new file mode 100644 index 000000000..4f6d74f1e --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/Package.wxs @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TSOClient/FSO.Installer.Windows/dialog.jpg b/TSOClient/FSO.Installer.Windows/dialog.jpg new file mode 100644 index 000000000..984cf2be5 Binary files /dev/null and b/TSOClient/FSO.Installer.Windows/dialog.jpg differ diff --git a/TSOClient/FSO.Installer.Windows/header.jpg b/TSOClient/FSO.Installer.Windows/header.jpg new file mode 100644 index 000000000..fd43a424c Binary files /dev/null and b/TSOClient/FSO.Installer.Windows/header.jpg differ diff --git a/TSOClient/FSO.Installer.Windows/ico.png b/TSOClient/FSO.Installer.Windows/ico.png new file mode 100644 index 000000000..caa6413a3 Binary files /dev/null and b/TSOClient/FSO.Installer.Windows/ico.png differ diff --git a/TSOClient/FSO.Installer.Windows/license.rtf b/TSOClient/FSO.Installer.Windows/license.rtf new file mode 100644 index 000000000..5b3c3ee81 --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/license.rtf @@ -0,0 +1,839 @@ +{\rtf1\ansi\deff3\adeflang1025 +{\fonttbl{\f0\froman\fprq2\fcharset0 Times New Roman;}{\f1\froman\fprq2\fcharset2 Symbol;}{\f2\fswiss\fprq2\fcharset0 Arial;}{\f3\froman\fprq2\fcharset0 Liberation Serif{\*\falt Times New Roman};}{\f4\fswiss\fprq2\fcharset0 Liberation Sans{\*\falt Arial};}{\f5\fmodern\fprq1\fcharset0 Liberation Mono{\*\falt Courier New};}{\f6\fnil\fprq2\fcharset0 AR PL SungtiL GB;}{\f7\fnil\fprq2\fcharset0 Noto Sans Devanagari;}{\f8\fswiss\fprq0\fcharset128 Noto Sans Devanagari;}} +{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0;\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;} +{\stylesheet{\s0\snext0\nowidctlpar\hyphpar0\ltrpar\cf0\rtlch\af7\afs24\alang1081\ltrch\hich\af3\afs24\alang1033\dbch\af6\langfe2052\loch\f3\fs24\lang1033 Normal;} +{\s15\sbasedon0\snext16\sb240\sa120\keepn\rtlch\af7\afs28\ltrch\hich\af4\afs28\dbch\af6\loch\f4\fs28 Heading;} +{\s16\sbasedon0\snext16\sl276\slmult1\sb0\sa140 Body Text;} +{\s17\sbasedon16\snext17\rtlch\af8\ltrch List;} +{\s18\sbasedon0\snext18\sb120\sa120\noline\rtlch\af8\afs24\ai\ltrch\fs24\i caption;} +{\s19\sbasedon0\snext19\noline\rtlch\af8\ltrch Index;} +{\s20\sbasedon0\snext20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14 Preformatted Text;} +}{\*\listtable{\list\listtemplateid1 +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0} +{\listlevel\levelnfc255\leveljc0\levelstartat1\levelfollow2{\leveltext \'00;}{\levelnumbers;}\fi0\li0}\listid1} +}{\listoverridetable{\listoverride\listid1\listoverridecount0\ls1}}{\*\generator LibreOffice/26.2.3.2$Linux_X86_64 LibreOffice_project/620$Build-2}{\info{\creatim\yr0\mo0\dy0\hr0\min0}{\revtim\yr0\mo0\dy0\hr0\min0}{\printim\yr0\mo0\dy0\hr0\min0}}{\*\userprops}\deftab709 +\hyphauto1\viewscale100\formshade\nobrkwrptbl\paperh16838\paperw11906\margl1134\margr1134\margt1134\margb1134\sectd\sbknone\sftnnar\saftnnrlc\sectunlocked1\pgwsxn11906\pghsxn16838\marglsxn1134\margrsxn1134\margtsxn1134\margbsxn1134\ftnbj\ftnstart1\ftnrstcont\ftnnar\fet\aftnrstcont\aftnstart1\aftnnrlc +{\*\ftnsep\chftnsep}\pgndec\pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Mozilla Public License Version 2.0} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +==================================} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1. Definitions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +--------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.1. "Contributor"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means each individual or legal entity that creates, contributes to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +the creation of, or owns Covered Software.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.2. "Contributor Version"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means the combination of the Contributions of others (if any) used} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +by a Contributor and that particular Contributor's Contribution.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.3. "Contribution"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means Covered Software of a particular Contributor.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.4. "Covered Software"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means Source Code Form to which the initial Contributor has attached} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +the notice in Exhibit A, the Executable Form of such Source Code} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Form, and Modifications of such Source Code Form, in each case} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +including portions thereof.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.5. "Incompatible With Secondary Licenses"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +(a) that the initial Contributor has attached the notice described} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +in Exhibit B to the Covered Software; or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +(b) that the Covered Software was made available under the terms of} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +version 1.1 or earlier of the License, but not also under the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +terms of a Secondary License.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.6. "Executable Form"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means any form of the work other than Source Code Form.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.7. "Larger Work"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means a work that combines Covered Software with other material, in} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +a separate file or files, that is not Covered Software.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.8. "License"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means this document.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.9. "Licensable"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means having the right to grant, to the maximum extent possible,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +whether at the time of the initial grant or subsequently, any and} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +all of the rights conveyed by this License.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.10. "Modifications"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means any of the following:} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +(a) any file in Source Code Form that results from an addition to,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +deletion from, or modification of the contents of Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Software; or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +(b) any new file in Source Code Form that contains any Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Software.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.11. "Patent Claims" of a Contributor} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means any patent claim(s), including without limitation, method,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +process, and apparatus claims, in any patent Licensable by such} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Contributor that would be infringed, but for the grant of the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +License, by the making, using, selling, offering for sale, having} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +made, import, or transfer of either its Contributions or its} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Contributor Version.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.12. "Secondary License"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means either the GNU General Public License, Version 2.0, the GNU} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Lesser General Public License, Version 2.1, the GNU Affero General} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Public License, Version 3.0, or any later versions of those} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +licenses.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.13. "Source Code Form"} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means the form of the work preferred for making modifications.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +1.14. "You" (or "Your")} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +means an individual or a legal entity exercising rights under this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +License. For legal entities, "You" includes any entity that} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +controls, is controlled by, or is under common control with You. For} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +purposes of this definition, "control" means (a) the power, direct} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +or indirect, to cause the direction or management of such entity,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +whether by contract or otherwise, or (b) ownership of more than} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +fifty percent (50%) of the outstanding shares or beneficial} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +ownership of such entity.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2. License Grants and Conditions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +--------------------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.1. Grants} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Each Contributor hereby grants You a world-wide, royalty-free,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +non-exclusive license:} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(a) under intellectual property rights (other than patent or trademark)} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Licensable by such Contributor to use, reproduce, make available,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +modify, display, perform, distribute, and otherwise exploit its} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Contributions, either on an unmodified basis, with Modifications, or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +as part of a Larger Work; and} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(b) under Patent Claims of such Contributor to make, use, sell, offer} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +for sale, have made, import, and otherwise transfer either its} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Contributions or its Contributor Version.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.2. Effective Date} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +The licenses granted in Section 2.1 with respect to any Contribution} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +become effective for each Contribution on the date the Contributor first} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +distributes such Contribution.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.3. Limitations on Grant Scope} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +The licenses granted in this Section 2 are the only rights granted under} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +this License. No additional rights or licenses will be implied from the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +distribution or licensing of Covered Software under this License.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Notwithstanding Section 2.1(b) above, no patent license is granted by a} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Contributor:} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(a) for any code that a Contributor has removed from Covered Software;} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(b) for infringements caused by: (i) Your and any other third party's} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +modifications of Covered Software, or (ii) the combination of its} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Contributions with other software (except as part of its Contributor} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Version); or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(c) under Patent Claims infringed by Covered Software in the absence of} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +its Contributions.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +This License does not grant any rights in the trademarks, service marks,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +or logos of any Contributor (except as may be necessary to comply with} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the notice requirements in Section 3.4).} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.4. Subsequent Licenses} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +No Contributor makes additional grants as a result of Your choice to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +distribute the Covered Software under a subsequent version of this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +License (see Section 10.2) or under the terms of a Secondary License (if} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +permitted under the terms of Section 3.3).} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.5. Representation} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Each Contributor represents that the Contributor believes its} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Contributions are its original creation(s) or it has sufficient rights} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +to grant the rights to its Contributions conveyed by this License.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.6. Fair Use} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +This License is not intended to limit any rights You have under} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +applicable copyright doctrines of fair use, fair dealing, or other} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +equivalents.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.7. Conditions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +in Section 2.1.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3. Responsibilities} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +-------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3.1. Distribution of Source Form} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +All distribution of Covered Software in Source Code Form, including any} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Modifications that You create or to which You contribute, must be under} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the terms of this License. You must inform recipients that the Source} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Code Form of the Covered Software is governed by the terms of this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +License, and how they can obtain a copy of this License. You may not} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +attempt to alter or restrict the recipients' rights in the Source Code} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Form.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3.2. Distribution of Executable Form} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +If You distribute Covered Software in Executable Form then:} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(a) such Covered Software must also be made available in Source Code} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Form, as described in Section 3.1, and You must inform recipients of} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +the Executable Form how they can obtain a copy of such Source Code} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +Form by reasonable means in a timely manner, at a charge no more} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +than the cost of distribution to the recipient; and} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(b) You may distribute such Executable Form under the terms of this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +License, or sublicense it under different terms, provided that the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +license for the Executable Form does not attempt to limit or alter} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +the recipients' rights in the Source Code Form under this License.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3.3. Distribution of a Larger Work} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You may create and distribute a Larger Work under terms of Your choice,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +provided that You also comply with the requirements of this License for} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the Covered Software. If the Larger Work is a combination of Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Software with a work governed by one or more Secondary Licenses, and the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Covered Software is not Incompatible With Secondary Licenses, this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +License permits You to additionally distribute such Covered Software} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +under the terms of such Secondary License(s), so that the recipient of} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the Larger Work may, at their option, further distribute the Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Software under the terms of either this License or such Secondary} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +License(s).} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3.4. Notices} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You may not remove or alter the substance of any license notices} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +(including copyright notices, patent notices, disclaimers of warranty,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +or limitations of liability) contained within the Source Code Form of} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the Covered Software, except that You may alter any license notices to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the extent required to remedy known factual inaccuracies.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +3.5. Application of Additional Terms} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You may choose to offer, and to charge a fee for, warranty, support,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +indemnity or liability obligations to one or more recipients of Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Software. However, You may do so only on Your own behalf, and not on} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +behalf of any Contributor. You must make it absolutely clear that any} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +such warranty, support, indemnity, or liability obligation is offered by} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You alone, and You hereby agree to indemnify every Contributor for any} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +liability incurred by such Contributor as a result of warranty, support,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +indemnity or liability terms You offer. You may include additional} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +disclaimers of warranty and limitations of liability specific to any} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +jurisdiction.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +4. Inability to Comply Due to Statute or Regulation} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +---------------------------------------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +If it is impossible for You to comply with any of the terms of this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +License with respect to some or all of the Covered Software due to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +statute, judicial order, or regulation then You must: (a) comply with} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +the terms of this License to the maximum extent possible; and (b)} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +describe the limitations and the code they affect. Such description must} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +be placed in a text file included with all distributions of the Covered} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Software under this License. Except to the extent prohibited by statute} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +or regulation, such description must be sufficiently detailed for a} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +recipient of ordinary skill to be able to understand it.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +5. Termination} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +--------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +5.1. The rights granted under this License will terminate automatically} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +if You fail to comply with any of its terms. However, if You become} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +compliant, then the rights granted under this License from a particular} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Contributor are reinstated (a) provisionally, unless and until such} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Contributor explicitly and finally terminates Your grants, and (b) on an} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +ongoing basis, if such Contributor fails to notify You of the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +non-compliance by some reasonable means prior to 60 days after You have} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +come back into compliance. Moreover, Your grants from a particular} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Contributor are reinstated on an ongoing basis if such Contributor} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +notifies You of the non-compliance by some reasonable means, this is the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +first time You have received notice of non-compliance with this License} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +from such Contributor, and You become compliant prior to 30 days after} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Your receipt of the notice.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +5.2. If You initiate litigation against any entity by asserting a patent} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +infringement claim (excluding declaratory judgment actions,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +counter-claims, and cross-claims) alleging that a Contributor Version} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +directly or indirectly infringes any patent, then the rights granted to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You by any and all Contributors for the Covered Software under Section} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +2.1 of this License shall terminate.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +5.3. In the event of termination under Sections 5.1 or 5.2 above, all} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +end user license agreements (excluding distributors and resellers) which} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +have been validly granted by You or Your distributors under this License} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +prior to termination shall survive termination.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +************************************************************************} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* 6. Disclaimer of Warranty *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* ------------------------- *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* Covered Software is provided under this License on an "as is" *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* basis, without warranty of any kind, either expressed, implied, or *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* statutory, including, without limitation, warranties that the *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* Covered Software is free of defects, merchantable, fit for a *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* particular purpose or non-infringing. The entire risk as to the *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* quality and performance of the Covered Software is with You. *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* Should any Covered Software prove defective in any respect, You *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* (not any Contributor) assume the cost of any necessary servicing, *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* repair, or correction. This disclaimer of warranty constitutes an *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* essential part of this License. No use of any Covered Software is *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* authorized under this License except under this disclaimer. *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +************************************************************************} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +************************************************************************} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* 7. Limitation of Liability *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* -------------------------- *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* Under no circumstances and under no legal theory, whether tort *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* (including negligence), contract, or otherwise, shall any *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* Contributor, or anyone who distributes Covered Software as *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* permitted above, be liable to You for any direct, indirect, *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* special, incidental, or consequential damages of any character *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* including, without limitation, damages for lost profits, loss of *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* goodwill, work stoppage, computer failure or malfunction, or any *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* and all other commercial damages or losses, even if such party *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* shall have been informed of the possibility of such damages. This *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* limitation of liability shall not apply to liability for death or *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* personal injury resulting from such party's negligence to the *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* extent applicable law prohibits such limitation. Some *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* jurisdictions do not allow the exclusion or limitation of *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* incidental or consequential damages, so this exclusion and *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* limitation may not apply to You. *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +* *} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +************************************************************************} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +8. Litigation} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +-------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Any litigation relating to this License may be brought only in the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +courts of a jurisdiction where the defendant maintains its principal} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +place of business and such litigation shall be governed by laws of that} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +jurisdiction, without reference to its conflict-of-law provisions.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Nothing in this Section shall prevent a party's ability to bring} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +cross-claims or counter-claims.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +9. Miscellaneous} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +----------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +This License represents the complete agreement concerning the subject} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +matter hereof. If any provision of this License is held to be} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +unenforceable, such provision shall be reformed only to the extent} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +necessary to make it enforceable. Any law or regulation which provides} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +that the language of a contract shall be construed against the drafter} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +shall not be used to construe this License against a Contributor.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10. Versions of the License} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +---------------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10.1. New Versions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Mozilla Foundation is the license steward. Except as provided in Section} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10.3, no one other than the license steward has the right to modify or} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +publish new versions of this License. Each version will be given a} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +distinguishing version number.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10.2. Effect of New Versions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You may distribute the Covered Software under the terms of the version} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +of the License under which You originally received the Covered Software,} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +or under the terms of any subsequent version published by the license} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +steward.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10.3. Modified Versions} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +If you create software not governed by this License, and you want to} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +create a new license for such software, you may create and use a} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +modified version of this License if you rename the license and remove} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +any references to the name of the license steward (except to note that} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +such modified license differs from this License).} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +10.4. Distributing Source Code Form that is Incompatible With Secondary} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Licenses} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +If You choose to distribute Source Code Form that is Incompatible With} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Secondary Licenses under the terms of this version of the License, the} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +notice described in Exhibit B of this License must be attached.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Exhibit A - Source Code Form License Notice} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +-------------------------------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +This Source Code Form is subject to the terms of the Mozilla Public} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +License, v. 2.0. If a copy of the MPL was not distributed with this} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +file, You can obtain one at https://mozilla.org/MPL/2.0/.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +If it is not possible or desirable to put the notice in a particular} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +file, then You may include the notice in a location (such as a LICENSE} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +file in a relevant directory) where a recipient would be likely to look} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +for such a notice.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +You may add additional accurate notices of copyright ownership.} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +Exhibit B - "Incompatible With Secondary Licenses" Notice} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ +---------------------------------------------------------} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar + +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +This Source Code Form is "Incompatible With Secondary Licenses", as} +\par \pard\plain \s20\sb0\sa0\rtlch\af5\afs14\ltrch\hich\af5\afs14\dbch\af5\loch\f5\fs14\ql\sb0\sa0\ltrpar{ + }{ +defined by the Mozilla Public License, v. 2.0.} +\par } \ No newline at end of file diff --git a/TSOClient/FSO.Installer.Windows/license.txt b/TSOClient/FSO.Installer.Windows/license.txt new file mode 100644 index 000000000..d0a1fa148 --- /dev/null +++ b/TSOClient/FSO.Installer.Windows/license.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/TSOClient/FSO.Patcher.Unix/CLIPatcher.cs b/TSOClient/FSO.Patcher.Unix/CLIPatcher.cs new file mode 100644 index 000000000..0a2a3b654 --- /dev/null +++ b/TSOClient/FSO.Patcher.Unix/CLIPatcher.cs @@ -0,0 +1,341 @@ +using System.Diagnostics; +using System.IO.Compression; +using System.Net; +using Mono.Unix; + +namespace FSO.Patcher.Unix +{ + public class CLIPatcher + { + private string[] Args; + private List Path; + private int PathProgress = 0; + private ReversiblePatcher CurrentPatcher; + private bool AllowMonogameMod; + private bool CleanPatch; + public CLIPatcher(List extractPath, string[] args) + { + Path = extractPath; + Args = args; + } + + private void FSONotClosed() + { + Console.WriteLine("Could not update FreeSO as write access could not be gained to the game files. Try running update.exe as an administrator."); + Cleanup(); + Environment.Exit(0); + } + + private void FileMissing(string path) + { + Console.WriteLine($"A file has been removed while advancing through the update chain ({path}). The update must now be aborted."); + Cleanup(); + Environment.Exit(0); + } + + private void FileCorrupt(string path) + { + Console.WriteLine($"An update archive was corrupt({ path}). The update must now be aborted."); + Cleanup(); + Environment.Exit(0); + } + + private void Cleanup() + { + try + { + var fsoExe = GetFreeSOName(); + if (File.Exists(fsoExe+".old")) + File.Move(fsoExe+".old", fsoExe); + } + catch (Exception) + { + + } + } + + private async Task AdvanceExtract() + { + if (PathProgress >= Path.Count) + { + //done + StartFreeSO(); + } + else + { + //extract next zip + var path = Path[PathProgress++]; + Console.WriteLine($"===== Extracting {path} ({PathProgress}/{Path.Count}) ====="); + if (File.Exists(path)) + { + ZipArchive archive; + try + { + archive = ZipFile.OpenRead(path); + } catch (Exception) + { + FileCorrupt(path); + return; + } + var patcher = new ReversiblePatcher(archive); + if (path.Contains("extra") && AllowMonogameMod) + { + patcher.IgnoreFiles.RemoveWhere(x => x.Contains("MonoGame")); + } + CurrentPatcher = patcher; + patcher.OnStatus += Patcher_OnStatus; + if (PathProgress == 1) + { + //first patch + if (CleanPatch && Directory.Exists("Content/Patch/")) + { + foreach (var file in Directory.GetFiles("Content/Patch/")) + { + //delete any stray patch files. Don't delete user or subfolders (eg. translations) because they might be important + try + { + File.Delete(file); + } + catch (Exception) + { + + } + } + } + var worked = await patcher.AttemptRename(8); + if (!worked) + { + PathProgress--; + FSONotClosed(); + return; + } + } + while (patcher.ToExtract.Count > 0) + { + await patcher.AttemptExtract(); + var remaining = patcher.GetIncompleteFiles(); + if (remaining.Count > 0) + { + //dilemma! + var arc = await ShowErrors(remaining); + if (arc == 0) + { + //abort. + patcher.Revert(); + Cleanup(); + StartFreeSO(); + return; + } + else if (arc == 1) + { + //retry + } + else if (arc == 2) + { + //ignore + patcher.Final(); + File.Delete(path); + break; + } + } + else + { + Console.WriteLine($"===== Completed {path} ====="); + patcher.Final(); + File.Delete(path); + await AdvanceExtract(); + } + } + } + else + { + FileMissing(path); + } + } + } + + private async Task ShowErrors(List remaining) + { + var dialogResponse = new TaskCompletionSource(); + string fileList; + if (remaining.Count > 10) + { + fileList = string.Join("\r\n", remaining.Take(9)); + fileList += $"\r\n ...and {remaining.Count - 9} more."; + } + else fileList = string.Join("\r\n", remaining); + + string errorText = "Couldn't write one or more files. Make sure you are not running an instance of FreeSO! \r\nFiles:\r\n\r\n" + fileList; + + Console.WriteLine(errorText); + + try + { + File.WriteAllText("updateError.txt", errorText); + } + catch + { + // Not urgent if we can't write the error message. + } + + return 0; + } + + + private void Patcher_OnStatus(string message, float percent) + { + Console.WriteLine(message); + } + + private string GetFreeSOName() + { + if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) { + return "FreeSO"; + } else { + return "FreeSO.exe"; + } + } + + private bool ChmodX(string path) { + try + { + var fileInfo = new UnixFileInfo(path); + + fileInfo.FileAccessPermissions = fileInfo.FileAccessPermissions | FileAccessPermissions.UserExecute | FileAccessPermissions.GroupExecute | FileAccessPermissions.OtherExecute; + + fileInfo.Refresh(); + + return true; + } + catch + { + return false; + } + } + + private void ChmodAllExes(string basePath) + { + var files = Directory.GetFiles(basePath); + + foreach (var file in files) + { + if (System.IO.Path.GetFileName(file) == "update") + { + continue; + } + + var ext = System.IO.Path.GetExtension(file); + + if (ext.Length == 0 || ext == ".dylib") + { + if (!ChmodX(file)) + { + Console.WriteLine($" ! Failed to chmod '{file}' - FreeSO may fail to launch."); + } + } + } + } + + public void StartFreeSO() + { + var fsoExe = GetFreeSOName(); + if (!File.Exists(fsoExe)) + { + if (File.Exists(fsoExe + ".old")) + { + File.Copy(fsoExe + ".old", fsoExe, true); + } + else + { + Console.WriteLine($"FreeSO is not present. If you want to redownload the latest version of FreeSO, run with the --client argument."); + return; + } + } + + if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) + { + Console.WriteLine($"===== Starting FreeSO... Please wait! ====="); + ChmodAllExes("./"); + + if (OperatingSystem.IsMacOS()) + { + var args = string.Join(" ", Args); + var startArgs = new ProcessStartInfo("open", $"../../ --args " + args); + startArgs.UseShellExecute = false; + System.Diagnostics.Process.Start(startArgs); + } + else + { + var args = string.Join(" ", Args); + var startArgs = new ProcessStartInfo(fsoExe, args); + startArgs.UseShellExecute = false; + System.Diagnostics.Process.Start(startArgs); + } + } + else + { + System.Diagnostics.Process.Start(fsoExe, string.Join(" ", Args)); + } + Environment.Exit(0); + } + + public async Task DownloadAndAdvance() + { + Console.WriteLine("Downloading archives:"); + //download the file then set it as our path + var client = new WebClient(); + Directory.CreateDirectory("PatchFiles/"); + + int i = 0; + foreach (var file in ToDownload) { + try + { + Console.WriteLine($"Downloading {file}..."); + await client.DownloadFileTaskAsync(new Uri(file), $"PatchFiles/extra{i}.zip"); + Path.Add($"PatchFiles/extra{i}.zip"); + } + catch (Exception e) + { + Console.WriteLine($"Could not download {file}: {e.Message}"); + } + i++; + } + await AdvanceExtract(); + } + + public List ToDownload = new List(); + + public void Begin() + { + Console.WriteLine("===== FreeSO Patcher CLI - 2026 ====="); + Console.WriteLine(Path.Count + " update(s) to apply."); + + if (Args.Contains("--client")) + { + Console.WriteLine("FreeSO client requested. Downloading from freeso.org."); + ToDownload.Add("https://fso-archive-beta.riperiperi.workers.dev/"); + } + + if (ToDownload.Count > 0) + { + CleanPatch = true; + Task.Run(() => DownloadAndAdvance()).Wait(); + } + else { + CleanPatch = File.Exists("PatchFiles/clean.txt"); + if (CleanPatch) + { + try + { + File.Delete("PatchFiles/clean.txt"); + } + catch + { + + } + } + Task.Run(() => AdvanceExtract()).Wait(); + } + } + } +} diff --git a/TSOClient/FSO.Patcher.Unix/FSO.Patcher.Unix.csproj b/TSOClient/FSO.Patcher.Unix/FSO.Patcher.Unix.csproj new file mode 100644 index 000000000..22b3d1b4a --- /dev/null +++ b/TSOClient/FSO.Patcher.Unix/FSO.Patcher.Unix.csproj @@ -0,0 +1,27 @@ + + + + Exe + net9.0 + enable + enable + update + patcher.ico + true + true + true + + + + + + + + + + + + + + + diff --git a/TSOClient/FSO.Patcher.Unix/Program.cs b/TSOClient/FSO.Patcher.Unix/Program.cs new file mode 100644 index 000000000..f7d774a30 --- /dev/null +++ b/TSOClient/FSO.Patcher.Unix/Program.cs @@ -0,0 +1,33 @@ +using System.Text.RegularExpressions; + +namespace FSO.Patcher.Unix +{ + internal class Program + { + static void Main(string[] args) + { + var path = UpdatePath(); + //console only application + var patcher = new CLIPatcher(path, args); + patcher.Begin(); + } + + static List UpdatePath() + { + try + { + var files = Directory.GetFiles("PatchFiles/"); + return files.Where(x => x.EndsWith(".zip") && !x.EndsWith("patch.zip")).OrderBy(x => { + var match = Regex.Match(x, @"\d+").Value ?? "200"; + if (match == "") match = "200"; + return int.Parse(match); + } + ).ToList(); + } + catch (Exception) + { + return new List(); + } + } + } +} diff --git a/TSOClient/FSO.Patcher.Unix/ReversiblePatcher.cs b/TSOClient/FSO.Patcher.Unix/ReversiblePatcher.cs new file mode 100644 index 000000000..1f1160188 --- /dev/null +++ b/TSOClient/FSO.Patcher.Unix/ReversiblePatcher.cs @@ -0,0 +1,230 @@ +using System.IO.Compression; + +namespace FSO.Patcher.Unix +{ + public class ReversiblePatcher + { + public List FileChanges; + public HashSet Extracted; + public HashSet ToExtract; + public List Errors; + + public ZipArchive Archive; + public event Action OnStatus; + + public int Total; + + public ReversiblePatcher(ZipArchive zip) + { + Archive = zip; + ToExtract = new HashSet(zip.Entries); + Total = ToExtract.Count; + Extracted = new HashSet(); + + try + { + Directory.Delete("updateBackup/", true); + } + catch (Exception) + { + + } + Directory.CreateDirectory("updateBackup/"); + } + + public HashSet IgnoreFiles = new HashSet() + { + //"updater.exe", + "Content/config.ini", + "NLog.config", + "update.pdb", + "delta.json", + "updateError.txt", + + //monogame in the base directory is not used on fso windows, and is manually replaced on unix + "MonoGame.Framework.dll", + "MonoGame.Framework.xml", + + }; + + public static HashSet UnimportantFiles = new HashSet() + { + "discord-rpc.dll", + //runtime stuff - not really necessary to update + "ucrtbase.dll" + }; + + public static HashSet DeferredUpdate = new HashSet() + { + + }; + + private void Status(string message) + { + OnStatus?.Invoke(message, 1f - (ToExtract.Count / (float)Total)); + } + + public List GetIncompleteFiles() + { + return ToExtract.Select(file => file.FullName).Where(name => !UnimportantFiles.Contains(name)).ToList(); + } + + private string RewriteName(string fileName) + { + if (fileName == "update.exe") + { + return "update2.exe"; + } + + if (fileName == "update") + { + return "update2"; + } + + return fileName; + } + + public async Task ExtractEntry(ZipArchiveEntry entry, int tryNum) + { + var name = RewriteName(entry.FullName); + var targPath = Path.Combine("./", name); + Directory.CreateDirectory(Path.GetDirectoryName(targPath)); + try + { + if (File.Exists(targPath) && tryNum == 0) + { + //copy to backup folder + var backupPath = Path.Combine("updateBackup/", targPath); + Directory.CreateDirectory(Path.GetDirectoryName(backupPath)); + File.Copy(targPath, backupPath, true); + } + entry.ExtractToFile(targPath, true); + Status(name + " Extracted..."); + Extracted.Add(targPath); + return true; + } + catch (Exception e) + { + if (e is DirectoryNotFoundException) return true; + if (tryNum++ > 3 || Errors.Count > 4 || UnimportantFiles.Contains(name)) + { + Status($"Could not replace {targPath}!"); + Errors.Add($"{targPath}: {e.Message}"); + return false; + } + else + { + Status($"Waiting for {name} ({tryNum}/4)... {e.ToString()}"); + await Task.Delay(3000); + return await ExtractEntry(entry, tryNum); + } + + } + } + + public static int RENAME_MAX_ATTEMPTS = 5; + + private string GetFreeSOName() + { + if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) { + return "FreeSO"; + } else { + return "FreeSO.exe"; + } + } + + public async Task AttemptRename(int renameRetry) + { + try + { + var fsoExe = GetFreeSOName(); + File.Delete(fsoExe+".old"); + if (File.Exists(fsoExe)) //shouldn't be in use, unless the user has incorrectly renamed and run the freeso executable + File.Move(fsoExe, fsoExe+".old"); + } + catch (Exception) + { + if (renameRetry++ < RENAME_MAX_ATTEMPTS) + { + Status($"Waiting for FreeSO to Close ({renameRetry}/{RENAME_MAX_ATTEMPTS})..."); + await Task.Delay(2000); + return await AttemptRename(renameRetry); + } + else + { + return false; + } + } + return true; + } + + public async Task AttemptExtract() + { + Errors = new List(); + //file being replaced? + var clone = ToExtract.ToList(); + foreach (var entry in clone) + { + if (IgnoreFiles.Contains(entry.FullName)) + { + ToExtract.Remove(entry); + continue; + } + var result = await ExtractEntry(entry, 0); + if (result) + { + ToExtract.Remove(entry); + } + } + } + + public bool Revert() + { + bool success = true; + foreach (var file in Extracted) + { + var backupPath = Path.Combine("updateBackup/", file); + try + { + Status($"Restoring backup for {file}..."); + File.Copy(backupPath, file, true); + } + catch (FileNotFoundException) + { + Status($"Backup for {file} not found, skipping..."); + } + catch (Exception e) + { + Status($"Could not restore backup for {file}: {e.Message}"); + Errors.Add($"{file}: {e.Message}"); + success = false; + } + } + if (success) + { + try + { + Directory.Delete("updateBackup/", true); + } + catch + { + //can't delete backup for some reason. just ignore. + } + } + return success; + } + + public void Final() + { + try + { + Directory.Delete("updateBackup/", true); + } + catch + { + //can't delete backup for some reason. just ignore. + } + Archive.Dispose(); + } + } +} diff --git a/TSOClient/FSO.Patcher.Unix/patcher.ico b/TSOClient/FSO.Patcher.Unix/patcher.ico new file mode 100644 index 000000000..668d683cf Binary files /dev/null and b/TSOClient/FSO.Patcher.Unix/patcher.ico differ diff --git a/TSOClient/FSO.Patcher/App.config b/TSOClient/FSO.Patcher/App.config deleted file mode 100644 index d1428ad71..000000000 --- a/TSOClient/FSO.Patcher/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TSOClient/FSO.Patcher/FSO.Patcher.csproj b/TSOClient/FSO.Patcher/FSO.Patcher.csproj index 00ca7a96f..00b746e60 100644 --- a/TSOClient/FSO.Patcher/FSO.Patcher.csproj +++ b/TSOClient/FSO.Patcher/FSO.Patcher.csproj @@ -1,120 +1,25 @@ - - - + + - Debug - AnyCPU - {4E43CE64-343F-4C53-A055-BBF0F4986A16} + net9.0-windows + enable + disable WinExe Properties FSO.Patcher update - v4.5 512 true - + true + patcher.ico - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - - - - - - - - - - - - - Form - - - FormsPatcher.cs - - - Form - - - Patcher.cs - - - - - - FormsPatcher.cs - - - Patcher.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - + - + + - + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Patcher/Program.cs b/TSOClient/FSO.Patcher/Program.cs index 3f9eec871..0498b0bba 100644 --- a/TSOClient/FSO.Patcher/Program.cs +++ b/TSOClient/FSO.Patcher/Program.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Windows.Forms; +using System.Text.RegularExpressions; namespace FSO.Patcher { diff --git a/TSOClient/FSO.Patcher/Properties/AssemblyInfo.cs b/TSOClient/FSO.Patcher/Properties/AssemblyInfo.cs deleted file mode 100644 index 4f277697b..000000000 --- a/TSOClient/FSO.Patcher/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FreeSO Patcher")] -[assembly: AssemblyDescription("Self-updater application for FreeSO. Extracts artifacts downloaded by FreeSO in order.")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FreeSO Patcher")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("4e43ce64-343f-4c53-a055-bbf0f4986a16")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.1.0.0")] -[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/TSOClient/FSO.Patcher/ReversiblePatcher.cs b/TSOClient/FSO.Patcher/ReversiblePatcher.cs index 2901ad8e7..215374e90 100644 --- a/TSOClient/FSO.Patcher/ReversiblePatcher.cs +++ b/TSOClient/FSO.Patcher/ReversiblePatcher.cs @@ -43,6 +43,7 @@ public ReversiblePatcher(ZipArchive zip) "Content/config.ini", "NLog.config", "update.pdb", + "delta.json", //monogame in the base directory is not used on fso windows, and is manually replaced on unix "MonoGame.Framework.dll", diff --git a/TSOClient/FSO.Server.Api.Core/Api.cs b/TSOClient/FSO.Server.Api.Core/Api.cs index e975f71ee..205543705 100644 --- a/TSOClient/FSO.Server.Api.Core/Api.cs +++ b/TSOClient/FSO.Server.Api.Core/Api.cs @@ -6,6 +6,7 @@ using FSO.Server.Domain; using FSO.Server.Servers.Api.JsonWebToken; using Microsoft.AspNetCore.Http; +using Microsoft.Data.Sqlite; using System; using System.Collections.Specialized; using System.Linq; @@ -42,8 +43,12 @@ public void Init(NameValueCollection appSettings) Config.CDNUrl = appSettings["cdnUrl"]; Config.NFSdir = appSettings["nfsdir"]; Config.UseProxy = bool.Parse(appSettings["useProxy"]); + Config.Name = appSettings["name"] ?? ""; Config.UpdateID = (appSettings["updateID"] == "") ? (int?)null : int.Parse(appSettings["updateID"]); Config.BranchName = appSettings["branchName"] ?? "beta"; + Config.AllOpenable = bool.TryParse(appSettings["allOpenable"], out var allOpenable) && allOpenable; + Config.VersionInfoJson = appSettings["versionInfoJson"]; // May be null + // new smtp config vars if (appSettings["smtpHost"]!=null&& @@ -63,12 +68,23 @@ public void Init(NameValueCollection appSettings) Key = System.Text.UTF8Encoding.UTF8.GetBytes(Config.Secret) }); - DAFactory = new MySqlDAFactory(new Database.DatabaseConfiguration() + var config = new Database.DatabaseConfiguration() { + Engine = appSettings["databaseEngine"] ?? "mysql", ConnectionString = appSettings["connectionString"] - }); + }; + + switch (config.Engine) + { + case "mysql": + DAFactory = new MySqlDAFactory(config); + break; + case "sqlite": + DAFactory = new SqliteDAFactory(config); + break; + } - Shards = new Shards(DAFactory); + Shards = new Shards(DAFactory, null); // TODO: does this need nfs? Shards.AutoUpdate(); } diff --git a/TSOClient/FSO.Server.Api.Core/ApiConfig.cs b/TSOClient/FSO.Server.Api.Core/ApiConfig.cs index b7c851d33..e1c463d52 100644 --- a/TSOClient/FSO.Server.Api.Core/ApiConfig.cs +++ b/TSOClient/FSO.Server.Api.Core/ApiConfig.cs @@ -34,9 +34,14 @@ public class ApiConfig public bool SmtpEnabled { get; set; } public bool UseProxy { get; set; } + public string Name { get; set; } = ""; public int? UpdateID { get; set; } public string BranchName { get; set; } = "dev"; + + public bool AllOpenable { get; set; } + public IUpdateUploader UpdateUploader { get; set; } + public string VersionInfoJson { get; set; } } } \ No newline at end of file diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs index aa52623ac..b44e6f5e4 100644 --- a/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs +++ b/TSOClient/FSO.Server.Api.Core/Controllers/Admin/AdminOAuthController.cs @@ -44,7 +44,7 @@ public IActionResult Post([FromForm] AuthRequest auth) } var authSettings = da.Users.GetAuthenticationSettings(user.user_id); - var isPasswordCorrect = PasswordHasher.Verify(auth.password, new PasswordHash + var isPasswordCorrect = (authSettings == null && ip == "127.0.0.1") || PasswordHasher.Verify(auth.password, new PasswordHash { data = authSettings.data, scheme = authSettings.scheme_class diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/CityJSONController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/CityJSONController.cs index d89d67a99..d486c311c 100644 --- a/TSOClient/FSO.Server.Api.Core/Controllers/CityJSONController.cs +++ b/TSOClient/FSO.Server.Api.Core/Controllers/CityJSONController.cs @@ -18,6 +18,7 @@ public class CityJSONController : ControllerBase [Route("userapi/city/{shardid}/city.json")] public IActionResult Get(int shardid) { + // TODO: support multiple shards var api = Api.INSTANCE; var now = Epoch.Now; diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/InitialConnectController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/InitialConnectController.cs index 900a029ce..131ec9ba5 100644 --- a/TSOClient/FSO.Server.Api.Core/Controllers/InitialConnectController.cs +++ b/TSOClient/FSO.Server.Api.Core/Controllers/InitialConnectController.cs @@ -1,4 +1,5 @@ -using FSO.Server.Api.Core.Utils; +using FSO.Common; +using FSO.Server.Api.Core.Utils; using FSO.Server.Common; using FSO.Server.Protocol.CitySelector; using FSO.Server.Servers.Api.JsonWebToken; @@ -61,19 +62,22 @@ public IActionResult Get(string ticket, string version) var update = db.Updates.GetUpdate(shardOne.UpdateID.Value); response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized() { - FSOBranch = shardOne.VersionName, - FSOVersion = shardOne.VersionNumber, + FSOBranch = shardOne.VersionBranch, + FSOVersion = shardOne.VersionId, FSOUpdateUrl = update.full_zip, FSOCDNUrl = api.Config.CDNUrl }); } else { + var fsoVersion = FSOVersionInfo.Current; + response = ApiResponse.Xml(HttpStatusCode.OK, new UserAuthorized() { - FSOBranch = shardOne.VersionName, - FSOVersion = shardOne.VersionNumber, - FSOUpdateUrl = api.Config.UpdateUrl, + FSOBranch = shardOne.VersionBranch, + FSOVersion = shardOne.VersionId, + FSOUpdateUrl = fsoVersion.channelUrl, + FSOUpdateKey = fsoVersion.publicKey, FSOCDNUrl = api.Config.CDNUrl }); } diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/LotInfoController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/LotInfoController.cs index eaae56a2c..07332cbce 100644 --- a/TSOClient/FSO.Server.Api.Core/Controllers/LotInfoController.cs +++ b/TSOClient/FSO.Server.Api.Core/Controllers/LotInfoController.cs @@ -489,11 +489,28 @@ public IActionResult GetFSOV(int shardid, uint id) FileStream stream; try { - var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/state_" + lot.ring_backup_num.ToString() + ".fsov"); - + int backupNum = lot.ring_backup_num; - stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); - return File(stream, "application/octet-stream"); + // TODO: more or less backups + for (int i = 0; i < 10; i++) + { + var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/state_" + lot.ring_backup_num.ToString() + ".fsov"); + if (System.IO.File.Exists(path)) + { + stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + return File(stream, "application/octet-stream"); + } + else + { + lot.ring_backup_num--; + if (lot.ring_backup_num < 0) + { + lot.ring_backup_num += 10; + } + } + } + + return NotFound(); } catch (Exception e) { @@ -577,48 +594,49 @@ public IActionResult UploadFacade(int shardid, uint id, List files) return NotFound(); } } + } - /* + [HttpPost] + [Route("userapi/city/{shardid}/uploadthumb/{id}")] + public IActionResult UploadThumb(int shardid, uint id, List files) + { var api = Api.INSTANCE; api.DemandModerator(Request); - if (!Request.Content.IsMimeMultipartContent()) - return new HttpResponseMessage(HttpStatusCode.UnsupportedMediaType); - - var provider = new MultipartMemoryStreamProvider(); - var files = Request.Content.ReadAsMultipartAsync(provider).Result; + if (files == null) + return NotFound(); byte[] data = null; - foreach (var file in provider.Contents) + foreach (var file in files) { - var filename = file.Headers.ContentDisposition.FileName.Trim('\"'); - data = file.ReadAsByteArrayAsync().Result; + var filename = file.FileName.Trim('\"'); + using (var memoryStream = new MemoryStream()) + { + file.CopyTo(memoryStream); + data = memoryStream.ToArray(); + } } - if (data == null) return new HttpResponseMessage(HttpStatusCode.NotFound); + if (data == null) return NotFound(); using (var da = api.DAFactory.Get()) { var lot = da.Lots.GetByLocation(shardid, id); - if (lot == null) return new HttpResponseMessage(HttpStatusCode.NotFound); + if (lot == null) return NotFound(); FileStream stream; try { - var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.fsof"); + var path = Path.Combine(api.Config.NFSdir, "Lots/" + lot.lot_id.ToString("x8") + "/thumb.png"); stream = System.IO.File.Open(path, FileMode.Create, FileAccess.Write, FileShare.Write); stream.Write(data, 0, data.Length); - - HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK); - response.Content = new StringContent("", Encoding.UTF8, "text/plain"); - return response; + return Ok(); } catch (Exception e) { - return new HttpResponseMessage(HttpStatusCode.NotFound); + return NotFound(); } } - */ } } diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/ServerInfoController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/ServerInfoController.cs new file mode 100644 index 000000000..e10434ab1 --- /dev/null +++ b/TSOClient/FSO.Server.Api.Core/Controllers/ServerInfoController.cs @@ -0,0 +1,83 @@ +using FSO.Server.Api.Core.Utils; +using FSO.Server.Common; +using FSO.Server.Database.DA.Shards; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Mvc; +using System.Linq; +using System.Net; + +namespace FSO.Server.Api.Core.Controllers +{ + [EnableCors] + [ApiController] + public class ServerInfoController : ControllerBase + { + private static object ModelLock = new object { }; + private static ServerInfoModel LastModel = new ServerInfoModel(); + private static uint LastModelUpdate; + + private static bool ShardUp(ShardStatus status) + { + return !(status == ShardStatus.Closed || status == ShardStatus.Down); + } + + [HttpGet] + [Route("userapi/status.json")] + public IActionResult Get(int shardid) + { + var api = Api.INSTANCE; + + var now = Epoch.Now; + if (LastModelUpdate < now - 15) + { + LastModelUpdate = now; + lock (ModelLock) + { + LastModel = new ServerInfoModel(); + using (var da = api.DAFactory.Get()) + { + var shards = da.Shards.All(); + // TODO: only list shards for this server? + LastModel.shards = [.. shards.Where(shard => ShardUp(shard.status)).Select(shard => shard.shard_id)]; + + string name = api.Config.Name; + + if (string.IsNullOrEmpty(name)) + { + // Try pull the name from the first shard. + + name = shards.FirstOrDefault()?.name ?? "FreeSO Server"; + } + + LastModel.name = name; + + int onlineCount = 0; + + foreach (int shardId in LastModel.shards) + { + var lotstatus = da.LotClaims.AllLocations(shardid); + + onlineCount += lotstatus.Sum(x => x.active); + } + + LastModel.onlineCount = onlineCount; + LastModel.versionInfo = api.Config.VersionInfoJson; + } + } + } + + lock (ModelLock) + { + return ApiResponse.Json(HttpStatusCode.OK, LastModel); + } + } + } + + class ServerInfoModel + { + public string name; + public int[] shards; + public int onlineCount; + public string versionInfo; + } +} diff --git a/TSOClient/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs b/TSOClient/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs index 829daebb1..aae800085 100644 --- a/TSOClient/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs +++ b/TSOClient/FSO.Server.Api.Core/Controllers/ShardSelectorController.cs @@ -85,6 +85,7 @@ public IActionResult Get(string shardName, string avatarId) result.Ticket = ticket.ticket_id; result.ConnectionID = ticket.ticket_id; result.AvatarID = avatarId; + result.SpectatorMode = api.Config.AllOpenable; return ApiResponse.Xml(HttpStatusCode.OK, result); } diff --git a/TSOClient/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj b/TSOClient/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj index 8ecab64a7..7f6dbf1e1 100644 --- a/TSOClient/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj +++ b/TSOClient/FSO.Server.Api.Core/FSO.Server.Api.Core.csproj @@ -1,9 +1,12 @@ - + - netcoreapp2.2 + net9.0 true - false + false + True + + false @@ -17,27 +20,13 @@ - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - + + + + + + + @@ -47,10 +36,9 @@ - - - + + diff --git a/TSOClient/FSO.Server.Api.Core/Startup.cs b/TSOClient/FSO.Server.Api.Core/Startup.cs index ccc01fa16..a044ef46c 100644 --- a/TSOClient/FSO.Server.Api.Core/Startup.cs +++ b/TSOClient/FSO.Server.Api.Core/Startup.cs @@ -35,7 +35,10 @@ public void ConfigureServices(IServiceCollection services) { builder.WithOrigins("https://freeso.org", "http://localhost:8080").AllowAnyMethod().AllowAnyHeader().AllowCredentials().WithExposedHeaders("content-disposition"); }); - }).AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + }).AddMvc(options => + { + options.EnableEndpointRouting = false; + }); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/TSOClient/FSO.Server.Clients/ApiClient.cs b/TSOClient/FSO.Server.Clients/ApiClient.cs index 529fc2232..47b28477f 100644 --- a/TSOClient/FSO.Server.Clients/ApiClient.cs +++ b/TSOClient/FSO.Server.Clients/ApiClient.cs @@ -2,196 +2,311 @@ using FSO.Server.Clients.Framework; using Newtonsoft.Json; using RestSharp; -using System; -using System.Linq; namespace FSO.Server.Clients { - public class ApiClient : AbstractHttpClient + public class ApiClient : AbstractHttpClient, IDisposable { private RestClient client; public static string CDNUrl; public static string AuthKey; - public ApiClient(string baseUrl) : base(baseUrl) { + public ApiClient(string baseUrl) : base(baseUrl) + { client = Client(); } - public void GetThumbnailAsync(uint shardID, uint location, Action callback) + public async Task GetThumbnailAsync(uint shardID, uint location, Action callback) { - //var client = Client(); - var request = new RestRequest("userapi/city/" + shardID + "/" + location + ".png"); + var client = Client(); + var request = new RestRequest($"userapi/city/{shardID}/{location}.png", Method.Get); - client.ExecuteAsync(request, (resp, h) => + try { - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); // no lambda, returns RestResponse + + GameThread.NextUpdate(_ => { - if (resp.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK) callback(null); else - callback(resp.RawBytes); + callback(response.RawBytes); }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } } - public void GetFacadeAsync(uint shardID, uint location, Action callback) + public async Task GetFacadeAsync(uint shardID, uint location, Action callback) { - //var client = Client(); - var request = new RestRequest("userapi/city/" + shardID + "/" + location + ".fsof"); + var client = Client(); + var request = new RestRequest($"userapi/city/{shardID}/{location}.fsof", Method.Get); - client.ExecuteAsync(request, (resp, h) => + try { - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); + + GameThread.NextUpdate(_ => { - if (resp.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK) callback(null); else - callback(resp.RawBytes); + callback(response.RawBytes); }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } } - public void AdminLogin(string username, string password, Action callback) + public async Task AdminLoginAsync(string username, string password, Action callback) { var client = Client(); - var request = new RestRequest("admin/oauth/token", Method.POST); + var request = new RestRequest("admin/oauth/token", Method.Post); + request.AddParameter("application/x-www-form-urlencoded", - "grant_type=password&username="+username+"&password="+password, + $"grant_type=password&username={username}&password={password}", ParameterType.RequestBody); - client.ExecuteAsync(request, (resp, h) => + try { - var ok = resp.StatusCode == System.Net.HttpStatusCode.OK; - if (ok) { - dynamic obj = JsonConvert.DeserializeObject(resp.Content); + var response = await client.ExecuteAsync(request); + + bool ok = response.StatusCode == System.Net.HttpStatusCode.OK; + + if (ok) + { + // Deserialize the token + dynamic obj = JsonConvert.DeserializeObject(response.Content); AuthKey = obj.access_token; } - GameThread.NextUpdate(x => - { - callback(ok); - }); - }); + + // Call back on the game thread + GameThread.NextUpdate(_ => callback(ok)); + } + catch + { + GameThread.NextUpdate(_ => callback(false)); + } } - public void GetWork(Action callback) + public async Task GetWork(Action callback) { var client = Client(); - var request = new RestRequest("userapi/city/thumbwork.json", Method.GET); + var request = new RestRequest("userapi/city/thumbwork.json", Method.Get); request.AddHeader("authorization", "bearer " + AuthKey); - client.ExecuteAsync(request, (resp, h) => + try { - var ok = resp.StatusCode == System.Net.HttpStatusCode.OK; - - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); + bool ok = response.StatusCode == System.Net.HttpStatusCode.OK; + + GameThread.NextUpdate(_ => { - if (!ok || resp.Content == "") callback(-1, (ok)?0:uint.MaxValue); + if (!ok || string.IsNullOrEmpty(response.Content)) + callback(-1, ok ? 0 : uint.MaxValue); else { - dynamic obj = JsonConvert.DeserializeObject(resp.Content); + dynamic obj = JsonConvert.DeserializeObject(response.Content); callback(Convert.ToInt32(obj.shard_id), Convert.ToUInt32(obj.location)); } }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(-1, uint.MaxValue)); + } } - public void GetFSOV(uint shardID, uint lotLocation, Action callback) + public async Task GetFSOV(uint shardID, uint lotLocation, Action callback) { var client = Client(); - var request = new RestRequest("userapi/city/"+shardID+"/"+lotLocation+".fsov", Method.GET); + var request = new RestRequest($"userapi/city/{shardID}/{lotLocation}.fsov", Method.Get); request.AddHeader("authorization", "bearer " + AuthKey); - client.ExecuteAsync(request, (resp, h) => + try { - var ok = resp.StatusCode == System.Net.HttpStatusCode.OK; - byte[] dat = resp.RawBytes; - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); + bool ok = response.StatusCode == System.Net.HttpStatusCode.OK; + byte[] dat = response.RawBytes; + + GameThread.NextUpdate(_ => { - callback(ok?dat:null); + callback(ok ? dat : null); }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } } - public void UploadFSOF(uint shardID, uint lotLocation, byte[] data, Action callback) + public async Task UploadFSOF(uint shardID, uint lotLocation, byte[] data, Action callback) { var client = Client(); - var request = new RestRequest("userapi/city/" + shardID + "/uploadfacade/" + lotLocation, Method.POST); - request.AddFile("files", data, lotLocation + ".fsof", "application/octet-stream"); + var request = new RestRequest($"userapi/city/{shardID}/uploadfacade/{lotLocation}", Method.Post); + + request.AddFile("files", data, $"{lotLocation}.fsof", "application/octet-stream"); request.AddHeader("authorization", "bearer " + AuthKey); - client.ExecuteAsync(request, (resp, h) => + try { - var ok = resp.StatusCode == System.Net.HttpStatusCode.OK; - Console.WriteLine(resp.StatusCode); - GameThread.NextUpdate(x => - { - callback(ok); - }); - }); + var response = await client.ExecuteAsync(request); // returns RestResponse + bool ok = response.StatusCode == System.Net.HttpStatusCode.OK; + Console.WriteLine(response.StatusCode); + + GameThread.NextUpdate(_ => callback(ok)); + } + catch + { + GameThread.NextUpdate(_ => callback(false)); + } + } + + public async Task UploadThumb(uint shardID, uint lotLocation, byte[] data, Action callback) + { + var client = Client(); + var request = new RestRequest($"userapi/city/{shardID}/uploadthumb/{lotLocation}", Method.Post); + + request.AddFile("files", data, $"{lotLocation}.png", "application/octet-stream"); + request.AddHeader("authorization", "bearer " + AuthKey); + + try + { + var response = await client.ExecuteAsync(request); // returns RestResponse + bool ok = response.StatusCode == System.Net.HttpStatusCode.OK; + Console.WriteLine(response.StatusCode); + + GameThread.NextUpdate(_ => callback(ok)); + } + catch + { + GameThread.NextUpdate(_ => callback(false)); + } } - public void GetLotList(uint shardID, Action callback) + public async Task GetLotList(uint shardID, Action callback) { var client = Client(); - var request = new RestRequest("userapi/city/" + shardID + "/city.json"); + var request = new RestRequest($"userapi/city/{shardID}/city.json", Method.Get); - client.ExecuteAsync(request, (resp, h) => + try { - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); // returns RestResponse + + GameThread.NextUpdate(_ => { - if (resp.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK || string.IsNullOrEmpty(response.Content)) + { callback(null); + } else { - dynamic obj = JsonConvert.DeserializeObject(resp.Content); + dynamic obj = JsonConvert.DeserializeObject(response.Content); Newtonsoft.Json.Linq.JArray data = obj.reservedLots; uint[] result = data.Select(y => Convert.ToUInt32(y)).ToArray(); callback(result); } }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } } - public void GetUpdateList(Action callback) + public async Task GetUpdateList(Action callback) { var client = Client(); - var request = new RestRequest("userapi/update"); + var request = new RestRequest("userapi/update", Method.Get); - client.ExecuteAsync(request, (resp, h) => + try { - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); // returns RestResponse + + GameThread.NextUpdate(_ => { - if (resp.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK || string.IsNullOrEmpty(response.Content)) + { callback(null); + } else { - var obj = JsonConvert.DeserializeObject(resp.Content); + var obj = JsonConvert.DeserializeObject(response.Content); callback(obj); } }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } } - public void GetUpdateList(string branchName, Action callback) + public async Task GetUpdateList(string branchName, Action callback) { var client = Client(); - var request = new RestRequest("userapi/updates/" + branchName); + var request = new RestRequest($"userapi/updates/{branchName}", Method.Get); - client.ExecuteAsync(request, (resp, h) => + try { - GameThread.NextUpdate(x => + var response = await client.ExecuteAsync(request); // returns RestResponse + + GameThread.NextUpdate(_ => { - if (resp.StatusCode != System.Net.HttpStatusCode.OK) + if (response.StatusCode != System.Net.HttpStatusCode.OK || string.IsNullOrEmpty(response.Content)) + { callback(null); + } else { - var obj = JsonConvert.DeserializeObject(resp.Content); + var obj = JsonConvert.DeserializeObject(response.Content); callback(obj); } }); - }); + } + catch + { + GameThread.NextUpdate(_ => callback(null)); + } + } + + public async Task GetStatus() + { + var client = Client(); + var request = new RestRequest($"userapi/status.json", Method.Get) + { + Timeout = TimeSpan.FromSeconds(2) + }; + + try + { + RestResponse response = await client.ExecuteAsync(request); + + if (response.StatusCode != System.Net.HttpStatusCode.OK || string.IsNullOrEmpty(response.Content)) + { + return null; + } + else + { + var obj = JsonConvert.DeserializeObject(response.Content); + return obj; + } + } + catch + { + return null; + } + } + + public void Dispose() + { + client.Dispose(); } } } diff --git a/TSOClient/FSO.Server.Clients/ApiStatus.cs b/TSOClient/FSO.Server.Clients/ApiStatus.cs new file mode 100644 index 000000000..07cb9634c --- /dev/null +++ b/TSOClient/FSO.Server.Clients/ApiStatus.cs @@ -0,0 +1,11 @@ +namespace FSO.Server.Clients +{ + public class ApiStatus + { + public string name { get; set; } + public int[] shards { get; set; } + public int onlineCount { get; set; } + public string versionInfo { get; set; } + } + +} diff --git a/TSOClient/FSO.Server.Clients/ApiUpdate.cs b/TSOClient/FSO.Server.Clients/ApiUpdate.cs index eb871992b..f7d7538f1 100644 --- a/TSOClient/FSO.Server.Clients/ApiUpdate.cs +++ b/TSOClient/FSO.Server.Clients/ApiUpdate.cs @@ -1,4 +1,6 @@ -using System; +using FSO.Common; +using FSO.Files.FSO; +using System; using System.Collections.Generic; using System.Linq; @@ -66,4 +68,57 @@ public static UpdatePath FindPath(List updates, string current, strin return null; //no clue what to do here, we could not find a full zip to build from. this is fatal. } } + + public class UpdatePathNew + { + public List Path; + public bool FullZipStart; + + public FSOUpdateMetadata Destination => Path.LastOrDefault(); + + public UpdatePathNew(List path, bool fullZip) + { + Path = path; + FullZipStart = fullZip; + } + + public static UpdatePathNew FindPath(FSOUpdateChannel channel, FSOVersionInfo current, FSOVersionInfo target) + { + var to = channel.updates.FirstOrDefault(x => x.id == target.id); + if (to == null) return null; //cannot find update on this channel, we can't download it. + var from = (current.channel == channel.channel && current.publicKey == channel.publicKey) ? + channel.updates.FirstOrDefault(x => x.id == current.id) : + null; + + if (from != null) + { + //search for route from "to" to "from". recursive search - we then return the updates in order of application + var follow = to; + var result = new List(); + while (follow != null) + { + if (follow == from) return new UpdatePathNew(result, false); //we got here with incremental updates. + result.Insert(0, follow); + var myDelta = follow.delta?.CurrentPlatform(); + if (myDelta == null) break; + follow = (follow.lastid == null) ? null : channel.updates.FirstOrDefault(x => x.id == follow.lastid); + } + } + + //we couldn't find a path to our current version. find a path to any update that has a full zip. + { + var follow = to; + var result = new List(); + while (follow != null) + { + result.Insert(0, follow); + var myFull = follow.full?.CurrentPlatform(); + if (myFull != null) return new UpdatePathNew(result, true); //we found a full zip + follow = (follow.lastid == null) ? null : channel.updates.FirstOrDefault(x => x.id == follow.lastid); + } + } + + return null; //no clue what to do here, we could not find a full zip to build from. this is fatal. + } + } } diff --git a/TSOClient/FSO.Server.Clients/AriesClient.cs b/TSOClient/FSO.Server.Clients/AriesClient.cs index f2d087ac8..b010493a6 100644 --- a/TSOClient/FSO.Server.Clients/AriesClient.cs +++ b/TSOClient/FSO.Server.Clients/AriesClient.cs @@ -73,6 +73,8 @@ public class AriesClient : IoHandler private List MessageSubscribers = new List(); private List EventSubscribers = new List(); + + public int Timeout = 10000; public AriesClient(IKernel kernel) { @@ -129,13 +131,16 @@ public void Connect(IPEndPoint target) Connector.Handler = new NullIOHandler(); //don't hmu //we can't cancel a mina.net connector, but we can sure as hell ~~avenge it~~ stop it from firing events. //if we tried to dispose it, we'd get random disposed object exceptions because mina doesn't expect you to cancel that early. - Disconnect(); //if we have already established a connection, make sure it is closed. + if (Session.Connected) + { + Disconnect(); //if we have already established a connection, make sure it is closed. + } } var socketConnector = new AsyncSocketConnector(); socketConnector.SessionConfig.NoDelay = true; Connector = socketConnector; var connector = Connector; - Connector.ConnectTimeoutInMillis = 10000; + Connector.ConnectTimeoutInMillis = Timeout; //Connector.FilterChain.AddLast("logging", new LoggingFilter()); Connector.Handler = this; @@ -157,7 +162,7 @@ public void Connect(IPEndPoint target) Task.Run(() => { - if (!future.Await(10000)) SessionClosed(null); + if (!future.Await(Timeout)) SessionClosed(null); if (future.Canceled || future.Exception != null) SessionClosed(null); }); } @@ -184,6 +189,14 @@ public bool IsConnected } } + public IPEndPoint RemoteEndPoint + { + get + { + return Session?.RemoteEndPoint as IPEndPoint; + } + } + public void SessionCreated(IoSession session) { List _subs; diff --git a/TSOClient/FSO.Server.Clients/AuthClient.cs b/TSOClient/FSO.Server.Clients/AuthClient.cs index b238de6a7..11f665069 100644 --- a/TSOClient/FSO.Server.Clients/AuthClient.cs +++ b/TSOClient/FSO.Server.Clients/AuthClient.cs @@ -7,7 +7,8 @@ namespace FSO.Server.Clients { public class AuthClient : AbstractHttpClient { - public AuthClient(string baseUrl) : base(baseUrl) { + public AuthClient(string baseUrl) : base(baseUrl) + { } public AuthResult Authenticate(AuthRequest input) @@ -21,7 +22,7 @@ public AuthResult Authenticate(AuthRequest input) .AddQueryParameter("version", input.Version) .AddQueryParameter("clientid", input.ClientID); - + var response = client.Execute(request); var result = new AuthResult(); result.Valid = false; @@ -53,7 +54,8 @@ public AuthResult Authenticate(AuthRequest input) break; } } - } else + } + else { result.ReasonCode = "36 301"; } @@ -61,4 +63,4 @@ public AuthResult Authenticate(AuthRequest input) return result; } } -} +} \ No newline at end of file diff --git a/TSOClient/FSO.Server.Clients/CityClient.cs b/TSOClient/FSO.Server.Clients/CityClient.cs index 051e85607..75d731ffc 100644 --- a/TSOClient/FSO.Server.Clients/CityClient.cs +++ b/TSOClient/FSO.Server.Clients/CityClient.cs @@ -2,14 +2,13 @@ using FSO.Server.Clients.Framework; using FSO.Server.Protocol.CitySelector; using RestSharp; -using System; -using System.Collections.Generic; namespace FSO.Server.Clients { public class CityClient : AbstractHttpClient { - public CityClient(string baseUrl) : base(baseUrl) { + public CityClient(string baseUrl) : base(baseUrl) + { } public ShardSelectorServletResponse ShardSelectorServlet(ShardSelectorServletRequest input) @@ -19,9 +18,10 @@ public ShardSelectorServletResponse ShardSelectorServlet(ShardSelectorServletReq var request = new RestRequest("cityselector/app/ShardSelectorServlet") .AddQueryParameter("shardName", input.ShardName) .AddQueryParameter("avatarId", input.AvatarID); - + var response = client.Execute(request); - if(response.StatusCode != System.Net.HttpStatusCode.OK){ + if (response.StatusCode != System.Net.HttpStatusCode.OK) + { throw new Exception("Unknown error during ShardSelectorServlet"); } @@ -81,4 +81,4 @@ public List ShardStatus() return result; } } -} +} \ No newline at end of file diff --git a/TSOClient/FSO.Server.Clients/FSO.Server.Clients.csproj b/TSOClient/FSO.Server.Clients/FSO.Server.Clients.csproj index 853979367..09a223f60 100644 --- a/TSOClient/FSO.Server.Clients/FSO.Server.Clients.csproj +++ b/TSOClient/FSO.Server.Clients/FSO.Server.Clients.csproj @@ -1,117 +1,35 @@ - - - + + - Debug - AnyCPU - {329E0AEE-7871-40A7-B5AF-8C0D0086EF71} + net9.0 + enable + disable Library - Properties FSO.Server.Clients FSO.Server.Clients - v4.5 512 - + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + + + True - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - ..\packages\RestSharp.105.2.3\lib\net45\RestSharp.dll - - - - - - - - - - - - - - - - - - - - - - - - - - + - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - + + - - + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.Clients/Framework/AbstractHttpClient.cs b/TSOClient/FSO.Server.Clients/Framework/AbstractHttpClient.cs index f06f84611..4c268c915 100644 --- a/TSOClient/FSO.Server.Clients/Framework/AbstractHttpClient.cs +++ b/TSOClient/FSO.Server.Clients/Framework/AbstractHttpClient.cs @@ -6,22 +6,34 @@ namespace FSO.Server.Clients.Framework public abstract class AbstractHttpClient { public string BaseUrl { get; internal set; } - private CookieContainer Cookies = new CookieContainer(); + private readonly CookieContainer Cookies = new CookieContainer(); + private RestClient _client; - public AbstractHttpClient(string baseUrl){ - this.BaseUrl = baseUrl; + public AbstractHttpClient(string baseUrl) + { + BaseUrl = baseUrl; } public virtual void SetBaseUrl(string url) { BaseUrl = url; + _client?.Dispose(); + _client = null; } protected RestClient Client() { - var client = new RestClient(BaseUrl); - client.CookieContainer = Cookies; - return client; + if (_client == null) + { + var options = new RestClientOptions(BaseUrl) + { + CookieContainer = Cookies + }; + + _client = new RestClient(options); + } + + return _client; } } } diff --git a/TSOClient/FSO.Server.Clients/Framework/AbstractRegulator.cs b/TSOClient/FSO.Server.Clients/Framework/AbstractRegulator.cs index f2867e52f..66cfb8708 100644 --- a/TSOClient/FSO.Server.Clients/Framework/AbstractRegulator.cs +++ b/TSOClient/FSO.Server.Clients/Framework/AbstractRegulator.cs @@ -189,9 +189,12 @@ public bool SyncTransition(string newState, object data) public void SyncProcessMessage(object message) { - if (this.CurrentState != null) + lock (this) { - this.CurrentState.ProcessMessage(message); + if (this.CurrentState != null) + { + this.CurrentState.ProcessMessage(message); + } } } diff --git a/TSOClient/FSO.Server.Clients/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Clients/Properties/AssemblyInfo.cs deleted file mode 100644 index 50918c4c5..000000000 --- a/TSOClient/FSO.Server.Clients/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Clients")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Clients")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("329e0aee-7871-40a7-b5af-8c0d0086ef71")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Clients/StatusChecker.cs b/TSOClient/FSO.Server.Clients/StatusChecker.cs new file mode 100644 index 000000000..e32f50db8 --- /dev/null +++ b/TSOClient/FSO.Server.Clients/StatusChecker.cs @@ -0,0 +1,112 @@ +using FSO.Common; +using FSO.Server.Protocol.Aries.Packets; +using FSO.Server.Protocol.Utils; +using Ninject; + +namespace FSO.Server.Clients +{ + public readonly struct StatusCheckResult(bool isOnline, string name = "", FSOVersionInfo version = null, int players = 0) + { + public readonly bool IsOnline = isOnline; + public readonly string Name = name; + public readonly FSOVersionInfo Version = version; + public readonly int Players = players; + } + + public static class StatusChecker + { + private class ArchiveStatusChecker : IAriesEventSubscriber, IAriesMessageSubscriber + { + private readonly TaskCompletionSource Source = new(); + public Task Task => Source.Task; + + public void MessageReceived(AriesClient client, object message) + { + if (message is RequestClientSessionArchive data) + { + Source.TrySetResult(new StatusCheckResult( + true, + data.Name, + FSOVersionInfo.FromJson(data.VersionInfo), + data.PlayerCount + )); + } + } + + public void InputClosed(AriesClient session) + { + // Note: if there's already a result by the time the socket closes, it won't be overwritten. + Source.TrySetResult(new StatusCheckResult(false)); + } + + public void SessionClosed(AriesClient client) + { + Source.TrySetResult(new StatusCheckResult(false)); + } + + public void SessionCreated(AriesClient client) + { + } + + public void SessionIdle(AriesClient client) + { + } + + public void SessionOpened(AriesClient client) + { + } + } + + public static async Task FreeSOStatus(string address) + { + try + { + using (var client = new ApiClient(address)) + { + ApiStatus status = await client.GetStatus(); + + if (status == null) + { + return new StatusCheckResult(false); + } + else + { + return new StatusCheckResult(true, status.name, FSOVersionInfo.FromJson(status.versionInfo), status.onlineCount); + } + } + } + catch (Exception) + { + return new StatusCheckResult(false); + } + } + + public static async Task ArchiveStatus(IKernel kernel, string address) + { + var client = new AriesClient(kernel) + { + Timeout = 2000 + }; + + var checker = new ArchiveStatusChecker(); + + // Add handlers. + client.AddSubscriber(checker); + + try + { + client.Connect(PortTransformer.DefaultCityPort(address)); + } + catch (Exception) + { + return new StatusCheckResult(false); + } + + var result = await checker.Task; + + client.Disconnect(); + + return result; + } + } +} diff --git a/TSOClient/FSO.Server.Clients/app.config b/TSOClient/FSO.Server.Clients/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Server.Clients/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Clients/packages.config b/TSOClient/FSO.Server.Clients/packages.config deleted file mode 100644 index d5da56a38..000000000 --- a/TSOClient/FSO.Server.Clients/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Common/Config/AWSConfig.cs b/TSOClient/FSO.Server.Common/Config/AWSConfig.cs index ebd5343a6..95a073b0e 100644 --- a/TSOClient/FSO.Server.Common/Config/AWSConfig.cs +++ b/TSOClient/FSO.Server.Common/Config/AWSConfig.cs @@ -1,10 +1,16 @@ -namespace FSO.Server.Common.Config +using Newtonsoft.Json; + +namespace FSO.Server.Common.Config { public class AWSConfig { + [JsonProperty("region")] public string Region { get; set; } = "eu-west-2"; + [JsonProperty("bucket")] public string Bucket { get; set; } = "fso-updates"; + [JsonProperty("accessKeyID")] public string AccessKeyID { get; set; } + [JsonProperty("secretAccessKey")] public string SecretAccessKey { get; set; } } } diff --git a/TSOClient/FSO.Server.Common/Config/FilesystemConfig.cs b/TSOClient/FSO.Server.Common/Config/FilesystemConfig.cs index 5f139ca80..a7a4f13bf 100644 --- a/TSOClient/FSO.Server.Common/Config/FilesystemConfig.cs +++ b/TSOClient/FSO.Server.Common/Config/FilesystemConfig.cs @@ -1,8 +1,12 @@ -namespace FSO.Server.Common.Config +using Newtonsoft.Json; + +namespace FSO.Server.Common.Config { public class FilesystemConfig { + [JsonProperty("basePath")] public string BasePath { get; set; } = "./public"; + [JsonProperty("baseURL")] public string BaseURL { get; set; } } } diff --git a/TSOClient/FSO.Server.Common/Config/GithubConfig.cs b/TSOClient/FSO.Server.Common/Config/GithubConfig.cs index 965c06238..ef3c87318 100644 --- a/TSOClient/FSO.Server.Common/Config/GithubConfig.cs +++ b/TSOClient/FSO.Server.Common/Config/GithubConfig.cs @@ -1,16 +1,24 @@ -namespace FSO.Server.Common.Config +using Newtonsoft.Json; + +namespace FSO.Server.Common.Config { public class GithubConfig { + [JsonProperty("appName")] public string AppName { get; set; } = "FreeSO"; + [JsonProperty("user")] public string User { get; set; } = "riperiperi"; + [JsonProperty("repository")] public string Repository { get; set; } = "FreeSO"; + [JsonProperty("clientID")] public string ClientID { get; set; } + [JsonProperty("clientSecret")] public string ClientSecret { get; set; } - + /// /// Must be generated by installing the app on your user account. Browse to the /github/ API endpoint when this is null. (yes, on this server) /// + [JsonProperty("accessToken")] public string AccessToken { get; set; } } } diff --git a/TSOClient/FSO.Server.Common/FSO.Server.Common.csproj b/TSOClient/FSO.Server.Common/FSO.Server.Common.csproj index c46d335d0..2331f6337 100644 --- a/TSOClient/FSO.Server.Common/FSO.Server.Common.csproj +++ b/TSOClient/FSO.Server.Common/FSO.Server.Common.csproj @@ -1,105 +1,35 @@ - - - + + - Debug - AnyCPU - {39B61962-FE43-4B64-8E57-8F793737FFFE} + net9.0 + enable + disable Library - Properties FSO.Server.Common FSO.Server.Common - v4.5 512 - + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + + + True - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Portable.BouncyCastle.1.8.0\lib\net45\crypto.dll - - - ..\packages\Portable.JWT.1.0.5\lib\portable-net45+win+wpa81+wp80+MonoAndroid10+xamarinios10+MonoTouch10\JWT.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + + - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs b/TSOClient/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs index 03ccf24c3..3578ffb7e 100644 --- a/TSOClient/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs +++ b/TSOClient/FSO.Server.Common/JsonWebToken/JWTokenFactory.cs @@ -1,6 +1,7 @@ using FSO.Server.Common; +using JWT.Algorithms; +using JWT.Builder; using Newtonsoft.Json; -using System.Collections.Generic; namespace FSO.Server.Servers.Api.JsonWebToken { @@ -21,7 +22,7 @@ public JWTFactory(JWTConfiguration config) public JWTUser DecodeToken(string token) { - var payload = JWT.JsonWebToken.Decode(token, Config.Key, true); + var payload = JwtBuilder.Create().WithAlgorithm(new HMACSHA384Algorithm()).WithSecret(Config.Key).Decode(token); Dictionary payloadParsed = JsonConvert.DeserializeObject>(payload); return Newtonsoft.Json.JsonConvert.DeserializeObject(payloadParsed["data"]); } @@ -41,7 +42,7 @@ private JWTInstance CreateToken(string data, int expiresIn) { "data", data } }; - var token = JWT.JsonWebToken.Encode(payload, Config.Key, JWT.JwtHashAlgorithm.HS384); + var token = JwtBuilder.Create().WithAlgorithm(new HMACSHA384Algorithm()).WithSecret(Config.Key).Encode(payload); return new JWTInstance { Token = token, ExpiresIn = expiresIn }; } } diff --git a/TSOClient/FSO.Server.Common/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Common/Properties/AssemblyInfo.cs deleted file mode 100644 index 957c5f7f5..000000000 --- a/TSOClient/FSO.Server.Common/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Common")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Common")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("39b61962-fe43-4b64-8e57-8f793737fffe")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Common/app.config b/TSOClient/FSO.Server.Common/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Server.Common/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Common/packages.config b/TSOClient/FSO.Server.Common/packages.config deleted file mode 100644 index e81fbed1f..000000000 --- a/TSOClient/FSO.Server.Common/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Core/CoreImageLoader.cs b/TSOClient/FSO.Server.Core/CoreImageLoader.cs index b7526faf6..d3a8c8446 100644 --- a/TSOClient/FSO.Server.Core/CoreImageLoader.cs +++ b/TSOClient/FSO.Server.Core/CoreImageLoader.cs @@ -13,17 +13,21 @@ public static TexBitmap SoftImageFetch(Stream stream, AbstractTextureRef texRef) Image result = null; try { - result = Image.Load(stream); + result = Image.Load(stream); } catch (Exception) { return new TexBitmap() { Data = new byte[0] }; } stream.Close(); - + if (result == null) return null; - var data = result.SavePixelData(); + // Get pixel data + var data = new byte[result.Width * result.Height * 4]; + result.CopyPixelDataTo(data); + + // Swap red and blue channels for (int i = 0; i < data.Length; i += 4) { var temp = data[i]; diff --git a/TSOClient/FSO.Server.Core/FSO.Server.Core.csproj b/TSOClient/FSO.Server.Core/FSO.Server.Core.csproj index 46ee52975..f0fee99fd 100644 --- a/TSOClient/FSO.Server.Core/FSO.Server.Core.csproj +++ b/TSOClient/FSO.Server.Core/FSO.Server.Core.csproj @@ -2,30 +2,34 @@ Exe - netcoreapp2.2 + net9.0 true win-x64;linux-x64 false + ../FSO.Server/FreeSO.ico + True + false + false + False + true + + true + + + + + + - - - - - - - - - - - - - - + + + + diff --git a/TSOClient/FSO.Server.Core/Program.cs b/TSOClient/FSO.Server.Core/Program.cs index 97a7274d2..c2f52c887 100644 --- a/TSOClient/FSO.Server.Core/Program.cs +++ b/TSOClient/FSO.Server.Core/Program.cs @@ -1,4 +1,5 @@ -using FSO.Server.Api.Core.Services; +using FSO.Common; +using FSO.Server.Api.Core.Services; using FSO.Server.Common; using FSO.Server.Servers.UserApi; using System.Collections.Specialized; @@ -31,14 +32,19 @@ public static IAPILifetime StartWebApi(UserApi api, string url) settings.Add("updateUrl", userApiConfig.UpdateUrl); settings.Add("cdnUrl", userApiConfig.CDNUrl); settings.Add("connectionString", config.Database.ConnectionString); + settings.Add("databaseEngine", config.Database.Engine ?? "mysql"); settings.Add("NFSdir", config.SimNFS); settings.Add("smtpHost", userApiConfig.SmtpHost); settings.Add("smtpUser", userApiConfig.SmtpUser); settings.Add("smtpPassword", userApiConfig.SmtpPassword); settings.Add("smtpPort", userApiConfig.SmtpPort.ToString()); settings.Add("useProxy", userApiConfig.UseProxy.ToString()); + settings.Add("name", config.Name ?? ""); settings.Add("updateID", config.UpdateID?.ToString() ?? ""); settings.Add("branchName", config.UpdateBranch); + settings.Add("allOpenable", config.AllOpenable.ToString()); + settings.Add("versionInfoJson", FSOVersionInfo.Current.ToJson()); + var api2 = new FSO.Server.Api.Core.Api(); api2.Init(settings); diff --git a/TSOClient/FSO.Server.Core/Properties/launchSettings.json b/TSOClient/FSO.Server.Core/Properties/launchSettings.json index c759ef195..0cdc7fc1a 100644 --- a/TSOClient/FSO.Server.Core/Properties/launchSettings.json +++ b/TSOClient/FSO.Server.Core/Properties/launchSettings.json @@ -1,7 +1,8 @@ { "profiles": { "FSO.Server.Core": { - "commandName": "Project" + "commandName": "Project", + "commandLineArgs": "run" } } } \ No newline at end of file diff --git a/TSOClient/FSO.Server.DataService/DataService.cs b/TSOClient/FSO.Server.DataService/DataService.cs index ebb0c128b..45d71d8bf 100644 --- a/TSOClient/FSO.Server.DataService/DataService.cs +++ b/TSOClient/FSO.Server.DataService/DataService.cs @@ -94,11 +94,14 @@ public Task GetMany(object[] keys){ return GetMany(typeof(T), keys).ContinueWith(x => { if (x.IsFaulted) { throw x.Exception; } - var result = new List(); - foreach(var item in x.Result){ - result.Add((T)item); + var result = new T[x.Result.Length]; + + for (int i = 0; i < x.Result.Length; i++) + { + result[i] = (T)x.Result[i]; } - return result.ToArray(); + + return result; }); } diff --git a/TSOClient/FSO.Server.DataService/FSO.Common.DataService.csproj b/TSOClient/FSO.Server.DataService/FSO.Common.DataService.csproj index 6723f9b6d..24e6eef27 100644 --- a/TSOClient/FSO.Server.DataService/FSO.Common.DataService.csproj +++ b/TSOClient/FSO.Server.DataService/FSO.Common.DataService.csproj @@ -1,166 +1,25 @@ - - - + + - Debug - AnyCPU - {88C69E02-78D4-4D71-9C26-43A9B118285A} Library - Properties FSO.Common.DataService FSO.Common.DataService - v4.5 512 - + net9.0 + enable + disable + True - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - - {9848faf5-444a-48cc-a26a-8115d8c4fb52} - FSO.Common.Domain - - - {329e0aee-7871-40a7-b5af-8c0d0086ef71} - FSO.Server.Clients - - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - + + + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.DataService/Framework/ReceiveOnlyServiceProvider.cs b/TSOClient/FSO.Server.DataService/Framework/ReceiveOnlyServiceProvider.cs index 6acfd715d..cc67d1b2e 100644 --- a/TSOClient/FSO.Server.DataService/Framework/ReceiveOnlyServiceProvider.cs +++ b/TSOClient/FSO.Server.DataService/Framework/ReceiveOnlyServiceProvider.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; +using System.Collections.Concurrent; namespace FSO.Common.DataService.Framework { public abstract class ReceiveOnlyServiceProvider : AbstractDataServiceProvider where VALUE : IModel { //protected Dictionary Items = new Dictionary(); - protected Dictionary> Values = new Dictionary>(); + protected ConcurrentDictionary> Values = []; protected TimeSpan LazyLoadTimeout = TimeSpan.FromSeconds(10); public override Task Get(object key) @@ -20,22 +17,7 @@ public override Task Get(object key) var castKey = (KEY)key; - if (Values.ContainsKey(castKey)) - { - return Values[castKey]; - } - - lock (Values) - { - if (Values.ContainsKey(castKey)) - { - return Values[castKey]; - } - - var result = ResolveMissingKey(castKey); - Values.Add(castKey, result); - return result; - } + return Values.GetOrAdd(castKey, (KEY key) => ResolveMissingKey(key)); } private Task ResolveMissingKey(object key) diff --git a/TSOClient/FSO.Server.DataService/Model/Lot.cs b/TSOClient/FSO.Server.DataService/Model/Lot.cs index 06050df88..983eb37a7 100644 --- a/TSOClient/FSO.Server.DataService/Model/Lot.cs +++ b/TSOClient/FSO.Server.DataService/Model/Lot.cs @@ -180,6 +180,9 @@ public uint Lot_Location_Packed [Persist] public cTSOGenericData Lot_Thumbnail { get; set; } + [Persist] + public cTSOGenericData Lot_Facade { get; set; } + public uint Lot_ThumbnailCheckSum { get; set; } public bool IsDefaultName diff --git a/TSOClient/FSO.Server.DataService/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.DataService/Properties/AssemblyInfo.cs deleted file mode 100644 index 1a73ac9fc..000000000 --- a/TSOClient/FSO.Server.DataService/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.DataService")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.DataService")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("88c69e02-78d4-4d71-9c26-43a9b118285a")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.DataService/app.config b/TSOClient/FSO.Server.DataService/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Server.DataService/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.DataService/packages.config b/TSOClient/FSO.Server.DataService/packages.config deleted file mode 100644 index d892a718a..000000000 --- a/TSOClient/FSO.Server.DataService/packages.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/DbArchiveFeatured.cs b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/DbArchiveFeatured.cs new file mode 100644 index 000000000..d3b1e7b18 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/DbArchiveFeatured.cs @@ -0,0 +1,17 @@ +namespace FSO.Server.Database.DA.ArchiveFeatured +{ + public class DbArchiveFeatured + { + public int id { get; set; } + public string name { get; set; } + public int lot_id { get; set; } + public int category { get; set; } + public string description { get; set; } + public int shard_id { get; set; } + } + + public class DbArchiveFeaturedWithLocation : DbArchiveFeatured + { + public uint location { get; set; } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/IArchiveFeatured.cs b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/IArchiveFeatured.cs new file mode 100644 index 000000000..070a18e4e --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/IArchiveFeatured.cs @@ -0,0 +1,13 @@ +using FSO.Common.Enum; +using System.Collections.Generic; + +namespace FSO.Server.Database.DA.ArchiveFeatured +{ + public interface IArchiveFeatured + { + IEnumerable All(int shard_id); + IEnumerable GetByCategory(int shard_id, LotCategory category); + int Create(DbArchiveFeatured featured); + bool Clear(int shard_id); + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/SqlArchiveFeatured.cs b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/SqlArchiveFeatured.cs new file mode 100644 index 000000000..c56f76eb5 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveFeatured/SqlArchiveFeatured.cs @@ -0,0 +1,44 @@ +using Dapper; +using FSO.Common.Enum; +using System.Collections.Generic; +using System.Linq; + +namespace FSO.Server.Database.DA.ArchiveFeatured +{ + public class SqlArchiveFeatured : AbstractSqlDA, IArchiveFeatured + { + public SqlArchiveFeatured(ISqlContext context) : base(context) + { + } + + public int Create(DbArchiveFeatured featured) + { + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_archive_featured (name, lot_id, category, description, shard_id)" + + " VALUES (@name, @lot_id, @category, @description, @shard_id);" + + " SELECT LAST_INSERT_ID();"), featured).First(); + return result; + } + + public IEnumerable All(int shard_id) + { + return Context.Connection.Query("SELECT * FROM fso_archive_featured WHERE shard_id = @shard_id").ToList(); + } + + public bool Clear(int shard_id) + { + return Context.Connection.Execute("DELETE FROM fso_archive_featured WHERE shard_id = @shard_id", new { shard_id }) > 0; + } + + public DbArchiveFeatured Get(int id) + { + return Context.Connection.Query("SELECT * FROM fso_archive_featured WHERE id = @id", new { id }).FirstOrDefault(); + } + + public IEnumerable GetByCategory(int shard_id, LotCategory category) + { + return Context.Connection.Query( + "SELECT f.id, f.name, f.lot_id, f.category, f.description, f.shard_id, l.location FROM fso_archive_featured f INNER JOIN fso_lots l ON f.lot_id = l.lot_id WHERE f.shard_id = @shard_id AND f.category = @category", + new { shard_id, category = (int)category }).ToList(); + } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveRecents/DbArchiveRecent.cs b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/DbArchiveRecent.cs new file mode 100644 index 000000000..165424c3f --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/DbArchiveRecent.cs @@ -0,0 +1,9 @@ +namespace FSO.Server.Database.DA.ArchiveRecents +{ + public class DbArchiveRecent + { + public int user_id { get; set; } + public int avatar_id { get; set; } + public DateTime last_timestamp { get; set; } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveRecents/IArchiveRecents.cs b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/IArchiveRecents.cs new file mode 100644 index 000000000..ea5d81290 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/IArchiveRecents.cs @@ -0,0 +1,8 @@ +namespace FSO.Server.Database.DA.ArchiveRecents +{ + public interface IArchiveRecents + { + IEnumerable AvatarsByUser(int user_id, int limit); + void RecordAvatarUse(int user_id, int avatar_id); + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveRecents/SqlArchiveRecents.cs b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/SqlArchiveRecents.cs new file mode 100644 index 000000000..fe0bd646e --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveRecents/SqlArchiveRecents.cs @@ -0,0 +1,35 @@ +using Dapper; + +namespace FSO.Server.Database.DA.ArchiveRecents +{ + internal class SqlArchiveRecents : AbstractSqlDA, IArchiveRecents + { + public SqlArchiveRecents(ISqlContext context) : base(context) + { + } + + public IEnumerable AvatarsByUser(int user_id, int limit) + { + return Context.Connection.Query( + Context.CompatLayer("SELECT avatar_id from fso_archive_recents " + + "WHERE user_id = @user_id " + + "ORDER BY last_timestamp DESC " + + "LIMIT @limit"), + new { user_id, limit }); + } + + public void RecordAvatarUse(int user_id, int avatar_id) + { + var use = new DbArchiveRecent + { + user_id = user_id, + avatar_id = avatar_id, + last_timestamp = DateTime.UtcNow, + }; + + Context.Connection.Execute(Context.CompatLayer("INSERT INTO fso_archive_recents (user_id, avatar_id, last_timestamp) " + + "VALUES(@user_id, @avatar_id, @last_timestamp) " + + "ON DUPLICATE KEY UPDATE last_timestamp = @last_timestamp; ", "`user_id`,`avatar_id`"), use); + } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveUsers/ArchiveUser.cs b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/ArchiveUser.cs new file mode 100644 index 000000000..c869f3ebd --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/ArchiveUser.cs @@ -0,0 +1,11 @@ +using FSO.Server.Database.DA.Users; + +namespace FSO.Server.Database.DA.ArchiveUsers +{ + public class ArchiveUser : User + { + public string display_name { get; set; } + public bool is_verified { get; set; } + public bool shared_user { get; set; } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveUsers/IArchiveUsers.cs b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/IArchiveUsers.cs new file mode 100644 index 000000000..856cc501f --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/IArchiveUsers.cs @@ -0,0 +1,10 @@ +namespace FSO.Server.Database.DA.ArchiveUsers +{ + public interface IArchiveUsers + { + ArchiveUser GetByClientHash(string clientHash); + ArchiveUser GetByDisplayName(string displayName); + void UpdateDisplayName(uint id, string displayName); + uint Create(ArchiveUser user); + } +} diff --git a/TSOClient/FSO.Server.Database/DA/ArchiveUsers/SqlArchiveUsers.cs b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/SqlArchiveUsers.cs new file mode 100644 index 000000000..e3b051249 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/ArchiveUsers/SqlArchiveUsers.cs @@ -0,0 +1,36 @@ +using Dapper; +using System.Linq; + +namespace FSO.Server.Database.DA.ArchiveUsers +{ + internal class SqlArchiveUsers : AbstractSqlDA, IArchiveUsers + { + public SqlArchiveUsers(ISqlContext context) : base(context) + { + } + + public ArchiveUser GetByClientHash(string client_hash) + { + return Context.Connection.Query("SELECT * FROM fso_users WHERE username = @client_hash", new { client_hash }).FirstOrDefault(); + } + + public ArchiveUser GetByDisplayName(string display_name) + { + return Context.Connection.Query("SELECT * FROM fso_users WHERE display_name = @display_name", new { display_name }).FirstOrDefault(); + } + + public void UpdateDisplayName(uint id, string display_name) + { + Context.Connection.Execute("UPDATE fso_users SET display_name = @display_name WHERE user_id = @user_id", new { user_id = id, display_name = display_name }); + } + + public uint Create(ArchiveUser user) + { + return Context.Connection.Query(Context.CompatLayer( + "insert into fso_users (username, email, register_date, register_ip, last_ip, is_admin, is_moderator, is_banned, display_name, is_verified, shared_user)" + + " VALUES (@username, @email, @register_date, @register_ip, @last_ip, @is_admin, @is_moderator, @is_banned, @display_name, @is_verified, @shared_user); select LAST_INSERT_ID();"), + user + ).First(); + } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs b/TSOClient/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs index aa5bca4fa..500967829 100644 --- a/TSOClient/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs +++ b/TSOClient/FSO.Server.Database/DA/AvatarClaims/SqlAvatarClaims.cs @@ -1,6 +1,7 @@ using Dapper; using MySql.Data.MySqlClient; using System.Collections.Generic; +using System.Data.Common; using System.Linq; namespace FSO.Server.Database.DA.AvatarClaims @@ -19,7 +20,7 @@ public bool Claim(int id, string previousOwner, string newOwner, uint location) var newClaim = Context.Connection.Query("SELECT * FROM fso_avatar_claims WHERE avatar_claim_id = @claim_id AND owner = @owner", new { claim_id = (int)id, owner = newOwner }).FirstOrDefault(); return newClaim != null; } - catch (MySqlException ex) + catch (DbException ex) { return false; } @@ -51,8 +52,8 @@ public IEnumerable GetAll() } public IEnumerable GetAllActiveAvatars() { - return Context.Connection.Query("SELECT b.*, a.location FROM fso.fso_avatar_claims as a "+ - "inner join fso.fso_avatars as b ON a.avatar_id = b.avatar_id;"); + return Context.Connection.Query("SELECT b.*, a.location FROM fso_avatar_claims as a "+ + "inner join fso_avatars as b ON a.avatar_id = b.avatar_id;"); } public int? GetAllActiveAvatarsCount() { @@ -72,10 +73,10 @@ public IEnumerable GetAllByOwner(string owner) { try { - return Context.Connection.Query("INSERT INTO fso_avatar_claims (avatar_id, owner, location) " + - " VALUES (@avatar_id, @owner, @location); SELECT LAST_INSERT_ID();", claim).First(); + return Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_avatar_claims (avatar_id, owner, location) " + + " VALUES (@avatar_id, @owner, @location); SELECT LAST_INSERT_ID();"), claim).First(); } - catch (MySqlException ex) + catch (DbException ex) { return null; } diff --git a/TSOClient/FSO.Server.Database/DA/Avatars/IAvatars.cs b/TSOClient/FSO.Server.Database/DA/Avatars/IAvatars.cs index dbbcea595..5ed319313 100644 --- a/TSOClient/FSO.Server.Database/DA/Avatars/IAvatars.cs +++ b/TSOClient/FSO.Server.Database/DA/Avatars/IAvatars.cs @@ -31,12 +31,14 @@ public interface IAvatars DbTransactionResult Transaction(uint source_id, uint avatar_id, int amount, short reason, Func transactionInject); DbTransactionResult TestTransaction(uint source_id, uint avatar_id, int amount, short reason); + void UpdateUser(uint id, uint user_id); void UpdateDescription(uint id, string description); void UpdatePrivacyMode(uint id, byte privacy); void UpdateAvatarLotSave(uint id, DbAvatar avatar); void UpdateAvatarJobLevel(DbJobLevel jobLevel); void UpdateMoveDate(uint id, uint date); void UpdateMayorNhood(uint id, uint? nhood); + void UpdateModerationLevel(uint id, int level); List SearchExact(int shard_id, string name, int limit); List SearchWildcard(int shard_id, string name, int limit); diff --git a/TSOClient/FSO.Server.Database/DA/Avatars/SqlAvatars.cs b/TSOClient/FSO.Server.Database/DA/Avatars/SqlAvatars.cs index bd37c355a..91f75ce85 100644 --- a/TSOClient/FSO.Server.Database/DA/Avatars/SqlAvatars.cs +++ b/TSOClient/FSO.Server.Database/DA/Avatars/SqlAvatars.cs @@ -63,12 +63,12 @@ public int GetModerationLevel(uint id) public uint Create(DbAvatar avatar) { - return (uint)Context.Connection.Query("INSERT INTO fso_avatars (shard_id, user_id, name, " + + return (uint)Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_avatars (shard_id, user_id, name, " + "gender, date, skin_tone, head, body, description, budget, moderation_level, " + - " body_swimwear, body_sleepwear) " + + " body_swimwear, body_sleepwear, motive_data) " + " VALUES (@shard_id, @user_id, @name, @gender, @date, " + " @skin_tone, @head, @body, @description, @budget, @moderation_level, "+ - " @body_swimwear, @body_sleepwear); SELECT LAST_INSERT_ID();", new + " @body_swimwear, @body_sleepwear, @motive_data); SELECT LAST_INSERT_ID();"), new { shard_id = avatar.shard_id, user_id = avatar.user_id, @@ -82,7 +82,8 @@ public uint Create(DbAvatar avatar) budget = avatar.budget, moderation_level = avatar.moderation_level, body_swimwear = avatar.body_swimwear, - body_sleepwear = avatar.body_sleepwear + body_sleepwear = avatar.body_sleepwear, + motive_data = new byte[32] }).First(); //for now, everything else assumes default values. } @@ -132,6 +133,11 @@ public List SearchWildcard(int shard_id, string name, int limit) ).ToList(); } + public void UpdateUser(uint id, uint user_id) + { + Context.Connection.Query("UPDATE fso_avatars SET user_id = @user_id WHERE avatar_id = @id", new { id = id, user_id }); + } + public void UpdateDescription(uint id, string description) { Context.Connection.Query("UPDATE fso_avatars SET description = @desc WHERE avatar_id = @id", new { id = id, desc = description }); @@ -152,6 +158,11 @@ public void UpdateMayorNhood(uint id, uint? nhood) Context.Connection.Query("UPDATE fso_avatars SET mayor_nhood = @nhood WHERE avatar_id = @id", new { id = id, nhood = nhood }); } + public void UpdateModerationLevel(uint id, int moderation_level) + { + Context.Connection.Query("UPDATE fso_avatars SET moderation_level = @moderation_level WHERE avatar_id = @id", new { id, moderation_level }); + } + public void UpdateAvatarLotSave(uint id, DbAvatar avatar) { @@ -270,9 +281,11 @@ public DbTransactionResult Transaction(uint source_id, uint dest_id, int amount, if (success && ((reason > 7 && reason != 9) || (source_id != uint.MaxValue && dest_id != uint.MaxValue))) { var days = (DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalDays; - Context.Connection.Execute("INSERT INTO fso_transactions (from_id, to_id, transaction_type, day, value, count) "+ + Context.Connection.Execute(Context.CompatLayer( + "INSERT INTO fso_transactions (from_id, to_id, transaction_type, day, value, count) "+ "VALUES (@from_id, @to_id, @transaction_type, @day, @value, @count) " + - "ON DUPLICATE KEY UPDATE value = value + @value, count = count+1", new + "ON DUPLICATE KEY UPDATE value = value + @value, count = count+1", + "`from_id`,`to_id`,`transaction_type`,`day`"), new //duplicate key update not supported... { from_id = (amount>0)?source_id:dest_id, to_id = (amount>0)?dest_id:source_id, @@ -368,10 +381,11 @@ public List GetJobLevels(uint avatar_id) public void UpdateAvatarJobLevel(DbJobLevel jobLevel) { - Context.Connection.Query("INSERT INTO fso_joblevels (avatar_id, job_type, job_experience, job_level, job_sickdays, job_statusflags) " + Context.Connection.Query(Context.CompatLayer( + "INSERT INTO fso_joblevels (avatar_id, job_type, job_experience, job_level, job_sickdays, job_statusflags) " + "VALUES (@avatar_id, @job_type, @job_experience, @job_level, @job_sickdays, @job_statusflags) " - + "ON DUPLICATE KEY UPDATE job_experience=VALUES(`job_experience`), job_level=VALUES(`job_level`), " - +" job_sickdays=VALUES(`job_sickdays`), job_statusflags=VALUES(`job_statusflags`); ", jobLevel); + + "ON DUPLICATE KEY UPDATE job_experience=@job_experience, job_level=@job_level, " + +" job_sickdays=@job_sickdays, job_statusflags=@job_statusflags; ", "`avatar_id`,`job_type`"), jobLevel); return; } diff --git a/TSOClient/FSO.Server.Database/DA/Bans/IBans.cs b/TSOClient/FSO.Server.Database/DA/Bans/IBans.cs index 1f1d438dd..c792c024f 100644 --- a/TSOClient/FSO.Server.Database/DA/Bans/IBans.cs +++ b/TSOClient/FSO.Server.Database/DA/Bans/IBans.cs @@ -2,10 +2,12 @@ { public interface IBans { + List All(); DbBan GetByIP(string ip); void Add(string ip, uint userid, string reason, int enddate, string client_id); DbBan GetByClientId(string client_id); void Remove(uint user_id); + void RemoveByIp(string ip); } } diff --git a/TSOClient/FSO.Server.Database/DA/Bans/SqlBans.cs b/TSOClient/FSO.Server.Database/DA/Bans/SqlBans.cs index 165311633..992906f50 100644 --- a/TSOClient/FSO.Server.Database/DA/Bans/SqlBans.cs +++ b/TSOClient/FSO.Server.Database/DA/Bans/SqlBans.cs @@ -9,6 +9,11 @@ public SqlBans(ISqlContext context) : base(context) { } + public List All() + { + return Context.Connection.Query("SELECT * FROM fso_ip_ban").ToList(); + } + public DbBan GetByIP(string ip) { return Context.Connection.Query("SELECT * FROM fso_ip_ban WHERE ip_address = @ip", new { ip = ip }).FirstOrDefault(); @@ -34,7 +39,8 @@ public void Add(string ip, uint userid, string reason, int enddate, string clien user_id = userid, ip_address = ip, banreason = reason, - end_date = enddate + end_date = enddate, + client_id } ); } @@ -47,5 +53,10 @@ public void Remove(uint user_id) { Context.Connection.Query("DELETE FROM fso_ip_ban WHERE user_id = @user_id", new { user_id = user_id }); } + + public void RemoveByIp(string ip) + { + Context.Connection.Query("DELETE FROM fso_ip_ban WHERE ip_address = @ip_address", new { ip_address = ip }); + } } } diff --git a/TSOClient/FSO.Server.Database/DA/Bonus/SqlBonus.cs b/TSOClient/FSO.Server.Database/DA/Bonus/SqlBonus.cs index 8a78b1af4..56d03d7b1 100644 --- a/TSOClient/FSO.Server.Database/DA/Bonus/SqlBonus.cs +++ b/TSOClient/FSO.Server.Database/DA/Bonus/SqlBonus.cs @@ -39,7 +39,9 @@ OR sim_rank IS NOT NULL" public void Insert(IEnumerable bonus) { - Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_bonus (avatar_id, period, bonus_visitor, bonus_property, bonus_sim) VALUES (@avatar_id, @period, @bonus_visitor, @bonus_property, @bonus_sim) ON DUPLICATE KEY UPDATE fso_bonus.avatar_id = fso_bonus.avatar_id", bonus, 100); + Context.Connection.ExecuteBufferedInsert(Context.CompatLayer( + "INSERT INTO fso_bonus (avatar_id, period, bonus_visitor, bonus_property, bonus_sim) VALUES (@avatar_id, @period, @bonus_visitor, @bonus_property, @bonus_sim) ON DUPLICATE KEY UPDATE avatar_id = @avatar_id", + "`avatar_id`,`period`"), bonus, 100); } public void Purge(DateTime date) diff --git a/TSOClient/FSO.Server.Database/DA/Bulletin/SqlBulletinPosts.cs b/TSOClient/FSO.Server.Database/DA/Bulletin/SqlBulletinPosts.cs index 596b0fd75..b94c38f67 100644 --- a/TSOClient/FSO.Server.Database/DA/Bulletin/SqlBulletinPosts.cs +++ b/TSOClient/FSO.Server.Database/DA/Bulletin/SqlBulletinPosts.cs @@ -12,8 +12,8 @@ public SqlBulletinPosts(ISqlContext context) : base(context) public uint Create(DbBulletinPost bulletin) { - return Context.Connection.Query("INSERT INTO fso_bulletin_posts (neighborhood_id, avatar_id, title, body, date, flags, lot_id, type) " + - " VALUES (@neighborhood_id, @avatar_id, @title, @body, @date, @flags, @lot_id, @string_type); SELECT LAST_INSERT_ID();" + return Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_bulletin_posts (neighborhood_id, avatar_id, title, body, date, flags, lot_id, type) " + + " VALUES (@neighborhood_id, @avatar_id, @title, @body, @date, @flags, @lot_id, @string_type); SELECT LAST_INSERT_ID();") , bulletin).First(); } diff --git a/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs b/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs index 7448126d3..79e4e2bea 100644 --- a/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs +++ b/TSOClient/FSO.Server.Database/DA/DbEvents/SqlEvents.cs @@ -21,10 +21,10 @@ public PagedList All(int offset = 1, int limit = 20, string orderBy = " public int Add(DbEvent evt) { - var result = Context.Connection.Query("INSERT INTO fso_events (title, description, start_day, " + + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_events (title, description, start_day, " + "end_day, type, value, value2, mail_subject, mail_message, mail_sender, mail_sender_name) " + " VALUES (@title, @description, @start_day, @end_day, @type_str, @value, @value2, " + - " @mail_subject, @mail_message, @mail_sender, @mail_sender_name); SELECT LAST_INSERT_ID();", evt).First(); + " @mail_subject, @mail_message, @mail_sender, @mail_sender_name); SELECT LAST_INSERT_ID();"), evt).First(); return result; } diff --git a/TSOClient/FSO.Server.Database/DA/DbUppercaseEnum.cs b/TSOClient/FSO.Server.Database/DA/DbUppercaseEnum.cs new file mode 100644 index 000000000..e6ad2f4f5 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/DbUppercaseEnum.cs @@ -0,0 +1,16 @@ +namespace FSO.Server.Database.DA +{ + public struct DbUppercaseEnum where T : System.Enum + { + private readonly T Value; + + public DbUppercaseEnum(T value) + { + Value = value; + } + + public static implicit operator T(DbUppercaseEnum wrapper) => wrapper.Value; + + public static implicit operator DbUppercaseEnum(T value) => new DbUppercaseEnum(value); + } +} diff --git a/TSOClient/FSO.Server.Database/DA/DynPayouts/IDynPayouts.cs b/TSOClient/FSO.Server.Database/DA/DynPayouts/IDynPayouts.cs index 7faf9a132..3fffeb049 100644 --- a/TSOClient/FSO.Server.Database/DA/DynPayouts/IDynPayouts.cs +++ b/TSOClient/FSO.Server.Database/DA/DynPayouts/IDynPayouts.cs @@ -7,7 +7,7 @@ public interface IDynPayouts { List GetSummary(int limitDay); bool InsertDynRecord(List dynPayout); - bool ReplaceDynTuning(List dynTuning); + bool ReplaceDynTuning(List dynTuning, int owner = 1); List GetPayoutHistory(int limitDay); bool Purge(int limitDay); diff --git a/TSOClient/FSO.Server.Database/DA/DynPayouts/SqlDynPayouts.cs b/TSOClient/FSO.Server.Database/DA/DynPayouts/SqlDynPayouts.cs index 58d3e61bc..7592957f1 100644 --- a/TSOClient/FSO.Server.Database/DA/DynPayouts/SqlDynPayouts.cs +++ b/TSOClient/FSO.Server.Database/DA/DynPayouts/SqlDynPayouts.cs @@ -20,7 +20,7 @@ public List GetPayoutHistory(int limitDay) public List GetSummary(int limitDay) { - return Context.Connection.Query("SELECT transaction_type, sum(value) AS value, sum(count) AS sum FROM fso.fso_transactions " + return Context.Connection.Query("SELECT transaction_type, sum(value) AS value, sum(count) AS sum FROM fso_transactions " +"WHERE transaction_type > 40 AND transaction_type < 51 AND day >= @limitDay GROUP BY transaction_type", new { limitDay = limitDay }).ToList(); } @@ -28,7 +28,9 @@ public bool InsertDynRecord(List dynPayout) { try { - Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_dyn_payouts (day, skilltype, multiplier, flags) VALUES (@day, @skilltype, @multiplier, @flags) ON DUPLICATE KEY UPDATE multiplier = @multiplier", dynPayout, 100); + Context.Connection.ExecuteBufferedInsert(Context.CompatLayer( + "INSERT INTO fso_dyn_payouts (day, skilltype, multiplier, flags) VALUES (@day, @skilltype, @multiplier, @flags) ON DUPLICATE KEY UPDATE multiplier = @multiplier", + "`day`,`skilltype`"), dynPayout, 100); } catch (SqlException) { @@ -43,12 +45,15 @@ public bool Purge(int limitDay) return true; } - public bool ReplaceDynTuning(List dynTuning) + public bool ReplaceDynTuning(List dynTuning, int owner = 1) { try { - var deleted = Context.Connection.Execute("DELETE FROM fso_tuning WHERE owner_type = 'DYNAMIC' AND owner_id = 1"); - Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_tuning (tuning_type, tuning_table, tuning_index, value, owner_type, owner_id) VALUES (@tuning_type, @tuning_table, @tuning_index, @value, @owner_type, @owner_id)", dynTuning, 100); + var deleted = Context.Connection.Execute($"DELETE FROM fso_tuning WHERE owner_type = 'DYNAMIC' AND owner_id = {owner}"); + if (dynTuning.Count > 0) + { + Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_tuning (tuning_type, tuning_table, tuning_index, value, owner_type, owner_id) VALUES (@tuning_type, @tuning_table, @tuning_index, @value, @owner_type, @owner_id)", dynTuning, 100); + } } catch (SqlException) { return false; diff --git a/TSOClient/FSO.Server.Database/DA/Elections/SqlElections.cs b/TSOClient/FSO.Server.Database/DA/Elections/SqlElections.cs index 873385c96..058ce54b9 100644 --- a/TSOClient/FSO.Server.Database/DA/Elections/SqlElections.cs +++ b/TSOClient/FSO.Server.Database/DA/Elections/SqlElections.cs @@ -125,8 +125,8 @@ public bool DeleteCandidate(uint election_cycle_id, uint candidate_avatar_id) public uint CreateCycle(DbElectionCycle cycle) { - var result = Context.Connection.Query("INSERT INTO fso_election_cycles (start_date, end_date, current_state, election_type) " - + "VALUES (@start_date, @end_date, @current_state, @election_type); SELECT LAST_INSERT_ID();", + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_election_cycles (start_date, end_date, current_state, election_type) " + + "VALUES (@start_date, @end_date, @current_state, @election_type); SELECT LAST_INSERT_ID();"), new { cycle.start_date, cycle.end_date, current_state = cycle.current_state.ToString(), election_type = cycle.election_type.ToString() }).FirstOrDefault(); diff --git a/TSOClient/FSO.Server.Database/DA/GlobalCooldowns/SqlGlobalCooldowns.cs b/TSOClient/FSO.Server.Database/DA/GlobalCooldowns/SqlGlobalCooldowns.cs index a3ab8419a..5e794d1f6 100644 --- a/TSOClient/FSO.Server.Database/DA/GlobalCooldowns/SqlGlobalCooldowns.cs +++ b/TSOClient/FSO.Server.Database/DA/GlobalCooldowns/SqlGlobalCooldowns.cs @@ -14,15 +14,15 @@ public DbGlobalCooldowns Get(uint objguid, uint avatarOrUserid, bool useAccount, { if (useAccount) return Context.Connection.Query("SELECT * FROM fso_global_cooldowns WHERE object_guid = @guid AND " + - "user_id = @id AND category = @category", new { guid = objguid, id = avatarOrUserid, category = category }).FirstOrDefault(); + "user_id = @id AND category = @category", new { guid = (ulong)objguid, id = avatarOrUserid, category = category }).FirstOrDefault(); else return Context.Connection.Query("SELECT * FROM fso_global_cooldowns WHERE object_guid = @guid AND " + - "avatar_id = @id AND category = @category", new { guid = objguid, id = avatarOrUserid, category = category }).FirstOrDefault(); + "avatar_id = @id AND category = @category", new { guid = (ulong)objguid, id = avatarOrUserid, category = category }).FirstOrDefault(); } public List GetAllByObj(uint objguid) { - return Context.Connection.Query("SELECT * FROM fso_global_cooldowns WHERE object_guid = @guid", new { guid = objguid }).ToList(); + return Context.Connection.Query("SELECT * FROM fso_global_cooldowns WHERE object_guid = @guid", new { guid = (ulong)objguid }).ToList(); } public List GetAllByAvatar(uint avatarid) @@ -33,7 +33,7 @@ public List GetAllByAvatar(uint avatarid) public List GetAllByObjectAndAvatar(uint objguid, uint avatarid) { return Context.Connection.Query("SELECT * FROM fso_global_cooldowns WHERE object_guid = @guid AND " + - "avatar_id = @avatarid", new { guid = objguid, avatarid = avatarid }).ToList(); + "avatar_id = @avatarid", new { guid = (ulong)objguid, avatarid = avatarid }).ToList(); } public bool Create(DbGlobalCooldowns newCooldown) { @@ -43,7 +43,14 @@ public bool Create(DbGlobalCooldowns newCooldown) public bool Update(DbGlobalCooldowns updatedCooldown) { return Context.Connection.Execute("UPDATE fso_global_cooldowns SET expiry = @expiry WHERE object_guid = @object_guid AND " + - "avatar_id = @avatar_id AND user_id = @user_id AND category = @category", updatedCooldown) > 0; + "avatar_id = @avatar_id AND user_id = @user_id AND category = @category", + new { + updatedCooldown.category, + updatedCooldown.avatar_id, + updatedCooldown.user_id, + object_guid = (ulong)updatedCooldown.object_guid, + updatedCooldown.expiry + }) > 0; } } } diff --git a/TSOClient/FSO.Server.Database/DA/IDA.cs b/TSOClient/FSO.Server.Database/DA/IDA.cs index 9d4762a7d..a0b1aeaa6 100644 --- a/TSOClient/FSO.Server.Database/DA/IDA.cs +++ b/TSOClient/FSO.Server.Database/DA/IDA.cs @@ -30,6 +30,9 @@ using FSO.Server.Database.DA.Bulletin; using FSO.Server.Database.DA.Updates; using FSO.Server.Database.DA.GlobalCooldowns; +using FSO.Server.Database.DA.ArchiveUsers; +using FSO.Server.Database.DA.ArchiveFeatured; +using FSO.Server.Database.DA.ArchiveRecents; namespace FSO.Server.Database.DA { @@ -68,6 +71,9 @@ public interface IDA : IDisposable IEmailConfirmations EmailConfirmations { get; } IUpdates Updates { get; } IGlobalCooldowns GlobalCooldowns { get; } + IArchiveUsers ArchiveUsers { get; } + IArchiveFeatured ArchiveFeatured { get; } + IArchiveRecents ArchiveRecents { get; } void Flush(); } } diff --git a/TSOClient/FSO.Server.Database/DA/ISqlContext.cs b/TSOClient/FSO.Server.Database/DA/ISqlContext.cs index 65c59b826..8683a507b 100644 --- a/TSOClient/FSO.Server.Database/DA/ISqlContext.cs +++ b/TSOClient/FSO.Server.Database/DA/ISqlContext.cs @@ -5,7 +5,10 @@ namespace FSO.Server.Database.DA { public interface ISqlContext : IDisposable { + bool SupportsFunctions { get; } + bool UseBlobInventory { get; } DbConnection Connection { get; } void Flush(); + string CompatLayer(string sql, string updateKey = null); } } diff --git a/TSOClient/FSO.Server.Database/DA/Inbox/SqlInbox.cs b/TSOClient/FSO.Server.Database/DA/Inbox/SqlInbox.cs index f72735871..0a52178ce 100644 --- a/TSOClient/FSO.Server.Database/DA/Inbox/SqlInbox.cs +++ b/TSOClient/FSO.Server.Database/DA/Inbox/SqlInbox.cs @@ -12,10 +12,10 @@ public SqlInbox(ISqlContext context) : base(context){ public int CreateMessage(DbInboxMsg msg) { - var result = Context.Connection.Query("INSERT INTO fso_inbox (sender_id, target_id, subject, " + + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_inbox (sender_id, target_id, subject, " + "body, sender_name, time, msg_type, msg_subtype, read_state) " + " VALUES (@sender_id, @target_id, @subject, @body, @sender_name, " + - " @time, @msg_type, @msg_subtype, @read_state); SELECT LAST_INSERT_ID();", msg).First(); + " @time, @msg_type, @msg_subtype, @read_state); SELECT LAST_INSERT_ID();"), msg).First(); return result; } @@ -41,6 +41,7 @@ public List GetMessages(uint avatarID) public List GetMessagesAfter(uint avatarID, DateTime after) { + after = after.AddMicroseconds(1); // Added due to sqlite not knowing what a > comparison is return Context.Connection.Query("SELECT * FROM fso_inbox WHERE target_id = @id AND time > @after", new { id = avatarID, after = after }).ToList(); } } diff --git a/TSOClient/FSO.Server.Database/DA/LotClaims/SqlLotsClaims.cs b/TSOClient/FSO.Server.Database/DA/LotClaims/SqlLotsClaims.cs index cbbdda3b9..306359697 100644 --- a/TSOClient/FSO.Server.Database/DA/LotClaims/SqlLotsClaims.cs +++ b/TSOClient/FSO.Server.Database/DA/LotClaims/SqlLotsClaims.cs @@ -2,6 +2,7 @@ using FSO.Common.Enum; using MySql.Data.MySqlClient; using System.Collections.Generic; +using System.Data.Common; using System.Linq; namespace FSO.Server.Database.DA.LotClaims @@ -19,7 +20,7 @@ public bool Claim(uint id, string previousOwner, string newOwner) var newClaim = Context.Connection.Query("SELECT * FROM fso_lot_claims WHERE claim_id = @claim_id AND owner = @owner", new { claim_id = (int)id, owner = newOwner }).FirstOrDefault(); return newClaim != null; } - catch (MySqlException ex) + catch (DbException ex) { return false; } @@ -53,9 +54,9 @@ public void RemoveAllByOwner(string owner) public uint? TryCreate(DbLotClaim claim){ try { - return (uint)Context.Connection.Query("INSERT INTO fso_lot_claims (shard_id, lot_id, owner) " + - " VALUES (@shard_id, @lot_id, @owner); SELECT LAST_INSERT_ID();", claim).First(); - }catch(MySqlException ex){ + return (uint)Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_lot_claims (shard_id, lot_id, owner) " + + " VALUES (@shard_id, @lot_id, @owner); SELECT LAST_INSERT_ID();"), claim).First(); + }catch(DbException ex){ return null; } } @@ -63,8 +64,8 @@ public void RemoveAllByOwner(string owner) public List AllLocations(int shard_id) { return Context.Connection.Query("SELECT b.location AS location, active " + - "FROM fso.fso_lot_claims AS a " + - "JOIN fso.fso_lots AS b " + + "FROM fso_lot_claims AS a " + + "JOIN fso_lots AS b " + "ON a.lot_id = b.lot_id " + "JOIN(SELECT location, COUNT(*) as active FROM fso_avatar_claims GROUP BY location) AS c " + "ON b.location = c.location WHERE a.shard_id = @shard_id", new { shard_id = shard_id }).ToList(); @@ -73,17 +74,17 @@ public List AllLocations(int shard_id) public List AllActiveLots(int shard_id) { return Context.Connection.Query("SELECT b.*, active "+ - "FROM fso.fso_lot_claims as a "+ - "right JOIN fso.fso_lots as b ON a.lot_id = b.lot_id "+ - "JOIN (select location, count(*) as active FROM fso.fso_avatar_claims group by location) as c "+ + "FROM fso_lot_claims as a "+ + "right JOIN fso_lots as b ON a.lot_id = b.lot_id "+ + "JOIN (select location, count(*) as active FROM fso_avatar_claims group by location) as c "+ "on b.location = c.location where a.shard_id = @shard_id", new { shard_id = shard_id }).ToList(); } public List Top100Filter(int shard_id, LotCategory category, int limit) { return Context.Connection.Query("SELECT b.location AS location, active " + - "FROM fso.fso_lot_claims AS a " + - "JOIN fso.fso_lots AS b " + + "FROM fso_lot_claims AS a " + + "JOIN fso_lots AS b " + "ON a.lot_id = b.lot_id " + "JOIN(SELECT location, COUNT(*) as active FROM fso_avatar_claims GROUP BY location) AS c " + "ON b.location = c.location WHERE a.shard_id = @shard_id " + diff --git a/TSOClient/FSO.Server.Database/DA/LotTop100/SqlLotTop100.cs b/TSOClient/FSO.Server.Database/DA/LotTop100/SqlLotTop100.cs index 8cc9c59d2..6a1b13eb7 100644 --- a/TSOClient/FSO.Server.Database/DA/LotTop100/SqlLotTop100.cs +++ b/TSOClient/FSO.Server.Database/DA/LotTop100/SqlLotTop100.cs @@ -21,13 +21,71 @@ public bool Calculate(DateTime date, int shard_id) { try { - Context.Connection.Execute("CALL fso_lot_top_100_calc_all(@date, @shard_id);", new { date = date, shard_id = shard_id }); + if (Context.SupportsFunctions) + { + Context.Connection.Execute("CALL fso_lot_top_100_calc_all(@date, @shard_id);", new { date = date, shard_id = shard_id }); + } + else + { + CalculateCategory("money", date, shard_id); + CalculateCategory("offbeat", date, shard_id); + CalculateCategory("romance", date, shard_id); + CalculateCategory("services", date, shard_id); + CalculateCategory("shopping", date, shard_id); + CalculateCategory("skills", date, shard_id); + CalculateCategory("welcome", date, shard_id); + CalculateCategory("games", date, shard_id); + CalculateCategory("entertainment", date, shard_id); + CalculateCategory("residence", date, shard_id); + } return true; }catch(Exception ex) { return false; } } + + public void CalculateCategory(string category, DateTime date, int shard_id) + { + var transaction = Context.Connection.BeginTransaction(); + + var start_date = date - TimeSpan.FromDays(4); + var timestamp = DateTime.Now; + + try + { + Context.Connection.Execute(@"DELETE FROM fso_lot_top_100 WHERE shard_id = @shard_id AND category = @category; + INSERT INTO fso_lot_top_100 (category, rank, shard_id, lot_id, minutes, date) + SELECT category, + rank, + shard_id, + lot_id, + minutes, + date + FROM ( + SELECT lot.category, lot.lot_id, lot.shard_id, FLOOR(AVG(visits.minutes)) as minutes, @timestamp as date, + ROW_NUMBER () OVER ( + ORDER BY minutes DESC + ) rank + FROM fso_lot_visit_totals visits + INNER JOIN fso_lots lot ON visits.lot_id = lot.lot_id + WHERE lot.category = @category + AND date BETWEEN @start_date AND @date + AND lot.shard_id = @shard_id + GROUP BY lot.lot_id + ORDER BY minutes DESC + LIMIT 100 + ) as top100;", new { date, shard_id, category, timestamp, start_date }); + + transaction.Commit(); + } + catch (Exception e) + { + transaction.Rollback(); + throw e; + } + } + public IEnumerable GetAllByShard(int shard_id) { return Context.Connection.Query("SELECT top.*, l.name as lot_name, l.location as lot_location FROM fso_lot_top_100 top LEFT JOIN fso_lots l ON top.lot_id = l.lot_id WHERE top.shard_id = @shard_id", new diff --git a/TSOClient/FSO.Server.Database/DA/LotVisitTotals/SqlLotVisitTotals.cs b/TSOClient/FSO.Server.Database/DA/LotVisitTotals/SqlLotVisitTotals.cs index 98e68ac30..a21d01a88 100644 --- a/TSOClient/FSO.Server.Database/DA/LotVisitTotals/SqlLotVisitTotals.cs +++ b/TSOClient/FSO.Server.Database/DA/LotVisitTotals/SqlLotVisitTotals.cs @@ -14,7 +14,9 @@ public SqlLotVisitTotals(ISqlContext context) : base(context) public void Insert(IEnumerable input) { try { - Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_lot_visit_totals (lot_id, date, minutes) VALUES (@lot_id, @date, @minutes) ON DUPLICATE KEY UPDATE minutes=VALUES(minutes)", input, 100); + Context.Connection.ExecuteBufferedInsert(Context.CompatLayer( + "INSERT INTO fso_lot_visit_totals (lot_id, date, minutes) VALUES (@lot_id, @date, @minutes) ON DUPLICATE KEY UPDATE minutes=@minutes", + "`lot_id`,`date`"), input, 100); }catch(Exception ex) { } diff --git a/TSOClient/FSO.Server.Database/DA/LotVisits/SqlLotVisits.cs b/TSOClient/FSO.Server.Database/DA/LotVisits/SqlLotVisits.cs index 2ac75b569..97e26897a 100644 --- a/TSOClient/FSO.Server.Database/DA/LotVisits/SqlLotVisits.cs +++ b/TSOClient/FSO.Server.Database/DA/LotVisits/SqlLotVisits.cs @@ -13,12 +13,37 @@ public SqlLotVisits(ISqlContext context) : base(context){ public int? Visit(uint avatar_id, DbLotVisitorType visitor_type, int lot_id) { try { - //Stored procedure will handle erroring any active visits that should no longer be active - return Context.Connection.Query("SELECT `fso_lot_visits_create`(@avatar_id, @lot_id, @type)", new { + if (Context.SupportsFunctions) + { + //Stored procedure will handle erroring any active visits that should no longer be active + return Context.Connection.Query("SELECT `fso_lot_visits_create`(@avatar_id, @lot_id, @type)", new + { avatar_id = avatar_id, lot_id = lot_id, type = visitor_type.ToString() }).First(); + } + else + { + /* +CREATE FUNCTION `fso_lot_visits_create`(`p_avatar_id` INT, `p_lot_id` INT, `p_visitor_type` VARCHAR(50)) RETURNS int(11) + READS SQL DATA +BEGIN + #Error any open active visit, can only have one active + UPDATE fso_lot_visits SET `status` = 'failed', time_closed = current_timestamp WHERE avatar_id = p_avatar_id AND `status` = 'active'; + #Record visit + INSERT INTO fso_lot_visits (avatar_id, lot_id, type, status) VALUES (p_avatar_id, p_lot_id, p_visitor_type, 'active'); + RETURN LAST_INSERT_ID(); +END;*/ + return Context.Connection.Query(Context.CompatLayer(@"UPDATE fso_lot_visits SET `status` = 'failed', time_closed = current_timestamp WHERE avatar_id = @avatar_id AND `status` = 'active'; + INSERT INTO fso_lot_visits (avatar_id, lot_id, type, status) VALUES (@avatar_id, @lot_id, @type, 'active'); + SELECT LAST_INSERT_ID();"), new { + avatar_id = avatar_id, + lot_id = lot_id, + type = visitor_type.ToString() + }).First(); + + } }catch(Exception ex){ return null; } diff --git a/TSOClient/FSO.Server.Database/DA/Lots/DbLot.cs b/TSOClient/FSO.Server.Database/DA/Lots/DbLot.cs index 937c98f6d..4dcc12037 100644 --- a/TSOClient/FSO.Server.Database/DA/Lots/DbLot.cs +++ b/TSOClient/FSO.Server.Database/DA/Lots/DbLot.cs @@ -2,6 +2,56 @@ namespace FSO.Server.Database.DA.Lots { + [Flags] + public enum LotMoveFlags + { + None = 0, + + /// + /// This lot has moved. + /// Flatten the buildable area, regenerate the terrain. + /// + Moved = 1, + + /// + /// This lot is new, or being reset as new. + /// The terrain will be fully reset, and unowned objects will be placed on it. + /// + New = 1 << 1, + + /// + /// This lot is being deleted when the lot container shuts down. + /// Typically when a lot has this flag, it was opened to migrate all roomie objects into their inventories. + /// + PermanentDelete = 1 << 2, + + /// + /// Similar to Moved, but doesn't flatten the buildable area. + /// Triggers when the city terrain is changed around this lot. + /// + TerrainRegen = 1 << 3, + + ShouldClearObjects = New | PermanentDelete + } + + [Flags] + public enum LotArchiveFlags + { + /// + /// Archive a property from an old save. + /// Objects unowned by roommates should be transformed into ownerless objects. + /// The terrain should be recalculated without damaging the buildable area. + /// After loading, the archive flags change to 2. + /// + ArchiveFromOldSave = 1, + + /// + /// Some special rules for archive lots. + /// The object limit disable isn't active, similar to community lots. + /// + ArchiveRules = 1 << 1, + } + public class DbLot { public int lot_id { get; set; } @@ -23,6 +73,33 @@ public class DbLot public byte thumb3d_dirty { get; set; } public uint thumb3d_time { get; set; } + + // Added for archive + public byte archive_flags { get; set; } + + public LotMoveFlags MoveFlags + { + get + { + return (LotMoveFlags)move_flags; + } + set + { + move_flags = (byte)value; + } + } + + public LotArchiveFlags ArchiveFlags + { + get + { + return (LotArchiveFlags)archive_flags; + } + set + { + archive_flags = (byte)value; + } + } } /**Lot diff --git a/TSOClient/FSO.Server.Database/DA/Lots/ILots.cs b/TSOClient/FSO.Server.Database/DA/Lots/ILots.cs index 9725129ae..1375b4629 100644 --- a/TSOClient/FSO.Server.Database/DA/Lots/ILots.cs +++ b/TSOClient/FSO.Server.Database/DA/Lots/ILots.cs @@ -23,12 +23,14 @@ public interface ILots void RenameLot(int id, string newName); void SetDirty(int id, byte dirty); + void SetTerrainDirty(IEnumerable ids); DbLot Get3DWork(); List SearchExact(int shard_id, string name, int limit); List SearchWildcard(int shard_id, string name, int limit); void UpdateRingBackup(int lot_id, sbyte ring_backup_num); + void UpdateRingBackupSilent(int lot_id, sbyte ring_backup_num); void UpdateDescription(int lot_id, string description); void UpdateLotCategory(int lot_id, LotCategory category, uint skillMode); void UpdateLotSkillMode(int lot_id, uint skillMode); @@ -36,6 +38,7 @@ public interface ILots bool UpdateLocation(int lot_id, uint location, bool startFresh); void UpdateOwner(int lot_id, uint? avatar_id); void ReassignOwner(int lot_id); + void UpdateArchiveFlags(int lot_id, sbyte flags); void CreateLotServerTicket(DbLotServerTicket ticket); void DeleteLotServerTicket(string id); diff --git a/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs b/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs index aa12b21ce..2b5fa4933 100644 --- a/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs +++ b/TSOClient/FSO.Server.Database/DA/Lots/SqlLots.cs @@ -5,6 +5,7 @@ using FSO.Server.Database.DA.Utils; using System; using System.Collections.Generic; +using Microsoft.Data.Sqlite; using System.Linq; namespace FSO.Server.Database.DA.Lots @@ -48,10 +49,10 @@ public uint Create(DbLot lot) var t = Context.Connection.BeginTransaction(); try { - var result = (uint)Context.Connection.Query("INSERT INTO fso_lots (shard_id, name, description, " + + var result = (uint)Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_lots (shard_id, name, description, " + "owner_id, location, neighborhood_id, created_date, category_change_date, category, buildable_area) " + " VALUES (@shard_id, @name, @description, @owner_id, @location, " + - " @neighborhood_id, @created_date, @category_change_date, @category, @buildable_area); SELECT LAST_INSERT_ID();", new + " @neighborhood_id, @created_date, @category_change_date, @category, @buildable_area); SELECT LAST_INSERT_ID();"), new { shard_id = lot.shard_id, name = lot.name, @@ -148,9 +149,19 @@ public DbLot GetByLocation(int shard_id, uint location) public List GetAdjToLocation(int shard_id, uint location) { + uint[] locations = new uint[8]; + int i = 0; + for (int y = -1; y < 2; y++) + { + for (int x = -1; x < 2; x++) + { + if (y == 0 && x == 0) continue; + locations[i++] = (uint)(location + (x * 65536) + y); + } + } + return Context.Connection.Query("SELECT * FROM fso_lots WHERE " - + "(ABS(CAST((location&65535) AS SIGNED) - CAST((@location&65535) AS SIGNED)) = 1 OR ABS(CAST((location/65536) AS SIGNED) - CAST((@location/65536) AS SIGNED)) = 1) " - + "AND shard_id = @shard_id AND move_flags = 0", new { location = location, shard_id = shard_id }).ToList(); + + "shard_id = @shard_id AND location IN @locations AND move_flags = 0", new { locations, shard_id }).ToList(); } public void RenameLot(int id, string newName) @@ -169,6 +180,11 @@ public void SetDirty(int id, byte dirty) } } + public void SetTerrainDirty(IEnumerable ids) + { + Context.Connection.Query("UPDATE fso_lots SET move_flags = 8 WHERE location in @ids AND move_flags = 0", new { ids }); + } + public DbLot Get3DWork() { var item = Context.Connection.Query("SELECT * FROM fso_lots WHERE thumb3d_dirty = 1 AND thumb3d_time < @time ORDER BY thumb3d_time LIMIT 1", new { time = Epoch.Now - 300 }).FirstOrDefault(); @@ -206,6 +222,17 @@ public void UpdateRingBackup(int lot_id, sbyte ring_backup_num) new { ring_backup_num = ring_backup_num, id = lot_id }); } + public void UpdateRingBackupSilent(int lot_id, sbyte ring_backup_num) + { + Context.Connection.Query("UPDATE fso_lots SET ring_backup_num = @ring_backup_num WHERE lot_id = @id", + new { ring_backup_num, id = lot_id }); + } + + public void UpdateArchiveFlags(int lot_id, sbyte archive_flags) + { + Context.Connection.Query("UPDATE fso_lots SET archive_flags = @archive_flags WHERE lot_id = @id", + new { archive_flags, id = lot_id }); + } public void CreateLotServerTicket(DbLotServerTicket ticket) { @@ -268,29 +295,60 @@ public bool UpdateLocation(int lot_id, uint location, bool startFresh) return success; } - private static string NHoodQuery = - "UPDATE fso.fso_lots l " + + private static string NHoodQuery = + "UPDATE fso_lots l " + "SET neighborhood_id = " + "COALESCE((SELECT neighborhood_id " + - "FROM fso.fso_neighborhoods n " + + "FROM fso_neighborhoods n " + + "ORDER BY(POWER(((l.location & 65535) + 0.0) - ((n.location & 65535) + 0.0), 2) + " + + "POWER((FLOOR(l.location / 65536) + 0.0) - (FLOOR(n.location / 65536) + 0.0), 2)) " + + "LIMIT 1), 0) "; + + + private static string NHoodSqliteQuery = + "UPDATE fso_lots " + + "SET neighborhood_id = " + + "COALESCE((SELECT n.neighborhood_id " + + "FROM fso_neighborhoods n JOIN fso_lots l " + + "WHERE l.lot_id = fso_lots.lot_id " + "ORDER BY(POWER(((l.location & 65535) + 0.0) - ((n.location & 65535) + 0.0), 2) + " + "POWER((FLOOR(l.location / 65536) + 0.0) - (FLOOR(n.location / 65536) + 0.0), 2)) " + "LIMIT 1), 0) "; public int UpdateAllNeighborhoods(int shard_id) { - return Context.Connection.Execute( - NHoodQuery + - "WHERE l.shard_id = @shard_id" - , new { shard_id = shard_id }); + if (Context.Connection is SqliteConnection) + { + return Context.Connection.Execute( + NHoodSqliteQuery + + "WHERE shard_id = @shard_id" + , new { shard_id = shard_id }); + } + else + { + return Context.Connection.Execute( + NHoodQuery + + "WHERE l.shard_id = @shard_id" + , new { shard_id = shard_id }); + } } public bool UpdateNeighborhood(int lot_id) { - return (Context.Connection.Execute( - NHoodQuery + - "WHERE l.lot_id = @lot_id" - , new { lot_id = lot_id })) > 0; + if (Context.Connection is SqliteConnection) + { + return (Context.Connection.Execute( + NHoodSqliteQuery + + "WHERE lot_id = @lot_id" + , new { lot_id = lot_id })) > 0; + } + else + { + return (Context.Connection.Execute( + NHoodQuery + + "WHERE l.lot_id = @lot_id" + , new { lot_id = lot_id })) > 0; + } } } } diff --git a/TSOClient/FSO.Server.Database/DA/MySqlContext.cs b/TSOClient/FSO.Server.Database/DA/MySqlContext.cs index 44e7f3a23..9ff9c87b2 100644 --- a/TSOClient/FSO.Server.Database/DA/MySqlContext.cs +++ b/TSOClient/FSO.Server.Database/DA/MySqlContext.cs @@ -7,6 +7,8 @@ namespace FSO.Server.Database.DA { public class MySqlContext : ISqlContext, IDisposable { + public bool SupportsFunctions => true; + public bool UseBlobInventory => false; private readonly string _connectionString; private DbConnection _connection; @@ -29,6 +31,11 @@ public DbConnection Connection } } + public string CompatLayer(string sql, string updateKey = null) + { + return sql; + } + public void Dispose() { if (_connection != null) diff --git a/TSOClient/FSO.Server.Database/DA/MySqlDAFactory.cs b/TSOClient/FSO.Server.Database/DA/MySqlDAFactory.cs index ddb0d641d..00e4039cf 100644 --- a/TSOClient/FSO.Server.Database/DA/MySqlDAFactory.cs +++ b/TSOClient/FSO.Server.Database/DA/MySqlDAFactory.cs @@ -1,4 +1,8 @@ -namespace FSO.Server.Database.DA +using Dapper; +using FSO.Server.Database.DA.Tuning; +using FSO.Server.Database.SqliteCompat; + +namespace FSO.Server.Database.DA { public class MySqlDAFactory : IDAFactory { @@ -7,6 +11,7 @@ public class MySqlDAFactory : IDAFactory public MySqlDAFactory(DatabaseConfiguration config) { this.Config = config; + SqlMapper.AddTypeHandler(new DbEnumHandler()); } public IDA Get() diff --git a/TSOClient/FSO.Server.Database/DA/Neighborhoods/SqlNeighborhoods.cs b/TSOClient/FSO.Server.Database/DA/Neighborhoods/SqlNeighborhoods.cs index 90a3a1ef0..976730706 100644 --- a/TSOClient/FSO.Server.Database/DA/Neighborhoods/SqlNeighborhoods.cs +++ b/TSOClient/FSO.Server.Database/DA/Neighborhoods/SqlNeighborhoods.cs @@ -14,10 +14,10 @@ public SqlNeighborhoods(ISqlContext context) : base(context) public int AddNhood(DbNeighborhood hood) { - var result = Context.Connection.Query("INSERT INTO fso_neighborhoods (name, description, " + + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_neighborhoods (name, description, " + "shard_id, location, color, guid) " + " VALUES (@name, @description, " + - " @shard_id, @location, @color, @guid); SELECT LAST_INSERT_ID();", hood).First(); + " @shard_id, @location, @color, @guid); SELECT LAST_INSERT_ID();"), hood).First(); return result; } @@ -62,7 +62,7 @@ public DbNeighborhood GetByLocation(uint location) { return Context.Connection.Query( "SELECT neighborhood_id " + - "FROM fso.fso_neighborhoods n " + + "FROM fso_neighborhoods n " + "ORDER BY(POWER(((@location & 65535) + 0.0) - ((n.location & 65535) + 0.0), 2) + " + "POWER((FLOOR(@location / 65536) + 0.0) - (FLOOR(n.location / 65536) + 0.0), 2)) " + "LIMIT 1", new { location = location }).FirstOrDefault(); @@ -118,10 +118,10 @@ public DbNhoodBan GetNhoodBan(uint user_id) public bool AddNhoodBan(DbNhoodBan ban) { - var result = Context.Connection.Query("INSERT INTO fso_nhood_ban (user_id, ban_reason, end_date) " + + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_nhood_ban (user_id, ban_reason, end_date) " + "VALUES (@user_id, @ban_reason, @end_date) " + "ON DUPLICATE KEY UPDATE ban_reason = @ban_reason, end_date = @end_date; " + - "SELECT LAST_INSERT_ID();", ban).First(); + "SELECT LAST_INSERT_ID();", "`user_id`"), ban).First(); return result > 0; } diff --git a/TSOClient/FSO.Server.Database/DA/Objects/DbObject.cs b/TSOClient/FSO.Server.Database/DA/Objects/DbObject.cs index c724eb7e1..ca11bb8c3 100644 --- a/TSOClient/FSO.Server.Database/DA/Objects/DbObject.cs +++ b/TSOClient/FSO.Server.Database/DA/Objects/DbObject.cs @@ -29,4 +29,38 @@ public class DbObject public List AugmentedAttributes; } + + public class DbObjectCreate + { + public uint object_id { get; set; } + public int shard_id { get; set; } + public uint? owner_id { get; set; } + public int? lot_id { get; set; } + public string dyn_obj_name { get; set; } + public ulong type { get; set; } //guid: when creating this is a ulong to work around sqlite bugs + public ushort graphic { get; set; } + public uint value { get; set; } + public int budget { get; set; } + public ulong dyn_flags_1 { get; set; } + public ulong dyn_flags_2 { get; set; } + public uint upgrade_level { get; set; } + public byte has_db_attributes { get; set; } + + public DbObjectCreate(DbObject obj) + { + object_id = obj.object_id; + shard_id = obj.shard_id; + owner_id = obj.owner_id; + lot_id = obj.lot_id; + dyn_obj_name = obj.dyn_obj_name; + type = obj.type; + graphic = obj.graphic; + value = obj.graphic; + budget = obj.budget; + dyn_flags_1 = obj.dyn_flags_1; + dyn_flags_2 = obj.dyn_flags_2; + upgrade_level = obj.upgrade_level; + has_db_attributes = obj.has_db_attributes; + } + } } diff --git a/TSOClient/FSO.Server.Database/DA/Objects/IObjects.cs b/TSOClient/FSO.Server.Database/DA/Objects/IObjects.cs index c69489707..f44755a84 100644 --- a/TSOClient/FSO.Server.Database/DA/Objects/IObjects.cs +++ b/TSOClient/FSO.Server.Database/DA/Objects/IObjects.cs @@ -29,5 +29,14 @@ public interface IObjects int GetSpecificObjectAttribute(uint objectID, int index); void SetObjectAttributes(List attrs); int TotalObjectAttributes(uint guid, int index); + + List ListIDs(bool onLot); + + bool GetDbObjectState(uint id, out byte[] data); + bool SetDbObjectState(uint id, byte[] data); + + List GetByType(uint guid); + + int PurgeStateOnLot(); } } diff --git a/TSOClient/FSO.Server.Database/DA/Objects/SqlObjects.cs b/TSOClient/FSO.Server.Database/DA/Objects/SqlObjects.cs index 1b91144ed..63cc1026f 100644 --- a/TSOClient/FSO.Server.Database/DA/Objects/SqlObjects.cs +++ b/TSOClient/FSO.Server.Database/DA/Objects/SqlObjects.cs @@ -7,6 +7,7 @@ namespace FSO.Server.Database.DA.Objects { + // NOTE: sqlite tends to interpret uint parameters as negative, which is a problem for GUID. I'm casting them all to long for now, but this might make mysql unhappy. public class SqlObjects : AbstractSqlDA, IObjects { public SqlObjects(ISqlContext context) : base(context){ @@ -19,11 +20,11 @@ public IEnumerable All(int shard_id) public uint Create(DbObject obj) { - return (uint)Context.Connection.Query("INSERT INTO fso_objects (shard_id, owner_id, lot_id, " + + return (uint)Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_objects (shard_id, owner_id, lot_id, " + "dyn_obj_name, type, graphic, value, budget, upgrade_level, has_db_attributes) " + " VALUES (@shard_id, @owner_id, @lot_id, @dyn_obj_name, @type," + - " @graphic, @value, @budget, @upgrade_level, @has_db_attributes); SELECT LAST_INSERT_ID();" - , obj).First(); + " @graphic, @value, @budget, @upgrade_level, @has_db_attributes); SELECT LAST_INSERT_ID();") + , new DbObjectCreate(obj)).First(); } public DbObject Get(uint id) @@ -63,13 +64,13 @@ public List GetAvatarInventoryWithAttrs(uint avatar_id) public List ObjOfTypeForAvatar(uint avatar_id, uint guid) { return Context.Connection.Query("SELECT * FROM fso_objects WHERE owner_id = @avatar_id AND type = @guid", - new { avatar_id = avatar_id, guid = guid }).ToList(); + new { avatar_id = avatar_id, guid = (ulong)guid }).ToList(); } public List ObjOfTypeInAvatarInventory(uint avatar_id, uint guid) { return Context.Connection.Query("SELECT * FROM fso_objects WHERE owner_id = @avatar_id AND lot_id IS NULL AND type = @guid", - new { avatar_id = avatar_id, guid = guid}).ToList(); + new { avatar_id = avatar_id, guid = (ulong)guid }).ToList(); } public int ReturnLostObjects(uint lot_id, IEnumerable object_ids) @@ -212,14 +213,66 @@ public int GetSpecificObjectAttribute(uint objectID, int index) public void SetObjectAttributes(List attrs) { - Context.Connection.ExecuteBufferedInsert("INSERT INTO fso_object_attributes (object_id, `index`, value) VALUES (@object_id, @index, @value) ON DUPLICATE KEY UPDATE value = @value", attrs, 100); + Context.Connection.ExecuteBufferedInsert(Context.CompatLayer("INSERT INTO fso_object_attributes (object_id, `index`, value) VALUES (@object_id, @index, @value) ON DUPLICATE KEY UPDATE value = @value", "`object_id`,`index`"), attrs, 100); } public int TotalObjectAttributes(uint guid, int index) { return Context.Connection.Query("SELECT SUM(a.value) " + "FROM fso_object_attributes a JOIN fso_objects o ON a.object_id = o.object_id " + - "WHERE `type` = @guid AND `index` = @index", new { guid, index }).FirstOrDefault(); + "WHERE `type` = @guid AND `index` = @index", new { guid = (ulong)guid, index }).FirstOrDefault(); + } + + public List ListIDs(bool onLot) + { + if (onLot) + { + return Context.Connection.Query("SELECT object_id FROM fso_objects WHERE lot_id IS NOT NULL").ToList(); + } + else + { + return Context.Connection.Query("SELECT object_id FROM fso_objects").ToList(); + } + } + + public bool GetDbObjectState(uint id, out byte[] data) + { + data = null; + if (!Context.UseBlobInventory) + { + return false; + } + + data = Context.Connection.Query("SELECT inventory_state " + + "FROM fso_objects WHERE object_id = @object_id", new { object_id = id }).FirstOrDefault(); + + return true; + } + + public bool SetDbObjectState(uint id, byte[] data) + { + if (!Context.UseBlobInventory) + { + return false; + } + + Context.Connection.Query("UPDATE fso_objects SET " + + "inventory_state = @inventory_state " + + "WHERE object_id = @object_id", new { object_id = id, inventory_state = data }); + + return true; + } + + public List GetByType(uint guid) + { + // SQLite trouble: it doesn't seem to accept this as uint here, and it converts it to signed. Passing as ulong instead. + return Context.Connection.Query("SELECT * FROM fso_objects WHERE type = @guid", new { guid = (ulong)guid }).ToList(); + } + + + public int PurgeStateOnLot() + { + return Context.Connection.Execute("UPDATE fso_objects SET inventory_state = NULL WHERE lot_id IS NOT NULL"); } } } diff --git a/TSOClient/FSO.Server.Database/DA/Outfits/SqlOutfits.cs b/TSOClient/FSO.Server.Database/DA/Outfits/SqlOutfits.cs index 9ae6dfd39..f94ebdb75 100644 --- a/TSOClient/FSO.Server.Database/DA/Outfits/SqlOutfits.cs +++ b/TSOClient/FSO.Server.Database/DA/Outfits/SqlOutfits.cs @@ -13,9 +13,9 @@ public SqlOutfits(ISqlContext context) : base(context){ public uint Create(DbOutfit outfit) { try { - return (uint)Context.Connection.Query("INSERT INTO fso_outfits (avatar_owner, object_owner, asset_id, sale_price, purchase_price, outfit_type, outfit_source) " + + return (uint)Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_outfits (avatar_owner, object_owner, asset_id, sale_price, purchase_price, outfit_type, outfit_source) " + " VALUES (@avatar_owner, @object_owner, @asset_id, @sale_price, @purchase_price, @outfit_type, @outfit_source); " + - " SELECT LAST_INSERT_ID();" + " SELECT LAST_INSERT_ID();") , new { avatar_owner = outfit.avatar_owner, object_owner = outfit.object_owner, diff --git a/TSOClient/FSO.Server.Database/DA/Relationships/SqlRelationships.cs b/TSOClient/FSO.Server.Database/DA/Relationships/SqlRelationships.cs index d419f28d8..30cf905a9 100644 --- a/TSOClient/FSO.Server.Database/DA/Relationships/SqlRelationships.cs +++ b/TSOClient/FSO.Server.Database/DA/Relationships/SqlRelationships.cs @@ -42,43 +42,38 @@ public List GetOutgoing(uint entity_id) public int UpdateMany(List entries) { var date = Epoch.Now; - var conn = (MySqlConnection)Context.Connection; + int rows; - using (MySqlCommand cmd = new MySqlCommand("", conn)) + try { - try - { - StringBuilder sCommand = new StringBuilder("INSERT INTO fso_relationships (from_id, to_id, value, `index`, `date`) VALUES "); + StringBuilder sCommand = new StringBuilder("INSERT INTO fso_relationships (from_id, to_id, value, `index`, `date`) VALUES "); - bool first = true; - foreach (var item in entries) - { - if (!first) sCommand.Append(","); - first = false; - sCommand.Append("("); - sCommand.Append(item.from_id); - sCommand.Append(","); - sCommand.Append(item.to_id); - sCommand.Append(","); - sCommand.Append(item.value); - sCommand.Append(","); - sCommand.Append(item.index); - sCommand.Append(","); - sCommand.Append(date); - sCommand.Append(")"); - } - sCommand.Append(" ON DUPLICATE KEY UPDATE value = VALUES(`value`); "); - - cmd.CommandTimeout = 300; - cmd.CommandText = sCommand.ToString(); - rows = cmd.ExecuteNonQuery(); - } - catch (Exception e) + bool first = true; + foreach (var item in entries) { - return -1; + if (!first) sCommand.Append(','); + first = false; + sCommand.Append('('); + sCommand.Append(item.from_id); + sCommand.Append(','); + sCommand.Append(item.to_id); + sCommand.Append(','); + sCommand.Append(item.value); + sCommand.Append(','); + sCommand.Append(item.index); + sCommand.Append(','); + sCommand.Append(date); + sCommand.Append(')'); } - return rows; + sCommand.Append(" ON DUPLICATE KEY UPDATE value = VALUES(`value`);"); + + rows = Context.Connection.Execute(Context.CompatLayer(sCommand.ToString(), "`from_id`,`to_id`,`index`"), commandTimeout: 300); + } + catch (Exception e) + { + return -1; } + return rows; } } } diff --git a/TSOClient/FSO.Server.Database/DA/Roommates/SqlRoommates.cs b/TSOClient/FSO.Server.Database/DA/Roommates/SqlRoommates.cs index 1d267a17b..633e9511a 100644 --- a/TSOClient/FSO.Server.Database/DA/Roommates/SqlRoommates.cs +++ b/TSOClient/FSO.Server.Database/DA/Roommates/SqlRoommates.cs @@ -27,9 +27,9 @@ public bool CreateOrUpdate(DbRoommate roomie) { try { - return (uint)Context.Connection.Execute("INSERT INTO fso_roommates (avatar_id, lot_id, permissions_level, is_pending) " + + return (uint)Context.Connection.Execute(Context.CompatLayer("INSERT INTO fso_roommates (avatar_id, lot_id, permissions_level, is_pending) " + "VALUES (@avatar_id, @lot_id, @permissions_level, @is_pending) " + - "ON DUPLICATE KEY UPDATE permissions_level = @permissions_level, is_pending = 0", roomie) > 0; + "ON DUPLICATE KEY UPDATE permissions_level = @permissions_level, is_pending = 0", "`avatar_id`,`lot_id`"), roomie) > 0; } catch (SqlException) { diff --git a/TSOClient/FSO.Server.Database/DA/Shards/IShards.cs b/TSOClient/FSO.Server.Database/DA/Shards/IShards.cs index a35dac7eb..96e280c05 100644 --- a/TSOClient/FSO.Server.Database/DA/Shards/IShards.cs +++ b/TSOClient/FSO.Server.Database/DA/Shards/IShards.cs @@ -10,6 +10,7 @@ public interface IShards void DeleteTicket(string ticket_id); ShardTicket GetTicket(string ticket_id); void PurgeTickets(uint time); - void UpdateStatus(int shard_id, string internal_host, string public_host, string name, string number, int? update_id); + void UpdateStatus(int shard_id, string internal_host, string public_host, string channel, string version, int? update_id); + void UpdateInfo(int shard_id, string name, string map); } } diff --git a/TSOClient/FSO.Server.Database/DA/Shards/SqlShards.cs b/TSOClient/FSO.Server.Database/DA/Shards/SqlShards.cs index c57db7652..4b765a168 100644 --- a/TSOClient/FSO.Server.Database/DA/Shards/SqlShards.cs +++ b/TSOClient/FSO.Server.Database/DA/Shards/SqlShards.cs @@ -35,17 +35,27 @@ public void PurgeTickets(uint time) Context.Connection.Query("DELETE FROM fso_shard_tickets WHERE date < @time", new { time = time }); } - public void UpdateStatus(int shard_id, string internal_host, string public_host, string name, string number, int? update_id) + public void UpdateStatus(int shard_id, string internal_host, string public_host, string channel, string version, int? update_id) { Context.Connection.Query("UPDATE fso_shards SET internal_host = @internal_host, public_host = @public_host, version_name = @version_name, version_number = @version_number, update_id = @update_id WHERE shard_id = @shard_id", new { internal_host, public_host, - version_name = name, - version_number = number, + version_name = channel, + version_number = version, update_id, shard_id }); } + + public void UpdateInfo(int shard_id, string name, string map) + { + Context.Connection.Query("UPDATE fso_shards SET name = @name, map = @map WHERE shard_id = @shard_id", new + { + name, + map, + shard_id + }); + } } } diff --git a/TSOClient/FSO.Server.Database/DA/SqlDA.cs b/TSOClient/FSO.Server.Database/DA/SqlDA.cs index 56d036aef..954bd81b6 100644 --- a/TSOClient/FSO.Server.Database/DA/SqlDA.cs +++ b/TSOClient/FSO.Server.Database/DA/SqlDA.cs @@ -29,6 +29,9 @@ using FSO.Server.Database.DA.Bulletin; using FSO.Server.Database.DA.Updates; using FSO.Server.Database.DA.GlobalCooldowns; +using FSO.Server.Database.DA.ArchiveUsers; +using FSO.Server.Database.DA.ArchiveFeatured; +using FSO.Server.Database.DA.ArchiveRecents; namespace FSO.Server.Database.DA { @@ -423,6 +426,39 @@ public IGlobalCooldowns GlobalCooldowns } } + private IArchiveUsers _ArchiveUsers; + public IArchiveUsers ArchiveUsers + { + get + { + if (_ArchiveUsers == null) + { + _ArchiveUsers = new SqlArchiveUsers(Context); + } + return _ArchiveUsers; + } + } + + private IArchiveFeatured _ArchiveFeatured; + public IArchiveFeatured ArchiveFeatured + { + get + { + if (_ArchiveFeatured == null) _ArchiveFeatured = new SqlArchiveFeatured(Context); + return _ArchiveFeatured; + } + } + + private IArchiveRecents _ArchiveRecents; + public IArchiveRecents ArchiveRecents + { + get + { + if (_ArchiveRecents == null) _ArchiveRecents = new SqlArchiveRecents(Context); + return _ArchiveRecents; + } + } + public void Flush() { Context.Flush(); diff --git a/TSOClient/FSO.Server.Database/DA/SqliteContext.cs b/TSOClient/FSO.Server.Database/DA/SqliteContext.cs new file mode 100644 index 000000000..aa06ba8e7 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/SqliteContext.cs @@ -0,0 +1,97 @@ +using System; +using System.Data.Common; +using System.Data; +using Microsoft.Data.Sqlite; +using FSO.Server.Database.SqliteCompat; + +namespace FSO.Server.Database.DA +{ + internal class SqliteContext : ISqlContext, IDisposable + { + public bool SupportsFunctions => false; + public bool UseBlobInventory => true; + private readonly string _connectionString; + private DbConnection _connection; + private SqliteConnectionPool _pool; + + public SqliteContext(string connectionString) + { + this._connectionString = connectionString; + } + + public SqliteContext(SqliteConnectionPool pool) + { + this._pool = pool; + } + + public DbConnection Connection + { + get + { + if (_connection == null) + { + + if (_pool != null) + { + _connection = _pool.Rent(); + } + else + { + _connection = new SqliteConnection(_connectionString); + } + } + + if (_connection.State != ConnectionState.Open) + _connection.Open(); + + return _connection; + } + } + + public void Dispose() + { + if (_connection != null) + { + if (_pool != null) + { + _pool.Return((SqliteConnection)_connection); + } + else + { + _connection.Dispose(); + } + + _connection = null; + } + } + + public void Flush() + { + Dispose(); + } + + public string CompatLayer(string sql, string updateKey = null) + { + if (sql.StartsWith("INSERT IGNORE")) + { + sql = "INSERT OR " + sql.Substring("INSERT ".Length); + } + + sql = sql.Replace("LAST_INSERT_ID()", "last_insert_rowid()"); + sql = sql.Replace("NOW()", "CURRENT_TIMESTAMP"); + + if (updateKey != null) + { + sql = sql.Replace("ON DUPLICATE KEY UPDATE", $"ON CONFLICT({updateKey}) DO UPDATE SET"); + + var valuesPatch = "VALUES(`value`);"; + if (sql.EndsWith(valuesPatch)) + { + sql = string.Concat(sql.AsSpan(0, sql.Length - valuesPatch.Length), "excluded.`value`;"); + } + } + + return sql; + } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/SqliteDAFactory.cs b/TSOClient/FSO.Server.Database/DA/SqliteDAFactory.cs new file mode 100644 index 000000000..e02e2c930 --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/SqliteDAFactory.cs @@ -0,0 +1,36 @@ +using Dapper; +using FSO.Server.Database.DA.Tuning; +using FSO.Server.Database.SqliteCompat; +using System.Net.NetworkInformation; + +namespace FSO.Server.Database.DA +{ + public class SqliteDAFactory : IDAFactory + { + private DatabaseConfiguration Config; + + private SqliteConnectionPool _pool; + + public SqliteDAFactory(DatabaseConfiguration config) + { + this.Config = config; + + // TODO: pass config connection string + // _pool = new SqliteConnectionPool("Data Source=fsoarchive.db;Version=3;UTF8Encoding=True"); + Environment.SetEnvironmentVariable("SQLite_NoPlugins", "true", EnvironmentVariableTarget.Process); + + SqlMapper.AddTypeHandler(new ByteHandler()); + SqlMapper.AddTypeHandler(new SbyteHandler()); + SqlMapper.AddTypeHandler(new Uint16Handler()); + SqlMapper.AddTypeHandler(new Uint32Handler()); + SqlMapper.AddTypeHandler(new Int32Handler()); + SqlMapper.AddTypeHandler(new DbEnumHandler()); + } + + public IDA Get() + { + // Currently not using the pool. + return new SqlDA(new SqliteContext(Config.ConnectionString));//new SqliteContext(_pool)); + } + } +} diff --git a/TSOClient/FSO.Server.Database/DA/Tasks/SqlTasks.cs b/TSOClient/FSO.Server.Database/DA/Tasks/SqlTasks.cs index a45b9df19..2f9f8dae7 100644 --- a/TSOClient/FSO.Server.Database/DA/Tasks/SqlTasks.cs +++ b/TSOClient/FSO.Server.Database/DA/Tasks/SqlTasks.cs @@ -14,8 +14,8 @@ public SqlTasks(ISqlContext context) : base(context) public int Create(DbTask task) { return Context.Connection.Query( - "INSERT INTO fso_tasks (task_type, task_status, shard_id) " + - "VALUES (@task_type, @task_status, @shard_id); SELECT LAST_INSERT_ID();", new + Context.CompatLayer("INSERT INTO fso_tasks (task_type, task_status, shard_id) " + + "VALUES (@task_type, @task_status, @shard_id); SELECT LAST_INSERT_ID();"), new { task_type = task.task_type.ToString(), task_status = task.task_status.ToString(), diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/DbTuning.cs b/TSOClient/FSO.Server.Database/DA/Tuning/DbTuning.cs index 4f33f6443..4635feab1 100644 --- a/TSOClient/FSO.Server.Database/DA/Tuning/DbTuning.cs +++ b/TSOClient/FSO.Server.Database/DA/Tuning/DbTuning.cs @@ -4,7 +4,7 @@ namespace FSO.Server.Database.DA.Tuning { public class DbTuning : DynTuningEntry { - public DbTuningType owner_type { get; set; } + public DbUppercaseEnum owner_type { get; set; } public int owner_id { get; set; } } diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs b/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs index 79fad8983..97bd485eb 100644 --- a/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs +++ b/TSOClient/FSO.Server.Database/DA/Tuning/ITuning.cs @@ -16,5 +16,8 @@ public interface ITuning int CreatePreset(DbTuningPreset preset); int CreatePresetItem(DbTuningPresetItem item); bool DeletePreset(int preset_id); + bool DeletePresetItem(int item_id); + + void UpdatePresetItemValue(int item_id, float value); } } diff --git a/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs b/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs index f0a711ed1..6933f253d 100644 --- a/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs +++ b/TSOClient/FSO.Server.Database/DA/Tuning/SqlTuning.cs @@ -32,10 +32,10 @@ public IEnumerable GetPresetItems(int preset_id) public bool ActivatePreset(int preset_id, int owner_id) { - return Context.Connection.Execute("INSERT IGNORE INTO fso_tuning (tuning_type, tuning_table, tuning_index, value, owner_type, owner_id) " + + return Context.Connection.Execute(Context.CompatLayer("INSERT IGNORE INTO fso_tuning (tuning_type, tuning_table, tuning_index, value, owner_type, owner_id) " + "SELECT p.tuning_type, p.tuning_table, p.tuning_index, p.value, 'EVENT' as owner_type, @owner_id as owner_id " + "FROM fso_tuning_preset_items as p " + - "WHERE p.preset_id = @preset_id", new { preset_id, owner_id }) > 0; + "WHERE p.preset_id = @preset_id"), new { preset_id, owner_id }) > 0; } public bool ClearPresetTuning(int owner_id) @@ -50,17 +50,17 @@ public bool ClearInactiveTuning(int[] active_ids) public int CreatePreset(DbTuningPreset preset) { - var result = Context.Connection.Query("INSERT INTO fso_tuning_presets (name, description, flags) " - + "VALUES (@name, @description, @flags); SELECT LAST_INSERT_ID();", - preset).FirstOrDefault(); + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_tuning_presets (name, description, flags) " + + "VALUES (@name, @description, @flags); SELECT LAST_INSERT_ID();"), + new { preset.name, preset.description, preset.flags }).FirstOrDefault(); return result; } public int CreatePresetItem(DbTuningPresetItem item) { - var result = Context.Connection.Query("INSERT INTO fso_tuning_preset_items (preset_id, tuning_type, tuning_table, tuning_index, value) " - + "VALUES (@preset_id, @tuning_type, @tuning_table, @tuning_index, @value); SELECT LAST_INSERT_ID();", - item).FirstOrDefault(); + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_tuning_preset_items (preset_id, tuning_type, tuning_table, tuning_index, value) " + + "VALUES (@preset_id, @tuning_type, @tuning_table, @tuning_index, @value); SELECT LAST_INSERT_ID();"), + new { item.preset_id, item.tuning_type, item.tuning_table, item.tuning_index, item.value }).FirstOrDefault(); return result; } @@ -68,5 +68,15 @@ public bool DeletePreset(int preset_id) { return Context.Connection.Execute("DELETE FROM fso_tuning_presets WHERE preset_id = @preset_id", new { preset_id }) > 0; } + + public bool DeletePresetItem(int item_id) + { + return Context.Connection.Execute("DELETE FROM fso_tuning_preset_items WHERE item_id = @item_id", new { item_id }) > 0; + } + + public void UpdatePresetItemValue(int item_id, float value) + { + Context.Connection.Query("UPDATE fso_tuning_preset_items SET value = @value WHERE item_id = @item_id", new { item_id, value }); + } } } diff --git a/TSOClient/FSO.Server.Database/DA/Updates/SqlUpdates.cs b/TSOClient/FSO.Server.Database/DA/Updates/SqlUpdates.cs index 8e6bd8b43..e06be7153 100644 --- a/TSOClient/FSO.Server.Database/DA/Updates/SqlUpdates.cs +++ b/TSOClient/FSO.Server.Database/DA/Updates/SqlUpdates.cs @@ -55,10 +55,10 @@ public bool AddBranch(DbUpdateBranch branch) public int AddUpdate(DbUpdate update) { - var result = Context.Connection.Query("INSERT INTO fso_updates " + + var result = Context.Connection.Query(Context.CompatLayer("INSERT INTO fso_updates " + "(version_name, addon_id, branch_id, full_zip, incremental_zip, manifest_url, server_zip, last_update_id, flags, publish_date, deploy_after) " + "VALUES (@version_name, @addon_id, @branch_id, @full_zip, @incremental_zip, @manifest_url, @server_zip, @last_update_id, @flags, @publish_date, @deploy_after); " + - "SELECT LAST_INSERT_ID();", + "SELECT LAST_INSERT_ID();"), update).FirstOrDefault(); return result; } @@ -105,10 +105,10 @@ public IEnumerable GetRecentUpdatesForBranchByID(int branch_id, int li public IEnumerable GetRecentUpdatesForBranchByName(string branch_name, int limit) { - return Context.Connection.Query("SELECT * FROM fso_updates u JOIN fso_update_branch b ON u.branch_id = b.branch_id " + + return Context.Connection.Query(Context.CompatLayer("SELECT * FROM fso_updates u JOIN fso_update_branch b ON u.branch_id = b.branch_id " + "WHERE b.branch_name = @branch_name AND publish_date IS NOT NULL AND deploy_after IS NOT NULL AND deploy_after < NOW() " + "ORDER BY publish_date DESC " + - "LIMIT @limit", new { branch_name, limit }); + "LIMIT @limit"), new { branch_name, limit }); } public IEnumerable GetPublishableByBranchName(string branch_name) diff --git a/TSOClient/FSO.Server.Database/DA/Users/IUsers.cs b/TSOClient/FSO.Server.Database/DA/Users/IUsers.cs index 6c7c0bef5..01c97fdb9 100644 --- a/TSOClient/FSO.Server.Database/DA/Users/IUsers.cs +++ b/TSOClient/FSO.Server.Database/DA/Users/IUsers.cs @@ -7,17 +7,22 @@ public interface IUsers { User GetById(uint id); List GetByRegisterIP(string ip); + List GetByLastIP(string ip); void UpdateConnectIP(uint id, string ip); void UpdateBanned(uint id, bool banned); void UpdateClientID(uint id, string cid); + void UpdateVerified(uint id, bool verified); User GetByUsername(string username); UserAuthenticate GetAuthenticationSettings(uint userId); PagedList All(int offset = 0, int limit = 20, string orderBy = "register_date"); + List AllSummaries(); uint Create(User user); + bool Delete(uint id); void CreateAuth(UserAuthenticate auth); User GetByEmail(string email); void UpdateAuth(UserAuthenticate auth); void UpdateLastLogin(uint id, uint last_login); + void UpdatePermissions(uint id, bool is_moderator, bool is_admin); DbAuthAttempt GetRemainingAuth(uint user_id, string ip); int FailedConsecutive(uint user_id, string ip); diff --git a/TSOClient/FSO.Server.Database/DA/Users/SqlUsers.cs b/TSOClient/FSO.Server.Database/DA/Users/SqlUsers.cs index def710823..3ca6e760a 100644 --- a/TSOClient/FSO.Server.Database/DA/Users/SqlUsers.cs +++ b/TSOClient/FSO.Server.Database/DA/Users/SqlUsers.cs @@ -36,6 +36,11 @@ public List GetByRegisterIP(string ip) return Context.Connection.Query("SELECT * FROM fso_users WHERE register_ip = @ip ORDER BY register_date DESC", new { ip = ip }).AsList(); } + public List GetByLastIP(string ip) + { + return Context.Connection.Query("SELECT * FROM fso_users WHERE last_ip = @ip ORDER BY register_date DESC", new { ip = ip }).AsList(); + } + public void UpdateConnectIP(uint id, string ip) { Context.Connection.Execute("UPDATE fso_users SET last_ip = @ip WHERE user_id = @user_id", new { user_id = id, ip = ip }); @@ -46,6 +51,11 @@ public void UpdateClientID(uint id, string uid) Context.Connection.Execute("UPDATE fso_users SET client_id = @id WHERE user_id = @user_id", new { user_id = id, id = uid }); } + public void UpdateVerified(uint id, bool verified) + { + Context.Connection.Execute("UPDATE fso_users SET is_verified = @verified WHERE user_id = @user_id", new { user_id = id, verified }); + } + public void UpdateBanned(uint id, bool banned) { Context.Connection.Execute("UPDATE fso_users SET is_banned = @ban WHERE user_id = @user_id", new { user_id = id, ban = banned }); @@ -56,6 +66,11 @@ public void UpdateLastLogin(uint id, uint last_login) Context.Connection.Execute("UPDATE fso_users SET last_login = @last_login WHERE user_id = @user_id", new { user_id = id, last_login = last_login }); } + public void UpdatePermissions(uint id, bool is_moderator, bool is_admin) + { + Context.Connection.Execute("UPDATE fso_users SET is_moderator = @is_moderator, is_admin = @is_admin WHERE user_id = @user_id", new { user_id = id, is_moderator, is_admin }); + } + public PagedList All(int offset = 1, int limit = 20, string orderBy = "register_date") { var connection = Context.Connection; @@ -64,15 +79,25 @@ public PagedList All(int offset = 1, int limit = 20, string orderBy = "reg return new PagedList(results, offset, total); } + public List AllSummaries() + { + return Context.Connection.Query("SELECT u.*, count(a.avatar_id) AS avatar_count FROM fso_users u LEFT OUTER JOIN fso_avatars a ON u.user_id = a.user_id GROUP BY u.user_id").ToList(); + } + public uint Create(User user) { - return Context.Connection.Query( + return Context.Connection.Query(Context.CompatLayer( "insert into fso_users set username = @username, email = @email, register_date = @register_date, register_ip = @register_ip, last_ip = @last_ip, " + - "is_admin = @is_admin, is_moderator = @is_moderator, is_banned = @is_banned; select LAST_INSERT_ID();", + "is_admin = @is_admin, is_moderator = @is_moderator, is_banned = @is_banned; select LAST_INSERT_ID();"), user ).First(); } + public bool Delete(uint id) + { + return Context.Connection.Execute("DELETE FROM fso_users WHERE user_id = @id", new { id = id }) > 0; + } + public void CreateAuth(UserAuthenticate auth) { Context.Connection.Execute( diff --git a/TSOClient/FSO.Server.Database/DA/Users/UserSummary.cs b/TSOClient/FSO.Server.Database/DA/Users/UserSummary.cs new file mode 100644 index 000000000..58ac30a2f --- /dev/null +++ b/TSOClient/FSO.Server.Database/DA/Users/UserSummary.cs @@ -0,0 +1,21 @@ +namespace FSO.Server.Database.DA.Users +{ + public class UserSummary + { + public uint user_id { get; set; } + public string username { get; set; } + public string email { get; set; } + public UserState user_state { get; set; } + public uint register_date { get; set; } + public bool is_admin { get; set; } + public bool is_moderator { get; set; } + public bool is_banned { get; set; } + public string register_ip { get; set; } + public string last_ip { get; set; } + public string client_id { get; set; } + public uint last_login { get; set; } + public int avatar_count { get; set; } + public string display_name { get; set; } // Archive exclusive + public bool is_verified { get; set; } // Archive exclusive + } +} diff --git a/TSOClient/FSO.Server.Database/DatabaseConfiguration.cs b/TSOClient/FSO.Server.Database/DatabaseConfiguration.cs index be9770621..d3b5623a0 100644 --- a/TSOClient/FSO.Server.Database/DatabaseConfiguration.cs +++ b/TSOClient/FSO.Server.Database/DatabaseConfiguration.cs @@ -1,7 +1,12 @@ -namespace FSO.Server.Database +using Newtonsoft.Json; + +namespace FSO.Server.Database { public class DatabaseConfiguration { + [JsonProperty("engine")] + public string Engine { get; set; } = "mysql"; + [JsonProperty("connectionString")] public string ConnectionString { get; set; } } } diff --git a/TSOClient/FSO.Server.Database/DatabaseModule.cs b/TSOClient/FSO.Server.Database/DatabaseModule.cs index 25030d2c0..6c035e5fd 100644 --- a/TSOClient/FSO.Server.Database/DatabaseModule.cs +++ b/TSOClient/FSO.Server.Database/DatabaseModule.cs @@ -1,5 +1,8 @@ -using FSO.Server.Database.DA; +using FSO.Common.Serialization; +using FSO.Server.Database.DA; +using Ninject.Activation; using Ninject.Modules; +using System; namespace FSO.Server.Database { @@ -7,8 +10,38 @@ public class DatabaseModule : NinjectModule { public override void Load() { - //TODO: If we add more drivers make this a provider - this.Bind().To().InSingletonScope(); + this.Bind().ToProvider().InSingletonScope(); + } + + class DAFactoryProvider : IProvider + { + private DatabaseConfiguration Config; + + public DAFactoryProvider(DatabaseConfiguration config) + { + this.Config = config; + } + + public Type Type + { + get + { + return typeof(IDAFactory); + } + } + + public object Create(IContext context) + { + switch (Config.Engine) + { + case "mysql": + return new MySqlDAFactory(Config); + case "sqlite": + return new SqliteDAFactory(Config); + } + + throw new NotSupportedException($"Unsupported database engine {Config.Engine}"); + } } } } diff --git a/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj b/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj index bb64af65b..102e276b4 100644 --- a/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj +++ b/TSOClient/FSO.Server.Database/FSO.Server.Database.csproj @@ -1,225 +1,51 @@ - - - + + - Debug - AnyCPU - {430ACD60-E798-43F0-AD61-8B5A35DF6AB2} + net9.0 + enable + disable Library - Properties FSO.Server.Database FSO.Server.Database - v4.5 512 - + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset + + + True + + + + + + + + + - - False - Libs\MyBatis\Castle.DynamicProxy.dll - - - ..\packages\Dapper.1.42\lib\net45\Dapper.dll - - - False - Libs\MyBatis\IBatisNet.Common.dll - - - False - Libs\MyBatis\IBatisNet.Common.Logging.Log4Net.dll - - - False - Libs\MyBatis\IBatisNet.DataAccess.dll - - - False - Libs\MyBatis\log4net.dll - - - ..\packages\MySql.Data.6.9.7\lib\net45\MySql.Data.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - - - - - - - + + + - - - + PreserveNewest PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - PreserveNewest @@ -262,18 +88,8 @@ Always - - - - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - + PreserveNewest @@ -351,13 +167,5 @@ Always - - - + \ No newline at end of file diff --git a/TSOClient/FSO.Server.Database/Management/DbChangeTool.cs b/TSOClient/FSO.Server.Database/Management/DbChangeTool.cs index 6eab649d7..79d37460e 100644 --- a/TSOClient/FSO.Server.Database/Management/DbChangeTool.cs +++ b/TSOClient/FSO.Server.Database/Management/DbChangeTool.cs @@ -10,6 +10,7 @@ using FSO.Server.Database.DA.DbChanges; using FSO.Server.Common; using MySql.Data.MySqlClient; +using System.Data.Common; namespace FSO.Server.Database.Management { @@ -82,13 +83,13 @@ public void ApplyChange(DbChangeScript change, bool repair) { cmd.ExecuteNonQuery(); } - catch (MySqlException e) + catch (DbException e) { throw new DbMigrateException(e.ToString()); } } - connection.Execute("INSERT INTO fso_db_changes VALUES (@id, @filename, @date, @hash) ON DUPLICATE KEY UPDATE hash=@hash, date = @date, filename = @filename", new DbChange { + connection.Execute(Context.CompatLayer("INSERT INTO fso_db_changes VALUES (@id, @filename, @date, @hash) ON DUPLICATE KEY UPDATE hash=@hash, date = @date, filename = @filename", "`id`"), new DbChange { id = change.ScriptID, date = Epoch.Now, filename = change.ScriptFilename, diff --git a/TSOClient/FSO.Server.Database/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Database/Properties/AssemblyInfo.cs deleted file mode 100644 index df7a0372a..000000000 --- a/TSOClient/FSO.Server.Database/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Database")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Database")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("430acd60-e798-43f0-ad61-8b5a35df6ab2")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/ByteHandler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/ByteHandler.cs new file mode 100644 index 000000000..6bd1497ed --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/ByteHandler.cs @@ -0,0 +1,33 @@ +using Dapper; +using System; +using System.Data; +using System.Globalization; + +namespace FSO.Server.Database.SqliteCompat +{ + public class ByteHandler : SqlMapper.TypeHandler + { + /// + public override byte? Parse(object value) + { + if (value == null) + { + return null; + } + + return Convert.ToByte(value, CultureInfo.InvariantCulture); + } + + /// + public override void SetValue(IDbDataParameter parameter, byte? value) + { + if (parameter == null) + { + return; + } + + parameter.DbType = DbType.Byte; + parameter.Value = value; + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/DbEnumHandler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/DbEnumHandler.cs new file mode 100644 index 000000000..a9f932b1c --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/DbEnumHandler.cs @@ -0,0 +1,33 @@ +using Dapper; +using FSO.Server.Database.DA; +using System; +using System.Data; + +namespace FSO.Server.Database.SqliteCompat +{ + public class DbEnumHandler : SqlMapper.TypeHandler?> where T : Enum + { + /// + public override DbUppercaseEnum? Parse(object value) + { + if (value == null || !(value is string strValue)) + { + return null; + } + + return new DbUppercaseEnum((T)Enum.Parse(typeof(T), strValue)); + } + + /// + public override void SetValue(IDbDataParameter parameter, DbUppercaseEnum? value) + { + if (parameter == null || value == null) + { + return; + } + + parameter.DbType = DbType.String; + parameter.Value = Enum.GetName(typeof(T), (T)(value)); + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/Int32Handler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/Int32Handler.cs new file mode 100644 index 000000000..56409473d --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/Int32Handler.cs @@ -0,0 +1,34 @@ +using Dapper; +using System; +using System.Data; +using System.Globalization; + +namespace FSO.Server.Database.SqliteCompat +{ + public class Int32Handler : SqlMapper.TypeHandler + { + /// + public override int? Parse(object value) + { + if (value == null) + { + return null; + } + + // Sqlite tends to store int32 as int64. + return Convert.ToInt32(value, CultureInfo.InvariantCulture); + } + + /// + public override void SetValue(IDbDataParameter parameter, int? value) + { + if (parameter == null) + { + return; + } + + parameter.DbType = DbType.Int32; + parameter.Value = value; + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/SbyteHandler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/SbyteHandler.cs new file mode 100644 index 000000000..f62884e75 --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/SbyteHandler.cs @@ -0,0 +1,33 @@ +using Dapper; +using System; +using System.Data; +using System.Globalization; + +namespace FSO.Server.Database.SqliteCompat +{ + public class SbyteHandler : SqlMapper.TypeHandler + { + /// + public override sbyte? Parse(object value) + { + if (value == null) + { + return null; + } + + return (sbyte)Convert.ToInt64(value, CultureInfo.InvariantCulture); + } + + /// + public override void SetValue(IDbDataParameter parameter, sbyte? value) + { + if (parameter == null) + { + return; + } + + parameter.DbType = DbType.SByte; + parameter.Value = value; + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/SqliteConnectionPool.cs b/TSOClient/FSO.Server.Database/SqliteCompat/SqliteConnectionPool.cs new file mode 100644 index 000000000..73e421493 --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/SqliteConnectionPool.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Microsoft.Data.Sqlite; + +namespace FSO.Server.Database.SqliteCompat +{ + internal class SqliteConnectionPool + { + private string _connectionString; + private Stack _pool = new Stack(); + + public SqliteConnectionPool(string connectionString) + { + _connectionString = connectionString; + } + + public SqliteConnection Rent() + { + lock (_pool) + { + if (_pool.Count == 0) + { + return new SqliteConnection(_connectionString); + } + + return _pool.Pop(); + } + } + + public void Return(SqliteConnection conn) + { + lock (_pool) + { + _pool.Push(conn); + } + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/Uint16Handler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/Uint16Handler.cs new file mode 100644 index 000000000..caa9d11cd --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/Uint16Handler.cs @@ -0,0 +1,34 @@ +using Dapper; +using System; +using System.Data; +using System.Globalization; + +namespace FSO.Server.Database.SqliteCompat +{ + public class Uint16Handler : SqlMapper.TypeHandler + { + /// + public override ushort? Parse(object value) + { + if (value == null) + { + return null; + } + + return Convert.ToUInt16(value, CultureInfo.InvariantCulture); + } + + /// + public override void SetValue(IDbDataParameter parameter, ushort? value) + { + if (parameter == null) + { + return; + } + + // Sending as an Int16 seems to make the result negative if it overflows 31 bits, so send as a larger type. + parameter.DbType = DbType.UInt64; + parameter.Value = value; + } + } +} diff --git a/TSOClient/FSO.Server.Database/SqliteCompat/Uint32Handler.cs b/TSOClient/FSO.Server.Database/SqliteCompat/Uint32Handler.cs new file mode 100644 index 000000000..54044e0e4 --- /dev/null +++ b/TSOClient/FSO.Server.Database/SqliteCompat/Uint32Handler.cs @@ -0,0 +1,44 @@ +using Dapper; +using System; +using System.Data; +using System.Globalization; + +namespace FSO.Server.Database.SqliteCompat +{ + public class Uint32Handler : SqlMapper.TypeHandler + { + /// + public override uint? Parse(object value) + { + if (value == null) + { + return null; + } + + if (value.GetType() == typeof(long)) + { + long longValue = (long)value; + + // The value might be negative due to sqlite not supporting unsigned values - cast it to uint. + return (uint)longValue; + } + + // Sqlite tends to store uint32 as int64. + return Convert.ToUInt32(value, CultureInfo.InvariantCulture); + } + + /// + public override void SetValue(IDbDataParameter parameter, uint? value) + { + if (parameter == null) + { + return; + } + + // Sending as an Int32 seems to make the result negative if it overflows 31 bits, so send as a larger type. + // This doesn't seem to trigger all the time, so Parse also handles conversions back from int to uint. + parameter.DbType = DbType.UInt64; + parameter.Value = value; + } + } +} diff --git a/TSOClient/FSO.Server.Database/app.config b/TSOClient/FSO.Server.Database/app.config deleted file mode 100644 index f7e2c5989..000000000 --- a/TSOClient/FSO.Server.Database/app.config +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSO.Server.Database/packages.config b/TSOClient/FSO.Server.Database/packages.config deleted file mode 100644 index 58803fb2f..000000000 --- a/TSOClient/FSO.Server.Database/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Debug/App.config b/TSOClient/FSO.Server.Debug/App.config deleted file mode 100644 index 2de6da497..000000000 --- a/TSOClient/FSO.Server.Debug/App.config +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSO.Server.Debug/FSO.Server.Debug.csproj b/TSOClient/FSO.Server.Debug/FSO.Server.Debug.csproj index 265e38e54..c495ab66f 100644 --- a/TSOClient/FSO.Server.Debug/FSO.Server.Debug.csproj +++ b/TSOClient/FSO.Server.Debug/FSO.Server.Debug.csproj @@ -1,189 +1,47 @@ - - - + + - Debug - AnyCPU - {7296CCEC-F459-4071-A9D2-9A07DCC29210} - WinExe - Properties + net9.0-windows + enable + true + disable + Exe FSO.Server.Debug FSO.Server.Debug - v4.5 512 - true - + false + partial - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true + + + True - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + + + True - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - - False - libs\Be.Windows.Forms.HexBox.dll - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - libs\ScintillaNET.dll - - - - - - - - - - - - - - - - - - - - - Form - - - NetworkDebugger.cs - - - - - UserControl - - - PacketView.cs - - - - - - Form - - - StringDialog.cs - - - NetworkDebugger.cs - - - PacketView.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - True - - - StringDialog.cs - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - + - - - - - {c051793d-1a9c-4554-9bb8-bafdc01a096a} - FSO.Common.DatabaseService - - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - + + + + + + - - - \ No newline at end of file + + + + + + + + + + diff --git a/TSOClient/FSO.Server.Debug/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Debug/Properties/AssemblyInfo.cs deleted file mode 100644 index 911c9fbbe..000000000 --- a/TSOClient/FSO.Server.Debug/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Debug")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Debug")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("7296ccec-f459-4071-a9d2-9a07dcc29210")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Debug/packages.config b/TSOClient/FSO.Server.Debug/packages.config deleted file mode 100644 index 87286c7e4..000000000 --- a/TSOClient/FSO.Server.Debug/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Domain/Domain/Shards.cs b/TSOClient/FSO.Server.Domain/Domain/Shards.cs index 802f4099b..dcd02a038 100644 --- a/TSOClient/FSO.Server.Domain/Domain/Shards.cs +++ b/TSOClient/FSO.Server.Domain/Domain/Shards.cs @@ -1,28 +1,30 @@ -using FSO.Common.Domain.Shards; +using FSO.Common.Domain; +using FSO.Common.Domain.Shards; +using FSO.Content.Model; using FSO.Server.Database.DA; using FSO.Server.Protocol.CitySelector; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.Xna.Framework; +using System.Runtime.InteropServices; namespace FSO.Server.Domain { public class Shards : IShardsDomain { + private IServerNFSProvider NFS; private List _Shards = new List(); private IDAFactory _DbFactory; private DateTime _LastPoll; - public Shards(IDAFactory factory) + public Shards(IDAFactory factory, IServerNFSProvider nfs) { _DbFactory = factory; Poll(); + NFS = nfs; } public List All { - get{ + get { return _Shards; } } @@ -39,9 +41,9 @@ public void AutoUpdate() { Task.Delay(60000).ContinueWith(x => { - try{ + try { Poll(); - }catch(Exception ex){ + } catch (Exception ex) { } AutoUpdate(); }); @@ -55,7 +57,7 @@ public void Update() private void Poll() { _LastPoll = DateTime.UtcNow; - + using (var db = _DbFactory.Get()) { _Shards = db.Shards.All().Select(x => new ShardStatusItem() @@ -67,8 +69,8 @@ private void Poll() Status = (Server.Protocol.CitySelector.ShardStatus)(byte)x.status, PublicHost = x.public_host, InternalHost = x.internal_host, - VersionName = x.version_name, - VersionNumber = x.version_number, + VersionBranch = x.version_name, + VersionId = x.version_number, UpdateID = x.update_id }).ToList(); } @@ -83,5 +85,97 @@ public ShardStatusItem GetByName(string name) { return _Shards.FirstOrDefault(x => x.Name == name); } + + public CityMap GetMapForId(int id) + { + var shard = GetById(id); + + if (shard == null) + { + return null; + } + + if (shard.Map.StartsWith("dynamic")) + { + var path = NFS.GetShardMapDirectory(id); + + return new CityMap(path); + } + + return FSO.Content.Content.Get().CityMaps.Get(shard.Map); + } + + public void MakeDynamic(int id, Action savePNG) + { + var shard = GetById(id); + + // Try and copy the current map data into the dynamic map folder. + + var baseMap = FSO.Content.Content.Get().CityMaps.Get(shard.Map); + + var target = NFS.GetShardMapDirectory(id); + + Directory.CreateDirectory(target); + + // Save all aspects + + SaveTex(target, "terraintype", baseMap.TerrainTypeColorData, savePNG); + SaveTex(target, "elevation", baseMap.ElevationColorData, savePNG); + SaveTex(target, "roadmap", baseMap.RoadColorData, savePNG); + SaveTex(target, "forestdensity", baseMap.ForestDensityColorData, savePNG); + SaveTex(target, "foresttype", baseMap.ForestTypeColorData, savePNG); + + var thumbImage = baseMap.Thumbnail.GetImage(); + SaveTex(target, "thumbnail", GetImageColor(thumbImage), savePNG, thumbImage.Width, thumbImage.Height); + + using (var db = _DbFactory.Get()) + { + db.Shards.UpdateInfo(id, shard.Name, "dynamic"); + } + + Poll(); + } + + private static Color[] GetImageColor(TexBitmap bmp) + { + var width = bmp.Width; + var height = bmp.Height; + var pixelSize = bmp.PixelSize; + var length = width * height; + var result = new Color[length]; + var bytes = bmp.Data; + + var index = 0; + + int i = 0; + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) + { + var a = pixelSize == 3 ? 255 : bytes[index + 3]; + var r = bytes[index + 2]; + var g = bytes[index + 1]; + var b = bytes[index]; + + index += pixelSize; + + result[i++] = new Color(r, g, b, a); + } + } + + return result; + } + + private static void SaveTex(string baseDir, string filename, Color[] data, Action savePNG, int width = 512, int height = 512) + { + string filePath = Path.Combine(baseDir, $"{filename}.png"); + + Directory.CreateDirectory(baseDir); + + using (FileStream fs = File.Open(filePath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + savePNG(data, width, height, fs); + } + } } } diff --git a/TSOClient/FSO.Server.Domain/FSO.Server.Domain.csproj b/TSOClient/FSO.Server.Domain/FSO.Server.Domain.csproj index b3c885fb8..5d899440c 100644 --- a/TSOClient/FSO.Server.Domain/FSO.Server.Domain.csproj +++ b/TSOClient/FSO.Server.Domain/FSO.Server.Domain.csproj @@ -1,95 +1,24 @@ - - - + + - Debug - AnyCPU - {A1D9ABA0-0105-436D-8F8C-2418DB768080} + net9.0 + enable + disable Library - Properties FSO.Server.Domain FSO.Server.Domain - v4.5 512 + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - - - - - - - - - - - - - - - - {9848faf5-444a-48cc-a26a-8115d8c4fb52} - FSO.Common.Domain - - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {430acd60-e798-43f0-ad61-8b5a35df6ab2} - FSO.Server.Database - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - + - - + + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.Domain/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Domain/Properties/AssemblyInfo.cs deleted file mode 100644 index f8ff490a5..000000000 --- a/TSOClient/FSO.Server.Domain/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Domain")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Domain")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a1d9aba0-0105-436d-8f8c-2418db768080")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Domain/app.config b/TSOClient/FSO.Server.Domain/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Server.Domain/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Domain/packages.config b/TSOClient/FSO.Server.Domain/packages.config deleted file mode 100644 index cb9a27f81..000000000 --- a/TSOClient/FSO.Server.Domain/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Protocol/Aries/AriesPacketType.cs b/TSOClient/FSO.Server.Protocol/Aries/AriesPacketType.cs index e527a7e36..8f486bb8d 100644 --- a/TSOClient/FSO.Server.Protocol/Aries/AriesPacketType.cs +++ b/TSOClient/FSO.Server.Protocol/Aries/AriesPacketType.cs @@ -15,6 +15,8 @@ public enum AriesPacketType AnswerChallenge, AnswerAccepted, + RequestClientSessionArchive, + Unknown } @@ -42,6 +44,8 @@ public static AriesPacketType FromPacketCode(uint code) return AriesPacketType.RequestClientSession; case 21: return AriesPacketType.RequestClientSessionResponse; + case 2000: + return AriesPacketType.RequestClientSessionArchive; default: return AriesPacketType.Unknown; } @@ -69,6 +73,8 @@ public static uint GetPacketCode(this AriesPacketType type) return 22; case AriesPacketType.RequestClientSessionResponse: return 21; + case AriesPacketType.RequestClientSessionArchive: + return 2000; default: throw new Exception("Unknown aries packet type " + type.ToString()); } diff --git a/TSOClient/FSO.Server.Protocol/Aries/AriesPackets.cs b/TSOClient/FSO.Server.Protocol/Aries/AriesPackets.cs index cf1882d15..f34c94bbe 100644 --- a/TSOClient/FSO.Server.Protocol/Aries/AriesPackets.cs +++ b/TSOClient/FSO.Server.Protocol/Aries/AriesPackets.cs @@ -13,7 +13,9 @@ public class AriesPackets typeof(RequestChallenge), typeof(RequestChallengeResponse), typeof(AnswerChallenge), - typeof(AnswerAccepted) + typeof(AnswerAccepted), + + typeof(RequestClientSessionArchive), }; static AriesPackets() diff --git a/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionArchive.cs b/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionArchive.cs new file mode 100644 index 000000000..8759bc259 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionArchive.cs @@ -0,0 +1,53 @@ +using Mina.Core.Buffer; +using FSO.Common.Serialization; +using FSO.Common; + +namespace FSO.Server.Protocol.Aries.Packets +{ + public class RequestClientSessionArchive : IAriesPacket + { + public string Name; + public int PlayerCount; + public string VersionInfo; + + public string ServerKey; + public string Nonce; + public ArchiveConfigFlags ArchiveConfig; + public uint ShardId; + public string ShardName; + public string ShardMap; + + public void Deserialize(IoBuffer input, ISerializationContext context) + { + Name = input.GetPascalVLCString(); + PlayerCount = input.GetInt32(); + VersionInfo = input.GetPascalVLCString(); + + ServerKey = input.GetPascalVLCString(); + Nonce = input.GetPascalVLCString(); + ArchiveConfig = input.GetEnum(); + ShardId = input.GetUInt32(); + ShardName = input.GetPascalVLCString(); + ShardMap = input.GetPascalVLCString(); + } + + public AriesPacketType GetPacketType() + { + return AriesPacketType.RequestClientSessionArchive; + } + + public void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutPascalVLCString(Name); + output.PutInt32(PlayerCount); + output.PutPascalVLCString(VersionInfo); + + output.PutPascalVLCString(ServerKey); + output.PutPascalVLCString(Nonce); + output.PutEnum(ArchiveConfig); + output.PutUInt32(ShardId); + output.PutPascalVLCString(ShardName); + output.PutPascalVLCString(ShardMap); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionResponse.cs b/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionResponse.cs index 989f8e495..397ab9cd8 100644 --- a/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionResponse.cs +++ b/TSOClient/FSO.Server.Protocol/Aries/Packets/RequestClientSessionResponse.cs @@ -26,7 +26,15 @@ public void Deserialize(IoBuffer input, ISerializationContext context) this.Unknown = input.Get(); this.ServiceIdent = input.GetString(3, Encoding.ASCII); this.Unknown2 = input.GetUInt16(); - this.Password = input.GetString(32, Encoding.ASCII); + + if (this.Unknown == 40) + { + this.Password = input.GetPascalVLCString(); + } + else + { + this.Password = input.GetString(32, Encoding.ASCII); + } } public AriesPacketType GetPacketType() @@ -44,7 +52,15 @@ public void Serialize(IoBuffer output, ISerializationContext context) output.Put(this.Unknown); output.PutString(this.ServiceIdent, 3, Encoding.ASCII); output.PutUInt16(this.Unknown2); - output.PutString(this.Password, 32, Encoding.ASCII); + + if (this.Unknown == 40) + { + output.PutPascalVLCString(this.Password); + } + else + { + output.PutString(this.Password, 32, Encoding.ASCII); + } } } } diff --git a/TSOClient/FSO.Server.Protocol/CitySelector/ShardSelectorServletResponse.cs b/TSOClient/FSO.Server.Protocol/CitySelector/ShardSelectorServletResponse.cs index 129b4397e..2afcfc27f 100644 --- a/TSOClient/FSO.Server.Protocol/CitySelector/ShardSelectorServletResponse.cs +++ b/TSOClient/FSO.Server.Protocol/CitySelector/ShardSelectorServletResponse.cs @@ -10,6 +10,9 @@ public class ShardSelectorServletResponse : IXMLEntity public uint PlayerID; public string AvatarID; + public bool ExplicitPort; + public bool SpectatorMode; + public bool PreAlpha = false; #region IXMLPrinter Members @@ -27,6 +30,8 @@ public System.Xml.XmlElement Serialize(System.Xml.XmlDocument doc) result.AppendTextNode("EntitlementLevel", ""); } result.AppendTextNode("AvatarID", AvatarID); //freeso now uses this + if (SpectatorMode) + result.AppendTextNode("SpectatorMode", "1"); return result; } @@ -38,6 +43,7 @@ public void Parse(System.Xml.XmlElement element) this.PlayerID = uint.Parse(element.ReadTextNode("PlayerID")); this.AvatarID = element.ReadTextNode("AvatarID"); + this.SpectatorMode = element.ReadTextNode("SpectatorMode") == "1"; } #endregion diff --git a/TSOClient/FSO.Server.Protocol/CitySelector/ShardStatusItem.cs b/TSOClient/FSO.Server.Protocol/CitySelector/ShardStatusItem.cs index 80994e4bc..d232217c6 100644 --- a/TSOClient/FSO.Server.Protocol/CitySelector/ShardStatusItem.cs +++ b/TSOClient/FSO.Server.Protocol/CitySelector/ShardStatusItem.cs @@ -12,8 +12,8 @@ public class ShardStatusItem : IXMLEntity public int Id; public string PublicHost; public string InternalHost; - public string VersionName; - public string VersionNumber; + public string VersionBranch; + public string VersionId; public int? UpdateID; public ShardStatusItem() diff --git a/TSOClient/FSO.Server.Protocol/CitySelector/UserAuthorized.cs b/TSOClient/FSO.Server.Protocol/CitySelector/UserAuthorized.cs index b1e955726..7e2f3918b 100644 --- a/TSOClient/FSO.Server.Protocol/CitySelector/UserAuthorized.cs +++ b/TSOClient/FSO.Server.Protocol/CitySelector/UserAuthorized.cs @@ -1,4 +1,5 @@ -using FSO.Common.Utils; +using FSO.Common; +using FSO.Common.Utils; namespace FSO.Server.Protocol.CitySelector { @@ -7,6 +8,7 @@ public class UserAuthorized : IXMLEntity public string FSOVersion; public string FSOBranch; public string FSOUpdateUrl; + public string FSOUpdateKey; public string FSOCDNUrl; public System.Xml.XmlElement Serialize(System.Xml.XmlDocument doc) @@ -15,6 +17,7 @@ public System.Xml.XmlElement Serialize(System.Xml.XmlDocument doc) element.AppendTextNode("FSO-Version", FSOVersion); element.AppendTextNode("FSO-Branch", FSOBranch); element.AppendTextNode("FSO-UpdateUrl", FSOUpdateUrl); + element.AppendTextNode("FSO-UpdateKey", FSOUpdateKey); element.AppendTextNode("FSO-CDNUrl", FSOCDNUrl); return element; } @@ -24,7 +27,19 @@ public void Parse(System.Xml.XmlElement element) this.FSOVersion = element.ReadTextNode("FSO-Version"); this.FSOBranch = element.ReadTextNode("FSO-Branch"); this.FSOUpdateUrl = element.ReadTextNode("FSO-UpdateUrl"); + this.FSOUpdateKey = element.ReadTextNode("FSO-UpdateKey"); this.FSOCDNUrl = element.ReadTextNode("FSO-CDNUrl"); } + + public FSOVersionInfo GetVersion() + { + return new FSOVersionInfo() + { + id = FSOVersion, + channel = FSOBranch, + channelUrl = FSOUpdateUrl ?? "", + publicKey = FSOUpdateKey ?? "" + }; + } } } diff --git a/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs b/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs index f374ebb9f..5a836fe3e 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/ElectronPacketType.cs @@ -29,6 +29,22 @@ public enum ElectronPacketType : ushort BulletinRequest, BulletinResponse, GlobalTuningUpdate, + CityResourceRequest, + CityResourceResponse, + ArchiveAvatarsRequest, + ArchiveAvatarsResponse, + ArchiveAvatarSelectRequest, + ArchiveAvatarSelectResponse, + ArchiveClientList, + ArchiveModerationRequest, + VerificationNotification, + JoinLotWithTransitionRequest, + FSOVMSurroundPuppets, + CityUpdateRequest, + CityUpdateResponse, + CityUpdateCommand, + CityInitRequest, + CityInitResponse, Unknown = 0xFFFF } diff --git a/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs b/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs index 688e17980..0c96a4f53 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/ElectronPackets.cs @@ -32,7 +32,23 @@ public class ElectronPackets typeof(NhoodCandidateList), typeof(BulletinRequest), typeof(BulletinResponse), - typeof(GlobalTuningUpdate) + typeof(CityResourceRequest), + typeof(CityResourceResponse), + typeof(GlobalTuningUpdate), + typeof(ArchiveAvatarSelectRequest), + typeof(ArchiveAvatarSelectResponse), + typeof(ArchiveAvatarsRequest), + typeof(ArchiveAvatarsResponse), + typeof(ArchiveClientList), + typeof(ArchiveModerationRequest), + typeof(VerificationNotification), + typeof(JoinLotWithTransitionRequest), + typeof(FSOVMSurroundPuppets), + typeof(CityUpdateRequest), + typeof(CityUpdateResponse), + typeof(CityUpdateCommand), + typeof(CityInitRequest), + typeof(CityInitResponse), }; static ElectronPackets() diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/ArchiveModerationRequestType.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/ArchiveModerationRequestType.cs new file mode 100644 index 000000000..8c55b475c --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/ArchiveModerationRequestType.cs @@ -0,0 +1,11 @@ +namespace FSO.Server.Protocol.Electron.Model +{ + public enum ArchiveModerationRequestType + { + APPROVE_USER, + REJECT_USER, + BAN_USER, + KICK_USER, + CHANGE_MOD_LEVEL + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityData.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityData.cs new file mode 100644 index 000000000..6393b3cb3 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityData.cs @@ -0,0 +1,58 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace FSO.Server.Protocol.Electron.Model +{ + public class CityData : ICompressedContainerItem + { + public const int Width = 512; + public const int Height = 512; + + public byte[] Elevation; + public byte[] ForestDensity; + public uint[] ForestType; + public byte[] RoadMap; + public uint[] TerrainType; + + public void Deserialize(IoBuffer input, ISerializationContext context) + { + int pixelCount = Width * Height; + + Elevation = input.GetSlice(pixelCount).GetBytes(); + ForestDensity = input.GetSlice(pixelCount).GetBytes(); + ForestType = GetArray(input, pixelCount); + RoadMap = input.GetSlice(pixelCount).GetBytes(); + TerrainType = GetArray(input, pixelCount); + } + + private static T[] GetArray(IoBuffer input, int size) where T : unmanaged + { + var bytes = input.GetSlice(Unsafe.SizeOf() * size).GetBytes(); + + return MemoryMarshal.Cast(bytes).ToArray(); + } + + private static byte[] ToBytes(T[] data) where T : unmanaged + { + return MemoryMarshal.Cast(data).ToArray(); + } + + public void Serialize(IoBuffer output, ISerializationContext context) + { + int pixelCount = Width * Height; + + if (Elevation.Length != pixelCount || ForestDensity.Length != pixelCount || ForestType.Length != pixelCount || RoadMap.Length != pixelCount || TerrainType.Length != pixelCount) + { + throw new Exception($"Invalid pixel count for city map - expected {Width}x{Height}"); + } + + output.Put(Elevation); + output.Put(ForestDensity); + output.Put(ToBytes(ForestType)); + output.Put(RoadMap); + output.Put(ToBytes(TerrainType)); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditAltitude.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditAltitude.cs new file mode 100644 index 000000000..e0b7bedbb --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditAltitude.cs @@ -0,0 +1,52 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System.Runtime.InteropServices; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public class CityEditAltitude : CityEditBase + { + public bool AutoTerrainType; + public CityEditBitmap Bitmap; + public short[] AltitudeDeltas; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + base.Deserialize(input, context); + AutoTerrainType = input.GetBool(); + Bitmap = new CityEditBitmap(input); + var altBytes = input.GetSlice(Bitmap.Width * Bitmap.Height * sizeof(ushort)).GetBytes(); + AltitudeDeltas = MemoryMarshal.Cast(altBytes).ToArray(); + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + base.Serialize(output, context); + + output.PutBool(AutoTerrainType); + Bitmap.Serialize(output); + output.Put(MemoryMarshal.Cast(AltitudeDeltas).ToArray()); + } + + public void Trim() + { + var before = Bitmap; + var deltas = AltitudeDeltas; + var trimmed = Bitmap.Trim(); + Bitmap = trimmed; + + int width = before.Width; + int twidth = trimmed?.Width ?? 0; + int theight = trimmed?.Height ?? 0; + AltitudeDeltas = new short[twidth * theight]; + + foreach (var line in before.GetSetLines()) + { + var from = deltas.AsSpan(line.y * width + line.x, line.count); + var to = AltitudeDeltas.AsSpan((line.y - trimmed.Y) * twidth + line.x - trimmed.X, line.count); + + from.CopyTo(to); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBase.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBase.cs new file mode 100644 index 000000000..5951a2d11 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBase.cs @@ -0,0 +1,61 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public abstract class CityEditBase + { + public const int MaxReservedLocations = 512 * 256; + + public uint AvatarId; + public uint Color = uint.MaxValue; + public int UserModId; + public HashSet ReservedLocations; + public bool IsTemp; + + public virtual void Deserialize(IoBuffer input, ISerializationContext context) + { + AvatarId = input.GetUInt32(); + Color = input.GetUInt32(); + UserModId = input.GetInt32(); + var reservedCount = input.GetInt32(); + + if (reservedCount > MaxReservedLocations) + { + throw new Exception("Invalid number of reserved locations for city edit"); + } + + ReservedLocations = []; + + for (int i = 0; i < reservedCount; i++) + { + ReservedLocations.Add(input.GetUInt32()); + } + + IsTemp = input.GetBool(); + } + + public virtual void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutUInt32(AvatarId); + output.PutUInt32(Color); + output.PutInt32(UserModId); + + if (ReservedLocations != null) + { + output.PutInt32(ReservedLocations.Count); + + foreach (var location in ReservedLocations) + { + output.PutUInt32(location); + } + } + else + { + output.PutInt32(0); + } + + output.PutBool(IsTemp); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBitmap.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBitmap.cs new file mode 100644 index 000000000..5dd59a753 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditBitmap.cs @@ -0,0 +1,275 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public class CityEditBitmap + { + public const int MaxWidth = 512; + public const int MaxHeight = 512; + private const int BitsPerItem = 64; + private const ulong AllBits = 0xFFFF_FFFF_FFFF_FFFF; + + public int X; + public int Y; + public int Width; + public int Height; + public ulong[] Data; + + public CityEditBitmap(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + Data = new ulong[GetDataCount()]; + } + + public CityEditBitmap(int width, int height) : this(0, 0, width, height) + { + } + + public CityEditBitmap(IoBuffer input) + { + Deserialize(input); + } + + private int GetDataCount() + { + return (Width * Height + BitsPerItem - 1) / BitsPerItem; + } + + public virtual void Deserialize(IoBuffer input) + { + X = input.GetInt32(); + Y = input.GetInt32(); + Width = input.GetInt32(); + Height = input.GetInt32(); + + if (Width < 0 || Height < 0 || Width > MaxWidth || Height > MaxHeight) + { + throw new Exception("City edit bitmap too large"); + } + + if (X < 0 || Y < 0 || X + Width > MaxWidth || Y + Height > MaxHeight) + { + throw new Exception("City edit bitmap out of bounds"); + } + + var bytes = input.GetSlice(GetDataCount() * sizeof(ulong)).GetBytes(); + + Data = MemoryMarshal.Cast(bytes).ToArray(); + } + + public virtual void Serialize(IoBuffer output) + { + output.PutInt32(X); + output.PutInt32(Y); + output.PutInt32(Width); + output.PutInt32(Height); + + var cast = MemoryMarshal.Cast(Data).ToArray(); + output.Put(cast); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ((int x, int y, int count) line, bool hasMore) FragmentLine(ref (int index, int count) builder) + { + int width = Width; + int startY = builder.index / width; + int endY = (builder.index + builder.count - 1) / width; + + int x = builder.index % width; + + if (endY > startY) + { + int newCount = width - x; + builder.index += newCount; + builder.count -= newCount; + + return ((x, startY, newCount), true); + } + else + { + return ((x, startY, builder.count), false); + } + } + + /// + /// Get horizontal lines that have been set. Iterates through coordinate start points + /// and the number of pixels in the line that are set. + /// + /// + public IEnumerable<(int x, int y, int count)> GetSetLines() + { + int dataIndex = 0; + int bitIndex = 0; + + (int index, int count) builder = default; + + while (dataIndex < Data.Length) + { + ulong data = Data[dataIndex++]; + + while (true) + { + int toStart = 0; + + if (builder.count == 0) + { + // Try find the start of a range. + // Get zero count (number of bits to skip) + toStart = BitOperations.TrailingZeroCount(data); + + if (toStart == BitsPerItem) + { + break; + } + + builder = (bitIndex + toStart, 0); + } + // If the above case isn't true, then we're continuing a range from the previous data. + + ulong endMask = 0xFFFFFFFFFFFFFFFFul << toStart; + + // Count the number of set bits: + // - invert everything after the start, so we can count 0s again until the next "1" (actually 0) + ulong inverted = data ^ endMask; + var toEnd = BitOperations.TrailingZeroCount(inverted); + + builder.count += toEnd - toStart; + if (toEnd < BitsPerItem) + { + ulong remainingMask = 0xFFFFFFFFFFFFFFFFul << toEnd; + data &= remainingMask; + + bool hasMore; + do + { + var fragment = FragmentLine(ref builder); + hasMore = fragment.hasMore; + yield return fragment.line; + } + while (hasMore); + + builder = default; + } + else + { + break; + } + } + + bitIndex += BitsPerItem; + } + + if (builder.count != 0) + { + bool hasMore; + do + { + var fragment = FragmentLine(ref builder); + hasMore = fragment.hasMore; + yield return fragment.line; + } + while (hasMore); + } + + yield break; + } + + public void Set(int x, int y) + { + int index = y * Width + x; + + int dataIndex = index / BitsPerItem; + int dataBit = index % BitsPerItem; + ulong bit = 1ul << dataBit; + + Data[dataIndex] |= bit; + } + + public void Set(int x, int y, int count) + { + int startIndex = y * Width + x; + int endIndex = startIndex + count; + + int startDataIndex = startIndex / BitsPerItem; + int endDataIndex = (endIndex + BitsPerItem - 1) / BitsPerItem; + + int startBit = startIndex % BitsPerItem; + int endBit = endIndex % BitsPerItem; + + ulong startMask = (AllBits << startBit); + ulong endMask = (AllBits >> (BitsPerItem - endBit)); + + int index = startDataIndex; + int dataCount = endDataIndex - startDataIndex; + + for (int i = 0; i < dataCount; i++) + { + ulong bits = i == 0 ? startMask : AllBits; + + if (i == dataCount - 1) + { + bits &= endMask; + } + + Data[index++] |= bits; + } + } + + public bool IsSet(int x, int y) + { + int index = y * Width + x; + + int dataIndex = index / BitsPerItem; + int dataBit = index % BitsPerItem; + ulong bit = 1ul << dataBit; + + return (Data[dataIndex] & bit) != 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool InBounds(int x, int y) + { + return x >= 0 && y >= 0 && x < Width && y < Height; + } + + public CityEditBitmap Trim() + { + int minX = 512, minY = 512, maxX = 0, maxY = 0; + + foreach (var line in GetSetLines()) + { + if (line.x < minX) minX = line.x; + if (line.x + line.count - 1 > maxX) maxX = line.x + line.count - 1; + if (line.y < minY) minY = line.y; + if (line.y > maxY) maxY = line.y; + } + + if (minX > maxX) + { + // This bitmap is empty. + return null; + } + + var newBitmap = new CityEditBitmap(minX, minY, 1 + maxX - minX, 1 + maxY - minY); + + foreach (var (x, y, count) in GetSetLines()) + { + newBitmap.Set(x - minX, y - minY, count); + } + + return newBitmap; + } + + public void Clear() + { + Array.Clear(Data); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditCommand.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditCommand.cs new file mode 100644 index 000000000..f53673622 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditCommand.cs @@ -0,0 +1,63 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public struct CityEditCommand + { + private static Dictionary TypeToEnum = new() + { + { + typeof(CityEditAltitude), CityUpdateCommandType.Altitude + }, + { + typeof(CityEditPaint), CityUpdateCommandType.Paint + }, + { + typeof(CityEditRoad), CityUpdateCommandType.Road + }, + { + typeof(CityEditForest), CityUpdateCommandType.Forest + } + }; + + public CityEditBase Command; + + public CityEditCommand(CityEditBase command) + { + Command = command; + } + + public CityEditCommand(IoBuffer input, ISerializationContext context) + { + Deserialize(input, context); + } + + public void Deserialize(IoBuffer input, ISerializationContext context) + { + var eType = (CityUpdateCommandType)input.Get(); + + Command = eType switch + { + CityUpdateCommandType.Altitude => new CityEditAltitude(), + CityUpdateCommandType.Paint => new CityEditPaint(), + CityUpdateCommandType.Road => new CityEditRoad(), + CityUpdateCommandType.Forest => new CityEditForest(), + _ => throw new NotSupportedException($"Unknown city command type: {eType}") + }; + + Command.Deserialize(input, context); + } + + public void Serialize(IoBuffer output, ISerializationContext context) + { + if (!TypeToEnum.TryGetValue(Command.GetType(), out var type)) + { + throw new NotSupportedException($"Unknown city command type: {Command.GetType()}"); + } + + output.Put((byte)type); + Command.Serialize(output, context); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditForest.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditForest.cs new file mode 100644 index 000000000..f4769dc20 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditForest.cs @@ -0,0 +1,53 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public class CityEditForest : CityEditBase + { + public bool Erasing; + public byte ForestType; + public CityEditBitmap Bitmap; + public byte[] Intensities; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + base.Deserialize(input, context); + Erasing = input.GetBool(); + ForestType = input.Get(); + Bitmap = new CityEditBitmap(input); + Intensities = input.GetSlice(Bitmap.Width * Bitmap.Height).GetBytes(); + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + base.Serialize(output, context); + + output.PutBool(Erasing); + output.Put(ForestType); + Bitmap.Serialize(output); + output.Put(Intensities); + } + + public void Trim() + { + var before = Bitmap; + var intensities = Intensities; + var trimmed = Bitmap.Trim(); + Bitmap = trimmed; + + int width = before.Width; + int twidth = trimmed?.Width ?? 0; + int theight = trimmed?.Height ?? 0; + Intensities = new byte[twidth * theight]; + + foreach (var line in before.GetSetLines()) + { + var from = intensities.AsSpan(line.y * width + line.x, line.count); + var to = Intensities.AsSpan((line.y - trimmed.Y) * twidth + line.x - trimmed.X, line.count); + + from.CopyTo(to); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditPaint.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditPaint.cs new file mode 100644 index 000000000..236f8c01d --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditPaint.cs @@ -0,0 +1,41 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public enum CityEditPaintType : byte + { + TerrainType, + ForestDensity, + ForestType, + } + + public class CityEditPaint : CityEditBase + { + public CityEditPaintType Type; + public byte Value; + public CityEditBitmap Bitmap; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + base.Deserialize(input, context); + Type = (CityEditPaintType)input.Get(); + Value = input.Get(); + Bitmap = new CityEditBitmap(input); + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + base.Serialize(output, context); + + output.Put((byte)Type); + output.Put(Value); + Bitmap.Serialize(output); + } + + public void Trim() + { + Bitmap = Bitmap.Trim(); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditRoad.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditRoad.cs new file mode 100644 index 000000000..a1fbedb06 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityEditRoad.cs @@ -0,0 +1,41 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public class CityEditRoad : CityEditBase + { + public int StartX; + public int StartY; + public int Length; + public int Direction; + public bool Delete; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + base.Deserialize(input, context); + StartX = input.GetInt32(); + StartY = input.GetInt32(); + + Length = input.GetInt32(); + Direction = input.GetInt32(); + Delete = input.GetBool(); + + if (Direction < 0 && Direction > 3) + { + throw new Exception($"Road direction {Direction} out of range."); + } + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + base.Serialize(output, context); + output.PutInt32(StartX); + output.PutInt32(StartY); + + output.PutInt32(Length); + output.PutInt32(Direction); + output.PutBool(Delete); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityUpdateCommandType.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityUpdateCommandType.cs new file mode 100644 index 000000000..c6733642c --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CityEditCommands/CityUpdateCommandType.cs @@ -0,0 +1,10 @@ +namespace FSO.Server.Protocol.Electron.Model.CityEditCommands +{ + public enum CityUpdateCommandType : byte + { + Altitude, + Paint, + Road, + Forest + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Model/CompressedContainer.cs b/TSOClient/FSO.Server.Protocol/Electron/Model/CompressedContainer.cs new file mode 100644 index 000000000..1458efa63 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Model/CompressedContainer.cs @@ -0,0 +1,103 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System.IO.Compression; +using System.Text; + +namespace FSO.Server.Protocol.Electron.Model +{ + public interface ICompressedContainerItem : IoBufferSerializable, IoBufferDeserializable + { + } + + /// + /// Container that compresses the data when serializing into an IoBuffer. + /// The compressed data is cached, so it can be reused across multiple serializations without compressing each time. + /// + /// + internal class CompressedContainer where T : ICompressedContainerItem + { + private byte[] _compressedData; + private T _item; + + public T Item + { + get + { + if (_item == null) + { + Decompress(); + } + + return _item; + } + + set + { + _compressedData = null; + _item = value; + } + } + + private void Compress() + { + if (_item == null) + { + _compressedData = null; + return; + } + + var buffer = IoBufferUtils.SerializableToIoBuffer(_item, null); + + var data = buffer.GetBytes(); + + using (var dstStream = new MemoryStream()) + { + using (var cStream = new GZipStream(dstStream, CompressionMode.Compress)) + using (var srcStream = new MemoryStream(data)) + { + srcStream.CopyTo(cStream); + }; + + _compressedData = dstStream.ToArray(); + } + } + + private void Decompress() + { + if (_compressedData == null) + { + _item = default; + return; + } + + using (var compressed = new MemoryStream(_compressedData)) + using (var cStream = new GZipStream(compressed, CompressionMode.Decompress)) + using (var dstStream = new MemoryStream()) + { + cStream.CopyTo(dstStream); + + var data = dstStream.ToArray(); + + _item = IoBufferUtils.Deserialize(data, null); + } + } + + public void Deserialize(IoBuffer input, ISerializationContext context) + { + int compressedDataSize = input.GetInt32(); + + _compressedData = input.GetSlice(compressedDataSize).GetBytes(); + } + + public void Serialize(IoBuffer output, ISerializationContext context) + { + Compress(); + + output.PutInt32(_compressedData?.Length ?? 0); + if (_compressedData != null) + { + output.Put(_compressedData); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectRequest.cs new file mode 100644 index 000000000..b7855437f --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectRequest.cs @@ -0,0 +1,25 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class ArchiveAvatarSelectRequest : AbstractElectronPacket + { + public uint AvatarId; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + AvatarId = input.GetUInt32(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveAvatarSelectRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutUInt32(AvatarId); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectResponse.cs new file mode 100644 index 000000000..7bcecc685 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarSelectResponse.cs @@ -0,0 +1,36 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public enum ArchiveAvatarSelectCode + { + Success = 0, + NotFound, + NoPermission, + InUseSelf, + InUse, + UnknownError + } + + public class ArchiveAvatarSelectResponse : AbstractElectronPacket + { + public ArchiveAvatarSelectCode Code; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Code = input.GetEnum(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveAvatarSelectResponse; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutEnum(Code); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsRequest.cs new file mode 100644 index 000000000..a569c2efe --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsRequest.cs @@ -0,0 +1,28 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class ArchiveAvatarsRequest : AbstractElectronPacket, IActionRequest + { + public object OType => 0; + + public bool NeedsValidation => false; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + input.GetUInt32(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveAvatarsRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutUInt32(0); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsResponse.cs new file mode 100644 index 000000000..fba253449 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveAvatarsResponse.cs @@ -0,0 +1,135 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.CitySelector; +using FSO.Server.Protocol.Electron.Model; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public struct ArchiveAvatar + { + public uint UserId; + public uint AvatarId; + public uint LotId; + public string Name; + public string LotName; + public AvatarAppearanceType Type; + public ulong Head; + public ulong Body; + + public static ArchiveAvatar Deserialize(IoBuffer input) + { + return new ArchiveAvatar() + { + UserId = input.GetUInt32(), + AvatarId = input.GetUInt32(), + LotId = input.GetUInt32(), + Name = input.GetPascalVLCString(), + LotName = input.GetPascalVLCString(), + Head = input.GetUInt64(), + Body = input.GetUInt64(), + }; + } + + public void Serialize(IoBuffer output) + { + output.PutUInt32(UserId); + output.PutUInt32(AvatarId); + output.PutUInt32(LotId); + output.PutPascalVLCString(Name); + output.PutPascalVLCString(LotName); + output.PutUInt64(Head); + output.PutUInt64(Body); + } + } + + public class ArchiveAvatarsResponse : AbstractElectronPacket, IActionResponse + { + public bool Success => true; + + public object OCode => 0; + public bool IsVerified; + public bool CasEnabled; + public uint[] RecentAvatars; + public ArchiveAvatar[] UserAvatars; + public ArchiveAvatar[] SharedAvatars; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + IsVerified = input.GetBool(); + CasEnabled = input.GetBool(); + + int recentCount = input.GetInt32(); + + if (recentCount > 5) + { + throw new System.Exception($"Too many recent avatars: {recentCount}"); + } + + RecentAvatars = new uint[recentCount]; + for (int i = 0; i < recentCount; i++) + { + RecentAvatars[i] = input.GetUInt32(); + } + + int userCount = input.GetInt32(); + + if (userCount > 8192) + { + throw new System.Exception($"Too many user avatars: {userCount}"); + } + + UserAvatars = new ArchiveAvatar[userCount]; + for (int i = 0; i < userCount; i++) + { + UserAvatars[i] = ArchiveAvatar.Deserialize(input); + } + + // TODO: compression? + + int sharedCount = input.GetInt32(); + + if (sharedCount > 500000) + { + throw new System.Exception($"Too many shared avatars: {sharedCount}"); + } + + SharedAvatars = new ArchiveAvatar[sharedCount]; + for (int i = 0; i < sharedCount; i++) + { + SharedAvatars[i] = ArchiveAvatar.Deserialize(input); + } + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveAvatarsResponse; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutBool(IsVerified); + output.PutBool(CasEnabled); + + output.PutInt32(RecentAvatars.Length); + + foreach (var recent in RecentAvatars) + { + output.PutUInt32(recent); + } + + output.PutInt32(UserAvatars.Length); + + foreach (var user in UserAvatars) + { + user.Serialize(output); + } + + output.PutInt32(SharedAvatars.Length); + + foreach (var shared in SharedAvatars) + { + shared.Serialize(output); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveClientList.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveClientList.cs new file mode 100644 index 000000000..87ce78bae --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveClientList.cs @@ -0,0 +1,113 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public struct ArchiveClient + { + public uint UserId; + public string DisplayName; + public uint AvatarId; + public uint ModerationLevel; + public uint SessionUID; + + public static ArchiveClient Deserialize(IoBuffer input) + { + return new ArchiveClient() + { + UserId = input.GetUInt32(), + DisplayName = input.GetPascalVLCString(), + AvatarId = input.GetUInt32(), + ModerationLevel = input.GetUInt32(), + SessionUID = input.GetUInt32() + }; + } + + public void Serialize(IoBuffer output) + { + output.PutUInt32(UserId); + output.PutPascalVLCString(DisplayName); + output.PutUInt32(AvatarId); + output.PutUInt32(ModerationLevel); + output.PutUInt32(SessionUID); + } + } + + public struct ArchivePendingVerification + { + public uint UserId; + public string DisplayName; + + public static ArchivePendingVerification Deserialize(IoBuffer input) + { + return new ArchivePendingVerification() + { + UserId = input.GetUInt32(), + DisplayName = input.GetPascalVLCString(), + }; + } + + public void Serialize(IoBuffer output) + { + output.PutUInt32(UserId); + output.PutPascalVLCString(DisplayName); + } + } + + public class ArchiveClientList : AbstractElectronPacket + { + public ArchiveClient[] Clients; + public ArchivePendingVerification[] Pending; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + int clientCount = input.GetInt32(); + + if (clientCount > 8192) + { + throw new System.Exception($"Too many clients: {clientCount}"); + } + + Clients = new ArchiveClient[clientCount]; + for (int i = 0; i < clientCount; i++) + { + Clients[i] = ArchiveClient.Deserialize(input); + } + + int verificationCount = input.GetInt32(); + + if (verificationCount > 8192) + { + throw new System.Exception($"Too many pending verifications: {verificationCount}"); + } + + Pending = new ArchivePendingVerification[verificationCount]; + for (int i = 0; i < verificationCount; i++) + { + Pending[i] = ArchivePendingVerification.Deserialize(input); + } + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveClientList; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutInt32(Clients.Length); + + foreach (var client in Clients) + { + client.Serialize(output); + } + + output.PutInt32(Pending.Length); + + foreach (var pending in Pending) + { + pending.Serialize(output); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveModerationRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveModerationRequest.cs new file mode 100644 index 000000000..c284660e4 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/ArchiveModerationRequest.cs @@ -0,0 +1,32 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using FSO.Server.Protocol.Electron.Model; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class ArchiveModerationRequest : AbstractElectronPacket + { + public ArchiveModerationRequestType Type; + public uint EntityId; + public int Value; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Type = input.GetEnum(); + EntityId = input.GetUInt32(); + Value = input.GetInt32(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.ArchiveModerationRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutEnum(Type); + output.PutUInt32(EntityId); + output.PutInt32(Value); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitRequest.cs new file mode 100644 index 000000000..85ae99c9e --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitRequest.cs @@ -0,0 +1,28 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityInitRequest : AbstractElectronPacket, IActionRequest + { + public object OType => 0; + + public bool NeedsValidation => false; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + input.GetUInt32(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityInitRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutUInt32(0); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitResponse.cs new file mode 100644 index 000000000..a11971367 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityInitResponse.cs @@ -0,0 +1,52 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityInitResponse : AbstractElectronPacket, IActionResponse + { + public bool Success => true; + + public object OCode => 0; + + public byte[] CityData; + public CityEditCommand[] Commands; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + int cityDataLength = input.GetInt32(); + + CityData = input.GetSlice(cityDataLength).GetBytes(); + + var commandCount = input.GetInt32(); + + var commands = new List(); + for (int i = 0; i < commandCount; i++) + { + commands.Add(new CityEditCommand(input, context)); + } + + Commands = [.. commands]; + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityInitResponse; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutInt32(CityData.Length); + output.Put(CityData); + + output.PutInt32(Commands.Length); + + foreach (var command in Commands) + { + command.Serialize(output, context); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceRequest.cs new file mode 100644 index 000000000..2cb6682aa --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceRequest.cs @@ -0,0 +1,43 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityResourceRequest : AbstractElectronPacket, IActionRequest + { + public CityResourceRequestType Type; + public uint ResourceID; + public uint RequestID; // Needed for the client to know exactly what response is for what request. + + public object OType => Type; + public bool NeedsValidation => false; //the CAN POST items are one off requests, rather than a state machine. + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Type = input.GetEnum(); + ResourceID = input.GetUInt32(); + RequestID = input.GetUInt32(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityResourceRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutEnum(Type); + output.PutUInt32(ResourceID); + output.PutUInt32(RequestID); + } + } + + public enum CityResourceRequestType : byte + { + LOT_THUMBNAIL = 0, + LOT_FACADE = 1, + AVATAR_DESCRIPTION = 2, + CITY_THUMBNAIL = 3, + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceResponse.cs new file mode 100644 index 000000000..951c46ee0 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityResourceResponse.cs @@ -0,0 +1,36 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityResourceResponse : AbstractElectronPacket + { + public CityResourceRequestType Type; + public uint ResourceID; + public uint RequestID; // Needed for the client to know exactly what response is for what request. + public byte[] Data; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Type = input.GetEnum(); + ResourceID = input.GetUInt32(); + RequestID = input.GetUInt32(); + int length = input.GetInt32(); + Data = input.GetSlice(length).GetBytes(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityResourceResponse; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutEnum(Type); + output.PutUInt32(ResourceID); + output.PutUInt32(RequestID); + output.PutInt32(Data.Length); + output.Put(Data); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateCommand.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateCommand.cs new file mode 100644 index 000000000..ca1ef0e0b --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateCommand.cs @@ -0,0 +1,82 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public enum CityUpdateCommandMode : byte + { + ClearTemp, + Undo, + SetCityName, + SetThumbnail, + CommandError, + UndoError, + HollowLotRefresh + } + + public class CityUpdateCommand : AbstractElectronPacket + { + private const int MaxThumbnailSizeBytes = 180 * 135 * 4 + 4096; // Raw image plus some allowance. + private const int MaxCityNameWidth = 24; + + public CityUpdateCommandMode Mode; + public uint AvatarID; + public int TargetUID; + + public string CityName; + + public byte[] Thumbnail; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Mode = (CityUpdateCommandMode)input.Get(); + switch (Mode) + { + case CityUpdateCommandMode.SetCityName: + CityName = input.GetPascalString(); + if (CityName.Length > MaxCityNameWidth) + { + throw new InvalidDataException("City name size is out of range"); + } + break; + case CityUpdateCommandMode.SetThumbnail: + var length = input.GetInt32(); + if (length > MaxThumbnailSizeBytes) + { + throw new InvalidDataException("City thumbnail is too large"); + } + Thumbnail = input.GetSlice(length).GetBytes(); + break; + default: + AvatarID = input.GetUInt32(); + TargetUID = input.GetInt32(); + break; + } + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityUpdateCommand; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.Put((byte)Mode); + + switch (Mode) + { + case CityUpdateCommandMode.SetCityName: + output.PutPascalString(CityName); + break; + case CityUpdateCommandMode.SetThumbnail: + output.PutInt32(Thumbnail.Length); + output.Put(Thumbnail); + break; + default: + output.PutUInt32(AvatarID); + output.PutInt32(TargetUID); + break; + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateRequest.cs new file mode 100644 index 000000000..e391f87cd --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateRequest.cs @@ -0,0 +1,26 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityUpdateRequest : AbstractElectronPacket + { + public CityEditCommand Command; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Command = new CityEditCommand(input, context); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityUpdateRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + Command.Serialize(output, context); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateResponse.cs new file mode 100644 index 000000000..5daa77992 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CityUpdateResponse.cs @@ -0,0 +1,42 @@ +using FSO.Common.Serialization; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class CityUpdateResponse : AbstractElectronPacket + { + public int StartIndex; + public CityEditCommand[] Commands; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + StartIndex = input.GetInt32(); + var commandCount = input.GetInt32(); + + var commands = new List(); + for (int i = 0; i < commandCount; i++) + { + commands.Add(new CityEditCommand(input, context)); + } + + Commands = [.. commands]; + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.CityUpdateResponse; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutInt32(StartIndex); + output.PutInt32(Commands.Length); + + foreach (var command in Commands) + { + command.Serialize(output, context); + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/CreateASimResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/CreateASimResponse.cs index de91a95ff..c7f5afd74 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/Packets/CreateASimResponse.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/CreateASimResponse.cs @@ -42,6 +42,7 @@ public enum CreateASimFailureReason NAME_VALIDATION_ERROR = 0x02, DESC_VALIDATION_ERROR = 0x03, BODY_VALIDATION_ERROR = 0x04, - HEAD_VALIDATION_ERROR = 0x05 + HEAD_VALIDATION_ERROR = 0x05, + CAS_DISABLED = 0x06 } } diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMSurroundPuppets.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMSurroundPuppets.cs new file mode 100644 index 000000000..dbb0e1bb8 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMSurroundPuppets.cs @@ -0,0 +1,272 @@ +using FSO.Common.Model; +using FSO.Common.Serialization; +using Microsoft.Xna.Framework; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public struct SurroundPuppetTick + { + public uint TickID; + public SurroundPuppetLot[] Lots; + } + + public struct SurroundPuppetLot + { + public uint LotLocation; + public SurroundPuppet[] Puppets; + // If this is true, the tick is outdated and shouldn't have its timestamp updated. + public bool Outdated; + + // Runtime only + // If this is true, this tick should have all dirty bits. + public bool ForceDirty; + } + + public class FSOVMSurroundPuppets : AbstractElectronPacket + { + private const int MAX_LOTS = 9; + private const int MAX_TICKS = 64; + private const int MAX_CHARACTERS = 1024; + private const int MAX_ANIMATIONS = 10; + private const int MAX_APPEARANCES = 512; + + public SurroundPuppetTick[] Ticks; + + // Runtime only + public bool NewPlayer; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + int tickCount = input.GetInt32(); + if (tickCount > MAX_TICKS) + { + throw new Exception($"Invalid tick count {tickCount}"); + } + + Ticks = new SurroundPuppetTick[tickCount]; + + for (int i = 0; i < Ticks.Length; i++) + { + Ticks[i] = new SurroundPuppetTick() + { + TickID = input.GetUInt32() + }; + + int lotCount = input.GetInt32(); + + if (lotCount > MAX_LOTS) + { + throw new Exception($"Invalid puppet lot count {lotCount}"); + } + + var lots = new SurroundPuppetLot[lotCount]; + + for (int j = 0; j < lotCount; j++) + { + var lot = new SurroundPuppetLot() + { + LotLocation = input.GetUInt32() + }; + + int puppetCount = input.GetInt32(); + + if (puppetCount == -1) + { + lot.Puppets = []; + lot.Outdated = true; + } + else + { + if (puppetCount > MAX_CHARACTERS) + { + throw new Exception($"Invalid character count {lotCount}"); + } + + var puppets = new SurroundPuppet[puppetCount]; + + for (int k = 0; k < puppetCount; k++) + { + puppets[k] = ReadPuppet(input); + } + + lot.Puppets = puppets; + } + + lots[j] = lot; + } + + Ticks[i].Lots = lots; + } + } + + private static SurroundPuppet ReadPuppet(IoBuffer input) + { + var delta = (SurroundPuppetDelta)input.GetInt32(); + + SurroundPuppet puppet = new SurroundPuppet() + { + Delta = delta + }; + + puppet.PersistID = input.GetUInt32(); + if (delta.HasFlag(SurroundPuppetDelta.BodyInfo)) + { + puppet.SkinTone = input.GetUInt32(); + puppet.HeadOutfit = input.GetUInt64(); + puppet.BodyOutfit = input.GetUInt64(); + puppet.SkeletonName = input.GetPascalVLCString(); + } + + if (delta.HasFlag(SurroundPuppetDelta.Position)) + { + puppet.VisualPositionStart = new Microsoft.Xna.Framework.Vector4(input.GetSingle(), input.GetSingle(), input.GetSingle(), input.GetSingle()); + puppet.Velocity = new Microsoft.Xna.Framework.Vector4(input.GetSingle(), input.GetSingle(), input.GetSingle(), input.GetSingle()); + } + + if ((delta & SurroundPuppetDelta.Animation) != 0) + { + int animationCount = input.GetInt32(); + + if (animationCount > MAX_ANIMATIONS) + { + throw new Exception($"Invalid animation count {animationCount}"); + } + + var animations = new SurroundPuppetAnimation[animationCount]; + var readName = delta.HasFlag(SurroundPuppetDelta.AnimationNames); + var readMeta = delta.HasFlag(SurroundPuppetDelta.AnimationState); + + for (int i = 0; i < animations.Length; i++) + { + animations[i] = new SurroundPuppetAnimation( + readName ? input.GetPascalVLCString() : null, + readMeta ? input.GetSingle() : 0, + readMeta ? input.GetSingle() : 0, + readMeta ? input.GetSingle() : 0, + readMeta ? (SurroundPuppetAnimationFlags)input.GetInt32() : 0); + } + + puppet.Animations = animations; + } + + if (delta.HasFlag(SurroundPuppetDelta.Appearances)) + { + int appearanceCount = input.GetInt32(); + + if (appearanceCount > MAX_APPEARANCES) + { + throw new Exception($"Invalid appearance count {appearanceCount}"); + } + + var appearances = new string[appearanceCount]; + + for (int i = 0; i < appearances.Length; i++) + { + appearances[i] = input.GetPascalVLCString(); + } + + puppet.Appearances = appearances; + } + + return puppet; + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.FSOVMSurroundPuppets; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutInt32(Ticks.Length); + + foreach (ref var tick in Ticks.AsSpan()) + { + output.PutUInt32(tick.TickID); + + var tickCount = tick.Lots.Length; + + output.PutInt32(tickCount); + + foreach (ref var lot in tick.Lots.AsSpan()) + { + output.PutUInt32(lot.LotLocation); + + if (!NewPlayer && lot.Outdated) + { + output.PutInt32(-1); + continue; + } + + output.PutInt32(lot.Puppets.Length); + + foreach (ref var puppet in lot.Puppets.AsSpan()) + { + WritePuppet(output, ref puppet, lot.ForceDirty); + } + } + } + } + + private static void PutVector4(IoBuffer output, Vector4 vec) + { + output.PutSingle(vec.X); + output.PutSingle(vec.Y); + output.PutSingle(vec.Z); + output.PutSingle(vec.W); + } + + private void WritePuppet(IoBuffer output, ref SurroundPuppet puppet, bool forceDirty) + { + var delta = NewPlayer || forceDirty ? SurroundPuppetDelta.All : puppet.Delta; + output.PutInt32((int)delta); + + output.PutUInt32(puppet.PersistID); + if (delta.HasFlag(SurroundPuppetDelta.BodyInfo)) + { + output.PutUInt32(puppet.SkinTone); + output.PutUInt64(puppet.HeadOutfit); + output.PutUInt64(puppet.BodyOutfit); + output.PutPascalVLCString(puppet.SkeletonName); + } + + if (delta.HasFlag(SurroundPuppetDelta.Position)) + { + PutVector4(output, puppet.VisualPositionStart); + PutVector4(output, puppet.Velocity); + } + + if ((delta & SurroundPuppetDelta.Animation) != 0) + { + output.PutInt32(puppet.Animations.Length); + + foreach (ref var animation in puppet.Animations.AsSpan()) + { + if (delta.HasFlag(SurroundPuppetDelta.AnimationNames)) + { + output.PutPascalVLCString(animation.Name); + } + + if (delta.HasFlag(SurroundPuppetDelta.AnimationState)) + { + output.PutSingle(animation.CurrentFrame); + output.PutSingle(animation.Speed); + output.PutSingle(animation.Weight); + output.PutInt32((int)animation.Flags); + } + } + } + + if (delta.HasFlag(SurroundPuppetDelta.Appearances)) + { + output.PutInt32(puppet.Appearances.Length); + + foreach (ref var appearance in puppet.Appearances.AsSpan()) + { + output.PutPascalVLCString(appearance); + } + } + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMTickBroadcast.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMTickBroadcast.cs index bdab5dad9..3d6a93943 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMTickBroadcast.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/FSOVMTickBroadcast.cs @@ -5,10 +5,12 @@ namespace FSO.Server.Protocol.Electron.Packets { public class FSOVMTickBroadcast : AbstractElectronPacket { + public bool Catchup; public byte[] Data; public override void Deserialize(IoBuffer input, ISerializationContext context) { + Catchup = input.GetBool(); var dataLen = input.GetInt32(); //TODO: limits? 4MB is probably reasonable. Data = new byte[dataLen]; input.Get(Data, 0, dataLen); @@ -21,6 +23,7 @@ public override ElectronPacketType GetPacketType() public override void Serialize(IoBuffer output, ISerializationContext context) { + output.PutBool(Catchup); output.PutInt32(Data.Length); output.Put(Data, 0, Data.Length); } diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/FindLotRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/FindLotRequest.cs index 50aedc93b..91b71bfb1 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/Packets/FindLotRequest.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/FindLotRequest.cs @@ -1,4 +1,5 @@ -using FSO.Common.Serialization; +using FSO.Common.Model; +using FSO.Common.Serialization; using Mina.Core.Buffer; namespace FSO.Server.Protocol.Electron.Packets diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/JoinLotWithTransitionRequest.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/JoinLotWithTransitionRequest.cs new file mode 100644 index 000000000..0bd97e0da --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/JoinLotWithTransitionRequest.cs @@ -0,0 +1,66 @@ +using FSO.Common.Model; +using FSO.Common.Serialization; +using Mina.Core.Buffer; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class JoinLotWithTransitionRequest : AbstractElectronPacket + { + public LotTransitionInfo Transition; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + Transition = GetTransition(input); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.JoinLotWithTransitionRequest; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + PutTransition(output, Transition); + } + + private void PutTransition(IoBuffer output, LotTransitionInfo info) + { + output.PutUInt32(info.BeforeLocation); + output.PutInt32(info.RelativeChangeX); + output.PutInt32(info.RelativeChangeY); + + output.PutInt32(info.AvatarLotTilePosX); + output.PutInt32(info.AvatarLotTilePosY); + output.PutSingle(info.AvatarDirection); + + output.PutEnum(info.Type); + output.PutUInt32(info.RoutingTargetLocation); + output.PutInt32(info.RoutingLotTilePosX); + output.PutInt32(info.RoutingLotTilePosY); + } + + private LotTransitionInfo GetTransition(IoBuffer input) + { + return new LotTransitionInfo() + { + BeforeLocation = input.GetUInt32(), + RelativeChangeX = input.GetInt32(), + RelativeChangeY = input.GetInt32(), + + AvatarLotTilePosX = input.GetInt32(), + AvatarLotTilePosY = input.GetInt32(), + AvatarDirection = input.GetSingle(), + + Type = input.GetEnum(), + RoutingTargetLocation = input.GetUInt32(), + RoutingLotTilePosX = input.GetInt32(), + RoutingLotTilePosY = input.GetInt32(), + }; + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/PurchaseLotResponse.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/PurchaseLotResponse.cs index 4dcb77892..d7d7d585d 100644 --- a/TSOClient/FSO.Server.Protocol/Electron/Packets/PurchaseLotResponse.cs +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/PurchaseLotResponse.cs @@ -50,6 +50,7 @@ public enum PurchaseLotFailureReason NOT_OFFLINE_FOR_MOVE = 0x07, LOCATION_TAKEN = 0x08, NHOOD_RESERVED = 0x09, + PURCHASE_DISABLED = 0x10, TH_NOT_MAYOR = 0x80, TH_INCORRECT_NHOOD = 0x81, diff --git a/TSOClient/FSO.Server.Protocol/Electron/Packets/VerificationNotification.cs b/TSOClient/FSO.Server.Protocol/Electron/Packets/VerificationNotification.cs new file mode 100644 index 000000000..beee43dc5 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Electron/Packets/VerificationNotification.cs @@ -0,0 +1,25 @@ +using FSO.Common.Serialization; +using Mina.Core.Buffer; + +namespace FSO.Server.Protocol.Electron.Packets +{ + public class VerificationNotification : AbstractElectronPacket + { + public bool IsVerified; + + public override void Deserialize(IoBuffer input, ISerializationContext context) + { + IsVerified = input.GetBool(); + } + + public override ElectronPacketType GetPacketType() + { + return ElectronPacketType.VerificationNotification; + } + + public override void Serialize(IoBuffer output, ISerializationContext context) + { + output.PutBool(IsVerified); + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Embedded/ArchiveManagement.cs b/TSOClient/FSO.Server.Protocol/Embedded/ArchiveManagement.cs new file mode 100644 index 000000000..1847d3290 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Embedded/ArchiveManagement.cs @@ -0,0 +1,33 @@ +namespace FSO.Server.Protocol.Embedded +{ + public enum ArchiveDbUserStatus + { + Normal, + Unverified, + Banned, + Mod, + Admin + + } + public struct ArchiveDbUser + { + public uint ID; + public int AvatarCount; + public ArchiveDbUserStatus Status; + public string Name; + public string IP; + } + + public struct ArchiveDbAvatar + { + public uint ID; + public string Name; + public string LotName; + public ulong LastLogin; + } + + public struct ArchiveDbIpBan + { + public string IP; + } +} diff --git a/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj b/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj index 58fefe59c..b69d98962 100644 --- a/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj +++ b/TSOClient/FSO.Server.Protocol/FSO.Server.Protocol.csproj @@ -1,229 +1,32 @@ - - - + + - Debug - AnyCPU - {A08ADE32-27E2-44F4-BC52-11A16C56BAA8} + net9.0 + enable + disable Library - Properties FSO.Server.Protocol FSO.Server.Protocol - v4.5 512 - + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + + + True - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - + + + - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.Protocol/Gluon/Model/ChangeType.cs b/TSOClient/FSO.Server.Protocol/Gluon/Model/ChangeType.cs index ce1516edc..7ccd99e44 100644 --- a/TSOClient/FSO.Server.Protocol/Gluon/Model/ChangeType.cs +++ b/TSOClient/FSO.Server.Protocol/Gluon/Model/ChangeType.cs @@ -6,6 +6,7 @@ public enum ChangeType ADD_ROOMMATE, BECOME_OWNER, BECOME_OWNER_WITH_OBJECTS, - ROOMIE_INHERIT_OBJECTS_ONLY + ROOMIE_INHERIT_OBJECTS_ONLY, + RELOAD_PERMISSIONS } } diff --git a/TSOClient/FSO.Server.Protocol/Gluon/Model/ClaimAction.cs b/TSOClient/FSO.Server.Protocol/Gluon/Model/ClaimAction.cs index 3c39bc526..26b039030 100644 --- a/TSOClient/FSO.Server.Protocol/Gluon/Model/ClaimAction.cs +++ b/TSOClient/FSO.Server.Protocol/Gluon/Model/ClaimAction.cs @@ -2,9 +2,46 @@ { public enum ClaimAction { + /// + /// Not determined - likely going to host or spectate. + /// DEFAULT, + /// + /// Opens the lot normally. + /// LOT_HOST, - LOT_CLEANUP + + /// + /// Opens the lot starting in spectator mode (saving is disabled). + /// The lot will transition to regular host mode when a roommate joins. + /// + LOT_SPECTATOR, + + /// + /// Open the lot and immediately save + close it. + /// Removes objects that shouldn't be on a property and deletes it if there is no owner, + /// applies terrain changes, and updates the hollow save for surrounding lots. + /// + LOT_CLEANUP, + + /// + /// Opens the lot and immediately closes it, saving only the hollow.fsov used for surrounding lots. + /// Move flags and ownership rules are applied to the lot for the hollow save, but not consumed. + /// + LOT_CLEANUP_HOLLOW + } + + public static class ClaimActionExtensions + { + /// + /// Check if the claim action is a cleanup type action. This means that the lot shouldn't expect anyone to join and should close as soon as possible. + /// + /// Claim action + /// True if the action is a cleanup type action, false otherwise + public static bool IsCleanup(this ClaimAction action) + { + return action == ClaimAction.LOT_CLEANUP || action == ClaimAction.LOT_CLEANUP_HOLLOW; + } } } diff --git a/TSOClient/FSO.Server.Protocol/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Protocol/Properties/AssemblyInfo.cs deleted file mode 100644 index 421cbaf15..000000000 --- a/TSOClient/FSO.Server.Protocol/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Protocol")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Protocol")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("a08ade32-27e2-44f4-bc52-11a16c56baa8")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server.Protocol/Utils/PortTransformer.cs b/TSOClient/FSO.Server.Protocol/Utils/PortTransformer.cs new file mode 100644 index 000000000..14fae0fb3 --- /dev/null +++ b/TSOClient/FSO.Server.Protocol/Utils/PortTransformer.cs @@ -0,0 +1,31 @@ +namespace FSO.Server.Protocol.Utils +{ + public static class PortTransformer + { + public static string TransformAddress(string address, string connType = "101") + { + int portSplit = address.LastIndexOf(":"); + + if (portSplit == -1) + { + return address; + } + + string port = address.Substring(portSplit + 1); + + return port.Length == 2 ? address + connType : address; + } + + public static string DefaultCityPort(string address) + { + int portSplit = address.LastIndexOf(":"); + + if (portSplit != -1) + { + return address; + } + + return $"{address}:33101"; + } + } +} diff --git a/TSOClient/FSO.Server.Protocol/Voltron/Packets/AnnouncementMsgPDU.cs b/TSOClient/FSO.Server.Protocol/Voltron/Packets/AnnouncementMsgPDU.cs index ef2d11f5e..75fe40317 100644 --- a/TSOClient/FSO.Server.Protocol/Voltron/Packets/AnnouncementMsgPDU.cs +++ b/TSOClient/FSO.Server.Protocol/Voltron/Packets/AnnouncementMsgPDU.cs @@ -12,6 +12,15 @@ public class AnnouncementMsgPDU : AbstractVoltronPacket public string Subject = ""; public string Message = ""; + public AnnouncementMsgPDU() + { + } + + public AnnouncementMsgPDU(bool critical) + { + Badge = (byte)(critical ? 255 : 0); + } + public override void Deserialize(IoBuffer input, ISerializationContext context) { this.SenderID = input.GetPascalString(); diff --git a/TSOClient/FSO.Server.Protocol/app.config b/TSOClient/FSO.Server.Protocol/app.config deleted file mode 100644 index 51e422beb..000000000 --- a/TSOClient/FSO.Server.Protocol/app.config +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Protocol/packages.config b/TSOClient/FSO.Server.Protocol/packages.config deleted file mode 100644 index 9477ab48a..000000000 --- a/TSOClient/FSO.Server.Protocol/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.Server.Updater/App.config b/TSOClient/FSO.Server.Updater/App.config deleted file mode 100644 index d1428ad71..000000000 --- a/TSOClient/FSO.Server.Updater/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/TSOClient/FSO.Server.Updater/FSO.Server.Watchdog.csproj b/TSOClient/FSO.Server.Updater/FSO.Server.Watchdog.csproj index 5590a8c36..63c712daf 100644 --- a/TSOClient/FSO.Server.Updater/FSO.Server.Watchdog.csproj +++ b/TSOClient/FSO.Server.Updater/FSO.Server.Watchdog.csproj @@ -1,70 +1,18 @@ - - - + + - Debug - AnyCPU - {BEDCDF02-3349-4E64-9BFD-38C499303822} + net9.0 + enable + disable Exe - Properties FSO.Server.Watchdog watchdog - v4.5 512 - true - + + false - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - - - - - - - - - - - - - - - - - + - PreserveNewest @@ -72,12 +20,5 @@ PreserveNewest - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server.Updater/Program.cs b/TSOClient/FSO.Server.Updater/Program.cs index e172fa0d5..b3efbc777 100644 --- a/TSOClient/FSO.Server.Updater/Program.cs +++ b/TSOClient/FSO.Server.Updater/Program.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -40,38 +41,45 @@ static int Main(string[] args) while (restart) { - var setup = AppDomain.CurrentDomain.SetupInformation; - setup.ConfigurationFile = Path.Combine(Path.GetDirectoryName(setup.ConfigurationFile), "server.exe.config"); - var childDomain = AppDomain.CreateDomain("serverDomain", null, setup); int result = 3; try { - result = childDomain.ExecuteAssembly("server.exe", args); + // Use Process instead of AppDomain + var process = new Process(); + process.StartInfo.FileName = "server.exe"; + process.StartInfo.Arguments = string.Join(" ", args.Select(a => $"\"{a}\"")); + process.StartInfo.UseShellExecute = false; + process.Start(); + process.WaitForExit(); + result = process.ExitCode; } catch (Exception e) { Console.WriteLine("Unhandled exception occurred!"); Console.WriteLine(e.ToString()); - e.ToString(); } - AppDomain.Unload(childDomain); if (result > 1) { - //safe exit. switch (result) { case 2: - restart = false; break; + restart = false; + break; case 4: - Update(new string[0]); break; + Update(new string[0]); + break; } } - return result; //was trying to do something smart here with appdomains to reload the app without closing it //but it breaks mono... so to loop running the application you need to use a shell script. //just loop while this watcher doesn't return 2 (shutdown) + else + { + restart = false; // exit loop if normal + } } + return 0; } diff --git a/TSOClient/FSO.Server.Updater/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server.Updater/Properties/AssemblyInfo.cs deleted file mode 100644 index b0a1dddba..000000000 --- a/TSOClient/FSO.Server.Updater/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server.Watchdog")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server.Watchdog")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("bedcdf02-3349-4e64-9bfd-38c499303822")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server/App.config b/TSOClient/FSO.Server/App.config deleted file mode 100644 index 2fc295f80..000000000 --- a/TSOClient/FSO.Server/App.config +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSO.Server/DataService/Providers/ServerAvatarProvider.cs b/TSOClient/FSO.Server/DataService/Providers/ServerAvatarProvider.cs index 6fe0c226e..1066ff0a3 100644 --- a/TSOClient/FSO.Server/DataService/Providers/ServerAvatarProvider.cs +++ b/TSOClient/FSO.Server/DataService/Providers/ServerAvatarProvider.cs @@ -22,12 +22,14 @@ public class ServerAvatarProvider : LazyDataServiceProvider { private static Logger LOG = LogManager.GetCurrentClassLogger(); private int ShardId; + private ServerConfiguration Config; private IDAFactory DAFactory; - public ServerAvatarProvider([Named("ShardId")] int shardId, IDAFactory factory) + public ServerAvatarProvider([Named("ShardId")] int shardId, IDAFactory factory, ServerConfiguration config) { this.ShardId = shardId; this.DAFactory = factory; + Config = config; } public override void PersistMutation(object entity, MutationType type, string path, object value) @@ -147,6 +149,11 @@ public override void DemandMutation(object entity, MutationType type, string pat var filter = db.Lots.GetCommunityLocations(ShardId); avatar.Avatar_Top100ListFilter.Top100ListFilter_ResultsVec = ImmutableList.ToImmutableList(filter); } + else if (Config.Archive != null) + { + var filter = db.ArchiveFeatured.GetByCategory(ShardId, cat); + avatar.Avatar_Top100ListFilter.Top100ListFilter_ResultsVec = ImmutableList.ToImmutableList(filter.Select(x => (uint)x.location)); + } else { var filter = db.LotClaims.Top100Filter(ShardId, cat, 10); @@ -290,8 +297,7 @@ public override void Invalidate(object key) if (!(key is uint)) return; var castKey = (uint)key; lock (Values) { - var val = Values[castKey]; - if (val.Ready) + if (Values.TryGetValue(castKey, out var val) && val.Ready) { ((Avatar)val.GetReady()).Invalidated = true; } diff --git a/TSOClient/FSO.Server/DataService/Providers/ServerLotProvider.cs b/TSOClient/FSO.Server/DataService/Providers/ServerLotProvider.cs index 25d30c351..0d1164dc1 100644 --- a/TSOClient/FSO.Server/DataService/Providers/ServerLotProvider.cs +++ b/TSOClient/FSO.Server/DataService/Providers/ServerLotProvider.cs @@ -19,6 +19,9 @@ using FSO.Common.Enum; using FSO.Server.Common; using FSO.Server.Database.DA.Neighborhoods; +using FSO.Files.RC; +using FSO.Common.Domain; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; namespace FSO.Server.DataService.Providers { @@ -32,14 +35,18 @@ public class ServerLotProvider : EagerDataServiceProvider { LotCategory.welcome, 1 }, }; - private Dictionary LotsByName = new Dictionary(); + private readonly Dictionary LotsByName = []; public City CityRepresentation; - private IRealestateDomain GlobalRealestate; - private IShardRealestateDomain Realestate; - private int ShardId; - private IDAFactory DAFactory; - private IServerNFSProvider NFS; + private readonly IRealestateDomain GlobalRealestate; + private readonly IShardRealestateDomain Realestate; + private readonly int ShardId; + private readonly IDAFactory DAFactory; + private readonly IServerNFSProvider NFS; + + private volatile int Version; + + public CityEditBitmap AllLotsBitmap { get; } public ServerLotProvider([Named("ShardId")] int shardId, IRealestateDomain realestate, IDAFactory daFactory, IServerNFSProvider nfs) { @@ -61,6 +68,8 @@ public ServerLotProvider([Named("ShardId")] int shardId, IRealestateDomain reale City_Top100ListIDs = ImmutableList.Create(), City_TopTenNeighborhoodsVector = ImmutableList.Create() }; + + AllLotsBitmap = new CityEditBitmap(512, 512); } protected override void PreLoad(Callback appender) @@ -98,6 +107,8 @@ protected override Lot LoadOne(uint key) protected override void Insert(uint key, Lot value) { base.Insert(key, value); + Version++; + lock (LotsByName) LotsByName[value.Lot_Name] = value; lock (CityRepresentation.City_ReservedLotInfo) CityRepresentation.City_ReservedLotInfo = CityRepresentation.City_ReservedLotInfo.SetItem(value.Lot_Location_Packed, value.Lot_IsOnline); } @@ -107,6 +118,7 @@ protected override Lot Remove(uint key) var value = base.Remove(key); if (value != null) { + Version++; lock (LotsByName) LotsByName.Remove(value.Lot_Name); lock (CityRepresentation.City_ReservedLotInfo) CityRepresentation.City_ReservedLotInfo = CityRepresentation.City_ReservedLotInfo.Remove(value.Lot_Location_Packed); @@ -157,6 +169,7 @@ protected Lot HydrateOne(DbLot lot, List roommates, List Lot_LastCatChange = lot.category_change_date, Lot_Description = lot.description, Lot_Thumbnail = new cTSOGenericData(new byte[0]), + Lot_Facade = new cTSOGenericData(new byte[0]), Lot_NeighborhoodID = (uint)(nhood?.neighborhood_id ?? 0), Lot_NeighborhoodName = nhood?.name ?? "" }; @@ -189,6 +202,7 @@ protected override Lot LazyLoad(uint key) { Id = key, + Lot_Name = $"({location.X}, {location.Y})", Lot_IsOnline = false, Lot_Location = new Location { Location_X = location.X, Location_Y = location.Y }, //Lot_Price = 0, @@ -197,6 +211,7 @@ protected override Lot LazyLoad(uint key) Lot_RoommateVec = ImmutableList.Create(), Lot_Thumbnail = new cTSOGenericData(new byte[0]), + Lot_Facade = new cTSOGenericData(new byte[0]), Lot_ThumbnailCheckSum = key }; } @@ -213,20 +228,40 @@ public override void PersistMutation(object entity, MutationType type, string pa } break; case "Lot_Thumbnail": - var imgpath = Path.Combine(NFS.GetBaseDirectory(), "Lots/" + lot.DbId.ToString("x8") + "/thumb.png"); - var data = (cTSOGenericData)value; - - using (var db = DAFactory.Get()) { - db.Lots.SetDirty(lot.DbId, 1); + var imgpath = Path.Combine(NFS.GetBaseDirectory(), "Lots/" + lot.DbId.ToString("x8") + "/thumb.png"); + var data = (cTSOGenericData)value; + + using (var db = DAFactory.Get()) + { + db.Lots.SetDirty(lot.DbId, 1); + } + + using (FileStream fs = File.Open(imgpath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.Write(data.Data, 0, data.Data.Length); + } + lot.Lot_Thumbnail = new cTSOGenericData(new byte[0]); + break; } - using (FileStream fs = File.Open(imgpath, FileMode.Create, FileAccess.Write, FileShare.None)) + case "Lot_Facade": { - fs.Write(data.Data, 0, data.Data.Length); + var imgpath = Path.Combine(NFS.GetBaseDirectory(), "Lots/" + lot.DbId.ToString("x8") + "/thumb.fsof"); + var data = (cTSOGenericData)value; + + using (var db = DAFactory.Get()) + { + db.Lots.SetDirty(lot.DbId, 0); // Facade worker doesn't need to regen if the user sent it in + } + + using (FileStream fs = File.Open(imgpath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.Write(data.Data, 0, data.Data.Length); + } + lot.Lot_Facade = new cTSOGenericData(new byte[0]); + break; } - lot.Lot_Thumbnail = new cTSOGenericData(new byte[0]); - break; case "Lot_Category": uint minSkill; if (!SkillGameplayCategory.TryGetValue((LotCategory)lot.Lot_Category, out minSkill)) minSkill = 0; @@ -267,7 +302,18 @@ public override void PersistMutation(object entity, MutationType type, string pa public override void DemandMutation(object entity, MutationType type, string path, object value, ISecurityContext context) { var lot = entity as Lot; - if (lot.DbId == 0) { throw new SecurityException("Unclaimed lots cannot be mutated"); } + if (lot.DbId == 0) { + switch (path) { + case "Lot_IsOnline": + case "Lot_NumOccupants": + case "Lot_RoommateVec": + case "Lot_SpotLightText": + context.DemandInternalSystem(); + return; + default: + throw new SecurityException("Unclaimed lots cannot be mutated"); + } + } var roomies = lot.Lot_RoommateVec; switch (path) @@ -317,9 +363,38 @@ public override void DemandMutation(object entity, MutationType type, string pat break; //roommate only case "Lot_Thumbnail": - if (lot.Lot_Category == 11) context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); - else context.DemandAvatars(roomies, AvatarPermissions.WRITE); - //TODO: needs to be generic data, png, size 288x288, less than 1MB + { + if (!context.HasModerationLevel(1)) + { + if (lot.Lot_Category == 11) context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); + else context.DemandAvatars(roomies, AvatarPermissions.WRITE); + } + + var dataValue = (cTSOGenericData)value; + if (dataValue.Data.Length > 1024 * 1024) + { + throw new SecurityException("Thumbnail is too large"); + } + //TODO: needs to be generic data, png, size 288x288 + } + break; + + case "Lot_Facade": + { + if (!context.HasModerationLevel(1)) + { + if (lot.Lot_Category == 11) context.DemandAvatar(lot.Lot_LeaderID, AvatarPermissions.WRITE); + else context.DemandAvatars(roomies, AvatarPermissions.WRITE); + } + + var dataValue = (cTSOGenericData)value; + + var fsof = new FSOF(); + + using var mem = new MemoryStream(dataValue.Data); + + fsof.ValidateFSO(mem); + } break; case "Lot_IsOnline": case "Lot_NumOccupants": @@ -390,5 +465,28 @@ public Lot GetByName(string name) } return null; } + + public void AddLocationsTo(HashSet locations) + { + foreach (var pair in Values) + { + locations.Add(pair.Key); + } + } + + public void UpdateReservedCache(HashSet reservedTiles, ref int version) + { + if (version == Version) + { + return; + } + + reservedTiles.Clear(); + + foreach (var pair in Values) + { + reservedTiles.Add(pair.Key); + } + } } } diff --git a/TSOClient/FSO.Server/DataService/Providers/ServerNeighborhoodProvider.cs b/TSOClient/FSO.Server/DataService/Providers/ServerNeighborhoodProvider.cs index cfc78263d..20b393dc6 100644 --- a/TSOClient/FSO.Server/DataService/Providers/ServerNeighborhoodProvider.cs +++ b/TSOClient/FSO.Server/DataService/Providers/ServerNeighborhoodProvider.cs @@ -1,6 +1,7 @@ using FSO.Client.Rendering.City.Model; using FSO.Common.DataService.Framework; using FSO.Common.DataService.Model; +using FSO.Common.Domain; using FSO.Common.Domain.Realestate; using FSO.Common.Domain.RealestateDomain; using FSO.Common.Enum; diff --git a/TSOClient/FSO.Server/DataService/ServerDataService.cs b/TSOClient/FSO.Server/DataService/ServerDataService.cs index 16d1ad2c9..fad277295 100644 --- a/TSOClient/FSO.Server/DataService/ServerDataService.cs +++ b/TSOClient/FSO.Server/DataService/ServerDataService.cs @@ -12,6 +12,7 @@ public ServerDataService(IModelSerializer serializer, { AddProvider(kernel.Get()); var lots = kernel.Get(); + kernel.Bind().ToConstant(lots); AddProvider(lots); var city = kernel.Get(); AddProvider(city); diff --git a/TSOClient/FSO.Server/DataService/ServerNFSProvider.cs b/TSOClient/FSO.Server/DataService/ServerNFSProvider.cs index 378ab0e93..2f2a353d1 100644 --- a/TSOClient/FSO.Server/DataService/ServerNFSProvider.cs +++ b/TSOClient/FSO.Server/DataService/ServerNFSProvider.cs @@ -1,4 +1,4 @@ -using FSO.Common.DataService.Framework; +using FSO.Common.Domain; namespace FSO.Server.DataService { @@ -14,5 +14,10 @@ public string GetBaseDirectory() { return BasePath; } + + public string GetShardMapDirectory(int shardId) + { + return Path.Join(BasePath, $"City{shardId}"); + } } } diff --git a/TSOClient/FSO.Server/DataService/ShardDataServiceModule.cs b/TSOClient/FSO.Server/DataService/ShardDataServiceModule.cs index fc2829189..b64d9bfcc 100644 --- a/TSOClient/FSO.Server/DataService/ShardDataServiceModule.cs +++ b/TSOClient/FSO.Server/DataService/ShardDataServiceModule.cs @@ -1,5 +1,5 @@ using FSO.Common.DataService; -using FSO.Common.DataService.Framework; +using FSO.Common.Domain; using Ninject.Modules; namespace FSO.Server.DataService diff --git a/TSOClient/FSO.Server/Discord/DiscordConfiguration.cs b/TSOClient/FSO.Server/Discord/DiscordConfiguration.cs index e4577ac07..dcc861692 100644 --- a/TSOClient/FSO.Server/Discord/DiscordConfiguration.cs +++ b/TSOClient/FSO.Server/Discord/DiscordConfiguration.cs @@ -1,12 +1,19 @@ -namespace FSO.Server.Discord +using Newtonsoft.Json; + +namespace FSO.Server.Discord { public class DiscordConfiguration { + [JsonProperty("apiKey")] public string ApiKey; + [JsonProperty("serverID")] public ulong ServerID; + [JsonProperty("eventModChannelID")] public ulong EventModChannelID; + [JsonProperty("eventPublicChannelID")] public ulong EventPublicChannelID; + [JsonProperty("statusChannelID")] public ulong StatusChannelID; } } diff --git a/TSOClient/FSO.Server/Domain/LotIdFlags.cs b/TSOClient/FSO.Server/Domain/LotIdFlags.cs new file mode 100644 index 000000000..bc10ae6fe --- /dev/null +++ b/TSOClient/FSO.Server/Domain/LotIdFlags.cs @@ -0,0 +1,31 @@ +using System; + +namespace FSO.Server.Domain +{ + [Flags] + internal enum LotIdFlags : uint + { + None = 0, + + /// + /// Unowned lots can be opened in archive mode. + /// The lot is generated without a visible phone booth, trash and terrain marks for the vehicle portal. + /// When the lot is closed, it is not saved. + /// It shouldn't be possible to purchase a lot while it's opened as an unowned lot. + /// (future, when placing objects is allowed) If any objects were placed, they are returned to inventory. + /// + Unowned = 0x20000000, + + /// + /// Job Lot instances are managed by the job matchmaker. + /// It dynamically creates instances when players from different job types and levels go to work, + /// and can create more when instances are full or when players are blocked. + /// + JobLot = 0x40000000, + + Reserved = 0x80000000, + + SpecialMask = Unowned | JobLot, + NormalMask = ~(SpecialMask | Reserved) + } +} diff --git a/TSOClient/FSO.Server/Embedded/ArchiveConfigBuilder.cs b/TSOClient/FSO.Server/Embedded/ArchiveConfigBuilder.cs new file mode 100644 index 000000000..9d717fa2d --- /dev/null +++ b/TSOClient/FSO.Server/Embedded/ArchiveConfigBuilder.cs @@ -0,0 +1,177 @@ +using FSO.Common; + +namespace FSO.Server.Embedded +{ + internal static class ArchiveConfigBuilder + { + public static ServerConfiguration Build(ArchiveConfiguration config) + { + // TODO: server directory (build nfs and db string from this), server public host/ports + + string publicHost = "0.0.0.0"; // city connection is up to the user, lot connection automatically uses city + int cityPort = config.CityPort; + int lotPort = config.LotPort; + + string binding = config.Flags.HasFlag(ArchiveConfigFlags.Offline) ? "127.0.0.1" : "0.0.0.0"; + + var dbPath = Path.Combine(config.ArchiveDataDirectory, "fsoarchive.db"); + + return new ServerConfiguration() + { + Name = config.Name, + GameLocation = FSO.Content.Content.Get().BasePath, + Secret = Guid.NewGuid().ToString(), + Archive = config, + Events = config.Events, + SimNFS = config.ArchiveDataDirectory, + Database = new Database.DatabaseConfiguration() + { + Engine = "sqlite", + ConnectionString = $"Data Source={dbPath}", + }, + + Services = new ServerConfigurationservices() + { + Tasks = new Servers.Tasks.TaskServerConfiguration() + { + Enabled = true, + Call_Sign = "callisto", + Binding = "127.0.0.1:35101", + Internal_Host = "127.0.0.1:35101", + Public_Host = "127.0.0.1:35101", + Use_SSL = false, + Schedule = new List() + { + new Servers.Tasks.ScheduledTaskRunOptions() + { + Cron = "0 3 * * *", + Task = "prune_database", + Timeout = 3600, + Parameter = { } + }, + new Servers.Tasks.ScheduledTaskRunOptions() + { + Cron = "0 4 * * *", + Task = "bonus", + Timeout = 3600, + Run_If_Missed = true, + Shard_Id = 1, + Parameter = { } + }, + new Servers.Tasks.ScheduledTaskRunOptions() + { + Cron = "0 4 * * *", + Task = "job_balance", + Timeout = 3600, + Run_If_Missed = true, + Parameter = { } + }, + new Servers.Tasks.ScheduledTaskRunOptions() + { + Cron = "0 0 * * *", + Task = "neighborhood_tick", + Timeout = 3600, + Run_If_Missed = true, + Parameter = { } + }, + new Servers.Tasks.ScheduledTaskRunOptions() + { + Cron = "0 0 * * *", + Task = "birthday_gift", + Timeout = 3600, + Run_If_Missed = true, + Parameter = { } + } + }, + Tuning = new Servers.Tasks.TaskTuning() + { + Bonus = new Servers.Tasks.Domain.BonusTaskTuning() + { + property_bonus = new Servers.Tasks.Domain.PropertyBonusTuning() + { + per_unit = 10, + overrides = new Dictionary() + { + { 1, 1500 }, + { 2, 1250 }, + { 3, 1000 } + } + }, + visitor_bonus = new Servers.Tasks.Domain.VisitorBonusTuning() + { + per_unit = 8 + } + }, + BirthdayGift = new Servers.Tasks.Domain.BirthdayGiftTaskTuning() + { + items = new List() + /* + { + new Servers.Tasks.Domain.BirthdayGiftItem() + { + age = 1000, + guid = 1303919565, + mail_subject = "1000 Days!", + mail_message = "This is an example gift that shows how birthday gifts can be awarded by the server at different milestones - this one is for 1000 days. Please change this message. Or leave it the same, I don't mind.\n - Rhys", + mail_sender_name = "FreeSO Developers" + } + } + */ + } + } + }, + Cities = new List() + { + new Servers.City.CityServerConfiguration() + { + Call_Sign = "ganymede", + ID = 1, + Binding = $"{binding}:{cityPort}", + Internal_Host = $"127.0.0.1:{cityPort}", + Public_Host = $"{publicHost}:{cityPort}", + Use_SSL = false, + + Neighborhoods = new Servers.City.CityServerNhoodConfiguration() + { + Mayor_Elegibility_Limit = 4, + Mayor_Elegibility_Falloff = 4, + Min_Nominations = 2, + Election_Week_Align = true, + Election_Move_Penalty = 14 + }, + Maintenance = new Servers.City.CityServerMaintenanceConfiguration() + { + Cron = "0 4 * * *", + Timeout = 3600, + Visits_Retention_Period = 7, + }, + + Initial_Funds = config.InitialFunds + } + }, + Lots = new List() + { + new Servers.Lot.LotServerConfiguration() + { + Call_Sign = "europa", + Binding = $"{binding}:{lotPort}", + Internal_Host = $"127.0.0.1:{lotPort}", + Public_Host = $"{publicHost}:{lotPort}", + Max_Lots = 100, + Use_SSL = false, + Tick_Rate_Divider = config.Flags.HasFlag(ArchiveConfigFlags.ReducedTickRate) ? 4 : 1, + Cities = new Servers.Lot.LotServerConfigurationCity[] + { + new Servers.Lot.LotServerConfigurationCity() + { + ID = 1, + Host = $"127.0.0.1:{cityPort}" + } + } + } + } + } + }; + } + } +} diff --git a/TSOClient/FSO.Server/Embedded/ArchiveConfigExporter.cs b/TSOClient/FSO.Server/Embedded/ArchiveConfigExporter.cs new file mode 100644 index 000000000..f87d05fd5 --- /dev/null +++ b/TSOClient/FSO.Server/Embedded/ArchiveConfigExporter.cs @@ -0,0 +1,44 @@ +using FSO.Common; + +namespace FSO.Server.Embedded +{ + public static class ArchiveConfigExporter + { + private static string MakeRelativePath(string path) + { + string root = AppDomain.CurrentDomain.BaseDirectory; + + return Path.GetRelativePath(root, path); + } + + public static string BuildAndExport(ArchiveConfiguration config, bool archiveAbsolute, bool tsoAbsolute) + { + if (!archiveAbsolute) + { + config.ArchiveDataDirectory = MakeRelativePath(config.ArchiveDataDirectory); + } + else + { + config.ArchiveDataDirectory = Path.GetFullPath(config.ArchiveDataDirectory); + } + + var serverConfig = ArchiveConfigBuilder.Build(config); + + if (!tsoAbsolute) + { + serverConfig.GameLocation = MakeRelativePath(serverConfig.GameLocation); + } + else + { + serverConfig.GameLocation = Path.GetFullPath(serverConfig.GameLocation); + } + + var json = Newtonsoft.Json.JsonConvert.SerializeObject( + serverConfig, + Newtonsoft.Json.Formatting.Indented, + new Newtonsoft.Json.JsonSerializerSettings() { NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore }); + + return json; + } + } +} diff --git a/TSOClient/FSO.Server/Embedded/ArchiveManagement.cs b/TSOClient/FSO.Server/Embedded/ArchiveManagement.cs new file mode 100644 index 000000000..c66dad725 --- /dev/null +++ b/TSOClient/FSO.Server/Embedded/ArchiveManagement.cs @@ -0,0 +1,224 @@ +using FSO.Common; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Avatars; +using FSO.Server.Database.DA.Bans; +using FSO.Server.Database.DA.Users; +using FSO.Server.Protocol.Embedded; + +namespace FSO.Server.Embedded +{ + public class ArchiveManagement + { + private IDAFactory DAFactory; + + public ArchiveManagement(ArchiveConfiguration config) + { + var sConfig = ArchiveConfigBuilder.Build(config); + + DAFactory = new SqliteDAFactory(sConfig.Database); + } + + private ArchiveDbAvatar AvatarFromSummary(DbAvatarSummary summary) + { + return new ArchiveDbAvatar() + { + ID = summary.avatar_id, + Name = summary.name, + LotName = summary.lot_name, + LastLogin = 0 + }; + } + + private ArchiveDbUser UserFromSummary(UserSummary summary) + { + ArchiveDbUserStatus status; + + if (summary.is_banned) + { + status = ArchiveDbUserStatus.Banned; + } + else if (!summary.is_verified) // todo: server doesn't need verification? + { + status = ArchiveDbUserStatus.Unverified; + } + else if (summary.is_admin) + { + status = ArchiveDbUserStatus.Admin; + } + else if (summary.is_moderator) + { + status = ArchiveDbUserStatus.Mod; + } + else + { + status = ArchiveDbUserStatus.Normal; + } + + return new ArchiveDbUser() + { + ID = summary.user_id, + AvatarCount = summary.avatar_count, + Status = status, + Name = summary.display_name, + IP = summary.last_ip, + }; + } + + private ArchiveDbIpBan BanFromDb(DbBan ban) + { + return new ArchiveDbIpBan() + { + IP = ban.ip_address + }; + } + + public List GetUsers() + { + using (var da = DAFactory.Get()) + { + var users = da.Users.AllSummaries(); + + return [.. users.Select(UserFromSummary)]; + } + } + + public List GetAvatars(uint userId) + { + using (var da = DAFactory.Get()) + { + var avatars = da.Avatars.GetSummaryByUserId(userId); + + return [.. avatars.Select(AvatarFromSummary)]; + } + } + + public List GetIpBans() + { + using (var da = DAFactory.Get()) + { + var bans = da.Bans.All(); + + return [.. bans.Select(BanFromDb)]; + } + } + + public void DeleteUser(int userId) + { + using (var da = DAFactory.Get()) + { + var avatars = da.Avatars.GetByUserId((uint)userId); + + foreach (var ava in avatars) + { + da.Avatars.UpdateUser(ava.avatar_id, 1); // TODO: better indicator for archive user? + } + + da.Users.Delete((uint)userId); + } + } + + public void BanUser(int userId) + { + using (var da = DAFactory.Get()) + { + var user = da.Users.GetById((uint)userId); + + if (user == null) + { + return; + } + + var existingBan = da.Bans.GetByIP(user.last_ip); + + if (existingBan == null) + { + BanIp(user.last_ip); + } + } + } + + public void UnbanUser(int userId) + { + using (var da = DAFactory.Get()) + { + var user = da.Users.GetById((uint)userId); + + if (user == null) + { + return; + } + + da.Bans.Remove((uint)userId); + da.Bans.RemoveByIp(user.last_ip); + } + } + + public void DeleteAvatar(int avatarId) + { + using (var da = DAFactory.Get()) + { + // TODO: safety stuff (does sqlite properly cascade anything?) + da.Avatars.Delete((uint)avatarId); + } + } + + public void MigrateAvatar(int avatarId, int userId) + { + using (var da = DAFactory.Get()) + { + da.Avatars.UpdateUser((uint)avatarId, (uint)userId); + } + } + + public void BanIp(string ip) + { + using (var da = DAFactory.Get()) + { + var relatedUsers = da.Users.GetByLastIP(ip); + + uint userId = 0; + + foreach (var user in relatedUsers) + { + da.Users.UpdateBanned(user.user_id, true); + userId = user.user_id; + } + + da.Bans.Add(ip, userId, "Archive management", 0, ""); + } + } + + public void UnbanIp(string ip) + { + using (var da = DAFactory.Get()) + { + da.Bans.RemoveByIp(ip); + + var users = da.Users.GetByLastIP(ip); + + foreach (var user in users) + { + da.Users.UpdateBanned(user.user_id, false); + } + } + } + + public List GetUsersForIp(string ip) + { + using (var da = DAFactory.Get()) + { + var users = da.Users.AllSummaries().Where((x) => x.last_ip == ip); + + return [.. users.Select(UserFromSummary)]; + } + } + + public void SetInfo(string name, string map) + { + using (var da = DAFactory.Get()) + { + da.Shards.UpdateInfo(1, name, map); + } + } + } +} diff --git a/TSOClient/FSO.Server/Embedded/EmbeddedServer.cs b/TSOClient/FSO.Server/Embedded/EmbeddedServer.cs new file mode 100644 index 000000000..c6eec7966 --- /dev/null +++ b/TSOClient/FSO.Server/Embedded/EmbeddedServer.cs @@ -0,0 +1,102 @@ +using FSO.Common; +using FSO.Server.Database; +using FSO.Server.DataService; +using FSO.Server.Utils; +using Ninject; +using Ninject.Parameters; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace FSO.Server.Embedded +{ + public class EmbeddedServer + { + public bool Ready { get; private set; } + public float ReadyPercent { get; private set; } + public Exception Error { get; private set; } + + private Thread ServerThread; + private Action ShutdownAction; + + public ArchiveConfiguration Config { get; } + + public EmbeddedServer(ArchiveConfiguration config) + { + Config = config; + } + + public void Start() + { + + ServerThread = new Thread(() => + { + var config = ArchiveConfigBuilder.Build(Config); + + var kernel = new StandardKernel( + new ServerConfigurationModule(config), + new DatabaseModule(), + new GlobalDataServiceModule(), + new GluonHostPoolModule() + ); + + var tool = kernel.Get(new ConstructorArgument("options", new RunServerOptions())); + + tool.RunEmbedded( + (Action shutdown) => + { + ShutdownAction = shutdown; + Ready = true; + }, + (float progress) => + { + ReadyPercent = progress; + }, + (Exception error) => + { + Error = error; + } + ); + }); + + ServerThread.Start(); + } + + private void DisposeResources() + { + if (Config.Disposables != null) + { + foreach (var item in Config.Disposables) + { + item.Dispose(); + } + } + } + + public Task Shutdown() + { + return Task.Run(() => + { + while (true) + { + if (ShutdownAction == null) + { + if (ServerThread.Join(10)) + { + DisposeResources(); + return true; + } + } + else + { + ShutdownAction(); + ShutdownAction = null; + ServerThread.Join(); + DisposeResources(); + return true; + } + } + }); + } + } +} diff --git a/TSOClient/FSO.Server/FSO.Server.csproj b/TSOClient/FSO.Server/FSO.Server.csproj index 775ca8739..499c0e1fe 100644 --- a/TSOClient/FSO.Server/FSO.Server.csproj +++ b/TSOClient/FSO.Server/FSO.Server.csproj @@ -1,439 +1,62 @@ - - - + + - Debug - AnyCPU - {8F125201-FDF0-4A13-886F-19662707D34D} + net9.0 + enable + disable Library - Properties FSO.Server server - v4.5 512 - true - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - - FreeSO.ico + true + true + true + full - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - Always - - - - + - - ..\packages\CommandLineParser.1.9.71\lib\net45\CommandLine.dll - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\Portable.BouncyCastle.1.7.0.2\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch10+MonoAndroid10+xamarinmac20+xamarinios10\crypto.dll - - - ..\packages\Portable.JWT.1.0.3\lib\4.5\JWT.dll - - - ..\tso.world\Mario.dll - - - ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Cors.3.0.1\lib\net45\Microsoft.Owin.Cors.dll - - - ..\packages\Microsoft.Owin.Host.HttpListener.2.0.2\lib\net45\Microsoft.Owin.Host.HttpListener.dll - - - ..\packages\Microsoft.Owin.Hosting.2.0.2\lib\net45\Microsoft.Owin.Hosting.dll - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Ninject.3.3.4\lib\net45\Ninject.dll - - - ..\packages\Ninject.Extensions.ChildKernel.3.3.0\lib\net45\Ninject.Extensions.ChildKernel.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - ..\packages\SixLabors.Core.1.0.0-beta0006\lib\netstandard1.1\SixLabors.Core.dll - - - ..\packages\SixLabors.ImageSharp.1.0.0-beta0004\lib\netstandard1.1\SixLabors.ImageSharp.dll - - - - ..\packages\System.Buffers.4.5.0\lib\netstandard1.1\System.Buffers.dll - - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - - - ..\packages\System.Memory.4.5.1\lib\netstandard1.1\System.Memory.dll - - - ..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll - - - - ..\packages\System.Numerics.Vectors.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Numerics.Vectors.dll - - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - - - - - - ..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll - - - ..\packages\Microsoft.AspNet.Cors.5.2.3\lib\net45\System.Web.Cors.dll - - - ..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll - - - ..\packages\Microsoft.AspNet.WebApi.Cors.5.2.3\lib\net45\System.Web.Http.Cors.dll - - - ..\packages\Microsoft.AspNet.WebApi.Owin.5.2.3\lib\net45\System.Web.Http.Owin.dll - - - - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - PreserveNewest - - - - + + + + + + + + + - - {d8232422-9d79-4200-a981-eb70ed82ccf3} - TargaImagePCL - - - {c051793d-1a9c-4554-9bb8-bafdc01a096a} - FSO.Common.DatabaseService - - - {9848faf5-444a-48cc-a26a-8115d8c4fb52} - FSO.Common.Domain - - - {b5b2c04d-b8e4-47c7-9731-48e30fd5f70d} - FSO.Content.TSO - - - {329e0aee-7871-40a7-b5af-8c0d0086ef71} - FSO.Server.Clients - - - {39b61962-fe43-4b64-8e57-8f793737fffe} - FSO.Server.Common - - - {430acd60-e798-43f0-ad61-8b5a35df6ab2} - FSO.Server.Database - - - {88c69e02-78d4-4d71-9c26-43a9b118285a} - FSO.Common.DataService - - - {a1d9aba0-0105-436d-8f8c-2418db768080} - FSO.Server.Domain - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {bedcdf02-3349-4e64-9bfd-38c499303822} - FSO.Server.Watchdog - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {5eddefd2-c850-49c1-812d-ddeff09125ef} - FSO.SimAntics - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - - - {b1a6e4c2-e080-4c34-a604-d11b5296a9b8} - FSO.LotView - + + + + + + + + + - - False - Microsoft .NET Framework 4.5 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - + + ..\tso.world\Mario.dll + + - + PreserveNewest - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServer.cs b/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServer.cs index 199876fc5..b75482504 100644 --- a/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServer.cs +++ b/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServer.cs @@ -39,11 +39,14 @@ public abstract class AbstractAriesServer : AbstractServer, IoHandler, ISocketSe private int TotalConnectionCount; private int MigrationCount; - private List _SessionInterceptors = new List(); + private List _SessionInterceptors = []; + private List _DisposableHandlers = []; public int UnexpectedDisconnectWaitSeconds = 0; public bool TimeoutIfNoAuth; + protected virtual RequestClientSessionArchive ArchiveHandshake(IoSession session) => null; + public AbstractAriesServer(AbstractAriesServerConfig config, IKernel kernel) { _Sessions = new Sessions(this); @@ -98,6 +101,10 @@ public override void Start() Acceptor = new AsyncSocketAcceptor(); try { + // "old mode" attempts to open an SSL acceptor on xx100 and plain on xx101 + // The new mode is either one or the other, and makes no assumptions about the port shape + bool oldMode = Config.Use_SSL == null; + if (Config.Certificate != null) { var ssl = new SslFilter(new System.Security.Cryptography.X509Certificates.X509Certificate2(Config.Certificate)); @@ -115,16 +122,29 @@ public override void Start() LOG.Info("Listening on " + Acceptor.LocalEndPoint + " with TLS"); } - //Bind in the plain too as a workaround until we can get Mina.NET to work nice for TLS in the AriesClient - PlainAcceptor = new AsyncSocketAcceptor(); - if (Debugger != null){ - PlainAcceptor.FilterChain.AddLast("packetLogger", new AriesProtocolLogger(Debugger.GetPacketLogger(), Kernel.Get())); + if (!oldMode) + { + if (Config.Use_SSL.Value && Config.Certificate == null) + { + throw new NotSupportedException("Can't currently use SSL without a certificate."); + } } - PlainAcceptor.FilterChain.AddLast("protocol", new ProtocolCodecFilter(Kernel.Get())); - PlainAcceptor.Handler = this; - PlainAcceptor.Bind(IPEndPointUtils.CreateIPEndPoint(Config.Binding.Replace("100", "101"))); - LOG.Info("Listening on " + PlainAcceptor.LocalEndPoint + " in the plain"); + if (oldMode || !Config.Use_SSL.Value) + { + //Bind in the plain too as a workaround until we can get Mina.NET to work nice for TLS in the AriesClient + PlainAcceptor = new AsyncSocketAcceptor(); + if (Debugger != null) + { + PlainAcceptor.FilterChain.AddLast("packetLogger", new AriesProtocolLogger(Debugger.GetPacketLogger(), Kernel.Get())); + } + + PlainAcceptor.FilterChain.AddLast("protocol", new ProtocolCodecFilter(Kernel.Get())); + PlainAcceptor.Handler = this; + // TODO: mode where only one is available and it doesn't do the port replace + PlainAcceptor.Bind(IPEndPointUtils.CreateIPEndPoint(oldMode ? Config.Binding.Replace("100", "101") : Config.Binding)); + LOG.Info("Listening on " + PlainAcceptor.LocalEndPoint + " in the plain"); + } } catch(Exception ex) { @@ -158,8 +178,15 @@ protected virtual void Bootstrap() { var handlerInstance = Kernel.Get(handler); _Router.AddHandlers(handlerInstance); - if(handlerInstance is IAriesSessionInterceptor){ - _SessionInterceptors.Add((IAriesSessionInterceptor)handlerInstance); + + if (handlerInstance is IAriesSessionInterceptor interceptor) + { + _SessionInterceptors.Add(interceptor); + } + + if (handlerInstance is IDisposable disposable) + { + _DisposableHandlers.Add(disposable); } } } @@ -184,7 +211,15 @@ public void SessionCreated(IoSession session) if (TimeoutIfNoAuth) ariesSession.TimeoutIfNoAuth(20000); //Ask for session info - session.Write(new RequestClientSession()); + var handshake = ArchiveHandshake(session); + if (handshake != null) + { + session.Write(handshake); + } + else + { + session.Write(new RequestClientSession()); + } } /// @@ -406,6 +441,11 @@ public override void Shutdown() session.Close(); } + foreach (var disposable in _DisposableHandlers) + { + disposable.Dispose(); + } + MarkHostDown(); } diff --git a/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServerConfig.cs b/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServerConfig.cs index 28bb561b2..5025bcb8e 100644 --- a/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServerConfig.cs +++ b/TSOClient/FSO.Server/Framework/Aries/AbstractAriesServerConfig.cs @@ -1,11 +1,20 @@ -namespace FSO.Server.Framework.Aries +using Newtonsoft.Json; + +namespace FSO.Server.Framework.Aries { public abstract class AbstractAriesServerConfig { + [JsonProperty("call_sign")] public string Call_Sign; + [JsonProperty("certificate")] public string Certificate; + [JsonProperty("binding")] public string Binding; + [JsonProperty("internal_host")] public string Internal_Host; + [JsonProperty("public_host")] public string Public_Host; + [JsonProperty("use_ssl")] + public bool? Use_SSL; } } diff --git a/TSOClient/FSO.Server/Framework/Aries/ISessions.cs b/TSOClient/FSO.Server/Framework/Aries/ISessions.cs index 1894fe5bc..83b2faa8a 100644 --- a/TSOClient/FSO.Server/Framework/Aries/ISessions.cs +++ b/TSOClient/FSO.Server/Framework/Aries/ISessions.cs @@ -10,6 +10,7 @@ public interface ISessions ISessionGroup GetOrCreateGroup(object id); IVoltronSession GetByAvatarId(uint id); + IVoltronSession[] GetAllByUserId(uint id); ISessionProxy All(); HashSet Clone(); diff --git a/TSOClient/FSO.Server/Framework/Aries/Sessions.cs b/TSOClient/FSO.Server/Framework/Aries/Sessions.cs index dc09a433e..16ee17c6c 100644 --- a/TSOClient/FSO.Server/Framework/Aries/Sessions.cs +++ b/TSOClient/FSO.Server/Framework/Aries/Sessions.cs @@ -45,6 +45,17 @@ public IVoltronSession GetByUserId(uint id) } } + public IVoltronSession[] GetAllByUserId(uint id) + { + lock (_Sessions) + { + return _Sessions.Where(x => + { + return x is IVoltronSession && ((IVoltronSession)x).UserId == id; + }).Select(x => (IVoltronSession)x).ToArray(); + } + } + public T UpgradeSession(IAriesSession session, Callback init) where T : AriesSession { var newSession = ((AriesSession)session).UpgradeSession(); diff --git a/TSOClient/FSO.Server/Framework/Gluon/GluonSession.cs b/TSOClient/FSO.Server/Framework/Gluon/GluonSession.cs index 3030305ed..f93987e09 100644 --- a/TSOClient/FSO.Server/Framework/Gluon/GluonSession.cs +++ b/TSOClient/FSO.Server/Framework/Gluon/GluonSession.cs @@ -17,6 +17,10 @@ public GluonSession(IoSession ioSession) : base(ioSession) public string PublicHost { get; set; } + public bool HasModerationLevel(int threshold) + { + return true; + } public void DemandAvatar(uint id, AvatarPermissions permission) diff --git a/TSOClient/FSO.Server/Framework/Voltron/VoltronSession.cs b/TSOClient/FSO.Server/Framework/Voltron/VoltronSession.cs index 0006d191c..c67edce6f 100644 --- a/TSOClient/FSO.Server/Framework/Voltron/VoltronSession.cs +++ b/TSOClient/FSO.Server/Framework/Voltron/VoltronSession.cs @@ -15,11 +15,18 @@ public class VoltronSession : AriesSession, IVoltronSession public uint AvatarId { get; set; } public int AvatarClaimId { get; set; } + public bool Unverified { get; set; } + + // Archive specific state + public string DisplayName { get; set; } + public uint ModerationLevel { get; set; } + public uint SessionUID { get; set; } + public bool IsAnonymous { get { - return AvatarId == 0; + return AvatarId == 0 || Unverified; } } @@ -40,6 +47,11 @@ public override void Close() base.Close(); } + public bool HasModerationLevel(int threshold) + { + return ModerationLevel >= threshold; + } + public void DemandAvatar(uint id, AvatarPermissions permission) { diff --git a/TSOClient/FSO.Server/Program.cs b/TSOClient/FSO.Server/Program.cs index 4123d2c8a..439e788ea 100644 --- a/TSOClient/FSO.Server/Program.cs +++ b/TSOClient/FSO.Server/Program.cs @@ -1,54 +1,89 @@ -using FSO.Server.Database; +using CommandLine; +using FSO.Server.Database; using FSO.Server.DataService; using FSO.Server.Utils; using Ninject; using Ninject.Parameters; -using System; namespace FSO.Server { public class Program { + private readonly struct ToolInfo(Type toolType, object toolOptions) + { + public readonly Type ToolType = toolType; + public readonly object ToolOptions = toolOptions; + } + public static int Main(string[] args) { - Type toolType = null; - object toolOptions = null; + ToolInfo? toolInfo = null; string[] a2 = args; if (args.Length == 0) a2 = new string[] { "run" }; var options = new ProgramOptions(); - var switchIsValid = new CommandLine.Parser().ParseArguments(a2, options, - (verb, subOptions) => + int result = Parser.Default.ParseArguments< + RunServerOptions, DatabaseInitOptions, ImportNhoodOptions, RestoreLotsOptions, + SqliteImportOptions, DataTrimOptions, ArchiveConvertOptions, ImportArchiveFeaturedOptions, + PluginAnonymizeOptions, BackupSelectionOptions>(a2) + .MapResult( + (RunServerOptions opts) => { - switch (verb) - { - case "run": - toolType = typeof(ToolRunServer); - toolOptions = subOptions; - break; - case "db-init": - toolType = typeof(ToolInitDatabase); - toolOptions = subOptions; - break; - case "import-nhood": - toolType = typeof(ToolImportNhood); - toolOptions = subOptions; - break; - case "restore-lots": - toolType = typeof(ToolRestoreLots); - toolOptions = subOptions; - break; - default: - Console.Write(options.GetUsage(verb)); - break; - } - } - ); + toolInfo = new(typeof(ToolRunServer), opts); + return 0; + }, + (DatabaseInitOptions opts) => + { + toolInfo = new(typeof(ToolInitDatabase), opts); + return 0; + }, + (ImportNhoodOptions opts) => + { + toolInfo = new(typeof(ToolImportNhood), opts); + return 0; + }, + (RestoreLotsOptions opts) => + { + toolInfo = new(typeof(ToolRestoreLots), opts); + return 0; + }, + (SqliteImportOptions opts) => + { + toolInfo = new(typeof(ToolSqliteImport), opts); + return 0; + }, + (DataTrimOptions opts) => + { + toolInfo = new(typeof(ToolDataTrim), opts); + return 0; + }, + (ArchiveConvertOptions opts) => + { + toolInfo = new(typeof(ToolArchiveConvert), opts); + return 0; + }, + (ImportArchiveFeaturedOptions opts) => + { + toolInfo = new(typeof(ToolImportArchiveFeatured), opts); + return 0; + }, + (PluginAnonymizeOptions opts) => + { + toolInfo = new(typeof(ToolPluginAnonymize), opts); + return 0; + }, + (BackupSelectionOptions opts) => + { + toolInfo = new(typeof(ToolBackupSelection), opts); + return 0; + }, + errs => 1 + ); - if (!switchIsValid || toolType == null) + if (result == 1 || toolInfo == null) { - Environment.Exit(CommandLine.Parser.DefaultExitCodeFail); + Environment.Exit(1); } var kernel = new StandardKernel( @@ -60,7 +95,7 @@ public static int Main(string[] args) //If db init, allow @ variables in the query itself. We could always enable this but for added security //we are conditionally adding it only for db migrations - if (toolType == typeof(ToolInitDatabase)) + if (toolInfo.Value.ToolType == typeof(ToolInitDatabase)) { var config = kernel.Get(); if (!config.Database.ConnectionString.EndsWith(";")){ @@ -69,7 +104,7 @@ public static int Main(string[] args) config.Database.ConnectionString += "Allow User Variables=True"; } - var tool = (ITool)kernel.Get(toolType, new ConstructorArgument("options", toolOptions)); + var tool = (ITool)kernel.Get(toolInfo.Value.ToolType, new ConstructorArgument("options", toolInfo.Value.ToolOptions)); return tool.Run(); } diff --git a/TSOClient/FSO.Server/ProgramOptions.cs b/TSOClient/FSO.Server/ProgramOptions.cs index 65addd231..f0cbf073b 100644 --- a/TSOClient/FSO.Server/ProgramOptions.cs +++ b/TSOClient/FSO.Server/ProgramOptions.cs @@ -5,74 +5,115 @@ namespace FSO.Server { public class ProgramOptions { - [VerbOption("run", HelpText = "Run the servers configured in config.json")] public RunServerOptions RunServerVerb { get; set; } - [VerbOption("db-init", HelpText = "Initialize the database.")] public DatabaseInitOptions DatabaseMaintenanceVerb { get; set; } - [VerbOption("import-nhood", - HelpText = "Import the neighborhood stored in the given JSON file to the specified shard.")] public ImportNhoodOptions ImportNhoodVerb { get; set; } - [VerbOption("restore-lots", - HelpText = "Create lots in the database from FSOV saves in the specified folder. (with specified shard)")] public RestoreLotsOptions RestoreLotsVerb { get; set; } - [HelpVerbOption] - public string GetUsage(string verb) - { - return HelpText.AutoBuild(this, verb); - } + public SqliteImportOptions SqliteImportVerb { get; set; } + + public DataTrimOptions DataTrimVerb { get; set; } + + public DataTrimOptions ArchiveConvertVerb { get; set; } + + public ImportArchiveFeaturedOptions ImportArchiveFeaturedVerb { get; set; } } + [Verb("db-init", HelpText = "Initialize the database.")] public class DatabaseInitOptions { } - + [Verb("sqlite-import", HelpText = "Imports a MariaDB export from a given directory into an sqlite database.")] + public class SqliteImportOptions + { + [Value(0)] + public string ImportDir { get; set; } + } + + [Verb("run", HelpText = "Run the servers configured in config.json")] public class RunServerOptions { - [Option('d', "debug", DefaultValue = false, HelpText = "Launches a network debug interface")] + [Option('d', "debug", Default = false, HelpText = "Launches a network debug interface")] public bool Debug { get; set; } } + [Verb("data-trim", HelpText = "Remove unimportant data, and optionally sensitive information from the database and NFS.")] + public class DataTrimOptions + { + [Option('a', "anon", Default = false, HelpText = "Strips any private information from the database and NFS. Does leave users intact - convert to archive to remove them.")] + public bool Anon { get; set; } + } + + [Verb("archive-convert", HelpText = "Convert the database for use as an archive server.")] + public class ArchiveConvertOptions + { + } + + [Verb("import-archive-featured", HelpText = "Import the featured lots in the given JSON file to the specified shard.")] + public class ImportArchiveFeaturedOptions + { + [Value(0)] + public int ShardId { get; set; } + [Value(1)] + public string JSON { get; set; } + } + + [Verb("import-nhood", HelpText = "Import the neighborhood stored in the given JSON file to the specified shard.")] public class ImportNhoodOptions { - [ValueOption(0)] + [Value(0)] public int ShardId { get; set; } - [ValueOption(1)] + [Value(1)] public string JSON { get; set; } } + [Verb("restore-lots", HelpText = "Create lots in the database from FSOV saves in the specified folder. (with specified shard)")] public class RestoreLotsOptions { - [ValueOption(0)] + [Value(0)] public int ShardId { get; set; } - [ValueOption(1)] + [Value(1)] public string RestoreFolder { get; set; } - [Option('l', "location", DefaultValue = 0u, HelpText = "Override location to place the property.")] + [Option('l', "location", Default = 0u, HelpText = "Override location to place the property.")] public uint Location { get; set; } - [Option('t', "owner", DefaultValue = 0u, HelpText = "Override avatar id to own the property.")] + [Option('t', "owner", Default = 0u, HelpText = "Override avatar id to own the property.")] public uint Owner { get; set; } - [Option('c', "category", DefaultValue = -1, HelpText = "Override property category.")] + [Option('c', "category", Default = -1, HelpText = "Override property category.")] public int Category { get; set; } - [Option('r', "report", DefaultValue = false, HelpText = "Report changes that would be made restoring the lot, " + + [Option('r', "report", Default = false, HelpText = "Report changes that would be made restoring the lot, " + "eg. add/remove/reown of objects, lot positon (and if we can restore it) ")] public bool Report { get; set; } - [Option('o', "objects", DefaultValue = false, HelpText = "Create new database entries for objects when they are still owned. " + + [Option('o', "objects", Default = false, HelpText = "Create new database entries for objects when they are still owned. " + "If 'safe' is enabled, then database entries will be created for objects on other lots, otherwise they will be created for all.")] public bool Objects { get; set; } - [Option('s', "safe", DefaultValue = false, HelpText = "Do not return objects that have been placed, only ones in inventories.")] + [Option('s', "safe", Default = false, HelpText = "Do not return objects that have been placed, only ones in inventories.")] public bool Safe { get; set; } - [Option('d', "donate", DefaultValue = false, HelpText = "Convert all objects to donated so they don't have to belong to roommates.")] + [Option('d', "donate", Default = false, HelpText = "Convert all objects to donated so they don't have to belong to roommates.")] public bool Donate { get; set; } } + + [Verb("plugin-anonymize", HelpText = "Tool for reviewing and stripping potentially sensitive plugin data")] + public class PluginAnonymizeOptions + { + [Value(0, Required = false)] + public string InputFile { get; set; } + } + + [Verb("backup-selection", HelpText = "Tool for selecting backups with the least number of evicted roommates for more complete historical lot data")] + public class BackupSelectionOptions + { + [Option('v', "validate", Default = false, HelpText = "Print information about backup selection without actually doing it.")] + public bool DryRun { get; set; } + } } diff --git a/TSOClient/FSO.Server/Properties/AssemblyInfo.cs b/TSOClient/FSO.Server/Properties/AssemblyInfo.cs deleted file mode 100644 index 6cb3ed3b1..000000000 --- a/TSOClient/FSO.Server/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Server")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Server")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8f125201-fdf0-4a13-886f-19662707d34d")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Server/ServerConfiguration.cs b/TSOClient/FSO.Server/ServerConfiguration.cs index 546ce3ccd..562af6cb0 100644 --- a/TSOClient/FSO.Server/ServerConfiguration.cs +++ b/TSOClient/FSO.Server/ServerConfiguration.cs @@ -1,46 +1,68 @@ -using FSO.Server.Database; +using FSO.Common; +using FSO.Server.Database; using FSO.Server.Discord; using FSO.Server.Servers.Api.JsonWebToken; using FSO.Server.Servers.City; using FSO.Server.Servers.Lot; using FSO.Server.Servers.Tasks; using FSO.Server.Servers.UserApi; +using Newtonsoft.Json; using Ninject.Activation; using Ninject.Modules; -using System; -using System.Collections.Generic; -using System.IO; namespace FSO.Server { public class ServerConfiguration { + [JsonProperty("name")] + public string Name; + [JsonProperty("gameLocation")] public string GameLocation; + [JsonProperty("simNFS")] public string SimNFS; + [JsonProperty("updateBranch")] public string UpdateBranch; + [JsonProperty("allOpenable")] + public bool AllOpenable; + + [JsonProperty("archive")] + public ArchiveConfiguration Archive; // If this is present, the server is running in archive mode + + [JsonProperty("database")] public DatabaseConfiguration Database; + [JsonProperty("services")] public ServerConfigurationservices Services; + [JsonProperty("discord")] public DiscordConfiguration Discord; /// /// Secret string used as a key for signing JWT tokens for the admin system /// + [JsonProperty("secret")] public string Secret; /// /// Update ID this server is running on. All shards that we host will report needing this version, and this is reported with our host information. /// Loaded from updateID.txt if present. /// + [JsonProperty("updateID")] public int? UpdateID; + + [JsonProperty("events")] + public EventConfig? Events; // If this is present, the server automatically schedules events on start. } public class ServerConfigurationservices { + [JsonProperty("userApi")] public ApiServerConfiguration UserApi; + [JsonProperty("tasks")] public TaskServerConfiguration Tasks; + [JsonProperty("cities")] public List Cities; + [JsonProperty("lots")] public List Lots; } @@ -48,8 +70,25 @@ public class ServerConfigurationservices public class ServerConfigurationModule : NinjectModule { + private ServerConfiguration ExplicitConfig; + + public ServerConfigurationModule() + { + + } + + public ServerConfigurationModule(ServerConfiguration config) + { + ExplicitConfig = config; + } + private ServerConfiguration GetConfiguration(IContext context) { + if (ExplicitConfig != null) + { + return ExplicitConfig; + } + //TODO: Allow config path to be overriden in a switch var configPath = "config.json"; if (!File.Exists(configPath)) diff --git a/TSOClient/FSO.Server/Servers/Api/ApiServer.cs b/TSOClient/FSO.Server/Servers/Api/ApiServer.cs deleted file mode 100644 index 77406f5cd..000000000 --- a/TSOClient/FSO.Server/Servers/Api/ApiServer.cs +++ /dev/null @@ -1,115 +0,0 @@ -using FSO.Server.Servers.Api.Controllers; -using Ninject; -using Ninject.Parameters; -using NLog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using Nancy.Hosting.Self; -using Nancy.Bootstrappers.Ninject; -using Nancy.Bootstrapper; -using Nancy; -using FSO.Server.Common; -using FSO.Server.Protocol.Gluon.Model; - -namespace FSO.Server.Servers.Api -{ - public class ApiServer : AbstractServer - { - private static Logger LOG = LogManager.GetCurrentClassLogger(); - - private ApiServerConfiguration Config; - private IKernel Kernel; - private NancyHost Nancy; - - //TODO: connect to shards to do these? right now this assumes the API server is on the same server as all shards. - //would mean we could move these out of this class too. - public event APIRequestShutdownDelegate OnRequestShutdown; - public event APIBroadcastMessageDelegate OnBroadcastMessage; - - public delegate void APIRequestShutdownDelegate(uint time, ShutdownType type); - public delegate void APIBroadcastMessageDelegate(string sender, string title, string message); - - public ApiServer(ApiServerConfiguration config, IKernel kernel) - { - this.Config = config; - this.Kernel = kernel; - - Kernel.Bind().ToConstant(this); - Kernel.Bind().ToConstant(config); - } - - public override void Start() - { - LOG.Info("Starting API server"); - - var configuration = new HostConfiguration(); - configuration.UrlReservations.CreateAutomatically = true; - var uris = new List(); - - foreach(var path in Config.Bindings) - { - uris.Add(new Uri(path)); - } - - Nancy = new NancyHost(new CustomNancyBootstrap(Kernel), configuration, uris.ToArray()); - Nancy.Start(); - } - - public override void Shutdown() - { - if(Nancy != null) - { - Nancy.Stop(); - } - } - - public void RequestShutdown(uint time, ShutdownType type) - { - OnRequestShutdown?.Invoke(time, type); - } - - public void BroadcastMessage(string sender, string title, string message) - { - OnBroadcastMessage?.Invoke(sender, title, message); - } - - public override void AttachDebugger(IServerDebugger debugger) - { - } - } - - - class CustomNancyBootstrap : NinjectNancyBootstrapper - { - private IKernel Kernel; - - public CustomNancyBootstrap(IKernel kernel) - { - this.Kernel = kernel; - } - - protected override void ApplicationStartup(IKernel container, IPipelines pipelines) - { - base.ApplicationStartup(container, pipelines); - - pipelines.AfterRequest.AddItemToEndOfPipeline(x => - x.Response.WithHeader("Access-Control-Allow-Origin", "*") - .WithHeader("Access-Control-Allow-Methods", "DELETE, GET, HEAD, POST, PUT, OPTIONS, PATCH") - .WithHeader("Access-Control-Allow-Headers", "Content-Type, Authorization") - .WithHeader("Access-Control-Expose-Headers", "X-Total-Count") - ); - } - - protected override IKernel GetApplicationContainer() - { - return this.Kernel; - } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/ApiServerConfiguration.cs b/TSOClient/FSO.Server/Servers/Api/ApiServerConfiguration.cs deleted file mode 100644 index 35a5de444..000000000 --- a/TSOClient/FSO.Server/Servers/Api/ApiServerConfiguration.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api -{ - public class ApiServerConfiguration - { - /// - /// If true, the API server will attempt to bind - /// - public bool Enabled { get; set; } - - /// - /// Hostname bindings - /// - public List Bindings { get; set; } - - /// - /// Indicates which routes to register on the api - /// - public List Controllers { get; set; } - - /// - /// How long an auth ticket is valid for - /// - public int AuthTicketDuration = 300; - - /// - /// If non-null, the user must provide this key to register an account. - /// - public string Regkey { get; set; } - - /// - /// If true, only authentication from moderators and admins will be accepted - /// - public bool Maintainance { get; set; } - public string UpdateUrl { get; set; } - public string CDNUrl { get; set; } - - public string SmtpHost { get; set; } - public int SmtpPort { get; set; } - public string SmtpPassword { get; set; } - public string SmtpUser { get; set; } - public bool ForceEmailConfirmation { get; set; } - public bool UseProxy { get; set; } = true; - } - - public enum ApiServerControllers - { - Auth, - CitySelector - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminHostsController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminHostsController.cs deleted file mode 100644 index a74681187..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminHostsController.cs +++ /dev/null @@ -1,36 +0,0 @@ -using FSO.Server.Database.DA; -using FSO.Server.Domain; -using FSO.Server.Servers.Api.JsonWebToken; -using FSO.Server.Utils; -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.Admin -{ - public class AdminHostsController : NancyModule - { - public AdminHostsController(IDAFactory daFactory, JWTFactory jwt, IGluonHostPool hostPool) : base("/admin") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.Get["/hosts"] = _ => - { - this.DemandAdmin(); - var hosts = hostPool.GetAll(); - - return Response.AsJson(hosts.Select(x => new { - role = x.Role, - call_sign = x.CallSign, - internal_host = x.InternalHost, - public_host = x.PublicHost, - connected = x.Connected, - time_boot = x.BootTime - })); - }; - } - } -} \ No newline at end of file diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminOAuthController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminOAuthController.cs deleted file mode 100644 index 713028d46..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminOAuthController.cs +++ /dev/null @@ -1,99 +0,0 @@ -using FSO.Server.Common; -using FSO.Server.Database.DA; -using FSO.Server.Servers.Api.JsonWebToken; -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.Admin -{ - public class AdminOAuthController : NancyModule - { - public AdminOAuthController(IDAFactory daFactory, JWTFactory jwt) : base("/admin/oauth") - { - this.Post["/token"] = _ => - { - var grant_type = this.Request.Form["grant_type"]; - - if (grant_type == "password") - { - var username = this.Request.Form["username"]; - var password = this.Request.Form["password"]; - - using (var da = daFactory.Get()) - { - var user = da.Users.GetByUsername(username); - if (user == null || user.is_banned || !(user.is_admin || user.is_moderator)) - { - return Response.AsJson(new OAuthError - { - error = "unauthorized_client", - error_description = "user_credentials_invalid" - }); - } - - var authSettings = da.Users.GetAuthenticationSettings(user.user_id); - var isPasswordCorrect = PasswordHasher.Verify(password, new PasswordHash - { - data = authSettings.data, - scheme = authSettings.scheme_class - }); - - if (!isPasswordCorrect) - { - return Response.AsJson(new OAuthError - { - error = "unauthorized_client", - error_description = "user_credentials_invalid" - }); - } - - JWTUserIdentity identity = new JWTUserIdentity(); - identity.UserName = user.username; - var claims = new List(); - if (user.is_admin || user.is_moderator) - { - claims.Add("moderator"); - } - if (user.is_admin) - { - claims.Add("admin"); - } - - identity.Claims = claims; - identity.UserID = user.user_id; - - var token = jwt.CreateToken(identity); - return Response.AsJson(new OAuthSuccess - { - access_token = token.Token, - expires_in = token.ExpiresIn - }); - } - } - - return Response.AsJson(new OAuthError - { - error = "invalid_request", - error_description = "unknown grant_type" - }); - }; - } - } - - - public class OAuthError - { - public string error_description { get; set; } - public string error { get; set; } - } - - public class OAuthSuccess - { - public string access_token { get; set; } - public int expires_in { get; set; } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardOpController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardOpController.cs deleted file mode 100644 index 724a62042..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardOpController.cs +++ /dev/null @@ -1,74 +0,0 @@ -using FSO.Server.Database.DA; -using FSO.Server.Protocol.Gluon.Model; -using FSO.Server.Servers.Api.JsonWebToken; -using Nancy; -using Nancy.ModelBinding; -using Nancy.Security; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.Admin -{ - public class AdminShardOpController : NancyModule - { - private IDAFactory DAFactory; - private ApiServer Server; - - public AdminShardOpController(IDAFactory daFactory, JWTFactory jwt, ApiServer server) : base("/admin/shards") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.DAFactory = daFactory; - this.Server = server; - - this.After.AddItemToEndOfPipeline(x => - { - x.Response.WithHeader("Access-Control-Allow-Origin", "*"); - }); - - this.Post["/shutdown"] = _ => - { - this.DemandAdmin(); - var shutdown = this.Bind(); - - ShutdownType type = ShutdownType.SHUTDOWN; - if (shutdown.update) type = ShutdownType.UPDATE; - else if (shutdown.restart) type = ShutdownType.RESTART; - - //JWTUserIdentity user = (JWTUserIdentity)this.Context.CurrentUser; - Server.RequestShutdown((uint)shutdown.timeout, type); - - return Response.AsJson(true); - }; - - this.Post["/announce"] = _ => - { - this.DemandModerator(); - var announce = this.Bind(); - - Server.BroadcastMessage(announce.sender, announce.subject, announce.message); - - return Response.AsJson(true); - }; - } - } - - public class AnnouncementModel - { - public string sender; - public string subject; - public string message; - public int[] shard_ids; - } - - public class ShutdownModel - { - public int timeout; - public bool restart; - public bool update; - public int[] shard_ids; - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardsController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardsController.cs deleted file mode 100644 index 8f5743911..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminShardsController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FSO.Server.Database.DA; -using FSO.Server.Servers.Api.JsonWebToken; -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.Admin -{ - public class AdminShardsController : NancyModule - { - public AdminShardsController(IDAFactory daFactory, JWTFactory jwt) : base("/admin") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.Get["/shards"] = _ => - { - this.DemandAdmin(); - - using (var db = daFactory.Get()) - { - var shards = db.Shards.All(); - return Response.AsJson(shards); - } - }; - } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminTasksController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminTasksController.cs deleted file mode 100644 index a02989e10..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminTasksController.cs +++ /dev/null @@ -1,77 +0,0 @@ -using FSO.Server.Database.DA; -using FSO.Server.Database.DA.Tasks; -using FSO.Server.Domain; -using FSO.Server.Protocol.Gluon.Packets; -using FSO.Server.Servers.Api.JsonWebToken; -using FSO.Server.Utils; -using Nancy; -using Nancy.ModelBinding; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.Admin -{ - public class AdminTasksController : NancyModule - { - public AdminTasksController(IDAFactory daFactory, JWTFactory jwt, IGluonHostPool hostPool) : base("/admin") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.Get["/tasks"] = _ => - { - this.DemandAdmin(); - - using (var da = daFactory.Get()) - { - var offset = this.Request.Query["offset"]; - var limit = this.Request.Query["limit"]; - - if (offset == null) { offset = 0; } - if (limit == null) { limit = 20; } - - if (limit > 100) - { - limit = 100; - } - - var result = da.Tasks.All((int)offset, (int)limit); - return Response.AsPagedList(result); - } - }; - - this.Post["/tasks/request"] = x => - { - var task = this.Bind(); - - var taskServer = hostPool.GetByRole(Database.DA.Hosts.DbHostRole.task).FirstOrDefault(); - if(taskServer == null) - { - return Response.AsJson(-1); - }else{ - try { - var id = taskServer.Call(new RequestTask() { - TaskType = task.task_type.ToString(), - ParameterJson = JsonConvert.SerializeObject(task.parameter), - ShardId = (task.shard_id == null || !task.shard_id.HasValue) ? -1 : task.shard_id.Value - }).Result; - return Response.AsJson(id); - }catch(Exception ex) - { - return Response.AsJson(-1); - } - } - }; - } - } - - public class TaskRequest - { - public DbTaskType task_type; - public int? shard_id; - public dynamic parameter; - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminUsersController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminUsersController.cs deleted file mode 100644 index b64d7ac4e..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/Admin/AdminUsersController.cs +++ /dev/null @@ -1,130 +0,0 @@ -using FSO.Server.Database.DA; -using Nancy; -using Nancy.Authentication.Stateless; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using Nancy.Authentication.Token; -using Nancy.Security; -using FSO.Server.Common; -using FSO.Server.Servers.Api.JsonWebToken; -using FSO.Server.Database.DA.Users; -using FSO.Server.Database.DA.Utils; -using Nancy.ModelBinding; - -namespace FSO.Server.Servers.Api.Controllers -{ - /// - /// Provides administration APIs for server setup - /// - public class AdminUsersController : NancyModule - { - private IDAFactory DAFactory; - - public AdminUsersController(IDAFactory daFactory, JWTFactory jwt) : base("/admin") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.DAFactory = daFactory; - - this.After.AddItemToEndOfPipeline(x => - { - x.Response.WithHeader("Access-Control-Allow-Origin", "*"); - }); - - //Get information about me, useful for the admin user interface to disable UI based on who you login as - this.Get["/users/current"] = _ => - { - this.RequiresAuthentication(); - JWTUserIdentity user = (JWTUserIdentity)this.Context.CurrentUser; - - using (var da = daFactory.Get()) - { - var userModel = da.Users.GetById(user.UserID); - if (userModel == null) { throw new Exception("Unable to find user"); } - return Response.AsJson(userModel); - } - }; - - //Get the attributes of a specific user - this.Get["/users/{id}"] = parameters => - { - this.DemandModerator(); - - using (var da = daFactory.Get()) - { - var userModel = da.Users.GetById((uint)parameters.id); - if (userModel == null) { throw new Exception("Unable to find user"); } - return Response.AsJson(userModel); - } - }; - - //List users - this.Get["/users"] = _ => - { - this.DemandModerator(); - using (var da = daFactory.Get()) - { - var offset = this.Request.Query["offset"]; - var limit = this.Request.Query["limit"]; - - if(offset == null) { offset = 0; } - if(limit == null) { limit = 20; } - - if(limit > 100){ - limit = 100; - } - - var result = da.Users.All((int)offset, (int)limit); - return Response.AsPagedList(result); - } - }; - - //Create a new user - this.Post["/users"] = x => - { - this.DemandModerator(); - var user = this.Bind(); - - if (user.is_admin){ - //I need admin claim to do this - this.DemandAdmin(); - } - - using (var da = daFactory.Get()) - { - var userModel = new User(); - userModel.username = user.username; - userModel.email = user.email; - userModel.is_admin = user.is_admin; - userModel.is_moderator = user.is_moderator; - userModel.user_state = UserState.valid; - userModel.register_date = Epoch.Now; - userModel.is_banned = false; - - var userId = da.Users.Create(userModel); - - userModel = da.Users.GetById(userId); - if (userModel == null) { throw new Exception("Unable to find user"); } - return Response.AsJson(userModel); - } - - return null; - }; - } - } - - class UserCreateModel - { - public string username; - public string email; - public string password; - public bool is_admin; - public bool is_moderator; - } - - -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/AuthController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/AuthController.cs deleted file mode 100644 index 3f5569c88..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/AuthController.cs +++ /dev/null @@ -1,113 +0,0 @@ -using FSO.Server.Common; -using FSO.Server.Database.DA; -using FSO.Server.Database.DA.AuthTickets; -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers -{ - public class AuthController : NancyModule - { - private const String ERROR_020_CODE = "INV-020"; - private const String ERROR_020_MSG = "Please enter your member name and password."; - - private const String ERROR_110_CODE = "INV-110"; - private const String ERROR_110_MSG = "The member name or password you have entered is incorrect. Please try again."; - - private const String ERROR_302_CODE = "INV-302"; - private const String ERROR_302_MSG = "The game has experienced an internal error. Please try again."; - - private const String ERROR_160_CODE = "INV-160"; - private const String ERROR_160_MSG = "The server is currently down for maintainance. Please try again later."; - - private IDAFactory DAFactory; - private ApiServerConfiguration Config; - - public AuthController(IDAFactory daFactory, ApiServerConfiguration config) - { - this.DAFactory = daFactory; - Config = config; - this.Get["/AuthLogin"] = _ => - { - var username = this.Request.Query["username"]; - var password = this.Request.Query["password"]; - var version = this.Request.Query["version"]; - var clientid = (string)this.Request.Query["clientid"]; - - if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) - { - return Response.AsText(printError(ERROR_020_CODE, ERROR_020_MSG)); - } - - AuthTicket ticket = null; - - using (var db = DAFactory.Get()) - { - var user = db.Users.GetByUsername(username); - if (user == null || user.is_banned) - { - return Response.AsText(printError(ERROR_110_CODE, ERROR_110_MSG)); - } - - if (config.Maintainance && !(user.is_admin || user.is_moderator)) - { - return Response.AsText(printError(ERROR_160_CODE, ERROR_160_MSG)); - } - - var authSettings = db.Users.GetAuthenticationSettings(user.user_id); - var isPasswordCorrect = PasswordHasher.Verify(password, new PasswordHash - { - data = authSettings.data, - scheme = authSettings.scheme_class - }); - - if (!isPasswordCorrect) - { - return Response.AsText(printError(ERROR_110_CODE, ERROR_110_MSG)); - } - - var tryIP = Request.Headers["X-Forwarded-For"].FirstOrDefault(); - if (tryIP != null) tryIP = tryIP.Substring(tryIP.LastIndexOf(',') + 1).Trim(); - var ip = tryIP ?? this.Request.UserHostAddress; - - var ban = db.Bans.GetByIP(ip); - if (ban != null) - { - return Response.AsText(printError(ERROR_110_CODE, ERROR_110_MSG)); - } - - db.Users.UpdateClientID(user.user_id, clientid ?? "0"); - - /** Make a ticket **/ - ticket = new AuthTicket(); - ticket.ticket_id = Guid.NewGuid().ToString().Replace("-", ""); - ticket.user_id = user.user_id; - ticket.date = Epoch.Now; - ticket.ip = ip; - - db.AuthTickets.Create(ticket); - } - - return Response.AsText("Valid=TRUE\r\nTicket=" + ticket.ticket_id.ToString() + "\r\n"); - }; - } - - - public static string printError(String code, String message) - { - StringBuilder result = new StringBuilder(); - result.AppendLine("Valid=FALSE"); - result.AppendLine("Ticket=0"); - result.AppendLine("reasontext=" + code + ";" + message); - result.AppendLine("reasonurl="); - - return result.ToString(); - } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/CitySelectorController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/CitySelectorController.cs deleted file mode 100644 index d51d4fb8d..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/CitySelectorController.cs +++ /dev/null @@ -1,283 +0,0 @@ -using FSO.Common.Utils; -using FSO.Server.Common; -using FSO.Server.Database.DA; -using FSO.Server.Protocol.CitySelector; -using FSO.Server.Servers.Api.JsonWebToken; -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Nancy.Security; -using FSO.Server.Database.DA.Shards; -using FSO.Common.Domain.Shards; -using NLog; -using System.IO; - -namespace FSO.Server.Servers.Api.Controllers -{ - public class CitySelectorController : NancyModule - { - private static String ERROR_MISSING_TOKEN_CODE = "501"; - private static String ERROR_MISSING_TOKEN_MSG = "Token not found"; - - private static String ERROR_EXPIRED_TOKEN_CODE = "502"; - private static String ERROR_EXPIRED_TOKEN_MSG = "Token has expired"; - - private static String ERROR_SHARD_NOT_FOUND_CODE = "503"; - private static String ERROR_SHARD_NOT_FOUND_MSG = "Shard not found"; - - private static String ERROR_AVATAR_NOT_FOUND_CODE = "504"; - private static String ERROR_AVATAR_NOT_FOUND_MSG = "Avatar not found"; - - private static String ERROR_AVATAR_NOT_YOURS_CODE = "505"; - private static String ERROR_AVATAR_NOT_YOURS_MSG = "You do not own this avatar!"; - - private static String ERROR_BANNED_CODE = "506"; - private static String ERROR_BANNED_MSG = "Your account has been banned."; - - private static Logger LOG = LogManager.GetCurrentClassLogger(); - - private string VersionNumber = "0"; - private string VersionName; - private string DownloadURL; - - public CitySelectorController(IDAFactory DAFactory, ApiServerConfiguration config, JWTFactory jwt, IShardsDomain shardsDomain) : base("/cityselector") - { - JsonWebToken.JWTTokenAuthentication.Enable(this, jwt); - - var str = GetServerVersion(); - var split = str.LastIndexOf('-'); - VersionName = str; - if (split != -1) - { - VersionName = str.Substring(0, split); - VersionNumber = str.Substring(split + 1); - } - - try - { - using (var file = File.Open("updateUrl.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - { - var reader = new StreamReader(file); - DownloadURL = reader.ReadLine(); - reader.Close(); - } - } catch (Exception) - { - DownloadURL = ""; // couldn't find info from the watchdog - } - - //Take the auth ticket, establish trust and then create a cookie (reusing JWT) - this.Get["/app/InitialConnectServlet"] = _ => - { - var ticketValue = this.Request.Query["ticket"]; - var version = this.Request.Query["version"]; - - if (ticketValue == null) - { - return Response.AsXml(new XMLErrorMessage(ERROR_MISSING_TOKEN_CODE, ERROR_MISSING_TOKEN_MSG)); - } - - using (var db = DAFactory.Get()) - { - var ticket = db.AuthTickets.Get((string)ticketValue); - if (ticket == null) - { - return Response.AsXml(new XMLErrorMessage(ERROR_MISSING_TOKEN_CODE, ERROR_MISSING_TOKEN_MSG)); - } - - - db.AuthTickets.Delete((string)ticketValue); - if (ticket.date + config.AuthTicketDuration < Epoch.Now) - { - return Response.AsXml(new XMLErrorMessage(ERROR_EXPIRED_TOKEN_CODE, ERROR_EXPIRED_TOKEN_MSG)); - } - - /** Is it a valid account? **/ - var user = db.Users.GetById(ticket.user_id); - if (user == null) - { - return Response.AsXml(new XMLErrorMessage(ERROR_MISSING_TOKEN_CODE, ERROR_MISSING_TOKEN_MSG)); - } - - //Use JWT to create and sign an auth cookies - var session = new JWTUserIdentity() - { - UserID = user.user_id, - UserName = user.username - }; - - var token = jwt.CreateToken(session); - return Response.AsXml(new UserAuthorized() - { - FSOBranch = VersionName, - FSOVersion = VersionNumber, - FSOUpdateUrl = DownloadURL - }) - .WithCookie("fso", token.Token); - } - }; - - //Return a list of the users avatars - this.Get["/app/AvatarDataServlet"] = _ => - { - this.RequiresAuthentication(); - var user = (JWTUserIdentity)this.Context.CurrentUser; - - var result = new XMLList("The-Sims-Online"); - - using (var db = DAFactory.Get()) - { - var avatars = db.Avatars.GetSummaryByUserId(user.UserID); - - foreach(var avatar in avatars){ - result.Add(new AvatarData { - ID = avatar.avatar_id, - Name = avatar.name, - ShardName = shardsDomain.GetById(avatar.shard_id).Name, - HeadOutfitID = avatar.head, - BodyOutfitID = avatar.body, - AppearanceType = (AvatarAppearanceType)Enum.Parse(typeof(AvatarAppearanceType), avatar.skin_tone.ToString()), - Description = avatar.description, - LotId = avatar.lot_id, - LotName = avatar.lot_name, - LotLocation = avatar.lot_location - }); - } - } - - return Response.AsXml(result); - }; - - this.Get["/app/ShardSelectorServlet"] = _ => - { - this.RequiresAuthentication(); - var user = (JWTUserIdentity)this.Context.CurrentUser; - - var shardName = this.Request.Query["shardName"]; - var avatarId = this.Request.Query["avatarId"]; - if(avatarId == null){ - //Using 0 to mean no avatar for CAS - avatarId = "0"; - } - - using (var db = DAFactory.Get()) - { - ShardStatusItem shard = shardsDomain.GetByName(shardName); - if (shard != null) - { - var tryIP = Request.Headers["X-Forwarded-For"].FirstOrDefault(); - if (tryIP != null) tryIP = tryIP.Substring(tryIP.LastIndexOf(',') + 1).Trim(); - var ip = tryIP ?? this.Request.UserHostAddress; - - uint avatarDBID = uint.Parse(avatarId); - - if (avatarDBID != 0) - { - var avatar = db.Avatars.Get(avatarDBID); - if (avatar == null) { - //can't join server with an avatar that doesn't exist - return Response.AsXml(new XMLErrorMessage(ERROR_AVATAR_NOT_FOUND_CODE, ERROR_AVATAR_NOT_FOUND_MSG)); - } - if (avatar.user_id != user.UserID || avatar.shard_id != shard.Id) - { - //make sure we own the avatar we're trying to connect with - LOG.Info("SECURITY: Invalid avatar login attempt from " + ip + ", user "+user.UserID); - return Response.AsXml(new XMLErrorMessage(ERROR_AVATAR_NOT_YOURS_CODE, ERROR_AVATAR_NOT_YOURS_MSG)); - } - } - - var ban = db.Bans.GetByIP(ip); - if (ban != null || db.Users.GetById(user.UserID)?.is_banned != false) - { - return Response.AsXml(new XMLErrorMessage(ERROR_BANNED_CODE, ERROR_BANNED_MSG)); - } - - /** Make an auth ticket **/ - var ticket = new ShardTicket - { - ticket_id = Guid.NewGuid().ToString().Replace("-", ""), - user_id = user.UserID, - avatar_id = avatarDBID, - date = Epoch.Now, - ip = ip - }; - - db.Users.UpdateConnectIP(ticket.user_id, ip); - db.Shards.CreateTicket(ticket); - - var result = new ShardSelectorServletResponse(); - result.PreAlpha = false; - - result.Address = shard.PublicHost; - result.PlayerID = user.UserID; - result.Ticket = ticket.ticket_id; - result.ConnectionID = ticket.ticket_id; - result.AvatarID = avatarId; - - return Response.AsXml(result); - } - else - { - return Response.AsXml(new XMLErrorMessage(ERROR_SHARD_NOT_FOUND_CODE, ERROR_SHARD_NOT_FOUND_MSG)); - } - } - }; - - //Get a list of shards (cities) - this.Get["/shard-status.jsp"] = _ => - { - var result = new XMLList("Shard-Status-List"); - var shards = shardsDomain.All; - - foreach(var shard in shards) - { - var status = Protocol.CitySelector.ShardStatus.Down; - /*switch (shard.Status) - { - case Database.DA.Shards.ShardStatus.Up: - status = Protocol.CitySelector.ShardStatus.Up; - break; - case Database.DA.Shards.ShardStatus.Full: - status = Protocol.CitySelector.ShardStatus.Full; - break; - case Database.DA.Shards.ShardStatus.Frontier: - status = Protocol.CitySelector.ShardStatus.Frontier; - break; - case Database.DA.Shards.ShardStatus.Down: - status = Protocol.CitySelector.ShardStatus.Down; - break; - case Database.DA.Shards.ShardStatus.Closed: - status = Protocol.CitySelector.ShardStatus.Closed; - break; - case Database.DA.Shards.ShardStatus.Busy: - status = Protocol.CitySelector.ShardStatus.Busy; - break; - }*/ - - result.Add(shard); - } - - return Response.AsXml(result); - }; - } - - private static string GetServerVersion() - { - if (File.Exists("version.txt")) - { - using (StreamReader Reader = new StreamReader(File.Open("version.txt", FileMode.Open, FileAccess.Read, FileShare.Read))) - { - return Reader.ReadLine(); - } - } - else - { - return "unknown-0"; - } - } - } - -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/StatusCodeHandler.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/StatusCodeHandler.cs deleted file mode 100644 index e656cc59c..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/StatusCodeHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Nancy; -using Nancy.ErrorHandling; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers -{ - public class StatusCodeHandler : IStatusCodeHandler - { - private readonly IRootPathProvider _rootPathProvider; - - public StatusCodeHandler(IRootPathProvider rootPathProvider) - { - _rootPathProvider = rootPathProvider; - } - - public bool HandlesStatusCode(HttpStatusCode statusCode, NancyContext context) - { - return statusCode == HttpStatusCode.NotFound; - } - - public void Handle(HttpStatusCode statusCode, NancyContext context) - { - context.Response.Contents = stream => - { - - }; - } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/CityInfoController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/CityInfoController.cs deleted file mode 100644 index aae1e6424..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/CityInfoController.cs +++ /dev/null @@ -1,82 +0,0 @@ -using FSO.Common.DataService.Framework; -using FSO.Server.Common; -using FSO.Server.Database.DA; -using Nancy; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.UserAPI -{ - public class CityInfoController : NancyModule - { - private IDAFactory DAFactory; - private IServerNFSProvider NFS; - private static object ModelLock = new object { }; - private static CityInfoModel LastModel = new CityInfoModel(); - private static uint LastModelUpdate; - - /* - * TODO: city data service access for desired shards. - * Need to maintain connections to shards and request from their data services... - * Either that or online status has to at least writeback to DB. - */ - - public CityInfoController(IDAFactory daFactory, IServerNFSProvider nfs) : base("/userapi/city") - { - this.DAFactory = daFactory; - this.NFS = nfs; - - this.After.AddItemToEndOfPipeline(x => - { - x.Response.WithHeader("Access-Control-Allow-Origin", "*"); - }); - - this.Get["/{shardid}/{id}.png"] = parameters => - { - using (var da = daFactory.Get()) - { - var lot = da.Lots.GetByLocation((int)parameters.shardid, (uint)parameters.id); - if (lot == null) return HttpStatusCode.NotFound; - return Response.AsImage(Path.Combine(NFS.GetBaseDirectory(), "Lots/" + lot.lot_id.ToString("x8") + "/thumb.png")); - } - }; - - this.Get["/{shardid}/city.json"] = parameters => - { - var now = Epoch.Now; - if (LastModelUpdate < now - 15) { - LastModelUpdate = now; - lock (ModelLock) - { - LastModel = new CityInfoModel(); - using (var da = daFactory.Get()) - { - var lots = da.Lots.AllLocations((int)parameters.shardid); - var lotstatus = da.LotClaims.AllLocations((int)parameters.shardid); - LastModel.reservedLots = lots.ConvertAll(x => x.location).ToArray(); - LastModel.names = lots.ConvertAll(x => x.name).ToArray(); - LastModel.activeLots = lotstatus.ConvertAll(x => x.location).ToArray(); - LastModel.onlineCount = lotstatus.ConvertAll(x => x.active).ToArray(); - } - } - } - lock (ModelLock) - { - return Response.AsJson(LastModel); - } - }; - } - } - - class CityInfoModel - { - public string[] names; - public uint[] reservedLots; - public uint[] activeLots; - public int[] onlineCount; - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/RegistrationController.cs b/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/RegistrationController.cs deleted file mode 100644 index e416ad559..000000000 --- a/TSOClient/FSO.Server/Servers/Api/Controllers/UserAPI/RegistrationController.cs +++ /dev/null @@ -1,156 +0,0 @@ -using FSO.Server.Common; -using FSO.Server.Database.DA; -using FSO.Server.Database.DA.Users; -using FSO.Server.Servers.Api.JsonWebToken; -using Nancy; -using Nancy.ModelBinding; -using Nancy.Security; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.Controllers.UserAPI -{ - public class RegistrationController : NancyModule - { - private IDAFactory DAFactory; - private const int REGISTER_THROTTLE_SECS = 60*60*24; - - /// - /// Alphanumeric (lowercase), no whitespace or special chars, cannot start with an underscore. - /// - private static Regex USERNAME_VALIDATION = new Regex("^([a-z0-9]){1}([a-z0-9_]){2,23}$"); - - public RegistrationController(IDAFactory daFactory, JWTFactory jwt, ApiServerConfiguration config) : base("/userapi/registration") - { - JWTTokenAuthentication.Enable(this, jwt); - - this.DAFactory = daFactory; - - this.After.AddItemToEndOfPipeline(x => - { - x.Response.WithHeader("Access-Control-Allow-Origin", "*"); - }); - - //Create a new user - this.Post["/"] = x => - { - var user = this.Bind(); - var tryIP = Request.Headers["X-Forwarded-For"].FirstOrDefault(); - if (tryIP != null) tryIP = tryIP.Substring(tryIP.LastIndexOf(',') + 1).Trim(); - var ip = tryIP ?? this.Request.UserHostAddress; - - user.username = user.username ?? ""; - user.username = user.username.ToLowerInvariant(); - user.email = user.email ?? ""; - user.key = user.key ?? ""; - string failReason = null; - if (user.username.Length < 3) failReason = "user_short"; - else if (user.username.Length > 24) failReason = "user_long"; - else if (!USERNAME_VALIDATION.IsMatch(user.username ?? "")) failReason = "user_invalid"; - else if ((user.password?.Length ?? 0) == 0) failReason = "pass_required"; - - if (failReason != null) - { - return Response.AsJson(new RegistrationError() - { - error = "bad_request", - error_description = failReason - }); - } - - bool isAdmin = false; - if (config.Regkey != null && config.Regkey != user.key) - { - return Response.AsJson(new RegistrationError() - { - error = "key_wrong", - error_description = failReason - }); - } - - var passhash = PasswordHasher.Hash(user.password); - - using (var da = daFactory.Get()) - { - //has this ip been banned? - var ban = da.Bans.GetByIP(ip); - if (ban != null) - { - return Response.AsJson(new RegistrationError() - { - error = "registration_failed", - error_description = "ip_banned" - }); - } - - //has this user registered a new account too soon after their last? - var now = Epoch.Now; - var prev = da.Users.GetByRegisterIP(ip); - if (now - (prev.FirstOrDefault()?.register_date ?? 0) < REGISTER_THROTTLE_SECS) - { - //cannot create a new account this soon. - return Response.AsJson(new RegistrationError() - { - error = "registration_failed", - error_description = "registrations_too_frequent" - }); - } - - //TODO: is this ip banned? - - var userModel = new User(); - userModel.username = user.username; - userModel.email = user.email; - userModel.is_admin = isAdmin; - userModel.is_moderator = isAdmin; - userModel.user_state = UserState.valid; - userModel.register_date = now; - userModel.is_banned = false; - userModel.register_ip = ip; - userModel.last_ip = ip; - - var authSettings = new UserAuthenticate(); - authSettings.scheme_class = passhash.scheme; - authSettings.data = passhash.data; - - try - { - var userId = da.Users.Create(userModel); - authSettings.user_id = userId; - da.Users.CreateAuth(authSettings); - - userModel = da.Users.GetById(userId); - if (userModel == null) { throw new Exception("Unable to find user"); } - return Response.AsJson(userModel); - } catch (Exception) - { - return Response.AsJson(new RegistrationError() - { - error = "registration_failed", - error_description = "user_exists" - }); - } - - } - }; - } - } - - class RegistrationError - { - public string error_description { get; set; } - public string error { get; set; } - } - - class RegistrationModel - { - public string username; - public string email; - public string password; - public string key; - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTTokenAuthentication.cs b/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTTokenAuthentication.cs deleted file mode 100644 index fed91f290..000000000 --- a/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTTokenAuthentication.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Nancy; -using Nancy.Authentication.Token; -using Nancy.Security; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.JsonWebToken -{ - public class JWTTokenAuthentication - { - private const string Scheme = "bearer"; - - public static void Enable(INancyModule module, JWTFactory factory) - { - if (module == null) - { - throw new ArgumentNullException("module"); - } - - module.Before.AddItemToStartOfPipeline(GetCredentialRetrievalHook(factory)); - } - - private static Func GetCredentialRetrievalHook(JWTFactory factory) - { - if (factory == null) - { - throw new ArgumentNullException("configuration"); - } - - return context => - { - RetrieveCredentials(context, factory); - return null; - }; - } - - private static void RetrieveCredentials(NancyContext context, JWTFactory factory) - { - var token = ExtractTokenFromHeader(context.Request); - if (token == null) - { - return; - } - - try { - var user = factory.DecodeToken(token); - if (user != null) { - var identity = new JWTUserIdentity() - { - UserID = user.UserID, - UserName = user.UserName, - Claims = user.Claims - }; - context.CurrentUser = identity; - } - }catch(Exception ex){ - //Expired - } - } - - private static string ExtractTokenFromHeader(Request request) - { - var authorization = request.Headers.Authorization; - - if (string.IsNullOrEmpty(authorization)) - { - //City selector puts it in a cookie - if (request.Cookies.ContainsKey("fso")) - { - return request.Cookies["fso"]; - } - return null; - } - - if (!authorization.StartsWith(Scheme)) - { - return null; - } - - try - { - var encodedToken = authorization.Substring(Scheme.Length).Trim(); - return String.IsNullOrWhiteSpace(encodedToken) ? null : encodedToken; - } - catch (FormatException) - { - return null; - } - } - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTUserIdentity.cs b/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTUserIdentity.cs deleted file mode 100644 index 0eba1ca3f..000000000 --- a/TSOClient/FSO.Server/Servers/Api/JsonWebToken/JWTUserIdentity.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Nancy.Security; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace FSO.Server.Servers.Api.JsonWebToken -{ - public class JWTUserIdentity : JWTUser, IUserIdentity - { - } -} diff --git a/TSOClient/FSO.Server/Servers/Api/NancyExtensions.cs b/TSOClient/FSO.Server/Servers/Api/NancyExtensions.cs deleted file mode 100644 index 72412234a..000000000 --- a/TSOClient/FSO.Server/Servers/Api/NancyExtensions.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Nancy; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Nancy.Security; -using FSO.Server.Database.DA.Utils; -using FSO.Common.Utils; -using System.Xml; -using FSO.Server.Servers.Api.JsonWebToken; - -namespace FSO.Server.Servers.Api -{ - public static class NancyExtensions - { - public static void DemandModerator(this NancyModule controller) - { - controller.RequiresAuthentication(); - var user = (JWTUserIdentity)controller.Context.CurrentUser; - user.Claims.Contains("moderator"); - } - - public static void DemandAdmin(this NancyModule controller) - { - controller.RequiresAuthentication(); - var user = (JWTUserIdentity)controller.Context.CurrentUser; - user.Claims.Contains("admin"); - } - - public static Response AsPagedList(this IResponseFormatter formatter, PagedList list) - { - return FormatterExtensions.AsJson>(formatter, list) - .WithHeader("X-Total-Count", list.Total.ToString()) - .WithHeader("X-Offset", list.Offset.ToString()); - } - - public static Response AsXml(this IResponseFormatter formatter, IXMLEntity entity) - { - var doc = new XmlDocument(); - var firstChild = entity.Serialize(doc); - doc.AppendChild(firstChild); - - return FormatterExtensions.AsText(formatter, doc.OuterXml).WithContentType("text/xml"); - } - } -} diff --git a/TSOClient/FSO.Server/Servers/City/CityServer.cs b/TSOClient/FSO.Server/Servers/City/CityServer.cs index 6a0b6ae09..0a3386d0e 100644 --- a/TSOClient/FSO.Server/Servers/City/CityServer.cs +++ b/TSOClient/FSO.Server/Servers/City/CityServer.cs @@ -1,38 +1,75 @@ -using FSO.Common.Domain.Shards; +using FSO.Common; +using FSO.Common.Domain.Shards; using FSO.Server.Common; using FSO.Server.Database.DA; +using FSO.Server.Database.DA.ArchiveUsers; using FSO.Server.Database.DA.AvatarClaims; +using FSO.Server.Database.DA.Bans; using FSO.Server.Database.DA.Hosts; using FSO.Server.Domain; using FSO.Server.Framework; using FSO.Server.Framework.Aries; using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Aries.Packets; +using FSO.Server.Protocol.Electron; +using FSO.Server.Protocol.Electron.Packets; using FSO.Server.Protocol.Voltron.Packets; using FSO.Server.Servers.City.Domain; using FSO.Server.Servers.City.Handlers; using FSO.Server.Servers.Shared.Handlers; +using FSO.Server.Utils; +using Mina.Core.Session; using Ninject; using NLog; -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; +using System.Net; +using System.Security.Cryptography; +using System.Text; namespace FSO.Server.Servers.City { public class CityServer : AbstractAriesServer { private static Logger LOG = LogManager.GetCurrentClassLogger(); + private ServerConfiguration RootConfig; private CityServerConfiguration Config; private ISessionGroup VoltronSessions; private CityLivenessEngine Liveness; public bool ShuttingDown; - public CityServer(CityServerConfiguration config, IKernel kernel) : base(config, kernel) + private string ShardName; + private string ShardMap; + private string VersionJson = FSOVersionInfo.Current.ToJson(); + + private uint SessionUID; + + protected override RequestClientSessionArchive ArchiveHandshake(IoSession session) + { + if (Config.Archive == null) return null; + + var nonce = Convert.ToBase64String(RandomNumberGenerator.GetBytes(16)); + + session.SetAttribute("ArchiveNonce", nonce); + + return new RequestClientSessionArchive() + { + Name = RootConfig.Name, + PlayerCount = CountPlayers(), // Maybe cache this? + VersionInfo = VersionJson, + + ServerKey = Config.Archive.ServerPublicKey.Replace('^','\n'), + Nonce = nonce, + ArchiveConfig = Config.Archive.Flags, + ShardId = (uint)Config.ID, + ShardName = ShardName, + ShardMap = ShardMap, + }; + } + + public CityServer(ServerConfiguration rootConfig, CityServerConfiguration config, IKernel kernel) : base(config, kernel) { this.UnexpectedDisconnectWaitSeconds = 30; this.TimeoutIfNoAuth = config.Timeout_No_Auth; + this.RootConfig = rootConfig; this.Config = config; VoltronSessions = Sessions.GetOrCreateGroup(Groups.VOLTRON); } @@ -54,11 +91,23 @@ protected override void Bootstrap() throw new Exception("Unable to find a shard with id " + Config.ID + ", check it exists in the database"); } + if ((Config.Archive?.Flags ?? 0).HasFlag(FSO.Common.ArchiveConfigFlags.CityEditor) && shard.Map != "dynamic") + { + LOG.Warn("The map for city " + shard.Name + " is not dynamic, but the city editor is enabled. Converting city to dynamic..."); + ((Shards)shards).MakeDynamic(Config.ID, CoreImageLoader.SavePNG); + shard = shards.GetById(Config.ID); + LOG.Warn("City " + shard.Name + " has been converted to dynamic."); + } + + ShardName = shard.Name; + ShardMap = shard.Map; LOG.Info("City identified as " + shard.Name); var context = new CityServerContext(); context.ShardId = shard.Id; context.Config = Config; + context.Sessions = Sessions; + context.BroadcastUserList = BroadcastUserList; Kernel.Bind().ToSelf().InSingletonScope(); Kernel.Bind().ToSelf().InSingletonScope(); Kernel.Bind().ToConstant(context); @@ -74,8 +123,9 @@ protected override void Bootstrap() IDAFactory da = Kernel.Get(); using (var db = da.Get()){ - var version = ServerVersion.Get(); - db.Shards.UpdateStatus(shard.Id, Config.Internal_Host, Config.Public_Host, version.Name, version.Number, version.UpdateID); + var version = FSOVersionInfo.Current; + + db.Shards.UpdateStatus(shard.Id, Config.Internal_Host, Config.Public_Host, version.channel, version.id, null); ((Shards)shards).Update(); var oldClaims = db.LotClaims.GetAllByOwner(context.Config.Call_Sign).ToList(); @@ -115,6 +165,191 @@ public async Task Shutdown(ShutdownType type) return task.Result; } + private bool ValidDisplayName(string name) + { + return name != null && name.Length > 0 && name.Length < 64; + } + + private RSA TryGetCrypto() + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(Config.Archive.ServerKey.Replace('^', '\n')); + + return rsa; + } + catch (Exception) + { + return null; + } + } + + private string TryDecrypt(string enc) + { + try + { + var rsa = TryGetCrypto(); + + if (rsa == null) + { + return null; + } + + var encrypted = Convert.FromBase64String(enc); + + var decrypted = rsa.Decrypt(encrypted, RSAEncryptionPadding.Pkcs1); + + return Encoding.UTF8.GetString(decrypted); + } + catch (Exception) + { + return null; + } + } + + private void HandleArchiveAuth(AriesSession session, RequestClientSessionResponse packet) + { + using (var da = DAFactory.Get()) + { + // Archive auth always starts as avatarless, but can be upgraded later + + // The "password" should be an encrypted nonce\clientID, base64 encoded. First step is trying to decrypt it. + // The note that client ID is different for each server. + + var password = TryDecrypt(packet.Password); + + if (password == null) + { + // Encryption failure. + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:90", Subject = "Encryption Failure", Message = "" }); + session.Close(); + return; + } + + var nonceSplit = password.IndexOf('\\'); + var nonce = nonceSplit == -1 ? null : password.Substring(0, nonceSplit); + var expectedNonce = (string)session.GetAttribute("ArchiveNonce"); + if (expectedNonce == null || nonce != expectedNonce) + { + // Nonce did not match (attempted replay?) + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:88", Subject = "Encryption Failure", Message = "" }); + session.Close(); + return; + } + + var clientId = password.Substring(nonceSplit + 1); + + // Try and find by the provided ID (should be 40 chars) + + if (clientId.Length != 40) + { + // Must be 40 character hash + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:86", Subject = "Invalid Client ID", Message = "" }); + session.Close(); + return; + } + + var user = da.ArchiveUsers.GetByClientHash(clientId); + var ip = (session.IoSession.RemoteEndPoint as IPEndPoint).Address.ToString(); + + // TODO: whitelist super admin ips? + bool superAdmin = ip == "127.0.0.1"; + + bool needsVerification = Config.Archive.Flags.HasFlag(FSO.Common.ArchiveConfigFlags.Verification) && !superAdmin; + + if (user == null) + { + // Try create a user for this hash + + var newUser = new ArchiveUser() + { + username = clientId, + user_state = Database.DA.Users.UserState.email_confirm, + email = "", + is_admin = superAdmin, + is_moderator = superAdmin, + is_banned = false, + client_id = "0", + register_ip = ip, + last_ip = ip, + shared_user = false, + is_verified = !needsVerification, + display_name = "", + }; + + var id = da.ArchiveUsers.Create(newUser); + + newUser.user_id = id; + + user = newUser; + } + else + { + da.Users.UpdateConnectIP(user.user_id, ip); + } + + var ipBan = da.Bans.GetByIP(ip); + + if (user.is_banned || (ipBan != null && !superAdmin)) + { + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:80", Subject = "Banned", Message = "" }); + session.Close(); + return; + } + + // Try and update the display name. + + if (!ClientArchiveConfiguration.ValidDisplayName(packet.User)) + { + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:82", Subject = "Invalid Name", Message = "" }); + session.Close(); + return; + } + + if (packet.User != user.display_name) + { + // Is it already taken? + var otherUser = da.ArchiveUsers.GetByDisplayName(packet.User); + + if (otherUser != null) + { + session.Write(new AnnouncementMsgPDU(true) { SenderID = "??cst:84", Subject = "Display Name Taken", Message = "" }); + session.Close(); + return; + } + + da.ArchiveUsers.UpdateDisplayName(user.user_id, packet.User); + + user.display_name = packet.User; + } + + if (superAdmin && (!user.is_admin || !user.is_moderator)) + { + da.Users.UpdatePermissions(user.user_id, true, true); + user.is_admin = true; + user.is_moderator = true; + } + + // We're authenticated by this point. Upgrade the session to voltron. + + var newSession = Sessions.UpgradeSession(session, x => { + x.UserId = user.user_id; + x.DisplayName = user.display_name; + x.ModerationLevel = superAdmin ? 3u : user.is_admin ? 2u : (user.is_moderator ? 1u : 0u); + x.SessionUID = SessionUID++; + x.AvatarId = 0; + session.IsAuthenticated = true; + x.Authenticate(packet.Password); + x.AvatarClaimId = 0; + x.Unverified = !user.is_verified; + }); + + BroadcastUserList(false); + } + } + protected override void HandleVoltronSessionResponse(IAriesSession session, object message) { var rawSession = (AriesSession)session; @@ -134,6 +369,13 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje return; } + if (Config.Archive != null) + { + // Server is in archive mode, authenticate differently + HandleArchiveAuth(rawSession, packet); + return; + } + using (var da = DAFactory.Get()) { var ticket = da.Shards.GetTicket(packet.Password); @@ -224,6 +466,98 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje rawSession.Close(); } + private int CountPlayers() + { + int players = 0; + + var clone = Sessions.Clone(); + foreach (var session in clone) + { + if (session is VoltronSession vSession) + { + if (vSession.UserId != 0) + { + if (!vSession.Unverified) + { + players++; + } + } + } + } + + return players; + } + + public void BroadcastUserList(bool adminOnly) + { + Task.Run(() => + { + var clients = new List(); + var pendingVerification = new List(); + + var clone = Sessions.Clone(); + foreach (var session in clone) + { + if (session is VoltronSession vSession) + { + if (vSession.UserId != 0) + { + if (vSession.Unverified) + { + pendingVerification.Add(new ArchivePendingVerification() + { + DisplayName = vSession.DisplayName, + UserId = vSession.UserId, + }); + } + else + { + clients.Add(new ArchiveClient() + { + DisplayName = vSession.DisplayName, + ModerationLevel = vSession.ModerationLevel, + AvatarId = vSession.AvatarId, + UserId = vSession.UserId, + SessionUID = vSession.SessionUID + }); + } + } + } + } + + var clientPacket = new ArchiveClientList() + { + Clients = clients.ToArray(), + Pending = new ArchivePendingVerification[0] + }; + + var adminPacket = new ArchiveClientList() + { + Clients = clientPacket.Clients, + Pending = pendingVerification.ToArray() + }; + + foreach (var session in clone) + { + if (session is VoltronSession vSession) + { + try + { + if (vSession.ModerationLevel > 0) + { + session.Write(adminPacket); + } + else if (!adminOnly) + { + session.Write(clientPacket); + } + } + catch (Exception) { } + } + } + }); + } + protected override DbHost CreateHost() { var host = base.CreateHost(); @@ -255,7 +589,13 @@ public override Type[] GetHandlers() typeof(MailHandler), typeof(MatchmakerNotifyHandler), typeof(NhoodHandler), - typeof(BulletinHandler) + typeof(BulletinHandler), + typeof(CityResourceHandler), + typeof(CityUpdateHandler), + + typeof(ArchiveAvatarsHandler), + typeof(ArchiveAvatarSelectHandler), + typeof(ArchiveModerationHandler) }; } } diff --git a/TSOClient/FSO.Server/Servers/City/CityServerConfiguration.cs b/TSOClient/FSO.Server/Servers/City/CityServerConfiguration.cs index 35f2f895c..1d4f07927 100644 --- a/TSOClient/FSO.Server/Servers/City/CityServerConfiguration.cs +++ b/TSOClient/FSO.Server/Servers/City/CityServerConfiguration.cs @@ -1,84 +1,112 @@ -using FSO.Server.Framework.Aries; +using FSO.Common; +using FSO.Server.Framework.Aries; +using Newtonsoft.Json; namespace FSO.Server.Servers.City { public class CityServerConfiguration : AbstractAriesServerConfig { + [JsonProperty("id")] public int ID; + [JsonProperty("timeout_no_auth")] public bool Timeout_No_Auth = true; + [JsonProperty("initial_funds")] + public int Initial_Funds = 0; + [JsonProperty("neighborhoods")] public CityServerNhoodConfiguration Neighborhoods = new CityServerNhoodConfiguration(); + [JsonProperty("maintenance")] public CityServerMaintenanceConfiguration Maintenance; + + // Copied from base config + public bool AllOpenable; + public ArchiveConfiguration Archive; + public string Name; } public class CityServerNhoodConfiguration { /** Minimum number of nominations required to run for mayor. */ + [JsonProperty("min_nominations")] public int Min_Nominations = 3; /** * if a neighbourhood with no elections is within this number from the top in activity (and not reserved), * we should start an election cycle anyways */ + [JsonProperty("mayor_elegibility_limit")] public int Mayor_Elegibility_Limit = 2; /** * if a neighbourhood that had elections is no longer within the falloff range in popularity, * elections are disabled. */ - public int Mayor_Elegilility_Falloff = 4; + [JsonProperty("mayor_elegibility_falloff")] + public int Mayor_Elegibility_Falloff = 4; /** * The number of days you must wait after moving before participating in an election. */ + [JsonProperty("election_move_penalty")] public int Election_Move_Penalty = 30; /** * The number of days you must wait after moving before rating a mayor. */ + [JsonProperty("rating_move_penalty")] public int Rating_Move_Penalty = 7; /** * The number of days you must wait after moving before posting on a bulletin board. */ + [JsonProperty("bulletin_move_penalty")] public int Bulletin_Move_Penalty = 7; /** * The number of days you must wait between bulletin posts. */ + [JsonProperty("bulletin_post_frequency")] public int Bulletin_Post_Frequency = 3; /** * The number of days the mayor must wait between bulletin posts. */ + [JsonProperty("bulletin_mayor_frequency")] public int Bulletin_Mayor_Frequency = 1; /** * If true, starts elections on the last monday in a month, rather than 7 days before the end of the month. */ + [JsonProperty("election_week_align")] public bool Election_Week_Align = true; /** * If true, sims in areas without an election are offered a free vote. */ + [JsonProperty("election_free_vote")] public bool Election_Free_Vote = true; /** * The value of a vote/nomination made by a resident. */ + [JsonProperty("vote_normal_value")] public int Vote_Normal_Value = 2; /** * The value of a vote/nomination made by a non-resident. */ + [JsonProperty("vote_free_value")] public int Vote_Free_Value = 1; } public class CityServerMaintenanceConfiguration { + [JsonProperty("cron")] public string Cron; + [JsonProperty("timeout")] public int Timeout = 3600; + [JsonProperty("visits_retention_period")] public int Visits_Retention_Period = 7; } } diff --git a/TSOClient/FSO.Server/Servers/City/CityServerContext.cs b/TSOClient/FSO.Server/Servers/City/CityServerContext.cs index 98e7e8d31..1fa3e7a25 100644 --- a/TSOClient/FSO.Server/Servers/City/CityServerContext.cs +++ b/TSOClient/FSO.Server/Servers/City/CityServerContext.cs @@ -1,8 +1,33 @@ -namespace FSO.Server.Servers.City +using FSO.Server.Framework.Aries; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron; + +namespace FSO.Server.Servers.City { public class CityServerContext { public int ShardId; public CityServerConfiguration Config; + public ISessions Sessions; + public Action BroadcastUserList; + + public void Broadcast(AbstractElectronPacket packet, Func filter = null) + { + Task.Run(() => + { + + var clone = Sessions.Clone(); + foreach (var session in clone) + { + if (session is VoltronSession vSession) + { + if (!vSession.IsAnonymous && (filter?.Invoke(vSession) != false)) + { + vSession.Write(packet); + } + } + } + }); + } } } diff --git a/TSOClient/FSO.Server/Servers/City/Domain/LotAllocations.cs b/TSOClient/FSO.Server/Servers/City/Domain/LotAllocations.cs index f62faa025..8418a45d5 100644 --- a/TSOClient/FSO.Server/Servers/City/Domain/LotAllocations.cs +++ b/TSOClient/FSO.Server/Servers/City/Domain/LotAllocations.cs @@ -1,7 +1,10 @@ -using FSO.Common.Enum; +using FSO.Common.Domain.Realestate; +using FSO.Common.Domain.RealestateDomain; +using FSO.Common.Enum; using FSO.Common.Security; using FSO.Server.Database.DA; using FSO.Server.Database.DA.Lots; +using FSO.Server.Domain; using FSO.Server.Framework.Gluon; using FSO.Server.Protocol.Electron.Model; using FSO.Server.Protocol.Gluon.Model; @@ -10,6 +13,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -22,6 +26,11 @@ public class LotAllocations private IDAFactory DAFactory; private CityServerContext Context; private JobMatchmaker Matchmaker; + private IShardRealestateDomain Realestate; + + private bool AllowGuestOpening => Context.Config.AllOpenable || ArchiveFreeRoam; + private bool ArchiveFreeRoam => Context.Config.Archive?.Flags.HasFlag(FSO.Common.ArchiveConfigFlags.AllOpenable) ?? false; + public int ActiveCount => _Locks.Count; public LotAllocations(LotServerPicker PickingEngine, IDAFactory daFactory, CityServerContext context, IKernel kernel) { @@ -29,16 +38,17 @@ public LotAllocations(LotServerPicker PickingEngine, IDAFactory daFactory, CityS this.DAFactory = daFactory; this.Context = context; this.Matchmaker = kernel.Get(); + this.Realestate = kernel.Get().GetByShard(Context.ShardId); } - public Task TryFindOrOpen(uint lotId, uint avatarId, ISecurityContext security) + public Task TryFindOrOpen(uint lotId, uint avatarId, ISecurityContext security, ClaimAction openAction = ClaimAction.DEFAULT) { - return TryFind(lotId, avatarId, true, security); + return TryFind(lotId, avatarId, true, security, openAction); } - public Task TryFind(uint lotId, uint avatarId, ISecurityContext security) + public Task TryFind(uint lotId, uint avatarId, ISecurityContext security, ClaimAction openAction = ClaimAction.DEFAULT) { - return TryFind(lotId, avatarId, false, security); + return TryFind(lotId, avatarId, false, security, openAction); } public void OnTransferClaimResponse(TransferClaimResponse response) @@ -54,7 +64,9 @@ public void OnTransferClaimResponse(TransferClaimResponse response) } } else { - location = (uint)response.EntityId; + // To the lot server, unowned lots are known by their location |'d with a flag. + // To the city server allocations, it's just known by the raw location. Remove the flag. + location = (uint)response.EntityId & (uint)(~LotIdFlags.Unowned); } if (location == null) return; @@ -96,8 +108,8 @@ public void TryClose(int lotId, uint claimId) } else { - //job lot. there is no claim, no db lot. Id is the location. - location = (uint)lotId; + //special lot. there is no claim, no db lot. Id is the location. + location = (uint)lotId & (uint)(~LotIdFlags.Unowned); } if (location == null) @@ -136,7 +148,7 @@ public void TryClose(int lotId, uint claimId) /// /// - private Task TryFind(uint lotId, uint avatarId, bool openIfClosed, ISecurityContext security) + private Task TryFind(uint lotId, uint avatarId, bool openIfClosed, ISecurityContext security, ClaimAction openAction) { bool jobLot = false; var originalId = lotId; @@ -145,7 +157,7 @@ private Task TryFind(uint lotId, uint avatarId, bool openIfClo //special: join available job lot instance var result = Matchmaker.TryGetJobLot(lotId, avatarId); lotId = result.Item1 ?? 0; - lotId |= 0x40000000; + lotId |= (uint)LotIdFlags.JobLot; originalId = result.Item2; jobLot = true; if (lotId == 0) return Immediate(new TryFindLotResult @@ -162,7 +174,7 @@ private Task TryFind(uint lotId, uint avatarId, bool openIfClo Status = FindLotResponseStatus.NO_ADMIT }); } - lotId |= 0x40000000; + lotId |= (uint)LotIdFlags.JobLot; jobLot = true; } @@ -184,57 +196,118 @@ private Task TryFind(uint lotId, uint avatarId, bool openIfClo if (!jobLot) { + var coords = MapCoordinates.Unpack(lotId); + DbLot lot = null; + bool isRoommate = false; + bool isAdmin = false; using (var db = DAFactory.Get()) { //Convert the lot location into a lot db id lot = db.Lots.GetByLocation(Context.ShardId, lotId); if (lot == null) { - Remove(lotId); - return Immediate(new TryFindLotResult + if (AllowGuestOpening) { - Status = FindLotResponseStatus.NO_SUCH_LOT - }); - } + // Empty lots can be opened, as long as the location is valid. - if (avatarId != 0) - { - var roomies = db.Roommates.GetLotRoommates(lot.lot_id); - var modState = db.Avatars.GetModerationLevel(avatarId); - var avatars = new List(); - foreach (var roomie in roomies) - { - if (roomie.is_pending == 0) avatars.Add(roomie.avatar_id); - } + if (!Realestate.IsOpenable(coords.X, coords.Y)) + { + return Immediate(new TryFindLotResult + { + Status = FindLotResponseStatus.NO_SUCH_LOT + }); + } - try - { - if (lot.admit_mode < 4 && modState == 0 && lot.category != FSO.Common.Enum.LotCategory.community) - security.DemandAvatars(avatars, AvatarPermissions.WRITE); + lot = new DbLot() + { + lot_id = 0, + location = lotId, + admit_mode = 4, + }; } - catch (Exception ex) + else { Remove(lotId); return Immediate(new TryFindLotResult { - Status = FindLotResponseStatus.NOT_PERMITTED_TO_OPEN + Status = FindLotResponseStatus.NO_SUCH_LOT }); } } + else + { + if (avatarId != 0 && !AllowGuestOpening) + { + var roomies = db.Roommates.GetLotRoommates(lot.lot_id); + var modState = db.Avatars.GetModerationLevel(avatarId); + var avatars = new List(); + foreach (var roomie in roomies) + { + if (roomie.is_pending == 0) avatars.Add(roomie.avatar_id); + } + + try + { + if (lot.admit_mode < 4 && modState == 0 && lot.category != FSO.Common.Enum.LotCategory.community) + security.DemandAvatars(avatars, AvatarPermissions.WRITE); + } + catch (Exception ex) + { + Remove(lotId); + return Immediate(new TryFindLotResult + { + Status = FindLotResponseStatus.NOT_PERMITTED_TO_OPEN + }); + } + } + + if (avatarId != 0 && AllowGuestOpening && !ArchiveFreeRoam) + { + isAdmin = db.Avatars.GetModerationLevel(avatarId) > 0; + + if (lot.lot_id != 0) + { + var roomies = db.Roommates.GetLotRoommates(lot.lot_id); + isRoommate = roomies.Any(r => r.is_pending == 0 && r.avatar_id == avatarId); + + // Spectators still respect ban rules + if (!isRoommate && !isAdmin + && ((lot.admit_mode == 1 && !db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 0).Contains(avatarId)) + || (lot.admit_mode == 2 && db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 1).Contains(avatarId)) + || lot.admit_mode == 3)) + { + Remove(lotId); + return Immediate(new TryFindLotResult + { + Status = FindLotResponseStatus.NOT_PERMITTED_TO_OPEN + }); + } + } + } + } } if (!allocation.TryClaim(lot)) { - Remove(lotId); return Immediate(new TryFindLotResult { Status = FindLotResponseStatus.CLAIM_FAILED }); } - allocation.SetLot(lot, 0, - (avatarId == 0) ? ClaimAction.LOT_CLEANUP : ClaimAction.LOT_HOST); + + if (openAction == ClaimAction.DEFAULT) + { + if (avatarId == 0) + openAction = ClaimAction.LOT_CLEANUP; + else if (AllowGuestOpening && !ArchiveFreeRoam && !isRoommate && !isAdmin) + openAction = ClaimAction.LOT_SPECTATOR; + else + openAction = ClaimAction.LOT_HOST; + } + + allocation.SetLot(lot, (uint)Context.ShardId, openAction); } else { allocation.SetLot(new DbLot() { lot_id = (int)lotId }, originalId, @@ -284,7 +357,7 @@ private Task TryFind(uint lotId, uint avatarId, bool openIfClo var lot = db.Lots.GetByLocation(Context.ShardId, lotId); if (lot != null) { - if (lot.admit_mode > 0 && lot.admit_mode < 4) + if (lot.admit_mode > 0 && lot.admit_mode < 4 && !AllowGuestOpening) { //special admit mode @@ -313,6 +386,24 @@ private Task TryFind(uint lotId, uint avatarId, bool openIfClo } } } + // Spectators (non-archive) still respect ban rules + else if (AllowGuestOpening && !ArchiveFreeRoam) + { + var roomies = db.Roommates.GetLotRoommates(lot.lot_id); + var isLotRoommate = roomies.Any(r => r.is_pending == 0 && r.avatar_id == avatarId); + var isLotAdmin = db.Avatars.GetModerationLevel(avatarId) > 0; + + if (!isLotRoommate && !isLotAdmin + && ((lot.admit_mode == 1 && !db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 0).Contains(avatarId)) + || (lot.admit_mode == 2 && db.LotAdmit.GetLotAdmitDeny(lot.lot_id, 1).Contains(avatarId)) + || lot.admit_mode == 3)) + { + return Immediate(new TryFindLotResult + { + Status = FindLotResponseStatus.NO_ADMIT + }); + } + } } } } @@ -347,6 +438,16 @@ private Task Immediate(T data) return tcs.Task; } + public LotAllocation TryGet(uint rawId) + { + if (_Locks.TryGetValue(rawId, out var value)) + { + return value; + } + + return null; + } + private LotAllocation Get(uint lotId) { return _Locks.GetOrAdd(lotId, x => { @@ -357,10 +458,37 @@ private LotAllocation Get(uint lotId) private LotAllocation Remove(uint lotId) { LotAllocation removed = null; - if ((lotId & 0x40000000) > 0) Matchmaker.RemoveJobLot(lotId & 0x3FFFFFFF); + if ((lotId & (uint)LotIdFlags.JobLot) > 0) Matchmaker.RemoveJobLot(lotId & (uint)LotIdFlags.NormalMask); _Locks.TryRemove(lotId, out removed); return removed; } + + public void AddLocationsTo(HashSet locations) + { + foreach (var pair in _Locks) + { + locations.Add(pair.Key); + } + } + + public void AddSurroundingLocationsTo(HashSet locations) + { + foreach (var pair in _Locks) + { + locations.Add(pair.Key); + locations.Add(pair.Key - 1); + locations.Add(pair.Key + 1); + + uint axis = 1u << 16; + locations.Add(pair.Key + axis); + locations.Add(pair.Key + axis - 1); + locations.Add(pair.Key + axis + 1); + + locations.Add(pair.Key - axis); + locations.Add((pair.Key - axis) - 1); + locations.Add((pair.Key - axis) + 1); + } + } } public class TryFindLotResult @@ -386,6 +514,8 @@ public class LotAllocation private uint SpecialId; private ClaimAction OpenAction; + public bool Unowned => Lot.lot_id == 0; + public LotAllocation(IDAFactory da, CityServerContext context) { Context = context; @@ -445,6 +575,11 @@ public bool TryClaim(DbLot lot) { Lot = lot; + if (Unowned) + { + return true; + } + //Write a db record to claim the lot using (var db = DAFactory.Get()) { @@ -530,7 +665,7 @@ public Task BeginPick(LotPickerAttempt attempt) Type = ClaimType.LOT, Action = OpenAction, //x,y used as id for lots - EntityId = Lot.lot_id, + EntityId = Unowned ? (int)(Lot.location | (uint)LotIdFlags.Unowned) : Lot.lot_id, SpecialId = SpecialId, ClaimId = ClaimId ?? 0, FromOwner = Context.Config.Call_Sign diff --git a/TSOClient/FSO.Server/Servers/City/Domain/Neighborhoods.cs b/TSOClient/FSO.Server/Servers/City/Domain/Neighborhoods.cs index fc1f10dab..f2745a017 100644 --- a/TSOClient/FSO.Server/Servers/City/Domain/Neighborhoods.cs +++ b/TSOClient/FSO.Server/Servers/City/Domain/Neighborhoods.cs @@ -361,7 +361,7 @@ public async Task TickNeighborhoods(DateTime now) else { //is our placement outwith bounds? - if (placement == -1 || placement >= config.Mayor_Elegilility_Falloff) + if (placement == -1 || placement >= config.Mayor_Elegibility_Falloff) { //make us ineligible. nhood.flag |= 2; diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarSelectHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarSelectHandler.cs new file mode 100644 index 000000000..38e960b9f --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarSelectHandler.cs @@ -0,0 +1,186 @@ +using FSO.Common; +using FSO.Common.DataService; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.AvatarClaims; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron.Packets; +using Ninject; +using NLog; +using System; +using System.Threading; + +namespace FSO.Server.Servers.City.Handlers +{ + internal static class ArchiveAvatarSelectExtensions + { + public static void Response(this IVoltronSession session, ArchiveAvatarSelectCode code) + { + session.Write(new ArchiveAvatarSelectResponse() + { + Code = code + }); + } + } + + internal class ArchiveAvatarSelectHandler + { + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DA; + private CityServerContext Context; + private IKernel Kernel; + + public ArchiveAvatarSelectHandler(CityServerContext context, IDAFactory da, IDataService dataService, IKernel kernel) + { + Context = context; + DA = da; + Kernel = kernel; + } + + public async void Handle(IVoltronSession session, ArchiveAvatarSelectRequest packet) + { + var config = Context.Config; + + if (config.Archive == null) + return; + + if (session.UserId == 0 || session.AvatarId != 0) + return; + + + if (session is VoltronSession vSession && vSession.Unverified) + { + return; + } + + uint avatarId = packet.AvatarId; + uint userId = session.UserId; + + try + { + using (var da = DA.Get()) + { + var ava = da.Avatars.Get(avatarId); + + if (ava == null) + { + session.Response(ArchiveAvatarSelectCode.NotFound); + return; + } + + // permissions check - currently supports shared and owned avatars but pretty fixed + bool canUseArchive = !Context.Config.Archive.Flags.HasFlag(ArchiveConfigFlags.LockArchivedSims) || session.HasModerationLevel(1); + + if (ava.user_id != session.UserId && (!canUseArchive || ava.user_id != 1)) + { + session.Response(ArchiveAvatarSelectCode.NoPermission); + return; + } + + // Try to claim the avatar for this session. + + int? claim = da.AvatarClaims.TryCreate(new DbAvatarClaim + { + avatar_id = avatarId, + location = 0, + owner = config.Call_Sign + }); + + if (!claim.HasValue) + { + //Try and disconnect this user, if we still can't get a claim out of luck + //The voltron session close should handle removing any lot tickets and disconnecting them from the target servers + //then it will remove the avatar claim. This takes time but it should be less than 5 seconds. + var existingSession = Context.Sessions.GetByAvatarId(avatarId); + if (existingSession != null) + { + // If the session is owned by another user, we can't close it. + // TODO: allow if the user is admin? + + if (existingSession.UserId != userId) + { + session.Response(ArchiveAvatarSelectCode.InUse); + return; + } + + existingSession.Close(); + } + else + { + //check if there really is an old claim + var oldClaim = da.AvatarClaims.GetByAvatarID(avatarId); + if (oldClaim != null) + { + da.AvatarClaims.Delete(oldClaim.avatar_claim_id, config.Call_Sign); + LOG.Debug("Zombie Avatar claim removed: Avatar ID " + avatarId); + } + else + { + LOG.Debug("Unknown claim error occurred. Connection will likely time out. Avatar ID " + avatarId); + } + } + + // Wait for the claim to disappear. + + int i = 0; + while (i < 10) + { + claim = da.AvatarClaims.TryCreate(new DbAvatarClaim + { + avatar_id = avatarId, + location = 0, + owner = config.Call_Sign + }); + + if (claim.HasValue) + { + break; + } + + Thread.Sleep(500); + i++; + } + + if (!claim.HasValue) + { + //No luck + session.Response(ArchiveAvatarSelectCode.InUseSelf); + session.Close(); + return; + } + } + + if (session is VoltronSession vSession2) + { + da.Avatars.UpdateModerationLevel(avatarId, (int)vSession2.ModerationLevel); + if (userId != ava.user_id) + { + da.ArchiveRecents.RecordAvatarUse((int)userId, (int)avatarId); + } + + vSession2.AvatarId = avatarId; + vSession2.AvatarClaimId = claim.Value; + + var lifecycle = Kernel.Get(); + + await lifecycle.AssignAvatar(vSession2, vSession2.ModerationLevel != ava.moderation_level); + } + else + { + throw new InvalidOperationException(); + } + + Context.BroadcastUserList(false); + + session.Response(ArchiveAvatarSelectCode.Success); + return; + } + } + catch + { + + } + + session.Response(ArchiveAvatarSelectCode.UnknownError); + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarsHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarsHandler.cs new file mode 100644 index 000000000..607c1a3f0 --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveAvatarsHandler.cs @@ -0,0 +1,134 @@ +using FSO.Common; +using FSO.Common.DataService; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Avatars; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.CitySelector; +using FSO.Server.Protocol.Electron.Packets; +using Ninject; +using NLog; + +namespace FSO.Server.Servers.City.Handlers +{ + internal class ArchiveAvatarsHandler + { + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DA; + private CityServerContext Context; + private IKernel Kernel; + + private Lock SharedAvatarsCacheLock = new(); + private Task SharedAvatarsCache; + + public ArchiveAvatarsHandler(CityServerContext context, IDAFactory da, IDataService dataService, IKernel kernel) + { + Context = context; + DA = da; + Kernel = kernel; + } + + private ArchiveAvatar[] GetSharedAvatars(IDA da) + { + Task task; + + lock (SharedAvatarsCacheLock) + { + if (SharedAvatarsCache == null) + { + SharedAvatarsCache = Task.Run(() => + { + var shared = da.Avatars.GetSummaryByUserId(1); + return shared.Select(ToArchiveAvatar).ToArray(); + }); + } + + task = SharedAvatarsCache; + } + + return task.Result; + } + + private static ArchiveAvatar ToArchiveAvatar(DbAvatarSummary ava) + { + return new ArchiveAvatar() + { + AvatarId = ava.avatar_id, + UserId = ava.user_id, + LotId = ava.lot_location ?? 0, + Name = ava.name, + LotName = ava.lot_name, + Type = (AvatarAppearanceType)ava.skin_tone, + Head = ava.head, + Body = ava.body + }; + } + + public async void Handle(IVoltronSession session, ArchiveAvatarsRequest _packet) + { + if (Context.Config.Archive == null) + return; + + if (session.UserId == 0) + return; + + try + { + if (session is VoltronSession vSession && vSession.Unverified) + { + // User must be verified first. + session.Write(new ArchiveAvatarsResponse() + { + IsVerified = false, + CasEnabled = false, + RecentAvatars = [], + UserAvatars = [], + SharedAvatars = [], + }); + + return; + } + + using (var da = DA.Get()) + { + var forUser = da.Avatars.GetSummaryByUserId(session.UserId); + + var userAvatars = forUser.Select(ToArchiveAvatar).ToArray(); + + // TODO: cache? + + var archiveFlags = Context.Config.Archive.Flags; + + bool canUseArchive = !archiveFlags.HasFlag(ArchiveConfigFlags.LockArchivedSims) || session.HasModerationLevel(1); + + ArchiveAvatar[] sharedAvatars; + + if (canUseArchive) + { + sharedAvatars = GetSharedAvatars(da); + } + else + { + sharedAvatars = []; + } + + // Can't cache this obviously + var mostRecent = da.ArchiveRecents.AvatarsByUser((int)session.UserId, 5); + var recentAvatars = mostRecent.Where(x => userAvatars.Any(y => y.AvatarId == x) || sharedAvatars.Any(y => y.AvatarId == x)).Select(x => (uint)x).ToArray(); + + session.Write(new ArchiveAvatarsResponse() + { + IsVerified = true, + CasEnabled = archiveFlags.HasFlag(ArchiveConfigFlags.AllowSimCreation) || session.HasModerationLevel(1), + UserAvatars = userAvatars, + SharedAvatars = sharedAvatars, + RecentAvatars = recentAvatars + }); + } + } + catch + { + + } + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveModerationHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveModerationHandler.cs new file mode 100644 index 000000000..ed64407b6 --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Handlers/ArchiveModerationHandler.cs @@ -0,0 +1,170 @@ +using FSO.Common.DataService; +using FSO.Common.DataService.Model; +using FSO.Server.Database.DA; +using FSO.Server.Domain; +using FSO.Server.Framework.Aries; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron.Model; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Gluon.Packets; +using FSO.Server.Servers.City.Domain; + +namespace FSO.Server.Servers.City.Handlers +{ + internal class ArchiveModerationHandler + { + private ISessions Sessions; + private IDAFactory DAFactory; + private CityServerContext Context; + private LotServerPicker LotServers; + private readonly LotAllocations Allocations; + private IDataService DataService; + + public ArchiveModerationHandler(IDAFactory da, ISessions sessions, CityServerContext context, LotServerPicker lotServers, LotAllocations allocations, IDataService dataService) + { + this.DAFactory = da; + this.Context = context; + this.Sessions = sessions; + this.LotServers = lotServers; + this.Allocations = allocations; + this.DataService = dataService; + } + + public void Handle(IVoltronSession session, ArchiveModerationRequest packet) + { + if (session.IsAnonymous) return; + using (var da = DAFactory.Get()) + { + int myLevel = (int)((session as VoltronSession)?.ModerationLevel ?? 0); + + if (myLevel == 0) return; + + // All requests are against users for now + var target = da.Users.GetById(packet.EntityId); + + if (target == null) return; + + int userLevel = target.is_admin ? 2 : (target.is_moderator ? 1 : 0); + + if (userLevel >= myLevel) + { + // Can't perform actions on people with a higher mod level... + return; + } + + // Try and find the user sessions - this can be useful for updating user state in real time. + var sessions = Sessions.GetAllByUserId(target.user_id); + + switch (packet.Type) + { + case ArchiveModerationRequestType.BAN_USER: + case ArchiveModerationRequestType.KICK_USER: + if (packet.Type == ArchiveModerationRequestType.BAN_USER) + { + da.Users.UpdateBanned(target.user_id, true); + if (target.last_ip != "127.0.0.1" && target.last_ip != "::1") + { + da.Bans.Add(target.last_ip, target.user_id, "Banned from ingame", 0, target.client_id); + } + } + + foreach (var targSession in sessions) + { + targSession?.Close(); + } + break; + case ArchiveModerationRequestType.CHANGE_MOD_LEVEL: + int level = packet.Value; + da.Users.UpdatePermissions(target.user_id, level >= 1, level >= 2); + + foreach (var targSession in sessions) + { + if (targSession is VoltronSession vSession) + { + vSession.ModerationLevel = (uint)level; + } + } + + // try to notify the lot(s) if possible + // slightly overcomplicated... + + foreach (var targSession in sessions) + { + var avatarId = targSession.AvatarId; + + if (avatarId != 0) + { + // Update this sim's moderation level. + da.Avatars.UpdateModerationLevel(avatarId, level); + DataService.Invalidate(avatarId); + + // Try find the lot that the avatar is on. + var claim = da.AvatarClaims.GetByAvatarID(avatarId); + + if (claim != null && claim.location != 0) + { + var lot = da.Lots.GetByLocation(Context.ShardId, claim.location); + + var lotServer = Allocations.TryGet(claim.location & (uint)LotIdFlags.NormalMask)?.Server; + if (lotServer != null) + { + //immediately notify lot of new roommate + lotServer.Write(new NotifyLotRoommateChange() + { + AvatarId = avatarId, + LotId = lot?.lot_id ?? (int)claim.location, + Change = Protocol.Gluon.Model.ChangeType.RELOAD_PERMISSIONS + }); + } + } + } + + } + + Context.BroadcastUserList(false); + + break; + case ArchiveModerationRequestType.APPROVE_USER: + case ArchiveModerationRequestType.REJECT_USER: + // If the user is already approved, we can't really do anything. + bool approval = packet.Type == ArchiveModerationRequestType.APPROVE_USER; + + if (approval) + { + // Allow session to continue, record in database + + da.Users.UpdateVerified(target.user_id, true); + + foreach (var targSession in sessions) + { + if (targSession is VoltronSession vSession) + { + vSession.Unverified = false; + vSession.Write(new VerificationNotification() + { + IsVerified = true + }); + } + } + + Context.BroadcastUserList(false); + } + else + { + // Close session... + foreach (var targSession in sessions) + { + targSession.Write(new VerificationNotification() + { + IsVerified = false + }); + + targSession?.Close(); + } + } + break; + } + } + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/AvatarRetireHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/AvatarRetireHandler.cs index 0b7e2358f..a9ad9ebb7 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/AvatarRetireHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/AvatarRetireHandler.cs @@ -39,7 +39,7 @@ public async void Handle(IVoltronSession session, AvatarRetireRequest packet) if (avatar.date > Epoch.Now - (60 * 60 * 24 * 7) && !da.Users.GetById(session.UserId).is_admin) { - session.Write(new Protocol.Voltron.Packets.AnnouncementMsgPDU() + session.Write(new Protocol.Voltron.Packets.AnnouncementMsgPDU(true) { SenderID = "??" + "System", Message = "\r\n" + "You cannot delete a sim younger than a week old!", diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/CityResourceHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/CityResourceHandler.cs new file mode 100644 index 000000000..d3509a32a --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Handlers/CityResourceHandler.cs @@ -0,0 +1,238 @@ +using FSO.Common.DataService; +using FSO.Common.Domain.Shards; +using FSO.Content.Model; +using FSO.Server.Database.DA; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron.Packets; +using Ninject; +using NLog; +using System.Collections.Concurrent; +using System.Runtime.Caching; +using System.Text; + +namespace FSO.Server.Servers.City.Handlers +{ + public class ShardLocationCache + { + public ConcurrentDictionary Dict = new ConcurrentDictionary(); + public DateTime CreateTime = DateTime.UtcNow; + + public ShardLocationCache(ConcurrentDictionary dict) + { + Dict = dict; + } + } + + public class CityResourceHandler + { + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DA; + private IDataService DataService; + private CityServerContext Context; + private ServerConfiguration Config; + private IShardsDomain Shards; + private IKernel Kernel; + private MemoryCache MemoryCacher = new("city_resource"); + + public CityResourceHandler(CityServerContext context, IDAFactory da, IDataService dataService, IKernel kernel, ServerConfiguration config, IShardsDomain shards) + { + Context = context; + DA = da; + DataService = dataService; + Kernel = kernel; + Config = config; + Shards = shards; + } + + public static ConcurrentDictionary LotLocationCache = new ConcurrentDictionary(); + + public int? IDForLocation(int shardid, uint loc) + { + var locToID = LotLocationCache.GetOrAdd(shardid, (ikey) => + { + using (var da = DA.Get()) + { + return new ShardLocationCache( + new ConcurrentDictionary(da.Lots.All(ikey).Select(x => new KeyValuePair(x.location, x.lot_id))) + ); + } + }); + if (DateTime.UtcNow - locToID.CreateTime > TimeSpan.FromMinutes(15)) + { + ShardLocationCache removed; + LotLocationCache.TryRemove(shardid, out removed); + } + + try + { + return locToID.Dict.GetOrAdd(loc, (ikey) => + { + using (var da = DA.Get()) + { + return da.Lots.GetByLocation(shardid, ikey).lot_id; + } + }); + } + catch (NullReferenceException e) + { + return null; + } + } + + public byte[] GetLotThumbnail(int shardid, uint id) + { + var dat = (byte[])MemoryCacher.Get("lt" + shardid + ":" + id); + if (dat != null) + { + return dat; + } + + var lot = IDForLocation(shardid, id); + if (lot == null) return new byte[0]; + + try + { + var path = Path.Combine(Config.SimNFS, "Lots/" + lot.Value.ToString("x8") + "/thumb.png"); + if (!File.Exists(path)) + { + return new byte[0]; + } + + var ndat = File.ReadAllBytes(path); + MemoryCacher.Add("lt" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0))); + + return ndat; + } + catch (Exception e) + { + return new byte[0]; + } + } + + public byte[] GetLotFacade(int shardid, uint id) + { + var dat = (byte[])MemoryCacher.Get("lf" + shardid + ":" + id); + if (dat != null) + { + return dat; + } + + var lot = IDForLocation(shardid, id); + if (lot == null) return new byte[0]; + + try + { + string path = Path.Combine(Config.SimNFS, "Lots/" + lot.Value.ToString("x8") + "/thumb.fsof"); + if (!File.Exists(path)) + { + return new byte[0]; + } + + var ndat = File.ReadAllBytes(path); + MemoryCacher.Add("lf" + shardid + ":" + id, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0))); + + return ndat; + } + catch (Exception e) + { + return new byte[0]; + } + } + + private byte[] GetAvatarDescription(int shardId, uint avatarId) + { + string data = ""; + + using (var da = DA.Get()) + { + var ava = da.Avatars.Get(avatarId); + + if (ava != null) + { + data = ava.description; + } + } + + return Encoding.UTF8.GetBytes(data); + } + + public byte[] GetCityThumbnail(int shardid) + { + var dat = (byte[])MemoryCacher.Get("ct" + shardid); + if (dat != null) + { + return dat; + } + + try + { + string path; + // Try and send the default thumbnail for this shard's map + + var map = Shards.GetMapForId(shardid); + if (map != null && map.Thumbnail is FileTextureRef file) + { + path = file.FilePath; + + if (!File.Exists(path)) + { + return new byte[0]; + } + } + else + { + return new byte[0]; + } + + var ndat = File.ReadAllBytes(path); + MemoryCacher.Add("ct" + shardid, ndat, DateTime.Now.Add(new TimeSpan(1, 0, 0))); + + return ndat; + } + catch (Exception e) + { + return new byte[0]; + } + } + + public void Handle(IVoltronSession session, CityResourceRequest packet) + { + byte[] data = null; + int shard = Context.ShardId; + + Task.Run(() => + { + try + { + switch (packet.Type) + { + case CityResourceRequestType.LOT_THUMBNAIL: + data = GetLotThumbnail(shard, packet.ResourceID); + break; + case CityResourceRequestType.LOT_FACADE: + data = GetLotFacade(shard, packet.ResourceID); + break; + case CityResourceRequestType.AVATAR_DESCRIPTION: + data = GetAvatarDescription(shard, packet.ResourceID); + break; + case CityResourceRequestType.CITY_THUMBNAIL: + data = GetCityThumbnail(shard); + break; + } + + session.Write(new CityResourceResponse() + { + Type = packet.Type, + RequestID = packet.RequestID, + ResourceID = packet.ResourceID, + Data = data ?? new byte[0] + }); + } + catch (Exception) + { + + } + }); + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/CityUpdateHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/CityUpdateHandler.cs new file mode 100644 index 000000000..77b4a22d5 --- /dev/null +++ b/TSOClient/FSO.Server/Servers/City/Handlers/CityUpdateHandler.cs @@ -0,0 +1,477 @@ +using FSO.Common; +using FSO.Common.Domain; +using FSO.Common.Domain.Realestate; +using FSO.Common.Domain.RealestateDomain; +using FSO.Common.Security; +using FSO.Content.Model; +using FSO.Server.Database.DA; +using FSO.Server.DataService.Providers; +using FSO.Server.Framework.Voltron; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Gluon.Model; +using FSO.Server.Servers.City.Domain; +using FSO.Server.Utils; +using Microsoft.Xna.Framework; +using Ninject; +using NLog; +using System.Collections.Concurrent; + +namespace FSO.Server.Servers.City.Handlers +{ + internal class CityUpdateHandler : IDisposable + { + private struct CityUpdate(int shardId, Color[] roads, Color[] elevation, Color[] forestDensity, Color[] forestType, Color[] terrainType) + { + public readonly int ShardID = shardId; + public readonly Color[] Roads = roads; + public readonly Color[] Elevation = elevation; + public readonly Color[] ForestDensity = forestDensity; + public readonly Color[] ForestType = forestType; + public readonly Color[] TerrainType = terrainType; + } + + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private readonly CityServerContext Context; + private readonly IRealestateDomain Realestate; + private readonly IDAFactory DAFactory; + private readonly IServerNFSProvider NFS; + private readonly ServerLotProvider LotProvider; + private readonly LotAllocations ActiveLots; + + private bool Running; + + private readonly ConcurrentQueue SerialActions = []; + private readonly AutoResetEvent ActionReady; + private readonly Thread SerialThread; + + private readonly HashSet ModifiedShards = []; + private readonly Dictionary UpdateByShard = []; + private readonly AutoResetEvent UpdateReady; + private readonly Thread UpdateThread; + + // These variables are only used by the serial thread + private readonly HashSet ReservedTiles = []; + private int ReservedTilesVersion = -1; + private readonly HashSet ToUpdateWorking = []; + private readonly HashSet BlockedTilesWorking = []; + + public CityUpdateHandler(CityServerContext context, IRealestateDomain realestate, IDAFactory daFactory, IServerNFSProvider nfs, IKernel kernel) + { + Context = context; + Realestate = realestate; + DAFactory = daFactory; + NFS = nfs; + + LotProvider = kernel.Get(); + ActiveLots = kernel.Get(); + + Running = true; + + ActionReady = new AutoResetEvent(false); + + // All city actions (and handling initial distribution of the city data + delta list) happen in sequence. + // This ensures that the sequence is completely synchronized for all clients. + SerialThread = new Thread(ThreadLoop); + SerialThread.Start(); + + UpdateReady = new AutoResetEvent(false); + + // This thread saves the city data to PNG. + UpdateThread = new Thread(UpdateLoop); + UpdateThread.Start(); + } + + private void TrySaveLot() + { + foreach (var shard in ModifiedShards) + { + var shardId = shard.ID; + var map = shard.GetMap(); + + lock (UpdateByShard) + { + if (UpdateByShard.ContainsKey(shardId)) + { + continue; + } + } + + var dirty = map.ConsumeDirty(); + + if (dirty != CityMapAspects.None) + { + var update = new CityUpdate( + shardId, + dirty.HasFlag(CityMapAspects.Road) ? [.. map.RoadData.Select(x => new Color(x, x, x, (byte)255))] : null, + dirty.HasFlag(CityMapAspects.Elevation) ? [.. map.ElevationData.Select(x => new Color(x, x, x, (byte)255))] : null, + dirty.HasFlag(CityMapAspects.Forest) ? [.. map.ForestDensityData.Select(x => new Color(x, x, x, (byte)255))] : null, + dirty.HasFlag(CityMapAspects.Forest) ? [.. map.ForestTypeColorData] : null, + dirty.HasFlag(CityMapAspects.TerrainType) ? [.. map.TerrainTypeColorData] : null); + + lock (UpdateByShard) + { + UpdateByShard.Add(shardId, update); + } + UpdateReady.Set(); + } + } + } + + private void ThreadLoop() + { + while (Running) + { + while (SerialActions.TryDequeue(out var action)) + { + action(); + } + + TrySaveLot(); + + ActionReady.WaitOne(); + } + } + + private void SaveCityPNGs(in CityUpdate update) + { + var baseDir = NFS.GetShardMapDirectory(update.ShardID); + + SaveTex(baseDir, "roadmap", update.Roads); + + SaveTex(baseDir, "elevation", update.Elevation); + + SaveTex(baseDir, "forestdensity", update.ForestDensity); + SaveTex(baseDir, "foresttype", update.ForestType); + + SaveTex(baseDir, "terraintype", update.TerrainType); + + lock (UpdateByShard) + { + UpdateByShard.Remove(update.ShardID); + } + } + + private void UpdateLoop() + { + List updates = []; + while (Running) + { + lock (UpdateThread) + { + updates.AddRange(UpdateByShard.Values); + } + + if (updates.Count > 0) + { + foreach (var update in updates) + { + SaveCityPNGs(in update); + } + + updates.Clear(); + + // Process the action thread again in case there are some dirty aspects that need saving again. + ActionReady.Set(); + } + + UpdateReady.WaitOne(); + } + } + + private void QueueAction(Action action) + { + SerialActions.Enqueue(action); + + ActionReady.Set(); + } + + private IShardRealestateDomain GetShard(IVoltronSession session, bool forEditor = true) + { + if (session.IsAnonymous) + return null; + + if (forEditor) + { + var flags = Context.Config.Archive?.Flags; + var threshold = + (flags?.HasFlag(ArchiveConfigFlags.CityEditorAllUsers) ?? false) ? 0u : + ((flags?.HasFlag(ArchiveConfigFlags.CityEditorMods) ?? false) ? 1u : 2u); + + if (threshold > 0 && !session.HasModerationLevel((int)threshold)) + return null; + + if (!(flags?.HasFlag(FSO.Common.ArchiveConfigFlags.CityEditor) ?? false)) + return null; + } + + var shard = Realestate.GetByShard(Context.ShardId); + + return shard.Dynamic ? shard : null; + } + + public async void Handle(IVoltronSession session, CityUpdateCommand packet) + { + if (session.IsAnonymous) + { + return; + } + + if (packet.Mode == CityUpdateCommandMode.HollowLotRefresh && session.HasModerationLevel(3)) + { + // This can be called even when the city editor is disabled. + _ = Task.Run(() => HollowLotRefresh(packet.TargetUID)); + return; + } + + var shard = GetShard(session); + + if (shard == null || Running == false) + return; + + QueueAction(() => + { + switch (packet.Mode) + { + case CityUpdateCommandMode.SetCityName: + // TODO: validate + + using (var da = DAFactory.Get()) + { + da.Shards.UpdateInfo(Context.ShardId, packet.CityName, "dynamic"); + } + + // Make sure everyone knows about the change. + Context.Broadcast(packet); + break; + case CityUpdateCommandMode.SetThumbnail: + if (CoreImageLoader.ValidatePNG(packet.Thumbnail, 180, 135)) + { + var dir = NFS.GetShardMapDirectory(Context.ShardId); + var imgpath = Path.Combine(dir, "thumbnail.png"); + + Directory.CreateDirectory(dir); + + using (FileStream fs = File.Open(imgpath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + fs.Write(packet.Thumbnail, 0, packet.Thumbnail.Length); + } + } + break; + case CityUpdateCommandMode.Undo: + if (session.AvatarId != packet.AvatarID) + return; + + var reservedTiles = ReservedTiles; + var toUpdate = ToUpdateWorking; + toUpdate.Clear(); + var blockedTiles = BlockedTilesWorking; + blockedTiles.Clear(); + + LotProvider.UpdateReservedCache(reservedTiles, ref ReservedTilesVersion); + + ActiveLots.AddSurroundingLocationsTo(blockedTiles); + reservedTiles.UnionWith(blockedTiles); + + if (shard.HandleUserCommand(packet, reservedTiles, toUpdate, blockedTiles)) + { + Context.Broadcast(packet); + ModifiedShards.Add(shard); + SetMoveFlags(toUpdate); + } + else + { + session.Write(new CityUpdateCommand() { Mode = CityUpdateCommandMode.UndoError }); + } + + break; + } + }); + } + + public async void Handle(IVoltronSession session, CityInitRequest packet) + { + var shard = GetShard(session, false); + + if (shard == null) + return; + + var attr = session.GetAttribute("hasInitCity"); + + if (!(attr is string strAttr && strAttr == "true")) + { + session.SetAttribute("hasInitCity", "true"); + + QueueAction(() => + { + var init = shard.GetInit(); + + session.Write(init); + }); + } + } + + private void SetMoveFlags(HashSet locations) + { + if (locations.Count > 0) + { + using var da = DAFactory.Get(); + da.Lots.SetTerrainDirty(locations); + } + } + + public async void Handle(IVoltronSession session, CityUpdateRequest packet) + { + var shard = GetShard(session); + + if (shard == null) + return; + + var cmd = packet.Command.Command; + cmd.AvatarId = session.AvatarId; + + if (cmd.IsTemp) + { + // Temp commands aren't reflected in the city - they are forwarded to everyone else though. + + return; + } + + // Some tiles are always reserved, even if the client doesn't want to be. + + QueueAction(() => + { + var reservedTiles = ReservedTiles; + var toUpdate = ToUpdateWorking; + toUpdate.Clear(); + + LotProvider.UpdateReservedCache(reservedTiles, ref ReservedTilesVersion); + + ActiveLots.AddSurroundingLocationsTo(cmd.ReservedLocations); + + if (cmd is CityEditPaint paint && paint.Type == CityEditPaintType.TerrainType && paint.Value == (byte)TerrainType.WATER) + { + // When drawing water, the reserved locations need to include all lots. + cmd.ReservedLocations.UnionWith(reservedTiles); + } + + int id = shard.AppendCommand(cmd, reservedTiles, toUpdate); + + if (id != -1) + { + Context.Broadcast(new CityUpdateResponse() + { + StartIndex = id, + Commands = [new(cmd)] + }); + + ModifiedShards.Add(shard); + + SetMoveFlags(toUpdate); + } + else + { + session.Write(new CityUpdateCommand() { Mode = CityUpdateCommandMode.CommandError }); + } + }); + } + + private static void SaveTex(string baseDir, string filename, Color[] data) + { + // Save as a temp file, then rename over the existing one. + // This avoids the target file ever being half written. + + if (data == null) + { + return; + } + + string tempPath = Path.Combine(baseDir, $"{filename}-temp.png"); + string filePath = Path.Combine(baseDir, $"{filename}.png"); + + Directory.CreateDirectory(baseDir); + + using (FileStream fs = File.Open(tempPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + CoreImageLoader.SavePNG(data, 512, 512, fs); + } + + File.Move(tempPath, filePath, true); + } + + private bool HollowRefreshActive; + + private async Task HollowLotRefresh(int mode) + { + if (Interlocked.Exchange(ref HollowRefreshActive, true) == true) + { + return; + } + + bool completeMoves = mode == 1; + ClaimAction action = completeMoves ? ClaimAction.LOT_CLEANUP : ClaimAction.LOT_CLEANUP_HOLLOW; + + using var da = DAFactory.Get(); + + var lots = new HashSet(); + LotProvider.AddLocationsTo(lots); + + int total = lots.Count; + + LOG.Info($"Starting hollow lot refresh for {lots.Count} lots..."); + + int i = 0; + int refreshCount = 0; + foreach (var lotId in lots) + { + if (completeMoves) + { + // Only complete moves for properties that have non-zero move flags. + var lot = da.Lots.GetByLocation(Context.ShardId, lotId); + + if (lot == null || lot.MoveFlags == Database.DA.Lots.LotMoveFlags.None) + { + LOG.Info($"Skipped lot {lotId} ({i}/{total})..."); + i++; + continue; + } + } + + try + { + LOG.Info($"Queuing hollow refresh for {lotId} ({i}/{total})..."); + refreshCount++; + await ActiveLots.TryFindOrOpen(lotId, 0, NullSecurityContext.INSTANCE, action); + } + catch (Exception e) + { + LOG.Info($"Error: Failed to start hollow lot: {e.Message}"); + } + + i++; + + while (ActiveLots.ActiveCount > 20) + { + await Task.Delay(20); + } + } + + LOG.Info($"Finished hollow refresh (completed {refreshCount})! Still might need to wait for lots to close."); + + Interlocked.Exchange(ref HollowRefreshActive, false); + } + + public void Dispose() + { + Running = false; + ActionReady.Set(); + UpdateReady.Set(); + + SerialThread.Join(); + UpdateThread.Join(); + + ActionReady.Dispose(); + UpdateReady.Dispose(); + } + } +} diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/JoinLotHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/JoinLotHandler.cs index 716dbc266..a9f63cab1 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/JoinLotHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/JoinLotHandler.cs @@ -1,6 +1,7 @@ using FSO.Server.Common; using FSO.Server.Database.DA; using FSO.Server.Database.DA.Lots; +using FSO.Server.Domain; using FSO.Server.Framework.Gluon; using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Electron.Packets; @@ -87,7 +88,7 @@ public async void Handle(IVoltronSession session, FindLotRequest packet) lot_owner = find.Server.CallSign, date = Epoch.Now, ip = session.IpAddress, - lot_id = find.LotDbId, + lot_id = find.LotDbId == 0 ? (int)find.LotId | (int)LotIdFlags.Unowned : find.LotDbId, avatar_claim_id = session.AvatarClaimId, avatar_claim_owner = Context.Config.Call_Sign }; diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/LotServerClosedownHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/LotServerClosedownHandler.cs index d7fd64d82..dea27188b 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/LotServerClosedownHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/LotServerClosedownHandler.cs @@ -33,16 +33,19 @@ public void Handle(IGluonSession session, TransferClaim request) } Lots.TryClose(request.EntityId, request.ClaimId); - try + if (request.ClaimId != 0) { - using (var db = DAFactory.Get()) + try { - db.LotClaims.Delete(request.ClaimId, request.FromOwner); + using (var db = DAFactory.Get()) + { + db.LotClaims.Delete(request.ClaimId, request.FromOwner); + } + } + catch (Exception e) + { + //probably already unclaimed. do nothing. } - } - catch (Exception e) - { - //probably already unclaimed. do nothing. } } } diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/PurchaseLotHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/PurchaseLotHandler.cs index 358ab06f2..293363762 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/PurchaseLotHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/PurchaseLotHandler.cs @@ -1,4 +1,5 @@ -using FSO.Common.DataService; +using FSO.Common; +using FSO.Common.DataService; using FSO.Common.DataService.Model; using FSO.Common.Domain.Realestate; using FSO.Common.Domain.RealestateDomain; @@ -37,6 +38,19 @@ public async void Handle(IVoltronSession session, PurchaseLotRequest packet) if (session.IsAnonymous) //CAS users can't do this. return; + if (Context.Config.Archive != null) + { + if (!Context.Config.Archive.Flags.HasFlag(ArchiveConfigFlags.AllowLotCreation) && !session.HasModerationLevel(1)) + { + session.Write(new PurchaseLotResponse() + { + Status = PurchaseLotStatus.FAILED, + Reason = PurchaseLotFailureReason.PURCHASE_DISABLED + }); + return; + } + } + var isPurchasable = Realestate.IsPurchasable(packet.LotLocation_X, packet.LotLocation_Y); if (!isPurchasable){ diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/RegistrationHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/RegistrationHandler.cs index a81513a6f..b525189f7 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/RegistrationHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/RegistrationHandler.cs @@ -10,6 +10,7 @@ using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Electron.Packets; using FSO.SimAntics.Engine.Scopes; +using FSO.Common; namespace FSO.Server.Servers.City.Handlers { @@ -70,6 +71,20 @@ public RegistrationHandler(CityServerContext context, IDAFactory daFactory, Cont /// public void Handle(IVoltronSession session, RSGZWrapperPDU packet) { + if (Context.Config.Archive != null) + { + if (!Context.Config.Archive.Flags.HasFlag(ArchiveConfigFlags.AllowSimCreation) && !session.HasModerationLevel(1)) + { + session.Write(new CreateASimResponse + { + Status = CreateASimStatus.FAILED, + Reason = CreateASimFailureReason.CAS_DISABLED + }); + + return; + } + } + PurchasableOutfit head = null; PurchasableOutfit body = null; @@ -138,7 +153,7 @@ public void Handle(IVoltronSession session, RSGZWrapperPDU packet) newAvatar.skin_tone = (byte)packet.SkinTone; newAvatar.gender = packet.Gender == Protocol.Voltron.Model.Gender.FEMALE ? DbAvatarGender.female : DbAvatarGender.male; newAvatar.user_id = session.UserId; - newAvatar.budget = 0; + newAvatar.budget = Context.Config.Initial_Funds; if(packet.Gender == Protocol.Voltron.Model.Gender.MALE){ newAvatar.body_swimwear = 0x5470000000D; @@ -152,7 +167,6 @@ public void Handle(IVoltronSession session, RSGZWrapperPDU packet) var user = db.Users.GetById(session.UserId); if ((user?.is_moderator) ?? false) { - newAvatar.budget = 100000; newAvatar.moderation_level = 1; } @@ -212,8 +226,12 @@ public void Handle(IVoltronSession session, RSGZWrapperPDU packet) return; } } - - ((VoltronSession)session).AvatarId = newId; + + if (Context.Config.Archive == null) + { + // Archive still needs to select the avatar after making it. + ((VoltronSession)session).AvatarId = newId; + } session.Write(new CreateASimResponse { Status = CreateASimStatus.SUCCESS, diff --git a/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs b/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs index 8606956b6..7625bd00b 100644 --- a/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs +++ b/TSOClient/FSO.Server/Servers/City/Handlers/VoltronConnectionLifecycleHandler.cs @@ -45,16 +45,24 @@ public void Handle(IVoltronSession session, ClientByePDU packet) public async void SessionClosed(IAriesSession session) { - if (!(session is IVoltronSession)) { + if (!(session is IVoltronSession)) + { return; } IVoltronSession voltronSession = (IVoltronSession)session; VoltronSessions.UnEnroll(session); + // TODO: If the user wasn't verified, then only admins need to know. + if (Context.Config.Archive != null && voltronSession.IsAuthenticated) + { + Context.BroadcastUserList(false); + } + if (voltronSession.IsAnonymous) return; - Liveness.EnqueueChange(() => { + Liveness.EnqueueChange(() => + { //unenroll in voltron group, mark as offline in data service. //since this can happen async make sure our session hasnt been reopened before trying to delete its claim if (Sessions.GetByAvatarId(voltronSession.AvatarId)?.Connected == true) return; @@ -117,15 +125,26 @@ public async void SessionUpgraded(IAriesSession oldSession, IAriesSession newSes }); //CAS, don't hydrate the user - if (voltronSession.IsAnonymous){ + if (voltronSession.IsAnonymous) + { return; } + await AssignAvatar(voltronSession); + } + + public async Task AssignAvatar(IVoltronSession voltronSession, bool invalidateAvatar = false) + { + if (invalidateAvatar) + { + DataService.Invalidate(voltronSession.AvatarId); + } + //New avatar, enroll in voltron group var avatar = await DataService.Get(voltronSession.AvatarId); //can throw? //Mark as online avatar.Avatar_IsOnline = true; - VoltronSessions.Enroll(newSession); + VoltronSessions.Enroll(voltronSession); Events.UserJoined(voltronSession); Neigh.UserJoined(voltronSession); TuningDomain.UserJoined(voltronSession); diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs index 90e6a710d..5408918b1 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContainer.cs @@ -13,10 +13,12 @@ using FSO.Server.Database.DA.Relationships; using FSO.Server.Database.DA.Roommates; using FSO.Server.Database.DA.Users; +using FSO.Server.Domain; using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Electron.Packets; using FSO.Server.Protocol.Gluon.Model; using FSO.Server.Servers.City.Domain; +using FSO.Server.Servers.Lot.Surround; using FSO.SimAntics; using FSO.SimAntics.Engine; using FSO.SimAntics.Marshals; @@ -31,10 +33,12 @@ using NLog; using System; using System.Collections.Generic; +using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace FSO.Server.Servers.Lot.Domain { @@ -46,6 +50,10 @@ public class LotContainer private const bool TIME_DILATION_ENABLED = true; private const int TIME_DILATION_THRESHOLD_MS = 500; // Accelerate through half second pauses. private const int TIME_DILATION_SKIP_THRESHOLD_MS = 5000; // 5 seconds, or 1 ingame minute + private const int HOLLOW_UPDATE_FREQ_MS = 5000; // 5 seconds + private const uint HOLLOW_LOAD_ALL = 0b111111111; + + private const uint TRANSITION_GUID = 0x746ED02B; private static Logger LOG = LogManager.GetCurrentClassLogger(); @@ -57,11 +65,13 @@ public class LotContainer private DbLot LotPersist; private List LotAdj; private List LotRoommates; + private CancellationTokenSource ClosedToken = new(); + private long LastHollowBroadcast; private VM Lot; private VMServerDriver VMDriver; private LotServerGlobalLink VMGlobalLink; - private byte[][] HollowLots; + private Task HollowLots; public int ClientCount = 0; public int TimeToShutdown = -1; public int LotSaveTicker = 0; @@ -82,11 +92,20 @@ public class LotContainer private IShardRealestateDomain Realestate; private VMTSOSurroundingTerrain Terrain; + private bool TransientLot; + private bool UnownedLot; private bool JobLot; private ManualResetEvent LotActive = new ManualResetEvent(false); private bool ActiveYet; private Queue LotThreadActions = new Queue(); + private LiveSurroundLotConnection SurroundConnection; + private HashSet FreeRoamLeaving = []; + + private bool AllowGuestOpening => Config.AllOpenable || ArchiveFreeRoam; + private bool ArchiveFreeRoam => Config.Archive?.Flags.HasFlag(FSO.Common.ArchiveConfigFlags.AllOpenable) ?? false; + private bool IsSpectatorMode; + private static HashSet ValidOOWGUIDs = new HashSet() { 0x37EB32F3, //skill controller @@ -153,6 +172,14 @@ public class LotContainer 0x352A8ACE }; + private static HashSet JobTuningFilter = + [ + "oj-rest-controller.iff", + "oj-robotfactorycontroller.iff", + "oj-nc-controller.iff", + "income_mul" + ]; + public LotContainer(IDAFactory da, LotContext context, ILotHost host, IKernel kernel, LotServerConfiguration config, IRealestateDomain realestate) { VM.UseWorld = false; @@ -162,7 +189,11 @@ public LotContainer(IDAFactory da, LotContext context, ILotHost host, IKernel ke Kernel = kernel; Config = config; - JobLot = (context.Id & 0x40000000) > 0; + TransientLot = context.SpecialLot || context.Action == ClaimAction.LOT_CLEANUP_HOLLOW; + UnownedLot = context.UnownedLot; + JobLot = context.JobLot; + + var skipAdj = context.Action.IsCleanup(); if (JobLot) { var jobPacked = Context.DbId - 0x200; var jobLevel = (short)((jobPacked - 1) & 0xF); @@ -172,21 +203,17 @@ public LotContainer(IDAFactory da, LotContext context, ILotHost host, IKernel ke lot_id = Context.DbId, location = Context.Id, category = LotCategory.money, - name = "{job:"+jobType+":"+jobLevel+"}", + name = "{job:" + jobType + ":" + jobLevel + "}", admit_mode = 4 }; LotAdj = new List(); LotRoommates = new List(); Terrain = new VMTSOSurroundingTerrain(); - Tuning = new DynamicTuning(new DynTuningEntry[] { - new DynTuningEntry() - { - tuning_type = "feature", - tuning_table = 0, - tuning_index = 1, - value = 1 - } - }); + + using (var db = DAFactory.Get()) + { + Tuning = new DynamicTuning(db.Tuning.All(), JobTuningFilter); + } for (int y = 0; y < 3; y++) { @@ -195,11 +222,39 @@ public LotContainer(IDAFactory da, LotContext context, ILotHost host, IKernel ke Terrain.Roads[x, y] = 0xF; //crossroads everywhere } } - } else { + } + else if (UnownedLot) + { + var location = Context.Id & (uint)LotIdFlags.NormalMask; + var coords = MapCoordinates.Unpack(location); + LotPersist = new DbLot + { + lot_id = 0, + shard_id = context.ShardId, + location = location, + category = LotCategory.none, + name = $"({coords.X}, {coords.Y})", + skill_mode = 2, + admit_mode = 4, + }; + + using (var db = DAFactory.Get()) + { + LotAdj = skipAdj ? [] : db.Lots.GetAdjToLocation(context.ShardId, LotPersist.location); + Tuning = new DynamicTuning(db.Tuning.All()); + } + + LotRoommates = new List(); + + Realestate = realestate.GetByShard(LotPersist.shard_id); + GenerateTerrain(); + } + else + { using (var db = DAFactory.Get()) { LotPersist = db.Lots.Get(context.DbId); - LotAdj = db.Lots.GetAdjToLocation(context.ShardId, LotPersist.location); + LotAdj = skipAdj ? [] : db.Lots.GetAdjToLocation(context.ShardId, LotPersist.location); LotRoommates = db.Roommates.GetLotRoommates(context.DbId); Tuning = new DynamicTuning(db.Tuning.All()); } @@ -294,10 +349,16 @@ public string AbortVM() return "Failed to obtain trace! (100 times)"; } - public void LoadAdj() + public byte[][] LoadAdj() { + var result = new byte[9][]; + + if (Context.Action.IsCleanup()) + { + return result; + } + LOG.Info("Loading adj lots for lot with dbid = " + Context.DbId); - HollowLots = new byte[9][]; var myPos = MapCoordinates.Unpack(LotPersist.location); foreach (var lot in LotAdj) { @@ -315,7 +376,7 @@ public void LoadAdj() int numBytesToRead = Convert.ToInt32(fs.Length); var file = new byte[(numBytesToRead)]; fs.Read(file, 0, numBytesToRead); - HollowLots[y * 3 + x] = file; + result[y * 3 + x] = file; } } catch (Exception e) @@ -325,6 +386,8 @@ public void LoadAdj() //don't bother } } + + return result; } public bool AttemptLoadRing() @@ -332,6 +395,7 @@ public bool AttemptLoadRing() //first let's try load our adjacent lots. int attempts = 0; var lotStr = LotPersist.lot_id.ToString("x8"); + int initialBackup = LotPersist.ring_backup_num; while (++attempts < Config.RingBufferSize) { @@ -344,7 +408,7 @@ public bool AttemptLoadRing() var marshal = new VMMarshal(); marshal.Deserialize(file); - if (LotPersist.move_flags > 0) + if (LotPersist.MoveFlags > 0) { //must rotate lot to face its new road direction! var oldDir = ((VMTSOLotState)marshal.PlatformState).Size >> 16; @@ -364,8 +428,13 @@ public bool AttemptLoadRing() } } - using (var db = DAFactory.Get()) - db.Lots.UpdateRingBackup(LotPersist.lot_id, LotPersist.ring_backup_num); + if (LotPersist.ring_backup_num != initialBackup) + { + // Avoid the server trying to load this invalid save again. + + using var db = DAFactory.Get(); + db.Lots.UpdateRingBackupSilent(LotPersist.lot_id, LotPersist.ring_backup_num); + } return true; } @@ -389,9 +458,42 @@ public bool AttemptLoadRing() return false; } + public bool SaveHollow() + { + var lotStr = LotPersist.lot_id.ToString("x8"); + Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/")); + try + { + var hmarshal = Lot.HollowSave(); + + Host.InBackground(() => { + try + { + string path = Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/hollow.fsoh"); + using (var output = new FileStream(path, FileMode.Create)) + { + hmarshal.SerializeInto(new BinaryWriter(output)); + } + } + catch (Exception e) + { + LOG.Warn(e, "Failed to save holow lot (to disk/db) with dbid = " + Context.DbId); + LOG.Warn(e.StackTrace); + } + }); + return true; + } + catch (Exception e) + { + LOG.Warn(e, "Failed to save hollow lot with dbid = " + Context.DbId); + LOG.Warn(e.StackTrace); + return false; + } + } + public bool SaveRing() { - if (JobLot) return true; //job lots never get saved. + if (TransientLot || IsSpectatorMode) return true; //transient/spectator lots never get saved. var newBackup = (sbyte)((LotPersist.ring_backup_num + 1) % Config.RingBufferSize); var lotStr = LotPersist.lot_id.ToString("x8"); Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/")); @@ -419,7 +521,12 @@ public bool SaveRing() using (var db = DAFactory.Get()) { db.Lots.UpdateRingBackup(LotPersist.lot_id, newBackup); - //db.Flush(); + + if ((LotPersist.ArchiveFlags & LotArchiveFlags.ArchiveFromOldSave) != 0) + { + LotPersist.ArchiveFlags = LotArchiveFlags.ArchiveRules; + db.Lots.UpdateArchiveFlags(LotPersist.lot_id, (sbyte)LotPersist.archive_flags); + } } } catch (Exception e) @@ -493,14 +600,18 @@ private void ReturnInvalidObjects() var persists = Lot.Context.ObjectQueries.MultitileByPersist.Keys.ToList(); Dictionary ownerInfo; + var adminOwners = new HashSet(); using (var da = DAFactory.Get()) { ownerInfo = da.Objects.GetObjectOwners(persists).ToDictionary(x => x.object_id); + foreach (var id in ownerInfo.Values.Select(x => x.owner_id ?? 0).Where(x => x != 0 && !Lot.TSOState.Roommates.Contains(x)).Distinct()) + if (da.Avatars.GetModerationLevel(id) > 0) adminOwners.Add(id); } var ents = new List(Lot.Entities); var needToCreate = new HashSet(RequiredGUIDs); - var removeAll = (LotPersist.move_flags & 6) > 0; + var removeAll = (LotPersist.MoveFlags & LotMoveFlags.ShouldClearObjects) > 0; + bool keepAsOwnerless = (LotPersist.ArchiveFlags & LotArchiveFlags.ArchiveFromOldSave) != 0; foreach (var ent in ents) { needToCreate.Remove(ent.Object.OBJ.GUID); @@ -532,7 +643,8 @@ private void ReturnInvalidObjects() //or if the object is not donated and the owner is not a roomie if (info.lot_id != Context.DbId) deleteMode = 2; - else if (removeAll || !(Lot.TSOState.Roommates.Contains(((VMTSOObjectState)ent.TSOState).OwnerID) + else if (removeAll || !(Lot.TSOState.Roommates.Contains(((VMTSOObjectState)ent.TSOState).OwnerID) + || adminOwners.Contains(((VMTSOObjectState)ent.TSOState).OwnerID) || ((VMTSOObjectState)ent.TSOState).ObjectFlags.HasFlag(VMTSOObjectFlags.FSODonated))) deleteMode = 1; } @@ -552,7 +664,19 @@ private void ReturnInvalidObjects() { total++; //this is run synchro. - if (deleteMode == 1) + if (keepAsOwnerless) + { + foreach (var obj in delE.MultitileGroup.Objects) + { + if (obj.TSOState is VMTSOObjectState tsoobj) + { + tsoobj.OwnerID = 0; + } + + obj.PersistID = 0; + } + } + else if (deleteMode == 1) { //return to inventory, since the object is actually on this lot VMGlobalLink.MoveToInventory(Lot, delE.MultitileGroup, (success, objid) => @@ -577,7 +701,7 @@ private void ReturnInvalidObjects() } } - if (objectsOnLot.Count != 0 && !JobLot) + if (objectsOnLot.Count != 0 && !TransientLot) { using (var da = DAFactory.Get()) { @@ -589,10 +713,10 @@ private void ReturnInvalidObjects() Lot.Context.CreateObjectInstance(obj, LotTilePos.OUT_OF_WORLD, Direction.NORTH); } - if ((LotPersist.move_flags & 2) > 0) + if ((LotPersist.MoveFlags & LotMoveFlags.New) > 0) { BlueprintReset(); - LotPersist.move_flags = 0; + LotPersist.MoveFlags = 0; } } @@ -688,8 +812,17 @@ public void BlueprintReset() public void ResetVM() { LOG.Info("Resetting VM for lot with dbid = " + Context.DbId); + IsSpectatorMode = (Context.Action == ClaimAction.LOT_SPECTATOR); VMGlobalLink = Kernel.Get(); + VMGlobalLink.Readonly = IsSpectatorMode; + if (AllowGuestOpening && !JobLot) + { + var host = Kernel.Get(); + SurroundConnection = host.Connect(LotPersist.location, this, Host); + } + VMDriver = new VMServerDriver(VMGlobalLink); + VMDriver.TicksPerPacket = Config.Tick_Rate_Divider; VMDriver.OnTickBroadcast += TickBroadcast; VMDriver.OnDirectMessage += DirectMessage; VMDriver.OnDropClient += DropClient; @@ -709,12 +842,14 @@ public void ResetVM() Lot = new VM(new VMContext(null), VMDriver, new VMNullHeadlineProvider()); Lot.OnChatEvent += Lot_OnChatEvent; + Lot.OnGenericVMEvent += Lot_OnGenericVMEvent; Lot.Init(); bool isNew = false; - bool isMoved = (LotPersist.move_flags > 0); - LoadAdj(); - if (!JobLot && LotPersist.ring_backup_num > -1 && AttemptLoadRing()) + bool archiveOldSave = LotPersist.ArchiveFlags.HasFlag(LotArchiveFlags.ArchiveFromOldSave); + bool isMoved = LotPersist.MoveFlags > 0 || archiveOldSave; + HollowLots = Task.Run(LoadAdj); + if (((!TransientLot) || Context.Action == ClaimAction.LOT_CLEANUP_HOLLOW) && LotPersist.ring_backup_num > -1 && AttemptLoadRing()) { LOG.Info("Successfully loaded and cleaned fsov for dbid = " + Context.DbId); } @@ -724,12 +859,43 @@ public void ResetVM() BlueprintReset(); } + RefreshLotState(isNew, isMoved, archiveOldSave); + + if (JobLot) + { + //for recording. must resave lot to get appropriate state changes from terrain population + //(important for playback to sync) + Lot.Tick(); + Lot.ForwardCommand(new VMStateSyncCmd() + { + State = Lot.Save(), + Run = false, + }); + Lot.Tick(); + } + + LotActive.Set(); + ActiveYet = true; + } + + private void RefreshLotState(bool isNew, bool isMoved, bool archiveOldSave) + { + if (UnownedLot) + { + // Maximum size (for admin placement) + Lot.TSOState.Size |= 10 | (3 << 8); + } + Lot.TSOState.Terrain = Terrain; Lot.TSOState.Name = LotPersist.name; Lot.TSOState.NhoodID = LotPersist.neighborhood_id; - Lot.TSOState.LotID = LotPersist.location; + Lot.TSOState.LotID = LotPersist.location & (uint)(LotIdFlags.NormalMask); Lot.TSOState.SkillMode = LotPersist.skill_mode; Lot.TSOState.PropertyCategory = (byte)LotPersist.category; + Lot.TSOState.Flags = + (LotPersist.ArchiveFlags != 0 ? VMTSOLotStateFlags.Archived : 0) | + (AllowGuestOpening && !JobLot ? VMTSOLotStateFlags.AllowFreeRoam : 0); + var isCommunity = LotPersist.category == LotCategory.community; if (isCommunity) @@ -771,15 +937,23 @@ public void ResetVM() Lot.Context.UpdateTSOBuildableArea(); Lot.MyUID = uint.MaxValue - 1; - if ((LotPersist.move_flags & 2) > 0) isNew = true; + if ((LotPersist.MoveFlags & LotMoveFlags.New) > 0) isNew = true; ReturnInvalidObjects(); if (!JobLot) ReturnOOWObjects(); - var restoreType = isCommunity ? RestoreLotType.Community : RestoreLotType.Normal; - if (isMoved || isNew) VMLotTerrainRestoreTools.RestoreTerrain(Lot, restoreType); + var keepHeights = LotPersist.MoveFlags.HasFlag(LotMoveFlags.TerrainRegen) || archiveOldSave; + (byte[], short[])? restoreData = keepHeights ? VMLotTerrainRestoreTools.SnapshotTerrain(Lot) : null; + + var restoreType = UnownedLot ? RestoreLotType.Blank : (isCommunity ? RestoreLotType.Community : RestoreLotType.Normal); + if (isMoved || isNew) VMLotTerrainRestoreTools.RestoreTerrain(Lot, restoreType, !keepHeights); VMLotTerrainRestoreTools.EnsureCoreObjects(Lot, restoreType); if (isNew) VMLotTerrainRestoreTools.PopulateBlankTerrain(Lot); + if (restoreData != null) + { + VMLotTerrainRestoreTools.RestoreBuildableTerrain(Lot, restoreData.Value); + } + ResyncTime(); if (Lot.Tuning == null || (Lot.Tuning.GetTuning("forcedTuning", 0, 0) ?? 0f) == 0f) @@ -788,9 +962,10 @@ public void ResetVM() { Tuning = Tuning }); - Lot.Tick(); } + Lot.Tick(); + Lot.Context.UpdateTSOBuildableArea(); var entClone = new List(Lot.Entities); @@ -840,22 +1015,85 @@ public void ResetVM() } } } - LotActive.Set(); - ActiveYet = true; + } - if (JobLot) + private void Lot_OnGenericVMEvent(VMEventType type, object data) + { + if (type == VMEventType.TSOUserLeaveBuildBuy) { - //for recording. must resave lot to get appropriate state changes from terrain population - //(important for playback to sync) - Lot.Tick(); - Lot.ForwardCommand(new VMStateSyncCmd() + var msg = (VMNetLeaveBuildBuyCmd)data; + + bool broadcastUpdate = msg.Build || true; + if (broadcastUpdate) { - State = Lot.Save(), - Run = false, - }); + if (LastHollowBroadcast == -1) + { + // In flight... + return; + } + + var msSinceLast = (Stopwatch.GetTimestamp() - LastHollowBroadcast) / (Stopwatch.Frequency / 1000); + + if (msSinceLast > HOLLOW_UPDATE_FREQ_MS) + { + HollowBroadcast(); + } + else + { + LastHollowBroadcast = -1; + Task.Delay((int)(HOLLOW_UPDATE_FREQ_MS - msSinceLast), ClosedToken.Token).ContinueWith((task) => + { + if (!task.IsCanceled) + { + BlockOnLotThread(HollowBroadcast); + } + }); + } + } } } + private void HollowBroadcast() + { + var didBroadcast = SurroundConnection?.HollowBroadcast((Action onData) => + { + var hmarshal = Lot.HollowSave(); + + Host.InBackground(() => { + try + { + + byte[] data; + using (var output = new MemoryStream()) + { + hmarshal.SerializeInto(new BinaryWriter(output)); + data = output.ToArray(); + } + + onData(data); + + if (!TransientLot) + { + var lotStr = LotPersist.lot_id.ToString("x8"); + string path = Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/hollow.fsoh"); + + using (var output = new FileStream(path, FileMode.Create)) + { + output.Write(data); + } + } + } + catch (Exception e) + { + LOG.Warn(e, "Failed to save hollow lot (to disk/db) with dbid = " + Context.DbId); + LOG.Warn(e.StackTrace); + } + }); + }) ?? false; + + LastHollowBroadcast = didBroadcast ? Stopwatch.GetTimestamp() : 0; + } + public void UpdateTuning(IEnumerable tuning) { Tuning = new DynamicTuning(tuning); @@ -937,14 +1175,14 @@ private void DirectMessage(VMNetClient target, VMNetMessage msg) { object packet = (msg.Type == VMNetMessageType.Direct) ? (object)(new FSOVMDirectToClient() { Data = msg.Data }) - : (object)(new FSOVMTickBroadcast() { Data = msg.Data }); + : (object)(new FSOVMTickBroadcast() { Data = msg.Data, Catchup = msg.Type == VMNetMessageType.CatchupTick }); Host.Send(target.PersistID, packet); } - private void TickBroadcast(VMNetMessage msg, HashSet ignore) + private void TickBroadcast(VMNetMessage msg, HashSet clients) { - HashSet ignoreIDs = new HashSet(ignore.Select(x => x.PersistID)); - Host.Broadcast(ignoreIDs, new FSOVMTickBroadcast() { Data = msg.Data }); + HashSet clientIDs = new HashSet(clients.Select(x => x.PersistID)); + Host.Broadcast(clientIDs, new FSOVMTickBroadcast() { Data = msg.Data }); } private void DereferenceLot() @@ -969,6 +1207,8 @@ private void DereferenceLot() /// public void Run() { + VM.UseWorld = false; + try { try @@ -990,6 +1230,12 @@ public void Run() long lastTick = 0; long skippedTimeMs = 0; + if (!Context.Action.IsCleanup()) + { + // Someone will be joining us, so might as well get sync prepared now. + VMDriver.PrepareSync(Lot); + } + LotSaveTicker = LOT_SAVE_PERIOD; AvatarSaveTicker = AVATAR_SAVE_PERIOD; while (true) @@ -998,9 +1244,9 @@ public void Run() lastTick++; //sometimes avatars can be killed immediately after their kill timer starts (this frame will run the leave lot interaction) //this works around that possibility. - var preTickAvatars = Lot.Context.ObjectQueries.AvatarsByPersist.Values.Select(x => x).ToList(); - var noRoomies = !(preTickAvatars.Any(x => ((VMTSOAvatarState)x.TSOState).Permissions > VMTSOAvatarPermissions.Visitor)) - && (LotPersist.admit_mode < 4 && LotPersist.category != LotCategory.community); + var preTickAvatars = Lot.Context.ObjectQueries.AvatarsByPersist.Values.ToList(); + var noRoomies = !IsSpectatorMode + && !preTickAvatars.Any(x => x.KillTimeout == -1 && x.AvatarState.Permissions > VMTSOAvatarPermissions.Visitor); try { @@ -1021,24 +1267,37 @@ public void Run() return; //background thread has already released all our avatars and our claim. exit immediately. } - if (noRoomies && !noRemainingUsers) + if (noRoomies && !noRemainingUsers && !ArchiveFreeRoam) { if (TimeToShutdown == -1) { - TimeToShutdown = (Context.Action == ClaimAction.LOT_CLEANUP) ? 1 : TICKRATE * 40; + if (Context.Action.IsCleanup()) + TimeToShutdown = 1; + else if (AllowGuestOpening) + TimeToShutdown = TICKRATE * 15; + else + TimeToShutdown = TICKRATE * 40; } - if (--TimeToShutdown < TICKRATE * 10) + // Only do the following if there are definitely avatars on the property. (and we can verify their permissions) + if (--TimeToShutdown < TICKRATE * 10 && preTickAvatars.Count > 0) { - //no roommates are here, so all visitors must be kicked out. - if (preTickAvatars.Count > 0) + if (AllowGuestOpening) { - Host.Broadcast(new HashSet(), new FSOVMProtocolMessage(true, "21", "22")); + TransitionToSpectatorMode(); } - foreach (var avatar in preTickAvatars) + else if (LotPersist.admit_mode < 4 && LotPersist.category != LotCategory.community) { - if (avatar.KillTimeout == -1) avatar.UserLeaveLot(); - VMDriver.DropAvatar(avatar); + //no roommates are here, so all visitors must be kicked out. + if (preTickAvatars.Count > 0) + { + Host.Broadcast(null, new FSOVMProtocolMessage(true, "21", "22")); + } + foreach (var avatar in preTickAvatars) + { + if (avatar.KillTimeout == -1) avatar.UserLeaveLot(); + VMDriver.DropAvatar(avatar); + } } } } @@ -1049,7 +1308,7 @@ public void Run() { //lot shuts down 20 seconds after everyone leaves //if we're doing a cleanup action, it closes immediately - TimeToShutdown = (Context.Action == ClaimAction.LOT_CLEANUP) ? 1 : TICKRATE * 20; + TimeToShutdown = (Context.Action.IsCleanup()) ? 1 : TICKRATE * 20; } else { @@ -1072,20 +1331,18 @@ public void Run() Host.UpdateActiveVisitRecords(); } - var beingKilled = preTickAvatars.Where(x => x.KillTimeout == 1); - if (beingKilled.Count() > 0) + SurroundConnection?.SubmitTick(Lot, false); + + if (AllowGuestOpening && !JobLot) { - //avatars that are being killed could die before their user disconnects. It's important to save them immediately. - SaveAvatars(beingKilled, true); + TickFreeRoam(); } - foreach (var avatar in Lot.Context.ObjectQueries.AvatarsByPersist) + var beingKilled = preTickAvatars.Where(x => x.KillTimeout == 1); + if (beingKilled.Any()) { - if (avatar.Value.KillTimeout == 1) - { - //this avatar has begun being killed. Save them immediately. - SaveAvatar(avatar.Value); - } + //avatars that are being killed could die before their user disconnects. It's important to save them immediately. + SaveAvatars(beingKilled, true); } if (--AvatarSaveTicker <= 0) @@ -1183,15 +1440,331 @@ public void BlockOnLotThread(Action action) evt.WaitOne(); } - public bool IsAvatarOnLot(uint pid) + private void TransitionFromSpectatorMode() + { + LOG.Info("Transitioning lot " + Context.DbId + " from spectator mode to writable mode."); + LotActive.Reset(); + VMDriver.RecordAvatarStateForTransition(Lot); + IsSpectatorMode = false; + VMGlobalLink.Readonly = IsSpectatorMode; + + bool loadedSave = false; + + if (LotPersist.ring_backup_num >= 0) + { + VMDriver.Transitioning = true; + try + { + var path = Path.Combine(Config.SimNFS, $"Lots/{LotPersist.lot_id:x8}/state_{LotPersist.ring_backup_num}.fsov"); + using var file = new BinaryReader(File.OpenRead(path)); + var marshal = new VMMarshal(); + marshal.Deserialize(file); + + bool archiveOldSave = LotPersist.ArchiveFlags.HasFlag(LotArchiveFlags.ArchiveFromOldSave); + bool isMoved = LotPersist.MoveFlags > 0 || archiveOldSave; + + if (isMoved) + { + var oldDir = ((VMTSOLotState)marshal.PlatformState).Size >> 16; + var newDir = VMLotTerrainRestoreTools.PickRoadDir(Terrain.Roads[1, 1]); + var rotate = new VMLotRotate(marshal); + rotate.Rotate(((newDir - oldDir) + 4) % 4); + } + + Lot.Load(marshal); + CleanLot(); + Lot.Reset(); + RefreshLotState(isNew: false, isMoved, archiveOldSave); // archive old save? + loadedSave = true; + } + catch (Exception e) + { + LOG.Warn(e, "Failed to load lot save for spectator transition on lot " + Context.DbId); + } + finally + { + VMDriver.Transitioning = false; + } + } + + if (loadedSave) + VMDriver.RejoinClients(Lot); + else + VMDriver.SyncAllClients(); + + LotSaveTicker = LOT_SAVE_PERIOD; + AvatarSaveTicker = AVATAR_SAVE_PERIOD; + + LotActive.Set(); + } + + private void TransitionToSpectatorMode() + { + LOG.Info("Transitioning lot " + Context.DbId + " to spectator mode."); + LotActive.Reset(); + VMDriver.RecordAvatarStateForTransition(Lot); + + // First, make sure the driver doesn't send any of this to the client (it might convince their client to disconnect) + VMDriver.Transitioning = true; + + // Try to force everyone to leave the lot safely. + CleanLot(); + + // Save the lot with nobody on it. + SaveRing(); + + // Transition to spectator mode and reintroduce the players who remained connected. (with a resync) + IsSpectatorMode = true; + VMGlobalLink.Readonly = IsSpectatorMode; + + VMDriver.Transitioning = false; + + VMDriver.RejoinClients(Lot, VMTSOAvatarFlags.Spectator); + Lot.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Lot transitioned to spectator mode.")); + LotActive.Set(); + } + + private bool TryBeginFreeRoam(uint persistID) + { + lock (FreeRoamLeaving) + { + if (!FreeRoamLeaving.Contains(persistID)) + { + FreeRoamLeaving.Add(persistID); + return true; + } + + return false; + } + } + + public void TickFreeRoam() + { + foreach (var obj in Lot.Context.ObjectQueries.Avatars) + { + var ava = obj as VMAvatar; + + var lastStack = ava.Thread.Stack.LastOrDefault(); + + if (ava.KillTimeout != -1) + { + // Can't transition if they're already leaving. + continue; + } + + // Direct control free roam + if (lastStack is VMDirectControlFrame dcFrame) + { + var edge = dcFrame.EdgeCheck(1); + if (edge != default) + { + // User appears to be attempting to leave the property. See if the lot can be entered and then tell the client to go and join it. + + var location = LotPersist.location; + var coords = MapCoordinates.Unpack(location); + var cityOffset = LotTransitionInfo.RelativeChangeLotToCity(new Point(edge.X, edge.Y)); + coords.X += (ushort)cityOffset.X; + coords.Y += (ushort)cityOffset.Y; + + if (Realestate.IsOpenable(coords.X, coords.Y)) + { + if (!TryBeginFreeRoam(ava.PersistID)) continue; + + LOG.Info($"Edge check {edge} ({coords.X}, {coords.Y}) {Stopwatch.GetTimestamp()}"); + + var pid = ava.PersistID; + + var info = new LotTransitionInfo() + { + BeforeLocation = LotPersist.location, + RelativeChangeX = edge.X, + RelativeChangeY = edge.Y, + + AvatarLotTilePosX = ava.Position.x, + AvatarLotTilePosY = ava.Position.y, + AvatarDirection = ava.RadianDirection, + + Type = LotTransitionType.DirectControl + }; + + SaveAvatar(ava, () => + { + Host.ReleaseDbAvatarClaim(pid); + + Lot.ForwardCommand(new VMNetBeginFreeRoamCmd() + { + AvatarPID = pid, + TargetLot = MapCoordinates.Pack(coords.X, coords.Y), + Transition = info + }); + }); + } + } + // Preload lots if the avatar is close to the edge. + // TODO + + // Future process: + // Server detects advancement to edge and tries to start the process a bit early (other stuff is the same) + //.. + // - Client gets request to hop to target lot and starts connecting in the background. + // - JoinLot includes a position as before, but it can be significantly out of bounds + // - Surrounding lots that can be reused from the previous lot are omitted to save space and time. + // - Client seamless switch between the old world and the new one when it's fully ready (uses async preload) + // - Client can correct its server position to better match the client (need to be aware of latency) + } + else if (lastStack.Callee.Object.GUID == TRANSITION_GUID) + { + var transitionDest = lastStack.Callee; + + var transitionRequested = transitionDest.GetAttribute(5); + + if (transitionRequested != 0) + { + var idLow = transitionDest.GetAttribute(1); + var idHigh = transitionDest.GetAttribute(2); + var destX = transitionDest.GetAttribute(3); + var destY = transitionDest.GetAttribute(4); + + var myCoords = MapCoordinates.Unpack(LotPersist.location); + + var location = (uint)((int)idLow | (idHigh << 16)); + var coords = MapCoordinates.Unpack(location); + + if (Realestate.IsOpenable(coords.X, coords.Y)) + { + if (!TryBeginFreeRoam(ava.PersistID)) continue; + + var pid = ava.PersistID; + var cityEdge = new Point(coords.X - myCoords.X, coords.Y - myCoords.Y); + var edge = LotTransitionInfo.RelativeChangeCityToLot(cityEdge); + + var info = new LotTransitionInfo() + { + BeforeLocation = LotPersist.location, + RelativeChangeX = edge.X, + RelativeChangeY = edge.Y, + + AvatarLotTilePosX = ava.Position.x, + AvatarLotTilePosY = ava.Position.y, + AvatarDirection = ava.RadianDirection, + + Type = LotTransitionType.Routing, + RoutingLotTilePosX = destX, + RoutingLotTilePosY = destY, + RoutingTargetLocation = location + }; + + SaveAvatar(ava, () => + { + Host.ReleaseDbAvatarClaim(pid); + + Lot.ForwardCommand(new VMNetBeginFreeRoamCmd() + { + AvatarPID = pid, + TargetLot = MapCoordinates.Pack(coords.X, coords.Y), + Transition = info + }); + }); + } + } + } + } + } + + public void SendHollowLotData(uint location, byte[] data) + { + // Sends the given hollow lot data to the players on the lot. + + var update = new VMHollowAdjEntry[9]; + + var myLocation = LotPersist?.location ?? 0; + + if (LotPersist == null) + { + return; + } + + for (int i = 0; i < 9; i++) + { + int x = (i % 3) - 1; + int y = (i / 3) - 1; + + if (myLocation + (x * 65536) + y == location) + { + var existingData = HollowLots.Result; + existingData[i] = data; + update[i] = new VMHollowAdjEntry(VMHollowAdjType.Hollow, data); + } + else + { + update[i] = new VMHollowAdjEntry(VMHollowAdjType.None); + } + } + + Lot?.SendCommand(new VMNetAdjHollowSyncCmd() + { + HollowAdj = update + }); + } + + public bool IsAvatarOnLot(uint pid, Dictionary visitors) { //we need to check if the avatar's sim is still on the lot. their data + claim might have left, but the avatar could still be here. bool result = false; if (!ActiveYet) return false; //we are not on an inactive lot. + + bool waitForDeletion = false; + BlockOnLotThread(() => { - result = Lot.Context.ObjectQueries.AvatarsByPersist.ContainsKey(pid); + var ava = Lot.GetAvatarByPersist(pid); + + lock (visitors) + { + result = ava != null || visitors.ContainsKey(pid); + } + + if (result && ava == null) + { + // Their visitor entry should disappear soon. + waitForDeletion = true; + } + else if (result && ava.KillTimeout != -1) + { + // If this avatar has started the leave lot animation, we might be able to get rid of them instantly. + if (ava.Thread.Stack.Any(x => x.Callee == ava && x.Routine.ID == 8373)) + { + Lot.ForwardCommand(new VMNetDeleteObjectCmd() + { + ObjectID = ava.ObjectID, + CleanupAll = true, + Verified = true, + }); + + waitForDeletion = true; + } + } }); + + if (waitForDeletion) + { + for (int i = 0; i < 30; i++) + { + BlockOnLotThread(() => + { + lock (visitors) + { + result = Lot.GetAvatarByPersist(pid) != null || visitors.ContainsKey(pid); + } + }); + + if (!result) + { + break; + } + } + } + return result; } @@ -1200,15 +1773,51 @@ public void SaveAvatars(IEnumerable avatars, bool ignoreKill) RelationshipsToSave.Clear(); foreach (var avatar in avatars) { - if (avatar != null && avatar.PersistID != 0 && (ignoreKill || avatar.KillTimeout == -1)) SaveAvatar(avatar); + if (avatar != null && avatar.PersistID != 0 && (ignoreKill || avatar.KillTimeout == -1)) + { + SaveAvatar(avatar); + } } if (RelationshipsToSave.Count > 0) BatchRelationshipSave(); } + private bool IsAvatarDebug(uint modLevel) + { + if (Config.Archive != null) + { + // Use the archive flags to gate debug features + var flags = Config.Archive.Flags; + + if (flags.HasFlag(FSO.Common.ArchiveConfigFlags.DebugFeatures)) + { + int requiredLevel = 2; + + if (flags.HasFlag(FSO.Common.ArchiveConfigFlags.DebugFeaturesAllUsers)) + { + requiredLevel = 0; + } + else if (flags.HasFlag(FSO.Common.ArchiveConfigFlags.DebugFeaturesMods)) + { + requiredLevel = 1; + } + + return modLevel >= requiredLevel; + } + else + { + return false; + } + } + + return modLevel > 0; + } + //Run on the background thread public void AvatarJoin(IVoltronSession session) { LotActive.WaitOne(); //wait til we're active at least + SurroundConnection?.AvatarJoin(session.AvatarId); + lock (FreeRoamLeaving) FreeRoamLeaving.Remove(session.AvatarId); using (var da = DAFactory.Get()) { ClientCount++; @@ -1224,10 +1833,13 @@ public void AvatarJoin(IVoltronSession session) //Load all the avatars data var state = StateFromDB(avatar, user, rels, jobinfo, myRoomieLots, myIgnored); + var transitionInfo = session.GetAttribute("lotTransitionInfo") as LotTransitionInfo; + var client = new VMNetClient(); client.AvatarState = state; client.RemoteIP = session.IpAddress; client.PersistID = session.AvatarId; + client.TransitionInfo = transitionInfo; if (TimeToShutdown == 0) { @@ -1237,11 +1849,13 @@ public void AvatarJoin(IVoltronSession session) } var visitorType = DbLotVisitorType.visitor; + bool isRoommate = false; if (myRoomieLots.Count > 0) { var roomieStatus = myRoomieLots.FindAll(x => x.lot_id == Context.DbId).FirstOrDefault(); if (roomieStatus != null && roomieStatus.is_pending == 0) { + isRoommate = true; switch (roomieStatus.permissions_level) { case 0: @@ -1254,10 +1868,34 @@ public void AvatarJoin(IVoltronSession session) } } } + + uint modLevel = avatar.moderation_level; + + bool isAdmin = modLevel >= 1; + if (IsSpectatorMode) + { + if (isRoommate || isAdmin) + { + // Roommate or admin joining, transition to writable mode + lock (LotThreadActions) + { + LotThreadActions.Enqueue(() => TransitionFromSpectatorMode()); + } + } + else + { + state.AvatarFlags |= VMTSOAvatarFlags.Spectator; + } + } + + state.AvatarFlags |= IsAvatarDebug(modLevel) ? VMTSOAvatarFlags.Debug : 0; + Host.RecordStartVisit(session, visitorType); + var hollowLoadMask = (transitionInfo?.GetSurroundingLotMask() ?? HOLLOW_LOAD_ALL); + VMDriver.ConnectClient(client); - VMDriver.SendDirectCommand(client, new VMNetAdjHollowSyncCmd { HollowAdj = HollowLots }); + VMDriver.SendDirectCommand(client, BuildHollowAsyncCmd(hollowLoadMask)); var vmInventory = new List(); foreach (var item in inventory) @@ -1275,6 +1913,29 @@ public void AvatarJoin(IVoltronSession session) } } + private VMNetAdjHollowSyncCmd BuildHollowAsyncCmd(uint loadMask) + { + return new VMNetAdjHollowSyncCmd + { + HollowAdj = [.. HollowLots.Result.Select((x, index) => + { + uint bit = 1u << index; + + VMHollowAdjType type; + if ((loadMask & bit) != 0) + { + type = x == null ? VMHollowAdjType.Terrain : VMHollowAdjType.Hollow; + } + else + { + type = VMHollowAdjType.Reuse; + } + + return new VMHollowAdjEntry(type, x); + })] + }; + } + public static VMInventoryItem InventoryItemFromDB(DbObject obj) { return new VMInventoryItem @@ -1305,7 +1966,7 @@ public void BatchRelationshipSave() }); } - public void SaveAvatar(VMAvatar avatar) + public void SaveAvatar(VMAvatar avatar, Action postSave = null) { var statevm = new VMNetAvatarPersistState(); statevm.Save(avatar); @@ -1355,6 +2016,8 @@ public void SaveAvatar(VMAvatar avatar) { db.Avatars.UpdateAvatarLotSave(pid, dbState); if (jobLevel != null) db.Avatars.UpdateAvatarJobLevel(jobLevel); + + postSave?.Invoke(); } }); } @@ -1423,32 +2086,7 @@ private VMNetAvatarPersistState StateFromDB(DbAvatar avatar, User user, List x.lot_id == Context.DbId).FirstOrDefault(); - if (roomieStatus != null && roomieStatus.is_pending == 0) - { - switch (roomieStatus.permissions_level) - { - case 0: - state.Permissions = VMTSOAvatarPermissions.Roommate; break; - case 1: - state.Permissions = VMTSOAvatarPermissions.BuildBuyRoommate; break; - case 2: - state.Permissions = VMTSOAvatarPermissions.Owner; break; - } - } - else state.Permissions = VMTSOAvatarPermissions.Visitor; - } - - if (avatar.moderation_level > 0) state.Permissions = VMTSOAvatarPermissions.Admin; + state.Permissions = GetAvatarPermissions(avatar, myRoomieLots); var motives = new short[16]; for (int i=0; i<16; i++) @@ -1458,28 +2096,66 @@ private VMNetAvatarPersistState StateFromDB(DbAvatar avatar, User user, List>(); + var relDict = new Dictionary>(rels.Count); foreach (var rel in rels) { - if (!relDict.ContainsKey(rel.to_id)) relDict[rel.to_id] = new List(); - var list = relDict[rel.to_id]; + if (!relDict.TryGetValue(rel.to_id, out var list)) + { + list = []; + relDict[rel.to_id] = list; + } while (list.Count <= rel.index) list.Add(0); list[(int)rel.index] = rel.value; } state.Relationships = new VMEntityPersistRelationshipMarshal[relDict.Count]; - for (int i=0; i (short)x).ToArray(); - state.Relationships[i] = marshal; + marshal.Values = [.. dictItem.Value.Select(x => (short)x)]; + state.Relationships[relI++] = marshal; } return state; } + private VMTSOAvatarPermissions GetAvatarPermissions(DbAvatar avatar, List myRoomieLots) + { + VMTSOAvatarPermissions permissions = VMTSOAvatarPermissions.Visitor; + + if (LotPersist.category == LotCategory.community) + { + if (LotPersist.owner_id == avatar.avatar_id) + { + permissions = VMTSOAvatarPermissions.Owner; + } + else permissions = VMTSOAvatarPermissions.Visitor; //needs to be set by the VM. + } + else + { + var roomieStatus = myRoomieLots.FindAll(x => x.lot_id == Context.DbId).FirstOrDefault(); + if (roomieStatus != null && roomieStatus.is_pending == 0) + { + switch (roomieStatus.permissions_level) + { + case 0: + permissions = VMTSOAvatarPermissions.Roommate; break; + case 1: + permissions = VMTSOAvatarPermissions.BuildBuyRoommate; break; + case 2: + permissions = VMTSOAvatarPermissions.Owner; break; + } + } + else permissions = VMTSOAvatarPermissions.Visitor; + } + + if (avatar.moderation_level > 0) permissions = VMTSOAvatarPermissions.Admin; + + return permissions; + } + public DbAvatar StateToDb(VMNetAvatarPersistState avatar) { var state = new DbAvatar(); @@ -1530,6 +2206,7 @@ public void NotifyRoommateChange(uint avatar_id, uint replace_id, ChangeType cha if (!signalled) return; //give up VMTSOAvatarPermissions newLevel = VMTSOAvatarPermissions.Visitor; VMChangePermissionsMode mode = VMChangePermissionsMode.NORMAL; + bool? debug = null; switch (change) { case ChangeType.ADD_ROOMMATE: @@ -1544,6 +2221,15 @@ public void NotifyRoommateChange(uint avatar_id, uint replace_id, ChangeType cha mode = VMChangePermissionsMode.OWNER_SWITCH_WITH_OBJECTS; break; case ChangeType.ROOMIE_INHERIT_OBJECTS_ONLY: mode = VMChangePermissionsMode.OBJECTS_ONLY; break; + case ChangeType.RELOAD_PERMISSIONS: + using (var da = DAFactory.Get()) + { + var ava = da.Avatars.Get(avatar_id); + var roomies = da.Roommates.GetAvatarsLots(avatar_id); + newLevel = GetAvatarPermissions(ava, roomies); + debug = IsAvatarDebug(ava.moderation_level); + } + break; } try @@ -1553,6 +2239,7 @@ public void NotifyRoommateChange(uint avatar_id, uint replace_id, ChangeType cha TargetUID = avatar_id, Level = newLevel, Mode = mode, + Debug = debug, ReplaceUID = replace_id, Verified = true, }); @@ -1571,46 +2258,61 @@ public void ForceShutdown() public void Shutdown() { //shut down this lot. Do a final save and close everything down. + ClosedToken?.Cancel(); + VMDriver.EndRecord(); LOG.Info("Lot with dbid = " + Context.DbId + " shutting down."); - if ((LotPersist.move_flags & 4) > 0) + + if (!TransientLot) { - //this lot is slated to be deleted from the database. - using (var da = DAFactory.Get()) + if ((LotPersist.MoveFlags & LotMoveFlags.PermanentDelete) > 0) { - da.Lots.Delete(Context.DbId); - var lotStr = LotPersist.lot_id.ToString("x8"); - Directory.Delete(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/"), true); + //this lot is slated to be deleted from the database. + using (var da = DAFactory.Get()) + { + da.Lots.Delete(Context.DbId); + var lotStr = LotPersist.lot_id.ToString("x8"); + Directory.Delete(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/"), true); + } } - } - try - { - ReturnInvalidObjects(); - } - catch (Exception e) { } - SaveRing(); + try + { + ReturnInvalidObjects(); + } + catch (Exception e) { } - //if we have a null owner, this lot needs to be deleted. + SaveRing(); - if (!(JobLot || LotPersist.category == LotCategory.community)) { - using (var da = DAFactory.Get()) + //if we have a null owner, this lot needs to be deleted. + if (LotPersist.category != LotCategory.community) { - var lot = da.Lots.Get(Context.DbId); - if (lot.owner_id == null) + using (var da = DAFactory.Get()) { - - try - { - var lotStr = LotPersist.lot_id.ToString("x8"); - Directory.Delete(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/"), true); - } catch (Exception) + var lot = da.Lots.Get(Context.DbId); + if (lot.owner_id == null) { - + + try + { + var lotStr = LotPersist.lot_id.ToString("x8"); + Directory.Delete(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/"), true); + } + catch (Exception) + { + + } + //note that the lot has to be deleted from db by lot allocations, since it still needs to unlock the location this property was at. } - //note that the lot has to be deleted from db by lot allocations, since it still needs to unlock the location this property was at. } } } + else if (Context.Action == ClaimAction.LOT_CLEANUP_HOLLOW && !Context.SpecialLot && !IsSpectatorMode) + { + SaveHollow(); + } + + SurroundConnection?.Dispose(); + SurroundConnection = null; Host.Shutdown(); } @@ -1618,6 +2320,8 @@ public void Shutdown() //Run on the background thread public void AvatarLeave(IVoltronSession session) { + SurroundConnection?.AvatarLeave(session.AvatarId); + //Exit lot, Persist the avatars data, remove avatar lock LOG.Info("Avatar "+session.AvatarId+" left lot "+Context.DbId); diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContext.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContext.cs index 80e7f5c7b..85c2f9be9 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotContext.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotContext.cs @@ -1,4 +1,5 @@ -using FSO.Server.Protocol.Gluon.Model; +using FSO.Server.Domain; +using FSO.Server.Protocol.Gluon.Model; namespace FSO.Server.Servers.Lot.Domain { @@ -9,13 +10,29 @@ public class LotContext public int ShardId; public uint ClaimId; public ClaimAction Action; - public bool HighMax; + public bool HighMax; + + public bool SpecialLot + { + get + { + return (Id & (uint)LotIdFlags.SpecialMask) != 0; + } + } + + public bool UnownedLot + { + get + { + return (Id & (uint)LotIdFlags.Unowned) != 0; + } + } public bool JobLot { get { - return (Id & 0x40000000) > 0; + return (Id & (uint)LotIdFlags.JobLot) != 0; } } } diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs index dff26668a..e0397af0b 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotHost.cs @@ -3,12 +3,14 @@ using FSO.Server.Database.DA; using FSO.Server.Database.DA.LotVisitors; using FSO.Server.DataService; +using FSO.Server.Domain; using FSO.Server.Framework.Gluon; using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Electron.Packets; using FSO.Server.Protocol.Gluon.Model; using FSO.Server.Protocol.Gluon.Packets; using FSO.Server.Servers.Lot.Lifecycle; +using FSO.SimAntics; using Ninject; using Ninject.Extensions.ChildKernel; using NLog; @@ -276,13 +278,15 @@ public LotHostEntry TryHost(int id, IGluonSession cityConnection) public bool TryAcceptClaim(int lotId, uint claimId, uint specialId, string previousOwner, ClaimAction openAction) { if (claimId == 0) - { //job lot + { //special lot + var unowned = (lotId & (uint)LotIdFlags.Unowned) != 0; + GetLot(lotId).Bootstrap(new LotContext { - DbId = (int)specialId, //contains job type/grade - Id = (uint)lotId, //lotId contains a "job lot location", not a DbId. + DbId = unowned ? lotId : (int)specialId, //contains job type/grade or location + Id = (uint)lotId, //lotId contains a "job lot / unowned location", not a DbId. ClaimId = claimId, - ShardId = 0, + ShardId = unowned ? (int)specialId : 0, Action = openAction }); return true; @@ -398,14 +402,14 @@ public void Send(uint avatarID, params object[] messages) } } - public void Broadcast(HashSet ignoreIDs, params object[] messages) + public void Broadcast(HashSet clientIDs, params object[] messages) { //TODO: Make this more efficient lock (_Visitors) { foreach (var visitor in _Visitors.Values) { - if (ignoreIDs.Contains(visitor.AvatarId)) continue; + if (clientIDs != null && !clientIDs.Contains(visitor.AvatarId)) continue; try { visitor.Write(messages); @@ -489,7 +493,7 @@ public void Bootstrap(LotContext context) //timeout for the background thread recieving more tasks. private static readonly int BACKGROUND_NOTIFY_TIMEOUT = 2000; //the number of times recieving no background tasks after which we assume the main thread is stuck in an infinite loop. - private static readonly int BACKGROUND_TIMEOUT_ABANDON_COUNT = 4; + private static readonly int BACKGROUND_TIMEOUT_ABANDON_COUNT = 2; private static readonly int BACKGROUND_TIMEOUT_SECONDS = 30; private uint LastTaskRecv = 0; private int BgTimeoutExpiredCount = 0; @@ -498,6 +502,8 @@ public void Bootstrap(LotContext context) private bool BgKilled; private void _DigestBackground() { + VM.UseWorld = false; + while (BgAlive) { if (LastTaskRecv == 0) LastTaskRecv = Epoch.Now; @@ -511,8 +517,13 @@ private void _DigestBackground() if (tasks.Count > 1000) LOG.Error("Surprising number of background tasks for lot with dbid = " + Context.DbId + ": " + tasks.Count); - if (tasks.Count > 0) LastTaskRecv = Epoch.Now; //BgTimeoutExpiredCount = 0; - else if (Epoch.Now - LastTaskRecv > BACKGROUND_TIMEOUT_SECONDS) //++BgTimeoutExpiredCount > BACKGROUND_TIMEOUT_ABANDON_COUNT) + if (tasks.Count > 0) + { + LastTaskRecv = Epoch.Now; + BgTimeoutExpiredCount = 0; + } + + else if (Epoch.Now - LastTaskRecv > BACKGROUND_TIMEOUT_SECONDS && ++BgTimeoutExpiredCount >= BACKGROUND_TIMEOUT_ABANDON_COUNT) { BgTimeoutExpiredCount = int.MinValue; @@ -599,7 +610,7 @@ public void Refresh(IVoltronSession session) public bool TryJoin(IVoltronSession session) { - if (Container.IsAvatarOnLot(session.AvatarId)) + if (Container.IsAvatarOnLot(session.AvatarId, _Visitors)) { session.Write(new FSOVMProtocolMessage(true, "11", "12")); return false; //already on the lot. @@ -615,7 +626,7 @@ public bool TryJoin(IVoltronSession session) using (var da = DAFactory.Get()) { var avatar = da.Avatars.Get(session.AvatarId); - if (avatar.moderation_level == 0 && !Context.JobLot) + if (avatar.moderation_level == 0 && !Context.SpecialLot) { if (da.Roommates.Get(session.AvatarId, Context.DbId) == null) { @@ -626,7 +637,7 @@ public bool TryJoin(IVoltronSession session) } } - session.SetAttribute("currentLot", ((Context.Id & 0x40000000) > 0)?(int)Context.Id:Context.DbId); + session.SetAttribute("currentLot", ((Context.Id & (uint)LotIdFlags.SpecialMask) > 0)?(int)Context.Id:Context.DbId); _Visitors.Add(session.AvatarId, session); SyncNumVisitors(); @@ -642,6 +653,36 @@ private void SyncNumVisitors() Host.Sync(Context, Model); } + public void ReleaseDbAvatarClaim(IVoltronSession session) + { + if ((bool)(session.GetAttribute("releasedClaim") ?? false)) + { + return; + } + + using (var db = DAFactory.Get()) + { + //return claim to the city we got it from. + + if ((bool)(session.GetAttribute("returnClaim") ?? true)) + db.AvatarClaims.Claim(session.AvatarClaimId, Config.Call_Sign, (string)session.GetAttribute("cityCallSign"), 0); + else + db.AvatarClaims.Delete(session.AvatarClaimId, Config.Call_Sign); + + session.SetAttribute("releasedClaim", true); + } + } + + public void ReleaseDbAvatarClaim(uint id) + { + IVoltronSession session = null; + lock (_Visitors) + { + _Visitors.TryGetValue(id, out session); + } + ReleaseDbAvatarClaim(session); + } + public void ReleaseAvatarClaim(uint id) { IVoltronSession session = null; @@ -667,21 +708,13 @@ public void ReleaseAvatarClaim(IVoltronSession session) { Mode = MatchmakerNotifyType.RemoveAvatar, AvatarID = session.AvatarId, - LotID = Context.Id & 0x3FFFFFFF + LotID = Context.Id & (uint)LotIdFlags.NormalMask }); } InBackground(() => { - using (var db = DAFactory.Get()) - { - //return claim to the city we got it from. - - if ((bool)(session.GetAttribute("returnClaim") ?? true)) - db.AvatarClaims.Claim(session.AvatarClaimId, Config.Call_Sign, (string)session.GetAttribute("cityCallSign"), 0); - else - db.AvatarClaims.Delete(session.AvatarClaimId, Config.Call_Sign); - } + ReleaseDbAvatarClaim(session); if (session.GetAttribute("visitId") != null) { @@ -697,7 +730,7 @@ public void ReleaseAvatarClaim(IVoltronSession session) public void Shutdown() { if (!ShuttingDown) ForceShutdown(true); - Host.RemoveLot(((Context.Id & 0x40000000) > 0)?(int)Context.Id:Context.DbId); + Host.RemoveLot(((Context.Id & (uint)LotIdFlags.SpecialMask) > 0)?(int)Context.Id:Context.DbId); SetOnline(false); SetSpotlight(false); ReleaseLotClaim(); @@ -748,7 +781,7 @@ public void ReleaseLotClaim() { Type = Protocol.Gluon.Model.ClaimType.LOT, ClaimId = Context.ClaimId, - EntityId = ((Context.Id & 0x40000000) > 0) ? (int)Context.Id : Context.DbId, + EntityId = ((Context.Id & (uint)LotIdFlags.SpecialMask) > 0) ? (int)Context.Id : Context.DbId, FromOwner = Config.Call_Sign }); }catch(Exception ex) @@ -793,7 +826,7 @@ public void SetSpotlight(bool on) public void RecordStartVisit(IVoltronSession session, DbLotVisitorType visitorType) { - if (Context.JobLot) return; + if (Context.SpecialLot) return; using (var da = DAFactory.Get()) { var id = da.LotVisits.Visit(session.AvatarId, visitorType, Context.DbId); @@ -805,7 +838,7 @@ public void RecordStartVisit(IVoltronSession session, DbLotVisitorType visitorTy public void UpdateActiveVisitRecords() { - if (Context.JobLot) return; + if (Context.SpecialLot) return; var visitIds = new List(); lock (_Visitors) @@ -835,9 +868,11 @@ public void UpdateActiveVisitRecords() public interface ILotHost { void Send(uint avatarID, params object[] messages); - void Broadcast(HashSet ignoreIDs, params object[] messages); + void Broadcast(HashSet clientIDs, params object[] messages); void DropClient(uint avatarID); void InBackground(Callback cb); + void ReleaseDbAvatarClaim(IVoltronSession session); + void ReleaseDbAvatarClaim(uint avatarID); void ReleaseAvatarClaim(IVoltronSession session); void ReleaseAvatarClaim(uint avatarID); void Shutdown(); diff --git a/TSOClient/FSO.Server/Servers/Lot/Domain/LotServerGlobalLink.cs b/TSOClient/FSO.Server/Servers/Lot/Domain/LotServerGlobalLink.cs index 51551634e..12ba900cd 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Domain/LotServerGlobalLink.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Domain/LotServerGlobalLink.cs @@ -38,6 +38,8 @@ public class LotServerGlobalLink : IVMTSOGlobalLink private CityConnections City; private bool WaitingOnArch; + public bool Readonly; + public LotServerGlobalLink(LotServerConfiguration config, IDAFactory da, LotContext context, ILotHost host, CityConnections city) { DAFactory = da; @@ -424,7 +426,6 @@ private void SaveInventoryState(bool isNew, uint objectPID, VMStandaloneObjectMa } var objStr = objectPID.ToString("x8"); //make sure this exists - Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Objects/" + objStr + "/")); byte[] data; using (var stream = new MemoryStream()) { @@ -432,31 +433,42 @@ private void SaveInventoryState(bool isNew, uint objectPID, VMStandaloneObjectMa state.SerializeInto(writer); data = stream.ToArray(); } - var file = File.Open(Path.Combine(Config.SimNFS, "Objects/" + objStr + "/inventoryState.fsoo"), FileMode.Create); - if (runSync) + using (var db = DAFactory.Get()) { - file.Write(data, 0, data.Length); - using (var db = DAFactory.Get()) + if (db.Objects.SetDbObjectState(objectPID, data)) { - //todo: race where inventory object could potentially be placed on the lot before the old instance of it is deleted - //probably just block objects with same persist id from being placed. db.Objects.UpdatePersistState(objectPID, dbState); callback(true, objectPID); } - file.Close(); - } - else - { - file.WriteAsync(data, 0, data.Length).ContinueWith((x) => + else { - using (var db = DAFactory.Get()) + Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Objects/" + objStr + "/")); + var file = File.Open(Path.Combine(Config.SimNFS, "Objects/" + objStr + "/inventoryState.fsoo"), FileMode.Create); + + if (runSync) { + file.Write(data, 0, data.Length); + + //todo: race where inventory object could potentially be placed on the lot before the old instance of it is deleted + //probably just block objects with same persist id from being placed. db.Objects.UpdatePersistState(objectPID, dbState); callback(true, objectPID); + file.Close(); } - file.Close(); - }); + else + { + file.WriteAsync(data, 0, data.Length).ContinueWith((x) => + { + using (var db2 = DAFactory.Get()) + { + db2.Objects.UpdatePersistState(objectPID, dbState); + callback(true, objectPID); + } + file.Close(); + }); + } + } } } catch (Exception e) @@ -616,14 +628,17 @@ private void RetrieveDbObject(VM vm, IDA db, DbObject obj, uint ownerID, bool se byte[] dat = null; try { - var objStr = objectPID.ToString("x8"); - var path = Path.Combine(Config.SimNFS, "Objects/" + objStr + "/inventoryState.fsoo"); - - //if path does not exist, will throw FileNotFoundException - using (var file = File.Open(path, FileMode.Open)) + if (!db.Objects.GetDbObjectState(objectPID, out dat)) { - dat = new byte[file.Length]; - file.Read(dat, 0, dat.Length); + var objStr = objectPID.ToString("x8"); + var path = Path.Combine(Config.SimNFS, "Objects/" + objStr + "/inventoryState.fsoo"); + + //if path does not exist, will throw FileNotFoundException + using (var file = File.Open(path, FileMode.Open)) + { + dat = new byte[file.Length]; + file.Read(dat, 0, dat.Length); + } } } catch (Exception e) @@ -705,6 +720,12 @@ private void UpdateInventoryFor(VM vm, uint targetPID) public void DeleteObject(VM vm, uint objectPID, VMAsyncDeleteObjectCallback callback) { + if (Readonly) + { + callback(false); + return; + } + Host.InBackground(() => { if (objectPID == 0) callback(true); @@ -723,6 +744,11 @@ public void DeleteObject(VM vm, uint objectPID, VMAsyncDeleteObjectCallback call public void SetSpotlightStatus(VM vm, bool on) { + if (Readonly) + { + return; + } + Host.SetSpotlight(on); } diff --git a/TSOClient/FSO.Server/Servers/Lot/Handlers/CityServerAuthenticationHandler.cs b/TSOClient/FSO.Server/Servers/Lot/Handlers/CityServerAuthenticationHandler.cs index 25599796a..a8da6d186 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Handlers/CityServerAuthenticationHandler.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Handlers/CityServerAuthenticationHandler.cs @@ -23,6 +23,12 @@ public void Handle(IGluonSession session, RequestClientSession request) session.Write(new RequestChallenge() { CallSign = session.CallSign, PublicHost = session.PublicHost, InternalHost = session.InternalHost }); } + public void Handle(IGluonSession session, RequestClientSessionArchive request) + { + //Same as above, don't really care about archive stuff for gluon auth + session.Write(new RequestChallenge() { CallSign = session.CallSign, PublicHost = session.PublicHost, InternalHost = session.InternalHost }); + } + public void Handle(IGluonSession session, RequestChallengeResponse challenge) { var rawSession = ((CityConnection)session); diff --git a/TSOClient/FSO.Server/Servers/Lot/Lifecycle/CityConnections.cs b/TSOClient/FSO.Server/Servers/Lot/Lifecycle/CityConnections.cs index 737200b9c..cd7378e73 100644 --- a/TSOClient/FSO.Server/Servers/Lot/Lifecycle/CityConnections.cs +++ b/TSOClient/FSO.Server/Servers/Lot/Lifecycle/CityConnections.cs @@ -15,6 +15,7 @@ namespace FSO.Server.Servers.Lot.Lifecycle { public class CityConnections { + public static bool UseCounters = true; private static Logger LOG = LogManager.GetCurrentClassLogger(); private Dictionary Connections; private Thread ConnectionWatcher; @@ -30,20 +31,24 @@ public class CityConnections public CityConnections(LotServerConfiguration config, IKernel kernel) { Config = config; - try + if (UseCounters) { - CpuCounter = new PerformanceCounter(); - CpuCounter.CategoryName = "Processor"; - CpuCounter.CounterName = "% Processor Time"; - CpuCounter.InstanceName = "_Total"; + try + { + CpuCounter = new PerformanceCounter(); + CpuCounter.CategoryName = "Processor"; + CpuCounter.CounterName = "% Processor Time"; + CpuCounter.InstanceName = "_Total"; - if (PerformanceCounterCategory.Exists("Processor")) + if (PerformanceCounterCategory.Exists("Processor")) + { + var firstValue = CpuCounter.NextValue(); + } + } + catch { - var firstValue = CpuCounter.NextValue(); + LOG.Info("Performance counters are not supported on this platform, running without."); } - } catch - { - LOG.Info("Performance counters are not supported on this platform, running without."); } Connections = new Dictionary(); @@ -107,6 +112,15 @@ private void CheckConnections() { LOG.Info("Attempting connection!"); connection.Connect(); + CityConnectionEvent connectionEvent = null; + + connectionEvent = (conn) => + { + conn.OnConnected -= connectionEvent; + conn.Write(capacity); + }; + + connection.OnConnected += connectionEvent; }else{ connection.Write(capacity); } @@ -248,6 +262,11 @@ public void SetAttribute(string key, object value) { } + public bool HasModerationLevel(int threshold) + { + return true; + } + public void DemandAvatar(uint id, AvatarPermissions permission) { } diff --git a/TSOClient/FSO.Server/Servers/Lot/LotServer.cs b/TSOClient/FSO.Server/Servers/Lot/LotServer.cs index 8ec6b5fba..7dad36a07 100644 --- a/TSOClient/FSO.Server/Servers/Lot/LotServer.cs +++ b/TSOClient/FSO.Server/Servers/Lot/LotServer.cs @@ -1,20 +1,22 @@ using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Hosts; using FSO.Server.Database.DA.Lots; +using FSO.Server.Domain; using FSO.Server.Framework.Aries; using FSO.Server.Framework.Voltron; using FSO.Server.Protocol.Aries.Packets; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Voltron.Packets; using FSO.Server.Servers.Lot.Domain; using FSO.Server.Servers.Lot.Handlers; using FSO.Server.Servers.Lot.Lifecycle; +using FSO.Server.Servers.Lot.Surround; +using FSO.Server.Servers.Shared.Handlers; using Ninject; using NLog; using System; using System.Linq; using System.Threading.Tasks; -using FSO.Server.Database.DA.Hosts; -using FSO.Server.Servers.Shared.Handlers; -using FSO.Server.Protocol.Voltron.Packets; -using FSO.Server.Protocol.Electron.Packets; namespace FSO.Server.Servers.Lot { @@ -36,6 +38,7 @@ public LotServer(LotServerConfiguration config, Ninject.IKernel kernel) : base(c Kernel.Bind().ToConstant(Config); Kernel.Bind().To().InSingletonScope(); Kernel.Bind().To().InSingletonScope(); + Kernel.Bind().To().InSingletonScope(); Kernel.Bind().ToConstant(this); LotLivenessTimer.AutoReset = true; @@ -130,7 +133,7 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje if (ticket != null) { uint location = 0; - if ((ticket.lot_id & 0x40000000) > 0) location = (uint)ticket.lot_id; + if ((ticket.lot_id & (uint)LotIdFlags.SpecialMask) > 0) location = (uint)ticket.lot_id; // job lot or unowned else { var lot = da.Lots.Get(ticket.lot_id); @@ -165,15 +168,31 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje newSession.SetAttribute("cityCallSign", ticket.avatar_claim_owner); + if (packet.ServiceIdent == "JLT") + { + // Join lot with transition + // A followup request with the transition will be made. + newSession.SetAttribute("joinLotTransition", true); + newSession.SetAttribute("joinLotId", ticket.lot_id); + + // If the session hasn't tried to join the lot in 5 seconds, close it. + Task.Delay(5000).ContinueWith((task) => + { + if ((bool)newSession.GetAttribute("joinLotTransition")) + { + newSession.SetAttribute("joinLotTransition", false); + + ReturnClaim(newSession); + } + }); + + return; + } + //Try and join the lot, no reason to keep this connection alive if you can't get in if (!Lots.TryJoin(ticket.lot_id, newSession)) { - newSession.Close(); - using (var db = DAFactory.Get()) - { - //return claim to the city we got it from. - db.AvatarClaims.Claim(newSession.AvatarClaimId, Config.Call_Sign, (string)newSession.GetAttribute("cityCallSign"), 0); - } + ReturnClaim(newSession); } return; } @@ -184,10 +203,43 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje rawSession.Close(); } + private void ReturnClaim(IVoltronSession voltronSession) + { + voltronSession.Close(); + using (var db = DAFactory.Get()) + { + //return claim to the city we got it from. + db.AvatarClaims.Claim(voltronSession.AvatarClaimId, Config.Call_Sign, (string)voltronSession.GetAttribute("cityCallSign"), 0); + } + } + + public void HandleTransition(IVoltronSession voltronSession, JoinLotWithTransitionRequest joinTransition) + { + var transition = voltronSession.GetAttribute("joinLotTransition"); + if (transition != null && (bool)transition) + { + var lot_id = (int)voltronSession.GetAttribute("joinLotId"); + + voltronSession.SetAttribute("joinLotTransition", false); + voltronSession.SetAttribute("lotTransitionInfo", joinTransition.Transition); + + if (!Lots.TryJoin(lot_id, voltronSession)) + { + ReturnClaim(voltronSession); + } + } + } + protected override void RouteMessage(IAriesSession session, object message) { - if(session is IVoltronSession) + if(session is IVoltronSession voltronSession) { + if (message is JoinLotWithTransitionRequest joinTransition) + { + HandleTransition(voltronSession, joinTransition); + return; + } + //Route to a specific lot Lots.RouteMessage(session as IVoltronSession, message); return; diff --git a/TSOClient/FSO.Server/Servers/Lot/LotServerConfiguration.cs b/TSOClient/FSO.Server/Servers/Lot/LotServerConfiguration.cs index 0887874df..abe6f0fcb 100644 --- a/TSOClient/FSO.Server/Servers/Lot/LotServerConfiguration.cs +++ b/TSOClient/FSO.Server/Servers/Lot/LotServerConfiguration.cs @@ -1,26 +1,43 @@ -using FSO.Server.Framework.Aries; +using FSO.Common; +using FSO.Server.Framework.Aries; +using Newtonsoft.Json; namespace FSO.Server.Servers.Lot { public class LotServerConfiguration : AbstractAriesServerConfig { + [JsonProperty("max_lots")] public int Max_Lots = 1; + [JsonProperty("tick_rate_divider")] + public int Tick_Rate_Divider = 4; + [JsonProperty("simNFS")] public string SimNFS; + [JsonProperty("ringBufferSize")] public int RingBufferSize = 10; + [JsonProperty("timeout_no_auth")] public bool Timeout_No_Auth = true; + [JsonProperty("logJobLots")] public bool LogJobLots = false; //Which cities to provide lot hosting for + [JsonProperty("cities")] public LotServerConfigurationCity[] Cities; //How often to reconnect lost connections to city servers and report capacity + [JsonProperty("cityReportingInterval")] public int CityReportingInterval = 10000; + + // Copied from base config + public bool AllOpenable; + public ArchiveConfiguration Archive; } public class LotServerConfigurationCity { + [JsonProperty("id")] public int ID; + [JsonProperty("host")] public string Host; } } diff --git a/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundHost.cs b/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundHost.cs new file mode 100644 index 000000000..6dac0e367 --- /dev/null +++ b/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundHost.cs @@ -0,0 +1,228 @@ +using FSO.Common.Domain.Realestate; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Servers.Lot.Domain; +using System.Diagnostics; + +namespace FSO.Server.Servers.Lot.Surround +{ + internal class LiveSurroundHost : IDisposable + { + private Lock ConnectionsLock = new(); + private readonly Dictionary ConnectionsById = []; + private readonly Dictionary> AdjacencyById = []; + private long LastTick; + private bool Active = true; + + private uint TickID = 0; + + private readonly Dictionary EvaluatedLots = []; + + public LiveSurroundHost() + { + var thread = new Thread(Run); + thread.IsBackground = true; + + thread.Start(); + } + + private uint OffsetCoords(uint location, int x, int y) + { + var loc = MapCoordinates.Unpack(location); + var loc2 = MapCoordinates.Offset(loc, x, y); + return MapCoordinates.Pack(loc2.X, loc2.Y); + } + + public void Run() + { + var tickPerMs = Stopwatch.Frequency / 1000; + LastTick = Stopwatch.GetTimestamp(); + while (Active) + { + SendSurrounds(); + + long nextTick = LastTick + (33 * tickPerMs); + long sleepTime = nextTick - Stopwatch.GetTimestamp(); + + if (sleepTime < (-33 * tickPerMs)) + { + // Too far in the past, reset the timer. + sleepTime = 0; + } + + if (sleepTime > 0) + { + Thread.Sleep((int)Math.Max(0, sleepTime / tickPerMs)); + } + + LastTick = nextTick; + } + } + + private void SendSurrounds() + { + var evaluatedLots = EvaluatedLots; + evaluatedLots.Clear(); + + lock (ConnectionsLock) + { + // Any lots with adjacency should send their tick data to the adjacent lots. + foreach (var target in AdjacencyById) + { + if (ConnectionsById.TryGetValue(target.Key, out var conn)) + { + // Build a broadcast packet for users in target, using all the surround data + FSOVMSurroundPuppets puppets = new(); + var lots = new List(); + + bool adjUpdated = conn.ConsumeDirty(); + + foreach (var adj in target.Value) + { + if (!evaluatedLots.TryGetValue(adj.LotLocation, out SurroundPuppetLot? value)) + { + value = adj.PullTick(); + + evaluatedLots[adj.LotLocation] = value; + } + + if (value.HasValue) + { + var lot = value.Value; + + if (adjUpdated) + { + lot.ForceDirty = true; + } + + lots.Add(lot); + } + } + + puppets.Ticks = [ + new SurroundPuppetTick() + { + TickID = TickID, + Lots = [.. lots] + } + ]; + + conn.Broadcast(puppets); + } + } + } + + TickID++; + } + + public LiveSurroundLotConnection Connect(uint location, LotContainer lotContainer, ILotHost lotHost) + { + var connection = new LiveSurroundLotConnection(this, lotContainer, lotHost, location); + + lock (ConnectionsLock) + { + if (ConnectionsById.ContainsKey(location)) + { + Disconnect(location); + } + + ConnectionsById[location] = connection; + + // Try and work out adjacency. + for (int y = -1; y < 2; y++) + { + for (int x = -1; x < 2; x++) + { + if (x == 0 && y == 0) continue; + + uint otherLocation = OffsetCoords(location, x, y); + if (ConnectionsById.TryGetValue(otherLocation, out var other)) + { + if (!AdjacencyById.TryGetValue(otherLocation, out var otherAdj)) + { + otherAdj = []; + AdjacencyById.Add(otherLocation, otherAdj); + } + + otherAdj.Add(connection); + + if (!AdjacencyById.TryGetValue(location, out var adj)) + { + adj = []; + AdjacencyById.Add(location, adj); + } + + adj.Add(other); + + connection.NotifyAdjacent(); + other.NotifyAdjacent(); + } + } + } + } + + return connection; + } + + public void Disconnect(uint location) + { + lock (ConnectionsLock) + { + if (ConnectionsById.TryGetValue(location, out var connection)) + { + ConnectionsById.Remove(location); + + if (AdjacencyById.TryGetValue(location, out var adj)) + { + AdjacencyById.Remove(location); + + foreach (var otherConn in adj) + { + if (AdjacencyById.TryGetValue(otherConn.LotLocation, out var adj2)) + { + adj2.Remove(connection); + + if (adj2.Count == 0) + { + AdjacencyById.Remove(otherConn.LotLocation); + } + } + } + } + } + } + } + + public bool HollowBroadcast(LiveSurroundLotConnection conn, Action> generateHollow) + { + LiveSurroundLotConnection[] adj = null; + + lock (ConnectionsLock) + { + if (AdjacencyById.TryGetValue(conn.LotLocation, out var adjList) && adjList.Count > 0) + { + adj = [.. adjList]; + } + } + + if (adj != null) + { + generateHollow((data) => + { + foreach (var conn2 in adj) + { + conn2.SendHollowLotData(conn.LotLocation, data); + } + }); + + return true; + } + + return false; + } + + public void Dispose() + { + Active = false; + } + } +} diff --git a/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundLotConnection.cs b/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundLotConnection.cs new file mode 100644 index 000000000..c8494d8ce --- /dev/null +++ b/TSOClient/FSO.Server/Servers/Lot/Surround/LiveSurroundLotConnection.cs @@ -0,0 +1,203 @@ +using FSO.Common.Model; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Servers.Lot.Domain; +using FSO.SimAntics; + +namespace FSO.Server.Servers.Lot.Surround +{ + internal class LiveSurroundLotConnection : IDisposable + { + private const int QUEUE_LENGTH_MAX = 3; + private const int QUEUE_LENGTH_RESET = 1; + + private readonly LiveSurroundHost Host; + private readonly ILotHost LotHost; + private readonly LotContainer LotContainer; + public readonly uint LotLocation; + private readonly Dictionary PuppetData = []; + private readonly HashSet ExpectedAvatars = []; + + private Lock QueueLock = new(); + private Queue TickQueue = []; + private SurroundPuppetLot? LastTick; + + private int AdjacencyCount; + + private Lock PlayersLock = new(); + private HashSet NewPlayers = []; + private HashSet Players = []; + + private HashSet PlayersCopy = []; + private HashSet NewPlayersCopy = []; + + private bool Dirty = false; + + public LiveSurroundLotConnection(LiveSurroundHost host, LotContainer lotContainer, ILotHost lotHost, uint lotLocation) + { + Host = host; + LotContainer = lotContainer; + LotHost = lotHost; + LotLocation = lotLocation; + } + + public void SubmitTick(VM vm, bool force) + { + if (AdjacencyCount <= 0 && !force) + { + return; + } + + ExpectedAvatars.Clear(); + ExpectedAvatars.UnionWith(PuppetData.Keys); + + foreach (var ava in vm.Context.ObjectQueries.Avatars) + { + var puppet = ((VMAvatar)ava).GetSurroundPuppet(); + + if (PuppetData.TryGetValue(puppet.PersistID, out var existing)) + { + puppet.CalculateDelta(in existing); + } + else + { + puppet.Delta = SurroundPuppetDelta.All; + } + + PuppetData[puppet.PersistID] = puppet; + ExpectedAvatars.Remove(puppet.PersistID); + } + + foreach (var id in ExpectedAvatars) + { + // These avatars weren't updated, so they must be deleted. + PuppetData.Remove(id); + } + + var tick = new SurroundPuppetLot() + { + LotLocation = LotLocation, + Puppets = PuppetData.Values.ToArray() + }; + + QueueTick(tick); + } + + private void QueueTick(SurroundPuppetLot tick) + { + lock (QueueLock) + { + TickQueue.Enqueue(tick); + tick.Outdated = true; + LastTick = tick; + if (TickQueue.Count > QUEUE_LENGTH_MAX) + { + while (TickQueue.Count > QUEUE_LENGTH_RESET) + { + TickQueue.Dequeue(); + } + } + } + } + + public SurroundPuppetLot? PullTick() + { + lock (QueueLock) + { + if (TickQueue.TryDequeue(out var tick)) + { + return tick; + } + + if (LastTick.HasValue) + { + return LastTick.Value; + } + } + + return null; + } + + public void NotifyAdjacent() + { + Dirty = true; + Interlocked.Increment(ref AdjacencyCount); + } + + public void NotifyAdjacentDecrement() + { + Interlocked.Decrement(ref AdjacencyCount); + } + + public bool ConsumeDirty() + { + bool dirty = Dirty; + Dirty = false; + return dirty; + } + + public void Broadcast(FSOVMSurroundPuppets broadcast) + { + // Note: this is called from the LiveSurroundHost thread. + + lock (PlayersLock) + { + PlayersCopy.Clear(); + PlayersCopy.UnionWith(Players); + + NewPlayersCopy.Clear(); + if (NewPlayers.Count > 0) + { + NewPlayersCopy.UnionWith(NewPlayers); + + Players.UnionWith(NewPlayers); + NewPlayers.Clear(); + } + } + + // Broadcast version with deltas for most people + if (PlayersCopy.Count > 0) + { + LotHost.Broadcast(PlayersCopy, broadcast); + } + + // Without deltas for the host + if (NewPlayersCopy.Count > 0) + { + var noDelta = new FSOVMSurroundPuppets() { Ticks = broadcast.Ticks, NewPlayer = true }; + LotHost.Broadcast(NewPlayersCopy, noDelta); + } + } + + public void AvatarJoin(uint persistId) + { + lock (PlayersLock) + { + NewPlayers.Add(persistId); + } + } + + public void AvatarLeave(uint persistId) + { + lock (PlayersLock) + { + NewPlayers.Remove(persistId); + Players.Remove(persistId); + } + } + + public bool HollowBroadcast(Action> generateHollow) + { + return Host.HollowBroadcast(this, generateHollow); + } + + public void SendHollowLotData(uint location, byte[] data) + { + LotContainer.SendHollowLotData(location, data); + } + + public void Dispose() + { + Host.Disconnect(LotLocation); + } + } +} diff --git a/TSOClient/FSO.Server/Servers/Tasks/Domain/BonusTask.cs b/TSOClient/FSO.Server/Servers/Tasks/Domain/BonusTask.cs index f5c1877c8..065db941f 100644 --- a/TSOClient/FSO.Server/Servers/Tasks/Domain/BonusTask.cs +++ b/TSOClient/FSO.Server/Servers/Tasks/Domain/BonusTask.cs @@ -98,7 +98,7 @@ public void Run(TaskContext context) float multiplier = 1; DbTuning cattuning; - if (vistorHourScale.TryGetValue((int)x.category, out cattuning)) + if (vistorHourScale.TryGetValue((int)(LotCategory)x.category, out cattuning)) { multiplier = cattuning.value; } diff --git a/TSOClient/FSO.Server/Servers/Tasks/TaskEngine.cs b/TSOClient/FSO.Server/Servers/Tasks/TaskEngine.cs index 0d67f3cdb..157843095 100644 --- a/TSOClient/FSO.Server/Servers/Tasks/TaskEngine.cs +++ b/TSOClient/FSO.Server/Servers/Tasks/TaskEngine.cs @@ -225,16 +225,23 @@ public class TaskEngineEntry public class TaskRunOptions { + [JsonProperty("task")] public string Task; + [JsonProperty("allowTaskOverlap")] public bool AllowTaskOverlap = false; + [JsonProperty("run_if_missed")] public bool Run_If_Missed = false; + [JsonProperty("timeout")] public int Timeout = 3600; //1hr + [JsonProperty("shard_id")] public int? Shard_Id; + [JsonProperty("parameter")] public dynamic Parameter; } public class ScheduledTaskRunOptions : TaskRunOptions { + [JsonProperty("cron")] public string Cron; public CronSchedule CronSchedule; } diff --git a/TSOClient/FSO.Server/Servers/Tasks/TaskServer.cs b/TSOClient/FSO.Server/Servers/Tasks/TaskServer.cs index 847f4ebeb..9e83bdd48 100644 --- a/TSOClient/FSO.Server/Servers/Tasks/TaskServer.cs +++ b/TSOClient/FSO.Server/Servers/Tasks/TaskServer.cs @@ -8,6 +8,7 @@ using FSO.Server.Servers.Shared.Handlers; using FSO.Server.Servers.Tasks.Handlers; using FSO.Server.Database.DA.Tasks; +using Newtonsoft.Json; namespace FSO.Server.Servers.Tasks { @@ -67,16 +68,24 @@ protected override void HandleVoltronSessionResponse(IAriesSession session, obje public class TaskServerConfiguration : AbstractAriesServerConfig { + [JsonProperty("enabled")] public bool Enabled { get; set; } = true; + [JsonProperty("schedule")] public List Schedule; + [JsonProperty("tuning")] public TaskTuning Tuning { get; set; } } + // Note: the tuning config types use the json casing so don't need the property attributes. public class TaskTuning { + [JsonProperty("bonus")] public BonusTaskTuning Bonus { get; set; } + [JsonProperty("shutdown")] public ShutdownTaskTuning Shutdown { get; set; } + [JsonProperty("jobBalance")] public JobBalanceTuning JobBalance { get; set; } + [JsonProperty("birthdayGift")] public BirthdayGiftTaskTuning BirthdayGift { get; set; } } } diff --git a/TSOClient/FSO.Server/Servers/UserApi/ApiServerConfiguration.cs b/TSOClient/FSO.Server/Servers/UserApi/ApiServerConfiguration.cs index 39ea6c7c8..7d32a1594 100644 --- a/TSOClient/FSO.Server/Servers/UserApi/ApiServerConfiguration.cs +++ b/TSOClient/FSO.Server/Servers/UserApi/ApiServerConfiguration.cs @@ -1,4 +1,5 @@ using FSO.Server.Common.Config; +using Newtonsoft.Json; using System.Collections.Generic; namespace FSO.Server.Servers.UserApi @@ -8,38 +9,53 @@ public class ApiServerConfiguration /// /// If true, the API server will attempt to bind /// + [JsonProperty("enabled")] public bool Enabled { get; set; } /// /// Hostname bindings /// + [JsonProperty("bindings")] public List Bindings { get; set; } - + /// /// How long an auth ticket is valid for /// + [JsonProperty("authTicketDuration")] public int AuthTicketDuration = 300; /// /// If non-null, the user must provide this key to register an account. /// + [JsonProperty("regkey")] public string Regkey { get; set; } /// /// If true, only authentication from moderators and admins will be accepted /// + [JsonProperty("maintenance")] public bool Maintenance { get; set; } + [JsonProperty("updateUrl")] public string UpdateUrl { get; set; } + [JsonProperty("cdnUrl")] public string CDNUrl { get; set; } + [JsonProperty("smtpHost")] public string SmtpHost { get; set; } + [JsonProperty("smtpPort")] public int SmtpPort { get; set; } + [JsonProperty("smtpPassword")] public string SmtpPassword { get; set; } + [JsonProperty("smtpUser")] public string SmtpUser { get; set; } + [JsonProperty("useProxy")] public bool UseProxy { get; set; } = true; + [JsonProperty("awsConfig")] public AWSConfig AwsConfig { get; set; } + [JsonProperty("githubConfig")] public GithubConfig GithubConfig { get; set; } + [JsonProperty("filesystemConfig")] public FilesystemConfig FilesystemConfig { get; set; } } diff --git a/TSOClient/FSO.Server/Servers/UserApi/UserApi.cs b/TSOClient/FSO.Server/Servers/UserApi/UserApi.cs index fdbb33676..31454e1ba 100644 --- a/TSOClient/FSO.Server/Servers/UserApi/UserApi.cs +++ b/TSOClient/FSO.Server/Servers/UserApi/UserApi.cs @@ -1,9 +1,5 @@ using System; using FSO.Server.Common; -using Microsoft.Owin.Hosting; -using System.Web.Http; -using Owin; -using System.Collections.Specialized; using Ninject; using FSO.Server.Domain; diff --git a/TSOClient/FSO.Server/ToolArchiveConvert.cs b/TSOClient/FSO.Server/ToolArchiveConvert.cs new file mode 100644 index 000000000..9ef9583d2 --- /dev/null +++ b/TSOClient/FSO.Server/ToolArchiveConvert.cs @@ -0,0 +1,139 @@ +using FSO.Server.Database.DA; +using Ninject; +using NLog; +using System; + +namespace FSO.Server +{ + internal class ToolArchiveConvert : ITool + { + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DAFactory; + + private string ArchiveUsersCreate = @"ALTER TABLE `fso_users` +ADD COLUMN `display_name` varchar(100) NOT NULL DEFAULT '0'; +ALTER TABLE `fso_users` +ADD COLUMN `is_verified` tinyint(3) NOT NULL DEFAULT 1; +ALTER TABLE `fso_users` +ADD COLUMN `shared_user` tinyint(3) NOT NULL DEFAULT 1; +CREATE INDEX `fso_users_display_name` ON `fso_users`(`display_name`);"; + + private string ArchiveFeaturedCreate = @"CREATE TABLE `fso_archive_featured` ( + `id` INTEGER, + `name` TEXT NOT NULL, + `lot_id` INTEGER NOT NULL, + `category` INTEGER NOT NULL, + `description` TEXT NOT NULL, + `shard_id` INTEGER NOT NULL, + PRIMARY KEY(`id` AUTOINCREMENT) + CONSTRAINT `fso_featured_shard_fk` FOREIGN KEY(`shard_id`) REFERENCES `fso_shards`(`shard_id`) ON DELETE CASCADE +); +CREATE INDEX fso_archive_featured_category_shard_idx ON fso_archive_featured (category, shard_id); +"; + + private string ArchiveRecentsCreate = @"CREATE TABLE `fso_archive_recents` ( + `user_id` INTEGER NOT NULL, + `avatar_id` INTEGER NOT NULL, + `last_timestamp` datetime NOT NULL DEFAULT current_timestamp, + PRIMARY KEY(`user_id`, `avatar_id`), + CONSTRAINT `fso_recent_user_fk` FOREIGN KEY(`user_id`) REFERENCES `fso_users`(`user_id`) ON DELETE CASCADE, + CONSTRAINT `fso_recent_avatar_fk` FOREIGN KEY(`avatar_id`) REFERENCES `fso_avatars`(`avatar_id`) ON DELETE CASCADE +); +CREATE INDEX fso_archive_recents_user_idx ON fso_archive_recents (user_id); +"; + + private string ArchiveLotsFlags = @"ALTER TABLE `fso_lots` +ADD COLUMN `archive_flags` tinyint(3) NOT NULL DEFAULT 0;"; + + private string User1Update = "UPDATE `fso_users` SET username='archive', register_date=0, email='unused', register_ip='0', last_ip='0', client_id='0', last_login=0 WHERE user_id=1;"; + + public ToolArchiveConvert(IDAFactory factory) + { + this.DAFactory = factory; + } + + private void RunCommand(SqlDA da, string sql) + { + var context = da.Context; + + var command = context.Connection.CreateCommand(); + + command.CommandText = sql; + + try + { + var result = command.ExecuteNonQuery(); + } + catch (Exception e) + { + throw e; + } + } + + public int Run() + { + using (var da = (SqlDA)DAFactory.Get()) + { + LOG.Info("Adding archive columns to fso_users"); + + RunCommand(da, ArchiveUsersCreate); + + LOG.Info("Adding featured lots table"); + + RunCommand(da, ArchiveFeaturedCreate); + + LOG.Info("Adding recent avatars table"); + + RunCommand(da, ArchiveRecentsCreate); + + LOG.Info("Adding lot archive flags"); + + RunCommand(da, ArchiveLotsFlags); + + LOG.Info("Removing avatar limit triggers"); + + RunCommand(da, "DROP TRIGGER `fso_avatars_BEFORE_INSERT`;"); + + LOG.Info("Repurposing user 1 as the archive shared user"); + + // TODO: If user 1 doesn't exist, create it + // TODO: avoid username collision on "archive"? + + RunCommand(da, User1Update); + + LOG.Info("Repointing every reference to user 1"); + + // already cleared by anon trim: fso_auth_attempts, fso_ip_ban, fso_nhood_ban, fso_auth_tickets, fso_lot_server_tickets, fso_shard_tickets + + // fso_avatars + + RunCommand(da, "UPDATE `fso_avatars` SET user_id=1;"); + + // fso_event_participation + + RunCommand(da, "DELETE FROM `fso_event_participation`"); // I don't think there's any reason to keep this around. + + // fso_global_cooldowns + + RunCommand(da, "DELETE FROM `fso_global_cooldowns`"); // I don't think there's any reason to keep this around. + + // fso_mayor_ratings (from/to, from avatar is lost) + + // TODO: how to get past the (from user + to avatar) unique constraint + //RunCommand(da, "UPDATE `fso_mayor_ratings` SET from_user_id=1, to_user_id=1, from_avatar_id=NULL;"); + + LOG.Info("Deleting all other users and auth"); + + RunCommand(da, "DELETE FROM `fso_users` WHERE user_id != 1;"); + RunCommand(da, "DELETE FROM `fso_user_authenticate`;"); + + // Cleanup + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + RunCommand(da, "vacuum"); + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + } + + return 0; + } + } +} diff --git a/TSOClient/FSO.Server/ToolBackupSelection.cs b/TSOClient/FSO.Server/ToolBackupSelection.cs new file mode 100644 index 000000000..c1203bb88 --- /dev/null +++ b/TSOClient/FSO.Server/ToolBackupSelection.cs @@ -0,0 +1,312 @@ +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Lots; +using FSO.SimAntics.Marshals; +using FSO.SimAntics.Model.TSOPlatform; +using NLog; + +namespace FSO.Server +{ + internal class ToolBackupSelection : ITool + { + private BackupSelectionOptions Options; + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DAFactory; + + private ServerConfiguration Config; + public ToolBackupSelection(BackupSelectionOptions options, ServerConfiguration config, IDAFactory daFactory) + { + Options = options; + Config = config; + DAFactory = daFactory; + } + + private VMMarshal LoadVM(string path) + { + using (var file = File.OpenRead(path)) + { + using (var reader = new BinaryReader(file)) + { + var result = new VMMarshal(); + + result.Deserialize(reader); + + return result; + } + } + } + + private Dictionary GetRoommateObjectCounts(VMMarshal vm) + { + var result = new Dictionary(); + var state = (VMTSOLotState)vm.PlatformState; + + if (state.OwnerID != 0) + { + result[state.OwnerID] = 0; + } + + foreach (var roomie in state.Roommates) + { + result[roomie] = 0; + } + + foreach (var ent in vm.Entities) + { + if (ent is VMGameObjectMarshal obj) + { + var objState = (VMTSOObjectState)obj.PlatformState; + + if (result.TryGetValue(objState.OwnerID, out int toUpdate)) + { + result[objState.OwnerID] = toUpdate + 1; + } + } + } + + return result; + } + + private string GetLotDirectory(uint id) + { + return Path.Combine(Config.SimNFS, "Lots/", id.ToString("x8")); + } + + private const int MinObjectsMissing = 10; + private const int MissingRoomieExtraScore = 10; + + private static int GetNewObjectCount(Dictionary future, Dictionary past) + { + var sharedOwners = new HashSet(future.Keys); + sharedOwners.IntersectWith(past.Keys); + + int newObjectCount = 0; + + foreach (var owner in sharedOwners) + { + newObjectCount += future[owner] - past[owner]; + } + + return newObjectCount; + } + + private static int GetScore(Dictionary refCounts, Dictionary compareCounts) + { + // Look for avatars who are no longer roommates + int score = 0; + + var missing = new HashSet(compareCounts.Keys); + missing.ExceptWith(refCounts.Keys); + + var newAvas = new HashSet(refCounts.Keys); + newAvas.ExceptWith(compareCounts.Keys); + + foreach (var ex in missing) + { + var missingSimObjCount = compareCounts[ex]; + + // If they had a notable number of objects, increase the score. + if (missingSimObjCount > MinObjectsMissing) + { + score += MissingRoomieExtraScore + missingSimObjCount; + } + } + + foreach (var newAva in newAvas) + { + var newSimObjCount = refCounts[newAva]; + + // If they have a notable number of objects, decrease the score. + if (newSimObjCount > MinObjectsMissing) + { + score -= MissingRoomieExtraScore + newSimObjCount; + } + } + + return score; + } + + private bool ProcessLot(IDA da, DbLot lot) + { + int bestBackup = -1; + int bestScore = 0; + int bestNewObjectsSince = 0; + int baseObjectCount = 0; + Dictionary refCounts = null; + Dictionary bestCounts = null; + VMMarshal bestVM = null; + + int backupCount = 10; + var dir = GetLotDirectory((uint)lot.lot_id); + + if (!Directory.Exists(dir)) + { + return false; + } + + // Find the backup with the most roomies and objects + + int nextBackup = lot.ring_backup_num; + bool modifiedForEnd = false; + + for (int i = 0; i < backupCount; i++) + { + try + { + var backup = nextBackup; + var path = Path.Combine(dir, $"state_{nextBackup}.fsov"); + + if (File.Exists(path)) + { + var fsov = LoadVM(path); + + var objCounts = GetRoommateObjectCounts(fsov); + + if (refCounts == null) + { + bestBackup = backup; + refCounts = objCounts; + baseObjectCount = refCounts.Values.Sum(); + + var modifiedTime = File.GetLastWriteTime(path); + modifiedForEnd = modifiedTime > new DateTime(2024, 12, 1) && modifiedTime < new DateTime(2024, 12, 12); + } + else + { + var backupScore = GetScore(refCounts, objCounts); + + if (backupScore > bestScore && objCounts.Values.Sum() > baseObjectCount) + { + bestScore = backupScore; + bestNewObjectsSince = GetNewObjectCount(refCounts, objCounts); + bestBackup = backup; + bestCounts = objCounts; + bestVM = fsov; + } + } + } + + nextBackup--; + + if (nextBackup < 0) + { + nextBackup += backupCount; + } + } + catch (Exception e) + { + if (!(e is FileNotFoundException)) + { + LOG.Warn($" * Failed to load backup {i} for lot {lot.lot_id}: {e.Message}. Continuing until there's a working one."); + } + + nextBackup--; + + if (nextBackup < 0) + { + nextBackup += backupCount; + } + } + } + + // Try to avoid cases where someone added objects to the lot right before the shutdown. + if (bestScore != 0 && (!modifiedForEnd || bestNewObjectsSince <= 0)) + { + if (Options.DryRun) + { + LOG.Info($"Lot {lot.name} ({lot.lot_id:x8}) would be switched to backup {bestBackup} from {lot.ring_backup_num} with score {bestScore}"); + } + else + { + var restoredRoomies = new HashSet(bestCounts.Keys); + restoredRoomies.ExceptWith(refCounts.Keys); + int objectsStolen = 0; + int totalObjects = 0; + + foreach (uint simId in restoredRoomies) + { + var toRestore = bestVM.Entities.Where(x => (x is VMGameObjectMarshal obj) && (((VMTSOObjectState)obj.PlatformState).OwnerID) == simId).Select(x => x.PersistID).ToArray(); + + totalObjects += toRestore.Length; + + foreach (uint objId in toRestore) + { + try + { + var obj = da.Objects.Get(objId); + if (obj != null && obj.lot_id == null) + { + da.Objects.SetInLot(objId, (uint)lot.lot_id); + objectsStolen++; + } + } catch { } + } + } + + da.Lots.UpdateRingBackupSilent(lot.lot_id, (sbyte)bestBackup); + da.Lots.UpdateArchiveFlags(lot.lot_id, 1); + + LOG.Info($"Lot {lot.name} ({lot.lot_id:x8}) switched to backup {bestBackup} from {lot.ring_backup_num} with score {bestScore}"); + LOG.Info($" - Stole {objectsStolen}/{totalObjects} from user inventories"); + } + + if (((VMTSOLotState)bestVM.PlatformState).Name != lot.name) + { + LOG.Info($" - Previously had name {((VMTSOLotState)bestVM.PlatformState).Name}"); + } + + if (modifiedForEnd) + { + LOG.Info($" - !!! This lot was modified before the end !!!"); + } + + if (bestNewObjectsSince != 0) + { + LOG.Info($" + ~~~ Has {bestNewObjectsSince} new objects ~~~"); + } + + return true; + } + + return false; + } + + public int Run() + { + int processedLots = 0; + int totalLots = 0; + + LOG.Info("Processing lots for backup selection"); + + if (Options.DryRun) + { + LOG.Info("-v argument provided, so no changes will be made."); + } + + using (var da = (SqlDA)DAFactory.Get()) + { + var shards = da.Shards.All(); + + foreach (var shard in shards) + { + int shardId = shard.shard_id; + + var lots = da.Lots.All(shardId); + + foreach (var lot in lots) + { + if (lot.admit_mode < 4 && lot.category != FSO.Common.Enum.LotCategory.community && ProcessLot(da, lot)) + { + processedLots++; + } + } + + totalLots += lots.Count(); + } + } + + LOG.Info($"Selected better backup for {processedLots}/{totalLots} lots."); + + return 0; + } + } +} diff --git a/TSOClient/FSO.Server/ToolDataTrim.cs b/TSOClient/FSO.Server/ToolDataTrim.cs new file mode 100644 index 000000000..454edb05a --- /dev/null +++ b/TSOClient/FSO.Server/ToolDataTrim.cs @@ -0,0 +1,490 @@ +using FSO.Files.Formats.IFF.Chunks; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Lots; +using FSO.Server.Servers.Lot; +using FSO.SimAntics; +using FSO.SimAntics.Marshals; +using Ninject; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.Caching; + +namespace FSO.Server +{ + internal class ToolDataTrim : ITool + { + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DAFactory; + private ServerConfiguration Config; + private DataTrimOptions Options; + private IKernel Kernel; + + private bool ObjectsInNFS; + + public ToolDataTrim(DataTrimOptions options, IDAFactory factory, ServerConfiguration config, IKernel kernel) + { + this.Options = options; + this.Config = config; + this.DAFactory = factory; + this.Kernel = kernel; + } + + private string GetObjectDirectory(uint id) + { + return Path.Combine(Config.SimNFS, "Objects/", id.ToString("x8")); + } + + private string GetLotDirectory(uint id) + { + return Path.Combine(Config.SimNFS, "Lots/", id.ToString("x8")); + } + + private bool DeleteIfEmpty(string dir) + { + if (Directory.GetFileSystemEntries(dir).Length == 0) + { + Directory.Delete(dir); + + return true; + } + + return false; + } + + private bool DeleteInventoryState(uint id) + { + var dir = GetObjectDirectory(id); + + string invPath = Path.Combine(dir, "inventoryState.fsoo"); + + if (File.Exists(invPath)) + { + File.Delete(invPath); + } + + return DeleteIfEmpty(dir); + } + + public List GetObjectIDsNFS() + { + var basepath = Path.Combine(Config.SimNFS, "Objects/"); + var result = new List(); + + if (Directory.Exists(basepath)) + { + foreach (var path in Directory.GetDirectories(basepath)) + { + var idStr = Path.GetFileName(path); + + if (uint.TryParse(idStr, NumberStyles.HexNumber, null, out uint id)) + { + result.Add(id); + } + } + } + + return result; + } + + public List GetLotIDsNFS() + { + var basepath = Path.Combine(Config.SimNFS, "Lots/"); + var result = new List(); + + foreach (var path in Directory.GetDirectories(basepath)) + { + var idStr = Path.GetFileName(path); + + if (uint.TryParse(idStr, NumberStyles.HexNumber, null, out uint id)) + { + result.Add(id); + } + } + + return result; + } + + public List GetObjectIDsDB(SqlDA da, bool onLot) + { + return da.Objects.ListIDs(onLot); + } + + public List GetLotsDB(SqlDA da) + { + // TODO: more than one shard... + return da.Lots.All(1).ToList(); + } + + private VMMarshal LoadVM(string path) + { + using (var file = File.OpenRead(path)) + { + using (var reader = new BinaryReader(file)) + { + var result = new VMMarshal(); + + result.Deserialize(reader); + + return result; + } + } + } + + private bool VerifyAndTrimBackups(DbLot lot) + { + int backupCount = 10; + var dir = GetLotDirectory((uint)lot.lot_id); + + if (!Directory.Exists(dir)) + { + return true; + } + + int newestBackup; + int oldestBackup; + if (lot.archive_flags == 1) + { + // Special mode: save the current backup, and the one with the latest modified date + oldestBackup = lot.ring_backup_num; + newestBackup = -1; + + int testBackup = lot.ring_backup_num; + DateTime bestDate = new DateTime(0); + for (int i = 0; i < backupCount; i++) + { + try + { + var path = Path.Combine(dir, $"state_{testBackup}.fsov"); + + if (File.Exists(path)) + { + DateTime modified = File.GetLastWriteTimeUtc(path); + + if (modified >= bestDate) + { + var fsov = LoadVM(path); + + bestDate = modified; + newestBackup = testBackup; + } + } + } + catch (Exception e) + { + if (!(e is FileNotFoundException)) + { + LOG.Warn($" * Failed to load backup {i} for lot {lot.lot_id}: {e.Message}. Continuing until there's a working one."); + } + } + + testBackup--; + + if (testBackup < 0) + { + testBackup += backupCount; + } + } + + if (newestBackup == -1) + { + LOG.Error($" * Failed to load ALL backups for lot {lot.lot_id}. Leaving it as-is."); + return false; + } + } + else + { + // Find the newest backup that still works. + + newestBackup = lot.ring_backup_num; + + for (int i = 0; i < backupCount; i++) + { + try + { + var path = Path.Combine(dir, $"state_{newestBackup}.fsov"); + + if (!File.Exists(path)) + { + newestBackup--; + + if (newestBackup < 0) + { + newestBackup += backupCount; + } + + continue; + } + + var fsov = LoadVM(path); + + break; + } + catch (Exception e) + { + if (!(e is FileNotFoundException)) + { + LOG.Warn($" * Failed to load backup {i} for lot {lot.lot_id}: {e.Message}. Continuing until there's a working one."); + } + + newestBackup--; + + if (newestBackup < 0) + { + newestBackup += backupCount; + } + } + + if (i == 9) + { + LOG.Error($" * Failed to load ALL backups for lot {lot.lot_id}. Leaving it as-is."); + return false; + } + } + + oldestBackup = (lot.ring_backup_num + 1) % backupCount; + + for (int i = 0; i < backupCount; i++) + { + try + { + var path = Path.Combine(dir, $"state_{oldestBackup}.fsov"); + + if (!File.Exists(path)) + { + oldestBackup = (oldestBackup + 1) % backupCount; + + continue; + } + + var fsov = LoadVM(path); + + break; + } + catch (Exception e) + { + if (!(e is FileNotFoundException)) + { + LOG.Warn($" * Failed to load oldest backup {i} for lot {lot.lot_id}: {e.Message}. Continuing until there's a working one."); + } + + oldestBackup = (oldestBackup + 1) % backupCount; + } + + if (i == 9) + { + LOG.Error($" * Failed to load ALL backups for lot {lot.lot_id}. Leaving it as-is."); + return false; + } + } + } + + // Delete everything that isn't the oldest and newest backup. + + for (int i = 0; i < backupCount; i++) + { + if (i != newestBackup && i != oldestBackup) + { + var path = Path.Combine(dir, $"state_{i}.fsov"); + if (File.Exists(path)) + { + File.Delete(path); + } + } + } + + // Delete any contained directories + foreach (var delDir in Directory.GetDirectories(dir)) + { + Directory.Delete(delDir, true); + } + + return true; + } + + private void RunCommand(SqlDA da, string sql) + { + var context = da.Context; + + var command = context.Connection.CreateCommand(); + + command.CommandText = sql; + + try + { + var result = command.ExecuteNonQuery(); + } + catch (Exception e) + { + throw e; + } + } + + public void DeleteAllRows(SqlDA da, string tableName) + { + RunCommand(da, $"DELETE FROM `{tableName}`"); + } + + public int Run() + { + LOG.Info("Scanning content"); + VMContext.InitVMConfig(false); + Content.Content.Init(Config.GameLocation, Content.ContentMode.SERVER); + Kernel.Bind().ToConstant(Content.Content.Get()); + Kernel.Bind().ToConstant(new MemoryCache("fso_server")); + + using (var da = (SqlDA)DAFactory.Get()) + { + ObjectsInNFS = (da.Context is MySqlContext); + + LOG.Info("Trimming relationships (invalid from)"); + + RunCommand(da, "DELETE FROM fso_relationships WHERE NOT EXISTS (SELECT 1 FROM fso_avatars a where from_id = a.avatar_id)"); + + LOG.Info("Trimming relationships (low value, person to person)"); + + RunCommand(da, "DELETE FROM fso_relationships WHERE value < 5 AND value > -5"); + + LOG.Info("Scanning objects for NFS trimming"); + + var nfsIds = GetObjectIDsNFS(); + var ids = GetObjectIDsDB(da, false); + var onLot = GetObjectIDsDB(da, true); + + LOG.Info("Trimming NFS objects (deleted objects)"); + + var toDelete = new HashSet(nfsIds); + var existsInNfs = new HashSet(); + + foreach (uint id in ids) + { + if (toDelete.Remove(id)) + { + existsInNfs.Add(id); + } + } + + LOG.Info($" - Cleaning state for {toDelete.Count} deleted objects."); + + foreach (uint del in toDelete) + { + var dir = GetObjectDirectory(del); + Directory.Delete(dir, true); + } + + LOG.Info("Trimming objects inventory state (deleting state when object is on lot)"); + + if (ObjectsInNFS) + { + var deleteNfsOnLot = new List(); + + foreach (uint id in onLot) + { + if (existsInNfs.Contains(id)) + { + deleteNfsOnLot.Add(id); + } + } + + LOG.Info($" - Removing saved inventory state for {deleteNfsOnLot.Count} on-lot objects. (plugin state is kept)"); + + int directoryDeleteCount = 0; + foreach (uint id in deleteNfsOnLot) + { + if (DeleteInventoryState(id)) + { + directoryDeleteCount++; + } + } + + LOG.Info($" - Deleted {directoryDeleteCount} directories (no remaining object state)."); + } + else + { + var deletedCount = da.Objects.PurgeStateOnLot(); + + LOG.Info($" - Removed saved inventory state for {deletedCount} on-lot objects. (plugin state is kept)"); + } + + LOG.Info("Trimming NFS lots (keeping only oldest and newest backup, deleting invalid lot saves)"); + + var nfsLotIds = GetLotIDsNFS(); + var lots = GetLotsDB(da); + + var dbLotHash = new HashSet(lots.Select(lot => (uint)lot.lot_id)); + + int deletedLeftoverLots = 0; + foreach (var lot in nfsLotIds) + { + if (!dbLotHash.Contains(lot)) + { + // This lot doesn't exist on the database... delete it. + var dir = GetLotDirectory(lot); + + Directory.Delete(dir, true); + + deletedLeftoverLots++; + } + } + + LOG.Info($" - Deleted {deletedLeftoverLots} NFS lots without database entries."); + LOG.Info($" - Verifying lot data. This could take a while."); + + foreach (var lot in lots) + { + VerifyAndTrimBackups(lot); + } + + LOG.Info("Clearing fso_auth_attempts"); + DeleteAllRows(da, "fso_auth_attempts"); + LOG.Info("Clearing fso_auth_tickets"); + DeleteAllRows(da, "fso_auth_tickets"); + LOG.Info("Clearing fso_lot_server_tickets"); + DeleteAllRows(da, "fso_lot_server_tickets"); + LOG.Info("Clearing fso_shard_tickets"); + DeleteAllRows(da, "fso_shard_tickets"); + LOG.Info("Clearing fso_tasks"); + DeleteAllRows(da, "fso_tasks"); + LOG.Info("Clearing fso_transactions"); + DeleteAllRows(da, "fso_transactions"); + + if (Options.Anon) + { + LOG.Info("Anonymize: Clearing fso_inbox"); + DeleteAllRows(da, "fso_inbox"); + LOG.Info("Anonymize: Clearing fso_bookmarks"); // This is also the ignore list. + DeleteAllRows(da, "fso_bookmarks"); + LOG.Info("Anonymize: Removing deleted fso_bulletin_posts"); // This used soft delete for moderation purposes. + RunCommand(da, $"DELETE FROM `fso_bulletin_posts` WHERE deleted=1"); + LOG.Info("Anonymize: Clearing fso_election_votes"); + DeleteAllRows(da, "fso_election_votes"); + LOG.Info("Anonymize: Clearing fso_election_freevotes"); + DeleteAllRows(da, "fso_election_freevotes"); + LOG.Info("Anonymize: Clearing fso_election_candidates"); + DeleteAllRows(da, "fso_election_candidates"); + LOG.Info("Anonymize: Clearing fso_ip_ban"); + DeleteAllRows(da, "fso_ip_ban"); + LOG.Info("Anonymize: Clearing fso_lot_visits"); + DeleteAllRows(da, "fso_lot_visits"); + LOG.Info("Anonymize: Removing identifying info from fso_mayor_ratings"); + RunCommand(da, "UPDATE fso_mayor_ratings SET from_avatar_id = NULL WHERE anonymous = 1"); + RunCommand(da, "UPDATE fso_mayor_ratings SET to_user_id = 1 WHERE anonymous = 1"); + LOG.Info("Anonymize: Clearing fso_nhood_ban"); + DeleteAllRows(da, "fso_nhood_ban"); + } + + LOG.Info("Anonymize: Clearing fso_lot_admit"); + DeleteAllRows(da, "fso_lot_admit"); + + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + RunCommand(da, "vacuum"); + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + + return 1; + } + } + } +} diff --git a/TSOClient/FSO.Server/ToolImportArchiveFeatured.cs b/TSOClient/FSO.Server/ToolImportArchiveFeatured.cs new file mode 100644 index 000000000..9beecffd8 --- /dev/null +++ b/TSOClient/FSO.Server/ToolImportArchiveFeatured.cs @@ -0,0 +1,70 @@ +using FSO.Server.Database.DA; +using FSO.Server.Utils; +using System; +using System.Collections.Generic; +using System.IO; + +namespace FSO.Server +{ + internal class ToolImportArchiveFeatured : ITool + { + private IDAFactory DAFactory; + private ImportArchiveFeaturedOptions Options; + + public ToolImportArchiveFeatured(ImportArchiveFeaturedOptions options, IDAFactory factory) + { + this.Options = options; + this.DAFactory = factory; + } + + public int Run() + { + if (Options.JSON == null) + { + Console.WriteLine("Please pass: "); + return 1; + } + Console.WriteLine("Starting archive featured import..."); + + List data = null; + //first load the JSON + try + { + data = Newtonsoft.Json.JsonConvert.DeserializeObject>(File.ReadAllText(Options.JSON)); + } + catch (FileNotFoundException) + { + Console.WriteLine("The JSON file specified could not be found! "); + return 1; + } + catch (Exception) + { + Console.WriteLine("An unknown error occurred loading your JSON file. "); + return 1; + } + + Console.WriteLine("Found " + data.Count + " featured lots."); + + using (var da = (SqlDA)DAFactory.Get()) + { + da.ArchiveFeatured.Clear(Options.ShardId); + + foreach (var item in data) + { + da.ArchiveFeatured.Create(new Database.DA.ArchiveFeatured.DbArchiveFeatured() + { + name = item.name, + lot_id = item.lot_id, + category = item.category, + description = item.description, + shard_id = Options.ShardId, + }); + } + + Console.WriteLine($"Imported {data.Count} featured lots for shard {Options.ShardId}"); + } + + return 0; + } + } +} diff --git a/TSOClient/FSO.Server/ToolPluginAnonymize.cs b/TSOClient/FSO.Server/ToolPluginAnonymize.cs new file mode 100644 index 000000000..71660d2f9 --- /dev/null +++ b/TSOClient/FSO.Server/ToolPluginAnonymize.cs @@ -0,0 +1,977 @@ +using FSO.LotView.Model; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Lots; +using FSO.Server.Database.DA.Objects; +using FSO.SimAntics; +using FSO.SimAntics.Engine; +using FSO.SimAntics.Engine.TSOTransaction; +using FSO.SimAntics.Marshals; +using FSO.SimAntics.Model; +using FSO.SimAntics.NetPlay.Drivers; +using FSO.SimAntics.NetPlay.EODs.Handlers.Data; +using FSO.SimAntics.NetPlay.Model.Commands; +using FSO.SimAntics.Primitives; +using Newtonsoft.Json; +using NLog; + +namespace FSO.Server +{ + internal class ToolPluginAnonymize : ITool + { + private PluginAnonymizeOptions Options; + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DAFactory; + + private ServerConfiguration Config; + public ToolPluginAnonymize(PluginAnonymizeOptions options, ServerConfiguration config, IDAFactory daFactory) + { + Options = options; + Config = config; + DAFactory = daFactory; + } + + private struct ModifyCount + { + public int Attempt; + public int Success; + + public void Add(bool success) + { + Attempt++; + + if (success) + { + Success++; + } + } + + public override string ToString() + { + return $"{Success}/{Attempt}"; + } + } + + private class PluginJson + { + [JsonProperty("objectID")] + public uint ObjectID { get; set; } + [JsonProperty("isReachable")] + public bool IsReachable { get; set; } = false; + [JsonProperty("delete")] + public bool Delete { get; set; } = false; + [JsonProperty("modified")] + public bool Modified { get; set; } = false; + } + + private class SignPluginJson : PluginJson + { + [JsonProperty("signFlags")] + public uint SignFlags { get; set; } + [JsonProperty("message")] + public string Message { get; set; } + } + + private class CardPluginJson : PluginJson + { + [JsonProperty("title")] + public string Title { get; set; } + [JsonProperty("description")] + public string Description { get; set; } + [JsonProperty("cardContents")] + public string[] CardContents { get; set; } + } + + private class DoorPluginJson : PluginJson + { + [JsonProperty("code")] + public uint Code { get; set; } + } + + private class HouseJson + { + [JsonProperty("houseName")] + public string HouseName { get; set; } + [JsonProperty("houseId")] + public uint HouseId { get; set; } + [JsonProperty("houseAdmitMode")] + public int HouseAdmitMode { get; set; } + + [JsonProperty("signs")] + public SignPluginJson[] Signs { get; set; } + [JsonProperty("cards")] + public CardPluginJson[] Cards { get; set; } + [JsonProperty("doors")] + public DoorPluginJson[] Doors { get; set; } + } + + private class ReviewJson + { + [JsonProperty("publicHouses")] + public HouseJson[] PublicHouses; // Admit all, ban list + [JsonProperty("privateHouses")] + public HouseJson[] PrivateHouses; // Admit list, ban all + } + + private const uint SIGN_PLUGIN = 0x2a6356a0; + private const uint DRAW_CARD_PLUGIN = 0x895C1CEB; + private const uint PERMISSION_DOOR_PLUGIN = 0x0A69F29F; + + // There's not really a brilliant way of getting all the objects that use the plugin type, + // So here's all the ones we expect with base content. + private static uint[] SignTypes = [ + 0xA92EFE75, // Rustic + 0x99F6D314, // Sandwich board + 0xFFEEA490, // Sci-fi + 0xE86BB6D7, // Shop + 0xDCECE8AA, // Theater + 0x23295F48, // robotfactory + 0xD067F355, // Warning + 0xA996978A, // Conference + 0x70BD99F7, // Corkboard + 0xEB402C8A, // Holiday + 0x5700D1C5, // Landmark + 0xBFBB8152, // Neon + 0xA9B78F1D, // Chalkboard L (unused?) + 0xA9A6DF80, // Chalkboard R (unused?) + + // FSO CC + 0x7E055DC7, // Lucky Folding Write Board + 0x59896C56, // Leaf Note Sign + 0x4BA28DDD, // Postcards + 0x2CB89BF8, // Halloween Sign + 0x2C47F9F4, // Chalk it down + 0x584B4823, // Chalk it up + ]; + + private const uint DRAW_A_CARD_TYPE = 0x34E956FE; + private const uint TELEPORTER_TYPE = 0x96A776CE; + private const int TELEPORT_INTERACTION = 8; + public static readonly int TICKRATE = 30; + + private const string DOOR_GLOBALS = "doorglobals"; + private const string TELEPORT_START_ANIM = "a20-teleporter-step-in"; + private const string TELEPORT_FAIL_ANIM = "a20-teleporter-check-self-insideout"; + + // If the teleporter start animation plays, it is reachable. + // If the interaction finishes after this and the fail animation plays, then the teleporter is obstructed + // If the interaction fihishes after this and the fail animation doesn't play, then the teleporter works. + + private static bool AdmitModePublic(int admitMode) + { + return !(admitMode == 1 || admitMode == 3); // admit list, ban all + } + + private HashSet GetUniqueLots(List objects) + { + var result = new HashSet(); + + foreach (var obj in objects) + { + if (obj.lot_id.HasValue) + { + result.Add(obj.lot_id.Value); + } + } + + return result; + } + + private HashSet GetUniqueInventorySims(List objects) + { + var result = new HashSet(); + + foreach (var obj in objects) + { + if (!obj.lot_id.HasValue && obj.owner_id.HasValue) + { + result.Add(obj.owner_id.Value); + } + } + + return result; + } + + private void CleanLot(VM Lot) + { + var avatars = new List(Lot.Entities.Where(x => x is VMAvatar && x.PersistID != 0)); + //step 1, force everyone to leave. + foreach (var avatar in avatars) + Lot.ForwardCommand(new VMNetSimLeaveCmd() + { + ActorUID = avatar.PersistID, + FromNet = false + }); + + //simulate for a bit to try get rid of the avatars on the lot + try + { + for (int i = 0; i < 30 * TICKRATE && Lot.Entities.FirstOrDefault(x => x is VMAvatar && x.PersistID > 0) != null; i++) + { + Lot.Tick(); + } + } + catch (Exception) { } //if something bad happens just immediately try to delete everyone + + avatars = new List(Lot.Entities.Where(x => x is VMAvatar && (x.PersistID != 0 || (!(x as VMAvatar).IsPet)))); + foreach (var avatar in avatars) avatar.Delete(true, Lot.Context); + } + + public (VM, DbLot)? AttemptLoad(int lotId) + { + DbLot LotPersist; + + using (var da = (SqlDA)DAFactory.Get()) + { + var lot = da.Lots.Get(lotId); + + if (lot == null) return null; + + LotPersist = lot; + } + + VM.UseWorld = false; + var link = new VMTSOGlobalLinkStub(); + link.Database = new SimAntics.Engine.TSOGlobalLink.VMTSOStandaloneDatabase(); + var Lot = new VM(new VMContext(null), new VMServerDriver(link), new VMNullHeadlineProvider()); + Lot.Init(); + + //first let's try load our adjacent lots. + int attempts = 0; + var lotStr = lotId.ToString("x8"); + var ringSize = Config.Services.Lots.First().RingBufferSize; + + while (++attempts < ringSize) + { + LOG.Info("Checking ring " + attempts + " for lot with dbid = " + lotId); + try + { + var path = Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/state_" + LotPersist.ring_backup_num.ToString() + ".fsov"); + using (var file = new BinaryReader(File.OpenRead(path))) + { + var marshal = new VMMarshal(); + marshal.Deserialize(file); + + // Don't bother using move flags to rotate. + + Lot.Load(marshal); + CleanLot(Lot); + Lot.Reset(); + } + + return (Lot, LotPersist); + } + catch (Exception e) + { + LOG.Info("Ring load failed with exception: " + e.ToString() + " for lot with dbid = " + lotId); + LotPersist.ring_backup_num--; + if (LotPersist.ring_backup_num < 0) LotPersist.ring_backup_num += (sbyte)ringSize; + } + } + + LOG.Error("FAILED to load all backups for lot with dbid = " + lotId + "! Forcing lot close"); + var backupPath = Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/failedRestore" + (DateTime.Now.ToBinary().ToString()) + "/"); + Directory.CreateDirectory(backupPath); + foreach (var file in Directory.EnumerateFiles(Path.Combine(Config.SimNFS, "Lots/" + lotStr + "/"))) + { + File.Copy(file, backupPath + Path.GetFileName(file)); + } + + return null; + } + + private List GetSignPluginData(List objs) + { + return objs.Select(obj => ReadSign(obj.object_id)).Where(x => x != null).ToList(); + } + + private List GetDrawCardPluginData(List objs) + { + return objs.Select(obj => ReadCards(obj.object_id)).Where(x => x != null).ToList(); + } + + private List GetDoorPluginData(List objs) + { + return objs.Select(obj => ReadDoor(obj.object_id)).Where(x => x != null).ToList(); + } + + private VMAvatar CreateAvatar(VM vm) + { + return (VMAvatar)vm.Context.CreateObjectInstance(VMAvatar.TEMPLATE_PERSON, LotTilePos.OUT_OF_WORLD, Direction.NORTH).Objects[0]; + } + + private void ResetMotives(VMAvatar sim) + { + sim.SetMotiveData(VMMotive.Hunger, 100); + sim.SetMotiveData(VMMotive.Comfort, 100); + sim.SetMotiveData(VMMotive.Energy, 100); + sim.SetMotiveData(VMMotive.Bladder, 100); + sim.SetMotiveData(VMMotive.Hygiene, 100); + sim.SetMotiveData(VMMotive.Fun, 100); + sim.SetMotiveData(VMMotive.Social, 100); + } + + private void ResetPosition(VM vm, VMAvatar sim) + { + var mailbox = vm.Entities.FirstOrDefault(x => (x.Object.OBJ.GUID == 0xEF121974 || x.Object.OBJ.GUID == 0x1D95C9B0)); + if (mailbox != null) VMFindLocationFor.FindLocationFor(sim, mailbox, vm.Context, VMPlaceRequestFlags.Default); + else sim.SetPosition(LotTilePos.FromBigTile(3, 3, 1), Direction.NORTH, vm.Context); + } + + private const int MAX_INTERACTION_ATTEMPT_COUNT = 30; + private const int MAX_ROUTING_TICKS = 30 * 180; // 3 minutes + + private void EndInteraction(VM vm, VMAvatar ava, VMQueuedAction action) + { + vm.SendCommand(new VMNetInteractionCancelCmd() + { + ActorUID = ava.PersistID, + ActionUID = action.UID, + }); + + for (int i = 0; i < MAX_INTERACTION_ATTEMPT_COUNT; i++) + { + // Wait for the interaction to end + + var newAction = ava.Thread.ActiveAction; + if (newAction != action) + { + return; + } + + vm.Tick(); + } + + ava.Reset(vm.Context); + } + + private bool TestSignRouteWithTeleporters(VM vm, VMAvatar ava, VMEntity sign, ref List teleporterStarts) + { + if (TestSignRoute(vm, ava, sign)) + { + return true; + } + + if (teleporterStarts == null) + { + // Evaluate what teleporters are reachable from the mailbox + + var teleporters = vm.Context.ObjectQueries.GetObjectsByGUID(TELEPORTER_TYPE); + + teleporterStarts = new List(); + + if (teleporters != null) + { + foreach (var teleporter in teleporters) + { + + } + } + } + + // Can we get there from any of the teleporters? + foreach (var start in teleporterStarts) + { + + } + + return false; + } + + private bool TestSignRoute(VM vm, VMAvatar ava, VMEntity sign) + { + // Place the avatar at the mailbox + ResetMotives(ava); + ResetPosition(vm, ava); + + // Interaction 2 is read. + // for the card thing, interaction 2 is deck info + + vm.SendCommand(new VMNetInteractionCmd() + { + Interaction = 2, + ActorUID = ava.PersistID, + CalleeID = sign.ObjectID, + Param0 = 0, + Global = false + }); + + VMQueuedAction spyAction = null; + + for (int i = 0; i < MAX_INTERACTION_ATTEMPT_COUNT; i++) + { + // Wait for the interaction to show up. + + var action = ava.Thread.ActiveAction; + if (action?.Callee == sign) + { + spyAction = action; + break; + } + + vm.Tick(); + + if (i == MAX_INTERACTION_ATTEMPT_COUNT - 1) + { + // Failed? + ava.Reset(vm.Context); + return false; + } + } + + // Wait for the action to either end (return false) or for the plugin to start (return true, forcibly end the interaction and wait) + + for (int i = 0; i < MAX_ROUTING_TICKS; i++) + { + // Has the plugin started? + + if (ava.Thread.EODConnection != null) + { + EndInteraction(vm, ava, spyAction); + return true; + } + + var action = ava.Thread.ActiveAction; + if (action != spyAction) + { + // The interaction ended + return false; + } + + vm.Tick(); + } + + EndInteraction(vm, ava, spyAction); + + return false; + } + + private HouseJson ProcessLot(int lotId, List allSigns, List allCards, List allDoors) + { + // Could be a bit faster by building a dictionary for this before each iteration, but not too important + var mySigns = allSigns.Where(x => x.lot_id == lotId).ToList(); + var myCards = allCards.Where(x => x.lot_id == lotId).ToList(); + var myDoors = allDoors.Where(x => x.lot_id == lotId).ToList(); + + var signData = GetSignPluginData(mySigns); + var cardData = GetDrawCardPluginData(myCards); + var doorData = GetDoorPluginData(myDoors); + + if (signData.Count > 0 || cardData.Count > 0) + { + var lot = AttemptLoad(lotId); + + if (lot != null) + { + var vm = lot.Value.Item1; + var dbLot = lot.Value.Item2; + + // Create a dummy avatar to route to the destination, with visitor permissions. + var dummy = CreateAvatar(vm); + dummy.PersistID = 1; + vm.Context.ObjectQueries.RegisterAvatarPersist(dummy, dummy.PersistID); + vm.MyUID = 1; + + bool admitGuests = AdmitModePublic(dbLot.admit_mode); + + // Constructed if any route attempts fail. See ConstructTeleportStarts for more info. + List teleporterStarts = null; + + foreach (var sign in signData) + { + // Try to look up the sign on the lot. + var realSign = vm.GetObjectByPersist(sign.ObjectID); + + if (realSign == null) continue; + + // Can we route to it from the mailbox? + sign.IsReachable = TestSignRouteWithTeleporters(vm, dummy, realSign, ref teleporterStarts); + sign.Delete = !sign.IsReachable || !admitGuests; + } + + foreach (var card in cardData) + { + // Try to look up the sign on the lot. + var realCard = vm.GetObjectByPersist(card.ObjectID); + + if (realCard == null) continue; + + // Can we route to it from the mailbox? + card.IsReachable = TestSignRouteWithTeleporters(vm, dummy, realCard, ref teleporterStarts); + card.Delete = !card.IsReachable || !admitGuests; + } + + return new HouseJson() + { + HouseId = (uint)lotId, + HouseAdmitMode = dbLot.admit_mode, + HouseName = dbLot.name, + Signs = [.. signData], + Cards = [.. cardData], + Doors = [.. doorData] + }; + } + + return new HouseJson() + { + HouseId = (uint)lotId, + HouseAdmitMode = 0, + HouseName = "(invalid)", + Signs = [.. signData], + Cards = [.. cardData] + }; + } + + return null; + } + + private static uint[] GetDoorTypes() + { + var builder = new List(); + var worldObj = Content.Content.Get().WorldObjects; + + var entries = worldObj.Entries.ToList(); + + foreach (var obj in entries) + { + var objRes = worldObj.Get(obj.Key); + + if (objRes.Resource.SemiGlobal?.Iff?.Filename == DOOR_GLOBALS+".iff") + { + builder.Add(objRes.OBJ.GUID); + } + } + + return [.. builder]; + } + + private bool DeletePlugin(uint id, uint pluginID) + { + try + { + var path = PluginPersistPath(id, pluginID); + + if (Path.Exists(path)) + { + File.Delete(path); + + var pluginDir = Path.GetDirectoryName(path); + + if (Directory.GetFiles(pluginDir).Length == 0) + { + Directory.Delete(pluginDir); + + var objectDir = Path.GetDirectoryName(pluginDir); + + if (Directory.GetFiles(objectDir).Length == 0) + { + Directory.Delete(objectDir); + } + } + + return true; + } + } + catch + { + // ... + } + + return false; + } + + private bool ModifySign(uint id, ushort flags, string message) + { + try + { + var path = PluginPersistPath(id, SIGN_PLUGIN); + + if (Path.Exists(path)) + { + var data = new VMEODSignsData() + { + Flags = flags, + Text = message + }; + + using var file = File.OpenWrite(path); + using var writer = new BinaryWriter(file); + + data.SerializeInto(writer); + + return true; + } + } + catch + { + // ... + } + + return false; + } + + private void ApplyReview(HouseJson house, ref ModifyCount signDeleteCount, ref ModifyCount signUpdateCount, ref ModifyCount cardsDeleteCount, ref ModifyCount doorDeleteCount) + { + foreach (var sign in house.Signs) + { + if (sign.Delete) + { + signDeleteCount.Add(DeletePlugin(sign.ObjectID, SIGN_PLUGIN)); + } + else if (sign.Modified) + { + signUpdateCount.Add(ModifySign(sign.ObjectID, (ushort)sign.SignFlags, sign.Message)); + } + } + + foreach (var card in house.Cards) + { + if (card.Delete) + { + cardsDeleteCount.Add(DeletePlugin(card.ObjectID, DRAW_CARD_PLUGIN)); + } + } + + foreach (var door in house.Doors) + { + if (door.Delete) + { + doorDeleteCount.Add(DeletePlugin(door.ObjectID, PERMISSION_DOOR_PLUGIN)); + } + } + } + + private void DeleteAll(List list) where T : PluginJson + { + foreach (var item in list) + { + item.Delete = true; + } + } + + private HouseJson ProcessInventory(IDA da, uint sim, List allSigns, List allCards, List allDoors) + { + // Could be a bit faster by building a dictionary for this before each iteration, but not too important + var mySigns = allSigns.Where(x => !x.lot_id.HasValue && x.owner_id == sim).ToList(); + var myCards = allCards.Where(x => !x.lot_id.HasValue && x.owner_id == sim).ToList(); + var myDoors = allDoors.Where(x => !x.lot_id.HasValue && x.owner_id == sim).ToList(); + + var signData = GetSignPluginData(mySigns); + var cardData = GetDrawCardPluginData(myCards); + var doorData = GetDoorPluginData(myDoors); + + if (signData.Count > 0 || cardData.Count > 0 || doorData.Count > 0) + { + DeleteAll(signData); + DeleteAll(cardData); + DeleteAll(doorData); + + return new HouseJson() + { + HouseId = sim, + HouseName = da.Avatars.Get(sim)?.name ?? "unknown avatar", + Signs = [.. signData], + Cards = [.. cardData], + Doors = [.. doorData] + }; + } + + return null; + } + + public int Run() + { + LOG.Info("Scanning content"); + VMContext.InitVMConfig(false); + Content.Content.Init(Config.GameLocation, Content.ContentMode.SERVER); + + var publicHouses = new List(); + var privateHouses = new List(); + + var doorTypes = GetDoorTypes(); + + LOG.Info("Scanning for objects... this might take a while"); + + using (var da = (SqlDA)DAFactory.Get()) + { + var allSigns = new List(); + foreach (uint guid in SignTypes) + { + allSigns.AddRange(da.Objects.GetByType(guid)); + } + + // This might be slightly insane, but I already wrote the code this way before I made it check doors. + var allDoors = new List(); + foreach (uint guid in doorTypes) + { + allDoors.AddRange(da.Objects.GetByType(guid)); + } + + var allDrawACard = da.Objects.GetByType(DRAW_A_CARD_TYPE); + + var allPluginObjects = new List(allSigns); + allPluginObjects.AddRange(allDrawACard); + allPluginObjects.AddRange(allDoors); + + if (Options.InputFile != null) + { + LOG.Info($"Loading input file {Options.InputFile}..."); + + ReviewJson review; + try + { + string json = File.ReadAllText(Options.InputFile); + review = JsonConvert.DeserializeObject(json); + } + catch (Exception e) + { + LOG.Info($"Failed to load JSON: {e.Message}"); + return 1; + } + + LOG.Info($"Applying public differences in review..."); + + ModifyCount signDeleteCount = default, signUpdateCount = default, cardsDeleteCount = default, doorDeleteCount = default; + + foreach (var lot in review.PublicHouses) + { + ApplyReview(lot, ref signDeleteCount, ref signUpdateCount, ref cardsDeleteCount, ref doorDeleteCount); + } + + LOG.Info($" - Signs deleted: {signDeleteCount.ToString()}, Signs updated: {signUpdateCount.ToString()}, Cards deleted: {cardsDeleteCount.ToString()}, Doors deleted: {doorDeleteCount.ToString()}"); + + LOG.Info($"Applying private differences in review..."); + + signDeleteCount = default; signUpdateCount = default; cardsDeleteCount = default; doorDeleteCount = default; + + foreach (var lot in review.PrivateHouses) + { + ApplyReview(lot, ref signDeleteCount, ref signUpdateCount, ref cardsDeleteCount, ref doorDeleteCount); + } + + LOG.Info($" - Signs deleted: {signDeleteCount.ToString()}, Signs updated: {signUpdateCount.ToString()}, Cards deleted: {cardsDeleteCount.ToString()}, Doors deleted: {doorDeleteCount.ToString()}"); + + LOG.Info($"Building inventory deletion records..."); + + var objectsByUser = new List(); + var sims = GetUniqueInventorySims(allPluginObjects); + + foreach (var sim in sims) + { + var simData = ProcessInventory(da, sim, allSigns, allDrawACard, allDoors); + + if (simData != null) + { + objectsByUser.Add(simData); + } + } + + // The review didn't have lots with only door codes, so add those too + var unreviewedDoors = new Dictionary(allDoors.Where(x => x.lot_id.HasValue).Select(x => new KeyValuePair(x.object_id, x))); + + foreach (var lot in review.PublicHouses) + { + foreach (var door in lot.Doors) + { + unreviewedDoors.Remove(door.ObjectID); + } + } + + foreach (var lot in review.PrivateHouses) + { + foreach (var door in lot.Doors) + { + unreviewedDoors.Remove(door.ObjectID); + } + } + + var doorData = GetDoorPluginData([.. unreviewedDoors.Values]); + + DeleteAll(doorData); + + objectsByUser.Add(new HouseJson() + { + HouseId = 0, + HouseName = "=== Door codes without signs or cards ===", + Doors = doorData.ToArray(), + Cards = [], + Signs = [], + HouseAdmitMode = 0, + }); + + var deletionJson = JsonConvert.SerializeObject(objectsByUser.ToArray(), Formatting.Indented); + + File.WriteAllText("inventoryPluginDeletion.json", deletionJson); + + LOG.Info($"Succesfully output at inventoryPluginDeletion.json - deleting the plugin data for real"); + + LOG.Info($"Deleting inventory data, and leftover doors"); + + foreach (var lot in objectsByUser) + { + ApplyReview(lot, ref signDeleteCount, ref signUpdateCount, ref cardsDeleteCount, ref doorDeleteCount); + } + + LOG.Info($" - Signs deleted: {signDeleteCount.ToString()}, Signs updated: {signUpdateCount.ToString()}, Cards deleted: {cardsDeleteCount.ToString()}, Doors deleted: {doorDeleteCount.ToString()}"); + } + else + { + HashSet lots = GetUniqueLots(allPluginObjects); + + LOG.Info("Scanning lots for plugin objects and building a report"); + + foreach (var lot in lots) + { + var lotData = ProcessLot(lot, allSigns, allDrawACard, allDoors); + + if (lotData != null) + { + if (AdmitModePublic(lotData.HouseAdmitMode)) + publicHouses.Add(lotData); + else + privateHouses.Add(lotData); + } + } + + LOG.Info("Done - outputting pluginReview.json"); + + var result = new ReviewJson() + { + PublicHouses = [.. publicHouses], + PrivateHouses = [.. privateHouses] + }; + + var json = JsonConvert.SerializeObject(result, Formatting.Indented); + + File.WriteAllText("pluginReview.json", json); + } + + } + + // - Find all objects with the interesting plugins, and their owner lots. + // - Without a lot, the plugin data should be lost. + // - Load a lot from the list. Determine a spawn location for a sim at the mailbox. + // - Find the plugin object on the lot and load the plugin data. + // - If there's plugin data, try see if the object is reachable from the start position. + // - Signs can be read from a distance, cards must be accessed from the front. + // - Consider the use of teleporters (reachable tps should have their destinations as possible new starting locations), and some special doors that can be passed through (escape room) + // - Permission doors are special in that their data should always be cleared (but their effect on routing calculations remains) + + // The user then hand validates the list of plugins that were accepted/rejected automatically, then can modify the json to specifically allow/deny entries based on opinion. + // This should be with respect to if the content should remain private or not. You can always feed back in the JSON without review. + + return 0; + } + + private SignPluginJson ReadSign(uint objectPID) + { + var data = LoadPluginPersist(objectPID, SIGN_PLUGIN); + + if (data != null) + { + try + { + var parsed = new VMEODSignsData(data); + + return new SignPluginJson() + { + ObjectID = objectPID, + SignFlags = parsed.Flags, + Message = parsed.Text + }; + } + catch (Exception ex) + { + return null; + } + } + + return null; + } + + private CardPluginJson ReadCards(uint objectPID) + { + var data = LoadPluginPersist(objectPID, DRAW_CARD_PLUGIN); + + if (data != null) + { + try + { + var parsed = new VMEODGameCompDrawACardData(data); + + return new CardPluginJson() + { + ObjectID = objectPID, + Title = parsed.GameTitle, + Description = parsed.GameDescription, + CardContents = [.. parsed.CardText], + }; + } + catch (Exception ex) + { + return null; + } + } + + return null; + } + + private DoorPluginJson ReadDoor(uint objectPID) + { + var data = LoadPluginPersist(objectPID, PERMISSION_DOOR_PLUGIN); + + if (data != null) + { + uint result = 0; + if (uint.TryParse(System.Text.Encoding.UTF8.GetString(data), out result)) + { + return new DoorPluginJson() + { + ObjectID = objectPID, + Code = result, + Delete = true, + }; + } + } + + return null; + } + + private string PluginPersistPath(uint objectPID, uint pluginID) + { + var objStr = objectPID.ToString("x8"); + return Path.Combine(Config.SimNFS, "Objects/" + objStr + "/Plugin/" + pluginID.ToString("x8") + ".dat"); + } + + private byte[] LoadPluginPersist(uint objectPID, uint pluginID) + { + if (objectPID == 0) return null; + try + { + var path = PluginPersistPath(objectPID, pluginID); + + if (!File.Exists(path)) + { + return null; + } + + //if path does not exist, will throw FileNotFoundException + using (var file = File.Open(path, FileMode.Open)) + { + var dat = new byte[file.Length]; + file.ReadExactly(dat); + return dat; + } + } + catch (Exception e) + { + //todo: specific types of exception that can be thrown here? instead of just catching em all + /* + if (!(e is FileNotFoundException)) + //LOG.Error(e, + Console.WriteLine("Failed to load plugin persist for object " + objectPID.ToString("x8") + " plugin " + pluginID.ToString("x8") + "!"); + */ + return null; + } + } + } +} diff --git a/TSOClient/FSO.Server/ToolRunServer.cs b/TSOClient/FSO.Server/ToolRunServer.cs index ba6cb06cb..b7b9a2570 100644 --- a/TSOClient/FSO.Server/ToolRunServer.cs +++ b/TSOClient/FSO.Server/ToolRunServer.cs @@ -1,12 +1,14 @@ -using FSO.Common.DataService.Framework; +using FSO.Common.Domain; using FSO.Common.Utils; using FSO.Server.Common; +using FSO.Server.Database.DA; using FSO.Server.DataService; using FSO.Server.Domain; using FSO.Server.Protocol.Electron.Packets; using FSO.Server.Servers; using FSO.Server.Servers.City; using FSO.Server.Servers.Lot; +using FSO.Server.Servers.Lot.Lifecycle; using FSO.Server.Servers.Tasks; using FSO.Server.Servers.UserApi; using FSO.Server.Utils; @@ -49,10 +51,10 @@ public ToolRunServer(RunServerOptions options, ServerConfiguration config, IKern this.HostPool = hostPool; } - public int Run() + public int RunEmbedded(Action onStarted, Action onProgress = null, Action onError = null) { - LOG.Info("Starting server"); - TimedReferenceController.SetMode(CacheType.PERMANENT); + onProgress?.Invoke(0); + LOG.Info("Starting embedded server"); if (Config.Services == null) { @@ -60,41 +62,76 @@ public int Run() return 1; } - if (!Directory.Exists(Config.GameLocation)) - { - LOG.Fatal("The directory specified as gameLocation in config.json does not exist"); - return 1; - } - Directory.CreateDirectory(Config.SimNFS); Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Lots/")); Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Objects/")); - if (Content.Model.AbstractTextureRef.ImageFetchFunction == null) - Content.Model.AbstractTextureRef.ImageFetchFunction = Utils.CoreImageLoader.SoftImageFetch; + Content.Model.AbstractTextureRef.ImageFetchFallback = Utils.CoreImageLoader.SoftImageFetch; - LOG.Info("Checking for scheduled updates..."); - if (AutoUpdateUtility.QueueUpdateIfRequired(Kernel, Config.UpdateBranch)) + if (Config.Archive == null) { - //update queued, restart - LOG.Info("An update was scheduled, and has been queued for the watchdog to apply. Restarting..."); - return 4; + throw new Exception("Can only run archive server embedded. Check configuration."); } - //get server update ID if present in a file (from auto updater) - if (File.Exists("updateID.txt")) + Content.Content.Get().Upgrades.LoadJSONTuning(); + + onProgress?.Invoke(10); + + // Don't really need performance reporting, and it causes a startup delay. + CityConnections.UseCounters = false; + + // Cities take 20%, lots take 20%, tasks take 10% + CommonInit(onProgress); + + onProgress?.Invoke(60); + + int i = 0; + LOG.Info("Starting services"); + foreach (AbstractServer server in Servers) { - var stringID = File.ReadAllText("updateID.txt"); - int id; - if (int.TryParse(stringID, out id)) { - Config.UpdateID = id; + LOG.Info("Starting " + server.GetType().ToString() + "..."); + server.Start(); + + onProgress?.Invoke(60 + ((20f * (++i)) / Servers.Count)); + } + + onProgress?.Invoke(80); + + HostPool.Start(); + + onProgress?.Invoke(100); + + onStarted(() => + { + RequestedShutdown(0, ShutdownType.SHUTDOWN); + }); + + //Hacky reference to maek sure the assembly is included + FSO.Common.DatabaseService.Model.LoadAvatarByIDRequest x; + + { + while (Running) + { + Thread.Sleep(50); + lock (Servers) + { + if (Servers.Count == 0) + { + LOG.Info("All servers shut down, shutting down pool..."); + + Kernel.Get().Stop(); + + return 2; + } + } } } - //TODO: Some content preloading - LOG.Info("Scanning content"); - VMContext.InitVMConfig(false); - Content.Content.Init(Config.GameLocation, Content.ContentMode.SERVER); + return 1; + } + + private void CommonInit(Action onProgress = null) + { Kernel.Bind().ToConstant(Content.Content.Get()); Kernel.Bind().ToConstant(new MemoryCache("fso_server")); @@ -105,23 +142,49 @@ public int Run() CityServers = new List(); Kernel.Bind().ToConstant(new ServerNFSProvider(Config.SimNFS)); + if (Config.Events.HasValue) + { + LOG.Info("Scheduling events"); + + try + { + EventGenerator.GenerateEvents(Kernel.Get(), Config.Events.Value); + } + catch (Exception e) + { + LOG.Warn($"Unable to schedule events - may be in an incomplete state.", e); + } + } + if (Config.Services.UserApi != null && Config.Services.UserApi.Enabled) { - var childKernel = new ChildKernel( - Kernel - ); - var api = new UserApi(Config, childKernel); - ActiveUApiServer = api; - Servers.Add(api); - api.OnRequestShutdown += RequestedShutdown; - api.OnBroadcastMessage += BroadcastMessage; - api.OnRequestUserDisconnect += RequestedUserDisconnect; - api.OnRequestMailNotify += RequestedMailNotify; + if (Config.Archive == null || Config.Archive.AllowUserApi) + { + var childKernel = new ChildKernel( + Kernel + ); + var api = new UserApi(Config, childKernel); + ActiveUApiServer = api; + Servers.Add(api); + api.OnRequestShutdown += RequestedShutdown; + api.OnBroadcastMessage += BroadcastMessage; + api.OnRequestUserDisconnect += RequestedUserDisconnect; + api.OnRequestMailNotify += RequestedMailNotify; + } + else + { + LOG.Info("Skipping User API for Archive Server (shouldn't be in the config...)"); + } } + int i = 0; + foreach (var cityServer in Config.Services.Cities) { + if (cityServer.Archive == null) cityServer.Archive = Config.Archive; + if (!cityServer.AllOpenable) cityServer.AllOpenable = Config.AllOpenable; + /** * Need to create a kernel for each city server as there is some data they do not share */ @@ -134,10 +197,16 @@ public int Run() var city = childKernel.Get(new ConstructorArgument("config", cityServer)); CityServers.Add(city); Servers.Add(city); + + onProgress?.Invoke(10 + (20 * (++i)) / Config.Services.Cities.Count); } + i = 0; foreach (var lotServer in Config.Services.Lots) { + if (lotServer.Archive == null) lotServer.Archive = Config.Archive; + if (!lotServer.AllOpenable) lotServer.AllOpenable = Config.AllOpenable; + if (lotServer.SimNFS == null) lotServer.SimNFS = Config.SimNFS; var childKernel = new ChildKernel( Kernel, @@ -147,6 +216,8 @@ public int Run() Servers.Add( childKernel.Get(new ConstructorArgument("config", lotServer)) ); + + onProgress?.Invoke(30 + (20 * (++i)) / Config.Services.Lots.Count); } if (Config.Services.Tasks != null @@ -172,6 +243,60 @@ public int Run() } Running = true; + } + + public int Run() + { + LOG.Info("Starting server"); + TimedReferenceController.SetMode(CacheType.PERMANENT); + + if (Config.Services == null) + { + LOG.Warn("No services found in the configuration file, exiting"); + return 1; + } + + if (!Directory.Exists(Config.GameLocation)) + { + LOG.Fatal("The directory specified as gameLocation in config.json does not exist"); + return 1; + } + + Directory.CreateDirectory(Config.SimNFS); + Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Lots/")); + Directory.CreateDirectory(Path.Combine(Config.SimNFS, "Objects/")); + + if (Content.Model.AbstractTextureRef.ImageFetchFunction == null) + Content.Model.AbstractTextureRef.ImageFetchFunction = Utils.CoreImageLoader.SoftImageFetch; + + LOG.Info("Checking for scheduled updates..."); + if (AutoUpdateUtility.QueueUpdateIfRequired(Kernel, Config.UpdateBranch)) + { + //update queued, restart + LOG.Info("An update was scheduled, and has been queued for the watchdog to apply. Restarting..."); + return 4; + } + + //get server update ID if present in a file (from auto updater) + if (File.Exists("updateID.txt")) + { + var stringID = File.ReadAllText("updateID.txt"); + int id; + if (int.TryParse(stringID, out id)) { + Config.UpdateID = id; + } + } + + if (Config.Archive != null) + { + LOG.Info("=== RUNNING IN ARCHIVE MODE! Only archive authentication will work! ==="); + } + + //TODO: Some content preloading + LOG.Info("Scanning content"); + VMContext.InitVMConfig(false); + Content.Content.Init(Config.GameLocation, Content.ContentMode.SERVER); + CommonInit(); AppDomain.CurrentDomain.DomainUnload += CurrentDomain_DomainUnload; AppDomain.CurrentDomain.ProcessExit += CurrentDomain_ProcessExit; diff --git a/TSOClient/FSO.Server/ToolSqliteImport.cs b/TSOClient/FSO.Server/ToolSqliteImport.cs new file mode 100644 index 000000000..bdaedd8ef --- /dev/null +++ b/TSOClient/FSO.Server/ToolSqliteImport.cs @@ -0,0 +1,861 @@ +using FSO.Server.Database.DA; +using FSO.Server.Utils; +using NLog; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace FSO.Server +{ + public class ToolSqliteImport : ITool + { + private struct IndexDescription + { + public string Identifier; + public string ColumnList; + } + + private static Logger LOG = LogManager.GetCurrentClassLogger(); + private IDAFactory DAFactory; + private SqliteImportOptions Options; + private ServerConfiguration Config; + + private Regex CreateRegex = new Regex("^CREATE TABLE (?[A-Za-z0-9_`]+) \\("); + private Regex ColumnRegex = new Regex("^(?\\s*[A-Za-z0-9_`]+\\s)(?[a-z]+(\\([A-Za-z0-9_\\,']+\\))?)(?\\sunsigned)?(?(\\s.*))(?,?)$"); + private Regex KeyRegex = new Regex("^\\s*((?[A-Z]+)\\s)?KEY(\\s(?[A-Za-z0-9_`]+))?\\s(?\\([a-z0-9_`,()]+\\))(?,?)$"); + private Regex CommentRegex = new Regex("(^|\\s)COMMENT\\s'.*'"); + + private Regex InsertRegex = new Regex("^\\s*INSERT INTO (?[A-Za-z0-9_`]+) VALUES \\("); + + private Regex RemoveCountsRegex = new Regex("`\\([0-9]+\\)"); + private Regex CommentStartRegex = new Regex("/\\*![0-9]{5}"); + + private string InventoryStateColumn = @"ALTER TABLE `fso_objects` + ADD COLUMN `inventory_state` BLOB DEFAULT NULL;"; + + public static string[] ImportOrder = new string[] + { + //=== Free (no dependency) + + "fso_auth_tickets", + "fso_db_changes", + "fso_dyn_payouts", + "fso_email_confirm", + "fso_events", + "fso_lot_claims", + "fso_relationships", //somehow... + "fso_shard_tickets", + "fso_transactions", + "fso_tuning", + + //=== + + "fso_update_addons", + + //--- + + "fso_update_branch", //(fso_update_addons) + + //--- + + "fso_updates", //(fso_update_addons, fso_update_branch, fso_updates) + + //--- + + "fso_users", + "fso_shards", //(fso_updates) + + //--- + + "fso_user_authenticate", //(fso_users) + "fso_auth_attempts", //(fso_users) + + "fso_lots", //(fso_shards) + + "fso_election_cycles", //(fso_neighborhoods ^) + + "fso_avatars", //(fso_users, fso_shards, fso_neighborhoods ^) + + "fso_event_participation", //(fso_events, fso_users) + + //=== CIRCULAR === + + "fso_neighborhoods", //(fso_election_cycles, fso_avatars, fso_shards, fso_lots) + + //--- + + "fso_bonus", //(fso_avatars) + "fso_bookmarks", //(fso_avatars) + "fso_avatar_claims", //(fso_avatars) + "fso_bulletin_posts", //(fso_lots, fso_neighborhoods, fso_avatars) + + "fso_election_candidates", //(fso_avatars, fso_election_cycles) + "fso_election_cyclemail", //(fso_avatars, fso_election_cycles) + "fso_election_freevotes", //(fso_avatars, fso_election_cycles, fso_neighborhoods) + "fso_election_votes", //(fso_election_cycles, fso_avatars) + + "fso_generic_avatar_participation", //(fso_avatars) + "fso_global_cooldowns", //(fso_avatars, fso_users) + "fso_inbox", //(fso_avatars) + "fso_ip_ban", //(fso_users) + "fso_joblevels", //(fso_avatars) + "fso_lot_admit", //(fso_avatars, fso_lots) + "fso_lot_top_100", //(fso_lots, fso_shards) + "fso_lot_visit_totals", //(fso_lots) + "fso_lot_visits", //(fso_avatars, fso_lots) + + "fso_mayor_ratings", //(fso_users, fso_avatars, fso_users, fso_avatars) + "fso_nhood_ban", //(fso_users) + "fso_objects", //(fso_lots, fso_shards, fso_avatars) + "fso_roommates", //(fso_avatars, fso_lots) + "fso_tasks", //(fso_shards) + + "temp_candy_punish", //(fso_avatars), shouldn't be imported + + //--- + + "fso_object_attributes", //(fso_objects) + "fso_lot_server_tickets", //(fso_avatar_claims) + + //--- + + "fso_outfits", //(fso_avatars, fso_objects) + + //--- + + "fso_hosts", //(fso_updates, fso_shards) + + //--- (events) + + "fso_tuning_presets", + + //--- + + "fso_tuning_preset_items", //(fso_tuning_presets) + + //"routines" //TODO... sqlite does not support stored procedures or functions, so these need to be moved to the da code + }; + + public ToolSqliteImport(SqliteImportOptions options, IDAFactory factory, ServerConfiguration config) + { + DAFactory = factory; + Options = options; + Config = config; + } + + public string RemoveComments(string sql) + { + int startIndex = 0; + do + { + var startMatch = CommentStartRegex.Match(sql, startIndex); + startIndex = !startMatch.Success ? -1 : startMatch.Index; + //startIndex = sql.IndexOf("/*", startIndex); + + if (startIndex != -1) + { + int endIndex = sql.IndexOf("*/", startIndex + 2); + + if (endIndex != -1) + { + sql = sql.Substring(0, startIndex) + sql.Substring(endIndex + 2); + } + } + } while (startIndex != -1); + + return sql; + } + + private enum InsertValuesContext + { + Root, + Tuple, + String + } + + public string SqliteEscape(string value, char quoteChar, bool isBinary) + { + if (isBinary) + { + // Render as a hex string + var data = StringToBytes(value); + var sb = new StringBuilder(); + + sb.Append('x'); + sb.Append(quoteChar); + + foreach (byte b in data) + { + sb.Append(b.ToString("x2")); + } + + sb.Append(quoteChar); + + return sb.ToString(); + } + + bool hasQuote = value.IndexOf(quoteChar) != -1; + + if (hasQuote) + { + value = value.Replace($"{quoteChar}", $"{quoteChar}{quoteChar}"); + } + + // Reinterpret the string as UTF8. + + value = Encoding.UTF8.GetString(StringToBytes(value)); + + return $"{quoteChar}{value}{quoteChar}"; + } + + public string RewriteInsert(string line, int valuesBegin, List columnBinary) + { + // Comma separated array of (value1, value2, ...) until the end of the line. + // When inserting string values, use special handling to remove mysql escape characters and work in sqlite escapes. + + var result = new StringBuilder(); + result.Append(line.Substring(0, valuesBegin)); + + char quotechar = '"'; + var stringBuilder = new StringBuilder(); + int tupleIndex = 0; + InsertValuesContext context = InsertValuesContext.Root; + + for (int i = valuesBegin; i < line.Length; i++) + { + char c = line[i]; + switch (context) + { + case InsertValuesContext.Root: + result.Append(c); + + if (c == '(') + { + // Begins a value tuple + context = InsertValuesContext.Tuple; + tupleIndex = 0; + } + break; + case InsertValuesContext.Tuple: + if (c == '\'' || c == '"') + { + stringBuilder.Clear(); + quotechar = c; + context = InsertValuesContext.String; + } + else + { + result.Append(c); + if (c == ',') + { + tupleIndex++; + } + else if (c == ')') + { + context = InsertValuesContext.Root; + } + } + break; + case InsertValuesContext.String: + if (c == quotechar) + { + // End of string. + // re-escape the string for sqlite and append it to the result + + result.Append(SqliteEscape(stringBuilder.ToString(), quotechar, columnBinary[tupleIndex])); + + context = InsertValuesContext.Tuple; + } + else if (c == '\\' && i + 1 < line.Length) + { + // Begin mysql escape character + bool keepBackslash = false; + + char escapeType = line[++i]; + char escape = escapeType; + + switch (escapeType) + { + case '0': + escape = '\0'; + break; + case '\'': + escape = '\''; + break; + case '"': + escape = '"'; + break; + case 'b': + escape = '\b'; + break; + case 'n': + escape = '\n'; + break; + case 'r': + escape = '\r'; + break; + case 't': + escape = '\t'; + break; + case 'Z': + escape = '\u001a'; + break; + case '\\': + escape = '\\'; + break; + + // different if in pattern matching context... but they include the \ otherwise. + case '%': + keepBackslash = true; + escape = '%'; + break; + case '_': + keepBackslash = true; + escape = '_'; + break; + } + + if (keepBackslash) + { + stringBuilder.Append('\\'); + } + + stringBuilder.Append(escape); + } + else + { + stringBuilder.Append(c); + } + + break; + } + } + + return result.ToString(); + } + + public string MysqlEscapesToSqlite(string sql) + { + char[] output = new char[sql.Length]; + int outputLength = 0; + + for (int i = 0; i < sql.Length - 1; i++) + { + if (sql[i] == '\\') + { + bool keepBackslash = false; + + char escapeType = sql[++i]; + char result = escapeType; + + switch (escapeType) + { + case '0': + result = '\0'; + break; + case '\'': + result = '\''; + break; + case '"': + result = '"'; + break; + case 'b': + result = '\b'; + break; + case 'n': + result = '\n'; + break; + case 'r': + result = '\r'; + break; + case 't': + result = '\t'; + break; + case 'Z': + result = '\u001a'; + break; + case '\\': + result = '\\'; + break; + + // different if in pattern matching context... but they include the \ otherwise. + case '%': + keepBackslash = true; + result = '%'; + break; + case '_': + keepBackslash = true; + result = '_'; + break; + } + + if (keepBackslash) + { + output[outputLength++] = '\\'; + output[outputLength++] = result; + } + else + { + if (result == '\'') + { + // Sqlite doubles the quote. Assume that we're only ever in single quotes, as that's how mariadb export works. + output[outputLength++] = '\''; + output[outputLength++] = '\''; + } + else + { + output[outputLength++] = result; + } + } + } + else + { + output[outputLength++] = sql[i]; + } + } + + return new String(output, 0, outputLength); + } + + public List ScanDumps() + { + var files = Directory.GetFiles(Options.ImportDir); + + var ordered = new List(); + var missing = new List(); + var toRead = new HashSet(files); + + string startAt = null; + + var fileList = startAt == null ? ImportOrder : ImportOrder.Skip(Array.IndexOf(ImportOrder, startAt)); + + foreach (var file in fileList) + { + var expectedName = Path.Combine(Options.ImportDir, $"fso_{file}.sql"); + + if (toRead.Contains(expectedName)) + { + ordered.Add(expectedName); + toRead.Remove(expectedName); + } + else + { + missing.Add(expectedName); + } + } + + return ordered; + } + + public string RewriteImport(string name, string sql) + { + sql = sql.Replace("\r", ""); + + sql = RemoveComments(sql); + + var indexes = new List(); + + // Split the query into lines. Assumes formatting from mariadb export. + + var lines = sql.Split('\n').Where(line => + { + // Remove some unsupported queries. + + if (line.StartsWith("CREATE DATABASE ")) + { + return false; + } + + if (line == ";" || line == ";;") + { + return false; + } + + if (line == "USE `fso`;") + { + return false; + } + + if (line.StartsWith("LOCK TABLES ")) + { + return false; + } + + if (line == "UNLOCK TABLES;") + { + return false; + } + + if (line.StartsWith("DELIMITER ")) + { + return false; + } + + return true; + }).ToList(); + + // Second, try to find the range of the "create table" query. + // Assume there is only one of these per file... + + bool creatingTable = false; + bool anyAutoIncrement = false; + string tableIdentifier = ""; + + var columnBinary = new List(); + + for (int i = 0; i < lines.Count; i++) + { + string line = lines[i]; + + if (creatingTable) + { + if (line.StartsWith(") ENGINE=InnoDB")) + { + creatingTable = false; + + // Previous line can't end with a comma. + var lastLine = lines[i - 1]; + if (lastLine[lastLine.Length - 1] == ',') + { + lines[i - 1] = lastLine.Substring(0, lastLine.Length - 1); + } + + // TODO: remember then apply rowid? UPDATE SQLITE_SEQUENCE SET seq = WHERE name = '' + lines[i] = ");"; // Default charset is already utf-8, can't use innodb. + + // Create indexes before inserting stuff. + foreach (var index in indexes) + { + lines.Insert(++i, $"CREATE INDEX {index.Identifier} ON {tableIdentifier}{index.ColumnList};"); + } + continue; + } + + // Rewrite individual lines to fix type definitions, attributes like auto increment, and primary/foreign/unique keys. + + var keyMatch = KeyRegex.Match(line); + + if (keyMatch.Success) + { + var keyType = keyMatch.Groups["KeyType"].Value; + var identifier = keyMatch.Groups["Identifier"].Value; + var columns = keyMatch.Groups["Columns"].Value; + var comma = keyMatch.Groups["Comma"].Value; + + columns = RemoveCountsRegex.Replace(columns, "`"); + + switch (keyType) + { + case "UNIQUE": + // Remove "KEY" and identifier. + lines[i] = $" UNIQUE {columns}{comma}"; + break; + case "PRIMARY": + // Keep intact + if (anyAutoIncrement) + { + // TODO: check if primary key is composite and leave the other ones... + lines.RemoveAt(i--); + } + else + { + lines[i] = $" {keyType}{(keyType == "" ? "" : " ")}KEY {identifier}{(identifier == "" ? "" : " ")}{columns}{comma}"; + } + break; + default: + // Remove + if (keyType == "") + { + // This is an index - we want to add these at the end with CREATE INDEX. + + indexes.Add(new IndexDescription() + { + Identifier = identifier, + ColumnList = columns + }); + } + lines.RemoveAt(i--); + break; + } + } + else + { + // Probably a column definition + var parsed = ColumnRegex.Match(line); + + if (parsed.Success) + { + var identifier = parsed.Groups["Identifier"].Value; + var type = parsed.Groups["Type"].Value; + var unsigned = parsed.Groups["Unsigned"].Value; + var extras = parsed.Groups["Extras"].Value.TrimEnd(','); + extras = CommentRegex.Replace(extras, ""); + extras = extras.Replace(" ON UPDATE current_timestamp()", ""); // TODO: make this a trigger automatically? + + bool isBlob = type.Contains("blob") || type.Contains("binary"); + + bool autoIncrement = false; + + var extrasSplit = extras.Split(' ').Where(extra => extra != "zerofill").Select(extra => + { + switch (extra) + { + case "current_timestamp()": + return "current_timestamp"; + case "AUTO_INCREMENT": + // Supposedly expensive, but we need this to match mysql. + autoIncrement = true; + anyAutoIncrement = true; + return "PRIMARY KEY AUTOINCREMENT"; + } + + return extra; + }).ToList(); + + if (autoIncrement) + { + // Must be signed integer to allow this. + type = "INTEGER"; + unsigned = ""; + } + + var unsignedPrefix = (unsigned != "") ? "unsigned " : ""; + + if (type.StartsWith("enum(")) + { + // Convert enum into string with checks + + var trimdentifier = identifier.Trim(); + + type = $"TEXT CHECK({trimdentifier} IN {type.Substring(4)})"; + } + + lines[i] = $"{identifier}{unsignedPrefix}{type}{string.Join(" ", extrasSplit)},"; + + columnBinary.Add(isBlob); + } + else + { + LOG.Error($"Unknown line: {line}"); + } + } + } + else + { + var createMatch = CreateRegex.Match(line); + if (createMatch.Success) + { + creatingTable = true; + + tableIdentifier = createMatch.Groups["Identifier"].Value; + } + + var insertMatch = InsertRegex.Match(line); + if (!creatingTable && insertMatch.Success) + { + lines[i] = RewriteInsert(line, insertMatch.Length - 1, columnBinary); + } + else + { + if (line.IndexOf("\\") != -1) + { + lines[i] = MysqlEscapesToSqlite(line); + } + } + } + } + + return string.Join("\n", lines); + } + + private void RunCommand(SqlDA da, string sql) + { + var context = da.Context; + + var command = context.Connection.CreateCommand(); + + command.CommandText = sql; + + try + { + var result = command.ExecuteNonQuery(); + } + catch (Exception e) + { + throw e; + } + } + + public void ImportTable(string name, string sql) + { + LOG.Info($"Importing table {name}..."); + + using (var da = (SqlDA)DAFactory.Get()) + { + var sqlRewrite = RewriteImport(name, sql); + + RunCommand(da, sqlRewrite); + } + } + + public void CreateTriggers() + { + LOG.Info($"Creating triggers..."); + + using (var da = (SqlDA)DAFactory.Get()) + { + var context = da.Context; + + foreach (var trigger in SqliteFunctions.All) + { + RunCommand(da, trigger); + } + } + } + + public void SetPragmas() + { + using (var da = (SqlDA)DAFactory.Get()) + { + RunCommand(da, "PRAGMA journal_mode=WAL;"); + } + } + + private string BytesToString(byte[] data) + { + // Like ascii but funnier. + var result = new char[data.Length]; + + int i = 0; + foreach (var b in data) + { + result[i++] = (char)b; + } + + return new string(result); + } + + private byte[] StringToBytes(string data) + { + // Like ascii but funnier. + var result = new byte[data.Length]; + + int i = 0; + foreach (var c in data) + { + result[i++] = (byte)c; + } + + return result; + } + + private bool DeleteIfEmpty(string dir) + { + if (Directory.GetFileSystemEntries(dir).Length == 0) + { + Directory.Delete(dir); + + return true; + } + + return false; + } + + public void MigrateInventoryState() + { + // First, create the table in the database. + using (var da = (SqlDA)DAFactory.Get()) + { + LOG.Info($"Adding inventory state column to database..."); + try + { + RunCommand(da, InventoryStateColumn); + } + catch (Exception) + { + LOG.Info($"- Seems like it's already there... continuing."); + } + + var objectDir = Path.Combine(Config.SimNFS, "Objects/"); + var objs = Path.Exists(objectDir) ? Directory.GetDirectories(objectDir) : []; + LOG.Info($"Migrating inventory to database... ({objs.Length} entries)"); + + int migratedCount = 0; + int folderDeletionCount = 0; + int processedCount = 0; + + foreach (var obj in objs) + { + if (!uint.TryParse(Path.GetFileName(obj), NumberStyles.HexNumber, null, out uint id)) + { + continue; + } + + var statePath = Path.Combine(obj, "inventoryState.fsoo"); + if (File.Exists(statePath)) + { + var data = File.ReadAllBytes(statePath); + + if (!da.Objects.SetDbObjectState(id, data)) + { + LOG.Info($"Current database configuration does not support inventory in database."); + return; // invalid? + } + + File.Delete(statePath); + + migratedCount++; + + if (DeleteIfEmpty(obj)) + { + folderDeletionCount++; + } + } + + if ((++processedCount % 1000) == 0) + { + LOG.Info($"- {processedCount}/{objs.Length}..."); + } + } + + LOG.Info($"Finished migration: {migratedCount}/{objs.Length} objects migrated to db, {folderDeletionCount} folders deleted."); + } + } + + private void Commit() + { + using (var da = (SqlDA)DAFactory.Get()) + { + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + RunCommand(da, "vacuum"); + RunCommand(da, "PRAGMA wal_checkpoint(TRUNCATE)"); + } + } + + public int Run() + { + SetPragmas(); + + var files = ScanDumps(); + + foreach (var file in files) + { + var sql = BytesToString(File.ReadAllBytes(file)); + + ImportTable(Path.GetFileNameWithoutExtension(file), sql); + } + + CreateTriggers(); + + MigrateInventoryState(); + + Commit(); + + return 1; + } + } +} diff --git a/TSOClient/FSO.Server/Utils/ArchiveFeaturedJSON.cs b/TSOClient/FSO.Server/Utils/ArchiveFeaturedJSON.cs new file mode 100644 index 000000000..43311bf08 --- /dev/null +++ b/TSOClient/FSO.Server/Utils/ArchiveFeaturedJSON.cs @@ -0,0 +1,11 @@ +namespace FSO.Server.Utils +{ + internal class ArchiveFeaturedJSON + { + public string name { get; set; } + public int lot_id { get; set; } + public int category { get; set; } + public string description { get; set; } + public int? mismatch { get; set; } + } +} diff --git a/TSOClient/FSO.Server/Utils/CoreImageLoader.cs b/TSOClient/FSO.Server/Utils/CoreImageLoader.cs index 35fecfbf4..d5daaf9a8 100644 --- a/TSOClient/FSO.Server/Utils/CoreImageLoader.cs +++ b/TSOClient/FSO.Server/Utils/CoreImageLoader.cs @@ -1,8 +1,8 @@ using FSO.Content.Model; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using System; -using System.IO; + +using XnaColor = Microsoft.Xna.Framework.Color; namespace FSO.Server.Utils { @@ -10,26 +10,81 @@ public class CoreImageLoader { public static TexBitmap SoftImageFetch(Stream stream, AbstractTextureRef texRef) { - Image result = null; + Image result = null; try { - result = Image.Load(stream); + result = Image.Load(stream); } catch (Exception) { return new TexBitmap() { Data = new byte[0] }; } - stream.Close(); - + finally + { + stream.Close(); + } + if (result == null) return null; + var pixels = new byte[result.Width * result.Height * 4]; + result.CopyPixelDataTo(pixels); + return new TexBitmap { - Data = result.SavePixelData(), + Data = pixels, Width = result.Width, Height = result.Height, PixelSize = 4 }; } + + public static void SavePNG(XnaColor[] data, int width, int height, Stream stream) + { + var image = new Image(width, height); + + int i = 0; + image.ProcessPixelRows(accessor => + { + for (int y = 0; y < accessor.Height; y++) + { + Span pixelRow = accessor.GetRowSpan(y); + + foreach (ref Bgra32 pixel in pixelRow) + { + var color = data[i++]; + + pixel = new Bgra32(color.R, color.G, color.B, color.A); + } + } + }); + + image.SaveAsPng(stream); + } + + public static bool ValidatePNG(byte[] data, int width, int height) + { + // TODO: Ideally do this without loading the image data? + try + { + var image = Image.Load(data); + + if (image.Width != width || image.Height != height) + { + return false; + } + + if (!image.Metadata.TryGetPngMetadata(out var meta)) + { + // Must be a PNG. + return false; + } + } + catch (Exception) + { + return false; + } + + return true; + } } } diff --git a/TSOClient/FSO.Server/Utils/EventGenerator.cs b/TSOClient/FSO.Server/Utils/EventGenerator.cs new file mode 100644 index 000000000..2d0814a52 --- /dev/null +++ b/TSOClient/FSO.Server/Utils/EventGenerator.cs @@ -0,0 +1,363 @@ +using FSO.Common; +using FSO.Server.Database.DA; +using FSO.Server.Database.DA.Tuning; + +namespace FSO.Server.Utils +{ + internal static class EventGenerator + { + public static void GenerateEvents(IDAFactory daFactory, EventConfig config) + { + using var da = daFactory.Get(); + + var tuning = da.Tuning.All(); + var presets = da.Tuning.GetAllPresets().ToList(); + var events = da.Events.All(limit: 9999); + + foreach (var modifier in config.modifiers) + { + var (start, end) = EventConfig.GetNextRange(modifier.startDate, modifier.endDate); + foreach (var option in modifier.options) + { + var optionStart = start; + var optionEnd = end; + + if (option.startDate != null && option.endDate != null) + { + (optionStart, optionEnd) = EventConfig.GetNextRange(option.startDate, option.endDate); + } + + if (!config.timed) + { + optionStart = DateTime.MinValue; + optionEnd = DateTime.MaxValue; + } + + var disabled = !(config.timed ? option.enableTimed : option.enableManual); + + if (option.tuning.Count > 0) + { + // Put this option's tuning into a preset + var presetLabel = $"{modifier.label}: {option.label}"; + var presetIdentifier = $"{modifier.name}-{option.name}"; + + var matchingPreset = presets.Find(preset => preset.description == presetIdentifier && preset.flags == 1); + + if (matchingPreset != null) + { + EnsurePresetItems(da, matchingPreset, option.tuning); + } + else + { + matchingPreset = new Database.DA.Tuning.DbTuningPreset() + { + name = presetLabel, + description = presetIdentifier, + flags = 1, + }; + + matchingPreset.preset_id = da.Tuning.CreatePreset(matchingPreset); + + EnsurePresetItems(da, matchingPreset, option.tuning, true); + } + + // Does the event need updated? + var existingEvent = events.Find(x => x.type == Database.DA.DbEvents.DbEventType.obj_tuning && x.value == matchingPreset.preset_id); + + if (existingEvent != null) + { + // Check the parameters... + if (disabled || existingEvent.start_day != optionStart || existingEvent.end_day != optionEnd) + { + da.Events.Delete(existingEvent.event_id); + existingEvent = null; + } + } + + if (existingEvent == null && !disabled) + { + // Create it new + da.Events.Add(new Database.DA.DbEvents.DbEvent() + { + type = Database.DA.DbEvents.DbEventType.obj_tuning, + value = matchingPreset.preset_id, + value2 = 0, + start_day = optionStart, + end_day = optionEnd, + }); + } + } + + if (option.gift != null) + { + var gift = option.gift.Value; + int index = 0; + foreach (var obj in gift.guids) + { + string mail_sender = index == 0 ? $"Event: {option.label}" : null; + string mail_subject = index == 0 ? gift.title : null; + string mail_message = index == 0 ? gift.description : null; + + index++; + + // Does the event need updated? + var existingEvent = events.Find(x => + x.type == Database.DA.DbEvents.DbEventType.free_object && + x.value == (int)obj && + x.value2 == 1 && + x.mail_sender_name == mail_sender && + x.mail_subject == mail_subject && + x.mail_message == mail_message); + + if (existingEvent != null) + { + // Check the parameters... + if (disabled || existingEvent.start_day != optionStart || existingEvent.end_day != optionEnd) + { + da.Events.Delete(existingEvent.event_id); + existingEvent = null; + } + } + + if (existingEvent == null && !disabled) + { + // Create it new + da.Events.Add(new Database.DA.DbEvents.DbEvent() + { + type = Database.DA.DbEvents.DbEventType.free_object, + value = (int)obj, + value2 = 1, + mail_sender_name = mail_sender, + mail_subject = mail_subject, + mail_message = mail_message, + start_day = optionStart, + end_day = optionEnd, + }); + } + } + } + } + } + + var dynTuning = new List(); + var semiglobal = Content.Content.Get().WorldObjectGlobals.Get("skillobjects"); + + float skillSpeed = config.skillSpeed ?? 1; + if (skillSpeed != 1) + { + // Modify the skill completion timings. + + var originalTable = semiglobal.Resource.Tuning.GetTable(8200); + + for (int i = 0; i < 11; i++) + { + short scaledValue = (short)(originalTable.GetKey(i).Value / skillSpeed); + + dynTuning.Add(new DbTuning() + { + tuning_type = "skillobjects.iff", + tuning_table = 8, + tuning_index = i, + value = scaledValue, + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + // Multiplier for skills above 10 + dynTuning.Add(new DbTuning() + { + tuning_type = "global.iff", + tuning_table = 29, + tuning_index = 1, + value = (short)(800 / skillSpeed), + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + float payoutScale = config.payoutScale ?? 1; + if (payoutScale != 1) + { + // Modify the payout multiplier. + + dynTuning.Add(new DbTuning() + { + tuning_type = "income_mul", + tuning_table = 0, + tuning_index = 0, + value = payoutScale, + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + float singleplayerPenalty = config.singleplayerPenalty ?? 1; + if (singleplayerPenalty != 1) + { + // Skills (% modifier) + // Move the bonus for >0 sims into the 0 sim bonus. + var skillTable = semiglobal.Resource.Tuning.GetTable(8198); + + int bonusSkill = 0; + for (int i = 0; i < 6; i++) + { + bonusSkill += skillTable.GetKey(i).Value; + } + + float pctZero = 1 - singleplayerPenalty; + + for (int i = 0; i < 6; i++) + { + int existingValue = skillTable.GetKey(i).Value; + + dynTuning.Add(new DbTuning() + { + tuning_type = "skillobjects.iff", + tuning_table = 6, + tuning_index = i, + value = i == 0 ? (short)(bonusSkill * pctZero) : (short)(existingValue * singleplayerPenalty), + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + // Money + + var moneyTable = semiglobal.Resource.Tuning.GetTable(8196); + // Move the multiplier for the max group into the payout multiplier + int moneyMultiplier = moneyTable.GetKey(4).Value; + + dynTuning.Add(new DbTuning() + { + tuning_type = "skillobjects.iff", + tuning_table = 4, + tuning_index = 4, + value = (short)(moneyMultiplier * singleplayerPenalty), + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + + dynTuning.Add(new DbTuning() + { + tuning_type = "income_mul", + tuning_table = 0, + tuning_index = 1, + value = 1 + ((moneyMultiplier - 10) / 10f) * pctZero, + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + int speedyJobProgression = config.speedyJobProgression ?? 0; + + if (speedyJobProgression == 1) + { + const int RestXpPerDay = 8; + const int FactoryXpPerDay = 8; + const int NcXpPerDay = 7; + + void AllLevels(string owner, Span levelList, int index, Func value) + { + int i = 0; + foreach (var level in levelList) + { + dynTuning.Add(new DbTuning() + { + tuning_type = owner, + tuning_table = level - 4096, + tuning_index = index, + value = value(i++), + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + } + + void AllLevelsConst(string owner, Span levelList, int index, short value) + { + AllLevels(owner, levelList, index, (index) => value); + } + + // Restaurant + Span restaurantLevels = [4096, 4102, 4103, 4104, 4105, 4106, 4107, 4108, 4109, 4110, 4111]; + + AllLevelsConst("oj-rest-controller.iff", restaurantLevels, 4, 0); // Friends requirement to 0 + AllLevels("oj-rest-controller.iff", restaurantLevels, 5, (i) => (short)(i * RestXpPerDay)); + + // Factory + Span factoryLevels = [4096, 4097, 4098, 4099, 4100, 4101, 4102, 4103, 4104, 4105, 4106]; + + AllLevelsConst("oj-robotfactorycontroller.iff", factoryLevels, 3, 0); // Friends requirement to 0 + AllLevels("oj-robotfactorycontroller.iff", factoryLevels, 4, (i) => (short)(i * FactoryXpPerDay)); + + // Nightclub + Span ncLevels = [4104, 4105, 4106, 4107, 4108, 4109, 4110, 4111, 4112, 4113, 4114]; + + AllLevelsConst("oj-nc-controller.iff", ncLevels, 1, 0); // DJ Friends requirement to 0 + AllLevels("oj-nc-controller.iff", ncLevels, 2, (i) => (short)(i * NcXpPerDay)); // DJ xp level + AllLevelsConst("oj-nc-controller.iff", ncLevels, 4, 0); // Dancer Friends requirement to 0 + AllLevels("oj-nc-controller.iff", ncLevels, 5, (i) => (short)(i * NcXpPerDay)); // Dancer xp level + + dynTuning.Add(new DbTuning() + { + tuning_type = "momistation.iff", + tuning_table = 0, + tuning_index = 0, + value = 1, + owner_type = DbTuningType.DYNAMIC, + owner_id = 2 + }); + } + + da.DynPayouts.ReplaceDynTuning(dynTuning, 2); + } + + private static void EnsurePresetItems(IDA da, DbTuningPreset preset, Dictionary tuning, bool isNew = false) + { + var existing = isNew ? [] : da.Tuning.GetPresetItems(preset.preset_id).ToList(); + + foreach (var item in tuning) + { + var split = item.Key.Split(':'); + + if (split.Length != 3 || !int.TryParse(split[1], out int table) || !int.TryParse(split[2], out int index)) + { + continue; + } + + string type = split[0]; + + var existingIndex = existing.FindIndex(x => x.tuning_type == type && x.tuning_table == table && x.tuning_index == index); + + if (existingIndex == -1) + { + da.Tuning.CreatePresetItem(new DbTuningPresetItem() + { + preset_id = preset.preset_id, + tuning_type = type, + tuning_table = table, + tuning_index = index, + value = item.Value, + }); + } + else + { + var existingItem = existing[existingIndex]; + existing.RemoveAt(existingIndex); + + if (existingItem.value != item.Value) + { + da.Tuning.UpdatePresetItemValue(existingItem.item_id, item.Value); + } + } + } + + // Delete anything that shouldn't be in the preset. + foreach (var item in existing) + { + da.Tuning.DeletePreset(item.item_id); + } + } + } +} diff --git a/TSOClient/FSO.Server/Utils/GluonHostPool.cs b/TSOClient/FSO.Server/Utils/GluonHostPool.cs index eb1856917..cb101447b 100644 --- a/TSOClient/FSO.Server/Utils/GluonHostPool.cs +++ b/TSOClient/FSO.Server/Utils/GluonHostPool.cs @@ -250,6 +250,10 @@ public GluonHost(GluonHostPool pool, string callSign, IKernel kernel, ServerConf { session.Write(new RequestChallenge() { CallSign = CallSign, PublicHost = PublicHost, InternalHost = InternalHost }); }); + Router.On((session, message) => + { + session.Write(new RequestChallenge() { CallSign = CallSign, PublicHost = PublicHost, InternalHost = InternalHost }); + }); Router.On((session, message) => { var challenge = (RequestChallengeResponse)message; @@ -327,7 +331,7 @@ public void Connect() { Status = GluonHostStatus.CONNECTING; //TODO: TLS - var endpoint = InternalHost + "101"; + var endpoint = PortTransformer.TransformAddress(InternalHost); Client.Connect(endpoint); } } @@ -386,6 +390,11 @@ public void SetAttribute(string key, object value) { } + public bool HasModerationLevel(int threshold) + { + return true; + } + public void DemandAvatar(uint id, AvatarPermissions permission) { } diff --git a/TSOClient/FSO.Server/Utils/SqliteFunctions.cs b/TSOClient/FSO.Server/Utils/SqliteFunctions.cs new file mode 100644 index 000000000..4d0dd25ec --- /dev/null +++ b/TSOClient/FSO.Server/Utils/SqliteFunctions.cs @@ -0,0 +1,92 @@ +namespace FSO.Server.Utils +{ + /** + * Triggers and functions for the database when running on sqlite. + **/ + internal static class SqliteFunctions + { + public static string AvatarCountLimitTrigger = @"CREATE TRIGGER `fso_avatars_BEFORE_INSERT` BEFORE INSERT ON `fso_avatars` FOR EACH ROW BEGIN + SELECT + CASE + WHEN (SELECT COUNT(*) FROM fso_avatars a WHERE NEW.user_id = a.user_id) >= 3 THEN + RAISE (ABORT, 'Cannot own more than 3 avatars.') + END; +END;"; + + + public static string AvatarBudgetNegativeTrigger = @"CREATE TRIGGER `fso_avatars_BEFORE_UPDATE` BEFORE UPDATE ON `fso_avatars` FOR EACH ROW BEGIN + SELECT + CASE + WHEN NEW.budget<0 THEN + RAISE (ABORT, 'Transaction would cause avatar to have negative budget.') + END; +END;"; + + public static string ObjectBudgetNegativeTrigger = @"CREATE TRIGGER `fso_objects_BEFORE_UPDATE` BEFORE UPDATE ON `fso_objects` FOR EACH ROW BEGIN + SELECT + CASE + WHEN NEW.budget<0 THEN + RAISE (ABORT, 'Transaction would cause object to have negative budget.') + END; +END;"; + + public static string RoommateValidationTrigger = @"CREATE TRIGGER `fso_roommates_BEFORE_INSERT` BEFORE INSERT ON `fso_roommates` FOR EACH ROW BEGIN + SELECT + CASE + WHEN (SELECT COUNT(*) FROM fso_roommates a WHERE NEW.avatar_id = a.avatar_id) > 0 THEN + RAISE (ABORT, 'Cannot be a roommate of more than one lot. (currently, will likely change in future.)') + END; + SELECT + CASE + WHEN (SELECT COUNT(*) FROM fso_roommates a WHERE NEW.lot_id = a.lot_id) >= 8 THEN + RAISE (ABORT, 'Cannot have more than 8 roommates in a lot.') + END; +END;"; + + + public static string OutfitRackLimitTrigger = @"CREATE TRIGGER `fso_outfits_before_insert` BEFORE INSERT ON `fso_outfits` FOR EACH ROW BEGIN + SELECT + CASE + WHEN NEW.object_owner IS NOT NULL AND (SELECT COUNT(*) FROM fso_outfits o WHERE NEW.object_owner = o.object_owner) >= 20 THEN + RAISE (ABORT, 'Cannot have more than 20 outfits in a rack.') + END; +END;"; + + public static string OutfitBackpackLimitTrigger = @"CREATE TRIGGER `fso_outfits_before_update` BEFORE UPDATE ON `fso_outfits` FOR EACH ROW BEGIN + SELECT + CASE + WHEN NEW.avatar_owner IS NOT NULL AND (SELECT COUNT(*) FROM fso_outfits o WHERE NEW.avatar_owner = o.avatar_owner AND o.outfit_type = NEW.outfit_type) >= 5 THEN + RAISE (ABORT, 'Cannot have more than 5 outfits per category in backpack.') + END; +END;"; + + public static string BonusToAvatarTrigger = @"CREATE TRIGGER `fso_bonus_after_insert` AFTER INSERT ON `fso_bonus` FOR EACH ROW BEGIN + UPDATE fso_avatars SET budget = (budget + IFNULL(NEW.bonus_visitor,0) + IFNULL(NEW.bonus_property,0) + IFNULL(NEW.bonus_sim,0)) WHERE avatar_id = NEW.avatar_id; +END;"; + + // I don't know if this works. + public static string SingleVoteTrigger = @"CREATE TRIGGER `fso_election_votes_BEFORE_INSERT` BEFORE INSERT ON `fso_election_votes` FOR EACH ROW BEGIN + SELECT + CASE + WHEN (SELECT COUNT(*) from fso_election_votes v INNER JOIN fso_avatars va ON v.from_avatar_id = va.avatar_id + WHERE v.election_cycle_id = NEW.election_cycle_id AND v.type = NEW.type AND va.user_id IN + (SELECT user_id FROM fso_users WHERE last_ip = + (SELECT last_ip FROM fso_avatars a JOIN fso_users u on a.user_id = u.user_id WHERE avatar_id = NEW.from_avatar_id) + )) > 0 THEN + RAISE (ABORT, 'A vote from this person or someone related already exists for this cycle.') + END; +END;"; + + public static string[] All = + { + AvatarCountLimitTrigger, + AvatarBudgetNegativeTrigger, + ObjectBudgetNegativeTrigger, + RoommateValidationTrigger, + OutfitRackLimitTrigger, + OutfitBackpackLimitTrigger, + BonusToAvatarTrigger, + SingleVoteTrigger, + }; + } +} diff --git a/TSOClient/FSO.Server/packages.config b/TSOClient/FSO.Server/packages.config deleted file mode 100644 index 6a5d172d4..000000000 --- a/TSOClient/FSO.Server/packages.config +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.SimAntics.JIT.Roslyn/FSO.SimAntics.JIT.Roslyn.csproj b/TSOClient/FSO.SimAntics.JIT.Roslyn/FSO.SimAntics.JIT.Roslyn.csproj index e637f13e0..4caff0a1a 100644 --- a/TSOClient/FSO.SimAntics.JIT.Roslyn/FSO.SimAntics.JIT.Roslyn.csproj +++ b/TSOClient/FSO.SimAntics.JIT.Roslyn/FSO.SimAntics.JIT.Roslyn.csproj @@ -1,7 +1,22 @@ - + - netstandard2.0 + net9.0 + enable + disable + True + true + true + true + full + + + + True + + + + True diff --git a/TSOClient/FSO.SimAntics.JIT.Roslyn/VMRoslynRoutine.cs b/TSOClient/FSO.SimAntics.JIT.Roslyn/VMRoslynRoutine.cs index 8435f0f03..659959e50 100644 --- a/TSOClient/FSO.SimAntics.JIT.Roslyn/VMRoslynRoutine.cs +++ b/TSOClient/FSO.SimAntics.JIT.Roslyn/VMRoslynRoutine.cs @@ -45,7 +45,7 @@ private VMPrimitiveExitCode ExecuteJIT(VMStackFrame frame, out VMInstruction ins private VMPrimitiveExitCode ExecuteJITInline(VMStackFrame frame, out VMInstruction instruction) { - var result = IFunction.Execute(frame, ref frame.InstructionPointer, frame.Args); + var result = IFunction.Execute(frame, ref frame.InstructionPointer, frame.Args.ToSpan()); instruction = frame.GetCurrentInstruction(); return result ? VMPrimitiveExitCode.RETURN_TRUE : VMPrimitiveExitCode.RETURN_FALSE; } diff --git a/TSOClient/FSO.SimAntics.JIT/FSO.SimAntics.JIT.csproj b/TSOClient/FSO.SimAntics.JIT/FSO.SimAntics.JIT.csproj index 2c65b7dd3..93ad1fc30 100644 --- a/TSOClient/FSO.SimAntics.JIT/FSO.SimAntics.JIT.csproj +++ b/TSOClient/FSO.SimAntics.JIT/FSO.SimAntics.JIT.csproj @@ -1,109 +1,23 @@ - - - + + - Debug - AnyCPU - {B8AB3711-7B4F-4126-9BF3-4DDDE9475B74} + net9.0 + enable + disable Library - Properties FSO.SimAntics.JIT FSO.SimAntics.JIT - v4.5 512 + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {5eddefd2-c850-49c1-812d-ddeff09125ef} - FSO.SimAntics - - - {072781d8-51ec-4143-9cae-daf50177d3ad} - FSO.HIT - - - {fd7957f7-a1e0-4d00-8f6c-3fa555eaa163} - FSO.Vitaboy.Engine - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - - - {b1a6e4c2-e080-4c34-a604-d11b5296a9b8} - FSO.LotView - - + - + + + - - \ No newline at end of file + + diff --git a/TSOClient/FSO.SimAntics.JIT/Properties/AssemblyInfo.cs b/TSOClient/FSO.SimAntics.JIT/Properties/AssemblyInfo.cs deleted file mode 100644 index e4ecc76d7..000000000 --- a/TSOClient/FSO.SimAntics.JIT/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.SimAntics.JIT")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.SimAntics.JIT")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("b8ab3711-7b4f-4126-9bf3-4ddde9475b74")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.SimAntics.JIT/Runtime/IBHAV.cs b/TSOClient/FSO.SimAntics.JIT/Runtime/IBHAV.cs index 1e7f25698..f9f92f283 100644 --- a/TSOClient/FSO.SimAntics.JIT/Runtime/IBHAV.cs +++ b/TSOClient/FSO.SimAntics.JIT/Runtime/IBHAV.cs @@ -10,19 +10,19 @@ public interface IBHAV public abstract class IInlineBHAV { - public abstract bool Execute(VMStackFrame context, ref byte instruction, params short[] args); + public abstract bool Execute(VMStackFrame context, ref byte instruction, params Span args); public virtual int ArgCount => 4; public GameObject CodeOwner; - public bool Execute(VMStackFrame context, params short[] args) + public bool Execute(VMStackFrame context, params Span args) { byte instruction = 0; var stackObj = context.StackObject; var stackObjID = context.StackObjectID; var oldArgs = context.Args; var oldLocals = context.Locals; - context.Args = args; + context.Args = new(args); var result = Execute(context, ref instruction, args); context.Args = oldArgs; context.Locals = oldLocals; @@ -34,7 +34,7 @@ public bool Execute(VMStackFrame context, params short[] args) return result; } - public bool ExecuteExternal(VMStackFrame context, params short[] args) + public bool ExecuteExternal(VMStackFrame context, params Span args) { //we need to set the code owner to the correct object. //var oldCodeOwner = context.CodeOwner; diff --git a/TSOClient/FSO.SimAntics.JIT/Runtime/VMAOTRoutine.cs b/TSOClient/FSO.SimAntics.JIT/Runtime/VMAOTRoutine.cs index 8dfb83441..aca9a1463 100644 --- a/TSOClient/FSO.SimAntics.JIT/Runtime/VMAOTRoutine.cs +++ b/TSOClient/FSO.SimAntics.JIT/Runtime/VMAOTRoutine.cs @@ -30,7 +30,7 @@ public VMAOTInlineRoutine(IInlineBHAV func) : base() public override VMPrimitiveExitCode Execute(VMStackFrame frame, out VMInstruction instruction) { - var result = Function.Execute(frame, ref frame.InstructionPointer, frame.Args); + var result = Function.Execute(frame, ref frame.InstructionPointer, frame.Args.ToSpan()); instruction = frame.GetCurrentInstruction(); return result ? VMPrimitiveExitCode.RETURN_TRUE : VMPrimitiveExitCode.RETURN_FALSE; } diff --git a/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/CSTranslationContext.cs b/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/CSTranslationContext.cs index 6670c90bb..0019aaf78 100644 --- a/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/CSTranslationContext.cs +++ b/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/CSTranslationContext.cs @@ -95,7 +95,7 @@ public class CSTranslationClass //useful helpers public string Interface => (InlineFunction) ? "IInlineBHAV" : "IBHAV"; public string FunctionHead => (InlineFunction) ? - "public override bool Execute(VMStackFrame context, ref byte instruction, params short[] args)" : + "public override bool Execute(VMStackFrame context, ref byte instruction, params Span args)" : "public VMPrimitiveExitCode Execute(VMStackFrame context, ref byte instruction)"; public string TrueExp => (InlineFunction) ? "true" : "VMPrimitiveExitCode.RETURN_TRUE"; public string FalseExp => (InlineFunction) ? "false" : "VMPrimitiveExitCode.RETURN_FALSE"; diff --git a/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/Primitives/CSSetToNextPrimitive.cs b/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/Primitives/CSSetToNextPrimitive.cs index 62d97db02..dfef9d1c8 100644 --- a/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/Primitives/CSSetToNextPrimitive.cs +++ b/TSOClient/FSO.SimAntics.JIT/Translation/CSharp/Primitives/CSSetToNextPrimitive.cs @@ -109,7 +109,7 @@ public override List CodeGen(TranslationContext context) bool loop = (operand.SearchType == VMSetToNextSearchType.ObjectOnSameTile); - codeResult.Add($"var ind = (entities.Count < 4)?0:VM.FindNextIndexInObjList(entities, targetValue);"); + codeResult.Add($"var ind = (entities.Count < 4)?0:entities.FindNextIndexInObjList(targetValue);"); codeResult.Add($"for (int i = ind; i < entities.Count; i++) {{"); codeResult.Add($"var tempObj = entities[i];"); diff --git a/TSOClient/FSO.SimAntics.JIT/app.config b/TSOClient/FSO.SimAntics.JIT/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/FSO.SimAntics.JIT/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/ContentStrings.cs b/TSOClient/FSO.UI/ContentStrings.cs index 5afa8d1b5..d8d65597c 100644 --- a/TSOClient/FSO.UI/ContentStrings.cs +++ b/TSOClient/FSO.UI/ContentStrings.cs @@ -1,10 +1,13 @@ -using System; +using FSO.Common; +using FSO.Files.Formats.IFF; +using FSO.Files.Formats.IFF.Chunks; +using FSO.SimAntics.Model; +using FSO.Vitaboy; +using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; -using System.IO; -using FSO.Files.Formats.IFF; -using FSO.Files.Formats.IFF.Chunks; namespace FSO.Client.GameContent { @@ -22,7 +25,7 @@ public ContentStrings() { LoadTS1(); } - else + else if (!FSOEnvironment.MissingTSO) { var tsodir = Path.Combine(GlobalSettings.Default.StartupPath, @"gamedata/uitext/"); @@ -142,6 +145,56 @@ public void LoadTS1() } } + public static Dictionary ReadTable(string file) + { + var tableData = new Dictionary(); + + var contentLines = File.ReadAllLines(file).ToList(); + /** Expected pattern: {digit} ^ {TXT} ^ **/ + + var io = 0; + var pos = 0; + var index = 0; + + for (int i = 0; i < contentLines.Count; i++) + { + var line = contentLines[i]; + if (line.StartsWith("//")) + { + /** Remove comment **/ + contentLines.RemoveAt(i); + i--; + } + } + + var content = String.Join("\r\n", contentLines.ToArray()); + + while ((pos = content.IndexOf("^", io)) != -1) + { + var id = content.Substring(io, pos - io).Trim(); + var lastLB = id.LastIndexOf("\r\n"); + if (lastLB != -1) + { + id = id.Substring(lastLB + 2).Trim(); + } + var endPOW = content.IndexOf("^", pos + 1); + if (endPOW == -1) { break; } + + pos++; + var strValue = content.Substring(pos, endPOW - pos); + io = endPOW + 1; + if (id.Length == 0) + { + id = index.ToString(); + } + + tableData[id] = strValue; + index++; + } + + return tableData; + } + /// /// Loads all string tables from a specified directory. /// @@ -164,53 +217,24 @@ public void Load(string dirName, string basePath) if (second_ == -1) return; tableID = tableID.Substring(1, second_ - 1); + + table[tableID] = ReadTable(file); //overwrites previous. + } + } - var tableData = new Dictionary(); - - var contentLines = File.ReadAllLines(file).ToList(); - /** Expected pattern: {digit} ^ {TXT} ^ **/ - - var io = 0; - var pos = 0; - var index = 0; - - for (int i = 0; i < contentLines.Count; i++){ - var line = contentLines[i]; - if (line.StartsWith("//")) - { - /** Remove comment **/ - contentLines.RemoveAt(i); - i--; - } - } - - var content = String.Join("\r\n", contentLines.ToArray()); + public string TransformLotName(string name) + { + if (name.StartsWith('{') && name.EndsWith('}')) + { + var split = name.Substring(1, name.Length - 2).Split(':'); - while ((pos = content.IndexOf("^", io)) != -1) + if (split.Length == 3 && split[0] == "job" && int.TryParse(split[1], out int type) && int.TryParse(split[2], out int level)) { - var id = content.Substring(io, pos - io).Trim(); - var lastLB = id.LastIndexOf("\r\n"); - if (lastLB != -1) - { - id = id.Substring(lastLB + 2).Trim(); - } - var endPOW = content.IndexOf("^", pos + 1); - if (endPOW == -1) { break; } - - pos++; - var strValue = content.Substring(pos, endPOW - pos); - io = endPOW + 1; - if (id.Length == 0) - { - id = index.ToString(); - } - - tableData[id] = strValue; - index++; + return GetString("UIText", "f132", (type * 100 + level).ToString()) ?? name; } - - table[tableID] = tableData; //overwrites previous. } + + return name; } } } diff --git a/TSOClient/FSO.UI/Controls/UIAlert.cs b/TSOClient/FSO.UI/Controls/UIAlert.cs index 012a90c03..69d8d6807 100644 --- a/TSOClient/FSO.UI/Controls/UIAlert.cs +++ b/TSOClient/FSO.UI/Controls/UIAlert.cs @@ -161,6 +161,7 @@ public UIAlert(UIAlertOptions options) : base(UIDialogStyle.Standard, true) { TextBox = new UITextBox(); TextBox.MaxChars = options.MaxChars; + TextBox.CurrentText = options.TextValue; this.Add(TextBox); } @@ -212,17 +213,17 @@ public void RefreshSize() h += size.Height; } - var buttonMaxWidth = (Buttons.Count == 0)? 0 : Buttons.Max(x => x.Width); + var buttonTotalWidth = Buttons.Sum(x => x.Width); var buttonSpacing = (Buttons.Count > 2) ? 5 : 50; SetSize(w, h); - var btnX = (w - ((Buttons.Count * buttonMaxWidth) + ((Buttons.Count - 1) * buttonSpacing))) / 2; + var btnX = (w - ((buttonTotalWidth) + ((Buttons.Count - 1) * buttonSpacing))) / 2; var btnY = h - 58; foreach (UIElement button in Buttons) { button.Y = btnY; button.X = btnX; - btnX += buttonMaxWidth + buttonSpacing; + btnX += button.Size.X + buttonSpacing; } } @@ -283,7 +284,10 @@ private UIButton AddButton(string label, UIAlertButtonType type, bool InternalHa { var btn = new UIButton(); btn.Caption = label; - btn.Width = 100; + + var labelWidth = btn.CaptionStyle.MeasureString(label).X; + + btn.Width = Math.Max(100, labelWidth + 20); if(InternalHandler) btn.OnButtonClick += new ButtonClickDelegate(x => @@ -392,6 +396,7 @@ public class UIAlertOptions public UIContainer GenericAddition; public bool TextEntry = false; + public string TextValue = ""; public UIAlertButton[] Buttons = new UIAlertButton[] { new UIAlertButton() }; } diff --git a/TSOClient/FSO.UI/Controls/UIButton.cs b/TSOClient/FSO.UI/Controls/UIButton.cs index c8a69f651..04df31b2c 100644 --- a/TSOClient/FSO.UI/Controls/UIButton.cs +++ b/TSOClient/FSO.UI/Controls/UIButton.cs @@ -9,6 +9,7 @@ using FSO.Common.Rendering.Framework.Model; using FSO.HIT; using FSO.Client.GameContent; +using Microsoft.Xna.Framework.Input; namespace FSO.Client.UI.Controls { @@ -17,7 +18,7 @@ namespace FSO.Client.UI.Controls /// /// A drawable, clickable button that is part of the GUI. /// - public class UIButton : UIElement + public class UIButton : UIElement, IFocusableUI { public static Texture2D StandardButton; @@ -46,12 +47,27 @@ static UIButton() private UITooltipHandler m_TooltipHandler; private UIElementState m_State = UIElementState.Normal; + public bool IsFocused { get; set; } + public void OnFocusChanged(FocusEvent newFocus) + { + if (newFocus == FocusEvent.FocusOut) CurrentFrame = 0; + Invalidate(); + } + + private int _tabIndex = 0; + public virtual int TabIndex + { + get => m_Disabled ? -1 : _tabIndex; + set => _tabIndex = value; + } public bool Hovered { get { return m_isOver; } } + public bool AlwaysClickable { get; set; } + /// /// Sets the margins to be used for automatic button widths. -1 (default) uses the width of the button ends. /// @@ -210,7 +226,6 @@ public Texture2D Texture set { m_Texture = value; m_Bounds = Rectangle.Empty; - m_Width = m_Texture.Width / m_ImageStates; m_WidthDiv3 = m_Width / 3; m_Height = m_Texture.Height / m_ButtonFrames; @@ -221,7 +236,7 @@ public Texture2D Texture ClickHandler.Region.Width = (m_ResizeWidth == 0) ? m_Width : (int)m_ResizeWidth; ClickHandler.Region.Height = m_Height; } - } + } } public void ActivateTooltip() { @@ -322,7 +337,7 @@ public bool IsDown protected void OnMouseEvent(UIMouseEventType type, UpdateState state) { - if ((m_Disabled || Opacity < 1f) && type != UIMouseEventType.MouseOut) { return; } + if (!AlwaysClickable && (m_Disabled || Opacity < 1f) && type != UIMouseEventType.MouseOut) { return; } Invalidate(); switch (type) { @@ -353,6 +368,7 @@ protected void OnMouseEvent(UIMouseEventType type, UpdateState state) case UIMouseEventType.MouseDown: m_isDown = true; CurrentFrame = 1; + state.InputManager.SetFocus(this); if (OnButtonDown != null) OnButtonDown(this); break; @@ -372,6 +388,19 @@ protected void OnMouseEvent(UIMouseEventType type, UpdateState state) } } + public override void Update(UpdateState state) + { + base.Update(state); + if (IsFocused && !m_Disabled) + { + if (state.ActivationKeyPressed) + { + OnButtonClick?.Invoke(this); + HITVM.Get().PlaySoundEvent(UISounds.Click); + } + } + } + public override void Draw(UISpriteBatch SBatch) { if (!Visible) { return; } @@ -391,6 +420,10 @@ public override void Draw(UISpriteBatch SBatch) { frame = 1; } + if (IsFocused && frame == 0) + { + frame = Math.Min(2, m_ImageStates - 1); + } if (ForceState > -1) frame = ForceState; frame = Math.Min(m_ImageStates - 1, frame); int offset = frame * m_Width; @@ -404,7 +437,7 @@ public override void Draw(UISpriteBatch SBatch) base.DrawLocalTexture(SBatch, m_Texture, new Rectangle(offset, vOffset, m_WidthDiv3, m_Height), Vector2.Zero); /** center **/ - base.DrawLocalTexture(SBatch, m_Texture, new Rectangle(offset + m_WidthDiv3, vOffset, m_WidthDiv3, m_Height), new Vector2(m_WidthDiv3, 0), new Vector2( (Width - (m_WidthDiv3 * 2)) / m_WidthDiv3, 1.0f)); + base.DrawLocalTexture(SBatch, m_Texture, new Rectangle(offset + m_WidthDiv3, vOffset, m_WidthDiv3, m_Height), new Vector2(m_WidthDiv3, 0), new Vector2(MathF.Ceiling(Width - (m_WidthDiv3 * 2)) / m_WidthDiv3, 1.0f)); /** right **/ base.DrawLocalTexture(SBatch, m_Texture, new Rectangle(offset + (m_Width - m_WidthDiv3), vOffset, m_WidthDiv3, m_Height), new Vector2(Width - m_WidthDiv3, 0)); diff --git a/TSOClient/FSO.UI/Controls/UIClickableLabel.cs b/TSOClient/FSO.UI/Controls/UIClickableLabel.cs index d83f66793..651280e56 100644 --- a/TSOClient/FSO.UI/Controls/UIClickableLabel.cs +++ b/TSOClient/FSO.UI/Controls/UIClickableLabel.cs @@ -1,15 +1,19 @@ using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; using FSO.Common.Rendering.Framework.IO; using FSO.Common.Rendering.Framework.Model; namespace FSO.Client.UI.Controls { - public class UIClickableLabel : UILabel + public class UIClickableLabel : UILabel, IFocusableUI { private UIMouseEventRef ClickHandler; public event ButtonClickDelegate OnButtonClick; public event UIMouseEvent OnMouseEvtExt; + public bool IsFocused { get; set; } + public int TabIndex { get; set; } + public UIClickableLabel() { ClickHandler = @@ -50,6 +54,7 @@ private void OnMouseEvent(UIMouseEventType type, UpdateState state) case UIMouseEventType.MouseDown: m_isDown = true; + state.InputManager.SetFocus(this); break; case UIMouseEventType.MouseUp: @@ -67,5 +72,11 @@ private void OnMouseEvent(UIMouseEventType type, UpdateState state) OnMouseEvtExt?.Invoke(type, state); } + public override void Update(UpdateState state) + { + base.Update(state); + if (IsFocused && state.ActivationKeyPressed) + OnButtonClick?.Invoke(this); + } } } diff --git a/TSOClient/FSO.UI/Controls/UIDialog.cs b/TSOClient/FSO.UI/Controls/UIDialog.cs index f5c7fc8b6..4a5d3d7ec 100644 --- a/TSOClient/FSO.UI/Controls/UIDialog.cs +++ b/TSOClient/FSO.UI/Controls/UIDialog.cs @@ -79,6 +79,8 @@ public UIDialog(UIDialogStyle style, UIDialogExtras extras, bool draggable) OKButton = new UIButton(GetTexture((ulong)9423158247425)); Add(OKBg); Add(OKButton); + + Overhang = new Vector2(4); } if ((style & UIDialogStyle.Close) > 0) diff --git a/TSOClient/FSO.UI/Controls/UIGridViewer.cs b/TSOClient/FSO.UI/Controls/UIGridViewer.cs index b3a256e83..e610a0bb2 100644 --- a/TSOClient/FSO.UI/Controls/UIGridViewer.cs +++ b/TSOClient/FSO.UI/Controls/UIGridViewer.cs @@ -2,16 +2,22 @@ using System.Collections.Generic; using FSO.Client.UI.Framework; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; using FSO.Client.UI.Framework.Parser; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; using Microsoft.Xna.Framework.Graphics; namespace FSO.Client.UI.Controls { - public class UIGridViewer : UIContainer + public class UIGridViewer : UIContainer, IFocusableUI { public event ChangeDelegate OnChange; public event ChangeDelegate OnSelectedPageChanged; + public bool IsFocused { get; set; } + public int TabIndex { get; set; } + /// /// Class to use as the item renderer for each cell in the grid /// @@ -272,5 +278,51 @@ protected void Render() SelectedIndex = m_SelectedIndex; } + public override void Update(UpdateState state) + { + base.Update(state); + if (!IsFocused || m_DataProvider == null || m_DataProvider.Count == 0) return; + + int index = m_SelectedIndex; + if (index < 0) index = 0; + + foreach (var key in state.NewKeys) + { + int col = index % myColumns; + int newIndex; + + switch (key) + { + case Keys.Left: + if (col == 0) continue; + newIndex = index - 1; + break; + case Keys.Right: + if (col >= myColumns - 1) continue; + newIndex = index + 1; + break; + case Keys.Up: + newIndex = index - myColumns; + break; + case Keys.Down: + newIndex = index + myColumns; + break; + case Keys.Enter: + if (OnChange != null) OnChange(this); + continue; + default: continue; + } + + if (newIndex < 0 || newIndex >= m_DataProvider.Count) continue; + + index = newIndex; + + int targetPage = index / ItemsPerPage; + if (targetPage != m_SelectedPage) + SelectedPage = targetPage; + + SelectedIndex = index; + } + } } } diff --git a/TSOClient/FSO.UI/Controls/UIGridViewerRender.cs b/TSOClient/FSO.UI/Controls/UIGridViewerRender.cs index 91aeb8279..14c19a183 100644 --- a/TSOClient/FSO.UI/Controls/UIGridViewerRender.cs +++ b/TSOClient/FSO.UI/Controls/UIGridViewerRender.cs @@ -32,6 +32,7 @@ void button_OnButtonClick(UIElement button) if (data != null) { owner.SelectedItem = data; + GameFacade.Screens.inputManager.SetFocus(owner); } } diff --git a/TSOClient/FSO.UI/Controls/UIImage.cs b/TSOClient/FSO.UI/Controls/UIImage.cs index b859db0d3..d77e7106d 100644 --- a/TSOClient/FSO.UI/Controls/UIImage.cs +++ b/TSOClient/FSO.UI/Controls/UIImage.cs @@ -155,6 +155,18 @@ public void SetSize(float width, float height) Invalidate(); } + public override Vector2 Size + { + get + { + return new Vector2(m_Width, m_Height); + } + set + { + SetSize(value.X, value.Y); + } + } + /// /// The source rectangle declares boundarys from the source texture of the UIElement, effectively /// masking it during draw calls. See SpiteBatch.Draw(sourceRectangle) and DrawLocalTexture() below. @@ -176,7 +188,7 @@ public float AbstractY } [UIAttribute("size")] - public new Point Size + public Point PointSize { get { diff --git a/TSOClient/FSO.UI/Controls/UILabel.cs b/TSOClient/FSO.UI/Controls/UILabel.cs index 741b07e52..77b1eeb5e 100644 --- a/TSOClient/FSO.UI/Controls/UILabel.cs +++ b/TSOClient/FSO.UI/Controls/UILabel.cs @@ -108,6 +108,16 @@ public bool Wrapped private UIWordWrapOutput _WrappedOutput = null; private bool _InDraw = false; + private void EnsureWrappedOutput() + { + if (_WrappedOutput == null) + { + var scale = new Vector2(CaptionStyle.Scale); + int width = m_Size.Width == 0 ? int.MaxValue : m_Size.Width; + _WrappedOutput = UIUtils.WordWrap(m_Text, width, CaptionStyle, MaxLines); + } + } + public override void Draw(UISpriteBatch SBatch) { _InDraw = true; @@ -124,11 +134,7 @@ public override void Draw(UISpriteBatch SBatch) { if (_Wrapped) { - if (_WrappedOutput == null) - { - var scale = new Vector2(CaptionStyle.Scale); - _WrappedOutput = UIUtils.WordWrap(m_Text, m_Size.Width, CaptionStyle, MaxLines); - } + EnsureWrappedOutput(); if(_WrappedOutput == null || _WrappedOutput.Lines == null){ _InDraw = false; @@ -174,7 +180,22 @@ public void NewStyle(Color color, int size) public void AutoSize() { - this.Size = CaptionStyle.MeasureString(Caption); + if (this.Size == default) + { + if (Wrapped) + { + EnsureWrappedOutput(); + + if (_WrappedOutput != null) + { + this.Size = new Vector2(_WrappedOutput.MaxWidth, _WrappedOutput.Height); + } + } + else + { + this.Size = CaptionStyle.MeasureString(Caption); + } + } } } } diff --git a/TSOClient/FSO.UI/Controls/UIListBox.cs b/TSOClient/FSO.UI/Controls/UIListBox.cs index 40806ddd4..a00b5c4d8 100644 --- a/TSOClient/FSO.UI/Controls/UIListBox.cs +++ b/TSOClient/FSO.UI/Controls/UIListBox.cs @@ -1,17 +1,32 @@ -using System; -using System.Collections.Generic; -using FSO.Client.UI.Framework; +using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using FSO.Common.Rendering.Framework.IO; using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; +using Microsoft.Xna.Framework.Input; namespace FSO.Client.UI.Controls { - public class UIListBox : UIElement + public class UIListBox : UIElement, IFocusableUI { + public bool IsFocused { get; set; } + public int TabIndex { get; set; } = 0; + public void OnFocusChanged(FocusEvent newFocus) + { + if (newFocus == FocusEvent.FocusIn && m_SelectedRow < 0 && Items != null && Items.Count > 0) + { + int enabledIndex = AllowDisabledSelection ? 0 : Items.FindIndex(IsItemEnabled); + + if (enabledIndex != -1) + { + InternalSelect(enabledIndex); + } + } + + Invalidate(); + } private UIMouseEventRef MouseHandler; public event ChangeDelegate OnChange; public event ButtonClickDelegate OnDoubleClick; @@ -28,9 +43,6 @@ public UIListBox() RowHeight = 16; } - - - #region Fields private int _RowHeight; @@ -174,7 +186,7 @@ public override Vector2 Size } } - + public bool UseChildElements { get; set; } #endregion @@ -245,29 +257,55 @@ void m_Slider_OnChange(UIElement element) public override void Update(UpdateState state) { base.Update(state); - var i = 0; - foreach (var item in Items) + + // Mouse wheel scrolling + if (m_MouseOver && state.MouseWheelDelta != 0) { - foreach (var col in item.Columns) + ScrollOffset = Math.Max(0, Math.Min(Items.Count - NumVisibleRows, ScrollOffset - state.MouseWheelDelta)); + if (m_Slider != null) + m_Slider.Value = ScrollOffset; + } + + if (UseChildElements) + { + var i = 0; + foreach (var item in Items) { - if (col is UIElement) + foreach (var col in item.Columns) { - var container = ((UIElement)col); - container.Visible = i >= ScrollOffset && i < ScrollOffset + NumVisibleRows; - if (container.Visible) - { - container.Parent = this.Parent; - container.InvalidateMatrix(); - container.Update(state); - container.Parent = null; - } else + if (col is UIElement) { - container.Update(state); + var container = ((UIElement)col); + container.Visible = i >= ScrollOffset && i < ScrollOffset + NumVisibleRows; + if (container.Visible) + { + container.Parent = this.Parent; + container.InvalidateMatrix(); + container.Update(state); + container.Parent = null; + } + else + { + container.Update(state); + } } } + i++; } - i++; } + + if (IsFocused) + { + if ((state.NewKeys.Remove(Keys.Up) || state.NewKeys.Remove(Keys.Left)) && Items.Count > 0) + InternalSelect(LastRow(m_SelectedRow)); + + if ((state.NewKeys.Remove(Keys.Down) || state.NewKeys.Remove(Keys.Right)) && Items.Count > 0) + InternalSelect(NextRow(m_SelectedRow)); + + if (SelectedItem != null && state.NewKeys.Contains(Keys.Enter)) + OnDoubleClick?.Invoke(this); + } + if (m_MouseOver) { var overRow = GetRowUnderMouse(state); @@ -279,6 +317,55 @@ public override void Update(UpdateState state) } } + private static bool IsItemEnabled(UIListBoxItem item) + { + return !ValuePointer.Get(item.Disabled); + } + + private int NextRow(int index) + { + if (AllowDisabledSelection) + { + return (index + 1) % Items.Count; + } + else + { + if (index < Items.Count - 1) + { + int afterInd = Items.FindIndex(index + 1, IsItemEnabled); + + if (afterInd != -1) + { + return afterInd; + } + } + + return Items.FindIndex(IsItemEnabled); + } + } + + private int LastRow(int index) + { + if (AllowDisabledSelection) + { + return (index < 0 ? Items.Count - 1 : (index - 1 + Items.Count) % Items.Count); + } + else + { + if (index > 0) + { + int beforeInd = Items.FindLastIndex(index - 1, IsItemEnabled); + + if (beforeInd != -1) + { + return beforeInd; + } + } + + return Items.FindLastIndex(IsItemEnabled); + } + } + private DoubleClick DoubleClicker = new DoubleClick(); private void OnMouseEvent(UIMouseEventType type, UpdateState update) @@ -307,6 +394,7 @@ private void OnMouseEvent(UIMouseEventType type, UpdateState update) /** Cant deselect once selected **/ InternalSelect(row); } + update.InputManager.SetFocus(this); break; } } @@ -341,6 +429,15 @@ private void InternalSelect(int index) { Invalidate(); m_SelectedRow = index; + + // Ensure selection is visible + if (index < ScrollOffset && index != -1) + ScrollOffset = index; + else if (index >= ScrollOffset + NumVisibleRows) + ScrollOffset = index - NumVisibleRows + 1; + + if (m_Slider != null) + m_Slider.Value = ScrollOffset; if (OnChange != null) { @@ -396,19 +493,18 @@ public override void PreDraw(UISpriteBatch batch) if (Mask) { var gd = batch.GraphicsDevice; - var size = Size; + var size = Size * Scale; if (Target == null || (int)size.X != Target.Width || (int)size.Y != Target.Height) { Target?.Dispose(); Target = new RenderTarget2D(gd, (int)size.X, (int)size.Y, false, SurfaceFormat.Color, DepthFormat.None); } - try { batch.End(); } catch { } + batch.End(); gd.SetRenderTarget(Target); gd.Clear(Color.Transparent); var pos = LocalPoint(0, 0); - var trans = Microsoft.Xna.Framework.Matrix.CreateTranslation(-pos.X, -pos.Y, 0) - * Microsoft.Xna.Framework.Matrix.CreateScale(1/Scale.X, 1/Scale.Y, 1f); + var trans = Microsoft.Xna.Framework.Matrix.CreateTranslation(-pos.X, -pos.Y, 0); batch.BatchMatrixStack.Push(trans); batch.Begin(transformMatrix: trans, blendState: BlendState.AlphaBlend, sortMode: SpriteSortMode.Deferred); batch.GraphicsDevice.RasterizerState = RasterizerState.CullNone; @@ -416,6 +512,7 @@ public override void PreDraw(UISpriteBatch batch) batch.End(); batch.BatchMatrixStack.Pop(); gd.SetRenderTarget(null); + batch.Resume(); } } @@ -429,7 +526,7 @@ public override void Draw(UISpriteBatch batch) if (Target != null) { - DrawLocalTexture(batch, Target, Vector2.Zero); + DrawLocalTexture(batch, Target, null, Vector2.Zero, new Vector2(1 / Scale.X, 1 / Scale.Y)); } } else @@ -453,13 +550,13 @@ private void _Draw(UISpriteBatch batch) var rowY = i * RowHeight; var columnX = 0; - var selected = rowIndex == m_SelectedRow; + var selected = rowIndex == m_SelectedRow || ValuePointer.Get(row.UseSelectedStyleByDefault); var hover = rowIndex == m_HoverRow; - if (selected) + if (selected && m_SelectionTexture != null) { /** Draw selection background **/ - var white = TextureGenerator.GetPxWhite(batch.GraphicsDevice); - DrawLocalTexture(batch, white, null, new Vector2(0, rowY), new Vector2(m_Width, RowHeight), m_SelectionFillColor); + var fillColor = IsFocused ? m_SelectionFillColor : m_SelectionFillColor * 0.8f; + DrawLocalTexture(batch, m_SelectionTexture, null, new Vector2(0, rowY), new Vector2(m_Width, RowHeight), fillColor); } var ts = TextStyle; @@ -700,6 +797,7 @@ public class UIListBoxItem public object Disabled = false; public UIListBoxTextStyle CustomStyle; public object UseDisabledStyleByDefault = false; //Offline avatars and properties use the disabled style without the row being disabled + public object UseSelectedStyleByDefault = false; public UIListBoxItem(object data, params object[] columns) { diff --git a/TSOClient/FSO.UI/Controls/UIProgressBar.cs b/TSOClient/FSO.UI/Controls/UIProgressBar.cs index bfe654e5e..f475445e5 100644 --- a/TSOClient/FSO.UI/Controls/UIProgressBar.cs +++ b/TSOClient/FSO.UI/Controls/UIProgressBar.cs @@ -148,6 +148,8 @@ public float Value { m_Value = newValue; } + + Invalidate(); } } diff --git a/TSOClient/FSO.UI/Controls/UIRadioButton.cs b/TSOClient/FSO.UI/Controls/UIRadioButton.cs index 3444bd1d2..b01775132 100644 --- a/TSOClient/FSO.UI/Controls/UIRadioButton.cs +++ b/TSOClient/FSO.UI/Controls/UIRadioButton.cs @@ -1,5 +1,7 @@ using FSO.Client.UI.Framework; +using FSO.Common.Rendering.Framework.Model; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; using System.Collections.Generic; namespace FSO.Client.UI.Controls @@ -9,6 +11,16 @@ public class UIRadioButton : UIButton private string _RadioGroup; public object RadioData { get; set; } + /// + /// Only the selected radio in a group is tab-stoppable. + /// Arrow keys handle cycling within the group. + /// + public override int TabIndex + { + get => (_RadioGroup != null && !Selected) ? -1 : base.TabIndex; + set => base.TabIndex = value; + } + public UIRadioButton() : base(GetTexture(0x0000049C00000001)) { } @@ -65,5 +77,32 @@ private void _FindRadioGroup(UIContainer container, string group, List CalculateLayout()); @@ -166,12 +178,54 @@ private void OnThumbClick(UIMouseEventType type, UpdateState state) case UIMouseEventType.MouseUp: m_ThumbDown = false; break; + + case UIMouseEventType.MouseOver: + m_MouseOver = true; + break; + + case UIMouseEventType.MouseOut: + m_MouseOver = false; + break; + } + } + + private void OnBackgroundMouse(UIMouseEventType type, UpdateState state) + { + switch (type) + { + case UIMouseEventType.MouseOver: + m_MouseOver = true; + break; + + case UIMouseEventType.MouseOut: + m_MouseOver = false; + break; } } public override void Update(UpdateState state) { base.Update(state); + + // Mouse wheel scrolling + if (m_MouseOver && state.MouseWheelDelta != 0) + { + float step = AllowDecimals ? 0.25f : 1f; + if (Orientation == 1) step *= -1; + Value += state.MouseWheelDelta * step; + } + + if (IsFocused && !m_ThumbDown) + { + float step = AllowDecimals ? 0.25f : 1f; + foreach (var key in state.NewKeys) + { + if (key == Keys.Up || key == Keys.Right) + Value += step; + else if (key == Keys.Down || key == Keys.Left) + Value -= step; + } + } if (m_ThumbDown) { /** Dragging the thumb **/ @@ -212,9 +266,12 @@ public override void Update(UpdateState state) /// public void SetSize(float width, float height) { + var minSize = (Orientation == 0 ? Texture?.Height : Texture?.Width) ?? 13; m_Width = width; m_Height = height; m_LayoutCache.Invalidate(); + + m_BackgroundEvent.Region = new Rectangle(0, 0, (int)Math.Max(minSize, width), (int)Math.Max(minSize, height)); } private UISliderLayout CalculateLayout() @@ -293,14 +350,21 @@ public override void Draw(UISpriteBatch batch) var layout = m_LayoutCache.Calculate("layout", x => CalculateLayout()); - batch.Draw(m_Texture, layout.TrackStartTo, layout.TrackStartFrom, Color.White, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); - batch.Draw(m_Texture, layout.TrackMiddleTo, layout.TrackMiddleFrom, Color.White, 0, Vector2.Zero, layout.TrackMiddleScale, SpriteEffects.None, 0); - batch.Draw(m_Texture, layout.TrackEndTo, layout.TrackEndFrom, Color.White, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); + var color = Color.White; + + if (Opacity < 1) + { + color *= Opacity; + } + + batch.Draw(m_Texture, layout.TrackStartTo, layout.TrackStartFrom, color, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); + batch.Draw(m_Texture, layout.TrackMiddleTo, layout.TrackMiddleFrom, color, 0, Vector2.Zero, layout.TrackMiddleScale, SpriteEffects.None, 0); + batch.Draw(m_Texture, layout.TrackEndTo, layout.TrackEndFrom, color, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); if (m_MaxValue > m_MinValue) { var buttonPosition = m_LayoutCache.Calculate("btn", x => CalculateButtonPosition(layout)); - batch.Draw(m_Texture, buttonPosition, layout.ThumbFrom, Color.White, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); + batch.Draw(m_Texture, buttonPosition, layout.ThumbFrom, color, 0, Vector2.Zero, _Scale, SpriteEffects.None, 0); } } @@ -333,6 +397,9 @@ public UISliderButtonHandler(UISlider slider, UIButton increase, UIButton decrea this.decrease = decrease; this.Change = change; + increase.TabIndex = -1; + decrease.TabIndex = -1; + increase.OnButtonClick += new ButtonClickDelegate(increase_OnButtonClick); decrease.OnButtonClick += new ButtonClickDelegate(decrease_OnButtonClick); diff --git a/TSOClient/FSO.UI/Controls/UISpacer.cs b/TSOClient/FSO.UI/Controls/UISpacer.cs new file mode 100644 index 000000000..b902f831e --- /dev/null +++ b/TSOClient/FSO.UI/Controls/UISpacer.cs @@ -0,0 +1,23 @@ +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; + +namespace FSO.UI.Controls +{ + public class UISpacer : UIElement + { + public override Vector2 Size { get; set; } + public UISpacer(int size) + { + Size = new Vector2(size); + } + + public UISpacer(int width, int height) + { + Size = new Vector2(width, height); + } + + public override void Draw(UISpriteBatch batch) + { + } + } +} diff --git a/TSOClient/FSO.UI/Controls/UITextEdit.cs b/TSOClient/FSO.UI/Controls/UITextEdit.cs index a8c3dd2a9..4c80a0747 100644 --- a/TSOClient/FSO.UI/Controls/UITextEdit.cs +++ b/TSOClient/FSO.UI/Controls/UITextEdit.cs @@ -1,20 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using FSO.Client.UI.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Input; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Framework.Parser; using FSO.Client.UI.Model; using FSO.Client.Utils; -using FSO.Client.UI.Framework.Parser; -using FSO.Common.Rendering.Framework.Model; +using FSO.Common; +using FSO.Common.Rendering.Framework; using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; -using FSO.Common.Rendering.Framework; -using FSO.Common; -using Microsoft.Xna.Framework.GamerServices; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System.Text; +//using Microsoft.Xna.Framework.GamerServices; namespace FSO.Client.UI.Controls { @@ -23,12 +20,15 @@ namespace FSO.Client.UI.Controls /// public class UITextEdit : UIElement, IFocusableUI, ITextControl { + public bool IsFocused { get; set; } + public int TabIndex { get; set; } = 0; /** * Standard modes */ public static UITextEdit CreateTextBox() { - return new UITextEdit { + return new UITextEdit + { MaxLines = 1, BackgroundTextureReference = UITextBox.StandardBackground, TextMargin = new Rectangle(8, 2, 8, 3) @@ -53,6 +53,7 @@ public static UITextEdit CreateTextBox() * Interaction */ private UIMouseEventRef m_MouseEvent; + private bool m_MouseOver; protected int SelectionStart = -1; protected int SelectionEnd = -1; @@ -201,7 +202,7 @@ public Color FrameColor /** * Properties */ - + /// /// Background texture /// @@ -247,7 +248,7 @@ public float Width { get { return m_Width; } } - + /// /// Component height /// @@ -279,7 +280,7 @@ public void SetSize(float width, float height) NineSliceMargins.CalculateScales(m_Width, m_Height); } m_Bounds = new Rectangle(0, 0, (int)m_Width, (int)m_Height); - + if (m_MouseEvent != null) { m_MouseEvent.Region = new Rectangle(0, 0, (int)m_Width, (int)m_Height); @@ -302,16 +303,31 @@ public override Rectangle GetBounds() /** * Interaction Functionality */ - + public void OnMouseEvent(UIMouseEventType evt, UpdateState state) { - if (m_IsReadOnly) { return; } + if (m_IsReadOnly) + { + switch (evt) + { + case UIMouseEventType.MouseOver: + m_MouseOver = true; + break; + + case UIMouseEventType.MouseOut: + m_MouseOver = false; + break; + } + + return; + } if (NoFocusPassthrough != null && state.InputManager.GetFocus() != this) { NoFocusPassthrough?.Invoke(evt, state); return; } + switch (evt) { case UIMouseEventType.MouseDown: @@ -332,10 +348,12 @@ public void OnMouseEvent(UIMouseEventType evt, UpdateState state) case UIMouseEventType.MouseOver: GameFacade.Cursor.SetCursor(CursorType.IBeam); + m_MouseOver = true; break; case UIMouseEventType.MouseOut: GameFacade.Cursor.SetCursor(CursorType.Normal); + m_MouseOver = false; break; case UIMouseEventType.MouseUp: @@ -357,30 +375,28 @@ public int GetSelectedInd() #region IFocusableUI Members - private bool IsFocused; private string QueuedChange; public void OnFocusChanged(FocusEvent newFocus) { - IsFocused = newFocus == FocusEvent.FocusIn; if (IsFocused) { m_cursorBlink = true; m_cursorBlinkLastTime = GameFacade.LastUpdateState.Time.TotalGameTime.Ticks; - if (FSOEnvironment.SoftwareKeyboard && FSOEnvironment.SoftwareDepth) - { - try - { - Guide.BeginShowKeyboardInput(PlayerIndex.One, "", "", CurrentText, (ar) => - { - var str = Guide.EndShowKeyboardInput(ar); - lock (this) - { - QueuedChange = str; - } - }, null); - } - catch (Exception e) { } - } + //if (FSOEnvironment.SoftwareKeyboard && FSOEnvironment.SoftwareDepth) + //{ + // try + // { + // Guide.BeginShowKeyboardInput(PlayerIndex.One, "", "", CurrentText, (ar) => + // { + // var str = Guide.EndShowKeyboardInput(ar); + // lock (this) + // { + // QueuedChange = str; + // } + // }, null); + // } + // catch (Exception e) { } + //} } else { @@ -417,6 +433,15 @@ public override void Update(UpdateState state) } } if (FSOEnvironment.SoftwareKeyboard && FSOEnvironment.SoftwareDepth && state.InputManager.GetFocus() == this) state.InputManager.SetFocus(null); + + // Mouse wheel scrolling + if (m_MouseOver && state.MouseWheelDelta != 0) + { + VerticalScrollPosition -= state.MouseWheelDelta; + if (m_Slider != null) + m_Slider.Value = VerticalScrollPosition; + } + if (m_IsReadOnly) { return; } if (FlashOnEmpty) @@ -585,8 +610,8 @@ public override void Update(UpdateState state) [UIAttribute("lines")] public int MaxLines { - get{ return m_MaxLines; } - set { m_MaxLines = (value<0)?int.MaxValue:value; } + get { return m_MaxLines; } + set { m_MaxLines = (value < 0) ? int.MaxValue : value; } } [UIAttribute("capacity")] public int MaxChars @@ -821,10 +846,12 @@ public void ComputeDrawingCommands() */ string txt = null; - if (m_Password){ + if (m_Password) + { /** Use * instead **/ txt = ""; - for(int i=0; i < m_SBuilder.Length; i++){ + for (int i = 0; i < m_SBuilder.Length; i++) + { txt += "*"; } } @@ -842,7 +869,7 @@ public void ComputeDrawingCommands() m_Lines.Clear(); //txt = txt.Replace("\r", ""); var words = txt.Split(' ').ToList(); - var spaceWidth = TextStyle.MeasureString(" ").X; + var spaceWidth = TextStyle.MeasureString(" ").X; /** * Modify the array to make manual line breaks their own segment @@ -884,7 +911,8 @@ public void ComputeDrawingCommands() if (Alignment.HasFlag(TextAlignment.Center)) { xPosition += (int)Math.Round((lineWidth - thisLineWidth) / 2); - } else if (Alignment.HasFlag(TextAlignment.Right)) + } + else if (Alignment.HasFlag(TextAlignment.Right)) { xPosition += (int)Math.Round((lineWidth - thisLineWidth)); } @@ -982,8 +1010,9 @@ public void ComputeDrawingCommands() } /** No cursor in read only mode **/ - if (m_IsReadOnly) { - m_DrawCmds.ForEach(x => x.Init()); + if (m_IsReadOnly) + { + m_DrawCmds.ForEach(x => x.Init()); return; } @@ -1053,7 +1082,7 @@ protected List CalculateSegments(UITextEditLine line, Lis if (!selected) { //look for selection start. is it before our next bbcommand? - if (start >= lastMod+lineStart && start <= nextBB) + if (start >= lastMod + lineStart && start <= nextBB) { selected = true; if (start != nextBB || nextBB == lineEnd) @@ -1072,7 +1101,7 @@ protected List CalculateSegments(UITextEditLine line, Lis { //look for selection end. is it before our next bbcommand? var selE = end - lineStart; - if (end >= lastMod+lineStart && end <= nextBB) + if (end >= lastMod + lineStart && end <= nextBB) { selected = false; if (end != nextBB || nextBB == lineEnd) @@ -1112,7 +1141,8 @@ protected List CalculateSegments(UITextEditLine line, Lis }); lastMod = (bbcmds[bbind - 1].Index - lineStart); } - } else + } + else { //remainder of the line result.Add(new UITextEditLineSegment @@ -1125,7 +1155,7 @@ protected List CalculateSegments(UITextEditLine line, Lis } return result; - + } /// @@ -1159,7 +1189,7 @@ public override void Draw(UISpriteBatch batch) { DrawingUtils.DrawBorder(batch, LocalRect(0, 0, m_Width, m_Height), 1, m_FrameTexture, m_FrameColor); } - + /** * Draw text */ @@ -1324,7 +1354,7 @@ public void InitDefaultSlider() public void PositionChildSlider() { m_Slider.Position = this.Position + new Vector2(this.Width + ScrollbarGutter, 0); - m_Slider.SetSize(1, this.Height); + m_Slider.SetSize(13, this.Height); } void m_Slider_OnChange(UIElement element) @@ -1444,14 +1474,14 @@ public TextDrawCmd_Emoji(TextStyle style, string emojiID, Vector2 position, Vect public virtual void Draw(UIElement ui, SpriteBatch batch) { batch.Draw( - EmojiTarget, - Position.ToPoint().ToVector2() + new Vector2(1, 0), - Slice, - (Shadow?Color.Black:Color.White) * (Style.Color.A / 255f), - 0f, - Vector2.Zero, - Scale * (Style.Size / 12f), - SpriteEffects.None, + EmojiTarget, + Position.ToPoint().ToVector2() + new Vector2(1, 0), + Slice, + (Shadow ? Color.Black : Color.White) * (Style.Color.A / 255f), + 0f, + Vector2.Zero, + Scale * (Style.Size / 12f), + SpriteEffects.None, 0f); } diff --git a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.Designer.cs b/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.Designer.cs deleted file mode 100644 index 6028bba85..000000000 --- a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.Designer.cs +++ /dev/null @@ -1,134 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class FSOExceptionDisplay - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FSOExceptionDisplay)); - this.ContinueButton = new System.Windows.Forms.Button(); - this.CopyButton = new System.Windows.Forms.Button(); - this.ExceptionBox = new System.Windows.Forms.TextBox(); - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.label1 = new System.Windows.Forms.Label(); - this.label2 = new System.Windows.Forms.Label(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.SuspendLayout(); - // - // ContinueButton - // - this.ContinueButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.ContinueButton.Location = new System.Drawing.Point(647, 312); - this.ContinueButton.Name = "ContinueButton"; - this.ContinueButton.Size = new System.Drawing.Size(75, 23); - this.ContinueButton.TabIndex = 0; - this.ContinueButton.Text = "Continue"; - this.ContinueButton.UseVisualStyleBackColor = true; - this.ContinueButton.Click += new System.EventHandler(this.ContinueButton_Click); - // - // CopyButton - // - this.CopyButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); - this.CopyButton.Location = new System.Drawing.Point(537, 312); - this.CopyButton.Name = "CopyButton"; - this.CopyButton.Size = new System.Drawing.Size(104, 23); - this.CopyButton.TabIndex = 1; - this.CopyButton.Text = "Copy to Clipboard"; - this.CopyButton.UseVisualStyleBackColor = true; - this.CopyButton.Click += new System.EventHandler(this.CopyButton_Click); - // - // ExceptionBox - // - this.ExceptionBox.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) - | System.Windows.Forms.AnchorStyles.Left) - | System.Windows.Forms.AnchorStyles.Right))); - this.ExceptionBox.Location = new System.Drawing.Point(8, 102); - this.ExceptionBox.Multiline = true; - this.ExceptionBox.Name = "ExceptionBox"; - this.ExceptionBox.ScrollBars = System.Windows.Forms.ScrollBars.Both; - this.ExceptionBox.Size = new System.Drawing.Size(714, 201); - this.ExceptionBox.TabIndex = 2; - // - // pictureBox1 - // - this.pictureBox1.BackgroundImage = global::FSO.Client.Properties.Resources.ico256; - this.pictureBox1.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Stretch; - this.pictureBox1.Location = new System.Drawing.Point(13, 12); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(77, 77); - this.pictureBox1.TabIndex = 3; - this.pictureBox1.TabStop = false; - // - // label1 - // - this.label1.AutoSize = true; - this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.label1.Location = new System.Drawing.Point(100, 12); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(288, 25); - this.label1.TabIndex = 4; - this.label1.Text = "A fatal error has occurred!"; - // - // label2 - // - this.label2.Location = new System.Drawing.Point(100, 42); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(331, 47); - this.label2.TabIndex = 5; - this.label2.Text = "FreeSO has encountered a problem and needs to close. The exception details are li" + - "sted below, so that this issue can be reported and eventually fixed!"; - // - // FSOExceptionDisplay - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(734, 347); - this.Controls.Add(this.label2); - this.Controls.Add(this.label1); - this.Controls.Add(this.pictureBox1); - this.Controls.Add(this.ExceptionBox); - this.Controls.Add(this.CopyButton); - this.Controls.Add(this.ContinueButton); - this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); - this.MinimumSize = new System.Drawing.Size(500, 300); - this.Name = "FSOExceptionDisplay"; - this.Text = "FreeSO"; - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.Button ContinueButton; - private System.Windows.Forms.Button CopyButton; - private System.Windows.Forms.TextBox ExceptionBox; - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.Label label2; - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.cs b/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.cs deleted file mode 100644 index ea18b48f6..000000000 --- a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace FSO.Client.Debug -{ - public partial class FSOExceptionDisplay : Form - { - public FSOExceptionDisplay() - { - InitializeComponent(); - } - - public FSOExceptionDisplay(string trace) : this() - { - ExceptionBox.Text = trace; - } - - private void ContinueButton_Click(object sender, EventArgs e) - { - Close(); - } - - private void CopyButton_Click(object sender, EventArgs e) - { - var text = ExceptionBox.Text; - var t = new Thread(() => - { - Clipboard.SetText(text); - }); - t.SetApartmentState(ApartmentState.STA); - t.Start(); - } - } -} diff --git a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.resx b/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.resx deleted file mode 100644 index 461e7c77a..000000000 --- a/TSOClient/FSO.UI/Debug/FSOExceptionDisplay.resx +++ /dev/null @@ -1,826 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - - - AAABAAUAEBAAAAEAIABoBAAAVgAAABgYAAABACAAiAkAAL4EAAAAAAAAAQAgAIVfAABGDgAAICAAAAEA - IACoEAAAy20AADAwAAABACAAqCUAAHN+AAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAASCwAAEgsAAAAA - AAAAAAAA////AP///wD///8A////ALZwO2C2bzuwtW48/7RtPf+0az3/s2o+/7JpP7CyZz9g////AP// - /wD///8A////AP///wD///8AuXU4ELh0OdC3cjr/t3E6/7ZwO/+1bjz/tW08/7RsPf+zaz7/s2k+/7Jo - P9CxZ0AQ////AP///wAAAAAAr3E0Ebl2N/C4dTj/t3Q5/7hzOf+2cTr/s246/7JtOv+ybDv/s2w9/7Rr - Pf+zaj7/sWk/8J5cOBIAAAABAAAAAbd3NdG0dTT/qWwy/6RpMv+maTL/r202/6tqN/+aXjD/nGAz/55g - NP+jYjb/sWo8/65nPP+nYjrTAAAAHrh6M2KzdjP/m2Us/5ljLP+fZi//o2kw/5piL/+RWy3/jlgs/49Y - Lf+ZXzL/nmEz/6dmN/+dXzb/klcx/3pIKni8fjLBrnQw/9C6ov+ie1D/lWEr/5ljLP+dajr//////8Oq - lP//////k1wv/55iM/+WXDH/kF87/9S/sv+ZWzPHvoAy/7+LS//69vP/+PXy/595UP+OXCj/ontQ/+LW - yv+tjGv/+PXy/5hmOv+RWy3/n3RT//Hq5f//////vIVe/8CDMf+7fjH/uYdJ//Ls5f//////spZ3/6WC - XP//////xq2V//////+mgV//nnZS//j18//z7Ob/vYdd/7NtOv/BhTD/voEw/7J5L//Ntpr/4NXI//// - ///DrJL/vaOG/6yKZv/ErJT/tJZ5///////p2sz/uoFQ/7VyOf+2cTr/wocv/7+DL/+0fC7/0Lyg/+LX - yf/g1MT//////9PCrv+HWSb/xK2T///////q28z/uHxC/7d1N/+4dTn/uHM5/8OILsDAhi//tn8t/+ba - yf//////so5d/8Ghev//////8uzl///////cwqX/tXY0/7p4Nv+6eDf/uXY4/7l1OMDEii1gwogu/7qD - Lf/XwaH/5dnJ/6uEUP+hbSn/yKFv//r38//VtIv/uXoz/7t7Nf+8ezX/u3o2/7p4N/+6dzdgAAAAAMOK - LdC/hy3/zadu/9/Ru/+5mWr/q3Ur/7p+MP+9gDH/voAy/71/M/+9fjT/vX00/7x7Nf+7ejbQ////AAAA - AADEiy0Qwoot8L+GLP/7+PP/+vfz/8qfYf+/gy//wIQx/8CCMf+/gTL/voAz/75/M/+9fTTwvHw1EP// - /wAAAAAA////AMSLLRDDii3Qwoks/8iWR//Fjjr/wYYv/8GFMP/BhDD/wIMx/7+CMv+/gDLQvn8zEP// - /wD///8A////AP///wD///8A////AMSLLWDEiy2ww4ou/8OJLv/Chy//wYYw/8GFMLDAhDFg////AP// - /wD///8A////APAPAADAAwAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAB - AACAAQAAwAMAAPAPAAAoAAAAGAAAADAAAAABACAAAAAAAAAJAAASCwAAEgsAAAAAAAAAAAAA////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AtnA7ELVv - PHC1bjzAtW08/7RsPf+0az3/s2o+/7NpPv+yaD/Asmg/cLFnQBD///8A////AP///wD///8A////AP// - /wD///8A////AP///wD///8A////AP///wC3cjqAt3E68LZwO/+2bzv/tW48/7VtPP+0bT3/tGw9/7Nr - Pv+zaj7/smk//7JoP/CyZz+A////AP///wD///8A////AP///wD///8A////AP///wD///8AuXU4ELh0 - OdC4czn/t3I6/7dxOv+2cDv/tnA7/7VvPP+1bjz/tW08/7RsPf+0az3/s2o+/7NpPv+yaD//smg/0LFn - QBD///8A////AP///wD///8A////AP///wC6dzcQuXY48Ll1OP+4dTn/uHM5/7dzOv+3cjr/t3E6/7Zw - O/+2bzv/tW48/7VtPP+0bD3/tGw9/7NrPv+zaj7/smk//7JoP/CyZz8Q////AP///wD///8A////AP// - /wC6eDfQung3/7p3N/+5djj/uXU4/7h0Of+4czn/t3I6/7dxOv+2cDv/tnA7/7VvPP+1bjz/tG08/7Rs - Pf+0az3/s2o+/7NpPv+yaD/Q////AP///wD///8AAAAAALx7NYC6ejb/uXg1/7l3N/+5dzf/uXY4/7l1 - OP+2czj/sm83/69uN/+vbTf/sG04/7FtOf+1bjv/tW48/7VtPP+0bD3/s2s9/7FpPf+yaT7/smk/gAAA - AAD///8AvX00ELt7NfC3eDT/snQ0/6RrMP+kajD/qGwy/7R1N/+zcjb/tYRZ/7GHY/+hcEj/r4Vj/6dz - Sv+gYjT/rGk4/7RvPP+zbTv/rmo6/6hlOf+dXjX/n1438rNpPhD///8AvX40cLl7M/+yfD3/5djK/6uA - U/+gaC7/pGsw/6RqMP+obDL/5NbL///////DqpT//////8itl/+fYzL/qmk2/7NuOv+tajj/omlB/+vg - 2f+jb0z/oF82/7JpPnH///8AvX8zwLl8Mv/Eo3v///////j18v+nflH/nWYu/6JpL/+aZC7/49bK//// - ///AqZT//////8eslv+bYTD/pGY0/61rN/+pelj/8Orl///////JrJj/rWg7/7NrPcD///8Av4Ey/7p9 - Mv+0fjv/0Luh////////////upt5/5pkK/+ZZSz/zbih/+DVyv+xlHj/4NXK/823ov+YYC//qmo1/6d6 - Vf/49fL//////9vKvv+sb0L/sWs7/7RtPP////8Av4Iy/76AMv+4fDH/rXQu/8Knhv///////////8au - lP+RXij/4tbK///////CqpP//////+LWyv+iZzH/uZl7////////////1MCw/6ZnNf+wbDn/tW47/7Vu - PP////8AwIMx/8CCMf+9gDH/tXov/7+gev/MuqP/7+rk///////Swa7/p4Nd//Hr5f++o4b/8evl/5dh - Lf+9oYf///////j18v/Bo4n/qmo1/7FuOP+2cDr/tnE7/7ZwO/////8AwYUw/8CEMf++gjH/tXsv/8yz - lP//////5NvQ//j18v//////6eDX/5NmNf+SYCr/jlwp/9PBr////////////7GIYv+tbjT/tHI4/7dz - Of+4czn/t3I6/7dxOv////8AwYYw/8GFMP++gzD/tnwv/76dcv/Twq3/08Kt/5NoNP/i18n//////+nh - 1/+nhF3/4dbJ///////p4Nf/r4JV/7BxNP+2dTb/uHY4/7l1OP+4dDn/uHM5/7dyOv////8AwocvwMKG - L/+/hDD/t34u/8y0k////////////5pnKP+ibSv/3c68//////////////////Hr5f+qdDv/sXQz/7h3 - Nf+5eDf/unc3/7l2OP+5dTj/uHU5/7h0OcD///8Aw4kucMKIL//Ahi//uYAu/9S/of/j2Mn/5NjJ/7CG - Uf+jbir/pW8r/8qylP//////3c68/615O/+0dzP/uno0/7t6Nv+7eTb/ung3/7p4N/+6dzf/uXY4/7l1 - OHD///8Aw4ouEMOJLvDChy7/vYMu/7F6K//Fq4X/p4JO/5xsKP+mcSr/pnEr/6lyLP+2hUj/sncw/7d6 - Mv+7fDP/vXw0/7x8Nf+8ezX/u3o2/7t5Nv+6eDf/unc38Ll2OBD///8AAAAAAMSKLYDDiS7/wIct/7qM - Rv/59vL//////9G8oP+gbin/r3gt/7x/MP+8fzH/vX4x/71/M/++fzP/vX40/719NP+8fDX/vHs1/7t6 - Nv+7eTb/ung3gAAAAAD///8AAAAAAAAAAADEiy3QwYku/7qDLP/Anmv/+fby///////WwKH/u38v/7+D - Mf/AgzH/v4Iy/7+BMv++gDP/vn8z/71+NP+9fTT/vHw1/7x7Nf+8ezXQ////AAAAAAD///8AAAAAAAAA - AADEiy0Qw4ot8MGILP+6gyz/tH4r/7mLRf+7hjn/vYMv/8CEMP/AhDH/wIMx/7+CMv+/gTL/v4Ay/76A - M/++fzP/vX40/719NPC8fDUQ////AAAAAAD///8AAAAAAAAAAAAAAAAAxIstEMOKLdDCii3/wIgt/7+G - Lf/Ahi7/wYYv/8KGL//BhTD/wYQw/8CDMf/AgzH/v4Iy/7+BMv++gDP/vn8z0L1+NBAAAAAAAAAAAAAA - AAD///8A////AP///wD///8A////AP///wDEiy2AxIst8MSKLf/DiS7/w4gu/8KIL//Chy//wYYw/8GF - MP/AhDH/wIMx/7+CMvC/gTKA////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP// - /wD///8AxIstEMSLLXDEiy3Aw4ou/8OJLv/CiC//wocv/8GGMP/BhTDAwYQwcMCDMRD///8A////AP// - /wD///8A////AP///wD///8A////APwAfwD4AD8A4AAPAMAABwDAAAcAgAADAAAAAQAAAAEAAAABAAAA - AQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAgAADAMAABwDAAAcA4AAPAPgAPwD8AH8AiVBORw0K - GgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAABfTElEQVR42u1deZwUxfX/VvfM3ssiXqioq+IVEBZv - QGBF8QQhGjT5megSc0dljUfUKJcHCCbBxAONxtXEA1FEw+ERYbkRVBYVPECzCCioHAt770zX74+q6q7u - 6Znp7umenV3m8ZkPu7Nd9erVzDvqvVfvEUopstAxoe/UZV0BlPFfywEAFKUASqXHhhAk+IyTf/yLpZ9r - AdTyMdUAQEBr1t4xeE9770UWvAHJCoDMh75Tl5WCMbp4dQUwxPSQzcdIknG324/e8rxl/sUA9gCoAVAD - ipq1dw6uba89y4IzyAqADIO+U5eVwczsQ5IOcsv8Xj7yxMyf6NnFEEIBqFl75+AaXzcsCylBVgC0M/Sd - tqwczHwvA0U5gBLHg+N8dL4yv3fGjwd1YMeHGgDVa+8cXO1yRVnwEbICIM3Qd9qyUgCjwJh+JABfNDKQ - UVrf7dyvAbQawJy1fxpS62GlWfAIWQGQBug7bdkoMIYfBeBo0x9T1MgC2k3r+0ID5XgAAJsBzAFQ/cGf - hsxxOXMWXEJWAAQEfaYtLyegFWBMH2vWp0Pre8ETpNaPGWNifDuoA8UcAFUf3DWk2gO2LCSBrADwEfpM - W14GoIKAjoJV08uQiVrfMibNWt/JGGEZVH1w15AaD9RlwQayAiBF6DNteVcAFfzVN+hzONDptb4THOsA - VIEJgz0eVpQFDlkB4BH6TFteDsb01wLp0JgM9jOt7wSeAUXVB3dnjwheICsAXEKfacsrAFQC6Cvey2r9 - ZGMCYXzrmHUEdPr7d5dXeZhpv4WsAHAAfR5c3hUUlWAa33S2b3fm3z+1fiIaNoMdD6a/f3f5Hg8z71eQ - FQAJoM+Dy7sCqOTMb/Lktzvje8HT+bR+IhrqAExHVhAkhKwAsIFEjA9kAPNntX5yGoznDUEwLisIrJAV - ABLojM+YP/MY3wuetGp99gZxPcZHGuLjyAoCG8gKAA59Hlw+AXEYH8gA5s/sVF5kmNZPBHVgQmCCh9V0 - OtjvBUCfB5ePAtMMR7eHRnaEJ6v1/WJ+GTYDqHx/XPkcDzvQaWC/FQB9HlxeBsb4Q9oj5p4uHB1d6yel - IUVaCOhiAJXvjT+3xsNKOzzsdwKAn/MnABgLoF3O4Y7wdKwLPO1Dgxcc0hjL/A8BmPDe+HP3eJixw8J+ - JQBM5j7QLho5KQ4vePav8J53PPaML8NmMGtgjgcKOiTsFwKAa/0qJLh/3yG1vmVMVusnH+NofuA1ABX7 - gzWgtPcCgoY+Dy6vBCtm6Y35KXxhfgLqv8nvlPlTpoFNQJCA+X3Yp8C0PnWw/+YxI0FRe/qERZUusXU4 - 6LQWQLtofS949ket75aGFHE4Znx7PMwamNA5rYFOKQD6PLi8HOzuOIvpZ8/6Hubv1Gd9tzjqAIx6b8K5 - 1R4ozmjodEeAPg8unw5gEYCSeGZpuzN/iuZy0MeJdGl9RwKy/ZkfYIpk0ekTFk33QHVGQ6exAPo8uLwU - TOv3BZCZjO8FT1bru8LjM+PbjVkHYNR7E8+t9TA646BTWAA8vFcDoG9W67uYuzNp/aCZ38DRl4DWnDF+ - 4SgPu5Fx0OEFAM/hfxXC5LdA8Ixj4AkKR/BRiiQe/qBp8IqDj3Hp4ffjWFEC4NUzxi+c4GHFGQUd9ghg - 8vJnU3k9rn+/9/A7GpMEx2sAKtZMHLrHw+ztDh1SAPA8/ioIk99KVEdM6knrWZ+90QEv8JjGuNL6weJY - ByYEajxgalfocAKAM381std2Pa6/Azv6Mkfr2+GoA1C+ZlLHEgIdygfQ58HlFQDWIk6xjs5w1u9wzO+W - BpsxbmgJ/Kzv3ZlYAmDtGeMWVrjE2q7QYQQAT+l9ulOl8qaNhv0uldc9+Cdgnj5j3MJKDytoF+gQR4A+ - Dy6vAmX1902L74hnfcuYDpnKm6ln/RRoCQDHM2smDa3wQHVaIeMFgCfmzyb1IBDGd0uDVzxpPOu7wuMG - B3v2mTX3ZLYQyGgB0Gfa8irAzPzZCzxOns+G99oVh3n+Z1bfc16Fh51IC2SsAHDN/Fmtj6zWb2c88efO - WCGQkQKgz7TlNZBabwEZwPxZrZ/V+qnNv271PeeVedidQCHjogBc8+vMn9D7m0KoR4ZssY4UafC6T3xM - O6Ty+keL8yhF3zPvfqfKww4FChllAVjN/qzWT/Z8Vuu3Kw5v8z+z+t7MOQ5kjACQmb9DhvfSetZnb3Tk - VN4OfdZPnYaMEQIZIQAcM38H0Pq+z5/V+p7wZJjWt4OMEAKBCABCkuomHU6ZuqwSwF87hdanmrNnne5P - J9P6judPFw63ePwXLDetue/86a7J9JFn21UAnDJ1WQWAp9td63vBIz9P7RxA1OZHy77E26d20PqO9iiF - fcpq/bgwZs1951e5IrUzCIBTpi4rI6BrE1PqEnE6tL4YQwVT0pjJaOxbfF/Mq7L9QzqYP6v1M42Gfmvu - P7/G8eMdXQBw5q8G7K/0ZoTWt9sXW8axMj3/QbM+TPRRpl8FaxOSTerxSEuAqbzpslzqAJSvvn9YjaMh - HVkA9Jm6tCuAalgSfeQNcQV+av24e0HjOslMjC9+oTQWh8zwxIjaEwKASkJAWuB+ofVToKWDa30rjnVg - QmBP0qE+8mx7JAJVwY75U06IYeA6qYdS42VaCHtRjYJq0iOUSkP4c1RjL42Cahp70Sioxl/6zxqoeJZq - fH5pUfy9/erabtDM7waHVxrcgj2OvmC8kVZIqwDoM3XpBIhOPZYNcQ1ewnsWx13sYd3M3FQzGJzQKAiN - GsyrMz1jahrVQLUowF9UfkWj0t+YgIBGAU3TcQIsgkCoC+Hlcp8ypliHF1qCZs400xAHRp5559sTXM6a - EqTtCNBn6tJRYNV7zRviFvw465u0vY0ZT6VB0gdGrRNR6XmqmSwCKh8HuMlPCAGIAgICKAr7mYD/zfiZ - b6Kv+7RfnfXd4MnMKMUPV98/bE7cqTqaD6DP1KWlYHX7DadfurS+6Xezxo89v1OuiCkopSDQbB1zOmiU - CQUqMT3VmOVgzQkgBAQKoCggxPy/wgUDEwSMKl0A+KD1veyr0zH71Vk/fTTUAShbPXlYre2ffeTZkG8z - JYY5SNCnz8GGxIA/Wl9mfMoZWGjxqGQIxDK+mIty7Q9KmZlPKQYcfzB+ds7xOLX0IBTmhbFpex3mrt2M - We9uRl1zBFAICFRAUUE0gCoKo0e2ADQKxwlDcWjOpvK2w9yWMa5wGHhKwHimzANWVxC4BdBn6tLpAMZK - xHnZEPP8bj381MKwMuNT5rhjzj2m8QmNWoQBtZ2fmuZgzD/u8n64qv/xtkvbvqcR42atxoovdkJRVBCF - CwFFBSEqY3iiiPiAOwGQ1fqZMb+/OB5aPXlYZcyjHeUI0Gfq0nKwRp3tF96Lx/wmxmfMCy1qaHOz6587 - BOPMz83/B6/uj0tOLU1K2qRZqzHr/S0gSghEDRmCgBAACv/fxhfggOas1u90NJy7evKwatPjHUEA8Hh/ - LeLU73e7IanF9a3MLzzx3IOvaSBahDG9HqqjGHj8ITjzuENw4mFdUZQXBgB8/s0evPPRViz7/BtDOFCK - S8qOwp+vHeKYxImz3sXLH3wNRQ1x5ucWge4LcCAAOovW52OyWt8W6gCUrp5s5Ad0FB9AlWvm90vrWx4w - Mb8Up4emAVqEv8dCdUd1y8cNF56CwScfoTO9DP2OORhXDTgea7/cgd88sRB1jc0oyQ9j4lX9XZE6fvRZ - WL9lHj7dGQXRwAKyYp0sOwhxswHSqPUdz58uHG7xdEytL0MJWH7AKA8rSwqB5AH0eWBpJahNvN/lhqRU - qcca449h/igQbQONRkCjbTiyaw6m/fhMLLh9BC7pV2rL/DL0O/ZQPPaLctBIC64b2gtFeTmu9+mOH56J - SEOdTgj1wMgZldTjFrzgcOPoSxPzB538RCgdedbtb1V6WJ2Tuf09AvSduqwrgFrAofYPROtDMs8BPUuP - Z+QRLaIn53TJVTD2wl5xHXfJ4E9Pv4k7fnwuivLdCwAAOPmGp6GE8xAu6maEBuWcgDjhwKzW7+Q0ICYp - rA5A6btTLtiT6UeAKqTA/L5c2zV57anh1NM0ECkzr/fhXXDv6NPRs3tXz8SmwvwNLW0ghIBqEUSa6hAu - 7OaI5v0qqSfTzvp8TNA4bDJCAzkK+HoE6Dt12SjAgekfxwzyt3gmz7PnAoDw1F3Kz/u9D++CJ385JCXm - B+CZ+QFg2pwPWIIQIaBRJgQS0ZxN5fWRBq/7FDDzE0oTpYOPPOv2t0a5XHUyfP6YE9z0rwFwtNsN8bVY - h5TKKzLzjPz8CLRIBF1yCZ79dTl6dncfoEgVtu9pQPVHW/HPdz7Clp37QFSeA8DNfyWci1BBCaw3BLNa - v/1paAetbwebAZStmpz81qAT8PMIMAF+M7/nLxs7+xNqhPREuA9Uw88Hnxw48z+3cB32NbcCINi2sx5b - d+7D1p312PL9XksCkKJbASAEWrQNWnMTlNwCnj6cIWd9L3gy7ayfiTTAMeMLOBqM1ypdUhIPd+oWQN+p - y8oArHWzIYGU6JLTfSWTX9b+NNqGd+64FN27Fvixf3HhxkfnYcF7nzEmlxmex/rZz8Lhx+4EsMgfEwah - gq4sR8AJ3W73ycn+p4IjaDz7n9a3w9Nv1RRnBUQSgV8+gOluNiXIRhnsAo9w/Bn/C+1/1AEFgTM/APyg - 9FDO5Dzbj2f8EUVlZr/M/KIaECFg/wCtpT45zZnI/F7P+pnE/O1/1neyT9NdUmULKQsA7viLTYGz+VAD - 7cDDb+/pST9S1h9olIUBo1EceUDwzA8An2zZyZlflRifCwIirACW9SfCfUS/DQjQaNS/feJjMq5Yhxta - ghYsXvfJDQ0cUtD6Mgw5+/a3R7mfyAx+WADTnWxIoO2xTFV5Ze3PcvT1Yh1Uw4dffe8Dyclh/Vff6cwP - JcSEgUj1VQTzc+1PCBSJ+eNeBMpQz/V+rfWDZv7EOKa7pDIGUhIAfacumwDZ8ec1vJfChhCq6cU5Rcku - c75/VA/9US2KuoZGbN9d7xKpO5i75gvm4deZ32zyE8Qyv6lmIAA1tzC1feJjOrTW7yw0IAWTPzEcffbt - qVUQ8iwAeNivMtFiA2nsaZnfblJKNSCq6Tn+IhRINQ00GsVziz9OZc8SQn1zGx56fbXF8acYJj8xMz4r - BiIIUqGE8xAqPABKODe1fUqDAytwrd/RLRcOPmt989yglf1vf6urS8p1SCUMWAmgJN5C09t3T9b+0It7 - UCnxh1INp/ToiuJcFeu/+h7f7NqHw7oVp0C+PUydvQpbdu7VTX6Ts4/LWyWnQGd8vRaAeDaVfZLGdOg0 - 2DQJr0BpgG9n/fjzs4dLQFEJFhp0DZ7CgHq+v81tv6BbfJnmN133pVLBTVGMM4LehxXhd+efjEEnH+5l - f1zBuOeW4KVln+gOP90C4KXAAIAQBaH8YighnkHouDuQs31yljfgZPL46+rQcf2gaeAQgLlvzC0eNsbU - AShd+cAFe9yu09sRgKLSNfOnHN6LZ6Zx7a9RIwmIHwNO6VGCJ385OI3Mv0FifnbWJ+KsD1YngQCgWoQT - 5aOjD8n2nxovGY/1FW8hQZvkXiyXDDwaBXTWN+aPZX6A3ROodLkbYr3uFtv3gdjbfmnV+qbnhMcf3ONv - 1v6PXXMmzjmxu5d9cQz1zW248fE3seLTbca5X1V5zT/FlOlHuLNPzS+GEsr1bZ+SMr7NmERXj4lejUge - 4zAr0S0dWa3vbG57xpfBkxXgxQKohFPmD0zrW5+1fsmZYCjK8+7ieH/jNry+6pOEz8x97wsMvfPfWPHZ - NiPGrxomPxGMLzE/ABDFUmsgSK1v+tWwjhD3xX0noKxCsiY+B83ZIrNa33ccDpgf8GgFuLIAZO3fblpf - PGs9/1vSfmk0ikevOQuDTvJmATw29138+aVqHNX9QNz0o8E44sAuOLxbMb7eXY/Ptu7CP9+u4Xn9wsPP - k3v0iz3E0P4S8yvhPKh5RSnvU/L9p9KPwvSXv0jUtrSUqR4hz02IsQgQ5+jiNw1e55fGZJzWd4HHIePL - 4NoKcKsiK5GM+VPwyJqIdjQ/TejPqt6wzbMAIEQBUUPYsnMfbn5igSmMp9f1V0P6+4SoAG/4odf5tzA/ - iAIlpyDlfXKr9U0VkG2qHVNQvk4CqrH1KpIgoCJTkQpaKExCINOYv/N4+N2uy3VEwNURgIBWBJrU4/Q4 - EXOuNTsA2VsUL678Epu+2eNyUQy27txr5PLr1Xtt8vr1kt6iyYdiz/wgUHMLmRAJKl5t0fri/oOogGyK - kAhLSYsYVlMkwluYRfSehibhASqtXbzvMw3xPnMXe5VxcX2Bx8ncYu3ej4UVboY4FgBlDyytQLzrvmk7 - 68Ni+hvIqbRpclOenz26EBu/2e1qafXNrVj5yVYjNs9Tdwl36hm3+lS9hJdCBPMTe+bPK2KOvyA9/Pqv - Vq0fjWX8aBu0aARaNIJehxdDi0SAaBsQbdUrJukNTTVxwUpcrwYMl4Bzv0BGpvK6AB8u8CSe35vWt445 - uv9tb1U4HebGAqhMgtjThrhu6Mn/YC7zDZMTSzZv6xpbcM3f38IHX2x3vMQpLy3Hlu/2GL38iFGrT5Tt - 1uv3Wxg/IfO7BZdan5FNY7S+mfEZ89NoBL2OKMbsm8/Dv288F2MvOhGaKJLKBYQQGrBYA1TTbKyB+J9f - xmn9BEuOB2kx+VNwclqg0gVdyTGWPSA1+PBAXLwxqTX5oDDCgJpR8Veq/kO1KCC+0JE2nHXMAagYdirK - +5Taoty+ux73v7iE3eMXdfolza87+cS5n5/5hSBgREkCgBCEcruAhBJXGI5Hu2dHn7wnetciQ6NDi2J0 - /+Pwh5H9UChVP6758lvc8I8l2NsUkWhXDZ8HVB7SjGfpsFW7oiHZZ+7HPqWCA+0e3vOK59yVUy+oTjaF - UydghVcC7Z73pcmHXvXXYH5Q/kWnEb0MmG4VKApWffE9ln00CyUFOehdehh6HNwVPQ4uwaoNm7G3sQUf - /W+7bvIb5wlx/mVfbQoKJcRv9kUjFsIMhlDCuVBziuJn+iWg3w3jM8VhEYa62S8anxhHgC55IfzpR2fg - 4tOOiZm27NhD8Mb4kZjwwkq8WbMVhKps/xQVhKqgBCCKwp2GKoT7EJAFAQWoi5yBRJ+5gzGdQusHg6MC - QLUDGhPPVvbA0q4AdrslLt5iU6vzL5v+vAuvVOff0PpR/ndhwhqMQCNRRJvqGKOINQmPt9Bussbn13Z1 - jUgUqLmFCOUXARTQIi36uoiqQlHCUHLyDS3pcp9cMz/lh3HxMzWEIdXMr949SvDX68rR/YBCJIOZSz7B - fS+/L91TkJ2eiinJSd87HiUg1iiBi++Hb/uUCg50WK1vxXPAiqkX7kn0jBMLoMLTYt0wvtv5KeVOKMb8 - Jbkh9Dq8RPd669aARg0NSMHPsQClhyPS0iitjehfZPaf+GJz8144/7ipr+bk4dNvm7G3RWPXduWMPxhH - Abd75Zj5Za0fY/Jbvf1MKF439CRUjjzN8ZKuGnwyTut5KCqfWIKvdjUajkZKAYVpf3HMIURh7wshAABE - 0OLTXQc3+5QCngwN73nFUYEkNQOcCYD2MPkTDTCZvRp6H16Mx8ac7XailOA3Ty7Fqi/3ACrhX37AjeKT - affV5Oc3H2mUHYOK81RM+dk5GNSrh2saex7eDS/efgkm/ns53ly3zRA4/AhACcuXoIQyR6g4EiTKGZDo - dgUBe/iBTqP15ecrkEQAJIwClE1ZWgaKvl4XG1wJMIP5WcxPczlR6tDW0gSttZlbGwYt+pEq2ZfJi9an - chqv6GpsdXwyhyeNRtD/+IPw1oQfemJ+AUV5OZj2i3Pxpx+dykKHESN8yCIGUdZsJWnOAEz75BgyObzn - dP5UtL6X/AED+g649c2yROOShQErvC7W1zsClOo8b0wgn329iNUUgQLRlgZorS0xcXgn++SsyUcik1/S - +JrEkDzMV3npKXj898NMXv5U4KohP8DsOy/FUd0KWMKQnjfQFj9nQA5tUQ/cn4YQYtBx/RSTehzjSTCm - ItHYZAJglNvFBqL1TSYk1Y+fxu/tIQCY4y/a0gCtrSV2DfGu+sKh1o9h/jixfYnxtWgbjuyah2fHXoDr - LjjFd5LFkWB0/6N5wpDAa/RaFH4ZuSwbpbJmcvBZZbW+YxwOBMyoRHPEFQBlU5aWw0mjj6C0PqzPi7On - xPztwPd2EG1pYNGAeF8or1rfZPLDZPIzhhNJO0zrX9SnB17646Xod9whgdFalJeDu/9vICZf0x/FeQpL - HhLrEFEYPe04yZEgzl5lnNZPsmTT3Jmh9WU4esCtb5bH+2MiJ2CF08UGfTNQvKFwf5vV4fbx1t341Yx3 - 0NbcgDuvPAfHH97NOS4XsPHrXbj/xaUgSggbtuxktHNNH21p5BGCfFtaXFXqSerllzL0uPa9+8rTceWg - kwKh2w4uObMnTjiiG8b9ewXWb62LjRIQS5QAYE7DJBeKsh7+AHDQ+DkBiQTAKCeLDfJmoPjF3rEuMs4I - 6hrbsGLjt4g2NWBfU6sHpM5gX2MrVqzfDKKGoYTCIArfPl0INOj9/USzD+eazIb543n5pfBe7yNKcM/V - A9Dz8AMCozse9DyiG54YewH+8spqvLLyfzw8S0EVyvMmROIQeHiUghIlVghkooffBZ60evjd4mHPjor3 - Z9sjQNmUpaNg1+I7LR5+8wSxzG8cAggVcXuekx/23qnXORh35U3nfJ4aG21pYCE435jf4uUXZ/5IBFee - dQyevGFYuzC/gKK8HIy7+hx2JMhVoGl2RwIRJYCtczAjz/oZxPyejhXmZ0sG3PLmKLvH4lkA5YkWGqzW - Z28kD6dLyTsgoPrFHZept26Bx77ZAqV8eIkWTy2w4zJ/rAAozlVx1+j+uPj0Y4Ol1QWYjgRbpCMBwCwB - DaAKeAqxBgqFaZ9ERUbi7ZVD6OCpvAYOt7TbP1sOYI71zXhOwFHxJgte68dhfpFyqjvUjBRUmRmDBxmf - 9L9YF7GcbZPRL0KZcZk/wsN8LAbf64gueOX2SzOK+QX0PKIbnv/jcIw57wRd+1MpX0Dv06CBdW52ykCZ - HN5zC+nT+lYYZfdmjAAom7K0DMDRdh7+IJt8xHj444zRTX5pZcaPSWdIDQgsFob55pvju9X6fkmbIF9u - MjG/wUTXDT0JL9x6qaNc/vaEyh+egYd/PRjFeYpBg34TUVzaskYHEny3XEBawntev+9BHiucRANuiU0K - svvOlmeM1o87BjBLCxK86W+HXLdG3Jz3LT8I7S9bAJY4f5dcFY/9ajAqR56aRhpTg0GnHIVZd1yKXkd0 - MSyAaBtnfk26xCTvGzX/uJ9pfYHHR61vnbvc+l6sAKDG+T+YpJ7YN5Iyf2KqJM2fJiGgXxwyrr0SRUUo - vwtC+V1YrUA7OuIxv1yNl19phhYFjUQw4PiD8cptF+GcFNJ52wu6H1CI5/54KSqGnsDqMsg1CaT6BTE5 - Apmo9V2O0Z8PUsC4Fyzl1vftnIAjTUSniNh+jA+M79cYLzh0D7903ifEKPxhtUZsaDef+6HH+fVQXzSC - i8uOwAPXDkwDUcFC5Q9Pw+HdCjH5lbVsvwivi6iBX7fWWHhQS3CBKA5kJON7wRMk4xtjRlr/brIAyiYv - LW+/8J4XHNIZ3IXvzTsIr7/sHEmCNCnzW5J8oizLD1oE5b2D72iULrj0rOMQadijCzdRx8GoMeiwzBiH - rNb3RsPAm98ol59RLAPK486WsqPPOOuTRM87wmHNICNpsf4N4Uhi0dlpfWrzSzyPv2B+UcYsGsFhXQsC - p6m+qRUffL4tcDyFeWFEIy2INOzWjwLQNNCoUa8B1E4IxH4h9oMLPEHSUC7/oiT6oxfC7BcbnMkvWJEg - hV7nrkDq8OOIFumDjjnzc4eY0PrRNl6Sm3vOAz7T1De14pr7XsBPxj+LuSs/SX3CJECjUUQjrYjU7zan - M2uWC0TU/gJRp9P6bpjfPxrK5V+sPDPEK2H2i/VT61vHGDPqypcEbQlIuQbWLEAT7RR2Wj/mLr8maX1L - yG/0wJ7od1xwfQ03btuJy+96BhtqvwNRw7jlkbkY98+3g9w8PPj74aBaBNFIC9rqdzPaTSXLzVWdZWuA - UL0OuXPIan07MPG4LgD6TV5S5pUw+8UG7+jTK3EDECW7ScBnAQJxucWSBWhHi0nryyE+i9YX5bj5rb4H - KgZh3P+dExgNazd+g5/e+yK2fF8PRQ3xVxizFn2McU+/g/qA7lNc2v9E/GdKBbrk5TAh0LBb6kHAfQKW - mgKgGogWaw0khKBj7h5wtLPWN8HAm98oEz/LFkCZW8LsFxuk1oc+q9URl5Y8ACJbAHFokZR/rNbXdJOf - VS1uM13n7ZKn4Nk/XIxLzjguMBLmrfocV987E/uaolDUMIgaBlHYS1FDeLn6Y4x5YDbqm4MRAj2POBBz - Jv8MvUoPRjTSjLaGXdD0a82WmgKa5B9x6iDs6Fo/PTSUiR9kAVDeIbS+FKXQ04GFUFDU4J2BciVcGv/I - YVvBR/LwQxTR4Mx/YVkPvDnpCvQ77tDAlv7nWStw64wF7DajzvxSuzP+Wl/7HcZMmY1N23YFso7u3Yow - a+JPcNPogYi2tSDatNdoSML3BlGproA1a5ACMV++dGh9F2MCSOX1hwaGo1z8qkh/KE1tI5yl8npYbAzR - ROrCY7wkxgwMpFbf1siDaL1t1fqaXMAjys39NpbbH21Dca6CR35Tjmk/L0dhXnC3Gcc/swhPzX9f6m0Y - Ys1PVGvfQ2YJbNj8PX5636zAhAAA/HL46Xju7qtwWNcc/QgE3rJMdxLq7chofGvAS8w9YJNfH+Nmfodz - +2C5lIofZAtgiONJ4pj8zse42xCd6ARmPmPLgOMA4vIPFCn2IK+Zn1tlra9FQaMa12ptphJeA044GC/f - PhyDeh0Z2JIbmlsxeuKLeLn6YygKY3DR9UhnfCUEoopGqEZD1H3NUVz2p39j3qqNga2v3/HdMXvST3DF - gGNAo63QItJ1Yrm3AY3y2o+SNWDqT5gkb8BLzF3/XJ09lrFaP3aMzuuEUop+9y8pA7DW/UY4uLab4vkq - JilJtLSWz9VRozgmjUYQadgDLdIq3UU3vjg6Emo3v+Th11tgKUYHYEXhzMEbhejXj1kWYCi/CwCjbZmI - dZuadPBy3XddeUbgFXw2bduFO558Gxs2f8dpCEk9DkSvQ3nfxdqZplVz8wENaK3fiXHXnIsfD+0d6HqX - fvQV7np6OfY2R7kgkvdeNGI1KgwB0s8JrhV3sGu78ef3l4Z+y/9yUY3YydKkk6RZ6+tEJ9gSAFIjCmGW - KwgVlDDtJpp6KNbmnoxx9Z5+4qV3Aja6/uoef4AzjoHbECRy/Jo35YhGQbQIc/QJzR9pQ+8eJZh9x/C0 - MP9P75+FDZu/5+a+uaU5pE5HRHRA4j8roRyEC7tBUfOgqCHkFh+Ie/+9GBOeXhjomgedchRm3jUCZ59w - kLFn0ajhJJSOUlS6PRnPQdhJLvAERUMpYBwBypxPEoCjz+as76jOoHjEJjbPLuWEYXISin96FSHLy+TF - IGY6iJ25Lzz9kubUHX2W+vmRVvx86Il4/tZLA6/gM2/V57jsT//CvqaIYe4rQosa2tQkFAXzh3MQyu8K - ooagCGGohBAuPACvLPsc102djYYAy65171aExyovwp1Xnc73z3xsAjWuFcctQQ4KInpFpCu852Z+hxCw - v6IMSCYA0hXesxLtcoxRUsscj1PzitgXX3+fNxGRC1jKL5NX2WLS6am7gGgWKjQ/JK2vh/ekL+2RB+Tj - 2coLUDnqdJeb4x6emPse8/TrTj6LCS26GhPzcQeEQMnJQ6igKxSVtzzXBQATAqGCLlizaRcqprwSqBAA - gNHlP8DLd49AryNLWKOTiChBHrU4CKOmYwugsWxBav0+OIDOr/VlKAMMAdA18WIzUOvrLCgzv/RF4F8K - NaeAfcHFeVzTDKa1vjQjTm/6UpmurQohYtS8p1FNT+phpmuU/9+GC/segZl/vBRlAYb3BIx7+h1Mn7Wc - MT6xOT/LzM/P0sIqCuUWQc0r5n5Owo9NKj8WGRaEkpOPz7c34bybnsKmbTsDpafnEd3weOWFuPb8k6Bp - bUa9Qb0UuWQNUK71dWvM/D1x/r1KDO1xgccxDnc0dAUMJyA1TWT5Jehru660vtUJaMopj+KZ356DstKD - PCwqOaz98jv8dOprOoOIYwKlFIpCoOYV6mG/LnkqJl8zIFAPv4D6plZUTJmF9bXfQiHMgw+u+WMclrrT - zGjpreYWQQ3l8G0WVg1grUtoOodHWpFPG/HYrZej3/GHBU5jzabtGPvoIuxtipgcsUQ+zginoBBwplBt - 6k1KPaXyBjV3Knj488v+ejFR+t2/pKs+STsm9SR83sL8to+JiECgQKXmm5qR1KOZU3nTEd4TsHHbTlw7 - +SWs/3IHC4PqXYolJ6fs1wAk5iAIFZRAjammbPOp6xYDd6AqKppIAf5vwnOYu+LTwOks69kd8++7Ahf0 - 6wGNdyXSomZrICZnAAksAa9aP5OYP0XL5ZybFnQNASjLeK1vfdv2DM870QTcJoxSDQpUhApYyE9ugVWc - q+DG4f3S1qBj47aduHrSc9jX0KZrefMFCbGJFOwN/j+lgBpCOK+Qmfn698/qC5F9IvKvInqiIVx4AG59 - dC4+3bwDt/zEWSqJVyjMz8EDvzoXp1VvwKOvr8PepjZQqrLPXaFGQxKFdS8GqH0zkv1R69uPKVMyXusn - fIwaD1MwLRAkUMrOm5FWROp3mXrj9e5RgqqxF6SN+eeu/BQjbnsKe+tbJZPXvIfyJSTdcUkp8+rnFTGG - kfeSSnspHwOEbyTmQ+FWRH4Jnpr3PsY9+VZaaL+y/AeYedcI/KCHUW/Q5Bg0XS+2pBDvr1o/zhgFQHmg - Hn4L8zsa4xY0poVdNZjwCjypR4u0sgo30TZUDu+D528bnrYGHY+//i5u+dtrEKnJlpxkyRSWU2fZ74oa - Rji/mBsEog6hZsP8IpGJShaXlOugKwu2hlBeEV5evAE/urMqsNuEMhzarQj//tNIXH9ZH4tTMArT5Std - CEr7kwQ6QCqvXzSUh9Kl9f0kTgeNguoNKI38+6Dg1WUbmDbkyz3yoGI88KuL0K9ncPf2rTDuqbfw0js1 - gKLi4v4n4/zTT8DhBxUDIKhvasWaz77GW+9twtbv9oJqzFKnGgCiQA3lQc3NB6VR7i+w7r/Uikz3rmsm - Z2Dvow7AGccfgtOOP4S3Hqeob2rF+599jbfXfIlPv/4eFfe8gIdvuRzduxUHvh8/v6QMp57QHeOrVmDr - ribjD9wiopT/T2j8voRiSICM73n+gHGQfvdWVwG41g/kds/7yvx6BIDykJ5cRKMNWqQV/7rxfJQd6293 - 3PrmVlQ88Ao+/nK7/t6VQ/vitqvOQVGAF3hMa2hqxbX3zcT6/+0AIcCD11+GS88+Ie7zNV98i0defw8r - 12/hGrqYNS7Vs/6Mysb6nnJtSaOC6cU9fQ2jBx6LqwYfj+MO65pwnfc8uwQvVX+MAtKK5yb+DD17HJiW - /WloasX0V97H7BVfGhebiJTCLTpH6ZEBswAIWuu7ntslDo94nlEPG1pRCbtUYF/O+v4RZxpDocd85cIR - VIvi8rOO9bVxxgebvsHoCc9hy7d79PeeuPkKVFzYDzkh1fvELmD7rnr8atpsbKj9FkRRMKDPsbjlqsQF - Q7p3K8Rl/Y/HgF5HoaZ2D/a18M2WMhqJ7hgUPhSNm/wG8/c+qgRTxwzA6EHHo1txXtK1Dul7NOau+Ay7 - Gin+s2QtDjuoBCccGUxYVoacsIrBfY5E76O6YcF7tWAVo0S+A4yLYgQmIaB/TzNN67sY44kGBnX2vQEz - SevbjEk0v58HgBcWfoiJVW+b04ApxeA+R/uIJTFs2rYLV9/zAvY2toKoXOAo7Mvc0NyGy8c9jy4FuTi7 - 11G4+vy+McKv7LhD8MIfL8Y9L6zGW+u2ST36CPcNiIpG1Eii4bfvfjTgWNx5lX324vZd9VhcU4u312zE - io/+h4vPPhm3/t9gdO9WiMMPLMSWHXVoUgtx26PzAEJwaf8T07JfA0/pgbb63QgXdmNan7IXhcZyBDjJ - rLGsSBd2cYXcY8zdFaTxWBGyezOVxQbC/BbGp3YP+Hj2r29uxV1PvY03Vn9mJM5AYpA0wbxVn2PSM+9g - X1MEihLSzfZt3+8DwCrt3vWzobh1xnysn7sGT/1nNUYPLcOoc04y+SUK88KYMmYgil54F7Pf3cyEgDgK - SM4x5uxjeQ5jzjsRN1zWN2ZNazdtx7/fWIsFqz7hnwlQUlSAM39wFLp3Y8Jn23f7oGkRkEgbwoXdcNsj - b2DNhi2YcN35adk3LdKGtobdyCk6EEDU8AeIPgQULFzopg9BBmp9P3Cohw2teNrTRDaMH5jJDzvBYs0B - YGfVH551LA5L4QiwcdtO3PC317Fi/WYIE9KMU8MNlwffrOPFhR/jriffQluE6vn8hJ9l9za04sCuxehd - ehCOPrQEPzmvL+oaW7G+dgc2/O8bzF78MdZu3I4flHZHty75+pyDT+mBb3ftxSfb6iwmoxCgzNH3owHH - 4JYrzG3INm3bhdseewMPvbQUm7Z+DwAoKczHb68YiGm/uxj9jmcCZ2b1BvxnxacgROFeeAWh/EJ8/MU3 - +Hb3PpxxUg/khIM9Oj3y8jIAFFq0DWo4z5QZaDKXTccBATbf4s6i9WPHlKqHnVsxIdXFBq31SbwHrAJA - 0/DDs70LgLmrPsPYh1/Hlm/rpApDAh3TkL1Ku+OqoX09ze8UxlctwmNz3pUKdKhsLYqR6bdkXS0O7FqE - 3qUHISekorysFFeUn4JuJUXY9v1erP/fdrzwTg2+q2tmTMf9FUNOORIffLoN23Y3S+QxPwClFL2P7oq/ - /HKQvpaG5jY8PPtd3PrIXN0PcvHZJ+M3o/rjgd9eiNNOOFyfe+lHW3Dfv5agNRLlGYMKwCMOobxCfFL7 - LZbVfIFL+p8YqBBY/MEX+HZ3PQgFqNYGJcx8F4RQEP17xf0fcmFXa9qwS6b0eA5vVwFD+t1T7Xy69j7r - y1EAKjfQZLfFtEgr/jXWWxRg2ktL8dTc1VIqrbQUHgLr3+toPDx2JIryg/H81ze3Yuzf5zPPvXQZxxzr - N8fq+/c6Er8feQbKjjPTvHbTDixa+wXeXL0Rexua8dgfRqGsJ7uQtGN3I66YOBuNyNO//CIR6OnKc9H3 - GOa02/T1blz/l9ewZcdu9Dr2MFwxpDeGlB2jm/oCGprb8MTc9/HPeR8Y0QVJbFNQKGoO1HAuopEWHFZE - 8PDNI9HziG6B7GNDUyvG3DsT6//3HRQlBCUcRk7BAYZAJVLKNJGchbI14MIvkInhPad4nAuAdjjrx/7d - RgBE+SWVSBu0qPswYH1zK66dPAvra3fA+CJIy+F5/1cO7Yt7fj7MJUHOYfuuetzwt7nYUPudfvsupmqP - vihN3wOR8HLkISUYc9GpKO97NA61WEDbdzdg49ZdOPX47jx2Dzz1xod4aPYahApK9L0dcNKhePi3g/Vx - MxetR3F+Dk494fAYpgeYgHju7Q8xa9FHEgPZXDfmlpoSzoOihqFpbSigjXh23FWBCQEAmPDkf/HywnUg - SgiKmoOcogP0+gjsZqTNDUkaxxqwgYztHegChzMBkI6kHgce/oQCINoGGmnDszee51gAfLDxG/zmL69g - b2MrZOanOi6KLvlhTPvdCAzpU+qBKGewadsu/PS+WdjX1CYV7lAgX9phS9IkAcCYn4BAzS1ApLleT4Xu - VdodF5xxHE4/4XBd61uhobkNZ1//NOtqXFACUIq///ocDPxB/Jt9Dc1t+GDjdrz36Va8tXojPxIYxVTM - sXbp5qH4gCllhVqoBhqNoK1+Fx74/SW4pH/8fIZUYeZ/P8TfZi7F3sY2qDn5COeXGLcjpUpRcpkxJzcJ - O7LWl+cPuZmkXbR+ciqQSErHg+cXfohJVW8Zt+UEkwG6X+GiM0/CvdcNC8zkB4B5qzbinn8tZNV7uIlq - XN01+yAYiPwHQCEq1LwiAEC4oARtjXtAKcX62u1YX/sNRFGcXsd2x9k/OBJdCnkcn5u+Rx5cgi3f1SHS - WIdQfhds/HoPPvvqe2haFOKose37OmzdUYet39XhK+4DkDMJ9E0zJCevls7eIJKAAFi6NiEqoALh4oPw - x0cXYF9jC64675RA9veq8/tgyKnHYtzjb2Hlx5uh5hSAUI3tM/jlIUJ5MSheLUoKFTIyxd6T9Gp9L3gc - Mr7+c1wLIFO0vul5fv6FZAEIK4CXjnr2hqEoO/bguFPUN7firiff4iE+4/ynzw+KLvk5GFcxDMPPDjZ2 - /eKijzGpaqFUkVfSRqY1AdYGI6oahhLON3iQPxdpquOlsqQN1qNccrFTc2cjRVERyi1CpKURWqSZZVda - Phdi85P1r6bCncIikI5VofwSnqJrfH6RxjpcMeQkjP/5eYHu98x3PsJj//kQ+xp50VFVZbcGub9Ft2D0 - Y5fZGhCNYTu61pefDyWbJEjmd6X1bR81Phwacw02FjZu24Xbn1iA9f/bLmkl6IwPStG/dykm/+JCdO9W - 5IEo5zC+ahFmVX/MS3NLZ35Fvr/PCZcu9kDToIRyQMJ5MN93ZxDKK0GkaS+7DWfdPyKraomdKQuZRVsb - oebkAdCgUcqadCT8TKgZASE89CdqMCos2YayUupqmBUZpVTj2pV9fuHCrpi9dCO2frsX08deisKALK6r - zjsFp510BCY8vQQbvtoDomkgKgWhinGdGJK217eKGtaA9HdHkIFaXx5jtgAyUeub8FgtAFHCy2gt9cz1 - Q1B2TKwFMHfV55j07NvY29CiUyhP3iU/B5VXDsb/De3jgSjnIDz9q9Zviancw87P0B1nTImzlNw5E0fj - uMNKHOH43cPvYOnajdCibfp+XXzWifjtqDPR8/AD8OSCdZg+a4VRNARGFEDNKUDNjGvR0NyGJeu+wj1V - 76CugV+yIQSEAlN/fwkuOauno7U8teBD/O3ld6GGcxHKKzYlVukVlHnFoWhrI048rAj/vPPywISAgH/O - XYuH59RIfRDkqIvhg1F0pcLW7ajKECPPMaRV61vGKPobaWJ+R/UAZBzWR6U9N0I4RljH7kN5cNZy3PLY - XIn5pcmphv69jsLr91cEzvzbd9VjzAOvYtWGrVJHHlV3RpksAALo531Q7HN4xXbCM4uxeM0GKKFchHIL - QRQFD/7uYvz5dxfq15VPP+Ew3mbb6GYkjgzRlnoALHvw4rOOw9t/HYNexx7K9kpjQuneqoXYtG23o/V8 - upmF4tTcIv1Yo/dSIKop10HNLcDnO5owbOxT2LQ1uI5EAPDz4f3w0oSR+MFRXY0KQ6YrxRGARrhwkiIu - JqsrXrTK+TqCrAsQw2s2YxQr4/tVrMNuTOomv1inpYS3frFDMbF/Q3MbRk+ciSfnrTGcffr8zMM/ruIC - PH3bjwI3+Tdt24VRdz+HDbXfG914THX6JQFGZKOMGiQmgfnvbsJLC9eBgqKteS8opfjJ+f1wyVnHW/ZW - visPnfmZEWDe+MK8MP524wh0yc/TnaN19Y244S+vo6G5Lema9tY3Q4u2ItrGrAid+WUPvBCGJAQllIdm - FOKaSS9h6brNgX4mPXt0w7/uugwVF5xsKj8OrZX/rJmKjMQvNWZhMgcQdF2ARFpfBiXuAI+I441JSesn - ppRd7AABEd5cAGu//BZDb6vCx7U7TCkp7D8N/XsfjdfvHxO41geAJR9uxk/vn4V9jRH+ZbfU6RfNOYQX - WvJNKCTETOcksGnbLtxT9Y4pM/KwYgWVV5wRZ5/FvQZR3IMJhQG9jop5tHu3QtxVMdTkiPxqxy6Me/K/ - DqhneKKtjYi2GkcJPRFHEXUGjZZkJBRGs1KE3017FfNXfh7453Pj6LPwzz9ejB7d8nh7Ml5vULIIjLoI - MPwx8nfVVHQkMbS31pdBMS0oGXIPi3Vt8ichL+ZXSqS+HkzLz1z6Ga6+/2Xsq5dMfnHbLarhptGD06L1 - AWDeuxvxmz+/xsJ8qtF7D9z0h2Lx+kughnIRyku+xobmVvz+L69hT30j/3Iyprtx9EA98cd2s0U4UWGa - Vw3nQQnZR4Yv7X8CBvQuZW3EQ7lQQrl4+/3N+OuslYkXJ0Ujoq1NiLY1849OFBkVAlA1PPNKCIoaQrjo - IPzx0TfxlxeWB/45lR3fHc+PH4mKC35g6ulAeSly6P0JtdiuRHot2sRf4EzR+jIogZn8CFDr89ntjhV3 - v/Ae7nl+GaAYTj6RONSr9FDMfWAMfj3iTLfIPMH4qkW49bE3zE04RRlrce6XnUpSWFIN50HNLdSp+2DT - DlscDU2tqLj/ZXy1Y7eh1amGow7pErdgiOhmJH6ORlsBiEy9+Kkhv/9Rf2iRFkBRoIRzQUJhPPPmesxf - FV9Lr/j4fwIpACDa2giNCwHxMeo9C4Qg4BaSooaRU9QNz7yxDhOfeifwz6swPwc3jD4TT91+MYrzFX4s - iPL2ZIazUghZViPSmV8g3eE9p3gSt9PNCK1v8zAFz4KLHfzV9/ug5BawlA0CvXrQTaMH45VJP8XxRwRf - oaa+uRU3PfqG1JE3BFNTUSnTTySgyKmzoZxCKDkFSfFs2rYLFfe/hI+/+NrYfO7YnPLbS+OOY1eFqW4V - gVKmnVsakaiyer/jD0P/U0oRbd4HGmkF4ceBO/5RjX+8vsbx/kRbGhFtbYbuxxE5+XofR8M5qCgqwvkl - eHX5Jlx11/OBdyQCmDUw94ErMey0I1kbd03qWKz3oeB+Ab6PdiFZAUG2+LI1+V2AvQBIh9b3sNgYAaM7 - r8SLfSAEBEouY6Dex3THf6ZU4NcjznCJzBs0NLdizJTZeGvNJrOnX5z7rVl+UjIOIQTh/GKmXWWPOWU7 - K2DTtl0Y/9R/MfzWp/Dxl9/wvTH2YEDv0uTNOuREIT4+2sYcdong+isGAFRDpHkfa+XN9//hOR/gwsp/ - YN6Kz+yZVC8+xGiPtjay44DUl5H5QNjRSOEvYRGEcorw+TdNuO6+VwKPEADMGpjy2/Pw0I3noUu+KjUr - jegNSgFqEgKWzbV8T11CgFpfHqMAWOwWcczzfoT3HG4IEdJW13bQz7yUF7OkvEfAzaMH4eXxP06L1gcY - Y573h6exYfNOi6c/JDn7pIsn4ggAltbLuhqHdebXs84k/0BDcyu+3rkPxUW5GHjKMZYNZdbO9Vc4qVdA - zUMB3UeSCPqdcDiuPK8foDEhQKMsWaikMAdlJ5WivqkF3+yq52ttg67lLXQAzPdgNGw1Lj4pen6+alhP - aghqTj4+/7YF106amRYhAADn9D0K/5kyGsNO6xFTglwXAqbWceKT0zJW60s4Foc8TyKNCVzrg28ojIQV - 9p/c20+YZVH07lGCSVcPSFuZboB5+m97/A2e0x+vKSfMKbicHxQlhFBeF5YwJ2xwKeGMgKC+iYXcCvNy - MLjP0aayZCf93zSuaSh+cVl/9DvhcGdbS2N9jyeXHpx03G8u7483Vm7A3vpmRJvr8asrBmPs6LNjntu4 - dZeeQafbOXKuhiVzU6FgGZ2UZRHqRXy5q5r9mIPmqIKKSS/hzoqhuGRAcBeJBBTm52Dyb87DaQvX49HX - PsS+JpFlKUKaAIXCvqPyjUIm9Zwj8qL13UJMHoCXidKt9eVWz5IHFrrmN/rXXTf05LS04JYhxtPPtZbh - 6SfxmV/NQSi/C3/GqGFn5DmwZz/dap94M2/lZzrz9z7ucPx61NnOFk2J7VezuCA36dDu3Yox9YaR3Jmo - Yebb78d/WL8YZI3g2FTlFRdwxD4pkiWgmMOETSjE7Q/PT0uYUMCPhvbCC+NH4KyTDzb3SuQ5AkTvneAg - Wcj0WSR/zEetbwIFFLWuJ0K6zvqaifl101+E9DTzhaCfDz0RY0f2c4ksNYj19KsWT3/8hpVqKI8xv37x - R5pY5xlDCNhB1bzVOvM//aerHN9cHNDnGEfPxYPBZcfgwbGjQEGxp24fFsSNBFiTtax/FZV0iK4w5RuE - cqhUWFYKYUI2XHQgbn/kTUx8MvgIgYBDuxXh4ZsuwEk9inh+QJRZnzxrkKVVOMwYTPIneY9Mz/sXQqwN - AS4EgI/ZfMnGmLQ+YOryYrR+4l2BuVOmOC/kAaE3qG9uxd3/XIi3Vm9kBTtjuvHKzTkRw/yhXObsE9Tq - TiT5PioMbWgnATZt24kuhXkYf91F+PH5fVOkiMRo5WRw6YCTcdpJPfDY7JV4d/1mXGwJO36zs95I+rER - YqrIOdC/J6JMl2EJUKqxqxGKwt7jVY0VhEBBkFN4AGYv+Qx7G5sx6ZfDAr9DICA/FEWktRHh3AImtyhb - F6NCAUBZ4VVTMxJBI9wzvsMx7p6ntc44Jq1nfUnECa0vN60QzK9fA44a3tnAuwMzEJ7+DZu/l8x9Rari - Y2Z+vdgEYV59NbcYCm/HnWifZGegncHe84gD8dQdo70TYsvw7j647t2KMfEXF9j+7evv98Xe0CSiMGe8 - a7XmO/gECigoCzkKIaLIzwHh/BIsXLsNW+99Gf+860fpEQJaFFrzXkQoEMotBFWhl1wX6wY0EZOSSBPC - PTEEy/xib9l14GoA4xM9225aXzb39TO/hfn1c1gEJ6Th3G9U74mYTH4jto+4Jj8hCquIo1g1H8QGGHtB - CJN7nIHWb9mN3/39bbQ1NUp7YeSnAzC6+4i9A3QNpLcOV1iobUPttzEfAgHF6o+/AgFBNNpmJBbJ5qzl - JpwpYiEJPkIUrPl0G/+LZNILO5/COqH9ZhCAaASUMIbSmUnhc2psvJpbhI3bmzD6zn/jbzePQs8ewZUa - A4AjDunCCqe07GOk5RSy7YUhBMCvFFPKnYMOypCnzPhJxxjMD6CanDpxUTmARfEmSQfzJ9b6hrefahpo - lAsAzvRaNIKjDizAvT8bGHiPPt3T32jn6Sex9eVMnv6wcd6Pu08yvVQKN0UAXvg02tqItuZ9PDVVauyp - X/CBJSZtMKa+Vj3ZRlxB5o5V3hJMDech2tIALdIqNRmVP2D5zoLE9PqVWpUnQBmOO0UhDDfENVsV4aID - kNBLLuOkdgpB0wuLiDZxWqQV+WhC1YQfBy4EZr61Dvc++SaP4hRDzSmM8QFBhDgR+53QPx/9p/RofQnO - JadOXNQVwG67iYLJ5rPi0EzvxZj8Us1/djtL6geoRXHlgGNx0+VnBN6jb967G3HrowukL7WU2KNwBojH - /CrP6U/I/PoGmK0dveYhe10x8Bh075qHaDQCU0oqoP9v9SXGamfWXGTWoo9YxjTf7h6HdMHl5b2hqiq7 - ny8SXnRGtJoBUjKT7LQjCl6tXo+t3+/jzC8nQTEBoKo5CBV0QVwBQG1+sRUClDngLN+N1oZdmHLDpYGH - CTdt2Ym7H5mPDbXfQc0tRii3wFzgBYp+4Ul36lotw4S0OwS3zM/eOiD0/rjyPadNqjZN0h5an/3HzdkY - JtBiEjC65KmYfM0gDOp9pIcFuIM/v7QCT81/31y9R9f6UhILYOvpV/OKPHlviSgKIsHws45D3+NSb35a - s3E7Xl64DlRqDXbkwSX45YjTU5uYw5pPtjEBIFEQR/GZIa5fwHAOiiUr4pikqcbdDy6Ac4oPxB2PzMfX - 39XhFyODywLteeSBeHL8VfjzvxbjlYUfghCwJqyUAkQFITwiIKIgko+XtSeziGv/HX1xBcySv1+6R5xW - FnvS+inkD1ilurnmnbiDHQWNiHO+oQUHnHAwXr5jeODMX9/civFVi/DP+e9bcvoF86sJmT+UW2wwvwsg - cT8HL9I2AWjG8cHIaPML/G66aahLojOSVIpcOGH1I0cY4cJueHjWqsDDhIX5ORj3q2F4+PYrUKC2QGtr - 0S3U2EtEst9GyhbUc1z82i+KGOY38+xiwEgE2hPcBR7zGKLfP0esSacZCT26d5+f87VIG2i0DWOHn4IZ - 1w/ztQOwHQhP/8uL11ty+vnZTggBydOvF48kBKG8LlBCuZ60vn+bnujjkBx8+rHLTwTxBQoB0Ruc6qQ5 - Qs8jCLxGnzhbGz4IcY+A5wsoYYQKumLO0s/xq/tnB36RaFBZKV6eWoEzTz6UfWej0iUiGpWShqKQ6zz6 - nzhEY1OvYp/fAxj1AGocU5lS1qCU1EOtWt9o8Q2pyq94HdWtALPvHI7rLgy+gMembbtw+bgXbHL6FVMB - D6O7jGLk6xAFofwSKKo7nwQRmWSJtpJ6MbsSfzimexR+WxgGmlh6iQq3Jq8RQSCm/dYrDxNLXT9VhaKG - EMotxnuf78Qv7nk5cCFw6IFFePS24fj95X35d1j6HmsR5qzULVxJ+cUkDsXZmNS0vgw1gHEbsCYpZSml - 8iaJ7cfE9eVS320Y3f8YzLz9krSk967dtB0/vX8Wtn5XL93iU/QvlamAh42nP5x/AAhxl5CUjPEDAWFy - igYjhEBRfezXZzFpCQmxGgc5Baw2vxJy/H2yL6RhPhJACsEa0Q5WZowoKtScAny+vRkXXv+PtFwkGjPi - VMy894c44qACdp042mquMBQ1Kg3FlBqzswYcaH1pV2KG20ANYAiA2oSPpqT14zB/PJM/2sbuYEfbUJyn - 4JFfD8HdP+mPwoC9/ADz9F99z0uWnH7VXL0noadfSut1AE60vvUz+GDjdp+olTU+1b3n/oGZLkpZnwEl - nAM1nA9FDTuaJXEhDTshIG5ZCg+8VFsglItmFKJi/ItYWlPrI632cFyPbnhu4ihcPug4kw9L+Ad0Pxc1 - Wr1R62Uwk2UQb58dmfxWqAW4AHhv/Lk1CeZ2BzHOxCRxXE0KdVkcfW9OvDwtXn6AefrNOf1S1R65aq9d - Tr+ax0teu2N+1+C3oWCZT9OCtUS01hZEW5ocZWw6L59lFQJGOFK/PyCEgBoCUcNoJkW4/oE5mL8i+ItE - hfk5uHPMYNzzq0EozlO4crPWGoyakq70/ZHcZfYQR+s7+BiX/P3SGsBcECSAugBJmD+qGSWXdOnYhruu - PA0zrh+WoJ6df2Dv6Tc36kjo6c8p0ttzOQFXWl+AJmkBX3lU/kz89i8IDGYNrrW16D0M4+5RQq1vP0L/ - iZqFAGyFQAg5Rd1w58Pz8dfnl/pOsx1c1P8EPD/phzj56K6sBHm0jZUhj9qUGoMoNGLsonVXPWp9ATqv - ywKgVprbHSQKIcZlfkPri5LMvXp0wew7h+PKQSel4SOx8/RzR59qTe+N4+nPKdZ7zzuB1LV++px0qc8p - tJMLq8ix1rfDZw0T2kcIjMSkEMIF3fDs/HWY9I//pqXU2KEHFuHZCT/E768oM6xdPUrAS4yJPgSwcwwa - /3vR+hLUih9kAVDtj9a3LhiGww+UERiVz/7MHPr5eSfiHzdckLZ7/GZPv9Qy2lKq29bTDwWhvBLpNl9y - cM38wSjkWDTUrbZ1Blu+rXNFgz+lss2pdXKEgKUfK1AUloas8KKj4fwSzFm6Cb+Y9FJahAAAVIw4FU/e - eQl6HJRvusuiV7MSYUFTdCBlrS/vV7X4VZH+UON6IiRJHBLanxNhECi8/hF0yVfx6K8Ho3LkaWkx+QE7 - T7+ldFciTz9hsWX9Qk8S8GTypysoECCerdt3O3rO/1LZUv0EyTmoiAgBjH4MwiII5Rbi82+a8ItJs7Bp - 687gNkWCvicchn9NHIXzTj8yJirAyo3LtQaF1ncTFUi4XzXiB10AvDchjiMwzkSJE4dozK9UyjoT2n/A - iYfgjfEjcU6vHmnZdMDq6VfN5n4iTz81LvQYveQTQ6Zq/UyBYEtli9uKlH/JjYs5zLcj3+kIQQ3nY+OO - FowZ/2La6g0W5udg8u/Owz2/HIguBSGIhi6yBaBrfpnelKwkYMnDzAEodkWGxU4mcnVXQDNMGL10F9Vw - 1+h+eOy356ZN6wN2nn5r9Z44mp+K6j0ljjz9Ga31bZDSVBjRMdiUAPOqxRwfK+SfJaEuHe9kC1AJ5aAZ - hRgzfiYWLP8syM0wwYX9e2LGHy9kDkJKQcEiAxTuuvzE3S8zmHjcKgCqE03kKl1Yk86Xpg+bYspPz8SP - BjjrLusXGJ5+Q+sn9fTzpYdynXv6g9T6pBOYCEGXyo7BQW3ChKIlmf4dkKoPK2E0owC3/30+Xnr7w7Tt - y3E9DsDTd18CyuswxNwHcZMqnPjRavmX5ALAi9aP67lmR4BFH20LYg9toaG5FaMnvMg8/YrKSncpanJP - P7inP9eZp983rW93ZY5IhTR8BXFZWNDr8/Qx2NKh9e2smTi5ApY6BixrkL1yCg/E/f9chElPOOl/6A+s - +3wH2prrgKjUdJWCWdE+7RcBrZZ/NwmA9yacWx0zEVwwf8IPyrAG3lj7FXbsbgxgC82wadsuVEx5FRs2 - f2dx9qmSh9ji6RcbJXL6HXj6g9b6nhnHBXjWym5pcAsuxiTepwRhQiiQ6zmKfIFwwQF4dfGnuPWv89IS - IXh+wQcgANqa9zFLIGkikLv9IqBY/PDwavk9O2/Wa4nDe+4RC/Tysy8uCfaMJUp3MeYP2Xj61djzvtgU - xZmnP+izfkpOMsc4gkJgOOGsKIji4M6BVyGZdE2Wi0QwXyQStwnlMOHCD7bgl5NmBioEduyqx9srDJ5o - a9rHLg85tcqS7Bf/Lr1mfV+xmahaGuAdsal2nDhPs5tcCoBXVn4R2GbOW7URl935L+xrtsnpJzaefqlI - o6I68/QHmcprq/Xl2ns+gVnA+Gv/J6IhaRTFN60ff5Rh3VouEinGRSKFC4NQbiE+/7oZV932LDZtCSZM - OG/JpzG0M0sgajmb2XxOSRhf4uVq698VmwHVfmn9mCww/fhFUNfQggVrvvR9I5+Y+z5unbEgvqffGuPX - yzY78/R71vpBJ8R4gcB9ii6FSiBaPw6eeBeJRAkvQkzVn9RQHnbsU/DzcS+g5rNvfN+pV99Zxxej8CMK - U5xtzXvjX9JypvXlZ6utz8QIgDUTh9YA2OxsE+Ojtv5MBDIpFPP6u/5aAROeXoSHXl7hLKcfZuZ34ulP - u9aPechnL51eLDRecbo0QjqORjF7G+scJARQIEUJiOETEGHCa+9+3tcw4bK1tdi6vY4LIEhOSvb3tua9 - oNQiBJxrffHs5sWPDK+xPhvPFpvjfBOTbLhYUEwMlmDFp9ux6WtnGWOJoKG5FVdOeBGzFn/szNMPI6cf - QFJPf/trfYkzfRUC0iUaix/En7nllz/75G/WoLQHxKKg5OvERAobq2HkFh2EO/42H0/Ncd4SPRG8vmi9 - lG4e20SFgKCtaa+j/UqQNzDH7vl4AqDa3SbaPEflLCzd66L/L6rEvlj9SUqbt2nbLoyZMhvrN39nLt0V - z9MPKaefKAgXdE3o6Q/62m7CL7Reydf42U8PgNypmL/lHxC5dLj5fc/75HKM/nzSoyqfX1gCiBMmVIxO - xTmF3fDwzJW454m3U9qmHTvr8dbKT00NZURkwuAZgKghJGoo4qB3YLXdOFsBsGbi0DkA6txsohWxOZwt - JJr5S0cUFW/WbEFDszfv6qZtu/Cz+2Zh/ebvk3v6LSa/ntabwNOftvBezN/MWpPIAtQnC0A3NYXmEUck - n0D/EnPhJWHNAK1vnt+0NtiECeXbhKqqHzHD+SV4tfpz/DqFUmNzl3wiMb/0XSXGWgDmn0pOQ9y9rVv8 - yPA5dmMTfeJznG6ijDxGEknRAKMuvapXbdnbGMGSj7a63rh5qz7HZXf+C3udePph36QjHvOnJbxn+0FZ - 02Uli4lbL+99tsP1XtkvwpISK0xP34AYMXeOz9M+uRyjP59U69ul2dqECS31BsV3S+GVh0N5xXjvs534 - 5cSXsGNnvetdeub1VSAW5jf9TwEQBYoaa6W66Bg8J94f4gsAiirXJn/MAHOmmW4JEEYUuFf+0bnrXG3a - P+a+j1tnxM/pN7RZLPOroTyECuJ7+ttL6ycYBMOFSiQnpg8gzrcQjSv8qwkoPOnGmVrapyRyJn1aP94Y - uwiBESYkMPwCihJCKKcAG79pxuhbqvCFizDhgmWfYV99my6AZctDpl0N2zO/af2J6a6K94e436Y1k4ZW - w2E0wFHCkDjL8C+brH2+2tmImi++TYoKACZULcL0JJ5+QhQuOS3Mn1MQ19OfcUk9+rHJCAsRP810U/KL - dBvSL9D9LoqhWWETGk51nwR41vrxnxb/mcKEVImtNKSqUEJ5aKYFGDP+BSxbW+toya9VfyyFpRVD0Ogr - YL8okvnvQusL2Lz4EXP2nwzJLrXPATA27oYDyRkfBOB13PUuNLpm4IkXlGLe6v+hLEHHm4bmVoz9+3ys - 3LCFOURMlzmkwg9QuANS7shqbcdtWWEmMb5YMzdJCQAq+g8oKvY1R/Hhl9/x+n3CmjDSrO0nkz8L9sXa - uGU3L3xihGn3NbZi3aZvQe3mTcQo8nlVT/ZRda1mNm193CcXn4e3vnv8gyBgDUp1ckUXUE4XJQAiIAij - RSvC9ZNnY/LYS3HxwBPjzrxjZz3e/fArw3IVzj9q5KgALDGN6NErV4wvYE5CChN1gzlj3MIyAGvjbbjz - hCHKv0MUsDa+FAUSo21YPm207fXg7bsbcOPf5mF97bc21XssZ9hkHXnNxDveRQ8bn4I2M+8XpdToj6hF - QaOtaGuqB422ArygJEQbMZkmibmNKIiRbGIwplijZtzg5PgIld6zmVvssZ1Pwbhya/5scooPhCyY0sr8 - nnAY65NL2rNKvqLZh6jwy4p7tDXuxjWXnY7Kq8+xnfKh51fgmdfWGHdUoEoWnlGBKpRXBNXaY8IdDf3s - 4v8CEtp8ayYNrQFgHNBTuSNgCbHoUk66gLHgvdqY4Zu+3o3L734BGzZ/D6KG7T39sGf+RM6+jL3AY3Me - NeVQ8PLW4YJiqKEcKEqYR0DCzDGlho0X/13sm0IsPhP5OqxeL48lvKjhHCiqCqKI+UJQQmHjpRq4iMrG - mXwxJqss9rOR98rzPnkx+T3hiHeRiAk/xbSX4jZhN/xr3jrc+493bCMEr77zkTEGkvUqW0pEMTO/+31a - l4j5gSQCgEOVvHne7ghYElmEQ4UY9fegqHi2epNpivmrv8A1k+cYnn7FktijKEYVWIfMn3Fnfdv9gnT2 - FGEh2cdBoIZyEM5n7cd0Z6jE7KIEtp7FRiSfiZzYItKj9T1lji02d57UDTnMXyHp95DB/CoXyMKnoNfi - E+ar+SgS9A1B/wtpyLkSVIoMGIlD8mUioqgI53XBa0u+wK/vmY2GJuOK7xsrN2FfY5vhfNUjPcbugACh - cE6C9TiioSrZI8kFAI8G+HIzULYCbAozbNvZiOUbWJ71S0s+w23/+C/2NbeZnC2m6j2IU6c/jqe/3VN5 - HeOITVHVQ4EK0TWNEspFqEAWAlL5a0W2lEKGZpZadduZ6or4Yqu5TAiEE80tldWCwnFJZr9UYMVMVkfQ - +vaz65+L5BwUVoGRMMQsJ6KGEcotxMZvmvGb+17Dl9v2AAD+s/hTw/mqH5FkK5bRoIbyU6WhKtmjxElH - 2DPvfqcKwLWOkSf6o3yO4vUBWS00Vhe9/4mH4NDiEGZWs2oshjPPSCHWvbLsgVhPf06BlUiXO+hu01PS - +k4e4Odzo5MvTPtItQgizQ3s+mi8+U3+AEtijokGygpl5ncx5m5pSHwhJWZuw9lLTMKMAiDIKTwwkM8j - 9bN+8j8S68Oa5fsM0etS8t/wQp/5agt+f1V/TH6q2nLWF0cA1vKcef5zEcpx3m/ChoZnFj86vCLZ406b - 2FUhmQBwHN6C0eOdUtbjHQRQVFBKsHLj96CRNihqLiiNSg4sAJCdV848/UFrfVe0u8bBvHOEUN3jTAn/ - shGD4YiSg3CBYgiBmHh7HO+7XmkIbH5KIXINdNNWyUFYUeyFgMT81nkZ1ljmD2KfvHn43TxvZX6+XyJC - IL7PYBECvZYf4XtKFRBQtGghTHl6CT/6iu8xjwCI5znYJf64pKHKyTBHFgAAnHn3OzUA+nrbQMvDuoki - ugOz96n+M0CjbexLRzUef0VcrS/acROp31zGan0XY2StbDfW2s2XRtsQbbaptGQ9CulfZXsaiKJAzS2M - mTvS0hj7fNy5ITE+THns4fwuvu1TerW+3Rj7CIH+Oyir7CMNam3aw0PVIlGNWqwlBTkFB6RCw7rFjw4v - czLUTRvb6QCedr558RZLQKBx5UZAiSJttApR/5gouUzzNDdyIcDHxzC/ilB+scnZl7YLPEHj0MdIDCvz - l+VyCFFyoBQkb6JqV6Un2VqJmoOcgpzEY+w8/CKrzed9ah+tH49owxIAITrjEwDQCN8CY3BuwYFoa9nH - a/9ZmB+AGs5PlYbpTrfBsQUAAGfe/U4tgKOdbaD9YmMq21Jpc6jN36IR3RKQTUrA8PSbYtKZ6OF3McYZ - Du9f/tj5ibNJXOPwwPgO8QTL/E60fpJJKWxyJ8whHgKKSGsjaKQV5tMRYdo/2aWv+OvZvPjR4aVOt8Jt - 7mdVKl5uu5tXemxVd60K7zF3joRyEM4rhqKE9BQCQFTvMZi//S7wOMDhu09ByqegxPx7nJe+ddQ6xmZO - 68shDuMA4FHre/HwB838jnGY91N8p43qU/LFIua/CucWIZRTaGJ+JZSbmPmTrIeAVrnZDncCgGI65GvC - yZ9PkDgke4thCALKX/K/UBihvEImBECghvNZTr/E/C7p8BbecwtBCphABIt3Gtr/Ao8DHDTxH60i0RsO - 83daVnDc728y95UwC7cK/5YSykk8f4L9IaB1cGH+Ay4FwOp7z9vjGEGM1o+/9JhfrS+wM6iaW8hKd3EH - VYdJ6kmCw7WA6bCWi3scma314+GwWAOER3JiJAx7lihh5OSzwjSKGnYwf+wecZhe/eiIPW6W7eX613Qk - swLiav14zzsxS8HSXXmYr+Ok8ibGE6TWT28HHv9xBKv12QMpa/2EY6QjkTgWUel301GYsOOACxosPOZa - +wMeBEBCK0Ay+VPbPFtRyf6S1frOaMhUre9wTPBan/qj9Z0xp4XxU6fBhsdca3/AmwUA2FkBrrU+XG94 - p9H6bsZkmNbXaQhIwASbysseCFbr+yS83DF/HffPuQZPAsBkBfim9RMQ3Nm0vhvmz1Qa3MJ+oPVtafBx - n2wVLFvP9OrH3Gt/wLsFAADTQVGXcVpf4HEyd2fT+pnkr3BBSzq0PsPjjXYnY4KmwZbH2Fuezv4CPAuA - 1feet4eAOkecLq2fAZ5rGUdW6zukweW6nOHwM7zXPjQk0PoCPGt/wGUmoB2c9af/1kJkB7okLsGi3A/K - EOdVSng6Aw0On8+cVF7veIKmIYHWF7C5+rERpR6o0sGPKpCVXgmMIThTtb6LMZ61fke3XFzsU1brJ5/b - AfMDiXjPIaRsAQDAWX/6bzWAIU4JjLMQ94i9nGGDxuGW9gwSXinhyWp9X2hwyPgAsLj6sRHlHqgzgV91 - oCtNi83U8J5byGp93/Yp+As8+43WF1DpErst+CIA3r3v/BoAD2VkeC8gz7UJh8t1pY0GNzjc0iCe9+Lh - 9535/U7ldUCDj/sUl/Hjj3mo+rERNS5XYQv+dYKgmAAnjUQE0R1c6ws8QWr9jLRcXODoOBd4HNLQ/lof - YDw2weUq4oJvAuDd+8/fAwdmSadL6gmKhky0XBziyGp9Z/PbPp8cT2UqYb+YdfjhBJThrDv/OwfAyDjI - 3E2WDsb3gidIxg+YhszswOPmeUs2XwC0pJ3xneN5rfqxEaM8rCgu+NgMTocKWO4JZLV+5tDgCYfDMR09 - lTeDtT7AeKrCw4oSgu8CgB8FJuhEZy/wZA4NAR0rshd4ko/xcNa3wgQ/TX99XX4fAQScfcfbcxDnKBAX - MiRenRKOzkCDCzxpr8obAB3tovXdge+mv4AgjgACKuC2fJgDyGp9lzR0YK3P8Hij3cmYdrzA4wbqQP03 - /QUEJgBWTR62B8CopA9miOdaxpG9wOOQBpfrcoajk4b3vAviUdUz/Df9BQRpAWDV5GHVAB5KSKADyFit - 31loEM9nqtbPpPBeerS+GPNQ9YwR1R5GO4bAfAAynH3H2zWQuwpl0BnWM47sWd9HHDaM7wVPpp31U6Nh - XfWMEWUeVukKArUAJBgF4Q/IkDOsCYfLdWW1vp84OrHW905DHZwcn32AtAiAVZOH1YKiIlPOsDE4AnL0 - uVlP2mhwgSN7gSf53L6Y/LHrqqieMaLW5SyeIF0WAFZNGTYHwMRkz3V0rZ/RNDjEkU3lTb6eALS+gInV - M0bMcTmLZ0iLD0CGs2+3zw/IpvJmBo7gtX72rJ/g+deqZwQT748HabMAJKgAsE5+I2NDY1mt7x5Pkj9m - tNZ3ML/t8/7QsA4BpPomg7RbAABw9u1vlwGoJqAlCTYkMexHWt8TDS5wZC/wuJg7FRriP18HoLx6hj93 - /N1AuwgAAOh/+1tlANZmnHc8G97zEUccre8jjpSFl9v5g8HRb9GMy2o8zJoytMcRAACwcsoFNaAY42pQ - kIzj9TgRNPMHdKzIXuBJPiZNzD+mvZgfaEcBAAArH7igCsBNSR8MmjkzJOZuS4Nb8KL1AwrvpYyjHU1+ - H1N5E+G4adGMy6o8rN43aLcjgAz9//hWFYBrbf8YtNYPau5048mQsz7DkwKOJGMyqCpvqjQ8s2jGZRUe - ZvYVMkIAADZCIHvW9xVPNpUXmWDuix8zgvmBDBIAgCQEMoT5s1rfyfNZre+ShoxhfiDDBAAA9L8twXFA - XnhW6zufPxAcnVTr+0yDBUdGMT+QgQIAAPrf9lYN5NuD1kVntb6zuT2sy9nzWa3vgYZ1i2ZcVuYBQ6DQ - rlGAeLBy6gVlAJ6xvp+9wOOCBpfrcva84eHPXuCJP8YGxzOZyPxAhloAAuTjQNou8LiFzqL1k47Jan1P - NGSg2W9abyYLAIAJAQLqyjGYSefwdODIXuCBW6YMngYGGc38QAcQAAAw4NY3q+DAMQig82h9R2NooBq5 - Q2h9t/OnC0cHYH4gQ30AVlgx7cIKJMsYzKBMOxlPEFlwBg3e1uUMR8dO5U3HWT+GBmPMTR2B+YEOYgEI - GHDrmxUAno75Q4acw1PG4XBM9gKPy/nThYONGbPo8fZN73UDHUoAAMCAW98sA1ANoCSTmNKEp0Oe9dkD - Hfnabsz86aDBeL4OQPmix9vvYo8X6BBHABlWTLuwBkA5qLmoSCLIXuBJ9rwR3ksZRzsxfxov8NiNWYcO - yPxAB7QABAy45c2uAKqQoP1YNqnHyfMdwOTPXK0PAK8BqFj0+GV7PGBsd+iwAkDAgFvenABgfAxh2bP+ - /hneS28IceKixy+b4AFjxkCHFwAAMOCWN0eBWQMlWa3v5Pms1vdEg/F8HZjWn+MBY0ZBpxAAADDgljdL - CegcAH07jdb3gmd/1Po+05AExzoAoxY9flmtB8oyDjqNABAw8OY3pgMY63hAVuv7hqeTa30AeGjR45dV - esCYsdDpBAAADLz5jXIAcwCUJHwwm8rrGx2dXOvXgWn9ag+UZTR0uDCgE1j+54uqAZSCeWhjwWV4Tx/j - Bhxn8wUd3svwWvzeMu3SyfyvASjtjMwPdFILQIaBN79RCWAChDWQ1fq+0dHRtX5cPIbWn7Do8cume6Cs - w0CnFwAAMPDmN7qCJs4ZkKHThfcy7azvdv504TDGdOjYvhvYLwSAgIF/eGMUgOkAjo73TDaVF84Zp/Np - /c0AKjtDeM8pdEofQDxY/peL5gAoA/CQ9W/ZVN7EYzp43z17GsxjHgJQtj8xP7CfWQAyDPzDG2Vg1sCQ - jq71GZ4UcCQZ04kv8ADAYjCtX+MBY4eH/VYACDjnDwtGgSY+FtjCfnDWD5qGdj7r73fmvh3s9wJAwDk3 - LZgAoBI+5A50Oq0fAA3tfG13ekfP4fcLsgJAgnNuWtAVTAhUwk4QZKLW94Jn/9T6ddzSm74/ePedQlYA - 2ECMIMhqfV9oaCfmzzJ+AsgKgARwzk0LuoImsAg4ZJN64PUcHiSOOgDTQbOMnwiyAsABnFOpWwQVkJyF - 2Vr8yZ9vB62/GUBVlvGdQVYAuIRzKhdUAKgkoH1Nf8hq/fhzB0SDtfUWgOmLZnScgpyZAFkB4BEGVc4v - B1AB6rBfgQxZre8nDc8AqFo0o3Ne1gkasgIgRRg0dn5XsKNBBRI0NAWQnlTeTq71OZ51YBWgqhbNyJr5 - qUBWAPgIg8bOLwMTBKNgTSzq6Frf7fz+49jMKz5VLZqxf2btBQFZARAQDBqrHxFGIWFyUfYCT4Ln68AK - u1RVzxhR7WHlWUgCWQGQBhh04/xRAMphsgyyWj/OmM1gTF9dPWPEHA+zZcEFZAVAmmHQjfNLAToKQDmR - 6xN0IK0fM3/qeF4D6/Y0p3rGiFoPs2TBI2QFQDvD4BvnlYOiHOyacjmS3UUAMs/R5w5HHYBqUNSAaflq - DyvMgk+QFQAZBoNvmFcGJgzEa4jpgY4X3lsMoEa8qh8bUePrhmUhJcgKgA4Ag2+YVwpqEgpdYREMGaD1 - FwPYAzOz17bXnmXBGWQFQAeGwdfP60pAy/iv5QAAilKwisgChiScJLnWXyz9Wguglo+p5u/VVD82Yk97 - 70UWvMH/A4+8Hty3bsIRAAAAAElFTkSuQmCCKAAAACAAAABAAAAAAQAgAAAAAAAAEAAAEgsAABILAAAA - AAAAAAAAAP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALVuPGC1 - bTygtG090LRsPf+0az3/s2s+/7NqPv+zaT7/smk/0LJoP6CyZz9g////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8At3E6ELZwO4C2 - cDvwtm87/7VuPP+1bjz/tW08/7RsPf+0bD3/tGs9/7NqPv+zaj7/s2k+/7JoP/+yaD/wsmc/gLFmQBD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALdzOlC3 - cjrwt3E6/7ZxO/+2cDv/tm87/7VvPP+1bjz/tW08/7RtPf+0bD3/tGs9/7NrPv+zaj7/s2k+/7JpP/+y - aD//smc/8LFnQFD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wC4 - dDmQuHQ5/7hzOf+3cjr/t3I6/7dxOv+2cDv/tnA7/7ZvO/+1bjz/tW48/7VtPP+0bD3/tGw9/7RrPf+z - aj7/s2o+/7NpPv+yaD//smg//7JnP5D///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8AuXY4oLl1OP+4dTn/uHQ5/7hzOf+3czr/t3I6/7dxOv+2cTv/tnA7/7ZvO/+1bzz/tW48/7VtPP+0 - bT3/tGw9/7RrPf+zaz7/s2o+/7NpPv+yaT//smg//7JnP6D///8A////AP///wD///8A////AP///wD/ - //8A////ALp4N5C6dzf/uXY4/7l2OP+5dTj/uHQ5/7hzOf+4czn/t3I6/7dyOv+3cTr/tnA7/7ZwO/+2 - bzv/tW48/7VuPP+1bTz/tGw9/7RsPf+0az3/s2o+/7NqPv+yaT//smg//7JoP5D///8A////AP///wD/ - //8A////AP///wC7eTZQung3/7p4N/+6dzf/uXc4/7l2OP+5dTj/uHU5/7h0Of+4czn/t3I6/7dyOv+3 - cTr/tnE7/7ZwO/+2bzv/tW88/7VuPP+1bTz/tG09/7RsPf+0az3/s2o+/7NqPv+zaT7/smk//7JoP1D/ - //8A////AP///wD///8AvHs1ELt6NvC7eTb/unk2/7p4N/+0czX/unc3/7l2OP+5djj/uXU4/7h0Of+3 - cjn/tXE4/69tN/+jZTP/omQz/7NuOv+jZDX/o2Q1/7BrOv+1bjz/tG08/7RsPf+0bD3/tGs9/7JqPv+z - aT7/rGY98LJoPxD///8A////AP///wC8fDWAu3s1/7p5NP+3eDX/snMz/6ZrMf+rbzP/uXY3/7l2OP+5 - djj/t3Q3/7FwN/+maTT/mmAw/5pgMf+ZXzD/mV4w/51hM/+iZDT/sWw5/7VvPP+1bjz/tW08/7NtPf+x - ajz/sGk8/6RhOP+gXzj/s2k+gP///wD///8A////AL19NPC6ezT/tHYz/8GZcP+cZS3/omkv/6ZsMP+r - bjL/uHY2/7l3N/+0czb/uYxl////////////j1ks////////////sIVk/6BjMv+wbTn/tm87/7ZvO/+0 - bTz/r2o6/6dlOP+3jnP/mlw0/55eN/+yaj7w////AP///wC+fjNgvH40/7l6M/+veTz//////+PXyv+e - bTj/oGgu/6VrMP+obTL/tXY2/7NyNf+1i2P///////////+JVir///////////+thGL/qWk2/7RwOf+2 - cDv/tG86/7BrOf+jYzb/07+x//////+tgWX/nl41/7NqPf+zaj5g////AL5/M6C9fzP/t3oy/8iqiP// - /////////+ng1/+idkX/n2cu/6VrMP+obTH/rW8z/8Gdff///////////4xZKv///////////66FYv+g - ZDH/rWs3/7VwOf+vbDf/o2tA/9rJvf///////////9XAsv+pZjj/s2s9/7RrPaD///8AvoAz0L1/M/+6 - fTL/sXw8/9zNvP////////////j18v+nflH/nmcu/6NrMP+haC//qXhI/6uGYP+pg1//kFws/6qDYP+o - gl//onJI/6RnM/+vbjb/rmw3/6FrPv/p39j////////////p39j/sH5a/69rOv+0bDz/tGw90P///wC/ - gTL/v4Ey/71/M/+4ezH/rnQv/8Knhv////////////j18v+0kWz/nGYs/51mLf/Jr5X///////////+R - XCv////////////Kr5b/pGgy/6xtNf+qfFb/8Orl////////////y7Wj/6RlNf+vazn/s247/7VuPP+1 - bTz/////AL+CMv+/gjL/v4Ey/71/M/+4fDH/rHMu/8avk/////////////////+5mnn/lWIq/7qbev/i - 1sr/4tbK/5VgLP/Ir5X/x62V/7OJYv+najP/p3tU//j18v///////////9vLvf+maDX/sW04/7VvO/+2 - cDv/tm87/7VuPP////8AwIMx/8CCMf+/gjL/v4Ey/7x/Mf+xdzD/1sWv/7Waef/n39b////////////T - wq//jlwo/+PXyv/Ww7D/tZBp/+XYy//LsJb/omcw/7eYe/////////////j18v+1j3D/qmo1/7FvOP+2 - cTr/t3E6/7ZxO/+2cDv/tm87/////wDAhDD/wIMx/8CDMf+/gjL/vX8x/7J4L//k2Mr//////8m2n//p - 4tn////////////azLz/nXFC/5hjK/+cZS3/m2Us/6NvOv/ErJT/////////////////oW08/65uNf+0 - cTj/t3Q5/7hzOf+3cjr/t3I6/7dxOv+2cDv/////AMGFMP/BhDD/wIQx/8CDMf++gDD/s3ov/9C7of/h - 1sn/4dbJ/6uLZP/i1sn////////////p4Nf/mWo2/5plK/+VYSr/08Kv////////////8Orl/7SKYv+v - cDX/tnQ3/7h1OP+4dTn/uHQ5/7hzOf+3czr/t3I6/7dxOv////8AwYYw0MGFMP/BhTD/wIQx/76BMP+z - ei7/0Lyh/+HWyP/i18n/poBQ/5ZlKP/Cpob////////////49fL/p4Rd/+HWyf///////////9rMvP+q - dDz/sXI0/7d2Nv+5dzf/uXY4/7l2OP+5dTj/uHQ5/7hzOf+4czn/t3I60P///wDChy+gwoYv/8GGMP/B - hTD/v4Iv/7J7Lv/j2Mn///////////+wjF7/qHAs/6x0Lv/EqIb////////////////////////////j - 1sr/qm4w/7R1NP+5eDX/uXg3/7p4N/+6dzf/uXc4/7l2OP+5dTj/uHU5/7h0Of+4czmg////AMKIL2DC - hy//wocv/8GGMP+/gy//tHwt//j28v///////////8aqhv+udS7/uX0w/7F3L/+6l23/+PXy//////// - ////z7mh/6txMP+1dzP/u3o1/7t6Nv+7eTb/u3k2/7p4N/+6dzf/unc3/7l2OP+5djj/uXU4/7h0OWD/ - //8A////AMOILvDCiC//wocv/8CFL/+5gC7/u5Vf/7OPXP+wjVz/qn5E/6dxKv+rdCz/t3ww/7V5L/+x - gUf/3s+8/7yYbv+vdTD/t3ky/7t7Nf+8fDX/vHs1/7x6Nf+7ejb/u3k2/7p4N/+6eDf/unc3/7l2OP+5 - djjw////AP///wD///8Aw4kugMOJLv/CiC//wYYv/7yDLv+2h0X/3s+7/7ufdv+aaij/o28q/6p0K/+v - dy7/t3sv/7l9MP+1ejH/t3ox/7p9Mv+8fjT/vX00/719NP+8fDX/vHs1/7x7Nf+7ejb/u3k2/7t5Nv+6 - eDf/unc3/7p3N4D///8A////AP///wDEii0Qw4ou8MOJLv/Chy7/vYQu/8KfbP/49vL///////j28v+t - hVD/pnIr/6t1K/+1ei//voEy/76AMv+9fzP/vX8z/75/M/++fjP/vX40/719NP+8fDX/vHw1/7x7Nf+7 - ejb/u3o2/7t5Nv+6eDfwung3EP///wD///8A////AAAAAADEiy1Qw4ou/8OJLv/Ahy3/tX8s/97Qu/// - /////////+TZyf+9m2v/sXkt/7+CMf/AgzH/v4Iy/7+BMv+/gTL/voAz/75/M/++fzP/vX40/719NP+9 - fTT/vHw1/7x7Nf+8ezX/u3o2/7t5NlAAAAAA////AP///wD///8AAAAAAAAAAADEiy2QxIot/8KJLv+9 - hS3/t4Q3/8iuhf/LtJL/8u3k/9G4lP+7gS7/v4Mw/8CEMf/AgzH/wIIx/7+CMv+/gTL/voAz/76AM/++ - fzP/vX40/71+NP+9fTT/vHw1/7x8Nf+8ezWQ////AAAAAAD///8A////AP///wAAAAAAAAAAAAAAAADE - iy2gxIst/8GJLv++hi3/uoMs/7eALP+3fyz/uoEt/76EL//BhTD/wYQw/8CEMf/AgzH/wIMx/7+CMv+/ - gTL/v4Ey/76AM/++fzP/vn8z/71+NP+9fTT/vX00oAAAAAAAAAAAAAAAAP///wD///8A////AAAAAAAA - AAAAAAAAAAAAAADEiy2QxIst/8OJLf/BiS7/wYct/8GGLf/Ahy//wYYv/8KGL//BhTD/wYUw/8CEMP/A - gzH/wIMx/7+CMv+/gjL/v4Ey/76AM/++gDP/vn8z/71+NJAAAAAAAAAAAAAAAAAAAAAA////AP///wD/ - //8A////AP///wD///8A////AP///wDEiy1QxIst8MSKLf/Dii7/w4ku/8OJLv/CiC//wocv/8KHL//B - hjD/wYUw/8GEMP/AhDH/wIMx/8CCMf+/gjL/v4Ey/7+BMvC+gDNQ////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wDEiy0QxIstgMSLLfDEii3/w4ku/8OJLv/D - iC7/wogv/8KHL//BhjD/wYUw/8GFMP/AhDD/wIMx/8CDMfC/gjKAv4EyEP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AMSLLWDE - ii2gw4ou0MOJLv/DiC7/wogv/8KHL//Chi//wYYw0MGFMKDBhDBg////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD//////8AH//4AAP/8AAB/+AAAP/AAAB/gAAAPwAAAB4AAAAOA - AAADgAAAAwAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABAAAAAQAAAAEAAAABgAAAA4AAAAOA - AAADwAAAB+AAAA/wAAAf+AAAP/wAAH/+AAD//8AH/ygAAAAwAAAAYAAAAAEAIAAAAAAAACQAABILAAAS - CwAAAAAAAAAAAAD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////ALVtPEC0bT2AtGw9wLRsPeC0az3/tGs9/7NqPv+zaj7/s2o+/7NpPv+y - aT/gsmg/wLJoP4CyZz9A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////ALZvOzC1bzygtW488LVuPP+1bTz/tG08/7RsPf+0bD3/tGw9/7RrPf+z - az7/s2o+/7NqPv+zaT7/s2k+/7JoP/+yaD//smg/8LJnP6CxZ0Aw////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wC2cDtAtnA7wLZwO/+2bzv/tW88/7VuPP+1bjz/tW08/7VtPP+0 - bT3/tGw9/7RsPf+0az3/tGs9/7NqPv+zaj7/s2o+/7NpPv+yaT//smg//7JoP/+yZz//smc/wLFnQED/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8At3I6ILdxOrC3cTr/tnE7/7ZwO/+2cDv/tm87/7ZvO/+1 - bzz/tW48/7VuPP+1bTz/tG08/7RsPf+0bD3/tGw9/7RrPf+zaz7/s2o+/7NqPv+zaT7/smk//7JoP/+y - aD//smg//7JnP/+xZ0CwsWZAIP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wC4czlQt3I68LdyOv+3cjr/t3E6/7dxOv+2 - cDv/tnA7/7ZwO/+2bzv/tW88/7VuPP+1bjz/tW08/7VtPP+0bT3/tGw9/7RsPf+0az3/s2s+/7NqPv+z - aj7/s2o+/7NpPv+yaT//smg//7JoP/+yZz//smc/8LFnQFD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////ALh0OZC4dDn/uHM5/7hzOf+3 - cjr/t3I6/7dxOv+3cTr/tnE7/7ZwO/+2cDv/tm87/7ZvO/+1bjz/tW48/7VuPP+1bTz/tG08/7RsPf+0 - bD3/tGw9/7RrPf+zaz7/s2o+/7NqPv+zaT7/smk//7JoP/+yaD//smg//7JnP/+xZ0CQ////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8AuXU4oLl1OP+4 - dDn/uHQ5/7hzOf+4czn/t3I6/7dyOv+3cjr/t3E6/7dxOv+2cDv/tnA7/7ZwO/+2bzv/tW88/7VuPP+1 - bjz/tW08/7VtPP+0bT3/tGw9/7RsPf+0az3/s2s+/7NqPv+zaj7/s2k+/7NpPv+yaT//smg//7JoP/+y - Zz//smc/oP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wC5 - djiguXY4/7l1OP+5dTj/uHQ5/7h0Of+4czn/uHM5/7hzOf+3cjr/t3I6/7dxOv+3cTr/tnE7/7ZwO/+2 - cDv/tm87/7ZvO/+1bjz/tW48/7VuPP+1bTz/tG09/7RsPf+0bD3/tGs9/7RrPf+zaz7/s2o+/7NqPv+z - aT7/smk//7JoP/+yaD//smg//7JnP6D///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////ALp3N5C6dzf/uXY4/7l2OP+5djj/uXU4/7l1OP+4dDn/uHQ5/7hzOf+4czn/t3I6/7dyOv+3 - cjr/t3E6/7dxOv+2cDv/tnA7/7ZvO/+2bzv/tW88/7VuPP+1bjz/tW08/7VtPP+0bT3/tGw9/7RsPf+0 - az3/s2s+/7NqPv+zaj7/s2k+/7NpPv+yaT//smg//7JoP/+yZz+Q////AP///wD///8A////AP///wD/ - //8A////AP///wD///8Aung3ULp4N/+6dzf/unc3/7l3OP+5djj/uXY4/7l1OP+5dTj/uHQ5/7h0Of+4 - czn/uHM5/7dzOv+3cjr/t3I6/7dxOv+3cTr/tnE7/7ZwO/+2cDv/tm87/7ZvO/+1bjz/tW48/7VuPP+1 - bTz/tG09/7RsPf+0bD3/tGs9/7RrPf+zaj7/s2o+/7NqPv+zaT7/smk//7JoP/+yaD//smg/UP///wD/ - //8A////AP///wD///8A////AP///wC7eTYgu3k28Lp4N/+6eDf/ung3/7p3N/+6dzf/uXY4/7l2OP+5 - dTj/uXU4/7h1Of+4dDn/uHQ5/7hzOf+4czn/t3I6/7dyOv+3cjr/t3E6/7dxOv+2cDv/tnA7/7ZvO/+2 - bzv/tW88/7VuPP+1bjz/tW08/7RtPP+0bD3/tGw9/7RsPf+0az3/s2s+/7NqPv+zaj7/s2k+/7NpPv+y - aT//smg/8LJoPyD///8A////AP///wAAAAAAAAAAAAAAAAC7ejawunk2/7p5Nv+6eTb/uXg3/7l4N/+6 - dzf/unc3/7l3OP+5djj/uXY4/7l1OP+5dTj/uHQ5/7h0Of+4czn/t3M5/7ZzOv+2cjr/tnI6/7ZxOv+2 - cTr/tXA7/7VwO/+1cDv/tm87/7ZvO/+1bjz/tW48/7VuPP+1bTz/tG09/7RsPf+0bD3/tGs9/7NrPf+y - aj7/smo+/7JqPv+yaT7/sWk//7JoP7AAAAAAAAAAAP///wAAAAAAAAAAALx7NUC7ezX/unk2/7l5Nf+5 - eDX/uXg1/7l3N/+5eDf/uXg3/7p3N/+6dzf/uXY4/7l2OP+5dTj/uXU4/7h1Of+3dDn/t3M5/7dyOf+2 - cjj/tXE5/7VxOf+1cTn/tXA5/7VwOf+1bzv/tXA7/7VvO/+2bzv/tW88/7VuPP+1bjz/tW08/7RtPP+0 - bD3/s2w9/7NrPf+yajz/sWo9/7FpPf+yaT7/smk+/7JpPv+yaT9AAAAAAP///wAAAAAAAAAAALt8NcC6 - ejT/uXk0/7d3Nf+2dzX/t3Y1/7d3Nf+5eDX/uXc3/7l4N/+6dzf/unc3/7l2OP+5djj/uXY4/7h1OP+4 - dDj/tXI4/7RyOP+zcDf/s3A3/7FvOP+xbjj/sm84/7NuOf+zbzn/tG86/7VvO/+1cDv/tm87/7VvPP+1 - bjz/tW48/7RtPP+0bTz/smw8/7FqPP+waTz/r2g7/7BoPP+vaD3/sWk9/7JpPv+yaT7AAAAAAP///wAA - AAAAuXszMbt7Nf+4ejT/tncz/7N1M/+tcDH/omkv/6FqLv+nbDH/t3Y1/7l3N/+5eDf/ung3/7p3N/+6 - dzf/uHY4/7h1OP+1czf/s3E2/69vNv+pazT/m2Iw/5thMP+bYTD/pGY0/5xhMf+eYTL/oGMz/6ZmNP+1 - bzv/tnA7/7ZvO/+2bzv/tG88/7RtPP+zbTv/sWo7/65pOv+sZzr/q2Y6/59fNf+cXTX/nl83/7FpPf+y - aj7/s2k+MP///wAAAAAAvH00oLp7M/+2eDP/snUy/8Waa//gzLb/oXA6/5tmLP+faC3/pWsv/7N1NP+6 - eDb/uXg3/7p4N/+5dzf/uXY3/7Z0N/+ycjb/r3A1/6ZpMv+WXy3/lF0u/5NdLf+TXC3/lFwu/5VeL/+Z - XzH/nWIy/6VmNP+1cDn/tXA7/7ZwO/+1cDv/tW47/7JtO/+wazr/rGk5/6lmOP+lYzf/w56F/9Cyn/+Y - WzT/nFw1/7BpPP+yaT7/smo+oP///wAAAAABvH008Ll8M/+1eDL/r3Qw/+DNt///////7uXb/55tOP+a - ZCz/nmgt/6NrL/+ydDT/uXg1/7l4N/+5eDf/uHY2/7Z0Nv+wcTX/1rme/////////////////7CKaP// - ///////////////p29D/mV8x/6NlM/+0bzn/tnA6/7VxO/+1bzv/s246/7BsOf+taTj/p2Y3/6BhNf/U - vKr///////r29P+UWTL/mVw0/69pO/+yajz/sms+8P///wC8fjJBvX4z/7l7M/+zeDH/sn0+//////// - //////////Tu5/+rgVX/mGMr/51nLf+hai7/sHEz/7l4Nf+6eDb/t3Y2/7R0Nf+ubzP/1Lic//////// - /////////6qGZf/////////////////o2s//l14v/6FkM/+zbzn/tXE5/7VwOf+zbzn/sGw5/6xqOP+m - Zjb/pGtC/9nFtv/////////////////AnIT/mV00/65pO/+xajz/s2o9/7NrPkC9fzOAvX4z/7p8Mv+z - eDH/07WS///////////////////////59/T/sYxj/5hjK/+dZi3/oWku/6xxMv+3dzX/t3c1/7NzNf+s - bzP/0rac/////////////////6aEZP/////////////////n2s//lF0u/6FkMv+zcDf/s3A5/7NvOf+x - bTj/rGo3/6ZnNv+hakH/7ePb///////////////////////i0cT/qWU4/65qOv+xajz/s2s9/7RsPYC9 - fzPAvX8z/7p9Mv+2ejH/r3Uv/9W7nv///////////////////////////7+gf/+WYiv/nGYs/59oLv+q - bjH/tXU0/7JzM/+rbjL/0rab/////////////////6WDY//////////////////n2s//lV0u/51jMf+x - bjf/sm83/69tN/+rajb/pWc0/66AW//t49v//////////////////////+ja0P+scET/q2g5/7BqOv+z - bDv/s209/7RsPcC/gTLgvn8y/7t+Mv+4fDH/s3gw/6xzLv+8lWj/9O7n///////////////////////N - tJr/lmEr/5tlLP+faC3/pGsw/69yM/+qbjH/0reb/////////////////6aEZP/////////////////m - 2s7/lF4t/5lgL/+pazT/rm02/6trNf+lZzP/q35a//n29P//////////////////////x6mR/6NkNf+q - aDf/rmo6/7FsO/+0bTz/tG08/7RtPeC/gTL/voAy/76AMv+6fTL/t3sx/7J3MP+rcy7/q3g7/+LTwv// - ////////////////////2si0/5RgKv+aZCz/nGYs/59nLv+kbDD/wZt2/821nP/Kspv/yLGb/5ZuR//A - po3/ybGb/8qynP/h0cP/kl0s/5dgLf+maTL/qGo0/6RnM/+/noL//////////////////////+zj2/+t - f1v/o2U1/6loN/+uajj/smw6/7NuO/+0bjz/tW48/7VtPP+/gjL/v4Iy/76AMv++gDL/u30x/7d7Mf+x - dzD/qnIu/6+BSv/z7uf//////////////////////+3k2/+abDf/lmIr/5hjK/+VYiv/6dzP//Pt5//5 - 9vP//////6eDYf/l2c7//////+zj2///////sIll/5pjLv+kaDH/oWUx/8Wojv////////////////// - /////////72XeP+lZjT/q2k2/65sOP+ybTr/tG86/7VvO/+2bzv/tW48/7VuPP/AgzH/wIIx/7+CMv++ - gDL/vX8x/7t+Mf+1ejH/rnUv/6ZvLf/Conz/3tC////////////////////////t5Nv/qIBU/5JfKf+S - Xyn/59vO/+3k2//FqY3//////6qGYv/l2c7/8+3n/7qcf///////sIll/5piL/+cZC//2ca1//////// - ///////////////t5Nv/tIRd/6ZnNf+sazb/sG44/7NvOf+1cDn/tXA7/7ZwO/+2bzv/tm87/7VvPP/A - gzH/wIMx/8CCMf+/gjL/voEy/7x/Mf+4fDD/sXcw/6hxLf//////0Lyl/6+Rbf/59vP///////////// - ////+fbz/6V+U/+NXSj/vZ59/8uzmf+uiWP/wqiL/5txRv+/oID/pHxU/8qymv/Kspr/nHBG/5hnOf/f - 0MH//////////////////////9O8qf+iZjL/qGk0/61sNv+xbzj/s3A5/7ZxOv+2cTr/t3E6/7ZwO/+2 - cDv/tnA7/7ZvO//AhDH/wIMx/8CDMf/AgzH/v4Ex/72AMf+6fTH/s3kv/6pzLf///////////+vj2v+U - bT//5NnM//////////////////////+6nX3/iVon/8qzmf/t5Nv//////7ONZf/07uj/8+3n/+bazv+i - e1X/k2Q4/+zj2//////////////////59vT/sIll/6RoMv+qbDX/rm42/7JwN/+1cTj/t3I5/7ZyOv+3 - cjr/t3I6/7dxOv+3cTr/tnA7/7ZwO//BhTD/wIQw/8CEMf/AgzH/wIMx/76BMf+6fjH/tHov/6tzLf// - ///////////////59vP/s5Zz////////////////////////////ybGY//n28//59/P/vp9+/5NgKv+x - hln/6NzP//n29P/f0cP/+fbz///////////////////////byLb/pWky/6ttNP+wbzX/tHI2/7VyOP+3 - czn/t3M5/7hzOf+3czr/t3I6/7dyOv+3cTr/t3E6/7ZwO//BhTDgwYUw/8GEMP/AhDH/wIMx/76CMf+7 - fzD/tHov/6t0Lf//////////////////////yrOX/6N+Uf/t5Nr//////////////////////9fGs/+J - Wif/jV0o/45dKP+RXyn/mGMr/6uIYv/59vP//////////////////////8qtkf+majH/rW8z/7FxNv+1 - czf/t3Q3/7h1OP+3dTn/uHQ5/7h0Of+4czn/uHM5/7dyOv+3cjr/t3E6/7dxOuDBhjDAwYUw/8GFMP/B - hDD/wIQx/76DMf+7fzD/tXsu/6x0LP+7lWX/rItf/6qJX/+riV//nXVD/5BhJ/+ecDf/18Kp//////// - ///////////////s5Nr/kmY1/4tbKP+LWyf/upx9///////////////////////t5Nv/tIhb/6hsMv+u - cDP/snI1/7Z0Nv+3dTf/uHY4/7l2OP+5dTj/uXU4/7h0Of+4dDn/uHM5/7hzOf+3czr/t3I6/7dyOsDC - hy+AwYYw/8GGMP/BhTD/wYUw/7+CL/+7gDD/tXsu/6t1LP//////////////////////zbaY/5VkJ/+e - air/pW8s/7qVZv/59/P/////////////////7OPa/6J8Uv/IsZj//////////////////////9O+qP+q - dT7/qW4x/69xM/+zczX/tnY2/7h2Nv+5dzf/uXc4/7l2OP+5djj/uXU4/7l1OP+4dTn/uHQ5/7h0Of+4 - czn/uHM5/7dyOoDChy9Awocv/8KGL//BhjD/wIQw/7+DL/+7gC7/tHwu/6p1K/////////////////// - ////zreY/5hmKP+eain/rHQu/6hxLf/Cn3T///////////////////////////////////////////// - ////59vO/6RrL/+rbzH/sXMz/7R1NP+3dzX/ung2/7l4N/+6eDf/unc3/7p3N/+5djj/uXY4/7l2OP+5 - dTj/uXU4/7h0Of+4dDn/uHM5/7hzOUAAAAAAwocv8MKHL//Chi//wIUw/7+EL/+7gS7/tHwt/6t1K/// - ////////////////////zreY/5tqKP+ocSv/sngv/7B2Lv+qci7/zrKP//r39P////////////////// - ///////////////o3M7/rXg+/61xMf+ydDL/tnYz/7h4Nf+6eTb/unk2/7t5Nv+6eDf/ung3/7p3N/+6 - dzf/uXc4/7l2OP+5djj/uXU4/7l1OP+4dTn/uHQ58P///wAAAAAAwogvoMKIL//Chy//wYYv/8CELv+8 - gi//tX0t/82vgv//////////////////////9O7n/55rKP+tdS7/tXov/7V6L/+yeC7/q3Mu/7iNWf/u - 5tv//////////////////////9G5nP+oby7/rXIx/7J2Mv+3eDT/uXk0/7t6Nf+6ejb/u3o2/7t5Nv+7 - eTb/ung3/7p4N/+6eDf/unc3/7p3N/+5djj/uXY4/7l2OP+5dTj/uXU4oP///wAAAAAAw4kuMMOILv/C - iC//wYYv/8CGL/+9gy7/tn8t/8WdZv/UvZv/1cKm/9jIsv/KtZf/zbeY/5hpJ/+fbSn/p3Es/7d8MP+3 - ezD/s3kv/691Lv+uezz/3822///////07uf/t41a/6pyL/+wdDD/tHcy/7h5M/+5ejT/u3s1/7t7Nf+8 - ezX/vHo1/7t6Nv+7ejb/u3k2/7t5Nv+6eDf/ung3/7p3N/+6dzf/uXc4/7l2OP+5djj/uXU4MP///wAA - AAAAAAAAAMOJLsDDiC7/wogv/8CGL/++hC7/uYEt/7F6K//FpHP////////////LtZb/sI5f/5ZoJv+c - ayj/o3Aq/611LP+5fjD/uHww/7V6L/+yeC7/rnUv/8mkdv+zfj3/r3Uv/7N3MP+2eTL/uXsz/7t8M/+8 - fTT/vHw1/7x8Nf+8ezX/vHs1/7x7Nf+7ejb/u3o2/7t5Nv+7eTb/ung3/7p4N/+6eDf/unc3/7p3N/+5 - djjAAAAAAP///wAAAAAAAAAAAMOJLkDDiS7/w4ku/8KHLv+/hi7/u4It/7R9LP/IpnP///////////// - /////////+7m2v+gczb/n20p/6RwKv+sdSz/tnsw/7p+Mf+4fDD/tnsw/7V5L/+0eTD/t3sx/7l8Mv+6 - fTL/u30z/7x+NP+9fTT/vX00/718NP+8fDX/vHw1/7x7Nf+8ezX/u3o2/7t6Nv+7ejb/u3k2/7t5Nv+6 - eDf/ung3/7p3N/+6dzdAAAAAAP///wAAAAAAAAAAAAAAAADDii6ww4ku/8KILv/Bhi3/vYQu/7eALP+w - eiv/1r6b///////////////////////dzLP/nGwo/6JwKf+mcir/tn0u/72AMP+8gDH/u34x/7t+Mf+7 - fjH/un0y/7x+Mv+9fjP/vX8z/75+M/+9fjT/vX40/719NP+9fTT/vHw1/7x8Nf+8ezX/vHs1/7x7Nf+7 - ejb/u3o2/7t5Nv+7eTb/ung3/7p4N7AAAAAAAAAAAP///wAAAAAAAAAAAAAAAADEii0gw4ou8MOJLv/B - iC7/v4Ut/7qDLf+0fiz/vpVX//r38///////////////////////69/O/72YY/+yey3/vIAw/76CMf++ - gTH/voEy/76BMv++gDL/voAy/71/M/++gDP/vn8z/75/M/++fzP/vX40/71+NP+9fTT/vX00/718NP+8 - fDX/vHw1/7x7Nf+8ezX/u3o2/7t6Nv+7eTb/u3k28Lt5NiAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAA - AAAAxIotUMOKLv/CiS7/wYct/76GLf+6giz/tH4r/7mMSf/hz7X/69/O/9a+nP///////////86pdv+6 - fy7/vYIw/76CMf+/gjH/v4Ix/7+BMf+/gjL/v4Ey/7+BMv+/gTL/voAz/76AM/++fzP/vn8z/75+M/+9 - fjT/vX40/719NP+9fTT/vHw1/7x8Nf+8ezX/vHs1/7x7Nf+7ejb/u3o2UAAAAAAAAAAAAAAAAP///wAA - AAAAAAAAAAAAAAAAAAAA////AMSLLZDEii3/woku/8GHLf++hi3/u4Is/7Z/LP+zfSv/sXsr/7F7K//C - l1n/vpBL/7h/Lv+8gS//v4Mv/7+DMP/AhDH/wIMx/8CDMf/AgjH/v4Iy/7+CMv+/gTL/v4Ey/76AM/++ - gDP/vn8z/75/M/++fzP/vX40/71+NP+9fTT/vX00/718NP+8fDX/vHw1/7x7Nf+8ezWQAAAAAAAAAAAA - AAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEiy2gxIot/8KJLv/Bhy3/v4Yt/72FLf+7 - giz/uIEt/7eALP+4gC3/uoIt/7yCL/++hC//v4Qw/8CEMP/BhDD/wIQx/8CDMf/AgzH/wIMx/7+CMv+/ - gjL/v4Ey/7+BMv+/gTL/voAz/76AM/++fzP/vn8z/75+M/+9fjT/vX40/719NP+9fTT/vHw1/7x8NaAA - AAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAxIstoMSLLf/D - iS3/wYku/8CHLf+/hi3/voUt/72FLv+9hS7/voQu/7+FLv/AhS//wIUw/8GFMP/BhTD/wYQw/8CEMf/A - hDH/wIMx/8CDMf/AgjH/v4Iy/7+CMv+/gTL/v4Ey/76AM/++gDP/vn8z/75/M/++fzP/vX40/71+NP+9 - fTT/vX00oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAMSLLZDEiy3/w4kt/8KJLv/BiC7/wYgu/8GHLf/Bhi3/wIcv/8CGL//Bhi//woYv/8GGMP/B - hjD/wYUw/8GFMP/BhDD/wIQx/8CDMf/AgzH/wIMx/7+CMv+/gjL/v4Ey/7+BMv+/gDL/voAz/76AM/++ - fzP/vn8z/75+M/+9fjSQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAADEiy1QxIst8MSKLf/Dii7/woku/8KILv/CiC7/wocu/8KIL//C - iC//wocv/8KHL//Chi//wYYw/8GFMP/BhTD/wYQw/8CEMf/AhDH/wIMx/8CDMf/AgjH/v4Iy/7+CMv+/ - gTL/v4Ey/76AM/++gDP/vn8z8L5/M1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8AxIstIMSLLbDEiy3/xIot/8OKLv/D - iS7/w4ku/8OJLv/DiC7/wogv/8KHL//Chy//woYv/8GGMP/BhTD/wYUw/8GFMP/BhDD/wIQx/8CDMf/A - gzH/wIMx/7+CMv+/gjL/v4Ey/7+BMv+/gDKwvoAzIP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wDE - iy1AxIstwMSKLf/Dii7/w4ou/8OJLv/DiS7/w4gu/8KIL//CiC//wocv/8KHL//Chi//wYYw/8GFMP/B - hTD/wYQw/8CEMf/AhDH/wIMx/8CDMf/AgjH/v4IywL+BMkD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AMSLLTDEiy2gxIot8MOKLv/DiS7/w4ku/8OJLv/DiC7/wogv/8KHL//C - hy//woYv/8GGMP/BhTD/wYUw/8GFMP/AhDD/wIQx8MCDMaDAgzEw////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AMSKLUDDii6Aw4ouwMOJLuDD - iS7/w4gu/8KIL//Chy//wocv/8KHL//BhjDgwYYwwMGFMIDBhTBA////AP///wD///8A////AP///wD/ - //8A////AP///wD///8A////AP///wD///8A////AP///wD///8A////AP///wD//4AB//8AAP/8AAA/ - /wAA//AAAA//AAD/wAAAA/8AAP+AAAAB/wAA/wAAAAD/AAD+AAAAAH8AAPwAAAAAPwAA+AAAAAAfAADw - AAAAAA8AAOAAAAAABwAA4AAAAAAHAADAAAAAAAMAAMAAAAAAAwAAgAAAAAABAACAAAAAAAEAAAAAAAAA - AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAA - AQAAgAAAAAABAACAAAAAAAEAAMAAAAAAAwAAwAAAAAADAADgAAAAAAcAAOAAAAAABwAA8AAAAAAPAAD4 - AAAAAB8AAPwAAAAAPwAA/gAAAAB/AAD/AAAAAP8AAP+AAAAB/wAA/8AAAAP/AAD/8AAAD/8AAP/8AAA/ - /wAA//+AAf//AAA= - - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.Designer.cs b/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.Designer.cs deleted file mode 100644 index 6751d7c97..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.Designer.cs +++ /dev/null @@ -1,131 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class TSOClientFindAssetSearch - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.pictureBox1 = new System.Windows.Forms.PictureBox(); - this.lblLooking = new System.Windows.Forms.Label(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.btnExportFile = new System.Windows.Forms.ToolStripButton(); - this.resultGrid = new System.Windows.Forms.DataGridView(); - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); - this.groupBox1.SuspendLayout(); - this.toolStrip1.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.resultGrid)).BeginInit(); - this.SuspendLayout(); - // - // pictureBox1 - // - this.pictureBox1.Image = global::FSO.Client.Properties.Resources.folder_horizontal; - this.pictureBox1.Location = new System.Drawing.Point(12, 12); - this.pictureBox1.Name = "pictureBox1"; - this.pictureBox1.Size = new System.Drawing.Size(50, 39); - this.pictureBox1.TabIndex = 0; - this.pictureBox1.TabStop = false; - // - // lblLooking - // - this.lblLooking.AutoSize = true; - this.lblLooking.Location = new System.Drawing.Point(68, 12); - this.lblLooking.Name = "lblLooking"; - this.lblLooking.Size = new System.Drawing.Size(63, 13); - this.lblLooking.TabIndex = 1; - this.lblLooking.Text = "Looking for:"; - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.toolStrip1); - this.groupBox1.Controls.Add(this.resultGrid); - this.groupBox1.Location = new System.Drawing.Point(12, 69); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(652, 273); - this.groupBox1.TabIndex = 2; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Results"; - // - // toolStrip1 - // - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.btnExportFile}); - this.toolStrip1.Location = new System.Drawing.Point(3, 16); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.Size = new System.Drawing.Size(646, 25); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; - // - // btnExportFile - // - this.btnExportFile.Image = global::FSO.Client.Properties.Resources.folder_export; - this.btnExportFile.ImageTransparentColor = System.Drawing.Color.Magenta; - this.btnExportFile.Name = "btnExportFile"; - this.btnExportFile.Size = new System.Drawing.Size(143, 22); - this.btnExportFile.Text = "Export Selected Assets"; - this.btnExportFile.ToolTipText = "Export Assets"; - // - // resultGrid - // - this.resultGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.resultGrid.Dock = System.Windows.Forms.DockStyle.Fill; - this.resultGrid.Location = new System.Drawing.Point(3, 16); - this.resultGrid.Name = "resultGrid"; - this.resultGrid.Size = new System.Drawing.Size(646, 254); - this.resultGrid.TabIndex = 0; - // - // TSOClientFindAssetSearch - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(676, 354); - this.Controls.Add(this.groupBox1); - this.Controls.Add(this.lblLooking); - this.Controls.Add(this.pictureBox1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; - this.Name = "TSOClientFindAssetSearch"; - this.Text = "Find Asset"; - ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.resultGrid)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.PictureBox pictureBox1; - private System.Windows.Forms.Label lblLooking; - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.DataGridView resultGrid; - private System.Windows.Forms.ToolStrip toolStrip1; - private System.Windows.Forms.ToolStripButton btnExportFile; - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.cs b/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.cs deleted file mode 100644 index 27513cf0b..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using System.Threading; -using FSO.Client.GameContent; - -namespace FSO.Client.Debug -{ - public partial class TSOClientFindAssetSearch : Form - { - private Thread thread; - - public TSOClientFindAssetSearch() - { - InitializeComponent(); - } - - - public void StartSearch(string query) - { - lblLooking.Text = "Searching for: " + query; - - thread = new Thread(new ParameterizedThreadStart(DoSearch)); - thread.Start(query); - } - - private void DoSearch(object queryObj) - { - var query = (string)queryObj; - } - } -} diff --git a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.resx b/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.resx deleted file mode 100644 index 673dcfdc8..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientFindAssetSearch.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientTools.Designer.cs b/TSOClient/FSO.UI/Debug/TSOClientTools.Designer.cs deleted file mode 100644 index 2a45b987c..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientTools.Designer.cs +++ /dev/null @@ -1,103 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class TSOClientTools - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.txtFindAsset = new System.Windows.Forms.TextBox(); - this.btnSearch = new System.Windows.Forms.Button(); - this.button1 = new System.Windows.Forms.Button(); - this.groupBox1.SuspendLayout(); - this.SuspendLayout(); - // - // groupBox1 - // - this.groupBox1.Controls.Add(this.btnSearch); - this.groupBox1.Controls.Add(this.txtFindAsset); - this.groupBox1.Location = new System.Drawing.Point(10, 95); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(200, 81); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Find Asset"; - this.groupBox1.Visible = false; - // - // txtFindAsset - // - this.txtFindAsset.Location = new System.Drawing.Point(6, 19); - this.txtFindAsset.Name = "txtFindAsset"; - this.txtFindAsset.Size = new System.Drawing.Size(188, 20); - this.txtFindAsset.TabIndex = 0; - // - // btnSearch - // - this.btnSearch.Image = global::FSO.Client.Properties.Resources.magnifier_left; - this.btnSearch.ImageAlign = System.Drawing.ContentAlignment.MiddleLeft; - this.btnSearch.Location = new System.Drawing.Point(6, 45); - this.btnSearch.Name = "btnSearch"; - this.btnSearch.Size = new System.Drawing.Size(95, 23); - this.btnSearch.TabIndex = 1; - this.btnSearch.Text = "Search"; - this.btnSearch.UseVisualStyleBackColor = true; - this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click); - // - // button1 - // - this.button1.Location = new System.Drawing.Point(10, 12); - this.button1.Name = "button1"; - this.button1.Size = new System.Drawing.Size(200, 30); - this.button1.TabIndex = 2; - this.button1.Text = "Edith"; - this.button1.UseVisualStyleBackColor = true; - this.button1.Click += new System.EventHandler(this.button1_Click); - // - // TSOClientTools - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(222, 188); - this.Controls.Add(this.button1); - this.Controls.Add(this.groupBox1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; - this.Name = "TSOClientTools"; - this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; - this.Text = "Debug Tools"; - this.groupBox1.ResumeLayout(false); - this.groupBox1.PerformLayout(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.TextBox txtFindAsset; - private System.Windows.Forms.Button btnSearch; - private System.Windows.Forms.Button button1; - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientTools.cs b/TSOClient/FSO.UI/Debug/TSOClientTools.cs deleted file mode 100644 index 23d737b06..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientTools.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using Microsoft.Xna.Framework; - -namespace FSO.Client.Debug -{ - public partial class TSOClientTools : Form - { - private TSOClientUIInspector uiInspetor; - private TSOSceneInspector sceneInspector; - - public TSOClientTools() - { - InitializeComponent(); - - /** - * UI Inspector - */ - uiInspetor = new TSOClientUIInspector(); - uiInspetor.Show(); - - sceneInspector = new TSOSceneInspector(); - sceneInspector.Show(); - - - } - - public void PositionAroundGame(GameWindow gameWindow) - { - - this.Location = new System.Drawing.Point(gameWindow.ClientBounds.X - this.Width - 10, gameWindow.ClientBounds.Y); - uiInspetor.Location = new System.Drawing.Point( - gameWindow.ClientBounds.X - uiInspetor.Width - 10, - gameWindow.ClientBounds.Y + this.Height + 10 - ); - sceneInspector.Location = new System.Drawing.Point( - gameWindow.ClientBounds.X + gameWindow.ClientBounds.Width + 10, - gameWindow.ClientBounds.Y - ); - } - - private void btnSearch_Click(object sender, EventArgs e) - { - var window = new TSOClientFindAssetSearch(); - window.StartSearch(txtFindAsset.Text); - window.Show(); - } - - private void button1_Click(object sender, EventArgs e) - { - //var window = new TSOEdith(); - //window.Show(); - } - } -} diff --git a/TSOClient/FSO.UI/Debug/TSOClientTools.resx b/TSOClient/FSO.UI/Debug/TSOClientTools.resx deleted file mode 100644 index 19dc0dd8b..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientTools.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.Designer.cs b/TSOClient/FSO.UI/Debug/TSOClientUIInspector.Designer.cs deleted file mode 100644 index 5dfd3d662..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.Designer.cs +++ /dev/null @@ -1,269 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class TSOClientUIInspector - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.refreshBtn = new System.Windows.Forms.ToolStripButton(); - this.uiTree = new System.Windows.Forms.TreeView(); - this.propertyBox = new System.Windows.Forms.GroupBox(); - this.valueAlpha = new System.Windows.Forms.NumericUpDown(); - this.label3 = new System.Windows.Forms.Label(); - this.valueScaleLock = new System.Windows.Forms.CheckBox(); - this.valueScaleY = new System.Windows.Forms.NumericUpDown(); - this.valueScaleX = new System.Windows.Forms.NumericUpDown(); - this.label2 = new System.Windows.Forms.Label(); - this.valueY = new System.Windows.Forms.NumericUpDown(); - this.valueX = new System.Windows.Forms.NumericUpDown(); - this.label1 = new System.Windows.Forms.Label(); - this.toolStrip1.SuspendLayout(); - this.propertyBox.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.valueAlpha)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueScaleY)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueScaleX)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueY)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueX)).BeginInit(); - this.SuspendLayout(); - // - // toolStrip1 - // - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.refreshBtn}); - this.toolStrip1.Location = new System.Drawing.Point(0, 0); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.Size = new System.Drawing.Size(222, 25); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; - // - // refreshBtn - // - this.refreshBtn.Image = global::FSO.Client.Properties.Resources.arrow_circle; - this.refreshBtn.ImageTransparentColor = System.Drawing.Color.Magenta; - this.refreshBtn.Name = "refreshBtn"; - this.refreshBtn.Size = new System.Drawing.Size(66, 22); - this.refreshBtn.Text = "Refresh"; - this.refreshBtn.ToolTipText = "Refresh UI Tree"; - this.refreshBtn.Click += new System.EventHandler(this.refreshBtn_Click); - // - // uiTree - // - this.uiTree.BackColor = System.Drawing.Color.AliceBlue; - this.uiTree.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.uiTree.Dock = System.Windows.Forms.DockStyle.Top; - this.uiTree.Location = new System.Drawing.Point(0, 25); - this.uiTree.Name = "uiTree"; - this.uiTree.Size = new System.Drawing.Size(222, 165); - this.uiTree.TabIndex = 2; - this.uiTree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.uiTree_AfterSelect); - // - // propertyBox - // - this.propertyBox.BackColor = System.Drawing.Color.White; - this.propertyBox.Controls.Add(this.valueAlpha); - this.propertyBox.Controls.Add(this.label3); - this.propertyBox.Controls.Add(this.valueScaleLock); - this.propertyBox.Controls.Add(this.valueScaleY); - this.propertyBox.Controls.Add(this.valueScaleX); - this.propertyBox.Controls.Add(this.label2); - this.propertyBox.Controls.Add(this.valueY); - this.propertyBox.Controls.Add(this.valueX); - this.propertyBox.Controls.Add(this.label1); - this.propertyBox.Location = new System.Drawing.Point(5, 196); - this.propertyBox.Name = "propertyBox"; - this.propertyBox.Size = new System.Drawing.Size(212, 187); - this.propertyBox.TabIndex = 3; - this.propertyBox.TabStop = false; - this.propertyBox.Text = "Properties"; - // - // valueAlpha - // - this.valueAlpha.DecimalPlaces = 5; - this.valueAlpha.Increment = new decimal(new int[] { - 1, - 0, - 0, - 65536}); - this.valueAlpha.Location = new System.Drawing.Point(15, 139); - this.valueAlpha.Maximum = new decimal(new int[] { - 1, - 0, - 0, - 0}); - this.valueAlpha.Name = "valueAlpha"; - this.valueAlpha.Size = new System.Drawing.Size(136, 20); - this.valueAlpha.TabIndex = 10; - this.valueAlpha.ValueChanged += new System.EventHandler(this.valueAlpha_ValueChanged); - // - // label3 - // - this.label3.Location = new System.Drawing.Point(12, 118); - this.label3.Name = "label3"; - this.label3.Size = new System.Drawing.Size(73, 18); - this.label3.TabIndex = 8; - this.label3.Text = "Alpha:"; - this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // valueScaleLock - // - this.valueScaleLock.AutoSize = true; - this.valueScaleLock.Checked = true; - this.valueScaleLock.CheckState = System.Windows.Forms.CheckState.Checked; - this.valueScaleLock.Location = new System.Drawing.Point(157, 93); - this.valueScaleLock.Name = "valueScaleLock"; - this.valueScaleLock.Size = new System.Drawing.Size(50, 17); - this.valueScaleLock.TabIndex = 7; - this.valueScaleLock.Text = "Lock"; - this.valueScaleLock.UseVisualStyleBackColor = true; - // - // valueScaleY - // - this.valueScaleY.DecimalPlaces = 5; - this.valueScaleY.Increment = new decimal(new int[] { - 1, - 0, - 0, - 65536}); - this.valueScaleY.Location = new System.Drawing.Point(86, 90); - this.valueScaleY.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.valueScaleY.Name = "valueScaleY"; - this.valueScaleY.Size = new System.Drawing.Size(65, 20); - this.valueScaleY.TabIndex = 6; - this.valueScaleY.ValueChanged += new System.EventHandler(this.valueScaleY_ValueChanged); - // - // valueScaleX - // - this.valueScaleX.DecimalPlaces = 5; - this.valueScaleX.Increment = new decimal(new int[] { - 1, - 0, - 0, - 65536}); - this.valueScaleX.Location = new System.Drawing.Point(15, 90); - this.valueScaleX.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.valueScaleX.Name = "valueScaleX"; - this.valueScaleX.Size = new System.Drawing.Size(65, 20); - this.valueScaleX.TabIndex = 5; - this.valueScaleX.ValueChanged += new System.EventHandler(this.valueScaleX_ValueChanged); - // - // label2 - // - this.label2.Location = new System.Drawing.Point(12, 68); - this.label2.Name = "label2"; - this.label2.Size = new System.Drawing.Size(73, 18); - this.label2.TabIndex = 4; - this.label2.Text = "Scale:"; - this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // valueY - // - this.valueY.Location = new System.Drawing.Point(115, 45); - this.valueY.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.valueY.Name = "valueY"; - this.valueY.Size = new System.Drawing.Size(85, 20); - this.valueY.TabIndex = 3; - this.valueY.ValueChanged += new System.EventHandler(this.valueY_ValueChanged); - // - // valueX - // - this.valueX.Location = new System.Drawing.Point(15, 45); - this.valueX.Maximum = new decimal(new int[] { - 99999, - 0, - 0, - 0}); - this.valueX.Name = "valueX"; - this.valueX.Size = new System.Drawing.Size(85, 20); - this.valueX.TabIndex = 1; - this.valueX.ValueChanged += new System.EventHandler(this.valueX_ValueChanged); - // - // label1 - // - this.label1.Location = new System.Drawing.Point(12, 23); - this.label1.Name = "label1"; - this.label1.Size = new System.Drawing.Size(73, 18); - this.label1.TabIndex = 0; - this.label1.Text = "Position:"; - this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // TSOClientUIInspector - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.AliceBlue; - this.ClientSize = new System.Drawing.Size(222, 388); - this.Controls.Add(this.propertyBox); - this.Controls.Add(this.uiTree); - this.Controls.Add(this.toolStrip1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; - this.Name = "TSOClientUIInspector"; - this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; - this.Text = "UI Inspetor"; - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - this.propertyBox.ResumeLayout(false); - this.propertyBox.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.valueAlpha)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueScaleY)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueScaleX)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueY)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.valueX)).EndInit(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ToolStrip toolStrip1; - private System.Windows.Forms.TreeView uiTree; - private System.Windows.Forms.ToolStripButton refreshBtn; - private System.Windows.Forms.GroupBox propertyBox; - private System.Windows.Forms.CheckBox valueScaleLock; - private System.Windows.Forms.NumericUpDown valueScaleY; - private System.Windows.Forms.NumericUpDown valueScaleX; - private System.Windows.Forms.Label label2; - private System.Windows.Forms.NumericUpDown valueY; - private System.Windows.Forms.NumericUpDown valueX; - private System.Windows.Forms.Label label1; - private System.Windows.Forms.NumericUpDown valueAlpha; - private System.Windows.Forms.Label label3; - - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.cs b/TSOClient/FSO.UI/Debug/TSOClientUIInspector.cs deleted file mode 100644 index 3c2f98ecd..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using FSO.Client.UI.Framework; - -namespace FSO.Client.Debug -{ - public partial class TSOClientUIInspector : Form - { - private Dictionary ItemMap; - - public TSOClientUIInspector() - { - ItemMap = new Dictionary(); - InitializeComponent(); - - propertyBox.Enabled = false; - RefreshUITree(); - } - - private void refreshBtn_Click(object sender, EventArgs e) - { - RefreshUITree(); - } - - private void RefreshUITree() - { - ItemMap.Clear(); - - var nodes = ExploreUIContainer(GameFacade.Screens.CurrentUIScreen); - uiTree.Nodes.Clear(); - - var rootNode = new TreeNode(GameFacade.Screens.CurrentUIScreen.ToString()); - ItemMap[rootNode] = GameFacade.Screens.CurrentUIScreen; - rootNode.Nodes.AddRange(nodes.ToArray()); - - uiTree.Nodes.Add(rootNode); - } - - private List ExploreUIContainer(UIContainer container){ - var result = new List(); - - foreach (var child in container.GetChildren()) - { - var node = new TreeNode(child.ToString()); - ItemMap.Add(node, child); - - if (child is UIContainer) - { - node.Nodes.AddRange(ExploreUIContainer((UIContainer)child).ToArray()); - } - result.Add(node); - } - - return result; - } - - private void uiTree_AfterSelect(object sender, TreeViewEventArgs e) - { - SetSelected(ItemMap[e.Node]); - } - - private UIElement Selected; - private void SetSelected(UIElement element) - { - if (element == null) - { - propertyBox.Enabled = false; - return; - } - - propertyBox.Enabled = true; - Selected = element; - - valueX.Value = (decimal)element.X; - valueY.Value = (decimal)element.Y; - valueScaleX.Value = (decimal)element.ScaleX; - valueScaleY.Value = (decimal)element.ScaleY; - valueAlpha.Value = (decimal)element.Opacity; - - } - - private void valueX_ValueChanged(object sender, EventArgs e) - { - if (Selected != null) - { - Selected.X = (float)valueX.Value; - } - } - - private void valueY_ValueChanged(object sender, EventArgs e) - { - if (Selected != null) - { - Selected.Y = (float)valueY.Value; - } - } - - private void valueScaleX_ValueChanged(object sender, EventArgs e) - { - if (valueScaleLock.Checked) - { - valueScaleY.Value = valueScaleX.Value; - if (Selected != null) - { - Selected.ScaleX = Selected.ScaleY = (float)valueScaleX.Value; - } - } - else - { - if (Selected != null) - { - Selected.ScaleX = (float)valueScaleX.Value; - } - } - } - - private void valueScaleY_ValueChanged(object sender, EventArgs e) - { - if (valueScaleLock.Checked) - { - valueScaleX.Value = valueScaleY.Value; - if (Selected != null) - { - Selected.ScaleX = Selected.ScaleY = (float)valueScaleY.Value; - } - } - else - { - if (Selected != null) - { - Selected.ScaleY = (float)valueScaleY.Value; - } - } - } - - private void valueAlpha_ValueChanged(object sender, EventArgs e) - { - if (Selected != null) - { - Selected.Opacity = (float)valueAlpha.Value; - } - } - - } -} diff --git a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.resx b/TSOClient/FSO.UI/Debug/TSOClientUIInspector.resx deleted file mode 100644 index 673dcfdc8..000000000 --- a/TSOClient/FSO.UI/Debug/TSOClientUIInspector.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOEdith.Designer.cs b/TSOClient/FSO.UI/Debug/TSOEdith.Designer.cs deleted file mode 100644 index f6e93b004..000000000 --- a/TSOClient/FSO.UI/Debug/TSOEdith.Designer.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class TSOEdith - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.SuspendLayout(); - // - // TSOEdith - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(766, 379); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; - this.Name = "TSOEdith"; - this.Text = "Edith"; - this.ResumeLayout(false); - - } - - #endregion - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOEdith.cs b/TSOClient/FSO.UI/Debug/TSOEdith.cs deleted file mode 100644 index 548d57f0d..000000000 --- a/TSOClient/FSO.UI/Debug/TSOEdith.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; - -namespace FSO.Client.Debug -{ - public partial class TSOEdith : Form - { - public TSOEdith() - { - InitializeComponent(); - } - } -} diff --git a/TSOClient/FSO.UI/Debug/TSOSceneInspector.Designer.cs b/TSOClient/FSO.UI/Debug/TSOSceneInspector.Designer.cs deleted file mode 100644 index 0f72785a2..000000000 --- a/TSOClient/FSO.UI/Debug/TSOSceneInspector.Designer.cs +++ /dev/null @@ -1,104 +0,0 @@ -namespace FSO.Client.Debug -{ - partial class TSOSceneInspector - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.toolStrip1 = new System.Windows.Forms.ToolStrip(); - this.refreshBtn = new System.Windows.Forms.ToolStripButton(); - this.uiTree = new System.Windows.Forms.TreeView(); - this.propertyGrid1 = new System.Windows.Forms.PropertyGrid(); - this.toolStrip1.SuspendLayout(); - this.SuspendLayout(); - // - // toolStrip1 - // - this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { - this.refreshBtn}); - this.toolStrip1.Location = new System.Drawing.Point(0, 0); - this.toolStrip1.Name = "toolStrip1"; - this.toolStrip1.Size = new System.Drawing.Size(227, 25); - this.toolStrip1.TabIndex = 1; - this.toolStrip1.Text = "toolStrip1"; - // - // refreshBtn - // - this.refreshBtn.Image = global::FSO.Client.Properties.Resources.arrow_circle; - this.refreshBtn.ImageTransparentColor = System.Drawing.Color.Magenta; - this.refreshBtn.Name = "refreshBtn"; - this.refreshBtn.Size = new System.Drawing.Size(66, 22); - this.refreshBtn.Text = "Refresh"; - this.refreshBtn.ToolTipText = "Refresh UI Tree"; - this.refreshBtn.Click += new System.EventHandler(this.refreshBtn_Click); - // - // uiTree - // - this.uiTree.BackColor = System.Drawing.Color.AliceBlue; - this.uiTree.BorderStyle = System.Windows.Forms.BorderStyle.None; - this.uiTree.Dock = System.Windows.Forms.DockStyle.Top; - this.uiTree.Location = new System.Drawing.Point(0, 25); - this.uiTree.Name = "uiTree"; - this.uiTree.Size = new System.Drawing.Size(227, 165); - this.uiTree.TabIndex = 2; - this.uiTree.AfterSelect += new System.Windows.Forms.TreeViewEventHandler(this.uiTree_AfterSelect); - // - // propertyGrid1 - // - this.propertyGrid1.Location = new System.Drawing.Point(0, 196); - this.propertyGrid1.Name = "propertyGrid1"; - this.propertyGrid1.Size = new System.Drawing.Size(227, 292); - this.propertyGrid1.TabIndex = 6; - // - // TSOSceneInspector - // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.BackColor = System.Drawing.Color.AliceBlue; - this.ClientSize = new System.Drawing.Size(227, 488); - this.Controls.Add(this.propertyGrid1); - this.Controls.Add(this.uiTree); - this.Controls.Add(this.toolStrip1); - this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; - this.Name = "TSOSceneInspector"; - this.StartPosition = System.Windows.Forms.FormStartPosition.Manual; - this.Text = "Scene Inspetor"; - this.toolStrip1.ResumeLayout(false); - this.toolStrip1.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.ToolStrip toolStrip1; - private System.Windows.Forms.TreeView uiTree; - private System.Windows.Forms.ToolStripButton refreshBtn; - private System.Windows.Forms.PropertyGrid propertyGrid1; - - } -} \ No newline at end of file diff --git a/TSOClient/FSO.UI/Debug/TSOSceneInspector.cs b/TSOClient/FSO.UI/Debug/TSOSceneInspector.cs deleted file mode 100644 index bdc4ef0ac..000000000 --- a/TSOClient/FSO.UI/Debug/TSOSceneInspector.cs +++ /dev/null @@ -1,90 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using FSO.Client.UI.Framework; -using FSO.Client.Utils; -using FSO.Common.Rendering.Framework; - -namespace FSO.Client.Debug -{ - public partial class TSOSceneInspector : Form - { - private Dictionary ItemMap; - - public TSOSceneInspector() - { - ItemMap = new Dictionary(); - InitializeComponent(); - RefreshUITree(); - } - - private void refreshBtn_Click(object sender, EventArgs e) - { - RefreshUITree(); - } - - private void RefreshUITree() - { - ItemMap.Clear(); - - uiTree.Nodes.Clear(); - foreach (var scene in GameFacade.Scenes.Scenes) - { - var node = new TreeNode(scene.ToString()); - var cameraNode = node.Nodes.Add("Camera"); - ItemMap.Add(cameraNode, scene.Camera); - - ItemMap.Add(node, scene); - node.Nodes.AddRange(ExploreScene(scene).ToArray()); - uiTree.Nodes.Add(node); - } - - //foreach (var scene in GameFacade.Scenes.ExternalScenes) - //{ - // var node = new TreeNode(scene.ToString()); - - // ItemMap.Add(node, scene); - // node.Nodes.AddRange(ExploreScene(scene).ToArray()); - // uiTree.Nodes.Add(node); - //} - - - - - //var rootNode = new TreeNode("Scenes"); - //ItemMap[rootNode] = GameFacade.Screens.CurrentUIScreen; - //rootNode.Nodes.AddRange(nodes.ToArray()); - - //uiTree.Nodes.Add(rootNode); - } - - private List ExploreScene(_3DAbstract container) - { - var result = new List(); - - foreach (var child in container.GetElements()) - { - var node = new TreeNode(child.ToString()); - ItemMap.Add(node, child); - result.Add(node); - } - - return result; - } - - private void uiTree_AfterSelect(object sender, TreeViewEventArgs e) - { - if (ItemMap.ContainsKey(e.Node)) - { - var value = ItemMap[e.Node]; - propertyGrid1.SelectedObject = value; - } - } - - } -} diff --git a/TSOClient/FSO.UI/Debug/TSOSceneInspector.resx b/TSOClient/FSO.UI/Debug/TSOSceneInspector.resx deleted file mode 100644 index 673dcfdc8..000000000 --- a/TSOClient/FSO.UI/Debug/TSOSceneInspector.resx +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 17, 17 - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll b/TSOClient/FSO.UI/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll deleted file mode 100644 index 501d73414..000000000 Binary files a/TSOClient/FSO.UI/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll and /dev/null differ diff --git a/TSOClient/FSO.UI/Dependencies/TargaImage.dll b/TSOClient/FSO.UI/Dependencies/TargaImage.dll deleted file mode 100644 index b49d9d8cf..000000000 Binary files a/TSOClient/FSO.UI/Dependencies/TargaImage.dll and /dev/null differ diff --git a/TSOClient/FSO.UI/Dependencies/nunit.framework.dll b/TSOClient/FSO.UI/Dependencies/nunit.framework.dll deleted file mode 100644 index 50e26cc46..000000000 Binary files a/TSOClient/FSO.UI/Dependencies/nunit.framework.dll and /dev/null differ diff --git a/TSOClient/FSO.UI/FSO.UI.csproj b/TSOClient/FSO.UI/FSO.UI.csproj index 7caa6ae0b..212d56c5d 100644 --- a/TSOClient/FSO.UI/FSO.UI.csproj +++ b/TSOClient/FSO.UI/FSO.UI.csproj @@ -1,208 +1,58 @@ - - - + + - Debug - AnyCPU - {73E2AD5B-720B-4EF3-9B7C-55931D0EC693} Library - Properties + net9.0 + enable + disable FSO.UI FSO.UI - v4.5 512 - + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + + + True - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - False - Dependencies\GOLDEngine.dll - - - - - - - - - - - False - Dependencies\TargaImage.dll - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - {6d6009f4-0afb-4806-89d7-7945f20270f5} - MonoGame.Framework.Net.WindowsGL - - - {7de47032-a904-4c29-bd22-2d235e8d91ba} - MonoGame.Framework.Windows - - - {eabea510-3e53-4f19-9f0b-75c5ca9dfa3b} - MSDFData - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {5eddefd2-c850-49c1-812d-ddeff09125ef} - FSO.SimAntics - - - {072781d8-51ec-4143-9cae-daf50177d3ad} - FSO.HIT - - - {b1a6e4c2-e080-4c34-a604-d11b5296a9b8} - FSO.LotView - + + + + + + + + - + + + + + + + - - - - + + Dependencies\GOLDEngine.dll + PreserveNewest - - - \ No newline at end of file + + diff --git a/TSOClient/FSO.UI/Framework/MSDFFont.cs b/TSOClient/FSO.UI/Framework/MSDFFont.cs index 246738373..e96c9f38b 100644 --- a/TSOClient/FSO.UI/Framework/MSDFFont.cs +++ b/TSOClient/FSO.UI/Framework/MSDFFont.cs @@ -167,10 +167,9 @@ public void Draw(GraphicsDevice gd, string text, Vector2 pos, Color color, Vecto inds = mdata.Indices; } - var fglyph = glyph.Glyph; + var fglyph = glyph.Glyph.Value; var mscale = fglyph.Metrics.Scale; - var left = point.X - (fglyph.Metrics.Translation.X - 1/mscale) * subScale; var bottom = point.Y + (fglyph.Metrics.Translation.Y + activeFont.YOff/ activeFont.VectorScale - 1 / mscale) * subScale; @@ -201,7 +200,7 @@ public void Draw(GraphicsDevice gd, string text, Vector2 pos, Color color, Vecto if (next.Glyph != null) { KerningPair pair; - if (pairs.TryGetValue(fglyph.Character | (next.Glyph.Character << 16), out pair)) + if (pairs.TryGetValue(fglyph.Character | (next.Glyph.Value.Character << 16), out pair)) { point.X += pair.Advance * subScale; } @@ -274,7 +273,7 @@ public Vector2 MeasureString(string text) subScale = (activeFont != this) ? activeFont.VectorScale / VectorScale : 1f; } - size.X += glyph.Glyph.Metrics.Advance * subScale; + size.X += glyph.Glyph.Value.Metrics.Advance * subScale; if (i < text.Length - 1) { @@ -284,7 +283,7 @@ public Vector2 MeasureString(string text) if (next.Glyph != null) { KerningPair pair; - if (pairs.TryGetValue(glyph.Glyph.Character | (next.Glyph.Character << 16), out pair)) + if (pairs.TryGetValue(glyph.Glyph.Value.Character | (next.Glyph.Value.Character << 16), out pair)) { size.X += pair.Advance * subScale; } @@ -312,10 +311,10 @@ public MSDFRenderGroup(MSDFFont font, int length) public struct MSDFGlyph { - public FieldGlyph Glyph; + public FieldGlyph? Glyph; public MSDFFont Font; - public MSDFGlyph(FieldGlyph glyph, MSDFFont font) + public MSDFGlyph(FieldGlyph? glyph, MSDFFont font) { Glyph = glyph; Font = font; diff --git a/TSOClient/FSO.UI/Framework/Matrix2D.cs b/TSOClient/FSO.UI/Framework/Matrix2D.cs index aa5142c5e..890b72a9e 100644 --- a/TSOClient/FSO.UI/Framework/Matrix2D.cs +++ b/TSOClient/FSO.UI/Framework/Matrix2D.cs @@ -80,18 +80,12 @@ public static Vector2 TransformPoint(this float[] M, Vector2 point) ); } - public static float[] ExtractScale(this float[] M) + public static Vector2 ExtractScaleVector(this float[] M) { - return new float[2]{ + return new Vector2( (float)Math.Sqrt(M[0] * M[0] + M[1] * M[1]), (float)Math.Sqrt(M[2] * M[2] + M[3] * M[3]) - }; - } - - public static Vector2 ExtractScaleVector(this float[] M) - { - float[] result = ExtractScale(M); - return new Vector2(result[0], result[1]); + ); } public static float[] CloneMatrix(this float[] M) @@ -99,7 +93,14 @@ public static float[] CloneMatrix(this float[] M) return new float[6] { M[0], M[1], M[2], M[3], M[4], M[5] }; } - - + public static void CopyMatrix(this float[] M, float[] to) + { + to[0] = M[0]; + to[1] = M[1]; + to[2] = M[2]; + to[3] = M[3]; + to[4] = M[4]; + to[5] = M[5]; + } } } diff --git a/TSOClient/FSO.UI/Framework/UICachedContainer.cs b/TSOClient/FSO.UI/Framework/UICachedContainer.cs index 9bde884db..3a69f69f6 100644 --- a/TSOClient/FSO.UI/Framework/UICachedContainer.cs +++ b/TSOClient/FSO.UI/Framework/UICachedContainer.cs @@ -1,8 +1,8 @@ -using Microsoft.Xna.Framework.Graphics; -using System.Collections.Generic; -using Microsoft.Xna.Framework; +using FSO.Common; using FSO.Common.Rendering.Framework.Model; -using FSO.Common; +using FSO.Common.Utils; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; namespace FSO.Client.UI.Framework { @@ -13,10 +13,11 @@ public class UICachedContainer : UIContainer protected RenderTarget2D Target; public UIContainer DynamicOverlay = new UIContainer(); public Point BackOffset; - public Color ClearColor = Color.TransparentBlack; + public Color ClearColor = ColorExtensions.TransparentBlack; public bool UseMip; public bool UseZ; public bool InternalBefore; + public Vector2 Overhang; public UICachedContainer() { @@ -40,11 +41,11 @@ public override void PreDraw(UISpriteBatch batch) var gd = batch.GraphicsDevice; if (Invalidated) { - var size = Size * Scale; + var size = (Size + Overhang) * Scale; if (Target == null || (int)size.X != Target.Width || (int)size.Y != Target.Height) { Target?.Dispose(); - Target = new RenderTarget2D(gd, (int)size.X, (int)size.Y, UseMip, SurfaceFormat.Color, (UseZ)?DepthFormat.Depth24:DepthFormat.None, (UseMultisample && !FSOEnvironment.DirectX)?4:0, RenderTargetUsage.PreserveContents); + Target = new RenderTarget2D(gd, (int)size.X, (int)size.Y, UseMip, SurfaceFormat.Color, (UseZ) ? DepthFormat.Depth24 : DepthFormat.None, (UseMultisample && !FSOEnvironment.DirectX) ? 4 : 0, RenderTargetUsage.PreserveContents); } lock (Children) @@ -56,7 +57,7 @@ public override void PreDraw(UISpriteBatch batch) } } - try { batch.End(); } catch { } + batch.End(); gd.SetRenderTarget(Target); gd.Clear(ClearColor); @@ -65,7 +66,7 @@ public override void PreDraw(UISpriteBatch batch) var mat = Microsoft.Xna.Framework.Matrix.CreateTranslation(-(pos.X), -(pos.Y), 0) * Microsoft.Xna.Framework.Matrix.CreateScale(1f) * Microsoft.Xna.Framework.Matrix.CreateTranslation( - BackOffset.X * FSOEnvironment.DPIScaleFactor, + BackOffset.X * FSOEnvironment.DPIScaleFactor, BackOffset.Y * FSOEnvironment.DPIScaleFactor, 0); batch.BatchMatrixStack.Push(mat); @@ -86,6 +87,7 @@ public override void PreDraw(UISpriteBatch batch) batch.End(); gd.SetRenderTarget(null); Invalidated = false; + batch.Resume(); } DynamicOverlay.PreDraw(batch); } @@ -120,7 +122,7 @@ public override void Draw(UISpriteBatch batch) if (!Visible) return; if (Target != null) { - DrawLocalTexture(batch, Target, null, -BackOffset.ToVector2(), new Vector2(1/(Scale.X), 1/(Scale.Y))); + DrawLocalTexture(batch, Target, null, -BackOffset.ToVector2(), new Vector2(1 / (Scale.X), 1 / (Scale.Y))); } DynamicOverlay.Draw(batch); } diff --git a/TSOClient/FSO.UI/Framework/UIContainer.cs b/TSOClient/FSO.UI/Framework/UIContainer.cs index 35b1db3f9..5971ab5a7 100644 --- a/TSOClient/FSO.UI/Framework/UIContainer.cs +++ b/TSOClient/FSO.UI/Framework/UIContainer.cs @@ -116,7 +116,7 @@ public void Remove(UIElement child) { Children.Remove(child); child?.Removed(); - //if (child?.Parent == this) child.Parent = null; + if (child?.Parent == this) child.Parent = null; } } diff --git a/TSOClient/FSO.UI/Framework/UIElement.cs b/TSOClient/FSO.UI/Framework/UIElement.cs index b4a8d179b..9a442f085 100644 --- a/TSOClient/FSO.UI/Framework/UIElement.cs +++ b/TSOClient/FSO.UI/Framework/UIElement.cs @@ -405,13 +405,13 @@ public virtual void CalculateMatrix() //Otherwise, assume our matrix is IDENTITY (aka no scale & positioned at 0,0) if (_Parent != null) { - _Mtx = _Parent.Matrix.CloneMatrix(); + _Parent.Matrix.CopyMatrix(_Mtx); _ScaleParent = _Parent.Scale; } else { + Matrix2D.IDENTITY.CopyMatrix(_Mtx); _ScaleParent = Vector2.One; - _Mtx = Matrix2D.IDENTITY.CloneMatrix(); } //Translate by our x and y coordinates @@ -444,7 +444,6 @@ public virtual void CalculateMatrix() public void Invalidate() { - if (InvalidationParent?.GetType()?.Name == "UIUpgradeItem") { } if (InvalidationParent != null) InvalidationParent.Invalidated = true; } @@ -645,10 +644,13 @@ public Vector2 LocalPoint(float x, float y) return LocalPoint(new Vector2(x, y)); } - public Vector2 FlooredLocalPoint(Vector2 point) + public Vector2 AlignedLocalPoint(Vector2 point, Vector2 scale) { var pos = LocalPoint(point); - return new Vector2((float)Math.Floor(pos.X), (float)Math.Floor(pos.Y)); + + bool align = scale.X == 1 || scale.Y == 1; + + return align ? Vector2.Floor(pos) : pos; } /// @@ -816,7 +818,7 @@ public void DrawLocalString(SpriteBatch batch, string text, Vector2 to, TextStyl //pos.Y += style.BaselineOffset; /** Draw the string **/ - pos = FlooredLocalPoint(pos); + pos = AlignedLocalPoint(pos, scale); if (style.VFont != null) { @@ -827,7 +829,7 @@ public void DrawLocalString(SpriteBatch batch, string text, Vector2 to, TextStyl mat = ui.BatchMatrixStack.Peek(); if (style.Shadow) - style.VFont.Draw(batch.GraphicsDevice, text, pos + new Vector2(FSOEnvironment.DPIScaleFactor), Color.Black, scale, mat); + style.VFont.Draw(batch.GraphicsDevice, text, pos + new Vector2(FSOEnvironment.DPIScaleFactor), Color.Black * Opacity, scale, mat); style.VFont.Draw(batch.GraphicsDevice, text, pos, style.GetColor(state) * Opacity, scale, mat); if (mat != null) @@ -837,7 +839,7 @@ public void DrawLocalString(SpriteBatch batch, string text, Vector2 to, TextStyl } else { - if (style.Shadow) batch.DrawString(style.SpriteFont, text, pos + new Vector2(FSOEnvironment.DPIScaleFactor), Color.Black, 0, Vector2.Zero, scale, SpriteEffects.None, 0); + if (style.Shadow) batch.DrawString(style.SpriteFont, text, pos + new Vector2(FSOEnvironment.DPIScaleFactor), Color.Black * Opacity, 0, Vector2.Zero, scale, SpriteEffects.None, 0); batch.DrawString(style.SpriteFont, text, pos, style.GetColor(state) * Opacity, 0, Vector2.Zero, scale, SpriteEffects.None, 0); } @@ -869,7 +871,7 @@ public void DrawLocalTexture(SpriteBatch batch, Texture2D texture, Vector2 to) { //if (!m_IsInvalidated) //{ - batch.Draw(texture, FlooredLocalPoint(to), null, _BlendColor, 0.0f, + batch.Draw(texture, AlignedLocalPoint(to, _Scale), null, _BlendColor, 0.0f, new Vector2(0.0f, 0.0f), _Scale, _SpriteEffects, 0.0f); //} } @@ -886,7 +888,7 @@ public void DrawLocalTexture(SpriteBatch batch, Texture2D texture, Rectangle fro { //if (!m_IsInvalidated) //{ - batch.Draw(texture, FlooredLocalPoint(to), from, _BlendColor, 0.0f, + batch.Draw(texture, AlignedLocalPoint(to, _Scale), from, _BlendColor, 0.0f, new Vector2(0.0f, 0.0f), _Scale, _SpriteEffects, 0.0f); //} } @@ -904,8 +906,9 @@ public void DrawLocalTexture(SpriteBatch batch, Texture2D texture, Nullable from, Vector2 to, Vector2 scale, Color color, float rotation, Vector2 origin) { DPISwitch(ref texture, ref scale, ref from); - batch.Draw(texture, FlooredLocalPoint(to), from, color, rotation, - origin, _Scale * scale, _SpriteEffects, 0.0f); + Vector2 finalScale = _Scale * scale; + batch.Draw(texture, AlignedLocalPoint(to, finalScale), from, color, rotation, + origin, finalScale, _SpriteEffects, 0.0f); } protected SpriteEffects SprEffects = SpriteEffects.None; @@ -981,7 +986,7 @@ public void DrawTiledTexture(SpriteBatch batch, Texture2D texture, Rectangle des var tex = texture; Rectangle? from = new Rectangle(0, 0, Math.Min(texture.Width, dest.Width - x), Math.Min(texture.Height, dest.Height - y)); DPISwitch(ref tex, ref scale, ref from); - batch.Draw(texture, FlooredLocalPoint(new Vector2(dest.X + x, dest.Y + y)), from, col, 0.0f, + batch.Draw(texture, AlignedLocalPoint(new Vector2(dest.X + x, dest.Y + y), scale), from, col, 0.0f, new Vector2(0.0f, 0.0f), scale, SpriteEffects.None, 0.0f); } } diff --git a/TSOClient/FSO.UI/Framework/UIScreen.cs b/TSOClient/FSO.UI/Framework/UIScreen.cs index f82a68e34..35c50d86a 100644 --- a/TSOClient/FSO.UI/Framework/UIScreen.cs +++ b/TSOClient/FSO.UI/Framework/UIScreen.cs @@ -29,10 +29,10 @@ public static UIScreen Current } } - public static UIAlert GlobalShowAlert(UIAlertOptions options, bool modal) + public static UIAlert GlobalShowAlert(UIAlertOptions options, bool modal, bool focus = false) { var alert = new UIAlert(options); - GlobalShowDialog(alert, modal); + GlobalShowDialog(alert, modal, focus); alert.CenterAround(UIScreen.Current, -(int)UIScreen.Current.X * 2, -(int)UIScreen.Current.Y * 2); return alert; } @@ -41,12 +41,13 @@ public static UIAlert GlobalShowAlert(UIAlertOptions options, bool modal) /// Adds a popup dialog /// /// - public static void GlobalShowDialog(UIElement dialog, bool modal) + public static void GlobalShowDialog(UIElement dialog, bool modal, bool focus = false) { GlobalShowDialog(new DialogReference { Dialog = dialog, - Modal = modal + Modal = modal, + Focus = focus }); } diff --git a/TSOClient/FSO.UI/GlobalSettings.cs b/TSOClient/FSO.UI/GlobalSettings.cs index 96b43d356..e566a8ed5 100644 --- a/TSOClient/FSO.UI/GlobalSettings.cs +++ b/TSOClient/FSO.UI/GlobalSettings.cs @@ -1,4 +1,5 @@ using FSO.Common; +using System; using System.Collections.Generic; using System.IO; @@ -6,6 +7,7 @@ namespace FSO.Client { public class GlobalSettings : IniConfig { + public override string HeadingComment => "FreeSO Settings File. Properties are self explanatory."; private static GlobalSettings defaultInstance; public static GlobalSettings Default @@ -25,11 +27,21 @@ public static GlobalSettings Default defaultInstance.CitySelectorUrl = "https://api.freeso.org"; } + if (defaultInstance.ArchiveClientGUID == "") + { + defaultInstance.ArchiveClientGUID = GenerateGUID(); + } } return defaultInstance; } } + private static string GenerateGUID() + { + return Guid.NewGuid().ToString(); + } + + public GlobalSettings(string path) : base(path) { } private Dictionary _DefaultValues = new Dictionary() @@ -48,7 +60,7 @@ public GlobalSettings(string path) : base(path) { } { "FXVolume", "10"}, { "MusicVolume", "10"}, { "VoxVolume", "10"}, - { "AmbienceVolume", "1"}, + { "AmbienceVolume", "8"}, { "StartupPath", ""}, { "DocumentsPath", ""}, { "Windowed", "true"}, @@ -74,8 +86,10 @@ public GlobalSettings(string path) : base(path) { } { "TS1HybridPath", "D:/Games/The Sims/" }, { "TS1HybridEnable", "false" }, + { "TS1IsSteamInstall", "false" }, + { "TS1InstallationConfigured", "false" }, - { "Shadows3D", "false" }, + { "Shadows3D", "true" }, { "CitySkybox", "true" }, { "LightingMode", "-1" }, @@ -95,10 +109,15 @@ public GlobalSettings(string path) : base(path) { } {"ChatDeltaScale", "8" }, { "ChatWindowsOpacity", "0.8" }, - { "ComplexShaders", "false" }, + { "ComplexShaders", "true" }, { "GlobalGraphicsMode", "0" }, //2d, 2d hybrid, 3d - { "EnableTransitions", "true" } + { "EnableTransitions", "true" }, + + { "ArchiveClientGUID", "" }, + { "TS1FreeWill", "true" }, + { "IgnoreVersion", "" }, }; + public override Dictionary DefaultValues { get { return _DefaultValues; } @@ -141,6 +160,8 @@ public override Dictionary DefaultValues public string TS1HybridPath { get; set; } public bool TS1HybridEnable { get; set; } + public bool TS1IsSteamInstall { get; set; } + public bool TS1InstallationConfigured { get; set; } public bool Shadows3D { get; set; } public bool CitySkybox { get; set; } @@ -167,6 +188,12 @@ public override Dictionary DefaultValues public int GlobalGraphicsMode { get; set; } public bool EnableTransitions { get; set; } + + public string ArchiveClientGUID { get; set; } + public string IgnoreVersion { get; set; } + + public bool TS1FreeWill { get; set; } + public static int TARGET_COMPAT_STATE = 2; } } diff --git a/TSOClient/FSO.UI/Model/DiscordRpcEngine.cs b/TSOClient/FSO.UI/Model/DiscordRpcEngine.cs index 44ff58cc0..2fbb1f4ea 100644 --- a/TSOClient/FSO.UI/Model/DiscordRpcEngine.cs +++ b/TSOClient/FSO.UI/Model/DiscordRpcEngine.cs @@ -1,17 +1,99 @@ using FSO.Common.Enum; -using System; - +using System.Text; using static FSO.UI.Model.DiscordRpc; namespace FSO.UI.Model { + public struct RpcSecret + { + public bool ArchiveMode; + public string ServerID; + public string ServerHostname; + public uint LotID; + + private static void XorBytes(byte[] data, byte[] pattern) + { + for (int i = 0; i < data.Length; i++) + { + data[i] ^= pattern[i % pattern.Length]; + } + } + + public static string EncodeHostname(string id, string hostname) + { + var hostnameBytes = Encoding.UTF8.GetBytes(hostname); + var idBytes = Encoding.UTF8.GetBytes(id); + + XorBytes(hostnameBytes, idBytes); + + return Convert.ToBase64String(hostnameBytes); + } + + public static string DecodeHostname(string id, string encoded) + { + var encodedBytes = Convert.FromBase64String(encoded); + var idBytes = Encoding.UTF8.GetBytes(id); + + XorBytes(encodedBytes, idBytes); + + return Encoding.UTF8.GetString(encodedBytes); + } + + public RpcSecret(string secret) + { + if (secret.StartsWith('#')) + { + ArchiveMode = true; + var split = secret[1..].Split('#'); + + if (split.Length != 3) + { + throw new FormatException("Invalid number of join secret fields"); + } + + ServerID = split[0]; + ServerHostname = DecodeHostname(split[0], split[1]); + if (!uint.TryParse(split[2], out LotID)) + { + LotID = 0; + } + } + else + { + var split = secret.Split('#'); + + if (!uint.TryParse(split[0], out LotID)) + { + LotID = 0; + } + } + } + + public override string ToString() + { + if (ArchiveMode) + { + return $"#{ServerID ?? ""}#{EncodeHostname(ServerID, ServerHostname ?? "")}#{LotID}"; + } + else + { + return $"{LotID}#"; + } + } + } + public static class DiscordRpcEngine { public static bool Active; public static bool Disable; - public static string Secret; + public static RpcSecret? Secret; public static EventHandlers Events; + private static RpcSecret BroadcastSecret; + + public static bool PublicArchive => BroadcastSecret.ArchiveMode && !string.IsNullOrEmpty(BroadcastSecret.ServerHostname); + public static string ArchiveID => BroadcastSecret.ServerID ?? ""; + public static void Init() { try @@ -45,6 +127,33 @@ public static void Update() Disable = true; } } + + public static void SetArchiveAddress(string address) + { + BroadcastSecret.ArchiveMode = true; + BroadcastSecret.ServerHostname = address; + + SendFSOPresenceIngame(); + } + + public static void SetArchiveID(string id) + { + BroadcastSecret.ArchiveMode = true; + BroadcastSecret.ServerID = id; + } + + public static void SetArchivePlayers(int count) + { + ArchivePlayers = count; + + SendFSOPresenceIngame(); + } + + public static void Reset() + { + BroadcastSecret = default; + } + // Method for other game screens public static void SendFSOPresence(string state, string details = null) { @@ -60,22 +169,47 @@ public static void SendFSOPresence(string state, string details = null) DiscordRpc.UpdatePresence(ref presence); } - // Standard DiscordRpc presence method + + private static string ActiveSim; + private static string LotName; + private static int LotID; + private static int Players; + private static int MaxSize; + private static int CatID; + private static string CDNUrl; + private static bool IsPrivate; + private static int ArchivePlayers = 1; + public static void SendFSOPresence(string activeSim, string lotName, int lotID, int players, int maxSize, int catID, string cdnUrl, bool isPrivate = false) + { + ActiveSim = activeSim; + LotName = lotName; + LotID = lotID; + Players = players; + MaxSize = maxSize; + CatID = catID; + CDNUrl = cdnUrl; + IsPrivate = isPrivate; + + SendFSOPresenceIngame(); + } + + // Standard DiscordRpc presence method + private static void SendFSOPresenceIngame() { if (!Active) return; var presence = new DiscordRpc.RichPresence(); bool isJob = false; - if (!isPrivate) + if (!IsPrivate) { - if (lotName?.StartsWith("{job:") == true) + if (LotName?.StartsWith("{job:") == true) { isJob = true; var jobStr = ""; - var split = lotName.Split(':'); + var split = LotName.Split(':'); if (split.Length > 2) { switch (split[1]) @@ -97,20 +231,20 @@ public static void SendFSOPresence(string activeSim, string lotName, int lotID, } else jobStr = "Job Lot"; - if (activeSim != null) presence.details = "Playing as " + activeSim; + if (ActiveSim != null) presence.details = "Playing as " + ActiveSim; presence.state = jobStr; } else { - if (activeSim == null) + if (ActiveSim == null) { - presence.state = lotName ?? "Idle in City"; + presence.state = LotName ?? "Idle in City"; presence.details = ""; } else { - presence.details = "Playing as " + activeSim; - presence.state = lotName ?? "Idle in City"; + presence.details = "Playing as " + ActiveSim; + presence.state = LotName ?? "Idle in City"; } } @@ -120,32 +254,50 @@ public static void SendFSOPresence(string activeSim, string lotName, int lotID, presence.state = "Online"; presence.details = "Privacy Enabled"; } - presence.largeImageKey = "sunrise_crater"; presence.largeImageText = "Sunrise Crater"; - if (lotName != null && !isPrivate) + BroadcastSecret.LotID = (uint)LotID; + + if (BroadcastSecret.ArchiveMode) + { + presence.state += " (archive)"; + if (PublicArchive) + { + presence.details += " (server joinable)"; + presence.joinSecret = BroadcastSecret.ToString(); + + presence.smallImageKey = "sunrise_crater"; + presence.smallImageText = "Joinable Server"; + + presence.partyMax = 128; + presence.partySize = ArchivePlayers; + presence.partyId = "shared"; + } + } + + if (LotName != null && !IsPrivate) { - presence.joinSecret = lotID + "#" + lotName; + presence.joinSecret = BroadcastSecret.ToString(); //presence.matchSecret = lotID + "#" + lotName+"."; - presence.spectateSecret = lotID + "#" + lotName + ".."; - presence.partyMax = maxSize; - presence.partySize = players; - presence.partyId = lotID.ToString(); + //presence.spectateSecret = lotID + "#" + lotName + ".."; + presence.partyMax = MaxSize; + presence.partySize = Players; + presence.partyId = LotID.ToString(); - if (cdnUrl != null && !isJob) + if (CDNUrl != null && !isJob) { - presence.smallImageKey = "cat_" + catID; - presence.smallImageText = CapFirstWord(((LotCategory)catID).ToString()); + presence.smallImageKey = "cat_" + CatID; + presence.smallImageText = CapFirstWord(((LotCategory)CatID).ToString()); - presence.largeImageKey = $"{cdnUrl}/userapi/city/1/{lotID}.png"; + presence.largeImageKey = $"{CDNUrl}/userapi/city/1/{LotID}.png"; presence.largeImageText = presence.state; } else { - presence.largeImageKey = "cat_" + catID; - presence.largeImageText = CapFirstWord(((LotCategory)catID).ToString()); + presence.largeImageKey = "cat_" + CatID; + presence.largeImageText = CapFirstWord(((LotCategory)CatID).ToString()); } } @@ -169,12 +321,12 @@ public static void Error(int errorCode, string message) public static void Join(string secret) { - Secret = secret; + Secret = new RpcSecret(secret); } public static void Spectate(string secret) { - Secret = secret; + Secret = new RpcSecret(secret); } public static void Disconnected(int errorCode, string message) diff --git a/TSOClient/FSO.UI/Panels/UI3DThumb.cs b/TSOClient/FSO.UI/Panels/UI3DThumb.cs index be55449d0..65781b4e9 100644 --- a/TSOClient/FSO.UI/Panels/UI3DThumb.cs +++ b/TSOClient/FSO.UI/Panels/UI3DThumb.cs @@ -1,4 +1,5 @@ using FSO.Client; +using FSO.Common; using FSO.Common.Rendering.Framework; using FSO.Common.Rendering.Framework.Camera; using FSO.LotView.Components; @@ -30,7 +31,7 @@ public UI3DThumb(VMEntity ent) { Camera = new BasicCamera(GameFacade.GraphicsDevice, new Vector3(3, 1, 0), new Vector3(0, 0, 0), new Vector3(0, 1, 0)); Camera.NearPlane = 0.001f; - Scene = new _3DTargetScene(GameFacade.GraphicsDevice, Camera, new Point(150, 150), 0); + Scene = new _3DTargetScene(GameFacade.GraphicsDevice, Camera, new Point((int)(150 * FSOEnvironment.DPIScaleFactor), (int)(150 * FSOEnvironment.DPIScaleFactor)), 0); Scene.Initialize(GameFacade.Scenes); if (Comp3D != null) diff --git a/TSOClient/FSO.UI/Properties/AssemblyInfo.cs b/TSOClient/FSO.UI/Properties/AssemblyInfo.cs deleted file mode 100644 index 27b3ba833..000000000 --- a/TSOClient/FSO.UI/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.UI")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.UI")] -[assembly: AssemblyCopyright("Copyright © 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("73e2ad5b-720b-4ef3-9b7c-55931d0ec693")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.UI/UILayer.cs b/TSOClient/FSO.UI/UILayer.cs index def52b215..808f4d65f 100644 --- a/TSOClient/FSO.UI/UILayer.cs +++ b/TSOClient/FSO.UI/UILayer.cs @@ -198,6 +198,8 @@ public void RemoveProcess(IUIProcess Proc) /// The UIScreen instance to be added. public void AddScreen(UIScreen Screen) { + AssetStreaming.EndStreaming(); + /*if (currentScreen != null) { mainUI.Remove(currentScreen); @@ -275,8 +277,6 @@ public void RemoveCurrent() public void Update(UpdateState state) { - GameThread.DigestUpdate(state); - if (GameFacade.Game.Window == null) return; var mousePosition = state.MouseState.Position; @@ -298,6 +298,8 @@ public void Update(UpdateState state) } state.MouseEvents.Clear(); + ValidateFocus(inputManager); + state.InputManager = inputManager; Content.Content.Get()?.Changes.RunResModifications(); mainUI.Update(state); @@ -325,10 +327,90 @@ public void Update(UpdateState state) item.Update(state); } + HandleFocusNavigation(state, mainUI); + Tooltip = state.UIState.Tooltip; TooltipProperties = state.UIState.TooltipProperties; } + private void ValidateFocus(InputManager inputManager) + { + var current = inputManager.GetFocus(); + var root = UIScreen.Current; + + if (current != null && current is UIElement elem) + { + // If the element is no longer visible or doesn't exist on the current screen, it should lose focus. + + do + { + if (!elem.Visible) + { + inputManager.SetFocus(null); + } + + if (elem == root) + { + return; + } + + elem = elem.Parent; + } while (elem != null); + + inputManager.SetFocus(null); + } + } + + private void HandleFocusNavigation(UpdateState state, UIContainer root) + { + if (!state.FocusNextPressed && !state.FocusPrevPressed) return; + + // Scope focus navigation to the top-most modal dialog if one exists + var topModal = Dialogs.LastOrDefault(x => x.Modal); + UIElement tabRoot = topModal != null ? topModal.Dialog : root; + + // If nothing is focused and no dialogs are open, let Tab pass through to gameplay (e.g. free cam) + if (inputManager.GetFocus() == null && Dialogs.Count == 0) return; + + var focusables = new List(); + CollectFocusables(tabRoot, focusables); + if (focusables.Count == 0) return; + + // Sort: explicit TabIndex > 0 first (ascending), then TabIndex == 0 by screen position (Y, X). + focusables.Sort((a, b) => + { + int aIdx = a.TabIndex, bIdx = b.TabIndex; + bool aExplicit = aIdx > 0, bExplicit = bIdx > 0; + if (aExplicit != bExplicit) return aExplicit ? -1 : 1; + if (aExplicit && aIdx != bIdx) return aIdx.CompareTo(bIdx); + + var aPos = ((UIElement)a).LocalPoint(Vector2.Zero); + var bPos = ((UIElement)b).LocalPoint(Vector2.Zero); + int cmp = aPos.Y.CompareTo(bPos.Y); + return cmp != 0 ? cmp : aPos.X.CompareTo(bPos.X); + }); + + var current = inputManager.GetFocus(); + int currentIdx = current != null ? focusables.IndexOf(current) : -1; + int dir = state.FocusPrevPressed ? -1 : 1; + int next = (currentIdx + dir + focusables.Count) % focusables.Count; + inputManager.SetFocus(focusables[next]); + } + + private void CollectFocusables(UIElement element, List result) + { + if (!element.Visible) return; + + if (element is IFocusableUI focusable && focusable.TabIndex >= 0 && element.WillDraw()) + result.Add(focusable); + + if (element is UIContainer container) + { + foreach (var child in container.GetChildren()) + CollectFocusables(child, result); + } + } + public void PreDraw(UISpriteBatch SBatch) { mainUI.PreDraw(SBatch); @@ -379,7 +461,7 @@ public void DrawTooltip(SpriteBatch batch, Vector2 position, float opacity, Colo for (int i = 0; i < wrapped.Lines.Count; i++) { int thisWidth = (int)(style.MeasureString(wrapped.Lines[i]).X); - var pos = position + new Vector2((width - thisWidth) / 2, 0); + var pos = position + new Vector2((width - thisWidth) / 2, 1); if (style.VFont != null) { batch.End(); @@ -406,6 +488,14 @@ public void AddDialog(DialogReference dialog) Dialogs.Add(dialog); AdjustModal(); + + if (dialog.Modal || dialog.Focus) + { + var focusables = new List(); + CollectFocusables(dialog.Dialog, focusables); + if (focusables.Count > 0) + inputManager.SetFocus(focusables[0]); + } } public void RemoveDialog(DialogReference dialog) @@ -416,6 +506,7 @@ public void RemoveDialog(DialogReference dialog) dialog.Dialog.Parent.Remove(dialog.Dialog); } Dialogs.Remove(dialog); + inputManager.SetFocus(null); AdjustModal(); } @@ -426,6 +517,7 @@ public void RemoveDialog(UIElement dialog) { Dialogs.Remove(reference); dialog.Parent.Remove(reference.Dialog); + inputManager.SetFocus(null); AdjustModal(); } } @@ -459,20 +551,25 @@ public void PreDraw(GraphicsDevice device) { lock (m_ExtContainers) { - foreach (var ext in m_ExtContainers) + if (m_ExtContainers.Count > 0) { - lock (ext) + SpriteBatch.UIBegin(BlendState.AlphaBlend, SpriteSortMode.Immediate); + foreach (var ext in m_ExtContainers) { - if (!ext.HasUpdated) ext.Update(null); - ext.PreDraw(SpriteBatch); - ext.Draw(SpriteBatch); + lock (ext) + { + if (!ext.HasUpdated) ext.Update(null); + ext.PreDraw(SpriteBatch); + ext.Draw(SpriteBatch); + } } + SpriteBatch.End(); } } SpriteBatch.UIBegin(BlendState.AlphaBlend, SpriteSortMode.Immediate); this.PreDraw(SpriteBatch); - try { SpriteBatch.End(); } catch { } + SpriteBatch.Pause(); } public void Draw(GraphicsDevice device) @@ -500,6 +597,7 @@ public class DialogReference { public UIElement Dialog; public bool Modal; + public bool Focus; public object Controller; public UIContainer LogicalParent; } diff --git a/TSOClient/FSO.UI/Utils/CatThumbGenerator.cs b/TSOClient/FSO.UI/Utils/CatThumbGenerator.cs index 912989ec4..ed99290f2 100644 --- a/TSOClient/FSO.UI/Utils/CatThumbGenerator.cs +++ b/TSOClient/FSO.UI/Utils/CatThumbGenerator.cs @@ -1,16 +1,56 @@ using FSO.Client; using FSO.Common.Utils; +using FSO.LotView; using FSO.LotView.Components; +using FSO.LotView.Model; using FSO.SimAntics; +using FSO.SimAntics.Engine.TSOTransaction; using FSO.SimAntics.Entities; +using FSO.SimAntics.Model; +using FSO.SimAntics.NetPlay.Drivers; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; namespace FSO.UI.Utils { public static class CatThumbGenerator { + private static VM ThumbVM; + + private static VM GetThumbVM() + { + if (ThumbVM == null) + { + var world = new ExternalWorld(GameFacade.GraphicsDevice); + world.Initialize(GameFacade.Scenes); + var context = new VMContext(world); + + ThumbVM = new VM(context, new VMServerDriver(new VMTSOGlobalLinkStub()), new VMNullHeadlineProvider()); + ThumbVM.Init(); + + var blueprint = new Blueprint(1, 1) + { + Light = + [ + new RoomLighting() { OutsideLight = 100 }, + new RoomLighting() { OutsideLight = 100 }, + new RoomLighting() { OutsideLight = 100 }, + ], + OutsideColor = Color.White + }; + blueprint.GenerateRoomLights(); + blueprint.RoomColors[2].A /= 2; + world.State.AmbientLight.SetData(blueprint.RoomColors); + world.State.OutsidePx.SetData([Color.White]); + + world.InitBlueprint(blueprint); + context.Blueprint = blueprint; + context.Architecture = new VMArchitecture(1, 1, blueprint, ThumbVM.Context); + } + + return ThumbVM; + } + public static Texture2D GenerateThumb(VMMultitileGroup obj, VM vm) { var gd = GameFacade.GraphicsDevice; @@ -34,7 +74,15 @@ public static Texture2D GenerateThumb(VMMultitileGroup obj, VM vm) var oldRts = gd.GetRenderTargets(); gd.SetRenderTarget(result); gd.Clear(Color.Black); - sb.Begin(blendState: BlendState.AlphaBlend); + var sampler = new SamplerState() + { + AddressU = TextureAddressMode.Clamp, + AddressV = TextureAddressMode.Clamp, + AddressW = TextureAddressMode.Clamp, + Filter = TextureFilter.Linear, + MipMapLevelOfDetailBias = -0.5f, + }; + sb.Begin(blendState: BlendState.NonPremultiplied, samplerState: sampler); var minScale = Math.Min(37f/newAgain.Width, 37f/newAgain.Height); if (minScale > 1) minScale = 1; var rect = new Rectangle( @@ -56,5 +104,23 @@ public static Texture2D GenerateThumb(VMMultitileGroup obj, VM vm) newAgain.Dispose(); return result; } + + public static Texture2D GenerateThumb(uint guid) + { + var vm = GetThumbVM(); + + var obj = vm.Context.CreateObjectInstance(guid, LotTilePos.OUT_OF_WORLD, Direction.NORTH, true); + + if (obj == null) + { + return null; + } + + var icon = GenerateThumb(obj, vm); + + obj.Delete(vm.Context); + + return icon; + } } } diff --git a/TSOClient/FSO.UI/app.config b/TSOClient/FSO.UI/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/FSO.UI/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FSO.UI/discord-rpc.dll b/TSOClient/FSO.UI/discord-rpc.dll index c77f6d5d0..8493c5490 100644 Binary files a/TSOClient/FSO.UI/discord-rpc.dll and b/TSOClient/FSO.UI/discord-rpc.dll differ diff --git a/TSOClient/FSO.Unix/FSO.Unix.csproj b/TSOClient/FSO.Unix/FSO.Unix.csproj new file mode 100644 index 000000000..007b5a3b6 --- /dev/null +++ b/TSOClient/FSO.Unix/FSO.Unix.csproj @@ -0,0 +1,101 @@ + + + + + Exe + net9.0 + FSO.Unix + FreeSO + fso.ico + false + true + true + <_EnableMacOSCodeSign>false + false + false + + + + + + Icon.bmp + + + + + + + PreserveNewest + + + PreserveNewest + + + Icon.icns + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/TSOClient/FSO.Unix/Icon.bmp b/TSOClient/FSO.Unix/Icon.bmp new file mode 100644 index 000000000..b812fc5e5 Binary files /dev/null and b/TSOClient/FSO.Unix/Icon.bmp differ diff --git a/TSOClient/FSO.Unix/Icon.icns b/TSOClient/FSO.Unix/Icon.icns new file mode 100644 index 000000000..5c4b7835a Binary files /dev/null and b/TSOClient/FSO.Unix/Icon.icns differ diff --git a/TSOClient/FSO.Unix/Info.plist b/TSOClient/FSO.Unix/Info.plist new file mode 100644 index 000000000..97c99a6c9 --- /dev/null +++ b/TSOClient/FSO.Unix/Info.plist @@ -0,0 +1,27 @@ + + + + + CFBundleName + FreeSO + CFBundleDisplayName + FreeSO + CFBundleIdentifier + org.freeso.fso + CFBundleVersion + 1.0 + CFBundleExecutable + FreeSO + CFBundleIconFile + Icon + LSApplicationCategoryType + public.app-category.simulation-games + LSMinimumSystemVersion + 13.0 + NSHumanReadableCopyright + + + diff --git a/TSOClient/FSO.Unix/Program.cs b/TSOClient/FSO.Unix/Program.cs new file mode 100644 index 000000000..cdfb633f0 --- /dev/null +++ b/TSOClient/FSO.Unix/Program.cs @@ -0,0 +1,137 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using FSO.Client; +using FSO.Client.UI.Panels; +using FSO.Common; +using SixLabors.ImageSharp; +using SixLabors.ImageSharp.PixelFormats; +using SixLabors.ImageSharp.Formats.Png; + +namespace FSO.Unix +{ + public static class Program + { + /// + /// The main entry point for the application. + /// + public static void Main(string[] args) + { + InitUnix(); + + var mgAssembly = typeof(Microsoft.Xna.Framework.Game).Assembly; + var platform = mgAssembly.GetType("MonoGame.Framework.Utilities.PlatformInfo"); + var backend = platform?.GetProperty("GraphicsBackend")?.GetValue(null); + Console.WriteLine($"[FreeSO] MonoGame: {mgAssembly.GetName().Version} | Backend: {backend ?? "Unknown"}"); + + FSOEnvironment.Enable3D = true; + + if ((new FSOProgram()).InitWithArguments(args)) + { + var startProxy = new GameStartProxy(); + startProxy.Start(false); + } + + Environment.Exit(0); + } + + public static void InitUnix() + { + FSO.Files.ImageLoaderHelpers.BitmapFunction = BitmapReader; + FSO.Files.ImageLoaderHelpers.SavePNGFunc = SavePNG; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + FSOProgram.ShowDialog = ShowDialog; + } + + public static void ShowDialog(string text) + { + ShowDialog(text, "FreeSO Message"); + } + + private static string Escape(string s) => s.Replace("\"", "\\\""); + + private static void ShowDialog(string text, string title) + { + if (text.Length > 1500) text = text.Substring(0, 1500) + "..."; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + var psi = new ProcessStartInfo + { + FileName = "osascript", + Arguments = $"-e \"display alert \\\"{Escape(title)}\\\" message \\\"{Escape(text)}\\\" giving up after 15\"", + UseShellExecute = true + }; + Process.Start(psi)?.WaitForExit(); + } + else + { + try + { + var psi = new ProcessStartInfo + { + FileName = "zenity", + Arguments = $"--error --title=\"{Escape(title)}\" --text=\"{Escape(text)}\"", + UseShellExecute = false + }; + Process.Start(psi)?.WaitForExit(); + } + catch + { + Console.Error.WriteLine($"[{title}] {text}"); + } + } + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + string title = e.ExceptionObject is OutOfMemoryException + ? "Out of Memory! FreeSO needs to close." + : "A fatal error occured! Screenshot this dialog and post it on Discord."; + + ShowDialog(e.ExceptionObject.ToString(), title); + Environment.Exit(1); + } + + public static void SavePNG(byte[] data, int width, int height, Stream str) + { + using var image = new Image(width, height); + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int i = (y * width + x) * 4; + image[x, y] = new Rgba32(data[i], data[i + 1], data[i + 2], data[i + 3]); + } + } + + image.Save(str, new PngEncoder()); + } + + public static Tuple BitmapReader(Stream str) + { + using var image = Image.Load(str); + int width = image.Width; + int height = image.Height; + + var data = new byte[width * height * 4]; + + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + int i = (y * width + x) * 4; + Rgba32 px = image[x, y]; + data[i] = px.R; + data[i + 1] = px.G; + data[i + 2] = px.B; + data[i + 3] = px.A; + } + } + + return new Tuple(data, width, height); + } + } +} diff --git a/TSOClient/FSO.Unix/Properties/launchSettings.json b/TSOClient/FSO.Unix/Properties/launchSettings.json new file mode 100644 index 000000000..63253d57d --- /dev/null +++ b/TSOClient/FSO.Unix/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "FSO.Unix": { + "commandName": "Project" + }, + } +} diff --git a/TSOClient/FSO.Unix/README.md b/TSOClient/FSO.Unix/README.md new file mode 100644 index 000000000..5ab5b376e --- /dev/null +++ b/TSOClient/FSO.Unix/README.md @@ -0,0 +1,68 @@ +## Build + +### macOS + +1. Navigate to the `FSO.Unix` folder. +2. Run the following command in Terminal to build a release publish: + +```bash +dotnet publish -c Release -r osx-arm64 --self-contained true +``` + +This will generate a .app bundle in: bin/Release/net9.0/osx-arm64/publish folder. +You can then copy the app to your Applications folder. + +### Linux + +1. Navigate to the `FSO.Unix` folder. +2. Run the following command to build: + +```bash +dotnet build -r linux-x64 +``` + +Or to publish a self-contained release: + +```bash +dotnet publish -c Release -r linux-x64 --self-contained true +``` + +## Deploy + +To build, install, and launch in one step: + +```bash +./deploy.sh +``` + +This script works on both macOS and Linux: +- **macOS**: Installs to `/Applications/FreeSO.app` and opens the app +- **Linux**: Installs to `~/.local/share/FreeSO` and creates a desktop entry + +## Launch 3D + +### macOS + +To open the app in 3D mode, run the open command with `--args -3d`: + +E.g when the application is placed in the Applications folder, you can run: + +```bash +open /Applications/FreeSO.app --args -3d +``` + +### Linux + +```bash +./FreeSO -3d +``` + +## Troubleshooting + +### Deploy Script Permissions + +If you get a permission denied error when running `deploy.sh`, make it executable: + +```bash +chmod +x deploy.sh +``` diff --git a/TSOClient/FSO.Unix/deploy.sh b/TSOClient/FSO.Unix/deploy.sh new file mode 100755 index 000000000..2a7f99de6 --- /dev/null +++ b/TSOClient/FSO.Unix/deploy.sh @@ -0,0 +1,50 @@ +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$SCRIPT_DIR" + +OS="$(uname -s)" + +if [ "$OS" = "Darwin" ]; then + RID="osx-arm64" +elif [ "$OS" = "Linux" ]; then + RID="linux-x64" +else + echo "Unsupported platform: $OS" && exit 1 +fi + +PUBLISH_DIR="$SCRIPT_DIR/bin/Release/net9.0/$RID/publish" + +echo "Building FSO.Unix for $OS..." +dotnet publish -c Release -r "$RID" --self-contained true -p:PublishSingleFile=true + +if [ "$OS" = "Darwin" ]; then + rm -rf /Applications/FreeSO.app + cp -R "$PUBLISH_DIR/FreeSO.app" /Applications/ + open /Applications/FreeSO.app +else + INSTALL_DIR="$HOME/.local/share/FreeSO" + mkdir -p "$INSTALL_DIR" + rm -rf "$INSTALL_DIR"/* + cp -R "$PUBLISH_DIR"/* "$INSTALL_DIR/" + chmod +x "$INSTALL_DIR/FreeSO" + + mkdir -p "$HOME/.local/share/icons" + cp "$SCRIPT_DIR/fso.png" "$HOME/.local/share/icons/freeso.png" + + cat > "$HOME/.local/share/applications/FreeSO.desktop" << EOF +[Desktop Entry] +Name=FreeSO +Comment=Free re-implementation of The Sims Online +Exec=$INSTALL_DIR/FreeSO +Icon=$HOME/.local/share/icons/freeso.png +Terminal=false +Type=Application +Categories=Game; +EOF + + "$INSTALL_DIR/FreeSO" & +fi + +echo "Done." diff --git a/TSOClient/FSO.Unix/fso.ico b/TSOClient/FSO.Unix/fso.ico new file mode 100644 index 000000000..8bc0f1389 Binary files /dev/null and b/TSOClient/FSO.Unix/fso.ico differ diff --git a/TSOClient/FSO.Unix/fso.png b/TSOClient/FSO.Unix/fso.png new file mode 100644 index 000000000..aa94a6ccd Binary files /dev/null and b/TSOClient/FSO.Unix/fso.png differ diff --git a/TSOClient/FSO.Windows/App.config b/TSOClient/FSO.Windows/App.config deleted file mode 100644 index 074cce363..000000000 --- a/TSOClient/FSO.Windows/App.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSO.Windows/FSO.Windows.csproj b/TSOClient/FSO.Windows/FSO.Windows.csproj index 94df8a62f..14bdd7268 100644 --- a/TSOClient/FSO.Windows/FSO.Windows.csproj +++ b/TSOClient/FSO.Windows/FSO.Windows.csproj @@ -1,155 +1,105 @@ - - - + + - Debug - AnyCPU - {39201960-F96F-4039-84B1-1331D5DDE3C2} + net9.0-windows + enable + disable WinExe FSO.Windows FreeSO - v4.5 512 - true - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true + true + true + false + false + partial + fso.ico + true + true + true + + + + + fso.ico - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - + - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - - - - - - + + + - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer + + Icon.ico + + + Icon.bmp - - True - Resources.resx - True - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - + - - Designer - + + + + - - {635e68fa-3905-4943-b4f5-d463a8c02e87} - FSO.Client - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - + + + + + $(USERPROFILE)\.nuget\packages + $(HOME)/.nuget/packages + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + True + True + Settings.settings + + - - False - Microsoft .NET Framework 4.6.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - + + SettingsSingleFileGenerator + Settings.Designer.cs + - - \ No newline at end of file + + diff --git a/TSOClient/FSO.Windows/Platform/WindowsMultimediaTimerResolution.cs b/TSOClient/FSO.Windows/Platform/WindowsMultimediaTimerResolution.cs new file mode 100644 index 000000000..e8bb998ff --- /dev/null +++ b/TSOClient/FSO.Windows/Platform/WindowsMultimediaTimerResolution.cs @@ -0,0 +1,109 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace FSO.Windows.Platform +{ + [SupportedOSPlatform("windows")] + public partial class WindowsMultimediaTimerResolution : IDisposable + { + [StructLayout(LayoutKind.Sequential)] + public struct TimeCaps + { + public uint wPeriodMin; + public uint wPeriodMax; + } + + [LibraryImport("winmm.dll", EntryPoint = "timeGetDevCaps", SetLastError = true)] + private static partial uint TimeGetDevCaps(ref TimeCaps timeCaps, uint sizeTimeCaps); + + [LibraryImport("winmm.dll", EntryPoint = "timeBeginPeriod")] + private static partial uint TimeBeginPeriod(uint uMilliseconds); + + [LibraryImport("winmm.dll", EntryPoint = "timeEndPeriod")] + private static partial uint TimeEndPeriod(uint uMilliseconds); + + private uint _targetResolutionInMilliseconds; + private bool _isActive; + + /// + /// Create a new and activate the given resolution. + /// + /// + public WindowsMultimediaTimerResolution(uint targetResolutionInMilliseconds) + { + _targetResolutionInMilliseconds = targetResolutionInMilliseconds; + + EnsureResolutionSupport(); + Activate(); + } + + private void EnsureResolutionSupport() + { + TimeCaps timeCaps = default; + + uint result = TimeGetDevCaps(ref timeCaps, (uint)Unsafe.SizeOf()); + + if (result != 0) + { + Console.WriteLine($"timeGetDevCaps failed with result: {result}"); + } + else + { + uint supportedTargetResolutionInMilliseconds = Math.Min(Math.Max(timeCaps.wPeriodMin, _targetResolutionInMilliseconds), timeCaps.wPeriodMax); + + if (supportedTargetResolutionInMilliseconds != _targetResolutionInMilliseconds) + { + Console.WriteLine($"Target resolution isn't supported by OS, using closest resolution: {supportedTargetResolutionInMilliseconds}ms"); + + _targetResolutionInMilliseconds = supportedTargetResolutionInMilliseconds; + } + } + } + + private void Activate() + { + uint result = TimeBeginPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Console.WriteLine($"timeBeginPeriod failed with result: {result}"); + } + else + { + _isActive = true; + } + } + + private void Disable() + { + if (_isActive) + { + uint result = TimeEndPeriod(_targetResolutionInMilliseconds); + + if (result != 0) + { + Console.WriteLine($"timeEndPeriod failed with result: {result}"); + } + else + { + _isActive = false; + } + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Disable(); + } + } + } +} diff --git a/TSOClient/FSO.Windows/Program.cs b/TSOClient/FSO.Windows/Program.cs index 1636f1182..ffc67a281 100644 --- a/TSOClient/FSO.Windows/Program.cs +++ b/TSOClient/FSO.Windows/Program.cs @@ -1,12 +1,9 @@ -using System; -using System.IO; -using System.Drawing; +using FSO.Client; +using FSO.Client.UI.Panels; +using FSO.Common.Rendering.Framework.IO; +using FSO.Windows.Platform; using System.Drawing.Imaging; using System.Runtime.InteropServices; -using FSO.Common.Rendering.Framework.IO; -using FSO.Client; -using FSO.Client.UI.Panels; -using System.Windows.Forms; namespace FSO.Windows { @@ -18,17 +15,27 @@ public static class Program /// The main entry point for the application. /// + [STAThread] public static void Main(string[] args) { InitWindows(); + if ((new FSOProgram()).InitWithArguments(args)) - (new GameStartProxy()).Start(UseDX); + { + var startProxy = new GameStartProxy(); + startProxy.Start(UseDX); + } + + TimerControl?.Dispose(); } + public static IDisposable TimerControl; + public static void InitWindows() { //initialize some platform specific stuff FSO.Files.ImageLoaderHelpers.BitmapFunction = BitmapReader; + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); ClipboardHandler.Default = new WinFormsClipboard(); FSO.Files.ImageLoaderHelpers.SavePNGFunc = SavePNG; @@ -40,6 +47,45 @@ public static void InitWindows() AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; FSOProgram.ShowDialog = ShowDialog; + if (OperatingSystem.IsWindows()) + { + // Monogame sleeps between frames to control update timing, which is governed by the timer resolution. + // Windows timer precision is low by default, so push it to give us better frame timing. + // We could actually get 0.5ms timing with another method, but this is a lot hackier and not too important for us. + TimerControl = new WindowsMultimediaTimerResolution(1); + + // On linux and macos, timers are a lot more precise. + + FSOProgram.RegisterDragCallback = (window, callback) => + { + var bindThread = new Thread(x => + { + var form = System.Windows.Forms.Form.FromHandle(window) as System.Windows.Forms.Form; + form?.BeginInvoke(() => + { + form.AllowDrop = true; + form.DragEnter += (sender, e) => + { + if (e.Data.GetDataPresent(DataFormats.FileDrop)) + { + e.Effect = DragDropEffects.Copy; + } + else + { + e.Effect = DragDropEffects.None; + } + }; + form.DragDrop += (sender, e) => + { + var path = (e.Data.GetData(DataFormats.FileDrop) as string[])[0]; + callback(path); + }; + }); + }); + bindThread.SetApartmentState(ApartmentState.STA); + bindThread.Start(); + }; + } } public static void ShowDialog(string text) @@ -70,7 +116,9 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc Console.WriteLine("===== FATAL ERROR ====="); Console.WriteLine(e.ExceptionObject.ToString()); Environment.Exit(0); - } else { + } + else + { if (exception is OutOfMemoryException) { MessageBox.Show(e.ExceptionObject.ToString(), "Out of Memory! FreeSO needs to close."); @@ -95,7 +143,7 @@ public static void SavePNG(byte[] data, int width, int height, Stream str) if (bitmapData.Stride != image.Width * 4) throw new NotImplementedException(); - + for (int i = 0; i < data.Length; i += 4) { //if (data[i+3] == 0) { } @@ -128,12 +176,7 @@ public static Tuple BitmapReader(Stream str) Marshal.Copy(bitmapData.Scan0, data, 0, data.Length); image.UnlockBits(bitmapData); - for (int i = 0; i < data.Length; i += 4) - { - var temp = data[i]; - data[i] = data[i + 2]; - data[i + 2] = temp; - } + RGBToBGRSoft(data); return new Tuple(data, image.Width, image.Height); } @@ -143,6 +186,48 @@ public static Tuple BitmapReader(Stream str) } } + private static void RGBToBGROld(byte[] data) + { + for (int i = 0; i < data.Length; i += 4) + { + var temp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = temp; + } + } + + private const ulong MaskR = 0x000000FF000000FF; + private const ulong MaskB = 0x00FF000000FF0000; + private const ulong MaskElse = 0xFF00FF00FF00FF00; + + private unsafe static void RGBToBGRSoft(byte[] data) + { + // Do 8 bytes at a time with ulong. + // Could do this with an SSE shuffle, but .NET 4 doesn't have intrinsics. + + fixed (void* dataPtr = data) + { + ulong* longPtr = (ulong*)dataPtr; + + int longCount = data.Length / 8; + + for (int i = 0; i < longCount; i++) + { + ulong px = longPtr[i]; + longPtr[i] = ((px >> 16) & MaskR) | ((px << 16) & MaskB) | (px & MaskElse); + } + } + + if (data.Length % 8 != 0) + { + // Deal with the remainder. + int i = data.Length - 4; + var temp = data[i]; + data[i] = data[i + 2]; + data[i + 2] = temp; + } + } + // RGB to BGR convert Matrix private static float[][] rgbtobgr = new float[][] { diff --git a/TSOClient/FSO.Windows/Properties/AssemblyInfo.cs b/TSOClient/FSO.Windows/Properties/AssemblyInfo.cs deleted file mode 100644 index 33361828f..000000000 --- a/TSOClient/FSO.Windows/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FreeSO")] -[assembly: AssemblyDescription("Reimplementation of The Sims Online")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("FreeSO Team")] -[assembly: AssemblyProduct("FreeSO")] -[assembly: AssemblyCopyright("")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("39201960-f96f-4039-84b1-1331d5dde3c2")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSO.Windows/Properties/Settings.Designer.cs b/TSOClient/FSO.Windows/Properties/Settings.Designer.cs index e3fffa6a2..5475465a1 100644 --- a/TSOClient/FSO.Windows/Properties/Settings.Designer.cs +++ b/TSOClient/FSO.Windows/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace FSO.Windows.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.7.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/TSOClient/FSO.Windows/Properties/launchSettings.json b/TSOClient/FSO.Windows/Properties/launchSettings.json new file mode 100644 index 000000000..2ef563688 --- /dev/null +++ b/TSOClient/FSO.Windows/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "profiles": { + "FSO.Windows": { + "commandName": "Project" + }, + "FSO.Windows 3D": { + "commandName": "Project", + "commandLineArgs": "-3d" + }, + "FSO.Windows GL": { + "commandName": "Project", + "commandLineArgs": "-gl" + } + } +} \ No newline at end of file diff --git a/TSOClient/FSO.Windows/UITTSContext.cs b/TSOClient/FSO.Windows/UITTSContext.cs index 9e0c51535..014a50372 100644 --- a/TSOClient/FSO.Windows/UITTSContext.cs +++ b/TSOClient/FSO.Windows/UITTSContext.cs @@ -1,7 +1,6 @@ using FSO.Client.UI.Panels; using FSO.Common.Utils; using Microsoft.Xna.Framework.Audio; -using System; namespace FSO.Windows { @@ -20,23 +19,23 @@ public override void Dispose() { } - public override void Speak(string text, bool gender, int ipitch) + public override void Speak(string text, bool gender, int ipitch, uint persistID) { + var voiceGender = (gender) ? System.Speech.Synthesis.VoiceGender.Female : System.Speech.Synthesis.VoiceGender.Male; var Synth = new System.Speech.Synthesis.SpeechSynthesizer(); - try - { - Synth.SelectVoiceByHints((gender) ? System.Speech.Synthesis.VoiceGender.Female : System.Speech.Synthesis.VoiceGender.Male); - } catch - { - //couldnt find any tts voices... - return; - } if (text == "") return; + var voci = Synth.GetInstalledVoices(); + var genderVoices = voci.Where(x => x.VoiceInfo.Gender == voiceGender).ToArray(); + if (genderVoices.Length == 0) return; + + uint voiceInd = persistID % (uint)genderVoices.Length; + Synth.SelectVoice(genderVoices[(int)voiceInd].VoiceInfo.Name); + var stream = new System.IO.MemoryStream(); - var pitch = Math.Max(0.1f, ipitch/100f + 1f); //below 0.1 is just stupid, so just clamp there. + var pitch = Math.Max(0.1f, ipitch / 100f + 1f); //below 0.1 is just stupid, so just clamp there. if (pitch < 1f) - Synth.Rate = 10-(int)(pitch*10); + Synth.Rate = 10 - (int)(pitch * 10); else Synth.Rate = (int)(10 / pitch) - 10; Synth.SetOutputToWaveStream(stream); diff --git a/TSOClient/FSO.Windows/packages.config b/TSOClient/FSO.Windows/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/FSO.Windows/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/FSOFacadeWorker/App.config b/TSOClient/FSOFacadeWorker/App.config deleted file mode 100644 index 7c419c042..000000000 --- a/TSOClient/FSOFacadeWorker/App.config +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/FSOFacadeWorker/FSOFacadeWorker.csproj b/TSOClient/FSOFacadeWorker/FSOFacadeWorker.csproj index 46d66d25a..f5bf3da9f 100644 --- a/TSOClient/FSOFacadeWorker/FSOFacadeWorker.csproj +++ b/TSOClient/FSOFacadeWorker/FSOFacadeWorker.csproj @@ -1,172 +1,54 @@ - - - - + + - Debug - AnyCPU - {0241317C-CC82-43D1-9ABF-40F3635DC41C} + net9.0-windows + enable + disable Exe Properties FSOFacadeWorker FSOFacadeWorker - v4.6.1 512 - true - - - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - true + partial + True + - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\SixLabors.Core.1.0.0-beta0008\lib\netstandard2.0\SixLabors.Core.dll - - - ..\packages\SixLabors.ImageSharp.1.0.0-beta0007\lib\netstandard2.0\SixLabors.ImageSharp.dll - - - - ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll - - - - ..\packages\System.Memory.4.5.1\lib\netstandard2.0\System.Memory.dll - - - - ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.1\lib\netstandard2.0\System.Runtime.CompilerServices.Unsafe.dll - - - - - - - - - - - - - - + + - - - + + + - - {7de47032-a904-4c29-bd22-2d235e8d91ba} - MonoGame.Framework.Windows - - - {5deb20eb-1eb7-48f9-922c-463abae56e63} - FSO.IDE - - - {329e0aee-7871-40a7-b5af-8c0d0086ef71} - FSO.Server.Clients - - - {39201960-f96f-4039-84b1-1331d5dde3c2} - FSO.Windows - - - {635e68fa-3905-4943-b4f5-d463a8c02e87} - FSO.Client - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - - - {c0068df7-f2e8-4399-846d-556bf9a35c00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {5eddefd2-c850-49c1-812d-ddeff09125ef} - FSO.SimAntics - - - {072781d8-51ec-4143-9cae-daf50177d3ad} - FSO.HIT - - - {fd7957f7-a1e0-4d00-8f6c-3fa555eaa163} - FSO.Vitaboy.Engine - - - {9d9558a9-755e-43f9-8bb6-b26f365f5042} - FSO.Vitaboy - - - {b1a6e4c2-e080-4c34-a604-d11b5296a9b8} - FSO.LotView - + + + + + + + + + + + + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - \ No newline at end of file + + + $(USERPROFILE)\.nuget\packages + $(HOME)/.nuget/packages + + + + + + + + + + + + diff --git a/TSOClient/FSOFacadeWorker/GraphicsDeviceServiceMock.cs b/TSOClient/FSOFacadeWorker/GraphicsDeviceServiceMock.cs index 88f806590..7a678c1c8 100644 --- a/TSOClient/FSOFacadeWorker/GraphicsDeviceServiceMock.cs +++ b/TSOClient/FSOFacadeWorker/GraphicsDeviceServiceMock.cs @@ -26,7 +26,9 @@ public GraphicsDeviceServiceMock() IsFullScreen = false }; - _GraphicsDevice = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, Parameters); + var adapter = GraphicsAdapter.DefaultAdapter; + + _GraphicsDevice = new GraphicsDevice(adapter, GraphicsProfile.HiDef, Parameters); _GraphicsDevice.Present(); } diff --git a/TSOClient/FSOFacadeWorker/Program.cs b/TSOClient/FSOFacadeWorker/Program.cs index 0f38b151e..b89ed3fe9 100644 --- a/TSOClient/FSOFacadeWorker/Program.cs +++ b/TSOClient/FSOFacadeWorker/Program.cs @@ -18,11 +18,6 @@ using Microsoft.Xna.Framework.Graphics; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; namespace FSOFacadeWorker { @@ -145,7 +140,7 @@ static void Main(string[] args) public static void Login() { api = new ApiClient(Config.Api_Url); - api.AdminLogin(Config.User, Config.Password, (result) => + _ = api.AdminLoginAsync(Config.User, Config.Password, (result) => { if (!result) { @@ -160,16 +155,15 @@ public static void Login() } else { - api.GetLotList(1, (lots) => + _ = api.GetLotList(1, (lots) => { Console.WriteLine("Got a lot list for full thumbnail rebake."); - //LotQueue.AddRange(lots); - //TotalLotNum += lots.Length; - //for (int i = 0; i < 4000; i++) - //{ - // LotQueue.RemoveAt(0); - //} - RenderLot(); + //LotQueue.AddRange(lots); + //TotalLotNum += lots.Length; + //for (int i = 0; i < 4000; i++) + //{ + // LotQueue.RemoveAt(0); + //} RenderLot(); }); } @@ -188,13 +182,16 @@ private static void SaveRawImage(byte[] data, int width, int height, string path } var image = Image.LoadPixelData(data, width, height); - using (var stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) { + using (var stream = File.Open(path, FileMode.Create, FileAccess.Write, FileShare.None)) + { image.SaveAsPng(stream); } } private static void RenderStandaloneDebug(uint shard, uint location) { + GD.Present(); + Console.WriteLine("===== Trying standalone render for " + location + "! ====="); api.GetFSOV((uint)shard, location, (bt) => { @@ -206,7 +203,9 @@ private static void RenderStandaloneDebug(uint shard, uint location) } else { - var fsof = RenderFSOF(bt, GD, false); + bool compressed = true; + byte[] thumbData = null; + var fsof = RenderFSOF(bt, GD, compressed, (data) => thumbData = data); Directory.CreateDirectory("test/"); using (var mem = new MemoryStream()) { @@ -214,10 +213,14 @@ private static void RenderStandaloneDebug(uint shard, uint location) File.WriteAllBytes("test/" + location + ".fsof", mem.ToArray()); } //save the images - SaveRawImage(fsof.FloorTextureData, fsof.FloorWidth, fsof.FloorHeight, "test/" + location + "_floor.png"); - SaveRawImage(fsof.WallTextureData, fsof.WallWidth, fsof.WallHeight, "test/" + location + "_wall.png"); - SaveRawImage(fsof.NightFloorTextureData, fsof.FloorWidth, fsof.FloorHeight, "test/" + location + "_nfloor.png"); - SaveRawImage(fsof.NightWallTextureData, fsof.WallWidth, fsof.WallHeight, "test/" + location + "n_wall.png"); + File.WriteAllBytes("test/" + location + "_thumb.png", thumbData); + if (!compressed) + { + SaveRawImage(fsof.FloorTextureData, fsof.FloorWidth, fsof.FloorHeight, "test/" + location + "_floor.png"); + SaveRawImage(fsof.WallTextureData, fsof.WallWidth, fsof.WallHeight, "test/" + location + "_wall.png"); + SaveRawImage(fsof.NightFloorTextureData, fsof.FloorWidth, fsof.FloorHeight, "test/" + location + "_nfloor.png"); + SaveRawImage(fsof.NightWallTextureData, fsof.WallWidth, fsof.WallHeight, "test/" + location + "n_wall.png"); + } Console.WriteLine("===== Done! ====="); } } @@ -232,7 +235,7 @@ private static void RenderStandaloneDebug(uint shard, uint location) private static void RenderLot() { GD.Present(); - Console.WriteLine("Requesting work..."); + Console.WriteLine($"Requesting work... ({Done} so far)"); api.GetWork((shard, location) => { @@ -245,7 +248,8 @@ private static void RenderLot() LoginSent = false; GameThread.OnWork.Set(); return; - } else + } + else { if (Config.Sleep_Time == 0) { @@ -267,12 +271,14 @@ private static void RenderLot() { if (bt == null) { + Console.WriteLine("Missing FSOV for " + location + "..."); RenderLot(); } else { Console.WriteLine("Rendering lot " + location + "..."); - var fsof = RenderFSOF(bt, GD, true); + byte[] thumbData = null; + var fsof = RenderFSOF(bt, GD, true, (data) => thumbData = data); using (var mem = new MemoryStream()) { fsof.Save(mem); @@ -295,6 +301,14 @@ private static void RenderLot() } RenderLot(); }); + + api.UploadThumb(1, location, thumbData, (success) => + { + if (!success) + { + Console.WriteLine("Uploading thumb for " + location + " did not succeed."); + } + }); } } } @@ -314,19 +328,20 @@ public static void WorkerLoop() int loginAttempts = 0; while (true) { + GD.Present(); if (!LoginSent) { LoginSent = true; - Console.WriteLine("Attempting Login... ("+(loginAttempts++)+")"); + Console.WriteLine("Attempting Login... (" + (loginAttempts++) + ")"); Login(); } - GameThread.OnWork.WaitOne(1000); + GameThread.OnWork.WaitOne(50); GameThread.DigestUpdate(null); } - + } - public static FSOF RenderFSOF(byte[] fsov, GraphicsDevice gd, bool compressed) + public static FSOF RenderFSOF(byte[] fsov, GraphicsDevice gd, bool compressed, Action thumbAction = null) { var marshal = new VMMarshal(); using (var mem = new MemoryStream(fsov)) @@ -355,6 +370,20 @@ public static FSOF RenderFSOF(byte[] fsov, GraphicsDevice gd, bool compressed) SetAllLights(vm, world, 0.5f, 0); + if (thumbAction != null) + { + var bigThumb = world.GetLotThumb(gd, null); + byte[] data; + using (var stream = new MemoryStream()) + { + var tex = TextureUtils.Decimate(bigThumb, gd, 2, false); + tex.SaveAsPng(stream, bigThumb.Width / 2, bigThumb.Height / 2); + data = stream.ToArray(); + tex.Dispose(); + } + thumbAction(data); + } + var result = facade.GetFSOF(gd, world, vm.Context.Blueprint, () => { SetAllLights(vm, world, 0.0f, 100); }, compressed); Layer.Remove(world); @@ -391,4 +420,4 @@ private static void SetOutsideTime(GraphicsDevice gd, VM vm, World world, float vm.Context.Architecture.SetTimeOfDay(); } } -} +} \ No newline at end of file diff --git a/TSOClient/FSOFacadeWorker/Properties/AssemblyInfo.cs b/TSOClient/FSOFacadeWorker/Properties/AssemblyInfo.cs deleted file mode 100644 index a9df21b7d..000000000 --- a/TSOClient/FSOFacadeWorker/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSOFacadeWorker")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSOFacadeWorker")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("0241317c-cc82-43d1-9abf-40f3635dc41c")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/FSOFacadeWorker/facadeconfig.sample.json b/TSOClient/FSOFacadeWorker/facadeconfig.sample.json index 61789b85e..bec8e513e 100644 --- a/TSOClient/FSOFacadeWorker/facadeconfig.sample.json +++ b/TSOClient/FSOFacadeWorker/facadeconfig.sample.json @@ -5,6 +5,6 @@ "api_url": "https://api.freeso.org", "user": "admin", "password": "yourpassword", - "limit": 1000, + "limit": 1000000, "sleep_time": 30000 } \ No newline at end of file diff --git a/TSOClient/FSOFacadeWorker/packages.config b/TSOClient/FSOFacadeWorker/packages.config deleted file mode 100644 index 238ca3744..000000000 --- a/TSOClient/FSOFacadeWorker/packages.config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/FreeSO.sln b/TSOClient/FreeSO.sln index fa6769890..2bddceabc 100644 --- a/TSOClient/FreeSO.sln +++ b/TSOClient/FreeSO.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31919.166 +# Visual Studio Version 18 +VisualStudioVersion = 18.6.11723.189 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.Client", "tso.client\FSO.Client.csproj", "{635E68FA-3905-4943-B4F5-D463A8C02E87}" EndProject @@ -44,23 +44,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.Common.Domain", "FSO.Common.Domain\FSO.Common.Domain.csproj", "{9848FAF5-444A-48CC-A26A-8115D8C4FB52}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.IDE", "FSO.IDE\FSO.IDE.csproj", "{5DEB20EB-1EB7-48F9-922C-463ABAE56E63}" - ProjectSection(ProjectDependencies) = postProject - {6D75E618-19CA-4C51-9546-F10965FBC0B8} = {6D75E618-19CA-4C51-9546-F10965FBC0B8} - {35253CE1-C864-4CD3-8249-4D1319748E8F} = {35253CE1-C864-4CD3-8249-4D1319748E8F} - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.WindowsGL", "..\Other\libs\FSOMonoGame\MonoGame.Framework\MonoGame.Framework.WindowsGL.csproj", "{6D75E618-19CA-4C51-9546-F10965FBC0B8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.Windows", "..\Other\libs\FSOMonoGame\MonoGame.Framework\MonoGame.Framework.Windows.csproj", "{7DE47032-A904-4C29-BD22-2D235E8D91BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.Linux", "..\Other\libs\FSOMonoGame\MonoGame.Framework\MonoGame.Framework.Linux.csproj", "{35253CE1-C864-4CD3-8249-4D1319748E8F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimplePaletteQuantizer", "..\Other\libs\ColorQuantizer\SimplePaletteQuantizer\SimplePaletteQuantizer.csproj", "{37812A22-91F3-4220-891E-5C26DA64A975}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mp3Sharp", "..\Other\libs\mp3sharp\mp3sharp\Mp3Sharp.csproj", "{834CAB58-648D-47CC-AC6F-D01C08C809A4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.Net.WindowsGL", "..\Other\libs\FSOMonoGame\MonoGame.Framework\MonoGame.Framework.Net.WindowsGL.csproj", "{6D6009F4-0AFB-4806-89D7-7945F20270F5}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lidgren.Network.WindowsGL", "..\Other\libs\FSOMonoGame\ThirdParty\Lidgren.Network\Lidgren.Network.WindowsGL.csproj", "{AE483C29-042E-4226-BA52-D247CE7676DA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TargaImagePCL", "..\Other\libs\TargaImagePCL\TargaImagePCL.csproj", "{D8232422-9D79-4200-A981-EB70ED82CCF3}" @@ -98,14 +84,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSDFData", "..\Other\libs\M EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSDFExtension", "..\Other\libs\MSDFExtension\MSDFExtension.csproj", "{EBF08DC7-916D-4133-BADE-38C31E29F18A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoGame.Framework.Content.Pipeline.Windows", "..\Other\libs\FSOMonoGame\MonoGame.Framework.Content.Pipeline\MonoGame.Framework.Content.Pipeline.Windows.csproj", "{B950DE10-AC5D-4BD9-B817-51247C4A732D}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.SimAntics.JIT", "FSO.SimAntics.JIT\FSO.SimAntics.JIT.csproj", "{B8AB3711-7B4F-4126-9BF3-4DDDE9475B74}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FSO.SimAntics.JIT.Roslyn", "FSO.SimAntics.JIT.Roslyn\FSO.SimAntics.JIT.Roslyn.csproj", "{B3DE74C1-B7A1-4773-BD36-993988B23527}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.Content.TSO", "FSO.Content.TSO\FSO.Content.TSO.csproj", "{B5B2C04D-B8E4-47C7-9731-48E30FD5F70D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.Unix", "FSO.Unix\FSO.Unix.csproj", "{69DBDA50-1616-4B1B-9295-78E130C5119E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FSO.Patcher.Unix", "FSO.Patcher.Unix\FSO.Patcher.Unix.csproj", "{2AF8665E-8A5B-4F32-B9EB-66930D65964B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Android|Any CPU = Android|Any CPU @@ -131,8 +119,8 @@ Global {635E68FA-3905-4943-B4F5-D463A8C02E87}.Android|Any CPU.ActiveCfg = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Android|iPhone.ActiveCfg = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Android|iPhoneSimulator.ActiveCfg = Release|x86 - {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|Any CPU.ActiveCfg = Debug|x86 - {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|Any CPU.Build.0 = Debug|x86 + {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|Any CPU.Build.0 = Debug|Any CPU {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|iPhone.ActiveCfg = Debug|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|iPhone.Build.0 = Debug|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86 @@ -140,8 +128,8 @@ Global {635E68FA-3905-4943-B4F5-D463A8C02E87}.iOS|Any CPU.ActiveCfg = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.iOS|iPhone.ActiveCfg = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.iOS|iPhoneSimulator.ActiveCfg = Release|x86 - {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|Any CPU.ActiveCfg = Release|x86 - {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|Any CPU.Build.0 = Release|x86 + {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|Any CPU.Build.0 = Release|Any CPU {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|iPhone.ActiveCfg = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|iPhone.Build.0 = Release|x86 {635E68FA-3905-4943-B4F5-D463A8C02E87}.Release|iPhoneSimulator.ActiveCfg = Release|x86 @@ -782,8 +770,8 @@ Global {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Android|Any CPU.Build.0 = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Android|iPhone.ActiveCfg = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|Any CPU.ActiveCfg = Debug|x86 - {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|Any CPU.Build.0 = Debug|x86 + {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|Any CPU.Build.0 = Debug|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|iPhone.ActiveCfg = Debug|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|iPhone.Build.0 = Debug|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU @@ -792,8 +780,8 @@ Global {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.iOS|Any CPU.Build.0 = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.iOS|iPhone.ActiveCfg = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|Any CPU.ActiveCfg = Release|x86 - {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|Any CPU.Build.0 = Release|x86 + {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|Any CPU.Build.0 = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|iPhone.ActiveCfg = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|iPhone.Build.0 = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU @@ -808,96 +796,6 @@ Global {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Windows|Any CPU.Build.0 = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Windows|iPhone.ActiveCfg = Release|Any CPU {5DEB20EB-1EB7-48F9-922C-463ABAE56E63}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Android|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Android|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Android|iPhone.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|iPhone.Build.0 = Debug|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.iOS|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.iOS|iPhone.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|iPhone.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|iPhone.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|iPhone.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Windows|Any CPU.Build.0 = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Windows|iPhone.ActiveCfg = Release|Any CPU - {6D75E618-19CA-4C51-9546-F10965FBC0B8}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Android|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Android|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Android|iPhone.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|iPhone.Build.0 = Debug|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.iOS|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.iOS|iPhone.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|iPhone.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|iPhone.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|iPhone.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Windows|Any CPU.Build.0 = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Windows|iPhone.ActiveCfg = Release|Any CPU - {7DE47032-A904-4C29-BD22-2D235E8D91BA}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Android|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Android|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Android|iPhone.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|iPhone.Build.0 = Debug|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.iOS|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.iOS|iPhone.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|iPhone.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|iPhone.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|iPhone.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Windows|Any CPU.Build.0 = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Windows|iPhone.ActiveCfg = Release|Any CPU - {35253CE1-C864-4CD3-8249-4D1319748E8F}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU {37812A22-91F3-4220-891E-5C26DA64A975}.Android|Any CPU.ActiveCfg = Release|Any CPU {37812A22-91F3-4220-891E-5C26DA64A975}.Android|Any CPU.Build.0 = Release|Any CPU {37812A22-91F3-4220-891E-5C26DA64A975}.Android|iPhone.ActiveCfg = Release|Any CPU @@ -928,72 +826,6 @@ Global {37812A22-91F3-4220-891E-5C26DA64A975}.Windows|Any CPU.Build.0 = Release|Any CPU {37812A22-91F3-4220-891E-5C26DA64A975}.Windows|iPhone.ActiveCfg = Release|Any CPU {37812A22-91F3-4220-891E-5C26DA64A975}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Android|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Android|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Android|iPhone.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|iPhone.Build.0 = Debug|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.iOS|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.iOS|iPhone.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|iPhone.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|iPhone.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|iPhone.ActiveCfg = ServerRelease|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|iPhone.Build.0 = ServerRelease|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|iPhoneSimulator.ActiveCfg = ServerRelease|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.ServerRelease|iPhoneSimulator.Build.0 = ServerRelease|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Windows|Any CPU.Build.0 = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Windows|iPhone.ActiveCfg = Release|Any CPU - {834CAB58-648D-47CC-AC6F-D01C08C809A4}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|iPhone.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|iPhone.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Android|iPhoneSimulator.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|iPhone.Build.0 = Debug|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|iPhone.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|iPhone.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.iOS|iPhoneSimulator.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|iPhone.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|iPhone.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|iPhone.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|Any CPU.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|iPhone.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|iPhone.Build.0 = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {6D6009F4-0AFB-4806-89D7-7945F20270F5}.Windows|iPhoneSimulator.Build.0 = Release|Any CPU {AE483C29-042E-4226-BA52-D247CE7676DA}.Android|Any CPU.ActiveCfg = Release|Any CPU {AE483C29-042E-4226-BA52-D247CE7676DA}.Android|Any CPU.Build.0 = Release|Any CPU {AE483C29-042E-4226-BA52-D247CE7676DA}.Android|iPhone.ActiveCfg = Release|Any CPU @@ -1300,8 +1132,8 @@ Global {0241317C-CC82-43D1-9ABF-40F3635DC41C}.iOS|iPhone.Build.0 = Release|Any CPU {0241317C-CC82-43D1-9ABF-40F3635DC41C}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU {0241317C-CC82-43D1-9ABF-40F3635DC41C}.iOS|iPhoneSimulator.Build.0 = Release|Any CPU - {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|Any CPU.ActiveCfg = Release|x86 - {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|Any CPU.Build.0 = Release|x86 + {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|Any CPU.Build.0 = Release|Any CPU {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|iPhone.ActiveCfg = Release|Any CPU {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|iPhone.Build.0 = Release|Any CPU {0241317C-CC82-43D1-9ABF-40F3635DC41C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU @@ -1568,8 +1400,8 @@ Global {EBF08DC7-916D-4133-BADE-38C31E29F18A}.iOS|iPhone.Build.0 = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.iOS|iPhoneSimulator.Build.0 = Release|Any CPU - {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|Any CPU.Build.0 = Debug|Any CPU + {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|Any CPU.Build.0 = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|iPhone.ActiveCfg = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|iPhone.Build.0 = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU @@ -1586,42 +1418,6 @@ Global {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Windows|iPhone.Build.0 = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU {EBF08DC7-916D-4133-BADE-38C31E29F18A}.Windows|iPhoneSimulator.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|Any CPU.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|Any CPU.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|iPhone.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|iPhone.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Android|iPhoneSimulator.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|iPhone.ActiveCfg = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|iPhone.Build.0 = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|Any CPU.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|Any CPU.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|iPhone.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|iPhone.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.iOS|iPhoneSimulator.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|Any CPU.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|iPhone.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|iPhone.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|Any CPU.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|iPhone.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|Any CPU.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|Any CPU.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|iPhone.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|iPhone.Build.0 = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU - {B950DE10-AC5D-4BD9-B817-51247C4A732D}.Windows|iPhoneSimulator.Build.0 = Release|Any CPU {B8AB3711-7B4F-4126-9BF3-4DDDE9475B74}.Android|Any CPU.ActiveCfg = Release|Any CPU {B8AB3711-7B4F-4126-9BF3-4DDDE9475B74}.Android|Any CPU.Build.0 = Release|Any CPU {B8AB3711-7B4F-4126-9BF3-4DDDE9475B74}.Android|iPhone.ActiveCfg = Release|Any CPU @@ -1730,6 +1526,78 @@ Global {B5B2C04D-B8E4-47C7-9731-48E30FD5F70D}.Windows|iPhone.Build.0 = Debug|Any CPU {B5B2C04D-B8E4-47C7-9731-48E30FD5F70D}.Windows|iPhoneSimulator.ActiveCfg = Debug|Any CPU {B5B2C04D-B8E4-47C7-9731-48E30FD5F70D}.Windows|iPhoneSimulator.Build.0 = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|Any CPU.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|Any CPU.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|iPhone.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|iPhone.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Android|iPhoneSimulator.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|iPhone.Build.0 = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|Any CPU.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|Any CPU.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|iPhone.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|iPhone.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|iPhoneSimulator.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.iOS|iPhoneSimulator.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|Any CPU.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|iPhone.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|iPhone.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|Any CPU.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|iPhone.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|Any CPU.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|iPhone.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|iPhone.Build.0 = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU + {69DBDA50-1616-4B1B-9295-78E130C5119E}.Windows|iPhoneSimulator.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|Any CPU.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|Any CPU.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|iPhone.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|iPhone.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Android|iPhoneSimulator.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|iPhone.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|Any CPU.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|Any CPU.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|iPhone.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|iPhone.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.iOS|iPhoneSimulator.Build.0 = Debug|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|Any CPU.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|iPhone.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|iPhone.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|Any CPU.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|Any CPU.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|iPhone.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|iPhone.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.ServerRelease|iPhoneSimulator.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|Any CPU.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|Any CPU.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|iPhone.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|iPhone.Build.0 = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2AF8665E-8A5B-4F32-B9EB-66930D65964B}.Windows|iPhoneSimulator.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/TSOClient/tso.client/Content/ArchiveCities/Empty/archive.ini b/TSOClient/tso.client/Content/ArchiveCities/Empty/archive.ini new file mode 100644 index 000000000..b79bc3ef5 --- /dev/null +++ b/TSOClient/tso.client/Content/ArchiveCities/Empty/archive.ini @@ -0,0 +1,9 @@ +# Archive manifest +Name=New... +Description=New archive template - for use with the city selector UI. +Size=0 +Map=0001 +ZipLocation= +ZipHash= +LocalDir=data/ +Template=True \ No newline at end of file diff --git a/TSOClient/tso.client/Content/ArchiveCities/Empty/data/events.json b/TSOClient/tso.client/Content/ArchiveCities/Empty/data/events.json new file mode 100644 index 000000000..c7bd2accd --- /dev/null +++ b/TSOClient/tso.client/Content/ArchiveCities/Empty/data/events.json @@ -0,0 +1,235 @@ +{ + "timed": true, + "catalog": [ + { + "label": "Spring", + "value": 1, + "startDate": "1-3", + "endDate": "1-6" + }, + { + "label": "Summer", + "value": 2, + "startDate": "1-6", + "endDate": "1-9" + }, + { + "label": "Autumn", + "value": 4, + "startDate": "1-9", + "endDate": "1-12" + }, + { + "label": "Winter", + "value": 8, + "startDate": "1-12", + "endDate": "1-3" + }, + { + "label": "Debug", + "value": 16 + } + ], + "modifiers": [ + { + "name": "spring", + "label": "Spring", + "type": "seasonal", + "startDate": "1-3", + "endDate": "1-4", + "options": [] + }, + { + "name": "af", + "label": "April Fools", + "type": "seasonal", + "startDate": "1-4", + "endDate": "2-4", + "options": [ + { + "name": "fire", + "label": "Enable Fire (2017)", + "category": "Effects", + "tuning": { + "special:0:0": 1 + }, + "enableTimed": true + }, + { + "name": "emoji", + "label": "Emoji Only (2018)", + "category": "Effects", + "tuning": { + "ui:0:0": 1 + }, + "enableTimed": false + }, + { + "name": "pizza", + "label": "Pizza Roulette (2018)", + "category": "Effects", + "tuning": { + "global.iff:44:0": 1 + }, + "enableTimed": true + }, + { + "name": "hat", + "label": "Hat Towers (2020)", + "category": "Effects", + "tuning": { + "global.iff:44:1": 1 + }, + "enableTimed": true + }, + { + "name": "motives", + "label": "Inverted Motives (2020)", + "category": "Effects", + "tuning": { + "aprilfools:0:2020": 1 + }, + "enableTimed": false + }, + { + "name": "amongso", + "label": "AmongSO (2021)", + "category": "Effects", + "tuning": { + "global.iff:44:2": 1 + }, + "enableTimed": true + }, + { + "name": "platformer", + "label": "Platformer (2022, needs files)", + "category": "Effects", + "tuning": { + "platformer:0:0": 1 + }, + "enableTimed": false + }, + { + "name": "peeso", + "label": "PeeSO (2024)", + "category": "Effects", + "tuning": { + "aprilfools:0:2019": 1, + "personglobals.iff:11:0": 1 + }, + "enableTimed": true + } + ] + }, + { + "name": "summer", + "label": "Summer", + "type": "seasonal", + "startDate": "1-7", + "endDate": "1-8", + "options": [ + { + "name": "heatwave", + "label": "Heatwave", + "category": "Weather", + "unique": "weather", + "tuning": { + "city:0:0": 1 + }, + "enableTimed": false + }, + { + "name": "fructose", + "label": "Fructose Monsoon", + "category": "Weather", + "unique": "weather", + "tuning": { + "city:0:2": 1 + }, + "enableTimed": false + } + ] + }, + { + "name": "halloween", + "label": "Halloween", + "type": "seasonal", + "startDate": "24-10", + "endDate": "1-11", + "options": [ + { + "name": "deadworld", + "label": "Dead World", + "category": "Weather & Extras", + "unique": "weather", + "tuning": { + "city:0:0": 2, + "city:0:2": 2, + "special:0:2": 0.01 + }, + "enableTimed": true + }, + { + "name": "candy", + "label": "Candy & Zombies", + "category": "Weather & Extras", + "tuning": { + "global.iff:44:3": 1 + }, + "enableTimed": true + } + ] + }, + { + "name": "winter", + "label": "Winter", + "type": "seasonal", + "startDate": "12-12", + "endDate": "29-1", + "options": [ + { + "name": "snowfall", + "label": "Snowfall", + "category": "Weather & Extras", + "unique": "weather", + "tuning": { + "global.iff:44:6": 1, + "city:0:0": 0 + }, + "enableTimed": true + }, + { + "name": "snowball", + "label": "Snowball Fights", + "category": "Weather & Extras", + "tuning": { + "global.iff:44:4": 1 + }, + "enableTimed": true + }, + { + "name": "tree", + "label": "Christmas Tree", + "category": "Weather & Extras", + "tuning": { + "global.iff:44:5": 1 + }, + "gift": { + "title": "Merry Christmas!", + "description": "It's that time of year again, can you believe it? A beautifully trimmed christmas tree has been delivered to your inventory to celebrate the occasion.\n Place your new tree and some freshly baked cookies next to a fireplace, and you might be in for a 12am celebrity visit...", + "guids": [ + 719571418 + ] + }, + "enableTimed": true, + "startDate": "18-12", + "endDate": "26-12" + } + ] + } + ], + "skillSpeed": 5.0, + "payoutScale": 5.0, + "singleplayerPenalty": 0.0, + "speedyJobProgression": 1 +} \ No newline at end of file diff --git a/TSOClient/tso.client/Content/ArchiveCities/Empty/data/fsoarchive.db b/TSOClient/tso.client/Content/ArchiveCities/Empty/data/fsoarchive.db new file mode 100644 index 000000000..54024fa35 Binary files /dev/null and b/TSOClient/tso.client/Content/ArchiveCities/Empty/data/fsoarchive.db differ diff --git a/TSOClient/tso.client/Content/ArchiveCities/FreeSO Archive/archive.ini b/TSOClient/tso.client/Content/ArchiveCities/FreeSO Archive/archive.ini new file mode 100644 index 000000000..7eb20e295 --- /dev/null +++ b/TSOClient/tso.client/Content/ArchiveCities/FreeSO Archive/archive.ini @@ -0,0 +1,9 @@ +# Archive manifest +Name=FreeSO Archive +Description=Archived data from the official FreeSO server from December 6th, 2024. +ZipSize=1685424672 +Size=2460946754 +Map=0100 +ZipLocation=http://localhost:8080/archive_test_2.zip +ZipHash= +LocalDir= \ No newline at end of file diff --git a/TSOClient/tso.client/Content/UI/hints/english.dir/lot_finaltownhall.json b/TSOClient/tso.client/Content/UI/hints/english.dir/lot_finaltownhall.json new file mode 100644 index 000000000..6a77b0c78 --- /dev/null +++ b/TSOClient/tso.client/Content/UI/hints/english.dir/lot_finaltownhall.json @@ -0,0 +1,10 @@ +{ + "guid": "eaf0cc8b-302a-49c5-9fab-2492d496f4ff", + "trigger": "lot:Sunrise Crater:7209277", + "title": "The Final Town Hall", + "category": "Archive Mode", + "image": "hint_finaltownhall.png", + "body": "[s][color=white]Welcome to Sunrise Crater![/color][/s] You've landed in the [s]Final Town Hall[/s] - a celebration of FreeSO built by the last four mayors elected by the community, and a hub for exploring the best the city has to offer. \n\nThis island is filled with [s]\"Lot Links\"[/s] - objects that act as a gateway to important properties around the city:\n\n[s][color=white]Event Lots[/color][/s]\n[s]In front of the \"busses\" in the central plaza[/s], you'll find links to every FreeSO event lot. Specially created for seasonal events (such as Halloween, Christmas and April Fools day), most event lots have a unique challenge to complete, or have city-wide effects if you configure the server's Seasonal Events. [color=#B3FF99]Try them out with friends![/color]\n\n[s][color=white]Player's Choice[/color][/s]\nIn FreeSO's final week, we asked the community for some unforgettable properties to immortalize with links from The Final Town Hall. The top 8 are featured [s]on top of the hill at the back of the town hall[/s], so make sure you give them a visit!\n\n[s][color=white]M.O.M.I's Choice[/color][/s]\n[s]In the hotel to the right of the plaza[/s], you'll find more lot links to properties that didn't quite make the top 8 in the Player's Choice vote, but that members of the FreeSO team felt were an important part of Sunrise Crater history.\n\n[s][color=white]AFA Winners[/color][/s]\nIf you're looking for even more interesting spots, check out the category filters in city view. Each property featured is a past winner of the Annual FreeSO Awards (AFAs) for that category. Still not satisfied? Simply scroll around and pick something that looks cool! FreeSO lots are filled with a rich history, told through signs, lot links and even object placements. [color=#B3FF99]See what you can discover![/color]\n\nCheck out signs around the property for extra tips, and most importantly, have fun!\n\n[s][color=#B3FF99]NOTE: You won't land here automatically in future, but the property have a shortcut visible each time you enter the city.[/color][/s]", + "bodySize": 9, + "ignoreDisable": true +} \ No newline at end of file diff --git a/TSOClient/tso.client/Content/UI/hints/english.dir/screen_archive_host.json b/TSOClient/tso.client/Content/UI/hints/english.dir/screen_archive_host.json new file mode 100644 index 000000000..09d46e004 --- /dev/null +++ b/TSOClient/tso.client/Content/UI/hints/english.dir/screen_archive_host.json @@ -0,0 +1,8 @@ +{ + "guid": "b6c88af3-0ea5-451c-b714-97cb732afeb1", + "trigger": "screen:archive_host", + "title": "Archive Server", + "category": "Archive Mode", + "image": "hint_archive_server.png", + "body": "[s][color=white]You're hosting an Archive Server![/color][/s]\nOther players should be able to join you as long as your game client remains open. Click the [s][color=white](i)[/color][/s] next to the city/lot name to see information about your server, like the IP the server is accessible from on each of your network interfaces. \n\nYou can also enable Discord join functionality, so that people don't have to manually enter your IP each time. This will share your public IP, so if you're using a private VPN like Hamachi, you should instead manually share the IP for that interface.\n\nIf you've enabled user verification (or need to eject some unwanted visitors), you should check out the User List button on the UCP at the bottom left. It'll start flashing if anyone is waiting to be verified, so keep an eye on it!\n\nIf you're having trouble getting other people to connect, check out [s][color=white]https://freeso.org/port-forwarding[/color][/s] ! No, we can't embed links in the game." +} \ No newline at end of file diff --git a/TSOClient/tso.client/Content/UI/hints/english.dir/ui_cityeditor.json b/TSOClient/tso.client/Content/UI/hints/english.dir/ui_cityeditor.json new file mode 100644 index 000000000..6c5103232 --- /dev/null +++ b/TSOClient/tso.client/Content/UI/hints/english.dir/ui_cityeditor.json @@ -0,0 +1,8 @@ +{ + "guid": "3f1e11d7-0f1b-438c-82ab-6ab237595ddb", + "trigger": "ui:city_editor", + "title": "The City Editor", + "category": "Archive Mode", + "image": "hint_city_editor.png", + "body": "[s][color=white]The City Editor[/color][/s] lets you landscape, draw roads and unleash your inner arborist to create a locale worth living in! There are four different tabs, primarily brush type tools that let you draw directly onto the city. You can Undo and Redo with the usual keyboard shortcuts, so there's no need to worry about making mistakes.\n\nOn a blank city, you'll be most interested in sculpting the terrain. The best way to get started is to use the Elevation tools with the \"Auto Terrain Type\" toggle enabled, which will choose terrain types based on elevation and slope to let you easily create mountains and valleys. If you want to place water (or manually set terrain types), you can do it from the Terrain Type tool.\n\nOnce the landscape is shaping up, you should paint forests and draw roads for people to build around. Players don't HAVE to build next to a road, but it will make cars a lot happier, and \"bridges\" can let players cross water in Free Roam mode.\n\nOther players with the right permissions can use the City Editor simultaneously, and you can see their changes in real-time. With friends helping you out, it'll be much easier to fill the huge map area with unique landscapes and neighborhoods.\n\nWhen the property lock button appears locked, changes to existing properties and surrounding tiles will be ignored. Click it to toggle the lock on or off. Note that they are always ignored for properties that are open!\n\nWhen you're finished, click the [s][color=white]\"Update City Thumbnail\"[/color][/s] button to update the city image that appears on the save listing and the Select-a-Sim screen." +} \ No newline at end of file diff --git a/TSOClient/tso.client/Content/UI/hints/images/hint_archive_server.png b/TSOClient/tso.client/Content/UI/hints/images/hint_archive_server.png new file mode 100644 index 000000000..d2e0b91e1 Binary files /dev/null and b/TSOClient/tso.client/Content/UI/hints/images/hint_archive_server.png differ diff --git a/TSOClient/tso.client/Content/UI/hints/images/hint_city_editor.png b/TSOClient/tso.client/Content/UI/hints/images/hint_city_editor.png new file mode 100644 index 000000000..140b31e1f Binary files /dev/null and b/TSOClient/tso.client/Content/UI/hints/images/hint_city_editor.png differ diff --git a/TSOClient/tso.client/Content/UI/hints/images/hint_finaltownhall.png b/TSOClient/tso.client/Content/UI/hints/images/hint_finaltownhall.png new file mode 100644 index 000000000..9d0742797 Binary files /dev/null and b/TSOClient/tso.client/Content/UI/hints/images/hint_finaltownhall.png differ diff --git a/TSOClient/tso.client/Controllers/ArchiveCharactersSelectorController.cs b/TSOClient/tso.client/Controllers/ArchiveCharactersSelectorController.cs new file mode 100644 index 000000000..18b5bbffe --- /dev/null +++ b/TSOClient/tso.client/Controllers/ArchiveCharactersSelectorController.cs @@ -0,0 +1,80 @@ +using FSO.Client.Regulators; +using FSO.Client.UI.Archive; +using FSO.Common.Utils; +using FSO.Server.Protocol.Electron.Packets; +using System; + +namespace FSO.Client.Controllers +{ + public interface IArchiveCharacterSelector + { + void SetData(ArchiveAvatarsResponse data); + } + + internal class ArchiveCharactersSelectorController : IDisposable + { + private IArchiveCharacterSelector View; + private GenericActionRegulator ConnectionReg; + public CityResourceController CityResource; + + public ArchiveCharactersSelectorController(IArchiveCharacterSelector view, Network.Network network, GenericActionRegulator regulator) + { + View = view; + CityResource = new CityResourceController(network); + regulator.OnError += Regulator_OnError; + regulator.OnTransition += Regulator_OnTransition; + regulator.OnMessage += Regulator_OnMessage; + + ConnectionReg = regulator; + } + + private void Regulator_OnMessage(object data) + { + if (data is VerificationNotification verification) + { + if (verification.IsVerified) + { + Refresh(); + } + else + { + FSOFacade.Controller.FatalError(GameFacade.Strings.GetString("f128", "92"), GameFacade.Strings.GetString("f128", "93"), 1); + } + } + } + + public void Dispose() + { + ConnectionReg.OnError -= Regulator_OnError; + ConnectionReg.OnTransition -= Regulator_OnTransition; + + CityResource.Dispose(); + } + + public void Refresh() + { + ConnectionReg.MakeRequest(new ArchiveAvatarsRequest()); + } + + private void Regulator_OnError(object data) + { + // TODO: tell the view so it can try again? or handle weird errors like missing auth + } + + private void Regulator_OnTransition(string state, object data) + { + var progress = 0; + + GameThread.InUpdate(() => + { + switch (state) + { + case "ActionSuccess": + var packet = (ArchiveAvatarsResponse)data; + View.SetData(packet); + break; + } + }); + } + } +} diff --git a/TSOClient/tso.client/Controllers/CityResourceController.cs b/TSOClient/tso.client/Controllers/CityResourceController.cs new file mode 100644 index 000000000..ba47ac987 --- /dev/null +++ b/TSOClient/tso.client/Controllers/CityResourceController.cs @@ -0,0 +1,104 @@ +using FSO.Common.Utils; +using FSO.Server.Clients; +using FSO.Server.Protocol.Electron.Packets; +using Ninject.Activation; +using System; +using System.Collections.Concurrent; +using System.Threading; + +namespace FSO.Client.Controllers +{ + public class CityResourceController : IAriesMessageSubscriber, IDisposable + { + private struct CityResourceCallback + { + public uint RequestID; + public Action Callback; + + public CityResourceCallback(uint requestID, Action callback) + { + RequestID = requestID; + Callback = callback; + } + } + + private Network.Network Network; + private ConcurrentDictionary Callbacks; + private static int CallbackID = 0; + + public CityResourceController(Network.Network network) + { + Network = network; + Callbacks = new ConcurrentDictionary(); + + Network.CityClient.AddSubscriber(this); + } + + private uint GetRequestID() + { + return (uint)Interlocked.Increment(ref CallbackID); + } + + private Action CallbackOnMainThread(Action callback) + { + return (data) => + { + GameThread.NextUpdate(x => + { + callback(data.Length == 0 ? null : data); + }); + }; + } + + private void GetResourceAsync(CityResourceRequestType type, uint shardID, uint id, Action callback) + { + callback = CallbackOnMainThread(callback); + var requestId = GetRequestID(); + + Network.CityClient.Write(new CityResourceRequest() + { + Type = type, + RequestID = requestId, + ResourceID = id, + }); + + Callbacks.TryAdd(requestId, new CityResourceCallback(requestId, callback)); + } + + public void GetThumbnailAsync(uint shardID, uint location, Action callback) + { + GetResourceAsync(CityResourceRequestType.LOT_THUMBNAIL, shardID, location, callback); + } + + public void GetFacadeAsync(uint shardID, uint location, Action callback) + { + GetResourceAsync(CityResourceRequestType.LOT_FACADE, shardID, location, callback); + } + + public void GetAvatarDescriptionAsync(uint shardID, uint avatarId, Action callback) + { + GetResourceAsync(CityResourceRequestType.AVATAR_DESCRIPTION, shardID, avatarId, callback); + } + + public void GetCityThumbnailAsync(uint shardID, Action callback) + { + GetResourceAsync(CityResourceRequestType.CITY_THUMBNAIL, shardID, 0, callback); + } + + public void MessageReceived(AriesClient client, object message) + { + if (message is CityResourceResponse res) + { + if (Callbacks.TryRemove(res.RequestID, out CityResourceCallback cb)) + { + cb.Callback(res.Data); + } + } + } + + public void Dispose() + { + Network.CityClient.RemoveSubscriber(this); + } + } +} diff --git a/TSOClient/tso.client/Controllers/ConnectArchiveController.cs b/TSOClient/tso.client/Controllers/ConnectArchiveController.cs new file mode 100644 index 000000000..b1ac8c05f --- /dev/null +++ b/TSOClient/tso.client/Controllers/ConnectArchiveController.cs @@ -0,0 +1,379 @@ +using FSO.Client.Regulators; +using FSO.Client.UI.Archive; +using FSO.Client.UI.Archive.Management; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Model; +using FSO.Client.UI.Screens; +using FSO.Common; +using FSO.Common.DatabaseService.Model; +using FSO.Common.Utils; +using FSO.HIT; +using FSO.Server.Embedded; +using FSO.Server.Protocol.CitySelector; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Utils; +using FSO.UI.Model; + +namespace FSO.Client.Controllers +{ + public enum ConnectArchiveMode + { + Landing, + Create, + Join, + JoinRPC + } + + public class ConnectArchiveController : IDisposable + { + private TransitionScreen View; + private CityConnectionRegulator CityConnectionRegulator; + private Callback onConnect; + private Callback onError; + private UIElement Dialog; + public LoadAvatarByIDResponse AvatarData; + + private ConnectArchiveMode LastMode; + private ArchiveAvatarSelectCode LastSelectCode = ArchiveAvatarSelectCode.Success; + + public ShardSelectorServletRequest Shard => CityConnectionRegulator.CurrentShard; + + public ConnectArchiveController(TransitionScreen view, + CityConnectionRegulator cityConnectionRegulator) + { + this.View = view; + this.CityConnectionRegulator = cityConnectionRegulator; + this.CityConnectionRegulator.OnTransition += CityConnectionRegulator_OnTransition; + this.CityConnectionRegulator.OnError += CityConnectionRegulator_OnError; + + View.ShowProgress = true; + View.SetProgress(0, 4); + + View.ShowSandboxMode(); + } + + private void EnsureDisplayName(Action action) + { + var clientConfig = ClientArchiveConfiguration.Default; + + if (!ClientArchiveConfiguration.ValidDisplayName(clientConfig.PlayerName)) + { + ShowMainDialog(null); + + UIArchiveDisplayName.ShowDisplayNameDialog((string newName) => + { + if (newName == null) + { + SwitchMode(ConnectArchiveMode.Landing); + } + else + { + ClientArchiveConfiguration.Default.PlayerName = newName; + ClientArchiveConfiguration.Default.Save(); + + action(); + } + }); + } + else + { + action(); + } + } + + public void SwitchMode(ConnectArchiveMode mode) + { + LastMode = mode; + bool sandboxVisible = false; + + switch (mode) + { + case ConnectArchiveMode.Join: + EnsureDisplayName(() => + { + ShowMainDialog(new UIArchiveJoinDialog()); + }); + break; + case ConnectArchiveMode.JoinRPC: + EnsureDisplayName(() => + { + ShowMainDialog(new UIArchiveJoinRPCDialog()); + }); + break; + case ConnectArchiveMode.Landing: + sandboxVisible = true; + ShowMainDialog(new UIArchiveLandingDialog()); + break; + case ConnectArchiveMode.Create: + if (FSOFacade.Controller.HasServer()) + { + ExistingServerDialog(); + } + else + { + EnsureDisplayName(() => + { + ShowMainDialog(new UIArchiveCreateServer()); + }); + } + break; + } + + View.SetSandboxVisibility(sandboxVisible); + } + + private void ExistingServerDialog() + { + var config = FSOFacade.Controller.GetServerConfig(); + + UIAlert alert = null; + alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Message = GameFacade.Strings.GetString("f128", "96"), + Width = 400, + Buttons = [ + new UIAlertButton(UIAlertButtonType.Yes, (btn => + { + // User management + UIScreen.RemoveDialog(alert); + UIScreen.GlobalShowDialog(new UIArchiveUserManageDialog(new ArchiveManagement(config)), true); + }), GameFacade.Strings.GetString("f128", "97")), + new UIAlertButton(UIAlertButtonType.No, (btn => + { + // Close server + UIScreen.RemoveDialog(alert); + FSOFacade.Controller.CloseServer(() => + { + ShowMainDialog(new UIArchiveCreateServer()); + }); + }), GameFacade.Strings.GetString("f128", "98")), + new UIAlertButton(UIAlertButtonType.Cancel, (btn => + { + // Join server + UIScreen.RemoveDialog(alert); + ShowMainDialog(null); + FSOFacade.Controller.ConnectToArchive(ClientArchiveConfiguration.Default.PlayerName, $"127.0.0.1:{config.CityPort}", true); + }), GameFacade.Strings.GetString("f128", "99")), + ], + }, true); + } + + public void ReturnToSAS(Callback onConnect, Callback onError) + { + this.onConnect = onConnect; + this.onError = onError; + + if (!CityConnectionRegulator.ReturnToSASArchive()) + { + // If we can't do this, re-initialize archive mode. + Initialize(); + } + } + + public void Connect(string displayName, string address, bool selfHost, Callback onConnect, Callback onError) + { + this.onConnect = onConnect; + this.onError = onError; + + address = PortTransformer.DefaultCityPort(address); + + CityConnectionRegulator.ConnectArchive(new ConnectArchiveRequest + { + CityAddress = address, + DisplayName = displayName, + SelfHost = selfHost + }); + } + + public void SetCallbacks(Callback onConnect, Callback onError) + { + this.onConnect = onConnect; + this.onError = onError; + } + + public void SkipAvatarSelection(uint avatarId) + { + CityConnectionRegulator.CurrentShard.AvatarID = avatarId.ToString(); + CityConnectionRegulator.AsyncTransition("AskForAvatarData"); + } + + public void SelectAvatar(uint avatarId, uint lotId = 0) + { + FSOFacade.Controller.SetArchiveLot(lotId); + + CityConnectionRegulator.AsyncProcessMessage(new ArchiveAvatarSelectRequest() + { + AvatarId = avatarId + }); + } + + public void CreateServer(ArchiveConfiguration config) + { + View.SetSandboxVisibility(false); + + var embedded = new EmbeddedServer(config); + + embedded.Start(); + + FSOFacade.Controller.RegisterServer(embedded); + + ShowMainDialog(new UIArchiveServerStatusDialog(true, embedded, () => + { + ShowMainDialog(null); + FSOFacade.Controller.ConnectToArchive(ClientArchiveConfiguration.Default.PlayerName, $"127.0.0.1:{config.CityPort}", true); + })); + } + + private void CityConnectionRegulator_OnError(object data) + { + onError(); + } + + private void ShowMainDialog(UIElement dialog) + { + // if there's currently a dialog, dispose of it + if (Dialog != null) + { + UIScreen.RemoveDialog(Dialog); + } + + Dialog = dialog; + + if (dialog != null) + { + UIScreen.ShowDialog(dialog, false); + } + } + + public void Initialize() + { + GameFacade.Cursor.SetCursor(Common.Rendering.Framework.CursorType.Normal); + + HITVM.Get().PlaySoundEvent(UIMusic.None); + GlobalSettings.Default.Save(); + + SwitchMode(LastMode); + View.SetProgressArchive(0, "Awaiting user input"); + } + + public void TickRPC() + { + var rpc = DiscordRpcEngine.Secret; + + if (rpc != null) + { + if (rpc.Value.ArchiveMode) + { + if (!string.IsNullOrEmpty(rpc.Value.ServerHostname)) + { + SwitchMode(ConnectArchiveMode.JoinRPC); + } + } + else + { + UIAlert.Alert("", GameFacade.Strings.GetString("f128", "114"), true); + DiscordRpcEngine.Secret = null; + } + } + } + + private void CityConnectionRegulator_OnTransition(string state, object data) + { + GameThread.NextUpdate((x) => + { + switch (state) + { + case "Disconnected": + Initialize(); + break; + case "ArchiveConnect": + //4 ^Starting engines^ # City is Selected... + LastSelectCode = ArchiveAvatarSelectCode.Success; + View.SetSandboxVisibility(false); + ShowMainDialog(null); + View.SetProgress((1.0f / 14.0f) * 100, 4); + break; + case "OpenSocket": + //7 ^Sterilizing TCP/IP sockets^ # Connecting to City... + View.SetProgress((4.0f / 14.0f) * 100, 7); + break; + case "RequestClientSessionArchive": + View.SetProgressArchive((4.5f / 14.0f) * 100, "Performing handshake with Archive Server"); + break; + case "PartiallyConnected": + View.SetProgressArchive((5.0f / 14.0f) * 100, "Connected, awaiting avatar selection"); + + // Show the avatar selection UI. + // Need to force resize due to showing a screen as a dialog. + var select = new ArchivePersonSelection() { ScaleX = 1, ScaleY = 1 }; + select.GameResized(); + + ShowMainDialog(select); + + if (LastSelectCode != ArchiveAvatarSelectCode.Success) + { + select.ShowSelectionError(LastSelectCode); + LastSelectCode = ArchiveAvatarSelectCode.Success; + } + + break; + case "ArchiveSelectAvatar": + View.SetProgressArchive((5.5f / 14.0f) * 100, "Selecting avatar"); + ShowMainDialog(null); + break; + + case "ArchiveSelectedAvatar": + if (data is ArchiveAvatarSelectResponse sel) + { + LastSelectCode = sel.Code; + } + break; + + case "AskForAvatarData": + //9 ^Reticulating spleens^ # Asking for Avatar data from DB... + View.SetProgress((6.0f / 14.0f) * 100, 9); + break; + case "ReceivedAvatarData": + //10 ^Spleens Reticulated^ # Received Avatar data from DB... + + var dbResponse = (LoadAvatarByIDResponse)data; + if (dbResponse != null) + { + AvatarData = dbResponse; + } + + View.SetProgress((7.0f / 14.0f) * 100, 10); + break; + case "AskForCharacterData": + //11 ^Purging psychographic metrics^ # Asking for Character data from DB... + View.SetProgress((8.0f / 14.0f) * 100, 11); + break; + + case "ReceivedCharacterData": + //12 ^Metrics Purged^ # Received Character data from DB... + View.SetProgress((9.0f / 14.0f) * 100, 12); + break; + + case "AskForCityData": + View.SetProgress((10.0f / 14.0f) * 100, 8, "f100"); + break; + + case "ReceivedCityData": + View.SetProgress((13.0f / 14.0f) * 100, 9, "f100"); + break; + + case "Connected": + onConnect(); + break; + } + }); + } + + public void Dispose() + { + CityConnectionRegulator.OnTransition -= CityConnectionRegulator_OnTransition; + CityConnectionRegulator.OnError -= CityConnectionRegulator_OnError; + } + } +} diff --git a/TSOClient/tso.client/Controllers/ConnectCityController.cs b/TSOClient/tso.client/Controllers/ConnectCityController.cs index 4adec8480..78fb2ca50 100644 --- a/TSOClient/tso.client/Controllers/ConnectCityController.cs +++ b/TSOClient/tso.client/Controllers/ConnectCityController.cs @@ -96,6 +96,15 @@ private void CityConnectionRegulator_OnTransition(string state, object data) //12 ^Metrics Purged^ # Received Character data from DB... View.SetProgress((9.0f / 14.0f) * 100, 12); break; + + case "AskForCityData": + View.SetProgress((10.0f / 14.0f) * 100, 8, "f100"); + break; + + case "ReceivedCityData": + View.SetProgress((13.0f / 14.0f) * 100, 9, "f100"); + break; + case "Connected": onConnect(); break; diff --git a/TSOClient/tso.client/Controllers/CoreGameScreenController.cs b/TSOClient/tso.client/Controllers/CoreGameScreenController.cs index a0009bf13..be5af4438 100644 --- a/TSOClient/tso.client/Controllers/CoreGameScreenController.cs +++ b/TSOClient/tso.client/Controllers/CoreGameScreenController.cs @@ -3,11 +3,15 @@ using FSO.Client.Regulators; using FSO.Client.UI.Framework; using FSO.Client.UI.Screens; +using FSO.Client.Utils; +using FSO.Common; using FSO.Common.DataService; using FSO.Common.DataService.Model; using FSO.Common.Enum; +using FSO.Common.Model; using FSO.Common.Utils; using FSO.Files.Formats.tsodata; +using FSO.Server.DataService.Model; using FSO.Server.Protocol.Electron.Model; using FSO.Server.Protocol.Electron.Packets; using FSO.SimAntics.NetPlay; @@ -34,17 +38,40 @@ public class CoreGameScreenController : IDisposable /// Lot to connect to immediately after disconnecting. Used for job lots and switching lots. /// public uint ReconnectLotID = 0; + public LotTransitionInfo ReconnectTransition; public TerrainController Terrain; public NeighborhoodActionController NeighborhoodProtocol; public BulletinActionController BulletinProtocol; + public CityResourceController CityResource; + + public CityConnectionMode Mode => Network.Mode; + public ArchiveConfigFlags ArchiveConfig => Network.ArchiveConfig; + public ConnectArchiveRequest ArchiveHost => Network.ArchiveHost; + public uint ModerationLevel => Network.ModerationLevel; + private uint CityEditorThreshold => + ArchiveConfig.HasFlag(ArchiveConfigFlags.CityEditorAllUsers) ? 0u : + (ArchiveConfig.HasFlag(ArchiveConfigFlags.CityEditorMods) ? 1u : 2u); + + public bool AllowCityEditor => + ArchiveConfig.HasFlag(ArchiveConfigFlags.CityEditor) && + ModerationLevel >= CityEditorThreshold; + + public bool CanPurchaseLots => + Mode != CityConnectionMode.ARCHIVE || + ArchiveConfig.HasFlag(ArchiveConfigFlags.AllowLotCreation) || + ModerationLevel > 0; + + public bool LocalTransition => ReconnectLotID != 0 && ReconnectTransition != null; public CoreGameScreenController(CoreGameScreen view, Network.Network network, IClientDataService dataService, IKernel kernel, LotConnectionRegulator joinLotRegulator) { + view.Controller = this; // Set this early so all FindController<> calls work. this.Screen = view; this.Network = network; this.DataService = dataService; this.Chat = new MessagingController(this, view.MessageTray, network, dataService); + this.CityResource = new CityResourceController(network); this.JoinLotRegulator = joinLotRegulator; this.RoommateProtocol = new RoommateRequestController(this, network, dataService); this.NeighborhoodProtocol = kernel.Get(); @@ -54,7 +81,11 @@ public CoreGameScreenController(CoreGameScreen view, Network.Network network, IC var shard = Network.MyShard; Terrain = kernel.Get(new ConstructorArgument("parent", this)); - view.Initialize(shard.Name, int.Parse(shard.Map), Terrain); + + view.Initialize(shard.Name, Terrain); + + if (Mode == CityConnectionMode.ARCHIVE) + view.ucp.InitArchive(); } public void AddWindow(UIContainer window) @@ -90,11 +121,12 @@ private void JoinLotRegulator_OnTransition(string transition, object data) break; case "Disconnected": Screen.CleanupLastWorld(); + if (ReconnectLotID != 0) { - GameThread.SetTimeout(() => { - if (ReconnectLotID != 0) JoinLot(ReconnectLotID); - }, 100); + GameThread.InUpdate(() => { + if (ReconnectLotID != 0) JoinLot(ReconnectLotID, ReconnectTransition); + }); } //destroy the currently active lot (if possible) break; @@ -108,8 +140,8 @@ private void JoinLotRegulator_OnTransition(string transition, object data) //doesn't really need to be next update... but we don't want to catch the VM in a half-init state. if (data == null) break; VMNetMessage msg = null; - if (data is FSOVMTickBroadcast) - msg = new VMNetMessage(VMNetMessageType.BroadcastTick, ((FSOVMTickBroadcast)data).Data); + if (data is FSOVMTickBroadcast broadcast) + msg = new VMNetMessage(broadcast.Catchup ? VMNetMessageType.CatchupTick : VMNetMessageType.BroadcastTick, broadcast.Data); else msg = new VMNetMessage(VMNetMessageType.Direct, ((FSOVMDirectToClient)data).Data); @@ -119,13 +151,14 @@ private void JoinLotRegulator_OnTransition(string transition, object data) }); } - public void JoinLot(uint id) + public void JoinLot(uint id, LotTransitionInfo transition = null) { var lot = JoinLotRegulator.GetCurrentLotID(); if (lot == 0) { - JoinLotRegulator.JoinLot(id); + JoinLotRegulator.JoinLot(id, transition); ReconnectLotID = 0; + ReconnectTransition = transition; } else if (lot == id) { @@ -139,26 +172,47 @@ public void JoinLot(uint id) } } - public void SwitchLot(uint id) + public void SwitchLot(uint id, LotTransitionInfo transition) { if (JoinLotRegulator.GetCurrentLotID() == 0) { - JoinLotRegulator.JoinLot(id); + JoinLotRegulator.JoinLot(id, transition); ReconnectLotID = 0; + ReconnectTransition = null; } else { //force a switch to the target lot + JoinLotRegulator.LeavingLot = true; ReconnectLotID = id; + ReconnectTransition = transition; Screen.InitiateLotSwitch(); + + // If there's a transition, we can leave immediately. + if (transition != null) + { + JoinLotRegulator.Disconnect(); + } } } + public uint GetVisualLotID() + { + uint lotID = Screen.VisualVM?.TSOState?.LotID ?? 0; + + return lotID == 0 ? JoinLotRegulator.GetCurrentLotID() : lotID; + } + public uint GetCurrentLotID() { return JoinLotRegulator.GetCurrentLotID(); } + public bool IsLotSelected() + { + return JoinLotRegulator.CurrentState.Name != "Disconnected"; + } + public void CallAvatar(uint avatarId){ DataService.Get(avatarId).ContinueWith(x => { @@ -226,7 +280,7 @@ public void WriteEmail(uint avatarId, string subject) }); } - public void UploadLotThumbnail() + public void UploadLotThumbnail(bool buildMode) { if (!Screen.InLot) return; var lotID = JoinLotRegulator.GetCurrentLotID(); @@ -240,12 +294,36 @@ public void UploadLotThumbnail() //tex.Dispose(); data = stream.ToArray(); } + + byte[] facadeData = null; + + if (buildMode && FSOEnvironment.Enable3D) + { + var result = new FSOFHelper(GameFacade.GraphicsDevice, Screen.vm, Screen.vm.Context.World).GenerateIngameFSOF(); + Terrain.OverrideLotFacade(lotID, result); + + using var mem = new MemoryStream(); + + result.Save(mem); + + facadeData = mem.ToArray(); + } + DataService.Get(lotID).ContinueWith(x => { var lot = x.Result; if (lot == null) return; //uh, oops! lot.Lot_Thumbnail = new Common.Serialization.Primitives.cTSOGenericData(data); - DataService.Sync(lot, new string[] { "Lot_Thumbnail" }); + + if (facadeData != null) + { + lot.Lot_Facade = new Common.Serialization.Primitives.cTSOGenericData(facadeData); + DataService.Sync(lot, ["Lot_Thumbnail", "Lot_Facade"]); + } + else + { + DataService.Sync(lot, ["Lot_Thumbnail"]); + } }); } @@ -378,9 +456,33 @@ public void ModRequest(uint entityId, ModerationRequestType type) }); } + public void ArchiveModRequest(uint entityId, ArchiveModerationRequestType type, int value = 0) + { + Network.CityClient.Write(new ArchiveModerationRequest() + { + EntityId = entityId, + Type = type, + Value = value + }); + } + + public void RegenerateHollowLots(bool completeMoves) + { + Network.CityClient.Write(new CityUpdateCommand() + { + AvatarID = Network.MyCharacter, + Mode = CityUpdateCommandMode.HollowLotRefresh, + TargetUID = completeMoves ? 1 : 0 + }); + } + public void HandleVMShutdown(VMCloseNetReason reason) { - JoinLotRegulator.AsyncTransition("Disconnect"); + var state = JoinLotRegulator.CurrentState.Name; + if (state != "Disconnected" && state != "Disconnect") + { + JoinLotRegulator.AsyncTransition("Disconnect"); + } } public bool IsMe(uint id) @@ -393,6 +495,11 @@ public uint MyID() return Network.MyCharacter; } + public string TryGetUsername(uint id) + { + return Network.TryGetUsername(id); + } + public void Dispose() { JoinLotRegulator.OnTransition -= JoinLotRegulator_OnTransition; @@ -400,6 +507,7 @@ public void Dispose() GameFacade.Scenes.Clear(); Terrain.Dispose(); Chat.Dispose(); + CityResource.Dispose(); RoommateProtocol.Dispose(); Screen.JoinLotProgress.FindController()?.Dispose(); ((PersonPageController)Screen.PersonPage.Controller)?.Dispose(); diff --git a/TSOClient/tso.client/Controllers/DisconnectController.cs b/TSOClient/tso.client/Controllers/DisconnectController.cs index 10cf2f14b..1e08afe54 100644 --- a/TSOClient/tso.client/Controllers/DisconnectController.cs +++ b/TSOClient/tso.client/Controllers/DisconnectController.cs @@ -69,7 +69,18 @@ public void Disconnect(Action onDisconnected, bool forceLogin) CityConnectionRegulator.Disconnect(); LotConnectionRegulator.Disconnect(); - if (!forceLogin) LoginRegulator.AsyncTransition("AvatarData"); + if (!forceLogin) + { + if (CityConnectionRegulator.Mode != CityConnectionMode.ARCHIVE) + { + LoginRegulator.AsyncTransition("AvatarData"); + } + else + { + targetComplete = 1; + this.onDisconnected = (_) => FSOFacade.Controller.ReturnToSASArchive(); + } + } } public void Dispose() diff --git a/TSOClient/tso.client/Controllers/GizmoController.cs b/TSOClient/tso.client/Controllers/GizmoController.cs index 1e6114bd8..ada63efbc 100644 --- a/TSOClient/tso.client/Controllers/GizmoController.cs +++ b/TSOClient/tso.client/Controllers/GizmoController.cs @@ -1,8 +1,9 @@ -using FSO.Client.UI.Panels; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; using FSO.Common.DataService; using FSO.Common.DataService.Model; using FSO.Common.Enum; -using System; +using System.Collections.Immutable; namespace FSO.Client.Controllers { @@ -12,11 +13,21 @@ public class GizmoController : IDisposable private Network.Network Network; private IClientDataService DataService; + public ImmutableList FilterList + { + set + { + HandleFilterList(value); + } + } + public GizmoController(UIGizmo view, Network.Network network, IClientDataService dataService) { this.Gizmo = view; this.Network = network; this.DataService = dataService; + this.Gizmo.CurrentAvatar + .WithBinding(this, "FilterList", "Avatar_Top100ListFilter.Top100ListFilter_ResultsVec"); Initialize(); } @@ -28,10 +39,41 @@ private void Initialize() if (!x.IsFaulted){ Gizmo.CurrentAvatar.Value = x.Result; FSO.UI.Model.DiscordRpcEngine.SendFSOPresence(x.Result.Avatar_Name, null, 0, 0, 0, 0, null, x.Result.Avatar_PrivacyMode > 0); + + if (Network.Mode == Regulators.CityConnectionMode.ARCHIVE) + { + RequestFilter(LotCategory.archive_welcome); + } + else if (x.Result.Avatar_Age < 14) + { + RequestFilter(LotCategory.welcome); + } } }); } + private void HandleFilterList(ImmutableList lots) + { + if (Gizmo.CurrentAvatar.Value != null && Gizmo.CurrentAvatar.Value.Avatar_Top100ListFilter.Top100ListFilter_Top100ListID == (uint)LotCategory.archive_welcome && lots.Count == 1) + { + // If the player isn't currently on a lot, and this archive_welcome lot has a hint that hasn't been seen yet, then make them automatically join it. + uint targetLot = lots[0]; + + var controller = UIScreen.Current.FindController(); + + if (controller != null && !controller.IsLotSelected()) + { + var hints = FSOFacade.Hints; + string trigger = $"lot:{GameFacade.CurrentCityName}:{targetLot}"; + + if (!hints.IsHintTriggered(trigger)) + { + controller.JoinLot(targetLot); + } + } + } + } + public void Dispose() { try { diff --git a/TSOClient/tso.client/Controllers/JoinLotProgressController.cs b/TSOClient/tso.client/Controllers/JoinLotProgressController.cs index 75ff10fa0..5d058ba6a 100644 --- a/TSOClient/tso.client/Controllers/JoinLotProgressController.cs +++ b/TSOClient/tso.client/Controllers/JoinLotProgressController.cs @@ -35,6 +35,7 @@ private void Regulator_OnError(object data) //UIScreen.RemoveDialog(View); GameThread.InUpdate(() => { + GameFacade.Cursor.SetCursorPriority(0); GameFacade.Cursor.SetCursor(CursorType.Normal); var errorTitle = GameFacade.Strings.GetString("211", "45"); diff --git a/TSOClient/tso.client/Controllers/LoginController.cs b/TSOClient/tso.client/Controllers/LoginController.cs index 699c1789e..f232241f8 100644 --- a/TSOClient/tso.client/Controllers/LoginController.cs +++ b/TSOClient/tso.client/Controllers/LoginController.cs @@ -33,7 +33,7 @@ private void Regulator_OnTransition(string transition, object data) View.LoginDialog.Visible = false; View.LoginProgress.Visible = false; var controller = new UpdateController(ContinueFromUpdate); - controller.DoUpdate((info.FSOBranch ?? "") + "-" + (info.FSOVersion ?? ""), info.FSOUpdateUrl ?? ""); + controller.DoUpdate(info.GetVersion()); break; } } @@ -54,107 +54,6 @@ private void ContinueFromUpdate(bool toSAS) } } - public void DoUpdate(string branch, string version, string url) - { - View.LoginDialog.Visible = false; - View.LoginProgress.Visible = false; - - var str = GlobalSettings.Default.ClientVersion; - - var split = str.LastIndexOf('-'); - int verNum = 0; - string curBranch = str; - if (split != -1) - { - int.TryParse(str.Substring(split + 1), out verNum); - curBranch = str.Substring(0, split); - } - - _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions - { - Title = GameFacade.Strings.GetString("f101", "3"), - Message = GameFacade.Strings.GetString("f101", "4", new string[] { version, branch, verNum.ToString(), curBranch }), - Width = 500, - Buttons = UIAlertButton.YesNo(x => - { - UIScreen.RemoveDialog(_UpdaterAlert); - var downloader = new UIWebDownloaderDialog(GameFacade.Strings.GetString("f101", "1"), new DownloadItem[] - { - new DownloadItem { - Url = url, - DestPath = "PatchFiles/patch.zip", - Name = GameFacade.Strings.GetString("f101", "10") - } - }); - downloader.OnComplete += (bool success) => { - UIScreen.RemoveDialog(downloader); - UIScreen.GlobalShowAlert(new UIAlertOptions - { - Title = GameFacade.Strings.GetString("f101", "3"), - Message = GameFacade.Strings.GetString("f101", "13"), - Buttons = UIAlertButton.Ok(y => - { - RestartGamePatch(); - }) - }, true); - }; - GameThread.NextUpdate(y => UIScreen.GlobalShowDialog(downloader, true)); - }, - x => - { - GameThread.NextUpdate(state => - { - UIScreen.RemoveDialog(_UpdaterAlert); - if (state.ShiftDown) - { - _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions - { - Title = GameFacade.Strings.GetString("f101", "11"), - Message = GameFacade.Strings.GetString("f101", "12"), - Width = 500, - Buttons = UIAlertButton.Ok(y => - { - Regulator.AsyncTransition("AvatarData"); - UIScreen.RemoveDialog(_UpdaterAlert); - View.LoginDialog.Visible = true; - View.LoginProgress.Visible = true; - }) - }, true); - } - else - { - View.LoginDialog.Visible = true; - View.LoginProgress.Visible = true; - Regulator.AsyncReset(); - } - }); - }) - }, true); - } - - public void RestartGamePatch() - { - if (FSOEnvironment.Linux) - { - System.Diagnostics.Process.Start("mono", "update.exe "+FSOEnvironment.Args); - } - else - { - var args = new ProcessStartInfo(".\\update.exe", FSOEnvironment.Args); - try - { - - System.Diagnostics.Process.Start(args); - } - catch (Exception) - { - args.FileName = "update.exe"; - System.Diagnostics.Process.Start(args); - } - } - GameFacade.Kill(); - } - public void Dispose() { View.Dispose(); diff --git a/TSOClient/tso.client/Controllers/Panels/LotPageController.cs b/TSOClient/tso.client/Controllers/Panels/LotPageController.cs index 999bee9e4..ca725a25f 100644 --- a/TSOClient/tso.client/Controllers/Panels/LotPageController.cs +++ b/TSOClient/tso.client/Controllers/Panels/LotPageController.cs @@ -1,5 +1,6 @@ using FSO.Client.Network; using FSO.Client.UI.Panels; +using FSO.Common; using FSO.Common.DataService; using FSO.Common.DataService.Model; using FSO.Server.DataService.Model; @@ -12,13 +13,20 @@ public class LotPageController private UILotPage View; private IClientDataService DataService; private ITopicSubscription Topic; + private Network.Network Network; private uint LotId; - public LotPageController(UILotPage view, IClientDataService dataService) + // Note: maybe should indicate if mod powers are causing the lot to be openable. + public bool CanOpenAnyLot => + (Network.Mode == Regulators.CityConnectionMode.ARCHIVE && Network.ArchiveConfig.HasFlag(ArchiveConfigFlags.AllOpenable)) + || Network.SpectatorMode || Network.ModerationLevel > 0; + + public LotPageController(UILotPage view, IClientDataService dataService, Network.Network network) { this.View = view; this.DataService = dataService; this.Topic = dataService.CreateTopicSubscription(); + this.Network = network; } ~LotPageController(){ diff --git a/TSOClient/tso.client/Controllers/Panels/NeighPageController.cs b/TSOClient/tso.client/Controllers/Panels/NeighPageController.cs index 8d1f59e13..b46a2d295 100644 --- a/TSOClient/tso.client/Controllers/Panels/NeighPageController.cs +++ b/TSOClient/tso.client/Controllers/Panels/NeighPageController.cs @@ -14,14 +14,18 @@ class NeighPageController { private UINeighPage View; private IClientDataService DataService; + private Network.Network Network; private ITopicSubscription Topic; private uint NeighId; - public NeighPageController(UINeighPage view, IClientDataService dataService) + public uint ModerationLevel => Network.ModerationLevel; + + public NeighPageController(UINeighPage view, IClientDataService dataService, Network.Network network) { this.View = view; this.DataService = dataService; this.Topic = dataService.CreateTopicSubscription(); + this.Network = network; ControllerUtils.BindController(this.View.MayorRatingBox1); ControllerUtils.BindController(this.View.MayorRatingBox2); diff --git a/TSOClient/tso.client/Controllers/Panels/PersonPageController.cs b/TSOClient/tso.client/Controllers/Panels/PersonPageController.cs index 4b283178b..fec5c27ff 100644 --- a/TSOClient/tso.client/Controllers/Panels/PersonPageController.cs +++ b/TSOClient/tso.client/Controllers/Panels/PersonPageController.cs @@ -192,7 +192,7 @@ public void MessageReceived(AriesClient client, object message) switch (loc.Status) { case FindAvatarResponseStatus.FOUND: - View.FindController()?.ShowLotPage(loc.LotId & 0x3FFFFFFF); //ignore transient part + View.FindController()?.ShowLotPage(loc.LotId & 0x1FFFFFFF); //ignore transient part break; default: if (loc.Status == FindAvatarResponseStatus.PRIVACY_ENABLED) loc.Status = FindAvatarResponseStatus.NOT_ON_LOT; diff --git a/TSOClient/tso.client/Controllers/Panels/RatingSummaryController.cs b/TSOClient/tso.client/Controllers/Panels/RatingSummaryController.cs index 501310575..8230ac8ae 100644 --- a/TSOClient/tso.client/Controllers/Panels/RatingSummaryController.cs +++ b/TSOClient/tso.client/Controllers/Panels/RatingSummaryController.cs @@ -20,6 +20,8 @@ public class RatingSummaryController : IDisposable private BookmarkType CurrentType = BookmarkType.AVATAR; private uint RatingID = uint.MaxValue; + public uint ModerationLevel => Network.ModerationLevel; + public RatingSummaryController(IUIAbstractRating view, IClientDataService dataService, Network.Network network) { this.Network = network; diff --git a/TSOClient/tso.client/Controllers/PersonSelectionEditController.cs b/TSOClient/tso.client/Controllers/PersonSelectionEditController.cs index 12a9bd1cf..639034d74 100644 --- a/TSOClient/tso.client/Controllers/PersonSelectionEditController.cs +++ b/TSOClient/tso.client/Controllers/PersonSelectionEditController.cs @@ -14,6 +14,8 @@ public class PersonSelectionEditController : IDisposable private PersonSelectionEdit View; private CreateASimRegulator CASRegulator; + public bool Archive; + public PersonSelectionEditController(PersonSelectionEdit view, CreateASimRegulator casRegulator) { this.View = view; @@ -55,7 +57,14 @@ private void CASRegulator_OnTransition(string state, object data) case "Success": //Connect to the city with our new avatar var response = (CreateASimResponse)data; - FSOFacade.Controller.ConnectToCity(null, response.NewAvatarId, null); + if (Archive) + { + FSOFacade.Controller.SelectFromCASArchive(response.NewAvatarId); + } + else + { + FSOFacade.Controller.ConnectToCity(null, response.NewAvatarId, null); + } break; } }); diff --git a/TSOClient/tso.client/Controllers/SandboxGameScreenController.cs b/TSOClient/tso.client/Controllers/SandboxGameScreenController.cs new file mode 100644 index 000000000..e59be9cb2 --- /dev/null +++ b/TSOClient/tso.client/Controllers/SandboxGameScreenController.cs @@ -0,0 +1,21 @@ +using FSO.Client.UI.Screens; + +namespace FSO.Client.Controllers +{ + internal class SandboxGameScreenController : IDisposable + { + public SandboxGameScreen Screen; + + public SandboxGameScreenController(SandboxGameScreen view) + { + view.Controller = this; + this.Screen = view; + } + + public void Dispose() + { + Screen.CleanupLastWorld(); + GameFacade.Scenes.Clear(); + } + } +} diff --git a/TSOClient/tso.client/Controllers/TerrainController.cs b/TSOClient/tso.client/Controllers/TerrainController.cs index 79a4df676..c7b4401f9 100644 --- a/TSOClient/tso.client/Controllers/TerrainController.cs +++ b/TSOClient/tso.client/Controllers/TerrainController.cs @@ -1,16 +1,21 @@ using FSO.Client.Regulators; using FSO.Client.Rendering.City; +using FSO.Client.Rendering.City.Plugins; using FSO.Client.UI; using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Panels; +using FSO.Client.UI.Screens; using FSO.Common.DataService; using FSO.Common.DataService.Model; using FSO.Common.Domain.Realestate; using FSO.Common.Domain.RealestateDomain; using FSO.Common.Utils; +using FSO.Content.Model; using FSO.Files.RC; +using FSO.Server.Clients; using FSO.Server.DataService.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; using FSO.Server.Protocol.Electron.Packets; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -21,14 +26,16 @@ namespace FSO.Client.Controllers { - public class TerrainController : IDisposable + public class TerrainController : IAriesMessageSubscriber, IDisposable { public CoreGameScreenController Parent; + public IShardRealestateDomain Realestate { get; } + private Terrain View; private IClientDataService DataService; - private IShardRealestateDomain Realestate; private PurchaseLotRegulator PurchaseRegulator; private LotThumbContent LotThumbs; + private uint ShardId; private Binding CurrentHoverLot; private Binding CurrentCity; @@ -50,14 +57,81 @@ public TerrainController(CoreGameScreenController parent, IClientDataService ds, PurchaseRegulator.OnError += PurchaseRegulator_OnError; PurchaseRegulator.OnTransition += PurchaseRegulator_OnTransition; PurchaseRegulator.OnPurchased += PurchaseRegulator_OnPurchased; - Realestate = domain.GetByShard(network.MyShard.Id); + ShardId = (uint)network.MyShard.Id; + Realestate = domain.GetByShard((int)ShardId); CurrentHoverLot = new Binding() .WithMultiBinding(RefreshTooltip, "Lot_Price", "Lot_IsOnline", "Lot_Name", "Lot_NumOccupants", "Lot_LeaderID"); CurrentCity = new Binding().WithMultiBinding(RefreshCity, "City_ReservedLotInfo", "City_SpotlightsVector"); - LotThumbs = new LotThumbContent(); + LotThumbs = new LotThumbContent(parent.CityResource); + + Realestate.TrackUndo(network.MyCharacter); + Realestate.OnMapChange += MapChange; + + network.CityClient.AddSubscriber(this); + } + + private void MapChange(Rectangle obj) + { + View.GenerateCityMesh(GameFacade.GraphicsDevice, obj); + View.RegenerateVertexColor(); + } + + public void CommitMapChange(CityEditBase cmd) + { + cmd.AvatarId = Network.MyCharacter; + + // Send it over to the server. + // Make sure it's not temp (but don't overwrite it for the local copy) + + var prevTemp = cmd.IsTemp; + cmd.IsTemp = false; + cmd.Color = GlobalSettings.Default.ChatColor; + Network.CityClient.Write(new CityUpdateRequest() { Command = new(cmd) }); + cmd.IsTemp = prevTemp; + } + + public void UpdateThumbnail(byte[] data) + { + Network.CityClient.Write(new CityUpdateCommand() { Mode = CityUpdateCommandMode.SetThumbnail, Thumbnail = data }); + } + + public void UpdateCityName(string name) + { + Network.CityClient.Write(new CityUpdateCommand() { Mode = CityUpdateCommandMode.SetCityName, CityName = name }); + } + + public void SendCityCommand(CityUpdateCommandMode mode, int uid) + { + Network.CityClient.Write(new CityUpdateCommand() + { + AvatarID = Network.MyCharacter, + Mode = mode, + TargetUID = uid + }); + } + + public bool UpdateTempMapChange(CityEditBase cmd) + { + if (cmd != null) + { + cmd.AvatarId = Network.MyCharacter; + cmd.IsTemp = true; + + // TODO: submit temp to city? + } + + bool valid = Realestate.SetMyTempCommand(cmd); + + if (!valid && View.Plugin is MapPainterPlugin painter) + { + painter.ShowError(painter.LockProperties ? 55 : 53); + return false; + } + + return true; } private void PurchaseRegulator_OnPurchased(int newBudget) @@ -71,6 +145,8 @@ public void Dispose() PurchaseRegulator.OnTransition -= PurchaseRegulator_OnTransition; PurchaseRegulator.OnPurchased -= PurchaseRegulator_OnPurchased; + Network.CityClient.RemoveSubscriber(this); + LotThumbs.Dispose(); } @@ -83,12 +159,22 @@ public void ZoomOut(){ CurrentHoverLot.Value = null; } + public CityMap GetCityMap() + { + return Realestate.GetMap(); + } + + public void HideTooltip() + { + CurrentHoverLot.Value = null; + } + private void RefreshTooltip(BindingChange[] changes) { //Called if price, online or name change GameThread.NextUpdate((state) => { - if (CurrentHoverLot.Value != null) + if (CurrentHoverLot.Value != null && View.Plugin == null) { var lot = CurrentHoverLot.Value; var name = lot.Lot_Name; @@ -132,27 +218,16 @@ private void RefreshCity(BindingChange[] changes) { if (CurrentCity.Value != null) { - var mapData = LotTileEntry.GenFromCity(CurrentCity.Value); - var neighJSON = CurrentCity.Value.City_NeighJSON; - - //We know if lots are online, we can update the data service - DataService.GetMany(mapData.Select(x => (object)(uint)x.packed_pos).ToArray()).ContinueWith(x => + GameThread.NextUpdate((state) => { - if (!x.IsCompleted){ - return; - } + bool updated = View.LotTiles.UpdateWithCity(CurrentCity.Value, DataService); + var neighJSON = CurrentCity.Value.City_NeighJSON; - foreach (var lot in x.Result) + if (updated) { - var mapItem = mapData.FirstOrDefault(y => y.packed_pos == lot.Id); - if (mapItem != null) { - lot.Lot_IsOnline = (mapItem.flags & LotTileFlags.Online) == LotTileFlags.Online; - } + View.SignalCityDirty(); } - }); - GameThread.NextUpdate((state) => { - View.populateCityLookup(mapData); if (neighJSON != LastLotJSON) { try @@ -168,8 +243,7 @@ private void RefreshCity(BindingChange[] changes) LastLotJSON = neighJSON; } - - }); + }); } } @@ -193,7 +267,7 @@ public void RequestNewCity() public bool IsPurchasable(int x, int y) { - return Realestate.IsPurchasable((ushort)x, (ushort)y); + return Realestate.IsPurchasable((ushort)x, (ushort)y) && Parent.CanPurchaseLots; } private bool IsTileOccupied(int x, int y) @@ -207,7 +281,7 @@ public void HoverTile(int x, int y) CurrentHoverLot.Value = null; if (HoverTimeout != null) { HoverTimeout.Clear(); } - if (Realestate.IsPurchasable((ushort)x, (ushort)y)) + if (IsPurchasable(x, y)) { HoverTimeout = GameThread.SetTimeout(() => { @@ -229,46 +303,50 @@ public void HoverTile(int x, int y) } public Texture2D RequestLotThumb(uint location) { - return LotThumbs.GetLotThumbForFrame((uint)Network.MyShard.Id, location); + return LotThumbs.GetLotThumbForFrame(ShardId, location); } public FSOF RequestLotFacade(uint location) { - return LotThumbs.GetLotFacadeForFrame((uint)Network.MyShard.Id, location); + return LotThumbs.GetLotFacadeForFrame(ShardId, location); } public void OverrideLotThumb(uint location, Texture2D tex) { - LotThumbs.OverrideLotThumb((uint)Network.MyShard.Id, location, tex); + LotThumbs.OverrideLotThumb(ShardId, location, tex); + } + + public void OverrideLotFacade(uint location, FSOF tex) + { + LotThumbs.OverrideLotFacade(ShardId, location, tex); } public LotThumbEntry LockLotThumb(uint location) { - return LotThumbs.GetLotEntry((uint)Network.MyShard.Id, location, false); + return LotThumbs.GetLotEntry(ShardId, location, false); } public void UnlockLotThumb(uint location) { - LotThumbs.ReleaseLotThumb((uint)Network.MyShard.Id, location, false); + LotThumbs.ReleaseLotThumb(ShardId, location, false); } public LotThumbEntry LockLotFacade(uint location) { - return LotThumbs.GetLotEntry((uint)Network.MyShard.Id, location, true); + return LotThumbs.GetLotEntry(ShardId, location, true); } public void UnlockLotFacade(uint location) { - LotThumbs.ReleaseLotThumb((uint)Network.MyShard.Id, location, true); + LotThumbs.ReleaseLotThumb(ShardId, location, true); } - public void ClickLot(int x, int y) + public void ClickLot(int x, int y, bool forceLotPage) { var id = MapCoordinates.Pack((ushort)x, (ushort)y); var occupied = IsTileOccupied(x, y); DataService.Get(id).ContinueWith(result => { - if (occupied) { GameThread.InUpdate(() => @@ -279,7 +357,14 @@ public void ClickLot(int x, int y) Parent.ShowLotPage(id); }); } - else if (!Realestate.IsPurchasable((ushort)x, (ushort)y)) + else if (forceLotPage && Realestate.IsOpenable((ushort)x, (ushort)y)) + { + GameThread.InUpdate(() => + { + Parent.ShowLotPage(id); + }); + } + else if (!IsPurchasable(x, y)) return; else if (PlacingTownHall && View.NeighGeom.NhoodNearestDB(x, y) != TownHallNhood) { @@ -460,12 +545,15 @@ private void ShowLotBuyDialog(Lot lot) else { //we don't have a lot - _LotBuyAlert = null; - ShowNormalLotBuy("$"+price.ToString(), "$" + ourCash.ToString()); - var canBuy = price <= ourCash; - UIButton toDisable; - if (_LotBuyAlert.ButtonMap.TryGetValue(UIAlertButtonType.Yes, out toDisable)) toDisable.Disabled = !canBuy; - GameFacade.Cursor.SetCursor(Common.Rendering.Framework.CursorType.Normal); + GameThread.InUpdate(() => + { + _LotBuyAlert = null; + ShowNormalLotBuy("$" + price.ToString(), "$" + ourCash.ToString()); + var canBuy = price <= ourCash; + UIButton toDisable; + if (_LotBuyAlert.ButtonMap.TryGetValue(UIAlertButtonType.Yes, out toDisable)) toDisable.Disabled = !canBuy; + GameFacade.Cursor.SetCursor(Common.Rendering.Framework.CursorType.Normal); + }); } }); }); @@ -644,5 +732,47 @@ public void ShowCreationProgressBar(bool show) } } } + + public void MessageReceived(AriesClient client, object message) + { + if (message is CityUpdateCommand cmd && View.Plugin is MapPainterPlugin map) + { + GameThread.NextUpdate(x => + { + switch (cmd.Mode) + { + case CityUpdateCommandMode.CommandError: + Realestate.SetMyTempCommand(null); + map.ShowError(53); + break; + case CityUpdateCommandMode.UndoError: + map.ShowError(54); + break; + } + }); + } + else if (message is CityUpdateResponse response) + { + GameThread.NextUpdate(x => + { + var screen = UIScreen.Current as CoreGameScreen; + + foreach (var item in response.Commands) + { + if (item.Command.AvatarId == Network.MyCharacter) + { + return; + } + + var mod = CityModification.FromCommand(View.MapData, item.Command); + if (mod != null) + { + View.AddModification(mod); + screen?.CityUpdateLayer?.RegisterModification(mod); + } + } + }); + } + } } } diff --git a/TSOClient/tso.client/Controllers/UpdateController.cs b/TSOClient/tso.client/Controllers/UpdateController.cs index 5517c4b56..578764246 100644 --- a/TSOClient/tso.client/Controllers/UpdateController.cs +++ b/TSOClient/tso.client/Controllers/UpdateController.cs @@ -1,19 +1,21 @@ -using FSO.Client.UI.Controls; +using FSO.Client.UI.Archive; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Panels; using FSO.Common; using FSO.Common.Utils; +using FSO.Files.FSO; using FSO.Server.Clients; -using System; -using System.Collections.Generic; +using Newtonsoft.Json; +using RestSharp; using System.Diagnostics; -using System.Linq; +using System.Security.Cryptography; namespace FSO.Client.Controllers { public class UpdateController : IDisposable { - private UIAlert _UpdaterAlert; + private UIDialog _UpdaterAlert; public ApiClient Api; private Action Continue; @@ -28,80 +30,43 @@ public void Dispose() } - public string GetPathString(UpdatePath path) + public void ShowUpdateDialog(UpdatePathNew path, bool autoUpdate = false) { - var result = ""; - for (int i = 0; i < path.Path.Count; i++) - { - var item = path.Path[i]; - if (i == 0) - { - if (path.FullZipStart) - { - result += "=> " + GameFacade.Strings.GetString("f101", path.MissingInfo ? "25" : "24") - + item.version_name + ((path.Path.Count == 1) ? "" : " \n"); - } - else - { - result += GameFacade.Strings.GetString("f101", "26") + GlobalSettings.Default.ClientVersion + " \n"; - } - } - if (i != 0 || !path.FullZipStart) - { - result += " -> "; - result += GameFacade.Strings.GetString("f101", "23"); - result += item.version_name + "\n"; - } - } - return result; - } + _UpdaterAlert = new UIUpdateDialog(path, autoUpdate); + _UpdaterAlert.SetController(this); - public void ShowUpdateDialog(UpdatePath path) - { - var targVer = path.Path.Last(); - _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions - { - Title = GameFacade.Strings.GetString("f101", "21"), - Message = GameFacade.Strings.GetString("f101", "22", new string[] { targVer.version_name, GlobalSettings.Default.ClientVersion, GetPathString(path) }), - Width = 500, - Buttons = UIAlertButton.YesNo(x => - { - AcceptUpdate(path); - }, - x => - { - RejectUpdate(); - }) - }, true); + UIScreen.GlobalShowDialog(_UpdaterAlert, true); } - public DownloadItem[] BuildFiles(UpdatePath path) + public DownloadItem[] BuildFiles(UpdatePathNew path) { + Directory.CreateDirectory("PatchFiles"); + File.WriteAllText($"PatchFiles/path.json", JsonConvert.SerializeObject(path)); + var result = new List(); - for (int i=0; i { + downloader.OnComplete += (bool success, string failedFile = null) => { UIScreen.RemoveDialog(downloader); if (success) { @@ -137,12 +104,13 @@ public void AcceptUpdate(UpdatePath path) } else { - UIScreen.GlobalShowAlert(new UIAlertOptions + _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions { Title = GameFacade.Strings.GetString("f101", "30"), - Message = GameFacade.Strings.GetString("f101", "28"), + Message = GameFacade.Strings.GetString("f101", "28", [ failedFile ]), Buttons = UIAlertButton.Ok(y => { + UIScreen.RemoveDialog(_UpdaterAlert); Continue(false); }) }, true); @@ -165,60 +133,305 @@ public void RejectUpdate() Width = 500, Buttons = UIAlertButton.Ok(y => { - //Regulator.AsyncTransition("AvatarData"); UIScreen.RemoveDialog(_UpdaterAlert); Continue(true); - //View.LoginDialog.Visible = true; - //View.LoginProgress.Visible = true; }) }, true); } else { Continue(false); - //View.LoginDialog.Visible = true; - //View.LoginProgress.Visible = true; - //Regulator.AsyncReset(); } }); } - public void DoUpdate(string versionName, string url) + private static FSOUpdateChannel TryGetChannel(FSOUpdateResponse response, FSOVersionInfo targetVersion) { - var str = GlobalSettings.Default.ClientVersion; + return response.channels.FirstOrDefault(x => x.channel == targetVersion.channel && x.publicKey == targetVersion.publicKey); + } - var split = str.LastIndexOf('-'); - int verNum = 0; - string curBranch = str; - if (split != -1) + private static RSA TryGetCrypto(string publicKey) + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(publicKey.Replace('^', '\n')); + + return rsa; + } + catch (Exception) { - int.TryParse(str.Substring(split + 1), out verNum); - curBranch = str.Substring(0, split); + return null; } + } + + public void PromptUpdate(FSOVersionInfo targetVersion) + { + // If the channel is different or the target version is a downgrade, show a warning before we fetch the changelog + + var current = FSOVersionInfo.Current; + + bool sameChannel = current.channel == targetVersion.channel; + bool sameUrl = current.channelUrl == targetVersion.channelUrl; + bool wasFsoCrypto = current.publicKey == FSOVersionInfo.FreeSOPublicKey; + bool fsoCrypto = targetVersion.publicKey == FSOVersionInfo.FreeSOPublicKey; + bool sameCrypto = current.publicKey == targetVersion.publicKey; + bool warnNoCrypto = !wasFsoCrypto && string.IsNullOrEmpty(targetVersion.publicKey); + + if (!sameUrl || !sameCrypto || !sameChannel || !fsoCrypto || warnNoCrypto) + { + // Warn the user before they've even fetched the data. + string message = GameFacade.Strings.GetString("f101", "40", + [ + BBCodeParser.SanitizeBB(targetVersion.channel), + BBCodeParser.SanitizeBB(targetVersion.id) + ]); + + if (string.IsNullOrEmpty(targetVersion.channelUrl)) + { + message += GameFacade.Strings.GetString("f101", "42"); + + _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("f101", "21"), + Message = message, + AllowBB = true, + Buttons = [ + new UIAlertButton(UIAlertButtonType.OK, (btn) => + { + RejectUpdate(); + }), + ] + }, true); + + return; + } + else if (warnNoCrypto) + { + // No crypto, not on official update + message += GameFacade.Strings.GetString("f101", "57"); + } + else if (!sameCrypto) + { + // Different provider + message += (wasFsoCrypto && !fsoCrypto) ? GameFacade.Strings.GetString("f101", "50") : GameFacade.Strings.GetString("f101", "56"); + } + else if (!sameUrl) + { + // Different update source + message += GameFacade.Strings.GetString("f101", "58"); + } + else if (!sameChannel) + { + // Different channel + message += GameFacade.Strings.GetString("f101", "51"); + } + _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("f101", "21"), + Message = message, + AllowBB = true, + Buttons = [ + new UIAlertButton(UIAlertButtonType.Cancel, (btn) => + { + RejectUpdate(); + }, GameFacade.Strings.GetString("f101", "44")), + new UIAlertButton(UIAlertButtonType.Yes, (btn) => + { + UIScreen.RemoveDialog(_UpdaterAlert); + DoUpdate(targetVersion); + }, GameFacade.Strings.GetString("f101", "37")), + ] + }, true); + } + else + { + DoUpdate(targetVersion); + } + } + + private static FSOVersionInfo GetVersionInfo(string url, FSOUpdateChannel channel, FSOUpdateMetadata update) + { + return new FSOVersionInfo() + { + id = update.id, + channelUrl = url, + channel = channel.channel, + publicKey = channel.publicKey, + }; + } + + public static void TryGetAutoUpdate(Action onResult) + { + var current = FSOVersionInfo.Current; + + RSA crypto = null; + if (current.publicKey.Length > 0) + { + crypto = TryGetCrypto(current.publicKey); + } + + var client = new RestClient(); + client.GetAsync(new RestRequest(current.channelUrl)).ContinueWith((x) => + { + if (!x.IsFaulted && !x.IsCanceled && x.Result.IsSuccessStatusCode) + { + var result = JsonConvert.DeserializeObject(x.Result.Content); + + GameThread.InUpdate(() => + { + Content.Content.Get().RCMeshes.Packages.TryUpdate(result); + }); + + FSOUpdateChannel channel; + if (result != null && (channel = TryGetChannel(result, current)) != null) + { + var target = channel.updates.FirstOrDefault(x => x.full?.CurrentPlatform() != null); + + if (target == null) + { + // No eligible version to update to. Just assume we're on the latest. + onResult(true, null, null); + return; + } + + var targetVersion = GetVersionInfo(current.channelUrl, channel, target); + var path = UpdatePathNew.FindPath(channel, current, targetVersion); + + if (path != null) + { + if (crypto != null) + { + // Validate signatures of the hashes for each part of the path. + + bool first = true; + foreach (var step in path.Path) + { + var file = ((path.FullZipStart && first) ? step.full : step.delta)?.CurrentPlatform(); + + if (file == null || !crypto.VerifyHash(Convert.FromBase64String(file.hash), Convert.FromBase64String(file.signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) + { + // The hash's signature doesn't match. + onResult(false, null, null); + } + + first = false; + } + } + + if (path.Path.Count == 0) + { + // Currently on the latest version. + onResult(true, null, null); + return; + } + + + onResult(true, targetVersion, path); + return; + } + } + } + + onResult(false, null, null); + }); + } + + public void DoUpdate(FSOVersionInfo targetVersion) + { + var current = FSOVersionInfo.Current; + + // Temporary dialog shown while getting update data. _UpdaterAlert = UIScreen.GlobalShowAlert(new UIAlertOptions() { Title = "", Message = GameFacade.Strings.GetString("f101", "27"), - Buttons = new UIAlertButton[0] + Buttons = [] }, true); - Api.GetUpdateList((updates) => + var client = new RestClient(); + client.GetAsync(new RestRequest(targetVersion.channelUrl)).ContinueWith((x) => { - UIScreen.RemoveDialog(_UpdaterAlert); - GameThread.InUpdate(() => + string failReason = GameFacade.Strings.GetString("f101", "32", [ targetVersion.channelUrl ]); + if (!x.IsFaulted && !x.IsCanceled && x.Result.IsSuccessStatusCode) { - UpdatePath path = null; - if (updates != null) - { - path = UpdatePath.FindPath(updates.ToList(), str, versionName); - } - if (path == null) + var result = JsonConvert.DeserializeObject(x.Result.Content); + + FSOUpdateChannel channel; + if (result != null && (channel = TryGetChannel(result, targetVersion)) != null) { - path = new UpdatePath(new List() { new ApiUpdate() { version_name = versionName, full_zip = url } }, true); - path.MissingInfo = true; + if (targetVersion.publicKey != null && targetVersion.publicKey != channel.publicKey) + { + // Public key is different between the server and the update channel. + failReason = GameFacade.Strings.GetString("f101", "54"); + } + else + { + var path = UpdatePathNew.FindPath(channel, current, targetVersion); + + if (path != null) + { + bool success = true; + + RSA crypto = null; + if (channel.publicKey.Length > 0) + { + crypto = TryGetCrypto(channel.publicKey); + } + + if (crypto != null) + { + // Validate signatures of the hashes for each part of the path. + + bool first = true; + foreach (var step in path.Path) + { + var file = ((path.FullZipStart && first) ? step.full : step.delta)?.CurrentPlatform(); + + if (file == null || !crypto.VerifyHash(Convert.FromBase64String(file.hash), Convert.FromBase64String(file.signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) + { + // The hash's signature doesn't match. + failReason = GameFacade.Strings.GetString("f101", "54"); + success = false; + } + + first = false; + } + } + + if (path.Path.Count == 0) + { + failReason = GameFacade.Strings.GetString("f101", "42"); + success = false; + } + + if (success) + { + GameThread.InUpdate(() => + { + UIScreen.RemoveDialog(_UpdaterAlert); + ShowUpdateDialog(path); + }); + return; + } + } + } } - ShowUpdateDialog(path); + } + + GameThread.InUpdate(() => + { + UIScreen.RemoveDialog(_UpdaterAlert); + + UIAlert.Alert( + GameFacade.Strings.GetString("f101", "30"), // Updater failed + failReason, + true + ); + + Continue(false); }); }); } @@ -230,8 +443,7 @@ public void RestartGamePatch() if (FSOEnvironment.Linux) { var fsoargs = FSOEnvironment.Args; - if (fsoargs.Length > 0) fsoargs = " " + fsoargs; - var args = new ProcessStartInfo("mono", "update.exe" + fsoargs); + var args = new ProcessStartInfo("update", fsoargs); args.UseShellExecute = false; System.Diagnostics.Process.Start(args); } diff --git a/TSOClient/tso.client/Controllers/UserListController.cs b/TSOClient/tso.client/Controllers/UserListController.cs new file mode 100644 index 000000000..cf050a11b --- /dev/null +++ b/TSOClient/tso.client/Controllers/UserListController.cs @@ -0,0 +1,89 @@ +using FSO.Client.Regulators; +using FSO.Client.UI.Model; +using FSO.Common.Utils; +using FSO.Server.Clients; +using FSO.Server.Protocol.Electron.Packets; +using FSO.UI.Model; +using System; + +namespace FSO.Client.Controllers +{ + internal class UserListController : IAriesMessageSubscriber, IDisposable + { + private CityConnectionRegulator City; + + public ArchiveClientList UserList; + + public Action FlashCallback; + + public UserListController(CityConnectionRegulator city) + { + City = city; + + UserList = City.UserList; + + City.Client.AddSubscriber(this); + } + + private void SignalNewVerification() + { + FlashCallback?.Invoke(true); + HIT.HITVM.Get().PlaySoundEvent(UISounds.LetterQueueFull); + } + + private void SignalNoVerifications() + { + FlashCallback?.Invoke(false); + } + + private void UpdateUserList(ArchiveClientList newList) + { + if (UserList != null && newList != null) + { + // Try determine the difference. If there are new verifications pending, play a sound and notify the user list button. + + if (newList.Pending.Length == 0) + { + SignalNoVerifications(); + } + else if (newList.Pending.Length != UserList.Pending.Length) + { + SignalNewVerification(); + } + else + { + // Are any new pending verifications not in the last? + + foreach (var newEntry in newList.Pending) + { + if (Array.FindIndex(UserList.Pending, (oldEntry) => oldEntry.UserId == newEntry.UserId) == -1) + { + SignalNewVerification(); + break; + } + } + } + + DiscordRpcEngine.SetArchivePlayers(UserList.Clients.Length); + } + + UserList = newList; + } + + public void MessageReceived(AriesClient client, object message) + { + if (message is ArchiveClientList list) + { + GameThread.InUpdate(() => + { + UpdateUserList(list); + }); + } + } + + public void Dispose() + { + City.Client.RemoveSubscriber(this); + } + } +} diff --git a/TSOClient/tso.client/Dependencies/GOLDEngine.dll b/TSOClient/tso.client/Dependencies/GOLDEngine.dll deleted file mode 100644 index c6e125652..000000000 Binary files a/TSOClient/tso.client/Dependencies/GOLDEngine.dll and /dev/null differ diff --git a/TSOClient/tso.client/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll b/TSOClient/tso.client/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll deleted file mode 100644 index 501d73414..000000000 Binary files a/TSOClient/tso.client/Dependencies/Nuclex.Fonts.Content.TrueTypeImporter.dll and /dev/null differ diff --git a/TSOClient/tso.client/Dependencies/TargaImage.dll b/TSOClient/tso.client/Dependencies/TargaImage.dll deleted file mode 100644 index b49d9d8cf..000000000 Binary files a/TSOClient/tso.client/Dependencies/TargaImage.dll and /dev/null differ diff --git a/TSOClient/tso.client/Dependencies/nunit.framework.dll b/TSOClient/tso.client/Dependencies/nunit.framework.dll deleted file mode 100644 index 50e26cc46..000000000 Binary files a/TSOClient/tso.client/Dependencies/nunit.framework.dll and /dev/null differ diff --git a/TSOClient/tso.client/FSO.Client.csproj b/TSOClient/tso.client/FSO.Client.csproj index 0f8b1c503..de58e3a4f 100644 --- a/TSOClient/tso.client/FSO.Client.csproj +++ b/TSOClient/tso.client/FSO.Client.csproj @@ -1,790 +1,75 @@ - - + + - Debug - x86 - 8.0.30703 - 2.0 - {635E68FA-3905-4943-B4F5-D463A8C02E87} + net9.0 + enable + disable + fso.ico + True + true + true + true + full Library Properties FSO.Client FSO.Client 512 - false - v4.5 - - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - x86 - true - full - false - bin\WindowsGL\Debug\ - DEBUG;TRACE;WINDOWS - prompt - 4 - AllRules.ruleset - true - false - - - x86 - pdbonly - true - bin\WindowsGL\Release\ - TRACE;WINDOWS - prompt - 4 - AllRules.ruleset - false - true - - - fso.ico - - - - - true - bin\Debug\ - DEBUG;TRACE;WINDOWS - full - AnyCPU - false - prompt - AllRules.ruleset + + + True - - bin\Release\ - TRACE;WINDOWS - true - pdbonly - AnyCPU - prompt - AllRules.ruleset - - - bin\x86\ServerRelease\ - TRACE;WINDOWS - true - pdbonly - x86 - prompt - AllRules.ruleset - - - bin\ServerRelease\ - TRACE;WINDOWS - true - pdbonly - AnyCPU - prompt - AllRules.ruleset - true + + + True + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Monogame\Linux\MonoGame.Framework.dll - PreserveNewest - - - Monogame\WindowsGL\MonoGame.Framework.dll - PreserveNewest - - - Monogame\Windows\MonoGame.Framework.dll - PreserveNewest - - - Monogame\Windows\SharpDX.D3DCompiler.dll - PreserveNewest - - - Monogame\Windows\SharpDX.D3DCompiler.xml - PreserveNewest - - - Monogame\Windows\SharpDX.Direct2D1.dll - PreserveNewest - - - Monogame\Windows\SharpDX.Direct2D1.xml - PreserveNewest - - - Monogame\Windows\SharpDX.Direct3D11.dll - PreserveNewest - - - Monogame\Windows\SharpDX.Direct3D11.xml - PreserveNewest - - - Monogame\Windows\SharpDX.dll - PreserveNewest - - - Monogame\Windows\SharpDX.DXGI.dll - PreserveNewest - - - Monogame\Windows\SharpDX.DXGI.xml - PreserveNewest - - - Monogame\Windows\SharpDX.MediaFoundation.dll - PreserveNewest - - - Monogame\Windows\SharpDX.MediaFoundation.xml - PreserveNewest - - - Monogame\Windows\SharpDX.XAudio2.dll - PreserveNewest - - - Monogame\Windows\SharpDX.XAudio2.xml - PreserveNewest - - - Monogame\Windows\SharpDX.XInput.dll - PreserveNewest - - - Monogame\Windows\SharpDX.XInput.xml - PreserveNewest - - - Monogame\Windows\SharpDX.xml - PreserveNewest - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + + PreserveNewest + - - Always - - - - PreserveNewest - - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\MIConvexHull.1.1.17.1019\lib\netstandard1.0\MIConvexHull.dll - - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - ..\packages\MonoGame.Framework.WindowsGL.3.4.0.459\lib\net40\OpenTK.dll - True - - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - - - - - - - Dependencies\GOLDEngine.dll - + - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - - - - + + + + + + + + + - + + + + + + + + + + + + + - - OpenTK.dll.config - Always - - - Designer - - - PreserveNewest - - - PreserveNewest - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - {6d6009f4-0afb-4806-89d7-7945f20270f5} - MonoGame.Framework.Net.WindowsGL - - - {6d75e618-19ca-4c51-9546-f10965fbc0b8} - MonoGame.Framework.WindowsGL - - - {834cab58-648d-47cc-ac6f-d01c08c809a4} - Mp3Sharp - - - {eabea510-3e53-4f19-9f0b-75c5ca9dfa3b} - MSDFData - - - {5d6b850b-3084-4c45-a8d7-7ccf67260b21} - VoronoiLib - - - {c051793d-1a9c-4554-9bb8-bafdc01a096a} - FSO.Common.DatabaseService - - - {9848faf5-444a-48cc-a26a-8115d8c4fb52} - FSO.Common.Domain - - - {b5b2c04d-b8e4-47c7-9731-48e30fd5f70d} - FSO.Content.TSO - - - {4e43ce64-343f-4c53-a055-bbf0f4986a16} - FSO.Patcher - - - {329e0aee-7871-40a7-b5af-8c0d0086ef71} - FSO.Server.Clients - - - {88c69e02-78d4-4d71-9c26-43a9b118285a} - FSO.Common.DataService - - - {a08ade32-27e2-44f4-bc52-11a16c56baa8} - FSO.Server.Protocol - - - {73e2ad5b-720b-4ef3-9b7c-55931d0ec693} - FSO.UI - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4} - FSO.Debug - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF} - FSO.SimAntics - - - {072781D8-51EC-4143-9CAE-DAF50177D3AD} - FSO.HIT - - - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} - FSO.Vitaboy.Engine - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - - - {B1A6E4C2-E080-4C34-A604-D11B5296A9B8} - FSO.LotView - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + NVorbis.dll + + + OpenTK.dll + - - - - \ No newline at end of file + + diff --git a/TSOClient/tso.client/FSO.Client.csproj.rhys.nvuser b/TSOClient/tso.client/FSO.Client.csproj.rhys.nvuser deleted file mode 100644 index 9d26b2c13..000000000 --- a/TSOClient/tso.client/FSO.Client.csproj.rhys.nvuser +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.client/FSOFacade.cs b/TSOClient/tso.client/FSOFacade.cs index e44c54f19..61e5d853f 100644 --- a/TSOClient/tso.client/FSOFacade.cs +++ b/TSOClient/tso.client/FSOFacade.cs @@ -1,7 +1,9 @@ using FSO.Client.Network; using FSO.Client.UI.Hints; using FSO.Client.UI.Panels; +using FSO.Common; using Ninject; +using System.Diagnostics; namespace FSO.Client { @@ -13,5 +15,55 @@ public class FSOFacade public static NetworkStatus NetStatus = new NetworkStatus(); public static UIHintManager Hints; + + private static string GetFreeSOName() + { + if (Environment.OSVersion.Platform == PlatformID.Unix || Environment.OSVersion.Platform == PlatformID.MacOSX) + { + return "FreeSO"; + } + else + { + return "FreeSO.exe"; + } + } + + public static void RestartGame() + { + try + { + var fsoExe = GetFreeSOName(); + + var args = FSOEnvironment.Args; + if (OperatingSystem.IsWindows()) + { + Process.Start(fsoExe, string.Join(" ", FSOEnvironment.Args)); + } + else if (OperatingSystem.IsMacOS()) + { + var startArgs = new ProcessStartInfo("open", $"../../ --args " + args) + { + UseShellExecute = false + }; + + Process.Start(startArgs); + } + else + { + var startArgs = new ProcessStartInfo(fsoExe, args) + { + UseShellExecute = false + }; + + Process.Start(startArgs); + } + } + catch + { + + } + + GameFacade.Kill(); + } } } diff --git a/TSOClient/tso.client/FSOProgram.cs b/TSOClient/tso.client/FSOProgram.cs index e6c7f79ce..6a661cdb7 100644 --- a/TSOClient/tso.client/FSOProgram.cs +++ b/TSOClient/tso.client/FSOProgram.cs @@ -1,12 +1,9 @@ using FSO.Client.Utils; using FSO.Client.Utils.GameLocator; using FSO.Common; +using FSO.Files.FSO; using FSO.UI; -using System; -using System.IO; -using System.Linq; using System.Reflection; -using System.Threading; namespace FSO.Client { @@ -16,6 +13,8 @@ public class FSOProgram : IFSOProgram public static Action ShowDialog = DefaultShowDialog; + public static Action> RegisterDragCallback = (window, func) => { }; + public static void DefaultShowDialog(string text) { Console.WriteLine(text); @@ -41,7 +40,7 @@ public bool InitWithArguments(string[] args) else gameLocator = new WindowsLocator(); - bool useDX = false; + bool useDX = !linux; #region User resolution parmeters @@ -90,6 +89,11 @@ public bool InitWithArguments(string[] args) break; case "3d": FSOEnvironment.Enable3D = true; + FSOEnvironment.Default3D = true; + break; + case "2d": + FSOEnvironment.Enable3D = false; + FSOEnvironment.Default3D = false; break; case "touch": FSOEnvironment.SoftwareKeyboard = true; @@ -113,15 +117,21 @@ public bool InitWithArguments(string[] args) UseDX = MonogameLinker.Link(useDX); - var path = gameLocator.FindTheSimsOnline(); + var settingsPath = GlobalSettings.Default.StartupPath; + + var path = ILocator.ValidPath(settingsPath) ? settingsPath : gameLocator.FindTheSimsOnline(); + + if (!Path.EndsInDirectorySeparator(path)) + { + path += Path.DirectorySeparatorChar; + } if (path != null) { //check if this path has tso in it. tuning.dat should be a good indication. - if (!File.Exists(Path.Combine(path, "tuning.dat"))) + if (!ILocator.ValidPath(path)) { - ShowDialog("The Sims Online appears to be missing. The game expects TSO at directory '"+path+"', but some core files are missing from that folder. If you know you installed TSO into a different directory, please move it into the directory specified."); - return false; + FSOEnvironment.MissingTSO = true; } FSOEnvironment.Args = string.Join(" ", args); @@ -174,19 +184,7 @@ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionE private string GetClientVersion() { - string ExeDir = GlobalSettings.Default.StartupPath; - - if (File.Exists("version.txt")) - { - using (StreamReader Reader = new StreamReader(File.Open("version.txt", FileMode.Open, FileAccess.Read, FileShare.Read))) - { - return Reader.ReadLine(); - } - } - else - { - return "(?)"; - } + return FSOVersionInfo.Current.id; } } } diff --git a/TSOClient/tso.client/GameContent/ContentManager.cs b/TSOClient/tso.client/GameContent/ContentManager.cs deleted file mode 100644 index 3fbdea77a..000000000 --- a/TSOClient/tso.client/GameContent/ContentManager.cs +++ /dev/null @@ -1,677 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -Mats 'Afr0' Vederhus. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Drawing; -using System.Threading; -using System.IO; -using System.Linq; -using System.Xml; -using FSO.Client.Network; -using Microsoft.Xna.Framework.Graphics; -using LogThis; -using FSO.Client.Utils; -using FSO.Client.UI.Framework; -using FSO.Files.FAR3; -using FSO.Client; - -namespace FSO.Client.GameContent -{ - public delegate void OnLoadingUpdatedDelegate(string LoadingText); - - public class ContentManager - { - private const int m_CACHESIZE = 104857600; //100 megabytes. - private static int m_CurrentCacheSize = 0; - - private static Dictionary m_Resources; - private static Dictionary m_LoadedResources; - private static bool initComplete = false; - - private static ManualResetEvent m_ResetEvent = new ManualResetEvent(false); - //public static event OnLoadingUpdatedDelegate OnLoadingUpdatedEvent; - - private static Dictionary m_Archives = new Dictionary(); - - /// - /// These are all the resources which have been precomputed and cached, this is to improve load time - /// - private static Dictionary m_CachedResources = new Dictionary(); - - static ContentManager() - { - m_Resources = new Dictionary(); - m_LoadedResources = new Dictionary(); - - XmlDocument AnimTable = new XmlDocument(); - AnimTable.Load(GlobalSettings.Default.StartupPath + "packingslips/animtable.xml"); - - XmlNodeList NodeList = AnimTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - //TODO: Figure out when to use avatardata2 and avatardata3... - string FileName = GlobalSettings.Default.StartupPath + "avatardata/animations/animations.dat"; - - m_Resources.Add(FileID, FileName); - } - - XmlDocument UIGraphicsTable = new XmlDocument(); - UIGraphicsTable.Load(GlobalSettings.Default.StartupPath + "packingslips/uigraphics.xml"); - - NodeList = UIGraphicsTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - else - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - - m_Resources.Add(FileID, FileName); - } - - XmlDocument CollectionsTable = new XmlDocument(); - CollectionsTable.Load(GlobalSettings.Default.StartupPath + "packingslips/collections.xml"); - - NodeList = CollectionsTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - m_Resources.Add(FileID, FileName); - } - - XmlDocument PurchasablesTable = new XmlDocument(); - PurchasablesTable.Load(GlobalSettings.Default.StartupPath + "packingslips/purchasables.xml"); - - NodeList = PurchasablesTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - m_Resources.Add(FileID, FileName); - } - - XmlDocument OutfitsTable = new XmlDocument(); - OutfitsTable.Load(GlobalSettings.Default.StartupPath + "packingslips/alloutfits.xml"); - - NodeList = OutfitsTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - m_Resources.Add(FileID, FileName); - } - - XmlDocument AppearancesTable = new XmlDocument(); - AppearancesTable.Load(GlobalSettings.Default.StartupPath + "packingslips/appearances.xml"); - - NodeList = AppearancesTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - if (!m_Resources.ContainsKey(FileID)) - m_Resources.Add(FileID, FileName); - } - - XmlDocument ThumbnailsTable = new XmlDocument(); - ThumbnailsTable.Load(GlobalSettings.Default.StartupPath + "packingslips/thumbnails.xml"); - - NodeList = ThumbnailsTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - m_Resources.Add(FileID, FileName); - } - - XmlDocument MeshTable = new XmlDocument(); - MeshTable.Load(GlobalSettings.Default.StartupPath + "packingslips/meshes.xml"); - - NodeList = MeshTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - if (!m_Resources.ContainsKey(FileID)) - m_Resources.Add(FileID, FileName); - } - - XmlDocument TextureTable = new XmlDocument(); - TextureTable.Load(GlobalSettings.Default.StartupPath + "packingslips/textures.xml"); - - NodeList = TextureTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - if (!m_Resources.ContainsKey(FileID)) - m_Resources.Add(FileID, FileName); - } - - XmlDocument BindingsTable = new XmlDocument(); - BindingsTable.Load(GlobalSettings.Default.StartupPath + "packingslips/bindings.xml"); - - NodeList = BindingsTable.GetElementsByTagName("DefineAssetString"); - - foreach (XmlNode Node in NodeList) - { - ulong FileID = Convert.ToUInt64(Node.Attributes["assetID"].Value, 16); - string FileName = ""; - - if (Node.Attributes["key"].Value.Contains(".dat")) - { - FileName = GlobalSettings.Default.StartupPath + Node.Attributes["key"].Value; - } - - if (!m_Resources.ContainsKey(FileID)) - m_Resources.Add(FileID, FileName); - } - - /*var cacheFiles = Directory.GetFiles(GameFacade.CacheDirectory); - foreach (var file in cacheFiles) - { - var fileName = Path.GetFileNameWithoutExtension(file); - m_CachedResources.Add(ulong.Parse(fileName), file); - }*/ - - m_Resources.Add(0x100000005, GlobalSettings.Default.StartupPath + "avatardata/skeletons/skeletons.dat"); - - initComplete = true; - GameFacade.TriggerContentLoaderReady(); - } - - public static byte[] GetResourceFromLongID(ulong ID) - { - var rsrc = GetResourceInfo(ID); - if (rsrc != null) { return rsrc.Data; } - return null; - } - - public static ContentResource GetResourceInfo(ulong ID) - { - /** Busy wait until we are ready **/ - while (!initComplete) ; - - ContentResource result = null; - - if (!m_LoadedResources.TryGetValue(ID, out result)) - { - result = new ContentResource - { - ID = ID - }; - - string path = m_Resources[ID].Replace('\\', '/'); - result.FilePath = path; - result.FileExtension = Path.GetExtension(path).ToLowerInvariant(); - - if (!path.EndsWith(".dat")) - { - /** Isnt an archive **/ - result.Data = File.ReadAllBytes(path); - return result; - } - - if (!m_Archives.ContainsKey(path)) - { - FAR3Archive Archive = new FAR3Archive(path); - m_Archives.Add(path, Archive); - } - - result.Data = m_Archives[path].GetItemByID(ID); - - return result; - } - else - { - return result; - } - } - - public byte[] this[ulong FileID] - { - get - { - var result = GetResourceInfo(FileID); - if (result == null) { return null; } - return result.Data; - } - } - - /// - /// Tries to store a resource in the internal cache. - /// - /// The ID of the resource to store. - /// The resource to store. - public static void TryToStoreResource(ulong ID, ContentResource Resource) - { - lock (m_LoadedResources) - { - if (m_CurrentCacheSize < m_CACHESIZE) - { - if (!m_LoadedResources.ContainsKey(ID)) - { - m_LoadedResources.Add(ID, Resource); - m_CurrentCacheSize += Resource.Data.Length; - } - } - else - { - ulong LastKey = m_LoadedResources.Keys.Last(); - - m_CurrentCacheSize -= m_LoadedResources[LastKey].Data.Length; - m_LoadedResources.Remove(LastKey); - - m_LoadedResources.Add(ID, Resource); - m_CurrentCacheSize += Resource.Data.Length; - } - } - } - - private static EventWaitHandle myLoadingScreenEWH; - - /// - /// Initializes loading of resources. - /// - /// A ScreenManager instance, used to access a GraphicsDevice. - public static void InitLoading() - { - Thread T = new Thread(new ParameterizedThreadStart(LoadContent)); - //TODO: This should only be set to speed up debug - T.Priority = ThreadPriority.AboveNormal; - T.Start(); - - } - - - public static float PreloadProgress = 0.0f; - - /// - /// Threading function that takes care of loading. - /// - private static void LoadContent(object ThreadObject) - { - PreloadProgress = 1.0f; - return; - - var loadingList = new List(); - - /** UI Textures **/ - loadingList.AddRange( - CollectionUtils.Select( - Enum.GetValues(typeof(FileIDs.UIFileIDs)), - x => new ContentPreload - { - ID = (ulong)((long)x), - Type = ContentPreloadType.Other - } - ) - ); - - ///** Sim textures for CAS **/ - loadingList.AddRange( - CollectionUtils.Select( - Enum.GetValues(typeof(FileIDs.OutfitsFileIDs)), - x => new ContentPreload - { - ID = (ulong)((long)x), - Type = ContentPreloadType.Other - } - ) - ); - loadingList.AddRange( - CollectionUtils.Select( - Enum.GetValues(typeof(FileIDs.AppearancesFileIDs)), - x => new ContentPreload - { - ID = (ulong)((long)x), - Type = ContentPreloadType.Other - } - ) - ); - loadingList.AddRange( - CollectionUtils.Select( - Enum.GetValues(typeof(FileIDs.PurchasablesFileIDs)), - x => new ContentPreload - { - ID = (ulong)((long)x), - Type = ContentPreloadType.Other - } - ) - ); - loadingList.AddRange( - CollectionUtils.Select( - Enum.GetValues(typeof(FileIDs.ThumbnailsFileIDs)), - x => new ContentPreload - { - ID = (ulong)((long)x), - Type = ContentPreloadType.Other - } - ) - ); - - var startTime = DateTime.Now; - - var totalItems = (float)loadingList.Count; - loadingList.Shuffle(); - - var loadingListLength = loadingList.Count; - for (var i = 0; i < loadingListLength; i++) - { - var item = loadingList[i]; - try - { - ContentResource contentItem = null; - contentItem = ContentManager.GetResourceInfo(item.ID); - - switch (item.Type) - { - case ContentPreloadType.UITexture: - /** Apply alpha channel masking & load into GD **/ - UIElement.StoreTexture(item.ID, contentItem, true, true); - break; - - case ContentPreloadType.UITexture_NoMask: - UIElement.StoreTexture(item.ID, contentItem, false, true); - break; - - case ContentPreloadType.Other: - ContentManager.TryToStoreResource(item.ID, contentItem); - break; - } - } - catch (Exception) - { - } - - PreloadProgress = i / totalItems; - } - - var endTime = DateTime.Now; - System.Diagnostics.Debug.WriteLine("Content took " + new TimeSpan(endTime.Ticks - startTime.Ticks).ToString() + " to load"); - - PreloadProgress = 1.0f; - - - } - - private static void ProcessResource(ContentPreload resource, ContentResource item) - { - var id = resource.ID; - - try - { - switch (resource.Type) - { - case ContentPreloadType.UITexture: - /** Apply alpha channel masking & load into GD **/ - UIElement.StoreTexture(id, item, true, true); - break; - - case ContentPreloadType.UITexture_NoMask: - UIElement.StoreTexture(id, item, false, true); - break; - - case ContentPreloadType.Other: - ContentManager.TryToStoreResource(id, item); - break; - } - } - catch (Exception e) - { - System.Diagnostics.Debug.WriteLine("Failed to load file: " + id + ", " + e.Message); - } - } - - public static Dictionary GetResources() - { - return m_Resources; - } - } - - /// - /// Creates a binary file which contains several other non compressed files, - /// The file format is: - /// [fileID],[fileLength],[fileData] - /// - public class BlobCache - { - private BinaryWriter Writer; - private string FilePath; - - public BlobCache(string filePath) - { - this.FilePath = filePath; - } - - public Dictionary ReadAll() - { - var result = new Dictionary(); - if (!File.Exists(FilePath)) - { - return result; - } - - using (var reader = new BinaryReader(File.OpenRead(FilePath))) - { - - var len = reader.BaseStream.Length; - while (len > 8) - { - var fileID = reader.ReadUInt64(); - var fileLength = reader.ReadInt32(); - var fileData = reader.ReadBytes(fileLength); - - result.Add(fileID, fileData); - - len -= 16; - len -= fileLength; - } - - return result; - } - } - - public void StartWrite() - { - if (File.Exists(FilePath)) - { - - Writer = new BinaryWriter(File.OpenWrite(FilePath)); - } - else - { - - Writer = new BinaryWriter(File.Create(FilePath)); - } - } - - public void AddFile(ulong id, byte[] data) - { - Writer.Write(id); - Writer.Write(data.Length); - Writer.Write(data); - } - - public void Flush() - { - Writer.Flush(); - Writer.Close(); - } - } - - public class ContentProcessingPool - { - private Semaphore m_Lock; - private bool m_Done; - private List m_Work = new List(); - - public ContentProcessingPool(int maxThreads) - { - m_Lock = new Semaphore(maxThreads, maxThreads); - - /** Spawn threads **/ - m_Done = false; - - for (var i = 0; i < maxThreads; i++) - { - var thread = new Thread(new ThreadStart(DoProcess)); - thread.Start(); - } - } - - private void DoProcess() - { - while (!m_Done) - { - ContentPreload nextWork = null; - lock (m_Work) - { - if (m_Work.Count > 0) - { - nextWork = m_Work[0]; - m_Work.RemoveAt(0); - } - } - if (nextWork != null) - { - ProcessResource(nextWork); - m_Lock.Release(); - - } - } - } - - public void Process(ContentPreload resource, ContentResource item) - { - m_Lock.WaitOne(); - - lock (m_Work) - { - resource.Item = item; - m_Work.Add(resource); - } - } - - private void ProcessResource(ContentPreload resource) - { - try - { - var id = resource.ID; - var item = resource.Item; - - switch (resource.Type) - { - case ContentPreloadType.UITexture: - /** Apply alpha channel masking & load into GD **/ - UIElement.StoreTexture(id, item, true, true); - break; - - case ContentPreloadType.UITexture_NoMask: - UIElement.StoreTexture(id, item, false, false); - break; - - case ContentPreloadType.Other: - ContentManager.TryToStoreResource(id, item); - break; - } - } - catch - { - } - } - - } - - public class ContentResource - { - public ulong ID; - public byte[] Data; - public string FileExtension; - public string FilePath; - public bool FromCache; - } - - public class ContentPreload - { - public ContentPreloadType Type; - public ulong ID; - public ContentResource Item; - } - - public enum ContentPreloadType - { - UITexture, - UITexture_NoMask, - Other - } -} \ No newline at end of file diff --git a/TSOClient/tso.client/GameController.cs b/TSOClient/tso.client/GameController.cs index 81698b232..7132a0818 100644 --- a/TSOClient/tso.client/GameController.cs +++ b/TSOClient/tso.client/GameController.cs @@ -1,26 +1,28 @@ -using System; -using System.Collections.Generic; -using FSO.Client.UI.Screens; -using FSO.Client.Network; -using FSO.Client.UI.Framework; +using FSO.Client.Controllers; +using FSO.Client.Controllers.Panels; using FSO.Client.GameContent; -using Ninject; -using FSO.Server.Protocol.CitySelector; -using FSO.Client.Controllers; -using FSO.Common.Utils; +using FSO.Client.Network; +using FSO.Client.UI; +using FSO.Client.UI.Archive; using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; using FSO.Client.UI.Panels; -using FSO.Client.UI; -using FSO.Common.DatabaseService.Model; -using FSO.Server.Protocol.Electron.Packets; +using FSO.Client.UI.Screens; using FSO.Client.Utils; -using FSO.Server.Protocol.Voltron.Packets; -using FSO.Client.Controllers.Panels; -using System.Collections.Immutable; +using FSO.Common; +using FSO.Common.DatabaseService.Model; using FSO.Common.DataService.Model; using FSO.Common.Serialization.Primitives; +using FSO.Common.Utils; +using FSO.Server.Clients; +using FSO.Server.Embedded; +using FSO.Server.Protocol.CitySelector; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Voltron.Packets; using FSO.UI.Model; using MSDFData; +using Ninject; +using System.Collections.Immutable; namespace FSO.Client { @@ -34,6 +36,13 @@ public class GameController private IKernel Kernel; private static bool DummyLinker; + private EmbeddedServer Server; + private UIDialog ShutdownDialog; + private uint? ArchiveLotId; + + private UIAlert FatalAlert; + private int FatalAlertPriority; + public GameController(IKernel kernel) { this.Kernel = kernel; @@ -62,8 +71,16 @@ public void StartLoading() public void Start() { var version = Content.Content.Get().VersionString; - if (version == "1.1097.1.0") StartLoading(); - else WrongVersion(); + FSOProgram.RegisterDragCallback(GameFacade.Game.Window.Handle, DragDrop); + if (FSOEnvironment.MissingTSO) + { + TsoInstaller(); + } + else + { + if (version == "1.1097.1.0") StartLoading(); + else WrongVersion(); + } } /// @@ -76,21 +93,79 @@ public void WrongVersion() GameFacade.Screens.AddScreen(screen); } + /// + /// Shows up if The Sims Online isn't detected, allows the user to download and extract the game files. + /// + public void TsoInstaller() + { + var screen = Kernel.Get(); + GameFacade.Screens.RemoveCurrent(); + GameFacade.Screens.AddScreen(screen); + } + + private void UpdaterCleanup() + { + try + { + if (File.Exists("updateError.txt")) + { + UIAlert.Alert(GameFacade.Strings.GetString("f101", "30"), File.ReadAllText("updateError.txt"), true); + File.Delete("updateError.txt"); + } + } + catch (Exception) + { + //maybe signal to user that the updater update failed + } + + try + { + if (File.Exists("update2.exe")) + { + File.Delete("update.exe"); + File.Move("update2.exe", "update.exe"); + } + + if (File.Exists("update2")) + { + File.Delete("update"); + File.Move("update2", "update"); + } + } + catch (Exception) + { + //maybe signal to user that the updater update failed + } + } + /// /// Show the login screen /// public void ShowLogin() + { + InitializeArchive(); + UpdaterCleanup(); + } + + public void ShowServerLogin() { ChangeState((view, controller) => { - DiscordRpcEngine.SendFSOPresence("In Main Menu"); }); - /* - var screen = Kernel.Get(); - GameFacade.Screens.RemoveCurrent(); - GameFacade.Screens.AddScreen(screen); - */ + } + + public void ShowServerLogin(string url) + { + GlobalSettings.Default.GameEntryUrl = url; + GlobalSettings.Default.CitySelectorUrl = url; + GlobalSettings.Default.Save(); + + var kernel = FSOFacade.Kernel; + kernel.Get().SetBaseUrl(url); + kernel.Get().SetBaseUrl(url); + + ShowServerLogin(); } /// @@ -149,7 +224,7 @@ public void LinkEveryController() var casr = new Regulators.CreateASimRegulator(null); var purch = new Regulators.PurchaseLotRegulator(null); var conn = new Regulators.LotConnectionRegulator(null, null, null); - var t2 = new Regulators.CityConnectionRegulator(null, null, null, null, Kernel, null); + var t2 = new Regulators.CityConnectionRegulator(null, null, null, null, Kernel, null, null); var neigh = new Regulators.GenericActionRegulator(null); var bulletin = new Regulators.GenericActionRegulator(null); var regu = new Regulators.RegulatorsModule(); @@ -201,14 +276,14 @@ public void LinkEveryController() new LotAdmitController(null, null, null), new GizmoController(null, null, null), new PersonPageController(null,null,null), - new LotPageController(null, null), + new LotPageController(null, null, null), new BookmarksController(null, null, null), new RelationshipDialogController(null, null, null, null), new InboxController(null, null, null, null), new JoinLotProgressController(null, null), new DisconnectController(null, null, null, null, null), new GenericSearchController(null, null), - new NeighPageController(null, null), + new NeighPageController(null, null, null), new RatingListController(null, null, null), new RatingSummaryController(null, null, null), new NeighborhoodActionController(null), @@ -223,6 +298,15 @@ public void LinkEveryController() }; } + public void InitializeArchive() + { + ChangeState((view, controller) => + { + controller.Initialize(); + DiscordRpcEngine.SendFSOPresence("In Archive Mode"); + }); + } + public void ConnectToCity(string cityName, uint avatarId, uint? lotId) { @@ -232,6 +316,18 @@ public void ConnectToCity(string cityName, uint avatarId, uint? lotId) }); } + public void SetArchiveLot(uint lotId) + { + ArchiveLotId = lotId == 0 ? null : lotId; + } + + public void ConnectToArchive(string displayName, string address, bool selfHost) + { + ArchiveLotId = null; + var controller = CurrentController as ConnectArchiveController; + controller.Connect(displayName, address, selfHost, () => { GotoCity(controller.AvatarData, ArchiveLotId); }, new Common.Utils.Callback(Disconnect)); + } + public void RetireAvatar(string cityName, uint avatarId) { ChangeState((view, controller) => @@ -257,13 +353,32 @@ public void ConnectToCAS(string cityName) */ ChangeState((view, controller) => { - controller.Connect(cityName, new Common.Utils.Callback(GotoCAS), new Common.Utils.Callback(Disconnect)); + controller.Connect(cityName, new Common.Utils.Callback(() => GotoCAS()), new Common.Utils.Callback(Disconnect)); + }); + } + + public void SelectFromCASArchive(uint avatarId) + { + // Change back to the archive transition screen and choose the new character. + ChangeState((view, controller) => + { + controller.SetCallbacks(() => { GotoCity(controller.AvatarData, null); }, new Common.Utils.Callback(Disconnect)); + controller.SelectAvatar(avatarId); }); } - public void GotoCAS(){ + public void ReturnToSASArchive() + { + // Change back to the archive transition screen and reconnect. + ChangeState((view, controller) => + { + controller.ReturnToSAS(() => { GotoCity(controller.AvatarData, ArchiveLotId); }, new Common.Utils.Callback(Disconnect)); + }); + } + + public void GotoCAS(bool archive = false){ ChangeState((view, controller) => { - + controller.Archive = archive; }); } @@ -283,12 +398,20 @@ public void GotoCity(LoadAvatarByIDResponse dbAvatar, uint? lotId) }); } + private bool InCity => CurrentController is CoreGameScreenController || + CurrentController is ConnectCASController || + CurrentController is ConnectCityController || + CurrentController is PersonSelectionEditController; + public void Disconnect() { - Disconnect(false); + Disconnect(!InCity); } - public void Disconnect(bool toLogin){ + public void Disconnect(bool toLogin) + { + DiscordRpcEngine.Secret = null; + FatalAlertPriority = 0; ChangeState((view, controller) => { controller.Disconnect((forceLogin) => HandleDisconnect(forceLogin || toLogin), toLogin); @@ -301,7 +424,7 @@ private void HandleDisconnect(bool forceLogin){ //to SAS or login screen if (forceLogin) ShowLogin(); - else + else ShowPersonSelection(); } @@ -312,13 +435,51 @@ public void FatalNetworkError(int code) FatalError(title, desc); } + public void FatalErrorMessage(AnnouncementMsgPDU pdu) + { + var sender = pdu.SenderID.Length > 2 ? pdu.SenderID.Substring(2) : ""; + + if (sender.StartsWith("cst:")) + { + var cstId = sender.Substring(4); + if (int.TryParse(cstId, out int cstNum)) { + + var title = GameFacade.Strings.GetString("f128", cstNum.ToString()); + var desc = GameFacade.Strings.GetString("f128", (cstNum + 1).ToString()); + FatalError(title, desc, 1); + return; + } + } + else + { + FatalError(pdu.Subject, pdu.Message, 1); + } + } + /// /// When something goes very wrong, e.g. the server connection drops /// This method should be used. The game controller will tell the user /// and then work to clean everything up /// - public void FatalError(string errorTitle, string errorMessage){ - var alert = UIScreen.GlobalShowAlert(new UI.Controls.UIAlertOptions { + public void FatalError(string errorTitle, string errorMessage, int alertPriority = 0){ + if (ShutdownDialog != null) + { + // If the game is shutting down, don't show any errors. + return; + } + + if (alertPriority < FatalAlertPriority && FatalAlert?.Parent == UIScreen.Current) + { + return; + } + + if (FatalAlert != null) + { + UIScreen.RemoveDialog(FatalAlert); + } + + FatalAlertPriority = alertPriority; + FatalAlert = UIScreen.GlobalShowAlert(new UI.Controls.UIAlertOptions { Message = errorMessage, Title = errorTitle, Buttons = UIAlertButton.Ok(x => Disconnect()) @@ -375,21 +536,98 @@ private void ChangeState(Callback onCrea }); } + private void ChangeState(Callback onCreated) where TView : UIScreen + { + Binding.DisposeAll(); + GameThread.InUpdate(() => + { + GameFacade.Cursor.SetCursor(Common.Rendering.Framework.CursorType.Normal); //reset cursor + if (CurrentController != null) + { + if (CurrentController is IDisposable) + { + ((IDisposable)CurrentController).Dispose(); + } + } + + var view = (UIScreen)Kernel.Get(); + GameFacade.Screens.RemoveCurrent(); + GameFacade.Screens.AddScreen(view); + + CurrentController = null; + CurrentView = view; + + onCreated((TView)view); + }); + } + public void EnterSandboxMode(string lotName, bool external) { - var screen = new SandboxGameScreen(); - GameFacade.Screens.RemoveCurrent(); - GameFacade.Screens.AddScreen(screen); - screen.Initialize(lotName, external); - DiscordRpcEngine.SendFSOPresence("Playing Sandbox Mode"); + ChangeState((screen, controller) => + { + screen.Initialize(lotName, external); + DiscordRpcEngine.SendFSOPresence("Playing Sandbox Mode"); + }); } public void ShowCredits() { var screen = Kernel.Get(); - GameFacade.Screens.RemoveCurrent(); - GameFacade.Screens.AddScreen(screen); - DiscordRpcEngine.SendFSOPresence("Viewing Credits"); + UIScreen.GlobalShowDialog(screen, true); + } + + public void RegisterServer(EmbeddedServer server) + { + Server = server; + } + + public bool CloseAttempt() + { + if (Server != null) + { + if (ShutdownDialog == null) + { + ShutdownDialog = new UIArchiveServerStatusDialog(false, Server, null); + + UIScreen.GlobalShowDialog(ShutdownDialog, true); + } + + return false; + } + + return true; + } + + public bool HasServer() + { + return Server != null; + } + + public void CloseServer(Action callback) + { + if (Server != null) + { + if (ShutdownDialog == null) + { + ShutdownDialog = new UIArchiveServerStatusDialog(false, Server, () => { + UIScreen.RemoveDialog(ShutdownDialog); + ShutdownDialog = null; + Server = null; + callback(); + }); + + UIScreen.GlobalShowDialog(ShutdownDialog, true); + + return; + } + } + + callback(); + } + + public ArchiveConfiguration GetServerConfig() + { + return Server?.Config; } public void StartDebugTools() @@ -415,6 +653,23 @@ public void StartDebugTools() */ //debugWindow.PositionAroundGame(GameFacade.Game.Window); } + + private void DragDrop(string path) + { + var current = UIScreen.Current; + if (current is SandboxGameScreen || current is LoginScreen) + { + if (UIScreen.Current is SandboxGameScreen) + { + var sand = (SandboxGameScreen)UIScreen.Current; + sand.Initialize(path, false); + } + else + { + FSOFacade.Controller.EnterSandboxMode(path, false); + } + } + } } } diff --git a/TSOClient/tso.client/GameStartProxy.cs b/TSOClient/tso.client/GameStartProxy.cs index cf36403c4..832b858fc 100644 --- a/TSOClient/tso.client/GameStartProxy.cs +++ b/TSOClient/tso.client/GameStartProxy.cs @@ -9,21 +9,24 @@ namespace FSO.Client /// public class GameStartProxy : IGameStartProxy { + public static Action, IntPtr> BindClosingHandler; + public void Start(bool useDX) { GameFacade.DirectX = useDX; - World.DirectX = useDX; + World.DirectX = useDX; TSOGame game = new TSOGame(); + game.Run(); game.Dispose(); } - public void SetPath(string path) - { - GlobalSettings.Default.StartupPath = path; + public void SetPath(string path) + { + GlobalSettings.Default.StartupPath = path; GlobalSettings.Default.Windowed = false; - } + } - } + } } diff --git a/TSOClient/tso.client/MessagesCache.cs b/TSOClient/tso.client/MessagesCache.cs index ddd84a868..ff21c4551 100644 --- a/TSOClient/tso.client/MessagesCache.cs +++ b/TSOClient/tso.client/MessagesCache.cs @@ -1,48 +1,48 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. +///*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +//If a copy of the MPL was not distributed with this file, You can obtain one at +//http://mozilla.org/MPL/2.0/. -The Original Code is the TSOClient. +//The Original Code is the TSOClient. -The Initial Developer of the Original Code is -Mats 'Afr0' Vederhus. All Rights Reserved. +//The Initial Developer of the Original Code is +//Mats 'Afr0' Vederhus. All Rights Reserved. -Contributor(s): ______________________________________. -*/ +//Contributor(s): ______________________________________. +//*/ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; +//using System; +//using System.Collections.Generic; +//using System.IO; +//using System.Text; -namespace TSOClient -{ - /// - /// Cache for storing letters and IMs received by other players. - /// - public class MessagesCache - { - private static string CacheDir = GlobalSettings.Default.DocumentsPath + - "MessageCache\\" + PlayerAccount.Username; +//namespace TSOClient +//{ +// /// +// /// Cache for storing letters and IMs received by other players. +// /// +// public class MessagesCache +// { +// private static string CacheDir = GlobalSettings.Default.DocumentsPath + +// "MessageCache\\" + PlayerAccount.Username; - /// - /// Caches a letter received from a player. - /// - /// Player the letter was received from. - /// Subject of the letter. - /// Content. - public static void CacheLetter(string From, string Subject, string Message) - { - if (!Directory.Exists(CacheDir)) - Directory.CreateDirectory(CacheDir); +// /// +// /// Caches a letter received from a player. +// /// +// /// Player the letter was received from. +// /// Subject of the letter. +// /// Content. +// public static void CacheLetter(string From, string Subject, string Message) +// { +// if (!Directory.Exists(CacheDir)) +// Directory.CreateDirectory(CacheDir); - using (BinaryWriter Writer = new BinaryWriter(File.Create(CacheDir + "\\" + - From + ", " + Subject + ".txt"))) - { - Writer.Write(Message); - Writer.Flush(); - Writer.Close(); - } - } - } -} +// using (BinaryWriter Writer = new BinaryWriter(File.Create(CacheDir + "\\" + +// From + ", " + Subject + ".txt"))) +// { +// Writer.Write(Message); +// Writer.Flush(); +// Writer.Close(); +// } +// } +// } +//} diff --git a/TSOClient/tso.client/Model/Archive/ArchiveManifest.cs b/TSOClient/tso.client/Model/Archive/ArchiveManifest.cs new file mode 100644 index 000000000..bc2f824f4 --- /dev/null +++ b/TSOClient/tso.client/Model/Archive/ArchiveManifest.cs @@ -0,0 +1,40 @@ +using FSO.Common; +using System.Collections.Generic; + +namespace FSO.Client.Model.Archive +{ + public class ArchiveManifest : IniConfig + { + public override string HeadingComment => "Archive manifest"; + + public ArchiveManifest(string path) : base(path) { } + + private Dictionary _DefaultValues = new Dictionary() + { + { "Name", "Untitled" }, + { "Description", "" }, + { "Size", "0" }, + { "Map", "0100" }, + { "ZipLocation", ""}, + { "ZipHash", ""}, + { "LocalDir", ""}, + { "Template", "False"}, + }; + + public override Dictionary DefaultValues + { + get { return _DefaultValues; } + set { _DefaultValues = value; } + } + + public string Name { get; set; } + public string Description { get; set; } + public string Size { get; set; } + public string Map { get; set; } + public string ZipLocation { get; set; } + public string ZipHash { get; set; } + public string ZipSize { get; set; } + public string LocalDir { get; set; } + public bool Template { get; set; } + } +} diff --git a/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.dll b/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.dll deleted file mode 100644 index 7603798b7..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.xml b/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.xml deleted file mode 100644 index fcf8bd669..000000000 --- a/TSOClient/tso.client/Monogame/MacOS/Lidgren.Network.xml +++ /dev/null @@ -1,2472 +0,0 @@ - - - - Lidgren.Network - - - - - Interface for an encryption algorithm - - - - - NetPeer - - - - - Constructor - - - - - Encrypt an outgoing message in place - - - - - Decrypt an incoming message in place - - - - - Base for a non-threadsafe encryption class - - - - - Block size in bytes for this cipher - - - - - NetBlockEncryptionBase constructor - - - - - Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted - - - - - Decrypt an incoming message encrypted with corresponding Encrypt - - message to decrypt - true if successful; false if failed - - - - Encrypt a block of bytes - - - - - Decrypt a block of bytes - - - - - Example class; not very good encryption - - - - - NetXorEncryption constructor - - - - - NetXorEncryption constructor - - - - - Encrypt an outgoing message - - - - - Decrypt an incoming message - - - - - Methods to encrypt and decrypt data using the XTEA algorithm - - - - - Gets the block size for this cipher - - - - - 16 byte key - - - - - 16 byte key - - - - - String to hash for key - - - - - Encrypts a block of bytes - - - - - Decrypts a block of bytes - - - - - Lidgren Network Library - - - - - Big integer class based on BouncyCastle (http://www.bouncycastle.org) big integer code - - - - - Fixed size vector of booleans - - - - - Gets the number of bits/booleans stored in this vector - - - - - NetBitVector constructor - - - - - Returns true if all bits/booleans are set to zero/false - - - - - Returns the number of bits/booleans set to one/true - - - - - - Shift all bits one step down, cycling the first bit to the top - - - - - Gets the first (lowest) index set to true - - - - - Gets the bit/bool at the specified index - - - - - Sets or clears the bit/bool at the specified index - - - - - Gets the bit/bool at the specified index - - - - - Sets all bits/booleans to zero/false - - - - - Returns a string that represents this object - - - - - Helper class for NetBuffer to write/read bits - - - - - Read 1-8 bits from a buffer into a byte - - - - - Read several bytes from a buffer - - - - - Write 0-8 bits of data to buffer - - - - - Write several whole bytes - - - - - Reads an unsigned 16 bit integer - - - - - Reads the specified number of bits into an UInt32 - - - - - Writes an unsigned 16 bit integer - - - - - Writes the specified number of bits into a byte array - - - - - Writes the specified number of bits into a byte array - - - - - Write Base128 encoded variable sized unsigned integer - - number of bytes written - - - - Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset! - - - - - Base class for NetIncomingMessage and NetOutgoingMessage - - - - - Number of bytes to overallocate for each message to avoid resizing - - - - - Gets or sets the internal data buffer - - - - - Gets or sets the length of the used portion of the buffer in bytes - - - - - Gets or sets the length of the used portion of the buffer in bits - - - - - Gets or sets the read position in the buffer, in bits (not bytes) - - - - - Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure. - - - - - Gets the internal data buffer - - - - - Reads a 1-bit Boolean without advancing the read pointer - - - - - Reads a Byte without advancing the read pointer - - - - - Reads an SByte without advancing the read pointer - - - - - Reads the specified number of bits into a Byte without advancing the read pointer - - - - - Reads the specified number of bytes without advancing the read pointer - - - - - Reads the specified number of bytes without advancing the read pointer - - - - - Reads an Int16 without advancing the read pointer - - - - - Reads a UInt16 without advancing the read pointer - - - - - Reads an Int32 without advancing the read pointer - - - - - Reads the specified number of bits into an Int32 without advancing the read pointer - - - - - Reads a UInt32 without advancing the read pointer - - - - - Reads the specified number of bits into a UInt32 without advancing the read pointer - - - - - Reads a UInt64 without advancing the read pointer - - - - - Reads an Int64 without advancing the read pointer - - - - - Reads the specified number of bits into an UInt64 without advancing the read pointer - - - - - Reads the specified number of bits into an Int64 without advancing the read pointer - - - - - Reads a 32-bit Single without advancing the read pointer - - - - - Reads a 32-bit Single without advancing the read pointer - - - - - Reads a 64-bit Double without advancing the read pointer - - - - - Reads a string without advancing the read pointer - - - - - Reads a boolean value (stored as a single bit) written using Write(bool) - - - - - Reads a byte - - - - - Reads a byte and returns true or false for success - - - - - Reads a signed byte - - - - - Reads 1 to 8 bits into a byte - - - - - Reads the specified number of bytes - - - - - Reads the specified number of bytes and returns true for success - - - - - Reads the specified number of bytes into a preallocated array - - The destination array - The offset where to start writing in the destination array - The number of bytes to read - - - - Reads the specified number of bits into a preallocated array - - The destination array - The offset where to start writing in the destination array - The number of bits to read - - - - Reads a 16 bit signed integer written using Write(Int16) - - - - - Reads a 16 bit unsigned integer written using Write(UInt16) - - - - - Reads a 32 bit signed integer written using Write(Int32) - - - - - Reads a 32 bit signed integer written using Write(Int32) - - - - - Reads a signed integer stored in 1 to 32 bits, written using Write(Int32, Int32) - - - - - Reads an 32 bit unsigned integer written using Write(UInt32) - - - - - Reads an 32 bit unsigned integer written using Write(UInt32) and returns true for success - - - - - Reads an unsigned integer stored in 1 to 32 bits, written using Write(UInt32, Int32) - - - - - Reads a 64 bit unsigned integer written using Write(UInt64) - - - - - Reads a 64 bit signed integer written using Write(Int64) - - - - - Reads an unsigned integer stored in 1 to 64 bits, written using Write(UInt64, Int32) - - - - - Reads a signed integer stored in 1 to 64 bits, written using Write(Int64, Int32) - - - - - Reads a 32 bit floating point value written using Write(Single) - - - - - Reads a 32 bit floating point value written using Write(Single) - - - - - Reads a 32 bit floating point value written using Write(Single) - - - - - Reads a 64 bit floating point value written using Write(Double) - - - - - Reads a variable sized UInt32 written using WriteVariableUInt32() - - - - - Reads a variable sized UInt32 written using WriteVariableUInt32() and returns true for success - - - - - Reads a variable sized Int32 written using WriteVariableInt32() - - - - - Reads a variable sized Int64 written using WriteVariableInt64() - - - - - Reads a variable sized UInt32 written using WriteVariableInt64() - - - - - Reads a 32 bit floating point value written using WriteSignedSingle() - - The number of bits used when writing the value - A floating point value larger or equal to -1 and smaller or equal to 1 - - - - Reads a 32 bit floating point value written using WriteUnitSingle() - - The number of bits used when writing the value - A floating point value larger or equal to 0 and smaller or equal to 1 - - - - Reads a 32 bit floating point value written using WriteRangedSingle() - - The minimum value used when writing the value - The maximum value used when writing the value - The number of bits used when writing the value - A floating point value larger or equal to MIN and smaller or equal to MAX - - - - Reads a 32 bit integer value written using WriteRangedInteger() - - The minimum value used when writing the value - The maximum value used when writing the value - A signed integer value larger or equal to MIN and smaller or equal to MAX - - - - Reads a string written using Write(string) - - - - - Reads a string written using Write(string) and returns true for success - - - - - Reads a value, in local time comparable to NetTime.Now, written using WriteTime() for the connection supplied - - - - - Reads a stored IPv4 endpoint description - - - - - Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. - - - - - Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. - - - - - Pads data with the specified number of bits. - - - - - Reads all public and private declared instance fields of the object in alphabetical order using reflection - - - - - Reads all fields with the specified binding of the object in alphabetical order using reflection - - - - - Reads all public and private declared instance fields of the object in alphabetical order using reflection - - - - - Reads all fields with the specified binding of the object in alphabetical order using reflection - - - - - Ensures the buffer can hold this number of bits - - - - - Ensures the buffer can hold this number of bits - - - - - Writes a boolean value using 1 bit - - - - - Write a byte - - - - - Writes a signed byte - - - - - Writes 1 to 8 bits of a byte - - - - - Writes all bytes in an array - - - - - Writes the specified number of bytes from an array - - - - - Writes an unsigned 16 bit integer - - - - - - Writes a 16 bit unsigned integer at a given offset in the buffer - - - - - Writes an unsigned integer using 1 to 16 bits - - - - - Writes a signed 16 bit integer - - - - - Writes a 16 bit signed integer at a given offset in the buffer - - - - - Writes a 32 bit signed integer - - - - - Writes a 32 bit signed integer at a given offset in the buffer - - - - - Writes a 32 bit unsigned integer - - - - - Writes a 32 bit unsigned integer at a given offset in the buffer - - - - - Writes a 32 bit signed integer - - - - - Writes a signed integer using 1 to 32 bits - - - - - Writes a 64 bit unsigned integer - - - - - Writes a 64 bit unsigned integer at a given offset in the buffer - - - - - Writes an unsigned integer using 1 to 64 bits - - - - - Writes a 64 bit signed integer - - - - - Writes a signed integer using 1 to 64 bits - - - - - Writes a 32 bit floating point value - - - - - Writes a 64 bit floating point value - - - - - Write Base128 encoded variable sized unsigned integer of up to 32 bits - - number of bytes written - - - - Write Base128 encoded variable sized signed integer of up to 32 bits - - number of bytes written - - - - Write Base128 encoded variable sized signed integer of up to 64 bits - - number of bytes written - - - - Write Base128 encoded variable sized unsigned integer of up to 64 bits - - number of bytes written - - - - Compress (lossy) a float in the range -1..1 using numberOfBits bits - - - - - Compress (lossy) a float in the range 0..1 using numberOfBits bits - - - - - Compress a float within a specified range using a certain number of bits - - - - - Writes an integer with the least amount of bits need for the specified range - Returns number of bits written - - - - - Write a string - - - - - Writes an endpoint description - - - - - Writes the current local time to a message; readable (and convertable to local time) by the remote host using ReadTime() - - - - - Writes a local timestamp to a message; readable (and convertable to local time) by the remote host using ReadTime() - - - - - Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. - - - - - Pads data with the specified number of bits. - - - - - Append all the bits of message to this message - - - - - Writes all public and private declared instance fields of the object in alphabetical order using reflection - - - - - Writes all fields with specified binding in alphabetical order using reflection - - - - - Writes all public and private declared instance properties of the object in alphabetical order using reflection - - - - - Writes all properties with specified binding in alphabetical order using reflection - - - - - Utility struct for writing Singles - - - - - Value as a 32 bit float - - - - - Value as an unsigned 32 bit integer - - - - - Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property - - - - - Gets the connection to the server, if any - - - - - Gets the connection status of the server connection (or NetConnectionStatus.Disconnected if no connection) - - - - - NetClient constructor - - - - - - Connect to a remote server - - The remote endpoint to connect to - The hail message to pass - server connection, or null if already connected - - - - Disconnect from server - - reason for disconnect - - - - Sends message to server - - - - - Sends message to server - - - - - Returns a string that represents this object - - - - - Represents a connection to a remote peer - - - - - Gets or sets the application defined object containing data about the connection - - - - - Gets the peer which holds this connection - - - - - Gets the current status of the connection (synced to the last status message read) - - - - - Gets various statistics for this connection - - - - - Gets the remote endpoint for the connection - - - - - Gets the unique identifier of the remote NetPeer for this connection - - - - - Gets the local hail message that was sent as part of the handshake - - - - - Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT) - - - - - Send a message to this remote connection - - The message to send - How to deliver the message - Sequence channel within the delivery method - - - - Zero windowSize indicates that the channel is not yet instantiated (used) - Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window - - - - - Returns a string that represents this object - - - - - The message that the remote part specified via Connect() or Approve() - can be null. - - - - - Approves this connection; sending a connection response to the remote host - - - - - Approves this connection; sending a connection response to the remote host - - The local hail message that will be set as RemoteHailMessage on the remote host - - - - Denies this connection; disconnecting it - - - - - Denies this connection; disconnecting it - - The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host - - - - Disconnect from the remote peer - - the message to send with the disconnect message - - - - Gets the current average roundtrip time in seconds - - - - - Time offset between this peer and the remote peer - - - - - Gets local time value comparable to NetTime.Now from a remote value - - - - - Gets the remote time value for a local time value produced by NetTime.Now - - - - - Gets the current MTU in bytes. If PeerConfiguration.AutoExpandMTU is false, this will be PeerConfiguration.MaximumTransmissionUnit. - - - - - Statistics for a NetConnection instance - - - - - Gets the number of sent packets for this connection - - - - - Gets the number of received packets for this connection - - - - - Gets the number of sent bytes for this connection - - - - - Gets the number of received bytes for this connection - - - - - Gets the number of resent reliable messages for this connection - - - - - Returns a string that represents this object - - - - - Status for a NetConnection instance - - - - - No connection, or attempt, in place - - - - - Connect has been sent; waiting for ConnectResponse - - - - - Connect was received, but ConnectResponse hasn't been sent yet - - - - - Connect was received and ApprovalMessage released to the application; awaiting Approve() or Deny() - - - - - Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished - - - - - Connected - - - - - In the process of disconnecting - - - - - Disconnected - - - - - All the constants used when compiling the library - - - - - Number of channels which needs a sequence number to work - - - - - Number of reliable channels - - - - - How the library deals with resends and handling of late messages - - - - - Indicates an error - - - - - Unreliable, unordered delivery - - - - - Unreliable delivery, but automatically dropping late messages - - - - - Reliable delivery, but unordered - - - - - Reliable delivery, except for late messages which are dropped - - - - - Reliable, ordered delivery - - - - - Exception thrown in the Lidgren Network Library - - - - - NetException constructor - - - - - NetException constructor - - - - - NetException constructor - - - - - Throws an exception, in DEBUG only, if first parameter is false - - - - - Throws an exception, in DEBUG only, if first parameter is false - - - - - Incoming message either sent from a remote peer or generated within the library - - - - - Gets the type of this incoming message - - - - - Gets the delivery method this message was sent with (if user data) - - - - - Gets the sequence channel this message was sent with (if user data) - - - - - IPEndPoint of sender, if any - - - - - NetConnection of sender, if any - - - - - What local time the message was received from the network - - - - - Decrypt a message - - The encryption algorithm used to encrypt the message - true on success - - - - Reads a value, in local time comparable to NetTime.Now, written using WriteTime() - Must have a connected sender - - - - - Returns a string that represents this object - - - - - The type of a NetIncomingMessage - - - - - Error; this value should never appear - - - - - Status for a connection changed - - - - - Data sent using SendUnconnectedMessage - - - - - Connection approval is needed - - - - - Application data - - - - - Receipt of delivery - - - - - Discovery request for a response - - - - - Discovery response to a request - - - - - Verbose debug message - - - - - Debug message - - - - - Warning message - - - - - Error message - - - - - NAT introduction was successful - - - - - A roundtrip was measured and NetConnection.AverageRoundtripTime was updated - - - - - Represents a local peer capable of holding zero, one or more connections to remote peers - - - - - Send NetIntroduction to hostExternal and clientExternal; introducing client to host - - - - - Called when host/client receives a NatIntroduction message from a master server - - - - - Called when receiving a NatPunchMessage from a remote endpoint - - - - - Gets the NetPeerStatus of the NetPeer - - - - - Signalling event which can be waited on to determine when a message is queued for reading. - Note that there is no guarantee that after the event is signaled the blocked thread will - find the message in the queue. Other user created threads could be preempted and dequeue - the message before the waiting thread wakes up. - - - - - Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called! - - - - - Gets the port number this NetPeer is listening and sending on, if Start() has been called - - - - - Returns an UPnP object if enabled in the NetPeerConfiguration - - - - - Gets or sets the application defined object containing data about the peer - - - - - Gets a copy of the list of connections - - - - - Gets the number of active connections - - - - - Statistics on this NetPeer since it was initialized - - - - - Gets the configuration used to instanciate this NetPeer - - - - - NetPeer constructor - - - - - Binds to socket and spawns the networking thread - - - - - Get the connection, if any, for a certain remote endpoint - - - - - Read a pending message from any connection, blocking up to maxMillis if needed - - - - - Read a pending message from any connection, if any - - - - - Read a pending message from any connection, if any - - - - - Create a connection to a remote endpoint - - - - - Create a connection to a remote endpoint - - - - - Create a connection to a remote endpoint - - - - - Create a connection to a remote endpoint - - - - - Send raw bytes; only used for debugging - - - - - In DEBUG, throws an exception, in RELEASE logs an error message - - - - - - Disconnects all active connections and closes the socket - - - - - Emit a discovery signal to all hosts on your subnet - - - - - Emit a discovery signal to a single known host - - - - - Emit a discovery signal to a single known host - - - - - Send a discovery response message - - - - - Gets the socket, if Start() has been called - - - - - Call this to register a callback for when a new message arrives - - - - - Call this to unregister a callback, but remember to do it in the same synchronization context! - - - - - If NetPeerConfiguration.AutoFlushSendQueue() is false; you need to call this to send all messages queued using SendMessage() - - - - - Creates a new message for sending - - - - - Creates a new message for sending and writes the provided string to it - - - - - Creates a new message for sending - - initial capacity in bytes - - - - Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector - - - - - Recycles a list of NetIncomingMessage instances for reuse; taking pressure off the garbage collector - - - - - Creates an incoming message with the required capacity for releasing to the application - - - - - Send a message to a specific connection - - The message to send - The recipient connection - How to deliver the message - - - - Send a message to a specific connection - - The message to send - The recipient connection - How to deliver the message - Sequence channel within the delivery method - - - - Send a message to a list of connections - - The message to send - The list of recipients to send to - How to deliver the message - Sequence channel within the delivery method - - - - Send a message to an unconnected host - - - - - Send a message to an unconnected host - - - - - Send a message to an unconnected host - - - - - Send a message to this exact same netpeer (loopback) - - - - - Outgoing message used to send data to remote peer(s) - - - - - Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt! - - - - - Returns a string that represents this object - - - - - Partly immutable after NetPeer has been initialized - - - - - Default MTU value in bytes - - - - - NetPeerConfiguration constructor - - - - - Gets the identifier of this application; the library can only connect to matching app identifier peers - - - - - Enables receiving of the specified type of message - - - - - Disables receiving of the specified type of message - - - - - Enables or disables receiving of the specified type of message - - - - - Gets if receiving of the specified type of message is enabled - - - - - Gets or sets the behaviour of unreliable sends above MTU - - - - - Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument - - - - - Gets or sets the time between latency calculating pings - - - - - Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the number of seconds timeout will be postponed on a successful ping/pong - - - - - Enables UPnP support; enabling port forwarding and getting external ip - - - - - Enables or disables automatic flushing of the send queue. If disabled, you must manully call NetPeer.FlushSendQueue() to flush sent messages to network. - - - - - Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the local broadcast address to use when broadcasting - - - - - Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. - - - - - Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient. - - - - - Gets or sets the number of seconds between handshake attempts - - - - - Gets or sets the maximum number of handshake attempts before failing to connect - - - - - Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size - - - - - Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled - - - - - Gets or sets the number of failed expand mtu attempts to perform before setting final MTU - - - - - Creates a memberwise shallow clone of this configuration - - - - - Behaviour of unreliable sends above MTU - - - - - Sending an unreliable message will ignore MTU and send everything in a single packet; this is the new default - - - - - Old behaviour; use normal fragmentation for unreliable messages - if a fragment is dropped, memory for received fragments are never reclaimed! - - - - - Alternate behaviour; just drops unreliable messages above MTU - - - - - Statistics for a NetPeer instance - - - - - Gets the number of sent packets since the NetPeer was initialized - - - - - Gets the number of received packets since the NetPeer was initialized - - - - - Gets the number of sent messages since the NetPeer was initialized - - - - - Gets the number of received messages since the NetPeer was initialized - - - - - Gets the number of sent bytes since the NetPeer was initialized - - - - - Gets the number of received bytes since the NetPeer was initialized - - - - - Gets the number of bytes allocated (and possibly garbage collected) for message storage - - - - - Gets the number of bytes in the recycled pool - - - - - Returns a string that represents this object - - - - - Status for a NetPeer instance - - - - - NetPeer is not running; socket is not bound - - - - - NetPeer is in the process of starting up - - - - - NetPeer is bound to socket and listening for packets - - - - - Shutdown has been requested and will be executed shortly - - - - - Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst() - - - - - Gets the number of items in the queue - - - - - Gets the current capacity for the queue - - - - - NetQueue constructor - - - - - Adds an item last/tail of the queue - - - - - Adds an item last/tail of the queue - - - - - Places an item first, at the head of the queue - - - - - Gets an item from the head of the queue, or returns default(T) if empty - - - - - Gets all items from the head of the queue, or returns number of items popped - - - - - Returns default(T) if queue is empty - - - - - Determines whether an item is in the queue - - - - - Copies the queue items to a new array - - - - - Removes all objects from the queue - - - - - NetRandom base class - - - - - Get global instance of NetRandom (uses MWCRandom) - - - - - Constructor with randomized seed - - - - - Constructor with provided 32 bit seed - - - - - (Re)initialize this instance with provided 32 bit seed - - - - - Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively - - - - - Generates a random value that is greater or equal than 0 and less than Int32.MaxValue - - - - - Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively) - - - - - Returns random value larger or equal to 0.0 and less than 1.0 - - - - - Returns random value is greater or equal than 0.0 and less than 1.0 - - - - - Returns random value is greater or equal than 0.0f and less than 1.0f - - - - - Returns a random value is greater or equal than 0 and less than maxValue - - - - - Returns a random value is greater or equal than minValue and less than maxValue - - - - - Generates a random value between UInt64.MinValue to UInt64.MaxValue - - - - - Returns true or false, randomly - - - - - Fills all bytes from offset to offset + length in buffer with random values - - - - - Fill the specified buffer with random values - - - - - Multiply With Carry random - - - - - Get global instance of MWCRandom - - - - - Constructor with randomized seed - - - - - (Re)initialize this instance with provided 32 bit seed - - - - - (Re)initialize this instance with provided 64 bit seed - - - - - Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively - - - - - Xor Shift based random - - - - - Get global instance of XorShiftRandom - - - - - Constructor with randomized seed - - - - - Constructor with provided 64 bit seed - - - - - (Re)initialize this instance with provided 32 bit seed - - - - - (Re)initialize this instance with provided 64 bit seed - - - - - Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively - - - - - Mersenne Twister based random - - - - - Get global instance of MersenneTwisterRandom - - - - - Constructor with randomized seed - - - - - Constructor with provided 32 bit seed - - - - - (Re)initialize this instance with provided 32 bit seed - - - - - Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively - - - - - RNGCryptoServiceProvider based random; very slow but cryptographically safe - - - - - Global instance of CryptoRandom - - - - - Seed in CryptoRandom does not create deterministic sequences - - - - - Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively - - - - - Fill the specified buffer with random values - - - - - Fills all bytes from offset to offset + length in buffer with random values - - - - - Class for generating random seeds - - - - - Generates a 32 bit random seed - - - - - Generates a 64 bit random seed - - - - - Sender part of Selective repeat ARQ for a particular NetChannel - - - - - Result of a SendMessage call - - - - - Message failed to enqueue because there is no connection - - - - - Message was immediately sent - - - - - Message was queued for delivery - - - - - Message was dropped immediately since too many message were queued - - - - - Specialized version of NetPeer used for "server" peers - - - - - NetServer constructor - - - - - Send a message to all connections - - The message to send - How to deliver the message - - - - Send a message to all connections except one - - The message to send - How to deliver the message - Don't send to this particular connection - Which sequence channel to use for the message - - - - Returns a string that represents this object - - - - - Helper methods for implementing SRP authentication - - - - - Compute multiplier (k) - - - - - Create 16 bytes of random salt - - - - - Create 32 bytes of random ephemeral value - - - - - Computer private key (x) - - - - - Creates a verifier that the server can later use to authenticate users later on (v) - - - - - SHA hash data - - - - - Compute client public ephemeral value (A) - - - - - Compute server ephemeral value (B) - - - - - Compute intermediate value (u) - - - - - Computes the server session value - - - - - Computes the client session value - - - - - Create XTEA symmetrical encryption object from sessionValue - - - - - Time service - - - - - Get number of seconds since the application started - - - - - Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds) - - - - - Sender part of Selective repeat ARQ for a particular NetChannel - - - - - Status of the UPnP capabilities - - - - - Still discovering UPnP capabilities - - - - - UPnP is not available - - - - - UPnP is available and ready to use - - - - - UPnP support class - - - - - Status of the UPnP capabilities of this NetPeer - - - - - NetUPnP constructor - - - - - Add a forwarding rule to the router using UPnP - - - - - Delete a forwarding rule from the router using UPnP - - - - - Retrieve the extern ip using UPnP - - - - - Utility methods - - - - - Resolve endpoint callback - - - - - Resolve address callback - - - - - Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number (asynchronous version) - - - - - Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number - - - - - Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname (asynchronous version) - - - - - Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname - - - - - Returns the physical (MAC) address for the first usable network interface - - - - - Create a hex string from an Int64 value - - - - - Create a hex string from an array of bytes - - - - - Create a hex string from an array of bytes - - - - - Gets the local broadcast address - - - - - Gets my local IPv4 address (not necessarily external) and subnet mask - - - - - Returns true if the IPEndPoint supplied is on the same subnet as this host - - - - - Returns true if the IPAddress supplied is on the same subnet as this host - - - - - Returns how many bits are necessary to hold a certain number - - - - - Returns how many bytes are required to hold a certain number of bits - - - - - Convert a hexadecimal string to a byte array - - - - - Converts a number of bytes to a shorter, more readable string representation - - - - - Gets the window size used internally in the library for a certain delivery method - - - - - Creates a comma delimited string from a lite of items - - - - - Create a SHA1 digest from a string - - - - - Create a SHA1 digest from a byte buffer - - - - - Create a SHA1 digest from a byte buffer - - - - diff --git a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.dll b/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.dll deleted file mode 100644 index 55475461c..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.xml b/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.xml deleted file mode 100644 index 2728c5690..000000000 --- a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.Net.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - MonoGame.Framework.Net - - - - - Used to Simulate the delay between computers - - - - - Used to simulate the number of packets you might expect to loose. - - - - - Contacts the Master Server on the net and gets a list of available host games - - - - - diff --git a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.dll b/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.dll deleted file mode 100644 index 40da3e1cc..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.xml b/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.xml deleted file mode 100644 index 68ee25c8e..000000000 --- a/TSOClient/tso.client/Monogame/MacOS/MonoGame.Framework.xml +++ /dev/null @@ -1,16492 +0,0 @@ - - - - MonoGame.Framework - - - - - Create a bounding box from the given list of points. - - The list of Vector3 instances defining the point cloud to bound - A bounding box that encapsulates the given point cloud. - Thrown if the given list has no points. - - - - Defines a viewing frustum for intersection operations. - - - - - The number of planes in the frustum. - - - - - The number of corner points in the frustum. - - - - - Gets or sets the of the frustum. - - - - - Gets the near plane of the frustum. - - - - - Gets the far plane of the frustum. - - - - - Gets the left plane of the frustum. - - - - - Gets the right plane of the frustum. - - - - - Gets the top plane of the frustum. - - - - - Gets the bottom plane of the frustum. - - - - - Constructs the frustum by extracting the view planes from a matrix. - - Combined matrix which usually is (View * Projection). - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified . - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified as an output parameter. - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified . - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified . - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified as an output parameter. - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified . - - - - Containment test between this and specified . - - A for testing. - Result of testing for containment between this and specified as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Returns a copy of internal corners array. - - The array of corners. - - - - Returns a copy of internal corners array. - - The array which values will be replaced to corner values of this instance. It must have size of . - - - - Gets the hash code of this . - - Hash code of this . - - - - Gets whether or not a specified intersects with this . - - A for intersection test. - true if specified intersects with this ; false otherwise. - - - - Gets whether or not a specified intersects with this . - - A for intersection test. - true if specified intersects with this ; false otherwise as an output parameter. - - - - Gets whether or not a specified intersects with this . - - An other for intersection test. - true if other intersects with this ; false otherwise. - - - - Gets whether or not a specified intersects with this . - - A for intersection test. - true if specified intersects with this ; false otherwise. - - - - Gets whether or not a specified intersects with this . - - A for intersection test. - true if specified intersects with this ; false otherwise as an output parameter. - - - - Gets type of intersection between specified and this . - - A for intersection test. - A plane intersection type. - - - - Gets type of intersection between specified and this . - - A for intersection test. - A plane intersection type as an output parameter. - - - - Gets the distance of intersection of and this or null if no intersection happens. - - A for intersection test. - Distance at which ray intersects with this or null if no intersection happens. - - - - Gets the distance of intersection of and this or null if no intersection happens. - - A for intersection test. - Distance at which ray intersects with this or null if no intersection happens as an output parameter. - - - - Returns a representation of this in the format: - {Near:[nearPlane] Far:[farPlane] Left:[leftPlane] Right:[rightPlane] Top:[topPlane] Bottom:[bottomPlane]} - - representation of this . - - - - Describes a sphere in 3D-space for bounding operations. - - - - - The sphere center. - - - - - The sphere radius. - - - - - Constructs a bounding sphere with the specified center and radius. - - The sphere center. - The sphere radius. - - - - Test if a bounding box is fully inside, outside, or just intersecting the sphere. - - The box for testing. - The containment type. - - - - Test if a bounding box is fully inside, outside, or just intersecting the sphere. - - The box for testing. - The containment type as an output parameter. - - - - Test if a frustum is fully inside, outside, or just intersecting the sphere. - - The frustum for testing. - The containment type. - - - - Test if a frustum is fully inside, outside, or just intersecting the sphere. - - The frustum for testing. - The containment type as an output parameter. - - - - Test if a sphere is fully inside, outside, or just intersecting the sphere. - - The other sphere for testing. - The containment type. - - - - Test if a sphere is fully inside, outside, or just intersecting the sphere. - - The other sphere for testing. - The containment type as an output parameter. - - - - Test if a point is fully inside, outside, or just intersecting the sphere. - - The vector in 3D-space for testing. - The containment type. - - - - Test if a point is fully inside, outside, or just intersecting the sphere. - - The vector in 3D-space for testing. - The containment type as an output parameter. - - - - Creates the smallest that can contain a specified . - - The box to create the sphere from. - The new . - - - - Creates the smallest that can contain a specified . - - The box to create the sphere from. - The new as an output parameter. - - - - Creates the smallest that can contain a specified . - - The frustum to create the sphere from. - The new . - - - - Creates the smallest that can contain a specified list of points in 3D-space. - - List of point to create the sphere from. - The new . - - - - Creates the smallest that can contain two spheres. - - First sphere. - Second sphere. - The new . - - - - Creates the smallest that can contain two spheres. - - First sphere. - Second sphere. - The new as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Gets whether or not a specified intersects with this sphere. - - The box for testing. - true if intersects with this sphere; false otherwise. - - - - Gets whether or not a specified intersects with this sphere. - - The box for testing. - true if intersects with this sphere; false otherwise. As an output parameter. - - - - Gets whether or not the other intersects with this sphere. - - The other sphere for testing. - true if other intersects with this sphere; false otherwise. - - - - Gets whether or not the other intersects with this sphere. - - The other sphere for testing. - true if other intersects with this sphere; false otherwise. As an output parameter. - - - - Gets whether or not a specified intersects with this sphere. - - The plane for testing. - Type of intersection. - - - - Gets whether or not a specified intersects with this sphere. - - The plane for testing. - Type of intersection as an output parameter. - - - - Gets whether or not a specified intersects with this sphere. - - The ray for testing. - Distance of ray intersection or null if there is no intersection. - - - - Gets whether or not a specified intersects with this sphere. - - The ray for testing. - Distance of ray intersection or null if there is no intersection as an output parameter. - - - - Returns a representation of this in the format: - {Center:[] Radius:[]} - - A representation of this . - - - - Creates a new that contains a transformation of translation and scale from this sphere by the specified . - - The transformation . - Transformed . - - - - Creates a new that contains a transformation of translation and scale from this sphere by the specified . - - The transformation . - Transformed as an output parameter. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Describes a 32-bit packed color. - - - - - Constructs an RGBA color from a packed value. - The value is a 32-bit unsigned integer, with R in the least significant octet. - - The packed value. - - - - Constructs an RGBA color from the XYZW unit length components of a vector. - - A representing color. - - - - Constructs an RGBA color from the XYZ unit length components of a vector. Alpha value will be opaque. - - A representing color. - - - - Constructs an RGBA color from a and an alpha value. - - A for RGB values of new instance. - The alpha component value from 0 to 255. - - - - Constructs an RGBA color from color and alpha value. - - A for RGB values of new instance. - Alpha component value from 0.0f to 1.0f. - - - - Constructs an RGBA color from scalars representing red, green and blue values. Alpha value will be opaque. - - Red component value from 0.0f to 1.0f. - Green component value from 0.0f to 1.0f. - Blue component value from 0.0f to 1.0f. - - - - Constructs an RGBA color from scalars representing red, green, blue and alpha values. - - Red component value from 0.0f to 1.0f. - Green component value from 0.0f to 1.0f. - Blue component value from 0.0f to 1.0f. - Alpha component value from 0.0f to 1.0f. - - - - Constructs an RGBA color from scalars representing red, green and blue values. Alpha value will be opaque. - - Red component value from 0 to 255. - Green component value from 0 to 255. - Blue component value from 0 to 255. - - - - Constructs an RGBA color from scalars representing red, green, blue and alpha values. - - Red component value from 0 to 255. - Green component value from 0 to 255. - Blue component value from 0 to 255. - Alpha component value from 0 to 255. - - - - Constructs an RGBA color from scalars representing red, green, blue and alpha values. - - - This overload sets the values directly without clamping, and may therefore be faster than the other overloads. - - - - - - - - - Gets or sets the blue component. - - - - - Gets or sets the green component. - - - - - Gets or sets the red component. - - - - - Gets or sets the alpha component. - - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Compares whether current instance is equal to specified object. - - The to compare. - true if the instances are equal; false otherwise. - - - - TransparentBlack color (R:0,G:0,B:0,A:0). - - - - - Transparent color (R:0,G:0,B:0,A:0). - - - - - AliceBlue color (R:240,G:248,B:255,A:255). - - - - - AntiqueWhite color (R:250,G:235,B:215,A:255). - - - - - Aqua color (R:0,G:255,B:255,A:255). - - - - - Aquamarine color (R:127,G:255,B:212,A:255). - - - - - Azure color (R:240,G:255,B:255,A:255). - - - - - Beige color (R:245,G:245,B:220,A:255). - - - - - Bisque color (R:255,G:228,B:196,A:255). - - - - - Black color (R:0,G:0,B:0,A:255). - - - - - BlanchedAlmond color (R:255,G:235,B:205,A:255). - - - - - Blue color (R:0,G:0,B:255,A:255). - - - - - BlueViolet color (R:138,G:43,B:226,A:255). - - - - - Brown color (R:165,G:42,B:42,A:255). - - - - - BurlyWood color (R:222,G:184,B:135,A:255). - - - - - CadetBlue color (R:95,G:158,B:160,A:255). - - - - - Chartreuse color (R:127,G:255,B:0,A:255). - - - - - Chocolate color (R:210,G:105,B:30,A:255). - - - - - Coral color (R:255,G:127,B:80,A:255). - - - - - CornflowerBlue color (R:100,G:149,B:237,A:255). - - - - - Cornsilk color (R:255,G:248,B:220,A:255). - - - - - Crimson color (R:220,G:20,B:60,A:255). - - - - - Cyan color (R:0,G:255,B:255,A:255). - - - - - DarkBlue color (R:0,G:0,B:139,A:255). - - - - - DarkCyan color (R:0,G:139,B:139,A:255). - - - - - DarkGoldenrod color (R:184,G:134,B:11,A:255). - - - - - DarkGray color (R:169,G:169,B:169,A:255). - - - - - DarkGreen color (R:0,G:100,B:0,A:255). - - - - - DarkKhaki color (R:189,G:183,B:107,A:255). - - - - - DarkMagenta color (R:139,G:0,B:139,A:255). - - - - - DarkOliveGreen color (R:85,G:107,B:47,A:255). - - - - - DarkOrange color (R:255,G:140,B:0,A:255). - - - - - DarkOrchid color (R:153,G:50,B:204,A:255). - - - - - DarkRed color (R:139,G:0,B:0,A:255). - - - - - DarkSalmon color (R:233,G:150,B:122,A:255). - - - - - DarkSeaGreen color (R:143,G:188,B:139,A:255). - - - - - DarkSlateBlue color (R:72,G:61,B:139,A:255). - - - - - DarkSlateGray color (R:47,G:79,B:79,A:255). - - - - - DarkTurquoise color (R:0,G:206,B:209,A:255). - - - - - DarkViolet color (R:148,G:0,B:211,A:255). - - - - - DeepPink color (R:255,G:20,B:147,A:255). - - - - - DeepSkyBlue color (R:0,G:191,B:255,A:255). - - - - - DimGray color (R:105,G:105,B:105,A:255). - - - - - DodgerBlue color (R:30,G:144,B:255,A:255). - - - - - Firebrick color (R:178,G:34,B:34,A:255). - - - - - FloralWhite color (R:255,G:250,B:240,A:255). - - - - - ForestGreen color (R:34,G:139,B:34,A:255). - - - - - Fuchsia color (R:255,G:0,B:255,A:255). - - - - - Gainsboro color (R:220,G:220,B:220,A:255). - - - - - GhostWhite color (R:248,G:248,B:255,A:255). - - - - - Gold color (R:255,G:215,B:0,A:255). - - - - - Goldenrod color (R:218,G:165,B:32,A:255). - - - - - Gray color (R:128,G:128,B:128,A:255). - - - - - Green color (R:0,G:128,B:0,A:255). - - - - - GreenYellow color (R:173,G:255,B:47,A:255). - - - - - Honeydew color (R:240,G:255,B:240,A:255). - - - - - HotPink color (R:255,G:105,B:180,A:255). - - - - - IndianRed color (R:205,G:92,B:92,A:255). - - - - - Indigo color (R:75,G:0,B:130,A:255). - - - - - Ivory color (R:255,G:255,B:240,A:255). - - - - - Khaki color (R:240,G:230,B:140,A:255). - - - - - Lavender color (R:230,G:230,B:250,A:255). - - - - - LavenderBlush color (R:255,G:240,B:245,A:255). - - - - - LawnGreen color (R:124,G:252,B:0,A:255). - - - - - LemonChiffon color (R:255,G:250,B:205,A:255). - - - - - LightBlue color (R:173,G:216,B:230,A:255). - - - - - LightCoral color (R:240,G:128,B:128,A:255). - - - - - LightCyan color (R:224,G:255,B:255,A:255). - - - - - LightGoldenrodYellow color (R:250,G:250,B:210,A:255). - - - - - LightGray color (R:211,G:211,B:211,A:255). - - - - - LightGreen color (R:144,G:238,B:144,A:255). - - - - - LightPink color (R:255,G:182,B:193,A:255). - - - - - LightSalmon color (R:255,G:160,B:122,A:255). - - - - - LightSeaGreen color (R:32,G:178,B:170,A:255). - - - - - LightSkyBlue color (R:135,G:206,B:250,A:255). - - - - - LightSlateGray color (R:119,G:136,B:153,A:255). - - - - - LightSteelBlue color (R:176,G:196,B:222,A:255). - - - - - LightYellow color (R:255,G:255,B:224,A:255). - - - - - Lime color (R:0,G:255,B:0,A:255). - - - - - LimeGreen color (R:50,G:205,B:50,A:255). - - - - - Linen color (R:250,G:240,B:230,A:255). - - - - - Magenta color (R:255,G:0,B:255,A:255). - - - - - Maroon color (R:128,G:0,B:0,A:255). - - - - - MediumAquamarine color (R:102,G:205,B:170,A:255). - - - - - MediumBlue color (R:0,G:0,B:205,A:255). - - - - - MediumOrchid color (R:186,G:85,B:211,A:255). - - - - - MediumPurple color (R:147,G:112,B:219,A:255). - - - - - MediumSeaGreen color (R:60,G:179,B:113,A:255). - - - - - MediumSlateBlue color (R:123,G:104,B:238,A:255). - - - - - MediumSpringGreen color (R:0,G:250,B:154,A:255). - - - - - MediumTurquoise color (R:72,G:209,B:204,A:255). - - - - - MediumVioletRed color (R:199,G:21,B:133,A:255). - - - - - MidnightBlue color (R:25,G:25,B:112,A:255). - - - - - MintCream color (R:245,G:255,B:250,A:255). - - - - - MistyRose color (R:255,G:228,B:225,A:255). - - - - - Moccasin color (R:255,G:228,B:181,A:255). - - - - - MonoGame orange theme color (R:231,G:60,B:0,A:255). - - - - - NavajoWhite color (R:255,G:222,B:173,A:255). - - - - - Navy color (R:0,G:0,B:128,A:255). - - - - - OldLace color (R:253,G:245,B:230,A:255). - - - - - Olive color (R:128,G:128,B:0,A:255). - - - - - OliveDrab color (R:107,G:142,B:35,A:255). - - - - - Orange color (R:255,G:165,B:0,A:255). - - - - - OrangeRed color (R:255,G:69,B:0,A:255). - - - - - Orchid color (R:218,G:112,B:214,A:255). - - - - - PaleGoldenrod color (R:238,G:232,B:170,A:255). - - - - - PaleGreen color (R:152,G:251,B:152,A:255). - - - - - PaleTurquoise color (R:175,G:238,B:238,A:255). - - - - - PaleVioletRed color (R:219,G:112,B:147,A:255). - - - - - PapayaWhip color (R:255,G:239,B:213,A:255). - - - - - PeachPuff color (R:255,G:218,B:185,A:255). - - - - - Peru color (R:205,G:133,B:63,A:255). - - - - - Pink color (R:255,G:192,B:203,A:255). - - - - - Plum color (R:221,G:160,B:221,A:255). - - - - - PowderBlue color (R:176,G:224,B:230,A:255). - - - - - Purple color (R:128,G:0,B:128,A:255). - - - - - Red color (R:255,G:0,B:0,A:255). - - - - - RosyBrown color (R:188,G:143,B:143,A:255). - - - - - RoyalBlue color (R:65,G:105,B:225,A:255). - - - - - SaddleBrown color (R:139,G:69,B:19,A:255). - - - - - Salmon color (R:250,G:128,B:114,A:255). - - - - - SandyBrown color (R:244,G:164,B:96,A:255). - - - - - SeaGreen color (R:46,G:139,B:87,A:255). - - - - - SeaShell color (R:255,G:245,B:238,A:255). - - - - - Sienna color (R:160,G:82,B:45,A:255). - - - - - Silver color (R:192,G:192,B:192,A:255). - - - - - SkyBlue color (R:135,G:206,B:235,A:255). - - - - - SlateBlue color (R:106,G:90,B:205,A:255). - - - - - SlateGray color (R:112,G:128,B:144,A:255). - - - - - Snow color (R:255,G:250,B:250,A:255). - - - - - SpringGreen color (R:0,G:255,B:127,A:255). - - - - - SteelBlue color (R:70,G:130,B:180,A:255). - - - - - Tan color (R:210,G:180,B:140,A:255). - - - - - Teal color (R:0,G:128,B:128,A:255). - - - - - Thistle color (R:216,G:191,B:216,A:255). - - - - - Tomato color (R:255,G:99,B:71,A:255). - - - - - Turquoise color (R:64,G:224,B:208,A:255). - - - - - Violet color (R:238,G:130,B:238,A:255). - - - - - Wheat color (R:245,G:222,B:179,A:255). - - - - - White color (R:255,G:255,B:255,A:255). - - - - - WhiteSmoke color (R:245,G:245,B:245,A:255). - - - - - Yellow color (R:255,G:255,B:0,A:255). - - - - - YellowGreen color (R:154,G:205,B:50,A:255). - - - - - Performs linear interpolation of . - - Source . - Destination . - Interpolation factor. - Interpolated . - - - - should be used instead of this function. - - Interpolated . - - - - Multiply by value. - - Source . - Multiplicator. - Multiplication result. - - - - Multiply by value. - - Source . - Multiplicator. - Multiplication result. - - - - Gets a representation for this object. - - A representation for this object. - - - - Gets a representation for this object. - - A representation for this object. - - - - Gets or sets packed value of this . - - - - - Returns a representation of this in the format: - {R:[red] G:[green] B:[blue] A:[alpha]} - - representation of this . - - - - Translate a non-premultipled alpha to a that contains premultiplied alpha. - - A representing color. - A which contains premultiplied alpha data. - - - - Translate a non-premultipled alpha to a that contains premultiplied alpha. - - Red component value. - Green component value. - Blue component value. - Alpha component value. - A which contains premultiplied alpha data. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Defines how the bounding volumes intersects or contain one another. - - - - - Indicates that there is no overlap between two bounding volumes. - - - - - Indicates that one bounding volume completely contains another volume. - - - - - Indicates that bounding volumes partially overlap one another. - - - - - Defines the continuity of keys on a . - - - - - Interpolation can be used between this key and the next. - - - - - Interpolation cannot be used. A position between the two points returns this point. - - - - - Contains a collection of points in 2D space and provides methods for evaluating features of the curve they define. - - - - - Returns true if this curve is constant (has zero or one points); false otherwise. - - - - - The collection of curve keys. - - - - - Defines how to handle weighting values that are greater than the last control point in the curve. - - - - - Defines how to handle weighting values that are less than the first control point in the curve. - - - - - Constructs a curve. - - - - - Creates a copy of this curve. - - A copy of this curve. - - - - Evaluate the value at a position of this . - - The position on this . - Value at the position on this . - - - - Computes tangents for all keys in the collection. - - The tangent type for both in and out. - - - - Computes tangents for all keys in the collection. - - The tangent in-type. for more details. - The tangent out-type. for more details. - - - - Computes tangent for the specific key in the collection. - - The index of a key in the collection. - The tangent type for both in and out. - - - - Computes tangent for the specific key in the collection. - - The index of key in the collection. - The tangent in-type. for more details. - The tangent out-type. for more details. - - - - The collection of the elements and a part of the class. - - - - - Indexer. - - The index of key in this collection. - at position. - - - - Returns the count of keys in this collection. - - - - - Returns false because it is not a read-only collection. - - - - - Creates a new instance of class. - - - - - Adds a key to this collection. - - New key for the collection. - Throws if is null. - The new key would be added respectively to a position of that key and the position of other keys. - - - - Removes all keys from this collection. - - - - - Creates a copy of this collection. - - A copy of this collection. - - - - Determines whether this collection contains a specific key. - - The key to locate in this collection. - true if the key is found; false otherwise. - - - - Copies the keys of this collection to an array, starting at the array index provided. - - Destination array where elements will be copied. - The zero-based index in the array to start copying from. - - - - Returns an enumerator that iterates through the collection. - - An enumerator for the . - - - - Finds element in the collection and returns its index. - - Element for the search. - Index of the element; or -1 if item is not found. - - - - Removes element at the specified index. - - The index which element will be removed. - - - - Removes specific element. - - The element - true if item is successfully removed; false otherwise. This method also returns false if item was not found. - - - - Key point on the . - - - - - Gets or sets the indicator whether the segment between this point and the next point on the curve is discrete or continuous. - - - - - Gets a position of the key on the curve. - - - - - Gets or sets a tangent when approaching this point from the previous point on the curve. - - - - - Gets or sets a tangent when leaving this point to the next point on the curve. - - - - - Gets a value of this point. - - - - - Creates a new instance of class with position: 0 and value: 0. - - - - - Creates a new instance of class. - - Position on the curve. - Value of the control point. - - - - Creates a new instance of class. - - Position on the curve. - Value of the control point. - Tangent approaching point from the previous point on the curve. - Tangent leaving point toward next point on the curve. - - - - Creates a new instance of class. - - Position on the curve. - Value of the control point. - Tangent approaching point from the previous point on the curve. - Tangent leaving point toward next point on the curve. - Indicates whether the curve is discrete or continuous. - - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Creates a copy of this key. - - A copy of this key. - - - - Defines how the value is determined for position before first point or after the end point on the . - - - - - The value of will be evaluated as first point for positions before the beginning and end point for positions after the end. - - - - - The positions will wrap around from the end to beginning of the for determined the value. - - - - - The positions will wrap around from the end to beginning of the . - The value will be offset by the difference between the values of first and end multiplied by the wrap amount. - If the position is before the beginning of the the difference will be subtracted from its value; otherwise the difference will be added. - - - - - The value at the end of the act as an offset from the same side of the toward the opposite side. - - - - - The linear interpolation will be performed for determined the value. - - - - - Defines the different tangent types to be calculated for points in a . - - - - - The tangent which always has a value equal to zero. - - - - - The tangent which contains a difference between current tangent value and the tangent value from the previous . - - - - - The smoouth tangent which contains the inflection between and by taking into account the values of both neighbors of the . - - - - - Defines the orientation of the display. - - - - - The default orientation. - - - - - The display is rotated counterclockwise into a landscape orientation. Width is greater than height. - - - - - The display is rotated clockwise into a landscape orientation. Width is greater than height. - - - - - The display is rotated as portrait, where height is greater than width. - - - - - The display is rotated as inverted portrait, where height is greater than width. - - - - - Unknown display orientation. - - - - - Event that is triggered when a is added - to this . - - - - - Event that is triggered when a is removed - from this . - - - - - Removes every from this . - Triggers once for each removed. - - - - - Shuts down the component. - - - - - Shuts down the component. - - - - - The maximum amount of time we will frameskip over and only perform Update calls with no Draw calls. - MonoGame extension. - - - - - The SortingFilteringCollection class provides efficient, reusable - sorting and filtering based on a configurable sort comparer, filter - predicate, and associate change events. - - - - - When implemented in a derived class, reports the default - GameRunBehavior for this platform. - - - - - Gets the Game instance that owns this GamePlatform instance. - - - - - Raises the AsyncRunLoopEnded event. This method must be called by - derived classes when the asynchronous run loop they start has - stopped running. - - - - - Gives derived classes an opportunity to do work before any - components are initialized. Note that the base implementation sets - IsActive to true, so derived classes should either call the base - implementation or set IsActive to true by their own means. - - - - - Gives derived classes an opportunity to do work just before the - run loop is begun. Implementations may also return false to prevent - the run loop from starting. - - - - - - When implemented in a derived, ends the active run loop. - - - - - When implemented in a derived, starts the run loop and blocks - until it has ended. - - - - - When implemented in a derived, starts the run loop and returns - immediately. - - - - - Gives derived classes an opportunity to do work just before Update - is called for all IUpdatable components. Returning false from this - method will result in this round of Update calls being skipped. - - - - - - - Gives derived classes an opportunity to do work just before Draw - is called for all IDrawable components. Returning false from this - method will result in this round of Draw calls being skipped. - - - - - - - When implemented in a derived class, causes the game to enter - full-screen mode. - - - - - When implemented in a derived class, causes the game to exit - full-screen mode. - - - - - Gives derived classes an opportunity to modify - Game.TargetElapsedTime before it is set. - - The proposed new value of TargetElapsedTime. - The new value of TargetElapsedTime that will be set. - - - - Starts a device transition (windowed to full screen or vice versa). - - - Specifies whether the device will be in full-screen mode upon completion of the change. - - - - - Completes a device transition. - - - Screen device name. - - - The new width of the game's client window. - - - The new height of the game's client window. - - - - - Gives derived classes an opportunity to take action after - Game.TargetElapsedTime has been set. - - - - - MSDN: Use this method if your game is recovering from a slow-running state, and ElapsedGameTime is too large to be useful. - Frame timing is generally handled by the Game class, but some platforms still handle it elsewhere. Once all platforms - rely on the Game class's functionality, this method and any overrides should be removed. - - - - - Used by the GraphicsDeviceManager to update the platform window - after the graphics device has changed the presentation. - - - - - Performs application-defined tasks associated with freeing, - releasing, or resetting unmanaged resources. - - - - - Log the specified Message. - - - - - - - - Defines how should be runned. - - - - - The game loop will be runned asynchronous. - - - - - The game loop will be runned synchronous. - - - - - Used by the platform code to control the graphics device. - - - - - Called at the start of rendering a frame. - - Returns true if the frame should be rendered. - - - - Called to create the graphics device. - - Does nothing if the graphics device is already created. - - - - Called after rendering to present the frame to the screen. - - - - - Contains commonly used precalculated values and mathematical operations. - - - - - Represents the mathematical constant e(2.71828175). - - - - - Represents the log base ten of e(0.4342945). - - - - - Represents the log base two of e(1.442695). - - - - - Represents the value of pi(3.14159274). - - - - - Represents the value of pi divided by two(1.57079637). - - - - - Represents the value of pi divided by four(0.7853982). - - - - - Represents the value of pi times two(6.28318548). - - - - - Returns the Cartesian coordinate for one axis of a point that is defined by a given triangle and two normalized barycentric (areal) coordinates. - - The coordinate on one axis of vertex 1 of the defining triangle. - The coordinate on the same axis of vertex 2 of the defining triangle. - The coordinate on the same axis of vertex 3 of the defining triangle. - The normalized barycentric (areal) coordinate b2, equal to the weighting factor for vertex 2, the coordinate of which is specified in value2. - The normalized barycentric (areal) coordinate b3, equal to the weighting factor for vertex 3, the coordinate of which is specified in value3. - Cartesian coordinate of the specified point with respect to the axis being used. - - - - Performs a Catmull-Rom interpolation using the specified positions. - - The first position in the interpolation. - The second position in the interpolation. - The third position in the interpolation. - The fourth position in the interpolation. - Weighting factor. - A position that is the result of the Catmull-Rom interpolation. - - - - Restricts a value to be within a specified range. - - The value to clamp. - The minimum value. If value is less than min, min will be returned. - The maximum value. If value is greater than max, max will be returned. - The clamped value. - - - - Restricts a value to be within a specified range. - - The value to clamp. - The minimum value. If value is less than min, min will be returned. - The maximum value. If value is greater than max, max will be returned. - The clamped value. - - - - Calculates the absolute value of the difference of two values. - - Source value. - Source value. - Distance between the two values. - - - - Performs a Hermite spline interpolation. - - Source position. - Source tangent. - Source position. - Source tangent. - Weighting factor. - The result of the Hermite spline interpolation. - - - - Linearly interpolates between two values. - - Source value. - Destination value. - Value between 0 and 1 indicating the weight of value2. - Interpolated value. - This method performs the linear interpolation based on the following formula: - value1 + (value2 - value1) * amount. - Passing amount a value of 0 will cause value1 to be returned, a value of 1 will cause value2 to be returned. - See for a less efficient version with more precision around edge cases. - - - - - Linearly interpolates between two values. - This method is a less efficient, more precise version of . - See remarks for more info. - - Source value. - Destination value. - Value between 0 and 1 indicating the weight of value2. - Interpolated value. - This method performs the linear interpolation based on the following formula: - ((1 - amount) * value1) + (value2 * amount). - Passing amount a value of 0 will cause value1 to be returned, a value of 1 will cause value2 to be returned. - This method does not have the floating point precision issue that has. - i.e. If there is a big gap between value1 and value2 in magnitude (e.g. value1=10000000000000000, value2=1), - right at the edge of the interpolation range (amount=1), will return 0 (whereas it should return 1). - This also holds for value1=10^17, value2=10; value1=10^18,value2=10^2... so on. - For an in depth explanation of the issue, see below references: - Relevant Wikipedia Article: https://en.wikipedia.org/wiki/Linear_interpolation#Programming_language_support - Relevant StackOverflow Answer: http://stackoverflow.com/questions/4353525/floating-point-linear-interpolation#answer-23716956 - - - - - Returns the greater of two values. - - Source value. - Source value. - The greater value. - - - - Returns the greater of two values. - - Source value. - Source value. - The greater value. - - - - Returns the lesser of two values. - - Source value. - Source value. - The lesser value. - - - - Returns the lesser of two values. - - Source value. - Source value. - The lesser value. - - - - Interpolates between two values using a cubic equation. - - Source value. - Source value. - Weighting value. - Interpolated value. - - - - Converts radians to degrees. - - The angle in radians. - The angle in degrees. - - This method uses double precission internally, - though it returns single float - Factor = 180 / pi - - - - - Converts degrees to radians. - - The angle in degrees. - The angle in radians. - - This method uses double precission internally, - though it returns single float - Factor = pi / 180 - - - - - Reduces a given angle to a value between π and -π. - - The angle to reduce, in radians. - The new angle, in radians. - - - - Determines if value is powered by two. - - A value. - true if value is powered by two; otherwise false. - - - - Represents the right-handed 4x4 floating point matrix, which can store translation, scale and rotation information. - - - - - Constructs a matrix. - - A first row and first column value. - A first row and second column value. - A first row and third column value. - A first row and fourth column value. - A second row and first column value. - A second row and second column value. - A second row and third column value. - A second row and fourth column value. - A third row and first column value. - A third row and second column value. - A third row and third column value. - A third row and fourth column value. - A fourth row and first column value. - A fourth row and second column value. - A fourth row and third column value. - A fourth row and fourth column value. - - - - Constructs a matrix. - - A first row of the created matrix. - A second row of the created matrix. - A third row of the created matrix. - A fourth row of the created matrix. - - - - A first row and first column value. - - - - - A first row and second column value. - - - - - A first row and third column value. - - - - - A first row and fourth column value. - - - - - A second row and first column value. - - - - - A second row and second column value. - - - - - A second row and third column value. - - - - - A second row and fourth column value. - - - - - A third row and first column value. - - - - - A third row and second column value. - - - - - A third row and third column value. - - - - - A third row and fourth column value. - - - - - A fourth row and first column value. - - - - - A fourth row and second column value. - - - - - A fourth row and third column value. - - - - - A fourth row and fourth column value. - - - - - The backward vector formed from the third row M31, M32, M33 elements. - - - - - The down vector formed from the second row -M21, -M22, -M23 elements. - - - - - The forward vector formed from the third row -M31, -M32, -M33 elements. - - - - - Returns the identity matrix. - - - - - The left vector formed from the first row -M11, -M12, -M13 elements. - - - - - The right vector formed from the first row M11, M12, M13 elements. - - - - - Rotation stored in this matrix. - - - - - Position stored in this matrix. - - - - - Scale stored in this matrix. - - - - - The upper vector formed from the second row M21, M22, M23 elements. - - - - - Creates a new which contains sum of two matrixes. - - The first matrix to add. - The second matrix to add. - The result of the matrix addition. - - - - Creates a new which contains sum of two matrixes. - - The first matrix to add. - The second matrix to add. - The result of the matrix addition as an output parameter. - - - - Creates a new for spherical billboarding that rotates around specified object position. - - Position of billboard object. It will rotate around that vector. - The camera position. - The camera up vector. - Optional camera forward vector. - The for spherical billboarding. - - - - Creates a new for spherical billboarding that rotates around specified object position. - - Position of billboard object. It will rotate around that vector. - The camera position. - The camera up vector. - Optional camera forward vector. - The for spherical billboarding as an output parameter. - - - - Creates a new for cylindrical billboarding that rotates around specified axis. - - Object position the billboard will rotate around. - Camera position. - Axis of billboard for rotation. - Optional camera forward vector. - Optional object forward vector. - The for cylindrical billboarding. - - - - Creates a new for cylindrical billboarding that rotates around specified axis. - - Object position the billboard will rotate around. - Camera position. - Axis of billboard for rotation. - Optional camera forward vector. - Optional object forward vector. - The for cylindrical billboarding as an output parameter. - - - - Creates a new which contains the rotation moment around specified axis. - - The axis of rotation. - The angle of rotation in radians. - The rotation . - - - - Creates a new which contains the rotation moment around specified axis. - - The axis of rotation. - The angle of rotation in radians. - The rotation as an output parameter. - - - - Creates a new rotation from a . - - of rotation moment. - The rotation . - - - - Creates a new rotation from a . - - of rotation moment. - The rotation as an output parameter. - - - - Creates a new rotation from the specified yaw, pitch and roll values. - - The yaw rotation value in radians. - The pitch rotation value in radians. - The roll rotation value in radians. - The rotation . - For more information about yaw, pitch and roll visit http://en.wikipedia.org/wiki/Euler_angles. - - - - - Creates a new rotation from the specified yaw, pitch and roll values. - - The yaw rotation value in radians. - The pitch rotation value in radians. - The roll rotation value in radians. - The rotation as an output parameter. - For more information about yaw, pitch and roll visit http://en.wikipedia.org/wiki/Euler_angles. - - - - - Creates a new viewing . - - Position of the camera. - Lookup vector of the camera. - The direction of the upper edge of the camera. - The viewing . - - - - Creates a new viewing . - - Position of the camera. - Lookup vector of the camera. - The direction of the upper edge of the camera. - The viewing as an output parameter. - - - - Creates a new projection for orthographic view. - - Width of the viewing volume. - Height of the viewing volume. - Depth of the near plane. - Depth of the far plane. - The new projection for orthographic view. - - - - Creates a new projection for orthographic view. - - Width of the viewing volume. - Height of the viewing volume. - Depth of the near plane. - Depth of the far plane. - The new projection for orthographic view as an output parameter. - - - - Creates a new projection for customized orthographic view. - - Lower x-value at the near plane. - Upper x-value at the near plane. - Lower y-coordinate at the near plane. - Upper y-value at the near plane. - Depth of the near plane. - Depth of the far plane. - The new projection for customized orthographic view. - - - - Creates a new projection for customized orthographic view. - - The viewing volume. - Depth of the near plane. - Depth of the far plane. - The new projection for customized orthographic view. - - - - Creates a new projection for customized orthographic view. - - Lower x-value at the near plane. - Upper x-value at the near plane. - Lower y-coordinate at the near plane. - Upper y-value at the near plane. - Depth of the near plane. - Depth of the far plane. - The new projection for customized orthographic view as an output parameter. - - - - Creates a new projection for perspective view. - - Width of the viewing volume. - Height of the viewing volume. - Distance to the near plane. - Distance to the far plane. - The new projection for perspective view. - - - - Creates a new projection for perspective view. - - Width of the viewing volume. - Height of the viewing volume. - Distance to the near plane. - Distance to the far plane. - The new projection for perspective view as an output parameter. - - - - Creates a new projection for perspective view with field of view. - - Field of view in the y direction in radians. - Width divided by height of the viewing volume. - Distance to the near plane. - Distance to the far plane. - The new projection for perspective view with FOV. - - - - Creates a new projection for perspective view with field of view. - - Field of view in the y direction in radians. - Width divided by height of the viewing volume. - Distance of the near plane. - Distance of the far plane. - The new projection for perspective view with FOV as an output parameter. - - - - Creates a new projection for customized perspective view. - - Lower x-value at the near plane. - Upper x-value at the near plane. - Lower y-coordinate at the near plane. - Upper y-value at the near plane. - Distance to the near plane. - Distance to the far plane. - The new for customized perspective view. - - - - Creates a new projection for customized perspective view. - - The viewing volume. - Distance to the near plane. - Distance to the far plane. - The new for customized perspective view. - - - - Creates a new projection for customized perspective view. - - Lower x-value at the near plane. - Upper x-value at the near plane. - Lower y-coordinate at the near plane. - Upper y-value at the near plane. - Distance to the near plane. - Distance to the far plane. - The new for customized perspective view as an output parameter. - - - - Creates a new rotation around X axis. - - Angle in radians. - The rotation around X axis. - - - - Creates a new rotation around X axis. - - Angle in radians. - The rotation around X axis as an output parameter. - - - - Creates a new rotation around Y axis. - - Angle in radians. - The rotation around Y axis. - - - - Creates a new rotation around Y axis. - - Angle in radians. - The rotation around Y axis as an output parameter. - - - - Creates a new rotation around Z axis. - - Angle in radians. - The rotation around Z axis. - - - - Creates a new rotation around Z axis. - - Angle in radians. - The rotation around Z axis as an output parameter. - - - - Creates a new scaling . - - Scale value for all three axises. - The scaling . - - - - Creates a new scaling . - - Scale value for all three axises. - The scaling as an output parameter. - - - - Creates a new scaling . - - Scale value for X axis. - Scale value for Y axis. - Scale value for Z axis. - The scaling . - - - - Creates a new scaling . - - Scale value for X axis. - Scale value for Y axis. - Scale value for Z axis. - The scaling as an output parameter. - - - - Creates a new scaling . - - representing x,y and z scale values. - The scaling . - - - - Creates a new scaling . - - representing x,y and z scale values. - The scaling as an output parameter. - - - - Creates a new that flattens geometry into a specified as if casting a shadow from a specified light source. - - A vector specifying the direction from which the light that will cast the shadow is coming. - The plane onto which the new matrix should flatten geometry so as to cast a shadow. - A that can be used to flatten geometry onto the specified plane from the specified direction. - - - - Creates a new that flattens geometry into a specified as if casting a shadow from a specified light source. - - A vector specifying the direction from which the light that will cast the shadow is coming. - The plane onto which the new matrix should flatten geometry so as to cast a shadow. - A that can be used to flatten geometry onto the specified plane from the specified direction as an output parameter. - - - - Creates a new translation . - - X coordinate of translation. - Y coordinate of translation. - Z coordinate of translation. - The translation . - - - - Creates a new translation . - - X,Y and Z coordinates of translation. - The translation as an output parameter. - - - - Creates a new translation . - - X,Y and Z coordinates of translation. - The translation . - - - - Creates a new translation . - - X coordinate of translation. - Y coordinate of translation. - Z coordinate of translation. - The translation as an output parameter. - - - - Creates a new reflection . - - The plane that used for reflection calculation. - The reflection . - - - - Creates a new reflection . - - The plane that used for reflection calculation. - The reflection as an output parameter. - - - - Creates a new world . - - The position vector. - The forward direction vector. - The upward direction vector. Usually . - The world . - - - - Creates a new world . - - The position vector. - The forward direction vector. - The upward direction vector. Usually . - The world as an output parameter. - - - - Decomposes this matrix to translation, rotation and scale elements. Returns true if matrix can be decomposed; false otherwise. - - Scale vector as an output parameter. - Rotation quaternion as an output parameter. - Translation vector as an output parameter. - true if matrix can be decomposed; false otherwise. - - - - Returns a determinant of this . - - Determinant of this - See more about determinant here - http://en.wikipedia.org/wiki/Determinant. - - - - - Divides the elements of a by the elements of another matrix. - - Source . - Divisor . - The result of dividing the matrix. - - - - Divides the elements of a by the elements of another matrix. - - Source . - Divisor . - The result of dividing the matrix as an output parameter. - - - - Divides the elements of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a matrix by a scalar. - - - - Divides the elements of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a matrix by a scalar as an output parameter. - - - - Compares whether current instance is equal to specified without any tolerance. - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified without any tolerance. - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Creates a new which contains inversion of the specified matrix. - - Source . - The inverted matrix. - - - - Creates a new which contains inversion of the specified matrix. - - Source . - The inverted matrix as output parameter. - - - - Creates a new that contains linear interpolation of the values in specified matrixes. - - The first . - The second . - Weighting value(between 0.0 and 1.0). - >The result of linear interpolation of the specified matrixes. - - - - Creates a new that contains linear interpolation of the values in specified matrixes. - - The first . - The second . - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified matrixes as an output parameter. - - - - Creates a new that contains a multiplication of two matrix. - - Source . - Source . - Result of the matrix multiplication. - - - - Creates a new that contains a multiplication of two matrix. - - Source . - Source . - Result of the matrix multiplication as an output parameter. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - Result of the matrix multiplication with a scalar. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - Result of the matrix multiplication with a scalar as an output parameter. - - - - Copy the values of specified to the float array. - - The source . - The array which matrix values will be stored. - - Required for OpenGL 2.0 projection matrix stuff. - - - - - Returns a matrix with the all values negated. - - Source . - Result of the matrix negation. - - - - Returns a matrix with the all values negated. - - Source . - Result of the matrix negation as an output parameter. - - - - Adds two matrixes. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the matrixes. - - - - Divides the elements of a by the elements of another . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the matrixes. - - - - Divides the elements of a by a scalar. - - Source on the left of the div sign. - Divisor scalar on the right of the div sign. - The result of dividing a matrix by a scalar. - - - - Compares whether two instances are equal without any tolerance. - - Source on the left of the equal sign. - Source on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal without any tolerance. - - Source on the left of the not equal sign. - Source on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Multiplies two matrixes. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the matrix multiplication. - - Using matrix multiplication algorithm - see http://en.wikipedia.org/wiki/Matrix_multiplication. - - - - - Multiplies the elements of matrix by a scalar. - - Source on the left of the mul sign. - Scalar value on the right of the mul sign. - Result of the matrix multiplication with a scalar. - - - - Subtracts the values of one from another . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the matrix subtraction. - - - - Inverts values in the specified . - - Source on the right of the sub sign. - Result of the inversion. - - - - Creates a new that contains subtraction of one matrix from another. - - The first . - The second . - The result of the matrix subtraction. - - - - Creates a new that contains subtraction of one matrix from another. - - The first . - The second . - The result of the matrix subtraction as an output parameter. - - - - Returns a representation of this in the format: - {M11:[] M12:[] M13:[] M14:[]} - {M21:[] M12:[] M13:[] M14:[]} - {M31:[] M32:[] M33:[] M34:[]} - {M41:[] M42:[] M43:[] M44:[]} - - A representation of this . - - - - Swap the matrix rows and columns. - - The matrix for transposing operation. - The new which contains the transposing result. - - - - Swap the matrix rows and columns. - - The matrix for transposing operation. - The new which contains the transposing result as an output parameter. - - - - Helper method for using the Laplace expansion theorem using two rows expansions to calculate major and - minor determinants of a 4x4 matrix. This method is used for inverting a matrix. - - - - - Provides functionality to handle input from keyboards, mice, gamepads, etc. - - - - - Support for playing sound effects and XACT audio. - - - - - The runtime support for loading content pipeline content. - - - - - Returns a value indicating what side (positive/negative) of a plane a point is - - The point to check with - The plane to check against - Greater than zero if on the positive side, less than zero if on the negative size, 0 otherwise - - - - Returns the perpendicular distance from a point to a plane - - The point to check - The place to check - The perpendicular distance from the point to the plane - - - - Transforms a normalized plane by a matrix. - - The normalized plane to transform. - The transformation matrix. - The transformed plane. - - - - Transforms a normalized plane by a matrix. - - The normalized plane to transform. - The transformation matrix. - The transformed plane. - - - - Transforms a normalized plane by a quaternion rotation. - - The normalized plane to transform. - The quaternion rotation. - The transformed plane. - - - - Transforms a normalized plane by a quaternion rotation. - - The normalized plane to transform. - The quaternion rotation. - The transformed plane. - - - - Defines the intersection between a and a bounding volume. - - - - - There is no intersection, the bounding volume is in the negative half space of the plane. - - - - - There is no intersection, the bounding volume is in the positive half space of the plane. - - - - - The plane is intersected. - - - - - Defines the index of player for various MonoGame components. - - - - - The first player index. - - - - - The second player index. - - - - - The third player index. - - - - - The fourth player index. - - - - - Describes a 2D-point. - - - - - The x coordinate of this . - - - - - The y coordinate of this . - - - - - Returns a with coordinates 0, 0. - - - - - Constructs a point with X and Y from two values. - - The x coordinate in 2d-space. - The y coordinate in 2d-space. - - - - Constructs a point with X and Y set to the same value. - - The x and y coordinates in 2d-space. - - - - Adds two points. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the points. - - - - Subtracts a from a . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the subtraction. - - - - Multiplies the components of two points by each other. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the multiplication. - - - - Divides the components of a by the components of another . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the points. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Returns a representation of this in the format: - {X:[] Y:[]} - - representation of this . - - - - Gets a representation for this object. - - A representation for this object. - - - - The arguments to the event. - - - - - Create a new instance of the event. - - The default settings to be used in device creation. - - - - The default settings that will be used in device creation. - - - - - Interface used to add an object to be loaded on the primary thread - - - - - Static class that is called before every draw to load resources that need to finish loading on the primary thread - - - - - Loops through list and loads the item. If successful, it is removed from the list. - - - - - An efficient mathematical representation for three dimensional rotations. - - - - - The x coordinate of this . - - - - - The y coordinate of this . - - - - - The z coordinate of this . - - - - - The rotation component of this . - - - - - Constructs a quaternion with X, Y, Z and W from four values. - - The x coordinate in 3d-space. - The y coordinate in 3d-space. - The z coordinate in 3d-space. - The rotation component. - - - - Constructs a quaternion with X, Y, Z from and rotation component from a scalar. - - The x, y, z coordinates in 3d-space. - The rotation component. - - - - Constructs a quaternion from . - - The x, y, z coordinates in 3d-space and the rotation component. - - - - Returns a quaternion representing no rotation. - - - - - Creates a new that contains the sum of two quaternions. - - Source . - Source . - The result of the quaternion addition. - - - - Creates a new that contains the sum of two quaternions. - - Source . - Source . - The result of the quaternion addition as an output parameter. - - - - Creates a new that contains concatenation between two quaternion. - - The first to concatenate. - The second to concatenate. - The result of rotation of followed by rotation. - - - - Creates a new that contains concatenation between two quaternion. - - The first to concatenate. - The second to concatenate. - The result of rotation of followed by rotation as an output parameter. - - - - Transforms this quaternion into its conjugated version. - - - - - Creates a new that contains conjugated version of the specified quaternion. - - The quaternion which values will be used to create the conjugated version. - The conjugate version of the specified quaternion. - - - - Creates a new that contains conjugated version of the specified quaternion. - - The quaternion which values will be used to create the conjugated version. - The conjugated version of the specified quaternion as an output parameter. - - - - Creates a new from the specified axis and angle. - - The axis of rotation. - The angle in radians. - The new quaternion builded from axis and angle. - - - - Creates a new from the specified axis and angle. - - The axis of rotation. - The angle in radians. - The new quaternion builded from axis and angle as an output parameter. - - - - Creates a new from the specified . - - The rotation matrix. - A quaternion composed from the rotation part of the matrix. - - - - Creates a new from the specified . - - The rotation matrix. - A quaternion composed from the rotation part of the matrix as an output parameter. - - - - Creates a new from the specified yaw, pitch and roll angles. - - Yaw around the y axis in radians. - Pitch around the x axis in radians. - Roll around the z axis in radians. - A new quaternion from the concatenated yaw, pitch, and roll angles. - - - - Creates a new from the specified yaw, pitch and roll angles. - - Yaw around the y axis in radians. - Pitch around the x axis in radians. - Roll around the z axis in radians. - A new quaternion from the concatenated yaw, pitch, and roll angles as an output parameter. - - - - Divides a by the other . - - Source . - Divisor . - The result of dividing the quaternions. - - - - Divides a by the other . - - Source . - Divisor . - The result of dividing the quaternions as an output parameter. - - - - Returns a dot product of two quaternions. - - The first quaternion. - The second quaternion. - The dot product of two quaternions. - - - - Returns a dot product of two quaternions. - - The first quaternion. - The second quaternion. - The dot product of two quaternions as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Returns the inverse quaternion which represents the opposite rotation. - - Source . - The inverse quaternion. - - - - Returns the inverse quaternion which represents the opposite rotation. - - Source . - The inverse quaternion as an output parameter. - - - - Returns the magnitude of the quaternion components. - - The magnitude of the quaternion components. - - - - Returns the squared magnitude of the quaternion components. - - The squared magnitude of the quaternion components. - - - - Performs a linear blend between two quaternions. - - Source . - Source . - The blend amount where 0 returns and 1 . - The result of linear blending between two quaternions. - - - - Performs a linear blend between two quaternions. - - Source . - Source . - The blend amount where 0 returns and 1 . - The result of linear blending between two quaternions as an output parameter. - - - - Performs a spherical linear blend between two quaternions. - - Source . - Source . - The blend amount where 0 returns and 1 . - The result of spherical linear blending between two quaternions. - - - - Performs a spherical linear blend between two quaternions. - - Source . - Source . - The blend amount where 0 returns and 1 . - The result of spherical linear blending between two quaternions as an output parameter. - - - - Creates a new that contains subtraction of one from another. - - Source . - Source . - The result of the quaternion subtraction. - - - - Creates a new that contains subtraction of one from another. - - Source . - Source . - The result of the quaternion subtraction as an output parameter. - - - - Creates a new that contains a multiplication of two quaternions. - - Source . - Source . - The result of the quaternion multiplication. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the quaternion multiplication with a scalar. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the quaternion multiplication with a scalar as an output parameter. - - - - Creates a new that contains a multiplication of two quaternions. - - Source . - Source . - The result of the quaternion multiplication as an output parameter. - - - - Flips the sign of the all the quaternion components. - - Source . - The result of the quaternion negation. - - - - Flips the sign of the all the quaternion components. - - Source . - The result of the quaternion negation as an output parameter. - - - - Scales the quaternion magnitude to unit length. - - - - - Scales the quaternion magnitude to unit length. - - Source . - The unit length quaternion. - - - - Scales the quaternion magnitude to unit length. - - Source . - The unit length quaternion an output parameter. - - - - Returns a representation of this in the format: - {X:[] Y:[] Z:[] W:[]} - - A representation of this . - - - - Gets a representation for this object. - - A representation for this object. - - - - Adds two quaternions. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the vectors. - - - - Divides a by the other . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the quaternions. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Multiplies two quaternions. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the quaternions multiplication. - - - - Multiplies the components of quaternion by a scalar. - - Source on the left of the mul sign. - Scalar value on the right of the mul sign. - Result of the quaternion multiplication with a scalar. - - - - Subtracts a from a . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the quaternion subtraction. - - - - Flips the sign of the all the quaternion components. - - Source on the right of the sub sign. - The result of the quaternion negation. - - - - Describes a 2D-rectangle. - - - - - The x coordinate of the top-left corner of this . - - - - - The y coordinate of the top-left corner of this . - - - - - The width of this . - - - - - The height of this . - - - - - Returns a with X=0, Y=0, Width=0, Height=0. - - - - - Returns the x coordinate of the left edge of this . - - - - - Returns the x coordinate of the right edge of this . - - - - - Returns the y coordinate of the top edge of this . - - - - - Returns the y coordinate of the bottom edge of this . - - - - - Whether or not this has a and - of 0, and a of (0, 0). - - - - - The top-left coordinates of this . - - - - - The width-height coordinates of this . - - - - - A located in the center of this . - - - If or is an odd number, - the center point will be rounded down. - - - - - Creates a new instance of struct, with the specified - position, width, and height. - - The x coordinate of the top-left corner of the created . - The y coordinate of the top-left corner of the created . - The width of the created . - The height of the created . - - - - Creates a new instance of struct, with the specified - location and size. - - The x and y coordinates of the top-left corner of the created . - The width and height of the created . - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Gets whether or not the provided coordinates lie within the bounds of this . - - The x coordinate of the point to check for containment. - The y coordinate of the point to check for containment. - true if the provided coordinates lie inside this ; false otherwise. - - - - Gets whether or not the provided coordinates lie within the bounds of this . - - The x coordinate of the point to check for containment. - The y coordinate of the point to check for containment. - true if the provided coordinates lie inside this ; false otherwise. - - - - Gets whether or not the provided lies within the bounds of this . - - The coordinates to check for inclusion in this . - true if the provided lies inside this ; false otherwise. - - - - Gets whether or not the provided lies within the bounds of this . - - The coordinates to check for inclusion in this . - true if the provided lies inside this ; false otherwise. As an output parameter. - - - - Gets whether or not the provided lies within the bounds of this . - - The coordinates to check for inclusion in this . - true if the provided lies inside this ; false otherwise. - - - - Gets whether or not the provided lies within the bounds of this . - - The coordinates to check for inclusion in this . - true if the provided lies inside this ; false otherwise. As an output parameter. - - - - Gets whether or not the provided lies within the bounds of this . - - The to check for inclusion in this . - true if the provided 's bounds lie entirely inside this ; false otherwise. - - - - Gets whether or not the provided lies within the bounds of this . - - The to check for inclusion in this . - true if the provided 's bounds lie entirely inside this ; false otherwise. As an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Adjusts the edges of this by specified horizontal and vertical amounts. - - Value to adjust the left and right edges. - Value to adjust the top and bottom edges. - - - - Adjusts the edges of this by specified horizontal and vertical amounts. - - Value to adjust the left and right edges. - Value to adjust the top and bottom edges. - - - - Gets whether or not the other intersects with this rectangle. - - The other rectangle for testing. - true if other intersects with this rectangle; false otherwise. - - - - Gets whether or not the other intersects with this rectangle. - - The other rectangle for testing. - true if other intersects with this rectangle; false otherwise. As an output parameter. - - - - Creates a new that contains overlapping region of two other rectangles. - - The first . - The second . - Overlapping region of the two rectangles. - - - - Creates a new that contains overlapping region of two other rectangles. - - The first . - The second . - Overlapping region of the two rectangles as an output parameter. - - - - Changes the of this . - - The x coordinate to add to this . - The y coordinate to add to this . - - - - Changes the of this . - - The x coordinate to add to this . - The y coordinate to add to this . - - - - Changes the of this . - - The x and y components to add to this . - - - - Changes the of this . - - The x and y components to add to this . - - - - Returns a representation of this in the format: - {X:[] Y:[] Width:[] Height:[]} - - representation of this . - - - - Creates a new that completely contains two other rectangles. - - The first . - The second . - The union of the two rectangles. - - - - Creates a new that completely contains two other rectangles. - - The first . - The second . - The union of the two rectangles as an output parameter. - - - - This class is used for the game window's TextInput event as EventArgs. - - - - - Checks if the code is currently running on the UI thread. - - true if the code is currently running on the UI thread. - - - - Throws an exception if the code is not currently running on the UI thread. - - Thrown if the code is not currently running on the UI thread. - - - - Runs the given action on the UI thread and blocks the current thread while the action is running. - If the current thread is the UI thread, the action will run immediately. - - The action to be run on the UI thread - - - - Returns an open stream to an exsiting file in the title storage area. - - The filepath relative to the title storage area. - A open stream or null if the file is not found. - - - - Describes a 2D-vector. - - - - - The x coordinate of this . - - - - - The y coordinate of this . - - - - - Returns a with components 0, 0. - - - - - Returns a with components 1, 1. - - - - - Returns a with components 1, 0. - - - - - Returns a with components 0, 1. - - - - - Constructs a 2d vector with X and Y from two values. - - The x coordinate in 2d-space. - The y coordinate in 2d-space. - - - - Constructs a 2d vector with X and Y set to the same value. - - The x and y coordinates in 2d-space. - - - - Inverts values in the specified . - - Source on the right of the sub sign. - Result of the inversion. - - - - Adds two vectors. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the vectors. - - - - Subtracts a from a . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the vector subtraction. - - - - Multiplies the components of two vectors by each other. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication. - - - - Multiplies the components of vector by a scalar. - - Source on the left of the mul sign. - Scalar value on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Multiplies the components of vector by a scalar. - - Scalar value on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Divides the components of a by the components of another . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the vectors. - - - - Divides the components of a by a scalar. - - Source on the left of the div sign. - Divisor scalar on the right of the div sign. - The result of dividing a vector by a scalar. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Performs vector addition on and . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Performs vector addition on and - , storing the result of the - addition in . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 2d-triangle. - - The first vector of 2d-triangle. - The second vector of 2d-triangle. - The third vector of 2d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 2d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 2d-triangle. - The cartesian translation of barycentric coordinates. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 2d-triangle. - - The first vector of 2d-triangle. - The second vector of 2d-triangle. - The third vector of 2d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 2d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 2d-triangle. - The cartesian translation of barycentric coordinates as an output parameter. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation as an output parameter. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value as an output parameter. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors as an output parameter. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors as an output parameter. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors as an output parameter. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar as an output parameter. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector. - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector as an output parameter. - - - - Returns the length of this . - - The length of this . - - - - Returns the squared length of this . - - The squared length of this . - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors as an output parameter. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors as an output parameter. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication as an output parameter. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the vector multiplication with a scalar. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the multiplication with a scalar as an output parameter. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion as an output parameter. - - - - Turns this to a unit vector with the same direction. - - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector. - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector as an output parameter. - - - - Creates a new that contains reflect vector of the given vector and normal. - - Source . - Reflection normal. - Reflected vector. - - - - Creates a new that contains reflect vector of the given vector and normal. - - Source . - Reflection normal. - Reflected vector as an output parameter. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction as an output parameter. - - - - Returns a representation of this in the format: - {X:[] Y:[]} - - A representation of this . - - - - Gets a representation for this object. - - A representation for this object. - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The transformation . - Transformed . - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The transformation . - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 2d-vector by the specified , representing the rotation. - - Source . - The which contains rotation transformation. - Transformed . - - - - Creates a new that contains a transformation of 2d-vector by the specified , representing the rotation. - - Source . - The which contains rotation transformation. - Transformed as an output parameter. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The transformation . - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The which contains rotation transformation. - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The transformation . - Destination array. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The which contains rotation transformation. - Destination array. - - - - Creates a new that contains a transformation of the specified normal by the specified . - - Source which represents a normal vector. - The transformation . - Transformed normal. - - - - Creates a new that contains a transformation of the specified normal by the specified . - - Source which represents a normal vector. - The transformation . - Transformed normal as an output parameter. - - - - Apply transformation on normals within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The transformation . - Destination array. - The starting index in the destination array, where the first should be written. - The number of normals to be transformed. - - - - Apply transformation on all normals within array of by the specified and places the results in an another array. - - Source array. - The transformation . - Destination array. - - - - Describes a 3D-vector. - - - - - The x coordinate of this . - - - - - The y coordinate of this . - - - - - The z coordinate of this . - - - - - Returns a with components 0, 0, 0. - - - - - Returns a with components 1, 1, 1. - - - - - Returns a with components 1, 0, 0. - - - - - Returns a with components 0, 1, 0. - - - - - Returns a with components 0, 0, 1. - - - - - Returns a with components 0, 1, 0. - - - - - Returns a with components 0, -1, 0. - - - - - Returns a with components 1, 0, 0. - - - - - Returns a with components -1, 0, 0. - - - - - Returns a with components 0, 0, -1. - - - - - Returns a with components 0, 0, 1. - - - - - Constructs a 3d vector with X, Y and Z from three values. - - The x coordinate in 3d-space. - The y coordinate in 3d-space. - The z coordinate in 3d-space. - - - - Constructs a 3d vector with X, Y and Z set to the same value. - - The x, y and z coordinates in 3d-space. - - - - Constructs a 3d vector with X, Y from and Z from a scalar. - - The x and y coordinates in 3d-space. - The z coordinate in 3d-space. - - - - Performs vector addition on and . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Performs vector addition on and - , storing the result of the - addition in . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 3d-triangle. - - The first vector of 3d-triangle. - The second vector of 3d-triangle. - The third vector of 3d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 3d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 3d-triangle. - The cartesian translation of barycentric coordinates. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 3d-triangle. - - The first vector of 3d-triangle. - The second vector of 3d-triangle. - The third vector of 3d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 3d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 3d-triangle. - The cartesian translation of barycentric coordinates as an output parameter. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation as an output parameter. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value as an output parameter. - - - - Computes the cross product of two vectors. - - The first vector. - The second vector. - The cross product of two vectors. - - - - Computes the cross product of two vectors. - - The first vector. - The second vector. - The cross product of two vectors as an output parameter. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors as an output parameter. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors as an output parameter. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar as an output parameter. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors as an output parameter. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector. - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector as an output parameter. - - - - Returns the length of this . - - The length of this . - - - - Returns the squared length of this . - - The squared length of this . - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors as an output parameter. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors as an output parameter. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the vector multiplication with a scalar. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the multiplication with a scalar as an output parameter. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication as an output parameter. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion as an output parameter. - - - - Turns this to a unit vector with the same direction. - - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector. - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector as an output parameter. - - - - Creates a new that contains reflect vector of the given vector and normal. - - Source . - Reflection normal. - Reflected vector. - - - - Creates a new that contains reflect vector of the given vector and normal. - - Source . - Reflection normal. - Reflected vector as an output parameter. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction as an output parameter. - - - - Returns a representation of this in the format: - {X:[] Y:[] Z:[]} - - A representation of this . - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The transformation . - Transformed . - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The transformation . - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 3d-vector by the specified , representing the rotation. - - Source . - The which contains rotation transformation. - Transformed . - - - - Creates a new that contains a transformation of 3d-vector by the specified , representing the rotation. - - Source . - The which contains rotation transformation. - Transformed as an output parameter. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The transformation . - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The which contains rotation transformation. - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The transformation . - Destination array. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The which contains rotation transformation. - Destination array. - - - - Creates a new that contains a transformation of the specified normal by the specified . - - Source which represents a normal vector. - The transformation . - Transformed normal. - - - - Creates a new that contains a transformation of the specified normal by the specified . - - Source which represents a normal vector. - The transformation . - Transformed normal as an output parameter. - - - - Apply transformation on normals within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The transformation . - Destination array. - The starting index in the destination array, where the first should be written. - The number of normals to be transformed. - - - - Apply transformation on all normals within array of by the specified and places the results in an another array. - - Source array. - The transformation . - Destination array. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Adds two vectors. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the vectors. - - - - Inverts values in the specified . - - Source on the right of the sub sign. - Result of the inversion. - - - - Subtracts a from a . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the vector subtraction. - - - - Multiplies the components of two vectors by each other. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication. - - - - Multiplies the components of vector by a scalar. - - Source on the left of the mul sign. - Scalar value on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Multiplies the components of vector by a scalar. - - Scalar value on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Divides the components of a by the components of another . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the vectors. - - - - Divides the components of a by a scalar. - - Source on the left of the div sign. - Divisor scalar on the right of the div sign. - The result of dividing a vector by a scalar. - - - - Describes a 4D-vector. - - - - - The x coordinate of this . - - - - - The y coordinate of this . - - - - - The z coordinate of this . - - - - - The w coordinate of this . - - - - - Returns a with components 0, 0, 0, 0. - - - - - Returns a with components 1, 1, 1, 1. - - - - - Returns a with components 1, 0, 0, 0. - - - - - Returns a with components 0, 1, 0, 0. - - - - - Returns a with components 0, 0, 1, 0. - - - - - Returns a with components 0, 0, 0, 1. - - - - - Constructs a 3d vector with X, Y, Z and W from four values. - - The x coordinate in 4d-space. - The y coordinate in 4d-space. - The z coordinate in 4d-space. - The w coordinate in 4d-space. - - - - Constructs a 3d vector with X and Z from and Z and W from the scalars. - - The x and y coordinates in 4d-space. - The z coordinate in 4d-space. - The w coordinate in 4d-space. - - - - Constructs a 3d vector with X, Y, Z from and W from a scalar. - - The x, y and z coordinates in 4d-space. - The w coordinate in 4d-space. - - - - Constructs a 4d vector with X, Y, Z and W set to the same value. - - The x, y, z and w coordinates in 4d-space. - - - - Performs vector addition on and . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Performs vector addition on and - , storing the result of the - addition in . - - The first vector to add. - The second vector to add. - The result of the vector addition. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 4d-triangle. - - The first vector of 4d-triangle. - The second vector of 4d-triangle. - The third vector of 4d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 4d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 4d-triangle. - The cartesian translation of barycentric coordinates. - - - - Creates a new that contains the cartesian coordinates of a vector specified in barycentric coordinates and relative to 4d-triangle. - - The first vector of 4d-triangle. - The second vector of 4d-triangle. - The third vector of 4d-triangle. - Barycentric scalar b2 which represents a weighting factor towards second vector of 4d-triangle. - Barycentric scalar b3 which represents a weighting factor towards third vector of 4d-triangle. - The cartesian translation of barycentric coordinates as an output parameter. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation. - - - - Creates a new that contains CatmullRom interpolation of the specified vectors. - - The first vector in interpolation. - The second vector in interpolation. - The third vector in interpolation. - The fourth vector in interpolation. - Weighting factor. - The result of CatmullRom interpolation as an output parameter. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value. - - - - Clamps the specified value within a range. - - The value to clamp. - The min value. - The max value. - The clamped value as an output parameter. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors. - - - - Returns the distance between two vectors. - - The first vector. - The second vector. - The distance between two vectors as an output parameter. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors. - - - - Returns the squared distance between two vectors. - - The first vector. - The second vector. - The squared distance between two vectors as an output parameter. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar. - - - - Divides the components of a by a scalar. - - Source . - Divisor scalar. - The result of dividing a vector by a scalar as an output parameter. - - - - Divides the components of a by the components of another . - - Source . - Divisor . - The result of dividing the vectors as an output parameter. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors. - - - - Returns a dot product of two vectors. - - The first vector. - The second vector. - The dot product of two vectors as an output parameter. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Compares whether current instance is equal to specified . - - The to compare. - true if the instances are equal; false otherwise. - - - - Gets the hash code of this . - - Hash code of this . - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector. - - - - Creates a new that contains hermite spline interpolation. - - The first position vector. - The first tangent vector. - The second position vector. - The second tangent vector. - Weighting factor. - The hermite spline interpolation vector as an output parameter. - - - - Returns the length of this . - - The length of this . - - - - Returns the squared length of this . - - The squared length of this . - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors. - - - - Creates a new that contains linear interpolation of the specified vectors. - Uses on MathHelper for the interpolation. - Less efficient but more precise compared to . - See remarks section of on MathHelper for more info. - - The first vector. - The second vector. - Weighting value(between 0.0 and 1.0). - The result of linear interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors. - - - - Creates a new that contains a maximal values from the two vectors. - - The first vector. - The second vector. - The with maximal values from the two vectors as an output parameter. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors. - - - - Creates a new that contains a minimal values from the two vectors. - - The first vector. - The second vector. - The with minimal values from the two vectors as an output parameter. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the vector multiplication with a scalar. - - - - Creates a new that contains a multiplication of and a scalar. - - Source . - Scalar value. - The result of the multiplication with a scalar as an output parameter. - - - - Creates a new that contains a multiplication of two vectors. - - Source . - Source . - The result of the vector multiplication as an output parameter. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion. - - - - Creates a new that contains the specified vector inversion. - - Source . - The result of the vector inversion as an output parameter. - - - - Turns this to a unit vector with the same direction. - - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector. - - - - Creates a new that contains a normalized values from another vector. - - Source . - Unit vector as an output parameter. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors. - - - - Creates a new that contains cubic interpolation of the specified vectors. - - Source . - Source . - Weighting value. - Cubic interpolation of the specified vectors as an output parameter. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction. - - - - Creates a new that contains subtraction of on from a another. - - Source . - Source . - The result of the vector subtraction as an output parameter. - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The transformation . - Transformed . - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed . - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The transformation . - Transformed . - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed . - - - - Creates a new that contains a transformation of 4d-vector by the specified . - - Source . - The transformation . - Transformed . - - - - Creates a new that contains a transformation of 4d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed . - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The transformation . - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 2d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The transformation . - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 3d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 4d-vector by the specified . - - Source . - The transformation . - Transformed as an output parameter. - - - - Creates a new that contains a transformation of 4d-vector by the specified . - - Source . - The which contains rotation transformation. - Transformed as an output parameter. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The transformation . - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on vectors within array of by the specified and places the results in an another array. - - Source array. - The starting index of transformation in the source array. - The which contains rotation transformation. - Destination array. - The starting index in the destination array, where the first should be written. - The number of vectors to be transformed. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The transformation . - Destination array. - - - - Apply transformation on all vectors within array of by the specified and places the results in an another array. - - Source array. - The which contains rotation transformation. - Destination array. - - - - Returns a representation of this in the format: - {X:[] Y:[] Z:[] W:[]} - - A representation of this . - - - - Inverts values in the specified . - - Source on the right of the sub sign. - Result of the inversion. - - - - Compares whether two instances are equal. - - instance on the left of the equal sign. - instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance on the left of the not equal sign. - instance on the right of the not equal sign. - true if the instances are not equal; false otherwise. - - - - Adds two vectors. - - Source on the left of the add sign. - Source on the right of the add sign. - Sum of the vectors. - - - - Subtracts a from a . - - Source on the left of the sub sign. - Source on the right of the sub sign. - Result of the vector subtraction. - - - - Multiplies the components of two vectors by each other. - - Source on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication. - - - - Multiplies the components of vector by a scalar. - - Source on the left of the mul sign. - Scalar value on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Multiplies the components of vector by a scalar. - - Scalar value on the left of the mul sign. - Source on the right of the mul sign. - Result of the vector multiplication with a scalar. - - - - Divides the components of a by the components of another . - - Source on the left of the div sign. - Divisor on the right of the div sign. - The result of dividing the vectors. - - - - Divides the components of a by a scalar. - - Source on the left of the div sign. - Divisor scalar on the right of the div sign. - The result of dividing a vector by a scalar. - - - - Represents how many channels are used in the audio data. - - - - Single channel. - - - Two channels. - - - - Represents a 3D audio emitter. Used to simulate 3D audio effects. - - - - Initializes a new AudioEmitter instance. - - - Gets or sets a scale applied to the Doppler effect between the AudioEmitter and an AudioListener. - - Defaults to 1.0 - A value of 1.0 leaves the Doppler effect unmodified. - - - - Gets or sets the emitter's forward vector. - - Defaults to Vector3.Forward. (new Vector3(0, 0, -1)) - Used with AudioListener.Velocity to calculate Doppler values. - The Forward and Up values must be orthonormal. - - - - Gets or sets the position of this emitter. - - - Gets or sets the emitter's Up vector. - - Defaults to Vector3.Up. (new Vector3(0, -1, 1)). - The Up and Forward vectors must be orthonormal. - - - - Gets or sets the emitter's velocity vector. - - Defaults to Vector3.Zero. - This value is only used when calculating Doppler values. - - - - - Represents a 3D audio listener. Used when simulating 3D Audio. - - - - Gets or sets the listener's forward vector. - - Defaults to Vector3.Forward. (new Vector3(0, 0, -1)) - Used with AudioListener.Velocity and AudioEmitter.Velocity to calculate Doppler values. - The Forward and Up vectors must be orthonormal. - - - - Gets or sets the listener's position. - - Defaults to Vector3.Zero. - - - - - Gets or sets the listener's up vector.. - - - Defaults to Vector3.Up (New Vector3(0, -1, 0)). - Used with AudioListener.Velocity and AudioEmitter.Velocity to calculate Doppler values. - The values of the Forward and Up vectors must be orthonormal. - - - - Gets or sets the listener's velocity vector. - - Defaults to Vector3.Zero. - Scaled by DopplerScale to calculate the Doppler effect value applied to a Cue. - This value is only used to calculate Doppler values. - - - - - A for which the audio buffer is provided by the game at run time. - - - - - This value has no effect on DynamicSoundEffectInstance. - It may not be set. - - - - - Returns the number of audio buffers queued for playback. - - - - - The event that occurs when the number of queued audio buffers is less than or equal to 2. - - - This event may occur when is called or during playback when a buffer is completed. - - - - Sample rate, in Hertz (Hz). - Number of channels (mono or stereo). - - - - Returns the duration of an audio buffer of the specified size, based on the settings of this instance. - - Size of the buffer, in bytes. - The playback length of the buffer. - - - - Returns the size, in bytes, of a buffer of the specified duration, based on the settings of this instance. - - The playback length of the buffer. - The data size of the buffer, in bytes. - - - - Plays or resumes the DynamicSoundEffectInstance. - - - - - Pauses playback of the DynamicSoundEffectInstance. - - - - - Resumes playback of the DynamicSoundEffectInstance. - - - - - Immediately stops playing the DynamicSoundEffectInstance. - - - Calling this also releases all queued buffers. - - - - - Stops playing the DynamicSoundEffectInstance. - If the parameter is false, this call has no effect. - - - Calling this also releases all queued buffers. - - When set to false, this call has no effect. - - - - Queues an audio buffer for playback. - - - The buffer length must conform to alignment requirements for the audio format. - - The buffer containing PCM audio data. - - - - Queues an audio buffer for playback. - - - The buffer length must conform to alignment requirements for the audio format. - - The buffer containing PCM audio data. - The starting position of audio data. - The amount of bytes to use. - - - - Handles the buffer events of all DynamicSoundEffectInstance instances. - - - - - Updates buffer queues of the currently playing instances. - - - XNA posts events always on the main thread. - - - - - The exception thrown when the system attempts to play more SoundEffectInstances than allotted. - - - Most platforms have a hard limit on how many sounds can be played simultaneously. This exception is thrown when that limit is exceeded. - - - - - - * A bunch of magical numbers that predict the sample data from the - * MSADPCM wavedata. Do not attempt to understand at all costs! - - - - - * Splits the MSADPCM samples from each byte block. - * @param block An MSADPCM sample byte - * @param nibbleBlock we copy the parsed shorts into here - - - - - * Calculates PCM samples based on previous samples and a nibble input. - * @param nibble A parsed MSADPCM sample we got from getNibbleBlock - * @param predictor The predictor we get from the MSADPCM block's preamble - * @param sample_1 The first sample we use to predict the next sample - * @param sample_2 The second sample we use to predict the next sample - * @param delta Used to calculate the final sample - * @return The calculated PCM sample - - - - - * Decodes MSADPCM data to signed 16-bit PCM data. - * @param Source A BinaryReader containing the headerless MSADPCM data - * @param numChannels The number of channels (WAVEFORMATEX nChannels) - * @param blockAlign The ADPCM block size (WAVEFORMATEX nBlockAlign) - * @return A byte array containing the raw 16-bit PCM wavedata - * - * NOTE: The original MSADPCMToPCM class returns as a short[] array! - - - - The exception thrown when no audio hardware is present, or driver issues are detected. - - - - A message describing the error. - - - A message describing the error. - The exception that is the underlying cause of the current exception. If not null, the current exception is raised in a try/catch block that handled the innerException. - - - - Sets up the hardware resources used by the controller. - - - - - Open the sound device, sets up an audio context, and makes the new context - the current context. Note that this method will stop the playback of - music that was running prior to the game start. If any error occurs, then - the state of the controller is reset. - - True if the sound controller was setup, and false if not. - - - - Checks the error state of the OpenAL driver. If a value that is not AlcError.NoError - is returned, then the operation message and the error code is output to the console. - - the operation message - true if an error occurs, and false if not. - - - - Destroys the AL context and closes the device, when they exist. - - - - - Dispose of the OpenALSoundCOntroller. - - - - - Dispose of the OpenALSoundCOntroller. - - If true, the managed resources are to be disposed. - - - - Reserves the given sound buffer. If there are no available sources then false is - returned, otherwise true will be returned and the sound buffer can be played. If - the controller was not able to setup the hardware, then false will be returned. - - The sound buffer you want to play - True if the buffer can be played, and false if not. - - - - Checks if the AL controller was initialized properly. If there was an - exception thrown during the OpenAL init, then that exception is thrown - inside of NoAudioHardwareException. - - True if the controller was initialized, false if not. - - - - - * Returns a byte buffer containing all the pcm data. - - - Represents a loaded sound resource. - - A SoundEffect represents the buffer used to hold audio data and metadata. SoundEffectInstances are used to play from SoundEffects. Multiple SoundEffectInstance objects can be created and played from the same SoundEffect object. - The only limit on the number of loaded SoundEffects is restricted by available memory. When a SoundEffect is disposed, all SoundEffectInstances created from it will become invalid. - SoundEffect.Play() can be used for 'fire and forget' sounds. If advanced playback controls like volume or pitch is required, use SoundEffect.CreateInstance(). - - - - - Create a sound effect. - - The buffer with the sound data. - The sound data sample rate in hertz. - The number of channels in the sound data. - This only supports uncompressed 16bit PCM wav data. - - - - Create a sound effect. - - The buffer with the sound data. - The offset to the start of the sound data in bytes. - The length of the sound data in bytes. - The sound data sample rate in hertz. - The number of channels in the sound data. - The position where the sound should begin looping in samples. - The duration of the sound data loop in samples. - This only supports uncompressed 16bit PCM wav data. - - - - Releases unmanaged resources and performs other cleanup operations before the - is reclaimed by garbage collection. - - - - - Creates a new SoundEffectInstance for this SoundEffect. - - A new SoundEffectInstance for this SoundEffect. - Creating a SoundEffectInstance before calling SoundEffectInstance.Play() allows you to access advanced playback features, such as volume, pitch, and 3D positioning. - - - - Creates a new SoundEffect object based on the specified data stream. - - A stream containing the PCM wave data. - A new SoundEffect object. - The stream must point to the head of a valid PCM wave file in the RIFF bitstream format. - - - - Returns the duration for 16bit PCM audio. - - The length of the audio data in bytes. - Sample rate, in Hertz (Hz). Must be between 8000 Hz and 48000 Hz - Number of channels in the audio data. - The duration of the audio data. - - - - Returns the data size in bytes for 16bit PCM audio. - - The total duration of the audio data. - Sample rate, in Hertz (Hz), of audio data. Must be between 8,000 and 48,000 Hz. - Number of channels in the audio data. - The size in bytes of a single sample of audio data. - - - Gets an internal SoundEffectInstance and plays it. - True if a SoundEffectInstance was successfully played, false if not. - - Play returns false if more SoundEffectInstances are currently playing then the platform allows. - To loop a sound or apply 3D effects, call SoundEffect.CreateInstance() and SoundEffectInstance.Play() instead. - SoundEffectInstances used by SoundEffect.Play() are pooled internally. - - - - Gets an internal SoundEffectInstance and plays it with the specified volume, pitch, and panning. - True if a SoundEffectInstance was successfully created and played, false if not. - Volume, ranging from 0.0 (silence) to 1.0 (full volume). Volume during playback is scaled by SoundEffect.MasterVolume. - Pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave). - Panning, ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker). - - Play returns false if more SoundEffectInstances are currently playing then the platform allows. - To apply looping or simulate 3D audio, call SoundEffect.CreateInstance() and SoundEffectInstance.Play() instead. - SoundEffectInstances used by SoundEffect.Play() are pooled internally. - - - - - Returns a sound effect instance from the pool or null if none are available. - - - - Gets the duration of the SoundEffect. - - - Gets or sets the asset name of the SoundEffect. - - - - Gets or sets the master volume scale applied to all SoundEffectInstances. - - - Each SoundEffectInstance has its own Volume property that is independent to SoundEffect.MasterVolume. During playback SoundEffectInstance.Volume is multiplied by SoundEffect.MasterVolume. - This property is used to adjust the volume on all current and newly created SoundEffectInstances. The volume of an individual SoundEffectInstance can be adjusted on its own. - - - - - Gets or sets the scale of distance calculations. - - - DistanceScale defaults to 1.0 and must be greater than 0.0. - Higher values reduce the rate of falloff between the sound and listener. - - - - - Gets or sets the scale of Doppler calculations applied to sounds. - - - DopplerScale defaults to 1.0 and must be greater or equal to 0.0 - Affects the relative velocity of emitters and listeners. - Higher values more dramatically shift the pitch for the given relative velocity of the emitter and listener. - - - - Returns the speed of sound used when calculating the Doppler effect.. - - Defaults to 343.5. Value is measured in meters per second. - Has no effect on distance attenuation. - - - - Indicates whether the object is disposed. - - - Releases the resources held by this . - - - - Releases the resources held by this . - - If set to true, Dispose was called explicitly. - If the disposing parameter is true, the Dispose method was called explicitly. This - means that managed objects referenced by this instance should be disposed or released as - required. If the disposing parameter is false, Dispose was called by the finalizer and - no managed objects should be touched because we do not know if they are still valid or - not at that time. Unmanaged resources should always be released. - - - Represents a single instance of a playing, paused, or stopped sound. - - SoundEffectInstances are created through SoundEffect.CreateInstance() and used internally by SoundEffect.Play() - - - - Enables or Disables whether the SoundEffectInstance should repeat after playback. - This value has no effect on an already playing sound. - - - Gets or sets the pan, or speaker balance.. - Pan value ranging from -1.0 (left speaker) to 0.0 (centered), 1.0 (right speaker). Values outside of this range will throw an exception. - - - Gets or sets the pitch adjustment. - Pitch adjustment, ranging from -1.0 (down an octave) to 0.0 (no change) to 1.0 (up an octave). Values outside of this range will throw an Exception. - - - Gets or sets the volume of the SoundEffectInstance. - Volume, ranging from 0.0 (silence) to 1.0 (full volume). Volume during playback is scaled by SoundEffect.MasterVolume. - - This is the volume relative to SoundEffect.MasterVolume. Before playback, this Volume property is multiplied by SoundEffect.MasterVolume when determining the final mix volume. - - - - Gets the SoundEffectInstance's current playback state. - - - Indicates whether the object is disposed. - - - - Releases unmanaged resources and performs other cleanup operations before the - is reclaimed by garbage collection. - - - - Applies 3D positioning to the SoundEffectInstance using a single listener. - Data about the listener. - Data about the source of emission. - - - Applies 3D positioning to the SoundEffectInstance using multiple listeners. - Data about each listener. - Data about the source of emission. - - - Pauses playback of a SoundEffectInstance. - Paused instances can be resumed with SoundEffectInstance.Play() or SoundEffectInstance.Resume(). - - - Plays or resumes a SoundEffectInstance. - Throws an exception if more sounds are playing than the platform allows. - - - Resumes playback for a SoundEffectInstance. - Only has effect on a SoundEffectInstance in a paused state. - - - Immediately stops playing a SoundEffectInstance. - - - Stops playing a SoundEffectInstance, either immediately or as authored. - Determined whether the sound stops immediately, or after playing its release phase and/or transitions. - Stopping a sound with the immediate argument set to false will allow it to play any release phases, such as fade, before coming to a stop. - - - Releases the resources held by this . - - - - Releases the resources held by this . - - If set to true, Dispose was called explicitly. - If the disposing parameter is true, the Dispose method was called explicitly. This - means that managed objects referenced by this instance should be disposed or released as - required. If the disposing parameter is false, Dispose was called by the finalizer and - no managed objects should be touched because we do not know if they are still valid or - not at that time. Unmanaged resources should always be released. - - - - Creates a standalone SoundEffectInstance from given wavedata. - - - - - Gets the OpenAL sound controller, constructs the sound buffer, and sets up the event delegates for - the reserved and recycled events. - - - - - Converts the XNA [-1, 1] pitch range to OpenAL pitch (0, INF) or Android SoundPool playback rate [0.5, 2]. - The pitch of the sound in the Microsoft XNA range. - - - - - Gets a value indicating whether the platform has capacity for more sounds to be played at this time. - - true if more sounds can be played; otherwise, false. - - - - Add the specified instance to the pool if it is a pooled instance and removes it from the - list of playing instances. - - The SoundEffectInstance - - - - Adds the SoundEffectInstance to the list of playing instances. - - The SoundEffectInstance to add to the playing list. - - - - Returns a pooled SoundEffectInstance if one is available, or allocates a new - SoundEffectInstance if the pool is empty. - - The SoundEffectInstance. - - - - Iterates the list of playing instances, returning them to the pool if they - have stopped playing. - - - - - Iterates the list of playing instances, stop them and return them to the pool if they are instances of the given SoundEffect. - - The SoundEffect - - - Described the playback state of a SoundEffectInstance. - - - The SoundEffectInstance is currently playing. - - - The SoundEffectInstance is currently paused. - - - The SoundEffectInstance is currently stopped. - - - - Provides functionality for manipulating multiple sounds at a time. - - - - - Gets the category's friendly name. - - - - - Pauses all associated sounds. - - - - - Resumes all associated paused sounds. - - - - - Stops all associated sounds. - - - - - Determines whether two AudioCategory instances are equal. - - First AudioCategory instance to compare. - Second AudioCategory instance to compare. - true if the objects are equal or false if they aren't. - - - - Determines whether two AudioCategory instances are not equal. - - First AudioCategory instance to compare. - Second AudioCategory instance to compare. - true if the objects are not equal or false if they are. - - - - Determines whether two AudioCategory instances are equal. - - AudioCategory to compare with this instance. - true if the objects are equal or false if they aren't - - - - Determines whether two AudioCategory instances are equal. - - Object to compare with this instance. - true if the objects are equal or false if they aren't. - - - - Gets the hash code for this instance. - - Hash code for this object. - - - - Returns the name of this AudioCategory - - Friendly name of the AudioCategory - - - - Class used to create and manipulate code audio objects. - - - - - The current content version. - - - - Path to a XACT settings file. - - - Path to a XACT settings file. - Determines how many milliseconds the engine will look ahead when determing when to transition to another sound. - A string that specifies the audio renderer to use. - For the best results, use a lookAheadTime of 250 milliseconds or greater. - - - - Performs periodic work required by the audio engine. - - Must be called at least once per frame. - - - Returns an audio category by name. - Friendly name of the category to get. - The AudioCategory with a matching name. Throws an exception if not found. - - - Gets the value of a global variable. - Friendly name of the variable. - float value of the queried variable. - A global variable has global scope. It can be accessed by all code within a project. - - - Sets the value of a global variable. - Friendly name of the variable. - Value of the global variable. - - - - This event is triggered when the AudioEngine is disposed. - - - - - Is true if the AudioEngine has been disposed. - - - - - Disposes the AudioEngine. - - - - Controls how Cue objects should cease playback when told to stop. - - - Stop normally, playing any pending release phases or transitions. - - - Immediately stops the cue, ignoring any pending release phases or transitions. - - - Manages the playback of a sound or set of sounds. - - Cues are comprised of one or more sounds. - Cues also define specific properties such as pitch or volume. - Cues are referenced through SoundBank objects. - - - - Indicates whether or not the cue is currently paused. - IsPlaying and IsPaused both return true if a cue is paused while playing. - - - Indicates whether or not the cue is currently playing. - IsPlaying and IsPaused both return true if a cue is paused while playing. - - - Indicates whether or not the cue is currently stopped. - - - Gets the friendly name of the cue. - The friendly name is a value set from the designer. - - - Pauses playback. - - - Requests playback of a prepared or preparing Cue. - Calling Play when the Cue already is playing can result in an InvalidOperationException. - - - Resumes playback of a paused Cue. - - - Stops playback of a Cue. - Specifies if the sound should play any pending release phases or transitions before stopping. - - - - Sets the value of a cue-instance variable based on its friendly name. - - Friendly name of the variable to set. - Value to assign to the variable. - The friendly name is a value set from the designer. - - - Gets a cue-instance variable value based on its friendly name. - Friendly name of the variable. - Value of the variable. - - Cue-instance variables are useful when multiple instantiations of a single cue (and its associated sounds) are required (for example, a "car" cue where there may be more than one car at any given time). While a global variable allows multiple audio elements to be controlled in unison, a cue instance variable grants discrete control of each instance of a cue, even for each copy of the same cue. - The friendly name is a value set from the designer. - - - - Updates the simulated 3D Audio settings calculated between an AudioEmitter and AudioListener. - The listener to calculate. - The emitter to calculate. - - This must be called before Play(). - Calling this method automatically converts the sound to monoaural and sets the speaker mix for any sound played by this cue to a value calculated with the listener's and emitter's positions. Any stereo information in the sound will be discarded. - - - - - This event is triggered when the Cue is disposed. - - - - - Is true if the Cue has been disposed. - - - - - Disposes the Cue. - - - - Represents a collection of Cues. - - - - Is true if the SoundBank has any live Cues in use. - - - - AudioEngine that will be associated with this sound bank. - Path to a .xsb SoundBank file. - - - - Returns a pooled Cue object. - - Friendly name of the cue to get. - a unique Cue object from a pool. - - Cue instances are unique, even when sharing the same name. This allows multiple instances to simultaneously play. - - - - - Plays a cue. - - Name of the cue to play. - - - - Plays a cue with static 3D positional information. - - - Commonly used for short lived effects. To dynamically change the 3D - positional information on a cue over time use and . - The name of the cue to play. - The listener state. - The cue emitter state. - - - - This event is triggered when the SoundBank is disposed. - - - - - Is true if the SoundBank has been disposed. - - - - - Disposes the SoundBank. - - - - Represents a collection of wave files. - - - - - - - - - - - Instance of the AudioEngine to associate this wave bank with. - Path to the .xwb file to load. - This constructor immediately loads all wave data into memory at once. - - - Instance of the AudioEngine to associate this wave bank with. - Path to the .xwb to stream from. - DVD sector-aligned offset within the wave bank data file. - Stream packet size, in sectors, to use for each stream. The minimum value is 2. - - This constructor streams wave data as needed. - Note that packetsize is in sectors, which is 2048 bytes. - AudioEngine.Update() must be called at least once before using data from a streaming wave bank. - - - - - This event is triggered when the WaveBank is disposed. - - - - - Is true if the WaveBank has been disposed. - - - - - Disposes the WaveBank. - - - - - Set the combined volume scale from the parent objects. - - The volume scale. - - - - Set the volume for the clip. - - The volume level. - - - - Virtual property to allow a derived ContentManager to have it's assets reloaded - - - - - External reference reader, provided for compatibility with XNA Framework built content - - - - - Creates an instance of the attribute. - - - - - Returns the overriden XML element name or the default "Item". - - - - - Returns true if the default CollectionItemName value was overridden. - - - - - This is used to specify the XML element name to use for each item in a collection. - - - - - Creates an instance of the attribute. - - The XML element name to use for each item in the collection. - - - - The XML element name to use for each item in the collection. - - - - - This is used to specify the type to use when deserializing this object at runtime. - - - - - Creates an instance of the attribute. - - The name of the type to use at runtime. - - - - The name of the type to use at runtime. - - - - - This is used to specify the version when deserializing this object at runtime. - - - - - Creates an instance of the attribute. - - The version passed to the type at runtime. - - - - The version passed to the type at runtime. - - - - - Removes Version, Culture and PublicKeyToken from a type string. - - - Supports multiple generic types (e.g. Dictionary<TKey,TValue>) and nested generic types (e.g. List<List<int>>). - - - A - - - A - - - - - Adds the type creator. - - - Type string. - - - Create function. - - - - - Defines the buffers for clearing when calling operation. - - - - - Color buffer. - - - - - Depth buffer. - - - - - Stencil buffer. - - - - - Defines the color channels for render target blending operations. - - - - - No channels selected. - - - - - Red channel selected. - - - - - Green channel selected. - - - - - Blue channel selected. - - - - - Alpha channel selected. - - - - - All channels selected. - - - - - Defines the faces in a cube map for the class. - - - - - Positive X face in the cube map. - - - - - Negative X face in the cube map. - - - - - Positive Y face in the cube map. - - - - - Negative Y face in the cube map. - - - - - Positive Z face in the cube map. - - - - - Negative Z face in the cube map. - - - - - The settings used in creation of the graphics device. - See . - - - - - The graphics adapter on which the graphics device will be created. - - - This is only valid on desktop systems where multiple graphics - adapters are possible. Defaults to . - - - - - The requested graphics device feature set. - - - - - The settings that define how graphics will be presented to the display. - - - - - Gets or sets the boolean which defines how window switches from windowed to fullscreen state. - "Hard" mode(true) is slow to switch, but more effecient for performance, while "soft" mode(false) is vice versa. - The default value is true. - - - - - This method is used by MonoGame Android to adjust the game's drawn to area to fill - as much of the screen as possible whilst retaining the aspect ratio inferred from - aspectRatio = (PreferredBackBufferWidth / PreferredBackBufferHeight) - - NOTE: this is a hack that should be removed if proper back buffer to screen scaling - is implemented. To disable it's effect, in the game's constructor use: - - graphics.IsFullScreen = true; - graphics.PreferredBackBufferHeight = Window.ClientBounds.Height; - graphics.PreferredBackBufferWidth = Window.ClientBounds.Width; - - - - - - A snapshot of rendering statistics from to be used for runtime debugging and profiling. - - - - - Number of times Clear was called. - - - - - Number of times Draw was called. - - - - - Number of times the pixel shader was changed on the GPU. - - - - - Number of rendered primitives. - - - - - Number of sprites and text characters rendered via . - - - - - Number of times a target was changed on the GPU. - - - - - Number of times a texture was changed on the GPU. - - - - - Number of times the vertex shader was changed on the GPU. - - - - - Returns the difference between two sets of metrics. - - Source on the left of the sub sign. - Source on the right of the sub sign. - Difference between two sets of metrics. - - - - Returns the combination of two sets of metrics. - - Source on the left of the add sign. - Source on the right of the add sign. - Combination of two sets of metrics. - - - - Built-in effect that supports alpha testing. - - - - - Gets or sets the world matrix. - - - - - Gets or sets the view matrix. - - - - - Gets or sets the projection matrix. - - - - - Gets or sets the material diffuse color (range 0 to 1). - - - - - Gets or sets the material alpha. - - - - - Gets or sets the fog enable flag. - - - - - Gets or sets the fog start distance. - - - - - Gets or sets the fog end distance. - - - - - Gets or sets the fog color. - - - - - Gets or sets the current texture. - - - - - Gets or sets whether vertex color is enabled. - - - - - Gets or sets the alpha compare function (default Greater). - - - - - Gets or sets the reference alpha value (default 0). - - - - - Creates a new AlphaTestEffect with default parameter settings. - - - - - Creates a new AlphaTestEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current AlphaTestEffect instance. - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - Built-in effect that supports optional texturing, vertex coloring, fog, and lighting. - - - - - Gets or sets the world matrix. - - - - - Gets or sets the view matrix. - - - - - Gets or sets the projection matrix. - - - - - Gets or sets the material diffuse color (range 0 to 1). - - - - - Gets or sets the material emissive color (range 0 to 1). - - - - - Gets or sets the material specular color (range 0 to 1). - - - - - Gets or sets the material specular power. - - - - - Gets or sets the material alpha. - - - - - - - - Gets or sets the per-pixel lighting prefer flag. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Gets or sets whether texturing is enabled. - - - - - Gets or sets the current texture. - - - - - Gets or sets whether vertex color is enabled. - - - - - Creates a new BasicEffect with default parameter settings. - - - - - Creates a new BasicEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current BasicEffect instance. - - - - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - Built-in effect that supports two-layer multitexturing. - - - - - Gets or sets the world matrix. - - - - - Gets or sets the view matrix. - - - - - Gets or sets the projection matrix. - - - - - Gets or sets the material diffuse color (range 0 to 1). - - - - - Gets or sets the material alpha. - - - - - Gets or sets the fog enable flag. - - - - - Gets or sets the fog start distance. - - - - - Gets or sets the fog end distance. - - - - - Gets or sets the fog color. - - - - - Gets or sets the current base texture. - - - - - Gets or sets the current overlay texture. - - - - - Gets or sets whether vertex color is enabled. - - - - - Creates a new DualTextureEffect with default parameter settings. - - - - - Creates a new DualTextureEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current DualTextureEffect instance. - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - The MonoGame Effect file format header identifier ("MGFX"). - - - - - The current MonoGame Effect file format versions - used to detect old packaged content. - - - We should avoid supporting old versions for very long if at all - as users should be rebuilding content when packaging their game. - - - - - Clone the source into this existing object. - - - Note this is not overloaded in derived classes on purpose. This is - only a reason this exists is for caching effects. - - The source effect to clone from. - - - - Returns a deep copy of the effect where immutable types - are shared and mutable data is duplicated. - - - See "Cloning an Effect" in MSDN: - http://msdn.microsoft.com/en-us/library/windows/desktop/ff476138(v=vs.85).aspx - - The cloned effect. - - - - Track which effect parameters need to be recomputed during the next OnApply. - - - - - Helper code shared between the various built-in effects. - - - - - Sets up the standard key/fill/back lighting rig. - - - - - Lazily recomputes the world+view+projection matrix and - fog vector based on the current effect parameter settings. - - - - - Sets a vector which can be dotted with the object space vertex position to compute fog amount. - - - - - Lazily recomputes the world inverse transpose matrix and - eye position based on the current effect parameter settings. - - - - - Sets the diffuse/emissive/alpha material color parameters. - - - - - Defines classes for effect parameters and shader constants. - - - - - Scalar class type. - - - - - Vector class type. - - - - - Matrix class type. - - - - - Class type for textures, shaders or strings. - - - - - Structure class type. - - - - - The next state key used when an effect parameter - is updated by any of the 'set' methods. - - - - - The current state key which is used to detect - if the parameter value has been changed. - - - - - Property referenced by the DebuggerDisplayAttribute. - - - - - Defines types for effect parameters and shader constants. - - - - - Pointer to void type. - - - - - Boolean type. Any non-zero will be true; false otherwise. - - - - - 32-bit integer type. - - - - - Float type. - - - - - String type. - - - - - Any texture type. - - - - - 1D-texture type. - - - - - 2D-texture type. - - - - - 3D-texture type. - - - - - Cubic texture type. - - - - - Internal helper for accessing the bytecode for stock effects. - - - - - Built-in effect that supports environment mapping. - - - - - Gets or sets the world matrix. - - - - - Gets or sets the view matrix. - - - - - Gets or sets the projection matrix. - - - - - Gets or sets the material diffuse color (range 0 to 1). - - - - - Gets or sets the material emissive color (range 0 to 1). - - - - - Gets or sets the material alpha. - - - - - Gets or sets the ambient light color (range 0 to 1). - - - - - Gets the first directional light. - - - - - Gets the second directional light. - - - - - Gets the third directional light. - - - - - Gets or sets the fog enable flag. - - - - - Gets or sets the fog start distance. - - - - - Gets or sets the fog end distance. - - - - - Gets or sets the fog color. - - - - - Gets or sets the current texture. - - - - - Gets or sets the current environment map texture. - - - - - Gets or sets the amount of the environment map RGB that will be blended over - the base texture. Range 0 to 1, default 1. If set to zero, the RGB channels - of the environment map will completely ignored (but the environment map alpha - may still be visible if EnvironmentMapSpecular is greater than zero). - - - - - Gets or sets the amount of the environment map alpha channel that will - be added to the base texture. Range 0 to 1, default 0. This can be used - to implement cheap specular lighting, by encoding one or more specular - highlight patterns into the environment map alpha channel, then setting - EnvironmentMapSpecular to the desired specular light color. - - - - - Gets or sets the Fresnel factor used for the environment map blending. - Higher values make the environment map only visible around the silhouette - edges of the object, while lower values make it visible everywhere. - Setting this property to 0 disables Fresnel entirely, making the - environment map equally visible regardless of view angle. The default is - 1. Fresnel only affects the environment map RGB (the intensity of which is - controlled by EnvironmentMapAmount). The alpha contribution (controlled by - EnvironmentMapSpecular) is not affected by the Fresnel setting. - - - - - This effect requires lighting, so we explicitly implement - IEffectLights.LightingEnabled, and do not allow turning it off. - - - - - Creates a new EnvironmentMapEffect with default parameter settings. - - - - - Creates a new EnvironmentMapEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current EnvironmentMapEffect instance. - - - - - Sets up the standard key/fill/back lighting rig. - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - The common effect fog rendering parameters. - - - - - The floating point fog color. - - - - - Used to toggle the rendering of fog. - - - - - The world space distance from the camera at which fogging is fully applied. - - - FogEnd should be greater than FogStart. If FogEnd and FogStart - are the same value everything is fully fogged. - - - - - The world space distance from the camera at which fogging begins. - - - FogStart should be less than FogEnd. If FogEnd and FogStart are the - same value everything is fully fogged. - - - - - The common effect light rendering parameters. - - - - - The floating point ambient light color. - - - - - Returns the first directional light. - - - - - Returns the second directional light. - - - - - Returns the third directional light. - - - - - Toggles the rendering of lighting. - - - - - Initializes the lights to the standard key/fill/back lighting rig. - - - - - Built-in effect for rendering skinned character models. - - - - - Gets or sets the world matrix. - - - - - Gets or sets the view matrix. - - - - - Gets or sets the projection matrix. - - - - - Gets or sets the material diffuse color (range 0 to 1). - - - - - Gets or sets the material emissive color (range 0 to 1). - - - - - Gets or sets the material specular color (range 0 to 1). - - - - - Gets or sets the material specular power. - - - - - Gets or sets the material alpha. - - - - - Gets or sets the per-pixel lighting prefer flag. - - - - - Gets or sets the ambient light color (range 0 to 1). - - - - - Gets the first directional light. - - - - - Gets the second directional light. - - - - - Gets the third directional light. - - - - - Gets or sets the fog enable flag. - - - - - Gets or sets the fog start distance. - - - - - Gets or sets the fog end distance. - - - - - Gets or sets the fog color. - - - - - Gets or sets the current texture. - - - - - Gets or sets the number of skinning weights to evaluate for each vertex (1, 2, or 4). - - - - - Sets an array of skinning bone transform matrices. - - - - - Gets a copy of the current skinning bone transform matrices. - - - - - This effect requires lighting, so we explicitly implement - IEffectLights.LightingEnabled, and do not allow turning it off. - - - - - Creates a new SkinnedEffect with default parameter settings. - - - - - Creates a new SkinnedEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current SkinnedEffect instance. - - - - - Sets up the standard key/fill/back lighting rig. - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - The default effect used by SpriteBatch. - - - - - Creates a new SpriteEffect. - - - - - Creates a new SpriteEffect by cloning parameter settings from an existing instance. - - - - - Creates a clone of the current SpriteEffect instance. - - - - - Looks up shortcut references to our effect parameters. - - - - - Lazily computes derived parameter values immediately before applying the effect. - - - - - Defines the driver type for graphics adapter. Usable only on DirectX platforms for now. - - - - - Hardware device been used for rendering. Maximum speed and performance. - - - - - Emulates the hardware device on CPU. Slowly, only for testing. - - - - - Useful when acceleration does not work. - - - - - Used to request creation of the reference graphics device, - or the default hardware accelerated device (when set to false). - - - This only works on DirectX platforms where a reference graphics - device is available and must be defined before the graphics device - is created. It defaults to false. - - - - - Used to request creation of a specific kind of driver. - - - These values only work on DirectX platforms and must be defined before the graphics device - is created. by default. - - - - - Gets a indicating whether - has a - Width:Height ratio corresponding to a widescreen . - Common widescreen modes include 16:9, 16:10 and 2:1. - - - - - Provides information about the capabilities of the - current graphics device. A very useful thread for investigating GL extenion names - http://stackoverflow.com/questions/3881197/opengl-es-2-0-extensions-on-android-devices - - - - - Whether the device fully supports non power-of-two textures, including - mip maps and wrap modes other than CLAMP_TO_EDGE - - - - - Whether the device supports anisotropic texture filtering - - - - - Gets the support for DXT1 - - - - - Gets the support for S3TC (DXT1, DXT3, DXT5) - - - - - Gets the support for PVRTC - - - - - Gets the support for ETC1 - - - - - Gets the support for ATITC - - - - - True, if GL_ARB_framebuffer_object is supported; false otherwise. - - - - - True, if GL_EXT_framebuffer_object is supported; false otherwise. - - - - - Gets the max texture anisotropy. This value typically lies - between 0 and 16, where 0 means anisotropic filtering is not - supported. - - - - - True, if sRGB is supported. On Direct3D platforms, this is always true. - On OpenGL platforms, it is true if both framebuffer sRGB - and texture sRGB are supported. - - - - - The active vertex shader. - - - - - The active pixel shader. - - - - - The cache of effects from unique byte streams. - - - - - The rendering information for debugging and profiling. - The metrics are reset every frame after draw within . - - - - - Initializes a new instance of the class. - - The graphics adapter. - The graphics profile. - The presentation options. - - is . - - - - - Trigger the DeviceResetting event - Currently internal to allow the various platforms to send the event at the appropriate time. - - - - - Trigger the DeviceReset event to allow games to be notified of a device reset. - Currently internal to allow the various platforms to send the event at the appropriate time. - - - - - Draw geometry by indexing into the vertex buffer. - - The type of primitives in the index buffer. - Used to offset the vertex range indexed from the vertex buffer. - This is unused and remains here only for XNA API compatibility. - This is unused and remains here only for XNA API compatibility. - The index within the index buffer to start drawing from. - The number of primitives to render from the index buffer. - Note that minVertexIndex and numVertices are unused in MonoGame and will be ignored. - - - - Adds a dispose action to the list of pending dispose actions. These are executed at the end of each call to Present(). - This allows GL resources to be disposed from other threads, such as the finalizer. - - The action to execute for the dispose. - - - - Activates the Current Vertex/Pixel shader pair into a program. - - - - - Describes the status of the . - - - - - The device is normal. - - - - - The device has been lost. - - - - - The device has not been reset. - - - - - Defines a set of graphic capabilities. - - - - - Use a limited set of graphic features and capabilities, allowing the game to support the widest variety of devices. - - - - - Use the largest available set of graphic features and capabilities to target devices, that have more enhanced graphic capabilities. - - - - - Called before the device is reset. Allows graphics resources to - invalidate their state so they can be recreated after the device reset. - Warning: This may be called after a call to Dispose() up until - the resource is garbage collected. - - - - - The method that derived classes should override to implement disposing of managed and native resources. - - True if managed objects should be disposed. - Native resources should always be released regardless of the value of the disposing parameter. - - - - Represents a render target. - - - - - Gets the width of the render target in pixels - - The width of the render target in pixels. - - - - Gets the height of the render target in pixels - - The height of the render target in pixels. - - - - Gets the usage mode of the render target. - - The usage mode of the render target. - - - - Represents a set of bones associated with a model. - - - - - Retrieves a ModelBone from the collection, given the name of the bone. - - The name of the bone to retrieve. - - - - Finds a bone with a given name if it exists in the collection. - - The name of the bone to find. - The bone named boneName, if found. - true if the bone was found - - - - Returns a ModelMeshCollection.Enumerator that can iterate through a ModelMeshCollection. - - - - - - Provides the ability to iterate through the bones in an ModelMeshCollection. - - - - - Gets the current element in the ModelMeshCollection. - - - - - Advances the enumerator to the next element of the ModelMeshCollection. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Transform of this node from the root of the model not from the parent - - - - - A basic 3D model with per mesh parent bones. - - - - - A collection of objects which describe how each mesh in the - mesh collection for this model relates to its parent mesh. - - - - - A collection of objects which compose the model. Each - in a model may be moved independently and may be composed of multiple materials - identified as objects. - - - - - Root bone for this model. - - - - - Custom attached object. - - Skinning data is example of attached object for model. - - - - - - Constructs a model. - - A valid reference to . - The collection of bones. - The collection of meshes. - - - - Draws the model meshes. - - The world transform. - The view transform. - The projection transform. - - - - Copies bone transforms relative to all parent bones of the each bone from this model to a given array. - - The array receiving the transformed bones. - - - - Copies bone transforms relative to bone from a given array to this model. - - The array of prepared bone transform data. - - - - Copies bone transforms relative to bone from this model to a given array. - - The array receiving the transformed bones. - - - - Represents a collection of ModelMesh objects. - - - - - Retrieves a ModelMesh from the collection, given the name of the mesh. - - The name of the mesh to retrieve. - - - - Finds a mesh with a given name if it exists in the collection. - - The name of the mesh to find. - The mesh named meshName, if found. - true if a mesh was found - - - - Returns a ModelMeshCollection.Enumerator that can iterate through a ModelMeshCollection. - - - - - - Provides the ability to iterate through the bones in an ModelMeshCollection. - - - - - Gets the current element in the ModelMeshCollection. - - - - - Advances the enumerator to the next element of the ModelMeshCollection. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Gets a value indicating whether the occlusion query has completed. - - - if the occlusion query has completed; otherwise, - . - - - - - Gets the number of visible pixels. - - The number of visible pixels. - - The occlusion query has not yet completed. Check before reading - the result! - - - - - Initializes a new instance of the class. - - The graphics device. - - is . - - - The current graphics profile does not support occlusion queries. - - - - - Begins the occlusion query. - - - is called again before calling . - - - - - Ends the occlusion query. - - - is called before calling . - - - - - Packed vector type containing a single 8 bit normalized W values that is ranging from 0 to 1. - - - - - Gets and sets the packed value. - - - - - Creates a new instance of Alpha8. - - The alpha component - - - - Gets the packed vector in float format. - - The packed vector in Vector3 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Compares an object with the packed vector. - - The object to compare. - True if the object is equal to the packed vector. - - - - Compares another Alpha8 packed vector with the packed vector. - - The Alpha8 packed vector to compare. - True if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing unsigned normalized values ranging from 0 to 1. The x and z components use 5 bits, and the y component uses 6 bits. - - - - - Creates a new instance of Bgr565. - - The x component - The y component - The z component - - - - Creates a new instance of Bgr565. - - Vector containing the components for the packed vector. - - - - Gets and sets the packed value. - - - - - Gets the packed vector in Vector3 format. - - The packed vector in Vector3 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Compares an object with the packed vector. - - The object to compare. - true if the object is equal to the packed vector. - - - - Compares another Bgr565 packed vector with the packed vector. - - The Bgr565 packed vector to compare. - true if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing unsigned normalized values, ranging from 0 to 1, using 4 bits each for x, y, z, and w. - - - - - Creates a new instance of Bgra4444. - - The x component - The y component - The z component - The w component - - - - Creates a new instance of Bgra4444. - - Vector containing the components for the packed vector. - - - - Gets and sets the packed value. - - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Compares an object with the packed vector. - - The object to compare. - true if the object is equal to the packed vector. - - - - Compares another Bgra4444 packed vector with the packed vector. - - The Bgra4444 packed vector to compare. - true if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing unsigned normalized values ranging from 0 to 1. - The x , y and z components use 5 bits, and the w component uses 1 bit. - - - - - Gets and sets the packed value. - - - - - Creates a new instance of Bgra5551. - - The x component - The y component - The z component - The w component - - - - Creates a new instance of Bgra5551. - - - Vector containing the components for the packed vector. - - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Compares an object with the packed vector. - - The object to compare. - True if the object is equal to the packed vector. - - - - Compares another Bgra5551 packed vector with the packed vector. - - The Bgra5551 packed vector to compare. - True if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing four 8-bit unsigned integer values, ranging from 0 to 255. - - - - - Initializes a new instance of the Byte4 class. - - A vector containing the initial values for the components of the Byte4 structure. - - - - Initializes a new instance of the Byte4 class. - - Initial value for the x component. - Initial value for the y component. - Initial value for the z component. - Initial value for the w component. - - - - Compares the current instance of a class to another instance to determine whether they are different. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are different; false otherwise. - - - - Compares the current instance of a class to another instance to determine whether they are the same. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are the same; false otherwise. - - - - Directly gets or sets the packed representation of the value. - - The packed representation of the value. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Gets the hash code for the current instance. - - Hash code for the instance. - - - - Returns a string representation of the current instance. - - String that represents the object. - - - - Packs a vector into a uint. - - The vector containing the values to pack. - The ulong containing the packed values. - - - - Sets the packed representation from a Vector4. - - The vector to create the packed representation from. - - - - Expands the packed representation into a Vector4. - - The expanded vector. - - - - Packed vector type containing four 16-bit floating-point values. - - - - - Initializes a new instance of the HalfVector4 structure. - - Initial value for the x component. - Initial value for the y component. - Initial value for the z component. - Initial value for the q component. - - - - Initializes a new instance of the HalfVector4 structure. - - A vector containing the initial values for the components of the HalfVector4 structure. - - - - Sets the packed representation from a Vector4. - - The vector to create the packed representation from. - - - - Packs a vector into a ulong. - - The vector containing the values to pack. - The ulong containing the packed values. - - - - Expands the packed representation into a Vector4. - - The expanded vector. - - - - Directly gets or sets the packed representation of the value. - - The packed representation of the value. - - - - Returns a string representation of the current instance. - - String that represents the object. - - - - Gets the hash code for the current instance. - - Hash code for the instance. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Compares the current instance of a class to another instance to determine whether they are the same. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are the same; false otherwise. - - - - Compares the current instance of a class to another instance to determine whether they are different. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are different; false otherwise. - - - - Packed vector type containing two 16-bit unsigned normalized values ranging from 0 to 1. - - - - - Gets and sets the packed value. - - - - - Creates a new instance of Rg32. - - The x component - The y component - - - - Creates a new instance of Rg32. - - - Vector containing the components for the packed vector. - - - - - Gets the packed vector in Vector2 format. - - The packed vector in Vector2 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Compares an object with the packed vector. - - The object to compare. - True if the object is equal to the packed vector. - - - - Compares another Rg32 packed vector with the packed vector. - - The Rg32 packed vector to compare. - True if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing four 16-bit unsigned normalized values ranging from 0 to 1. - - - - - Gets and sets the packed value. - - - - - Creates a new instance of Rgba64. - - The x component - The y component - The z component - The w component - - - - Creates a new instance of Rgba64. - - - Vector containing the components for the packed vector. - - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Compares an object with the packed vector. - - The object to compare. - True if the object is equal to the packed vector. - - - - Compares another Rgba64 packed vector with the packed vector. - - The Rgba64 packed vector to compare. - True if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing unsigned normalized values ranging from 0 to 1. - The x, y and z components use 10 bits, and the w component uses 2 bits. - - - - - Gets and sets the packed value. - - - - - Creates a new instance of Rgba1010102. - - The x component - The y component - The z component - The w component - - - - Creates a new instance of Rgba1010102. - - - Vector containing the components for the packed vector. - - - - - Gets the packed vector in Vector4 format. - - The packed vector in Vector4 format - - - - Sets the packed vector from a Vector4. - - Vector containing the components. - - - - Compares an object with the packed vector. - - The object to compare. - True if the object is equal to the packed vector. - - - - Compares another Rgba1010102 packed vector with the packed vector. - - The Rgba1010102 packed vector to compare. - True if the packed vectors are equal. - - - - Gets a string representation of the packed vector. - - A string representation of the packed vector. - - - - Gets a hash code of the packed vector. - - The hash code for the packed vector. - - - - Packed vector type containing four 16-bit signed integer values. - - - - - Initializes a new instance of the Short4 class. - - A vector containing the initial values for the components of the Short4 structure. - - - - Initializes a new instance of the Short4 class. - - Initial value for the x component. - Initial value for the y component. - Initial value for the z component. - Initial value for the w component. - - - - Compares the current instance of a class to another instance to determine whether they are different. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are different; false otherwise. - - - - Compares the current instance of a class to another instance to determine whether they are the same. - - The object to the left of the equality operator. - The object to the right of the equality operator. - true if the objects are the same; false otherwise. - - - - Directly gets or sets the packed representation of the value. - - The packed representation of the value. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Returns a value that indicates whether the current instance is equal to a specified object. - - The object with which to make the comparison. - true if the current instance is equal to the specified object; false otherwise. - - - - Gets the hash code for the current instance. - - Hash code for the instance. - - - - Returns a string representation of the current instance. - - String that represents the object. - - - - Packs a vector into a ulong. - - The vector containing the values to pack. - The ulong containing the packed values. - - - - Sets the packed representation from a Vector4. - - The vector to create the packed representation from. - - - - Expands the packed representation into a Vector4. - - The expanded vector. - - - - Defines how updates the game window. - - - - - Equivalent to . - - - - - The driver waits for the vertical retrace period, before updating window client area. Present operations are not affected more frequently than the screen refresh rate. - - - - - The driver waits for the vertical retrace period, before updating window client area. Present operations are not affected more frequently than every second screen refresh. - - - - - The driver updates the window client area immediately. Present operations might be affected immediately. There is no limit for framerate. - - - - - Allows child class to specify the surface type, eg: a swap chain. - - - - - Represents a texture cube that can be used as a render target. - - - - - Gets the depth-stencil buffer format of this render target. - - The format of the depth-stencil buffer. - - - - Gets the number of multisample locations. - - The number of multisample locations. - - - - Gets the usage mode of this render target. - - The usage mode of the render target. - - - - - - - - - - Initializes a new instance of the class. - - The graphics device. - The width and height of a texture cube face in pixels. - to generate a full mipmap chain; otherwise . - The preferred format of the surface. - The preferred format of the depth-stencil buffer. - - - - Initializes a new instance of the class. - - The graphics device. - The width and height of a texture cube face in pixels. - to generate a full mipmap chain; otherwise . - The preferred format of the surface. - The preferred format of the depth-stencil buffer. - The preferred number of multisample locations. - The usage mode of the render target. - - - - Defines if the previous content in a render target is preserved when it set on the graphics device. - - - - - The render target content will not be preserved. - - - - - The render target content will be preserved even if it is slow or requires extra memory. - - - - - The render target content might be preserved if the platform can do so without a penalty in performance or memory usage. - - - - - The newly created resource object. - - - - - The name of the destroyed resource. - - - - - The resource manager tag of the destroyed resource. - - - - - Mark all the sampler slots as dirty. - - - - - Defines how vertex or index buffer data will be flushed during a SetData operation. - - - - - The SetData can overwrite the portions of existing data. - - - - - The SetData will discard the entire buffer. A pointer to a new memory area is returned and rendering from the previous area do not stall. - - - - - The SetData operation will not overwrite existing data. This allows the driver to return immediately from a SetData operation and continue rendering. - - - - - A hash value which can be used to compare constant buffers. - - - - - Returns the platform specific shader profile identifier. - - - - - A hash value which can be used to compare shaders. - - - - - This class is used to Cache the links between Vertex/Pixel Shaders and Constant Buffers. - It will be responsible for linking the programs under OpenGL if they have not been linked - before. If an existing link exists it will be resused. - - - - - Clear the program cache releasing all shader programs. - - - - - Helper class for drawing text strings and sprites in one or more optimized batches. - - - - - Constructs a . - - The , which will be used for sprite rendering. - Thrown when is null. - - - - Begins a new sprite and text batch with the specified render state. - - The drawing order for sprite and text drawing. by default. - State of the blending. Uses if null. - State of the sampler. Uses if null. - State of the depth-stencil buffer. Uses if null. - State of the rasterization. Uses if null. - A custom to override the default sprite effect. Uses default sprite effect if null. - An optional matrix used to transform the sprite geometry. Uses if null. - Thrown if is called next time without previous . - This method uses optional parameters. - The Begin should be called before drawing commands, and you cannot call it again before subsequent . - - - - Flushes all batched text and sprites to the screen. - - This command should be called after and drawing commands. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing location on screen or null if is used. - The drawing bounds on screen or null if is used. - An optional region on the texture which will be rendered. If null - draws full texture. - An optional center of rotation. Uses if null. - An optional rotation of this sprite. 0 by default. - An optional scale vector. Uses if null. - An optional color mask. Uses if null. - The optional drawing modificators. by default. - An optional depth of the layer of this sprite. 0 by default. - Throwns if both and been used. - This overload uses optional parameters. This overload requires only one of and been used. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing location on screen. - An optional region on the texture which will be rendered. If null - draws full texture. - A color mask. - A rotation of this sprite. - Center of the rotation. 0,0 by default. - A scaling of this sprite. - Modificators for drawing. Can be combined. - A depth of the layer of this sprite. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing location on screen. - An optional region on the texture which will be rendered. If null - draws full texture. - A color mask. - A rotation of this sprite. - Center of the rotation. 0,0 by default. - A scaling of this sprite. - Modificators for drawing. Can be combined. - A depth of the layer of this sprite. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing bounds on screen. - An optional region on the texture which will be rendered. If null - draws full texture. - A color mask. - A rotation of this sprite. - Center of the rotation. 0,0 by default. - Modificators for drawing. Can be combined. - A depth of the layer of this sprite. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing location on screen. - An optional region on the texture which will be rendered. If null - draws full texture. - A color mask. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing bounds on screen. - An optional region on the texture which will be rendered. If null - draws full texture. - A color mask. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing location on screen. - A color mask. - - - - Submit a sprite for drawing in the current batch. - - A texture. - The drawing bounds on screen. - A color mask. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - A rotation of this string. - Center of the rotation. 0,0 by default. - A scaling of this string. - Modificators for drawing. Can be combined. - A depth of the layer of this string. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - A rotation of this string. - Center of the rotation. 0,0 by default. - A scaling of this string. - Modificators for drawing. Can be combined. - A depth of the layer of this string. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - A rotation of this string. - Center of the rotation. 0,0 by default. - A scaling of this string. - Modificators for drawing. Can be combined. - A depth of the layer of this string. - - - - Submit a text string of sprites for drawing in the current batch. - - A font. - The text which will be drawn. - The drawing location on screen. - A color mask. - A rotation of this string. - Center of the rotation. 0,0 by default. - A scaling of this string. - Modificators for drawing. Can be combined. - A depth of the layer of this string. - - - - Immediately releases the unmanaged resources used by this object. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - This class handles the queueing of batch items into the GPU by creating the triangle tesselations - that are used to draw the sprite textures. This class supports int.MaxValue number of sprites to be - batched and will process them into short.MaxValue groups (strided by 6 for the number of vertices - sent to the GPU). - - - - - Initialization size for the batch item list and queue. - - - - - The maximum number of batch items that can be processed per iteration - - - - - Initialization size for the vertex array, in batch units. - - - - - The list of batch items to process. - - - - - Index pointer to the next available SpriteBatchItem in _batchItemList. - - - - - The target graphics device. - - - - - Vertex index array. The values in this array never change. - - - - - Reuse a previously allocated SpriteBatchItem from the item pool. - if there is none available grow the pool and initialize new items. - - - - - - Resize and recreate the missing indices for the index and vertex position color buffers. - - - - - - Sorts the batch items and then groups batch drawing into maximal allowed batch sets that do not - overflow the 16 bit array indices for vertices. - - The type of depth sorting desired for the rendering. - The custom effect to apply to the drawn geometry - - - - Sends the triangle list to the graphics device. Here is where the actual drawing starts. - - Start index of vertices to draw. Not used except to compute the count of vertices to draw. - End index of vertices to draw. Not used except to compute the count of vertices to draw. - The custom effect to apply to the geometry - The texture to draw. - - - - Defines sprite visual options for mirroring. - - - - - No options specified. - - - - - Render the sprite reversed along the X axis. - - - - - Render the sprite reversed along the Y axis. - - - - - Gets the texture that this SpriteFont draws from. - - Can be used to implement custom rendering of a SpriteFont - - - - Returns a copy of the dictionary containing the glyphs in this SpriteFont. - - A new Dictionary containing all of the glyphs inthis SpriteFont - Can be used to calculate character bounds when implementing custom SpriteFont rendering. - - - - Gets a collection of the characters in the font. - - - - - Gets or sets the character that will be substituted when a - given character is not included in the font. - - - - - Gets or sets the line spacing (the distance from baseline - to baseline) of the font. - - - - - Gets or sets the spacing (tracking) between characters in - the font. - - - - - Returns the size of a string when rendered in this font. - - The text to measure. - The size, in pixels, of 'text' when rendered in - this font. - - - - Returns the size of the contents of a StringBuilder when - rendered in this font. - - The text to measure. - The size, in pixels, of 'text' when rendered in - this font. - - - - Struct that defines the spacing, Kerning, and bounds of a character. - - Provides the data necessary to implement custom SpriteFont rendering. - - - - The char associated with this glyph. - - - - - Rectangle in the font texture where this letter exists. - - - - - Cropping applied to the BoundsInTexture to calculate the bounds of the actual character. - - - - - The amount of space between the left side ofthe character and its first pixel in the X dimention. - - - - - The amount of space between the right side of the character and its last pixel in the X dimention. - - - - - Width of the character before kerning is applied. - - - - - Width of the character before kerning is applied. - - - - - Defines sprite sort rendering options. - - - - - All sprites are drawing when invokes, in order of draw call sequence. Depth is ignored. - - - - - Each sprite is drawing at individual draw call, instead of . Depth is ignored. - - - - - Same as , except sprites are sorted by texture prior to drawing. Depth is ignored. - - - - - Same as , except sprites are sorted by depth in back-to-front order prior to drawing. - - - - - Same as , except sprites are sorted by depth in front-to-back order prior to drawing. - - - - - Defines a blend mode. - - - - - Each component of the color is multiplied by {1, 1, 1, 1}. - - - - - Each component of the color is multiplied by {0, 0, 0, 0}. - - - - - Each component of the color is multiplied by the source color. - {Rs, Gs, Bs, As}, where Rs, Gs, Bs, As are color source values. - - - - - Each component of the color is multiplied by the inverse of the source color. - {1 − Rs, 1 − Gs, 1 − Bs, 1 − As}, where Rs, Gs, Bs, As are color source values. - - - - - Each component of the color is multiplied by the alpha value of the source. - {As, As, As, As}, where As is the source alpha value. - - - - - Each component of the color is multiplied by the inverse of the alpha value of the source. - {1 − As, 1 − As, 1 − As, 1 − As}, where As is the source alpha value. - - - - - Each component color is multiplied by the destination color. - {Rd, Gd, Bd, Ad}, where Rd, Gd, Bd, Ad are color destination values. - - - - - Each component of the color is multiplied by the inversed destination color. - {1 − Rd, 1 − Gd, 1 − Bd, 1 − Ad}, where Rd, Gd, Bd, Ad are color destination values. - - - - - Each component of the color is multiplied by the alpha value of the destination. - {Ad, Ad, Ad, Ad}, where Ad is the destination alpha value. - - - - - Each component of the color is multiplied by the inversed alpha value of the destination. - {1 − Ad, 1 − Ad, 1 − Ad, 1 − Ad}, where Ad is the destination alpha value. - - - - - Each component of the color is multiplied by a constant in the . - - - - - Each component of the color is multiplied by a inversed constant in the . - - - - - Each component of the color is multiplied by either the alpha of the source color, or the inverse of the alpha of the source color, whichever is greater. - {f, f, f, 1}, where f = min(As, 1 − As), where As is the source alpha value. - - - - - Defines a function for color blending. - - - - - The function will adds destination to the source. (srcColor * srcBlend) + (destColor * destBlend) - - - - - The function will subtracts destination from source. (srcColor * srcBlend) − (destColor * destBlend) - - - - - The function will subtracts source from destination. (destColor * destBlend) - (srcColor * srcBlend) - - - - - The function will extracts minimum of the source and destination. min((srcColor * srcBlend),(destColor * destBlend)) - - - - - The function will extracts maximum of the source and destination. max((srcColor * srcBlend),(destColor * destBlend)) - - - - - Returns the target specific blend state. - - The 0 to 3 target blend state index. - A target blend state. - - - - Enables use of the per-target blend states. - - - - - The comparison function used for depth, stencil, and alpha tests. - - - - - Always passes the test. - - - - - Never passes the test. - - - - - Passes the test when the new pixel value is less than current pixel value. - - - - - Passes the test when the new pixel value is less than or equal to current pixel value. - - - - - Passes the test when the new pixel value is equal to current pixel value. - - - - - Passes the test when the new pixel value is greater than or equal to current pixel value. - - - - - Passes the test when the new pixel value is greater than current pixel value. - - - - - Passes the test when the new pixel value does not equal to current pixel value. - - - - - Defines a culling mode for faces in rasterization process. - - - - - Do not cull faces. - - - - - Cull faces with clockwise order. - - - - - Cull faces with counter clockwise order. - - - - - Defines formats for depth-stencil buffer. - - - - - Depth-stencil buffer will not be created. - - - - - 16-bit depth buffer. - - - - - 24-bit depth buffer. Equivalent of for DirectX platforms. - - - - - 32-bit depth-stencil buffer. Where 24-bit depth and 8-bit for stencil used. - - - - - Defines options for filling the primitive. - - - - - Draw solid faces for each primitive. - - - - - Draw lines for each primitive. - - - - - When using comparison sampling, also set to . - - - - - Defines stencil buffer operations. - - - - - Does not update the stencil buffer entry. - - - - - Sets the stencil buffer entry to 0. - - - - - Replaces the stencil buffer entry with a reference value. - - - - - Increments the stencil buffer entry, wrapping to 0 if the new value exceeds the maximum value. - - - - - Decrements the stencil buffer entry, wrapping to the maximum value if the new value is less than 0. - - - - - Increments the stencil buffer entry, clamping to the maximum value. - - - - - Decrements the stencil buffer entry, clamping to 0. - - - - - Inverts the bits in the stencil buffer entry. - - - - - Defines modes for addressing texels using texture coordinates that are outside of the range of 0.0 to 1.0. - - - - - Texels outside range will form the tile at every integer junction. - - - - - Texels outside range will be set to color of 0.0 or 1.0 texel. - - - - - Same as but tiles will also flipped at every integer junction. - - - - - Texels outside range will be set to the border color. - - - - - Defines filtering types for texture sampler. - - - - - Use linear filtering. - - - - - Use point filtering. - - - - - Use anisotropic filtering. - - - - - Use linear filtering to shrink or expand, and point filtering between mipmap levels (mip). - - - - - Use point filtering to shrink (minify) or expand (magnify), and linear filtering between mipmap levels. - - - - - Use linear filtering to shrink, point filtering to expand, and linear filtering between mipmap levels. - - - - - Use linear filtering to shrink, point filtering to expand, and point filtering between mipmap levels. - - - - - Use point filtering to shrink, linear filtering to expand, and linear filtering between mipmap levels. - - - - - Use point filtering to shrink, linear filtering to expand, and point filtering between mipmap levels. - - - - - Filtering modes for texture samplers. - - - - - Defines types of surface formats. - - - - - Unsigned 32-bit ARGB pixel format for store 8 bits per channel. - - - - - Unsigned 16-bit BGR pixel format for store 5 bits for blue, 6 bits for green, and 5 bits for red. - - - - - Unsigned 16-bit BGRA pixel format where 5 bits reserved for each color and last bit is reserved for alpha. - - - - - Unsigned 16-bit BGRA pixel format for store 4 bits per channel. - - - - - DXT1. Texture format with compression. Surface dimensions must be a multiple 4. - - - - - DXT3. Texture format with compression. Surface dimensions must be a multiple 4. - - - - - DXT5. Texture format with compression. Surface dimensions must be a multiple 4. - - - - - Signed 16-bit bump-map format for store 8 bits for u and v data. - - - - - Signed 16-bit bump-map format for store 8 bits per channel. - - - - - Unsigned 32-bit RGBA pixel format for store 10 bits for each color and 2 bits for alpha. - - - - - Unsigned 32-bit RG pixel format using 16 bits per channel. - - - - - Unsigned 64-bit RGBA pixel format using 16 bits per channel. - - - - - Unsigned A 8-bit format for store 8 bits to alpha channel. - - - - - IEEE 32-bit R float format for store 32 bits to red channel. - - - - - IEEE 64-bit RG float format for store 32 bits per channel. - - - - - IEEE 128-bit RGBA float format for store 32 bits per channel. - - - - - Float 16-bit R format for store 16 bits to red channel. - - - - - Float 32-bit RG format for store 16 bits per channel. - - - - - Float 64-bit ARGB format for store 16 bits per channel. - - - - - Float pixel format for high dynamic range data. - - - - - For compatibility with WPF D3DImage. - - - - - For compatibility with WPF D3DImage. - - - - - Unsigned 32-bit RGBA sRGB pixel format that supports 8 bits per channel. - - - - - Unsigned 32-bit sRGB pixel format that supports 8 bits per channel. 8 bits are unused. - - - - - Unsigned 32-bit sRGB pixel format that supports 8 bits per channel. - - - - - DXT1. sRGB texture format with compression. Surface dimensions must be a multiple of 4. - - - - - DXT3. sRGB texture format with compression. Surface dimensions must be a multiple of 4. - - - - - DXT5. sRGB texture format with compression. Surface dimensions must be a multiple of 4. - - - - - PowerVR texture compression format (iOS and Android). - - - - - PowerVR texture compression format (iOS and Android). - - - - - PowerVR texture compression format (iOS and Android). - - - - - PowerVR texture compression format (iOS and Android). - - - - - Ericcson Texture Compression (Android) - - - - - DXT1 version where 1-bit alpha is used. - - - - - ATC/ATITC compression (Android) - - - - - ATC/ATITC compression (Android) - - - - - Gets the dimensions of the texture - - - - - Creates a new texture of the given size - - - - - - - - Creates a new texture of a given size with a surface format and optional mipmaps - - - - - - - - - - Creates a new texture array of a given size with a surface format and optional mipmaps. - Throws ArgumentException if the current GraphicsDevice can't work with texture arrays - - - - - - - - - - - Creates a new texture of a given size with a surface format and optional mipmaps. - - - - - - - - - - - Changes the pixels of the texture - Throws ArgumentNullException if data is null - Throws ArgumentException if arraySlice is greater than 0, and the GraphicsDevice does not support texture arrays - - - Layer of the texture to modify - Index inside the texture array - Area to modify - New data for the texture - Start position of data - - - - - Changes the pixels of the texture - - - Layer of the texture to modify - Area to modify - New data for the texture - Start position of data - - - - - Changes the texture's pixels - - - New data for the texture - Start position of data - - - - - Changes the texture's pixels - - New data for the texture - - - - - Retrieves the contents of the texture - Throws ArgumentException if data is null, data.length is too short or - if arraySlice is greater than 0 and the GraphicsDevice doesn't support texture arrays - - - Layer of the texture - Index inside the texture array - Area of the texture to retrieve - Destination array for the data - Starting index of data where to write the pixel data - Number of pixels to read - - - - Retrieves the contents of the texture - Throws ArgumentException if data is null, data.length is too short or - if arraySlice is greater than 0 and the GraphicsDevice doesn't support texture arrays - - - Layer of the texture - Area of the texture - Destination array for the texture data - First position in data where to write the pixel data - Number of pixels to read - - - - Retrieves the contents of the texture - Throws ArgumentException if data is null, data.length is too short or - if arraySlice is greater than 0 and the GraphicsDevice doesn't support texture arrays - - - Destination array for the texture data - First position in data where to write the pixel data - Number of pixels to read - - - - Retrieves the contents of the texture - Throws ArgumentException if data is null, data.length is too short or - if arraySlice is greater than 0 and the GraphicsDevice doesn't support texture arrays - - - Destination array for the texture data - - - - Creates a Texture2D from a stream, supported formats bmp, gif, jpg, png, tif and dds (only for simple textures). - May work with other formats, but will not work with tga files. - - - - - - - - Converts the texture to a JPG image - - Destination for the image - - - - - - Converts the texture to a PNG image - - Destination for the image - - - - - - Gets a copy of 3D texture data, specifying a mipmap level, source box, start index, and number of elements. - - The type of the elements in the array. - Mipmap level. - Position of the left side of the box on the x-axis. - Position of the top of the box on the y-axis. - Position of the right side of the box on the x-axis. - Position of the bottom of the box on the y-axis. - Position of the front of the box on the z-axis. - Position of the back of the box on the z-axis. - Array of data. - Index of the first element to get. - Number of elements to get. - - - - Gets a copy of 3D texture data, specifying a start index and number of elements. - - The type of the elements in the array. - Array of data. - Index of the first element to get. - Number of elements to get. - - - - Gets a copy of 3D texture data. - - The type of the elements in the array. - Array of data. - - - - Marks all texture slots as dirty. - - - - - Gets a unique identifier of this texture for sorting purposes. - - - For example, this value is used by when drawing with . - The value is an implementation detail and may change between application launches or MonoGame versions. - It is only guaranteed to stay consistent during application lifetime. - - - - - Gets the width and height of the cube map face in pixels. - - The width and height of a cube map face in pixels. - - - - Gets a copy of cube texture data specifying a cubemap face. - - - The cube map face. - The data. - - - - A usage hint for optimizing memory placement of graphics buffers. - - - - - No special usage. - - - - - The buffer will not be readable and will be optimized for rendering and writing. - - - - - Special offset used internally by GraphicsDevice.DrawUserXXX() methods. - - - - - Special offset used internally by GraphicsDevice.DrawUserXXX() methods. - - - - - Gets the relevant IndexElementSize enum value for the given type. - - The graphics device. - The type to use for the index buffer - The IndexElementSize enum value that matches the type - - - - The GraphicsDevice is resetting, so GPU resources must be recreated. - - - - - If the IBO does not exist, create it. - - - - - Defines size for index in and . - - - - - 16-bit short/ushort value been used. - - - - - 32-bit int/uint value been used. - - - - - Defines how vertex data is ordered. - - - - - Renders the specified vertices as a sequence of isolated triangles. Each group of three vertices defines a separate triangle. Back-face culling is affected by the current winding-order render state. - - - - - Renders the vertices as a triangle strip. The back-face culling flag is flipped automatically on even-numbered triangles. - - - - - Renders the vertices as a list of isolated straight line segments; the count may be any positive integer. - - - - - Renders the vertices as a single polyline; the count may be any positive integer. - - - - - The GraphicsDevice is resetting, so GPU resources must be recreated. - - - - - Sets the vertex buffer data, specifying the index at which to start copying from the source data array, - the number of elements to copy from the source data array, - and how far apart elements from the source data array should be when they are copied into the vertex buffer. - - Type of elements in the data array. - Offset in bytes from the beginning of the vertex buffer to the start of the copied data. - Data array. - Index at which to start copying from . - Must be within the array bounds. - Number of elements to copy from . - The combination of and - must be within the array bounds. - Specifies how far apart, in bytes, elements from should be when - they are copied into the vertex buffer. - In almost all cases this should be sizeof(T), to create a tightly-packed vertex buffer. - If you specify sizeof(T), elements from will be copied into the - vertex buffer with no padding between each element. - If you specify a value greater than sizeof(T), elements from will be copied - into the vertex buffer with padding between each element. - If you specify 0 for this parameter, it will be treated as if you had specified sizeof(T). - With the exception of 0, you must specify a value greater than or equal to sizeof(T). - - If T is VertexPositionTexture, but you want to set only the position component of the vertex data, - you would call this method as follows: - - Vector3[] positions = new Vector3[numVertices]; - vertexBuffer.SetData(0, positions, 0, numVertices, vertexBuffer.VertexDeclaration.VertexStride); - - - Continuing from the previous example, if you want to set only the texture coordinate component of the vertex data, - you would call this method as follows (note the use of : - - Vector2[] texCoords = new Vector2[numVertices]; - vertexBuffer.SetData(12, texCoords, 0, numVertices, vertexBuffer.VertexDeclaration.VertexStride); - - - - If you provide a byte[] in the parameter, then you should almost certainly - set to 1, to avoid leaving any padding between the byte values - when they are copied into the vertex buffer. - - - - - Sets the vertex buffer data, specifying the index at which to start copying from the source data array, - and the number of elements to copy from the source data array. This is the same as calling - with offsetInBytes equal to 0, - and vertexStride equal to sizeof(T). - - Type of elements in the data array. - Data array. - Index at which to start copying from . - Must be within the array bounds. - Number of elements to copy from . - The combination of and - must be within the array bounds. - - - - Sets the vertex buffer data. This is the same as calling - with offsetInBytes and startIndex equal to 0, elementCount equal to data.Length, - and vertexStride equal to sizeof(T). - - Type of elements in the data array. - Data array. - - - - If the VBO does not exist, create it. - - - - - Defines how a vertex buffer is bound to the graphics device for rendering. - - - - - Gets the vertex buffer. - - The vertex buffer. - - - - Gets the index of the first vertex in the vertex buffer to use. - - The index of the first vertex in the vertex buffer to use. - - - - Gets the number of instances to draw using the same per-instance data before advancing - in the buffer by one element. - - - The number of instances to draw using the same per-instance data before advancing in the - buffer by one element. This value must be 0 for an element that contains per-vertex - data and greater than 0 for per-instance data. - - - - - Creates an instance of . - - The vertex buffer to bind. - - - - Creates an instance of . - - The vertex buffer to bind. - - The index of the first vertex in the vertex buffer to use. - - - - - Creates an instance of VertexBufferBinding. - - The vertex buffer to bind. - - The index of the first vertex in the vertex buffer to use. - - - The number of instances to draw using the same per-instance data before advancing in the - buffer by one element. This value must be 0 for an element that contains per-vertex data - and greater than 0 for per-instance data. - - - is . - - - or is invalid. - - - - - Stores the vertex buffers to be bound to the input assembler stage. - - - - - Initializes a new instance of the class. - - The maximum number of vertex buffer slots. - - - - Clears the vertex buffer slots. - - - if the input layout was changed; otherwise, - . - - - - - Binds the specified vertex buffer to the first input slot. - - The vertex buffer. - - The offset (in vertices) from the beginning of the vertex buffer to the first vertex to - use. - - - if the input layout was changed; otherwise, - . - - - - - Binds the the specified vertex buffers to the input slots. - - The vertex buffer bindings. - - if the input layout was changed; otherwise, - . - - - - - Gets vertex buffer bound to the specified input slots. - - The vertex buffer binding. - - - - Gets vertex buffers bound to the input slots. - - The vertex buffer bindings. - - - - Helper class which ensures we only lookup a vertex - declaration for a particular type once. - - A vertex structure which implements IVertexType. - - - - Defines per-vertex data of a vertex buffer. - - - implements and can be used as - a key in a dictionary. Two vertex declarations are considered equal if the vertices are - structurally equivalent, i.e. the vertex elements and the vertex stride are identical. (The - properties and are - ignored in and !) - - - - - Gets the internal vertex elements array. - - The internal vertex elements array. - - - - Initializes a new instance of the class. - - The vertex elements. - - is or empty. - - - - - Initializes a new instance of the class. - - The size of a vertex (including padding) in bytes. - The vertex elements. - - is or empty. - - - - - Returns the VertexDeclaration for Type. - - A value type which implements the IVertexType interface. - The VertexDeclaration. - - Prefer to use VertexDeclarationCache when the declaration lookup - can be performed with a templated type. - - - - - Gets a copy of the vertex elements. - - A copy of the vertex elements. - - - - Gets the size of a vertex (including padding) in bytes. - - The size of a vertex (including padding) in bytes. - - - - Determines whether the specified is equal to this instance. - - The object to compare with the current object. - - if the specified is equal to this instance; - otherwise, . - - - - - Determines whether the specified is equal to this - instance. - - The object to compare with the current object. - - if the specified is equal to this - instance; otherwise, . - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data - structures like a hash table. - - - - - Compares two instances to determine whether they are the - same. - - The first instance. - The second instance. - - if the and are - the same; otherwise, . - - - - - Compares two instances to determine whether they are - different. - - The first instance. - The second instance. - - if the and are - the different; otherwise, . - - - - - Vertex attribute information for a particular shader/vertex declaration combination. - - - - - Defines a single element in a vertex. - - - - - Gets or sets the offset in bytes from the beginning of the stream to the vertex element. - - The offset in bytes. - - - - Gets or sets the data format. - - The data format. - - - - Gets or sets the HLSL semantic of the element in the vertex shader input. - - The HLSL semantic of the element in the vertex shader input. - - - - Gets or sets the semantic index. - - - The semantic index, which is required if the semantic is used for more than one vertex - element. - - - Usage indices in a vertex declaration usually start with 0. When multiple vertex buffers - are bound to the input assembler stage (see ), - MonoGame internally adjusts the usage indices based on the order in which the vertex - buffers are bound. - - - - - Initializes a new instance of the struct. - - The offset in bytes from the beginning of the stream to the vertex element. - The element format. - The HLSL semantic of the element in the vertex shader input-signature. - The semantic index, which is required if the semantic is used for more than one vertex element. - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data - structures like a hash table. - - - - - Returns a that represents this instance. - - A that represents this instance. - - - - Determines whether the specified is equal to this instance. - - The object to compare with the current object. - - if the specified is equal to this instance; - otherwise, . - - - - - Determines whether the specified is equal to this - instance. - - The object to compare with the current object. - - if the specified is equal to this - instance; otherwise, . - - - - - Compares two instances to determine whether they are the - same. - - The first instance. - The second instance. - - if the and are - the same; otherwise, . - - - - - Compares two instances to determine whether they are - different. - - The first instance. - The second instance. - - if the and are - the different; otherwise, . - - - - - Defines vertex element formats. - - - - - Single 32-bit floating point number. - - - - - Two component 32-bit floating point number. - - - - - Three component 32-bit floating point number. - - - - - Four component 32-bit floating point number. - - - - - Four component, packed unsigned byte, mapped to 0 to 1 range. - - - - - Four component unsigned byte. - - - - - Two component signed 16-bit integer. - - - - - Four component signed 16-bit integer. - - - - - Normalized, two component signed 16-bit integer. - - - - - Normalized, four component signed 16-bit integer. - - - - - Two component 16-bit floating point number. - - - - - Four component 16-bit floating point number. - - - - - Defines usage for vertex elements. - - - - - Position data. - - - - - Color data. - - - - - Texture coordinate data or can be used for user-defined data. - - - - - Normal data. - - - - - Binormal data. - - - - - Tangent data. - - - - - Blending indices data. - - - - - Blending weight data. - - - - - Depth data. - - - - - Fog data. - - - - - Point size data. Usable for drawing point sprites. - - - - - Sampler data for specifies the displacement value to look up. - - - - - Single, positive float value, specifies a tessellation factor used in the tessellation unit to control the rate of tessellation. - - - - - Stores the vertex layout (input elements) for the input assembler stage. - - - In the DirectX version the input layouts are cached in a dictionary. The - is used as the key in the dictionary and therefore needs to - implement . Two instance are - considered equal if the vertex layouts are structurally identical. - - - - - Gets or sets the number of used input slots. - - The number of used input slots. - - - - Initializes a new instance of the class. - - The maximum number of vertex buffer slots. - - - - Initializes a new instance of the class. - - The array for storing vertex declarations. - The array for storing instance frequencies. - The number of used slots. - - - - Determines whether the specified is equal to this instance. - - The object to compare with the current object. - - if the specified is equal to this instance; - otherwise, . - - - - - Determines whether the specified is equal to this - instance. - - The object to compare with the current object. - - if the specified is equal to this - instance; otherwise, . - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data - structures like a hash table. - - - - - Compares two instances to determine whether they are the - same. - - The first instance. - The second instance. - - if the and are - the same; otherwise, . - - - - - Compares two instances to determine whether they are - different. - - The first instance. - The second instance. - - if the and are - the different; otherwise, . - - - - - Describes the view bounds for render-target surface. - - - - - The height of the bounds in pixels. - - - - - The upper limit of depth of this viewport. - - - - - The lower limit of depth of this viewport. - - - - - The width of the bounds in pixels. - - - - - The y coordinate of the beginning of this viewport. - - - - - The x coordinate of the beginning of this viewport. - - - - - Gets the aspect ratio of this , which is width / height. - - - - - Gets or sets a boundary of this . - - - - - Returns the subset of the viewport that is guaranteed to be visible on a lower quality display. - - - - - Constructs a viewport from the given values. The will be 0.0 and will be 1.0. - - The x coordinate of the upper-left corner of the view bounds in pixels. - The y coordinate of the upper-left corner of the view bounds in pixels. - The width of the view bounds in pixels. - The height of the view bounds in pixels. - - - - Constructs a viewport from the given values. - - The x coordinate of the upper-left corner of the view bounds in pixels. - The y coordinate of the upper-left corner of the view bounds in pixels. - The width of the view bounds in pixels. - The height of the view bounds in pixels. - The lower limit of depth. - The upper limit of depth. - - - - Creates a new instance of struct. - - A that defines the location and size of the in a render target. - - - - Projects a from world space into screen space. - - The to project. - The projection . - The view . - The world . - - - - - Unprojects a from screen space into world space. - - The to unproject. - The projection . - The view . - The world . - - - - - Returns a representation of this in the format: - {X:[] Y:[] Width:[] Height:[] MinDepth:[] MaxDepth:[]} - - A representation of this . - - - - Defines the buttons on gamepad. - - - - - Directional pad up. - - - - - Directional pad down. - - - - - Directional pad left. - - - - - Directional pad right. - - - - - START button. - - - - - BACK button. - - - - - Left stick button (pressing the left stick). - - - - - Right stick button (pressing the right stick). - - - - - Left bumper (shoulder) button. - - - - - Right bumper (shoulder) button. - - - - - Big button. - - - - - A button. - - - - - B button. - - - - - X button. - - - - - Y button. - - - - - Left stick is towards the left. - - - - - Right trigger. - - - - - Left trigger. - - - - - Right stick is towards up. - - - - - Right stick is towards down. - - - - - Right stick is towards the right. - - - - - Right stick is towards the left. - - - - - Left stick is towards up. - - - - - Left stick is towards down. - - - - - Left stick is towards the right. - - - - - Defines a button state for buttons of mouse, gamepad or joystick. - - - - - The button is released. - - - - - The button is pressed. - - - - - Supports querying the game controllers and setting the vibration motors. - - - Supports querying the game controllers and setting the vibration motors. - - - - - Returns the capabilites of the connected controller. - - Player index for the controller you want to query. - The capabilites of the controller. - - - - Returns the capabilites of the connected controller. - - Index for the controller you want to query. - The capabilites of the controller. - - - - Gets the current state of a game pad controller with an independent axes dead zone. - - Player index for the controller you want to query. - The state of the controller. - - - - Gets the current state of a game pad controller with an independent axes dead zone. - - Index for the controller you want to query. - The state of the controller. - - - - Gets the current state of a game pad controller, using a specified dead zone - on analog stick positions. - - Player index for the controller you want to query. - Enumerated value that specifies what dead zone type to use. - The state of the controller. - - - - Gets the current state of a game pad controller, using a specified dead zone - on analog stick positions. - - Index for the controller you want to query. - Enumerated value that specifies what dead zone type to use. - The state of the controller. - - - - Sets the vibration motor speeds on the controller device if supported. - - Player index that identifies the controller to set. - The speed of the left motor, between 0.0 and 1.0. This motor is a low-frequency motor. - The speed of the right motor, between 0.0 and 1.0. This motor is a high-frequency motor. - Returns true if the vibration motors were set. - - - - Sets the vibration motor speeds on the controller device if supported. - - Index for the controller you want to query. - The speed of the left motor, between 0.0 and 1.0. This motor is a low-frequency motor. - The speed of the right motor, between 0.0 and 1.0. This motor is a high-frequency motor. - Returns true if the vibration motors were set. - - - - The maximum number of game pads supported on this system. Attempting to - access a gamepad index higher than this number will result in an - being thrown by the API. - - - - - Determines whether two specified instances of are equal. - - The first object to compare. - The second object to compare. - true if and are equal; otherwise, false. - - - - Determines whether two specified instances of are not equal. - - The first object to compare. - The second object to compare. - true if and are not equal; otherwise, false. - - - - Returns a value indicating whether this instance is equal to a specified object. - - An object to compare to this instance. - true if is a and has the same value as this instance; otherwise, false. - - - - Determines whether two specified instances of are equal. - - The first object to compare. - The second object to compare. - true if and are equal; otherwise, false. - - - - Determines whether two specified instances of are not equal. - - The first object to compare. - The second object to compare. - true if and are not equal; otherwise, false. - - - - Returns a value indicating whether this instance is equal to a specified object. - - An object to compare to this instance. - true if is a and has the same value as this instance; otherwise, false. - - - - The default initialized gamepad state. - - - - - Define this method in platform partial classes to initialize default - values for platform-specific fields. - - - - - Gets the button mask along with 'virtual buttons' like LeftThumbstickLeft. - - - - - Determines whether two specified instances of are equal. - - The first object to compare. - The second object to compare. - true if and are equal; otherwise, false. - - - - Determines whether two specified instances of are not equal. - - The first object to compare. - The second object to compare. - true if and are not equal; otherwise, false. - - - - Returns a value indicating whether this instance is equal to a specified object. - - An object to compare to this instance. - true if is a and has the same value as this instance; otherwise, false. - - - - Determines whether two specified instances of are equal. - - The first object to compare. - The second object to compare. - true if and are equal; otherwise, false. - - - - Determines whether two specified instances of are not equal. - - The first object to compare. - The second object to compare. - true if and are not equal; otherwise, false. - - - - Returns a value indicating whether this instance is equal to a specified object. - - An object to compare to this instance. - true if is a and has the same value as this instance; otherwise, false. - - - - Defines a type of gamepad. - - - - - Unknown. - - - - - GamePad is the XBOX controller. - - - - - GamePad is a wheel. - - - - - GamePad is an arcade stick. - - - - - GamePad is a flight stick. - - - - - GamePad is a dance pad. - - - - - GamePad is a guitar. - - - - - GamePad is an alternate guitar. - - - - - GamePad is a drum kit. - - - - - GamePad is a big button pad. - - - - - Allows interaction with joysticks. Unlike the number of Buttons/Axes/DPads is not limited. - - - - - Gets the capabilites of the joystick. - - Index of the joystick you want to access. - The capabilites of the joystick. - - - - Gets the current state of the joystick. - - Index of the joystick you want to access. - The state of the joystick. - - - - Describes joystick capabilities. - - - - - Gets a value indicating whether the joystick is connected. - - true if the joystick is connected; otherwise, false. - - - - Gets the unique identifier of the joystick. - - String representing the unique identifier of the joystick. - - - - Gets the axis count. - - The number of axes that the joystick possesses. - - - - Gets the button count. - - The number of buttons that the joystick possesses. - - - - Gets the hat count. - - The number of hats/dpads that the joystick possesses. - - - - Describes joystick hat state. - - - - - Gets if joysticks hat "down" is pressed. - - if the button is pressed otherwise, . - - - - Gets if joysticks hat "left" is pressed. - - if the button is pressed otherwise, . - - - - Gets if joysticks hat "right" is pressed. - - if the button is pressed otherwise, . - - - - Gets if joysticks hat "up" is pressed. - - if the button is pressed otherwise, . - - - - Describes current joystick state. - - - - - Gets a value indicating whether the joystick is connected. - - true if the joystick is connected; otherwise, false. - - - - Gets the joystick axis values. - - An array list of floats that indicate axis values. - - - - Gets the joystick button values. - - An array list of ButtonState that indicate button values. - - - - Gets the joystick hat values. - - An array list of that indicate hat values. - - - - Allows getting keystrokes from keyboard. - - - - - Returns the current keyboard state. - - Current keyboard state. - - - - Returns the current keyboard state for a given player. - - Player index of the keyboard. - Current keyboard state. - - - - Holds the state of keystrokes by a keyboard. - - - - - Gets the current state of the Caps Lock key. - - - - - Gets the current state of the Num Lock key. - - - - - Initializes a new instance of the class. - - List of keys to be flagged as pressed on initialization. - Caps Lock state. - Num Lock state. - - - - Initializes a new instance of the class. - - List of keys to be flagged as pressed on initialization. - - - - Returns the state of a specified key. - - The key to query. - The state of the key. - - - - Gets whether given key is currently being pressed. - - The key to query. - true if the key is pressed; false otherwise. - - - - Gets whether given key is currently being not pressed. - - The key to query. - true if the key is not pressed; false otherwise. - - - - Returns an array of values holding keys that are currently being pressed. - - The keys that are currently being pressed. - - - - Gets the hash code for instance. - - Hash code of the object. - - - - Compares whether two instances are equal. - - instance to the left of the equality operator. - instance to the right of the equality operator. - true if the instances are equal; false otherwise. - - - - Compares whether two instances are not equal. - - instance to the left of the inequality operator. - instance to the right of the inequality operator. - true if the instances are different; false otherwise. - - - - Compares whether current instance is equal to specified object. - - The to compare. - true if the provided instance is same with current; false otherwise. - - - - Defines the keys on a keyboard. - - - - - Reserved. - - - - - BACKSPACE key. - - - - - TAB key. - - - - - ENTER key. - - - - - CAPS LOCK key. - - - - - ESC key. - - - - - SPACEBAR key. - - - - - PAGE UP key. - - - - - PAGE DOWN key. - - - - - END key. - - - - - HOME key. - - - - - LEFT ARROW key. - - - - - UP ARROW key. - - - - - RIGHT ARROW key. - - - - - DOWN ARROW key. - - - - - SELECT key. - - - - - PRINT key. - - - - - EXECUTE key. - - - - - PRINT SCREEN key. - - - - - INS key. - - - - - DEL key. - - - - - HELP key. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - A key. - - - - - B key. - - - - - C key. - - - - - D key. - - - - - E key. - - - - - F key. - - - - - G key. - - - - - H key. - - - - - I key. - - - - - J key. - - - - - K key. - - - - - L key. - - - - - M key. - - - - - N key. - - - - - O key. - - - - - P key. - - - - - Q key. - - - - - R key. - - - - - S key. - - - - - T key. - - - - - U key. - - - - - V key. - - - - - W key. - - - - - X key. - - - - - Y key. - - - - - Z key. - - - - - Left Windows key. - - - - - Right Windows key. - - - - - Applications key. - - - - - Computer Sleep key. - - - - - Numeric keypad 0 key. - - - - - Numeric keypad 1 key. - - - - - Numeric keypad 2 key. - - - - - Numeric keypad 3 key. - - - - - Numeric keypad 4 key. - - - - - Numeric keypad 5 key. - - - - - Numeric keypad 6 key. - - - - - Numeric keypad 7 key. - - - - - Numeric keypad 8 key. - - - - - Numeric keypad 9 key. - - - - - Multiply key. - - - - - Add key. - - - - - Separator key. - - - - - Subtract key. - - - - - Decimal key. - - - - - Divide key. - - - - - F1 key. - - - - - F2 key. - - - - - F3 key. - - - - - F4 key. - - - - - F5 key. - - - - - F6 key. - - - - - F7 key. - - - - - F8 key. - - - - - F9 key. - - - - - F10 key. - - - - - F11 key. - - - - - F12 key. - - - - - F13 key. - - - - - F14 key. - - - - - F15 key. - - - - - F16 key. - - - - - F17 key. - - - - - F18 key. - - - - - F19 key. - - - - - F20 key. - - - - - F21 key. - - - - - F22 key. - - - - - F23 key. - - - - - F24 key. - - - - - NUM LOCK key. - - - - - SCROLL LOCK key. - - - - - Left SHIFT key. - - - - - Right SHIFT key. - - - - - Left CONTROL key. - - - - - Right CONTROL key. - - - - - Left ALT key. - - - - - Right ALT key. - - - - - Browser Back key. - - - - - Browser Forward key. - - - - - Browser Refresh key. - - - - - Browser Stop key. - - - - - Browser Search key. - - - - - Browser Favorites key. - - - - - Browser Start and Home key. - - - - - Volume Mute key. - - - - - Volume Down key. - - - - - Volume Up key. - - - - - Next Track key. - - - - - Previous Track key. - - - - - Stop Media key. - - - - - Play/Pause Media key. - - - - - Start Mail key. - - - - - Select Media key. - - - - - Start Application 1 key. - - - - - Start Application 2 key. - - - - - The OEM Semicolon key on a US standard keyboard. - - - - - For any country/region, the '+' key. - - - - - For any country/region, the ',' key. - - - - - For any country/region, the '-' key. - - - - - For any country/region, the '.' key. - - - - - The OEM question mark key on a US standard keyboard. - - - - - The OEM tilde key on a US standard keyboard. - - - - - The OEM open bracket key on a US standard keyboard. - - - - - The OEM pipe key on a US standard keyboard. - - - - - The OEM close bracket key on a US standard keyboard. - - - - - The OEM singled/double quote key on a US standard keyboard. - - - - - Used for miscellaneous characters; it can vary by keyboard. - - - - - The OEM angle bracket or backslash key on the RT 102 key keyboard. - - - - - IME PROCESS key. - - - - - Attn key. - - - - - CrSel key. - - - - - ExSel key. - - - - - Erase EOF key. - - - - - Play key. - - - - - Zoom key. - - - - - PA1 key. - - - - - CLEAR key. - - - - - Green ChatPad key. - - - - - Orange ChatPad key. - - - - - PAUSE key. - - - - - IME Convert key. - - - - - IME NoConvert key. - - - - - Kana key on Japanese keyboards. - - - - - Kanji key on Japanese keyboards. - - - - - OEM Auto key. - - - - - OEM Copy key. - - - - - OEM Enlarge Window key. - - - - - Identifies the state of a keyboard key. - - - - - Key is released. - - - - - Key is pressed. - - - - - Allows reading position and button click information from mouse. - - - - - Gets or sets the window handle for current mouse processing. - - - - - This API is an extension to XNA. - Gets mouse state information that includes position and button - presses for the provided window - - Current state of the mouse. - - - - Gets mouse state information that includes position and button presses - for the primary window - - Current state of the mouse. - - - - Sets mouse cursor's relative position to game-window. - - Relative horizontal position of the cursor. - Relative vertical position of the cursor. - - - - Sets the cursor image to the specified MouseCursor. - - Mouse cursor to use for the cursor image. - - - - Describes a mouse cursor. - - - - - Gets the default arrow cursor. - - - - - Gets the cursor that appears when the mouse is over text editing regions. - - - - - Gets the waiting cursor that appears while the application/system is busy. - - - - - Gets the crosshair ("+") cursor. - - - - - Gets the cross between Arrow and Wait cursors. - - - - - Gets the northwest/southeast ("\") cursor. - - - - - Gets the northeast/southwest ("/") cursor. - - - - - Gets the horizontal west/east ("-") cursor. - - - - - Gets the vertical north/south ("|") cursor. - - - - - Gets the size all cursor which points in all directions. - - - - - Gets the cursor that points that something is invalid, usually a cross. - - - - - Gets the hand cursor, usually used for web links. - - - - - Creates a mouse cursor from the specified texture. - - Texture to use as the cursor image. - X cordinate of the image that will be used for mouse position. - Y cordinate of the image that will be used for mouse position. - - - - Represents a mouse state with cursor position and button press information. - - - - - Initializes a new instance of the MouseState. - - Horizontal position of the mouse in relation to the window. - Vertical position of the mouse in relation to the window. - Mouse scroll wheel's value. - Left mouse button's state. - Middle mouse button's state. - Right mouse button's state. - XBUTTON1's state. - XBUTTON2's state. - Normally should be used to get mouse current state. The constructor is provided for simulating mouse input. - - - - Compares whether two MouseState instances are equal. - - MouseState instance on the left of the equal sign. - MouseState instance on the right of the equal sign. - true if the instances are equal; false otherwise. - - - - Compares whether two MouseState instances are not equal. - - MouseState instance on the left of the equal sign. - MouseState instance on the right of the equal sign. - true if the objects are not equal; false otherwise. - - - - Compares whether current instance is equal to specified object. - - The MouseState to compare. - - - - - Gets the hash code for MouseState instance. - - Hash code of the object. - - - - Gets horizontal position of the cursor in relation to the window. - - - - - Gets vertical position of the cursor in relation to the window. - - - - - Gets cursor position. - - - - - Gets state of the left mouse button. - - - - - Gets state of the middle mouse button. - - - - - Gets state of the right mouse button. - - - - - Returns cumulative scroll wheel value since the game start. - - - - - Gets state of the XButton1. - - - - - Gets state of the XButton2. - - - - - Represents data from a multi-touch gesture over a span of time. - - - - - Gets the type of the gesture. - - - - - Gets the starting time for this multi-touch gesture sample. - - - - - Gets the position of the first touch-point in the gesture sample. - - - - - Gets the position of the second touch-point in the gesture sample. - - - - - Gets the delta information for the first touch-point in the gesture sample. - - - - - Gets the delta information for the second touch-point in the gesture sample. - - - - - Initializes a new . - - - - - - - - - - - Enumuration of values that represent different gestures that can be processed by . - - - - - No gestures. - - - - - The user touched a single point. - - - - - States completion of a drag gesture(VerticalDrag, HorizontalDrag, or FreeDrag). - - No position or delta information is available for this sample. - - - - States that a touch was combined with a quick swipe. - - Flicks does not contain position information. The velocity of it can be read from - - - - The use touched a point and then performed a free-form drag. - - - - - The use touched a single point for approximately one second. - - As this is a single event, it will not be contionusly fired while the user is holding the touch-point. - - - - The user touched the screen and performed either left to right or right to left drag gesture. - - - - - The user either converged or diverged two touch-points on the screen which is like a two-finger drag. - - When this gesture-type is enabled and two fingers are down, it takes precedence over drag gestures. - - - - An in-progress pinch operation was completed. - - No position or delta information is available for this sample. - - - - The user tapped the device twice which is always preceded by a Tap gesture. - - If the time between two touchs are long enough, insted two seperate single Tap gestures will be generated. - - - - The user touched the screen and performed either top to bottom or bottom to top drag gesture. - - - - - Provides state information for a touch screen enabled device. - - - - - States if a touch screen is available. - - - - - Initializes a new instance of the with a pre-determined set of touch locations. - - Array of items to initialize with. - - - - Returns specified by ID. - - - - - - - - States if touch collection is read only. - - - - - Returns the index of the first occurrence of specified item in the collection. - - to query. - - - - - Inserts a item into the indicated position. - - The position to insert into. - The item to insert. - - - - Removes the item at specified index. - - Index of the item that will be removed from collection. - - - - Gets or sets the item at the specified index of the collection. - - Position of the item. - - - - - Adds a to the collection. - - The item to be added. - - - - Clears all the items in collection. - - - - - Returns true if specified item exists in the collection, false otherwise./> - - The item to query for. - Returns true if queried item is found, false otherwise. - - - - Copies the collection to specified array starting from the given index. - - The array to copy items. - The starting index of the copy operation. - - - - Returns the number of items that exist in the collection. - - - - - Removes the specified item from the collection. - - The item to remove. - - - - - Returns an enumerator for the . - - Enumerable list of objects. - - - - Returns an enumerator for the . - - Enumerable list of objects. - - - - Returns an enumerator for the . - - Enumerable list of objects. - - - - Provides the ability to iterate through the TouchLocations in an TouchCollection. - - - - - Gets the current element in the TouchCollection. - - - - - Advances the enumerator to the next element of the TouchCollection. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Attributes - - - - - True if this touch was pressed and released on the same frame. - In this case we will keep it around for the user to get by GetState that frame. - However if they do not call GetState that frame, this touch will be forgotten. - - - - - Helper for assigning an invalid touch location. - - - - - Returns a copy of the touch with the state changed to moved. - - The new touch location. - - - - Updates the touch location using the new event. - - The next event for this touch location. - - - - Holds the possible state information for a touch location.. - - - - - This touch location position is invalid. - - Typically, you will encounter this state when a new touch location attempts to get the previous state of itself. - - - - This touch location position was updated or pressed at the same position. - - - - - This touch location position is new. - - - - - This touch location position was released. - - - - - Allows retrieval of information from Touch Panel device. - - - - - Gets the current state of the touch panel. - - - - - - Returns the next available gesture on touch panel device. - - - - - - The window handle of the touch panel. Purely for Xna compatibility. - - - - - Gets or sets the display height of the touch panel. - - - - - Gets or sets the display orientation of the touch panel. - - - - - Gets or sets the display width of the touch panel. - - - - - Gets or sets enabled gestures. - - - - - Returns true if a touch gesture is available. - - - - - Allows retrieval of capabilities information from touch panel device. - - - - - Returns true if a device is available for use. - - - - - Returns the maximum number of touch locations tracked by the touch panel device. - - - - - The reserved touchId for all mouse touch points. - - - - - The current touch state. - - - - - The current gesture state. - - - - - The positional scale to apply to touch input. - - - - - The current size of the display. - - - - - The next touch location identifier. - The value 1 is reserved for the mouse touch point. - - - - - The current timestamp that we use for setting the timestamp of new TouchLocations - - - - - The mapping between platform specific touch ids - and the touch ids we assign to touch locations. - - - - - The window handle of the touch panel. Purely for Xna compatibility. - - - - - Returns capabilities of touch panel device. - - - - - - Age all the touches, so any that were Pressed become Moved, and any that were Released are removed - - - - - Apply the given new touch to the state. If it is a Pressed it will be added as a new touch, otherwise we update the existing touch it matches - - - - - This will release all touch locations. It should only be - called on platforms where touch state is reset all at once. - - - - - Gets or sets the display height of the touch panel. - - - - - Gets or sets the display orientation of the touch panel. - - - - - Gets or sets the display width of the touch panel. - - - - - Gets or sets enabled gestures. - - - - - Returns true if a touch gesture is available. - - - - - Returns the next available gesture on touch panel device. - - - - - - Maximum distance a touch location can wiggle and - not be considered to have moved. - - - - - The pinch touch locations. - - - - - If true the pinch touch locations are valid and - a pinch gesture has begun. - - - - - Used to disable emitting of tap gestures. - - - - - Used to disable emitting of hold gestures. - - - - - Gets the duration of the Album. - - - - - Gets the Genre of the Album. - - - - - Gets a value indicating whether the Album has associated album art. - - - - - Gets a value indicating whether the object is disposed. - - - - - Gets the name of the Album. - - - - - Gets a SongCollection that contains the songs on the album. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Returns the stream that contains the album art image data. - - - - - Returns the stream that contains the album thumbnail image data. - - - - - Returns a String representation of this Album. - - - - - Gets the hash code for this instance. - - - - - Gets the number of Album objects in the AlbumCollection. - - - - - Gets a value indicating whether the object is disposed. - - - - - Gets the Album at the specified index in the AlbumCollection. - - Index of the Album to get. - - - - Immediately releases the unmanaged resources used by this object. - - - - - Gets the AlbumCollection for the Artist. - - - - - Gets a value indicating whether the object is disposed. - - - - - Gets the name of the Artist. - - - - - Gets the SongCollection for the Artist. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Returns a String representation of the Artist. - - - - - Gets the hash code for this instance. - - - - - Gets the AlbumCollection for the Genre. - - - - - Gets a value indicating whether the object is disposed. - - - - - Gets the name of the Genre. - - - - - Gets the SongCollection for the Genre. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Returns a String representation of the Genre. - - - - - Gets the hash code for this instance. - - - - - Load the contents of MediaLibrary. This blocking call might take up to a few minutes depending on the platform and the size of the user's music library. - - Callback that reports back the progress of the music library loading in percents (0-100). - - - - Play clears the current playback queue, and then queues up the specified song for playback. - Playback starts immediately at the beginning of the song. - - - - - Play clears the current playback queue, and then queues up the specified song for playback. - Playback starts immediately at the given position of the song. - - - - - Gets the Album on which the Song appears. - - - - - Gets the Artist of the Song. - - - - - Gets the Genre of the Song. - - - - - Set the event handler for "Finished Playing". Done this way to prevent multiple bindings. - - - - - Type of sounds in a video - - - - - This video contains only music. - - - - - This video contains only dialog. - - - - - This video contains music and dialog. - - - - - Represents a video. - - - Represents a video. - - - - - I actually think this is a file PATH... - - - - - Gets the duration of the Video. - - - - - Gets the frame rate of this video. - - - - - Gets the height of this video, in pixels. - - - - - Gets the VideoSoundtrackType for this video. - - - - - Gets the width of this video, in pixels. - - - - - Gets a value that indicates whether the object is disposed. - - - - - Gets a value that indicates whether the player is playing video in a loop. - - - - - Gets or sets the muted setting for the video player. - - - - - Gets the play position within the currently playing video. - - - - - Gets the media playback state, MediaState. - - - - - Gets the Video that is currently playing. - - - - - Video player volume, from 0.0f (silence) to 1.0f (full volume relative to the current device volume). - - - - - Retrieves a Texture2D containing the current frame of video being played. - - The current frame of video. - Thrown if no video is set on the player - Thrown if the platform was unable to get a texture in a reasonable amount of time. Often the platform specific media code is running - in a different thread or process. Note: This may be a change from XNA behaviour - - - - Pauses the currently playing video. - - - - - Plays a Video. - - Video to play. - - - - Resumes a paused video. - - - - - Stops playing a video. - - - - - Immediately releases the unmanaged resources used by this object. - - - - - Compute a hash from a byte array. - - - Modified FNV Hash in C# - http://stackoverflow.com/a/468084 - - - - - Compute a hash from the content of a stream and restore the position. - - - Modified FNV Hash in C# - http://stackoverflow.com/a/468084 - - - - - Combines the filePath and relativeFile based on relativeFile being a file in the same location as filePath. - Relative directory operators (..) are also resolved - - "A\B\C.txt","D.txt" becomes "A\B\D.txt" - "A\B\C.txt","..\D.txt" becomes "A\D.txt" - Path to the file we are starting from - Relative location of another file to resolve the path to - - - - Returns true if the given type represents a non-object type that is not abstract. - - - - - Returns true if the get method of the given property exist and are public. - Note that we allow a getter-only property to be serialized (and deserialized), - *if* CanDeserializeIntoExistingObject is true for the property type. - - - - - Returns true if the given type can be assigned the given value - - - - - Returns true if the given type can be assigned a value with the given object type - - - - - Represents a Zlib stream for compression or decompression. - - - - - The ZlibStream is a Decorator on a . It adds ZLIB compression or decompression to any - stream. - - - Using this stream, applications can compress or decompress data via - stream Read() and Write() operations. Either compression or - decompression can occur through either reading or writing. The compression - format used is ZLIB, which is documented in IETF RFC 1950, "ZLIB Compressed - Data Format Specification version 3.3". This implementation of ZLIB always uses - DEFLATE as the compression method. (see IETF RFC 1951, "DEFLATE - Compressed Data Format Specification version 1.3.") - - - The ZLIB format allows for varying compression methods, window sizes, and dictionaries. - This implementation always uses the DEFLATE compression method, a preset dictionary, - and 15 window bits by default. - - - - This class is similar to DeflateStream, except that it adds the - RFC1950 header and trailer bytes to a compressed stream when compressing, or expects - the RFC1950 header and trailer bytes when decompressing. It is also similar to the - . - - - - - - - Create a ZlibStream using the specified CompressionMode. - - - - - When mode is CompressionMode.Compress, the ZlibStream - will use the default compression level. The "captive" stream will be - closed when the ZlibStream is closed. - - - - - - This example uses a ZlibStream to compress a file, and writes the - compressed data to another file. - - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (var raw = System.IO.File.Create(fileToCompress + ".zlib")) - { - using (Stream compressor = new ZlibStream(raw, CompressionMode.Compress)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - } - - - Using input As Stream = File.OpenRead(fileToCompress) - Using raw As FileStream = File.Create(fileToCompress & ".zlib") - Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - End Using - - - - The stream which will be read or written. - Indicates whether the ZlibStream will compress or decompress. - - - - Create a ZlibStream using the specified CompressionMode and - the specified CompressionLevel. - - - - - - When mode is CompressionMode.Decompress, the level parameter is ignored. - The "captive" stream will be closed when the ZlibStream is closed. - - - - - - This example uses a ZlibStream to compress data from a file, and writes the - compressed data to another file. - - - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (var raw = System.IO.File.Create(fileToCompress + ".zlib")) - { - using (Stream compressor = new ZlibStream(raw, - CompressionMode.Compress, - CompressionLevel.BestCompression)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - } - - - - Using input As Stream = File.OpenRead(fileToCompress) - Using raw As FileStream = File.Create(fileToCompress & ".zlib") - Using compressor As Stream = New ZlibStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - End Using - - - - The stream to be read or written while deflating or inflating. - Indicates whether the ZlibStream will compress or decompress. - A tuning knob to trade speed for effectiveness. - - - - Create a ZlibStream using the specified CompressionMode, and - explicitly specify whether the captive stream should be left open after - Deflation or Inflation. - - - - - - When mode is CompressionMode.Compress, the ZlibStream will use - the default compression level. - - - - This constructor allows the application to request that the captive stream - remain open after the deflation or inflation occurs. By default, after - Close() is called on the stream, the captive stream is also - closed. In some cases this is not desired, for example if the stream is a - that will be re-read after - compression. Specify true for the parameter to leave the stream - open. - - - - See the other overloads of this constructor for example code. - - - - - The stream which will be read or written. This is called the - "captive" stream in other places in this documentation. - Indicates whether the ZlibStream will compress or decompress. - true if the application would like the stream to remain - open after inflation/deflation. - - - - Create a ZlibStream using the specified CompressionMode - and the specified CompressionLevel, and explicitly specify - whether the stream should be left open after Deflation or Inflation. - - - - - - This constructor allows the application to request that the captive - stream remain open after the deflation or inflation occurs. By - default, after Close() is called on the stream, the captive - stream is also closed. In some cases this is not desired, for example - if the stream is a that will be - re-read after compression. Specify true for the parameter to leave the stream open. - - - - When mode is CompressionMode.Decompress, the level parameter is - ignored. - - - - - - - This example shows how to use a ZlibStream to compress the data from a file, - and store the result into another file. The filestream remains open to allow - additional data to be written to it. - - - using (var output = System.IO.File.Create(fileToCompress + ".zlib")) - { - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (Stream compressor = new ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, true)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - // can write additional data to the output stream here - } - - - Using output As FileStream = File.Create(fileToCompress & ".zlib") - Using input As Stream = File.OpenRead(fileToCompress) - Using compressor As Stream = New ZlibStream(output, CompressionMode.Compress, CompressionLevel.BestCompression, True) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - ' can write additional data to the output stream here. - End Using - - - - The stream which will be read or written. - - Indicates whether the ZlibStream will compress or decompress. - - - true if the application would like the stream to remain open after - inflation/deflation. - - - - A tuning knob to trade speed for effectiveness. This parameter is - effective only when mode is CompressionMode.Compress. - - - - - This property sets the flush behavior on the stream. - Sorry, though, not sure exactly how to describe all the various settings. - - - - - The size of the working buffer for the compression codec. - - - - - The working buffer is used for all stream operations. The default size is - 1024 bytes. The minimum size is 128 bytes. You may get better performance - with a larger buffer. Then again, you might not. You would have to test - it. - - - - Set this before the first call to Read() or Write() on the - stream. If you try to set it afterwards, it will throw. - - - - - Returns the total number of bytes input so far. - - - Returns the total number of bytes output so far. - - - - Dispose the stream. - - - - This may or may not result in a Close() call on the captive - stream. See the constructors that have a leaveOpen parameter - for more information. - - - This method may be invoked in two distinct scenarios. If disposing - == true, the method has been called directly or indirectly by a - user's code, for example via the public Dispose() method. In this - case, both managed and unmanaged resources can be referenced and - disposed. If disposing == false, the method has been called by the - runtime from inside the object finalizer and this method should not - reference other objects; in that case only unmanaged resources must - be referenced or disposed. - - - - indicates whether the Dispose method was invoked by user code. - - - - - Indicates whether the stream can be read. - - - The return value depends on whether the captive stream supports reading. - - - - - Indicates whether the stream supports Seek operations. - - - Always returns false. - - - - - Indicates whether the stream can be written. - - - The return value depends on whether the captive stream supports writing. - - - - - Flush the stream. - - - - - Reading this property always throws a . - - - - - The position of the stream pointer. - - - - Setting this property always throws a . Reading will return the total bytes - written out, if used in writing, or the total bytes read in, if used in - reading. The count may refer to compressed bytes or uncompressed bytes, - depending on how you've used the stream. - - - - - Read data from the stream. - - - - - - If you wish to use the ZlibStream to compress data while reading, - you can create a ZlibStream with CompressionMode.Compress, - providing an uncompressed data stream. Then call Read() on that - ZlibStream, and the data read will be compressed. If you wish to - use the ZlibStream to decompress data while reading, you can create - a ZlibStream with CompressionMode.Decompress, providing a - readable compressed data stream. Then call Read() on that - ZlibStream, and the data will be decompressed as it is read. - - - - A ZlibStream can be used for Read() or Write(), but - not both. - - - - - - The buffer into which the read data should be placed. - - - the offset within that data array to put the first byte read. - - the number of bytes to read. - - the number of bytes read - - - - Calling this method always throws a . - - - The offset to seek to.... - IF THIS METHOD ACTUALLY DID ANYTHING. - - - The reference specifying how to apply the offset.... IF - THIS METHOD ACTUALLY DID ANYTHING. - - - nothing. This method always throws. - - - - Calling this method always throws a . - - - The new value for the stream length.... IF - THIS METHOD ACTUALLY DID ANYTHING. - - - - - Write data to the stream. - - - - - - If you wish to use the ZlibStream to compress data while writing, - you can create a ZlibStream with CompressionMode.Compress, - and a writable output stream. Then call Write() on that - ZlibStream, providing uncompressed data as input. The data sent to - the output stream will be the compressed form of the data written. If you - wish to use the ZlibStream to decompress data while writing, you - can create a ZlibStream with CompressionMode.Decompress, and a - writable output stream. Then call Write() on that stream, - providing previously compressed data. The data sent to the output stream - will be the decompressed form of the data written. - - - - A ZlibStream can be used for Read() or Write(), but not both. - - - The buffer holding data to write to the stream. - the offset within that data array to find the first byte to write. - the number of bytes to write. - - - - Compress a string into a byte array using ZLIB. - - - - Uncompress it with . - - - - - - - - A string to compress. The string will first be encoded - using UTF8, then compressed. - - - The string in compressed form - - - - Compress a byte array into a new byte array using ZLIB. - - - - Uncompress it with . - - - - - - - A buffer to compress. - - - The data in compressed form - - - - Uncompress a ZLIB-compressed byte array into a single string. - - - - - - - A buffer containing ZLIB-compressed data. - - - The uncompressed string - - - - Uncompress a ZLIB-compressed byte array into a byte array. - - - - - - - A buffer containing ZLIB-compressed data. - - - The data in uncompressed form - - - - A bunch of constants used in the Zlib interface. - - - - - The maximum number of window bits for the Deflate algorithm. - - - - - The default number of window bits for the Deflate algorithm. - - - - - indicates everything is A-OK - - - - - Indicates that the last operation reached the end of the stream. - - - - - The operation ended in need of a dictionary. - - - - - There was an error with the stream - not enough data, not open and readable, etc. - - - - - There was an error with the data - not enough data, bad data, etc. - - - - - There was an error with the working buffer. - - - - - The size of the working buffer used in the ZlibCodec class. Defaults to 8192 bytes. - - - - - The minimum size of the working buffer used in the ZlibCodec class. Currently it is 128 bytes. - - - - - Encoder and Decoder for ZLIB and DEFLATE (IETF RFC1950 and RFC1951). - - - - This class compresses and decompresses data according to the Deflate algorithm - and optionally, the ZLIB format, as documented in RFC 1950 - ZLIB and RFC 1951 - DEFLATE. - - - - - The buffer from which data is taken. - - - - - An index into the InputBuffer array, indicating where to start reading. - - - - - The number of bytes available in the InputBuffer, starting at NextIn. - - - Generally you should set this to InputBuffer.Length before the first Inflate() or Deflate() call. - The class will update this number as calls to Inflate/Deflate are made. - - - - - Total number of bytes read so far, through all calls to Inflate()/Deflate(). - - - - - Buffer to store output data. - - - - - An index into the OutputBuffer array, indicating where to start writing. - - - - - The number of bytes available in the OutputBuffer, starting at NextOut. - - - Generally you should set this to OutputBuffer.Length before the first Inflate() or Deflate() call. - The class will update this number as calls to Inflate/Deflate are made. - - - - - Total number of bytes written to the output so far, through all calls to Inflate()/Deflate(). - - - - - used for diagnostics, when something goes wrong! - - - - - The compression level to use in this codec. Useful only in compression mode. - - - - - The number of Window Bits to use. - - - This gauges the size of the sliding window, and hence the - compression effectiveness as well as memory consumption. It's best to just leave this - setting alone if you don't know what it is. The maximum value is 15 bits, which implies - a 32k window. - - - - - The compression strategy to use. - - - This is only effective in compression. The theory offered by ZLIB is that different - strategies could potentially produce significant differences in compression behavior - for different data sets. Unfortunately I don't have any good recommendations for how - to set it differently. When I tested changing the strategy I got minimally different - compression performance. It's best to leave this property alone if you don't have a - good feel for it. Or, you may want to produce a test harness that runs through the - different strategy options and evaluates them on different file types. If you do that, - let me know your results. - - - - - The Adler32 checksum on the data transferred through the codec so far. You probably don't need to look at this. - - - - - Create a ZlibCodec. - - - If you use this default constructor, you will later have to explicitly call - InitializeInflate() or InitializeDeflate() before using the ZlibCodec to compress - or decompress. - - - - - Create a ZlibCodec that either compresses or decompresses. - - - Indicates whether the codec should compress (deflate) or decompress (inflate). - - - - - Initialize the inflation state. - - - It is not necessary to call this before using the ZlibCodec to inflate data; - It is implicitly called when you call the constructor. - - Z_OK if everything goes well. - - - - Initialize the inflation state with an explicit flag to - govern the handling of RFC1950 header bytes. - - - - By default, the ZLIB header defined in RFC 1950 is expected. If - you want to read a zlib stream you should specify true for - expectRfc1950Header. If you have a deflate stream, you will want to specify - false. It is only necessary to invoke this initializer explicitly if you - want to specify false. - - - whether to expect an RFC1950 header byte - pair when reading the stream of data to be inflated. - - Z_OK if everything goes well. - - - - Initialize the ZlibCodec for inflation, with the specified number of window bits. - - The number of window bits to use. If you need to ask what that is, - then you shouldn't be calling this initializer. - Z_OK if all goes well. - - - - Initialize the inflation state with an explicit flag to govern the handling of - RFC1950 header bytes. - - - - If you want to read a zlib stream you should specify true for - expectRfc1950Header. In this case, the library will expect to find a ZLIB - header, as defined in RFC - 1950, in the compressed stream. If you will be reading a DEFLATE or - GZIP stream, which does not have such a header, you will want to specify - false. - - - whether to expect an RFC1950 header byte pair when reading - the stream of data to be inflated. - The number of window bits to use. If you need to ask what that is, - then you shouldn't be calling this initializer. - Z_OK if everything goes well. - - - - Inflate the data in the InputBuffer, placing the result in the OutputBuffer. - - - You must have set InputBuffer and OutputBuffer, NextIn and NextOut, and AvailableBytesIn and - AvailableBytesOut before calling this method. - - - - private void InflateBuffer() - { - int bufferSize = 1024; - byte[] buffer = new byte[bufferSize]; - ZlibCodec decompressor = new ZlibCodec(); - - Console.WriteLine("\n============================================"); - Console.WriteLine("Size of Buffer to Inflate: {0} bytes.", CompressedBytes.Length); - MemoryStream ms = new MemoryStream(DecompressedBytes); - - int rc = decompressor.InitializeInflate(); - - decompressor.InputBuffer = CompressedBytes; - decompressor.NextIn = 0; - decompressor.AvailableBytesIn = CompressedBytes.Length; - - decompressor.OutputBuffer = buffer; - - // pass 1: inflate - do - { - decompressor.NextOut = 0; - decompressor.AvailableBytesOut = buffer.Length; - rc = decompressor.Inflate(FlushType.None); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - throw new Exception("inflating: " + decompressor.Message); - - ms.Write(decompressor.OutputBuffer, 0, buffer.Length - decompressor.AvailableBytesOut); - } - while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); - - // pass 2: finish and flush - do - { - decompressor.NextOut = 0; - decompressor.AvailableBytesOut = buffer.Length; - rc = decompressor.Inflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) - throw new Exception("inflating: " + decompressor.Message); - - if (buffer.Length - decompressor.AvailableBytesOut > 0) - ms.Write(buffer, 0, buffer.Length - decompressor.AvailableBytesOut); - } - while (decompressor.AvailableBytesIn > 0 || decompressor.AvailableBytesOut == 0); - - decompressor.EndInflate(); - } - - - - The flush to use when inflating. - Z_OK if everything goes well. - - - - Ends an inflation session. - - - Call this after successively calling Inflate(). This will cause all buffers to be flushed. - After calling this you cannot call Inflate() without a intervening call to one of the - InitializeInflate() overloads. - - Z_OK if everything goes well. - - - - I don't know what this does! - - Z_OK if everything goes well. - - - - Initialize the ZlibCodec for deflation operation. - - - The codec will use the MAX window bits and the default level of compression. - - - - int bufferSize = 40000; - byte[] CompressedBytes = new byte[bufferSize]; - byte[] DecompressedBytes = new byte[bufferSize]; - - ZlibCodec compressor = new ZlibCodec(); - - compressor.InitializeDeflate(CompressionLevel.Default); - - compressor.InputBuffer = System.Text.ASCIIEncoding.ASCII.GetBytes(TextToCompress); - compressor.NextIn = 0; - compressor.AvailableBytesIn = compressor.InputBuffer.Length; - - compressor.OutputBuffer = CompressedBytes; - compressor.NextOut = 0; - compressor.AvailableBytesOut = CompressedBytes.Length; - - while (compressor.TotalBytesIn != TextToCompress.Length && compressor.TotalBytesOut < bufferSize) - { - compressor.Deflate(FlushType.None); - } - - while (true) - { - int rc= compressor.Deflate(FlushType.Finish); - if (rc == ZlibConstants.Z_STREAM_END) break; - } - - compressor.EndDeflate(); - - - - Z_OK if all goes well. You generally don't need to check the return code. - - - - Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel. - - - The codec will use the maximum window bits (15) and the specified - CompressionLevel. It will emit a ZLIB stream as it compresses. - - The compression level for the codec. - Z_OK if all goes well. - - - - Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, - and the explicit flag governing whether to emit an RFC1950 header byte pair. - - - The codec will use the maximum window bits (15) and the specified CompressionLevel. - If you want to generate a zlib stream, you should specify true for - wantRfc1950Header. In this case, the library will emit a ZLIB - header, as defined in RFC - 1950, in the compressed stream. - - The compression level for the codec. - whether to emit an initial RFC1950 byte pair in the compressed stream. - Z_OK if all goes well. - - - - Initialize the ZlibCodec for deflation operation, using the specified CompressionLevel, - and the specified number of window bits. - - - The codec will use the specified number of window bits and the specified CompressionLevel. - - The compression level for the codec. - the number of window bits to use. If you don't know what this means, don't use this method. - Z_OK if all goes well. - - - - Initialize the ZlibCodec for deflation operation, using the specified - CompressionLevel, the specified number of window bits, and the explicit flag - governing whether to emit an RFC1950 header byte pair. - - - The compression level for the codec. - whether to emit an initial RFC1950 byte pair in the compressed stream. - the number of window bits to use. If you don't know what this means, don't use this method. - Z_OK if all goes well. - - - - Deflate one batch of data. - - - You must have set InputBuffer and OutputBuffer before calling this method. - - - - private void DeflateBuffer(CompressionLevel level) - { - int bufferSize = 1024; - byte[] buffer = new byte[bufferSize]; - ZlibCodec compressor = new ZlibCodec(); - - Console.WriteLine("\n============================================"); - Console.WriteLine("Size of Buffer to Deflate: {0} bytes.", UncompressedBytes.Length); - MemoryStream ms = new MemoryStream(); - - int rc = compressor.InitializeDeflate(level); - - compressor.InputBuffer = UncompressedBytes; - compressor.NextIn = 0; - compressor.AvailableBytesIn = UncompressedBytes.Length; - - compressor.OutputBuffer = buffer; - - // pass 1: deflate - do - { - compressor.NextOut = 0; - compressor.AvailableBytesOut = buffer.Length; - rc = compressor.Deflate(FlushType.None); - - if (rc != ZlibConstants.Z_OK && rc != ZlibConstants.Z_STREAM_END) - throw new Exception("deflating: " + compressor.Message); - - ms.Write(compressor.OutputBuffer, 0, buffer.Length - compressor.AvailableBytesOut); - } - while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); - - // pass 2: finish and flush - do - { - compressor.NextOut = 0; - compressor.AvailableBytesOut = buffer.Length; - rc = compressor.Deflate(FlushType.Finish); - - if (rc != ZlibConstants.Z_STREAM_END && rc != ZlibConstants.Z_OK) - throw new Exception("deflating: " + compressor.Message); - - if (buffer.Length - compressor.AvailableBytesOut > 0) - ms.Write(buffer, 0, buffer.Length - compressor.AvailableBytesOut); - } - while (compressor.AvailableBytesIn > 0 || compressor.AvailableBytesOut == 0); - - compressor.EndDeflate(); - - ms.Seek(0, SeekOrigin.Begin); - CompressedBytes = new byte[compressor.TotalBytesOut]; - ms.Read(CompressedBytes, 0, CompressedBytes.Length); - } - - - whether to flush all data as you deflate. Generally you will want to - use Z_NO_FLUSH here, in a series of calls to Deflate(), and then call EndDeflate() to - flush everything. - - Z_OK if all goes well. - - - - End a deflation session. - - - Call this after making a series of one or more calls to Deflate(). All buffers are flushed. - - Z_OK if all goes well. - - - - Reset a codec for another deflation session. - - - Call this to reset the deflation state. For example if a thread is deflating - non-consecutive blocks, you can call Reset() after the Deflate(Sync) of the first - block and before the next Deflate(None) of the second block. - - Z_OK if all goes well. - - - - Set the CompressionStrategy and CompressionLevel for a deflation session. - - the level of compression to use. - the strategy to use for compression. - Z_OK if all goes well. - - - - Set the dictionary to be used for either Inflation or Deflation. - - The dictionary bytes to use. - Z_OK if all goes well. - - - - Describes how to flush the current deflate operation. - - - The different FlushType values are useful when using a Deflate in a streaming application. - - - - No flush at all. - - - Closes the current block, but doesn't flush it to - the output. Used internally only in hypothetical - scenarios. This was supposed to be removed by Zlib, but it is - still in use in some edge cases. - - - - - Use this during compression to specify that all pending output should be - flushed to the output buffer and the output should be aligned on a byte - boundary. You might use this in a streaming communication scenario, so that - the decompressor can get all input data available so far. When using this - with a ZlibCodec, AvailableBytesIn will be zero after the call if - enough output space has been provided before the call. Flushing will - degrade compression and so it should be used only when necessary. - - - - - Use this during compression to specify that all output should be flushed, as - with FlushType.Sync, but also, the compression state should be reset - so that decompression can restart from this point if previous compressed - data has been damaged or if random access is desired. Using - FlushType.Full too often can significantly degrade the compression. - - - - Signals the end of the compression/decompression stream. - - - - The compression level to be used when using a DeflateStream or ZlibStream with CompressionMode.Compress. - - - - - None means that the data will be simply stored, with no change at all. - If you are producing ZIPs for use on Mac OSX, be aware that archives produced with CompressionLevel.None - cannot be opened with the default zip reader. Use a different CompressionLevel. - - - - - Same as None. - - - - - The fastest but least effective compression. - - - - - A synonym for BestSpeed. - - - - - A little slower, but better, than level 1. - - - - - A little slower, but better, than level 2. - - - - - A little slower, but better, than level 3. - - - - - A little slower than level 4, but with better compression. - - - - - The default compression level, with a good balance of speed and compression efficiency. - - - - - A synonym for Default. - - - - - Pretty good compression! - - - - - Better compression than Level7! - - - - - The "best" compression, where best means greatest reduction in size of the input data stream. - This is also the slowest compression. - - - - - A synonym for BestCompression. - - - - - Describes options for how the compression algorithm is executed. Different strategies - work better on different sorts of data. The strategy parameter can affect the compression - ratio and the speed of compression but not the correctness of the compresssion. - - - - - The default strategy is probably the best for normal data. - - - - - The Filtered strategy is intended to be used most effectively with data produced by a - filter or predictor. By this definition, filtered data consists mostly of small - values with a somewhat random distribution. In this case, the compression algorithm - is tuned to compress them better. The effect of Filtered is to force more Huffman - coding and less string matching; it is a half-step between Default and HuffmanOnly. - - - - - Using HuffmanOnly will force the compressor to do Huffman encoding only, with no - string matching. - - - - - An enum to specify the direction of transcoding - whether to compress or decompress. - - - - - Used to specify that the stream should compress the data. - - - - - Used to specify that the stream should decompress the data. - - - - - A general purpose exception class for exceptions in the Zlib library. - - - - - The ZlibException class captures exception information generated - by the Zlib library. - - - - - This ctor collects a message attached to the exception. - - the message for the exception. - - - - Performs an unsigned bitwise right shift with the specified number - - Number to operate on - Ammount of bits to shift - The resulting number from the shift operation - - - - Reads a number of characters from the current source TextReader and writes - the data to the target array at the specified index. - - - The source TextReader to read from - Contains the array of characteres read from the source TextReader. - The starting index of the target array. - The maximum number of characters to read from the source TextReader. - - - The number of characters read. The number will be less than or equal to - count depending on the data available in the source TextReader. Returns -1 - if the end of the stream is reached. - - - - - Computes an Adler-32 checksum. - - - The Adler checksum is similar to a CRC checksum, but faster to compute, though less - reliable. It is used in producing RFC1950 compressed streams. The Adler checksum - is a required part of the "ZLIB" standard. Applications will almost never need to - use this class directly. - - - - - - - Calculates the Adler32 checksum. - - - - This is used within ZLIB. You probably don't need to use this directly. - - - - To compute an Adler32 checksum on a byte array: - - var adler = Adler.Adler32(0, null, 0, 0); - adler = Adler.Adler32(adler, buffer, index, length); - - - - - - Map from a distance to a distance code. - - - No side effects. _dist_code[256] and _dist_code[257] are never used. - - - - - A class for compressing and decompressing GZIP streams. - - - - - The GZipStream is a Decorator on a - . It adds GZIP compression or decompression to any - stream. - - - - Like the System.IO.Compression.GZipStream in the .NET Base Class Library, the - Ionic.Zlib.GZipStream can compress while writing, or decompress while - reading, but not vice versa. The compression method used is GZIP, which is - documented in IETF RFC - 1952, "GZIP file format specification version 4.3". - - - A GZipStream can be used to decompress data (through Read()) or - to compress data (through Write()), but not both. - - - - If you wish to use the GZipStream to compress data, you must wrap it - around a write-able stream. As you call Write() on the GZipStream, the - data will be compressed into the GZIP format. If you want to decompress data, - you must wrap the GZipStream around a readable stream that contains an - IETF RFC 1952-compliant stream. The data will be decompressed as you call - Read() on the GZipStream. - - - - Though the GZIP format allows data from multiple files to be concatenated - together, this stream handles only a single segment of GZIP format, typically - representing a single file. - - - - - - - - The comment on the GZIP stream. - - - - - The GZIP format allows for each file to optionally have an associated - comment stored with the file. The comment is encoded with the ISO-8859-1 - code page. To include a comment in a GZIP stream you create, set this - property before calling Write() for the first time on the - GZipStream. - - - - When using GZipStream to decompress, you can retrieve this property - after the first call to Read(). If no comment has been set in the - GZIP bytestream, the Comment property will return null - (Nothing in VB). - - - - - - The FileName for the GZIP stream. - - - - - - The GZIP format optionally allows each file to have an associated - filename. When compressing data (through Write()), set this - FileName before calling Write() the first time on the GZipStream. - The actual filename is encoded into the GZIP bytestream with the - ISO-8859-1 code page, according to RFC 1952. It is the application's - responsibility to insure that the FileName can be encoded and decoded - correctly with this code page. - - - - When decompressing (through Read()), you can retrieve this value - any time after the first Read(). In the case where there was no filename - encoded into the GZIP bytestream, the property will return null (Nothing - in VB). - - - - - - The last modified time for the GZIP stream. - - - - GZIP allows the storage of a last modified time with each GZIP entry. - When compressing data, you can set this before the first call to - Write(). When decompressing, you can retrieve this value any time - after the first call to Read(). - - - - - The CRC on the GZIP stream. - - - This is used for internal error checking. You probably don't need to look at this property. - - - - - Create a GZipStream using the specified CompressionMode. - - - - - When mode is CompressionMode.Compress, the GZipStream will use the - default compression level. - - - - As noted in the class documentation, the CompressionMode (Compress - or Decompress) also establishes the "direction" of the stream. A - GZipStream with CompressionMode.Compress works only through - Write(). A GZipStream with - CompressionMode.Decompress works only through Read(). - - - - - - This example shows how to use a GZipStream to compress data. - - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (var raw = System.IO.File.Create(outputFile)) - { - using (Stream compressor = new GZipStream(raw, CompressionMode.Compress)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - } - - - Dim outputFile As String = (fileToCompress & ".compressed") - Using input As Stream = File.OpenRead(fileToCompress) - Using raw As FileStream = File.Create(outputFile) - Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - End Using - - - - - This example shows how to use a GZipStream to uncompress a file. - - private void GunZipFile(string filename) - { - if (!filename.EndsWith(".gz)) - throw new ArgumentException("filename"); - var DecompressedFile = filename.Substring(0,filename.Length-3); - byte[] working = new byte[WORKING_BUFFER_SIZE]; - int n= 1; - using (System.IO.Stream input = System.IO.File.OpenRead(filename)) - { - using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) - { - using (var output = System.IO.File.Create(DecompressedFile)) - { - while (n !=0) - { - n= decompressor.Read(working, 0, working.Length); - if (n > 0) - { - output.Write(working, 0, n); - } - } - } - } - } - } - - - - Private Sub GunZipFile(ByVal filename as String) - If Not (filename.EndsWith(".gz)) Then - Throw New ArgumentException("filename") - End If - Dim DecompressedFile as String = filename.Substring(0,filename.Length-3) - Dim working(WORKING_BUFFER_SIZE) as Byte - Dim n As Integer = 1 - Using input As Stream = File.OpenRead(filename) - Using decompressor As Stream = new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, True) - Using output As Stream = File.Create(UncompressedFile) - Do - n= decompressor.Read(working, 0, working.Length) - If n > 0 Then - output.Write(working, 0, n) - End IF - Loop While (n > 0) - End Using - End Using - End Using - End Sub - - - - The stream which will be read or written. - Indicates whether the GZipStream will compress or decompress. - - - - Create a GZipStream using the specified CompressionMode and - the specified CompressionLevel. - - - - - The CompressionMode (Compress or Decompress) also establishes the - "direction" of the stream. A GZipStream with - CompressionMode.Compress works only through Write(). A - GZipStream with CompressionMode.Decompress works only - through Read(). - - - - - - - This example shows how to use a GZipStream to compress a file into a .gz file. - - - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (var raw = System.IO.File.Create(fileToCompress + ".gz")) - { - using (Stream compressor = new GZipStream(raw, - CompressionMode.Compress, - CompressionLevel.BestCompression)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - } - - - - Using input As Stream = File.OpenRead(fileToCompress) - Using raw As FileStream = File.Create(fileToCompress & ".gz") - Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - End Using - - - The stream to be read or written while deflating or inflating. - Indicates whether the GZipStream will compress or decompress. - A tuning knob to trade speed for effectiveness. - - - - Create a GZipStream using the specified CompressionMode, and - explicitly specify whether the stream should be left open after Deflation - or Inflation. - - - - - This constructor allows the application to request that the captive stream - remain open after the deflation or inflation occurs. By default, after - Close() is called on the stream, the captive stream is also - closed. In some cases this is not desired, for example if the stream is a - memory stream that will be re-read after compressed data has been written - to it. Specify true for the parameter to leave - the stream open. - - - - The (Compress or Decompress) also - establishes the "direction" of the stream. A GZipStream with - CompressionMode.Compress works only through Write(). A GZipStream - with CompressionMode.Decompress works only through Read(). - - - - The GZipStream will use the default compression level. If you want - to specify the compression level, see . - - - - See the other overloads of this constructor for example code. - - - - - - The stream which will be read or written. This is called the "captive" - stream in other places in this documentation. - - - Indicates whether the GZipStream will compress or decompress. - - - - true if the application would like the base stream to remain open after - inflation/deflation. - - - - - Create a GZipStream using the specified CompressionMode and the - specified CompressionLevel, and explicitly specify whether the - stream should be left open after Deflation or Inflation. - - - - - - This constructor allows the application to request that the captive stream - remain open after the deflation or inflation occurs. By default, after - Close() is called on the stream, the captive stream is also - closed. In some cases this is not desired, for example if the stream is a - memory stream that will be re-read after compressed data has been written - to it. Specify true for the parameter to - leave the stream open. - - - - As noted in the class documentation, the CompressionMode (Compress - or Decompress) also establishes the "direction" of the stream. A - GZipStream with CompressionMode.Compress works only through - Write(). A GZipStream with CompressionMode.Decompress works only - through Read(). - - - - - - This example shows how to use a GZipStream to compress data. - - using (System.IO.Stream input = System.IO.File.OpenRead(fileToCompress)) - { - using (var raw = System.IO.File.Create(outputFile)) - { - using (Stream compressor = new GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, true)) - { - byte[] buffer = new byte[WORKING_BUFFER_SIZE]; - int n; - while ((n= input.Read(buffer, 0, buffer.Length)) != 0) - { - compressor.Write(buffer, 0, n); - } - } - } - } - - - Dim outputFile As String = (fileToCompress & ".compressed") - Using input As Stream = File.OpenRead(fileToCompress) - Using raw As FileStream = File.Create(outputFile) - Using compressor As Stream = New GZipStream(raw, CompressionMode.Compress, CompressionLevel.BestCompression, True) - Dim buffer As Byte() = New Byte(4096) {} - Dim n As Integer = -1 - Do While (n <> 0) - If (n > 0) Then - compressor.Write(buffer, 0, n) - End If - n = input.Read(buffer, 0, buffer.Length) - Loop - End Using - End Using - End Using - - - The stream which will be read or written. - Indicates whether the GZipStream will compress or decompress. - true if the application would like the stream to remain open after inflation/deflation. - A tuning knob to trade speed for effectiveness. - - - - This property sets the flush behavior on the stream. - - - - - The size of the working buffer for the compression codec. - - - - - The working buffer is used for all stream operations. The default size is - 1024 bytes. The minimum size is 128 bytes. You may get better performance - with a larger buffer. Then again, you might not. You would have to test - it. - - - - Set this before the first call to Read() or Write() on the - stream. If you try to set it afterwards, it will throw. - - - - - Returns the total number of bytes input so far. - - - Returns the total number of bytes output so far. - - - - Dispose the stream. - - - - This may or may not result in a Close() call on the captive - stream. See the constructors that have a leaveOpen parameter - for more information. - - - This method may be invoked in two distinct scenarios. If disposing - == true, the method has been called directly or indirectly by a - user's code, for example via the internal Dispose() method. In this - case, both managed and unmanaged resources can be referenced and - disposed. If disposing == false, the method has been called by the - runtime from inside the object finalizer and this method should not - reference other objects; in that case only unmanaged resources must - be referenced or disposed. - - - - indicates whether the Dispose method was invoked by user code. - - - - - Indicates whether the stream can be read. - - - The return value depends on whether the captive stream supports reading. - - - - - Indicates whether the stream supports Seek operations. - - - Always returns false. - - - - - Indicates whether the stream can be written. - - - The return value depends on whether the captive stream supports writing. - - - - - Flush the stream. - - - - - Reading this property always throws a . - - - - - The position of the stream pointer. - - - - Setting this property always throws a . Reading will return the total bytes - written out, if used in writing, or the total bytes read in, if used in - reading. The count may refer to compressed bytes or uncompressed bytes, - depending on how you've used the stream. - - - - - Read and decompress data from the source stream. - - - - With a GZipStream, decompression is done through reading. - - - - - byte[] working = new byte[WORKING_BUFFER_SIZE]; - using (System.IO.Stream input = System.IO.File.OpenRead(_CompressedFile)) - { - using (Stream decompressor= new Ionic.Zlib.GZipStream(input, CompressionMode.Decompress, true)) - { - using (var output = System.IO.File.Create(_DecompressedFile)) - { - int n; - while ((n= decompressor.Read(working, 0, working.Length)) !=0) - { - output.Write(working, 0, n); - } - } - } - } - - - The buffer into which the decompressed data should be placed. - the offset within that data array to put the first byte read. - the number of bytes to read. - the number of bytes actually read - - - - Calling this method always throws a . - - irrelevant; it will always throw! - irrelevant; it will always throw! - irrelevant! - - - - Calling this method always throws a . - - irrelevant; this method will always throw! - - - - Write data to the stream. - - - - - If you wish to use the GZipStream to compress data while writing, - you can create a GZipStream with CompressionMode.Compress, and a - writable output stream. Then call Write() on that GZipStream, - providing uncompressed data as input. The data sent to the output stream - will be the compressed form of the data written. - - - - A GZipStream can be used for Read() or Write(), but not - both. Writing implies compression. Reading implies decompression. - - - - The buffer holding data to write to the stream. - the offset within that data array to find the first byte to write. - the number of bytes to write. - - - - Compress a string into a byte array using GZip. - - - - Uncompress it with . - - - - - - - A string to compress. The string will first be encoded - using UTF8, then compressed. - - - The string in compressed form - - - - Compress a byte array into a new byte array using GZip. - - - - Uncompress it with . - - - - - - - A buffer to compress. - - - The data in compressed form - - - - Uncompress a GZip'ed byte array into a single string. - - - - - - - A buffer containing GZIP-compressed data. - - - The uncompressed string - - - - Uncompress a GZip'ed byte array into a byte array. - - - - - - - A buffer containing data that has been compressed with GZip. - - - The data in uncompressed form - - - - Computes a CRC-32. The CRC-32 algorithm is parameterized - you - can set the polynomial and enable or disable bit - reversal. This can be used for GZIP, BZip2, or ZIP. - - - This type is used internally by DotNetZip; it is generally not used - directly by applications wishing to create, read, or manipulate zip - archive files. - - - - - Indicates the total number of bytes applied to the CRC. - - - - - Indicates the current CRC for all blocks slurped in. - - - - - Returns the CRC32 for the specified stream. - - The stream over which to calculate the CRC32 - the CRC32 calculation - - - - Returns the CRC32 for the specified stream, and writes the input into the - output stream. - - The stream over which to calculate the CRC32 - The stream into which to deflate the input - the CRC32 calculation - - - - Get the CRC32 for the given (word,byte) combo. This is a - computation defined by PKzip for PKZIP 2.0 (weak) encryption. - - The word to start with. - The byte to combine it with. - The CRC-ized result. - - - - Update the value for the running CRC32 using the given block of bytes. - This is useful when using the CRC32() class in a Stream. - - block of bytes to slurp - starting point in the block - how many bytes within the block to slurp - - - - Process one byte in the CRC. - - the byte to include into the CRC . - - - - Process a run of N identical bytes into the CRC. - - - - This method serves as an optimization for updating the CRC when a - run of identical bytes is found. Rather than passing in a buffer of - length n, containing all identical bytes b, this method accepts the - byte value and the length of the (virtual) buffer - the length of - the run. - - - the byte to include into the CRC. - the number of times that byte should be repeated. - - - - Combines the given CRC32 value with the current running total. - - - This is useful when using a divide-and-conquer approach to - calculating a CRC. Multiple threads can each calculate a - CRC32 on a segment of the data, and then combine the - individual CRC32 values at the end. - - the crc value to be combined with this one - the length of data the CRC value was calculated on - - - - Create an instance of the CRC32 class using the default settings: no - bit reversal, and a polynomial of 0xEDB88320. - - - - - Create an instance of the CRC32 class, specifying whether to reverse - data bits or not. - - - specify true if the instance should reverse data bits. - - - - In the CRC-32 used by BZip2, the bits are reversed. Therefore if you - want a CRC32 with compatibility with BZip2, you should pass true - here. In the CRC-32 used by GZIP and PKZIP, the bits are not - reversed; Therefore if you want a CRC32 with compatibility with - those, you should pass false. - - - - - - Create an instance of the CRC32 class, specifying the polynomial and - whether to reverse data bits or not. - - - The polynomial to use for the CRC, expressed in the reversed (LSB) - format: the highest ordered bit in the polynomial value is the - coefficient of the 0th power; the second-highest order bit is the - coefficient of the 1 power, and so on. Expressed this way, the - polynomial for the CRC-32C used in IEEE 802.3, is 0xEDB88320. - - - specify true if the instance should reverse data bits. - - - - - In the CRC-32 used by BZip2, the bits are reversed. Therefore if you - want a CRC32 with compatibility with BZip2, you should pass true - here for the reverseBits parameter. In the CRC-32 used by - GZIP and PKZIP, the bits are not reversed; Therefore if you want a - CRC32 with compatibility with those, you should pass false for the - reverseBits parameter. - - - - - - Reset the CRC-32 class - clear the CRC "remainder register." - - - - Use this when employing a single instance of this class to compute - multiple, distinct CRCs on multiple, distinct data blocks. - - - - - - A Stream that calculates a CRC32 (a checksum) on all bytes read, - or on all bytes written. - - - - - This class can be used to verify the CRC of a ZipEntry when - reading from a stream, or to calculate a CRC when writing to a - stream. The stream should be used to either read, or write, but - not both. If you intermix reads and writes, the results are not - defined. - - - - This class is intended primarily for use internally by the - DotNetZip library. - - - - - - The default constructor. - - - - Instances returned from this constructor will leave the underlying - stream open upon Close(). The stream uses the default CRC32 - algorithm, which implies a polynomial of 0xEDB88320. - - - The underlying stream - - - - The constructor allows the caller to specify how to handle the - underlying stream at close. - - - - The stream uses the default CRC32 algorithm, which implies a - polynomial of 0xEDB88320. - - - The underlying stream - true to leave the underlying stream - open upon close of the CrcCalculatorStream; false otherwise. - - - - A constructor allowing the specification of the length of the stream - to read. - - - - The stream uses the default CRC32 algorithm, which implies a - polynomial of 0xEDB88320. - - - Instances returned from this constructor will leave the underlying - stream open upon Close(). - - - The underlying stream - The length of the stream to slurp - - - - A constructor allowing the specification of the length of the stream - to read, as well as whether to keep the underlying stream open upon - Close(). - - - - The stream uses the default CRC32 algorithm, which implies a - polynomial of 0xEDB88320. - - - The underlying stream - The length of the stream to slurp - true to leave the underlying stream - open upon close of the CrcCalculatorStream; false otherwise. - - - - A constructor allowing the specification of the length of the stream - to read, as well as whether to keep the underlying stream open upon - Close(), and the CRC32 instance to use. - - - - The stream uses the specified CRC32 instance, which allows the - application to specify how the CRC gets calculated. - - - The underlying stream - The length of the stream to slurp - true to leave the underlying stream - open upon close of the CrcCalculatorStream; false otherwise. - the CRC32 instance to use to calculate the CRC32 - - - - Gets the total number of bytes run through the CRC32 calculator. - - - - This is either the total number of bytes read, or the total number of - bytes written, depending on the direction of this stream. - - - - - Provides the current CRC for all blocks slurped in. - - - - The running total of the CRC is kept as data is written or read - through the stream. read this property after all reads or writes to - get an accurate CRC for the entire stream. - - - - - - Indicates whether the underlying stream will be left open when the - CrcCalculatorStream is Closed. - - - - Set this at any point before calling . - - - - - - Read from the stream - - the buffer to read - the offset at which to start - the number of bytes to read - the number of bytes actually read - - - - Write to the stream. - - the buffer from which to write - the offset at which to start writing - the number of bytes to write - - - - Indicates whether the stream supports reading. - - - - - Indicates whether the stream supports seeking. - - - - Always returns false. - - - - - - Indicates whether the stream supports writing. - - - - - Flush the stream. - - - - - Returns the length of the underlying stream. - - - - - The getter for this property returns the total bytes read. - If you use the setter, it will throw - . - - - - - Seeking is not supported on this stream. This method always throws - - - N/A - N/A - N/A - - - - This method always throws - - - N/A - - - - A custom encoding class that provides encoding capabilities for the - 'Western European (ISO)' encoding under Silverlight.
- This class was generated by a tool. For more information, visit - http://www.hardcodet.net/2010/03/silverlight-text-encoding-class-generator -
-
- - - Gets the name registered with the - Internet Assigned Numbers Authority (IANA) for the current encoding. - - - The IANA name for the current . - - - - - A character that can be set in order to make the encoding class - more fault tolerant. If this property is set, the encoding class will - use this property instead of throwing an exception if an unsupported - byte value is being passed for decoding. - - - - - A byte value that corresponds to the . - It is used in encoding scenarios in case an unsupported character is - being passed for encoding. - - - - - Encodes a set of characters from the specified character array into the specified byte array. - - - The actual number of bytes written into . - - The character array containing the set of characters to encode. - The index of the first character to encode. - The number of characters to encode. - The byte array to contain the resulting sequence of bytes. - The index at which to start writing the resulting sequence of bytes. - - - - - Decodes a sequence of bytes from the specified byte array into the specified character array. - - - The actual number of characters written into . - - The byte array containing the sequence of bytes to decode. - The index of the first byte to decode. - The number of bytes to decode. - The character array to contain the resulting set of characters. - The index at which to start writing the resulting set of characters. - - - - - Calculates the number of bytes produced by encoding a set of characters - from the specified character array. - - - The number of bytes produced by encoding the specified characters. This class - always returns the value of . - - - - - Calculates the number of characters produced by decoding a sequence - of bytes from the specified byte array. - - - The number of characters produced by decoding the specified sequence of bytes. This class - always returns the value of . - - - - - Calculates the maximum number of bytes produced by encoding the specified number of characters. - - - The maximum number of bytes produced by encoding the specified number of characters. This - class always returns the value of . - - The number of characters to encode. - - - - - Calculates the maximum number of characters produced by decoding the specified number of bytes. - - - The maximum number of characters produced by decoding the specified number of bytes. This class - always returns the value of . - - The number of bytes to decode. - - - - Gets the number of characters that are supported by this encoding. - This property returns a maximum value of 256, as the encoding class - only supports single byte encodings (1 byte == 256 possible values). - - - - - This table contains characters in an array. The index within the - array corresponds to the encoding's mapping of bytes to characters - (e.g. if a byte value of 5 is used to encode the character 'x', this - character will be stored at the array index 5. - - - - - This dictionary is used to resolve byte values for a given character. - - - - - Length of Data field - - - - - CRC of both Type and Data fields, but not Length field - - - - - Build CRC lookup table for performance (once-off) - - - - - Applies all PNG filters to the given scanline and returns the filtered scanline that is deemed - to be most compressible, using lowest total variation as proxy for compressibility. - - - - - - - - - Calculates the total variation of given byte array. Total variation is the sum of the absolute values of - neighbour differences. - - - - - - - Get a buffer that is at least as big as size. - - - - - Return the given buffer to the pool. - - - - - - Use this event to retrieve text for objects like textbox's. - This event is not raised by noncharacter keys. - This event also supports key repeat. - - -
-
diff --git a/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll b/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll deleted file mode 100644 index bd26e74c0..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll.config b/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll.config deleted file mode 100644 index 7098d39e9..000000000 --- a/TSOClient/tso.client/Monogame/MacOS/OpenTK.dll.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll b/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll deleted file mode 100644 index d2e2d4794..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll.config b/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll.config deleted file mode 100644 index ec83f1e92..000000000 --- a/TSOClient/tso.client/Monogame/MacOS/Tao.Sdl.dll.config +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TSOClient/tso.client/Monogame/MacOS/Xamarin.Mac.dll b/TSOClient/tso.client/Monogame/MacOS/Xamarin.Mac.dll deleted file mode 100644 index b9ff792c2..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/Xamarin.Mac.dll and /dev/null differ diff --git a/TSOClient/tso.client/Monogame/MacOS/libxammac.dylib b/TSOClient/tso.client/Monogame/MacOS/libxammac.dylib deleted file mode 100644 index dfa638333..000000000 Binary files a/TSOClient/tso.client/Monogame/MacOS/libxammac.dylib and /dev/null differ diff --git a/TSOClient/tso.client/Network/CharacterCreationStatus.cs b/TSOClient/tso.client/Network/CharacterCreationStatus.cs deleted file mode 100644 index 9b3c3e72c..000000000 --- a/TSOClient/tso.client/Network/CharacterCreationStatus.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Network -{ - public enum CharacterCreationStatus - { - NameAlreadyExisted, - ExceededCharacterLimit, - Success, - GeneralError - } -} diff --git a/TSOClient/tso.client/Network/CityInfo.cs b/TSOClient/tso.client/Network/CityInfo.cs deleted file mode 100644 index 6474994f1..000000000 --- a/TSOClient/tso.client/Network/CityInfo.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Network -{ - public class CityInfo - { - private string m_Name; - private string m_Description; - private ulong m_Thumbnail; - private string m_UUID; - private ulong m_Map; - public bool Online = true; - public CityInfoStatus Status; - - private string m_IP; - private int m_Port; - - /// - /// The name of this city. - /// - public string Name - { - get { return m_Name; } - } - - /// - /// This city's description. - /// - public string Description - { - get { return m_Description; } - } - - /// - /// The ID of this city's thumbnail. - /// - public ulong Thumbnail - { - get { return m_Thumbnail; } - } - - /// - /// This city's server's IP. - /// - public string IP - { - get { return m_IP; } - } - - /// - /// This city's server's port. - /// - public int Port - { - get { return m_Port; } - } - - /// - /// The ID for this city's map. - /// - public ulong Map - { - get { return m_Map; } - } - - public string UUID - { - get { return m_UUID; } - } - - public CityInfo(string Name, string Description, ulong Thumbnail, string UUID, ulong Map, string IP, int Port) - { - m_Name = Name; - m_Description = Description; - m_Thumbnail = Thumbnail; - m_UUID = UUID; - m_Map = Map; - m_IP = IP; - m_Port = Port; - } - - public List Messages; - } - - public class CityInfoMessageOfTheDay - { - public string From; - public string Subject; - public string Body; - } - - public enum CityInfoStatus - { - Ok = 1, - Busy = 2, - Full = 3, - Reserved = 4 - } -} diff --git a/TSOClient/tso.client/Network/LotPacketHandlers.cs b/TSOClient/tso.client/Network/LotPacketHandlers.cs deleted file mode 100644 index af2b0627a..000000000 --- a/TSOClient/tso.client/Network/LotPacketHandlers.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using TSOClient.VM; -using TSOClient.Lot; - -namespace TSOClient.Network -{ - class LotPacketHandlers - { - public static void OnSimulationState(NetworkClient Client, PacketStream Packet, LotScreen Lot) - { - List SimObjects = new List(); - - byte Opcode = (byte)Packet.ReadByte(); - - byte NumTicks = (byte)Packet.ReadByte(); - int NumObjects = Packet.ReadInt32(); - BinaryFormatter BinFormatter = new BinaryFormatter(); - - for (int i = 0; i < NumObjects; i++) - { - SimulationObject SimObject = (SimulationObject)BinFormatter.Deserialize(Packet); - SimObjects.Add(SimObject); - } - - Lot.UpdateSimulationState(NumTicks, SimObjects); - } - } -} diff --git a/TSOClient/tso.client/Network/LotPacketSenders.cs b/TSOClient/tso.client/Network/LotPacketSenders.cs deleted file mode 100644 index 77bcc35f8..000000000 --- a/TSOClient/tso.client/Network/LotPacketSenders.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.Serialization.Formatters.Binary; -using System.Text; -using System.IO; -using System.Security.Cryptography; -using TSOClient.Network.Encryption; -using TSOClient.VM; - -namespace TSOClient.Network -{ - class LotPacketSenders - { - /// - /// Sends a packet to create a SimulationObject on ter server. - /// Assumes the player is on a lot that he owns. - /// - /// The SimulationObject to create. - public static void SendCreatedSimulationObject(SimulationObject CreatedObject) - { - //TODO: Change this ID! - PacketStream CreateSimulationObjectPacket = new PacketStream(0x11, 0); - - BinaryFormatter BinFormatter = new BinaryFormatter(); - BinFormatter.Serialize(CreateSimulationObjectPacket, CreatedObject); - - PlayerAccount.Client.Send(FinalizePacket(0x11, new DESCryptoServiceProvider(), - CreateSimulationObjectPacket.ToArray())); - } - - /// - /// Writes a packet's header and encrypts the contents of the packet (not the header). - /// - /// The ID of the packet. - /// The packet's contents. - /// The finalized packet! - private static byte[] FinalizePacket(byte PacketID, DESCryptoServiceProvider CryptoService, byte[] PacketData) - { - MemoryStream FinalizedPacket = new MemoryStream(); - BinaryWriter PacketWriter = new BinaryWriter(FinalizedPacket); - - PasswordDeriveBytes Pwd = new PasswordDeriveBytes(Encoding.ASCII.GetBytes(PlayerAccount.Client.Password), - Encoding.ASCII.GetBytes("SALT"), "SHA1", 10); - - MemoryStream TempStream = new MemoryStream(); - CryptoStream EncryptedStream = new CryptoStream(TempStream, - CryptoService.CreateEncryptor(PlayerAccount.EncKey, Encoding.ASCII.GetBytes("@1B2c3D4e5F6g7H8")), - CryptoStreamMode.Write); - EncryptedStream.Write(PacketData, 0, PacketData.Length); - EncryptedStream.FlushFinalBlock(); - - PacketWriter.Write(PacketID); - //The length of the encrypted data can be longer or smaller than the original length, - //so write the length of the encrypted data. - PacketWriter.Write((byte)(3 + TempStream.Length)); - PacketWriter.Flush(); - //Also write the length of the unencrypted data. - PacketWriter.Write((byte)PacketData.Length); - PacketWriter.Flush(); - - PacketWriter.Write(TempStream.ToArray()); - PacketWriter.Flush(); - - byte[] ReturnPacket = FinalizedPacket.ToArray(); - - PacketWriter.Close(); - - return ReturnPacket; - } - } -} diff --git a/TSOClient/tso.client/Network/Network.cs b/TSOClient/tso.client/Network/Network.cs index a6359c6b4..91ca8d77c 100644 --- a/TSOClient/tso.client/Network/Network.cs +++ b/TSOClient/tso.client/Network/Network.cs @@ -1,5 +1,7 @@ using FSO.Client.Model; using FSO.Client.Regulators; +using FSO.Common; +using FSO.Common.DataService; using FSO.Common.Domain.Shards; using FSO.Server.Clients; using FSO.Server.Protocol.CitySelector; @@ -14,6 +16,11 @@ public class Network private LoginRegulator LoginRegulator; private IShardsDomain Shards; + public CityConnectionMode Mode => CityRegulator.Mode; + public ArchiveConfigFlags ArchiveConfig => CityRegulator.ArchiveConfig; + public bool SpectatorMode => CityRegulator.SpectatorMode; + public ConnectArchiveRequest ArchiveHost => CityRegulator.ArchiveSettings; + public Network(LoginRegulator loginReg, CityConnectionRegulator cityReg, LotConnectionRegulator lotReg, IShardsDomain shards) { this.Shards = shards; @@ -61,5 +68,23 @@ public ShardStatusItem MyShard return Shards.All.First(x => x.Name == CityRegulator.CurrentShard.ShardName); } } + + public uint ModerationLevel + { + get + { + return CityRegulator.ModerationLevel; + } + } + + public string TryGetUsername(uint id) + { + if (Mode == CityConnectionMode.ARCHIVE && !ArchiveConfig.HasFlag(ArchiveConfigFlags.HideNames) && CityRegulator.UserList != null) + { + return CityRegulator.UserList.Clients.FirstOrDefault(x => x.AvatarId == id).DisplayName; + } + + return null; + } } } diff --git a/TSOClient/tso.client/Network/NetworkClient.cs b/TSOClient/tso.client/Network/NetworkClient.cs index 3b6f7a835..9bf960c26 100644 --- a/TSOClient/tso.client/Network/NetworkClient.cs +++ b/TSOClient/tso.client/Network/NetworkClient.cs @@ -1,360 +1,360 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -Mats 'Afr0' Vederhus. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.Net; -using System.Net.Sockets; -using System.Threading; -using System.IO; -using System.Security.Cryptography; -using LogThis; -using TSOClient.Network.Encryption; - -namespace TSOClient.Network -{ - public delegate void NetworkErrorDelegate(SocketException Exception); - public delegate void ReceivedPacketDelegate(PacketStream Packet); - - public class NetworkClient - { - private Socket m_Sock; - private string m_IP; - private int m_Port; - - private bool m_Connected = false; - - //Buffer for storing packets that were not fully read. - private PacketStream m_TempPacket; - - //The number of bytes to be sent. See Send() - private int m_NumBytesToSend = 0; - private byte[] m_RecvBuf; - - private string m_Username, m_Password; - - public DESCryptoServiceProvider CryptoService = new DESCryptoServiceProvider(); - - public event NetworkErrorDelegate OnNetworkError; - public event ReceivedPacketDelegate OnReceivedData; - - /// - /// The user's password. - /// - public string Password - { - get { return m_Password; } - } - - public NetworkClient(string IP, int Port) - { - m_Sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - m_IP = IP; - m_Port = Port; - - m_RecvBuf = new byte[11024]; - } - - - /// - /// Connects to the login server. - /// - /// The user's username. - /// The user's password. - public void Connect(string Username, string Password) - { - m_Username = Username; - m_Password = Password; - - m_Sock.BeginConnect(IPAddress.Parse(m_IP), m_Port, new AsyncCallback(ConnectCallback), m_Sock); - } - - public void Send(byte[] Data) - { - m_NumBytesToSend = Data.Length; - m_Sock.BeginSend(Data, 0, Data.Length, SocketFlags.None, new AsyncCallback(OnSend), m_Sock); - } - - /// - /// Sends an encrypted packet to the server. - /// Automatically appends the length of the packet after the ID, as - /// the encrypted data can be smaller or longer than that of the - /// unencrypted data. - /// - /// The ID of the packet (will remain unencrypted). - /// The data that will be encrypted. - public void SendEncrypted(byte PacketID, byte[] Data) - { - m_NumBytesToSend = Data.Length; - byte[] EncryptedData = FinalizePacket(PacketID, Data); - - m_Sock.BeginSend(EncryptedData, 0, EncryptedData.Length, SocketFlags.None, - new AsyncCallback(OnSend), m_Sock); - } - - /// - /// Writes a packet's header and encrypts the contents of the packet (not the header). - /// - /// The ID of the packet. - /// The packet's contents. - /// The finalized packet! - private byte[] FinalizePacket(byte PacketID, byte[] PacketData) - { - return null; - } - - public void On(PacketType PType, ReceivedPacketDelegate PacketDelegate) - { - - } - - protected virtual void OnSend(IAsyncResult AR) - { - Socket ClientSock = (Socket)AR.AsyncState; - int NumBytesSent = ClientSock.EndSend(AR); - - Log.LogThis("Sent: " + NumBytesSent.ToString() + "!", eloglevel.info); - - if (NumBytesSent < m_NumBytesToSend) - Log.LogThis("Didn't send everything!", eloglevel.info); - } - - private void BeginReceive(/*object State*/) - { - //if (m_Connected) - //{ - m_Sock.BeginReceive(m_RecvBuf, 0, m_RecvBuf.Length, SocketFlags.None, - new AsyncCallback(ReceiveCallback), m_Sock); - //} - } - - private void ConnectCallback(IAsyncResult AR) - { - try - { - Socket Sock = (Socket)AR.AsyncState; - Sock.EndConnect(AR); - - m_Connected = true; - BeginReceive(); - - UIPacketSenders.SendLoginRequest(this, m_Username, m_Password); - } - catch (SocketException E) - { - //Hopefully all classes inheriting from NetworkedUIElement will subscribe to this... - if (OnNetworkError != null) - OnNetworkError(E); - } - } - - private void OnPacket(PacketStream packet, PacketHandler handler) - { - if (OnReceivedData != null) - { - OnReceivedData(packet); - } - - handler.Handler(packet); - } - - private void ReceiveCallback(IAsyncResult AR) - { - try - { - Socket Sock = (Socket)AR.AsyncState; - int NumBytesRead = Sock.EndReceive(AR); - - /** Cant do anything with this! **/ - if (NumBytesRead == 0) { return; } - - Log.LogThis("Received: " + NumBytesRead + " bytes!", eloglevel.info); - - byte[] TmpBuf = new byte[NumBytesRead]; - Buffer.BlockCopy(m_RecvBuf, 0, TmpBuf, 0, NumBytesRead); - - //The packet is given an ID of 0x00 because its ID is currently unknown. - PacketStream TempPacket = new PacketStream(0x00, NumBytesRead, TmpBuf); - byte ID = TempPacket.PeekByte(0); - - int PacketLength = 0; - var handler = FindPacketHandler(ID); - - if (handler != null) - { - PacketLength = handler.Length; - - Log.LogThis("PacketLength: " + PacketLength, eloglevel.info); - Log.LogThis("Found matching PacketID (" + ID + ")!\r\n\r\n", eloglevel.info); - - if (NumBytesRead == PacketLength) - { - Log.LogThis("Got packet - exact length!\r\n\r\n", eloglevel.info); - m_RecvBuf = new byte[11024]; - - OnPacket(new PacketStream(ID, PacketLength, TempPacket.ToArray()), handler); - } - else if (NumBytesRead < PacketLength) - { - m_TempPacket = new PacketStream(ID, PacketLength); - byte[] TmpBuffer = new byte[NumBytesRead]; - - //Store the number of bytes that were read in the temporary buffer. - Log.LogThis("Got data, but not a full packet - stored " + - NumBytesRead.ToString() + "bytes!\r\n\r\n", eloglevel.info); - Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, NumBytesRead); - m_TempPacket.WriteBytes(TmpBuffer); - - //And reset the buffers! - m_RecvBuf = new byte[11024]; - TmpBuffer = null; - } - else if (PacketLength == 0) - { - Log.LogThis("Received variable length packet!\r\n", eloglevel.info); - - if (NumBytesRead > (int)PacketHeaders.UNENCRYPTED) //Header is 3 bytes. - { - PacketLength = TempPacket.PeekUShort(1); - - if (NumBytesRead == PacketLength) - { - Log.LogThis("Received exact number of bytes for packet!\r\n", eloglevel.info); - - m_RecvBuf = new byte[11024]; - m_TempPacket = null; - OnPacket(new PacketStream(ID, PacketLength, TempPacket.ToArray()), handler); - } - else if (NumBytesRead < PacketLength) - { - Log.LogThis("Didn't receive entire packet - stored: " + PacketLength + " bytes!\r\n", - eloglevel.info); - - TempPacket.SetLength(PacketLength); - m_TempPacket = TempPacket; - m_RecvBuf = new byte[11024]; - } - else if (NumBytesRead > PacketLength) - { - Log.LogThis("Received more bytes than needed for packet. Excess: " + - (NumBytesRead - PacketLength) + "\r\n", eloglevel.info); - - byte[] TmpBuffer = new byte[NumBytesRead - PacketLength]; - Buffer.BlockCopy(TempPacket.ToArray(), 0, TmpBuffer, 0, TmpBuffer.Length); - m_TempPacket = new PacketStream(TmpBuffer[0], NumBytesRead - PacketLength, - TmpBuffer); - - byte[] PacketBuffer = new byte[PacketLength]; - Buffer.BlockCopy(TempPacket.ToArray(), 0, PacketBuffer, 0, PacketBuffer.Length); - - m_RecvBuf = new byte[11024]; - OnPacket(new PacketStream(ID, PacketLength, PacketBuffer), handler); - } - } - } - } - else - { - if (m_TempPacket != null) - { - if (m_TempPacket.Length < m_TempPacket.BufferLength) - { - //Received the exact number of bytes needed to complete the stored packet. - if ((m_TempPacket.BufferLength + NumBytesRead) == m_TempPacket.Length) - { - byte[] TmpBuffer = new byte[NumBytesRead]; - Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, NumBytesRead); - - m_RecvBuf = new byte[11024]; - TmpBuffer = null; - } - //Received more than the number of bytes needed to complete the packet! - else if ((m_TempPacket.BufferLength + NumBytesRead) > m_TempPacket.Length) - { - int Target = (int)((m_TempPacket.BufferLength + NumBytesRead) - m_TempPacket.Length); - byte[] TmpBuffer = new byte[Target]; - - Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, Target); - m_TempPacket.WriteBytes(TmpBuffer); - - //Now we have a full packet, so call the received event! - OnPacket(new PacketStream(m_TempPacket.PacketID, - (int)m_TempPacket.Length, m_TempPacket.ToArray()), handler); - - //Copy the remaining bytes in the receiving buffer. - TmpBuffer = new byte[NumBytesRead - Target]; - Buffer.BlockCopy(m_RecvBuf, Target, TmpBuffer, 0, (NumBytesRead - Target)); - - //Give the temporary packet an ID of 0x00 since we don't know its ID yet. - TempPacket = new PacketStream(0x00, NumBytesRead - Target, TmpBuffer); - ID = TempPacket.PeekByte(0); - handler = FindPacketHandler(ID); - - //This SHOULD be an existing ID, but let's sanity-check it... - if (handler != null) - { - m_TempPacket = new PacketStream(ID, handler.Length, TempPacket.ToArray()); - - //Congratulations, you just received another packet! - if (m_TempPacket.Length == m_TempPacket.BufferLength) - { - OnPacket(new PacketStream(m_TempPacket.PacketID, - (int)m_TempPacket.Length, m_TempPacket.ToArray()), handler); - - //No more data to store on this read, so reset everything... - m_TempPacket = null; - TmpBuffer = null; - m_RecvBuf = new byte[11024]; - } - } - else - { - //Houston, we have a problem (this should never occur)! - } - } - } - } - } - - m_Sock.BeginReceive(m_RecvBuf, 0, m_RecvBuf.Length, SocketFlags.None, - new AsyncCallback(ReceiveCallback), m_Sock); - } - catch (SocketException E) - { - Log.LogThis("SocketException: " + E.ToString(), eloglevel.info); - Disconnect(); - } - } - - /// - /// Disconnects this NetworkClient instance and stops - /// all sending and receiving of data. - /// - public void Disconnect() - { - try - { - m_Sock.Shutdown(SocketShutdown.Both); - m_Sock.Disconnect(true); - } - catch - { - } - } - - private PacketHandler FindPacketHandler(byte ID) - { - return PacketHandlers.Get(ID); - } - } -} +///*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. +//If a copy of the MPL was not distributed with this file, You can obtain one at +//http://mozilla.org/MPL/2.0/. + +//The Original Code is the TSOClient. + +//The Initial Developer of the Original Code is +//Mats 'Afr0' Vederhus. All Rights Reserved. + +//Contributor(s): ______________________________________. +//*/ + +//using System; +//using System.Collections.Generic; +//using System.Text; +//using System.Net; +//using System.Net.Sockets; +//using System.Threading; +//using System.IO; +//using System.Security.Cryptography; +//using LogThis; +//using TSOClient.Network.Encryption; + +//namespace TSOClient.Network +//{ +// public delegate void NetworkErrorDelegate(SocketException Exception); +// public delegate void ReceivedPacketDelegate(PacketStream Packet); + +// public class NetworkClient +// { +// private Socket m_Sock; +// private string m_IP; +// private int m_Port; + +// private bool m_Connected = false; + +// //Buffer for storing packets that were not fully read. +// private PacketStream m_TempPacket; + +// //The number of bytes to be sent. See Send() +// private int m_NumBytesToSend = 0; +// private byte[] m_RecvBuf; + +// private string m_Username, m_Password; + +// public DESCryptoServiceProvider CryptoService = new DESCryptoServiceProvider(); + +// public event NetworkErrorDelegate OnNetworkError; +// public event ReceivedPacketDelegate OnReceivedData; + +// /// +// /// The user's password. +// /// +// public string Password +// { +// get { return m_Password; } +// } + +// public NetworkClient(string IP, int Port) +// { +// m_Sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); +// m_IP = IP; +// m_Port = Port; + +// m_RecvBuf = new byte[11024]; +// } + + +// /// +// /// Connects to the login server. +// /// +// /// The user's username. +// /// The user's password. +// public void Connect(string Username, string Password) +// { +// m_Username = Username; +// m_Password = Password; + +// m_Sock.BeginConnect(IPAddress.Parse(m_IP), m_Port, new AsyncCallback(ConnectCallback), m_Sock); +// } + +// public void Send(byte[] Data) +// { +// m_NumBytesToSend = Data.Length; +// m_Sock.BeginSend(Data, 0, Data.Length, SocketFlags.None, new AsyncCallback(OnSend), m_Sock); +// } + +// /// +// /// Sends an encrypted packet to the server. +// /// Automatically appends the length of the packet after the ID, as +// /// the encrypted data can be smaller or longer than that of the +// /// unencrypted data. +// /// +// /// The ID of the packet (will remain unencrypted). +// /// The data that will be encrypted. +// public void SendEncrypted(byte PacketID, byte[] Data) +// { +// m_NumBytesToSend = Data.Length; +// byte[] EncryptedData = FinalizePacket(PacketID, Data); + +// m_Sock.BeginSend(EncryptedData, 0, EncryptedData.Length, SocketFlags.None, +// new AsyncCallback(OnSend), m_Sock); +// } + +// /// +// /// Writes a packet's header and encrypts the contents of the packet (not the header). +// /// +// /// The ID of the packet. +// /// The packet's contents. +// /// The finalized packet! +// private byte[] FinalizePacket(byte PacketID, byte[] PacketData) +// { +// return null; +// } + +// public void On(PacketType PType, ReceivedPacketDelegate PacketDelegate) +// { + +// } + +// protected virtual void OnSend(IAsyncResult AR) +// { +// Socket ClientSock = (Socket)AR.AsyncState; +// int NumBytesSent = ClientSock.EndSend(AR); + +// Log.LogThis("Sent: " + NumBytesSent.ToString() + "!", eloglevel.info); + +// if (NumBytesSent < m_NumBytesToSend) +// Log.LogThis("Didn't send everything!", eloglevel.info); +// } + +// private void BeginReceive(/*object State*/) +// { +// //if (m_Connected) +// //{ +// m_Sock.BeginReceive(m_RecvBuf, 0, m_RecvBuf.Length, SocketFlags.None, +// new AsyncCallback(ReceiveCallback), m_Sock); +// //} +// } + +// private void ConnectCallback(IAsyncResult AR) +// { +// try +// { +// Socket Sock = (Socket)AR.AsyncState; +// Sock.EndConnect(AR); + +// m_Connected = true; +// BeginReceive(); + +// UIPacketSenders.SendLoginRequest(this, m_Username, m_Password); +// } +// catch (SocketException E) +// { +// //Hopefully all classes inheriting from NetworkedUIElement will subscribe to this... +// if (OnNetworkError != null) +// OnNetworkError(E); +// } +// } + +// private void OnPacket(PacketStream packet, PacketHandler handler) +// { +// if (OnReceivedData != null) +// { +// OnReceivedData(packet); +// } + +// handler.Handler(packet); +// } + +// private void ReceiveCallback(IAsyncResult AR) +// { +// try +// { +// Socket Sock = (Socket)AR.AsyncState; +// int NumBytesRead = Sock.EndReceive(AR); + +// /** Cant do anything with this! **/ +// if (NumBytesRead == 0) { return; } + +// Log.LogThis("Received: " + NumBytesRead + " bytes!", eloglevel.info); + +// byte[] TmpBuf = new byte[NumBytesRead]; +// Buffer.BlockCopy(m_RecvBuf, 0, TmpBuf, 0, NumBytesRead); + +// //The packet is given an ID of 0x00 because its ID is currently unknown. +// PacketStream TempPacket = new PacketStream(0x00, NumBytesRead, TmpBuf); +// byte ID = TempPacket.PeekByte(0); + +// int PacketLength = 0; +// var handler = FindPacketHandler(ID); + +// if (handler != null) +// { +// PacketLength = handler.Length; + +// Log.LogThis("PacketLength: " + PacketLength, eloglevel.info); +// Log.LogThis("Found matching PacketID (" + ID + ")!\r\n\r\n", eloglevel.info); + +// if (NumBytesRead == PacketLength) +// { +// Log.LogThis("Got packet - exact length!\r\n\r\n", eloglevel.info); +// m_RecvBuf = new byte[11024]; + +// OnPacket(new PacketStream(ID, PacketLength, TempPacket.ToArray()), handler); +// } +// else if (NumBytesRead < PacketLength) +// { +// m_TempPacket = new PacketStream(ID, PacketLength); +// byte[] TmpBuffer = new byte[NumBytesRead]; + +// //Store the number of bytes that were read in the temporary buffer. +// Log.LogThis("Got data, but not a full packet - stored " + +// NumBytesRead.ToString() + "bytes!\r\n\r\n", eloglevel.info); +// Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, NumBytesRead); +// m_TempPacket.WriteBytes(TmpBuffer); + +// //And reset the buffers! +// m_RecvBuf = new byte[11024]; +// TmpBuffer = null; +// } +// else if (PacketLength == 0) +// { +// Log.LogThis("Received variable length packet!\r\n", eloglevel.info); + +// if (NumBytesRead > (int)PacketHeaders.UNENCRYPTED) //Header is 3 bytes. +// { +// PacketLength = TempPacket.PeekUShort(1); + +// if (NumBytesRead == PacketLength) +// { +// Log.LogThis("Received exact number of bytes for packet!\r\n", eloglevel.info); + +// m_RecvBuf = new byte[11024]; +// m_TempPacket = null; +// OnPacket(new PacketStream(ID, PacketLength, TempPacket.ToArray()), handler); +// } +// else if (NumBytesRead < PacketLength) +// { +// Log.LogThis("Didn't receive entire packet - stored: " + PacketLength + " bytes!\r\n", +// eloglevel.info); + +// TempPacket.SetLength(PacketLength); +// m_TempPacket = TempPacket; +// m_RecvBuf = new byte[11024]; +// } +// else if (NumBytesRead > PacketLength) +// { +// Log.LogThis("Received more bytes than needed for packet. Excess: " + +// (NumBytesRead - PacketLength) + "\r\n", eloglevel.info); + +// byte[] TmpBuffer = new byte[NumBytesRead - PacketLength]; +// Buffer.BlockCopy(TempPacket.ToArray(), 0, TmpBuffer, 0, TmpBuffer.Length); +// m_TempPacket = new PacketStream(TmpBuffer[0], NumBytesRead - PacketLength, +// TmpBuffer); + +// byte[] PacketBuffer = new byte[PacketLength]; +// Buffer.BlockCopy(TempPacket.ToArray(), 0, PacketBuffer, 0, PacketBuffer.Length); + +// m_RecvBuf = new byte[11024]; +// OnPacket(new PacketStream(ID, PacketLength, PacketBuffer), handler); +// } +// } +// } +// } +// else +// { +// if (m_TempPacket != null) +// { +// if (m_TempPacket.Length < m_TempPacket.BufferLength) +// { +// //Received the exact number of bytes needed to complete the stored packet. +// if ((m_TempPacket.BufferLength + NumBytesRead) == m_TempPacket.Length) +// { +// byte[] TmpBuffer = new byte[NumBytesRead]; +// Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, NumBytesRead); + +// m_RecvBuf = new byte[11024]; +// TmpBuffer = null; +// } +// //Received more than the number of bytes needed to complete the packet! +// else if ((m_TempPacket.BufferLength + NumBytesRead) > m_TempPacket.Length) +// { +// int Target = (int)((m_TempPacket.BufferLength + NumBytesRead) - m_TempPacket.Length); +// byte[] TmpBuffer = new byte[Target]; + +// Buffer.BlockCopy(m_RecvBuf, 0, TmpBuffer, 0, Target); +// m_TempPacket.WriteBytes(TmpBuffer); + +// //Now we have a full packet, so call the received event! +// OnPacket(new PacketStream(m_TempPacket.PacketID, +// (int)m_TempPacket.Length, m_TempPacket.ToArray()), handler); + +// //Copy the remaining bytes in the receiving buffer. +// TmpBuffer = new byte[NumBytesRead - Target]; +// Buffer.BlockCopy(m_RecvBuf, Target, TmpBuffer, 0, (NumBytesRead - Target)); + +// //Give the temporary packet an ID of 0x00 since we don't know its ID yet. +// TempPacket = new PacketStream(0x00, NumBytesRead - Target, TmpBuffer); +// ID = TempPacket.PeekByte(0); +// handler = FindPacketHandler(ID); + +// //This SHOULD be an existing ID, but let's sanity-check it... +// if (handler != null) +// { +// m_TempPacket = new PacketStream(ID, handler.Length, TempPacket.ToArray()); + +// //Congratulations, you just received another packet! +// if (m_TempPacket.Length == m_TempPacket.BufferLength) +// { +// OnPacket(new PacketStream(m_TempPacket.PacketID, +// (int)m_TempPacket.Length, m_TempPacket.ToArray()), handler); + +// //No more data to store on this read, so reset everything... +// m_TempPacket = null; +// TmpBuffer = null; +// m_RecvBuf = new byte[11024]; +// } +// } +// else +// { +// //Houston, we have a problem (this should never occur)! +// } +// } +// } +// } +// } + +// m_Sock.BeginReceive(m_RecvBuf, 0, m_RecvBuf.Length, SocketFlags.None, +// new AsyncCallback(ReceiveCallback), m_Sock); +// } +// catch (SocketException E) +// { +// Log.LogThis("SocketException: " + E.ToString(), eloglevel.info); +// Disconnect(); +// } +// } + +// /// +// /// Disconnects this NetworkClient instance and stops +// /// all sending and receiving of data. +// /// +// public void Disconnect() +// { +// try +// { +// m_Sock.Shutdown(SocketShutdown.Both); +// m_Sock.Disconnect(true); +// } +// catch +// { +// } +// } + +// private PacketHandler FindPacketHandler(byte ID) +// { +// return PacketHandlers.Get(ID); +// } +// } +//} diff --git a/TSOClient/tso.client/Network/NetworkStatus.cs b/TSOClient/tso.client/Network/NetworkStatus.cs index 8c287e12c..c299df028 100644 --- a/TSOClient/tso.client/Network/NetworkStatus.cs +++ b/TSOClient/tso.client/Network/NetworkStatus.cs @@ -1,4 +1,5 @@ -using FSO.Files.RC; +using FSO.Content; +using FSO.Files.RC; namespace FSO.Client.Network { @@ -14,11 +15,19 @@ public int RemeshesInProgress } } + public float? RemeshUpdateProgress + { + get + { + return RCDBPFContent.DownloadPercentage; + } + } + public bool Any { get { - return CityReconnectAttempt > 0 || LotReconnectAttempt > 0 || RemeshesInProgress > 0; + return CityReconnectAttempt > 0 || LotReconnectAttempt > 0 || RemeshesInProgress > 0 || RemeshUpdateProgress != null; } } diff --git a/TSOClient/tso.client/Network/PacketHandlers.cs b/TSOClient/tso.client/Network/PacketHandlers.cs deleted file mode 100644 index a2f0d4a80..000000000 --- a/TSOClient/tso.client/Network/PacketHandlers.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Network -{ - public class PacketHandlers - { - public static void Init() - { - Register(0x01, 1, new OnPacketReceive(NetworkFacade.Controller._OnLoginNotify)); - Register(0x02, 2, new OnPacketReceive(NetworkFacade.Controller._OnLoginFailure)); - Register(0x05, 0, new OnPacketReceive(NetworkFacade.Controller._OnCharacterList)); - Register(0x06, 0, new OnPacketReceive(NetworkFacade.Controller._OnCityList)); - - ////InitLoginNotify - 2 bytes - //NetworkClient.RegisterLoginPacketID(0x01, 2); - ////LoginFailResponse - 2 bytes - //NetworkClient.RegisterLoginPacketID(0x02, 2); - ///*LoginSuccessResponse - 33 bytes - //NetworkClient.RegisterLoginPacketID(0x04, 33);*/ - ////CharacterInfoResponse - Variable size - //NetworkClient.RegisterLoginPacketID(0x05, 0); - ////CityInfoResponse - //NetworkClient.RegisterLoginPacketID(0x06, 0); - ////CharacterCreate - //NetworkClient.RegisterLoginPacketID(0x07, 0); - - /* - LOGIN_NOTIFY = , - LOGIN_FAILURE = 0x2, - CHARACTER_LIST = 0x5, - CITY_LIST = 0x6*/ - } - - /** - * Framework - */ - private static Dictionary m_Handlers = new Dictionary(); - public static void Register(byte id, int size, OnPacketReceive handler) - { - //2 bytes for header - //Why is this here? This is fucking things up! - /*if (size != 0) - { - size += 2; - }*/ - m_Handlers.Add(id, new PacketHandler(id, size, handler)); - } - - public static void Handle(PacketStream stream) - { - byte ID = (byte)stream.ReadByte(); - if (m_Handlers.ContainsKey(ID)) - { - m_Handlers[ID].Handler(stream); - } - } - - public static PacketHandler Get(byte id) - { - return m_Handlers[id]; - } - } - - public delegate void OnPacketReceive(PacketStream Packet); - - public class PacketHandler - { - private byte m_ID; - private int m_Length; - private OnPacketReceive m_Handler; - - public PacketHandler(byte id, int size, OnPacketReceive handler) - { - this.m_ID = id; - this.m_Length = size; - this.m_Handler = handler; - } - - public byte ID - { - get { return m_ID; } - } - - public int Length - { - get { return m_Length; } - } - - public OnPacketReceive Handler - { - get - { - return m_Handler; - } - } - } -} diff --git a/TSOClient/tso.client/Network/PacketHeaders.cs b/TSOClient/tso.client/Network/PacketHeaders.cs deleted file mode 100644 index 274d13386..000000000 --- a/TSOClient/tso.client/Network/PacketHeaders.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Network -{ - /// - /// Size of packet headers. - /// - public enum PacketHeaders - { - UNENCRYPTED = 3, - ENCRYPTED = 5 - } -} diff --git a/TSOClient/tso.client/Network/PacketStream.cs b/TSOClient/tso.client/Network/PacketStream.cs deleted file mode 100644 index 978caec20..000000000 --- a/TSOClient/tso.client/Network/PacketStream.cs +++ /dev/null @@ -1,402 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSO LoginServer. - -The Initial Developer of the Original Code is -Mats 'Afr0' Vederhus. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using System.Security.Cryptography; -using TSOClient.Network.Encryption; -using LogThis; - -namespace TSOClient.Network -{ - public class PacketStream : Stream - { - //The ID of this PacketStream (identifies a packet). - private byte m_ID; - //The intended length of this PacketStream. Might not correspond with the - //length of m_BaseStream! - private int m_Length; - public bool m_VariableLength; - - private MemoryStream m_BaseStream; - private bool m_SupportsPeek = false; - private byte[] m_PeekBuffer; - private BinaryReader m_Reader; - private BinaryWriter m_Writer; - private long m_Position; - - public PacketStream(byte ID, int Length, byte[] DataBuffer) - : base() - { - m_ID = ID; - m_Length = Length; - - m_BaseStream = new MemoryStream(DataBuffer); - - m_SupportsPeek = true; - m_PeekBuffer = new byte[DataBuffer.Length]; - DataBuffer.CopyTo(m_PeekBuffer, 0); - - m_Reader = new BinaryReader(m_BaseStream); - m_Position = DataBuffer.Length; - } - - public PacketStream(byte ID, int Length) - { - m_ID = ID; - m_Length = Length; - - m_SupportsPeek = false; - - m_BaseStream = new MemoryStream(); - m_Writer = new BinaryWriter(m_BaseStream); - m_Position = 0; - } - - public PacketStream(byte ID, int Length, bool VariableLength) : this(ID, Length) - { - this.m_VariableLength = VariableLength; - } - - public bool VariableLength - { - get{ - return m_VariableLength; - } - } - - public override bool CanRead - { - get { return true; } - } - - public override bool CanWrite - { - get { return true; } - } - - public override bool CanSeek - { - get { return false; } - } - - public bool CanPeek - { - get { return m_SupportsPeek; } - } - - public byte PacketID - { - get { return m_ID; } - } - - /// - /// The current position of this PacketStream. - /// - public override long Position - { - get - { - return m_Position; - } - set - { - //TODO: Checks here? - m_Position = value; - } - } - - /// - /// The target length of this PacketStream. - /// To get the actual current length, use the BufferLength property. - /// - public override long Length - { - get { return m_Length; } - } - - /// - /// The current length of this PacketStream. - /// - public long BufferLength - { - get { return m_BaseStream.Length; } - } - - /// - /// Sets the length of this PacketStream to the specified value. - /// - /// The length of the stream. - public override void SetLength(long value) - { - byte[] Tmp = m_BaseStream.ToArray(); - //No idea if these two lines actually work, but they should... - m_BaseStream = new MemoryStream((int)value); - m_BaseStream.Write(Tmp, 0, Tmp.Length); - } - - /// - /// Do not call this! It will throw a NotImplementedException! - /// - /// The offset to seek to. - /// The origin of the seek. - /// The offset that was seeked to. - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } - - /// - /// Flushes the underlying stream. - /// - public override void Flush() - { - m_BaseStream.Flush(); - } - - public byte[] ToArray() - { - var bytes = m_BaseStream.ToArray(); - if (m_VariableLength) - { - var packetLength = (ushort)m_Position; - bytes[2] = (byte)(packetLength & 0xFF); - bytes[3] = (byte)(packetLength >> 8); - } - return bytes; - } - - /// - /// Decrypts the data in this PacketStream. - /// WARNING: ASSUMES THAT THE 7-BYTE HEADER - /// HAS BEEN READ (ID, LENGTH, DECRYPTEDLENGTH)! - /// - /// The client's en/decryptionkey. - /// The client's DESCryptoServiceProvider instance. - /// The packet's unencrypted length (third byte in the header). - public void DecryptPacket(byte[] Key, DESCryptoServiceProvider Service, ushort UnencryptedLength) - { - CryptoStream CStream = new CryptoStream(m_BaseStream, Service.CreateDecryptor(Key, - Encoding.ASCII.GetBytes("@1B2c3D4e5F6g7H8")), CryptoStreamMode.Read); - - byte[] DecodedBuffer = new byte[UnencryptedLength]; - CStream.Read(DecodedBuffer, 0, DecodedBuffer.Length); - - m_BaseStream = new MemoryStream(DecodedBuffer); - } - - #region Reading - - /// - /// Reads a specific number of bytes from this PacketStream. - /// - /// The buffer to read into. - /// The offset from which to start reading. - /// The number of bytes to read (must be at least equal to the length of buffer!) - /// The number of bytes that were read. - public override int Read(byte[] buffer, int offset, int count) - { - int Read = m_BaseStream.Read(buffer, offset, count); - m_Position -= Read; - - return Read; - } - - /// - /// Peeks a byte from the stream at the current position. - /// - /// The byte that was peeked. - public byte PeekByte() - { - if (m_SupportsPeek) - return m_PeekBuffer[m_Position]; - else - { - Log.LogThis("Tried peeking from a PacketStream instance that didn't support it!", eloglevel.warn); - return 0; - } - } - - /// - /// Peeks a byte from the stream at the specified position. - /// - /// The position to peek at. - /// The byte that was peeked. - public byte PeekByte(int Position) - { - if (m_SupportsPeek) - return m_PeekBuffer[Position]; - else - { - Log.LogThis("Tried peeking from a PacketStream instance that didn't support it!", eloglevel.warn); - return 0; - } - } - - /// - /// Peeks a ushort from the stream at the specified position. - /// - /// The position to peek at. - /// The ushort that was peeked. - public ushort PeekUShort(int Position) - { - MemoryStream MemStream = new MemoryStream(); - BinaryWriter Writer = new BinaryWriter(MemStream); - - Writer.Write((byte)PeekByte(Position)); - Writer.Write((byte)PeekByte(Position + 1)); - Writer.Flush(); - - return BitConverter.ToUInt16(MemStream.ToArray(), 0); - } - - public override int ReadByte() - { - m_Position -= 1; - return m_BaseStream.ReadByte(); - } - - public ushort ReadUShort() - { - m_Position -= 2; - - MemoryStream MemStream = new MemoryStream(); - BinaryWriter Writer = new BinaryWriter(MemStream); - - Writer.Write((byte)ReadByte()); - Writer.Write((byte)ReadByte()); - - return BitConverter.ToUInt16(MemStream.ToArray(), 0); - } - - public string ReadString() - { - string ReturnStr = m_Reader.ReadString(); - m_Position -= ReturnStr.Length; - - return ReturnStr; - } - - public string ReadString(int NumChars) - { - string ReturnStr = ""; - - for (int i = 0; i <= NumChars; i++) - ReturnStr = ReturnStr + m_Reader.ReadChar(); - - m_Position -= NumChars; - - return ReturnStr; - } - - public int ReadInt32() - { - m_Position -= 4; - return m_Reader.ReadInt32(); - } - - public long ReadInt64() - { - m_Position -= 8; - return m_Reader.ReadInt64(); - } - - public ushort ReadUInt16() - { - m_Position -= 2; - return m_Reader.ReadUInt16(); - } - - public ulong ReadUInt64() - { - m_Position -= 8; - return m_Reader.ReadUInt64(); - } - - #endregion - - #region Writing - - - /// - /// Writes the packet header - /// - public void WriteHeader() - { - WriteUInt16(this.m_ID); - if (m_VariableLength) - { - /** Leave 2 empty bytes to fill in during toArray() that will be the length **/ - WriteUInt16(0); - } - } - - /// - /// Writes a block of bytes to the current buffer using data read from the buffer. - /// - /// - /// - /// - public override void Write(byte[] buffer, int offset, int count) - { - m_BaseStream.Write(buffer, offset, count); - m_Position += count; - m_Writer.Flush(); - } - - public void WriteBytes(byte[] Buffer) - { - m_BaseStream.Write(Buffer, 0, Buffer.Length); - m_Position += Buffer.Length; - m_Writer.Flush(); - } - - public override void WriteByte(byte Value) - { - m_Writer.Write(Value); - m_Position += 1; - m_Writer.Flush(); - } - - - public void WriteInt32(int Value) - { - m_Writer.Write(Value); - m_Position += 4; - m_Writer.Flush(); - } - - public void WriteUInt16(ushort Value) - { - m_Writer.Write(Value); - m_Position += 2; - m_Writer.Flush(); - } - - public void WriteInt64(long Value) - { - m_Writer.Write(Value); - m_Position += 8; - m_Writer.Flush(); - } - - public void WriteASCII(string str) - { - WriteByte((byte)str.Length); - WriteBytes(Encoding.ASCII.GetBytes(str)); - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Network/PacketType.cs b/TSOClient/tso.client/Network/PacketType.cs deleted file mode 100644 index 3ce081036..000000000 --- a/TSOClient/tso.client/Network/PacketType.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Network -{ - public enum PacketType - { - LOGIN_REQUEST = 0x00, - LOGIN_NOTIFY = 0x01, - LOGIN_FAILURE = 0x02, - CHARACTER_LIST = 0x05, - CITY_LIST = 0x06, - CHARACTER_CREATE = 0x07, - - CHARACTER_CREATE_CITY = 0x64, - CHARACTER_CREATE_CITY_FAILED = 0x65, - REQUEST_CITY_TOKEN = 0x66, - CITY_TOKEN = 0x67 - } -} diff --git a/TSOClient/tso.client/Network/ProcessedPacket.cs b/TSOClient/tso.client/Network/ProcessedPacket.cs deleted file mode 100644 index 874f45aeb..000000000 --- a/TSOClient/tso.client/Network/ProcessedPacket.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Security.Cryptography; -using TSOClient.Events; -using TSOClient.Network.Events; - -namespace TSOClient.Network -{ - /// - /// A packet that has been decrypted and processed, ready to read from. - /// - public class ProcessedPacket : PacketStream - { - public ushort DecryptedLength; - - public ProcessedPacket(byte ID, bool Encrypted, int Length, byte[] DataBuffer) - : base(ID, Length, DataBuffer) - { - byte Opcode = (byte)this.ReadByte(); - this.m_Length = (ushort)this.ReadUShort(); - - if (Encrypted) - { - this.DecryptedLength = (ushort)this.ReadUShort(); - - if (this.DecryptedLength != this.m_Length) - { - //Something's gone haywire, throw an error... - EventSink.RegisterEvent(new PacketError(EventCodes.PACKET_PROCESSING_ERROR)); - } - } - - if(Encrypted) - this.DecryptPacket(PlayerAccount.EncKey, new DESCryptoServiceProvider(), this.DecryptedLength); - } - } -} diff --git a/TSOClient/tso.client/Network/Sandbox/FSOSandboxServer.cs b/TSOClient/tso.client/Network/Sandbox/FSOSandboxServer.cs index a171c9cc3..12620d70a 100644 --- a/TSOClient/tso.client/Network/Sandbox/FSOSandboxServer.cs +++ b/TSOClient/tso.client/Network/Sandbox/FSOSandboxServer.cs @@ -41,13 +41,13 @@ public void SendMessage(VMNetClient cli, VMNetMessage msg) ((IoSession)cli.NetHandle).Write(msg); } - public void Broadcast(VMNetMessage msg, HashSet ignore) + public void Broadcast(VMNetMessage msg, HashSet include) { List cliClone; lock (Sessions) cliClone = new List(Sessions); foreach (var s in cliClone) { - if (ignore.Contains(s.GetAttribute('c'))) continue; + if (!include.Contains(s.GetAttribute('c'))) continue; s.Write(msg); } } diff --git a/TSOClient/tso.client/Network/VMDataServiceNameCache.cs b/TSOClient/tso.client/Network/VMDataServiceNameCache.cs index fef74307c..49748016d 100644 --- a/TSOClient/tso.client/Network/VMDataServiceNameCache.cs +++ b/TSOClient/tso.client/Network/VMDataServiceNameCache.cs @@ -3,11 +3,10 @@ using FSO.Common.Utils; using FSO.SimAntics; using FSO.SimAntics.Model.TSOPlatform; -using System.Threading; namespace FSO.Client.Network { - public class VMDataServiceNameCache : VMBasicAvatarNameCache + public class VMDataServiceNameCache : VMBasicGlobalNameCache { private IClientDataService DataService; public VMDataServiceNameCache(IClientDataService dataService) @@ -15,26 +14,55 @@ public VMDataServiceNameCache(IClientDataService dataService) DataService = dataService; } - public override bool Precache(VM vm, uint persistID) + public override bool Precache(VM vm, VMGlobalEntityType type, uint persistID) { - if (!base.Precache(vm, persistID)) + if (!base.Precache(vm, type, persistID)) { - //we need to ask the data service for this name - DataService.Request(Server.DataService.Model.MaskedStruct.Messaging_Icon_Avatar, persistID).ContinueWith(x => + var cache = GetTypeCache(type); + + switch (type) { - if (x.IsFaulted || x.IsCanceled || x.Result == null) return; - var ava = (Avatar)x.Result; - var failCount = 0; - while (ava.Avatar_Name == "Retrieving...") - { - if (failCount++ > 100) return; - Thread.Sleep(100); - } - GameThread.NextUpdate(y => - { - AvatarNames[persistID] = ava.Avatar_Name; - }); - }); + case VMGlobalEntityType.Avatar: + { + //we need to ask the data service for this name + DataService.Request(Server.DataService.Model.MaskedStruct.Messaging_Icon_Avatar, persistID).ContinueWith(x => + { + if (x.IsFaulted || x.IsCanceled || x.Result == null) return; + var ava = (Avatar)x.Result; + var failCount = 0; + while (ava.Avatar_Name == "Retrieving...") + { + if (failCount++ > 100) return; + Thread.Sleep(100); + } + GameThread.NextUpdate(y => + { + cache[persistID] = ava.Avatar_Name; + }); + }); + break; + } + case VMGlobalEntityType.Lot: + { + //we need to ask the data service for this name + DataService.Request(Server.DataService.Model.MaskedStruct.Bookmark_Lot, persistID).ContinueWith(x => + { + if (x.IsFaulted || x.IsCanceled || x.Result == null) return; + var lot = (Lot)x.Result; + var failCount = 0; + while (lot.Lot_Name == "Retrieving...") + { + if (failCount++ > 100) return; + Thread.Sleep(100); + } + GameThread.NextUpdate(y => + { + cache[persistID] = lot.Lot_Name; + }); + }); + break; + } + } } return true; } diff --git a/TSOClient/tso.client/Properties/AssemblyInfo.cs b/TSOClient/tso.client/Properties/AssemblyInfo.cs deleted file mode 100644 index c3c70ba9c..000000000 --- a/TSOClient/tso.client/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FreeSO")] -[assembly: AssemblyProduct("FreeSO")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyDescription("Re-implementation of The Sims Online in C#.")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("acf9cd49-0ace-4319-bd07-6315ef96ac51")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs b/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs index e317dc1d8..c7f62b7b9 100644 --- a/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs +++ b/TSOClient/tso.client/Regulators/CityConnectionRegulator.cs @@ -1,29 +1,55 @@ using FSO.Client.Model; using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; +using FSO.Common; using FSO.Common.DatabaseService; using FSO.Common.DatabaseService.Model; using FSO.Common.DataService; +using FSO.Common.DataService.Model; +using FSO.Common.Domain.Realestate; +using FSO.Common.Domain.RealestateDomain; using FSO.Common.Domain.Shards; using FSO.Common.Model; using FSO.Common.Utils; +using FSO.Content.Model; using FSO.Server.Clients; using FSO.Server.Clients.Framework; using FSO.Server.DataService.Model; using FSO.Server.Protocol.Aries.Packets; using FSO.Server.Protocol.CitySelector; using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Utils; using FSO.Server.Protocol.Voltron.Packets; +using FSO.UI.Model; using Ninject; using System; using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; namespace FSO.Client.Regulators { + public class ConnectArchiveRequest + { + public string DisplayName; + public string CityAddress; + public bool SelfHost; + } + public class CityConnectionRegulator : AbstractRegulator, IAriesMessageSubscriber, IAriesEventSubscriber { public AriesClient Client { get; internal set; } public CityConnectionMode Mode { get; internal set; } = CityConnectionMode.NORMAL; + public ArchiveClientList UserList { get; internal set; } + public uint ModerationLevel { get; internal set; } + + private Binding AvatarBinding; + + public ConnectArchiveRequest ArchiveSettings { get; private set; } + public string ArchiveServerID { get; private set; } + private string ArchiveToken; + public ArchiveConfigFlags ArchiveConfig { get; private set; } + public bool SpectatorMode { get; private set; } private CityClient CityApi; private ShardSelectorServletResponse ShardSelectResponse; @@ -31,7 +57,9 @@ public class CityConnectionRegulator : AbstractRegulator, IAriesMessageSubscribe private IDatabaseService DB; private IClientDataService DataService; private IShardsDomain Shards; + private IRealestateDomain Realestate; private int _ReestablishAttempt; + private IShardRealestateDomain ShardRealestate; private int ReestablishAttempt { get @@ -46,7 +74,7 @@ private int ReestablishAttempt } public bool CanReestablish; - public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient cityClient, IDatabaseService db, IClientDataService ds, IKernel kernel, IShardsDomain shards) + public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient cityClient, IDatabaseService db, IClientDataService ds, IKernel kernel, IShardsDomain shards, IRealestateDomain realestate) { this.CityApi = cityApi; this.Client = cityClient; @@ -54,12 +82,18 @@ public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient c this.DB = db; this.DataService = ds; this.Shards = shards; + this.Realestate = realestate; AddState("Disconnected") .Default() .Transition() .OnData(typeof(ShardSelectorServletRequest)) - .TransitionTo("SelectCity"); + .TransitionTo("SelectCity") + .OnData(typeof(ConnectArchiveRequest)) + .TransitionTo("ArchiveConnect"); + + AddState("ArchiveConnect") + .OnlyTransitionFrom("Disconnected", "Reconnecting"); AddState("SelectCity") .OnlyTransitionFrom("Disconnected", "Reconnecting"); @@ -76,37 +110,61 @@ public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient c AddState("OpenSocket") .OnData(typeof(AriesConnected)).TransitionTo("SocketOpen") - .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") - .OnlyTransitionFrom("CitySelected"); + .OnData(typeof(AriesDisconnected)).TransitionTo("OpenSocketDisconnect") + .OnlyTransitionFrom("CitySelected", "ArchiveConnect"); AddState("SocketOpen") .OnData(typeof(RequestClientSession)).TransitionTo("RequestClientSession") + .OnData(typeof(RequestClientSessionArchive)).TransitionTo("RequestClientSessionArchive") .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") .OnlyTransitionFrom("OpenSocket"); + // Begin archive regulator states + + AddState("RequestClientSessionArchive") + .OnData(typeof(HostOnlinePDU)).TransitionTo("HostOnline") + .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") + .OnlyTransitionFrom("SocketOpen"); + + AddState("ArchiveSelectAvatar") + .OnData(typeof(ArchiveAvatarSelectResponse)).TransitionTo("ArchiveSelectedAvatar") + .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") + .OnlyTransitionFrom("PartiallyConnected"); + + AddState("ArchiveSelectedAvatar") + .OnlyTransitionFrom("ArchiveSelectAvatar"); + + // End archive regulator states + AddState("RequestClientSession") .OnData(typeof(HostOnlinePDU)).TransitionTo("HostOnline") .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") .OnlyTransitionFrom("SocketOpen"); - AddState("HostOnline").OnlyTransitionFrom("RequestClientSession"); + AddState("HostOnline").OnlyTransitionFrom("RequestClientSession", "RequestClientSessionArchive"); AddState("PartiallyConnected") .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") .OnData(typeof(ShardSelectorServletRequest)).TransitionTo("CompletePartialConnection") - .OnlyTransitionFrom("HostOnline"); + .OnData(typeof(ArchiveAvatarSelectRequest)).TransitionTo("ArchiveSelectAvatar") + .OnlyTransitionFrom("HostOnline", "ArchiveSelectedAvatar"); AddState("CompletePartialConnection").OnlyTransitionFrom("PartiallyConnected"); AddState("AskForAvatarData") .OnData(typeof(LoadAvatarByIDResponse)).TransitionTo("ReceivedAvatarData") - .OnlyTransitionFrom("PartiallyConnected", "CompletePartialConnection"); + .OnlyTransitionFrom("PartiallyConnected", "CompletePartialConnection", "ArchiveSelectedAvatar"); AddState("ReceivedAvatarData").OnlyTransitionFrom("AskForAvatarData"); AddState("AskForCharacterData").OnlyTransitionFrom("ReceivedAvatarData"); AddState("ReceivedCharacterData").OnlyTransitionFrom("AskForCharacterData"); + AddState("AskForCityData") + .OnData(typeof(CityInitResponse)).TransitionTo("ReceivedCityData") + .OnlyTransitionFrom("ReceivedCharacterData"); + AddState("ReceivedCityData").OnlyTransitionFrom("AskForCityData"); + AddState("Connected") .OnData(typeof(ServerByePDU)).TransitionTo("Disconnected") .OnData(typeof(AriesDisconnected)).TransitionTo("UnexpectedDisconnect") - .OnlyTransitionFrom("ReceivedCharacterData", "Reestablished"); + .OnlyTransitionFrom("ReceivedCharacterData", "ReceivedCityData", "Reestablished"); AddState("UnexpectedDisconnect"); @@ -144,6 +202,10 @@ public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient c .OnData(typeof(ShardSelectorServletRequest)).TransitionTo("SelectCity") .OnlyTransitionFrom("Reconnect"); + ClearUserList(); + + AvatarBinding = new Binding().WithBinding(this, "ModerationLevel", "Avatar_ModerationLevel"); + GameThread.SetInterval(() => { if (Client.IsConnected) @@ -153,8 +215,40 @@ public CityConnectionRegulator(CityClient cityApi, [Named("City")] AriesClient c }, 10000); //keep alive every 10 seconds. prevents disconnection by aggressive NAT. } + public string ServerIdFromPublicKey(string client) + { + HashAlgorithm algorithm = SHA1.Create(); + StringBuilder sb = new StringBuilder(); + var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(client)); + foreach (byte b in hash) + sb.Append(b.ToString("X2")); + + return sb.ToString().Substring(0, 8); + } + + public string ArchiveHash(string client, string server) + { + HashAlgorithm algorithm = SHA1.Create(); + StringBuilder sb = new StringBuilder(); + var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(client + server)); + foreach (byte b in hash) + sb.Append(b.ToString("X2")); + + return sb.ToString(); + } + + private void ClearUserList() + { + UserList = new ArchiveClientList() + { + Clients = new ArchiveClient[0], + Pending = new ArchivePendingVerification[0], + }; + } + public void Connect(CityConnectionMode mode, ShardSelectorServletRequest shard) { + ArchiveSettings = null; if(shard.ShardName == null && this.CurrentShard != null) { shard.ShardName = this.CurrentShard.ShardName; @@ -171,6 +265,33 @@ public void Connect(CityConnectionMode mode, ShardSelectorServletRequest shard) } } + public void ConnectArchive(ConnectArchiveRequest request) + { + ArchiveSettings = null; + Mode = CityConnectionMode.ARCHIVE; + if (CurrentState.Name != "Disconnected") + { + // TODO? + //CurrentShard = shard; + //AsyncTransition("Reconnect"); + } + else + { + AsyncProcessMessage(request); + } + } + + public bool ReturnToSASArchive() + { + if (ArchiveSettings != null) + { + ConnectArchive(ArchiveSettings); + return true; + } + + return false; + } + public void Disconnect(){ AsyncTransition("Disconnect"); } @@ -179,12 +300,38 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat { } + private static RSA TryGetCrypto(string publicKey) + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(publicKey); + + return rsa; + } + catch (Exception) + { + return null; + } + } + private ShardSelectorServletResponse LastSettings; protected override void OnBeforeTransition(RegulatorState oldState, RegulatorState newState, object data) { switch (newState.Name) { + case "ArchiveConnect": + var archiveOpt = data as ConnectArchiveRequest; + ArchiveSettings = archiveOpt; + this.AsyncTransition("OpenSocket", new ShardSelectorServletResponse() + { + Address = archiveOpt.CityAddress, + ExplicitPort = true + }); + break; + case "SelectCity": //TODO: Do this on logout / disconnect rather than on connect ResetGame(); @@ -202,6 +349,7 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta shard = data as ShardSelectorServletRequest; CurrentShard = shard; ShardSelectResponse = CityApi.ShardSelectorServlet(shard); + SpectatorMode = ShardSelectResponse.SpectatorMode; this.AsyncProcessMessage(ShardSelectResponse); break; @@ -217,7 +365,18 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta }else{ //101 is plain LastSettings = settings; - Client.Connect(settings.Address + "101"); + Client.Connect(settings.ExplicitPort ? settings.Address : PortTransformer.TransformAddress(settings.Address)); + } + break; + + case "OpenSocketDisconnect": + if (ArchiveSettings?.SelfHost == true) + { + GameThread.SetTimeout(() => AsyncTransition("OpenSocket", LastSettings), 100); + } + else + { + AsyncTransition("UnexpectedDisconnect"); } break; @@ -231,6 +390,53 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta }); break; + case "RequestClientSessionArchive": + var serverRequest = data as RequestClientSessionArchive; + + var rsa = TryGetCrypto(serverRequest.ServerKey); + + if (rsa == null || ArchiveSettings == null) + { + Disconnect(); + } + else + { + ((ClientShards)Shards).All = new List() + { + new ShardStatusItem() + { + Id = (int)serverRequest.ShardId, + Name = serverRequest.ShardName, + Status = ShardStatus.Up, + Map = serverRequest.ShardMap, + PublicHost = ArchiveSettings.CityAddress + } + }; + + CurrentShard = new ShardSelectorServletRequest() + { + ShardName = serverRequest.ShardName, + }; + + ArchiveConfig = serverRequest.ArchiveConfig; + + // Our ID for this server is derived from our client GUID - we don't send it directly. + var archiveUserID = ArchiveHash(GlobalSettings.Default.ArchiveClientGUID, serverRequest.ServerKey); + ArchiveServerID = ServerIdFromPublicKey(serverRequest.ServerKey); + DiscordRpcEngine.SetArchiveID(ArchiveServerID); + + // It's encrypted with the server's public key, so only the owner of this public key should be able to decrypt the id we use for their server. + ArchiveToken = Convert.ToBase64String(rsa.Encrypt(Encoding.UTF8.GetBytes($"{serverRequest.Nonce}\\{archiveUserID}"), RSAEncryptionPadding.Pkcs1)); + + Client.Write(new RequestClientSessionResponse + { + User = ArchiveSettings.DisplayName, + Unknown = 40, + Password = ArchiveToken, + }); + } + break; + case "HostOnline": ((ClientShards)Shards).CurrentShard = Shards.GetByName(CurrentShard.ShardName).Id; @@ -253,9 +459,28 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta } break; + case "ArchiveSelectAvatar": + var avaSelectRequest = data as ArchiveAvatarSelectRequest; + CurrentShard.AvatarID = avaSelectRequest.AvatarId.ToString(); + Client.Write(avaSelectRequest); + break; + + case "ArchiveSelectedAvatar": + var avaSelectResponse = data as ArchiveAvatarSelectResponse; + + if (avaSelectResponse.Code == ArchiveAvatarSelectCode.Success) + { + AsyncTransition("AskForAvatarData"); + } + else + { + AsyncTransition("PartiallyConnected"); + } + break; + case "CompletePartialConnection": var shardRequest = (ShardSelectorServletRequest)data; - if (shardRequest.ShardName != CurrentShard.ShardName) + if (Mode != CityConnectionMode.ARCHIVE && shardRequest.ShardName != CurrentShard.ShardName) { //Should never get into this state throw new Exception("You cant complete a partial connection for a different city"); @@ -291,14 +516,65 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta } else { + var ava = (Avatar)x.Result; + AvatarBinding.Value = ava; + + ModerationLevel = ava.Avatar_ModerationLevel; + AsyncTransition("ReceivedCharacterData"); } }); break; case "ReceivedCharacterData": - //For now, we will call this connected - AsyncTransition("Connected"); + // If the city map was reported as "dynamic", we need to load the map from the server. + { + var shards = (ClientShards)Shards; + var shardItem = shards.GetById(shards.CurrentShard ?? 1); + if (shardItem.Map == "dynamic") + { + // We need to ask the city for the city data. + AsyncTransition("AskForCityData"); + } + else + { + //For now, we will call this connected + AsyncTransition("Connected"); + } + break; + } + + case "AskForCityData": + Client.Write( + new CityInitRequest() + { + } + ); + break; + + case "ReceivedCityData": + var cityInit = data as CityInitResponse; + + GameThread.InUpdate(() => + { + var shards = (ClientShards)Shards; + var shardId = shards.CurrentShard ?? 1; + var marshal = new CityMapMarshal(); + marshal.Read(cityInit.CityData); + + shards.SetShardMapBase(shardId, marshal); + + // Initialize the realestate with the initial command list + + ShardRealestate = Realestate.GetByShard(shardId); + foreach (var cmd in cityInit.Commands) + { + ShardRealestate.AppendCommand(cmd.Command); + } + + AsyncTransition("Connected"); + }); + break; case "Connected": @@ -308,7 +584,10 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta case "UnexpectedDisconnect": if (ReestablishAttempt > 0 || !CanReestablish) { - FSOFacade.Controller.FatalNetworkError(23); + GameThread.InUpdate(() => + { + FSOFacade.Controller.FatalNetworkError(23); + }); } else { @@ -318,16 +597,29 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta case "Reestablish": ReestablishAttempt++; - Client.Connect(LastSettings.Address + "101"); + Client.Connect(LastSettings.ExplicitPort ? LastSettings.Address : PortTransformer.TransformAddress(LastSettings.Address)); break; case "Reestablishing": - Client.Write(new RequestClientSessionResponse + if (ArchiveSettings != null) { - Password = ShardSelectResponse.Ticket, - User = ShardSelectResponse.AvatarID, - Unknown2 = 1 - }); + Client.Write(new RequestClientSessionResponse + { + User = CurrentShard.AvatarID, + Password = ArchiveToken, + Unknown = 40, + Unknown2 = 1 + }); + } + else + { + Client.Write(new RequestClientSessionResponse + { + Password = ShardSelectResponse.Ticket, + User = ShardSelectResponse.AvatarID, + Unknown2 = 1 + }); + } break; case "Reestablished": @@ -385,9 +677,12 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta break; case "Disconnected": + DiscordRpcEngine.Reset(); ((ClientShards)Shards).CurrentShard = null; + Realestate.Reset(); ReestablishAttempt = 0; CanReestablish = false; + ClearUserList(); break; } } @@ -401,37 +696,72 @@ public void ResetGame() public void MessageReceived(AriesClient client, object message) { - if (message is RequestClientSession || - message is HostOnlinePDU || message is ServerByePDU) + if (message is RequestClientSession || message is RequestClientSessionArchive || + message is HostOnlinePDU || message is ServerByePDU || message is ArchiveAvatarSelectResponse || message is CityInitResponse) { this.AsyncProcessMessage(message); } - else if (message is AnnouncementMsgPDU) + else if (message is ArchiveClientList list) + { + GameThread.InUpdate(() => + { + UserList = list; + // TODO: notify + }); + } + else if (message is AnnouncementMsgPDU msg) { GameThread.InUpdate(() => { - var msg = (AnnouncementMsgPDU)message; - UIAlert alert = null; - alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + if (msg.Badge == 255) + { + // This message replaces the network error message when we disconnect. + FSOFacade.Controller.FatalErrorMessage(msg); + } + else { - Title = GameFacade.Strings.GetString("195", "30") + GameFacade.CurrentCityName, - Message = GameFacade.Strings.GetString("195", "28") + msg.SenderID.Substring(2) + "\r\n" - + GameFacade.Strings.GetString("195", "29") + msg.Subject + "\r\n" - + msg.Message, - Buttons = UIAlertButton.Ok((btn) => UIScreen.RemoveDialog(alert)), - Alignment = TextAlignment.Left - }, true); + UIAlert alert = null; + alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("195", "30") + GameFacade.CurrentCityName, + Message = GameFacade.Strings.GetString("195", "28") + msg.SenderID.Substring(2) + "\r\n" + + GameFacade.Strings.GetString("195", "29") + msg.Subject + "\r\n" + + msg.Message, + Buttons = UIAlertButton.Ok((btn) => UIScreen.RemoveDialog(alert)), + Alignment = TextAlignment.Left + }, true); + } }); } - else if (message is GlobalTuningUpdate) + else if (message is GlobalTuningUpdate tuning) { - var msg = (message as GlobalTuningUpdate); - DynamicTuning.Global = msg.Tuning; - Content.Content.Get().Upgrades.LoadNetTuning(msg.ObjectUpgrades); + DynamicTuning.Global = tuning.Tuning; + Content.Content.Get().Upgrades.LoadNetTuning(tuning.ObjectUpgrades); } - else if (message is ChangeRoommateResponse) + else if (message is CityUpdateResponse city) { - + GameThread.NextUpdate(x => + { + foreach (var cmd in city.Commands) + { + ShardRealestate?.AppendCommand(cmd.Command); + } + }); + } + else if (message is CityUpdateCommand cmd) + { + GameThread.NextUpdate(x => + { + switch (cmd.Mode) + { + case CityUpdateCommandMode.Undo: + ShardRealestate?.HandleUserCommand(cmd); + break; + case CityUpdateCommandMode.SetCityName: + GameFacade.CurrentCityName = cmd.CityName; + break; + } + }); } } @@ -464,7 +794,8 @@ public void InputClosed(AriesClient session) public enum CityConnectionMode { CAS, - NORMAL + NORMAL, + ARCHIVE } class AriesConnected { diff --git a/TSOClient/tso.client/Regulators/GenericActionRegulator.cs b/TSOClient/tso.client/Regulators/GenericActionRegulator.cs index d75adc968..65c0efb38 100644 --- a/TSOClient/tso.client/Regulators/GenericActionRegulator.cs +++ b/TSOClient/tso.client/Regulators/GenericActionRegulator.cs @@ -1,4 +1,5 @@ -using FSO.Server.Clients; +using FSO.Common.Utils; +using FSO.Server.Clients; using FSO.Server.Clients.Framework; using FSO.Server.Protocol.Electron.Model; using FSO.Server.Protocol.Electron.Packets; @@ -10,6 +11,7 @@ public class GenericActionRegulator : AbstractRegulator, IAriesMessageSub public T CurrentRequest; private Network.Network Network; public NhoodCandidateList CandidateList; + public event Callback OnMessage; public GenericActionRegulator(Network.Network network) { @@ -121,6 +123,10 @@ public void MessageReceived(AriesClient client, object message) { CandidateList = (NhoodCandidateList)message; } + else + { + OnMessage?.Invoke(message); + } } } } diff --git a/TSOClient/tso.client/Regulators/LoginRegulator.cs b/TSOClient/tso.client/Regulators/LoginRegulator.cs index 3fb8b1d23..bfaff7e7a 100644 --- a/TSOClient/tso.client/Regulators/LoginRegulator.cs +++ b/TSOClient/tso.client/Regulators/LoginRegulator.cs @@ -28,7 +28,7 @@ public LoginRegulator(AuthClient authClient, CityClient cityClient, IShardsDomai this.Shards = domain; this.AuthClient = authClient; this.CityClient = cityClient; - + AddState("NotLoggedIn") .Default() .Transition() @@ -76,9 +76,11 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat } break; case "InitialConnect": - try { + try + { var connectResult = CityClient.InitialConnectServlet( - new InitialConnectServletRequest { + new InitialConnectServletRequest + { Ticket = AuthResult.Ticket, Version = "Version 1.1097.1.0" }); @@ -102,7 +104,8 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat { base.ThrowErrorAndReset(ErrorMessage.FromLiteral(connectResult.Error.Code, connectResult.Error.Message)); } - }catch(Exception ex) + } + catch (Exception ex) { base.ThrowErrorAndReset(ex); } @@ -110,7 +113,8 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat case "UpdateRequired": break; case "AvatarData": - try { + try + { Avatars = CityClient.AvatarDataServlet(); AsyncTransition("ShardStatus"); } @@ -121,7 +125,8 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat break; case "ShardStatus": - try { + try + { ((ClientShards)Shards).All = CityClient.ShardStatus(); AsyncTransition("LoggedIn"); } @@ -140,23 +145,17 @@ public bool RequireUpdate(UserAuthorized auth) { if (auth.FSOVersion == null) return false; - var str = GlobalSettings.Default.ClientVersion; - var authstr = auth.FSOBranch + "-" + auth.FSOVersion; - - return str != authstr; - - /* - var split = str.LastIndexOf('-'); - int verNum = 0; - int.TryParse(split.) - */ + var version = FSOVersionInfo.Current; + + return !version.Equals(auth.GetVersion()); } protected override void OnBeforeTransition(RegulatorState oldState, RegulatorState newState, object data) { } - public void Login(AuthRequest request){ + public void Login(AuthRequest request) + { this.AsyncProcessMessage(request); } diff --git a/TSOClient/tso.client/Regulators/LotConnectionRegulator.cs b/TSOClient/tso.client/Regulators/LotConnectionRegulator.cs index 2983f7f03..a73d35910 100644 --- a/TSOClient/tso.client/Regulators/LotConnectionRegulator.cs +++ b/TSOClient/tso.client/Regulators/LotConnectionRegulator.cs @@ -1,10 +1,14 @@ using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Screens; using FSO.Common.DataService; +using FSO.Common.Model; using FSO.Common.Utils; using FSO.Server.Clients; using FSO.Server.Clients.Framework; using FSO.Server.Protocol.Aries.Packets; using FSO.Server.Protocol.Electron.Packets; +using FSO.Server.Protocol.Utils; using FSO.Server.Protocol.Voltron.Packets; using Ninject; using System; @@ -20,6 +24,7 @@ public class LotConnectionRegulator : AbstractRegulator, IAriesMessageSubscriber private bool IsDisconnecting = true; private string LastAddress; private int _ReestablishAttempt; + private int _ConnectionId; private int ReestablishAttempt { get @@ -33,7 +38,11 @@ private int ReestablishAttempt } } + private bool InLot; + public bool LeavingLot; + private FindLotResponse FindLotResponse; + private LotTransitionInfo ActiveTransition; private IClientDataService DataService; public LotConnectionRegulator([Named("City")] AriesClient cityClient, [Named("Lot")] AriesClient lotClient, IClientDataService dataService) @@ -111,19 +120,34 @@ public LotConnectionRegulator([Named("City")] AriesClient cityClient, [Named("Lo .TransitionTo("Disconnected"); } + private string TransformLotAddress(string address) + { + if (address.StartsWith("0.0.0.0:")) + { + // Use the same address as the city server. + return City.RemoteEndPoint.Address.ToString() + address.Substring(7); + } + + return address; + } + protected override void OnAfterTransition(RegulatorState oldState, RegulatorState newState, object data) { switch (newState.Name) { case "SelectLot": IsDisconnecting = false; + _ConnectionId++; + LeavingLot = false; AsyncTransition("FindLot", data); break; case "FindLot": //LotId = ((JoinLotRequest)data).LotId; + var joinReq = ((JoinLotRequest)data); + ActiveTransition = joinReq.Transition; City.Write(new FSO.Server.Protocol.Electron.Packets.FindLotRequest { - LotId = ((JoinLotRequest)data).LotId + LotId = joinReq.LotId }); break; case "FoundLot": @@ -132,7 +156,7 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat { LotId = result.LotId; FindLotResponse = result; - AsyncTransition("OpenSocket", result.Address); + AsyncTransition("OpenSocket", TransformLotAddress(result.Address)); } else { @@ -151,7 +175,7 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat else { //101 is plain - Client.Connect(address + "101"); + Client.Connect(PortTransformer.TransformAddress(address)); } break; @@ -162,8 +186,17 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat Client.Write(new RequestClientSessionResponse { Password = FindLotResponse.LotServerTicket, - User = FindLotResponse.User + User = FindLotResponse.User, + ServiceIdent = ActiveTransition != null ? "JLT" : null }); + + if (ActiveTransition != null) + { + Client.Write(new JoinLotWithTransitionRequest + { + Transition = ActiveTransition, + }); + } break; case "HostOnline": @@ -178,24 +211,33 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat //When we join a property, get the lot info to update the thumbnail cache DataService.Request(Server.DataService.Model.MaskedStruct.PropertyPage_LotInfo, LotId); break; + case "LotCommandStream": + InLot = true; + break; case "UnexpectedDisconnect": - if (ReestablishAttempt > 0) + if (LeavingLot || ReestablishAttempt > 0) { IsDisconnecting = true; AsyncTransition("Disconnected"); } else { + var oldId = _ConnectionId; + // We might be deliberately disconnecting, so wait a bit before re-establishing. GameThread.SetTimeout(() => { - if (CurrentState?.Name == "UnexpectedDisconnect") - { - AsyncTransition("Reestablish"); - } - else if (CurrentState?.Name != "Disconnected") + // If we started a new connection, we don't care anymore. + if (_ConnectionId == oldId) { - IsDisconnecting = true; - AsyncTransition("Disconnected"); + if (CurrentState?.Name == "UnexpectedDisconnect") + { + AsyncTransition("Reestablish"); + } + else if (CurrentState?.Name != "Disconnected") + { + IsDisconnecting = true; + AsyncTransition("Disconnected"); + } } }, 100); } @@ -203,7 +245,7 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat case "Reestablish": ReestablishAttempt++; - Client.Connect(LastAddress + "101"); + Client.Connect(PortTransformer.TransformAddress(LastAddress)); break; case "Reestablishing": @@ -256,6 +298,7 @@ protected override void OnAfterTransition(RegulatorState oldState, RegulatorStat break; case "Disconnected": + InLot = false; ReestablishAttempt = 0; break; } @@ -266,17 +309,20 @@ protected override void OnBeforeTransition(RegulatorState oldState, RegulatorSta } public void Disconnect() { - AsyncTransition("Disconnect"); + if (CurrentState.Name != "Disconnected") + { + AsyncTransition("Disconnect"); + } } - public void JoinLot(uint id) + public void JoinLot(uint id, LotTransitionInfo transition = null) { - AsyncProcessMessage(new JoinLotRequest { LotId = id }); + AsyncProcessMessage(new JoinLotRequest { LotId = id, Transition = transition }); } public uint GetCurrentLotID() { - if (CurrentState.Name == "LotCommandStream") return LotId; + if (InLot) return LotId; else return 0; } @@ -314,6 +360,13 @@ message is FSOVMDirectToClient || UIAlert.Alert(msg.Title, msg.Message, true); }); } + + if (message is FSOVMSurroundPuppets puppets) + { + GameThread.InUpdate(() => { + (UIScreen.Current as CoreGameScreen)?.SurroundPuppets?.Process(puppets); + }); + } } } @@ -348,5 +401,6 @@ public void InputClosed(AriesClient session) class JoinLotRequest { public uint LotId; + public LotTransitionInfo Transition; } } diff --git a/TSOClient/tso.client/Rendering/City/CityCamera.cs b/TSOClient/tso.client/Rendering/City/CityCamera.cs deleted file mode 100644 index 88ea752b1..000000000 --- a/TSOClient/tso.client/Rendering/City/CityCamera.cs +++ /dev/null @@ -1,314 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework; -using tso.common.rendering.framework.camera; - -namespace TSOClient.Code.Rendering.City -{ - public class CityCamera : ICamera - { - private float _FarZoomScale = 5.10f; - private float _NearZoomScale = 144.0f; - private float _ScreenWidth = 1024.0f; - private float _ScreenHeight = 768.0f; - private float _RotationX = 45.0f; - private float _RotationY = 30.0f; - private float _TranslationX = -360.0f; - private float _TranslationY = -512.0f; - private float _ZoomProgress = 0.0f; - - private float _ViewOffX = 0.0f; - private float _ViewOffY = 0.0f; - - - private bool _DirtyView = true; - private Matrix _View; - - private bool _DirtyProjection = true; - private Matrix _Projection; - - public float ZoomProgress - { - get - { - return _ZoomProgress; - } - set - { - _ZoomProgress = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float TranslationY - { - get - { - return _TranslationY; - } - set - { - _TranslationY = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float TranslationX - { - get - { - return _TranslationX; - } - set - { - _TranslationX = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float RotationY - { - get - { - return _RotationY; - } - set - { - _RotationY = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - - public float RotationX - { - get - { - return _RotationX; - } - set - { - _RotationX = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - - public float FarZoomScale - { - get - { - return _FarZoomScale; - } - set - { - _FarZoomScale = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float NearZoomScale - { - get - { - return _NearZoomScale; - } - set - { - _NearZoomScale = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float ScreenWidth - { - get - { - return _ScreenWidth; - } - set - { - _ScreenWidth = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - public float ScreenHeight - { - get - { - return _ScreenHeight; - } - set - { - _ScreenHeight = value; - _DirtyView = true; - _DirtyProjection = true; - } - } - - #region ICamera Members - - public Microsoft.Xna.Framework.Matrix View - { - get { - if (_DirtyView) - { - _View = Matrix.Identity; - _View *= Matrix.CreateRotationY((_RotationX / 180.0f) * MathHelper.Pi); - _View *= Matrix.CreateRotationX((_RotationY / 180.0f) * MathHelper.Pi); - _View *= Matrix.CreateTranslation(new Vector3(_TranslationX, 0.0f, _TranslationY)); - _View *= Matrix.CreateScale(1.0f, 0.5f + ((1.0f - _ZoomProgress) / 2.0f), 1.0f); - - _DirtyView = false; - } - return _View; - } - } - - public Microsoft.Xna.Framework.Matrix Projection - { - get { - - if (_DirtyProjection) - { - var device = GameFacade.GraphicsDevice; - var aspect = device.Viewport.AspectRatio * AspectRatioMultiplier; - - - var fisoScale = (float)Math.Sqrt(0.5f * 0.5f * 2.0f) / _FarZoomScale; // is 5.10 on far zoom - var zisoScale = (float)Math.Sqrt(0.5f * 0.5f * 2.0f) / _NearZoomScale; // currently set 144 to near zoom - - var zoomProgress = 0f; - var isoScale = (float)((1.0f - _ZoomProgress) * fisoScale + (_ZoomProgress) * zisoScale); - - var hb = ((_ScreenWidth) * isoScale); - var vb = ((_ScreenHeight) * isoScale) * AspectRatioMultiplier; - - - _Projection = Microsoft.Xna.Framework.Matrix.CreateOrthographicOffCenter((float)-hb + _ViewOffX, (float)hb + _ViewOffX, ((float)-vb + _ViewOffY), ((float)vb + _ViewOffY), 0.1f, 1000000); - _DirtyProjection = false; - } - - - return _Projection; - } - } - - public Microsoft.Xna.Framework.Vector3 Position - { - get - { - return Vector3.Zero; - } - set - { - } - } - - public Microsoft.Xna.Framework.Vector3 Target - { - get - { - return Vector3.Zero; - } - set - { - } - } - - public Microsoft.Xna.Framework.Vector3 Up - { - get - { - return Vector3.Up; - } - set - { - } - } - - public Microsoft.Xna.Framework.Vector3 Translation - { - get - { - return Vector3.Zero; - } - set - { - } - } - - public Microsoft.Xna.Framework.Vector2 ProjectionOrigin - { - get - { - return Vector2.Zero; - } - set - { - } - } - - public float NearPlane - { - get - { - throw new NotImplementedException(); - } - set - { - throw new NotImplementedException(); - } - } - - public float FarPlane - { - get - { - throw new NotImplementedException(); - } - set - { - throw new NotImplementedException(); - } - } - - public float Zoom - { - get - { - return 1.0f; - } - set - { - } - } - - private float _AspectRatioMultiplier = 1.0f; - public float AspectRatioMultiplier - { - get - { - return _AspectRatioMultiplier; - } - set - { - _AspectRatioMultiplier = value; - _DirtyProjection = true; - } - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/City/CityCamera2D.cs b/TSOClient/tso.client/Rendering/City/CityCamera2D.cs index fcabdb753..01415e6d8 100644 --- a/TSOClient/tso.client/Rendering/City/CityCamera2D.cs +++ b/TSOClient/tso.client/Rendering/City/CityCamera2D.cs @@ -14,7 +14,7 @@ namespace FSO.Client.Rendering.City public class CityCamera2D : ICityCamera { public static float NEAR_ZOOM_SIZE = 288; - public float m_WheelZoom; + public float m_WheelZoom = 0.5f; public float LotZoomProgress { get; set; } = 0; public float ZoomProgress { get; set; } //settable to avoid discontinuities public float m_LotZoomSize = 72 * 128; //near zoom, set by world @@ -61,15 +61,15 @@ public bool HideUI public float m_WheelZoomTarg = 0.5f; private int? m_LastWheelPos; //null if invalid, increments in 120 it seems. - private Vector2 LastTargOff; public float m_ViewOffX, m_ViewOffY, m_TargVOffX, m_TargVOffY; private float m_ScrollSpeed; private Vector2 m_MouseStart; private bool WasRMBDown; + private bool MouseIsOn = true; - public float GetIsoScale() + public float GetIsoScale(int width, int height) { - float ResScale = 768.0f / UIScreen.Current.ScreenHeight; //scales up the vertical height to match that of the target resolution (for the far view) + float ResScale = 768.0f / height; //scales up the vertical height to match that of the target resolution (for the far view) float FisoScale = (float)(Math.Sqrt(0.5 * 0.5 * 2) / 5.10f) * ResScale; // is 5.10 on far zoom float ZisoScale = (float)Math.Sqrt(0.5 * 0.5 * 2) / (NEAR_ZOOM_SIZE * m_WheelZoom); // currently set 144 to near zoom float LisoScale = (float)Math.Sqrt(0.5 * 0.5 * 2) / m_LotZoomSize; // currently set 144 to near zoom @@ -79,10 +79,23 @@ public float GetIsoScale() return (1 - LotZoomProgress) * IsoScale + LotZoomProgress * LisoScale; } + public float GetIsoScale() + { + var screen = UIScreen.Current; + return GetIsoScale(screen.ScreenWidth, screen.ScreenHeight); + } + public void MouseEvent(UIMouseEventType type, UpdateState state) { - if (type == UIMouseEventType.MouseOut) + if (type == UIMouseEventType.MouseOver) + { + MouseIsOn = true; + } + else if (type == UIMouseEventType.MouseOut) + { + MouseIsOn = false; m_LastWheelPos = null; + } } public float AspectRatioMultiplier @@ -245,14 +258,24 @@ public Matrix CalculateView() public Matrix CalculateProjection() { - var isoScale = GetIsoScale(); var screen = UIScreen.Current; - float HB = screen.ScreenWidth * isoScale; - float VB = screen.ScreenHeight * isoScale; + return CalculateProjection(screen.ScreenWidth, screen.ScreenHeight); + } + + public Matrix CalculateProjection(int width, int height) + { + var isoScale = GetIsoScale(width, height); + float HB = width * isoScale; + float VB = height * isoScale; return Matrix.CreateOrthographicOffCenter(-HB + m_ViewOffX, HB + m_ViewOffX, -VB + m_ViewOffY, VB + m_ViewOffY, 0.1f, 524); } - + + public void CalculateLotSquish(Matrix view) + { + + } + private bool PDirty = true; public void ProjectionDirty() { @@ -282,7 +305,7 @@ public void InheritPosition(Terrain parent, World lotWorld, CoreGameScreenContro { if (controller != null) { - var id = controller.GetCurrentLotID(); + var id = controller.GetVisualLotID(); if (id != 0) { //center on this lot, with the given camera offset @@ -326,7 +349,7 @@ public void Update(UpdateState state, Terrain city) { var screen = UIScreen.Current; - if (Zoomed == TerrainZoomMode.Near) + if (Zoomed == TerrainZoomMode.Near && MouseIsOn) { if (m_LastWheelPos != null && Math.Abs(m_LastWheelPos.Value - state.MouseState.ScrollWheelValue) < 1000) m_WheelZoomTarg = Math.Max(0.33f, Math.Min(1f, m_WheelZoomTarg - (m_LastWheelPos.Value - state.MouseState.ScrollWheelValue) / 1000f)); @@ -349,9 +372,6 @@ public void Update(UpdateState state, Terrain city) m_MouseStart = new Vector2(m_MouseState.X, m_MouseState.Y); //if middle mouse button activated, record where we started pressing it (to use for panning) } - LastTargOff = new Vector2(m_TargVOffX, m_TargVOffY); - - var rScale = 60f / FSOEnvironment.RefreshRate; if (Zoomed != TerrainZoomMode.Far) ZoomProgress += (1.0f - ZoomProgress) * (float)(1 - Math.Pow(4 / 5.0f, rScale)); if (Zoomed == TerrainZoomMode.Near) @@ -371,25 +391,26 @@ public void Update(UpdateState state, Terrain city) } else if (GlobalSettings.Default.EdgeScroll && state.ProcessMouseEvents) //edge scroll check - do this even if mouse events are blocked { - if (m_MouseState.X > screen.ScreenWidth - 32) + float scale = 1f / FSOEnvironment.DPIScaleFactor; + if (m_MouseState.X * scale > screen.ScreenWidth - 32) { Triggered = true; m_TargVOffX += m_ScrollSpeed * rScale; CursorManager.INSTANCE.SetCursor(CursorType.ArrowRight); } - if (m_MouseState.X < 32) + if (m_MouseState.X * scale < 32) { Triggered = true; m_TargVOffX -= m_ScrollSpeed * rScale; CursorManager.INSTANCE.SetCursor(CursorType.ArrowLeft); } - if (m_MouseState.Y > screen.ScreenHeight - 32) + if (m_MouseState.Y * scale > screen.ScreenHeight - 32) { Triggered = true; m_TargVOffY -= m_ScrollSpeed * rScale; CursorManager.INSTANCE.SetCursor(CursorType.ArrowDown); } - if (m_MouseState.Y < 32) + if (m_MouseState.Y * scale < 32) { Triggered = true; m_TargVOffY += m_ScrollSpeed * rScale; diff --git a/TSOClient/tso.client/Rendering/City/CityCamera3D.cs b/TSOClient/tso.client/Rendering/City/CityCamera3D.cs index b9f942d12..9385e57e4 100644 --- a/TSOClient/tso.client/Rendering/City/CityCamera3D.cs +++ b/TSOClient/tso.client/Rendering/City/CityCamera3D.cs @@ -11,6 +11,7 @@ using FSO.Client.UI.Panels; using FSO.Common.Rendering.Framework.IO; using FSO.LotView.Model; +using FSO.Common.Utils; namespace FSO.Client.Rendering.City { @@ -20,6 +21,7 @@ public class CityCamera3D : BasicCamera, ICityCamera, I3DRotate, ITouchable private Point LastMouse; private bool MouseWasDown; private UILotControlTouchHelper Touch; + public bool MouseIsOn { get; private set; } = true; public CityCameraCenter CenterCam { get; set; } // Set a center delta, then tween to 0 to smootly move to a target location. @@ -75,7 +77,7 @@ public float LotSquish { get { - return 1f / (0.33f + (float)(1.0 - LotZoomProgress) / 1.5f); + return LotZoomProgress > 0 ? _lotSquish : 1f; } } @@ -83,7 +85,7 @@ public float DepthBiasScale { get { - return 1f; + return LotZoomProgress > 0 ? 20f * LotZoomProgress + 0.05f : 1f; } } @@ -157,6 +159,18 @@ public Vector2 CalculateRShadow() return new Vector2(256, 256); } + private float _lotSquish; + + public void CalculateLotSquish(Matrix view) + { + var yProbe = new Vector3(0, 1, 0); + var xProbe = new Vector3(1, 0, 0); + var transformedY = Vector3.TransformNormal(yProbe, view); + var transformedX = Vector3.TransformNormal(xProbe, view); + + _lotSquish = 3 / (transformedY.Length() / transformedX.Length()); + } + public float GetIsoScale() { return 1f; @@ -164,11 +178,12 @@ public float GetIsoScale() private float TargRX; private float TargRY; + private float InheritElevation; public void InheritPosition(Terrain parent, World lotWorld, CoreGameScreenController controller, bool instant) { if (controller != null) { - var id = controller.GetCurrentLotID(); + var id = controller.GetVisualLotID(); if (id != 0) { //center on this lot, with the given camera offset @@ -182,11 +197,17 @@ public void InheritPosition(Terrain parent, World lotWorld, CoreGameScreenContro } float elev = parent.GetElevationAt((int)x, (int)y); + InheritElevation = elev / 12f; var tile = (lotWorld.State.CenterTile - new Vector2(2, 2)) / 72; //72 is the base lot size parent.LotPosition = new Vector3((float)(x + 1), elev / 12.0f, (float)(y + 0)); + if (LotZoomProgress == 1) + { + instant = true; + } + if (instant) { CenterTile = new Vector2((float)(x + 1) - tile.Y, (float)(y + 0) + tile.X); @@ -205,8 +226,12 @@ public void InheritPosition(Terrain parent, World lotWorld, CoreGameScreenContro } else if (LotZoomProgress != 1) { - RotationX += (TargRX - RotationX) / 10; - RotationY += (TargRY - RotationY) / 10; + var speed = 60.0 / FSOEnvironment.RefreshRate; + var interpPercent = 1 - (float)Math.Pow(0.9, speed); + + var dirDiff = (float)DirectionUtils.Difference(RotationX, TargRX); + RotationX += dirDiff * interpPercent; + RotationY += (TargRY - RotationY) * interpPercent; } else { @@ -229,8 +254,13 @@ public void MouseEvent(UIMouseEventType type, UpdateState state) { Touch.MiceDown.Remove(state.CurrentMouseID); } + else if (type == UIMouseEventType.MouseOver) + { + MouseIsOn = true; + } else if (type == UIMouseEventType.MouseOut) { + MouseIsOn = false; LastWheelPos = null; } } @@ -269,7 +299,7 @@ public void Update(UpdateState state, Terrain city) if (screen.vm != null && screen.vm.Ready && screen.WorldLoaded) { var controller = screen.FindController(); - var id = controller.GetCurrentLotID(); + var id = controller.GetVisualLotID(); var x = id >> 16; var y = id & 0xFFFF; @@ -284,24 +314,6 @@ public void Update(UpdateState state, Terrain city) if (TargetZoom > 2f && inCity) TargetZoom -= (TargetZoom - 2f) * (1f - (float)Math.Pow(0.975f, 60f / FSOEnvironment.RefreshRate)); Zoom3D += ((12f - (TargetZoom - 0.25f) * 6.8571428571428571428571428571429f) - Zoom3D) / 10; - /* - * replaced by touch helper - * - if (LastWheelPos != null && state.WindowFocused && state.MouseState.ScrollWheelValue != 0 && Zoomed != TerrainZoomMode.Lot) { - var diff = state.MouseState.ScrollWheelValue - LastWheelPos.Value; - UserModZoom = diff != 0; - TargetZoom = TargetZoom + diff / 1600f; - TargetZoom = Math.Max(0.25f, Math.Min(TargetZoom, 2.5f)); - } - if (state.WindowFocused) - { - LastWheelPos = state.MouseState.ScrollWheelValue; - } else - { - LastWheelPos = null; - } - */ - //rmb scroll if (state.MouseState.RightButton == ButtonState.Pressed) { @@ -363,13 +375,14 @@ public void Update(UpdateState state, Terrain city) var relative = ComputeCenterRelative(); terrainHeight = (city.InterpElevationAt(CenterTile)); - var targHeight = terrainHeight; + var heightBias = (float)Math.Pow(LotZoomProgress, 0.1); // Float to the target lot's height. + var targHeight = InheritElevation * heightBias + terrainHeight * (1 - heightBias); var heightAtCam = city.InterpElevationAt(new Vector2(Position.X, Position.Z)); if (relative.Y + targHeight < heightAtCam + 0.5f) targHeight = (heightAtCam + 0.5f) - relative.Y; //targHeight = Math.Max(heightAtCam, terrainHeight); CamHeight += (targHeight - CamHeight) * (1f - (float)Math.Pow(0.8f, 60f / FSOEnvironment.RefreshRate)); - if (inCity && state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Tab) && !state.AltDown) + if (inCity && state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Tab) && !state.AltDown && state.InputManager.GetFocus() == null) { CameraMode = !CameraMode; } diff --git a/TSOClient/tso.client/Rendering/City/CityContent.cs b/TSOClient/tso.client/Rendering/City/CityContent.cs index 8dc07582c..89dcbecb5 100644 --- a/TSOClient/tso.client/Rendering/City/CityContent.cs +++ b/TSOClient/tso.client/Rendering/City/CityContent.cs @@ -1,12 +1,12 @@ using FSO.Client.UI.Framework; using FSO.Common; +using FSO.Common.Domain.Realestate; using FSO.Common.Model; using FSO.Common.Utils; +using FSO.Content.Model; using FSO.Files; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.IO; namespace FSO.Client.Rendering.City { @@ -15,9 +15,9 @@ namespace FSO.Client.Rendering.City /// public class CityContent { - public Texture2D[] TerrainTextures = new Texture2D[5]; //grass, sand, rock, snow, water + public readonly Texture2D[] TerrainTextures = new Texture2D[5]; //grass, sand, rock, snow, water public Texture2D VertexColor; - public CityMapData MapData; + public CityMap MapData; public Texture2D[] TransA; //moved into an atlas public Texture2D[] TransB; //moved into an atlas @@ -25,7 +25,7 @@ public class CityContent public Texture2D[] Roads; //moved into an atlas public Texture2D[] RoadCorners; //moved into an atlas - public Texture2D[] TransAtlas = new Texture2D[4]; + public readonly Texture2D[] TransAtlas = new Texture2D[4]; public Texture2D RoadAtlas; public Texture2D Forest; public Texture2D WhiteLine; @@ -39,7 +39,14 @@ public class CityContent public Texture2D SmallWNormal; //small water normal, for small scale normal map changes public Texture2D TreeTex; - public string[] NeighTexNames = new string[] { "circles.png", "triangles.png", "squares.png" }; + public Texture2D PainterSpike; + public Texture2D PainterCursor; + public Texture2D PainterCursorActive; + public Texture2D PainterCursorAnchor; + public Texture2D PainterRoadIcon; + public Texture2D PainterRoadDel; + + public readonly string[] NeighTexNames = ["circles.png", "triangles.png", "squares.png"]; public Texture2D[] NeighTextures = new Texture2D[3]; /// @@ -48,39 +55,23 @@ public class CityContent /// /// 7x3 /// - public static int[] FlagLayout = new int[] - { + public static readonly int[] FlagLayout = + [ 11, 7, 15, 2, 9, 6, 0, 4, 1, 16, 20, 12, 14, 18, 10, 8 - }; + ]; - public static int RoadWidth = 8; - public static int RoadHeight = 4; - public static int[] RoadLayout = new int[] { -1, 5, 12, 13, 7, 6, 15, 14, 28, 29, 20, 21, 31, 30, 23, 22 }; - public static int[] RoadCLayout = new int[] { -1, 8, 2, 26, 3, 17, 16, 10, 25, 24, 9, 18, 1, 27, 11, 19 }; + public static readonly int RoadWidth = 8; + public static readonly int RoadHeight = 4; + public static readonly int[] RoadLayout = [-1, 5, 12, 13, 7, 6, 15, 14, 28, 29, 20, 21, 31, 30, 23, 22]; + public static readonly int[] RoadCLayout = [-1, 8, 2, 26, 3, 17, 16, 10, 25, 24, 9, 18, 1, 27, 11, 19]; - public void LoadContent(GraphicsDevice gd, int cityNumber) + public void LoadContent(GraphicsDevice gd, CityMap map) { String gamepath = GameFacade.GameFilePath(""); - string CityStr = "city_" + cityNumber.ToString("0000"); - string ext = "bmp"; - if (cityNumber >= 100) - { - //start FSO cities - //the first few will be client included - //probably after 200 will be inherited from content packs, when they are implemented - ext = "png"; - CityStr = Path.Combine(FSOEnvironment.ContentDir, "Cities/", CityStr); - } - else - { - CityStr = gamepath + "cities/" + CityStr; - } - VertexColor = LoadTex(CityStr + "/vertexcolor." + ext); + MapData = map; + VertexColor = map.VertexColour?.Get(gd); - MapData = new CityMapData(); - MapData.Load(CityStr, LoadTex, ext); - //special tuning from server var terrainTuning = DynamicTuning.Global?.GetTable("city", 0); float forceSnow = 0f; @@ -92,12 +83,12 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) if (forceSnow == 2) { TerrainTextures[0] = RTToMip(LoadTex(terrainpath + "autumn.png"), gd); - } + } else { TerrainTextures[0] = RTToMip(LoadTex(gamepath + "gamedata/terrain/newformat/gr.tga"), gd); } - + TerrainTextures[1] = RTToMip(LoadTex(gamepath + "gamedata/terrain/newformat/sd.tga"), gd); TerrainTextures[2] = RTToMip(LoadTex(gamepath + "gamedata/terrain/newformat/rk.tga"), gd); TerrainTextures[3] = RTToMip(LoadTex(gamepath + "gamedata/terrain/newformat/sn.tga"), gd); @@ -111,7 +102,7 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) LotOffline = UIElement.GetTexture(0x0000033100000001); //fills used for line drawing - + WhiteLine = TextureUtils.TextureFromColor(GameFacade.GraphicsDevice, Color.White); stpWhiteLine = TextureUtils.TextureFromColor(GameFacade.GraphicsDevice, new Color(255, 255, 255, 128)); @@ -157,15 +148,21 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) if (FSOEnvironment.EnableNPOTMip) TreeTex = RTToMip(TreeTex, gd); } - - for (int i=0; i<3; i++) + PainterSpike = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_spike.png"), gd); + PainterCursor = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_cursor_base.png"), gd); + PainterCursorActive = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_cursor_sel.png"), gd); + PainterCursorAnchor = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_cursor_anchor.png"), gd); + PainterRoadIcon = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_cursor_road.png"), gd); + PainterRoadDel = RTToMip(LoadTex("Content/uigraphics/cityedit/cityedit_cursor_roaddel.png"), gd); + + for (int i = 0; i < 3; i++) { NeighTextures[i] = RTToMip(LoadTex("Content/Textures/" + NeighTexNames[i]), gd); } var batch = new SpriteBatch(GameFacade.GraphicsDevice); - for (int i=0; i<4; i++) CreateTransparencyAtlas(gd, batch, i); + for (int i = 0; i < 4; i++) CreateTransparencyAtlas(gd, batch, i); CreateRoadAtlas(gd, batch); for (int x = 0; x < 30; x++) TransA[x].Dispose(); @@ -176,16 +173,21 @@ public void LoadContent(GraphicsDevice gd, int cityNumber) public void ForceSnow(float mode) { + if (VertexColor == null) + { + // Should handle this differently when dynamic city is enabled... + return; + } + var dat = new Color[VertexColor.Width * VertexColor.Height]; VertexColor.GetData(dat); - var typeC = MapData.TerrainTypeColorData; var type = MapData.TerrainType; for (int i = 0; i < dat.Length; i++) { var old = dat[i]; var greater = Math.Max(old.R, old.G); - var oldType = typeC[i]; + var oldType = type[i]; switch (mode) { @@ -196,16 +198,14 @@ public void ForceSnow(float mode) dat[i] = new Color(greater, greater, greater); } - if (oldType == new Color(0, 255, 0) || oldType == Color.Yellow) + if (oldType == TerrainType.GRASS || oldType == TerrainType.SAND) { - typeC[i] = Color.White; - type[i] = 3; + type[i] = TerrainType.SNOW; } break; case 1: // Summer - if (oldType == Color.White) + if (oldType == TerrainType.SNOW) { - typeC[i] = new Color(0, 255, 0); type[i] = 0; } break; @@ -219,7 +219,7 @@ private Texture2D RTToMip(Texture2D texture, GraphicsDevice device) { var data = new Color[texture.Width * texture.Height]; texture.GetData(data); - + Texture2D newTex = null; try { @@ -227,12 +227,14 @@ private Texture2D RTToMip(Texture2D texture, GraphicsDevice device) TextureUtils.UploadWithAvgMips(newTex, device, data); texture.Dispose(); texture = newTex; - } catch + } + catch { try { newTex?.Dispose(); - } catch + } + catch { } @@ -264,24 +266,24 @@ private Texture2D LoadTex(Stream stream) public void CreateTransparencyAtlas(GraphicsDevice gd, SpriteBatch spriteBatch, int type) { - var source = (type > 1)?TransB:TransA; + var source = (type > 1) ? TransB : TransA; var index = type % 2; var sizeX = source[index].Width; var sizeY = source[index].Height; - RenderTarget2D RTarget = new RenderTarget2D(gd, sizeX*7, sizeY*3, false, SurfaceFormat.Color, DepthFormat.Depth16, 0, RenderTargetUsage.PreserveContents); + RenderTarget2D RTarget = new RenderTarget2D(gd, sizeX * 7, sizeY * 3, false, SurfaceFormat.Color, DepthFormat.Depth16, 0, RenderTargetUsage.PreserveContents); gd.SetRenderTarget(RTarget); gd.Clear(Color.Black); spriteBatch.Begin(); - for (int i=0; i<15; i++) + for (int i = 0; i < 15; i++) { var x = FlagLayout[i] % 7; var y = FlagLayout[i] / 7; - spriteBatch.Draw(source[index+i*2], new Rectangle(x * sizeX, y*sizeY, sizeX, sizeY), Color.White); + spriteBatch.Draw(source[index + i * 2], new Rectangle(x * sizeX, y * sizeY, sizeX, sizeY), Color.White); } Texture2D black = new Texture2D(gd, 1, 1); @@ -306,7 +308,7 @@ public void CreateRoadAtlas(GraphicsDevice gd, SpriteBatch spriteBatch) RenderTarget2D RTarget = new RenderTarget2D(gd, sizeX * RoadWidth, sizeY * RoadHeight, false, SurfaceFormat.Color, DepthFormat.Depth16, 0, RenderTargetUsage.PreserveContents); gd.SetRenderTarget(RTarget); - gd.Clear(Color.TransparentBlack); + gd.Clear(Color.Transparent); spriteBatch.Begin(); @@ -337,7 +339,7 @@ public void CreateRoadAtlas(GraphicsDevice gd, SpriteBatch spriteBatch) public void Dispose() { foreach (var tex in TerrainTextures) tex.Dispose(); - VertexColor.Dispose(); + VertexColor?.Dispose(); foreach (var tex in TransAtlas) tex.Dispose(); RoadAtlas.Dispose(); diff --git a/TSOClient/tso.client/Rendering/City/CityData.cs b/TSOClient/tso.client/Rendering/City/CityData.cs deleted file mode 100644 index 07deb8202..000000000 --- a/TSOClient/tso.client/Rendering/City/CityData.cs +++ /dev/null @@ -1,401 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using System.IO; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.City -{ - public enum TerrainType - { - Grass = 0, - Snow = 1, - Sand = 2, - Rock = 3, - Water = 4 - } - - public enum NeighbourDir - { - North, - NorthEast, - East, - SouthEast, - South, - SouthWest, - West, - NorthWest - } - - public class CityData - { - private static Dictionary ColorToTerrain = new Dictionary() - { - {0xFF00FF00, (byte)TerrainType.Grass}, - {0xFFFFFFFF, (byte)TerrainType.Snow}, - {0xFFFFFF00, (byte)TerrainType.Sand}, - {0xFFFF0000, (byte)TerrainType.Rock}, - {0xFF0C00FF, (byte)TerrainType.Water} - }; - - private static Dictionary BlendTable = new Dictionary() - { - {"0000", 0}, - {"0100", 1}, - {"1000", 2}, - {"1100", 3}, - {"0001", 4}, - {"0101", 5}, - {"1001", 6}, - {"1101", 7}, - {"0010", 8}, - {"0110", 9}, - {"1010", 10}, - {"1110", 11}, - {"0011", 12}, - {"0111", 13}, - {"1011", 14}, - {"1111", 15} - }; - - public int Width { get; set; } - public int Height { get; set; } - public float[] Elevation { get; set; } - public Color[] VertexColor { get; set; } - public byte[] Terrain { get; set; } - public byte[] BackTerrain { get; set; } - public byte[] BlendMap { get; set; } - - - public float GetElevation(int x, int y) - { - return Elevation[(y * Width) + x]; - } - - public float GetElevation(int x, int y, float scale) - { - return Elevation[(y * Width) + x] * scale; - } - - public float GetElevation(int x, int y, NeighbourDir dir, float defaultValue, float scale) - { - var offset = GetOffset(x, y, dir); - if (offset == -1) - { - return defaultValue; - } - return Elevation[offset] * scale; - } - - - public byte GetTerrain(int x, int y) - { - return Terrain[y * Width + x]; - } - - public byte GetTerrain(int x, int y, NeighbourDir dir, byte defaultValue) - { - var offset = GetOffset(x, y, dir); - if (offset == -1) - { - return defaultValue; - } - return Terrain[offset]; - } - - /// - /// Gets the array offset for a given cell - /// - /// - /// - /// - public int GetOffset(int x, int y) - { - return y * Width + x; - } - - /// - /// Gets the array offset for a given cell's neighbour. - /// Returns -1 if not valid e.g. north for row 0 etc. - /// - /// - /// - /// - /// - public int GetOffset(int x, int y, NeighbourDir dir) - { - int yMod = 0; - - switch (dir) - { - case NeighbourDir.North: - if (y > 0) - { - return ((y - 1) * Width) + x; - } - return -1; - - case NeighbourDir.NorthEast: - if (y > 0 && x < Width - 1) - { - return ((y - 1) * Width) + x + 1; - } - return -1; - - case NeighbourDir.East: - if (x < Width - 1) - { - return (y * Width) + x + 1; - } - return -1; - - case NeighbourDir.SouthEast: - if (y < Height - 1 && x < Width - 1) - { - return ((y + 1) * Width) + x + 1; - } - return -1; - - case NeighbourDir.South: - if (y < Height - 1) - { - return ((y + 1) * Width) + x; - } - return -1; - - case NeighbourDir.SouthWest: - if (y < Height - 1 && x > 0) - { - return ((y + 1) * Width) + x - 1; - } - return -1; - - case NeighbourDir.West: - if (x > 0) - { - return (y * Width) + x - 1; - } - return -1; - - case NeighbourDir.NorthWest: - if (y > 0 && x > 0) - { - return ((y - 1) * Width) + x - 1; - } - return -1; - } - - //{ - // case NeighbourDir.North: - // yMod = (y % 2); - // if (y > 0 && x < Width - yMod) - // { - // return ((y - 1) * Width) + x + yMod; - // } - // return -1; - - // case NeighbourDir.NorthEast: - // if (x < Width - 1) - // { - // return y * Width + x + 1; - // } - // return -1; - - - // case NeighbourDir.East: - // yMod = (y % 2); - // if(y < Height - 1 && x < Width - yMod){ - // return ((y + 1) * Width) + x + yMod; - // } - // return -1; - - // case NeighbourDir.South: - // yMod = (y % 2 == 0 ? 1 : 0); - // if (y < Height - 1 && x > yMod) - // { - // return ((y + 1) * Width) + x - yMod; - // } - // return -1; - - // case NeighbourDir.SouthWest: - // if (x > 0) - // { - // return y * Width + x - 1; - // } - // return -1; - - // case NeighbourDir.West: - // yMod = (y % 2 == 0 ? 1 : 0); - // if (y > 0 && x > yMod) - // { - // return ((y - 1) * Width) + x - yMod; - // } - // return -1; - - // case NeighbourDir.SouthEast: - // if (y < Height - 2) - // { - // return ((y + 2) * Width) + x; - // } - // return -1; - - // case NeighbourDir.NorthWest: - // if (y > 2) - // { - // return ((y - 2) * Width) + x; - // } - // return -1; - //} - return -1; - } - - public Color[] RawElevationPixels { get; set; } - public Color[] VertexColorPixels { get; set; } - public Color[] RawTerrainTypePixels { get; set; } - - public byte GetTerrainType(Color color) - { - return ColorToTerrain[color.PackedValue]; - } - - public static CityData Load(GraphicsDevice gd, string path) - { - //TODO: Load textures the correct way - Texture2D elevationTexture = Texture2D.FromFile(gd, Path.Combine(path, "elevation.bmp")); - Texture2D terrainTexture = Texture2D.FromFile(gd, Path.Combine(path, "terraintype.bmp")); - Texture2D vertexTexture = Texture2D.FromFile(gd, Path.Combine(path, "vertexcolor.bmp")); - - var width = 205; - var height = 606; //613 - var mapWidth = elevationTexture.Width; - var mapHeight = elevationTexture.Height; - - /** Get data from textures **/ - var elevationRaw = new Color[mapWidth * mapHeight]; - elevationTexture.GetData(elevationRaw); - elevationTexture.Dispose(); - - var terrainRaw = new Color[mapWidth * mapHeight]; - terrainTexture.GetData(terrainRaw); - terrainTexture.Dispose(); - - var vertexRaw = new Color[mapWidth * mapHeight]; - vertexTexture.GetData(vertexRaw); - vertexTexture.Dispose(); - - - /** Result objects **/ - float[] elevation = new float[width * height]; - byte[] terrain = new byte[width * height]; - Color[] vertex = new Color[width * height]; - byte[] backTerrains = new byte[width * height]; - byte[] blendMap = new byte[width * height]; - - var rbmp = new System.Drawing.Bitmap(512, 512); - //height = 300; - - for (int y = 0; y < height; y++) - { - for (int x = 0; x < width; x++) - { - //x = (306 + x) - floor(y / 2) - //y = ceil(y/2) + x - var srcY = y; - var mapX = (x + 306) - (int)Math.Floor((double)srcY / 2); - var mapY = (int)Math.Ceiling((double)srcY / 2) + x; - var mapOffset = mapX + (mapY * mapWidth); - var resultOffset = (y * width) + x; - - var dcolor = System.Drawing.Color.FromArgb((int)terrainRaw[mapOffset].PackedValue); - rbmp.SetPixel(mapX, mapY, dcolor); - - elevation[resultOffset] = ((float)((float)elevationRaw[mapOffset].R / (float)255.0)); - vertex[resultOffset] = vertexRaw[mapOffset]; - terrain[resultOffset] = ColorToTerrain[terrainRaw[mapOffset].PackedValue]; - } - } - - //rbmp.Save(@"C:\Users\Darren\Desktop\TSO\mapExport.bmp"); - //elevationRaw = null; - //vertexRaw = null; - //terrainRaw = null; - - var result = new CityData - { - Width = width, - Height = height, - Elevation = elevation, - VertexColor = vertex, - Terrain = terrain, - BackTerrain = backTerrains, - BlendMap = blendMap, - RawElevationPixels = elevationRaw, - VertexColorPixels = vertexRaw, - RawTerrainTypePixels = terrainRaw - }; - - /** - * Calculate blending info - * Loops at 4 cells around the current cell - * and creates a pattern e.g. 1010 where 0 is same terrain type, 1 is dif - * - * That code is then mapped to a specific alpha map - **/ - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var myTerrain = terrain[(y * width) + x]; - var myOffset = y * width + x; - var backTerrain = myTerrain; - - var north = result.GetTerrain(x, y, NeighbourDir.North, myTerrain); - var east = result.GetTerrain(x, y, NeighbourDir.East, myTerrain); - var south = result.GetTerrain(x, y, NeighbourDir.South, myTerrain); - var west = result.GetTerrain(x, y, NeighbourDir.West, myTerrain); - - /** No blend **/ - var myBlend = (byte)15; - - - var key = (myTerrain == north ? 1 : 0).ToString() + - (myTerrain == east ? 1 : 0).ToString() + - (myTerrain == south ? 1 : 0).ToString() + - (myTerrain == west ? 1 : 0).ToString(); - - /*if (BlendTable.ContainsKey(key)) - { - myBlend = BlendTable[key]; - } - - if (east == south && south != myTerrain) - { - myBlend = 18; - }*/ - - backTerrains[myOffset] = myTerrain; - blendMap[myOffset] = myBlend; - //BlendTable - } - } - //44280000 - - - return result; - } - } -} diff --git a/TSOClient/tso.client/Rendering/City/CityFoliage.cs b/TSOClient/tso.client/Rendering/City/CityFoliage.cs index 3e888d711..4507ad179 100644 --- a/TSOClient/tso.client/Rendering/City/CityFoliage.cs +++ b/TSOClient/tso.client/Rendering/City/CityFoliage.cs @@ -1,12 +1,10 @@ -using FSO.Common.Utils; +using FSO.Common.Domain.Realestate; +using FSO.Common.Utils; +using FSO.Content.Model; using FSO.Files.RC; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; namespace FSO.Client.Rendering.City { @@ -17,37 +15,79 @@ namespace FSO.Client.Rendering.City /// public class CityFoliage : IDisposable { - public int ChunkSize = 16; - public CityMapData MapData; + private struct SimpleRandom + { + private ulong RandomSeed; + + public SimpleRandom(ulong seed) + { + RandomSeed = seed; + } + + /// + /// Returns a random number between 0 and less than the specified maximum. + /// + /// The upper bound of the random number. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong Next(ulong max) + { + if (max == 0) return 0; + RandomSeed ^= RandomSeed >> 12; + RandomSeed ^= RandomSeed << 25; + RandomSeed ^= RandomSeed >> 27; + return (RandomSeed * (ulong)(2685821657736338717)) % max; + } + } + + private readonly struct TreeGroup(string name, int index, int count) + { + public readonly string Name = name; + public readonly int Index = index; + public readonly int Count = count; + } + + public const int ChunkSize = 16; + public CityMap MapData; public Dictionary Chunks = new Dictionary(); public DGRP3DVert[][] TreeVerts; public int[][] TreeInds; + public readonly Matrix[] RotationMatrices; - public string[] TreeGroups = new string[] - { - "pine", - "tree", - "palm", - "cactus", //3 - "snow", //3 - }; + private uint ActiveLocation; + + private readonly TreeGroup[] TreeGroups = + [ + new("pine", 0, 4), //4 models + new("tree", 4, 4), //4 models + new("cactus", 8, 3), //3 models + new("palm", 11, 4), //4 models + new("snow", 15, 3) //3 models + ]; public CityFoliage() { TreeVerts = new DGRP3DVert[18][]; - TreeInds = new int[18][]; - for (int i=0; i<18; i++) + TreeInds = new int[18][]; + + foreach (var group in TreeGroups) { - var snow = (i >= 15); - var model = LoadModel(TreeGroups[(snow)?(4):(i / 4)] + (snow?(i-14):((i % 4) + 1)) + ".obj"); - //var tree = Content.Content.Get().RCMeshes.Get(TreeGroups[i/4]+((i%4)+1)+".fsom"); + for (int i = 0; i < group.Count; i++) + { + var model = LoadModel(group.Name + (i + 1) + ".obj"); - //var geom = tree.Geoms[0].ElementAt(0).Value; - TreeVerts[i] = model.Item1; - TreeInds[i] = model.Item2; + TreeVerts[group.Index + i] = model.Item1; + TreeInds[group.Index + i] = model.Item2; + } } + RotationMatrices = new Matrix[16]; + int length = RotationMatrices.Length; + for (int i = 0; i < length; i++) + { + RotationMatrices[i] = Matrix.CreateRotationY((MathF.PI * 2 * i) / length); + } } public Tuple LoadModel(string model) @@ -89,41 +129,73 @@ public Tuple LoadModel(string model) return new Tuple(outVerts.ToArray(), outInds.ToArray()); } - private Dictionary ForestTypes = new Dictionary() + private static readonly int[] TreeCounts = [1, 4, 7, 15]; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int O(int x, int y) { - { new Color(0, 0x6A, 0x28), 0 }, //heavy forest - { new Color(0, 0xEB, 0x42), 1}, //light forest - { new Color(255, 0xFC, 0), 2 }, //palm - { new Color(255, 0, 0), 3}, //cacti - { new Color(0, 0, 0), -1} //nothing; no forest - }; + return (Math.Max(0, Math.Min(511, y)) * 512 + Math.Max(0, Math.Min(511, x))); + } - public int[] TreeCounts = new int[] { 1, 4, 7, 15 }; + public void InvalidateChunks(Rectangle rect) + { + foreach (var chunkPair in Chunks) + { + int i = chunkPair.Key; + var chunk = chunkPair.Value; + + var x = i % 32; + var y = i / 32; + + var chunkRect = new Rectangle(x * ChunkSize, y * ChunkSize, ChunkSize, ChunkSize); + + if (rect.Intersects(chunkRect)) + { + chunk.Dirty = true; + } + } + } - private int O(int x, int y) + private void SetActiveLocation(uint location) { - return (Math.Max(0, Math.Min(511, y)) * 512 + Math.Max(0, Math.Min(511, x))); + if (ActiveLocation != location) + { + foreach (var chunk in Chunks.Values) + { + uint filtered = chunk.FilterActiveLocation(location); + + if (filtered != chunk.ActiveLocation) + { + chunk.ActiveLocation = filtered; + chunk.Dirty = true; + } + } + + ActiveLocation = location; + } } public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect VertexShader, Effect PixelShader, int passIndex, int size, BoundingFrustum frustrum) { var camPos = terrain.Camera.CalculateR(); + SetActiveLocation(terrain.ActiveLocation); - var cx = (int)Math.Round(camPos.X / 16); - var cy = (int)Math.Round(camPos.Y / 16); + var cx = (int)Math.Round(camPos.X / ChunkSize); + var cy = (int)Math.Round(camPos.Y / ChunkSize); - var invalid = Chunks.Keys.Where(i => + var invalid = Chunks.Where(chunkPair => { - var x = i % 32; - var y = i / 32; + var x = chunkPair.Value.X; + var y = chunkPair.Value.Y; + return (x < cx - 2) || (x > cx + 2) || (y < cy - 2) || (y > cy + 2); }).ToList(); foreach (var c in invalid) { - var chunk = Chunks[c]; + var chunk = c.Value; chunk.Dispose(); - Chunks.Remove(c); + Chunks.Remove(c.Key); } gd.RasterizerState = RasterizerState.CullNone; @@ -143,7 +215,7 @@ public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect VertexShader.CurrentTechnique = VertexShader.Techniques[1]; VertexShader.CurrentTechnique.Passes[5].Apply(); - var copy = new HashSet(terrain.LotTileLookup.Keys.Select(i => (int)i.Y*512+(int)i.X)); + HashSet copy = terrain.OccupiedTiles; for (int y = Math.Max(0, cy-size); y<= Math.Min(31, cy + size); y++) { @@ -151,10 +223,15 @@ public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect { var ind = y * 32 + x; CityFoliageChunk chunk; - if (!Chunks.TryGetValue(ind, out chunk)) { + if (!Chunks.TryGetValue(ind, out chunk)) + { chunk = GenerateChunk(gd, x, y, copy); Chunks.Add(chunk.Ind, chunk); } + else if (chunk.ShouldRegenerate()) + { + RegenerateChunk(chunk, gd, x, y, copy); + } if (chunk.Indices != null && chunk.Bounds.Intersects(frustrum)) { @@ -172,111 +249,177 @@ public void Draw(Terrain terrain, GraphicsDevice gd, CityContent content, Effect } } - public CityFoliageChunk GenerateChunk(GraphicsDevice gd, int x, int y, HashSet noTrees) + private (DGRP3DVert[], int[]) GetChunkData(int x, int y, HashSet noTrees, uint activeLocation) { - var chunk = new CityFoliageChunk(); - chunk.Bounds = new BoundingBox(new Vector3(x * ChunkSize, 0, y * ChunkSize), new Vector3((x+1) * 32, 255 / 12f, (y+1) * 32)); + var verts = new List(); + var inds = new List(); + var md = MapData.ElevationData; + var baseMat = Matrix.CreateScale(1 / 75f); - Task.Run(() => - { - var verts = new List(); - var inds = new List(); - var md = MapData.ElevationData; - var baseMat = Matrix.CreateScale(1 / 75f); + var startx = x * ChunkSize; + var endx = startx + ChunkSize; + var starty = y * ChunkSize; + var endy = starty + ChunkSize; - var startx = x * ChunkSize; - var endx = startx + ChunkSize; - var starty = y * ChunkSize; - var endy = starty + ChunkSize; + var forestTypeData = MapData.ForestTypeData; + var terrainTypeData = MapData.TerrainType; + var forestDensityData = MapData.ForestDensityData; + var roadData = MapData.RoadData; - for (int oy = starty; oy < endy; oy++) + var locationCoords = MapCoordinates.Unpack(activeLocation); + var treeCut = activeLocation == 0 ? Rectangle.Empty : new Rectangle(locationCoords.X - 1, locationCoords.Y - 1, 3, 3); + + for (int oy = starty; oy < endy; oy++) + { + for (int ox = startx; ox < endx; ox++) { - for (int ox = startx; ox < endx; ox++) + var ind = oy * 512 + ox; + var forestType = forestTypeData[ind]; + if (forestType != ForestType.NULL && !noTrees.Contains(ind) && !treeCut.Contains(ox, oy)) { - var ind = oy * 512 + ox; - var forestType = ForestTypes[MapData.ForestTypeData[ind]]; - if (forestType != -1 && !noTrees.Contains(ind)) + var terrainType = terrainTypeData[ind]; + if (forestType == 0 && terrainType == TerrainType.SNOW) forestType = ForestType.SNOW; + var densityN = ((forestDensityData[ind] * 4) / 255); + if (densityN == 0 || terrainType == TerrainType.WATER) continue; + var density = TreeCounts[densityN - 1]; + var rand = new SimpleRandom((ulong)(ind * 231458721));// new Random(ind); + + var road = roadData[ind] & 15; + float rangesx = 0; + float rangesy = 0; + float rangex = 1; + float rangey = 1; + + if ((road & 1) > 0) { - if (forestType == 0 && MapData.TerrainType[ind] == 3) forestType = 4; - var densityN = ((MapData.ForestDensityData[ind] * 4) / 255); - if (densityN == 0) continue; - var density = TreeCounts[densityN - 1]; - var rand = new Random(ind); - - var road = MapData.RoadData[ind] & 15; - float rangesx = 0; - float rangesy = 0; - float rangex = 1; - float rangey = 1; - - if ((road & 1) > 0) - { - rangesx += 0.15f; - rangex -= 0.15f; - } - if ((road & 2) > 0) - { - rangey -= 0.15f; - } - if ((road & 4) > 0) - { - rangex -= 0.15f; - } - if ((road & 8) > 0) - { - rangesy += 0.15f; - rangey -= 0.15f; - } - var fBase = Math.Min(15, forestType * 4); + rangesx += 0.15f; + rangex -= 0.15f; + } + if ((road & 2) > 0) + { + rangey -= 0.15f; + } + if ((road & 4) > 0) + { + rangex -= 0.15f; + } + if ((road & 8) > 0) + { + rangesy += 0.15f; + rangey -= 0.15f; + } + + var group = TreeGroups[(int)forestType]; + + var fBase = group.Index; - for (int i = 0; i < density; i++) + Span d = + [ + md[O(ox - 1, oy - 1)], md[O(ox - 1, oy)], md[O(ox - 1, oy + 1)], md[O(ox - 1, oy + 2)], + md[O(ox, oy - 1)], md[O(ox, oy)], md[O(ox, oy + 1)], md[O(ox, oy + 2)], + md[O(ox + 1, oy - 1)], md[O(ox + 1, oy)], md[O(ox + 1, oy + 1)], md[O(ox + 1, oy + 2)], + md[O(ox + 2, oy - 1)], md[O(ox + 2, oy)], md[O(ox + 2, oy + 1)], md[O(ox + 2, oy + 2)], + ]; + + for (int i = 0; i < density; i++) + { + var subtype = (int)rand.Next((ulong)group.Count); + var sx = (rand.Next(256) / 256f) * rangex + rangesx; + var sy = (rand.Next(256) / 256f) * rangey + rangesy; + + //get tree height + float y1 = CityGeometry.Cubic(d[0], d[1], d[2], d[3], sy, 0); + float y2 = CityGeometry.Cubic(d[4], d[5], d[6], d[7], sy, 0); + float y3 = CityGeometry.Cubic(d[8], d[9], d[10], d[11], sy, 0); + float y4 = CityGeometry.Cubic(d[12], d[13], d[14], d[15], sy, 0); + + var h = CityGeometry.Cubic(y1, y2, y3, y4, sx, 0); + + //add the tree + + var mat = baseMat * RotationMatrices[rand.Next((ulong)RotationMatrices.Length)]; + var pos = new Vector3(ox + sx, h / 12f, oy + sy); + + var model = fBase + subtype; + var baseV = verts.Count; + foreach (var vert in TreeVerts[model]) { - var subtype = rand.Next((forestType >= 3) ? 3 : 4); - var sx = (float)rand.NextDouble() * rangex + rangesx; - var sy = (float)rand.NextDouble() * rangey + rangesy; - - //get tree height - float y1 = CityGeometry.Cubic(md[O(ox - 1, oy - 1)], md[O(ox - 1, oy)], md[O(ox - 1, oy + 1)], md[O(ox - 1, oy + 2)], sy, 0); - float y2 = CityGeometry.Cubic(md[O(ox, oy - 1)], md[O(ox, oy)], md[O(ox, oy + 1)], md[O(ox, oy + 2)], sy, 0); - float y3 = CityGeometry.Cubic(md[O(ox + 1, oy - 1)], md[O(ox + 1, oy)], md[O(ox + 1, oy + 1)], md[O(ox + 1, oy + 2)], sy, 0); - float y4 = CityGeometry.Cubic(md[O(ox + 2, oy - 1)], md[O(ox + 2, oy)], md[O(ox + 2, oy + 1)], md[O(ox + 2, oy + 2)], sy, 0); - - var h = CityGeometry.Cubic(y1, y2, y3, y4, sx, 0); - - //add the tree - - var mat = baseMat * Matrix.CreateRotationY((float)(Math.PI * 2 * rand.NextDouble())); - var pos = new Vector3(ox + sx, h / 12f, oy + sy); - - var model = fBase + subtype; - var baseV = verts.Count; - foreach (var vert in TreeVerts[model]) - { - var vCopy = vert; - vCopy.Position = Vector3.Transform(vCopy.Position, mat); - vCopy.Normal = pos; - verts.Add(vCopy); - } - - foreach (var tind in TreeInds[model]) inds.Add(tind + baseV); + var vCopy = vert; + vCopy.Position = Vector3.Transform(vCopy.Position, mat); + vCopy.Normal = pos; + verts.Add(vCopy); } + + foreach (var tind in TreeInds[model]) inds.Add(tind + baseV); } } } + } + + return ([..verts], [..inds]); + } + + private void RegenerateChunk(CityFoliageChunk chunk, GraphicsDevice gd, int x, int y, HashSet noTrees) + { + if (chunk.Dead) + { + return; + } + + chunk.ActiveLocation = ActiveLocation; + + chunk.Regenerating = true; + + Task.Run(() => + { + var (verts, inds) = GetChunkData(x, y, noTrees, chunk.ActiveLocation); GameThread.NextUpdate(state => { - if (verts.Count > 0 && !chunk.Dead) + if (verts.Length > 0 && !chunk.Dead) { - var vbuf = new VertexBuffer(gd, typeof(DGRP3DVert), verts.Count, BufferUsage.None); - vbuf.SetData(verts.ToArray()); - var ibuf = new IndexBuffer(gd, IndexElementSize.ThirtyTwoBits, inds.Count, BufferUsage.None); - ibuf.SetData(inds.ToArray()); + VertexBuffer vbuf = chunk.Vertices; + if (vbuf == null || vbuf.VertexCount != verts.Length) + { + vbuf?.Dispose(); + vbuf = new VertexBuffer(gd, typeof(DGRP3DVert), verts.Length, BufferUsage.None); + } + vbuf.SetData(verts); + + IndexBuffer ibuf = chunk.Indices; + + if (ibuf == null || ibuf.IndexCount != inds.Length) + { + ibuf?.Dispose(); + ibuf = new IndexBuffer(gd, IndexElementSize.ThirtyTwoBits, inds.Length, BufferUsage.None); + } + ibuf.SetData(inds); chunk.Vertices = vbuf; chunk.Indices = ibuf; } + else + { + chunk.Vertices?.Dispose(); + chunk.Indices?.Dispose(); + + chunk.Vertices = null; + chunk.Indices = null; + } + + chunk.Regenerating = false; }); }); + } + + public CityFoliageChunk GenerateChunk(GraphicsDevice gd, int x, int y, HashSet noTrees) + { + var chunk = new CityFoliageChunk + { + Bounds = new BoundingBox(new Vector3(x * ChunkSize, 0, y * ChunkSize), new Vector3((x + 1) * 32, 255 / 12f, (y + 1) * 32)), + }; + + RegenerateChunk(chunk, gd, x, y, noTrees); + chunk.X = x; chunk.Y = y; chunk.Ind = y * 32 + x; @@ -303,8 +446,43 @@ public class CityFoliageChunk public IndexBuffer Indices; public BoundingBox Bounds; + /// + /// Properties around the active location have their city view trees removed to avoid overlapping lot graphics. + /// If the active location doesn't overlap this chunk, it's set to 0. + /// + public uint ActiveLocation; + + public bool Dirty; + public bool Regenerating; + public bool Dead; + public uint FilterActiveLocation(uint location) + { + if (location == 0) + { + return 0; + } + + var coords = MapCoordinates.Unpack(location); + + var chunkRect = new Rectangle(X * 16, Y * 16, 16, 16); + + return chunkRect.Contains(coords.X, coords.Y) ? location : 0; + } + + public bool ShouldRegenerate() + { + if (Dirty && !Regenerating) + { + Dirty = false; + + return true; + } + + return false; + } + public void Dispose() { Vertices?.Dispose(); diff --git a/TSOClient/tso.client/Rendering/City/CityGeom.cs b/TSOClient/tso.client/Rendering/City/CityGeom.cs deleted file mode 100644 index 2558d7149..000000000 --- a/TSOClient/tso.client/Rendering/City/CityGeom.cs +++ /dev/null @@ -1,498 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.City -{ - public class CityGeom : IDisposable, ICityGeom - { - /// - /// Vertices for the map - /// - - public TerrainVertex[] Vertices { get; internal set; } - public int[] Indexes { get; internal set; } - public IndexBuffer IndexBuffer { get; internal set; } - public VertexBuffer VertexBuffer { get; internal set; } - public int PrimitiveCount { get; internal set; } - public int VertexPerTile = 12; - - public float CellWidth { get; set; } - public float CellHeight { get; set; } - public float BorderWidth { get; set; } - public float BorderHeight { get; set; } - - public float CellYScale { get; set; } - public float TerrainSpread = 0.05f; - - /// - /// How many textures are in the terain sheet, aka how many terrain types - /// - public float TerrainSheetSize = 5.0f; - - public int Width { get; internal set; } - public int Height { get; internal set; } - - - public void GetTileVertices(int x, int y, TerrainVertex[] target) - { - var offset = ((y * Width + x) * VertexPerTile); - for (var i = 0; i < VertexPerTile; i++) - { - target[i] = Vertices[offset + i]; - } - } - - - protected Vector2[] CalculateTexCoord(int x, int y, byte terrainType) - { - var terrainXO = (terrainType / TerrainSheetSize); - var terrainSize = (1.0f / TerrainSheetSize); - - var txOrigin = terrainXO + ((x * (TerrainSpread * terrainSize)) % terrainSize); - var txMid = txOrigin + ((TerrainSpread * (terrainSize / 2)) % terrainSize); - var txEnd = txOrigin + ((TerrainSpread * terrainSize) % terrainSize); - - var tyOrigin = y * TerrainSpread; - var tyMid = (y + 0.5f) * TerrainSpread; - var tyEnd = (y + 1) * TerrainSpread; - - var textureP0 = new Vector2(txMid, tyMid); - var textureP1 = new Vector2(txOrigin, tyOrigin); - var textureP2 = new Vector2(txEnd, tyOrigin); - var textureP3 = new Vector2(txEnd, tyEnd); - var textureP4 = new Vector2(txOrigin, tyEnd); - - return new Vector2[] { - textureP0, - textureP1, - textureP2, - textureP3, - textureP4 - }; - } - - /// - /// Do the work of generating the city geom - /// - /// - public void Process(CityData city) - { - //Cleanup if someone is trying to reuse this object - Dispose(); - var now = DateTime.Now.Ticks; - - Width = city.Width; - Height = city.Height; - - - - - var vertexList = new List(); - var indexList = new List(); - - - - - BorderWidth = (CellWidth / 4) / 2; - BorderHeight = (CellHeight / 4) / 2; - - var spanX = CellWidth + (BorderWidth * 2); - var spanY = CellHeight + (BorderHeight * 2); - - - - var textureMap = new TextureMapper(); - textureMap.TerrainSheetSize = 5.0f; - - /** Build vertex & index structures **/ - for (int y = 0; y < Height; y++) - { - for (int x = 0; x < Width; x++) - { - var offset = (y * city.Width) + x; - - /** Settings **/ - var elevation = city.Elevation[offset]; - var vertexColor = city.VertexColor[offset]; - var terrainType = city.Terrain[offset]; - var blendIndex = city.BlendMap[offset]; - var backTerrainType = city.BackTerrain[offset]; - - textureMap.TerrainType = terrainType; - - - - //Main points - var mainElevation = city.GetElevation(x, y, CellYScale); - var northElevation = city.GetElevation(x, y, NeighbourDir.North, mainElevation, CellYScale); - var eastElevation = city.GetElevation(x, y, NeighbourDir.East, mainElevation, CellYScale); - var southElevation = city.GetElevation(x, y, NeighbourDir.South, mainElevation, CellYScale); - var westElevation = city.GetElevation(x, y, NeighbourDir.West, mainElevation, CellYScale); - var northWestElevation = city.GetElevation(x, y, NeighbourDir.NorthWest, mainElevation, CellYScale); - var northEastElevation = city.GetElevation(x, y, NeighbourDir.NorthEast, mainElevation, CellYScale); - var southEastElevation = city.GetElevation(x, y, NeighbourDir.SouthEast, mainElevation, CellYScale); - var southWestElevation = city.GetElevation(x, y, NeighbourDir.SouthWest, mainElevation, CellYScale); - - - - var startIndex = vertexList.Count; - var tex = new Vector2(0.5f, 0.5f); - - if (mainElevation == northElevation && - mainElevation == eastElevation && - mainElevation == southElevation && - mainElevation == westElevation && - mainElevation == northWestElevation && - mainElevation == northEastElevation && - mainElevation == southEastElevation && - mainElevation == southWestElevation) - { - /** We can just use 1 quad for this tile **/ - var fullTL = new Vector3((x * spanX) - BorderWidth, -(y * spanY) - BorderHeight, mainElevation); - var fullTR = new Vector3(fullTL.X + spanX, fullTL.Y, mainElevation); - var fullBL = new Vector3(fullTL.X, fullTL.Y - spanY, mainElevation); - var fullBR = new Vector3(fullTL.X + spanX, fullTL.Y - spanY, mainElevation); - - textureMap.Position(x, y, fullTL, fullBR); - - vertexList.Add(new TerrainVertex(fullTL, textureMap.MapTerrain(ref fullTL), vertexColor, tex, tex)); //0 - vertexList.Add(new TerrainVertex(fullTR, textureMap.MapTerrain(ref fullTR), vertexColor, tex, tex)); //1 - vertexList.Add(new TerrainVertex(fullBR, textureMap.MapTerrain(ref fullBR), vertexColor, tex, tex)); //2 - vertexList.Add(new TerrainVertex(fullBL, textureMap.MapTerrain(ref fullBL), vertexColor, tex, tex)); //3 - - indexList.Add(startIndex); - indexList.Add(startIndex + 1); - indexList.Add(startIndex + 2); - - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 3); - indexList.Add(startIndex); - - continue; - } - - - - - var mainTL = new Vector3(x * spanX, -(y * spanY), mainElevation); - var mainTR = new Vector3(mainTL.X + CellWidth, mainTL.Y, mainElevation); - var mainBL = new Vector3(mainTL.X, mainTL.Y - CellHeight, mainElevation); - var mainBR = new Vector3(mainTL.X + CellWidth, mainTL.Y - CellHeight, mainElevation); - - - - - /** West elevation **/ - var westElevationMid = (westElevation + mainElevation) / 2; - var borderTL_BL = new Vector3(mainTL.X - BorderWidth, mainTL.Y, westElevationMid); - var borderBL_TL = new Vector3(mainTL.X - BorderWidth, mainBL.Y, westElevationMid); - - /** East elevation **/ - var eastElevationMid = (eastElevation + mainElevation) / 2; - var borderTR_BR = new Vector3(mainTR.X + BorderWidth, mainTR.Y, eastElevationMid); - var borderBR_TR = new Vector3(mainBR.X + BorderWidth, mainBR.Y, eastElevationMid); - - /** North elevation **/ - var northElevationMid = (northElevation + mainElevation) / 2; - var borderTL_TR = new Vector3(mainTL.X, mainTL.Y + BorderHeight, northElevationMid); - var borderTR_TL = new Vector3(mainTR.X, mainTR.Y + BorderHeight, northElevationMid); - - /** South elevation **/ - var southElevationMid = (southElevation + mainElevation) / 2; - var borderBL_BR = new Vector3(mainBL.X, mainBL.Y - BorderHeight, southElevationMid); - var borderBR_BL = new Vector3(mainBR.X, mainBR.Y - BorderHeight, southElevationMid); - - var northWestElevationMid = (northWestElevation + northElevation + westElevation + mainElevation) / 4; - var borderTL_TL = new Vector3(mainTL.X - BorderWidth, mainTL.Y + BorderHeight, northWestElevationMid); - - var northEastElevationMid = (northEastElevation + northElevation + eastElevation + mainElevation) / 4; - var borderTR_TR = new Vector3(mainTR.X + BorderWidth, mainTR.Y + BorderHeight, northEastElevationMid); - - var southEastElevationMid = (southEastElevation + eastElevation + southElevation + mainElevation) / 4; - var borderBR_BR = new Vector3(mainBR.X + BorderWidth, mainBR.Y - BorderHeight, southEastElevationMid); - - var southWestElevationMid = (southWestElevation + southElevation + westElevation + mainElevation) / 4; - var borderBL_BL = new Vector3(mainBL.X - BorderWidth, mainBL.Y - BorderHeight, southWestElevationMid); - - - - textureMap.Position(x, y, borderTL_TL, borderBR_BR); - - - vertexList.Add(new TerrainVertex(mainTL, textureMap.MapTerrain(ref mainTL), vertexColor, tex, tex)); //0 - vertexList.Add(new TerrainVertex(mainTR, textureMap.MapTerrain(ref mainTR), vertexColor, tex, tex)); //1 - vertexList.Add(new TerrainVertex(mainBR, textureMap.MapTerrain(ref mainBR), vertexColor, tex, tex)); //2 - vertexList.Add(new TerrainVertex(mainBL, textureMap.MapTerrain(ref mainBL), vertexColor, tex, tex)); //3 - vertexList.Add(new TerrainVertex(borderTL_BL, textureMap.MapTerrain(ref borderTL_BL), vertexColor, tex, tex)); //4 - vertexList.Add(new TerrainVertex(borderBL_TL, textureMap.MapTerrain(ref borderBL_TL), vertexColor, tex, tex)); //5 - vertexList.Add(new TerrainVertex(borderTR_BR, textureMap.MapTerrain(ref borderTR_BR), vertexColor, tex, tex)); //6 - vertexList.Add(new TerrainVertex(borderBR_TR, textureMap.MapTerrain(ref borderBR_TR), vertexColor, tex, tex)); //7 - vertexList.Add(new TerrainVertex(borderTL_TR, textureMap.MapTerrain(ref borderTL_TR), vertexColor, tex, tex)); //8 - vertexList.Add(new TerrainVertex(borderTR_TL, textureMap.MapTerrain(ref borderTR_TL), vertexColor, tex, tex)); //9 - vertexList.Add(new TerrainVertex(borderBL_BR, textureMap.MapTerrain(ref borderBL_BR), vertexColor, tex, tex)); //10 - vertexList.Add(new TerrainVertex(borderBR_BL, textureMap.MapTerrain(ref borderBR_BL), vertexColor, tex, tex)); //11 - vertexList.Add(new TerrainVertex(borderTL_TL, textureMap.MapTerrain(ref borderTL_TL), vertexColor, tex, tex)); //12 - vertexList.Add(new TerrainVertex(borderTR_TR, textureMap.MapTerrain(ref borderTR_TR), vertexColor, tex, tex)); //13 - vertexList.Add(new TerrainVertex(borderBR_BR, textureMap.MapTerrain(ref borderBR_BR), vertexColor, tex, tex)); //14 - vertexList.Add(new TerrainVertex(borderBL_BL, textureMap.MapTerrain(ref borderBL_BL), vertexColor, tex, tex)); //15 - - - /** Main tile **/ - indexList.Add(startIndex); - indexList.Add(startIndex + 1); - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 3); - indexList.Add(startIndex); - - if (y > 0) - { - /** Top flap **/ - indexList.Add(startIndex + 8); - indexList.Add(startIndex + 9); - indexList.Add(startIndex + 1); - - indexList.Add(startIndex + 1); - indexList.Add(startIndex + 0); - indexList.Add(startIndex + 8); - } - - if (y < Height - 1) - { - /** Bottom flap **/ - indexList.Add(startIndex + 3); - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 11); - - indexList.Add(startIndex + 11); - indexList.Add(startIndex + 10); - indexList.Add(startIndex + 3); - - if (x > 0) - { - /** Bottom left corner **/ - indexList.Add(startIndex + 5); - indexList.Add(startIndex + 3); - indexList.Add(startIndex + 10); - - indexList.Add(startIndex + 10); - indexList.Add(startIndex + 15); - indexList.Add(startIndex + 5); - } - if (x < Width - 1) - { - /** Bottom right corner **/ - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 7); - indexList.Add(startIndex + 14); - - indexList.Add(startIndex + 14); - indexList.Add(startIndex + 11); - indexList.Add(startIndex + 2); - } - } - - if (x > 0) - { - /** Left flap **/ - indexList.Add(startIndex + 4); - indexList.Add(startIndex); - indexList.Add(startIndex + 3); - - indexList.Add(startIndex + 3); - indexList.Add(startIndex + 5); - indexList.Add(startIndex + 4); - - if (y > 0) - { - /** Top left corner **/ - indexList.Add(startIndex + 12); - indexList.Add(startIndex + 8); //tl_bl - indexList.Add(startIndex); //tl_tr - - indexList.Add(startIndex); - indexList.Add(startIndex + 4); - indexList.Add(startIndex + 12); - } - } - if (x < Width - 1) - { - /** Right flap **/ - indexList.Add(startIndex + 1); - indexList.Add(startIndex + 6); - indexList.Add(startIndex + 7); - - indexList.Add(startIndex + 7); - indexList.Add(startIndex + 2); - indexList.Add(startIndex + 1); - - if (y > 0) - { - /** Top right corner **/ - indexList.Add(startIndex + 9); - indexList.Add(startIndex + 13); - indexList.Add(startIndex + 6); - - indexList.Add(startIndex + 6); - indexList.Add(startIndex + 1); - indexList.Add(startIndex + 9); - } - } - - } - } - - - Vertices = vertexList.ToArray(); - Indexes = indexList.ToArray(); - PrimitiveCount = Indexes.Length / 3; - - System.Diagnostics.Debug.WriteLine("Took : " + (DateTime.Now.Ticks - now) + " ticks"); - } - - /// - /// Store the vertices in a vertex buffer - /// - /// - public void CreateBuffer(GraphicsDevice gd) - { - VertexBuffer = new VertexBuffer(gd, TerrainVertex.SizeInBytes * Vertices.Length, BufferUsage.WriteOnly); - VertexBuffer.SetData(Vertices); - - IndexBuffer = new IndexBuffer(gd, typeof(int), Indexes.Length, BufferUsage.WriteOnly); - IndexBuffer.SetData(Indexes); - } - - - public void Draw(GraphicsDevice gd) - { - gd.Vertices[0].SetSource(VertexBuffer, 0, TerrainVertex.SizeInBytes); - gd.VertexDeclaration = new VertexDeclaration(gd, TerrainVertex.VertexElements); - gd.Indices = IndexBuffer; - gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Vertices.Length, 0, PrimitiveCount); - } - - #region IDisposable Members - - /// - /// Cleans up the various objects used by the geom object - /// - public void Dispose() - { - } - - #endregion - } - - - - public class TextureMapper - { - private float minX; - private float minY; - private float ratioX; - private float ratioY; - private int X; - private int Y; - - public float TerrainSpread = 0.05f; - public byte TerrainType; - private float _TerrainSheetSize; - public float TerrainSheetSize - { - get - { - return _TerrainSheetSize; - } - set - { - _TerrainSheetSize = value; - TerrainSheetCellSize = 1 / value; - } - } - private float TerrainSheetCellSize; - - - public TextureMapper() - { - } - - public void Position(int x, int y, Vector3 TL, Vector3 BR) - { - X = x; - Y = y; - - minX = TL.X; - minY = TL.Y; - - ratioX = BR.X - TL.X; - ratioY = BR.Y - TL.Y; - } - - public void Position(int x, int y, Vector2 TL, Vector2 BR) - { - X = x; - Y = y; - - minX = TL.X; - minY = TL.Y; - - ratioX = BR.X - TL.X; - ratioY = BR.Y - TL.Y; - } - - public Vector2 MapTerrain(ref Vector3 point) - { - var xPosition = (point.X - minX) / ratioX; - var yPosition = (point.Y - minY) / ratioY; - - - /** - var terrainXO = (terrainType / TerrainSheetSize); - var terrainSize = (1.0f / TerrainSheetSize); - - var txOrigin = terrainXO + ((x * (TerrainSpread * terrainSize)) % terrainSize); - var txMid = txOrigin + ((TerrainSpread * (terrainSize / 2)) % terrainSize); - var txEnd = txOrigin + ((TerrainSpread * terrainSize) % terrainSize); - - var tyOrigin = y * TerrainSpread; - var tyMid = (y + 0.5f) * TerrainSpread; - var tyEnd = (y + 1) * TerrainSpread; - - var textureP0 = new Vector2(txMid, tyMid); - var textureP1 = new Vector2(txOrigin, tyOrigin); - var textureP2 = new Vector2(txEnd, tyOrigin); - var textureP3 = new Vector2(txEnd, tyEnd); - var textureP4 = new Vector2(txOrigin, tyEnd); -**/ - - var xTerrainStart = (TerrainSheetCellSize * TerrainType); - var xCellOffset = X * (TerrainSpread * TerrainSheetCellSize); - var xVertexOffset = (TerrainSpread * xPosition); - - xPosition = (xCellOffset + xVertexOffset) % TerrainSheetCellSize; - xPosition += xTerrainStart; - - - yPosition = (Y * TerrainSpread) + (yPosition * TerrainSpread); - - return new Vector2(xPosition, yPosition); - } - } -} diff --git a/TSOClient/tso.client/Rendering/City/CityGeometry.cs b/TSOClient/tso.client/Rendering/City/CityGeometry.cs index 1a0d14246..6bd9e595b 100644 --- a/TSOClient/tso.client/Rendering/City/CityGeometry.cs +++ b/TSOClient/tso.client/Rendering/City/CityGeometry.cs @@ -1,21 +1,40 @@ -using FSO.Common.Utils; +using FSO.Common.Domain.Realestate; +using FSO.Common.Utils; +using FSO.Content.Model; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Threading.Tasks; namespace FSO.Client.Rendering.City { - public class CityGeometry + public readonly record struct CitySliceKey { - private static Matrix RotToNormalXY = Matrix.CreateRotationZ((float)(Math.PI / 2)); - private static Matrix RotToNormalZY = Matrix.CreateRotationX(-(float)(Math.PI / 2)); + public readonly int SliceID; + public readonly Rectangle? FlattenRect; + + public CitySliceKey(int sliceID, uint lotId) + { + SliceID = sliceID; + FlattenRect = null; + + if (lotId != 0) + { + // Try and build a flatten rect around the target lot. + // If surrounding lots are disabled, it only affects the current lot. + // If they're enabled, it affects the surrounding lots too. + + var pos = MapCoordinates.Unpack(lotId); + FlattenRect = new Rectangle(pos.ToPoint(), new Point(1)); + } + } + } + + public class CityGeometry + { //draw order: //grass, sand, rock, snow, water - public CityMapData MapData; + public CityMap MapData; public IndexBuffer[] LayerIndices = new IndexBuffer[5]; public VertexBuffer[] LayerVertices = new VertexBuffer[5]; public int[][] LayerSubPrims = new int[5][]; @@ -27,7 +46,7 @@ public class CityGeometry public int Width; public int Height; public int Ready = -1; - public int CurrentSlice = -1; + public CitySliceKey? CurrentSlice = null; private bool MeshRegenInProgress; private bool MeshDirty; @@ -38,23 +57,22 @@ private static float GetElevationPoint(byte[] elevationData, int x, int y) return elevationData[(y * 512 + x)] / 6.0f; } - private Blend GetBlend(byte[] TerrainTypeData, int i, int j) + private Blend GetBlend(TerrainType[] TerrainTypeData, int i, int j) { - int[] edges; + Span edges = [-1, -1, -1, -1]; int sample; int t; - edges = new int[] { -1, -1, -1, -1 }; - sample = TerrainTypeData[i * 512 + j]; - t = TerrainTypeData[Math.Abs((i - 1) * 512 + j)]; + sample = (int)TerrainTypeData[i * 512 + j]; + t = (int)TerrainTypeData[Math.Abs((i - 1) * 512 + j)]; - if ((i - 1 >= 0) && (t > sample) && t != 255) edges[0] = t; - t = TerrainTypeData[i * 512 + j + 1]; - if ((j + 1 < 512) && (t > sample) && t != 255) edges[1] = t; - t = TerrainTypeData[Math.Min((i + 1), 511) * 512 + j]; - if ((i + 1 < 512) && (t > sample) && t != 255) edges[2] = t; - t = TerrainTypeData[i * 512 + j - 1]; - if ((j - 1 >= 0) && (t > sample) && t != 255) edges[3] = t; + if ((i - 1 >= 0) && (t > sample) && t != -1) edges[0] = t; + t = (int)TerrainTypeData[i * 512 + j + 1]; + if ((j + 1 < 512) && (t > sample) && t != -1) edges[1] = t; + t = (int)TerrainTypeData[Math.Min((i + 1), 511) * 512 + j]; + if ((i + 1 < 512) && (t > sample) && t != -1) edges[2] = t; + t = (int)TerrainTypeData[i * 512 + j - 1]; + if ((j - 1 >= 0) && (t > sample) && t != -1) edges[3] = t; int binary = @@ -71,10 +89,12 @@ private Blend GetBlend(byte[] TerrainTypeData, int i, int j) for (int x = 0; x < 4; x++) if (edges[x] < maxEdge && edges[x] != -1) maxEdge = edges[x]; - Blend ReturnBlend = new Blend(); - ReturnBlend.Binary = binary; - ReturnBlend.AtlasPosition = atlasPos; - ReturnBlend.MaxEdge = maxEdge; + Blend ReturnBlend = new Blend + { + Binary = binary, + AtlasPosition = atlasPos, + MaxEdge = maxEdge + }; return ReturnBlend; } @@ -83,42 +103,33 @@ private Vector3 GetNormalAt(byte[] elevationData, int x, int y) { var sum = new Vector3(); + float myElevation = GetElevationPoint(elevationData, x, y); + if (x < 511) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(elevationData, x + 1, y) - GetElevationPoint(elevationData, x, y); - vec = Vector3.Transform(vec, RotToNormalXY); - sum += vec; + sum.X -= GetElevationPoint(elevationData, x + 1, y) - myElevation; + sum.Y += 1; } if (x > 1) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(elevationData, x, y) - GetElevationPoint(elevationData, x - 1, y); - vec = Vector3.Transform(vec, RotToNormalXY); - sum += vec; + sum.X -= myElevation - GetElevationPoint(elevationData, x - 1, y); + sum.Y += 1; } if (y < 511) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(elevationData, x, y + 1) - GetElevationPoint(elevationData, x, y); - vec = Vector3.Transform(vec, RotToNormalZY); - sum += vec; + sum.Z -= GetElevationPoint(elevationData, x, y + 1) - myElevation; + sum.Y += 1; } if (y > 1) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(elevationData, x, y) - GetElevationPoint(elevationData, x, y - 1); - vec = Vector3.Transform(vec, RotToNormalZY); - sum += vec; + sum.Z -= myElevation - GetElevationPoint(elevationData, x, y - 1); + sum.Y += 1; } - if (sum != Vector3.Zero) sum.Normalize(); + + if (sum.Y != 0) sum.Normalize(); return sum; } @@ -189,7 +200,7 @@ public void RegenMeshVerts(GraphicsDevice gd, bool async) Action generate = () => { - byte[] terrainType = MapData.TerrainType; + TerrainType[] terrainType = MapData.TerrainType; byte[] roadData = MapData.RoadData; byte[] elevationData = MapData.ElevationData; @@ -230,7 +241,7 @@ public void RegenMeshVerts(GraphicsDevice gd, bool async) { //where the magic happens var ex = Math.Min(Math.Max(rXS, j), rXE - 1); var blendData = GetBlend(terrainType, i, ex); //gets information on what this tile blends into and what blend image to use for the alpha. - var type = terrainType[((i * 512) + ex)]; + var type = (byte)terrainType[((i * 512) + ex)]; byte roadByte = roadData[(i * 512 + ex)]; if (type == 255) @@ -497,8 +508,8 @@ public void RegenMeshVerts(GraphicsDevice gd, bool async) RoadIndices.SetData(roadIndices.ToArray()); RoadVertices = new VertexBuffer(gd, typeof(TLayerVertex), roadVertices.Count, BufferUsage.None); RoadVertices.SetData(roadVertices.ToArray()); - RoadPrims = roadIndices.Count / 3; } + RoadPrims = roadIndices.Count / 3; }; @@ -538,14 +549,43 @@ public void RegenMeshVerts(GraphicsDevice gd, bool async) } } - private int O(int x, int y, int minx, int maxx) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static int O(int x, int y, int minx, int maxx) { return (Math.Max(0, Math.Min(511, y)) * 512 + Math.Max(minx, Math.Min(maxx, x))); } - public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, int cpos) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float GetContinuity(int x, int y, in Rectangle range, in Rectangle? flattenRect) + { + if (x <= range.X || x >= range.Right || y <= range.Y || y >= range.Bottom) + { + return -1; + } + + if (flattenRect.HasValue) + { + Rectangle rect = flattenRect.Value; + + if (x >= rect.X && x <= rect.Right && y >= rect.Y && y <= rect.Bottom) + { + return -1; + } + } + + return 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float Lerp(float a, float b, float t) { - CurrentSlice = cpos; + return a * (1 - t) + b * t; + } + + public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle range, int subdiv, CitySliceKey slice) + { + var cpos = slice.SliceID; + CurrentSlice = slice; var indices = new List[5]; var vertices = new List[5]; @@ -569,15 +609,12 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i float subd1f = 1f / subdiv; int vertCount = subd1 * subd1; - if (range.HasValue) - { - yStart = range.Value.Y; - yEnd = range.Value.Bottom; - } + yStart = range.Y; + yEnd = range.Bottom; Task.Run(() => { - byte[] terrainType = MapData.TerrainType; + TerrainType[] terrainType = MapData.TerrainType; byte[] roadData = MapData.RoadData; byte[] elevationData = MapData.ElevationData; @@ -608,17 +645,14 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i xStart -= fadeRange; xEnd += fadeRange; - if (range.HasValue) - { - xStart = Math.Max(range.Value.X, xStart); - xEnd = Math.Min(range.Value.Right, xEnd); - } + xStart = Math.Max(range.X, xStart); + xEnd = Math.Min(range.Right, xEnd); for (int j = xStart; j < xEnd; j++) { //where the magic happens var ex = Math.Min(Math.Max(rXS, j), rXE - 1); var blendData = GetBlend(terrainType, i, ex); //gets information on what this tile blends into and what blend image to use for the alpha. - var type = terrainType[((i * 512) + ex)]; + var type = (byte)terrainType[((i * 512) + ex)]; byte roadByte = roadData[(i * 512 + ex)]; if (type == 255) @@ -648,35 +682,38 @@ public void SubRegenMeshVerts(GraphicsDevice gd, Rectangle? range, int subdiv, i var normalRoad = roadByte & 15; var cornerRoad = roadByte >> 4; - var yEdge = (j == range.Value.X) ? -1f : 0f; - var yEdge2 = (j == range.Value.Right - 1) ? -1f : 0f; - - var xEdge = (i == range.Value.Y) ? -1f : 0f; - var xEdge2 = (i == range.Value.Bottom - 1) ? -1f : 0f; + // Also enforce continuity when within the FlattenRect + var cont00 = GetContinuity(j, i, range, in slice.FlattenRect); + var cont10 = GetContinuity(j + 1, i, range, in slice.FlattenRect); + var cont01 = GetContinuity(j, i + 1, range, in slice.FlattenRect); + var cont11 = GetContinuity(j + 1, i + 1, range, in slice.FlattenRect); - var d = new float[] - { + Span d = + [ md[O(j - 1, i - 1, rXS, rXE)], md[O(j - 1, i, rXS, rXE)], md[O(j - 1, i + 1, rXS2, rXE2)], md[O(j - 1, i + 2, rXS2, rXE2)], md[O(j, i - 1, rXS, rXE)], md[O(j, i, rXS, rXE)], md[O(j, i + 1, rXS2, rXE2)], md[O(j, i + 2, rXS2, rXE2)], md[O(j + 1, i - 1, rXS, rXE)], md[O(j + 1, i, rXS, rXE)], md[O(j + 1, i + 1, rXS2, rXE2)], md[O(j + 1, i + 2, rXS2, rXE2)], md[O(j + 2, i - 1, rXS, rXE)], md[O(j + 2, i, rXS, rXE)], md[O(j + 2, i + 1, rXS2, rXE2)], md[O(j + 2, i + 2, rXS2, rXE2)], - }; + ]; var normalTile = (j > rXS && j < rXE); var yi = 0f; for (int y = 0; y < subd1; y++) { - var lXE = (yi * xEdge2) + ((1 - yi) * xEdge); var xi = 0f; for (int x = 0; x < subd1; x++) { + var yEdge = Lerp(cont00, cont01, yi); + var yEdge2 = Lerp(cont10, cont11, yi); + float y1 = Cubic(d[0], d[1], d[2], d[3], yi, yEdge); float y2 = Cubic(d[4], d[5], d[6], d[7], yi, yEdge); float y3 = Cubic(d[8], d[9], d[10], d[11], yi, yEdge2); float y4 = Cubic(d[12], d[13], d[14], d[15], yi, yEdge2); + var lXE = Lerp(yEdge, yEdge2, xi); var h = Cubic(y1, y2, y3, y4, xi, lXE); var lerpNX = Vector3.Lerp(norm1, norm2, xi); diff --git a/TSOClient/tso.client/Rendering/City/CityMapData.cs b/TSOClient/tso.client/Rendering/City/CityMapData.cs index 4a510c9b6..1087233b6 100644 --- a/TSOClient/tso.client/Rendering/City/CityMapData.cs +++ b/TSOClient/tso.client/Rendering/City/CityMapData.cs @@ -1,141 +1,36 @@ -using Microsoft.Xna.Framework; +using FSO.Content.Model; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; namespace FSO.Client.Rendering.City { - public class CityMapData + public static class CityMapExtensions { - public byte[] RoadData; - public byte[] ElevationData; - public byte[] ForestDensityData; - public Color[] ForestTypeData; - public Color[] TerrainTypeColorData; - public byte[] TerrainType; - - private Dictionary TerrainTypeMap = new Dictionary() - { - { new Color(0, 255, 0), 0 }, //grass - {new Color(12, 0, 255), 4 }, //water - {new Color(255, 255, 255), 3 }, //snow - {new Color(255, 0, 0), 2 }, //rock - {new Color(255, 255, 0), 1 }, //sand - {new Color(0, 0, 0), 255 }, //nothing, don't blend into this - }; - - public int Width; - public int Height; - - public CityMapData() - { - - } - - public CityMapData(string baseDir, Func texLoader) + public static void Save(this CityMap map, string baseDir, CityMapAspects aspects = CityMapAspects.All) { - Load(baseDir, texLoader, "bmp"); - } - - public void Load(string baseDir, Func texLoader, string filetype) - { - var elevation = texLoader(Path.Combine(baseDir, "elevation."+filetype)); - var terrainType = texLoader(Path.Combine(baseDir, "terraintype." + filetype)); - var forestType = texLoader(Path.Combine(baseDir, "foresttype." + filetype)); - var forestDensity = texLoader(Path.Combine(baseDir, "forestdensity." + filetype)); - var roadMap = texLoader(Path.Combine(baseDir, "roadmap." + filetype)); - - Width = elevation.Width; - Height = elevation.Height; - - var colorData = new Color[elevation.Width * elevation.Height]; - elevation.GetData(colorData); - ElevationData = Array.ConvertAll(colorData, (col) => col.R); - ElevationFlood(ElevationData); - roadMap.GetData(colorData); - RoadData = Array.ConvertAll(colorData, (col) => col.R); - forestDensity.GetData(colorData); - ForestDensityData = Array.ConvertAll(colorData, (col) => col.R); - - ForestTypeData = new Color[forestType.Width * forestType.Height]; - forestType.GetData(ForestTypeData); - TerrainTypeColorData = new Color[terrainType.Width * terrainType.Height]; - terrainType.GetData(TerrainTypeColorData); - TerrainType = Array.ConvertAll(TerrainTypeColorData, x => + if (aspects.HasFlag(CityMapAspects.Road)) { - byte result; - if (TerrainTypeMap.TryGetValue(x, out result)) - { - return result; - } - return (byte)255; - }); - - elevation.Dispose(); - terrainType.Dispose(); - forestType.Dispose(); - forestDensity.Dispose(); - roadMap.Dispose(); - } - - public bool IsInBounds(int x, int y) - { - return x > -0 && x < 512 && y >= 0 && y < 512; - } + SaveTex(Path.Combine(baseDir, "roadmap.png"), [.. map.RoadData.Select(x => new Color(x, x, x, (byte)255))]); + } - private Tuple InBounds(int x, int y) - { - int xStart, xEnd; - if (y < 306) - xStart = 306 - y; - else - xStart = y - 306; - if (y < 205) - xEnd = 307 + y; - else - xEnd = 512 - (y - 205); - int sD = xStart - x; - int eD = x - xEnd; - if (sD > eD) - { - return new Tuple(1, sD); - } else + if (aspects.HasFlag(CityMapAspects.Elevation)) { - return new Tuple(-1, eD); + SaveTex(Path.Combine(baseDir, "elevation.png"), [.. map.ElevationData.Select(x => new Color(x, x, x, (byte)255))]); } - } - public void ElevationFlood(byte[] data) - { - return; - var result = (byte[])data.Clone(); - for (int y=0; y<512; y++) + if (aspects.HasFlag(CityMapAspects.Forest)) { - for (int x=0; x<512; x++) - { - var dist = InBounds(x,y); - if (dist.Item2 > 0) - { - int avg = 0; - - //int destX = - } - } + SaveTex(Path.Combine(baseDir, "forestdensity.png"), [.. map.ForestDensityData.Select(x => new Color(x, x, x, (byte)255))]); + SaveTex(Path.Combine(baseDir, "foresttype.png"), [.. map.ForestTypeColorData]); } - } - public void Save(string baseDir) - { - SaveTex(Path.Combine(baseDir, "roadmap.png"), RoadData.Select(x => new Color(x, x, x, (byte)255)).ToArray()); - SaveTex(Path.Combine(baseDir, "elevation.png"), ElevationData.Select(x => new Color(x, x, x, (byte)255)).ToArray()); - SaveTex(Path.Combine(baseDir, "forestdensity.png"), ForestDensityData.Select(x => new Color(x, x, x, (byte)255)).ToArray()); - SaveTex(Path.Combine(baseDir, "foresttype.png"), ForestTypeData.ToArray()); - SaveTex(Path.Combine(baseDir, "terraintype.png"), TerrainTypeColorData.ToArray()); + if (aspects.HasFlag(CityMapAspects.TerrainType)) + { + SaveTex(Path.Combine(baseDir, "terraintype.png"), [.. map.TerrainTypeColorData]); + } } - public void SaveTex(string filename, Color[] data) + public static void SaveTex(string filename, Color[] data) { var tex = new Texture2D(GameFacade.GraphicsDevice, 512, 512); tex.SetData(data); diff --git a/TSOClient/tso.client/Rendering/City/CityModification.cs b/TSOClient/tso.client/Rendering/City/CityModification.cs new file mode 100644 index 000000000..39dd4de9d --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/CityModification.cs @@ -0,0 +1,105 @@ +using FSO.Common.Domain.Realestate; +using FSO.Content.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; + +namespace FSO.Client.Rendering.City +{ + internal class CityModification(CityEditBitmap bitmap, Color color, uint avatarId) + { + public const float FlashDuration = 0.25f; + public const float VisibleDuration = 1.5f; + public const float EdgeDuration = 2f; + public const float FadeTime = 0.5f; + public const float FillIntensity = 0.25f; + public const float EdgeFadeTime = 1f; + public const float EdgeIntensity = 0.60f; + + public readonly CityEditBitmap Bitmap = bitmap; + public readonly Color Color = color; + public readonly uint AvatarId = avatarId; + + public float Timer; + + public (Color edgeColor, Color fillColor) GetColors() + { + float fillAlpha = FillIntensity; + float edgeAlpha = EdgeIntensity; + + if (Timer < FlashDuration) + { + fillAlpha += (1 - fillAlpha) * ((FlashDuration - Timer) / FlashDuration); + edgeAlpha += (1 - edgeAlpha) * ((FlashDuration - Timer) / FlashDuration); + } + + if (Timer > VisibleDuration - FadeTime) + { + fillAlpha *= (VisibleDuration - Timer) / FadeTime; + } + + if (Timer > EdgeDuration - EdgeFadeTime) + { + edgeAlpha *= (EdgeDuration - Timer) / EdgeFadeTime; + } + + return (Color * edgeAlpha, new Color(Color, fillAlpha)); + } + + public float GetArrowAlpha() + { + float arrowAlpha = 1; + + if (Timer > EdgeDuration - EdgeFadeTime) + { + arrowAlpha *= (EdgeDuration - Timer) / EdgeFadeTime; + } + + return arrowAlpha; + } + + public static CityModification FromBitmap(CityEditBitmap bmp, Color color, uint avatarId) + { + if (bmp == null) return null; + + return new CityModification(bmp, color, avatarId); + } + + public static CityModification FromBounds(Rectangle? rectOpt, Color color, uint avatarId) + { + if (rectOpt == null) return null; + + var rect = rectOpt.Value; + + if (rect.IsEmpty) return null; + + var bmp = new CityEditBitmap(rect.X, rect.Y, rect.Width, rect.Height); + + bmp.Set(0, 0, rect.Width * rect.Height); + + return new CityModification(bmp, color, avatarId); + } + + public static CityModification FromCommand(CityMap map, CityEditBase cmd) + { + var color = new Color(cmd.Color); + var avatarId = cmd.AvatarId; + + if (cmd is CityEditAltitude alt) + { + return FromBitmap(alt.Bitmap, color, avatarId); + } + else if (cmd is CityEditPaint paint) + { + return FromBitmap(paint.Bitmap, color, avatarId); + } + else if (cmd is CityEditForest forest) + { + return FromBitmap(forest.Bitmap, color, avatarId); + } + else + { + return FromBounds(CityMapUtils.GetBounds(map, cmd), color, avatarId); + } + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/CityNeighGeom.cs b/TSOClient/tso.client/Rendering/City/CityNeighGeom.cs index d25639baf..1675f7e24 100644 --- a/TSOClient/tso.client/Rendering/City/CityNeighGeom.cs +++ b/TSOClient/tso.client/Rendering/City/CityNeighGeom.cs @@ -6,13 +6,8 @@ using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; using FSO.Files; -using MIConvexHull; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; namespace FSO.Client.Rendering.City { @@ -63,7 +58,7 @@ public void RandomData() { Name = "Rand" + i, Location = new Point(random.Next(512), random.Next(512)) - } + } ); } @@ -93,11 +88,22 @@ public void Generate(GraphicsDevice gd) pts.Add(new Vector2(-margin, mapSize + margin)); pts.Add(new Vector2(mapSize + margin, mapSize + margin)); + int tLOffset = 306; + int brOffset = 205; + Cells = new VoronoiCellGraph(pts).Result; NHoodToCell.Clear(); var index = 0; foreach (var cell in Cells) { + // Clip against the map boundaries + cell.Clip(new Vector2(0, tLOffset), new Vector2(tLOffset, 0)); + cell.Clip(new Vector2(tLOffset, 0), new Vector2(mapSize, brOffset)); + cell.Clip(new Vector2(mapSize, brOffset), new Vector2(brOffset, mapSize)); + cell.Clip(new Vector2(brOffset, mapSize), new Vector2(0, tLOffset)); + + cell.RecalculateCenter(); + //follow cell vertices, making them into a mesh. var cV = new List(); var cI = new List(); @@ -109,13 +115,13 @@ public void Generate(GraphicsDevice gd) cV.Add(new DGRP3DVert( new Vector3((float)vert.X, -1, (float)vert.Y), Vector3.Zero, - new Vector2(0, ((-1)-City.InterpElevationAt(vert))/10) + new Vector2(0, ((-1) - City.InterpElevationAt(vert)) / 10) )); cV.Add(new DGRP3DVert( new Vector3((float)vert.X, 100, (float)vert.Y), Vector3.Zero, - new Vector2(0, (100 - City.InterpElevationAt(vert))/10) + new Vector2(0, (100 - City.InterpElevationAt(vert)) / 10) )); //i mod @@ -149,12 +155,13 @@ public void Generate(GraphicsDevice gd) //add top and bottom tri fans - + i = 0; foreach (var vert in cell.Cycle) { - if (i > 3) { - cI.Add(i-2); //bottom cap + if (i > 3) + { + cI.Add(i - 2); //bottom cap cI.Add(i); cI.Add(0); @@ -248,8 +255,8 @@ public void DrawHover(GraphicsDevice gd, SpriteBatch batch, Effect VertexShader, { var cell = Cells[cid]; var nhood = Data[id]; - EdgeCell(gd, VertexShader, PixelShader, content, cell, (nhood.Color ?? Color.White) * f*0.6f); - FillCell(gd, VertexShader, PixelShader, content, cell, (nhood.Color ?? Color.White) * f*0.15f); + EdgeCell(gd, VertexShader, PixelShader, content, cell, (nhood.Color ?? Color.White) * f * 0.6f); + FillCell(gd, VertexShader, PixelShader, content, cell, (nhood.Color ?? Color.White) * f * 0.15f); DrawCellBanner(cell, toDraw, bannerContainer, f); } } @@ -267,7 +274,7 @@ public void DrawHover(GraphicsDevice gd, SpriteBatch batch, Effect VertexShader, } FillEdges(gd, VertexShader, PixelShader, content, Color.Black * 0.5f * BannerPct); } - + var toDelete = Banners.Except(toDraw).ToList(); foreach (var del in toDelete) @@ -474,21 +481,24 @@ public int NhoodNearest(Vector2 pos) public void Update(UpdateState state) { - if (City.m_Zoomed == TerrainZoomMode.Far) { + if (City.m_Zoomed == TerrainZoomMode.Far) + { //find the nhood we're hovering var pos = City.EstTileAtPosWithScroll(state.MouseState.Position.ToVector2() / FSOEnvironment.DPIScaleFactor, null); - + // Neighbourhoods are only interactive if there's more than one. if (City.HandleMouse && City.NeighGeom.Cells.Count > 1) { HoverNHood = NhoodNearest(pos); if (HoverNHood > -1 && !HoverPct.ContainsKey(HoverNHood)) HoverPct.Add(HoverNHood, 0f); - } else + } + else { HoverNHood = -1; } - } else + } + else { HoverNHood = -1; } @@ -573,21 +583,22 @@ public void Draw(GraphicsDevice gd, Effect VertexShader, Effect PixelShader, Cit VertexShader.Parameters["ObjModel"].SetValue(Matrix.Identity); VertexShader.Parameters["DepthBias"].SetValue(0f); - for (int i=0; i - /// Allows the game component to perform any initialization it needs to before starting - /// to run. This is where it can query for any required services and load content. - /// - public void Initialize() - { - //RenderTarget = RenderTargetUtils.CreateRenderTarget(game.GraphicsDevice, 1, SurfaceFormat.Color, 800, 600); - - /** Load the terrain effect **/ - effect = GameFacade.Game.Content.Load("Effects/TerrainSplat"); - - - /** Setup **/ - //SetCity("0020"); - SetCity("0013"); - - //transX = -(City.Width * Geom.CellWidth / 2); - //transY = +(City.Height / 2 * Geom.CellHeight / 2); - - /** - * Setup terrain texture - */ - - var device = GameFacade.GraphicsDevice; - - var textureBase = GameFacade.GameFilePath("gamedata/terrain/newformat/"); - - var grass = Texture2D.FromFile(device, Path.Combine(textureBase, "gr.tga")); - var rock = Texture2D.FromFile(device, Path.Combine(textureBase, "rk.tga")); - var snow = Texture2D.FromFile(device, Path.Combine(textureBase, "sn.tga")); - var sand = Texture2D.FromFile(device, Path.Combine(textureBase, "sd.tga")); - var water = Texture2D.FromFile(device, Path.Combine(textureBase, "wt.tga")); - - TextureTerrain = TextureUtils.MergeHorizontal(device, grass, snow, sand, rock, water); - - TextureGrass = grass; - TextureSand = sand; - TextureSnow = snow; - TextureRock = rock; - TextureWater = water; - - effect.Parameters["xTextureBlend"].SetValue(TextureBlend); - effect.Parameters["xTextureTerrain"].SetValue(TextureTerrain); - - - /** Dont need these anymore **/ - //grass.Dispose(); - //rock.Dispose(); - //snow.Dispose(); - //sand.Dispose(); - //water.Dispose(); - - /** - * Setup alpha map texture - */ - /** Construct a single texture out of the alpha maps **/ - Texture2D[] alphaMaps = new Texture2D[15]; - for (var t = 0; t < 15; t++) - { - var index = t.ToString(); - if (t < 10) { index = "0" + index; } - alphaMaps[t] = Texture2D.FromFile(device, Path.Combine(textureBase, "transb" + index + "b.tga")); - } - - /** We add an extra 64px so that the last slot in the sheet is a solid color aka no blending **/ - TextureBlend = TextureUtils.MergeHorizontal(device, 64, alphaMaps); - alphaMaps.ToList().ForEach(x => x.Dispose()); - - - effect.Parameters["xTextureBlend"].SetValue(TextureBlend); - effect.Parameters["xTextureTerrain"].SetValue(TextureTerrain); - - - //TextureBlend.Save(@"C:\Users\Admin\Desktop\blendBB.jpg", ImageFileFormat.Jpg); - } - - - public void SetCity(string code) - { - //currentCity = code; - City = CityData.Load(GameFacade.GraphicsDevice, GameFacade.GameFilePath("cities/city_" + code + "/")); - RecalculateGeometry(); - } - - public void RecalculateGeometry() - { - Geom = new RhysGeom(); - - Geom.CellHeight = CellHeight; - Geom.CellWidth = CellWidth; - Geom.CellYScale = CellScale; - - Geom.Process(City); - Geom.CreateBuffer(GameFacade.GraphicsDevice); - - - lightDirection = new Vector3((City.Width * CellWidth), (City.Height * CellHeight), -400f); - } - - public override void Update(UpdateState GState) - { - } - - public override void Draw(GraphicsDevice device) - { - var gd = GameFacade.GraphicsDevice; - - gd.VertexDeclaration = new VertexDeclaration(gd, TerrainVertex.VertexElements); - effect.CurrentTechnique = effect.Techniques["TerrainSplat"]; - - effect.Parameters["xWorld"].SetValue(World); - effect.Parameters["xView"].SetValue(View); - effect.Parameters["xProjection"].SetValue(Projection); - - effect.Parameters["xEnableLighting"].SetValue(true); - effect.Parameters["xAmbient"].SetValue(0.8f); - effect.Parameters["xLightDirection"].SetValue(lightDirection); - effect.CommitChanges(); - - effect.Begin(); - foreach (EffectPass pass in effect.CurrentTechnique.Passes) - { - pass.Begin(); - Geom.Draw(gd); - pass.End(); - } - effect.End(); - } - } -} diff --git a/TSOClient/tso.client/Rendering/City/CityVertex.cs b/TSOClient/tso.client/Rendering/City/CityVertex.cs deleted file mode 100644 index e668eeefb..000000000 --- a/TSOClient/tso.client/Rendering/City/CityVertex.cs +++ /dev/null @@ -1,55 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework; -using System.Runtime.InteropServices; -using Microsoft.Xna.Framework.Graphics; - -namespace TSOClient.Code.Rendering.City -{ - [StructLayout(LayoutKind.Sequential)] - public struct CityVertex - { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Vector4 TextureWeight1; - public Vector4 TextureWeight2; - - public static int SizeInBytes = (sizeof(float) * (3 + 2 + 4 + 4)) + 4; - public static VertexElement[] VertexElements = new VertexElement[] - { - new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ), - new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ), - new VertexElement( 0, (sizeof(float) * 3) + 4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ), - new VertexElement( 0, (sizeof(float) * (3 + 2)) + 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ), - new VertexElement( 0, (sizeof(float) * (3 + 2 + 4)) + 4, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2 ) - }; - - public CityVertex(Vector3 position, Vector2 textureCoords, Color color, TerrainType terrain) - { - this.Position = position; - this.Color = color; - this.TextureCoordinate = textureCoords; - this.TextureWeight1 = new Vector4(terrain == TerrainType.Grass ? 1 : 0, - terrain == TerrainType.Snow ? 1 : 0, - terrain == TerrainType.Sand ? 1 : 0, - terrain == TerrainType.Rock ? 1 : 0); - - this.TextureWeight2 = new Vector4(terrain == TerrainType.Water ? 1 : 0, 0, 0, 0); - } - } -} diff --git a/TSOClient/tso.client/Rendering/City/CityVertexColorGenerator.cs b/TSOClient/tso.client/Rendering/City/CityVertexColorGenerator.cs new file mode 100644 index 000000000..bc971e734 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/CityVertexColorGenerator.cs @@ -0,0 +1,350 @@ +using FSO.Common.Utils; +using FSO.Content.Model; +using FSO.LotView; +using FSO.LotView.Effects; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Runtime.InteropServices; + +namespace FSO.Client.Rendering.City +{ + internal class CityVertexColorGenerator : IDisposable + { + private struct ColorStop + { + public readonly Color Color; + public readonly float Stop; + + public ColorStop(Color color, float stop) + { + Color = color; + Stop = stop; + } + } + + private Terrain Parent; + private RenderTarget2D Normal; + private RenderTarget2D VertexColor; + private RenderTarget2D VertexColorTemp; + private RenderTarget2D JumpFlood; + private RenderTarget2D JumpFloodAlt; + private RenderTarget2D GaussianWorking; + private Texture2D TerrainType; + private Texture2D Elevation; + private RenderTarget2D ForestDensity; + private RenderTarget2D TerrainEdge; + private Texture2D WaterGradient; + + private MapGeneration Effect; + + private Color ForestColor = new Color(0xff3F7C49); + private BlendState AdditiveRGB; + + public CityVertexColorGenerator(Terrain parent) + { + Parent = parent; + } + + private void Init(GraphicsDevice gd) + { + Normal = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + VertexColor = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + VertexColorTemp = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + GaussianWorking = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + JumpFlood = new RenderTarget2D(gd, 512, 512); + JumpFloodAlt = new RenderTarget2D(gd, 512, 512); + TerrainType = new Texture2D(gd, 512, 512, false, SurfaceFormat.Alpha8); + Elevation = new Texture2D(gd, 512, 512, false, SurfaceFormat.Alpha8); + ForestDensity = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + TerrainEdge = new RenderTarget2D(gd, 512, 512, false, SurfaceFormat.Alpha8, DepthFormat.None); + WaterGradient = GenerateGradient(gd, [ + new(new Color(0xffFFEA45), 0.0f), + new(new Color(0xffFFEA45), 0.1f), + new(new Color(0xffFF8646), 0.8f), + new(new Color(0xffAB7448), 1) + ], 2.5f); + + Effect = WorldContent.MapGenerationEffect; + + AdditiveRGB = new BlendState() + { + ColorSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.One, + ColorDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.One, + AlphaDestinationBlend = Microsoft.Xna.Framework.Graphics.Blend.One, + AlphaSourceBlend = Microsoft.Xna.Framework.Graphics.Blend.Zero, + }; + } + + private void Blit(Texture2D src, RenderTarget2D target, BlendState blendState = null, SamplerState samplerState = null) + { + var gd = GameFacade.GraphicsDevice; + gd.SetRenderTarget(target); + + var effect = Effect; + + effect.BaseTexture = src; + effect.CurrentTechnique.Passes[0].Apply(); + + gd.BlendState = blendState ?? BlendState.Opaque; + gd.SetVertexBuffer(WorldContent.GetTextureVerts(gd)); + gd.DrawPrimitives(PrimitiveType.TriangleStrip, 0, 2); + + //effect.CurrentTechnique.Passes[0].Apply(); + /* + Batch.Begin(blendState: blendState ?? BlendState.Opaque, effect: effect, samplerState: samplerState ?? SamplerState.PointClamp, sortMode: SpriteSortMode.Immediate); + //effect.CurrentTechnique.Passes[0].Apply(); + Batch.Draw(src, new Vector2(), Color.White); + Batch.End(); + */ + + gd.SetRenderTarget(null); + } + + private void Blur(RenderTarget2D toBlur, float blurSize, RenderTarget2D outTex = null) + { + outTex ??= toBlur; + + Effect.PrepareGaussianKernel(blurSize); + Effect.SetTechnique(MapGenerationTechniques.Gaussian); + + Effect.GaussianStep = new Vector2(1f / toBlur.Width, 0); + Blit(toBlur, GaussianWorking); + + Effect.GaussianStep = new Vector2(0, 1f / toBlur.Height); + Blit(GaussianWorking, outTex); + } + + private RenderTarget2D GetEdgeMap(TerrainType type) + { + Effect.ImageSize = new Vector2(TerrainType.Width, TerrainType.Height); + Effect.EdgeValue = (int)type; + + Effect.SetTechnique(MapGenerationTechniques.CityEdgeDetect); + + Blit(TerrainType, TerrainEdge); + + /* + WaterGradient = GenerateGradient(Batch.GraphicsDevice, [ + new(new Color(0xffFFEA45), 0), + new(new Color(0xffFF8646), 1) + ], 2f); + */ + + return TerrainEdge; + } + + private RenderTarget2D GetDistanceMap(TerrainType type) + { + // Builds a distance map from the edge of the specified terrain type. + + var gd = GameFacade.GraphicsDevice; + var edge = GetEdgeMap(type); + + Effect.ImageSize = new Vector2(TerrainType.Width, TerrainType.Height); + + Effect.SetTechnique(MapGenerationTechniques.JumpFloodInit); + + Blit(edge, JumpFlood); + + int stepSize = 512; + int i = 0; + + while (stepSize > 0) + { + var alt = (i % 2) == 1; + var from = alt ? JumpFloodAlt : JumpFlood; + var to = alt ? JumpFlood : JumpFloodAlt; + + Effect.StepSize = stepSize; + + Effect.SetTechnique(MapGenerationTechniques.JumpFloodStep); + Blit(from, to); + + i++; + stepSize >>= 1; + } + + return (i % 2) == 1 ? JumpFloodAlt : JumpFlood; + } + + private Texture2D GenerateGradient(GraphicsDevice gd, Span colors, float power) + { + int width = 100; + var grad = new Texture2D(gd, width, 1); + var dat = new Color[width]; + var invPower = 1 / power; + + for (int i = 1; i < colors.Length; i++) + { + ColorStop from = colors[i - 1]; + ColorStop to = colors[i]; + + int fromI = (int)(MathF.Pow(from.Stop, power) * width); + int toI = (int)Math.Ceiling((MathF.Pow(to.Stop, power) * width)); + + float fromBase = from.Stop; + float range = to.Stop - from.Stop; + + for (int px = fromI; px <= Math.Min(width - 1, toI); px++) + { + float stopPos = MathF.Pow(px / (float)width, invPower); + + dat[px] = Color.Lerp(from.Color, to.Color, (stopPos - fromBase) / range); + } + } + + grad.SetData(dat); + + return grad; + } + + private void DrawTerrain(TerrainType type, Texture2D color, float sdfFade = 0, float sdfExpand = 0) + { + // Calculate the SDF for this terrain type + + var sdf = GetDistanceMap(type); + + // Set shader parameters + // Color (based off terrain type) + // Expand sets how far past the edge in pixels the terrain type is filled + // Fade sets how many pixels the edge is interpolated over. Starts at the expanded edge. + + Effect.ImageSize = new Vector2(TerrainType.Width, TerrainType.Height); + + Effect.SdfExpand = sdfExpand; + Effect.SdfFade = sdfFade; + Effect.GradientBase = 0; + Effect.GradientScale = 120; + + Effect.TerrainType = TerrainType; + Effect.DistToColor = color; + Effect.EdgeValue = (int)type; + + Effect.SetTechnique(MapGenerationTechniques.JumpDistFill); + + Blit(sdf, VertexColorTemp, BlendState.AlphaBlend); + + // TODO + // Forest effect scale + // Noise + } + + public int time = 0; + public void Update(GraphicsDevice gd) + { + if (VertexColor == null) + { + Init(gd); + } + + // Start by filling with white + gd.SetRenderTarget(VertexColorTemp); + gd.Clear(Color.White); + + // Upload the citymap terrain type to the texture + CityMap map = Parent.MapData; + var terrainType = map.GetRawTerrain(); + var why = MemoryMarshal.Cast(terrainType).ToArray(); + TerrainType.SetData(why); + + var elevation = map.GetRawElevation(); + Elevation.SetData(elevation); + + var forestDensity = map.GetRawForestDensity(); + var forestType = map.GetRawForestType(); + + var filteredDensity = new Color[forestDensity.Length]; + for (int i = 0; i < filteredDensity.Length; i++) + { + var value = forestType[i] == ForestType.NULL ? (byte)0 : forestDensity[i]; + filteredDensity[i] = new Color(value, value, value, value); + } + + ForestDensity.SetData(filteredDensity); + Blur(ForestDensity, 7f); + + // Build a distance map for the shore + + // Draw the water with depth simulated by the shore distance + + DrawTerrain(Content.Model.TerrainType.WATER, WaterGradient, sdfFade: 1f, sdfExpand: 0.5f); + + // Draw the grass and rock (color altered by forest density, blurred, the rock more than the grass) + // Draw the snow and sand (largely unaffected by anything) + var grass = TextureUtils.TextureFromColor(gd, new Color(0xff60BFA5)); + var rock = TextureUtils.TextureFromColor(gd, new Color(0xff799DC0)); + var sand = TextureUtils.TextureFromColor(gd, new Color(255, 255, 233)); + var snow = TextureUtils.TextureFromColor(gd, Color.White); + DrawTerrain(Content.Model.TerrainType.SAND, sand, sdfFade: 1.75f, sdfExpand: 0.25f); + DrawTerrain(Content.Model.TerrainType.GRASS, grass, sdfFade: 1.4f, sdfExpand: 0.25f); + DrawTerrain(Content.Model.TerrainType.ROCK, rock, sdfFade: 2.5f, sdfExpand: 0f); + DrawTerrain(Content.Model.TerrainType.SNOW, snow, sdfFade: 2f, sdfExpand: 0.25f); + + // Draw forests + + Effect.SetTechnique(MapGenerationTechniques.ForestOverlay); + + Effect.TerrainType = TerrainType; + ForestColor = new Color(0xff419e4d); + var colorVec = ForestColor.ToVector4(); + colorVec.W = 1.25f; + Effect.ColorVec = colorVec; + Blit(ForestDensity, VertexColorTemp, blendState: BlendState.AlphaBlend); + + // Draw the default lighting + + Effect.SetTechnique(MapGenerationTechniques.TerrainNormal); + + Effect.SunDir = -Vector3.Normalize(new Vector3(0, -2, -1.7f)); + Effect.TerrainScale = 1/20f; + Effect.TerrainType = TerrainType; + + Blit(Elevation, Normal); + + Blur(Normal, 5f); + + Effect.SetTechnique(MapGenerationTechniques.TerrainLighting); + Effect.DistToColor = Normal; + Blit(VertexColorTemp, VertexColor); + + Effect.SetTechnique(MapGenerationTechniques.TerrainSpecular); + + Effect.SunDir = -Vector3.Normalize(new Vector3(0, -2, -1f)); + Effect.SpecularPower = 6f; + Effect.SpecularIntensity = 0.25f; + + Blit(Normal, VertexColor, AdditiveRGB); + + gd.SetRenderTarget(null); + } + + public void DebugDraw(SpriteBatch sb) + { + if (VertexColor != null) + { + sb.Draw(VertexColor, new Vector2(), Color.White); + sb.Draw(Normal, new Vector2(0, 512), Color.White); + } + } + + public Texture2D GetVertexColor() + { + return VertexColor; + } + + public void Dispose() + { + Normal?.Dispose(); + VertexColor?.Dispose(); + VertexColorTemp?.Dispose(); + JumpFlood?.Dispose(); + JumpFloodAlt?.Dispose(); + GaussianWorking?.Dispose(); + TerrainType?.Dispose(); + Elevation?.Dispose(); + ForestDensity?.Dispose(); + TerrainEdge?.Dispose(); + WaterGradient?.Dispose(); + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Graph/VoronoiCellGraph.cs b/TSOClient/tso.client/Rendering/City/Graph/VoronoiCellGraph.cs index 62d759b92..b341bb071 100644 --- a/TSOClient/tso.client/Rendering/City/Graph/VoronoiCellGraph.cs +++ b/TSOClient/tso.client/Rendering/City/Graph/VoronoiCellGraph.cs @@ -309,6 +309,104 @@ public class CompleteVCell public IndexBuffer Indices; public float Size; + /// + /// Clip the voronoi cell against the given line. + /// Points are considered in-bounds if they're to the right of the line. + /// + /// Clipping line start + /// Clipping line end + public void Clip(Vector2 clipFrom, Vector2 clipTo) + { + if (Cycle.Length <= 1) + { + return; + } + + var dir = Vector2.Normalize(clipTo - clipFrom); + var normal = new Vector2(-dir.Y, dir.X); + var clipDot = Vector2.Dot(normal, clipFrom); + + // A positive + + var result = new List(); + + for (int i = 1; i < Cycle.Length + 1; i++) + { + Vector2 lFrom = Cycle[i - 1]; + Vector2 lTo = Cycle[i % Cycle.Length]; + Vector2 lVec = lTo - lFrom; + + float fromDist = Vector2.Dot(normal, lFrom) - clipDot; + float toDist = Vector2.Dot(normal, lTo) - clipDot; + + bool partialClip = fromDist > 0 != toDist > 0; + if (partialClip) + { + float totalDist = Math.Abs(fromDist) + Math.Abs(toDist); + float clipFraction = Math.Abs(fromDist) / totalDist; + + if (fromDist > 0) + { + // The "from" part of the line is in bounds. + if (result.Count == 0) + { + result.Add(lFrom); + } + + result.Add(lFrom + lVec * clipFraction); + } + else + { + // The "to" part of the line is in bounds. + + result.Add(lFrom + lVec * clipFraction); + + result.Add(lTo); + } + } + else if (fromDist <= 0) + { + // The whole line is clipped - don't include it. + } + else + { + if (result.Count == 0) + { + result.Add(lFrom); + } + + result.Add(lTo); + } + } + + for (int i = 1; i < result.Count; i++) + { + if (result[i] == result[i-1]) + { + result.RemoveAt(i--); + } + } + + if (result.Count > 1 && result[0] == result[result.Count - 1]) + { + result.RemoveAt(result.Count - 1); + } + + Cycle = result.ToArray(); + } + + public void RecalculateCenter() + { + // Not completely accurate, but it serves its purpose. + var total = new Vector2(); + foreach (Vector2 vec in Cycle) + { + total += vec; + } + + Center = total / Cycle.Length; + } + public void Dispose() { Vertices?.Dispose(); diff --git a/TSOClient/tso.client/Rendering/City/ICityCamera.cs b/TSOClient/tso.client/Rendering/City/ICityCamera.cs index 256991709..d2720240f 100644 --- a/TSOClient/tso.client/Rendering/City/ICityCamera.cs +++ b/TSOClient/tso.client/Rendering/City/ICityCamera.cs @@ -24,6 +24,7 @@ public interface ICityCamera : ICamera Vector2 CalculateR(); Vector2 CalculateRShadow(); void InheritPosition(Terrain parent, World lotWorld, CoreGameScreenController controller, bool instant); + void CalculateLotSquish(Matrix view); void CenterCamera(CityCameraCenter center); void ClearCenter(); diff --git a/TSOClient/tso.client/Rendering/City/ICityGeom.cs b/TSOClient/tso.client/Rendering/City/ICityGeom.cs deleted file mode 100644 index 303df34ce..000000000 --- a/TSOClient/tso.client/Rendering/City/ICityGeom.cs +++ /dev/null @@ -1,31 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; - -namespace TSOClient.Code.Rendering.City -{ - public interface ICityGeom - { - float CellWidth { get; set; } - float CellHeight { get; set; } - float CellYScale { get; set; } - - void Process(CityData city); - void CreateBuffer(GraphicsDevice gd); - void Draw(GraphicsDevice gd); - } -} diff --git a/TSOClient/tso.client/Rendering/City/LotThumbContent.cs b/TSOClient/tso.client/Rendering/City/LotThumbContent.cs index ac2022abe..5554894d3 100644 --- a/TSOClient/tso.client/Rendering/City/LotThumbContent.cs +++ b/TSOClient/tso.client/Rendering/City/LotThumbContent.cs @@ -1,12 +1,9 @@ -using FSO.Common.Utils; +using FSO.Client.Controllers; +using FSO.Common.Utils; using FSO.Files; using FSO.Files.RC; using FSO.Server.Clients; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; namespace FSO.Client.Rendering.City { @@ -23,9 +20,14 @@ public class LotThumbContent : IDisposable public int LoadLimit = 100; //about 6mb of 256x256 thumbnails public int ExpiryTime = 10; //thumbs expire after about 10 seconds. private ApiClient Client; + private CityResourceController Resource; - public LotThumbContent() + private bool IsArchive = true; + + public LotThumbContent(CityResourceController resource) { + Resource = resource; + GameThread.SetInterval(Update, 1000); Client = new ApiClient(ApiClient.CDNUrl ?? GlobalSettings.Default.GameEntryUrl); @@ -52,50 +54,46 @@ private LotThumbEntry GetLotEntryForFrame(uint shardID, uint location, bool faca if (facade) { result.LotFacade = DefaultFSOF; - - Client.GetFacadeAsync(shardID, location, (data) => + + Action callback = (data) => { - if (data != null && !result.Dead && !result.Loaded) + if (data != null && !result.Dead && !result.Loading) { - using (var mem = new MemoryStream(data)) - { - result.Loaded = true; - try - { - result.LotFacade = new FSOF(); - result.LotFacade.Read(mem); - result.LotFacade.LoadGPU(GameFacade.GraphicsDevice); - } - catch - { - result.LotFacade = null; - } - } + result.Loading = true; + result.LoadFSOFAsync(data); } - }); - + }; + + if (IsArchive) + { + Resource.GetFacadeAsync(shardID, location, callback); + } + else + { + Client.GetFacadeAsync(shardID, location, callback); + } } else { result.LotTexture = DefaultThumb; - Client.GetThumbnailAsync(shardID, location, (data) => + + Action callback = (data) => { - if (data != null && !result.Dead && !result.Loaded) + if (data != null && !result.Dead && !result.Loading) { - using (var mem = new MemoryStream(data)) - { - result.Loaded = true; - try - { - result.LotTexture = ImageLoader.FromStream(GameFacade.GraphicsDevice, mem); - } - catch - { - result.LotTexture = new Texture2D(GameFacade.GraphicsDevice, 1, 1); - } - } + result.Loading = true; + result.LoadTextureAsync(data); } - }); + }; + + if (IsArchive) + { + Resource.GetThumbnailAsync(shardID, location, callback); + } + else + { + Client.GetThumbnailAsync(shardID, location, callback); + } } entries[key] = result; } @@ -141,6 +139,19 @@ public void OverrideLotThumb(uint shardID, uint location, Texture2D tex) entry.Loaded = true; } + public void OverrideLotFacade(uint shardID, uint location, FSOF fsof) + { + var entry = GetLotEntry(shardID, location, true); + entry.Held++; //keep this forever + if (entry.Loaded) + { + entry.LotFacade?.Dispose(); + } + entry.LotFacade = fsof; + fsof?.LoadGPU(GameFacade.GraphicsDevice); + entry.Loaded = true; + } + private void Process(Dictionary Entries) { var ordered = Entries.OrderBy(x => x.Value.LastDrawSecond).ToList(); @@ -218,7 +229,82 @@ public class LotThumbEntry public FSOF LotFacade; public int Held; public bool Loaded; + public bool Loading; public bool Dead; public bool FacadeEntry; + + public void LoadTextureAsync(byte[] data) + { + Task.Run(() => + { + using (var mem = new MemoryStream(data)) + { + if (Dead || Loaded) + { + // If we're already loaded, then the thumbnail has an override. + return; + } + + Func loader; + try + { + loader = ImageLoader.NonUIFromStream(GameFacade.GraphicsDevice, mem); + } + catch + { + loader = () => new Texture2D(GameFacade.GraphicsDevice, 1, 1); + } + + AssetStreaming.InStreamUpdate(() => + { + if (Dead || Loaded) + { + // If we're already loaded, then the thumbnail has an override. + return; + } + + LotTexture = loader?.Invoke() ?? new Texture2D(GameFacade.GraphicsDevice, 1, 1); + Loaded = true; + }); + } + }); + } + + public void LoadFSOFAsync(byte[] data) + { + Task.Run(() => + { + using (var mem = new MemoryStream(data)) + { + if (Dead) + { + return; + } + + FSOF facade; + try + { + facade = new FSOF(); + facade.Read(mem); + } + catch + { + facade = null; + } + + AssetStreaming.InStreamUpdate(() => + { + if (Dead) + { + return; + } + + LotFacade = facade; + LotFacade?.LoadGPU(GameFacade.GraphicsDevice); + Loaded = true; + }); + } + }); + } } } diff --git a/TSOClient/tso.client/Rendering/City/LotTileEntry.cs b/TSOClient/tso.client/Rendering/City/LotTileEntry.cs index 063d54c91..84b2a9229 100644 --- a/TSOClient/tso.client/Rendering/City/LotTileEntry.cs +++ b/TSOClient/tso.client/Rendering/City/LotTileEntry.cs @@ -1,48 +1,201 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using FSO.Common.DataService; +using FSO.Common.DataService.Model; +using Microsoft.Xna.Framework; namespace FSO.Client.Rendering.City { - public class LotTileEntry + public class CityLotTiles { - public int lotid; - public int packed_pos + private readonly Dictionary TileByID = []; + private readonly HashSet UpdateOnlineSet = []; + private readonly HashSet DeletedSet = []; + + public readonly Dictionary TileByVector = []; + public readonly HashSet OccupiedTilesBase = []; + private readonly HashSet OnlineTiles = []; + + private HashSet OccupiedTilesCopy; + public HashSet OccupiedTiles { get { - return ((x << 16) | y); + if (OccupiedTilesCopy == null) + { + OccupiedTilesCopy = [.. OccupiedTilesBase]; + } + + return OccupiedTilesCopy; } } - public short x; - public short y; - public LotTileFlags flags; //bit 0 = online, bit 1 = spotlight, bit 2 = locked, bit 3 = occupied, other bits free for whatever use - public LotTileEntry(int Lotid, short X, short Y, LotTileFlags Flags) + public IEnumerable List => TileByID.Values; + + private static Vector2 GetVectorForId(uint id) { - this.lotid = Lotid; - this.x = X; - this.y = Y; - this.flags = Flags; + return new Vector2((short)(id >> 16), (short)(id & 0xFFFF)); } - public static LotTileEntry[] GenFromCity(Common.DataService.Model.City city) + private static int GetOccupiedTileId(LotTileEntry tile) { - var entries = new Dictionary(); + return (int)tile.y * 512 + (int)tile.x; + } + + public bool UpdateWithCity(Common.DataService.Model.City city, IClientDataService dataService) + { + var entries = TileByID; + var deletedSet = DeletedSet; + var updateOnlineSet = UpdateOnlineSet; + + var byVector = TileByVector; + var occupied = OccupiedTilesBase; + var online = OnlineTiles; + + deletedSet.Clear(); + deletedSet.UnionWith(entries.Keys); + + updateOnlineSet.Clear(); + + int newCount = 0; + int onlineChangeCount = 0; + foreach (var property in city.City_ReservedLotInfo) { - entries[property.Key] = new LotTileEntry((int)property.Key, (short)(property.Key >> 16), (short)(property.Key & 0xFFFF), property.Value?LotTileFlags.Online:0); + deletedSet.Remove(property.Key); + + if (entries.TryGetValue(property.Key, out var entry)) + { + var wasOnline = entry.flags.HasFlag(LotTileFlags.Online); + + if (wasOnline != property.Value) + { + updateOnlineSet.Add(property.Key); + onlineChangeCount++; + + if (property.Value) + { + online.Add(property.Key); + } + else + { + online.Remove(property.Key); + } + } + + entry.flags = property.Value ? LotTileFlags.Online : 0; + } + else + { + entry = new LotTileEntry((int)property.Key, (short)(property.Key >> 16), (short)(property.Key & 0xFFFF), property.Value ? LotTileFlags.Online : 0); + entries[property.Key] = entry; + byVector[GetVectorForId(property.Key)] = entry; + occupied.Add(GetOccupiedTileId(entry)); + + if (property.Value) + { + online.Add(property.Key); + + // Lot_IsOnline starts as false, so we need to set it to true. + updateOnlineSet.Add(property.Key); + } + + newCount++; + } } foreach (var spot in city.City_SpotlightsVector) { - LotTileEntry entry = null; - if (entries.TryGetValue(spot, out entry)) + if (entries.TryGetValue(spot, out var entry)) + { entry.flags |= LotTileFlags.Spotlight; + } + } + + foreach (var delete in deletedSet) + { + occupied.Remove(GetOccupiedTileId(entries[delete])); + + entries.Remove(delete); + byVector.Remove(GetVectorForId(delete)); + } + + if (updateOnlineSet.Count > 0) + { + dataService.GetMany([.. updateOnlineSet.Select(x => (object)x)]).ContinueWith(x => + { + if (!x.IsCompleted) + { + return; + } + + var entries = TileByID; + foreach (var lot in x.Result) + { + if (entries.TryGetValue(lot.Id, out var mapItem)) + { + lot.Lot_IsOnline = (mapItem.flags & LotTileFlags.Online) == LotTileFlags.Online; + } + } + }); } - return entries.Values.ToArray(); + if (newCount > 0 || deletedSet.Count > 0) + { + // Force the terrain to build a new one if it's required to generate foliage. + OccupiedTilesCopy = null; + } + + return newCount > 0 || deletedSet.Count > 0 || onlineChangeCount > 0; + } + + public void AddLocationsTo(HashSet locations) + { + foreach (var pair in TileByID) + { + locations.Add(pair.Key); + } + } + + public void AddOpenLotSurroundingsTo(HashSet locations) + { + foreach (var tile in OnlineTiles) + { + locations.Add(tile); + locations.Add(tile - 1); + locations.Add(tile + 1); + + uint axis = 1u << 16; + locations.Add(tile + axis); + locations.Add(tile + axis - 1); + locations.Add(tile + axis + 1); + + locations.Add(tile - axis); + locations.Add((tile - axis) - 1); + locations.Add((tile - axis) + 1); + } + } + } + + public class LotTileEntry + { + public int lotid; + public int packed_pos + { + get + { + return ((x << 16) | y); + } + } + public short x; + public short y; + public LotTileFlags flags; //bit 0 = online, bit 1 = spotlight, bit 2 = locked, bit 3 = occupied, other bits free for whatever use + + public LotTileEntry(int Lotid, short X, short Y, LotTileFlags Flags) + { + this.lotid = Lotid; + this.x = X; + this.y = Y; + this.flags = Flags; } } diff --git a/TSOClient/tso.client/Rendering/City/NioGeom.cs b/TSOClient/tso.client/Rendering/City/NioGeom.cs deleted file mode 100644 index 174ee678e..000000000 --- a/TSOClient/tso.client/Rendering/City/NioGeom.cs +++ /dev/null @@ -1,484 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using TSOClient.Code.Utils; - -namespace TSOClient.Code.Rendering.City -{ - public class NioGeom : IDisposable, ICityGeom - { -/// - /// Vertices for the map - /// - - public TerrainVertex[] Vertices { get; internal set; } - public int[] Indexes { get; internal set; } - public IndexBuffer IndexBuffer { get; internal set; } - public VertexBuffer VertexBuffer { get; internal set; } - public int PrimitiveCount { get; internal set; } - public int VertexPerTile = 12; - - public float CellWidth { get; set; } - public float CellHeight { get; set; } - public float BorderWidth { get; set; } - public float BorderHeight { get; set; } - - public float CellYScale { get; set; } - public float TerrainSpread = 0.05f; - - /// - /// How many textures are in the terain sheet, aka how many terrain types - /// - public float TerrainSheetSize = 5.0f; - - public int Width { get; internal set; } - public int Height { get; internal set; } - - - public void GetTileVertices(int x, int y, TerrainVertex[] target) - { - var offset = ((y * Width + x) * VertexPerTile); - for (var i = 0; i < VertexPerTile; i++) - { - target[i] = Vertices[offset + i]; - } - } - - - protected Vector2[] CalculateTexCoord(int x, int y, byte terrainType) - { - var terrainXO = (terrainType / TerrainSheetSize); - var terrainSize = (1.0f / TerrainSheetSize); - - var txOrigin = terrainXO + ((x * (TerrainSpread * terrainSize)) % terrainSize); - var txMid = txOrigin + ((TerrainSpread * (terrainSize / 2)) % terrainSize); - var txEnd = txOrigin + ((TerrainSpread * terrainSize) % terrainSize); - - var tyOrigin = y * TerrainSpread; - var tyMid = (y + 0.5f) * TerrainSpread; - var tyEnd = (y + 1) * TerrainSpread; - - var textureP0 = new Vector2(txMid, tyMid); - var textureP1 = new Vector2(txOrigin, tyOrigin); - var textureP2 = new Vector2(txEnd, tyOrigin); - var textureP3 = new Vector2(txEnd, tyEnd); - var textureP4 = new Vector2(txOrigin, tyEnd); - - return new Vector2[] { - textureP0, - textureP1, - textureP2, - textureP3, - textureP4 - }; - } - - /// - /// Do the work of generating the city geom - /// - /// - public void Process(CityData city) - { - //Cleanup if someone is trying to reuse this object - Dispose(); - var now = DateTime.Now.Ticks; - - Width = city.Width; - Height = city.Height; - - /** - * The geometry we create is basically a quad per tile for the property - * base plus 3 quads to join this quad to neighboring quads - */ - var mesh = new ThreeDMesh(); - var moditifer = 3.0f / 8.0f; - var mainTileSpan = 3.0f / 8.0f; //The main tile is 6 8ths of the tile - var edgeTileSpan = 4.0f / 8.0f; - - var textureMap = new TextureMapper(); - textureMap.TerrainSheetSize = 5.0f; - - for (var y = 0; y > -Height; y--) - { - for (var x = 0; x < Width; x++) - { - var mapY = -y; - - var offset = (mapY * city.Width) + x; - /** Settings **/ - var elevation = city.Elevation[offset]; - var vertexColor = city.VertexColor[offset]; - var terrainType = city.Terrain[offset]; - var blendIndex = city.BlendMap[offset]; - var backTerrainType = city.BackTerrain[offset]; - var z0 = elevation / 8.0f; - - textureMap.TerrainType = terrainType; - textureMap.Position(x, mapY, new Vector2(x - edgeTileSpan, y + edgeTileSpan), new Vector2(x + edgeTileSpan, y - edgeTileSpan)); - - var tex = new Vector2(0.5f, 0.5f); - var vertexTemplate = new TerrainVertex(new Vector3(0.0f, 0.0f, z0), Vector2.Zero, vertexColor, tex, tex); - - - var tl = vertexTemplate; - tl.Position.X = x - mainTileSpan; - tl.Position.Y = y + mainTileSpan; - tl.TextureCoordinate = textureMap.MapTerrain(ref tl.Position); - - var tr = vertexTemplate; - tr.Position.X = x + mainTileSpan; - tr.Position.Y = y + mainTileSpan; - tr.TextureCoordinate = textureMap.MapTerrain(ref tr.Position); - - var br = vertexTemplate; - br.Position.X = x + mainTileSpan; - br.Position.Y = y - mainTileSpan; - br.TextureCoordinate = textureMap.MapTerrain(ref br.Position); - - var bl = vertexTemplate; - bl.Position.X = x - mainTileSpan; - bl.Position.Y = y - mainTileSpan; - bl.TextureCoordinate = textureMap.MapTerrain(ref bl.Position); - - mesh.AddQuad(tl, tr, br, bl); - - /** - * Joining pieces - */ - if (x > 0) - { - var z1 = city.Elevation[offset - 1]; - - var tl2 = vertexTemplate; - tl2.Position.X = x - 1 + mainTileSpan; - tl2.Position.Y = y + mainTileSpan; - tl2.Position.Z = z1; - tl2.TextureCoordinate = textureMap.MapTerrain(ref tl2.Position); - - var tr2 = vertexTemplate; - tr2.Position.X = x - 0 - mainTileSpan; - tr2.Position.Y = y + mainTileSpan; - tr2.Position.Z = z0; - tr2.TextureCoordinate = textureMap.MapTerrain(ref tr2.Position); - - var br2 = vertexTemplate; - br2.Position.X = x - 0 - mainTileSpan; - br2.Position.Y = y - mainTileSpan; - br2.Position.Z = z0; - br2.TextureCoordinate = textureMap.MapTerrain(ref br2.Position); - - var bl2 = vertexTemplate; - bl2.Position.X = x - 1 + mainTileSpan; - bl2.Position.Y = y - mainTileSpan; - bl2.Position.Z = z1; - bl2.TextureCoordinate = textureMap.MapTerrain(ref bl2.Position); - - mesh.AddQuad(tl2, tr2, br2, bl2); - } - - - } - } - - - Vertices = mesh.GetVertexes(); - Indexes = mesh.GetIndexes(); - PrimitiveCount = mesh.PrimitiveCount; - - - - // for(var y=0; y>-512; y--){ - // for(var x=0; x<512; x++){ - // //We are scanning the bitmap from top to bottom, and plotting them bottom to top. - // //y will refer to the OpenGL coordinates, and -y will refer to the bitmap coordinates - // if(!validCoord(x,y)) continue; - // var z = city["elevation"].data[4*(512*-y + x)] / 8; - // meshes["city"].addQuad([ - // x - 3/8, y - 3/8, z, //Bottom-left - // x + 3/8, y - 3/8, z, //Bottom-right - // x + 3/8, y + 3/8, z, //Top-right - // x - 3/8, y + 3/8, z //Top-left - // ]); - // } - //} - - - //var vertexList = new List(); - //var indexList = new List(); - //BorderWidth = (CellWidth / 4) / 2; - //BorderHeight = (CellHeight / 4) / 2; - - //var spanX = CellWidth + (BorderWidth * 2); - //var spanY = CellHeight + (BorderHeight * 2); - //var textureMap = new TextureMapper(); - //textureMap.TerrainSheetSize = 5.0f; - ///** Build vertex & index structures **/ - //for (int y = 0; y < Height; y++) - //{ - // for (int x = 0; x < Width; x++) - // { - // var offset = (y * city.Width) + x; - // /** Settings **/ - // var elevation = city.Elevation[offset]; - // var vertexColor = city.VertexColor[offset]; - // var terrainType = city.Terrain[offset]; - // var blendIndex = city.BlendMap[offset]; - // var backTerrainType = city.BackTerrain[offset]; - // textureMap.TerrainType = terrainType; - // //Main points - // var mainElevation = city.GetElevation(x, y, CellYScale); - // var northElevation = city.GetElevation(x, y, NeighbourDir.North, mainElevation, CellYScale); - // var eastElevation = city.GetElevation(x, y, NeighbourDir.East, mainElevation, CellYScale); - // var southElevation = city.GetElevation(x, y, NeighbourDir.South, mainElevation, CellYScale); - // var westElevation = city.GetElevation(x, y, NeighbourDir.West, mainElevation, CellYScale); - // var northWestElevation = city.GetElevation(x, y, NeighbourDir.NorthWest, mainElevation, CellYScale); - // var northEastElevation = city.GetElevation(x, y, NeighbourDir.NorthEast, mainElevation, CellYScale); - // var southEastElevation = city.GetElevation(x, y, NeighbourDir.SouthEast, mainElevation, CellYScale); - // var southWestElevation = city.GetElevation(x, y, NeighbourDir.SouthWest, mainElevation, CellYScale); - // var startIndex = vertexList.Count; - // var tex = new Vector2(0.5f, 0.5f); - // if (mainElevation == northElevation && - // mainElevation == eastElevation && - // mainElevation == southElevation && - // mainElevation == westElevation && - // mainElevation == northWestElevation && - // mainElevation == northEastElevation && - // mainElevation == southEastElevation && - // mainElevation == southWestElevation) - // { - // /** We can just use 1 quad for this tile **/ - // var fullTL = new Vector3((x * spanX) - BorderWidth, -(y * spanY) - BorderHeight, mainElevation); - // var fullTR = new Vector3(fullTL.X + spanX, fullTL.Y, mainElevation); - // var fullBL = new Vector3(fullTL.X, fullTL.Y - spanY, mainElevation); - // var fullBR = new Vector3(fullTL.X + spanX, fullTL.Y - spanY, mainElevation); - // textureMap.Position(x, y, fullTL, fullBR); - // vertexList.Add(new TerrainVertex(fullTL, textureMap.MapTerrain(fullTL), vertexColor, tex, tex)); //0 - // vertexList.Add(new TerrainVertex(fullTR, textureMap.MapTerrain(fullTR), vertexColor, tex, tex)); //1 - // vertexList.Add(new TerrainVertex(fullBR, textureMap.MapTerrain(fullBR), vertexColor, tex, tex)); //2 - // vertexList.Add(new TerrainVertex(fullBL, textureMap.MapTerrain(fullBL), vertexColor, tex, tex)); //3 - // indexList.Add(startIndex); - // indexList.Add(startIndex + 1); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 3); - // indexList.Add(startIndex); - - // continue; - // } - - - - - // var mainTL = new Vector3(x * spanX, -(y * spanY), mainElevation); - // var mainTR = new Vector3(mainTL.X + CellWidth, mainTL.Y, mainElevation); - // var mainBL = new Vector3(mainTL.X, mainTL.Y - CellHeight, mainElevation); - // var mainBR = new Vector3(mainTL.X + CellWidth, mainTL.Y - CellHeight, mainElevation); - - - - - // /** West elevation **/ - // var westElevationMid = (westElevation + mainElevation) / 2; - // var borderTL_BL = new Vector3(mainTL.X - BorderWidth, mainTL.Y, westElevationMid); - // var borderBL_TL = new Vector3(mainTL.X - BorderWidth, mainBL.Y, westElevationMid); - - // /** East elevation **/ - // var eastElevationMid = (eastElevation + mainElevation) / 2; - // var borderTR_BR = new Vector3(mainTR.X + BorderWidth, mainTR.Y, eastElevationMid); - // var borderBR_TR = new Vector3(mainBR.X + BorderWidth, mainBR.Y, eastElevationMid); - - // /** North elevation **/ - // var northElevationMid = (northElevation + mainElevation) / 2; - // var borderTL_TR = new Vector3(mainTL.X, mainTL.Y + BorderHeight, northElevationMid); - // var borderTR_TL = new Vector3(mainTR.X, mainTR.Y + BorderHeight, northElevationMid); - - // /** South elevation **/ - // var southElevationMid = (southElevation + mainElevation) / 2; - // var borderBL_BR = new Vector3(mainBL.X, mainBL.Y - BorderHeight, southElevationMid); - // var borderBR_BL = new Vector3(mainBR.X, mainBR.Y - BorderHeight, southElevationMid); - - // var northWestElevationMid = (northWestElevation + northElevation + westElevation + mainElevation) / 4; - // var borderTL_TL = new Vector3(mainTL.X - BorderWidth, mainTL.Y + BorderHeight, northWestElevationMid); - - // var northEastElevationMid = (northEastElevation + northElevation + eastElevation + mainElevation) / 4; - // var borderTR_TR = new Vector3(mainTR.X + BorderWidth, mainTR.Y + BorderHeight, northEastElevationMid); - - // var southEastElevationMid = (southEastElevation + eastElevation + southElevation + mainElevation) / 4; - // var borderBR_BR = new Vector3(mainBR.X + BorderWidth, mainBR.Y - BorderHeight, southEastElevationMid); - - // var southWestElevationMid = (southWestElevation + southElevation + westElevation + mainElevation) / 4; - // var borderBL_BL = new Vector3(mainBL.X - BorderWidth, mainBL.Y - BorderHeight, southWestElevationMid); - - - - // textureMap.Position(x, y, borderTL_TL, borderBR_BR); - - - // vertexList.Add(new TerrainVertex(mainTL, textureMap.MapTerrain(mainTL), vertexColor, tex, tex)); //0 - // vertexList.Add(new TerrainVertex(mainTR, textureMap.MapTerrain(mainTR), vertexColor, tex, tex)); //1 - // vertexList.Add(new TerrainVertex(mainBR, textureMap.MapTerrain(mainBR), vertexColor, tex, tex)); //2 - // vertexList.Add(new TerrainVertex(mainBL, textureMap.MapTerrain(mainBL), vertexColor, tex, tex)); //3 - // vertexList.Add(new TerrainVertex(borderTL_BL, textureMap.MapTerrain(borderTL_BL), vertexColor, tex, tex)); //4 - // vertexList.Add(new TerrainVertex(borderBL_TL, textureMap.MapTerrain(borderBL_TL), vertexColor, tex, tex)); //5 - // vertexList.Add(new TerrainVertex(borderTR_BR, textureMap.MapTerrain(borderTR_BR), vertexColor, tex, tex)); //6 - // vertexList.Add(new TerrainVertex(borderBR_TR, textureMap.MapTerrain(borderBR_TR), vertexColor, tex, tex)); //7 - // vertexList.Add(new TerrainVertex(borderTL_TR, textureMap.MapTerrain(borderTL_TR), vertexColor, tex, tex)); //8 - // vertexList.Add(new TerrainVertex(borderTR_TL, textureMap.MapTerrain(borderTR_TL), vertexColor, tex, tex)); //9 - // vertexList.Add(new TerrainVertex(borderBL_BR, textureMap.MapTerrain(borderBL_BR), vertexColor, tex, tex)); //10 - // vertexList.Add(new TerrainVertex(borderBR_BL, textureMap.MapTerrain(borderBR_BL), vertexColor, tex, tex)); //11 - // vertexList.Add(new TerrainVertex(borderTL_TL, textureMap.MapTerrain(borderTL_TL), vertexColor, tex, tex)); //12 - // vertexList.Add(new TerrainVertex(borderTR_TR, textureMap.MapTerrain(borderTR_TR), vertexColor, tex, tex)); //13 - // vertexList.Add(new TerrainVertex(borderBR_BR, textureMap.MapTerrain(borderBR_BR), vertexColor, tex, tex)); //14 - // vertexList.Add(new TerrainVertex(borderBL_BL, textureMap.MapTerrain(borderBL_BL), vertexColor, tex, tex)); //15 - - - // /** Main tile **/ - // indexList.Add(startIndex); - // indexList.Add(startIndex + 1); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 3); - // indexList.Add(startIndex); - - // if (y > 0) - // { - // /** Top flap **/ - // indexList.Add(startIndex + 8); - // indexList.Add(startIndex + 9); - // indexList.Add(startIndex + 1); - - // indexList.Add(startIndex + 1); - // indexList.Add(startIndex + 0); - // indexList.Add(startIndex + 8); - // } - - // if (y < Height - 1) - // { - // /** Bottom flap **/ - // indexList.Add(startIndex + 3); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 11); - - // indexList.Add(startIndex + 11); - // indexList.Add(startIndex + 10); - // indexList.Add(startIndex + 3); - - // if (x > 0) - // { - // /** Bottom left corner **/ - // indexList.Add(startIndex + 5); - // indexList.Add(startIndex + 3); - // indexList.Add(startIndex + 10); - - // indexList.Add(startIndex + 10); - // indexList.Add(startIndex + 15); - // indexList.Add(startIndex + 5); - // } - // if (x < Width - 1) - // { - // /** Bottom right corner **/ - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 7); - // indexList.Add(startIndex + 14); - - // indexList.Add(startIndex + 14); - // indexList.Add(startIndex + 11); - // indexList.Add(startIndex + 2); - // } - // } - - // if (x > 0) - // { - // /** Left flap **/ - // indexList.Add(startIndex + 4); - // indexList.Add(startIndex); - // indexList.Add(startIndex + 3); - - // indexList.Add(startIndex + 3); - // indexList.Add(startIndex + 5); - // indexList.Add(startIndex + 4); - - // if (y > 0) - // { - // /** Top left corner **/ - // indexList.Add(startIndex + 12); - // indexList.Add(startIndex + 8); //tl_bl - // indexList.Add(startIndex); //tl_tr - - // indexList.Add(startIndex); - // indexList.Add(startIndex + 4); - // indexList.Add(startIndex + 12); - // } - // } - // if (x < Width - 1) - // { - // /** Right flap **/ - // indexList.Add(startIndex + 1); - // indexList.Add(startIndex + 6); - // indexList.Add(startIndex + 7); - - // indexList.Add(startIndex + 7); - // indexList.Add(startIndex + 2); - // indexList.Add(startIndex + 1); - - // if (y > 0) - // { - // /** Top right corner **/ - // indexList.Add(startIndex + 9); - // indexList.Add(startIndex + 13); - // indexList.Add(startIndex + 6); - - // indexList.Add(startIndex + 6); - // indexList.Add(startIndex + 1); - // indexList.Add(startIndex + 9); - // } - // } - - // } - //} - - - //Vertices = vertexList.ToArray(); - //Indexes = indexList.ToArray(); - //PrimitiveCount = Indexes.Length / 3; - - //System.Diagnostics.Debug.WriteLine("Took : " + (DateTime.Now.Ticks - now) + " ticks"); - } - - /// - /// Store the vertices in a vertex buffer - /// - /// - public void CreateBuffer(GraphicsDevice gd) - { - VertexBuffer = new VertexBuffer(gd, TerrainVertex.SizeInBytes * Vertices.Length, BufferUsage.WriteOnly); - VertexBuffer.SetData(Vertices); - - IndexBuffer = new IndexBuffer(gd, typeof(int), Indexes.Length, BufferUsage.WriteOnly); - IndexBuffer.SetData(Indexes); - } - - - public void Draw(GraphicsDevice gd) - { - gd.Vertices[0].SetSource(VertexBuffer, 0, TerrainVertex.SizeInBytes); - gd.VertexDeclaration = new VertexDeclaration(gd, TerrainVertex.VertexElements); - gd.Indices = IndexBuffer; - gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Vertices.Length, 0, PrimitiveCount); - } - - #region IDisposable Members - - /// - /// Cleans up the various objects used by the geom object - /// - public void Dispose() - { - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/AbstractCityPlugin.cs b/TSOClient/tso.client/Rendering/City/Plugins/AbstractCityPlugin.cs index cf8680489..1021642bd 100644 --- a/TSOClient/tso.client/Rendering/City/Plugins/AbstractCityPlugin.cs +++ b/TSOClient/tso.client/Rendering/City/Plugins/AbstractCityPlugin.cs @@ -8,7 +8,7 @@ public abstract class AbstractCityPlugin { public bool ForceNear { get; protected set; } - protected Terrain City; + public Terrain City { get; protected set; } public AbstractCityPlugin(Terrain city) { City = city; diff --git a/TSOClient/tso.client/Rendering/City/Plugins/MapPainterPlugin.cs b/TSOClient/tso.client/Rendering/City/Plugins/MapPainterPlugin.cs index 405faaf3d..426a6dac1 100644 --- a/TSOClient/tso.client/Rendering/City/Plugins/MapPainterPlugin.cs +++ b/TSOClient/tso.client/Rendering/City/Plugins/MapPainterPlugin.cs @@ -1,520 +1,233 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using FSO.Common.Rendering.Framework.Model; +using FSO.Client.Controllers; +using FSO.Client.Rendering.City.Plugins.PainterModes; using FSO.Client.UI.Framework; -using FSO.Common.Utils; -using Microsoft.Xna.Framework.Input; +using FSO.Client.UI.Model; using FSO.Common; -using System.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Content.Model; using FSO.Files; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; namespace FSO.Client.Rendering.City.Plugins { public class MapPainterPlugin : AbstractCityPlugin { - private static Point[] WLStartOff = { - - // Look at this way up <---- - // Starting at % line, going cw. Middle is (0,0), and below it is the tile (0,0).. - // - // /\ - // / \ +x - // /\ %\ - // / \% \ - // \ /\ / - // \/ \/ - // \ / +y - // \/ - - new Point(0, 0), - new Point(0, 0), - new Point(-1, 0), - new Point(0, -1), - }; - - private static RoadSegs[] WLMainSeg = - { - RoadSegs.TopRight, - RoadSegs.TopLeft, - RoadSegs.TopRight, - RoadSegs.TopLeft, - }; - - private static Point[] WLSubOff = - { - new Point(0, -1), - new Point(-1, 0), - new Point(0, -1), - new Point(-1, 0), - }; - - private static RoadSegs[] WLSubSeg = - { - RoadSegs.BottomLeft, - RoadSegs.BottomRight, - RoadSegs.BottomLeft, - RoadSegs.BottomRight, - }; - - - private static Point[] WLStep = - { - new Point(1, 0), - new Point(0, 1), - new Point(-1, 0), - new Point(0, -1), - }; - - private static RoadSegs[] MainCorner = - { - RoadSegs.Right, - RoadSegs.Left, - RoadSegs.Bottom, - RoadSegs.Bottom, - }; - private static RoadSegs[] SubCorner = - { - RoadSegs.Top, - RoadSegs.Top, - RoadSegs.Left, - RoadSegs.Right, - }; - private static RoadSegs[] MainEndCorner = - { - RoadSegs.Bottom, - RoadSegs.Bottom, - RoadSegs.Right, - RoadSegs.Left, - }; - private static RoadSegs[] SubEndCorner = - { - RoadSegs.Left, - RoadSegs.Right, - RoadSegs.Top, - RoadSegs.Top, - }; + private const float TooltipSeconds = 2; + private static int ClientCommandID; - private static Dictionary CornerRemovalEdges = new Dictionary - { - { RoadSegs.Bottom, RoadSegs.TopLeft | RoadSegs.TopRight }, //i likely have the names for these wrong... - { RoadSegs.Left, RoadSegs.TopLeft | RoadSegs.BottomLeft }, - { RoadSegs.Top, RoadSegs.BottomLeft | RoadSegs.BottomRight }, - { RoadSegs.Right, RoadSegs.BottomRight | RoadSegs.TopRight } - }; + private TerrainController Controller; public Vector2 LastPos; - public Point WallBase; - public Point WallTarget; - private int WallLength; - private int WallDir; - private bool Erasing; - private bool Accelerate; - public byte[] OriginalData; - - private bool MouseDown; - private bool MouseClicked; - private int MouseFloatTimer; - - public Color[] TerrainTypes = new Color[] { - new Color(0, 255, 0), //grass - new Color(12, 0, 255), //water - new Color(255, 0, 0), //rock - new Color(255, 255, 255), //snow - new Color(255, 255, 0) //sand - }; - - public byte[] TerrainTypeIndices = new byte[] { - 0, //grass - 4, //water - 2, //rock - 3, //snow - 1 //sand - }; - - public string[] TerrainTypeNames = new string[] { + + public bool Erasing => ErasingModifier != ErasingToggle; + public bool Accelerate { get; private set; } + + public Color[] TerrainTypes = [ + new(0, 255, 0), //grass + new(12, 0, 255), //water + new(255, 0, 0), //rock + new(255, 255, 255), //snow + new(255, 255, 0) //sand + ]; + + public TerrainType[] TerrainTypeIndices = [ + TerrainType.GRASS, + TerrainType.WATER, + TerrainType.ROCK, + TerrainType.SNOW, + TerrainType.SAND, + ]; + + public string[] TerrainTypeNames = [ "Grass", "Water", "Rock", "Snow", "Sand" - }; + ]; - public byte[] ForestDensities = new byte[] { + public byte[] ForestDensities = [ 0, 64, 128, 192, 255 - }; - - public Color[] ForestTypes = new Color[] { - new Color(0, 0x6A, 0x28), - new Color(0, 0xEB, 0x42), - new Color(255, 0, 0), - new Color(255, 0xFC, 0), - new Color(0, 0, 0), - }; + ]; + + public ForestType[] ForestTypeIndices = [ + ForestType.HEAVY, + ForestType.LIGHT, + ForestType.CACTI, + ForestType.PALM, + ForestType.NULL, + ]; + + public Color[] ForestTypes = [ + new(0, 0x6A, 0x28), + new(0, 0xEB, 0x42), + new(255, 0, 0), + new(255, 0xFC, 0), + new(0, 0, 0), + ]; + + public Color[] ForestDensityColors; + public int SelectedModifier; public int BrushSize; public PainterMode Mode; - private Rectangle? ChangeBounds; - private Dictionary ElevationMod; - private int ElevationFrames = 0; + public bool SprayBrush { get; set; } + public bool AutoTerrain { get; set; } = true; + public bool RoughTerrain { get; set; } + public bool LockProperties { get; set; } = true; + public bool Flatten { get; set; } + public float BrushIntensity { get; set; } + public bool ErasingModifier { get; private set; } + public bool ErasingToggle { get; set; } + + private IMapPainterMode Tool; + + private string Tooltip; + private float TooltipTimer; public MapPainterPlugin(Terrain city) : base(city) { + ForestDensityColors = [.. ForestDensities.Select(x => new Color(x, x, x, (byte)255))]; ForceNear = true; + + Controller = City.FindController(); + + SwitchMode(PainterMode.ROAD); } - private void AddChange(Point pos) + public override void Draw(SpriteBatch sb) { - var rect = new Rectangle(pos, new Point(1, 1)); - AddChange(rect); + sb.Begin(); + + Tool?.Draw(sb); + + sb.End(); } - private void AddChange(Rectangle rect) + public override void TileHover(Vector2? tile) { - if (ChangeBounds == null) ChangeBounds = rect; - else - { - ChangeBounds = Rectangle.Union(ChangeBounds.Value, rect); - } + Tool?.TileHover(tile); + + if (tile != null) LastPos = tile.Value; } - public override void Draw(SpriteBatch sb) + public override void TileMouseDown(Vector2 tile) { - TextStyle.DefaultLabel.VFont.Draw(sb.GraphicsDevice, Mode.ToString(), new Vector2(10, 10), Color.White, new Vector2(TextStyle.DefaultLabel.Scale), null); + Tool?.TileMouseDown(tile); + } - sb.Begin(); - var ePos = new Point((int)Math.Round(LastPos.X), (int)Math.Round(LastPos.Y)); + public override void TileMouseUp(Vector2? tile) + { + Tool?.TileMouseUp(tile); + } - switch (Mode) + private void UpdateReserved(CityEditBase cmd) + { + if (cmd.ReservedLocations == null) { - case PainterMode.ROAD: - if (MouseDown) - { - var onScreen2 = City.Get2DFromTile(WallBase.X, WallBase.Y); - City.DrawLine(TextureGenerator.GetPxWhite(sb.GraphicsDevice), onScreen2, onScreen2 + new Vector2(0, -50), sb, 5, 100); - } - - var wallPos = new Point((int)Math.Round(LastPos.X), (int)Math.Round(LastPos.Y)); - var onScreen = City.Get2DFromTile(wallPos.X, wallPos.Y); - City.DrawLine(TextureGenerator.GetPxWhite(sb.GraphicsDevice), onScreen, onScreen + new Vector2(0, -30), sb, 3, 100); - break; - case PainterMode.TERRAINTYPE: - case PainterMode.FORESTDENSITY: - case PainterMode.FORESTTYPE: - float iScale = (float)(1 / (City.GetIsoScale() * 2)); - - Color selColor = Color.White; - - switch (Mode) - { - case PainterMode.TERRAINTYPE: - selColor = TerrainTypes[SelectedModifier]; break; - case PainterMode.FORESTDENSITY: - var intensity = ForestDensities[SelectedModifier]; - selColor = new Color(intensity, intensity, intensity); break; - case PainterMode.FORESTTYPE: - selColor = ForestTypes[SelectedModifier]; break; - } + cmd.ReservedLocations = []; + } - BrushFunc(BrushSize, (x, y, strength) => - { - if (strength > 0) City.PathTile((int)LastPos.X + x, (int)LastPos.Y + y, iScale, new Color(selColor, 0.5f)); - }); - City.Draw2DPoly(false); - break; - case PainterMode.ELEVATION_CIRCLE: - - BrushFunc(BrushSize, (x, y, strength) => - { - //if (strength <= 0) return; - var multiplier = (Accelerate) ? 2 : 1; - var eOnScreen = City.Get2DFromTile(ePos.X + x, ePos.Y + y); - City.DrawLine(TextureGenerator.GetPxWhite(sb.GraphicsDevice), eOnScreen, eOnScreen + new Vector2(0, -50) * strength * multiplier, sb, 3, 100); - }); - break; - - case PainterMode.ELEVATION_FLAT: - var elevations = new List(); - BrushFunc(BrushSize, (x, y, strength) => - { - var index = ePos.X + x + (ePos.Y + y) * 512; - if (index < 0 || index > City.MapData.ElevationData.Length) return; - elevations.Add(City.MapData.ElevationData[index]); - }); + var reserved = cmd.ReservedLocations; - var sorted = elevations.OrderBy(x => x).ToList(); - var elevation = sorted[sorted.Count / 2]; //median + reserved.Clear(); - BrushFunc(BrushSize, (x, y, strength) => - { - if (strength > 0) - { - var multiplier = (Accelerate) ? 2 : 1; - var index = ePos.X + x + (ePos.Y + y) * 512; - if (index < 0 || index > City.MapData.ElevationData.Length) return; - var elev = City.MapData.ElevationData[index]; + var tiles = City.LotTiles; - var change = (elevation - elev) / 50f; - if (change > 0) change = Math.Max(0.02f, change); - else change = Math.Min(-0.02f, change); + tiles.AddOpenLotSurroundingsTo(reserved); - var eOnScreen = City.Get2DFromTile(ePos.X + x, ePos.Y + y); - City.DrawLine(TextureGenerator.GetPxWhite(sb.GraphicsDevice), eOnScreen, eOnScreen + new Vector2(0, -50) * change * multiplier, sb, 3, 100); - } - }); - break; + if (LockProperties) + { + tiles.AddLocationsTo(reserved); } - - sb.End(); } - public override void TileHover(Vector2? tile) + public void Commit(bool hasChange) { - if (tile != null && MouseDown) { - var wallPos = new Point((int)Math.Round(tile.Value.X), (int)Math.Round(tile.Value.Y)); - var newPt = tile.Value.ToPoint(); - switch (Mode) + if (hasChange) + { + var cmd = Tool?.Command; + + if (cmd != null) { - case PainterMode.ROAD: - if (wallPos != WallTarget && OriginalData != null) - { - WallTarget = wallPos; - var xd = (WallTarget.X - WallBase.X); - var yd = (WallTarget.Y - WallBase.Y); - WallLength = (int)Math.Sqrt(xd * xd + yd * yd); - WallDir = (int)DirectionUtils.PosMod(Math.Round(Math.Atan2(yd, xd) / (Math.PI / 2)), 4); - - Array.Copy(OriginalData, City.MapData.RoadData, OriginalData.Length); - - try - { - if (Erasing) EraseWall(City.MapData.RoadData, WallBase, WallLength, WallDir); - else DrawWall(City.MapData.RoadData, WallBase, WallLength, WallDir); - } - catch (IndexOutOfRangeException) - { - Array.Copy(OriginalData, City.MapData.RoadData, OriginalData.Length); - } - - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - } - break; - case PainterMode.TERRAINTYPE: - if (MouseClicked || newPt != LastPos.ToPoint()) - { - BrushFunc(BrushSize, (x, y, strength) => - { - int targetX = newPt.X + x; - int targetY = newPt.Y + y; - - if (targetX < 0 || targetX >= 512 || targetY < 0 || targetY >= 512) - { - return; - } - - if (strength > 0) - { - City.MapData.TerrainTypeColorData[targetX + targetY * 512] = TerrainTypes[SelectedModifier]; - City.MapData.TerrainType[targetX + targetY * 512] = TerrainTypeIndices[SelectedModifier]; - } - }); - - AddChange(new Rectangle(newPt.X - (1+BrushSize), newPt.Y - (1+BrushSize), 3+BrushSize*2, 3+BrushSize*2)); - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - MouseClicked = false; - break; - } - break; - case PainterMode.ELEVATION_CIRCLE: - if (OriginalData == null) return; - - BrushFunc(BrushSize, (x, y, strength) => - { - var multiplier = (Accelerate) ? 2 : 1; - if (strength > 0) { - var loc = new Point(wallPos.X + x, wallPos.Y + y); - if (ElevationMod.ContainsKey(loc)) ElevationMod[loc] += ((Erasing)?-1:1)* strength * multiplier / 5; - else ElevationMod[loc] = ((Erasing) ? -1 : 1) * strength * multiplier / 5; - } - }); - AddChange(new Rectangle(wallPos.X - (1 + BrushSize), wallPos.Y - (1 + BrushSize), 2 + BrushSize * 2, 2 + BrushSize * 2)); - break; - case PainterMode.ELEVATION_FLAT: - if (OriginalData == null) return; - var elevations = new List(); - BrushFunc(BrushSize, (x, y, strength) => - { - var index = wallPos.X + x + (wallPos.Y + y) * 512; - if (index < 0 || index > City.MapData.ElevationData.Length) return; - elevations.Add(City.MapData.ElevationData[index]); - }); + cmd.UserModId = ClientCommandID; - var sorted = elevations.OrderBy(x => x).ToList(); - var elevation = sorted[sorted.Count / 2]; //median + UpdateReserved(cmd); + if (Controller.UpdateTempMapChange(cmd)) + { + Controller.CommitMapChange(cmd); + } - BrushFunc(BrushSize, (x, y, strength) => - { - var multiplier = (Accelerate) ? 2 : 1; - if (strength > 0) - { - var index = wallPos.X + x + (wallPos.Y + y) * 512; - if (index < 0 || index > City.MapData.ElevationData.Length) return; - var elev = City.MapData.ElevationData[index]; - - var loc = new Point(wallPos.X + x, wallPos.Y + y); - var change = (elevation - elev) / 50f * multiplier; - if (change > 0) change = Math.Max(0.02f, change); - else change = Math.Min(-0.02f, change); - - if (ElevationMod.ContainsKey(loc)) ElevationMod[loc] += change; - else ElevationMod[loc] = change; - } - }); - - AddChange(new Rectangle(wallPos.X - (1 + BrushSize), wallPos.Y - (1 + BrushSize), 2 + BrushSize * 2, 2 + BrushSize * 2)); - break; - case PainterMode.FORESTDENSITY: - if (MouseClicked || newPt != LastPos.ToPoint()) - { - BrushFunc(BrushSize, (x, y, strength) => - { - int targetX = newPt.X + x; - int targetY = newPt.Y + y; - - if (targetX < 0 || targetX >= 512 || targetY < 0 || targetY >= 512) - { - return; - } - - if (strength > 0) City.MapData.ForestDensityData[targetX + targetY * 512] = ForestDensities[SelectedModifier]; - }); - - AddChange(new Rectangle(newPt.X - (1 + BrushSize), newPt.Y - (1 + BrushSize), 3 + BrushSize * 2, 3 + BrushSize * 2)); - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - MouseClicked = false; - break; - } - break; - case PainterMode.FORESTTYPE: - if (MouseClicked || newPt != LastPos.ToPoint()) - { - BrushFunc(BrushSize, (x, y, strength) => - { - int targetX = newPt.X + x; - int targetY = newPt.Y + y; - - if (targetX < 0 || targetX >= 512 || targetY < 0 || targetY >= 512) - { - return; - } - - if (strength > 0) City.MapData.ForestTypeData[targetX + targetY * 512] = ForestTypes[SelectedModifier]; - }); - - AddChange(new Rectangle(newPt.X - (1 + BrushSize), newPt.Y - (1 + BrushSize), 3 + BrushSize * 2, 3 + BrushSize * 2)); - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - MouseClicked = false; - break; - } - break; + ClientCommandID++; } } + else + { + // Only clears the temp command. - if (tile != null) LastPos = tile.Value; + Controller.UpdateTempMapChange(null); + } } - public override void TileMouseDown(Vector2 tile) + public void UpdateTemp() { - ChangeBounds = null; - var wallPos = new Point((int)Math.Round(tile.X), (int)Math.Round(tile.Y)); - switch (Mode) - { - case PainterMode.ROAD: - OriginalData = new byte[City.MapData.RoadData.Length]; - Array.Copy(City.MapData.RoadData, OriginalData, OriginalData.Length); - - WallBase = wallPos; - WallTarget = wallPos; - WallLength = 0; - WallDir = 0; - break; - case PainterMode.ELEVATION_CIRCLE: - case PainterMode.ELEVATION_FLAT: - OriginalData = new byte[City.MapData.ElevationData.Length]; - Array.Copy(City.MapData.ElevationData, OriginalData, OriginalData.Length); - - ElevationMod = new Dictionary(); - ElevationFrames = 0; - break; - } + var cmd = Tool?.Command; - MouseDown = true; - MouseClicked = true; - MouseFloatTimer = 0; - } + if (cmd != null) + { + cmd.UserModId = ClientCommandID; + UpdateReserved(cmd); - public override void TileMouseUp(Vector2? tile) - { - switch (Mode) + Controller.UpdateTempMapChange(cmd); + } + else { - case PainterMode.ROAD: - if (WallLength != 0) - { - ChangeBounds = null; - } - else - { - RestoreOld(); - } - break; - case PainterMode.ELEVATION_CIRCLE: - case PainterMode.ELEVATION_FLAT: - ChangeBounds = null; - break; + Controller.UpdateTempMapChange(null); } - MouseDown = false; } public void SwitchMode(PainterMode newMode) { if (Mode != newMode) TileMouseUp(null); + + Tool = newMode switch + { + PainterMode.ROAD => new MapPainterRoad(this), + PainterMode.ELEVATION_CIRCLE => new MapPainterElevationCircle(this), + PainterMode.ELEVATION_FLAT => new MapPainterElevationFlat(this), + PainterMode.TERRAINTYPE => new MapPainterPaint(this, TerrainTypes, TerrainTypeIndices, CityEditPaintType.TerrainType), + PainterMode.FORESTTYPE => new MapPainterPaint(this, ForestTypes, ForestTypeIndices, CityEditPaintType.ForestType), + PainterMode.FORESTDENSITY => new MapPainterPaint(this, ForestDensityColors, ForestDensities, CityEditPaintType.ForestDensity), + PainterMode.FOREST => new MapPainterForest(this, ForestTypes, ForestTypeIndices, CityEditPaintType.ForestType), + _ => null + }; + Mode = newMode; - OriginalData = null; } public override void Update(UpdateState state) { - if ((Mode == PainterMode.ELEVATION_CIRCLE || Mode == PainterMode.ELEVATION_FLAT) && MouseDown && ChangeBounds != null && ElevationFrames-- <= 0) - { - Array.Copy(OriginalData, City.MapData.ElevationData, OriginalData.Length); - foreach (var mod in ElevationMod) - { - var index = mod.Key.X + mod.Key.Y * 512; - if (index < 0 || index > City.MapData.ElevationData.Length) continue; - City.MapData.ElevationData[index] = (byte)Math.Max(0, Math.Min(255, Math.Round(City.MapData.ElevationData[index]+mod.Value))); - } - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - ElevationFrames = 5; - } + Tool?.Update(state); var pressed = state.NewKeys; - Erasing = state.CtrlDown; + ErasingModifier = state.CtrlDown; Accelerate = state.ShiftDown; - + ///* for (int i = 2; i<11; i++) { Keys key; if (Enum.TryParse("F"+i, out key)) { - var dir = Path.Combine(FSOEnvironment.UserDir, "CityPainterSave" + i + "/"); + var dir = Path.Combine(Common.FSOEnvironment.UserDir, "CityPainterSave" + i + "/"); if (pressed.Contains(key)) { if (Accelerate) @@ -524,7 +237,7 @@ public override void Update(UpdateState state) } else if (Directory.Exists(dir)) { - City.MapData.Load(dir, LoadTex, "png"); + //City.MapData.Load(dir, LoadTex, "png"); City.GenerateCityMesh(GameFacade.GraphicsDevice, null); //UIScreen.GlobalShowAlert(new UI.Controls.UIAlertOptions { Title = "Load Success", Message = "Loaded city data " + i + "." }, true); } @@ -535,33 +248,14 @@ public override void Update(UpdateState state) } } } + //*/ - if (state.MouseState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Released) + if (TooltipTimer > 0) { - if (MouseFloatTimer++ >= 1) - { - if (MouseDown) RestoreOld(); - MouseDown = false; - MouseFloatTimer = 0; - } + state.UIState.SetTooltip(state, Tooltip); + + TooltipTimer -= 1f / FSOEnvironment.RefreshRate; } - var keys = state.KeyboardState; - if (keys.IsKeyDown(Keys.R)) SwitchMode(PainterMode.ROAD); - else if (keys.IsKeyDown(Keys.T)) SwitchMode(PainterMode.TERRAINTYPE); - else if (keys.IsKeyDown(Keys.E)) SwitchMode(PainterMode.ELEVATION_CIRCLE); - else if (keys.IsKeyDown(Keys.F)) SwitchMode(PainterMode.ELEVATION_FLAT); - else if (keys.IsKeyDown(Keys.C)) SwitchMode(PainterMode.FORESTTYPE); - else if (keys.IsKeyDown(Keys.D)) SwitchMode(PainterMode.FORESTDENSITY); - - var oldS = SelectedModifier; - if (keys.IsKeyDown(Keys.NumPad0)) SelectedModifier = 0; - if (keys.IsKeyDown(Keys.NumPad1)) SelectedModifier = 1; - if (keys.IsKeyDown(Keys.NumPad2)) SelectedModifier = 2; - if (keys.IsKeyDown(Keys.NumPad3)) SelectedModifier = 3; - if (keys.IsKeyDown(Keys.NumPad4)) SelectedModifier = 4; - - if (pressed.Contains(Keys.Up)) BrushSize += 1; - if (pressed.Contains(Keys.Down)) BrushSize = Math.Max(0, BrushSize - 1); } private Texture2D LoadTex(string Path) @@ -585,104 +279,12 @@ private Texture2D LoadTex(Stream stream) return result; } - public void BrushFunc(int width, Callback callback) - { - var boxWidth = width * 2 + 1; - for (int y = 0; y < boxWidth; y++) - { - for (int x = 0; x < boxWidth; x++) - { - var dist = Math.Sqrt((x-width)*(x-width) + (y-width)*(y-width)) / (width + 0.5); - int targetX = x - width; - int targetY = y - width; - - callback(targetX, targetY, (float)Math.Max(0, Math.Cos(dist * Math.PI / 2))); - } - } - } - - public void RestoreOld() - { - if (OriginalData != null) - { - if (Mode == PainterMode.ROAD) City.MapData.RoadData = OriginalData; - else if (Mode == PainterMode.ELEVATION_CIRCLE || Mode == PainterMode.ELEVATION_FLAT) City.MapData.ElevationData = OriginalData; - } - OriginalData = null; - City.GenerateCityMesh(GameFacade.GraphicsDevice, ChangeBounds); - ChangeBounds = null; - } - - public void TryAddCorner(byte[] map, int index, RoadSegs seg) - { - if ((map[index] & (byte)CornerRemovalEdges[seg]) == 0) - { - map[index] |= (byte)seg; - } - } - - public void DrawWall(byte[] map, Point pos, int length, int direction) - { - pos += WLStartOff[direction]; - - var endPos = pos - WLStep[direction]; - TryAddCorner(map, endPos.X + endPos.Y * 512, MainCorner[direction]); - AddChange(endPos); - - endPos += WLSubOff[direction]; - TryAddCorner(map, endPos.X + endPos.Y * 512, SubCorner[direction]); - AddChange(endPos); - - for (int i = 0; i < length; i++) - { - map[pos.X + pos.Y * 512] |= (byte)WLMainSeg[direction]; - map[pos.X + pos.Y * 512] &= (byte)(~MainCorner[direction]); - map[pos.X + pos.Y * 512] &= (byte)(~MainEndCorner[direction]); - AddChange(pos); - var tPos = pos + WLSubOff[direction]; - map[tPos.X + tPos.Y * 512] |= (byte)WLSubSeg[direction]; - map[tPos.X + tPos.Y * 512] &= (byte)(~SubCorner[direction]); - map[tPos.X + tPos.Y * 512] &= (byte)(~SubEndCorner[direction]); - AddChange(tPos); - pos += WLStep[direction]; - } - - endPos = pos; - AddChange(endPos); - TryAddCorner(map, endPos.X + endPos.Y * 512, MainEndCorner[direction]); - endPos += WLSubOff[direction]; - AddChange(endPos); - TryAddCorner(map, endPos.X + endPos.Y * 512, SubEndCorner[direction]); - } - - public void EraseWall(byte[] map, Point pos, int length, int direction) + public void ShowError(int id) { - pos += WLStartOff[direction]; - - var endPos = pos - WLStep[direction]; - map[endPos.X + endPos.Y * 512] &= (byte)(~MainCorner[direction]); - AddChange(endPos); - endPos += WLSubOff[direction]; - map[endPos.X + endPos.Y * 512] &= (byte)(~SubCorner[direction]); - AddChange(endPos); + HIT.HITVM.Get().PlaySoundEvent(UISounds.Error); - for (int i = 0; i < length; i++) - { - map[pos.X + pos.Y * 512] &= (byte)~WLMainSeg[direction]; - AddChange(pos); - var tPos = pos + WLSubOff[direction]; - map[tPos.X + tPos.Y * 512] &= (byte)~WLSubSeg[direction]; - AddChange(tPos); - - pos += WLStep[direction]; - } - - endPos = pos; - map[endPos.X + endPos.Y * 512] &= (byte)(~MainEndCorner[direction]); - AddChange(endPos); - endPos += WLSubOff[direction]; - map[endPos.X + endPos.Y * 512] &= (byte)(~SubEndCorner[direction]); - AddChange(endPos); + Tooltip = GameFacade.Strings.GetString("f130", id.ToString()); + TooltipTimer = TooltipSeconds; } } @@ -693,19 +295,7 @@ public enum PainterMode ELEVATION_CIRCLE, ELEVATION_FLAT, FORESTTYPE, - FORESTDENSITY - } - - public enum RoadSegs : byte - { - TopLeft = 1, - BottomLeft = 2, - BottomRight = 4, - TopRight = 8, - - Bottom = 16, - Left = 32, - Top = 64, - Right = 128 + FORESTDENSITY, + FOREST } } diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/IMapPainterMode.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/IMapPainterMode.cs new file mode 100644 index 000000000..39fc53c2d --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/IMapPainterMode.cs @@ -0,0 +1,39 @@ +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal interface IMapPainterMode + { + CityEditBase Command { get; } + + void TileHover(Vector2? tile); + + void TileMouseDown(Vector2 tile); + + void TileMouseUp(Vector2? tile); + + void Update(UpdateState state); + + void Draw(SpriteBatch sb); + + public static void BrushFunc(int width, Callback callback) + { + var boxWidth = width * 2 + 1; + for (int y = 0; y < boxWidth; y++) + { + for (int x = 0; x < boxWidth; x++) + { + var dist = Math.Sqrt((x - width) * (x - width) + (y - width) * (y - width)) / (width + 0.5); + int targetX = x - width; + int targetY = y - width; + + callback(targetX, targetY, (float)Math.Max(0, Math.Cos(dist * Math.PI / 2))); + } + } + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationCircle.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationCircle.cs new file mode 100644 index 000000000..942941227 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationCircle.cs @@ -0,0 +1,199 @@ +using FSO.Client.UI.Model; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterElevationCircle : IMapPainterMode + { + private readonly MapPainterPlugin Painter; + private Terrain City => Painter.City; + private Vector2 LastPos; + public CityEditBase Command => Painter.Flatten ? Flatten.Command : BuildCommand(); + + private Dictionary ElevationMod; + private int ElevationFrames = 0; + private bool MouseDown; + private readonly MapPainterElevationFlat Flatten; + private readonly MapPainterSpraypaint Spray; + + public MapPainterElevationCircle(MapPainterPlugin painter) + { + Painter = painter; + Flatten = new MapPainterElevationFlat(painter); + Spray = new MapPainterSpraypaint(); + Spray.NewSeed(); + } + + private static bool InBounds(Point loc) + { + return loc.X >= 0 && loc.Y >= 0 && loc.X < 512 && loc.Y < 512; + } + + public void Draw(SpriteBatch sb) + { + if (Painter.Flatten) + { + Flatten.Draw(sb); + return; + } + + var ePos = new Point((int)Math.Round(LastPos.X), (int)Math.Round(LastPos.Y)); + + var erasing = Painter.Erasing; + var rough = Painter.RoughTerrain; + float baseMul = erasing ? -1 : 1; + + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + var loc = new Point(ePos.X + x, ePos.Y + y); + + if (InBounds(loc)) + { + var multiplier = baseMul * (Painter.Accelerate ? 2f : 1f); + var eOnScreen = City.Get2DFromTile(ePos.X + x, ePos.Y + y); + + var color = erasing ? Color.Red : Color.White; + + float alpha = 0.2f + strength * 0.55f; + + if (rough) + { + strength = Spray.GetRoughEdge(loc.X * 512 + loc.Y, strength, Painter.BrushSize); + } + + City.DrawSpike(loc.ToVector2(), strength * multiplier * 1.5f, sb, 196, color * alpha); + + /* + City.DrawLine(TextureGenerator.GetPxWhite(sb.GraphicsDevice), eOnScreen, eOnScreen + new Vector2(0, -50) * strength * multiplier, sb, 3, 100); + */ + } + }); + } + + public void TileHover(Vector2? tile) + { + if (Painter.Flatten) + { + Flatten.TileHover(tile); + return; + } + + if (MouseDown) + { + var frameMul = 60f / FSOEnvironment.RefreshRate; + var wallPos = new Point((int)Math.Round(tile.Value.X), (int)Math.Round(tile.Value.Y)); + var size = Painter.BrushSize; + var multiplier = Painter.BrushIntensity * (Painter.Accelerate ? 4f : 2f); + var rough = Painter.RoughTerrain; + + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + if (strength > 0) + { + var loc = new Point(wallPos.X + x, wallPos.Y + y); + + if (rough && InBounds(loc)) + { + strength = Spray.GetRoughEdge(loc.X * 512 + loc.Y, strength, Painter.BrushSize); + } + + if (ElevationMod.ContainsKey(loc)) ElevationMod[loc] += ((Painter.Erasing) ? -1 : 1) * strength * multiplier * frameMul / 5; + else ElevationMod[loc] = ((Painter.Erasing) ? -1 : 1) * strength * multiplier * frameMul / 5; + } + }); + } + + if (tile != null) LastPos = tile.Value; + } + + public void TileMouseDown(Vector2 tile) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolDown); + if (Painter.Flatten) + { + Flatten.TileMouseDown(tile); + return; + } + + Spray.NewSeed(); + + ElevationMod = new Dictionary(); + ElevationFrames = 0; + MouseDown = true; + } + + public void TileMouseUp(Vector2? tile) + { + if (Painter.Flatten) + { + Flatten.TileMouseUp(tile); + return; + } + + if (ElevationMod != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolUp); + Painter.Commit(ElevationMod.Count != 0); + } + + ElevationMod = null; + MouseDown = false; + } + + public void Update(UpdateState state) + { + if (Painter.Flatten) + { + Flatten.Update(state); + return; + } + + var frameMul = FSOEnvironment.RefreshRate / 60f; + if (ElevationMod != null && ElevationFrames-- <= 0) + { + Painter.UpdateTemp(); + ElevationFrames = (int)(5 * frameMul); + } + } + + private CityEditAltitude BuildCommand() + { + if (ElevationMod == null) + { + return null; + } + + var alt = new CityEditAltitude(); + + int width = City.MapData.Width; + int height = City.MapData.Height; + + var bitmap = new CityEditBitmap(width, height); + short[] deltas = new short[width * height]; + + foreach (var mod in ElevationMod) + { + if (mod.Key.X < 0 || mod.Key.Y < 0 || mod.Key.X >= width || mod.Key.Y >= height) continue; + var index = mod.Key.X + mod.Key.Y * width; + bitmap.Set(mod.Key.X, mod.Key.Y); + deltas[index] = (short)Math.Clamp(Math.Round(mod.Value), short.MinValue, short.MaxValue); + } + + alt.AutoTerrainType = Painter.AutoTerrain; + alt.Bitmap = bitmap; + alt.AltitudeDeltas = deltas; + alt.Trim(); + + if (alt.Bitmap == null) + { + return null; + } + + return alt; + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationFlat.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationFlat.cs new file mode 100644 index 000000000..af5271231 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterElevationFlat.cs @@ -0,0 +1,178 @@ +using FSO.Client.UI.Model; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterElevationFlat : IMapPainterMode + { + private readonly MapPainterPlugin Painter; + private Terrain City => Painter.City; + private Vector2 LastPos; + public CityEditBase Command => BuildCommand(); + + private Dictionary ElevationMod; + private int ElevationFrames = 0; + + private bool MouseDown; + + public MapPainterElevationFlat(MapPainterPlugin painter) + { + Painter = painter; + } + + public void Draw(SpriteBatch sb) + { + var ePos = new Point((int)Math.Round(LastPos.X), (int)Math.Round(LastPos.Y)); + + var elevations = new List(); + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + var index = ePos.X + x + (ePos.Y + y) * 512; + if (index < 0 || index > City.MapData.ElevationData.Length) return; + elevations.Add(City.MapData.ElevationData[index]); + }); + + if (elevations.Count == 0) + { + return; + } + + var sorted = elevations.OrderBy(x => x).ToList(); + var elevation = sorted[sorted.Count / 2]; //median + var intensity = Painter.BrushIntensity; + var pxWhite = TextureGenerator.GetPxWhite(sb.GraphicsDevice); + + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + if (strength > 0) + { + var multiplier = (Painter.Accelerate) ? 2 : 1; + var index = ePos.X + x + (ePos.Y + y) * 512; + if (index < 0 || index > City.MapData.ElevationData.Length) return; + var elev = City.MapData.ElevationData[index]; + + var change = (elevation - elev) / 50f; + if (change > 0) change = Math.Max(0.02f, change); + else change = Math.Min(-0.02f, change); + + var alpha = (change < 0 ? 0.2f : 0.75f) * Math.Min(1.1f, 0.2f + intensity * 0.6f * multiplier); + City.DrawSpike(new Vector2(ePos.X + x, ePos.Y + y), change * 5, sb, 192, Color.White * alpha); + } + }); + } + + public void TileHover(Vector2? tile) + { + if (MouseDown) + { + var frameMul = 60f / FSOEnvironment.RefreshRate; + var wallPos = new Point((int)Math.Round(tile.Value.X), (int)Math.Round(tile.Value.Y)); + var size = Painter.BrushSize; + + var elevations = new List(); + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + var index = wallPos.X + x + (wallPos.Y + y) * 512; + if (index < 0 || index > City.MapData.ElevationData.Length) return; + elevations.Add(City.MapData.ElevationData[index]); + }); + + var sorted = elevations.OrderBy(x => x).ToList(); + var elevation = sorted[sorted.Count / 2]; //median + + var multiplier = Painter.BrushIntensity * (Painter.Accelerate ? 4f : 2f); + + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + if (strength > 0) + { + var index = wallPos.X + x + (wallPos.Y + y) * 512; + if (index < 0 || index > City.MapData.ElevationData.Length) return; + var elev = City.MapData.ElevationData[index]; + + var loc = new Point(wallPos.X + x, wallPos.Y + y); + var change = frameMul * (elevation - elev) / 50f * multiplier; + if (change > 0) change = Math.Max(0.02f, change); + else change = Math.Min(-0.02f, change); + + if (ElevationMod.ContainsKey(loc)) ElevationMod[loc] += change; + else ElevationMod[loc] = change; + } + }); + } + + if (tile != null) LastPos = tile.Value; + } + + public void TileMouseDown(Vector2 tile) + { + ElevationMod = new Dictionary(); + ElevationFrames = 0; + + MouseDown = true; + } + + public void TileMouseUp(Vector2? tile) + { + if (ElevationMod != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolUp); + Painter.Commit(ElevationMod.Count != 0); + } + + ElevationMod = null; + MouseDown = false; + } + + public void Update(UpdateState state) + { + var frameMul = FSOEnvironment.RefreshRate / 60f; + if (ElevationMod != null && ElevationFrames-- <= 0) + { + Painter.UpdateTemp(); + ElevationFrames = (int)(5 * frameMul); + } + } + + private CityEditAltitude BuildCommand() + { + if (ElevationMod == null) + { + return null; + } + + var alt = new CityEditAltitude(); + + int width = City.MapData.Width; + int height = City.MapData.Height; + + var bitmap = new CityEditBitmap(width, height); + short[] deltas = new short[width * height]; + + foreach (var mod in ElevationMod) + { + if (mod.Key.X < 0 || mod.Key.Y < 0 || mod.Key.X >= width || mod.Key.Y >= height) continue; + var index = mod.Key.X + mod.Key.Y * width; + bitmap.Set(mod.Key.X, mod.Key.Y); + deltas[index] = (short)Math.Clamp(Math.Round(mod.Value), short.MinValue, short.MaxValue); + } + + alt.AutoTerrainType = Painter.AutoTerrain; + alt.Bitmap = bitmap; + alt.AltitudeDeltas = deltas; + alt.Trim(); + + if (alt.Bitmap == null) + { + return null; + } + + return alt; + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterForest.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterForest.cs new file mode 100644 index 000000000..1ac508c57 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterForest.cs @@ -0,0 +1,218 @@ +using FSO.Client.UI.Model; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Runtime.InteropServices; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterForest : IMapPainterMode where T : unmanaged + { + private readonly float MaxIntensity = 4; + private readonly MapPainterPlugin Painter; + private readonly Color[] ModifierToColor; + private readonly T[] ModifierToValue; + private readonly CityEditPaintType Type; + + private Terrain City => Painter.City; + + private CityEditForest Forest; + public CityEditBase Command => Forest; + + private Vector2 LastPos; + private Dictionary ForestMod; + private readonly MapPainterSpraypaint Spray; + private int SprayFrames; + + private bool AnySet; + + public MapPainterForest(MapPainterPlugin painter, Color[] modifierToColor, T[] modifierToValue, CityEditPaintType type) + { + Painter = painter; + ModifierToColor = modifierToColor; + ModifierToValue = modifierToValue; + Type = type; + Spray = new MapPainterSpraypaint(); + Spray.NewSeed(); + } + + public void Draw(SpriteBatch sb) + { + float iScale = (float)(1 / (City.GetIsoScale() * 2)); + + Color selColor = Painter.SelectedModifier < 0 || Painter.SelectedModifier >= ModifierToColor.Length ? + Color.White : + ModifierToColor[Painter.SelectedModifier]; + + float intensity = Painter.BrushIntensity; + float multiplier = Painter.Accelerate ? 2 : 1; + var spray = Painter.SprayBrush; + + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + if (spray) + { + int ix = (int)LastPos.X + x; + int iy = (int)LastPos.Y + y; + if (ix >= 0 && iy >= 0 && ix < 512 && iy < 512) + { + var sprayIntensity = Spray.GetSpraypaint(iy * 512 + ix, strength); + City.PathTile(ix, iy, iScale, new Color(selColor, Math.Min(0.3f, sprayIntensity * (intensity + 0.2f) * multiplier * 0.3f))); + } + } + else + { + if (strength > 0) City.PathTile((int)LastPos.X + x, (int)LastPos.Y + y, iScale, new Color(selColor, 0.15f + 0.10f * intensity)); + } + }); + + City.Draw2DPoly(false); + } + + private void ResetSprayFrames() + { + var frameMul = FSOEnvironment.RefreshRate / 60f; + SprayFrames = (int)(5 * frameMul); + } + + private void EnrichCommand() + { + if (Forest != null) + { + int width = City.MapData.Width; + int height = City.MapData.Height; + + byte[] intensities = new byte[width * height]; + + foreach (var mod in ForestMod) + { + if (mod.Key.X < 0 || mod.Key.Y < 0 || mod.Key.X >= width || mod.Key.Y >= height) continue; + var index = mod.Key.X + mod.Key.Y * width; + // bitmap.Set(mod.Key.X, mod.Key.Y); + intensities[index] = (byte)Math.Clamp(Math.Round(mod.Value), 0, MaxIntensity); + } + + Forest.Intensities = intensities; + } + } + + private void Submit() + { + if (Forest != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolUp); + Forest.Trim(); + + Painter.Commit(AnySet); + + Forest = null; + } + } + + private void NewPaint() + { + Submit(); + Spray.NewSeed(); + + var valueAsByte = MemoryMarshal.Cast(ModifierToValue); + + Forest = new CityEditForest() + { + Erasing = Painter.Erasing, + ForestType = valueAsByte[Painter.SelectedModifier], + Bitmap = new CityEditBitmap(City.MapData.Width, City.MapData.Height) + }; + + AnySet = false; + ForestMod = []; + } + + private void ApplyBrush(Point newPt) + { + var frameMul = 60f / FSOEnvironment.RefreshRate; + var spray = Painter.SprayBrush; + var multiplier = Painter.Accelerate ? 2f : 1f; + var intensity = Painter.BrushIntensity * multiplier; + + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + int targetX = newPt.X + x; + int targetY = newPt.Y + y; + + if (targetX < 0 || targetX >= 512 || targetY < 0 || targetY >= 512) + { + return; + } + + if (strength > 0) + { + Forest.Bitmap.Set(targetX, targetY); + + var loc = new Point(targetX, targetY); + + if (spray) + { + var sprayIntensity = Spray.GetSpraypaint(targetY * 512 + targetX, strength); + + ForestMod.TryGetValue(loc, out float acc); + acc += sprayIntensity * frameMul * intensity * 0.25f; + ForestMod[loc] = acc; + } + else + { + ForestMod[loc] = intensity; + } + + AnySet = true; + } + }); + + if (!spray || --SprayFrames <= 0) + { + EnrichCommand(); + Painter.UpdateTemp(); + ResetSprayFrames(); + } + } + + public void TileHover(Vector2? tile) + { + if (Forest != null && tile != null) + { + var newPt = tile.Value.ToPoint(); + var spray = Painter.SprayBrush; + + if (spray || newPt != LastPos.ToPoint()) + { + ApplyBrush(newPt); + } + } + + if (tile != null) LastPos = tile.Value; + } + + public void TileMouseDown(Vector2 tile) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolDown); + + NewPaint(); + + ApplyBrush(tile.ToPoint()); + } + + public void TileMouseUp(Vector2? tile) + { + Submit(); + } + + public void Update(UpdateState state) + { + if (Forest != null && Forest.Erasing != Painter.Erasing) + { + Submit(); + } + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterPaint.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterPaint.cs new file mode 100644 index 000000000..fac331c92 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterPaint.cs @@ -0,0 +1,195 @@ +using FSO.Client.UI.Model; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Runtime.InteropServices; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterPaint : IMapPainterMode where T : unmanaged + { + private readonly MapPainterPlugin Painter; + private readonly Color[] ModifierToColor; + private readonly T[] ModifierToValue; + private readonly CityEditPaintType Type; + + private Terrain City => Painter.City; + + private CityEditPaint Paint; + private readonly MapPainterSpraypaint Spray; + public CityEditBase Command => Paint; + private Vector2 LastPos; + + private bool AnySet; + + private readonly Dictionary SprayIntensities = []; + private int SprayFrames; + + public MapPainterPaint(MapPainterPlugin painter, Color[] modifierToColor, T[] modifierToValue, CityEditPaintType type) + { + Painter = painter; + ModifierToColor = modifierToColor; + ModifierToValue = modifierToValue; + Type = type; + + Spray = new MapPainterSpraypaint(); + Spray.NewSeed(); + } + + public void Draw(SpriteBatch sb) + { + float iScale = (float)(1 / (City.GetIsoScale() * 2)); + + Color selColor = Painter.SelectedModifier < 0 || Painter.SelectedModifier >= ModifierToColor.Length ? + Color.White : + ModifierToColor[Painter.SelectedModifier]; + + float intensity = Painter.BrushIntensity; + float multiplier = Painter.Accelerate ? 2 : 1; + var spray = Painter.SprayBrush; + + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + if (spray) + { + int ix = (int)LastPos.X + x; + int iy = (int)LastPos.Y + y; + if (ix >= 0 && iy >= 0 && ix < 512 && iy < 512) + { + var sprayIntensity = Spray.GetSpraypaint(iy * 512 + ix, strength); + City.PathTile(ix, iy, iScale, new Color(selColor, Math.Min(0.5f, sprayIntensity * (intensity + 0.2f) * multiplier * 0.4f))); + } + } + else + { + if (strength > 0) City.PathTile((int)LastPos.X + x, (int)LastPos.Y + y, iScale, new Color(selColor, 0.5f)); + } + }); + + City.Draw2DPoly(false); + } + + private void ResetSprayFrames() + { + var frameMul = FSOEnvironment.RefreshRate / 60f; + SprayFrames = (int)(5 * frameMul); + } + + private void Submit() + { + if (Paint != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolUp); + Paint.Trim(); + Painter.Commit(AnySet); + + Paint = null; + } + } + + private void NewPaint() + { + Submit(); + SprayIntensities.Clear(); + if (Painter.SprayBrush) + { + Spray.NewSeed(); + } + SprayFrames = 0; + + var valueAsByte = MemoryMarshal.Cast(ModifierToValue); + + Paint = new CityEditPaint() + { + Type = Type, + Value = valueAsByte[Painter.SelectedModifier], + Bitmap = new CityEditBitmap(City.MapData.Width, City.MapData.Height) + }; + + AnySet = false; + } + + private void ApplyBrush(Point newPt) + { + var frameMul = 60f / FSOEnvironment.RefreshRate; + var spray = Painter.SprayBrush; + var intensity = Painter.BrushIntensity; + IMapPainterMode.BrushFunc(Painter.BrushSize, (x, y, strength) => + { + int targetX = newPt.X + x; + int targetY = newPt.Y + y; + + if (targetX < 0 || targetX >= 512 || targetY < 0 || targetY >= 512) + { + return; + } + + if (spray) + { + var key = new Point(targetX, targetY); + var sprayIntensity = Spray.GetSpraypaint(targetY * 512 + targetX, strength); + + SprayIntensities.TryGetValue(key, out float acc); + acc += sprayIntensity * frameMul * intensity; + SprayIntensities[key] = acc; + + if (acc > 4) + { + Paint.Bitmap.Set(targetX, targetY); + AnySet = true; + } + } + else + { + if (strength > 0) + { + Paint.Bitmap.Set(targetX, targetY); + AnySet = true; + } + } + }); + + if (!spray || --SprayFrames <= 0) + { + Painter.UpdateTemp(); + ResetSprayFrames(); + } + } + + public void TileHover(Vector2? tile) + { + if (Paint != null && tile != null) + { + var newPt = tile.Value.ToPoint(); + var spray = Painter.SprayBrush; + + if (spray || newPt != LastPos.ToPoint()) + { + ApplyBrush(newPt); + } + } + + if (tile != null) LastPos = tile.Value; + } + + public void TileMouseDown(Vector2 tile) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolDown); + NewPaint(); + + ApplyBrush(tile.ToPoint()); + } + + public void TileMouseUp(Vector2? tile) + { + Submit(); + } + + public void Update(UpdateState state) + { + + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterRoad.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterRoad.cs new file mode 100644 index 000000000..e97a6114e --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterRoad.cs @@ -0,0 +1,127 @@ +using FSO.Client.UI.Model; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Protocol.Electron.Model.CityEditCommands; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterRoad : IMapPainterMode + { + private readonly MapPainterPlugin Painter; + private Terrain City => Painter.City; + private CityEditRoad Road; + public CityEditBase Command => Road; + + private Point WallBase; + private Point WallTarget; + private Vector2 LastPos; + + private static Point[] WLStep = + { + new Point(1, 0), + new Point(0, 1), + new Point(-1, 0), + new Point(0, -1), + }; + + public MapPainterRoad(MapPainterPlugin painter) + { + Painter = painter; + } + + public void TileHover(Vector2? tile) + { + if (tile != null && Road != null) + { + var wallPos = new Point((int)Math.Round(tile.Value.X), (int)Math.Round(tile.Value.Y)); + var newPt = tile.Value.ToPoint(); + + if (wallPos != WallTarget) + { + WallTarget = wallPos; + var xd = (WallTarget.X - WallBase.X); + var yd = (WallTarget.Y - WallBase.Y); + Road.Length = (int)Math.Sqrt(xd * xd + yd * yd); + Road.Direction = (int)DirectionUtils.PosMod(Math.Round(Math.Atan2(yd, xd) / (Math.PI / 2)), 4); + Road.Delete = Painter.Erasing; + + Painter.UpdateTemp(); + } + } + + if (tile != null) LastPos = tile.Value; + } + + public void TileMouseDown(Vector2 tile) + { + var wallPos = new Point((int)Math.Round(tile.X), (int)Math.Round(tile.Y)); + + WallBase = wallPos; + WallTarget = wallPos; + + Road = new CityEditRoad() + { + StartX = wallPos.X, + StartY = wallPos.Y, + }; + + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolDown); + } + + public void TileMouseUp(Vector2? tile) + { + if (Road != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.BuildDragToolUp); + + Painter.Commit(Road.Length > 0); + + Road = null; + } + } + + public void Update(UpdateState state) + { + + } + + private Point GetWallEnd() + { + var pos = new Point(Road.StartX, Road.StartY); + var step = WLStep[Road.Direction]; + + pos += new Point(step.X * Road.Length, step.Y * Road.Length); + + return pos; + } + + public void Draw(SpriteBatch sb) + { + float cursorScale = 8; + if (Road != null) + { + var anchor = City.Content.PainterCursorAnchor; + + City.DrawLocal3D(sb, anchor, WallBase.ToVector2(), new Vector2(-anchor.Width / 2f, -anchor.Height), cursorScale, Vector2.One, Color.White); + } + + var wallPos = Road == null ? new Point((int)Math.Round(LastPos.X), (int)Math.Round(LastPos.Y)) : GetWallEnd(); + + var cursor = Road == null ? City.Content.PainterCursor : City.Content.PainterCursorActive; + City.DrawLocal3D(sb, cursor, wallPos.ToVector2(), new Vector2(-cursor.Width / 2f, -cursor.Height), cursorScale, Vector2.One, Color.White); + + var iconColor = Road == null ? new Color(203, 231, 225, 255) : new Color(253, 246, 153, 255); + var scale = Road == null ? 1f : (62f / 58f); + var icon = Painter.Erasing ? City.Content.PainterRoadDel : City.Content.PainterRoadIcon; + City.DrawLocal3D( + sb, + icon, + wallPos.ToVector2(), + new Vector2(-(icon.Width * scale) / 2f, (-cursor.Height + cursor.Width / 2) - (icon.Height * scale) / 2f), + cursorScale, + new Vector2(scale), iconColor); + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterSpraypaint.cs b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterSpraypaint.cs new file mode 100644 index 000000000..e2340e468 --- /dev/null +++ b/TSOClient/tso.client/Rendering/City/Plugins/PainterModes/MapPainterSpraypaint.cs @@ -0,0 +1,45 @@ +using FSO.Common.Domain.Realestate; + +namespace FSO.Client.Rendering.City.Plugins.PainterModes +{ + internal class MapPainterSpraypaint + { + private const float MinimumSpray = 128; + private const float Divisor = 1f / (256f + MinimumSpray); + private readonly byte[] Noise; + + public MapPainterSpraypaint() + { + Noise = new byte[512 * 512]; + } + + public MapPainterSpraypaint(bool shared) + { + Noise = shared ? CityMapUtils.GetRawNoise() : new byte[512 * 512]; + } + + public void NewSeed() + { + var random = Random.Shared.Next(); + CityMapUtils.GetSpraypaintNoise(Noise, (uint)random); + } + + public float GetSpraypaint(int index, float strength) + { + var dat = Noise[index]; + + return Math.Min(strength, (MinimumSpray + dat) * Divisor * strength); + } + + public float GetRoughEdge(int index, float strength, float brushSize) + { + var dat = Noise[index]; + + var middleDist = 0.5f - Math.Abs(0.5f - strength); + + // The closer to the middle, the more the random noise varies the strength. + + return strength - (dat / 255f) * middleDist * 0.7f * Math.Min(1f, 5f / brushSize); + } + } +} diff --git a/TSOClient/tso.client/Rendering/City/RhysGeom.cs b/TSOClient/tso.client/Rendering/City/RhysGeom.cs deleted file mode 100644 index 9fd61b7c5..000000000 --- a/TSOClient/tso.client/Rendering/City/RhysGeom.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Utils; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.City -{ - public class RhysGeom : ICityGeom - { - public TerrainVertex[] Vertices { get; internal set; } - public int[] Indexes { get; internal set; } - public IndexBuffer IndexBuffer { get; internal set; } - public VertexBuffer VertexBuffer { get; internal set; } - public int PrimitiveCount { get; internal set; } - - - - #region ICityGeom Members - - - public void Process(CityData city) - { - /**var verts = [] - var texUV = [] - var texUV2 = [] - var texUV3 = [] - var texUVB = [] - var data = getDataForImage(images["elevation.bmp"]) - elevData = data; - fDensityData = getDataForImage(images["forestdensity.bmp"]) - fTypeData = new Uint32Array(getDataForImage(images["foresttype.bmp"]).buffer) - var tData = new Uint32Array(getDataForImage(images["terraintype.bmp"]).buffer) - typeData = tData; - for (i=0; i<512; i++) { - if (i<306) var xStart = 306-i - else var xStart = (i-306) - if (i<205) var xEnd = 307+i - else var xEnd = 512-(i-205) - for (var j=xStart; j(); - var elevation = city.RawElevationPixels; - var vertexColors = city.VertexColorPixels; - var terrainTypes = city.RawTerrainTypePixels; - var terrainSpread = 4.0f; - //We have a sprite sheet that contains 5 terrain types - var terrainSheetSpread = (1.0f/5.0f) / terrainSpread; - - for (var y = 0; y < 512; y++) - { - var xStart = y < 306 ? (306 - y) : (y - 306); - var xEnd = y < 205 ? (307 + y) : (512-(y-205)); - - for (var x = xStart; x < xEnd; x++) - { - var pixelOffset = (y * 512) + x; - var terrainType = terrainTypes[pixelOffset]; - var vertexColor = vertexColors[pixelOffset]; - - var terrainTextureUV = new Vector2(city.GetTerrainType(terrainType) / 5.0f, 0.0f); - terrainTextureUV += new Vector2(terrainSheetSpread * (x % terrainSpread), (terrainSheetSpread / 2.0f) * (y % terrainSpread)); - - - var tl = new TerrainVertex( - new Vector3(x, elevation[(y*512)+x].R/12.0f, y), - terrainTextureUV, - vertexColor, - Vector2.Zero, - Vector2.Zero - ); - - var tr = new TerrainVertex( - new Vector3(x + 1, elevation[(y * 512) + Math.Min(511, x + 1)].R / 12.0f, y), - terrainTextureUV + new Vector2(terrainSheetSpread, 0.0f), - vertexColor, - Vector2.Zero, - Vector2.Zero - ); - - var br = new TerrainVertex( - new Vector3(x + 1, elevation[((Math.Min(511, y+1)*512)+Math.Min(511, x+1))].R / 12.0f, y + 1), - terrainTextureUV + new Vector2(terrainSheetSpread, terrainSheetSpread), - vertexColor, - Vector2.Zero, - Vector2.Zero - ); - - var bl = new TerrainVertex( - new Vector3(x, elevation[(Math.Min(511, y + 1) * 512) + x].R / 12.0f, y + 1), - terrainTextureUV + new Vector2(0.0f, terrainSheetSpread), - vertexColor, - Vector2.Zero, - Vector2.Zero - ); - - mesh.AddQuad(tl, tr, br, bl); - } - } - - Vertices = mesh.GetVertexes(); - Indexes = mesh.GetIndexes(); - PrimitiveCount = mesh.PrimitiveCount; - } - - - - public void CreateBuffer(Microsoft.Xna.Framework.Graphics.GraphicsDevice gd) - { - VertexBuffer = new VertexBuffer(gd, TerrainVertex.SizeInBytes * Vertices.Length, BufferUsage.WriteOnly); - VertexBuffer.SetData(Vertices); - - IndexBuffer = new IndexBuffer(gd, typeof(int), Indexes.Length, BufferUsage.WriteOnly); - IndexBuffer.SetData(Indexes); - } - - public void Draw(Microsoft.Xna.Framework.Graphics.GraphicsDevice gd) - { - - gd.Vertices[0].SetSource(VertexBuffer, 0, TerrainVertex.SizeInBytes); - gd.VertexDeclaration = new VertexDeclaration(gd, TerrainVertex.VertexElements); - gd.Indices = IndexBuffer; - gd.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Vertices.Length, 0, PrimitiveCount); - } - - #endregion - - - - - - - - - - - - - - - public float CellWidth - { - get - { - return 1; - } - set - { - } - } - - public float CellHeight - { - get - { - return 1; - } - set - { - } - } - - public float CellYScale - { - get - { - return 1; - } - set - { - } - } - } -} diff --git a/TSOClient/tso.client/Rendering/City/Terrain.cs b/TSOClient/tso.client/Rendering/City/Terrain.cs index acfbb3b83..04b85c990 100644 --- a/TSOClient/tso.client/Rendering/City/Terrain.cs +++ b/TSOClient/tso.client/Rendering/City/Terrain.cs @@ -1,28 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Collections; -using System.IO; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; +using FSO.Client.Controllers; +using FSO.Client.Rendering.City.Plugins; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; using FSO.Client.UI.Screens; +using FSO.Common; +using FSO.Common.Domain.RealestateDomain; +using FSO.Common.Rendering; using FSO.Common.Rendering.Framework; +using FSO.Common.Rendering.Framework.Camera; +using FSO.Common.Rendering.Framework.IO; using FSO.Common.Rendering.Framework.Model; -using FSO.Files; -using FSO.Client.UI.Framework; -using FSO.Client.Controllers; +using FSO.Content.Model; +using FSO.Files.RC; using FSO.LotView; -using FSO.Client.Rendering.City.Plugins; -using FSO.Common; -using FSO.LotView.RC; -using FSO.Common.Rendering.Framework.Camera; using FSO.LotView.Components; using FSO.LotView.Model; -using FSO.Files.RC; -using FSO.Common.Rendering.Framework.IO; -using FSO.Client.UI.Panels; -using FSO.Common.Rendering; +using FSO.LotView.RC; using FSO.LotView.Utils.Camera; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System.Collections; namespace FSO.Client.Rendering.City { @@ -43,47 +41,41 @@ public override void Add(_3DComponent item) public int ShadowRes = 2048; public bool RegenData = false; - public LotTileEntry[] LotTileData = new LotTileEntry[0]; + public readonly CityLotTiles LotTiles = new(); + public IEnumerable LotTileData => LotTiles.List; public bool LotTileDataDirty = true; + public Dictionary LotTileLookup => LotTiles.TileByVector; + public HashSet OccupiedTiles => LotTiles.OccupiedTiles; + public uint ActiveLocation; + public VertexBuffer LotOfflineVerts; public IndexBuffer LotOfflineInds; public VertexBuffer LotOnlineVerts; public IndexBuffer LotOnlineInds; - public Dictionary LotTileLookup = new Dictionary(); - public bool HandleMouse = false; - public CityMapData MapData { get + public CityMap MapData { get { return Content.MapData; } } private Color m_TintColor; + private Color m_TintColorSprite; public Effect Shader2D, PixelShader, VertexShader; private Vector3 m_LightPosition; - private int m_CityNumber; + private IShardRealestateDomain Realestate; private ArrayList m_2DVerts; - private Dictionary ForestTypes = new Dictionary() - { - { new Color(0, 0x6A, 0x28), 0 }, //heavy forest - { new Color(0, 0xEB, 0x42), 1}, //light forest - { new Color(255, 0, 0), 2}, //cacti - { new Color(255, 0xFC, 0), 3 }, //palm - { new Color(0, 0, 0), -1} //nothing; no forest - }; - - public static uint[] MASK_COLORS = new uint[]{ + public static uint[] MASK_COLORS = [ new Color(0xFF, 0x00, 0xFF, 0xFF).PackedValue, new Color(0xFE, 0x02, 0xFE, 0xFF).PackedValue, new Color(0xFF, 0x01, 0xFF, 0xFF).PackedValue - }; + ]; - //TODO: NEW 3D - public ICityCamera Camera = (GraphicsModeControl.Mode == GlobalGraphicsMode.Full3D)?new CityCamera3D():(ICityCamera)new CityCamera2D(); + public new ICityCamera Camera = (GraphicsModeControl.Mode == GlobalGraphicsMode.Full3D)?new CityCamera3D():new CityCamera2D(); public static float NEAR_ZOOM_SIZE = 288; public TerrainZoomMode m_Zoomed @@ -113,7 +105,6 @@ public float m_LotZoomProgress private MouseState m_MouseState, m_LastMouseState; private int m_ScrHeight, m_ScrWidth; - private Vector2 LastTargOff; public float m_ZoomProgress { get @@ -132,17 +123,17 @@ public float m_ZoomProgress private int[] m_SelTile = new int[] { -1, -1 }; private Vector2? m_VecSelTile; private Matrix m_MovMatrix; - private int[][] m_SurTileOffs = new int[][] - { - new int[] {0, -1}, - new int[] {1, -1}, - new int[] {1, 0}, - new int[] {1, 1}, - new int[] {0, 1}, - new int[] {-1, 1}, - new int[] {-1, 0}, - new int[] {-1, -1}, - }; + private int[][] m_SurTileOffs = + [ + [0, -1], + [1, -1], + [1, 0], + [1, 1], + [0, 1], + [-1, 1], + [-1, 0], + [-1, -1], + ]; private float DayOffset = 0.25f; private float DayDuration = 0.60f; @@ -166,36 +157,21 @@ public float m_ZoomProgress public CityNeighGeom NeighGeom; public CityFacadeLock NearFacades; - private Texture2D LoadTex(string Path) - { - using (var strm = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read)) - return LoadTex(strm); - } + private CityVertexColorGenerator VertexColorGenerator; + private bool VertexColorDirty; - private Texture2D LoadTex(Stream stream) - { - Texture2D result = null; - try - { - result = ImageLoader.FromStream(m_GraphicsDevice, stream); - } - catch (Exception) - { - result = new Texture2D(m_GraphicsDevice, 1, 1); - } - stream.Close(); - return result; - } + private List Modifications = []; public void LoadContent(GraphicsDevice GfxDevice) { Content = new CityContent(); - Content.LoadContent(GfxDevice, m_CityNumber); + Content.LoadContent(GfxDevice, Realestate.GetMap()); Geometry = new CityGeometry(); SubdivGeometry = new CityGeometry(); Foliage = new CityFoliage(); NeighGeom = new CityNeighGeom(this); NeighGeom.Generate(GfxDevice); + VertexColorGenerator = new CityVertexColorGenerator(this); m_GraphicsDevice = GfxDevice; VertexShader = GameFacade.Game.Content.Load("Effects/VerShader"); @@ -210,37 +186,17 @@ public Terrain(GraphicsDevice Device) : base(Device) Weather = new WeatherController(Particles); SkyDome = new AbstractSkyDome(Device, 0f); ParticleCamera = new BasicCamera(Device, Vector3.Zero, new Vector3(0, 0.5f, 0.86602540f), Vector3.Up); - //LoadContent(GfxDevice, Content); } - public override void DeviceReset(GraphicsDevice Device) + public void Initialize(IShardRealestateDomain realestate) { - //Dispose(); - //LoadContent(m_GraphicsDevice); - //RegenData = true; - } - - public void Initialize(int mapId) - { - m_CityNumber = mapId; + Realestate = realestate; GraphicsModeControl.ModeChanged += SwitchToMode; } - public void populateCityLookup(LotTileEntry[] TileData) + public void SignalCityDirty() { LotTileDataDirty = true; - LotTileData = TileData; - var oldLookup = new HashSet(LotTileLookup.Keys); - LotTileLookup = new Dictionary(); - for (int i = 0; i < TileData.Length; i++) - { - LotTileLookup[new Vector2(TileData[i].x, TileData[i].y)] = TileData[i]; - } - oldLookup.ExceptWith(new HashSet(LotTileLookup.Keys)); - foreach (var deleted in oldLookup) - { - //remove these from the cache. - } } public void GenerateAssets() @@ -257,6 +213,7 @@ public override void Dispose() Foliage.Dispose(); NearFacades?.Dispose(); NeighGeom?.Dispose(); + VertexColorGenerator?.Dispose(); foreach (var particle in Particles) particle.Dispose(); Particles.Clear(); @@ -270,177 +227,145 @@ public void DisposeOnLot() NearFacades = null; } - internal void DrawLine(Texture2D Fill, Vector2 Start, Vector2 End, SpriteBatch spriteBatch, int lineWidth, float opacity) //draws a line from Start to End. + internal void DrawLine(Texture2D fill, Vector2 start, Vector2 end, SpriteBatch spriteBatch, int lineWidth, Color tint) //draws a line from Start to End. { - double length = Math.Sqrt(Math.Pow(End.X - Start.X, 2) + Math.Pow(End.Y - Start.Y, 2)); - float direction = (float)Math.Atan2(End.Y - Start.Y, End.X - Start.X); - Color tint = new Color(1f, 1f, 1f, 1f) * opacity; - spriteBatch.Draw(Fill, new Rectangle((int)Start.X, (int)Start.Y-(int)(lineWidth/2), (int)length, lineWidth), null, tint, direction, new Vector2(0, 0.5f), SpriteEffects.None, 0); // + double length = Math.Sqrt(Math.Pow(end.X - start.X, 2) + Math.Pow(end.Y - start.Y, 2)); + float direction = (float)Math.Atan2(end.Y - start.Y, end.X - start.X); + spriteBatch.Draw(fill, new Rectangle((int)start.X, (int)start.Y - (int)(lineWidth / 2), (int)length, lineWidth), null, tint, direction, new Vector2(0, 0.5f), SpriteEffects.None, 0); // } - public void SwitchToMode(GlobalGraphicsMode mode) + internal void DrawLine(Texture2D fill, Vector2 start, Vector2 end, SpriteBatch spriteBatch, int lineWidth, float opacity) //draws a line from Start to End. { - var old = Camera; - Camera = (mode == GlobalGraphicsMode.Full3D) ? new CityCamera3D() : (ICityCamera)new CityCamera2D(); - Camera.Zoomed = old.Zoomed; - Camera.LotZoomProgress = old.LotZoomProgress; - Camera.ZoomProgress = old.ZoomProgress; - Camera.CenterCam = old.CenterCam; - Camera.Target = old.Target; - if (Camera is CityCamera3D) ((CityCamera3D)Camera).CenterTile = new Vector2(old.Target.X, old.Target.Z); - - if (Camera.Zoomed == TerrainZoomMode.Lot && LastWorld != null) - { - InheritPosition(LastWorld, FindController()?.Parent, true); - } + DrawLine(fill, start, end, spriteBatch, lineWidth, new Color(1f, 1f, 1f, 1f) * opacity); } - public void GenerateCityMesh(GraphicsDevice gd, Rectangle? range) + internal void DrawSpike(Vector2 start, float height, SpriteBatch batch, int width, Color color) { - Geometry.MapData = Content.MapData; - SubdivGeometry.MapData = Content.MapData; - Foliage.MapData = Content.MapData; - - if (range == null) + if (start.X < 0 || start.Y < 0 || start.X >= 512 || start.Y >= 512) { - Geometry.RegenMeshVerts(gd, false); + return; } - else - { - var pos = Camera.CalculateR(); - var slicex = Math.Max(0, Math.Min(30, (int)Math.Round(pos.X / 16f) - 1)); - var slicey = Math.Max(0, Math.Min(30, (int)Math.Round(pos.Y / 16f) - 1)); - var slice = slicex + slicey * 32; - Geometry.RegenMeshVerts(gd, true); - SubdivGeometry.SubRegenMeshVerts(m_GraphicsDevice, new Rectangle(slicex * 16, slicey * 16, 32, 32), 4, slice); - } - } + var alt = InterpElevationAt(start); - private Vector3 GetNormalAt(int x, int y) - { - var sum = new Vector3(); - var rotToNormalXY = Matrix.CreateRotationZ((float)(Math.PI/2)); - var rotToNormalZY = Matrix.CreateRotationX(-(float)(Math.PI / 2)); + var from = transformSpr4(new Vector3(start.X, alt, start.Y)); + var to = transformSpr4(new Vector3(start.X, alt + height, start.Y)); - if (x < 511) + if (from.Z <= 0 || to.Z <= 0) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(x + 1, y) - GetElevationPoint(x, y); - vec = Vector3.Transform(vec, rotToNormalXY); - sum += vec; + return; } - if (x > 1) + float width3d = width * GetSpriteScale(); + + var fromPos = new Vector2(from.X, from.Y); + var vec = new Vector2(to.X, to.Y) - fromPos; + + float direction = (float)Math.Atan2(vec.X, -vec.Y); + float dist = vec.Length(); + + if (dist == 0) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(x, y) - GetElevationPoint(x-1, y); - vec = Vector3.Transform(vec, rotToNormalXY); - sum += vec; + return; } - if (y < 511) + var spike = Content.PainterSpike; + + Vector2 origin = new(spike.Width / 2, spike.Height - 7); + Vector2 scale = new((width3d / spike.Width) / from.W, dist / origin.Y); + + Rectangle spikeTop = new Rectangle(0, 0, spike.Width, spike.Height - 7); + Rectangle spikeBottom = new Rectangle(0, spikeTop.Height, spike.Width, 7); + + batch.Draw(spike, fromPos, spikeTop, color, direction, origin, scale, SpriteEffects.None, 0); + + float coneScale = 1.5f; + Vector2 bottomScale = new(scale.X, scale.X * coneScale); + batch.Draw(spike, fromPos, spikeBottom, color, direction, new Vector2(origin.X, 0), bottomScale, SpriteEffects.None, 0); + + Get2DFromTile(start.X, start.Y); + } + + public void DrawLocal3D(SpriteBatch batch, Texture2D tex, Vector2 tile, Vector2 offset, float scale3D, Vector2 scale, Color color) + { + if (tile.X < 0 || tile.Y < 0 || tile.X >= 512 || tile.Y >= 512) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(x, y + 1) - GetElevationPoint(x, y); - vec = Vector3.Transform(vec, rotToNormalZY); - sum += vec; + return; } - if (y > 1) + scale3D *= GetSpriteScale(); + var alt = InterpElevationAt(tile); + + var basePos = transformSpr4(new Vector3(tile.X, alt, tile.Y)); + + if (basePos.Z <= 0) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(x, y) - GetElevationPoint(x, y - 1); - vec = Vector3.Transform(vec, rotToNormalZY); - sum += vec; + return; } - if (sum != Vector3.Zero) sum.Normalize(); - return sum; - } - private float GetElevationPoint(int x, int y) - { - return MapData.ElevationData[(y * 512 + x)] / 6.0f; + var size = scale3D / basePos.W; + var base2D = new Vector2(basePos.X, basePos.Y); + + batch.Draw(tex, base2D + offset * size, null, color, 0, Vector2.Zero, scale * size, SpriteEffects.None, 0); } - private Vector2 GetUVInTri(Vector2 a, Vector2 b, Vector2 c, Vector2 pt) + public void SwitchToMode(GlobalGraphicsMode mode) { - var ca = c - a; - var ba = b - a; - var pa = pt - a; + var old = Camera; + Camera = (mode == GlobalGraphicsMode.Full3D) ? new CityCamera3D() : (ICityCamera)new CityCamera2D(); + Camera.Zoomed = old.Zoomed; + Camera.LotZoomProgress = old.LotZoomProgress; + Camera.ZoomProgress = old.ZoomProgress; + Camera.CenterCam = old.CenterCam; + Camera.Target = old.Target; - var ca2 = Vector2.Dot(ca, ca); - var ca_ba = Vector2.Dot(ca, ba); - var ca_pa = Vector2.Dot(ca, pa); - var ba2 = Vector2.Dot(ba, ba); - var ba_pa = Vector2.Dot(ba, pa); + Camera.MouseEvent(HandleMouse ? UIMouseEventType.MouseOver : UIMouseEventType.MouseOut, null); - var inv = 1 / (ca2 * ba2 - ca_ba * ca_ba); - return new Vector2( - (ca2 * ba_pa - ca_ba * ca_pa) * inv, //factor to b - (ba2 * ca_pa - ca_ba * ba_pa) * inv //factor to c - ); + if (Camera is CityCamera3D cam3D) cam3D.CenterTile = new Vector2(old.Target.X, old.Target.Z); + if (Camera.Zoomed == TerrainZoomMode.Lot && LastWorld != null) + { + InheritPosition(LastWorld, FindController()?.Parent, true); + } } - public Vector2? GetHoverSquare(double[] bounds) + public void RegenerateVertexColor() { - return EstTileAtPosWithScroll(m_MouseState.Position.ToVector2() / FSOEnvironment.DPIScaleFactor, null); + VertexColorDirty = true; + } - var isoScale = GetIsoScale(); - double width = m_ScrWidth; - float iScale = (float)(1/(isoScale*2)); - - Vector2 mid = Camera.CalculateR(); - mid.X -= 6; - mid.Y += 6; - if (bounds == null) bounds = new double[] {Math.Round(mid.X-19), Math.Round(mid.Y-19), Math.Round(mid.X+19), Math.Round(mid.Y+19)}; - double[] pos = new double[] { m_MouseState.X, m_MouseState.Y }; + private (int slicex, int slicey, CitySliceKey key) GetCitySliceKey(Vector2 pos) + { + var slicex = Math.Max(0, Math.Min(30, (int)Math.Round(pos.X / 16f) - 1)); + var slicey = Math.Max(0, Math.Min(30, (int)Math.Round(pos.Y / 16f) - 1)); + var slice = slicex + slicey * 32; + return (slicex, slicey, new CitySliceKey(slice, ActiveLocation)); + } - Vector2? best = null; - float bestZ = float.MaxValue; + public void GenerateCityMesh(GraphicsDevice gd, Rectangle? range) + { + Geometry.MapData = Content.MapData; + SubdivGeometry.MapData = Content.MapData; + Foliage.MapData = Content.MapData; - for (int y=(int)bounds[3]; y>bounds[1]; y--) + if (range == null) { - if (y < 0 || y > 511) continue; - for (int x=(int)bounds[0]; x 511) continue; - //get the 4 points of this tile, and check if the mouse cursor is inside them. - var xy = transformSpr3(new Vector3(x+0, MapData.ElevationData[(y*512+x)]/12.0f, y+0)); - var xy2 = transformSpr3(new Vector3(x + 1, MapData.ElevationData[(y * 512 + Math.Min(x + 1, 511))] / 12.0f, y + 0)); - var xy3 = transformSpr3(new Vector3(x + 1, MapData.ElevationData[(Math.Min(y + 1, 511) * 512 + Math.Min(x + 1, 511))] / 12.0f, y + 1)); - var xy4 = transformSpr3(new Vector3(x + 0, MapData.ElevationData[(Math.Min(y + 1, 511) * 512 + x)] / 12.0f, y + 1)); - var minZ = Math.Min(xy.Z, Math.Min(xy2.Z, Math.Min(xy3.Z, xy4.Z))); - if (minZ > 0 && IsInsidePoly(new double[] { xy.X, xy.Y, xy2.X, xy2.Y, xy3.X, xy3.Y, xy4.X, xy4.Y }, pos) && minZ < bestZ) - { - bestZ = minZ; - //find closest point as well, it can be used by plugins - var vPos = new Vector2((float)pos[0], (float)pos[1]); - - var uv1 = GetUVInTri(vxy(xy), vxy(xy2), vxy(xy4), vPos); - if (uv1.X + uv1.Y < 1) - { - best = new Vector2(x,y) + uv1; - } - else - { - var uv2 = GetUVInTri(vxy(xy3), vxy(xy4), vxy(xy2), vPos); - best = new Vector2(x+1, y+1) - uv2; - } - } - } + Geometry.RegenMeshVerts(gd, false); + } + else + { + var pos = Camera.CalculateR(); + var (slicex, slicey, key) = GetCitySliceKey(pos); + + Geometry.RegenMeshVerts(gd, true); + SubdivGeometry.SubRegenMeshVerts(m_GraphicsDevice, new Rectangle(slicex * 16, slicey * 16, 32, 32), 4, key); + Foliage.InvalidateChunks(range.Value); } - return best; } - private Vector2 vxy(Vector3 v) + public Vector2? GetHoverSquare(double[] bounds) { - return new Vector2(v.X, v.Y); + return EstTileAtPosWithScroll(m_MouseState.Position.ToVector2() / FSOEnvironment.DPIScaleFactor, null); } @@ -575,36 +500,15 @@ public Vector2 EstTileAtPosWithScroll(Vector2 pos, List hits) } #endregion - private bool IsInsidePoly(double[] Poly, double[] Pos) - { - if (Poly.Length % 2 != 0) return false; //invalid polygon - int n = Poly.Length / 2; - bool result = false; - - for (int i=0; i= Math.Min(x1, x2)) && (Pos[0] < Math.Max(x1, x2))) - result = !(result); - } - - return result; - } - private void drawBorderSide(Vector2 xy, Vector2 xy2, Vector2 xy3, Vector2 xy4, SpriteBatch spriteBatch, float opacity) { double o = (17.0/144.0); //used for border segments double p = (1-o); - double[] int1 = new double[] { xy.X * p + xy2.X * o, xy.Y * p + xy2.Y * o }; - double[] int2 = new double[] { xy4.X * p + xy3.X * o, xy4.Y * p + xy3.Y * o }; - double[] int3 = new double[] { xy.X * o + xy2.X * p, xy.Y * o + xy2.Y * p }; - double[] int4 = new double[] { xy4.X * o + xy3.X * p, xy4.Y * o + xy3.Y * p }; + double[] int1 = [xy.X * p + xy2.X * o, xy.Y * p + xy2.Y * o]; + double[] int2 = [xy4.X * p + xy3.X * o, xy4.Y * p + xy3.Y * o]; + double[] int3 = [xy.X * o + xy2.X * p, xy.Y * o + xy2.Y * p]; + double[] int4 = [xy4.X * o + xy3.X * p, xy4.Y * o + xy3.Y * p]; DrawLine(Content.stpWhiteLine, new Vector2((float)(int1[0]), (float)(int1[1])), new Vector2((float)(int1[0] * p + int2[0] * o), (float)(int1[1] * p + int2[1] * o)), spriteBatch, 2, opacity); DrawLine(Content.stpWhiteLine, new Vector2((float)(int1[0] * p + int2[0] * o), (float)(int1[1] * p + int2[1] * o)), new Vector2((float)(int3[0] * p + int4[0] * o), (float)(int3[1] * p + int4[1] * o)), spriteBatch, 2,opacity); @@ -629,6 +533,8 @@ private void drawTileCorner(Vector2 xy, Vector2 xy2, Vector2 xy3, SpriteBatch sp private void DrawTileBorders(float iScale, SpriteBatch spriteBatch) { + var tint = m_TintColorSprite.ToVector3(); + float baseOpacity = (tint.X + tint.Y + tint.Z) / 3f; if (m_SelTile[0] != -1) { @@ -657,12 +563,12 @@ private void DrawTileBorders(float iScale, SpriteBatch spriteBatch) bool[] surTile = new bool[8]; for (int i=0; i().IsPurchasable(x, y); } @@ -710,13 +616,13 @@ private void DrawSpotlights(float HB) float iScale = (float)m_ScrWidth/(HB*2.0f); float spotlightScale = (float)(iScale*(2.0*Math.Sqrt(0.5*0.5*2)/5.10)); - LotTileEntry[] lots = LotTileData; - for (int i = 0; i < lots.Length; i++) + int i = 0; + foreach (var entry in LotTileData) { - if ((lots[i].flags & LotTileFlags.Spotlight) > 0) + if ((entry.flags & LotTileFlags.Spotlight) > 0) { - Vector2 pos = new Vector2(lots[i].x, lots[i].y); + Vector2 pos = new Vector2(entry.x, entry.y); Vector4 xy = transformSpr4(new Vector3(pos.X + 0.5f, MapData.ElevationData[((int)pos.Y * 512 + (int)pos.X)] / 12.0f, pos.Y + 0.5f)); //get position to place spotlight Vector3 xyz = new Vector3(xy.X, xy.Y, 1); @@ -731,13 +637,24 @@ private void DrawSpotlights(float HB) m_2DVerts.Add(new VertexPositionColor((xyz + (Vector3.Transform(new Vector3(-12, -100, 0), trans) * spotlightScale)), new Color(1, 1, 1, 0.0f))); //top two vertices set to 0 opacity, creates gradient for spotlight effect. m_2DVerts.Add(new VertexPositionColor((xyz + (Vector3.Transform(new Vector3(12, -100, 0), trans) * spotlightScale)), new Color(1, 1, 1, 0.0f))); } + i++; } } + public Vector2 Get2DFromTile(float x, float y) + { + float iScale = (float)(1 / (m_LastIsoScale * 2)); + if (x < 0 || y < 0 || x >= 512 || y >= 512) return new Vector2(); + + var transform = transformSpr3(new Vector3(x, InterpElevationAt(new Vector2(x, y)), y)); + return (transform.Z > 0) ? new Vector2(transform.X, transform.Y) : new Vector2(float.MaxValue, 0); + } + public Vector2 Get2DFromTile(int x, int y) { float iScale = (float)(1/(m_LastIsoScale * 2)); if (x < 0 || y < 0 || x >= 512 || y >= 512) return new Vector2(); + var transform = transformSpr3(new Vector3(x, MapData.ElevationData[(y * 512 + x)] / 12.0f, y)); return (transform.Z > 0)?new Vector2(transform.X, transform.Y):new Vector2(float.MaxValue, 0); } @@ -745,29 +662,6 @@ public Vector2 Get2DFromTile(int x, int y) public Vector2 GetFar2DFromTile(int x, int y) { return Get2DFromTile(x, y); - /* - float iScale = (float)(1 / (GetFarzoomIsoScale() * 2)); - if (x < 0 || y < 0) return new Vector2(); - return transformSprFar(iScale, new Vector3(x, MapData.ElevationData[(y * 512 + x)] / 12.0f, y)); - */ - } - - private void DrawHouses(float HB) //draws house icons in far view - { - var spriteBatch = m_Batch; - spriteBatch.Begin(sortMode: SpriteSortMode.Texture); - float iScale = (float)m_ScrWidth / (HB * 2); - LotTileEntry[] lots = LotTileData; - for (int i=0; i 0); - Texture2D img = (online) ? Content.LotOnline : Content.LotOffline; //if house is online, use red house instead of gray one - double alpha = online?(0.5+Math.Sin(4*Math.PI*(m_SpotOsc%1))/2.0):1; //if house is online, flash the opacity using the oscillator variable. - spriteBatch.Draw(img, new Rectangle((int)Math.Round(xy.X-1), (int)Math.Round(xy.Y-2), 4, 3), Color.White*(float)alpha); - } - spriteBatch.End(); } private void Draw3DHouses(int passIndex) @@ -779,10 +673,10 @@ private void Draw3DHouses(int passIndex) var offindices = new List(); var offverts = new List(); var vCount = 0; - LotTileEntry[] lots = LotTileData; - for (int i = 0; i < lots.Length; i++) + + foreach (var lot in LotTileData) { - bool online = ((lots[i].flags & LotTileFlags.Online) > 0); + bool online = ((lot.flags & LotTileFlags.Online) > 0); var indices = (online) ? onindices : offindices; var verts = (online) ? onverts : offverts; vCount = verts.Count; @@ -793,8 +687,8 @@ private void Draw3DHouses(int passIndex) indices.Add(vCount + 2); indices.Add(vCount + 3); - short x = lots[i].x; - short y = lots[i].y; + short x = lot.x; + short y = lot.y; if (!MapData.IsInBounds(x, y)) continue; @@ -848,6 +742,8 @@ private void Draw3DHouses(int passIndex) VertexShader.Parameters["ObjModel"].SetValue(Matrix.Identity); VertexShader.CurrentTechnique.Passes[passIndex].Apply(); + m_GraphicsDevice.BlendState = BlendState.NonPremultiplied; + if (LotOfflineInds != null) { PixelShader.Parameters["ObjTex"].SetValue(Content.LotOffline); @@ -935,9 +831,14 @@ private void DrawSprites(float HB, float VB) float treeHeight = treeWidth*(80/128); Vector2 mid = Camera.CalculateR(); //determine approximate tile position at center of screen - mid.X -= 6; + var isoScale = GetIsoScale(); + var range = Math.Min(50, 10 + (int)(isoScale * 2500)); + + mid.X -= 6; mid.Y += 6; - float[] bounds = new float[] { (float)Math.Round(mid.X - 19), (float)Math.Round(mid.Y - 19), (float)Math.Round(mid.X + 19), (float)Math.Round(mid.Y + 19) }; + float[] bounds = new float[] { (float)Math.Round(mid.X - range), (float)Math.Round(mid.Y - range), (float)Math.Round(mid.X + range), (float)Math.Round(mid.Y + range) }; + + Texture2D img = Content.Forest; float fade = Math.Max(0, Math.Min(1, (m_ZoomProgress - 0.4f) * 2)); @@ -991,16 +892,21 @@ private void DrawSprites(float HB, float VB) double scale = Math.Round((treeWidth * iScale / 128.0)*1000)/1000; - spriteBatch.Draw(lotImg, new Rectangle((int)(xy.X - (lotImgWidth/2) * scale), (int)(xy.Y - (lotImgHeight/2) * scale), (int)(scale * lotImgWidth), (int)(scale * lotImgHeight)), m_TintColor); + spriteBatch.Draw(lotImg, new Rectangle((int)(xy.X - (lotImgWidth/2) * scale), (int)(xy.Y - (lotImgHeight/2) * scale), (int)(scale * lotImgWidth), (int)(scale * lotImgHeight)), m_TintColorSprite); } else //if there is no house, draw the forest that's meant to be here. { - double fType = ForestTypes[MapData.ForestTypeData[(y * 512 + x)]]; + double fType = (int)MapData.ForestTypeData[(y * 512 + x)]; double fDens = Math.Round((double)(MapData.ForestDensityData[(y * 512 + x)] * 4 / 255)); if (!(fType == -1 || fDens == 0)) { double scale = treeWidth * iScale / 128.0; - spriteBatch.Draw(Content.Forest, new Rectangle((int)(xy.X - 64.0 * scale), (int)(xy.Y - 56.0 * scale), (int)(scale * 128), (int)(scale * 80)), new Rectangle((int)(128 * (fDens - 1)), (int)(80 * fType), 128, 80), m_TintColor); + spriteBatch.Draw( + Content.Forest, + new Rectangle((int)(xy.X - 64.0 * scale), (int)(xy.Y - 56.0 * scale), (int)(scale * 128), (int)(scale * 80)), + new Rectangle((int)(128 * (fDens - 1)), + (int)(80 * fType), 128, 80), + m_TintColorSprite); //draw correct forest from forest atlas } } @@ -1055,10 +961,18 @@ public Vector2 transformSprFar(float iScale, Vector3 pos) public void UIMouseEvent(UIMouseEventType type, UpdateState state) { Camera.MouseEvent(type, state); - if (type == UIMouseEventType.MouseOver) HandleMouse = true; - if (type == UIMouseEventType.MouseOut) - { - HandleMouse = false; + + switch (type) + { + case UIMouseEventType.MouseDown: + state.InputManager.SetFocus(null); + break; + case UIMouseEventType.MouseOver: + HandleMouse = true; + break; + case UIMouseEventType.MouseOut: + HandleMouse = false; + break; } } @@ -1125,7 +1039,7 @@ public void Click(Point pt, UpdateState state) { if (m_SelTile[0] != -1 && m_SelTile[1] != -1) { - FindController().ClickLot(m_SelTile[0], m_SelTile[1]); + FindController().ClickLot(m_SelTile[0], m_SelTile[1], state.ShiftDown); } } } @@ -1137,11 +1051,30 @@ public void Click(Point pt, UpdateState state) public override void Update(UpdateState state) { ITime++; + + float updateRate = 1f / FSOEnvironment.RefreshRate; + for (int i = 0; i < Modifications.Count; i++) + { + var mod = Modifications[i]; + mod.Timer += updateRate; + if (mod.Timer > CityModification.EdgeDuration) + { + Modifications.RemoveAt(i--); + } + } + if (!(GameFacade.Screens.CurrentUIScreen is CoreGameScreen)) return; CoreGameScreen CurrentUIScr = (CoreGameScreen)GameFacade.Screens.CurrentUIScreen; if (Visible) { //if we're not visible, do not update CityRenderer state... + if (VertexColorDirty || Content.VertexColor == null) + { + VertexColorGenerator.Update(GameFacade.GraphicsDevice); + Content.VertexColor = VertexColorGenerator.GetVertexColor(); + VertexColorDirty = false; + } + Weather.TintColor = m_TintColor.ToVector4(); Weather.Update(); @@ -1220,15 +1153,15 @@ public override void Update(UpdateState state) } } - - private Color PowColor(Color col, float pow) + private Color SRGBSpriteMul(Color linearMul) { - var vec = col.ToVector4(); - vec.X = (float)Math.Pow(vec.X, pow); - vec.Y = (float)Math.Pow(vec.Y, pow); - vec.Z = (float)Math.Pow(vec.Z, pow); + var linearVec = linearMul.ToVector4(); + + linearVec.X = (float)Math.Pow(linearVec.X, 1 / 2.2f); + linearVec.Y = (float)Math.Pow(linearVec.Y, 1 / 2.2f); + linearVec.Z = (float)Math.Pow(linearVec.Z, 1 / 2.2f); - return new Color(vec); + return new Color(linearVec); } private float Time; @@ -1249,6 +1182,8 @@ public void SetTimeOfDay(double time) ); } + m_TintColorSprite = SRGBSpriteMul(m_TintColor); + m_LightPosition = new Vector3(0, 0, -263); Matrix Transform = Matrix.Identity; @@ -1383,12 +1318,27 @@ public void Draw2DPoly(bool depth) Shader2D.CurrentTechnique.Passes[0].Apply(); + if (m_GraphicsDevice.Indices != null) m_GraphicsDevice.Indices = null; m_GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, Vert2D, 0, Vert2D.Length/3); //draw 2d coloured triangle array (for spotlights etc) m_GraphicsDevice.DepthStencilState = DepthStencilState.Default; m_2DVerts.Clear(); } + public float GetSpriteScale() + { + if (Camera is CityCamera3D) + { + var height = m_GraphicsDevice.Viewport.Height; + return height / 800f; + } + else + { + // Scale based off of iso scale. + return (1/1600f) / GetIsoScale(); + } + } + public float GetIsoScale() { return Camera.GetIsoScale(); @@ -1401,9 +1351,57 @@ public float GetFarzoomIsoScale() return FisoScale; } + private BoundingFrustum PrepareTerrainShader(Matrix view, Matrix projection, float darken) + { + VertexShader.CurrentTechnique = VertexShader.Techniques[2]; + var mv = view; + var mvp = (mv) * projection; + VertexShader.Parameters["BaseMatrix"].SetValue(mvp); + VertexShader.Parameters["MV"].SetValue(mv); + + PixelShader.CurrentTechnique = PixelShader.Techniques[2]; + PixelShader.Parameters["LightCol"].SetValue(new Vector4(m_TintColor.R / 255.0f, m_TintColor.G / 255.0f, m_TintColor.B / 255.0f, 1) * 1.25f); + var lightVec = Vector3.Normalize(m_LightPosition - new Vector3(256, 0, 256)); + PixelShader.Parameters["LightVec"].SetValue(lightVec); + PixelShader.Parameters["Time"].SetValue(ITime / (float)FSOEnvironment.RefreshRate); + + var invView = Matrix.Invert(mv); + if (Camera is CityCamera3D) invView.Translation = Vector3.Zero; + else invView = new Matrix(new Vector4(0.7071068f, 0f, 0.7071068f, 0), + new Vector4(0.3535534f, 0.8660254f, -0.3535534f, 0), + new Vector4(-0.6123725f, 0.5f, 0.6123725f, 0), + new Vector4(0, 0, 0, 1)); + PixelShader.Parameters["InvView"].SetValue(invView); + var dist = 0.3f + lightVec.Y; + dist *= dist; + PixelShader.Parameters["SunStrength"].SetValue(((1 - 0.6f * darken) / dist) * (1.0f - m_ShadowMult) * 2); + + PixelShader.Parameters["BigWTex"].SetValue(Content.BigWNormal); + PixelShader.Parameters["SmallWTex"].SetValue(Content.SmallWNormal); + + PixelShader.Parameters["WavePow"].SetValue(5 / 2f); + PixelShader.Parameters["RealNormalPct"].SetValue(2f); + PixelShader.Parameters["ShadowMult"].SetValue(m_ShadowMult); + + return new BoundingFrustum(mvp); + } + + private void UpdateActiveLocation() + { + ActiveLocation = UIScreen.Current.FindController()?.GetVisualLotID() ?? 0; + } + private Matrix m_LightMatrix; public override void Draw(GraphicsDevice gfx) { + bool is2D = Camera is CityCamera2D; + if (m_LotZoomProgress > 0 && !is2D) + { + return; + } + + UpdateActiveLocation(); + m_GraphicsDevice = gfx; ShadowRes = GlobalSettings.Default.ShadowQuality; @@ -1428,40 +1426,9 @@ public override void Draw(GraphicsDevice gfx) if ((Camera is CityCamera3D && m_Zoomed == TerrainZoomMode.Lot) || (Camera is CityCamera2D && m_LotZoomProgress == 1f)) return; Matrix ProjectionMatrix = Camera.Projection; - Matrix ViewMatrix = Camera.View; - Matrix WorldMatrix = Matrix.Identity; - - VertexShader.CurrentTechnique = VertexShader.Techniques[2]; - var mv = WorldMatrix * ViewMatrix; - var mvp = (mv) * ProjectionMatrix; - VertexShader.Parameters["BaseMatrix"].SetValue(mvp); - var frustum = new BoundingFrustum(mvp); - VertexShader.Parameters["MV"].SetValue(mv); - - PixelShader.CurrentTechnique = PixelShader.Techniques[2]; - PixelShader.Parameters["LightCol"].SetValue(new Vector4(m_TintColor.R / 255.0f, m_TintColor.G / 255.0f, m_TintColor.B / 255.0f, 1)*1.25f); - var lightVec = Vector3.Normalize(m_LightPosition - new Vector3(256, 0, 256)); - PixelShader.Parameters["LightVec"].SetValue(lightVec); - PixelShader.Parameters["Time"].SetValue(ITime/(float)FSOEnvironment.RefreshRate); - var invView = Matrix.Invert(mv); - if (Camera is CityCamera3D) invView.Translation = Vector3.Zero; - else invView = new Matrix(new Vector4(0.7071068f, 0f, 0.7071068f, 0), - new Vector4(0.3535534f, 0.8660254f, -0.3535534f, 0), - new Vector4(-0.6123725f, 0.5f, 0.6123725f, 0), - new Vector4(0, 0, 0, 1)); - PixelShader.Parameters["InvView"].SetValue(invView); - var dist = 0.3f + lightVec.Y; - dist *= dist; - PixelShader.Parameters["SunStrength"].SetValue(((1 - 0.6f * Weather.Darken) / dist) * (1.0f-m_ShadowMult) * 2); - - PixelShader.Parameters["BigWTex"].SetValue(Content.BigWNormal); - PixelShader.Parameters["SmallWTex"].SetValue(Content.SmallWNormal); - - PixelShader.Parameters["WavePow"].SetValue(5/2f); - PixelShader.Parameters["RealNormalPct"].SetValue(2f); - PixelShader.Parameters["ShadowMult"].SetValue(m_ShadowMult); + var frustum = PrepareTerrainShader(ViewMatrix, ProjectionMatrix, Weather.Darken); var fog = true; //(Camera is CityCamera3D) || Weather.WeatherIntensity > 0.01f; if (fog) { @@ -1472,31 +1439,20 @@ public override void Draw(GraphicsDevice gfx) PixelShader.Parameters["FogColor"].SetValue(fogColor); } - Texture2D ShadowMap = null; - if (ShadowsEnabled) { if (--ShadowRegenTimer < 0 || (m_ZoomProgress > 0.1f && m_ZoomProgress < 0.9f)) { - Matrix LightView = Matrix.CreateLookAt(m_LightPosition, new Vector3(256, 0, 256), new Vector3(0, 1, 0)); //Create light view - looks from light position to center of mesh. - Vector2 pos = Camera.CalculateRShadow(); - Vector3 LightOff = Vector3.Transform(new Vector3(pos.X, 0, pos.Y), LightView); //finds position in light space of approximate center of camera (to be used for only shadowing near the camera in near view) - - var shadZoom = Camera is CityCamera3D ? 0f : m_ZoomProgress; - float size = (1 - shadZoom) * 262 + (shadZoom * 40); //size of draw window to use for shadowing. 40 is good for near view, it could be less but that wouldn't work correctly on higher ground. - Matrix LightProject = Matrix.CreateOrthographicOffCenter(-size + LightOff.X, size + LightOff.X, -size + LightOff.Y, size + LightOff.Y, 0.1f, 524); //create light projection using offsets + size. - - m_LightMatrix = (WorldMatrix * LightView) * LightProject; - VertexShader.Parameters["LightMatrix"].SetValue(m_LightMatrix); - ShadowMap = DrawDepth(); + RecalculateShadows(); ShadowRegenTimer = 60; } - ShadowMap = ShadowTarget; - if (ShadowMap != null) + + Texture2D shadowMap = ShadowTarget; + if (shadowMap != null) { - PixelShader.Parameters["ShadowMap"].SetValue(ShadowMap); - PixelShader.Parameters["ShadSize"].SetValue(new Vector2(ShadowMap.Width, ShadowMap.Height)); + PixelShader.Parameters["ShadowMap"].SetValue(shadowMap); + PixelShader.Parameters["ShadSize"].SetValue(new Vector2(shadowMap.Width, shadowMap.Height)); } } @@ -1512,20 +1468,19 @@ public override void Draw(GraphicsDevice gfx) //handle slices if (Camera.Zoomed == TerrainZoomMode.Far) { - if (SubdivGeometry.CurrentSlice != -1) + if (SubdivGeometry.CurrentSlice != null) { SubdivGeometry.Ready = -1; - SubdivGeometry.CurrentSlice = -1; + SubdivGeometry.CurrentSlice = null; } } else { var pos = Camera.CalculateR(); - var slicex = Math.Max(0, Math.Min(30, (int)Math.Round(pos.X / 16f) - 1)); - var slicey = Math.Max(0, Math.Min(30, (int)Math.Round(pos.Y / 16f) - 1)); - var slice = slicex + slicey * 32; - if (SubdivGeometry.CurrentSlice != slice) + var (slicex, slicey, key) = GetCitySliceKey(pos); + + if (SubdivGeometry.CurrentSlice != key) { - SubdivGeometry.SubRegenMeshVerts(m_GraphicsDevice, new Rectangle(slicex * 16, slicey * 16, 32, 32), 4, slice); + SubdivGeometry.SubRegenMeshVerts(m_GraphicsDevice, new Rectangle(slicex * 16, slicey * 16, 32, 32), 4, key); } } @@ -1545,7 +1500,7 @@ public override void Draw(GraphicsDevice gfx) if (m_Zoomed == TerrainZoomMode.Far) Draw3DHouses(pass); //DrawHouses(HB); //draw far view house icons - if (Camera is CityCamera2D) + if (is2D) { m_2DVerts = new ArrayList(); //refresh list for tris under houses DrawSprites(HB, VB); //draw near view trees and houses @@ -1573,16 +1528,102 @@ public override void Draw(GraphicsDevice gfx) DrawSpotlights(HB); //draw far view spotlights Draw2DPoly(false); //draw spotlights using 2DVert shader - foreach (var particle in Particles) { var tint = m_TintColor; particle.GenericDraw(gfx, ParticleCamera, tint, false); } + DrawModifications(m_Batch); Plugin?.Draw(m_Batch); } + private void DrawModifications(SpriteBatch batch) + { + if (Modifications.Count == 0) return; + + batch.Begin(); + + float iScale = (float)(1 / (GetIsoScale() * 2)); + + foreach (var mod in Modifications) + { + var (edgeColor, fillColor) = mod.GetColors(); + var px = Content.stpWhiteLine; + + var map = mod.Bitmap; + + for (int y = 0; y < map.Height; y++) + { + for (int x = 0; x < map.Width; x++) + { + if (map.IsSet(x, y)) + { + int tx = x + map.X; + int ty = y + map.Y; + + PathTile(tx, ty, iScale, fillColor); + + var vxy = transformSpr3(new Vector3(tx + 0, MapData.ElevationData[(ty * 512 + tx)] / 12.0f, ty + 0)); + var vxy2 = transformSpr3(new Vector3(tx + 1, MapData.ElevationData[(ty * 512 + Math.Min(tx + 1, 511))] / 12.0f, ty + 0)); + var vxy3 = transformSpr3(new Vector3(tx + 1, MapData.ElevationData[(Math.Min(ty + 1, 511) * 512 + Math.Min(tx + 1, 511))] / 12.0f, ty + 1)); + var vxy4 = transformSpr3(new Vector3(tx + 0, MapData.ElevationData[(Math.Min(ty + 1, 511) * 512 + tx)] / 12.0f, ty + 1)); + + var minZ = Math.Min(vxy.Z, Math.Min(vxy2.Z, Math.Min(vxy3.Z, vxy4.Z))); + + if (minZ < 0) continue; + //Vector2 mousedist = ((xy + xy2 + xy3 + xy4) / 4.0f - new Vector2(m_MouseState.X, m_MouseState.Y)); + var xy = new Vector2(vxy.X, vxy.Y); + var xy2 = new Vector2(vxy2.X, vxy2.Y); + var xy3 = new Vector2(vxy3.X, vxy3.Y); + var xy4 = new Vector2(vxy4.X, vxy4.Y); + + // Draw edges + if (x <= 0 || !map.IsSet(x - 1, y)) + { + DrawLine(px, xy, xy4, batch, 2, edgeColor); + } + + if (x >= map.Width - 1 || !map.IsSet(x + 1, y)) + { + DrawLine(px, xy2, xy3, batch, 2, edgeColor); + } + + if (y <= 0 || !map.IsSet(x, y - 1)) + { + DrawLine(px, xy, xy2, batch, 2, edgeColor); + } + + if (y >= map.Height - 1 || !map.IsSet(x, y + 1)) + { + DrawLine(px, xy3, xy4, batch, 2, edgeColor); + } + } + } + } + } + + batch.End(); + + Draw2DPoly(true); + } + + private void RecalculateShadows() + { + Matrix LightView = Matrix.CreateLookAt(m_LightPosition, new Vector3(256, 0, 256), new Vector3(0, 1, 0)); //Create light view - looks from light position to center of mesh. + Vector2 pos = Camera.CalculateRShadow(); + Vector3 LightOff = Vector3.Transform(new Vector3(pos.X, 0, pos.Y), LightView); //finds position in light space of approximate center of camera (to be used for only shadowing near the camera in near view) + + var shadZoom = Camera is CityCamera3D ? 0f : m_ZoomProgress; + float size = (1 - shadZoom) * 262 + (shadZoom * 40); //size of draw window to use for shadowing. 40 is good for near view, it could be less but that wouldn't work correctly on higher ground. + Matrix LightProject = Matrix.CreateOrthographicOffCenter(-size + LightOff.X, size + LightOff.X, -size + LightOff.Y, size + LightOff.Y, 0.1f, 524); //create light projection using offsets + size. + + m_LightMatrix = LightView * LightProject; // World matrix for terrain is Identity + VertexShader.Parameters["LightMatrix"].SetValue(m_LightMatrix); + + DrawDepth(); + } + public static DepthStencilState StencilWrite = new DepthStencilState() { StencilEnable = true, @@ -1611,19 +1652,74 @@ public override void Draw(GraphicsDevice gfx) public uint StencilLotID; public VertexBuffer StencilVertices; - public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor, int surroundNumber) { + public void DrawThumbnail(GraphicsDevice gfx, RenderTarget2D target) + { + // Generate a temporary default 2D far zoom camera to use for the thumbnail. + // We need to temporarily override the camera for some things to work, so we remember the current camera to restore it later. + + var oldCamera = Camera; + var camera = new CityCamera2D(); + Camera = camera; + + m_GraphicsDevice = gfx; + + ShadowRes = 2048; + ShadowsEnabled = true; + + m_GraphicsDevice.RasterizerState = RasterizerState.CullNone; //don't cull + m_GraphicsDevice.DepthStencilState = DepthStencilState.Default; + + m_ScrHeight = target.Height; + m_ScrWidth = target.Width; + + if (RegenData) GenerateAssets(); //if assets are flagged as requiring regeneration, regenerate them! + + // Update lighting to a fixed time of day + + SetTimeOfDay(0.4f); + + PrepareTerrainShader(Camera.View, camera.CalculateProjection(target.Width, target.Height), 0); + + RecalculateShadows(); + ShadowRegenTimer = -1; + var shadowMap = ShadowTarget; + + PixelShader.Parameters["ShadowMap"].SetValue(shadowMap); + PixelShader.Parameters["ShadSize"].SetValue(new Vector2(shadowMap.Width, shadowMap.Height)); + VertexShader.Parameters["LightMatrix"].SetValue(m_LightMatrix); + + gfx.SetRenderTarget(target); + m_GraphicsDevice.Clear(m_TintColor); + + PixelShader.Parameters["FogMaxDist"].SetValue(float.MaxValue); + PixelShader.Parameters["FogColor"].SetValue(Color.White.ToVector4()); + + Geometry.DrawSlice(m_GraphicsDevice, Content, VertexShader, PixelShader, 4, 4, -1, 16); + + gfx.SetRenderTarget(null); + + SetTimeOfDay(Time); + Camera = oldCamera; + + m_ScrHeight = m_GraphicsDevice.Viewport.Height; + m_ScrWidth = m_GraphicsDevice.Viewport.Width; + } + + public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor, int surroundNumber) + { if (!GlobalSettings.Default.CitySkybox) { - if (camera is CameraControllers) + if (camera is CameraControllers controllers) { - var controllers = (CameraControllers)camera; controllers.ClearExternalTransition(); } return; } + + UpdateActiveLocation(); m_GraphicsDevice = gfx; - var world = Matrix.CreateTranslation(-LotPosition + new Vector3(-1 / 75f, -0.011f, 1 / 75f)) * Matrix.CreateRotationY((float)Math.PI / 2) * Matrix.CreateScale(75f * 3, 75f * 3 / 3f, 75f * 3); + var world = Matrix.CreateTranslation(-LotPosition + new Vector3(-1 / 75f, -0.011f, 1 / 75f)) * Matrix.CreateRotationY((float)Math.PI / 2) * Matrix.CreateScale(75f * 3, 12 * 100 * Blueprint.TerrainFactorConst * 3, 75f * 3); float IsoScale = GetIsoScale(); m_LastIsoScale = IsoScale; @@ -1660,6 +1756,8 @@ public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor var v = camera.View; var p = camera.Projection; + Camera.CalculateLotSquish(v); + if (ViewMatrixN != null) { var dummy = ((camera as CameraControllers)?.GetExternalTransition()?.Camera as DummyCamera); @@ -1675,11 +1773,21 @@ public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor m_ScrHeight = m_GraphicsDevice.Viewport.Height; m_ScrWidth = m_GraphicsDevice.Viewport.Width; + // Update the current slice if necessary + var pos = Camera.CalculateR(); + var (slicex, slicey, key) = GetCitySliceKey(pos); + + if (SubdivGeometry.CurrentSlice != key) + { + SubdivGeometry.SubRegenMeshVerts(m_GraphicsDevice, new Rectangle(slicex * 16, slicey * 16, 32, 32), 4, key); + } + if (RegenData) GenerateAssets(); //if assets are flagged as requiring regeneration, regenerate them! VertexShader.CurrentTechnique = VertexShader.Techniques[2]; var mv = world * v; var mvp = mv * p * Matrix.CreateScale(1f, 1f, 0.3f); + m_MovMatrix = mvp; VertexShader.Parameters["BaseMatrix"].SetValue(mvp); VertexShader.Parameters["MV"].SetValue(mv); var frustum = new BoundingFrustum(mvp); @@ -1720,7 +1828,7 @@ public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor VertexShader.CurrentTechnique.Passes[3].Apply(); var controller = UIScreen.Current.FindController(); - var id = controller.GetCurrentLotID(); + var id = ActiveLocation; if (m_LotZoomProgress == 1) { @@ -1790,11 +1898,11 @@ public void DrawSurrounding(GraphicsDevice gfx, ICamera camera, Vector4 fogColor } } - private void DrawFacade(FSOF fsof, Vector3 position, int passIndex, bool drawNight) + private void DrawFacade(FSOF fsof, ref Matrix baseMat, Vector3 position, int passIndex, bool drawNight) { if (fsof == null) return; - var b = 1 / 77f; - var mat = Matrix.CreateScale(b, b*Camera.LotSquish, b) * Matrix.CreateRotationY((float)Math.PI / -2f) * Matrix.CreateTranslation(position + new Vector3(1, 0, 0)) ; + var b = (1 / 75f); + var mat = baseMat * Matrix.CreateTranslation(position + new Vector3(1 + b, b * 0.75f, -b)); var gfx = m_GraphicsDevice; VertexShader.Parameters["ObjModel"].SetValue(mat); VertexShader.Parameters["DepthBias"].SetValue(-0.18f * Camera.DepthBiasScale); @@ -1835,7 +1943,10 @@ private void DrawFacade(FSOF fsof, Vector3 position, int passIndex, bool drawNig private void DrawFacades(Vector2 mid, int passIndex, bool useLocked, BoundingFrustum frustum) { - float[] bounds = new float[] { (float)Math.Round(mid.X - 19), (float)Math.Round(mid.Y - 19), (float)Math.Round(mid.X + 19), (float)Math.Round(mid.Y + 19) }; + Span bounds = [ (float)Math.Round(mid.X - 19), (float)Math.Round(mid.Y - 19), (float)Math.Round(mid.X + 19), (float)Math.Round(mid.Y + 19) ]; + + var b = 1 / 75f; + var baseMat = Matrix.CreateScale(b, b * Camera.LotSquish, b) * Matrix.CreateRotationY((float)Math.PI / -2f); float fade = Math.Max(0, Math.Min(1, (m_ZoomProgress - 0.4f) * 2)); @@ -1903,7 +2014,7 @@ private void DrawFacades(Vector2 mid, int passIndex, bool useLocked, BoundingFru if (lotImg != null) { - DrawFacade(lotImg, new Vector3(x, elev / 12.0f, y), passIndex, night && online); + DrawFacade(lotImg, ref baseMat, new Vector3(x, elev / 12.0f, y), passIndex, night && online); } } } @@ -1929,7 +2040,7 @@ private void DrawFacades(Vector2 mid, int passIndex, bool useLocked, BoundingFru if (LotTileLookup.ContainsKey(house.Location)) lhouse = LotTileLookup[house.Location]; var online = ((lhouse?.flags ?? 0) & LotTileFlags.Online) > 0; - DrawFacade(house.LotImg.LotFacade, house.Position, passIndex, night && online); + DrawFacade(house.LotImg.LotFacade, ref baseMat, house.Position, passIndex, night && online); } } } @@ -1937,9 +2048,17 @@ private void DrawFacades(Vector2 mid, int passIndex, bool useLocked, BoundingFru PixelShader.CurrentTechnique = PixelShader.Techniques[2]; VertexShader.CurrentTechnique = VertexShader.Techniques[2]; } - } - + internal void AddModification(CityModification modification) + { + Modifications.Add(modification); + } + + public override void DeviceReset(GraphicsDevice Device) + { + + } + } public enum TerrainZoomMode { diff --git a/TSOClient/tso.client/Rendering/City/TerrainVertex.cs b/TSOClient/tso.client/Rendering/City/TerrainVertex.cs deleted file mode 100644 index d06c5f66e..000000000 --- a/TSOClient/tso.client/Rendering/City/TerrainVertex.cs +++ /dev/null @@ -1,53 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Runtime.InteropServices; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; - -namespace TSOClient.Code.Rendering.City -{ - [StructLayout(LayoutKind.Sequential)] - public struct TerrainVertex - { - public Vector3 Position; - public Color Color; - public Vector2 TextureCoordinate; - public Vector2 BlendCoordinate; - public Vector2 BackTextureCoordinate; - - - public static int SizeInBytes = (sizeof(float) * (3 + 2 + 2 + 2)) + 4; - public static VertexElement[] VertexElements = new VertexElement[] - { - new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ), - new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ), - new VertexElement( 0, (sizeof(float) * 3) + 4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 ), - new VertexElement( 0, (sizeof(float) * (3 + 2)) + 4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1 ), - new VertexElement( 0, (sizeof(float) * (3 + 2 + 2)) + 4, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 2 ) - }; - - - public TerrainVertex(Vector3 position, Vector2 textureCoords, Color color, Vector2 blendCoords, Vector2 backTextureCoords) - { - this.Position = position; - this.Color = color; - this.TextureCoordinate = textureCoords; - this.BlendCoordinate = blendCoords; - this.BackTextureCoordinate = backTextureCoords; - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/CubeComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/CubeComponent.cs deleted file mode 100644 index 8d6793f04..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/CubeComponent.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - /// - /// A 3D cube for debugging - /// - public class CubeComponent : House3DComponent - { - private BasicEffect Effect; - - private VertexPositionColor[] Geom; - private List GeomList; - - public CubeComponent(Color color, Vector3 size) - { - Effect = new BasicEffect(GameFacade.GraphicsDevice, null); - - /** Bottom Face **/ - var btmTL = new Vector3(0.0f, 0.0f, 0.0f); - var btmTR = new Vector3(size.X, 0.0f, 0.0f); - var btmBR = new Vector3(size.X, 0.0f, size.Z); - var btmBL = new Vector3(0.0f, 0.0f, size.Z); - - /** Top face **/ - var topTL = new Vector3(0.0f, size.Y, 0.0f); - var topTR = new Vector3(size.X, size.Y, 0.0f); - var topBR = new Vector3(size.X, size.Y, size.Z); - var topBL = new Vector3(0.0f, size.Y, size.Z); - - - GeomList = new List(); - AddQuad(color, topTL, topTR, topBR, topBL); - AddQuad(Color.Yellow, btmTL, btmTR, btmBR, btmBL); - AddQuad(Color.Green, topTL, topTR, btmTR, btmTL); - AddQuad(Color.Blue, topBL, topTL, btmTL, btmBL); - AddQuad(Color.Orange, topBR, topTR, btmTR, btmBR); - AddQuad(Color.White, topBL, topBR, btmBR, btmBL); - - Geom = GeomList.ToArray(); - } - - - private void AddQuad(Color color, Vector3 tl, Vector3 tr, Vector3 br, Vector3 bl) - { - GeomList.Add(new VertexPositionColor(tl, color)); - GeomList.Add(new VertexPositionColor(tr, color)); - GeomList.Add(new VertexPositionColor(br, color)); - - GeomList.Add(new VertexPositionColor(br, color)); - GeomList.Add(new VertexPositionColor(bl, color)); - GeomList.Add(new VertexPositionColor(tl, color)); - } - - public override void Draw(GraphicsDevice device, HouseRenderState state) - { - device.VertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements); - - - Effect.World = state.World * Matrix.CreateTranslation(Position); - Effect.View = state.Camera.View; - Effect.Projection = state.Camera.Projection; - Effect.VertexColorEnabled = true; - //Effect.EnableDefaultLighting(); - - Effect.Begin(); - foreach (var pass in Effect.CurrentTechnique.Passes) - { - pass.Begin(); - device.DrawUserPrimitives(PrimitiveType.TriangleList, Geom, 0, Geom.Length / 3); - pass.End(); - } - Effect.End(); - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/DummyZSprite.cs b/TSOClient/tso.client/Rendering/Lot/Components/DummyZSprite.cs deleted file mode 100644 index b8a435780..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/DummyZSprite.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Rendering.Lot.Framework; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public class DummyZSprite : House2DComponent - { - private HouseBatchSprite Sprite; - - - public DummyZSprite(string prefix) - { - var gd = GameFacade.GraphicsDevice; - - var alpha = Texture2D.FromFile(gd, prefix + "a.png"); - var pixel = Texture2D.FromFile(gd, prefix + "p.png"); - var depth = Texture2D.FromFile(gd, prefix + "z.png"); - - var tex = new Texture2D(gd, pixel.Width, pixel.Height); - var texData = new Color[pixel.Width * pixel.Height]; - var alphaData = new Color[pixel.Width * pixel.Height]; - var pixelData = new Color[pixel.Width * pixel.Height]; - - - pixel.GetData(pixelData); - alpha.GetData(alphaData); - - for (var i = 0; i < texData.Length; i++) - { - var pixelPx = pixelData[i]; - var alphaPx = alphaData[i]; - pixelPx.A = alphaPx.R; - - texData[i] = pixelPx; - } - - tex.SetData(texData); - pixel.Dispose(); - alpha.Dispose(); - - - Sprite = new HouseBatchSprite - { - Pixel = tex, - RenderMode = HouseBatchRenderMode.Z_BUFFER, - Depth = depth, - SrcRect = new Microsoft.Xna.Framework.Rectangle(0, 0, pixel.Width, pixel.Height), - DestRect = new Microsoft.Xna.Framework.Rectangle(0, 0, pixel.Width, pixel.Height) - }; - } - - - - public override int Height - { - get { return 0; } - } - - public override void Draw(HouseRenderState state, HouseBatch batch) - { - //ZBuffer - - batch.Draw(Sprite); - //batch.DrawZ(Texture, ZBuffer, new Microsoft.Xna.Framework.Rectangle(0, 0, Texture.Width, Texture.Height), Color.White); - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/FloorComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/FloorComponent.cs deleted file mode 100644 index 01c907cbe..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/FloorComponent.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Data; -using TSOClient.Code.Utils; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using TSOClient.Code.Data.Model; -using TSOClient.Code.Rendering.Lot.Model; -using TSOClient.Code.Rendering.Lot.Framework; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public class FloorComponent : House2DComponent - { - public int Level; - public int FloorStyle; - - private Texture2D Texture; - private Rectangle PaintCoords; - - private bool m_Dirty = true; - private bool m_Active = true; - - public FloorComponent() - { - } - - public override void OnRotationChanged(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - m_Dirty = true; - } - - public override void OnZoomChanged(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - m_Dirty = true; - } - - public override void OnScrollChange(HouseRenderState state) - { - m_Dirty = true; - } - - - /// - /// Floors only occupy their own tile - /// - public override int Height - { - get { return 0; } - } - - public override void Draw(HouseRenderState state, HouseBatch batch) - { - if (!m_Active) { return; } - - if (m_Dirty) - { - if (FloorStyle == 0) { m_Active = false; return; } - - var floorStyle = ArchitectureCatalog.GetFloor(FloorStyle); - if (floorStyle == null) { m_Active = false; return; } - - - var position = state.TileToScreen(Position); - PaintCoords = new Rectangle((int)position.X, (int)position.Y, state.CellWidth, state.CellHeight); - Texture = floorStyle.GetTexture(state.Zoom, state.Rotation); - m_Dirty = false; - } - - - - batch.Draw(new HouseBatchSprite { - Pixel = Texture, - DestRect = PaintCoords, - SrcRect = new Rectangle(0, 0, Texture.Width, Texture.Height), - RenderMode = HouseBatchRenderMode.NO_DEPTH - }); - //batch.Draw(Texture, PaintCoords, Color.White); - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/House2DComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/House2DComponent.cs deleted file mode 100644 index 86cc0ff37..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/House2DComponent.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework; -using TSOClient.Code.Rendering.Lot.Model; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public abstract class House2DComponent - { - /// - /// Position of fixed tile objects on the tile space - /// - public Point Position; - - /// - /// Height of this component, used to calculate damage region - /// - public abstract int Height { get; } - - public virtual void OnZoomChanged(HouseRenderState state) - { - } - - public virtual void OnRotationChanged(HouseRenderState state) - { - } - - public virtual void OnScrollChange(HouseRenderState state) - { - } - - public abstract void Draw(HouseRenderState state, HouseBatch batch); - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/House3DComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/House3DComponent.cs deleted file mode 100644 index 5a6df7014..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/House3DComponent.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public abstract class House3DComponent : IWorldObject - { - public Vector3 Position; - public abstract void Draw(GraphicsDevice device, HouseRenderState state); - - - #region IWorldObject Members - - public void OnZoomChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - } - - public void OnRotationChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - } - - public void OnScrollChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/ObjectComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/ObjectComponent.cs deleted file mode 100644 index 8bae40d61..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/ObjectComponent.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public class ObjectComponent : House2DComponent - { - public override int Height - { - get { return 0; } - } - - public override void Draw(HouseRenderState state, HouseBatch batch) - { - - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/TerrainComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/TerrainComponent.cs deleted file mode 100644 index 6c938515f..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/TerrainComponent.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using System.IO; -using TSOClient.Code.Rendering.Lot.Model; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public class TerrainComponent : House3DComponent - { - private VertexPositionTexture[] Geom; - private Texture2D Texture; - private BasicEffect Effect; - - public TerrainComponent(HouseRenderState state) - { - var textureBase = GameFacade.GameFilePath("gamedata/terrain/newformat/"); - var grass = Texture2D.FromFile(GameFacade.GraphicsDevice, Path.Combine(textureBase, "gr.tga")); - Texture = grass; - - Effect = new BasicEffect(GameFacade.GraphicsDevice, null); - Effect.TextureEnabled = true; - Effect.Texture = Texture; - - Geom = new VertexPositionTexture[4]; - - var repeatX = state.Size / 2.5f; - var repeatY = repeatX; - - var tl = state.GetWorldFromTile(new Vector2(1, 1)); - var tr = state.GetWorldFromTile(new Vector2(state.Size-1, 1)); - var bl = state.GetWorldFromTile(new Vector2(1, state.Size-1)); - var br = state.GetWorldFromTile(new Vector2(state.Size-1, state.Size-1)); - - Geom[0] = new VertexPositionTexture(tl, new Vector2(0, 0)); - Geom[1] = new VertexPositionTexture(tr, new Vector2(repeatX, 0)); - Geom[2] = new VertexPositionTexture(br, new Vector2(repeatX, repeatY)); - Geom[3] = new VertexPositionTexture(bl, new Vector2(0, repeatY)); - } - - - - public override void Draw(GraphicsDevice device, HouseRenderState state) - { - Effect.World = state.World; - Effect.View = state.Camera.View; - Effect.Projection = state.Camera.Projection; - - device.SamplerStates[0].AddressU = TextureAddressMode.Wrap; - device.SamplerStates[0].AddressV = TextureAddressMode.Wrap; - device.VertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); - - Effect.Begin(); - foreach (var pass in Effect.CurrentTechnique.Passes) - { - pass.Begin(); - device.DrawUserPrimitives(PrimitiveType.TriangleFan, Geom, 0, 2); - pass.End(); - } - Effect.End(); - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Components/WallComponent.cs b/TSOClient/tso.client/Rendering/Lot/Components/WallComponent.cs deleted file mode 100644 index 5429ac999..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Components/WallComponent.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; -using TSOClient.Code.Data; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.Lot.Components -{ - public class WallComponent : House2DComponent - { - private HouseDataWall WallInfo; - public int Level; - - public WallComponent(HouseDataWall wallInfo) - { - this.WallInfo = wallInfo; - } - - //public override void OnStateChanged(HouseRenderState state) - //{ - // base.OnStateChanged(state); - - // /** Change texture pointers **/ - - //} - - public override int Height{ - get { return 0; } - } - - public override void Draw(HouseRenderState state, HouseBatch batch) - { - var position = state.TileToScreen(Position); - - - - if ((WallInfo.Segments & WallSegments.BottomLeft) == WallSegments.BottomLeft) - { - var wall = ArchitectureCatalog.GetWallPattern(WallInfo.BottomLeftPattern); - if (wall != null) - { - var tx = wall.Far.RightTexture; - - //batch.Draw(tx, new Rectangle((int)position.X, (int)position.Y - 49, 16, 67), Color.White); - //batch.Draw(tx, new Rectangle((int)position.X, (int)position.Y - 49, 16, 67), Color.White); - } - } - - if ((WallInfo.Segments & WallSegments.BottomRight) == WallSegments.BottomRight) - { - var wall = ArchitectureCatalog.GetWallPattern(WallInfo.BottomRightPattern); - if (wall != null) - { - var tx = wall.Far.LeftTexture; - - //batch.Draw(tx, new Rectangle((int)position.X, (int)position.Y - 49, 16, 67), Color.White); - //batch.Draw(tx, new Rectangle((int)position.X + 16, (int)position.Y - 49, 16, 67), Color.White); - } - } - - - - - - //if ((WallInfo.Segments & WallSegments.BottomRight) == WallSegments.BottomRight) - //{ - // var wall = ArchitectureCatalog.GetWallPattern(WallInfo.BottomRightPattern); - // if (wall != null) - // { - // var tx = wall.Far.LeftTexture; - - // //batch.Draw(tx, new Rectangle((int)position.X, (int)position.Y - 49, 16, 67), Color.White); - // batch.Draw(tx, new Rectangle((int)position.X + 16, (int)position.Y - 49, 16, 67), Color.White); - // } - //} - - - //if ((WallInfo.Segments & WallSegments.BottomRight) == WallSegments.BottomRight) - //{ - // var wall = ArchitectureCatalog.GetWallPattern(WallInfo.BottomLeftPattern); - // if (wall != null) - // { - // var tx = wall.Far.LeftTexture; - // batch.Draw(tx, new Rectangle((int)position.X, (int)position.Y - 58, 16, 67), Color.White); - // } - //} - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/DataModel/HouseData.cs b/TSOClient/tso.client/Rendering/Lot/DataModel/HouseData.cs deleted file mode 100644 index 5ccb53c8f..000000000 --- a/TSOClient/tso.client/Rendering/Lot/DataModel/HouseData.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Xml.Serialization; -using System.IO; - -namespace TSOClient.Code.Rendering.Lot.Model -{ - [XmlRoot("house")] - public class HouseData - { - [XmlElement("size")] - public int Size {get; set;} - - [XmlElement("category")] - public int Category { get; set; } - - [XmlElement("world")] - public HouseDataWorld World { get; set; } - - - public static HouseData Parse(string xmlFilePath) - { - XmlSerializer serialize = new XmlSerializer(typeof(HouseData)); - - using (var reader = File.OpenRead(xmlFilePath)) - { - return (HouseData)serialize.Deserialize(reader); - } - } - } - - public class HouseDataWorld - { - [XmlArray("floors")] - [XmlArrayItem("floor")] - public List Floors; - - [XmlArray("walls")] - [XmlArrayItem("wall")] - public List Walls; - } - - public class HouseDataFloor - { - [XmlAttribute("level")] - public int Level { get; set; } - - [XmlAttribute("x")] - public int X { get; set; } - - [XmlAttribute("y")] - public int Y { get; set; } - - [XmlAttribute("value")] - public int Value { get; set; } - } - - - public class HouseDataWall - { - [XmlAttribute("level")] - public int Level { get; set; } - - [XmlAttribute("x")] - public int X { get; set; } - - [XmlAttribute("y")] - public int Y { get; set; } - - [XmlAttribute("segments")] - public int _Segments - { - get { return (int)Segments; } - set { Segments = (WallSegments)value; } - } - - public WallSegments Segments { get; set; } - - - [XmlAttribute("placement")] - public int Placement { get; set; } - - - [XmlAttribute("tls")] - public int LeftStyle { get; set; } - [XmlAttribute("trs")] - public int RightStyle { get; set; } - - [XmlAttribute("tlp")] - public int TopLeftPattern { get; set; } - [XmlAttribute("trp")] - public int TopRightPattern { get; set; } - [XmlAttribute("brp")] - public int BottomRightPattern { get; set; } - [XmlAttribute("blp")] - public int BottomLeftPattern { get; set; } - } - - [Flags] - public enum WallSegments - { - TopLeft = 1, - TopRight = 2, - BottomLeft = 8, - BottomRight = 4, - HorizontalDiag = 16, - VerticalDiag = 32 - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchRenderMode.cs b/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchRenderMode.cs deleted file mode 100644 index 443d0ce64..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchRenderMode.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Code.Rendering.Lot.Framework -{ - public enum HouseBatchRenderMode - { - NO_DEPTH, - Z_BUFFER - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSorter.cs b/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSorter.cs deleted file mode 100644 index 11ddc49d4..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSorter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace TSOClient.Code.Rendering.Lot.Framework -{ - public class HouseBatchSorter : IComparer where T : HouseBatchSprite - { - #region IComparer Members - - public int Compare(T x, T y) - { - if (x.DrawOrder > y.DrawOrder){ - return 1; - } - if (x.DrawOrder < y.DrawOrder){ - return -1; - } - return 0; - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSprite.cs b/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSprite.cs deleted file mode 100644 index eb866cc0c..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Framework/HouseBatchSprite.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.Rendering.Lot.Framework -{ - public class HouseBatchSprite - { - public HouseBatchRenderMode RenderMode { get; set; } - public Texture2D Pixel { get; set; } - public Texture2D Depth { get; set; } - public Vector2 TilePosition { get; set; } - - public Rectangle SrcRect { get; set; } - public Rectangle DestRect { get; set; } - - //For internal use, do not set this - public int DrawOrder { get; set; } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/House2DLayer.cs b/TSOClient/tso.client/Rendering/Lot/House2DLayer.cs deleted file mode 100644 index 5e7a73664..000000000 --- a/TSOClient/tso.client/Rendering/Lot/House2DLayer.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Components; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; - -namespace TSOClient.Code.Rendering.Lot -{ - public class House2DLayer : IWorldObject - { - protected List Components = new List(); - protected bool m_Dirty; - - - /// - /// - /// - /// - public void AddComponent(House2DComponent comp) - { - this.Components.Add(comp); - } - - - /// - /// - /// - /// - /// - /// - public void Draw(GraphicsDevice device, HouseBatch batch, HouseRenderState state) - { - Components.ForEach(x => x.Draw(state, batch)); - } - - #region IWorldObject Members - - public void OnZoomChange(HouseRenderState state) - { - Components.ForEach(x => x.OnZoomChanged(state)); - m_Dirty = true; - } - - public void OnRotationChange(HouseRenderState state) - { - Components.ForEach(x => x.OnRotationChanged(state)); - m_Dirty = true; - } - - public void OnScrollChange(HouseRenderState state) - { - Components.ForEach(x => x.OnScrollChange(state)); - m_Dirty = true; - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/House2DScene.cs b/TSOClient/tso.client/Rendering/Lot/House2DScene.cs deleted file mode 100644 index 843c7395e..000000000 --- a/TSOClient/tso.client/Rendering/Lot/House2DScene.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Rendering.Lot.Components; -using Microsoft.Xna.Framework; -using TSOClient.Code.Utils; -using tso.common.utils; - -namespace TSOClient.Code.Rendering.Lot -{ - public class House2DScene : IWorldObject - { - private House2DLayer Floor; - private House2DLayer Walls; - - - - public House2DScene() - { - Floor = new House2DLayer(); - Walls = new House2DLayer(); - } - - - /// - /// Setup the initial rendering objects for this house - /// model - /// - /// - public void LoadHouse(HouseModel model) - { - /** Get all the first floor tiles **/ - var floors = model.GetFloors().Where(x => x.Level == 0); - foreach (var floor in floors) { Floor.AddComponent(floor); } - - var walls = model.GetWalls().Where(x => x.Level == 0); - walls.Reverse(); - foreach (var wall in walls) { Walls.AddComponent(wall); } - - - //Walls.AddComponent(new DummyZSprite(@"E:\Temp\tso\tower_")); - //Walls.AddComponent(new DummyZSprite(@"E:\Temp\tso\chair_")); - } - - - - public void Draw(GraphicsDevice device, HouseRenderState state) - { - var batch = new HouseBatch(device); - //batch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState); - batch.Begin(); - Floor.Draw(device, batch, state); - Walls.Draw(device, batch, state); - - /** Draw indicator in center of screen **/ - var rectSize = 5; - var gw = GlobalSettings.Default.GraphicsWidth; - var gh = GlobalSettings.Default.GraphicsHeight; - - batch.Draw(new TSOClient.Code.Rendering.Lot.Framework.HouseBatchSprite { - DestRect = new Rectangle((gw-rectSize)/2, (gh-rectSize)/2, rectSize, rectSize), - SrcRect = new Rectangle(0, 0, 1, 1), - Pixel = TextureUtils.TextureFromColor(device, Color.Pink), - RenderMode = TSOClient.Code.Rendering.Lot.Framework.HouseBatchRenderMode.NO_DEPTH - }); - - batch.End(); - } - - - #region IWorldObject Members - - public void OnZoomChange(HouseRenderState state) - { - Floor.OnZoomChange(state); - Walls.OnZoomChange(state); - } - - public void OnRotationChange(HouseRenderState state) - { - Floor.OnRotationChange(state); - Walls.OnRotationChange(state); - } - - public void OnScrollChange(HouseRenderState state) - { - Floor.OnScrollChange(state); - Walls.OnScrollChange(state); - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/House3DScene.cs b/TSOClient/tso.client/Rendering/Lot/House3DScene.cs deleted file mode 100644 index ad575e96e..000000000 --- a/TSOClient/tso.client/Rendering/Lot/House3DScene.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; -using TSOClient.Code.Rendering.Lot.Components; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using tso.common.rendering.framework.camera; - -namespace TSOClient.Code.Rendering.Lot -{ - /// - /// 3D layer of house rendering, this includes terrain and sims - /// - public class House3DScene : IWorldObject - { - private HouseRenderState RenderState; - private List Components; - private OrthographicCamera Camera; - - public House3DScene(HouseRenderState state) - { - this.RenderState = state; - this.Camera = (OrthographicCamera)state.Camera; - this.Components = new List(); - } - - - /// - /// Provides the 3d layer with information about the lot - /// - /// - public void LoadHouse(HouseModel model) - { - /** Add terrain **/ - Components.Add(new TerrainComponent(RenderState)); - - var cube1 = new CubeComponent(Color.Red, new Vector3(3.0f, 3.0f, 3.0f)); - cube1.Position = new Vector3(32.0f * 3, 0.0f, 32.0f * 3); - Components.Add(cube1); - } - - /// - /// Render the 3D objects to the screen - /// - /// - /// - public void Draw(GraphicsDevice device, HouseRenderState state) - { - Components.ForEach(x => x.Draw(device, state)); - } - - - /// - /// A view component has changed (rotation, zoom, scroll). - /// We need to adjust the camera - /// - private void InvalidateCamera() - { - //Camera.Translation = new Vector3(-radius, 0.0f, -radius); - - //Camera translation for scroll position - var offsetX = RenderState.TileToWorld(RenderState.FocusTile.X); - var offsetY = RenderState.TileToWorld(RenderState.FocusTile.Y); - - var centerX = Camera.Target.X; - var centerY = Camera.Target.Z; - - offsetX -= centerX; - offsetY -= centerY;// *1.03f; - - switch (RenderState.Zoom) - { - case HouseZoom.FarZoom: - Camera.Zoom = 152; - break; - - case HouseZoom.MediumZoom: - Camera.Zoom = 76; - break; - - case HouseZoom.CloseZoom: - Camera.Zoom = 38; - break; - } - Camera.Translation = new Vector3(offsetX, 0.0f, offsetY); - - //Camera.Target = new Vector3(offsetX, 0.0f, offsetY); - //Camera.Position = new Vector3(offsetX + 96.0f, Camera.Position.Y, offsetY + 96.0f); - } - - - - #region IWorldObject Members - - public void OnZoomChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - InvalidateCamera(); - Components.ForEach(x => x.OnZoomChange(state)); - } - - public void OnRotationChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - InvalidateCamera(); - Components.ForEach(x => x.OnRotationChange(state)); - } - - public void OnScrollChange(TSOClient.Code.Rendering.Lot.Model.HouseRenderState state) - { - InvalidateCamera(); - Components.ForEach(x => x.OnScrollChange(state)); - } - - #endregion - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/HouseBatch.cs b/TSOClient/tso.client/Rendering/Lot/HouseBatch.cs deleted file mode 100644 index a1948775a..000000000 --- a/TSOClient/tso.client/Rendering/Lot/HouseBatch.cs +++ /dev/null @@ -1,384 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using TSOClient.Code.Rendering.Lot.Framework; - -namespace TSOClient.Code.Rendering.Lot -{ - /// - /// Similar to SpriteBatch but more fit for purpose - /// RE z-buffers - /// - public class HouseBatch - { - //private VertexPositionColorTexture[] Vertices; - //private short[] Indices; - //private int VertexCount = 0; - //private int IndexCount = 0; - //private VertexDeclaration Declaration; - private GraphicsDevice Device; - //private Texture2D Texture; - //private Texture2D ZTexture; - - public Matrix World; - public Matrix View; - public Matrix Projection; - public Effect Effect; - - public HouseBatch(GraphicsDevice gd) - { - this.Device = gd; - - //this.Vertices = new VertexPositionColorTexture[256]; - //this.Indices = new short[Vertices.Length * 3 / 2]; - - ResetMatrices(GlobalSettings.Default.GraphicsWidth, GlobalSettings.Default.GraphicsHeight); - Effect = GameFacade.Game.Content.Load("Effects/HouseBatch"); - } - - - - - - private int DrawOrder; - private List Sprites = new List(); - - public void Begin() - { - DrawOrder = 0; - Sprites.Clear(); - } - - public void Draw(HouseBatchSprite sprite){ - sprite.DrawOrder = DrawOrder++; - Sprites.Add(sprite); - } - - - public void End() - { - var color = Color.White; - - var declaration = new VertexDeclaration(Device, VertexPositionColorTexture.VertexElements); - Device.VertexDeclaration = declaration; - - var effect = this.Effect; - - // set the only parameter this effect takes. - effect.Parameters["viewProjection"].SetValue(this.View * this.Projection); - - /** - * Flush the sprites to the screen - */ - Sprites.Sort(new HouseBatchSorter()); - - /** Group by texture **/ - var groupByTexture = Sprites.GroupBy(x => new { Pixel = x.Pixel, Depth = x.Depth, Mode = x.RenderMode }); - foreach (var group in groupByTexture){ - var texture = group.Key.Pixel; - var depth = group.Key.Depth; - var mode = group.Key.Mode; - var numSprites = group.Count(); - - effect.Parameters["diffuseTexture"].SetValue(texture); - if (depth != null){ - effect.Parameters["depthTexture"].SetValue(depth); - } - - EffectTechnique technique = null; - switch (mode) - { - case HouseBatchRenderMode.NO_DEPTH: - technique = effect.Techniques["drawSimple"]; - break; - - case HouseBatchRenderMode.Z_BUFFER: - technique = effect.Techniques["drawWithDepth"]; - break; - } - - /** Build vertex data **/ - var verticies = new VertexPositionColorTexture[4 * numSprites]; - var indices = new short[6 * numSprites]; - var indexCount = 0; - var vertexCount = 0; - - foreach (var sprite in group){ - - var srcRectangle = sprite.SrcRect; - var dstRectangle = sprite.DestRect; - - indices[indexCount++] = (short)(vertexCount + 0); - indices[indexCount++] = (short)(vertexCount + 1); - indices[indexCount++] = (short)(vertexCount + 3); - indices[indexCount++] = (short)(vertexCount + 1); - indices[indexCount++] = (short)(vertexCount + 2); - indices[indexCount++] = (short)(vertexCount + 3); - // add the new vertices - - verticies[vertexCount++] = new VertexPositionColorTexture( - new Vector3(dstRectangle.Left, dstRectangle.Top, 0) - , color, GetUV(texture, srcRectangle.Left, srcRectangle.Top)); - verticies[vertexCount++] = new VertexPositionColorTexture( - new Vector3(dstRectangle.Right, dstRectangle.Top, 0) - , color, GetUV(texture, srcRectangle.Right, srcRectangle.Top)); - verticies[vertexCount++] = new VertexPositionColorTexture( - new Vector3(dstRectangle.Right, dstRectangle.Bottom, 0) - , color, GetUV(texture, srcRectangle.Right, srcRectangle.Bottom)); - verticies[vertexCount++] = new VertexPositionColorTexture( - new Vector3(dstRectangle.Left, dstRectangle.Bottom, 0) - , color, GetUV(texture, srcRectangle.Left, srcRectangle.Bottom)); - } - - effect.CurrentTechnique = technique; - effect.Begin(); - EffectPassCollection passes = technique.Passes; - for (int i = 0; i < passes.Count; i++) - { - EffectPass pass = passes[i]; - pass.Begin(); - Device.DrawUserIndexedPrimitives( - PrimitiveType.TriangleList, verticies, 0, verticies.Length, - indices, 0, indices.Length / 3); - pass.End(); - } - effect.End(); - } - } - - - - private Vector2 GetUV(Texture2D Texture, float x, float y) - { - return new Vector2(x / (float)Texture.Width, y / (float)Texture.Height); - } - - - - - - - - - - - - - - public void ResetMatrices(int width, int height) - { - this.World = Matrix.Identity; - this.View = new Matrix( - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, -1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, -1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f); - this.Projection = Matrix.CreateOrthographicOffCenter( - 0, width, -height, 0, 0, 1); - } - - - //public void DrawZ(Texture2D texture, Texture2D zbuffer, Rectangle dstRectangle, Color color) - //{ - // DrawZ(texture, zbuffer, new Rectangle(0, 0, texture.Width, texture.Height), dstRectangle, color); - //} - - //public void DrawZ(Texture2D texture, Texture2D zbuffer, Rectangle srcRectangle, Rectangle dstRectangle, Color color) - //{ - // // if the texture changes, we flush all queued sprites. - // if ((this.Texture != null && this.Texture != texture) || - // (this.ZTexture != null && this.ZTexture != zbuffer)) - // this.Flush(); - // this.Texture = texture; - // this.ZTexture = zbuffer; - - // // ensure space for my vertices and indices. - // this.EnsureSpace(6, 4); - - // // add the new indices - // Indices[IndexCount++] = (short)(VertexCount + 0); - // Indices[IndexCount++] = (short)(VertexCount + 1); - // Indices[IndexCount++] = (short)(VertexCount + 3); - // Indices[IndexCount++] = (short)(VertexCount + 1); - // Indices[IndexCount++] = (short)(VertexCount + 2); - // Indices[IndexCount++] = (short)(VertexCount + 3); - - // // add the new vertices - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Left, dstRectangle.Top, 0) - // , color, GetUV(srcRectangle.Left, srcRectangle.Top)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Right, dstRectangle.Top, 0) - // , color, GetUV(srcRectangle.Right, srcRectangle.Top)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Right, dstRectangle.Bottom, 0) - // , color, GetUV(srcRectangle.Right, srcRectangle.Bottom)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Left, dstRectangle.Bottom, 0) - // , color, GetUV(srcRectangle.Left, srcRectangle.Bottom)); - - // // we premultiply all vertices times the world matrix. - // // the world matrix changes alot and we don't want to have to flush - // // every time it changes. - // Matrix world = this.World; - // for (int i = VertexCount - 4; i < VertexCount; i++) - // Vector3.Transform(ref Vertices[i].Position, ref world, out Vertices[i].Position); - //} - - - - - - - - - - - //public void Draw(Texture2D texture, Rectangle dstRectangle, Color color) - //{ - // Draw(texture, new Rectangle(0, 0, texture.Width, texture.Height), dstRectangle, color); - //} - - //public void Draw(Texture2D texture, Rectangle srcRectangle, Rectangle dstRectangle, Color color) - //{ - // // if the texture changes, we flush all queued sprites. - // if (this.Texture != null && this.Texture != texture) - // this.Flush(); - // this.Texture = texture; - // this.ZTexture = null; - - // // ensure space for my vertices and indices. - // this.EnsureSpace(6, 4); - - // // add the new indices - // Indices[IndexCount++] = (short)(VertexCount + 0); - // Indices[IndexCount++] = (short)(VertexCount + 1); - // Indices[IndexCount++] = (short)(VertexCount + 3); - // Indices[IndexCount++] = (short)(VertexCount + 1); - // Indices[IndexCount++] = (short)(VertexCount + 2); - // Indices[IndexCount++] = (short)(VertexCount + 3); - - // // add the new vertices - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Left, dstRectangle.Top, 0) - // , color, GetUV(srcRectangle.Left, srcRectangle.Top)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Right, dstRectangle.Top, 0) - // , color, GetUV(srcRectangle.Right, srcRectangle.Top)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Right, dstRectangle.Bottom, 0) - // , color, GetUV(srcRectangle.Right, srcRectangle.Bottom)); - // Vertices[VertexCount++] = new VertexPositionColorTexture( - // new Vector3(dstRectangle.Left, dstRectangle.Bottom, 0) - // , color, GetUV(srcRectangle.Left, srcRectangle.Bottom)); - - // // we premultiply all vertices times the world matrix. - // // the world matrix changes alot and we don't want to have to flush - // // every time it changes. - // Matrix world = this.World; - // for (int i = VertexCount - 4; i < VertexCount; i++) - // Vector3.Transform(ref Vertices[i].Position, ref world, out Vertices[i].Position); - //} - - //private Vector2 GetUV(float x, float y) - //{ - // return new Vector2(x / (float)Texture.Width, y / (float)Texture.Height); - //} - - //private void EnsureSpace(int indexSpace, int vertexSpace) - //{ - // if (IndexCount + indexSpace >= Indices.Length) - // Array.Resize(ref Indices, Math.Max(IndexCount + indexSpace, Indices.Length * 2)); - // if (VertexCount + vertexSpace >= Vertices.Length) - // Array.Resize(ref Vertices, Math.Max(VertexCount + vertexSpace, Vertices.Length * 2)); - //} - - - //public void Flush() - //{ - // if (ZTexture != null) { this.FlushZ(); return; } - - // if (this.VertexCount > 0) - // { - // if (this.Declaration == null || this.Declaration.IsDisposed) - // this.Declaration = new VertexDeclaration(Device, VertexPositionColorTexture.VertexElements); - - // Device.VertexDeclaration = this.Declaration; - - // Effect effect = this.Effect; - // // set the only parameter this effect takes. - // effect.Parameters["viewProjection"].SetValue(this.View * this.Projection); - // effect.Parameters["diffuseTexture"].SetValue(this.Texture); - - // EffectTechnique technique = effect.CurrentTechnique; - // effect.Begin(); - // EffectPassCollection passes = technique.Passes; - // for (int i = 0; i < passes.Count; i++) - // { - // EffectPass pass = passes[i]; - // pass.Begin(); - - // Device.DrawUserIndexedPrimitives( - // PrimitiveType.TriangleList, this.Vertices, 0, this.VertexCount, - // this.Indices, 0, this.IndexCount / 3); - - // pass.End(); - // } - // effect.End(); - - // this.VertexCount = 0; - // this.IndexCount = 0; - // } - //} - - - - //public void FlushZ() - //{ - // if (this.VertexCount > 0) - // { - // if (this.Declaration == null || this.Declaration.IsDisposed) - // this.Declaration = new VertexDeclaration(Device, VertexPositionColorTexture.VertexElements); - - // Device.VertexDeclaration = this.Declaration; - - // Effect effect = this.Effect; - // // set the only parameter this effect takes. - // effect.Parameters["viewProjection"].SetValue(this.View * this.Projection); - // effect.Parameters["diffuseTexture"].SetValue(this.Texture); - // effect.Parameters["depthTexture"].SetValue(this.ZTexture); - - // EffectTechnique technique = effect.Techniques["PaintDepth"]; - // effect.Begin(); - // EffectPassCollection passes = technique.Passes; - // for (int i = 0; i < passes.Count; i++) - // { - // EffectPass pass = passes[i]; - // pass.Begin(); - - // Device.DrawUserIndexedPrimitives( - // PrimitiveType.TriangleList, this.Vertices, 0, this.VertexCount, - // this.Indices, 0, this.IndexCount / 3); - - // pass.End(); - // } - // effect.End(); - - // this.VertexCount = 0; - // this.IndexCount = 0; - // } - //} - - } - - public enum ZBlitMode - { - /** Writes the z value but does not do a comparason, useful for baloons etc **/ - WriteOnly, - /** Does a Z <= compare **/ - ReadWrite - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/HouseRenderer.cs b/TSOClient/tso.client/Rendering/Lot/HouseRenderer.cs deleted file mode 100644 index cb6bb2757..000000000 --- a/TSOClient/tso.client/Rendering/Lot/HouseRenderer.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.ThreeD; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Rendering.Lot.Components; -using TSOClient.Code.Utils; -using TSOClient.Code.Data; -using TSOClient.Code.UI.Model; -using tso.common.rendering.framework.model; -using tso.common.rendering.framework; - -namespace TSOClient.Code.Rendering.Lot -{ - public class HouseRenderer : _3DComponent - { - private HouseData House; - - private List StaticLayer; - private List DynamicLayer; - - private HouseRenderState RenderState; - - - public HouseRenderer() - { - } - - - public HouseRotation GetRotation() - { - return RenderState.Rotation; - } - - public void SetRotation(HouseRotation rotation) - { - RenderState.Rotation = rotation; - } - - public void SetZoom(HouseZoom zoom) - { - RenderState.Zoom = zoom; - } - - public void SetModel(HouseData house) - { - this.House = house; - - StaticLayer = new List(); - - //StaticLayer.Add(new TerrainComponent()); - - foreach (var floor in house.World.Floors.Where(x => x.Level == 0)) - { - StaticLayer.Add(new FloorComponent { - Position = new Microsoft.Xna.Framework.Point(floor.X, floor.Y) - }); - } - - foreach (var wall in house.World.Walls.Where(x => x.Level == 0)) - { - StaticLayer.Add(new WallComponent (wall){ - Position = new Microsoft.Xna.Framework.Point(wall.X, wall.Y) - }); - } - - - RenderState = new HouseRenderState(); - RenderState.Rotation = HouseRotation.Angle360; - RenderState.Size = 64; - //RenderState.ScrollOffset = new Microsoft.Xna.Framework.Vector2(32, 32); - RenderState.Zoom = HouseZoom.FarZoom; - RenderState.Device = GameFacade.GraphicsDevice; - - - - /*this.Layers = new LotLevel[2]; - for (var i = 0; i < 2; i++) - { - var layer = new LotLevel(i); - layer.Process(house); - layer.ProcessGeometry(); - Layers[i] = layer; - }*/ - } - - - public override void Update(UpdateState GState) - { - } - - public override void Draw(GraphicsDevice device) - { - var batch = new HouseBatch(GameFacade.GraphicsDevice); - //batch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.SaveState); - batch.Begin(); - foreach (var item in StaticLayer) - { - item.Draw(RenderState, batch); - } - batch.End(); - - - //var layer = Layers[0]; - //layer.DrawFloor(device, scene, this); - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/HouseScene.cs b/TSOClient/tso.client/Rendering/Lot/HouseScene.cs deleted file mode 100644 index 588dc201b..000000000 --- a/TSOClient/tso.client/Rendering/Lot/HouseScene.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.ThreeD; -using Microsoft.Xna.Framework; -using TSOClient.Code.Rendering.Lot.Model; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.UI.Model; -using TSOClient.Code.Utils; - -using tso.common.rendering.framework.camera; -using tso.common.rendering.framework.model; -using tso.common.rendering.framework; - -namespace TSOClient.Code.Rendering.Lot -{ - public class HouseScene : _3DScene - { - /** How many pixels from each edge of the screen before we start scrolling the view **/ - public int ScrollBounds = 6; - - - public HouseRenderState RenderState { get; internal set; } - private House2DScene Scene2D; - private House3DScene Scene3D; - - private HouseModel Model; - - - public HouseScene() - { - Init(); - } - - - /// - /// Setup the rendering scene - /// - public void Init() - { - //Default render state - RenderState = new HouseRenderState(); - RenderState.Rotation = HouseRotation.Angle90; - RenderState.Zoom = HouseZoom.FarZoom; - RenderState.Device = GameFacade.GraphicsDevice; - - - Init2DWorld(); - Init3DWorld(); - } - - /// - /// Setup the 2D world - /// - protected void Init2DWorld() - { - Scene2D = new House2DScene(); - } - - /// - /// Setup the 3D world - /// - protected void Init3DWorld() - { - //Camera, default to a random position, when we load the lot it will change - var cameraPosition = new Vector3(0,0,0); - var cameraTarget = new Vector3(0,0,0); - Camera = new OrthographicCamera(GameFacade.GraphicsDevice, cameraPosition, cameraTarget, Vector3.Up); - //We have to squish the output vertically a bit so that tiles are twice as wide as they are tall. - Camera.AspectRatioMultiplier = 0.96f;//0.95567f; - RenderState.Camera = Camera; - - Scene3D = new House3DScene(RenderState); - } - - - /// - /// Load a house from its definition - /// - /// - public void LoadHouse(HouseData house) - { - RenderState.Size = house.Size; - - /** - * Camera should be at the edge of the screen looking onto the - * center point of the lot at a 30 degree angle - */ - /** Size is how many tiles, the last tile has a size too so the actual vertex position is +1) **/ - var worldEdge = house.Size + 1.0f; - var radius = RenderState.TileToWorld((worldEdge / 2.0f)); - var opposite = (float)Math.Cos(MathHelper.ToRadians(30.0f)) * radius; - - Camera.Position = new Vector3(radius * 2, opposite, radius * 2); - Camera.Target = new Vector3(radius, 0.0f, radius); - //Camera.Translation = new Vector3(-radius, 0.0f, -radius); - Camera.Zoom = 178; - - /** - * Setup the 2D space - */ - Model = new HouseModel(); - Model.LoadHouse(house); - - Scene2D.LoadHouse(Model); - Scene3D.LoadHouse(Model); - - //Center point of the center most tile - ViewCenter = new Vector2((RenderState.Size / 2.0f)+30, (RenderState.Size / 2.0f)+30); - } - - - - public override void Update(UpdateState state) - { - base.Update(state); - - /** Check for mouse scrolling **/ - var mouse = state.MouseState; - - var screenWidth = GlobalSettings.Default.GraphicsWidth; - var screenHeight = GlobalSettings.Default.GraphicsHeight; - - /** Corners **/ - var xBound = screenWidth - ScrollBounds; - var yBound = screenHeight - ScrollBounds; - - var cursor = CursorType.Normal; - var scrollVector = new Vector2(0, 0); - - if (mouse.X > 0 && mouse.Y > 0 && mouse.X < screenWidth && mouse.Y < screenHeight) - { - if (mouse.Y <= ScrollBounds) - { - if (mouse.X <= ScrollBounds) - { - /** Scroll top left **/ - cursor = CursorType.ArrowUpLeft; - scrollVector = new Vector2(-1, -1); - } - else if (mouse.X >= xBound) - { - /** Scroll top right **/ - cursor = CursorType.ArrowUpRight; - scrollVector = new Vector2(1, -1); - } - else - { - /** Scroll up **/ - cursor = CursorType.ArrowUp; - scrollVector = new Vector2(0, -1); - } - } - else if (mouse.Y <= yBound) - { - if (mouse.X <= ScrollBounds) - { - /** Left **/ - cursor = CursorType.ArrowLeft; - scrollVector = new Vector2(-1, 0); - } - else if (mouse.X >= xBound) - { - /** Right **/ - cursor = CursorType.ArrowRight; - scrollVector = new Vector2(1, -1); - } - } - else - { - if (mouse.X <= ScrollBounds) - { - /** Scroll bottom left **/ - cursor = CursorType.ArrowDownLeft; - scrollVector = new Vector2(-1, 1); - } - else if (mouse.X >= xBound) - { - /** Scroll bottom right **/ - cursor = CursorType.ArrowDownRight; - scrollVector = new Vector2(1, 1); - } - else - { - /** Scroll down **/ - cursor = CursorType.ArrowDown; - scrollVector = new Vector2(0, 1); - } - } - } - - if (cursor != CursorType.Normal) - { - - /** - * Calculate scroll vector based on rotation & scroll type - */ - scrollVector = new Vector2(); - switch (Rotation){ - case HouseRotation.Angle90: - switch (cursor){ - case CursorType.ArrowDown: - scrollVector = new Vector2(1, 1); - break; - - case CursorType.ArrowUp: - scrollVector = new Vector2(-1, -1); - break; - - case CursorType.ArrowLeft: - scrollVector = new Vector2(-1, 1); - break; - - case CursorType.ArrowRight: - scrollVector = new Vector2(1, -1); - break; - } - break; - - - case HouseRotation.Angle180: - switch (cursor) - { - case CursorType.ArrowDown: - scrollVector = new Vector2(-1, 1); - break; - - case CursorType.ArrowUp: - scrollVector = new Vector2(1, -1); - break; - } - break; - - case HouseRotation.Angle270: - switch (cursor) - { - case CursorType.ArrowDown: - scrollVector = new Vector2(-1, -1); - break; - - case CursorType.ArrowUp: - scrollVector = new Vector2(1, 1); - break; - } - break; - - case HouseRotation.Angle360: - switch (cursor) - { - case CursorType.ArrowDown: - scrollVector = new Vector2(1, -1); - break; - - case CursorType.ArrowUp: - scrollVector = new Vector2(-1, 1); - break; - } - break; - } - - /** We need to scroll **/ - ViewCenter += scrollVector * new Vector2(0.0625f, 0.0625f); - } - - GameFacade.Cursor.SetCursor(cursor); - } - - - /// - /// Render the house scene - /// - /// - public override void Draw(GraphicsDevice device) - { - Scene3D.Draw(device, RenderState); - Scene2D.Draw(device, RenderState); - } - - - - - - - - - - - #region View Modification - - public HouseZoom Zoom - { - get - { - return RenderState.Zoom; - } - set - { - RenderState.Zoom = value; - Scene2D.OnZoomChange(RenderState); - Scene3D.OnZoomChange(RenderState); - } - } - - /// - /// Set the camera rotation - /// - public HouseRotation Rotation - { - get - { - return RenderState.Rotation; - } - set - { - RenderState.Rotation = value; - Scene2D.OnRotationChange(RenderState); - } - } - - public Vector2 ViewCenter - { - get { return RenderState.FocusTile; } - set - { - RenderState.FocusTile = value; - Scene2D.OnScrollChange(RenderState); - Scene3D.OnScrollChange(RenderState); - } - } - - #endregion - - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/IWorldObject.cs b/TSOClient/tso.client/Rendering/Lot/IWorldObject.cs deleted file mode 100644 index 6afa316d7..000000000 --- a/TSOClient/tso.client/Rendering/Lot/IWorldObject.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Model; - -namespace TSOClient.Code.Rendering.Lot -{ - public interface IWorldObject - { - void OnZoomChange(HouseRenderState state); - void OnRotationChange(HouseRenderState state); - void OnScrollChange(HouseRenderState state); - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Model/HouseModel.cs b/TSOClient/tso.client/Rendering/Lot/Model/HouseModel.cs deleted file mode 100644 index 0d07f242e..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Model/HouseModel.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.Rendering.Lot.Components; -using TSOClient.Code.Data; - -namespace TSOClient.Code.Rendering.Lot.Model -{ - public class HouseModel - { - private List _FloorList; - private FloorComponent[,,] _FloorLookup; - - private List _WallList; - private WallComponent[,,] _WallLookup; - - public FloorComponent GetFloor(int level, int x, int y) - { - return _FloorLookup[level, x, y]; - } - - public List GetFloors() - { - return _FloorList; - } - - public List GetWalls() - { - return _WallList; - } - - - public void LoadHouse(HouseData data) - { - _FloorList = new List(); - _FloorLookup = new FloorComponent[2, data.Size, data.Size]; - foreach (var floor in data.World.Floors){ - - var floorComponent = new FloorComponent() - { - Position = new Microsoft.Xna.Framework.Point(floor.X, floor.Y), - Level = floor.Level, - FloorStyle = floor.Value - }; - _FloorLookup[floor.Level, floor.X, floor.Y] = floorComponent; - _FloorList.Add(floorComponent); - } - - - _WallList = new List(); - _WallLookup = new WallComponent[2, data.Size, data.Size]; - - foreach (var wall in data.World.Walls) - { - var wallComponent = new WallComponent(wall) - { - Position = new Microsoft.Xna.Framework.Point(wall.X, wall.Y), - Level = wall.Level - }; - _WallList.Add(wallComponent); - _WallLookup[wall.Level, wall.X, wall.Y] = wallComponent; - } - } - } -} diff --git a/TSOClient/tso.client/Rendering/Lot/Model/HouseRenderState.cs b/TSOClient/tso.client/Rendering/Lot/Model/HouseRenderState.cs deleted file mode 100644 index 040b4be58..000000000 --- a/TSOClient/tso.client/Rendering/Lot/Model/HouseRenderState.cs +++ /dev/null @@ -1,290 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.ThreeD; - -using tso.common.rendering.framework.camera; - -namespace TSOClient.Code.Rendering.Lot.Model -{ - public class HouseRenderState - { - public const float WorldUnitsPerTile = 3.0f; - - - public GraphicsDevice Device; - public ICamera Camera; - public Matrix World = Matrix.Identity; - - - private HouseRotation _Rotation; - public HouseRotation Rotation - { - get - { - return _Rotation; - } - set - { - _Rotation = value; - InvalidateMetrics(); - } - } - - - private HouseZoom _Zoom; - public HouseZoom Zoom - { - get - { - return _Zoom; - } - set - { - _Zoom = value; - - switch (_Zoom) - { - case HouseZoom.CloseZoom: - CellWidth = 128; - CellHeight = 64; - CellHalfWidth = 64; - CellHalfHeight = 32; - break; - - case HouseZoom.MediumZoom: - CellWidth = 63; - CellHeight = 32; - CellHalfWidth = 31; - CellHalfHeight = 16; - break; - - case HouseZoom.FarZoom: - CellWidth = 32; - CellHeight = 16; - CellHalfWidth = 16; - CellHalfHeight = 8; - break; - } - CellPxSize = new Rectangle(0, 0, CellWidth, CellHeight); - InvalidateMetrics(); - } - } - - - private Vector2 m_FocusTile; - /// - /// Which tile in the view is currently the center focus - /// - public Vector2 FocusTile - { - get - { - return m_FocusTile; - } - set - { - m_FocusTile = value; - InvalidateMetrics(); - } - } - - - - private void InvalidateMetrics() - { - /* - CenterX = Size * CellHalfWidth; - RightX = Size * CellWidth; - MiddleY = Size * CellHalfHeight; - BottomY = Size * CellHeight; - */ - - /** - * We want to center the focus tile in the middle of the screen - */ - var screenWidth = GlobalSettings.Default.GraphicsWidth; - var screenHeight = GlobalSettings.Default.GraphicsHeight; - - var pxPoint = TileToScreenNoScroll(FocusTile); - //CellOffset = new Vector2( - // -(pxPoint.X) + (screenWidth/2.0f), - // -(pxPoint.Y) + (screenHeight/2.0f)); - //var cx = CenterX - (FocusTile.Y * CellHalfWidth) + (FocusTile.X * CellHalfWidth); - //var cy = (FocusTile.Y * CellHalfHeight) + (FocusTile.X * CellHalfHeight); - - //cy -= (FocusTile.Y / (float)Size) * CellHalfHeight; - //cx += (FocusTile.X / (float)Size) * CellHalfWidth; - - //cy += (FocusTile.Y / Size) * CellHeight; - //cx /= 1.03f; - //cy /= 1.03f; - - //CellOffset = new Vector2(-cx + (screenWidth/2), -cy + (screenHeight/2)); - var offset = this.TileToScreenNoScroll(FocusTile); - var screenx = offset.X; - var screeny = offset.Y; - - screenx += (screenWidth / 2.0f); - screeny += (screenHeight / 2.0f); - - CellOffset = new Vector2((float)screenx, (float)screeny); - - //TODO: I think we should fix this in the tile position calculation rather than - //by offseting scroll coords - - //switch (Rotation) - //{ - // case HouseRotation.Angle90: - // CellOffset -= new Vector2(CellHalfWidth, 0.0f); - // break; - - // case HouseRotation.Angle180: - // CellOffset -= new Vector2(0.0f, CellHalfHeight); - // break; - - // case HouseRotation.Angle270: - // CellOffset -= new Vector2(CellHalfWidth, CellHeight); - // break; - - // case HouseRotation.Angle360: - // CellOffset -= new Vector2(CellWidth, CellHalfHeight); - // break; - //} - - - //var scrollX = CenterX - (ScrollOffset.Y * CellHalfWidth) + (ScrollOffset.X * CellHalfWidth); - //var scrollY = (ScrollOffset.Y * CellHalfHeight) + (ScrollOffset.X * CellHalfHeight); - - //CellOffset = new Vector2(-(scrollX/2), -(scrollY/2)); - } - - - /** Numbers for internal calculation **/ - //private int CenterX; - //private int RightX; - //private int MiddleY; - //private int BottomY; - - public int CellWidth { get; internal set; } - public int CellHeight { get; internal set; } - public int CellHalfWidth { get; internal set; } - public int CellHalfHeight { get; internal set; } - - public Rectangle CellPxSize { get; internal set; } - private Vector2 CellOffset; - public int Size; - - - - - - - //private static Vector3 WorldOffset = new Vector3(-(32.0f * WorldUnitsPerTile), 0.0f, -(32.0f * WorldUnitsPerTile)); - - public Vector3 GetWorldFromTile(Vector2 tile) - { - //3 feet per tile - return new Vector3(tile.X * WorldUnitsPerTile, 0.0f, tile.Y * WorldUnitsPerTile);// +WorldOffset; - } - - public float TileToWorld(float tileCoord) - { - return WorldUnitsPerTile * tileCoord; - } - - public Vector2 TileToScreen(Point point) - { - return TileToScreen(new Vector2(point.X, point.Y)); - } - - public Vector2 TileToScreen(Vector2 point) - { - point.X -= FocusTile.X; - point.Y -= FocusTile.Y; - var position = TileToScreenNoScroll(point); - //return position + CellOffset; - - var screenWidth = GlobalSettings.Default.GraphicsWidth; - var screenHeight = GlobalSettings.Default.GraphicsHeight; - - position.X -= (CellHalfWidth); - - return position + new Vector2((float)screenWidth / 2.0f, (float)screenHeight / 2.0f); - } - - public Vector2 TileToScreenNoScroll(Vector2 point) - { - var tilex = point.X; - var tiley = point.Y; - - var sin60 = CellWidth / Math.Sqrt(5.0); // sin(arctan(2)) or cos(arctan(1/2)) - var sin30 = CellHeight / Math.Sqrt(5.0); // sin(arctan(1/2)) or cos(arctan(2)) - - var screenx = Math.Round((tilex - tiley) * sin60); - var screeny = Math.Round((tilex + tiley) * sin30); - - return new Vector2((float)screenx, (float)screeny); - - //screenx -= (screenWidth / 2.0f); - //screeny += (screenHeight / 2.0f); - //CellOffset = new Vector2((float)screenx, (float)screeny); - - - - - - - - - - //float x = 0.0f; - //float y = 0.0f; - - //switch (Rotation) - //{ - // case HouseRotation.Angle90: - // x = CenterX - (point.Y * CellHalfWidth) + (point.X * CellHalfWidth); - // y = (point.Y * CellHalfHeight) + (point.X * CellHalfHeight); - // break; - - // case HouseRotation.Angle180: - // x = (point.Y * CellHalfWidth) + (point.X * CellHalfWidth); - // y = MiddleY + (point.Y * CellHalfHeight) - (point.X * CellHalfHeight); - // break; - - // case HouseRotation.Angle270: - // x = CenterX + (point.Y * CellHalfWidth) - (point.X * CellHalfWidth); - // y = BottomY - (point.Y * CellHalfHeight) - (point.X * CellHalfHeight); - // break; - - // case HouseRotation.Angle360: - // x = RightX - (point.X * CellHalfWidth) - (point.Y * CellHalfWidth); - // y = MiddleY + (point.X * CellHalfHeight) - (point.Y * CellHalfHeight); - // break; - //} - - //return new Vector2(x, y); - } - } - - - public enum HouseZoom - { - CloseZoom, - MediumZoom, - FarZoom - } - - - public enum HouseRotation - { - Angle90 = 0, - Angle180 = 1, - Angle270 = 2, - Angle360 = 3 - } -} diff --git a/TSOClient/tso.client/Rendering/Sim/SimModelBinding.cs b/TSOClient/tso.client/Rendering/Sim/SimModelBinding.cs deleted file mode 100644 index d5e5dd4bd..000000000 --- a/TSOClient/tso.client/Rendering/Sim/SimModelBinding.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using SimsLib.ThreeD; -using Microsoft.Xna.Framework.Graphics; -using TSOClient.Code.Data; - -namespace TSOClient.Code.Rendering.Sim -{ - /// - /// Sims are made of body parts, each body part is a binding. - /// A binding is made up of a mesh & a texture. - /// - public class SimModelBinding - { - public SimModelBinding(ulong bindingID) - { - BindingID = bindingID; - - var binding = SimCatalog.GetBinding(bindingID); - Mesh = SimCatalog.GetOutfitMesh(binding.MeshAssetID); - Texture = SimCatalog.GetOutfitTexture(binding.TextureAssetID); - } - - public ulong BindingID; - public Mesh Mesh; - public Texture2D Texture; - } -} diff --git a/TSOClient/tso.client/Rendering/Sim/SimRenderer.cs b/TSOClient/tso.client/Rendering/Sim/SimRenderer.cs deleted file mode 100644 index 9d3d3d6a3..000000000 --- a/TSOClient/tso.client/Rendering/Sim/SimRenderer.cs +++ /dev/null @@ -1,186 +0,0 @@ -/*This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. -If a copy of the MPL was not distributed with this file, You can obtain one at -http://mozilla.org/MPL/2.0/. - -The Original Code is the TSOClient. - -The Initial Developer of the Original Code is -ddfczm. All Rights Reserved. - -Contributor(s): ______________________________________. -*/ - -using System; -using System.Collections.Generic; -using System.Text; -using System.IO; -using TSOClient.ThreeD; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using SimsLib.ThreeD; -using TSOClient.VM; - -namespace TSOClient.Code.Rendering.Sim -{ - public class SimRenderer : ThreeDElement - { - private List m_Effects; - private float m_Rotation; - private SpriteBatch m_SBatch; - - private bool m_IsInvalidated = false; - - public SimRenderer() - { - m_Effects = new List(); - m_SBatch = new SpriteBatch(GameFacade.GraphicsDevice); - m_Effects.Add(new BasicEffect(GameFacade.GraphicsDevice, null)); - } - - public override void DeviceReset(GraphicsDevice Device) - { - m_IsInvalidated = true; - - Device.VertexDeclaration = new VertexDeclaration(Device, VertexPositionNormalTexture.VertexElements); - Device.RenderState.CullMode = CullMode.None; - - m_Sim.SimSkeleton = new Skeleton(); - m_Sim.SimSkeleton.Read(new MemoryStream(ContentManager.GetResourceFromLongID(0x100000005))); - - for (int i = 0; i < m_Sim.HeadBindings.Count; i++) - m_Sim.HeadBindings[i] = new SimModelBinding(m_Sim.HeadBindings[i].BindingID); - - for (int i = 0; i < m_Sim.BodyBindings.Count; i++) - m_Sim.BodyBindings[i] = new SimModelBinding(m_Sim.BodyBindings[i].BindingID); - - //Hands... (data abstraction = PITA!) - for (int i = 0; i < m_Sim.LeftHandBindings.FistBindings.Count; i++) - m_Sim.LeftHandBindings.FistBindings[i] = new SimModelBinding(m_Sim.LeftHandBindings.FistBindings[i].BindingID); - for (int i = 0; i < m_Sim.LeftHandBindings.IdleBindings.Count; i++) - m_Sim.LeftHandBindings.IdleBindings[i] = new SimModelBinding(m_Sim.LeftHandBindings.IdleBindings[i].BindingID); - for (int i = 0; i < m_Sim.LeftHandBindings.PointingBindings.Count; i++) - m_Sim.LeftHandBindings.PointingBindings[i] = new SimModelBinding(m_Sim.LeftHandBindings.PointingBindings[i].BindingID); - - for (int i = 0; i < m_Sim.RightHandBindings.FistBindings.Count; i++) - m_Sim.RightHandBindings.FistBindings[i] = new SimModelBinding(m_Sim.RightHandBindings.FistBindings[i].BindingID); - for (int i = 0; i < m_Sim.RightHandBindings.IdleBindings.Count; i++) - m_Sim.RightHandBindings.IdleBindings[i] = new SimModelBinding(m_Sim.RightHandBindings.IdleBindings[i].BindingID); - for (int i = 0; i < m_Sim.RightHandBindings.PointingBindings.Count; i++) - m_Sim.RightHandBindings.PointingBindings[i] = new SimModelBinding(m_Sim.RightHandBindings.PointingBindings[i].BindingID); - - //This can be rewritten - I've no idea why the rotation seems to be reset... - RotationZ = 262.32f; - - m_IsInvalidated = false; - } - - /// - /// Information about the sim we are rendering - /// - private TSOClient.VM.Sim m_Sim; - public TSOClient.VM.Sim Sim - { - get { return m_Sim; } - set - { - m_Sim = value; - } - } - - public override void Draw(GraphicsDevice device, ThreeDScene scene) - { - if (m_Sim == null) { return; } - - if(!m_IsInvalidated) - { - device.VertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements); - device.RenderState.CullMode = CullMode.None; - - var world = World; - - foreach (var effect in m_Effects) - { - effect.World = world; - effect.View = scene.Camera.View; - effect.Projection = scene.Camera.Projection; - - /** Head **/ - foreach (var binding in m_Sim.HeadBindings) - { - effect.Texture = binding.Texture; - effect.TextureEnabled = true; - effect.CommitChanges(); - effect.Begin(); - - foreach (var pass in effect.CurrentTechnique.Passes) - { - pass.Begin(); - binding.Mesh.Draw(device); - pass.End(); - } - - effect.End(); - } - - foreach (var binding in m_Sim.BodyBindings) - { - effect.Texture = binding.Texture; - effect.TextureEnabled = true; - effect.CommitChanges(); - effect.Begin(); - - foreach (var pass in effect.CurrentTechnique.Passes) - { - pass.Begin(); - binding.Mesh.Draw(device); - pass.End(); - } - - effect.End(); - } - - //Only draw idle bindings for now... - foreach (var binding in m_Sim.LeftHandBindings.IdleBindings) - { - effect.Texture = binding.Texture; - effect.TextureEnabled = true; - effect.CommitChanges(); - effect.Begin(); - - foreach (var pass in effect.CurrentTechnique.Passes) - { - pass.Begin(); - binding.Mesh.Draw(device); - pass.End(); - } - - effect.End(); - } - - foreach (var binding in m_Sim.RightHandBindings.IdleBindings) - { - effect.Texture = binding.Texture; - effect.TextureEnabled = true; - effect.CommitChanges(); - effect.Begin(); - - foreach (var pass in effect.CurrentTechnique.Passes) - { - pass.Begin(); - binding.Mesh.Draw(device); - pass.End(); - } - - effect.End(); - } - } - } - } - - public override void Update(GameTime Time) - { - m_Rotation += 0.001f; - GameFacade.Scenes.WorldMatrix = Matrix.CreateRotationX(m_Rotation); - } - } -} diff --git a/TSOClient/tso.client/Rendering/VisualSurroundPuppets.cs b/TSOClient/tso.client/Rendering/VisualSurroundPuppets.cs new file mode 100644 index 000000000..5c1dbcde2 --- /dev/null +++ b/TSOClient/tso.client/Rendering/VisualSurroundPuppets.cs @@ -0,0 +1,470 @@ +using FSO.Client.UI.Screens; +using FSO.Common.Domain.Realestate; +using FSO.Common.Model; +using FSO.LotView.Components; +using FSO.LotView.Model; +using FSO.Server.Protocol.Electron.Packets; +using FSO.Vitaboy; +using JWT.Builder; +using Microsoft.Xna.Framework; +using System.Diagnostics; + +namespace FSO.Client.Rendering +{ + class VisualSurroundPuppet : IDisposable + { + private readonly VisualSurroundPuppets Parent; + private readonly uint LotLocation; + + private SurroundPuppet Puppet; + private long StartTimestamp; + + private SimAvatar TargetAvatar; + private AvatarComponent TargetAvatarComponent; + private Dictionary AnimByName = []; + + private Blueprint LastBp; + private bool ReloadPuppet = false; + private bool UpdateAppearances = false; + private string[] LastAppearances; + private Vector3 LotOffset; + + private SurroundPuppetDelta MissingDelta = SurroundPuppetDelta.Required; + + public bool IsLeaving => Puppet.Delta.HasFlag(SurroundPuppetDelta.Leaving); + + public VisualSurroundPuppet(VisualSurroundPuppets parent, uint lotLocation) + { + Parent = parent; + LotLocation = lotLocation; + } + + private void RecalculateOffset(uint parentLocation) + { + var loc = MapCoordinates.Unpack(LotLocation).ToPoint(); + var parent = MapCoordinates.Unpack(parentLocation).ToPoint(); + + var relative = LotTransitionInfo.RelativeChangeCityToLot(loc - parent); + + var width = LastBp.Width; + var height = LastBp.Height; + + LotOffset = new Vector3(relative.X * (width - 2), relative.Y * (height - 2), 0); + } + + public void SetPuppet(SurroundPuppet puppet, long startTimestamp) + { + Puppet.ApplyDelta(puppet); + + if (puppet.Delta.HasFlag(SurroundPuppetDelta.BodyInfo)) + { + ReloadPuppet = true; + } + + if (puppet.Delta.HasFlag(SurroundPuppetDelta.Appearances)) + { + UpdateAppearances = true; + } + + MissingDelta &= ~puppet.Delta; + + StartTimestamp = startTimestamp; + } + + public void PreDraw(uint parentLocation, Blueprint bp, long renderTimestamp) + { + if (MissingDelta != 0) + { + // Need to see at least one delta for certain fields to draw at all. + return; + } + + if ((bp != LastBp || ReloadPuppet || TargetAvatar == null) && bp != null) + { + if (TargetAvatar == null) + { + TargetAvatar = new SimAvatar(Content.Content.Get().AvatarSkeletons.Get(Puppet.SkeletonName+".skel")); + } + + TargetAvatar.Appearance = (AppearanceType)Puppet.SkinTone; + TargetAvatar.Head = FSO.Content.Content.Get().AvatarOutfits.Get(Puppet.HeadOutfit); + TargetAvatar.Body = FSO.Content.Content.Get().AvatarOutfits.Get(Puppet.BodyOutfit); + + if (Puppet.SkeletonName == "adult") + { + TargetAvatar.Handgroup = TargetAvatar.Body; + } + + if (bp != LastBp || TargetAvatarComponent == null) + { + LastBp?.RemoveAvatar(TargetAvatarComponent); + + TargetAvatarComponent = new() + { + Avatar = TargetAvatar + }; + TargetAvatarComponent.blueprint = bp; + + bp.AddAvatar(TargetAvatarComponent); + + LastBp = bp; + + RecalculateOffset(parentLocation); // Can somehow end up wildly negative + } + + LastBp = bp; + ReloadPuppet = false; + } + + if (TargetAvatar != null) + { + if (UpdateAppearances) + { + var oldApr = LastAppearances ?? []; + var newApr = Puppet.Appearances ?? []; + + var toAdd = newApr.Where(x => Array.IndexOf(oldApr, x) == -1); + var toRemove = oldApr.Where(x => Array.IndexOf(newApr, x) == -1); + + var appearances = Content.Content.Get().AvatarAppearances; + + foreach (var aprN in toAdd) + { + var apr = appearances.Get(aprN); + if (apr != null) TargetAvatar.AddAccessory(apr); + } + + foreach (var aprN in toRemove) + { + var apr = appearances.Get(aprN); + if (apr != null) TargetAvatar.RemoveAccessory(apr); + } + + LastAppearances = newApr; + } + + // Update the avatar's position and animation based on the frame timing. + float fraction = (renderTimestamp - StartTimestamp) / ((float)Stopwatch.Frequency / 30f); + + float totalWeight = 0f; + foreach (var state in Puppet.Animations) + { + totalWeight += state.Weight; + if (!state.EndReached && state.Name != null) + { + float visualFrame = state.CurrentFrame; + if (state.PlayingBackwards) visualFrame -= state.Speed * fraction; + else visualFrame += state.Speed * fraction; + + if (!AnimByName.TryGetValue(state.Name, out var anim)) + { + anim = Content.Content.Get().AvatarAnimations.Get(state.Name + ".anim"); + } + + if (anim != null) + { + Animator.RenderFrame(TargetAvatar, anim, (int)visualFrame, visualFrame % 1, state.Weight / totalWeight); + } + } + } + + var pos = Puppet.VisualPositionStart; + var vel = Puppet.Velocity; + bool visible = Puppet.Delta.HasFlag(SurroundPuppetDelta.Leaving) ? !Parent.IsPresentElsewhere(Puppet.PersistID) : true; + + TargetAvatar.ReloadSkeleton(); + TargetAvatarComponent.Position = new Vector3(pos.X, pos.Y, pos.Z) + fraction * new Vector3(vel.X, vel.Y, vel.Z) + LotOffset; + TargetAvatarComponent.RadianDirection = (double)(pos.W - Puppet.Velocity.W * fraction); + TargetAvatarComponent.Visible = visible; + } + } + + public void Dispose() + { + LastBp?.RemoveAvatar(TargetAvatarComponent); + } + } + + public class VisualSurroundPuppets + { + private const int QUEUE_LENGTH_MAX = 3; + + private readonly CoreGameScreen Screen; + private readonly Dictionary> LotIdToPuppet = []; + + private readonly HashSet ExpectedAvatars = []; + private readonly HashSet ExpectedLots = []; + + private readonly Queue TickQueue = []; + private readonly long TickRate; + + private Blueprint LastBp; + private bool Instant = false; + + private long LastTimestamp; + + public VisualSurroundPuppets(CoreGameScreen screen) + { + Screen = screen; + TickRate = Stopwatch.Frequency / 30; + } + + private Point CalculateOffset(uint parentLocation, uint lotLocation) + { + var loc = MapCoordinates.Unpack(lotLocation).ToPoint(); + var parent = MapCoordinates.Unpack(parentLocation).ToPoint(); + + return loc - parent; + } + + public void PreDraw() + { + RunTicks(); + + // Try update animation and position for any surround puppets + + var renderTimestamp = Stopwatch.GetTimestamp(); + + var vm = Screen.VisualVM; + + if (vm == null || !vm.Ready || vm.FSOVAsyncLoading) + { + return; + } + + uint parentLocation = vm.TSOState.LotID; + Blueprint bp = vm.Context.Blueprint; + bool bpChanged = bp != LastBp; + + List lotIdsToDelete = null; + + foreach (var lot in LotIdToPuppet) + { + if (bpChanged) + { + var delta = CalculateOffset(parentLocation, lot.Key); + + if ((delta.X == 0 && delta.Y == 0) || Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1) + { + lotIdsToDelete ??= []; + + lotIdsToDelete.Add(lot.Key); + + continue; + } + } + + foreach (var puppet in lot.Value) + { + puppet.Value.PreDraw(parentLocation, bp, renderTimestamp); + } + } + + if (lotIdsToDelete != null) + { + DeleteLots(lotIdsToDelete); + } + + LastBp = bp; + } + + private void RunTicks() + { + uint myID = Screen.VisualVM?.MyUID ?? 0; + + var now = Stopwatch.GetTimestamp(); + + int ticksToRun = Instant ? TickQueue.Count : (int)((now - LastTimestamp) / TickRate); + + if (ticksToRun >= TickQueue.Count) + { + ticksToRun = TickQueue.Count; + Instant = true; + } + + if (TickQueue.Count > QUEUE_LENGTH_MAX && ticksToRun < TickQueue.Count - 1) + { + // Try and catch up a little + ticksToRun++; + Instant = true; + } + + for (int i = 0; i < ticksToRun; i++) + { + var tick = TickQueue.Dequeue(); + + if (Instant) + { + LastTimestamp = Stopwatch.GetTimestamp(); + } + else + { + LastTimestamp += TickRate; + } + + ProcessTick(myID, tick); + } + + Instant = false; + } + + private void DeleteLots(T lots) where T : IEnumerable + { + foreach (var id in lots) + { + if (LotIdToPuppet.TryGetValue(id, out var puppets)) + { + foreach (var puppet in puppets) + { + puppet.Value.Dispose(); + } + + LotIdToPuppet.Remove(id); + } + } + } + + private void ProcessTick(uint myID, SurroundPuppetTick tick) + { + var isTransitioning = Screen.VisualVM != Screen.vm; + ExpectedLots.Clear(); + ExpectedLots.UnionWith(LotIdToPuppet.Keys); + + foreach (ref var lot in tick.Lots.AsSpan()) + { + if (lot.Outdated) + { + ExpectedLots.Remove(lot.LotLocation); + continue; + } + + if (!LotIdToPuppet.TryGetValue(lot.LotLocation, out var puppets)) + { + puppets = []; + LotIdToPuppet[lot.LotLocation] = puppets; + } + + ExpectedAvatars.Clear(); + ExpectedAvatars.UnionWith(puppets.Keys); + + foreach (var puppet in lot.Puppets) + { + if (puppet.PersistID == myID) + { + // You can't see a puppet of yourself... + continue; + } + + if (!puppets.TryGetValue(puppet.PersistID, out var visualPuppet)) + { + visualPuppet = new VisualSurroundPuppet(this, lot.LotLocation); + puppets[puppet.PersistID] = visualPuppet; + } + + visualPuppet.SetPuppet(puppet, LastTimestamp); + + ExpectedAvatars.Remove(puppet.PersistID); + } + + foreach (var toRemove in ExpectedAvatars) + { + if (puppets.TryGetValue(toRemove, out var visualPuppet)) + { + visualPuppet.Dispose(); + puppets.Remove(toRemove); + } + } + + ExpectedLots.Remove(lot.LotLocation); + } + + if (!isTransitioning) + { + DeleteLots(ExpectedLots); + } + } + + public bool ProcessInstantly(in SurroundPuppetTick tick) + { + ExpectedLots.Clear(); + ExpectedLots.UnionWith(LotIdToPuppet.Keys); + + foreach (ref var lot in tick.Lots.AsSpan()) + { + if (LotIdToPuppet.TryGetValue(lot.LotLocation, out var puppets)) + { + ExpectedAvatars.Clear(); + ExpectedAvatars.UnionWith(puppets.Keys); + + foreach (ref var puppet in lot.Puppets.AsSpan()) + { + if (puppets.TryGetValue(puppet.PersistID, out var visual)) + { + // Check for changes that require instant playback? + } + else + { + // New avatar + return true; + } + + ExpectedAvatars.Remove(puppet.PersistID); + } + + // Deleted avatar (ignore) + // ExpectedAvatars.Count > 0 + } + else + { + // New lot + return true; + } + + ExpectedLots.Remove(lot.LotLocation); + } + + // If any lot is deleted + return ExpectedLots.Count > 0; + } + + private void EnqueueTick(in SurroundPuppetTick tick) + { + TickQueue.Enqueue(tick); + + if (ProcessInstantly(tick)) + { + Instant = true; + } + } + + public void Process(FSOVMSurroundPuppets message) + { + foreach (var tick in message.Ticks) + { + EnqueueTick(tick); + } + } + + public bool IsPresentElsewhere(uint pid) + { + var vm = Screen.VisualVM; + if (vm.Ready && !vm.FSOVAsyncLoading && vm.GetObjectByPersist(pid) != null) + { + return true; + } + + foreach (var lot in LotIdToPuppet) + { + foreach (var puppet in lot.Value) + { + if (!puppet.Value.IsLeaving) + { + return true; + } + } + } + + return false; + } + } +} diff --git a/TSOClient/tso.client/TSOClient.csproj b/TSOClient/tso.client/TSOClient.csproj deleted file mode 100644 index a7d032044..000000000 --- a/TSOClient/tso.client/TSOClient.csproj +++ /dev/null @@ -1,390 +0,0 @@ - - - {FB0242B5-0866-4C2E-9040-4794B55DA6AC} - {6D335F3A-9D43-41b4-9D22-F6F17C4BE596};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Debug - x86 - WinExe - Properties - TSOClient - Project Dollhouse Client - v3.5 - v3.1 - Windows - 13351cec-20ee-47e9-9c2e-5757b9a691de - Project Dollhouse Client_32512.ico - GameThumbnail.png - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - app.manifest - - - true - full - false - bin\x86\Debug - DEBUG;TRACE;WINDOWS - prompt - 4 - true - false - x86 - false - - - pdbonly - true - bin\x86\Release - TRACE;WINDOWS - prompt - 4 - true - false - x86 - true - - - - - - False - LuaInterface_2.0.3\LuaInterface.dll - - - False - True - - - False - True - - - False - - - False - .\NAudio.dll - - - False - - - 3.5 - - - - - - False - - - - - - - Form - - - TSOSceneInspector.cs - - - Form - - - TSOClientFindAssetSearch.cs - - - Form - - - TSOClientTools.cs - - - Form - - - TSOClientUIInspector.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - Code - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - GlobalSettings.settings - - - True - True - Resource.resx - - - - - - - - - - - - - - - - - - - e28e55cd-2f79-44af-9579-c3d2c42b28c3 - False - - - - - False - .NET Framework Client Profile - false - - - False - .NET Framework 2.0 %28x86%29 - true - - - False - .NET Framework 3.0 %28x86%29 - false - - - False - .NET Framework 3.5 - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Microsoft XNA Framework Redistributable 3.1 - true - - - - - - - - - SettingsSingleFileGenerator - GlobalSettings.Designer.cs - - - - - {25A5DA9E-88E8-4BC2-AE80-45935276790E} - GonzoNet - - - {07F742C5-C66A-4D1E-A761-458E08D4E302} - ProtocolAbstractionLibraryD - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - tso.common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - tso.content - - - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4} - TSO.Debug - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - TSO.Files - - - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF} - TSO.Simantics - - - {072781D8-51EC-4143-9CAE-DAF50177D3AD} - tso.hit - - - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} - TSO.Vitaboy.Engine - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - TSO.Vitaboy.Model - - - {0B3E7EEB-032E-451C-9D4F-146BC43F3761} - TSO.World - - - - - TSOSceneInspector.cs - Designer - - - TSOClientFindAssetSearch.cs - Designer - - - TSOClientTools.cs - Designer - - - TSOClientUIInspector.cs - Designer - - - ResXFileCodeGenerator - Resource.Designer.cs - Designer - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.client/TSOClient.csproj.Debug.cachefile b/TSOClient/tso.client/TSOClient.csproj.Debug.cachefile deleted file mode 100644 index 4f8ca7e48..000000000 --- a/TSOClient/tso.client/TSOClient.csproj.Debug.cachefile +++ /dev/null @@ -1,19 +0,0 @@ -Content\Fonts\ProjectDollhouse_10px.xnb -Content\Effects\VerShader.xnb -Content\Fonts\ProjectDollhouse_12px.xnb -Content\ComicSans.spritefont -Content\Effects\2DWorldBatch.xnb -Content\login.xnb -Content\Effects\PixShader.xnb -Content\Effects\colorpoly2D.xnb -Content\Fonts\ProjectDollhouse_16px.xnb -Content\Textures\gridTexture.xnb -Content\ComicSansSmall.xnb -Content\Effects\TerrainSplat.xnb -Content\ComicSans.xnb -Content\Effects\HouseBatch.xnb -Content\ComicSansSmall.spritefont -Content\Effects\TerrainSplat2.xnb -Content\Fonts\ProjectDollhouse_14px.xnb -libs\SciLexer64.dll -libs\SciLexer.dll diff --git a/TSOClient/tso.client/TSOClient.sln b/TSOClient/tso.client/TSOClient.sln deleted file mode 100644 index 53028985e..000000000 --- a/TSOClient/tso.client/TSOClient.sln +++ /dev/null @@ -1,174 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TSOClient", "TSOClient.csproj", "{FB0242B5-0866-4C2E-9040-4794B55DA6AC}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.common", "..\tso.common\tso.common.csproj", "{C42962A1-8796-4F47-9DCD-79ED5904D8CA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.content", "..\tso.content\tso.content.csproj", "{C0068DF7-F2E8-4399-846D-556BF9A35C00}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.simantics", "..\tso.simantics\tso.simantics.csproj", "{5EDDEFD2-C850-49C1-812D-DDEFF09125EF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.hit", "..\tso.sound\tso.hit.csproj", "{072781D8-51EC-4143-9CAE-DAF50177D3AD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.vitaboy.engine", "..\tso.vitaboy.engine\tso.vitaboy.engine.csproj", "{FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.vitaboy.model", "..\tso.vitaboy.model\tso.vitaboy.model.csproj", "{9D9558A9-755E-43F9-8BB6-B26F365F5042}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.world", "..\tso.world\tso.world.csproj", "{0B3E7EEB-032E-451C-9D4F-146BC43F3761}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.files", "..\tso.files\tso.files.csproj", "{18583453-A970-4AC5-83B1-2D6BFDF94C24}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tso.debug", "..\tso.debug\tso.debug.csproj", "{43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GonzoNet", "..\..\Other\libs\GonzoNet\GonzoNet\Project Files\VS2k8\GonzoNet.csproj", "{25A5DA9E-88E8-4BC2-AE80-45935276790E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProtocolAbstractionLibraryD", "..\..\Other\libs\ProtocolAbstractionLibraryD\Project Files\VS2k8\ProtocolAbstractionLibraryD\ProtocolAbstractionLibraryD.csproj", "{07F742C5-C66A-4D1E-A761-458E08D4E302}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|Mixed Platforms = Debug|Mixed Platforms - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|Mixed Platforms = Release|Mixed Platforms - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Debug|Any CPU.ActiveCfg = Debug|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Debug|Mixed Platforms.Build.0 = Debug|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Debug|x86.ActiveCfg = Debug|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Debug|x86.Build.0 = Debug|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Release|Any CPU.ActiveCfg = Release|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Release|Mixed Platforms.Build.0 = Release|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Release|x86.ActiveCfg = Release|x86 - {FB0242B5-0866-4C2E-9040-4794B55DA6AC}.Release|x86.Build.0 = Release|x86 - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Debug|x86.ActiveCfg = Debug|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Release|Any CPU.Build.0 = Release|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C42962A1-8796-4F47-9DCD-79ED5904D8CA}.Release|x86.ActiveCfg = Release|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Debug|x86.ActiveCfg = Debug|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Release|Any CPU.Build.0 = Release|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {C0068DF7-F2E8-4399-846D-556BF9A35C00}.Release|x86.ActiveCfg = Release|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Debug|x86.ActiveCfg = Debug|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Release|Any CPU.Build.0 = Release|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF}.Release|x86.ActiveCfg = Release|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Debug|x86.ActiveCfg = Debug|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Release|Any CPU.Build.0 = Release|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {072781D8-51EC-4143-9CAE-DAF50177D3AD}.Release|x86.ActiveCfg = Release|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Debug|x86.ActiveCfg = Debug|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Release|Any CPU.Build.0 = Release|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163}.Release|x86.ActiveCfg = Release|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Debug|x86.ActiveCfg = Debug|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Release|Any CPU.Build.0 = Release|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {9D9558A9-755E-43F9-8BB6-B26F365F5042}.Release|x86.ActiveCfg = Release|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Debug|x86.ActiveCfg = Debug|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Release|Any CPU.Build.0 = Release|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {0B3E7EEB-032E-451C-9D4F-146BC43F3761}.Release|x86.ActiveCfg = Release|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Debug|x86.ActiveCfg = Debug|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Release|Any CPU.Build.0 = Release|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {18583453-A970-4AC5-83B1-2D6BFDF94C24}.Release|x86.ActiveCfg = Release|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Debug|x86.ActiveCfg = Debug|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Release|Any CPU.Build.0 = Release|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4}.Release|x86.ActiveCfg = Release|Any CPU - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Debug|Any CPU.ActiveCfg = Debug|x86 - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Debug|x86.ActiveCfg = Debug|x86 - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Release|Any CPU.ActiveCfg = Release|x86 - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {E28E55CD-2F79-44AF-9579-C3D2C42B28C3}.Release|x86.ActiveCfg = Release|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Debug|Any CPU.ActiveCfg = Debug|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Debug|x86.ActiveCfg = Debug|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Release|Any CPU.ActiveCfg = Release|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Release|Mixed Platforms.ActiveCfg = Release|x86 - {DF76A78E-9356-4236-96EA-E025D8ACC67F}.Release|x86.ActiveCfg = Release|x86 - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Debug|x86.ActiveCfg = Debug|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Release|Any CPU.Build.0 = Release|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {25A5DA9E-88E8-4BC2-AE80-45935276790E}.Release|x86.ActiveCfg = Release|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Debug|x86.ActiveCfg = Debug|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Release|Any CPU.Build.0 = Release|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {07F742C5-C66A-4D1E-A761-458E08D4E302}.Release|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/TSOClient/tso.client/TSOGame.cs b/TSOClient/tso.client/TSOGame.cs index 6bc31ce8f..5122b5022 100644 --- a/TSOClient/tso.client/TSOGame.cs +++ b/TSOClient/tso.client/TSOGame.cs @@ -1,30 +1,28 @@ -using System; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using System.Threading; -using FSO.Common.Rendering.Framework; -using FSO.LotView; -using FSO.HIT; -using FSO.Client.Network; -using FSO.Client.UI; using FSO.Client.GameContent; -using Ninject; +using FSO.Client.Network; using FSO.Client.Regulators; -using FSO.Server.Protocol.Voltron.DataService; +using FSO.Client.UI; +using FSO.Common; +using FSO.Common.Audio; using FSO.Common.DataService; -using FSO.Server.DataService.Providers.Client; using FSO.Common.Domain; +using FSO.Common.Rendering.Framework; using FSO.Common.Utils; -using FSO.Common; -using Microsoft.Xna.Framework.Audio; -using FSO.HIT.Model; -using FSO.UI.Model; -using FSO.Files.RC; using FSO.Files.Formats.IFF; +using FSO.Files.RC; +using FSO.HIT; +using FSO.HIT.Model; +using FSO.LotView; +using FSO.LotView.Model; +using FSO.Server.DataService.Providers.Client; +using FSO.Server.Protocol.Voltron.DataService; using FSO.UI.Framework; +using FSO.UI.Model; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Graphics; using MSDFData; -using FSO.Common.Audio; -using FSO.LotView.Model; +using Ninject; namespace FSO.Client { @@ -36,18 +34,23 @@ public class TSOGame : FSO.Common.Rendering.Framework.Game public UILayer uiLayer; public _3DLayer SceneMgr; - public TSOGame() : base() + public TSOGame() : base() { /* var test = new Utils.TestFunctions.ProjectionTest(); test.TestCombo(); */ - + GameFacade.Game = this; //if (GameFacade.DirectX) TimedReferenceController.SetMode(CacheType.PERMANENT); Content.RootDirectory = FSOEnvironment.GFXContentDir; Graphics.SynchronizeWithVerticalRetrace = true; + if (GraphicsAdapter.DefaultAdapter.IsProfileSupported(GraphicsProfile.HiDef)) + { + Graphics.GraphicsProfile = GraphicsProfile.HiDef; + } + FSOEnvironment.DPIScaleFactor = GlobalSettings.Default.DPIScaleFactor; if (!FSOEnvironment.SoftwareDepth) { @@ -68,7 +71,8 @@ public TSOGame() : base() { GameThread.Game = Thread.CurrentThread; Thread.CurrentThread.Name = "Game"; - } catch + } + catch { //fails on android } @@ -106,7 +110,6 @@ void Window_ClientSizeChanged(object sender, EventArgs e) /// protected override void Initialize() { - System.Net.ServicePointManager.DefaultConnectionLimit = 32; var kernel = new StandardKernel( new RegulatorsModule(), new NetworkModule(), @@ -127,7 +130,7 @@ protected override void Initialize() if (settings.Lighting) { if (settings.Shadows3D) - settings.LightingMode = 2; + settings.LightingMode = 3; else settings.LightingMode = 1; } @@ -178,9 +181,10 @@ protected override void Initialize() FSO.Content.Content.TS1Hybrid = GlobalSettings.Default.TS1HybridEnable; FSO.Content.Content.TS1HybridBasePath = GlobalSettings.Default.TS1HybridPath; - FSO.Content.Content.InitBasic(GlobalSettings.Default.StartupPath, GraphicsDevice); FSO.SimAntics.VMAvatar.MissingIconProvider = FSO.Client.UI.Model.UIIconCache.GetObject; FSO.SimAntics.VM.TestBinding = "Value"; + + FSO.Content.Content.InitBasic(GlobalSettings.Default.StartupPath, GraphicsDevice); //VMContext.InitVMConfig(); base.Initialize(); @@ -198,7 +202,11 @@ protected override void Initialize() GameFacade.Emojis = new Common.Rendering.Emoji.EmojiProvider(GraphicsDevice); CurLoader.BmpLoaderFunc = Files.ImageLoader.FromStream; GameFacade.Cursor = new CursorManager(GraphicsDevice); - if (!GameFacade.Linux) GameFacade.Cursor.Init(FSO.Content.Content.Get().GetPath(""), false); + + if (!FSOEnvironment.MissingTSO) + { + GameFacade.Cursor.Init(FSO.Content.Content.Get().GetPath(""), false); + } /** Init any computed values **/ GameFacade.Init(); @@ -216,10 +224,12 @@ protected override void Initialize() GraphicsDevice.RasterizerState = new RasterizerState() { CullMode = CullMode.None }; - try { + try + { var audioTest = new SoundEffect(new byte[2], 44100, AudioChannels.Mono); //initialises XAudio. audioTest.CreateInstance().Play(); - } catch (Exception e) + } + catch (Exception e) { FSOProgram.ShowDialog("Failed to initialize audio: \r\n\r\n" + e.StackTrace); } @@ -267,7 +277,7 @@ private void SaveGraphicsModePreference(GlobalGraphicsMode obj) /// public new void Run() { - Run(GameRunBehavior.Synchronous); + Run(GameRunBehavior.Synchronous); } /// @@ -288,7 +298,7 @@ void LostFocus(object sender, EventArgs e) GameFacade.Focus = false; } - protected override void OnExiting(object sender, EventArgs args) + protected override void OnExiting(object sender, ExitingEventArgs args) { base.OnExiting(sender, args); var kernel = FSOFacade.Kernel; @@ -298,6 +308,8 @@ protected override void OnExiting(object sender, EventArgs args) kernel.Get()?.Disconnect(); } GameThread.SetKilled(); + + args.Cancel = !(FSOFacade.Controller?.CloseAttempt() ?? true); } /// @@ -320,7 +332,7 @@ protected override void LoadContent() GameFacade.EdithFont.AddSize(12, Content.Load("Fonts/Trebuchet_12px")); GameFacade.EdithFont.AddSize(14, Content.Load("Fonts/Trebuchet_14px")); */ - + GameFacade.VectorFont = new MSDFFont(Content.Load("../Fonts/simdialogue")); GameFacade.EdithVectorFont = new MSDFFont(Content.Load("../Fonts/trebuchet")); @@ -329,16 +341,16 @@ protected override void LoadContent() GameFacade.EdithVectorFont.YOff = 11; MSDFFont.MSDFEffect = Content.Load("Effects/MSDFFont"); - vitaboyEffect = Content.Load((FSOEnvironment.GLVer == 2)?"Effects/VitaboyiOS":"Effects/Vitaboy"); + vitaboyEffect = Content.Load((FSOEnvironment.GLVer == 2) ? "Effects/VitaboyiOS" : "Effects/Vitaboy"); uiLayer = new UILayer(this); } catch (Exception e) { - FSOProgram.ShowDialog("Content could not be loaded. Make sure that the FreeSO content has been compiled! (ContentSrc/TSOClientContent.mgcb) \r\n\r\n"+e.ToString()); + FSOProgram.ShowDialog("Content could not be loaded. Make sure that the FreeSO content has been compiled! (ContentSrc/TSOClientContent.mgcb) \r\n\r\n" + e.ToString()); Exit(); Environment.Exit(0); } - + FSO.Vitaboy.Avatar.setVitaboyEffect(vitaboyEffect); } @@ -350,7 +362,7 @@ protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } - + /// /// Allows the game to run logic such as updating the world, /// checking for collisions, gathering input, and playing audio. diff --git a/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarManageDialog.cs b/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarManageDialog.cs new file mode 100644 index 000000000..b0a730142 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarManageDialog.cs @@ -0,0 +1,184 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using FSO.Server.Protocol.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework.Graphics; +using System.Numerics; + +namespace FSO.Client.UI.Archive.Management +{ + internal class UIArchiveAvatarManageDialog : UIArchiveDialog + { + private ArchiveManagement Management; + + private UIGenericTable AvatarTable; + private UITextBox SearchBox; + private UIButton IPBansButton; + + private UIListBoxTextStyle ListBoxColors; + private Texture2D AdminActionsButtonTexture; + + private ArchiveDbUser User; + private List Data; + public UIArchiveAvatarManageDialog(ArchiveManagement management, ArchiveDbUser user) : base(UIDialogStyle.Close, true) + { + User = user; + Management = management; + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + AdminActionsButtonTexture = ui.Get("archive_burgermenu.png").Get(gd); + + Caption = GetString("57", user.Name); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + var searchContainer = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + searchContainer.Add(new UILabel() + { + Caption = GetString("64") + }); + + searchContainer.Add(SearchBox = new UITextBox() { }); + SearchBox.SetSize(200, 25); + + searchContainer.AutoSize(); + + vbox.Add(searchContainer); + + vbox.Add(new UISpacer(1, 8)); + + vbox.Add(AvatarTable = new UIGenericTable([ + new UITableColumn(GetString("58"), 128), + new UITableColumn(GetString("59"), 128), + new UITableColumn("", 14), + ]) + { Loading = true }); + + /* + var vbox2 = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + + vbox.Add(new UISpacer(1, 8)); + + vbox2.Add(IPBansButton = new UIButton() + { + Caption = GetString("56") + }); + + vbox2.AutoSize(); //TODO: somehow force horiz size from parent? + + vbox.Add(vbox2); + */ + + DynamicOverlay.Add(vbox); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SearchBox.OnChange += (elem) => UpdateAvatarTable(); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + + CloseButton.OnButtonClick += (elem) => + { + UIScreen.RemoveDialog(this); + }; + + Fetch(); + } + + + private void Fetch() + { + Task.Run(() => + { + var avatars = Management.GetAvatars(User.ID); + + GameThread.InUpdate(() => + { + Data = avatars; + AvatarTable.Loading = false; + UpdateAvatarTable(); + }); + }); + } + + private void UpdateAvatarTable() + { + var query = (SearchBox.CurrentText ?? "").ToLower(); + + if (Data == null) + { + // Empty the list + AvatarTable.Items.Clear(); + } + else + { + var myItems = Data + .Where(x => x.Name.ToLower().Contains(query)) + .Select((ArchiveDbAvatar x) => + { + var actionButton = new UIButton(AdminActionsButtonTexture); + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, x); + }; + + return new UIListBoxItem(x, new object[] { x.Name, x.LotName, actionButton }) + { + CustomStyle = ListBoxColors, + }; + }); + + AvatarTable.Items.Clear(); + + AvatarTable.Items.AddRange(myItems); + } + + AvatarTable.Items = AvatarTable.Items; + Invalidate(); + } + + private void DeleteAvatar(ArchiveDbAvatar avatar) + { + UIAlert.Prompt(GetString("69", avatar.Name), (result, alert) => + { + if (result) + { + try + { + Management.DeleteAvatar((int)avatar.ID); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void TransferAvatar(ArchiveDbAvatar avatar) + { + UIScreen.GlobalShowDialog(new UIArchiveAvatarMigrateDialog(Management, avatar), true); + } + + private void OpenActions(UIElement anchor, ArchiveDbAvatar avatar) + { + var items = new List + { + new UIContextMenuItem(GetString("65"), () => { DeleteAvatar(avatar); }), + new UIContextMenuItem(GetString("74"), () => { TransferAvatar(avatar); }) + }; + + new UIContextMenu(anchor, items, AvatarTable); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarMigrateDialog.cs b/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarMigrateDialog.cs new file mode 100644 index 000000000..b9e6269dc --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/Management/UIArchiveAvatarMigrateDialog.cs @@ -0,0 +1,188 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using FSO.Server.Protocol.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive.Management +{ + internal class UIArchiveAvatarMigrateDialog : UIArchiveDialog + { + private ArchiveManagement Management; + + private UIGenericTable UserTable; + private UITextBox SearchBox; + private UIButton TransferButton; + + private UIListBoxTextStyle ListBoxColors; + + private ArchiveDbAvatar Avatar; + private List Data; + + public UIArchiveAvatarMigrateDialog(ArchiveManagement management, ArchiveDbAvatar avatar) : base(UIDialogStyle.Close, true) + { + Avatar = avatar; + Management = management; + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + + Caption = GetString("74"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + UILabel desc; + + vbox.Add(desc = new UILabel() + { + Caption = GetString("75", Avatar.Name), + Wrapped = true + }); + + desc.Size = new Vector2(200, 48); + + var searchContainer = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + searchContainer.Add(new UILabel() + { + Caption = GetString("64") + }); + + searchContainer.Add(SearchBox = new UITextBox() { }); + SearchBox.SetSize(200, 25); + + searchContainer.AutoSize(); + + vbox.Add(searchContainer); + + vbox.Add(new UISpacer(1, 8)); + + vbox.Add(UserTable = new UIGenericTable([ + new UITableColumn(GetString("49"), 128), + new UITableColumn(GetString("50"), 96), + new UITableColumn(GetString("51"), 62), + ]) + { Loading = true }); + + var vbox2 = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + + vbox.Add(new UISpacer(1, 8)); + + vbox2.Add(TransferButton = new UIButton() + { + Caption = GetString("76") + }); + + vbox2.AutoSize(); //TODO: somehow force horiz size from parent? + + vbox.Add(vbox2); + + DynamicOverlay.Add(vbox); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SearchBox.OnChange += (elem) => UpdateUserTable(); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + + UserTable.OnChange += (elem) => + { + TransferButton.Disabled = UserTable.SelectedIndex == -1; + }; + + CloseButton.OnButtonClick += (elem) => + { + UIScreen.RemoveDialog(this); + }; + + TransferButton.OnButtonClick += Transfer; + TransferButton.Disabled = true; + + Fetch(); + } + + private void Transfer(UIElement button) + { + var selected = UserTable.SelectedItem; + + if (selected == null) + { + return; + } + + var user = (ArchiveDbUser)selected.Data; + + UIAlert.Prompt(GetString("77", [Avatar.Name, user.Name]), (result, alert) => + { + if (result) + { + try + { + Management.MigrateAvatar((int)Avatar.ID, (int)user.ID); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + UIScreen.RemoveDialog(this); + } + }); + } + + private void Fetch() + { + Task.Run(() => + { + var users = Management.GetUsers(); + + GameThread.InUpdate(() => + { + Data = users; + UserTable.Loading = false; + UpdateUserTable(); + }); + }); + } + + private string GetStatusIcon(ArchiveDbUserStatus status) + { + return status.ToString(); + } + + private void UpdateUserTable() + { + var query = (SearchBox.CurrentText ?? "").ToLower(); + + if (Data == null) + { + // Empty the list + UserTable.Items.Clear(); + } + else + { + var myItems = Data + .Where(x => x.Name.ToLower().Contains(query)) + .Select((ArchiveDbUser x) => + { + return new UIListBoxItem(x, [x.Name, x.AvatarCount.ToString(), GetStatusIcon(x.Status)]) + { + CustomStyle = ListBoxColors, + }; + }); + + UserTable.Items.Clear(); + + UserTable.Items.AddRange(myItems); + } + + UserTable.Items = UserTable.Items; + Invalidate(); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/Management/UIArchiveBanManageDialog.cs b/TSOClient/tso.client/UI/Archive/Management/UIArchiveBanManageDialog.cs new file mode 100644 index 000000000..a63c08612 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/Management/UIArchiveBanManageDialog.cs @@ -0,0 +1,198 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using FSO.Server.Protocol.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive.Management +{ + internal class UIArchiveBanManageDialog : UIArchiveDialog + { + private ArchiveManagement Management; + + private UIGenericTable IpTable; + private UITextBox SearchBox; + private UIButton AddIpButton; + + private UIListBoxTextStyle ListBoxColors; + private Texture2D AdminActionsButtonTexture; + + private List Data; + + public UIArchiveBanManageDialog(ArchiveManagement management) : base(UIDialogStyle.Close, true) + { + Management = management; + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + AdminActionsButtonTexture = ui.Get("archive_burgermenu.png").Get(gd); + + Caption = GetString("56"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + var searchContainer = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + searchContainer.Add(new UILabel() + { + Caption = GetString("64") + }); + + searchContainer.Add(SearchBox = new UITextBox() { }); + SearchBox.SetSize(200, 25); + + searchContainer.AutoSize(); + + vbox.Add(searchContainer); + + vbox.Add(new UISpacer(1, 8)); + + vbox.Add(IpTable = new UIGenericTable([ + new UITableColumn(GetString("60"), 128), + new UITableColumn(GetString("61"), 128), + new UITableColumn("", 14), + ]) + { Loading = true }); + + + var vbox2 = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + + vbox.Add(new UISpacer(1, 8)); + + vbox2.Add(AddIpButton = new UIButton() + { + Caption = GetString("63") + }); + + vbox2.AutoSize(); //TODO: somehow force horiz size from parent? + + vbox.Add(vbox2); + + DynamicOverlay.Add(vbox); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SearchBox.OnChange += (elem) => UpdateIpTable(); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + + CloseButton.OnButtonClick += (elem) => + { + UIScreen.RemoveDialog(this); + }; + + AddIpButton.OnButtonClick += BanIp; + + Fetch(); + } + + private void BanIp(UIElement button) + { + UIAlert.Prompt("", GetString("67"), true, (string ip) => + { + if (ip != null) + { + try + { + Management.BanIp(ip); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void Fetch() + { + Task.Run(() => + { + var ips = Management.GetIpBans(); + + GameThread.InUpdate(() => + { + Data = ips; + IpTable.Loading = false; + UpdateIpTable(); + }); + }); + } + + private void UpdateIpTable() + { + var query = (SearchBox.CurrentText ?? "").ToLower(); + + if (Data == null) + { + // Empty the list + IpTable.Items.Clear(); + } + else + { + var myItems = Data + .Where(x => x.IP.ToLower().Contains(query)) + .Select((ArchiveDbIpBan x) => + { + var actionButton = new UIButton(AdminActionsButtonTexture); + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, x); + }; + + return new UIListBoxItem(x, new object[] { x.IP, "", actionButton }) + { + CustomStyle = ListBoxColors, + }; + }); + + IpTable.Items.Clear(); + + IpTable.Items.AddRange(myItems); + } + + IpTable.Items = IpTable.Items; + Invalidate(); + } + + private void UnbanIp(ArchiveDbIpBan ip) + { + UIAlert.Prompt(GetString("68", ip.IP), (result, alert) => + { + if (result) + { + try + { + Management.UnbanIp(ip.IP); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void OpenActions(UIElement anchor, ArchiveDbIpBan ip) + { + var items = new List + { + new UIContextMenuItem(GetString("62"), () => { UnbanIp(ip); }) + }; + + new UIContextMenu(anchor, items, IpTable); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/Management/UIArchiveUserManageDialog.cs b/TSOClient/tso.client/UI/Archive/Management/UIArchiveUserManageDialog.cs new file mode 100644 index 000000000..d4d9746e7 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/Management/UIArchiveUserManageDialog.cs @@ -0,0 +1,262 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using FSO.Server.Protocol.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive.Management +{ + internal class UIArchiveUserManageDialog : UIArchiveDialog + { + private ArchiveManagement Management; + + private UIGenericTable UserTable; + private UITextBox SearchBox; + private UIButton IPBansButton; + + private UIListBoxTextStyle ListBoxColors; + private Texture2D AdminActionsButtonTexture; + + private List Data; + + public UIArchiveUserManageDialog(ArchiveManagement management) : base(UIDialogStyle.Close, true) + { + Management = management; + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + AdminActionsButtonTexture = ui.Get("archive_burgermenu.png").Get(gd); + + Caption = GetString("48"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + var searchContainer = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + searchContainer.Add(new UILabel() + { + Caption = GetString("64") + }); + + searchContainer.Add(SearchBox = new UITextBox() { }); + SearchBox.SetSize(200, 25); + + searchContainer.AutoSize(); + + vbox.Add(searchContainer); + + vbox.Add(new UISpacer(1, 8)); + + vbox.Add(UserTable = new UIGenericTable([ + new UITableColumn(GetString("49"), 128), + new UITableColumn(GetString("50"), 96), + new UITableColumn(GetString("51"), 48), + new UITableColumn("", 14), + ]) + { Loading = true }); + + + var vbox2 = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + + vbox.Add(new UISpacer(1, 8)); + + vbox2.Add(IPBansButton = new UIButton() + { + Caption = GetString("56") + }); + + vbox2.AutoSize(); //TODO: somehow force horiz size from parent? + + vbox.Add(vbox2); + + DynamicOverlay.Add(vbox); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SearchBox.OnChange += (elem) => UpdateUserTable(); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + + CloseButton.OnButtonClick += (elem) => + { + UIScreen.RemoveDialog(this); + }; + + IPBansButton.OnButtonClick += OpenIPBans; + + Fetch(); + } + + private void OpenIPBans(UIElement button) + { + UIScreen.GlobalShowDialog(new UIArchiveBanManageDialog(Management), true); + } + + private void Fetch() + { + Task.Run(() => + { + var users = Management.GetUsers(); + + GameThread.InUpdate(() => + { + Data = users; + UserTable.Loading = false; + UpdateUserTable(); + }); + }); + } + + private string GetStatusIcon(ArchiveDbUserStatus status) + { + return status.ToString(); + } + + private void UpdateUserTable() + { + var query = (SearchBox.CurrentText ?? "").ToLower(); + + if (Data == null) + { + // Empty the list + UserTable.Items.Clear(); + } + else + { + var myItems = Data + .Where(x => x.Name.ToLower().Contains(query)) + .Select((ArchiveDbUser x) => + { + var actionButton = new UIButton(AdminActionsButtonTexture); + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, x); + }; + + return new UIListBoxItem(x, new object[] { x.Name, x.AvatarCount.ToString(), GetStatusIcon(x.Status), actionButton }) + { + CustomStyle = ListBoxColors, + }; + }); + + UserTable.Items.Clear(); + + UserTable.Items.AddRange(myItems); + } + + UserTable.Items = UserTable.Items; + Invalidate(); + } + + private void ViewAvatars(ArchiveDbUser user) + { + UIScreen.GlobalShowDialog(new UIArchiveAvatarManageDialog(Management, user), true); + } + + private void ShowIP(ArchiveDbUser user) + { + try + { + ClipboardHandler.Default.Set(user.IP); + } + catch + { + // No error right now. + } + + UIAlert.Alert("", GetString("70", user.Name, user.IP), true); + } + + private void BanUser(ArchiveDbUser user) + { + UIAlert.Prompt(GetString("72", user.Name), (result, alert) => + { + if (result) + { + try + { + Management.BanUser((int)user.ID); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void UnbanUser(ArchiveDbUser user) + { + UIAlert.Prompt(GetString("71", user.Name), (result, alert) => + { + if (result) + { + try + { + Management.UnbanUser((int)user.ID); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void DeleteUser(ArchiveDbUser user) + { + UIAlert.Prompt(GetString("73", user.Name), (result, alert) => + { + if (result) + { + try + { + Management.DeleteUser((int)user.ID); + } + catch + { + UIAlert.Alert("", GetString("78"), true); + return; + } + + Fetch(); + } + }); + } + + private void OpenActions(UIElement anchor, ArchiveDbUser user) + { + var items = new List + { + new(GetString("52"), () => { ViewAvatars(user); }), + new(GetString("53"), () => { ShowIP(user); }), + }; + + if (user.Status == ArchiveDbUserStatus.Banned) + { + items.Add(new UIContextMenuItem(GetString("55"), () => { UnbanUser(user); })); + } + else + { + items.Add(new UIContextMenuItem(GetString("54"), () => { BanUser(user); })); + } + + items.Add(new UIContextMenuItem(GetString("66"), () => { DeleteUser(user); })); + + new UIContextMenu(anchor, items, UserTable); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/Management/UIGenericTable.cs b/TSOClient/tso.client/UI/Archive/Management/UIGenericTable.cs new file mode 100644 index 000000000..2d3f10e42 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/Management/UIGenericTable.cs @@ -0,0 +1,199 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive.Management +{ + internal struct UITableColumn + { + public string Label; + public int Width; + public TextAlignment Aligngment; + + public UITableColumn(string label, int width, TextAlignment aligngment = TextAlignment.Left | TextAlignment.Middle) + { + Label = label; + Width = width; + Aligngment = aligngment; + } + } + + internal class UIGenericTable : UIContainer + { + private const int ColumnLegendHeight = 20; + private const int SliderWidth = 20; + + public List Items + { + get + { + return _listBox.Items; + } + set + { + _listBox.Items = value; + } + } + + public UIListBoxItem SelectedItem + { + get + { + return _listBox.SelectedItem; + } + set + { + _listBox.SelectedItem = value; + } + } + + public int SelectedIndex + { + get + { + return _listBox.SelectedIndex; + } + set + { + _listBox.SelectedIndex = value; + } + } + + public bool Loading + { + get + { + return _statusLabel.Visible; + } + set + { + _statusLabel.Visible = value; + } + } + + public override Vector2 Size { get; set; } + public event ChangeDelegate OnChange; + + private readonly UIImage _background; + private readonly UIListBox _listBox; + private readonly UILabel _statusLabel; + private readonly List _columns; + private List _columnLabels; + + public UIGenericTable(List columns, int height = 300) + { + _columns = columns; + + var gd = GameFacade.GraphicsDevice; + var ui = Content.Content.Get().CustomUI; + + var searchFont = TextStyle.DefaultLabel.Clone(); + searchFont.Size = 8; + + _background = new UIImage(ui.Get("archive_translist.png").Get(gd)).With9Slice(13, 13, 13, 13); + _background.Position = new Vector2(0, ColumnLegendHeight); + _background.SetSize(180, 300); + Add(_background); + + var textStyle = new UIListBoxTextStyle(searchFont) + { + SelectedColor = Color.Black, + HighlightedColor = new Color(255, 255, 255), + DisabledColor = new Color(150, 150, 150) + }; + + Add(_listBox = new UIListBox() + { + Position = _background.Position + new Vector2(10, 10), + Mask = true, + Columns = GenerateColumns(), + RowHeight = 20, + TextStyle = textStyle, + SelectionFillColor = new Color(250, 200, 140), + ScrollbarImage = GetTexture(0x31000000001), + ScrollbarGutter = 12, + UseChildElements = true, + }); + + var statusStyle = TextStyle.DefaultLabel.Clone(); + statusStyle.Shadow = true; + + Add(_statusLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "330"), + Position = _listBox.Position, + Size = _listBox.Size, + Wrapped = true, + Alignment = TextAlignment.Center | TextAlignment.Middle, + CaptionStyle = statusStyle, + }); + + _listBox.InitDefaultSlider(); + SetSize(_columns.Sum((col) => col.Width) + SliderWidth + 20, height); + PopulateColumnLabels(); + + _listBox.OnChange += _listBox_OnChange; + } + + private void _listBox_OnChange(UIElement element) + { + OnChange?.Invoke(this); + } + + public void SetSize(int width, int height) + { + _background.SetSize(width - SliderWidth, height - ColumnLegendHeight); + _listBox.Size = _background.Size - new Vector2(20, 20); + _statusLabel.Size = _statusLabel.Size; + _listBox.VisibleRows = (int)Math.Ceiling(_listBox.Height / _listBox.RowHeight); + _listBox.PositionChildSlider(); + + Size = new Vector2(width, height); + } + + private UIListBoxColumnCollection GenerateColumns() + { + var result = new UIListBoxColumnCollection(); + + foreach (var column in _columns) + { + result.Add(new UIListBoxColumn() { Width = column.Width, Alignment = column.Aligngment }); + } + + return result; + } + + private void PopulateColumnLabels() + { + if (_columnLabels != null) + { + foreach (var label in _columnLabels) + { + Remove(label); + _columnLabels.Remove(label); + } + + _columnLabels.Clear(); + } + else + { + _columnLabels = new List(); + } + + int totalWidth = 0; + foreach (var col in _columns) + { + var label = new UILabel() + { + Caption = col.Label, + Position = new Vector2(_listBox.X + totalWidth, 0), + }; + + Add(label); + _columnLabels.Add(label); + + totalWidth += col.Width; + } + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveAddServerDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveAddServerDialog.cs new file mode 100644 index 000000000..6b0143468 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveAddServerDialog.cs @@ -0,0 +1,147 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common.Utils; +using FSO.Server.Clients; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + public readonly struct UIAddServerResult(string address, StatusCheckResult status, bool isFreeSO) + { + public readonly string Address = address; + public readonly StatusCheckResult Status = status; + public readonly bool IsFreeSO = isFreeSO; + } + + internal class UIArchiveAddServerDialog : UIArchiveDialog + { + private readonly UILabel DescriptionLabel; + private readonly UILabel AddressLabel; + private readonly UITextBox AddressInput; + private readonly UILabel StatusLabel; + private readonly UIButton AddButton; + + private readonly TextStyle StatusStyle; + private readonly UIVBoxContainer RootBox; + + private bool IsFetching; + private readonly Action OnResult; + + public UIArchiveAddServerDialog(Action onResult) : base(UIDialogStyle.Close, true) + { + OnResult += onResult; + + RootBox = new UIVBoxContainer() + { + HorizontalAlignment = UIContainerHorizontalAlignment.Center + }; + + RootBox.Add(DescriptionLabel = new UILabel() + { + Caption = GetString("148"), + Size = new Vector2(300, 50), + Wrapped = true + }); + + StatusStyle = TextStyle.DefaultLabel.Clone(); + StatusStyle.Size = 9; + StatusStyle.Shadow = true; + + RootBox.Add(StatusLabel = new UILabel() + { + CaptionStyle = StatusStyle + }); + RootBox.Add(AddressInput = new UITextBox() { Size = new Vector2(300, 25) }); + + RootBox.Add(AddButton = new UIButton() { Caption = GetString("147"), Disabled = true }); + + Add(RootBox); + + AddressInput.OnChange += AddressChanged; + AddButton.OnButtonClick += AddServer; + + CloseButton.OnButtonClick += Close; + + RootBox.AutoSize(); + RootBox.Position = new Vector2(20, 40); + SetSize((int)RootBox.Size.X + 40, (int)RootBox.Size.Y + 60); + } + + private void Close(UIElement button) + { + GameFacade.Screens.RemoveDialog(this); + } + + private void AddressChanged(UIElement element) + { + AddButton.Disabled = IsFetching || AddressInput.CurrentText.Length == 0; + } + + private void CloseWithResult(UIAddServerResult result) + { + GameFacade.Screens.RemoveDialog(this); + OnResult(result); + } + + private void Reset() + { + StatusStyle.Color = new Color(255, 122, 77); + StatusLabel.Caption = GetString("151"); + AddressInput.Mode = UITextEditMode.Editor; + IsFetching = false; + AddressChanged(AddressInput); + } + + private void AddServer(UIElement button) + { + var address = AddressInput.CurrentText; + IsFetching = true; + StatusStyle.Color = Color.White; + StatusLabel.Caption = GetString("150"); + AddButton.Disabled = true; + AddressInput.Mode = UITextEditMode.ReadOnly; + + Task.Run(async () => + { + var archiveTask = Task.Run(async () => await StatusChecker.ArchiveStatus(FSOFacade.Kernel, address)); + var fsoTask = Task.Run(async () => await StatusChecker.FreeSOStatus(address)); + + var first = await Task.WhenAny(archiveTask, fsoTask); + + var firstResult = first.Result; + + if (firstResult.IsOnline) + { + GameThread.InUpdate(() => + { + CloseWithResult(new UIAddServerResult(address, firstResult, first == fsoTask)); + }); + } + else + { + var all = await Task.WhenAll(archiveTask, fsoTask); + + int index = 0; + foreach (var status in all) + { + if (status.IsOnline) + { + GameThread.InUpdate(() => + { + CloseWithResult(new UIAddServerResult(address, status, index == 1)); + }); + return; + } + + index++; + } + + GameThread.InUpdate(() => + { + Reset(); + }); + } + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveCitySelector.cs b/TSOClient/tso.client/UI/Archive/UIArchiveCitySelector.cs new file mode 100644 index 000000000..6cab74150 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveCitySelector.cs @@ -0,0 +1,438 @@ +using FSO.Client.GameContent; +using FSO.Client.Model.Archive; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + public class UIArchiveCitySelector : UIArchiveDialog + { + private const int StatusOnlineWidth = 153; + private const int TruncateCityNameWidth = 43; + private const int ListboxResizeX = StatusOnlineWidth + TruncateCityNameWidth; + + private readonly string[] BuiltinCityNames = [ + "Blazing Falls", + "Alphaville", + "Test Center", + "Interhogan", + "Ocean's Edge", + "East Jerome", + "Fancey Fields", + "Betaville", + "Charvatia", + "Dragon's Cove", + "Rancho Rizzo", + "Zavadaville", + "Queen Margaret’s", + "Shannopolis", + "Grantley Grove", + "Calvin’s Creek", + "Billabong", + "Mount Fuji", + "Dan’s Grove", + "Jolly Pines", + "Yatesport", + "Landry Lakes", + "Nichol's Notch", + "King Canyons", + "Virginia Islands", + "Pixie Point", + "West Darrington", + "Upper Shankelston", + "Albertstown", + "Terra Tablante", + ]; + + //Positioned & sized by UIScript + public UIImage CityListBoxBackground { get; set; } + public UIImage CityDescriptionBackground { get; set; } + + //Set by UIScript + public Texture2D CityIconImage { get; set; } + public Texture2D thumbnailBackgroundImage { get; set; } + public Texture2D thumbnailAlphaImage { get; set; } + public UIListBox CityListBox { get; set; } + public UISlider CityListSlider { get; set; } + public UIButton CityListScrollUpButton { get; set; } + public UIButton CityScrollDownButton { get; set; } + + public UITextEdit DescriptionText { get; set; } + public UISlider CityDescriptionSlider { get; set; } + public UIButton CityDescriptionScrollUpButton { get; set; } + public UIButton CityDescriptionDownButton { get; set; } + + public UIButton OkButton { get; set; } + public UIButton CancelButton { get; set; } + + // Sort buttons + public UIButton NameSortButton { get; set; } + public UIButton OnlineSortButton { get; set; } + public UIButton StatusSortButton { get; set; } + + + /** Strings **/ + public string OnlineStatusUp { get; set; } + public string OnlineStatusDown { get; set; } + public string StatusBusy { get; set; } + public string StatusFull { get; set; } + public string StatusBusyFull { get; set; } + public string StatusOk { get; set; } + + public string CityReservedDialogTitle { get; set; } + public string CityReservedDialogMessage { get; set; } + public string CityFullDialogTitle { get; set; } + public string CityFullDialogMessage { get; set; } + public string CityBusyDialogTitle { get; set; } + public string CityBusyDialogMessage { get; set; } + + //Internal + private UIImage CityThumb { get; set; } + + private UIListBoxTextStyle ListStyleNormal; + private Texture2D SimIconShared; + + private UITextBox NameInput; + private UITextEdit DescriptionInput; + private bool AutoName = true; + + private readonly ArchiveManifest Template; + private readonly Dictionary> CityCST = []; + + public event Action OnResult; + + public UIArchiveCitySelector(ArchiveManifest template) + : base(UIDialogStyle.Standard, true) + { + Template = template; + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; + + SimIconShared = custom.Get("archive_simshared.png").Get(gd); + CityListBoxBackground = new UIImage(UITextBox.StandardBackground); + this.Add(CityListBoxBackground); + CityDescriptionBackground = new UIImage(UITextBox.StandardBackground); + this.Add(CityDescriptionBackground); + + var script = this.RenderScript("cityselector.uis"); + this.DialogSize = (Point)script.GetControlProperty("DialogSize"); + + var cityThumbBG = new UIImage(thumbnailBackgroundImage); + cityThumbBG.Position = (Vector2)script.GetControlProperty("CityThumbnailBackgroundPosition"); + this.Add(cityThumbBG); + CityThumb = new UIImage(); + CityThumb.Position = (Vector2)script.GetControlProperty("CityThumbnailPosition"); + this.Add(CityThumb); + + CityDescriptionSlider.AttachButtons(CityDescriptionScrollUpButton, CityDescriptionDownButton, 1); + DescriptionText.AttachSlider(CityDescriptionSlider); + + OkButton.Disabled = true; + OkButton.OnButtonClick += new ButtonClickDelegate(OkButton_OnButtonClick); + CancelButton.OnButtonClick += new ButtonClickDelegate(CancelButton_OnButtonClick); + + this.Caption = (string)script["TitleString"]; + + // Reposition everything to fit the city configuration + CityListBox.SetSize(CityListBox.Width - ListboxResizeX, CityListBox.Height); + /* + CityListSlider.Position -= new Vector2(ListboxResizeX, 0); + CityListScrollUpButton.Position -= new Vector2(ListboxResizeX, 0); + CityScrollDownButton.Position -= new Vector2(ListboxResizeX, 0); + */ + CityListBox.Position += new Vector2(ListboxResizeX, 0); + CityListBoxBackground.Position += new Vector2(ListboxResizeX, 0); + NameSortButton.Position += new Vector2(ListboxResizeX, 0); + CityListBoxBackground.Size -= new Vector2(ListboxResizeX, 0); + NameSortButton.Size -= new Vector2(TruncateCityNameWidth, 0); + CityListBox.Columns.RemoveRange(CityListBox.Columns.Count - 2, 2); + + Remove(OnlineSortButton); + Remove(StatusSortButton); + Remove(CityListBox); + DynamicOverlay.Add(CityListBox); + + // Archive city configuration + var saveVbox = new UIVBoxContainer(); + saveVbox.Position = new Vector2(25, 39); + + saveVbox.Add(new UILabel() + { + Caption = GetString("320") + }); + + saveVbox.Add(NameInput = new UITextBox() + { + Size = new Microsoft.Xna.Framework.Vector2(166, 25), + CurrentText = GetString("321"), + }); + + saveVbox.Add(new UILabel() + { + Caption = GetString("322") + }); + + saveVbox.Add(DescriptionInput = new UITextEdit() + { + Size = new Microsoft.Xna.Framework.Vector2(166, 158), + CurrentText = "", + BackgroundTextureReference = UITextBox.StandardBackground, + ScrollbarImage = GetTexture(0x4AB00000001), + ScrollbarGutter = 4, + TextMargin = new Rectangle(8, 2, 8, 3), + MaxChars = 4096, + }); + + saveVbox.AutoSize(); + + Add(saveVbox); + + DescriptionInput.InitDefaultSlider(); + + NameInput.OnChange += NameChange; + + /** Parse the list styles **/ + ListStyleNormal = script.Create("CityListBoxColors", CityListBox.FontStyle); + + + CityListSlider.AttachButtons(CityListScrollUpButton, CityScrollDownButton, 1); + + CityListBox.TextStyle = ListStyleNormal; + CityListBox.AttachSlider(CityListSlider); + CityListBox.OnChange += new ChangeDelegate(CityListBox_OnChange); + + CityListBox.Items = BuildShards(); + + if (CityListBox.Items.Count > 0) { + CityListBox.SelectedIndex = 0; + } + } + + private void NameChange(UIElement element) + { + AutoName = false; + + var name = NameInput.CurrentText; + + OkButton.Disabled = name.Length == 0 || NameTaken(name); + } + + private string GetPath(string name) + { + return Path.Combine("Content/ArchiveCities", string.Join('_', name.Split(Path.GetInvalidFileNameChars()))); + } + + private bool NameTaken(string name) + { + var path = GetPath(name); + + return Path.Exists(path); + } + + private string GenerateAutoName() + { + var map = SelectedMap; + + if (map == null || !int.TryParse(map, out int id)) + { + return null; + } + + var basename = GetCityName(id); + var name = basename; + + int copyNumber = 2; + while (NameTaken(name)) + { + name = $"{basename} ({copyNumber++})"; + } + + return name; + } + + private string GetCityText(int id, string key) + { + if (!CityCST.TryGetValue(id, out var cst)) + { + var dir = Content.Content.Get().CityMaps.GetDir(id); + + string path = dir == null ? null : Path.Combine(dir, "info.cst"); + + if (path == null || !File.Exists(path)) + { + cst = new() + { + { "1", GetString("323") }, + { "2", GetString("324") }, + }; + } + else + { + cst = ContentStrings.ReadTable(path); + } + + CityCST[id] = cst; + } + + cst.TryGetValue(key, out var value); + + return value ?? "???"; + } + + private string GetCityName(int id) + { + var fsoMap = id >= 100; + + return fsoMap ? GetCityText(id, "1") : BuiltinCityNames[id - 1]; + } + + private List BuildShards() + { + var ids = Content.Content.Get().CityMaps.ListIDs(); + var result = new List(); + + foreach (var id in ids) + { + var fsoMap = id >= 100; + + result.Add(new UIListBoxItem(id.ToString().PadLeft(4, '0'), fsoMap ? SimIconShared : CityIconImage, GetCityName(id)) + { + CustomStyle = ListStyleNormal + }); + } + + return result; + } + + + void CancelButton_OnButtonClick(UIElement button) + { + UIScreen.RemoveDialog(this); + OnResult?.Invoke(null); + } + + void OkButton_OnButtonClick(UIElement button) + { + // Copy the template into the target folder, and initialize the city. + + var srcFolder = Path.GetDirectoryName(Template.ActivePath); + + string name = NameInput.CurrentText; + string description = DescriptionInput.CurrentText; + + var dstFolder = GetPath(name); + + CopyDirectory(srcFolder, dstFolder); + + var newTemplate = new ArchiveManifest(Path.Combine(dstFolder, "archive.ini")) + { + Name = name, + Description = description, + Map = SelectedMap, + Template = false + }; + newTemplate.LocalDir = "data/"; + + newTemplate.Save(); + + // Update the shard + + Visible = false; + + var factory = new ArchiveServerFactory(ArchiveServerFactory.GetQuickStartConfig(), null); + factory.Prepare(newTemplate, (success) => + { + if (success) + { + new ArchiveManagement(factory.GetConfig()).SetInfo(newTemplate.Name, newTemplate.Map); + + OnResult(newTemplate); + UIScreen.RemoveDialog(this); + } + else + { + Visible = true; + Directory.Delete(dstFolder, true); + UIAlert.Alert(GetString("325"), GetString("326"), true); + } + }); + } + + private static void CopyDirectory(string src, string dst) + { + Directory.CreateDirectory(dst); + + foreach (var file in Directory.GetFiles(src)) + { + File.Copy(file, Path.Combine(dst, Path.GetFileName(file))); + } + + foreach (var dir in Directory.GetDirectories(src)) + { + CopyDirectory(dir, Path.Combine(dst, Path.GetFileName(dir))); + } + } + + public string SelectedMap + { + get + { + if (CityListBox.SelectedItem != null) + { + return (string)CityListBox.SelectedItem.Data; + } + + return null; + } + } + + /// + /// Handle when a user selects a city + /// + /// + void CityListBox_OnChange(UIElement element) + { + var selectedItem = CityListBox.SelectedItem; + if (selectedItem == null) + { + return; + } + + if (AutoName) + { + var auto = GenerateAutoName(); + if (auto != null) + { + NameInput.CurrentText = auto; + OkButton.Disabled = false; + } + } + + var map = (string)selectedItem.Data; + + String gamepath = GameFacade.GameFilePath(""); + + + var fsoMap = int.Parse(map) >= 100; + + var cityThumb = (fsoMap) ? + Path.Combine(FSOEnvironment.ContentDir, "Cities/city_" + map + "/thumbnail.png") + : GameFacade.GameFilePath("cities/city_" + map + "/thumbnail.bmp"); + + //Take a copy so we dont change the original when we alpha mask it + Texture2D cityThumbTex = TextureUtils.Copy(GameFacade.GraphicsDevice, TextureUtils.TextureFromFile( + GameFacade.GraphicsDevice, cityThumb)); + TextureUtils.CopyAlpha(ref cityThumbTex, thumbnailAlphaImage); + + CityThumb.Texture = cityThumbTex; + DescriptionText.CurrentText = fsoMap ? GetCityText(int.Parse(map), "2") : GameFacade.Strings.GetString("238", int.Parse(map).ToString()); + DescriptionText.VerticalScrollPosition = 0; + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveConfigExportDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveConfigExportDialog.cs new file mode 100644 index 000000000..585554450 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveConfigExportDialog.cs @@ -0,0 +1,183 @@ +using FSO.Client.Model.Archive; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Rendering.Framework.IO; +using FSO.Server.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveConfigExportDialog : UIArchiveDialog + { + public UITextBox PathInput; + public UIButton ExportButton; + private bool ArchiveAbsolute; + private bool TSOAbsolute = true; + + private ArchiveConfiguration Config; + private ArchiveManifest Manifest; + + public UIArchiveConfigExportDialog(ArchiveConfiguration config, ArchiveManifest manifest) : base(UIDialogStyle.Close, true) + { + Config = config; + Manifest = manifest; + + Caption = GetString("36"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + UILabel desc; + + vbox.Add(desc = new UILabel() + { + Caption = GetString("37"), + Wrapped = true + }); + + desc.Size = new Vector2(350, 140); + + var pathBox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + pathBox.Add(new UILabel() + { + Caption = GetString("44") + }); + + pathBox.Add(PathInput = new UITextBox() { }); + + vbox.Add(pathBox); + + vbox.Add(new UISpacer(1, 8)); + + var flagsVbox = new UIVBoxContainer(); + + CreateCheck(flagsVbox, GetString("38"), ArchiveAbsolute, (bool value) => { ArchiveAbsolute = value; }); + CreateCheck(flagsVbox, GetString("39"), TSOAbsolute, (bool value) => { TSOAbsolute = value; }); + + flagsVbox.AutoSize(); + + vbox.Add(flagsVbox); + + var vbox2 = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + + vbox.Add(new UISpacer(1, 8)); + + vbox2.Add(ExportButton = new UIButton() + { + Caption = GetString("40") + }); + + vbox2.AutoSize(); //TODO: somehow force horiz size from parent? + + vbox.Add(vbox2); + + Add(vbox); + + PathInput.SetSize(350, 25); + PathInput.CurrentText = Path.GetFullPath("config.json"); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + + CloseButton.OnButtonClick += (elem) => + { + UIScreen.RemoveDialog(this); + }; + + ExportButton.OnButtonClick += Export; + } + + private void Export(UIElement button) + { + var factory = new ArchiveServerFactory(Config, null); + factory.Prepare(Manifest, (success) => + { + if (success) + { + var json = ArchiveConfigExporter.BuildAndExport(Config, ArchiveAbsolute, TSOAbsolute); + + var path = PathInput.CurrentText; + + try + { + var ext = Path.GetExtension(path); + + if (ext == null) + { + // Assume the user gave a directory + path = Path.Combine(path, "config.json"); + } + + // Ensure the directory exists + Directory.CreateDirectory(Path.GetDirectoryName(path)); + + { + using var file = File.Open(path, FileMode.Create); + using var writer = new StreamWriter(file); + + writer.Write(json); + } + + bool clipboardSuccess = true; + try + { + ClipboardHandler.Default.Set(path); + } + catch (Exception) + { + clipboardSuccess = false; + } + + UIAlert.Alert( + GetString("47"), + GetString(clipboardSuccess ? "45" : "43", path), + true); + } + catch (Exception) + { + UIAlert.Alert(GetString("41"), GetString("42", path), true); + } + } + }); + } + + private void CreateCheck(UIContainer target, string label, bool defaultValue, Action onChanged) + { + var flagHbox = new UIHBoxContainer(); + + var check = new UIButton(GetTexture(0x0000083600000001)); + check.Selected = defaultValue; + + flagHbox.Add(check); + + check.OnButtonClick += (elem) => + { + check.Selected = !check.Selected; + onChanged(check.Selected); + }; + + flagHbox.Add(new UILabel() + { + Caption = label, + }); + + /* + if (flag.HelpAction != null) + { + UIButton helpBtn = new UIButton(HelpButtonTexture); + var helpAction = flag.HelpAction; + helpBtn.OnButtonClick += (elem) => helpAction(); + flagHbox.Add(helpBtn); + } + */ + + flagHbox.AutoSize(); + + target.Add(flagHbox); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveCreateServer.cs b/TSOClient/tso.client/UI/Archive/UIArchiveCreateServer.cs new file mode 100644 index 000000000..038cd6cca --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveCreateServer.cs @@ -0,0 +1,624 @@ +using FSO.Client.Controllers; +using FSO.Client.Model.Archive; +using FSO.Client.UI.Archive.Management; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Utils; +using FSO.Server.Embedded; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveCreateServer : UIArchiveDialog + { + private const int CITY_IMAGE_WIDTH = 148; + private const int CITY_IMAGE_HEIGHT = 112; + private const int CITY_IMAGE_RADIUS = 8; + private const int CITY_IMAGE_MARGIN = 4; + + private struct ServerSubFlag + { + public ArchiveConfigFlags Value; + public string Caption; + public UIButton FlagCheck; + public UILabel Label; + + public ServerSubFlag(ArchiveConfigFlags value, string caption) + { + Value = value; + Caption = caption; + } + } + + private struct ServerFlag + { + public ArchiveConfigFlags Value; + public string Caption; + public int Indentation; + public Action HelpAction; + public UIButton FlagCheck; + public ServerSubFlag[] SubFlags; + + public ServerFlag(ArchiveConfigFlags value, string caption, int indentation = 0, Action helpAction = null, ServerSubFlag[] subFlags = null) + { + Value = value; + Caption = caption; + Indentation = indentation; + HelpAction = helpAction; + FlagCheck = null; + SubFlags = subFlags; + } + } + + private ServerFlag[] Flags = + [ + new ServerFlag(ArchiveConfigFlags.Offline, GetString("200")), + new ServerFlag(ArchiveConfigFlags.UPnP, GetString("201"), 0, UPnPHelp), + new ServerFlag(ArchiveConfigFlags.Verification, GetString("202"), 0, VerificationHelp), + new ServerFlag(ArchiveConfigFlags.CityEditor, GetString("203"), 0, CityEditorHelp, [new ServerSubFlag(ArchiveConfigFlags.CityEditorMods, GetString("220")), new ServerSubFlag(ArchiveConfigFlags.CityEditorAllUsers, GetString("221"))]), + default, // Gap (flag value is 0) + new ServerFlag(ArchiveConfigFlags.AllOpenable, GetString("204"), 0, AllOpenableHelp), + new ServerFlag(ArchiveConfigFlags.DebugFeatures, GetString("205"), 0, DebugModeHelp, [new ServerSubFlag(ArchiveConfigFlags.DebugFeaturesMods, GetString("220")), new ServerSubFlag(ArchiveConfigFlags.DebugFeaturesAllUsers, GetString("221"))]), + new ServerFlag(ArchiveConfigFlags.AllowLotCreation, GetString("206")), + new ServerFlag(ArchiveConfigFlags.AllowSimCreation, GetString("207")), + new ServerFlag(ArchiveConfigFlags.LockArchivedSims, GetString("208"), 1, ArchivedCharacterHelp), + new ServerFlag(ArchiveConfigFlags.HideNames, GetString("209")), + ]; + + private UIArchiveDisplayName DisplayName; + private UIButton ExportButton; + private UIButton UsersButton; + private UIButton CustomPortsButton; + private UIButton EventsButton; + private UIButton CheatsButton; + private UIButton StartButton; + private UITextBox NameInput; + private ArchiveConfiguration Config; + private Texture2D HelpButtonTexture = GetTexture(0x0000034200000001); + private UIImage CityImage; + + private UICombobox SaveCombo; + + public UIArchiveCreateServer() : base(UIDialogStyle.Close, true) + { + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; + + var clientConfig = ClientArchiveConfiguration.Default; + Config = clientConfig.ToHostConfig(); + + Caption = GetString("241"); + + var vbox = new UIVBoxContainer(); + + var headHbox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle, Spacing = 10 }; + + var imageBg = new UIImage(custom.Get("archive_translist.png").Get(gd)).With9Slice(13, 13, 13, 13); + imageBg.SetSize(CITY_IMAGE_WIDTH + CITY_IMAGE_MARGIN * 2, CITY_IMAGE_HEIGHT + CITY_IMAGE_MARGIN * 2); + headHbox.Add(imageBg); + + var saveVbox = new UIVBoxContainer() + { + Spacing = 0 + }; + + saveVbox.Add(DisplayName = new UIArchiveDisplayName()); + saveVbox.Add(new UISpacer(8)); + + SaveCombo = new UICombobox() + { + Width = 160 + }; + SaveCombo.OnSelect += UpdateSelectedSave; + + saveVbox.Add(SaveCombo); + saveVbox.Add(new UISpacer(5)); + + PopulateSaves(); + SelectSaveByName(clientConfig.SelectedArchiveName); + + saveVbox.Add(new UILabel() + { + Caption = GetString("230") + }); + saveVbox.Add(new UISpacer(2)); + + saveVbox.Add(NameInput = new UITextBox() + { + Size = new Microsoft.Xna.Framework.Vector2(160, 25), + CurrentText = clientConfig.GetServerNameOrDefault(), + }); + + saveVbox.AutoSize(); + + headHbox.Add(saveVbox); + + headHbox.AutoSize(); + vbox.Add(headHbox); + + var flagsVbox = new UIVBoxContainer(); + + for (int i = 0; i < Flags.Length; i++) + { + ref var flag = ref Flags[i]; + + if (flag.Value != ArchiveConfigFlags.None) + { + var flagHbox = new UIHBoxContainer(); + + var check = new UIButton(GetTexture(0x0000083600000001)); + check.Selected = Config.Flags.HasFlag(flag.Value); + + if (flag.Indentation > 0) + { + flagHbox.Add(new UISpacer(16, 1)); + } + + flag.FlagCheck = check; + + flagHbox.Add(check); + var value = flag.Value; + + check.OnButtonClick += (elem) => + { + ToggleFlag(value); + }; + + flagHbox.Add(new UILabel() + { + Caption = flag.Caption, + }); + + if (flag.HelpAction != null) + { + UIButton helpBtn = new UIButton(HelpButtonTexture); + var helpAction = flag.HelpAction; + helpBtn.OnButtonClick += (elem) => helpAction(); + flagHbox.Add(helpBtn); + } + + if (flag.SubFlags != null) + { + for (int j = 0; j < flag.SubFlags.Length; j++) + { + flagHbox.Add(new UISpacer(0)); + + ref var sub = ref flag.SubFlags[j]; + + var subcheck = new UIButton(GetTexture(0x0000083600000001)) + { + Visible = check.Selected, + Selected = Config.Flags.HasFlag(sub.Value) + }; + sub.FlagCheck = subcheck; + + flagHbox.Add(subcheck); + var subvalue = sub.Value; + + subcheck.OnButtonClick += (elem) => + { + ToggleFlag(subvalue); + }; + + var label = new UILabel() + { + Caption = sub.Caption, + Visible = check.Selected, + }; + + flagHbox.Add(label); + sub.Label = label; + } + } + + flagHbox.AutoSize(); + + flagsVbox.Add(flagHbox); + } + else + { + flagsVbox.Add(new UISpacer(16)); + } + } + + vbox.Add(new UISpacer(5)); + + flagsVbox.AutoSize(); + + vbox.Add(flagsVbox); + + vbox.Add(new UISpacer(10)); + + var actionsHbox = new UIHBoxContainer() { Spacing = 10 }; + + actionsHbox.Add(ExportButton = new UIButton(custom.Get("archive_configexport.png").Get(gd)) + { + Tooltip = GetString("231") + }); + + actionsHbox.Add(UsersButton = new UIButton(custom.Get("archive_configusers.png").Get(gd)) + { + Tooltip = GetString("232") + }); + + actionsHbox.Add(CustomPortsButton = new UIButton(custom.Get("archive_configports.png").Get(gd)) + { + Tooltip = GetString("233") + }); + + actionsHbox.Add(EventsButton = new UIButton(custom.Get("archive_configevents.png").Get(gd)) + { + Tooltip = GetString("234") + }); + + actionsHbox.Add(CheatsButton = new UIButton(custom.Get("archive_configcheats.png").Get(gd)) + { + Tooltip = GetString("235") + }); + + Add(StartButton = new UIButton() + { + Caption = GetString("79") + }); + + actionsHbox.AutoSize(); + vbox.Add(actionsHbox); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 45); + + // Manually position the start button at the bottom right of the box. + + StartButton.Position = vbox.Position + vbox.Size - StartButton.Size + new Vector2(0, 5); + + // (hack) Move to end so it draws on top. + saveVbox.Remove(SaveCombo); + saveVbox.Add(SaveCombo); + + vbox.Remove(headHbox); + vbox.Add(headHbox); + + // Added after auto sizing, since it floats on top. + headHbox.Add(CityImage = new UIImage() + { + Position = imageBg.Position + new Vector2(CITY_IMAGE_MARGIN), + Size = new Vector2(CITY_IMAGE_WIDTH, CITY_IMAGE_HEIGHT), + }); + + UpdateSelectedSave(SaveCombo); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + DynamicOverlay.Add(vbox); + + NameInput.OnChange += ValidateInputs; + CustomPortsButton.OnButtonClick += ChangePorts; + EventsButton.OnButtonClick += EditEvents; + CheatsButton.OnButtonClick += Cheats; + StartButton.OnButtonClick += Start; + CloseButton.OnButtonClick += Close; + ExportButton.OnButtonClick += Export; + UsersButton.OnButtonClick += Users; + DisplayName.OnChange += DisplayNameChanged; + + ValidateInputs(NameInput); + + UpdateButtons(); + } + + private void DisplayNameChanged(string newName) + { + var clientConfig = ClientArchiveConfiguration.Default; + var defaultName = clientConfig.GetDefaultServerName(); + + if (NameInput.CurrentText == defaultName) + { + clientConfig.PlayerName = newName; + NameInput.CurrentText = clientConfig.GetDefaultServerName(); + } + } + + private void Cheats(UIElement button) + { + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + var factory = new ArchiveServerFactory(Config, null); + factory.Prepare(selected, (success) => + { + if (success) + { + UIScreen.GlobalShowDialog(new UIArchiveGameplayScale(Config), true); + } + }); + } + + private void NewFromTemplate(ArchiveManifest template) + { + var cityPicker = new UIArchiveCitySelector(template); + cityPicker.OnResult += (ArchiveManifest manifest) => + { + SaveCombo.SelectedIndex = -1; + PopulateSaves(); + + int index = -1; + if (manifest != null) + { + index = SaveCombo.Items.FindIndex(x => ((ArchiveManifest)x.Value).ActivePath == manifest.ActivePath); + } + + if (index == -1) + { + var clientConfig = ClientArchiveConfiguration.Default; + SelectSaveByName(clientConfig.SelectedArchiveName); + } + else + { + SaveCombo.SelectedIndex = index; + } + }; + + UIScreen.ShowDialog(cityPicker, true); + } + + private Texture2D LoadCityThumbnail(string path) + { + try + { + //Take a copy so we dont change the original when we alpha mask it + Texture2D cityThumbTex = TextureUtils.Resize(GameFacade.GraphicsDevice, TextureUtils.TextureFromFile( + GameFacade.GraphicsDevice, path), CITY_IMAGE_WIDTH, CITY_IMAGE_HEIGHT); + + var mask = TextureGenerator.GenerateRoundedRectangle(GameFacade.GraphicsDevice, Color.White, CITY_IMAGE_WIDTH, CITY_IMAGE_HEIGHT, CITY_IMAGE_RADIUS); + TextureUtils.CopyAlpha(ref cityThumbTex, mask); + + mask.Dispose(); + + return cityThumbTex; + } + catch + { + return null; + } + } + + private void UpdateSelectedSave(object obj) + { + if (CityImage == null) + { + return; + } + + if (CityImage.Texture != null) + { + CityImage.Texture.Dispose(); + } + + if (SaveCombo.SelectedIndex == -1) + { + CityImage.Texture = null; + return; + } + + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + if (selected.Template) + { + NewFromTemplate(selected); + } + + // Load the city image. + + try + { + if (selected.LocalDir != null) + { + // TODO: get archive shard? currently just assumes it's 1 + var customThumbPath = Path.Combine(Path.GetDirectoryName(selected.ActivePath), selected.LocalDir, "City1/thumbnail.png"); + + if (File.Exists(customThumbPath)) + { + CityImage.Texture = LoadCityThumbnail(customThumbPath); + return; + } + } + } + catch + { + // Try load the default map image + } + + string map = selected.Map; + var fsoMap = int.Parse(map) >= 100; + + + var cityThumb = (fsoMap) ? + Path.Combine(FSOEnvironment.ContentDir, "Cities/city_" + map + "/thumbnail.png") + : GameFacade.GameFilePath("cities/city_" + map + "/thumbnail.bmp"); + + CityImage.Texture = LoadCityThumbnail(cityThumb); + } + + private void EditEvents(UIElement button) + { + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + var factory = new ArchiveServerFactory(Config, null); + factory.Prepare(selected, (success) => + { + if (success) + { + UIScreen.GlobalShowDialog(new UIArchiveEventsDialog(factory.GetConfig()), true); + } + }); + } + + private void Users(UIElement button) + { + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + var factory = new ArchiveServerFactory(Config, null); + factory.Prepare(selected, (success) => + { + if (success) + { + UIScreen.GlobalShowDialog(new UIArchiveUserManageDialog(new ArchiveManagement(factory.GetConfig())), true); + } + }); + } + + private void SelectSaveByName(string name) + { + SaveCombo.SelectedIndex = Math.Max(0, SaveCombo.Items.FindIndex((item) => item.Name == name)); + } + + private void ChangePorts(UIElement button) + { + UIArchiveServerPorts portDialog = null; + portDialog = new UIArchiveServerPorts(Config, () => + { + if (portDialog.GetLotPort(out ushort lotPort)) + { + Config.LotPort = lotPort; + } + + if (portDialog.GetCityPort(out ushort cityPort)) + { + Config.CityPort = cityPort; + } + }); + + UIScreen.GlobalShowDialog(portDialog, true); + } + + private void Export(UIElement button) + { + var selected = SaveCombo.SelectedItem as ArchiveManifest; + UIScreen.GlobalShowDialog(new UIArchiveConfigExportDialog(Config, selected), true); + } + + private void PopulateSaves() + { + var manifests = ArchiveSaves.ListManifests(); + var templates = ArchiveSaves.ListManifests(true); + + SaveCombo.Items = [.. manifests.Select(x => new UIComboboxItem() { Name = x.Name, Value = x }), .. templates.Select(x => new UIComboboxItem() { Name = x.Name, Value = x }),]; + + SaveCombo.SelectedIndex = manifests.Count > 0 ? 0 : -1; + } + + private void UpdateButtons() + { + CustomPortsButton.Disabled = Config.Flags.HasFlag(ArchiveConfigFlags.UPnP); + CustomPortsButton.Tooltip = CustomPortsButton.Disabled ? GetString("18") : GetString("233"); + } + + private void ToggleFlag(ArchiveConfigFlags flag) + { + Config.Flags ^= flag; + + foreach (var item in Flags) + { + bool selected = (item.Value & Config.Flags) != 0; + if (item.FlagCheck != null) + { + item.FlagCheck.Selected = selected; + } + + if (item.SubFlags != null) + { + foreach (var sub in item.SubFlags) + { + if (sub.FlagCheck != null) + { + sub.FlagCheck.Visible = selected; + sub.FlagCheck.Selected = (sub.Value & Config.Flags) != 0; + } + + if (sub.Label != null) + { + sub.Label.Visible = selected; + } + } + } + } + + UpdateButtons(); + } + + private void Close(Framework.UIElement button) + { + SaveConfig(); + FindController().SwitchMode(ConnectArchiveMode.Landing); + } + + private void ValidateInputs(Framework.UIElement element) + { + StartButton.Disabled = NameInput.CurrentText.Length == 0; + } + + private void Start(Framework.UIElement button) + { + SaveConfig(); + + Visible = false; + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + var factory = new ArchiveServerFactory(Config, FindController()); + + factory.Start(selected, (bool success) => + { + if (!success) + { + Visible = true; + } + }); + } + + private void SaveConfig() + { + var clientConfig = ClientArchiveConfiguration.Default; + var selected = SaveCombo.SelectedItem as ArchiveManifest; + + var defaultName = clientConfig.GetDefaultServerName(); + clientConfig.ServerName = defaultName == NameInput.CurrentText ? "" : NameInput.CurrentText; + Config.Name = clientConfig.GetServerNameOrDefault(); + + clientConfig.ApplyHostConfig(Config); + clientConfig.SelectedArchiveName = selected?.Name ?? ""; + clientConfig.Save(); + } + + public static void UPnPHelp() + { + UIAlert.Alert(GetString("201"), GetString("211"), true); + } + + public static void AllOpenableHelp() + { + UIAlert.Alert(GetString("204"), GetString("214"), true); + } + + public static void DebugModeHelp() + { + UIAlert.Alert(GetString("205"), GetString("215"), true); + } + + public static void ArchivedCharacterHelp() + { + UIAlert.Alert(GetString("208"), GetString("218"), true); + } + + public static void VerificationHelp() + { + UIAlert.Alert(GetString("202"), GetString("212"), true); + } + + public static void CityEditorHelp() + { + UIAlert.Alert(GetString("203"), GetString("121"), true); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveDialog.cs new file mode 100644 index 000000000..a40184b37 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveDialog.cs @@ -0,0 +1,25 @@ +using FSO.Client.UI.Controls; + +namespace FSO.Client.UI.Archive +{ + public class UIArchiveDialog : UIDialog + { + public UIArchiveDialog(UIDialogStyle style, bool draggable) : base(style, draggable) + { + } + + public UIArchiveDialog(UIDialogStyle style, UIDialogExtras extras, bool draggable) : base(style, extras, draggable) + { + } + + protected static string GetString(string id) + { + return GameFacade.Strings.GetString("f128", id); + } + + protected static string GetString(string id, params string[] args) + { + return GameFacade.Strings.GetString("f128", id, args); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveDisplayName.cs b/TSOClient/tso.client/UI/Archive/UIArchiveDisplayName.cs new file mode 100644 index 000000000..88e88b3ed --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveDisplayName.cs @@ -0,0 +1,103 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.Common.Utils; +using System.Numerics; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveDisplayName: UIVBoxContainer + { + public static void ShowDisplayNameDialog(Callback onResult) + { + UIAlert alert = null; + alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("f128", "143"), + Message = GameFacade.Strings.GetString("f128", "144"), + TextEntry = true, + TextValue = ClientArchiveConfiguration.Default.PlayerName, + Buttons = UIAlertButton.OkCancel( + (btn) => + { + if (!ClientArchiveConfiguration.ValidDisplayName(alert.ResponseText)) + { + UIAlert.Alert( + GameFacade.Strings.GetString("f128", "82"), + GameFacade.Strings.GetString("f128", "83"), + true); + + return; + } + + onResult(alert.ResponseText); + UIScreen.RemoveDialog(alert); + }, + (btn) => { onResult(null); UIScreen.RemoveDialog(alert); } + ) + }, true); + } + + private UIHBoxContainer NameBox; + private UILabel NameLabel; + private UIButton EditButton; + + public event Action OnChange; + + public UIArchiveDisplayName() + { + var gd = GameFacade.GraphicsDevice; + var ui = Content.Content.Get().CustomUI; + + var titleStyle = TextStyle.DefaultLabel.Clone(); + titleStyle.Size = 8; + + Spacing = 0; + + Add(new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "145"), + CaptionStyle = titleStyle, + }); + + NameBox = new UIHBoxContainer() + { + VerticalAlignment = UIContainerVerticalAlignment.Middle + }; + + NameBox.Add(NameLabel = new UILabel() + { + Caption = ClientArchiveConfiguration.Default.PlayerName + }); + + NameBox.Add(EditButton = new UIButton(ui.Get("archive_edit.png").Get(gd)) + { + Tooltip = GameFacade.Strings.GetString("f128", "146") + }); + + Add(NameBox); + + AutoSize(); + + EditButton.OnButtonClick += EditName; + } + + private void EditName(Framework.UIElement button) + { + ShowDisplayNameDialog((newName) => + { + if (newName != null) + { + NameLabel.Caption = newName; + NameLabel.Size = Vector2.Zero; + AutoSize(); + + OnChange?.Invoke(newName); + + ClientArchiveConfiguration.Default.PlayerName = newName; + ClientArchiveConfiguration.Default.Save(); + } + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveEventsDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveEventsDialog.cs new file mode 100644 index 000000000..58ecb3453 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveEventsDialog.cs @@ -0,0 +1,488 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveEventsDialog : UIArchiveDialog + { + private ArchiveConfiguration Config; + private EventConfig Events; + private TextStyle ModifierHeaderStyle; + private TextStyle GroupHeaderStyle; + + private UIVBoxContainer RootVBox; + private UIVBoxContainer ModifierVBox; + private UIHBoxContainer TabHBox; + private UIContainer ActiveModifierEditor; + private UIButton[] ModifierButtons; + private UIContainer[] ModifierEditors; + + private UIHBoxContainer ManualHBox; + private UIButton ManualClearButton; + private UIButton ManualTimedButton; + + private UILabel TimedDuration; + + private List CheckUpdateCallbacks; + + private bool ManualMode = false; + private bool IsChanged = false; + + public UIArchiveEventsDialog(ArchiveConfiguration config) : base(UIDialogStyle.OK, true) + { + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; + var tabTex = custom.Get("archive_tab.png").Get(gd); + + Caption = GetString("310"); + Config = config; + + config.LoadEvents(); + + Events = config.Events ?? new EventConfig() { catalog = [], modifiers = [] }; + ManualMode = !Events.timed; + + CheckUpdateCallbacks = []; + + ModifierHeaderStyle = TextStyle.DefaultLabel.Clone(); + ModifierHeaderStyle.Shadow = true; + ModifierHeaderStyle.Color = Color.White; + ModifierHeaderStyle.Size = 16; + + GroupHeaderStyle = TextStyle.DefaultLabel.Clone(); + GroupHeaderStyle.Shadow = true; + GroupHeaderStyle.Size = 14; + + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + RootVBox = vbox; + + var modeHbox = new UIHBoxContainer() { Spacing = 16 }; + + modeHbox.Add(new UILabel() + { + Caption = GetString("311") // Event schedule: + }); + AddCheck(modeHbox, GetString("312"), (check) => check.Selected = !ManualMode, (elem) => ManualMode = false, true); // Timed + AddCheck(modeHbox, GetString("313"), (check) => check.Selected = ManualMode, (elem) => ManualMode = true, true); // Manual + + modeHbox.AutoSize(); + + vbox.Add(modeHbox); + vbox.Add(new UISpacer(10)); + + var modifierVBox = new UIVBoxContainer(); + ModifierVBox = modifierVBox; + + var tabHbox = new UIHBoxContainer() { Spacing = 0 }; + + ModifierButtons = new UIButton[Events.modifiers.Length]; + ModifierEditors = new UIContainer[Events.modifiers.Length]; + + for (int i = 0; i < Events.modifiers.Length; i++) + { + var modifier = Events.modifiers[i]; + + var btn = new UIButton() + { + Texture = tabTex, + Caption = modifier.label, + AutoMargins = 32 + }; + + int btnI = i; + btn.OnButtonClick += (elem) => + { + SetModifierEditor(btnI); + }; + + ModifierButtons[i] = btn; + + tabHbox.Add(btn); + } + + modifierVBox.Add(tabHbox); + TabHBox = tabHbox; + + for (int i = 0; i < Events.modifiers.Length; i++) + { + ModifierEditors[i] = GenerateModifier(i); + } + + modifierVBox.Add(new UISpacer(10)); + + modifierVBox.AutoSize(); + vbox.Add(modifierVBox); + + var manualHbox = new UIHBoxContainer(); + manualHbox.Add(ManualClearButton = new UIButton() + { + Caption = GetString("314") + }); + manualHbox.Add(ManualTimedButton = new UIButton() + { + Caption = GetString("315") + }); + + ManualClearButton.OnButtonClick += ClearManual; + ManualTimedButton.OnButtonClick += SimulateTimed; + + ManualHBox = manualHbox; + + TimedDuration = new UILabel(); + TimedDuration.CaptionStyle = TimedDuration.CaptionStyle.Clone(); + TimedDuration.CaptionStyle.Shadow = true; + TimedDuration.CaptionStyle.Color = Color.White; + + vbox.Add(TimedDuration); + + UpdateModifierButtons(); + + vbox.Position = new Vector2(20, 45); + + if (Events.modifiers.Length > 0) + { + SetModifierEditor(0); + } + else + { + AutoSize(); + } + + Add(vbox); + + OKButton.OnButtonClick += OKButton_OnButtonClick; + } + + private void SimulateTimed(UIElement button) + { + // Matches manual with timed in the selected category. + var i = Array.IndexOf(ModifierEditors, ActiveModifierEditor); + + if (i == -1) + { + return; + } + + ref var modifier = ref Events.modifiers[i]; + + for (int j = 0; j < modifier.options.Length; j++) + { + ref var option = ref modifier.options[j]; + + if (option.enableTimed) + { + ClearOverlapping(i, in option); + option.enableManual = true; + } + } + + UpdateCheckButtons(); + } + + private void ClearManual(UIElement button) + { + // Clears the selected category. + var i = Array.IndexOf(ModifierEditors, ActiveModifierEditor); + + if (i == -1) + { + return; + } + + ref var modifier = ref Events.modifiers[i]; + + for (int j = 0; j < modifier.options.Length; j++) + { + modifier.options[j].enableManual = false; + } + + UpdateCheckButtons(); + } + + private int GetManualCount(in EventModifier modifier) + { + int count = 0; + + foreach (var option in modifier.options) + { + if (option.enableManual) + { + count++; + } + } + + return count; + } + + private void UpdateModifierButtons() + { + for (int i = 0; i < ModifierButtons.Length; i++) + { + var button = ModifierButtons[i]; + var modifier = Events.modifiers[i]; + + button.Caption = ManualMode ? $"{modifier.label} ({GetManualCount(in modifier)})" : modifier.label; + } + + TabHBox.AutoSize(); + + bool manualHboxVisible = ManualHBox.Parent?.GetChildren().Contains(ManualHBox) ?? false; + + if (manualHboxVisible != ManualMode) + { + if (ManualMode) + { + RootVBox.Add(ManualHBox); + RootVBox.Remove(TimedDuration); + } + else + { + RootVBox.Remove(ManualHBox); + RootVBox.Add(TimedDuration); + } + } + + AutoSize(); + } + + private void UpdateCheckButtons() + { + foreach (var action in CheckUpdateCallbacks) + { + action(); + } + + UpdateModifierButtons(); + } + + private static Texture2D GetCheckTexture(bool radio) + { + return GetTexture(radio ? 0x0000045200000001u : 0x0000083600000001u); + } + + private void AutoSize() + { + var vbox = RootVBox; + vbox.AutoSize(); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + } + + private void SetModifierEditor(int i) + { + var vbox = ModifierVBox; + var children = vbox.GetChildren(); + int insertIndex = children.IndexOf(TabHBox) + 1; + if (ActiveModifierEditor != null) + { + insertIndex = children.IndexOf(ActiveModifierEditor); + vbox.Remove(ActiveModifierEditor); + } + + ActiveModifierEditor = ModifierEditors[i]; + + vbox.AddAt(insertIndex, ActiveModifierEditor); + + for (int j = 0; j < ModifierButtons.Length; j++) + { + ModifierButtons[j].Selected = j == i; + } + + var modifier = Events.modifiers[i]; + var (start, end) = EventConfig.GetNextRange(modifier.startDate, modifier.endDate); + TimedDuration.Caption = $"{start:d} - {end:d}"; + TimedDuration.AutoSize(); + + AutoSize(); + } + + private void OKButton_OnButtonClick(UIElement button) + { + if (IsChanged) + { + Events.timed = !ManualMode; + + Config.Events = Events; + + Config.SaveEvents(); + } + + UIScreen.RemoveDialog(this); + } + + private ref bool GetCheckVar(ref EventModifierOption option) + { + if (ManualMode) + { + return ref option.enableManual; + } + else + { + return ref option.enableTimed; + } + } + + private void AddCheck(UIContainer container, string label, Action updateChecked, ButtonClickDelegate onClick, bool radio = false) + { + var hbox = new UIHBoxContainer(); + + var check = new UIButton(GetCheckTexture(radio)); + + Action updateMethod = () => + { + updateChecked(check); + }; + + check.OnButtonClick += (elem) => + { + onClick(elem); + + IsChanged = true; + + UpdateCheckButtons(); + }; + + CheckUpdateCallbacks.Add(updateMethod); + + updateMethod(); + + hbox.Add(check); + + var labelElem = new UILabel() + { + Caption = label + }; + + hbox.Add(labelElem); + + hbox.AutoSize(); + container.Add(hbox); + } + + private void ClearOverlapping(int modifierId, in EventModifierOption option) + { + if (option.unique == null) + { + return; + } + + // Need to clear all other overlapping uniques before checking this one. + + if (ManualMode) + { + for (int i = 0; i < Events.modifiers.Length; i++) + { + ref var modifier = ref Events.modifiers[i]; + + for (int j = 0; j < modifier.options.Length; j++) + { + ref var otherOption = ref modifier.options[j]; + + if (otherOption.unique == option.unique) + { + GetCheckVar(ref otherOption) = false; + } + } + } + } + else + { + // For timed, it's just within the same modifier. + ref var modifier = ref Events.modifiers[modifierId]; + + for (int j = 0; j < modifier.options.Length; j++) + { + ref var otherOption = ref modifier.options[j]; + + if (otherOption.unique == option.unique) + { + GetCheckVar(ref otherOption) = false; + } + } + } + } + + private void GenerateOption(UIContainer container, int modifierId, int optionId) + { + var option = Events.modifiers[modifierId].options[optionId]; + + AddCheck( + container, + option.label, + (check) => + { + var option = Events.modifiers[modifierId].options[optionId]; + + check.Selected = GetCheckVar(ref option); + }, + (elem) => + { + ref var option = ref Events.modifiers[modifierId].options[optionId]; + ref var isChecked = ref GetCheckVar(ref option); + + if (!isChecked) + { + ClearOverlapping(modifierId, in option); + } + + isChecked = !isChecked; + }, + option.unique != null); + } + + private void GenerateOptionGroup(UIContainer container, string categoryLabel, int modifierId, int[] optionIds) + { + var vbox = new UIVBoxContainer(); + + var label = new UILabel() + { + Caption = categoryLabel, + CaptionStyle = GroupHeaderStyle, + }; + + vbox.Add(label); + + foreach (int option in optionIds) + { + GenerateOption(vbox, modifierId, option); + } + + vbox.AutoSize(); + container.Add(vbox); + } + + private UIContainer GenerateModifier(int modifierId) + { + var modifier = Events.modifiers[modifierId]; + var vbox = new UIVBoxContainer(); + + var optByCategory = modifier.options.Select((x, index) => (index, x)).GroupBy((option) => option.x.category).ToArray(); + + for (int i = 0; i < optByCategory.Length; i += 2) + { + var hbox = new UIHBoxContainer(); + hbox.Add(new UISpacer(20)); + + var groupOne = optByCategory[i]; + + GenerateOptionGroup(hbox, groupOne.First().x.category, modifierId, groupOne.Select(x => x.index).ToArray()); + + if (i + 1 < optByCategory.Length) + { + hbox.Add(new UISpacer(20)); + + var groupTwo = optByCategory[i + 1]; + GenerateOptionGroup(hbox, groupTwo.First().x.category, modifierId, groupTwo.Select(x => x.index).ToArray()); + } + + vbox.Add(hbox); + } + + return vbox; + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveGameplayScale.cs b/TSOClient/tso.client/UI/Archive/UIArchiveGameplayScale.cs new file mode 100644 index 000000000..b0b104d0e --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveGameplayScale.cs @@ -0,0 +1,344 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveGameplayScale : UIArchiveDialog + { + public UITextBox FundsInput; + + public UISlider SkillSlider; + public UILabel SkillDisplay; + + public UISlider PayoutSlider; + public UILabel PayoutDisplay; + + public UISlider PenaltySlider; + public UILabel PenaltyDisplay; + + public UIButton SpeedyJobCheck; + + public UIButton HelpButton; + public UIButton ResetButton; + + private readonly ArchiveConfiguration Config; + private EventConfig Events; + private bool IsChanged; + + public UIArchiveGameplayScale(ArchiveConfiguration config) : base(UIDialogStyle.OK, true) + { + Config = config; + Caption = GetString("300"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + config.LoadEvents(); + + Events = config.Events ?? new EventConfig() { catalog = [], modifiers = [] }; + + TextStyle style = TextStyle.DefaultLabel.Clone(); + + style.Shadow = true; + style.Color = Color.White; + + UILabel desc; + + vbox.Add(desc = new UILabel() + { + Caption = GetString("301"), + Wrapped = true + }); + + desc.Size = new Vector2(320, 90); + + var fundsBox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Left, Spacing = 0 }; + + fundsBox.Add(new UILabel() + { + Caption = GetString("302") + }); + + fundsBox.Add(new UISpacer(250, 5)); + + var fundsBox2 = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + fundsBox2.Add(new UILabel() { Caption = "$" }); + + fundsBox2.Add(FundsInput = new UITextBox() + { + Size = new Vector2(100, 25), + CurrentText = config.InitialFunds.ToString() + }); + + fundsBox2.AutoSize(); + + fundsBox.Add(fundsBox2); + + FundsInput.OnChange += FundsInput_OnChange; + + vbox.Add(fundsBox); + + vbox.Add(new UISpacer(10)); + + var skillBox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Left }; + + skillBox.Add(new UILabel() + { + Caption = GetString("303") + }); + + skillBox.Add(SkillSlider = new UISlider() + { + Orientation = 0, + Texture = GetTexture(0x42500000001), + Size = new Vector2(250, 10), + MinValue = 1, + MaxValue = 25, + AllowDecimals = true, + Value = Events.skillSpeed ?? 1, + }); + + skillBox.Add(SkillDisplay = new UILabel() + { + Size = new Vector2(250, 10), + Alignment = TextAlignment.Center, + CaptionStyle = style + }); + + vbox.Add(skillBox); + + vbox.Add(new UISpacer(10)); + + var payoutBox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Left }; + + payoutBox.Add(new UILabel() + { + Caption = GetString("304") + }); + + payoutBox.Add(PayoutSlider = new UISlider() + { + Orientation = 0, + Texture = GetTexture(0x42500000001), + Size = new Vector2(250, 10), + MinValue = 1, + MaxValue = 10, + AllowDecimals = true, + Value = Events.payoutScale ?? 1 + }); + + payoutBox.Add(PayoutDisplay = new UILabel() + { + Size = new Vector2(250, 10), + Alignment = TextAlignment.Center, + CaptionStyle = style + }); + + vbox.Add(payoutBox); + + vbox.Add(new UISpacer(10)); + + var penaltyBox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Left }; + + penaltyBox.Add(new UILabel() + { + Caption = GetString("305") + }); + + penaltyBox.Add(PenaltySlider = new UISlider() + { + Orientation = 0, + Texture = GetTexture(0x42500000001), + Size = new Vector2(250, 10), + MinValue = 0, + MaxValue = 1, + AllowDecimals = true, + Value = Events.singleplayerPenalty ?? 1 + }); + + penaltyBox.Add(PenaltyDisplay = new UILabel() + { + Size = new Vector2(250, 10), + Alignment = TextAlignment.Center, + CaptionStyle = style + }); + + vbox.Add(penaltyBox); + + var jobBox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + jobBox.Add(SpeedyJobCheck = new UIButton(GetTexture(0x0000083600000001)) + { + Tooltip = GetString("307") + }); + + jobBox.Add(new UILabel() + { + Caption = GetString("306"), + Tooltip = GetString("307") + }); + + vbox.Add(new UISpacer(10)); + + vbox.Add(jobBox); + + vbox.Add(new UISpacer(10)); + + var buttonsBox = new UIHBoxContainer(); + + buttonsBox.Add(HelpButton = new UIButton() + { + Caption = GetString("127") + }); + + buttonsBox.Add(ResetButton = new UIButton() + { + Caption = GetString("128") + }); + + vbox.Add(buttonsBox); + + Add(vbox); + + HelpButton.OnButtonClick += HelpButton_OnButtonClick; + ResetButton.OnButtonClick += ResetButton_OnButtonClick; + + SkillSlider.OnChange += SkillSlider_OnChange; + PayoutSlider.OnChange += PayoutSlider_OnChange; + PenaltySlider.OnChange += PenaltySlider_OnChange; + SpeedyJobCheck.OnButtonClick += SpeedyJobCheck_OnButtonClick; + + UpdateDisplay(SkillDisplay, SkillSlider); + UpdateDisplay(PayoutDisplay, PayoutSlider); + UpdateDisplay(PenaltyDisplay, PenaltySlider, true); + SpeedyJobCheck.Selected = Events.speedyJobProgression == 1; + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + + OKButton.OnButtonClick += (elem) => + { + if (IsChanged) + { + config.Events = Events; + + config.SaveEvents(); + } + + UIScreen.RemoveDialog(this); + }; + } + + private void SpeedyJobCheck_OnButtonClick(UIElement button) + { + IsChanged = true; + + if (SpeedyJobCheck.Selected) + { + Events.speedyJobProgression = 0; + } + else + { + Events.speedyJobProgression = 1; + } + + SpeedyJobCheck.Selected = Events.speedyJobProgression == 1; + } + + private void FundsInput_OnChange(UIElement element) + { + if (int.TryParse(FundsInput.CurrentText, out int funds) && funds >= 0) + { + Config.InitialFunds = funds; + } + else + { + Config.InitialFunds = 0; + } + } + + private void ResetButton_OnButtonClick(UIElement button) + { + UIAlert alert = null; + alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GetString("128"), + Message = GetString("129"), + Buttons = [ + new UIAlertButton(UIAlertButtonType.Yes, (btn) => { Reset(ref Events, true); UpdateAll(); UIScreen.RemoveDialog(alert); }, GetString("130")), + new UIAlertButton(UIAlertButtonType.No, (btn) => { Reset(ref Events, false); UpdateAll(); UIScreen.RemoveDialog(alert); }, GetString("131")) + ] + }, true); + } + + private static void Reset(ref EventConfig events, bool tso) + { + events.skillSpeed = tso ? null : 5; + events.payoutScale = tso ? null : 5; + events.singleplayerPenalty = tso ? null : 0; + events.speedyJobProgression = tso ? 0 : 1; + } + + private void UpdateAll() + { + SkillSlider.Value = Events.skillSpeed ?? 1; + PayoutSlider.Value = Events.payoutScale ?? 1; + PenaltySlider.Value = Events.singleplayerPenalty ?? 1; + SpeedyJobCheck.Selected = Events.speedyJobProgression == 1; + + UpdateDisplay(SkillDisplay, SkillSlider); + UpdateDisplay(PayoutDisplay, PayoutSlider); + UpdateDisplay(PenaltyDisplay, PenaltySlider, true); + } + + private void HelpButton_OnButtonClick(UIElement button) + { + UIAlert alert = null; + alert = UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Title = GetString("127"), + Message = GetString("126"), + Width = 600, + Buttons = [ + new UIAlertButton(UIAlertButtonType.OK, (btn) => { UIScreen.RemoveDialog(alert); }), + ] + }, true); + } + + private void UpdateDisplay(UILabel display, UISlider slider, bool percent = false) + { + var value = slider.Value; + + display.Caption = percent ? $"{(value * 100).ToString("0.00")}%" : $"{value.ToString("0.00")}x"; + } + + private void PayoutSlider_OnChange(UIElement element) + { + IsChanged = true; + + Events.payoutScale = PayoutSlider.Value; + UpdateDisplay(PayoutDisplay, PayoutSlider); + } + + private void SkillSlider_OnChange(UIElement element) + { + IsChanged = true; + + Events.skillSpeed = SkillSlider.Value; + UpdateDisplay(SkillDisplay, SkillSlider); + } + + private void PenaltySlider_OnChange(UIElement element) + { + IsChanged = true; + + Events.singleplayerPenalty = PenaltySlider.Value; + UpdateDisplay(PenaltyDisplay, PenaltySlider, true); + } + + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveHostInformation.cs b/TSOClient/tso.client/UI/Archive/UIArchiveHostInformation.cs new file mode 100644 index 000000000..16d991cae --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveHostInformation.cs @@ -0,0 +1,382 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.UI.Controls; +using FSO.UI.Model; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Net; +using System.Net.NetworkInformation; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveHostInformation : UIArchiveDialog + { + private enum ArchiveServerType : int + { + ThisClient, + OtherClient, + Dedicated, + Offline + } + + public UIVBoxContainer Container; + public UILabel ServerTypeLabel; + public UILabel ServerWarningLabel; + public UIButton ShowIPButton; + public UIButton DiscordButton; + + public UIHBoxContainer PublicIPContainer; + public UILabel PublicIPLabel; + + private ArchiveServerType ServerType; + private Texture2D CopyButtonTexture; + private TextStyle TitleStyle; + private TextStyle InterfaceStyle; + private TextStyle CopyStyle; + + private string Port; + + public UIArchiveHostInformation(CoreGameScreenController controller) : base(UIDialogStyle.Close, false) + { + Caption = GetString("19"); + var ui = Content.Content.Get().CustomUI; + CopyButtonTexture = ui.Get("chat_cat.png").Get(GameFacade.GraphicsDevice); + + TitleStyle = TextStyle.DefaultLabel.Clone(); + TitleStyle.Color = Color.White; + TitleStyle.Shadow = true; + TitleStyle.Size = 15; + + InterfaceStyle = TextStyle.DefaultLabel.Clone(); + InterfaceStyle.Color = new Color(new Vector3(0.7f)); + + CopyStyle = TextStyle.DefaultLabel.Clone(); + CopyStyle.Size = 8; + CopyStyle.Shadow = true; + + DiscordButton = new UIButton(ui.Get("archive_discord.png").Get(GameFacade.GraphicsDevice)); + DiscordButton.OnButtonClick += EnableDiscord; + + Add(DiscordButton); + + UpdateDiscordButtonState(); + + var addr = controller.ArchiveHost.CityAddress; + int colonInd = addr.LastIndexOf(':'); + Port = colonInd == -1 ? ":33101" : addr.Substring(colonInd); + + var warningStyle = TextStyle.DefaultLabel.Clone(); + warningStyle.Size--; + warningStyle.Color = new Color(255, 122, 77); + + var serverType = GetServerType(controller); + ServerType = serverType; + + var vbox = new UIVBoxContainer() + { + HorizontalAlignment = UIContainerHorizontalAlignment.Center + }; + vbox.Position = new Vector2(20, 45); + + vbox.Add(ServerTypeLabel = new UILabel() + { + Caption = GetString((20 + (int)serverType).ToString()), + Wrapped = true, + }); + + if (serverType < ArchiveServerType.Dedicated) + { + vbox.Add(ServerWarningLabel = new UILabel() + { + Caption = GetString("24"), + CaptionStyle = warningStyle + }); + } + + if (serverType < ArchiveServerType.Offline) + { + vbox.Add(new UISpacer(1, 8)); + + vbox.Add(ShowIPButton = new UIButton() + { + Caption = GetString("25") + }); + + ShowIPButton.OnButtonClick += ShowIPs; + } + + Add(vbox); + + Container = vbox; + + RecalculateSize(); + + Background.BlockInput(); + } + + private void UpdateDiscordButtonState() + { + var enabled = DiscordRpcEngine.PublicArchive; + + DiscordButton.Tooltip = GetString(enabled ? "112" : "110"); + DiscordButton.Disabled = enabled; + } + + private void EnableDiscord(UIElement button) + { + DiscordButton.Disabled = true; + + if (ServerType == ArchiveServerType.ThisClient) + { + DetermineMyPublicIp().ContinueWith((task) => + { + GameThread.InUpdate(() => + { + if (task.IsCanceled || task.IsFaulted) + { + UIAlert.Alert("", GetString("119"), true); + } + else + { + DiscordRpcEngine.SetArchiveAddress(AddPort(task.Result)); + UIAlert.Alert(GetString("110"), GetString("111"), true); + } + + UpdateDiscordButtonState(); + }); + }); + } + else + { + var ip = FindController().ArchiveHost.CityAddress; + + DiscordRpcEngine.SetArchiveAddress(ip); + UIAlert.Alert(GetString("110"), GetString("111"), true); + + UpdateDiscordButtonState(); + } + } + + public override void Update(UpdateState state) + { + PositionDialog(); + + base.Update(state); + } + + private ArchiveServerType GetServerType(CoreGameScreenController controller) + { + if (controller == null || controller.Mode != Regulators.CityConnectionMode.ARCHIVE || controller.ArchiveConfig.HasFlag(Common.ArchiveConfigFlags.Offline)) + { + return ArchiveServerType.Offline; + } + + if (controller.ArchiveHost.SelfHost) + { + return ArchiveServerType.ThisClient; + } + + if (controller.ArchiveConfig.HasFlag(Common.ArchiveConfigFlags.DedicatedServer)) + { + return ArchiveServerType.Dedicated; + } + + return ArchiveServerType.OtherClient; + } + + private void PositionDialog() + { + var screenWidth = GameFacade.Screens.CurrentUIScreen.ScreenWidth; + + var pos = new Vector2(MathF.Round((screenWidth - Size.X) / 2), 24); + + if (Position != pos) + { + Position = pos; + } + } + + public void ShowIPs(UIElement element) + { + var vbox = Container; + vbox.Remove(ShowIPButton); + + vbox.Add(new UILabel() + { + Caption = GetString("26"), // Public IP: + CaptionStyle = TitleStyle, + }); + + var hbox = new UIHBoxContainer(); + hbox.Add(PublicIPLabel = new UILabel() + { + Caption = GetString("27") // fetching + }); + hbox.AutoSize(); + + PublicIPContainer = hbox; + + vbox.Add(hbox); + + if (ServerType == ArchiveServerType.ThisClient) + { + DetermineMyPublicIp(); + + vbox.Add(new UISpacer(1, 8)); + + // Display private IPs too + vbox.Add(new UILabel() + { + Caption = GetString("28"), // Private IPs: + CaptionStyle = TitleStyle, + }); + + NetworkInterface[] network = NetworkInterface.GetAllNetworkInterfaces(); + + foreach (NetworkInterface intf in network) + { + var props = intf.GetIPProperties(); + foreach (var unicast in props.UnicastAddresses) + { + if (unicast.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork) + { + var ip = unicast.Address.ToString(); + + if (ip == "127.0.0.1") + { + // Not really useful to know the loopback. + continue; + } + + hbox = new UIHBoxContainer(); + hbox.Add(new UILabel() + { + Caption = $"{intf.Name}:", + CaptionStyle = InterfaceStyle + }); + hbox.Add(new UILabel() + { + Caption = AddPort(ip) + }); + + AddCopyButton(hbox, AddPort(ip)); + + vbox.Add(hbox); + } + } + } + } + else + { + var ip = FindController().ArchiveHost.CityAddress; + + PublicIPLabel.Caption = ip; + PublicIPLabel.Size = default; + PublicIPLabel.AutoSize(); + AddCopyButton(PublicIPContainer, ip); + RecalculateSize(); + } + + RecalculateSize(); + } + + private void AddCopyButton(UIHBoxContainer container, string copyString) + { + var btn = new UIButton() + { + Texture = CopyButtonTexture, + Caption = GetString("33"), // Copy + CaptionStyle = CopyStyle, + }; + + btn.OnButtonClick += (elem) => + { + ClipboardHandler.Default.Set(copyString); + UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Message = GetString("34"), // Copied to clipboard + }, true); + }; + + container.Add(btn); + + container.AutoSize(); + } + + private void RecalculateSize() + { + var vbox = Container; + + vbox.AutoSize(); + + if (ShowIPButton != null) + { + var showIPBase = vbox.Position + ShowIPButton.Position; + + DiscordButton.Position = new Vector2(vbox.Size.X - 22, showIPBase.Y + 4); + } + else + { + DiscordButton.Visible = false; + } + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + PositionDialog(); + } + + private string AddPort(string ip) + { + // When the port is different from the default, + // append it to the IP. + return Port == ":33101" ? ip : (ip + Port); + } + + private Task DetermineMyPublicIp() + { + return Task.Run(() => + { + WebClient webClient = new WebClient(); + + string result; + try + { + result = webClient.DownloadString("https://api.ipify.org"); + } + catch + { + result = null; + } + + if (result != null && !IPAddress.TryParse(result, out IPAddress addr)) + { + result = null; + } + + GameThread.InUpdate(() => + { + if (PublicIPLabel != null) + { + if (result == null) + { + PublicIPLabel.Caption = GetString("35"); + } + else + { + PublicIPLabel.Caption = AddPort(result); + PublicIPLabel.Size = default; + PublicIPLabel.AutoSize(); + AddCopyButton(PublicIPContainer, AddPort(result)); + RecalculateSize(); + } + } + }); + + return result; + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveJoinDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveJoinDialog.cs new file mode 100644 index 000000000..e4d6de5a1 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveJoinDialog.cs @@ -0,0 +1,346 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Archive.Management; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Clients; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + internal class UIJoinServerEntry + { + private readonly UIArchiveJoinDialog Parent; + public readonly ClientArchiveHistoryItem Item; + + public ClientArchiveHistoryType ServerType => Item.ServerType; + public string Name => Item.Name; + public string Address => Item.Address; + + public bool IsFetching = true; + + public StatusCheckResult? Result; + + public UIJoinServerEntry(UIArchiveJoinDialog parent, ClientArchiveHistoryItem item) + { + Parent = parent; + Item = item; + + Task.Run(RefreshStatus); + } + + public async Task RefreshStatus() + { + StatusCheckResult result; + switch (Item.ServerType) + { + case ClientArchiveHistoryType.FreeSO: + // If the server is FreeSO, try and request the `/userapi/status.json`.07 + result = await StatusChecker.FreeSOStatus(Item.Address); + break; + case ClientArchiveHistoryType.Archive: + case ClientArchiveHistoryType.DiscordArchive: + // If it's archive, start a connection to the server, then disconnect after getting the RequestClientSessionArchive packet. + // Disconnect after two seconds of not receiving this packet. + result = await StatusChecker.ArchiveStatus(FSOFacade.Kernel, Item.Address); + break; + default: + return; + } + + GameThread.InUpdate(() => + { + IsFetching = false; + Result = result; + + // If the result's name/address doesn't match the saved one, we need to update it. + // TODO: save back to the config? + if (result.IsOnline) + { + if (Item.Name != result.Name) + { + Item.Name = result.Name; + } + } + + Parent?.UpdateServerTable(); // TODO: update just this item? + }); + } + } + + internal class UIArchiveJoinDialog : UIArchiveDialog + { + public UIArchiveDisplayName DisplayName; + public UIButton AddServerButton; + public UIButton JoinButton; + + private UIHBoxContainer ButtonBox; + private UIVBoxContainer CurrentLayout; + + private UIJoinServerEntry[] Servers; + private UIGenericTable ServerTable; + + private readonly Texture2D ActionsButtonTexture; + private readonly Texture2D ServerFreeSOIcon; + private readonly Texture2D ServerArchiveIcon; + private readonly Texture2D ServerDiscordIcon; + + public UIArchiveJoinDialog() : base(UIDialogStyle.Close, true) + { + Caption = GetString("99"); + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + ActionsButtonTexture = ui.Get("archive_burgermenu.png").Get(gd); + + ServerArchiveIcon = ui.Get("archive_simuser.png").Get(gd); + ServerFreeSOIcon = ui.Get("archive_simshared.png").Get(gd); + ServerDiscordIcon = ui.Get("archive_discordserver.png").Get(gd); + + ButtonBox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + ButtonBox.Add(AddServerButton = new UIButton() { Caption = GetString("147") }); + ButtonBox.Add(JoinButton = new UIButton() { Caption = "Join", Disabled = true }); + ButtonBox.AutoSize(); + + ServerTable = new UIGenericTable([ + new UITableColumn("", 22), + new UITableColumn(GetString("133"), 192), + new UITableColumn(GetString("134"), 64), + new UITableColumn(GetString("135"), 64), + new UITableColumn("", 14), + ], 250) + { Loading = false }; + + DisplayName = new UIArchiveDisplayName(); + + AddServerButton.OnButtonClick += AddServer; + JoinButton.OnButtonClick += Submit; + CloseButton.OnButtonClick += Close; + + BuildLayout(); + + var config = ClientArchiveConfiguration.Default; + + Servers = [..config.JoinHistory.Select(x => + { + return new UIJoinServerEntry(this, x); + })]; + + UpdateServerTable(); + + ServerTable.OnChange += SelectionChanged; + } + + private void AddServer(UIElement button) + { + var dialog = new UIArchiveAddServerDialog(AddServerResult); + + GameScreen.ShowDialog(dialog, true); + } + + private void AddServerResult(UIAddServerResult info) + { + var newServerInfo = new ClientArchiveHistoryItem(info.IsFreeSO ? ClientArchiveHistoryType.FreeSO : ClientArchiveHistoryType.Archive, info.Status.Name, info.Address, 0); + + var config = ClientArchiveConfiguration.Default; + config.RegisterJoin(newServerInfo); + + Servers = [..config.JoinHistory.Select(x => + Servers.FirstOrDefault(y => y.ServerType == x.ServerType && y.Address == x.Address) ?? new UIJoinServerEntry(this, x))]; + + UpdateServerTable(); + } + + private void SelectionChanged(UIElement element) + { + JoinButton.Disabled = ServerTable.SelectedIndex == -1 || (ServerTable.SelectedItem.Data as UIJoinServerEntry)?.Result?.IsOnline != true; + } + + private Texture2D GetTypeIcon(ClientArchiveHistoryType type) + { + switch (type) + { + case ClientArchiveHistoryType.Archive: + return ServerArchiveIcon; + case ClientArchiveHistoryType.FreeSO: + return ServerFreeSOIcon; + case ClientArchiveHistoryType.DiscordArchive: + return ServerDiscordIcon; + } + + return null; + } + + private void Refresh(UIJoinServerEntry server) + { + server.IsFetching = true; + Task.Run(server.RefreshStatus); + + UpdateServerTable(); + } + + private void Forget(UIJoinServerEntry server) + { + // Remove the server from this list (and the saved history) + + Servers = [.. Servers.Where(item => item != server)]; + + ClientArchiveConfiguration.Default.RemoveJoin(server.Item); + + UpdateServerTable(); + } + + private void CopyIP(UIJoinServerEntry server) + { + ClipboardHandler.Default.Set(server.Address); + UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Message = GetString("34"), // Copied to clipboard + }, true); + } + + private void OpenActions(UIElement anchor, UIJoinServerEntry server) + { + var items = new List + { + new(GetString("139"), () => { Refresh(server); }), + new(GetString("136"), () => { Forget(server); }), + new(GetString("137"), () => { CopyIP(server); }), + }; + + new UIContextMenu(anchor, items, ServerTable); + } + + public void UpdateServerTable() + { + ServerTable.Items.Clear(); + var items = ServerTable.Items; + + // First, stable sort the servers by online status, so the online servers always appear at the top. + + var orderedServers = Servers.OrderBy(server => !(server.Result?.IsOnline ?? false)); + + foreach (var server in orderedServers) + { + var actionButton = new UIButton(ActionsButtonTexture); + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, server); + }; + + var status = server.IsFetching ? null : server.Result; + + items.Add(new UIListBoxItem( + server, + GetTypeIcon(server.ServerType), + server.Name, + status?.Version?.id ?? "", + status == null ? "--" : (status.Value.IsOnline ? status.Value.Players.ToString() : GetString("138")), + actionButton) + { + Disabled = status?.IsOnline != true + }); + } + + ServerTable.Items = items; + } + + private void BuildLayout() + { + if (CurrentLayout != null) + Remove(CurrentLayout); + + CurrentLayout = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + CurrentLayout.Add(ServerTable); + CurrentLayout.Add(ButtonBox); + CurrentLayout.AutoSize(); + CurrentLayout.Position = new Vector2(20, 40); + SetSize((int)CurrentLayout.Size.X + 40, (int)CurrentLayout.Size.Y + 60); + Add(CurrentLayout); + + DisplayName.AutoSize(); + + CurrentLayout.Add(DisplayName); + DisplayName.Position = new Vector2(0, ButtonBox.Y + (ButtonBox.Size.Y - DisplayName.Size.Y) / 2); + + //JoinButton.Caption = server ? "Connect" : "Join"; + } + + private void Close(UIElement button) + { + FindController().SwitchMode(ConnectArchiveMode.Landing); + } + + private FSOVersionInfo GetTargetUpdate(UIJoinServerEntry server) + { + var current = FSOVersionInfo.Current; + + if (server.Result == null) + { + return null; + } + + var target = server.Result.Value.Version; + + return current.Equals(target) ? null : target; + } + + private void Join(UIJoinServerEntry selected) + { + ClientArchiveConfiguration.Default.RegisterJoin(selected.Item); + + if (selected.ServerType == ClientArchiveHistoryType.FreeSO) + { + var url = selected.Address; + + UIScreen.RemoveDialog(this); + FSOFacade.Controller.ShowServerLogin(url); + } + else + { + var displayName = ClientArchiveConfiguration.Default.PlayerName; + FSOFacade.Controller.ConnectToArchive(displayName, selected.Address, false); + } + } + + private void Submit(UIElement button) + { + var item = ServerTable.SelectedItem; + if (JoinButton.Disabled || item == null) + return; + + var selected = item.Data as UIJoinServerEntry; + var update = GetTargetUpdate(selected); + + if (update != null) + { + var controller = new UpdateController((bool skip) => + { + if (skip) + { + Join(selected); + } + }); + + controller.PromptUpdate(update); + } + else + { + Join(selected); + } + } + + public override void Update(UpdateState state) + { + base.Update(state); + FindController().TickRPC(); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveJoinRPCDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveJoinRPCDialog.cs new file mode 100644 index 000000000..6b4f1e0dc --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveJoinRPCDialog.cs @@ -0,0 +1,124 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Clients; +using FSO.UI.Model; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveJoinRPCDialog : UIArchiveDialog + { + private readonly UILabel JoinLabel; + private readonly UIVBoxContainer VBox; + + public UIArchiveJoinRPCDialog() : base(UIDialogStyle.Close, true) + { + Caption = GetString("117"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Right }; + VBox = vbox; + + var clientConfig = ClientArchiveConfiguration.Default; + + vbox.Add(JoinLabel = new UILabel() + { + Caption = GetString("118"), + Size = new Vector2(300, 35), + Wrapped = true + }); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 45); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + + Add(vbox); + CloseButton.OnButtonClick += Close; + + CheckStatusAndJoin(); + } + + private void CheckStatusAndJoin() + { + var rpc = DiscordRpcEngine.Secret; + var hostname = rpc.Value.ServerHostname; + + if (rpc.HasValue) + { + Task task; + + if (rpc.Value.ArchiveMode) + { + task = StatusChecker.ArchiveStatus(FSOFacade.Kernel, hostname); + } + else + { + task = StatusChecker.FreeSOStatus(hostname); + } + + task.ContinueWith(x => + { + GameThread.InUpdate(() => + { + // If we're not active anymore, don't go through with the join. + + var myScreen = this.FindParent(); + if (myScreen == null || myScreen != UIScreen.Current) + { + return; + } + + if (x.IsFaulted || x.IsCanceled || !x.Result.IsOnline) + { + JoinLabel.Caption = GetString("152"); + JoinLabel.Size = new Vector2(300, 60); + + VBox.AutoSize(); + SetSize((int)VBox.Size.X + 40, (int)VBox.Size.Y + 70); + } + else + { + var historyItem = new ClientArchiveHistoryItem( + rpc.Value.ArchiveMode ? ClientArchiveHistoryType.DiscordArchive : ClientArchiveHistoryType.FreeSO, + x.Result.Name, + hostname, + 0); + + ClientArchiveConfiguration.Default.RegisterJoin(historyItem); + + if (rpc.Value.ArchiveMode) + { + FSOFacade.Controller.ConnectToArchive(ClientArchiveConfiguration.Default.PlayerName, rpc.Value.ServerHostname, false); + } + else + { + FSOFacade.Controller.ShowServerLogin(rpc.Value.ServerHostname); + } + } + }); + }); + } + } + + public override void Update(UpdateState state) + { + base.Update(state); + + var rpc = DiscordRpcEngine.Secret; + + if (rpc == null || !rpc.Value.ArchiveMode || string.IsNullOrEmpty(rpc.Value.ServerHostname)) + { + FindController().SwitchMode(ConnectArchiveMode.Landing); + } + } + + private void Close(Framework.UIElement button) + { + DiscordRpcEngine.Secret = null; + FindController().SwitchMode(ConnectArchiveMode.Landing); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveLandingDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveLandingDialog.cs new file mode 100644 index 000000000..e5061551a --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveLandingDialog.cs @@ -0,0 +1,151 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveLandingDialog : UIArchiveDialog + { + public UIButton CreateButton; + public UIButton JoinButton; + public UIButton QuickStartButton; + + public Texture2D FreeSOLogoImage; + public Texture2D HostServerButtonImage; + public Texture2D JoinServerButtonImage; + public Texture2D QuickStartButtonImage; + + public TextStyle LargeButtonTextStyle; + public UILabel CreateButtonText; + public UILabel JoinButtonText; + public UILabel QuickStartButtonText; + + public UIImage FreeSOLogo; + + public UIArchiveLandingDialog() : base(UIDialogStyle.Standard, true) + { + var ui = Content.Content.Get().CustomUI; + + SetSize(496, 263); + + LargeButtonTextStyle = TextStyle.DefaultLabel.Clone(); + + LargeButtonTextStyle.Size = 17; + LargeButtonTextStyle.Shadow = true; + LargeButtonTextStyle.Color = Color.White; + + FreeSOLogoImage = ui.Get("archive_logo_1x.png").Get(GameFacade.GraphicsDevice); + QuickStartButtonImage = ui.Get("archive_quickstartbtn.png").Get(GameFacade.GraphicsDevice); + HostServerButtonImage = ui.Get("archive_hostbtn.png").Get(GameFacade.GraphicsDevice); + JoinServerButtonImage = ui.Get("archive_joinbtn.png").Get(GameFacade.GraphicsDevice); + + int margin = 2; + + FreeSOLogo = new UIImage(FreeSOLogoImage) + { + Position = new Vector2((Width - FreeSOLogoImage.Width) / 2, -31) + }; + + DynamicOverlay.Add(FreeSOLogo); + + QuickStartButton = new UIButton(QuickStartButtonImage) + { + Position = new Vector2((Width - QuickStartButtonImage.Width / 4) / 2, Height - 36), + Size = new Vector2(QuickStartButtonImage.Width / 4, QuickStartButtonImage.Height), + CaptionStyle = LargeButtonTextStyle, + Caption = GetString("240") + }; + + DynamicOverlay.Add(QuickStartButton); + + Add(CreateButton = new UIButton(HostServerButtonImage) + { + Position = new Vector2(Width / 2 - (HostServerButtonImage.Width / 4 + margin), 80) + }); + + Add(JoinButton = new UIButton(JoinServerButtonImage) + { + Position = new Vector2(Width / 2 + margin, 80) + }); + + Add(CreateButtonText = new UILabel() + { + Position = new Vector2(CreateButton.X + HostServerButtonImage.Width / 8, CreateButton.Y + 10), + Size = new Vector2(0, 1), + Alignment = Framework.TextAlignment.Center | Framework.TextAlignment.Top, + CaptionStyle = LargeButtonTextStyle, + Caption = GetString("241") + }); + + Add(JoinButtonText = new UILabel() + { + Position = new Vector2(JoinButton.X + JoinServerButtonImage.Width / 8, JoinButton.Y + 10), + Size = new Vector2(0, 1), + Alignment = Framework.TextAlignment.Center | Framework.TextAlignment.Top, + CaptionStyle = LargeButtonTextStyle, + Caption = GetString("99") + }); + + Add(new UILabel() + { + Caption = GetString("242"), + Position = new Vector2(Width / 2, 59), + Alignment = Framework.TextAlignment.Center | Framework.TextAlignment.Top, + Size = new Vector2(0, 1) + }); + + CreateButton.OnButtonClick += Create; + JoinButton.OnButtonClick += Join; + QuickStartButton.OnButtonClick += QuickStart; + + QuickStartButton.Tooltip = GetString("243"); + } + + private void QuickStart(Framework.UIElement button) + { + Visible = false; + + var config = FSOFacade.Controller.GetServerConfig(); + + if (config == null) + { + var factory = new ArchiveServerFactory( + ArchiveServerFactory.GetQuickStartConfig(), + FindController()); + + factory.Start((success) => + { + if (!success) + { + Visible = true; + } + }); + } + else + { + FSOFacade.Controller.ConnectToArchive(ClientArchiveConfiguration.Default.PlayerName, $"127.0.0.1:{config.CityPort}", true); + } + } + + private void Join(Framework.UIElement button) + { + FindController().SwitchMode(ConnectArchiveMode.Join); + } + + private void Create(Framework.UIElement button) + { + FindController().SwitchMode(ConnectArchiveMode.Create); + } + + public override void Update(UpdateState state) + { + base.Update(state); + FindController().TickRPC(); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchivePersonButton.cs b/TSOClient/tso.client/UI/Archive/UIArchivePersonButton.cs new file mode 100644 index 000000000..9ebbc97ef --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchivePersonButton.cs @@ -0,0 +1,113 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common.Rendering.Framework.Model; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.Neighborhoods +{ + public class UIArchivePersonButton : UIContainer + { + private Texture2D NormalImg; //0x83E00000001, blue + private Texture2D HoverImg; //0x83F00000001, green + private Texture2D PressedImg; //0xCF200000001, black + private Texture2D DisabledImg; //0xCEE00000001, gray + + private Texture2D OnlineBg; + public UISim Sim { get; set; } + //private UITooltipHandler m_TooltipHandler; + public UIButton MainButton { get; set; } + + private string _AvatarName = ""; + public string AvatarName + { + get + { + return _AvatarName; + } + set + { + MainButton.Tooltip = value; + _AvatarName = value; + } + } + + public UIArchivePersonButton() + { + NormalImg = GetTexture(0x83E00000001); + HoverImg = GetTexture(0x83F00000001); + PressedImg = GetTexture(0xCF200000001); + DisabledImg = GetTexture(0xCEE00000001); + MainButton = new UIButton(NormalImg) + { + Size = new Microsoft.Xna.Framework.Vector2(114, 169) + }; + Add(MainButton); + + Sim = new UISim(); + Sim.Size = new Vector2(80, 150); + Sim.Position = new Vector2(17, 13); + Sim.AutoRotate = true; + Sim.Visible = false; + Add(Sim); + + //m_TooltipHandler = UIUtils.GiveTooltip(this); + + MainButton.OnButtonClick += _Button_OnButtonClick; + } + + public void SetSim(ArchiveAvatar? ava) + { + if (ava == null) + { + Sim.Visible = false; + } + else + { + AvatarName = ava.Value.Name; + Sim.Avatar.HeadOutfitId = ava.Value.Head; + Sim.Avatar.BodyOutfitId = ava.Value.Body; + Sim.Avatar.Appearance = (Vitaboy.AppearanceType)ava.Value.Type; + + Sim.Visible = true; + } + } + + public override void Update(UpdateState state) + { + base.Update(state); + MainButton.Disabled = !Sim.Visible; + MainButton.BlendColor = Color.Transparent; + } + + private void _Button_OnButtonClick(UIElement button) + { + + } + + public override void Draw(UISpriteBatch batch) + { + if (!Visible) return; + //draw relevant button graphic + var frame = MainButton.CurrentFrame; + if (MainButton.Disabled) frame = 3; + switch (frame) + { + case 0: + DrawLocalTexture(batch, NormalImg, Vector2.Zero); + break; + case 1: + DrawLocalTexture(batch, PressedImg, Vector2.Zero); + break; + case 2: + DrawLocalTexture(batch, HoverImg, Vector2.Zero); + break; + case 3: + DrawLocalTexture(batch, DisabledImg, Vector2.Zero); + break; + } + base.Draw(batch); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveServerPorts.cs b/TSOClient/tso.client/UI/Archive/UIArchiveServerPorts.cs new file mode 100644 index 000000000..2afd6edf6 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveServerPorts.cs @@ -0,0 +1,80 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveServerPorts : UIArchiveDialog + { + public UITextBox LotInput; + public UITextBox CityInput; + + public UIArchiveServerPorts(ArchiveConfiguration config, Action onClose) : base(UIDialogStyle.OK, true) + { + Caption = GetString("250"); + var vbox = new UIVBoxContainer() { HorizontalAlignment = UIContainerHorizontalAlignment.Center }; + + UILabel desc; + + vbox.Add(desc = new UILabel() + { + Caption = GetString("251"), + Wrapped = true + }); + + desc.Size = new Vector2(300, 70); + + var cityPortBox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + cityPortBox.Add(new UILabel() + { + Caption = GetString("252") + }); + + cityPortBox.Add(CityInput = new UITextBox() { }); + + vbox.Add(cityPortBox); + + var lotPortBox = new UIHBoxContainer() { VerticalAlignment = UIContainerVerticalAlignment.Middle }; + + lotPortBox.Add(new UILabel() + { + Caption = GetString("253") + }); + + lotPortBox.Add(LotInput = new UITextBox() { }); + + vbox.Add(lotPortBox); + + Add(vbox); + + LotInput.SetSize(100, 25); + CityInput.SetSize(100, 25); + + LotInput.CurrentText = config.LotPort.ToString(); + CityInput.CurrentText = config.CityPort.ToString(); + + vbox.AutoSize(); + vbox.Position = new Vector2(20, 35); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 70); + + OKButton.OnButtonClick += (elem) => + { + onClose(); + UIScreen.RemoveDialog(this); + }; + } + + public bool GetCityPort(out ushort port) + { + return ushort.TryParse(CityInput.CurrentText, out port); + } + + public bool GetLotPort(out ushort port) + { + return ushort.TryParse(LotInput.CurrentText, out port); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveServerStatusDialog.cs b/TSOClient/tso.client/UI/Archive/UIArchiveServerStatusDialog.cs new file mode 100644 index 000000000..d97655e5f --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveServerStatusDialog.cs @@ -0,0 +1,83 @@ +using FSO.Client.UI.Controls; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Server.Embedded; + +namespace FSO.Client.UI.Archive +{ + internal class UIArchiveServerStatusDialog : UIArchiveDialog + { + private readonly UILabel InfoText; + private bool WaitStart; + private readonly Action OnComplete; + private readonly EmbeddedServer Server; + private readonly UIProgressBar ProgressBar; + + public UIArchiveServerStatusDialog(bool waitStart, EmbeddedServer server, Action onComplete) : base(UIDialogStyle.Standard, false) + { + WaitStart = waitStart; + OnComplete = onComplete; + Server = server; + Caption = GetString("260"); + + Add(InfoText = new UILabel() + { + Caption = waitStart ? GetString("261") : GetString("262"), + Position = new Microsoft.Xna.Framework.Vector2(20, 45), + Size = new Microsoft.Xna.Framework.Vector2(200, 50), + Wrapped = true, + }); + + int ySize = 50 + 70; + + if (waitStart) + { + ySize += 37; + + Add(ProgressBar = new UIProgressBar() + { + Position = new Microsoft.Xna.Framework.Vector2(20, 105), + Size = new Microsoft.Xna.Framework.Vector2(200, 27) + }); + } + + SetSize(200 + 40, ySize); + + if (!WaitStart) + { + Server.Shutdown().ContinueWith((t) => + { + GameThread.NextUpdate((state) => + { + if (onComplete != null) + { + onComplete(); + } + else + { + GameFacade.Kill(); + } + }); + }); + } + } + + public override void Update(UpdateState state) + { + base.Update(state); + + if (WaitStart) + { + if (Server.ReadyPercent != ProgressBar.Value) + { + ProgressBar.Value = Server.ReadyPercent; + } + + if (Server.Ready && OnComplete != null) + { + OnComplete(); + } + } + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIArchiveUserList.cs b/TSOClient/tso.client/UI/Archive/UIArchiveUserList.cs new file mode 100644 index 000000000..505985f9a --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIArchiveUserList.cs @@ -0,0 +1,345 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Server.Protocol.Electron.Model; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Archive +{ + public class UIArchiveUserList : UIArchiveDialog + { + private ArchiveClientList LastList; + private UIImage ListBackground; + private UIListBoxTextStyle ListBoxColors; + private UIListBox UserListBox; + + private Texture2D AdminActionsButtonTexture; + + private Texture2D UserAdminIcon; + private Texture2D UserModIcon; + private Texture2D UserVerifyIcon; + + private int FrameCount; + + public UIArchiveUserList() : base(UIDialogStyle.Close, true) + { + Caption = GetString("270"); + + var gd = GameFacade.GraphicsDevice; + + var ui = Content.Content.Get().CustomUI; + AdminActionsButtonTexture = ui.Get("archive_burgermenu.png").Get(gd); + + UserAdminIcon = ui.Get("archive_useradmin.png").Get(gd); + UserModIcon = ui.Get("archive_usermod.png").Get(gd); + UserVerifyIcon = ui.Get("archive_userverify.png").Get(gd); + + var vbox = new UIVBoxContainer(); + + var searchFont = TextStyle.DefaultLabel.Clone(); + searchFont.Size = 8; + + ListBoxColors = new UIListBoxTextStyle(searchFont) + { + NormalColor = new Color(247, 232, 145), + SelectedColor = new Color(0, 0, 0), + HighlightedColor = new Color(255, 255, 255), + DisabledColor = new Color(150, 150, 150) + }; + + ListBackground = new UIImage(ui.Get("archive_translist.png").Get(gd)).With9Slice(13, 13, 13, 13); + ListBackground.SetSize(180, 300); + vbox.Add(ListBackground); + + vbox.AutoSize(); + vbox.Position = new Vector2(15, 40); + Add(vbox); + + DynamicOverlay.Add(UserListBox = new UIListBox() + { + Size = ListBackground.Size - new Vector2(20, 20), + Position = vbox.Position + ListBackground.Position + new Vector2(10, 10), + Mask = true, + VisibleRows = 12, + Columns = new UIListBoxColumnCollection() + { + new UIListBoxColumn() { Width = 25, Alignment = TextAlignment.Left }, // Avatar button + new UIListBoxColumn() { Width = 99, Alignment = TextAlignment.Left | TextAlignment.Middle }, // Display name, unique ID + new UIListBoxColumn() { Width = 20, Alignment = TextAlignment.Left | TextAlignment.Middle }, // Admin status + new UIListBoxColumn() { Width = 15, Alignment = TextAlignment.Left | TextAlignment.Middle }, // Admin actions + }, + RowHeight = 20, + FontStyle = searchFont, + SelectionFillColor = new Color(250, 200, 140), + ScrollbarImage = GetTexture(0x31000000001), + ScrollbarGutter = 12, + UseChildElements = true, + }); + + UserListBox.InitDefaultSlider(); + + SetSize((int)vbox.Size.X + 30 + 16, (int)vbox.Size.Y + 60); + + CloseButton.OnButtonClick += Close; + } + + private bool FlashActive() + { + return (FrameCount % FSOEnvironment.RefreshRate) < FSOEnvironment.RefreshRate / 2; + } + + private void Close(UIElement button) + { + Visible = false; + } + + public override void Update(UpdateState state) + { + if (Visible) + { + var controller = FindController(); + ArchiveClientList list = controller?.UserList; + + if (LastList != list) + { + UpdateList(list); + } + + FrameCount++; + if (FrameCount % (FSOEnvironment.RefreshRate / 2) == 0) + { + bool flash = FlashActive(); + foreach (var item in UserListBox.Items) + { + if (item.Data is ArchivePendingVerification) + { + item.UseSelectedStyleByDefault = flash; + } + } + } + } + + base.Update(state); + } + + private void Approve(ArchivePendingVerification client) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.APPROVE_USER); + } + + private void Reject(ArchivePendingVerification client) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.REJECT_USER); + } + + private void Kick(ArchiveClient client) + { + UIAlert.YesNo(GetString("271", client.DisplayName), GetString("272", client.DisplayName), true, (bool result) => + { + if (result) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.KICK_USER); + } + }); + } + + private void Ban(ArchiveClient client) + { + UIAlert.YesNo(GetString("273", client.DisplayName), GetString("274", client.DisplayName), true, (bool result) => + { + if (result) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.BAN_USER); + } + }); + } + + private void Ban(ArchivePendingVerification client) + { + UIAlert.YesNo(GetString("273", client.DisplayName), GetString("275", client.DisplayName), true, (bool result) => + { + if (result) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.BAN_USER); + } + }); + } + + private Texture2D GetModIcon(uint level) + { + switch (level) + { + case 0: + return null; + case 1: + return UserModIcon; + case 2: + case 3: + return UserAdminIcon; + } + + return null; + } + + private string GetModString(int level) + { + // TODO: localization + + switch (level) + { + case 0: + return GetString("276"); + case 1: + return GetString("277"); + case 2: + return GetString("278"); + } + + return level.ToString(); //TODO + } + + private void ChangePermissions(ArchiveClient client, int currentLevel, int targetLevel) + { + string before = GetModString(currentLevel); + string after = GetModString(targetLevel); + + UIAlert.YesNo(GetString("273", client.DisplayName), GetString("279", client.DisplayName, before, after), true, (bool result) => + { + if (result) + { + var controller = FindController(); + controller?.ArchiveModRequest(client.UserId, ArchiveModerationRequestType.CHANGE_MOD_LEVEL, targetLevel); + } + }); + } + + private void OpenActions(UIElement anchor, ArchivePendingVerification client) + { + int myLevel = 2; + var items = new List(); + + if (myLevel > 0) + { + items.Add(new UIContextMenuItem(GetString("280"), () => { Approve(client); })); + items.Add(new UIContextMenuItem(GetString("281"), () => { Reject(client); })); + items.Add(new UIContextMenuItem(GetString("282"), () => { Ban(client); })); + } + + new UIContextMenu(anchor, items, this); + } + + private void OpenActions(UIElement anchor, ArchiveClient client, int myLevel) + { + int theirLevel = (int)client.ModerationLevel; + + var items = new List(); + + if (myLevel > theirLevel) + { + if (myLevel >= 2) + { + // Change moderation level for this user + if (theirLevel != 2) + { + items.Add(new UIContextMenuItem(GetString("283"), () => { ChangePermissions(client, theirLevel, 2); })); + } + + if (theirLevel != 1) + { + items.Add(new UIContextMenuItem(GetString("284"), () => { ChangePermissions(client, theirLevel, 1); })); + } + + if (theirLevel != 0) + { + items.Add(new UIContextMenuItem(GetString("285"), () => { ChangePermissions(client, theirLevel, 0); })); + } + } + + if (myLevel > 0) + { + items.Add(new UIContextMenuItem(GetString("286"), () => { Kick(client); })); + items.Add(new UIContextMenuItem(GetString("282"), () => { Ban(client); })); + } + } + + new UIContextMenu(anchor, items, this); + } + + public void UpdateList(ArchiveClientList list) + { + LastList = list; + + Caption = GetString("287", (list?.Clients?.Length ?? 0).ToString()); + + bool flash = FlashActive(); + + var items = new List(); + + if (list != null) + { + foreach (var client in list.Pending) + { + var actionButton = new UIButton(AdminActionsButtonTexture); + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, client); + }; + + items.Add(new UIListBoxItem( + client, + "", + client.DisplayName, + UserVerifyIcon, + actionButton) + { + CustomStyle = ListBoxColors, + UseSelectedStyleByDefault = flash + }); + } + + var screen = FindController(); + + var myId = screen.MyID(); + var myClient = list.Clients.FirstOrDefault(x => myId == x.AvatarId); + int myLevel = (int)(myClient.AvatarId != 0 ? myClient.ModerationLevel : screen.ModerationLevel); + + foreach (var client in list.Clients) + { + var actionButton = new UIButton(AdminActionsButtonTexture); + + var hasActions = myLevel > client.ModerationLevel; + + actionButton.OnButtonClick += (UIElement element) => + { + OpenActions(element, client, myLevel); + }; + + items.Add(new UIListBoxItem( + client, + client.AvatarId == 0 + ? (object)"" + : new UIPersonButton() { FrameSize = UIPersonButtonSize.SMALL, AvatarId = client.AvatarId }, + client.DisplayName, + hasActions ? GetModIcon(client.ModerationLevel) : null, + hasActions ? actionButton : GetModIcon(client.ModerationLevel)) + { + CustomStyle = ListBoxColors + }); + } + } + + UserListBox.Items = items; + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIAutoUpdater.cs b/TSOClient/tso.client/UI/Archive/UIAutoUpdater.cs new file mode 100644 index 000000000..f3a7039d5 --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIAutoUpdater.cs @@ -0,0 +1,166 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.Common.Utils; +using FSO.Server.Clients; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIAutoUpdater : UIContainer + { + private UIButton InfoButton; + private UILabel InfoLabel; + private UIHBoxContainer RootBox; + private FSOVersionInfo TargetVersion; + private UpdatePathNew Path; + private bool Failed = false; + + public UIAutoUpdater() + { + var ui = Content.Content.Get().CustomUI; + var btnTex = ui.Get("chat_cat.png").Get(GameFacade.GraphicsDevice); + + var updateStatusCaption = TextStyle.DefaultLabel.Clone(); + updateStatusCaption.Size = 9; + + var btnCaption = TextStyle.DefaultLabel.Clone(); + btnCaption.Size = 8; + btnCaption.Shadow = true; + + RootBox = new UIHBoxContainer() + { + VerticalAlignment = UIContainerVerticalAlignment.Middle, + Spacing = 2 + }; + + RootBox.Add(InfoButton = new UIButton(btnTex) + { + Caption = "i", + Width = btnTex.Height, + CaptionStyle = btnCaption + }); + + RootBox.Add(InfoLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f101", "62"), + CaptionStyle = updateStatusCaption, + }); + + RootBox.AutoSize(); + RootBox.Position = new Vector2(0, -RootBox.Size.Y); + ScaleX = ScaleY = 0.75f; + + InfoButton.OnButtonClick += Info; + + Add(RootBox); + + FetchUpdate(); + } + + private void Info(UIElement button) + { + var current = FSOVersionInfo.Current; + if (string.IsNullOrEmpty(current.channelUrl)) + { + UIAlert.Alert( + GameFacade.Strings.GetString("f101", "72"), + GameFacade.Strings.GetString("f101", "73"), + true + ); + } + else if (Failed) + { + UIAlert.Alert( + GameFacade.Strings.GetString("f101", "55"), + GameFacade.Strings.GetString("f101", "64", [ + string.IsNullOrEmpty(current.channelUrl) ? "(no update source)" : current.channelUrl + ]), + true + ); + } + else + { + if (TargetVersion != null && Path != null) + { + ShowUpdate(TargetVersion, Path); + } + else + { + UIAlert.Alert( + GameFacade.Strings.GetString("f101", "55"), + GameFacade.Strings.GetString("f101", "65", [current.channel, current.id]), + true + ); + } + } + } + + private void ShowUpdate(FSOVersionInfo targetVersion, UpdatePathNew path) + { + var controller = new UpdateController(skip => + { + // If the update was rejected here, it was ignored and the dialog shouldn't pop up again. + GlobalSettings.Default.IgnoreVersion = targetVersion.id; + GlobalSettings.Default.Save(); + }); + + controller.ShowUpdateDialog(path, true); + } + + private void SetLabel(string label, Color color) + { + InfoLabel.Size = Vector2.Zero; + InfoLabel.Caption = label; + InfoLabel.CaptionStyle.Color = color; + + RootBox.AutoSize(); + } + + private void FetchUpdate() + { + var current = FSOVersionInfo.Current; + if (string.IsNullOrEmpty(current.channelUrl)) + { + SetLabel(GameFacade.Strings.GetString("f101", "72"), TextStyle.DefaultLabel.Color); + return; + } + + UpdateController.TryGetAutoUpdate((bool success, FSOVersionInfo targetVersion, UpdatePathNew path) => + { + GameThread.InUpdate(() => + { + var myScreen = this.FindParent(); + + // A bit of a hack - if we're not on the active screen anymore, then we shouldn't do anything if update info comes back. + if (myScreen == null || myScreen != UIScreen.Current) + { + return; + } + + Path = path; + if (targetVersion != null) + { + SetLabel(GameFacade.Strings.GetString("f101", "60", [targetVersion.id]), Color.White); + TargetVersion = targetVersion; + + // If the user hasn't ignored this update, show the update dialog. + if (success && targetVersion.id != GlobalSettings.Default.IgnoreVersion) + { + ShowUpdate(targetVersion, path); + } + + Failed = !success; + } + else + { + SetLabel(GameFacade.Strings.GetString("f101", success ? "59" : "63"), success ? TextStyle.DefaultLabel.Color : Color.LightGray); + TargetVersion = null; + Failed = !success; + } + }); + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Archive/UIUpdateDialog.cs b/TSOClient/tso.client/UI/Archive/UIUpdateDialog.cs new file mode 100644 index 000000000..dbf737dfb --- /dev/null +++ b/TSOClient/tso.client/UI/Archive/UIUpdateDialog.cs @@ -0,0 +1,104 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Controls; +using FSO.Common; +using FSO.Files.FSO; +using FSO.Server.Clients; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Archive +{ + internal class UIUpdateDialog : UIDialog + { + public UITextEdit ChangelogTextEdit; + private UIButton NoButton; + private UIButton YesButton; + + private readonly UpdatePathNew Path; + + public UIUpdateDialog(UpdatePathNew path, bool autoUpdate) : base(UIDialogStyle.Close, true) + { + Path = path; + var current = FSOVersionInfo.Current; + + Caption = GameFacade.Strings.GetString("f101", autoUpdate ? "55" : "21"); + + var vbox = new UIVBoxContainer() + { + HorizontalAlignment = UIContainerHorizontalAlignment.Center + }; + + var targetVersion = path.Destination; + + vbox.Add(new UILabel() + { + Caption = GameFacade.Strings.GetString("f101", autoUpdate ? "61" : "41", [targetVersion.id]), //43 for downgrade. + Size = new Vector2(400, 45), + Wrapped = true + }); + + var changelogBox = new UIHBoxContainer() + { + VerticalAlignment = UIContainerVerticalAlignment.Middle + }; + + changelogBox.Add(ChangelogTextEdit = new UITextEdit() + { + BackgroundTextureReference = UITextBox.StandardBackground, + ScrollbarGutter = 7, + TextMargin = new Rectangle(12, 10, 12, 10), + ScrollbarImage = GetTexture(0x4AB00000001), + Size = new Vector2(400, 300), + CurrentText = BuildChangelog(path), + Mode = UITextEditMode.ReadOnly + }); + ChangelogTextEdit.InitDefaultSlider(); + + vbox.Add(changelogBox); + + var buttonBox = new UIHBoxContainer() + { + VerticalAlignment = UIContainerVerticalAlignment.Middle + }; + + buttonBox.Add(NoButton = new UIButton() + { + Caption = GameFacade.Strings.GetString("f101", autoUpdate ? "35" : "44") + }); + + buttonBox.Add(YesButton = new UIButton() + { + Caption = GameFacade.Strings.GetString("f101", "36", [targetVersion?.id ?? "unknown"]) + }); + + vbox.Add(buttonBox); + vbox.AutoSize(); + vbox.Position = new Vector2(20, 40); + + SetSize((int)vbox.Size.X + 40, (int)vbox.Size.Y + 60); + DynamicOverlay.Add(vbox); + + YesButton.OnButtonClick += Accept; + NoButton.OnButtonClick += Reject; + CloseButton.OnButtonClick += Reject; + } + + private void Reject(Framework.UIElement button) + { + FindController().RejectUpdate(); + } + + private void Accept(Framework.UIElement button) + { + FindController().AcceptUpdate(Path); + } + + private string BuildChangelog(UpdatePathNew path) + { + return string.Join( + '\n', + path.Path.Reverse().Select((x, index) => + $"# {x.id} {GameFacade.Strings.GetString("f101", path.FullZipStart && index == path.Path.Count - 1 ? "24" : "23")} \n{x.changelog}") + ); + } + } +} diff --git a/TSOClient/tso.client/UI/Controls/Catalog/UICatalog.cs b/TSOClient/tso.client/UI/Controls/Catalog/UICatalog.cs index 55793b79a..f56e7d696 100644 --- a/TSOClient/tso.client/UI/Controls/Catalog/UICatalog.cs +++ b/TSOClient/tso.client/UI/Controls/Catalog/UICatalog.cs @@ -11,6 +11,7 @@ using FSO.Content.Interfaces; using FSO.Client.UI.Panels; using System.Text.RegularExpressions; +using FSO.UI.Utils; namespace FSO.Client.UI.Controls.Catalog { @@ -428,19 +429,44 @@ void InnerSelect(UIElement button) public Texture2D GetObjIcon(uint GUID) { if (!IconCache.ContainsKey(GUID)) { - var obj = Content.Content.Get().WorldObjects.Get(GUID); + var objs = Content.Content.Get().WorldObjects; + var obj = objs.Get(GUID); if (obj == null) { IconCache[GUID] = null; return null; } var bmp = obj.Resource.Get(obj.OBJ.CatalogStringsID); - if (bmp != null) IconCache[GUID] = bmp.GetTexture(GameFacade.GraphicsDevice); - else IconCache[GUID] = null; + + if (bmp != null) + { + var result = bmp.GetTexture(GameFacade.GraphicsDevice); + result.Tag = this; // We can dispose this texture later. + IconCache[GUID] = result; + } + else + { + IconCache[GUID] = objs.GetOrAddGeneratedIcon(GUID, () => CatThumbGenerator.GenerateThumb(GUID)); + } } return IconCache[GUID]; } + public override void Removed() + { + foreach (var entry in IconCache.Values) + { + if (entry?.Tag == this) + { + entry.Dispose(); + } + } + + IconCache.Clear(); + + base.Removed(); + } + private class CatalogSorter : IComparer { #region IComparer Members diff --git a/TSOClient/tso.client/UI/Controls/UICombobox.cs b/TSOClient/tso.client/UI/Controls/UICombobox.cs new file mode 100644 index 000000000..db492c29f --- /dev/null +++ b/TSOClient/tso.client/UI/Controls/UICombobox.cs @@ -0,0 +1,293 @@ +using FSO.Client.Controllers; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Framework.Parser; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FSO.Client.UI.Controls +{ + public struct UIComboboxItem + { + public string Name; + public object Value; + } + + /// + /// Kind of hacks the message inbox dropdown into a combobox. + /// Can be resized horizontally. + /// + public class UICombobox : UIContainer, IFocusableUI + { + public bool IsFocused { get; set; } + public int TabIndex { get; set; } + + public object SelectedItem + { + get + { + return MenuListBox.SelectedItem?.Data; + } + set + { + Select(value); + } + } + + public int SelectedIndex + { + get + { + return MenuListBox.SelectedIndex; + } + set + { + MenuListBox.SelectedIndex = value; + } + } + + private int? _width; + public int? Width + { + get + { + return _width; + } + set + { + _width = value; + UpdateSize(); + } + } + + public override Vector2 Size + { + get => new Vector2(Width ?? 340, 24); + set => Width = (int)value.X; + } + + public Texture2D backgroundCollapsedImage { get; set; } + public Texture2D backgroundExpandedImage { get; set; } + + public UIButton DropDownButton { get; set; } + + public UIButton MenuScrollUpButton { get; set; } + public UIButton MenuScrollDownButton { get; set; } + public UISlider MenuSlider { get; set; } + + public UIListBox MenuListBox { get; set; } + public UITextEdit MenuTextEdit { get; set; } + + public UIImage Background; + public bool open; + + private List _items = new List(); + public List Items + { + get { return _items; } + set + { + _items = value; + + MenuListBox.Items.Clear(); + if (value != null) + { + MenuListBox.Items.AddRange(value.Select(x => + { + return new UIListBoxItem(x.Value, new object[] { x.Name }); + })); + } + + MenuListBox.Items = MenuListBox.Items; + } + } + + UIScript Script; + + public event Action OnSelect; + + public UICombobox() + { + var ui = Content.Content.Get().CustomUI; + + Script = this.RenderScript("messageinboxmenu.uis"); + backgroundCollapsedImage = ui.Get("archive_combobox.png").Get(GameFacade.GraphicsDevice); + Background = new UIImage(backgroundCollapsedImage).With9Slice(40, 40, 6, 6); + this.AddAt(0, Background); + + open = true; + ToggleOpen(); + + DropDownButton.OnButtonClick += new ButtonClickDelegate(DropDownButton_OnButtonClick); + DropDownButton.Tooltip = null; + MenuTextEdit.Mode = UITextEditMode.ReadOnly; + + MenuListBox.AttachSlider(MenuSlider); + MenuSlider.AttachButtons(MenuScrollUpButton, MenuScrollDownButton, 1f); + + MenuListBox.OnChange += SelectComboboxElement; + + MenuListBox.TextStyle = new UIListBoxTextStyle(MenuListBox.FontStyle) + { + SelectedColor = Color.Black, + HighlightedColor = new Color(255, 255, 255), + DisabledColor = new Color(150, 150, 150) + }; + + UpdateSize(); + } + + private void UpdateSize() + { + int width = Width ?? 340; + + if (open) + { + Background.SetSize(width, backgroundExpandedImage.Height); + } + else + { + Background.SetSize(width, backgroundCollapsedImage.Height); + } + + MenuTextEdit.SetSize(width - 45, MenuTextEdit.Height); + MenuListBox.SetSize(width - 49, MenuListBox.Height); + DropDownButton.X = width - 21; + MenuSlider.X = width - 19; + MenuScrollUpButton.X = width - 23; + MenuScrollDownButton.X = width - 23; + } + + private void SelectComboboxElement(UIElement button) + { + UpdateComboText(); + + var selected = MenuListBox.SelectedItem; + if (selected == null) return; + + OnSelect?.Invoke(selected.Data); + + if (open) + { + ToggleOpen(); + } + } + + + void DropDownButton_OnButtonClick(UIElement button) + { + ToggleOpen(); + GameFacade.Screens.inputManager.SetFocus(this); + } + + public void ToggleOpen() + { + Invalidate(); + int width = Width ?? 340; + + if (open) + { + Background.Texture = backgroundCollapsedImage; + Background.With9Slice(40, 40, 6, 6); + Background.SetSize(width, backgroundCollapsedImage.Height); + } + else + { + Background.Texture = backgroundExpandedImage; + Background.With9Slice(40, 40, 50, 25); + Background.SetSize(width, backgroundExpandedImage.Height); + } + + open = !open; + MenuSlider.Visible = open; + MenuScrollUpButton.Visible = open; + MenuScrollDownButton.Visible = open; + MenuListBox.Visible = open; + } + + public void Select(object value) + { + // Try find the value in the current items + if (value == null) + { + MenuListBox.SelectedItem = null; + } + else + { + MenuListBox.SelectedIndex = _items.FindIndex(x => x.Value == value); + } + + UpdateComboText(); + } + + private void UpdateComboText() + { + var selected = MenuListBox.SelectedIndex; + + if (selected == -1) + { + MenuTextEdit.CurrentText = ""; + } + else + { + MenuTextEdit.CurrentText = _items[selected].Name; + } + } + + public void OnFocusChanged(FocusEvent newFocus) + { + } + + private bool HasFocus(UpdateState state) + { + var focus = state.InputManager.GetFocus() as UIElement; + + while (focus != null) + { + if (focus == this) + { + return true; + } + + focus = focus.Parent; + } + + return false; + } + + public override void Update(UpdateState state) + { + base.Update(state); + + if (open && !HasFocus(state)) + { + // If the focus isn't a child of us, then instantly close the dropdown. + ToggleOpen(); + } + + if (!IsFocused) return; + + if (state.ActivationKeyPressed) + { + if (!open) ToggleOpen(); + else SelectComboboxElement(this); + } + if (state.NewKeys.Contains(Keys.Escape) && open) + ToggleOpen(); + if (state.NewKeys.Contains(Keys.Up) && open && MenuListBox.SelectedIndex > 0) + MenuListBox.SelectedIndex--; + if (state.NewKeys.Contains(Keys.Down)) + { + if (!open) ToggleOpen(); + else if (MenuListBox.SelectedIndex < _items.Count - 1) + MenuListBox.SelectedIndex++; + } + } + } +} diff --git a/TSOClient/tso.client/UI/Controls/UIInteraction.cs b/TSOClient/tso.client/UI/Controls/UIInteraction.cs index 0a30a408f..b746ad5dd 100644 --- a/TSOClient/tso.client/UI/Controls/UIInteraction.cs +++ b/TSOClient/tso.client/UI/Controls/UIInteraction.cs @@ -142,5 +142,16 @@ public override Rectangle GetBounds() { return new Rectangle(0, 0, ClickHandler.Region.Width, ClickHandler.Region.Height); } + + public override void Removed() + { + base.Removed(); + + if (Icon?.Tag == this) + { + Icon.Dispose(); + Icon = null; + } + } } } diff --git a/TSOClient/tso.client/UI/Controls/UILotButton.cs b/TSOClient/tso.client/UI/Controls/UILotButton.cs index 29ef16a76..d05f31ae2 100644 --- a/TSOClient/tso.client/UI/Controls/UILotButton.cs +++ b/TSOClient/tso.client/UI/Controls/UILotButton.cs @@ -261,8 +261,9 @@ public override void Draw(UISpriteBatch batch) var ThumbImg = Thumb.LotTexture; if (ThumbImg != null && BgImg != null && HoverImg != null) { + var dpi = FSOEnvironment.DPIScaleFactor; var terrain = ((CoreGameScreen)GameFacade.Screens.CurrentUIScreen).CityRenderer; - var Size = new Vector2(80, 50); + var Size = new Vector2(80, 50) / dpi; Vector2 startVec = new Vector2(40, 25) + Position; Vector2? dest = UITerrainHighlight.GetEndpointFromLotId(terrain, startVec, (int)LotId); if (!dest.HasValue) return; @@ -290,7 +291,7 @@ public override void Draw(UISpriteBatch batch) DrawLocalTexture(batch, (m_isOver && !m_isDown) ? HoverImg : BgImg, new Vector2()); var scale = new Vector2(0.25f, 0.25f); - DrawLocalTexture(batch, ThumbImg, null, new Vector2(40, 25) - new Vector2(32, 32), scale); + DrawLocalTexture(batch, ThumbImg, null, (new Vector2(40, 25) - new Vector2(32, 32)) / dpi, scale); var px = TextureGenerator.GetPxWhite(batch.GraphicsDevice); DrawLocalTexture(batch, px, null, new Vector2(0, 50), new Vector2(80, 16 * NameLabel.NumLines + 7), Color.Black * 0.6f); diff --git a/TSOClient/tso.client/UI/Controls/UIPersonButton.cs b/TSOClient/tso.client/UI/Controls/UIPersonButton.cs index 110ce8545..c6e0e8d2a 100644 --- a/TSOClient/tso.client/UI/Controls/UIPersonButton.cs +++ b/TSOClient/tso.client/UI/Controls/UIPersonButton.cs @@ -85,7 +85,8 @@ public uint AvatarId private void _Button_OnButtonClick(UIElement button) { - FindController()?.ShowPersonPage(User.Value); + var parent = Parent ?? UIScreen.Current; + parent.FindController()?.ShowPersonPage(User.Value); } private ITextureRef _FrameTexture; @@ -116,6 +117,10 @@ public ITextureRef Icon } } + public int ButtonFrame => _Button.CurrentFrame; + + public override Vector2 Size { get => new Vector2(_Button.Texture.Width / 4, _Button.Texture.Height); } + public override Rectangle GetBounds() { return _Button.GetBounds(); @@ -140,6 +145,12 @@ public override void Draw(UISpriteBatch batch) } } } + + public void SetButtonVisible(bool visible) + { + _Button.AlwaysClickable = !visible; + _Button.Opacity = visible ? 1 : 0.0001f; + } } public enum UIPersonButtonSize diff --git a/TSOClient/tso.client/UI/Hints/UIHint.cs b/TSOClient/tso.client/UI/Hints/UIHint.cs index d5dd011ad..52e42dd6e 100644 --- a/TSOClient/tso.client/UI/Hints/UIHint.cs +++ b/TSOClient/tso.client/UI/Hints/UIHint.cs @@ -12,5 +12,7 @@ public class UIHint public int? Order; public int? CatOrder; + public int? BodySize; + public bool? IgnoreDisable; } } diff --git a/TSOClient/tso.client/UI/Hints/UIHintListItem.cs b/TSOClient/tso.client/UI/Hints/UIHintListItem.cs index d2f16ec35..c65ebf465 100644 --- a/TSOClient/tso.client/UI/Hints/UIHintListItem.cs +++ b/TSOClient/tso.client/UI/Hints/UIHintListItem.cs @@ -1,10 +1,9 @@ using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; +using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System.Collections.Generic; -using FSO.Common.Rendering.Framework.Model; namespace FSO.Client.UI.Hints { @@ -16,7 +15,7 @@ public class UIHintListItem : UIContainer public bool Category; public string Name; - public Color Col = Color.TransparentBlack; + public Color Col = Color.Transparent; public int Index; public List ChildItems; @@ -41,13 +40,14 @@ public UIHintListItem(bool cat, string caption, int index) Label.CaptionStyle.Color = Color.White; Label.CaptionStyle.Size = 11; Col = new Color(31, 46, 63); - } else + } + else { if (Index % 2 == 1) Col = new Color(31, 46, 63) * 0.25f; Label.CaptionStyle.Size = 9; } - Label.Size = new Vector2(227-Indent, 24); - Label.X += 7+Indent; + Label.Size = new Vector2(227 - Indent, 24); + Label.X += 7 + Indent; Label.Alignment = TextAlignment.Middle | TextAlignment.Left; Add(Label); @@ -87,7 +87,7 @@ public void Select() public void Deselect() { - Label.CaptionStyle.Color = (FSOFacade.Hints.ShownGUIDs.Contains(Hint.GUID))?Color.LightBlue:TextStyle.DefaultLabel.Color; + Label.CaptionStyle.Color = (FSOFacade.Hints.ShownGUIDs.Contains(Hint.GUID)) ? Color.LightBlue : TextStyle.DefaultLabel.Color; Selected = false; } } diff --git a/TSOClient/tso.client/UI/Hints/UIHintManager.cs b/TSOClient/tso.client/UI/Hints/UIHintManager.cs index 738c74361..8c2d037aa 100644 --- a/TSOClient/tso.client/UI/Hints/UIHintManager.cs +++ b/TSOClient/tso.client/UI/Hints/UIHintManager.cs @@ -54,7 +54,7 @@ public UIHintManager() { //register this hint hint.Filename = fn; - var r = new UIHintRef() { Filename = fn, GUID = hint.GUID }; + var r = new UIHintRef() { Filename = fn, GUID = hint.GUID, IgnoreDisable = hint.IgnoreDisable ?? false }; AllHints.Add(r); List trigger = null; @@ -81,6 +81,22 @@ public void TriggerHint(string trigger) } } + public bool IsHintTriggered(string trigger) + { + List hintFiles = null; + if (!TriggerToHints.TryGetValue(trigger, out hintFiles)) + { + return true; //no hints available + } + + return hintFiles.All(x => IsHintTriggered(x)); + } + + public bool IsHintTriggered(UIHintRef r) + { + return ShownGUIDs.Contains(r.GUID); + } + public void TryShowHint(UIHintRef r) { if (ShownGUIDs.Contains(r.GUID)) return; @@ -126,7 +142,10 @@ public void MarkAllRead() { foreach (var hint in AllHints) { - ShownGUIDs.Add(hint.GUID); + if (!hint.IgnoreDisable) + { + ShownGUIDs.Add(hint.GUID); + } } SaveRead(); } @@ -152,12 +171,18 @@ public List LoadAllHints() { return AllHints.Select(x => LoadHint(x.Filename)).Where(x => x != null).OrderBy(x => x.Order ?? 0).ToList(); } + + public bool IsShowingHint() + { + return !(HintAlert == null || HintAlert.Dead); + } } public class UIHintRef { public string Filename; public string GUID; + public bool IgnoreDisable; } public class UIHintRead diff --git a/TSOClient/tso.client/UI/Hints/UIHintWindow.cs b/TSOClient/tso.client/UI/Hints/UIHintWindow.cs index d8735053c..9edc57004 100644 --- a/TSOClient/tso.client/UI/Hints/UIHintWindow.cs +++ b/TSOClient/tso.client/UI/Hints/UIHintWindow.cs @@ -56,6 +56,7 @@ public UIHintWindow() : base(UIDialogStyle.Close, true) ListBox = new UIListBox() { Position = new Vector2(15 + 7, 45 + 6) }; ListBox.SetSize(227, 530 - 12); + ListBox.UseChildElements = true; ListBox.RowHeight = 24; ListBox.Columns = new UIListBoxColumnCollection(); ListBox.Columns.Add(new UIListBoxColumn() { Width = 227 }); @@ -226,13 +227,21 @@ private void ComputeText(UIHint hint) { var msg = hint.Body; msg = GameFacade.Emojis.EmojiToBB(msg); + + var style = TextStyle.DefaultLabel.Clone(); + + if (hint.BodySize != null) + { + style.Size = hint.BodySize.Value; + } + m_MessageText = TextRenderer.ComputeText(msg, new TextRendererOptions { Alignment = TextAlignment.Left | TextAlignment.Top, MaxWidth = 492, Position = new Vector2(290, 65), Scale = _Scale, - TextStyle = TextStyle.DefaultLabel, + TextStyle = style, WordWrap = true, TopLeftIconSpace = IconSpace, BBCode = true diff --git a/TSOClient/tso.client/UI/Model/UIIconCache.cs b/TSOClient/tso.client/UI/Model/UIIconCache.cs index e37b1498b..229525950 100644 --- a/TSOClient/tso.client/UI/Model/UIIconCache.cs +++ b/TSOClient/tso.client/UI/Model/UIIconCache.cs @@ -58,7 +58,7 @@ public static Texture2D GenHeadTex(ulong headOft, ulong bodyOft) if (headOft == 0) headOft = bodyOft; Texture2D result = null; - if (!AvatarHeadCache.TryGetValue(headOft, out result)) + if (!AvatarHeadCache.TryGetValue(headOft, out result) || result.IsDisposed) { var ofts = Content.Content.Get().AvatarOutfits; var oft = ofts.Get(headOft); @@ -66,6 +66,7 @@ public static Texture2D GenHeadTex(ulong headOft, ulong bodyOft) else { result = GenHeadTex(oft, ofts.GetNameByID(headOft)); + result.Tag = "GenHeadTex"; } AvatarHeadCache[headOft] = result; } diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterOptions.cs b/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterOptions.cs new file mode 100644 index 000000000..ff0a6c54d --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterOptions.cs @@ -0,0 +1,191 @@ +using FSO.Client.Rendering.City.Plugins; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common.Rendering.Framework.Model; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Panels.CityPainter +{ + internal abstract class AbstractCityPainterOptions : UIContainer + { + protected UICityPainterIntensityConfig DisabledIntensity = new UICityPainterIntensityConfig(); + protected UICityPainterIntensityConfig DefaultIntensity = new UICityPainterIntensityConfig(0.1f, 1, true); + + protected UICityPainter Painter { get; private set; } + protected MapPainterPlugin MapPainter => Painter.MapPainter; + public abstract PainterMode Mode { get; } + public abstract string Graphic { get; } + public abstract string PreviewText { get; } + public abstract UICityPainterIntensityConfig IntensityConfig { get; } + + private UIHBoxContainer ModesHbox; + private UIHBoxContainer TogglesHbox; + private UIHBoxContainer RootHbox; + private (UIButton, UICityPainterToolMode)[] Modes; + private (UIButton, UICityPainterToolToggle)[] Toggles; + + private int SelectedMode = 0; + protected float SelectedIntensity = 0.5f; + + public AbstractCityPainterOptions() + { + RootHbox = new UIHBoxContainer(); + ModesHbox = new UIHBoxContainer(); + TogglesHbox = new UIHBoxContainer(); + + RootHbox.Add(ModesHbox); + RootHbox.Add(TogglesHbox); + + Add(RootHbox); + } + + public virtual void Init(UICityPainter painter) + { + Painter = painter; + } + + private void UpdateSelectedMode() + { + var modes = Modes; + + if (modes != null) + { + for (int i = 0; i < modes.Length; i++) + { + var mode = modes[i]; + + mode.Item1.Selected = MapPainter.SelectedModifier == mode.Item2.ModeValue; + } + } + } + + private void UpdateToggles() + { + var toggles = Toggles; + + if (toggles != null) + { + for (int i = 0; i < toggles.Length; i++) + { + var toggle = toggles[i]; + + toggle.Item1.Selected = toggle.Item2.Get(); + } + } + } + + public virtual void Selected() + { + MapPainter.SelectedModifier = SelectedMode; + MapPainter.BrushIntensity = SelectedIntensity; + + UpdateSelectedMode(); + UpdateToggles(); + } + + protected void SetModes(ReadOnlySpan modes) + { + var result = new (UIButton, UICityPainterToolMode)[modes.Length]; + + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + var strings = GameFacade.Strings; + + var buttonSeat = ui.Get("neighp_btab_seat.png").Get(gd); + var position = new Vector2(14, 10); + var seatOff = new Vector2(3, 3); + + for (int i = 0; i < modes.Length; i++) + { + var mode = modes[i]; + + var seat = new UIImage(buttonSeat) + { + Position = position + }; + + var button = new UIButton() + { + Texture = ui.Get($"cityedit_tool_{mode.Graphic}.png").Get(gd), + Tooltip = strings.GetString("f130", mode.CaptionID.ToString()), + Position = position + seatOff + }; + button.OnButtonClick += (btn) => + { + MapPainter.SelectedModifier = mode.ModeValue; + SelectedMode = mode.ModeValue; + UpdateSelectedMode(); + }; + + Add(seat); + Add(button); + + position.X += 33; + result[i] = (button, mode); + } + + Modes = result; + ModesHbox.AutoSize(); + RootHbox.AutoSize(); + + UpdateSelectedMode(); + } + + protected void SetToggles(ReadOnlySpan toggles) + { + var result = new (UIButton, UICityPainterToolToggle)[toggles.Length]; + + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + var strings = GameFacade.Strings; + + var buttonSeat = ui.Get("neighp_btab_seat.png").Get(gd); + var position = new Vector2(204, 10); + var seatOff = new Vector2(3, 3); + + for (int i = 0; i < toggles.Length; i++) + { + var toggle = toggles[i]; + + var seat = new UIImage(buttonSeat) + { + Position = position + }; + + var button = new UIButton() + { + Texture = ui.Get($"cityedit_tool_{toggle.Graphic}.png").Get(gd), + Tooltip = strings.GetString("f130", toggle.CaptionID.ToString()), + Selected = toggle.Get(), + Position = position + seatOff + }; + + button.OnButtonClick += (btn) => + { + var value = toggle.Get(); + toggle.Set(!value); + button.Selected = !value; + }; + + Add(seat); + Add(button); + + position.X -= 33; + + result[i] = (button, toggle); + } + + TogglesHbox.AutoSize(); + RootHbox.AutoSize(); + + Toggles = result; + } + + public override void Update(UpdateState state) + { + base.Update(state); + + SelectedIntensity = MapPainter.BrushIntensity; + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterPreview.cs b/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterPreview.cs new file mode 100644 index 000000000..c6b8ca15f --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/AbstractCityPainterPreview.cs @@ -0,0 +1,139 @@ +using FSO.Client.Rendering.City.Plugins; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.Common.Utils; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using XnaMatrix = Microsoft.Xna.Framework.Matrix; + +namespace FSO.Client.UI.Panels.CityPainter +{ + internal abstract class AbstractCityPainterPreview : UIElement + { + private Vector2 _Size; + public override Vector2 Size { get => _Size; set => _Size = value; } + + protected UICityPainter Painter { get; private set; } + protected MapPainterPlugin MapPainter => Painter.MapPainter; + + private XnaMatrix TileMatrix; + private float TileScale; + + public AbstractCityPainterPreview() + { + } + + public virtual void Init(UICityPainter painter) + { + Painter = painter; + } + + protected Texture2D LoadFSOTex(string name) + { + string path = Path.Combine(FSOEnvironment.ContentDir, "Textures/terrain/", name); + + return TextureUtils.TextureFromFile(GameFacade.GraphicsDevice, path); + } + + protected Texture2D[] LoadFSOTex(ReadOnlySpan names) + { + var result = new Texture2D[names.Length]; + + for (int i = 0; i < names.Length; i++) + { + result[i] = LoadFSOTex(names[i]); + } + + return result; + } + + protected Texture2D LoadTSOTex(string path) + { + string gamepath = GameFacade.GameFilePath($"gamedata/{path}"); + + return TextureUtils.TextureFromFile(GameFacade.GraphicsDevice, gamepath); + } + + protected Texture2D[] LoadTSOTex(ReadOnlySpan paths) + { + var result = new Texture2D[paths.Length]; + + for (int i = 0; i < paths.Length; i++) + { + result[i] = LoadTSOTex(paths[i]); + } + + return result; + } + + private (XnaMatrix, float) GetTileSpaceMatrix(Point size) + { + var diagSize = (size.X + size.Y) / 2f; + float diag = MathF.Sqrt(2); + + float scale = 1 / diagSize; + + float tileWidth = scale * 128 / diag; + + return ( + XnaMatrix.CreateTranslation(new Vector3(-0.5f, -0.5f, 0)) * + XnaMatrix.CreateRotationZ(MathF.PI / 4f) * + XnaMatrix.CreateScale(new Vector3(tileWidth, tileWidth / 2, 1)), + scale); + } + + protected void PrepareTileMatrix(Point size) + { + (TileMatrix, TileScale) = GetTileSpaceMatrix(size); + } + + protected (Vector2, Vector2) GetTilePosition(int x, int y, int width, int height) + { + var ctr = Vector2.Transform(new Vector2(x + 0.5f, y + 0.5f), TileMatrix) + (Size / 2f); + var scale = TileScale; + + return (ctr - new Vector2(width / 2, height - 32) * scale, new Vector2(scale, scale)); + } + + protected void BeginTile(UISpriteBatch batch, Point size) + { + batch.Pause(); + // Calculate a new matrix in tile space starting at the center of this component. + + var trueScale = Scale; + var trueCenter = LocalPoint(Size.X / 2f, Size.Y / 2f) / Scale; + + var toCenter = XnaMatrix.CreateTranslation(new Vector3(trueCenter, 0)) * XnaMatrix.CreateScale(new Vector3(trueScale, 1)); + + var mat = GetTileSpaceMatrix(size).Item1 * toCenter; + + batch.Begin(transformMatrix: mat); + } + + protected void EndTile(UISpriteBatch batch) + { + batch.End(); + batch.Resume(); + } + + protected void DrawLine(UISpriteBatch batch, Vector3 from, Vector3 to, float lineWidth, Color color) + { + var px = TextureGenerator.GetPxWhite(batch.GraphicsDevice); + + float heightScale = -32f * TileScale; + + var fromScreen = Vector2.Transform(new Vector2(from.X, from.Y), TileMatrix) + new Vector2(0, from.Z * heightScale); + var toScreen = Vector2.Transform(new Vector2(to.X, to.Y), TileMatrix) + new Vector2(0, to.Z * heightScale); + + var hSize = Size / 2; + var fromOrigin = LocalPoint(fromScreen + hSize); + var toOrigin = LocalPoint(toScreen + hSize); + var dir = toOrigin - fromOrigin; + var dist = dir.Length(); + + float rotation = (float)Math.Atan2(dir.Y, dir.X); + + batch.Draw(px, fromOrigin - new Vector2(0, lineWidth / -2), null, color, rotation, new Vector2(0, 0.5f), new Vector2(dist, lineWidth), SpriteEffects.None, 0); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterElevationOptions.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterElevationOptions.cs new file mode 100644 index 000000000..148734498 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterElevationOptions.cs @@ -0,0 +1,23 @@ +using FSO.Client.Rendering.City.Plugins; + +namespace FSO.Client.UI.Panels.CityPainter.Options +{ + internal class UICityPainterElevationOptions : AbstractCityPainterOptions + { + public override PainterMode Mode => PainterMode.ELEVATION_CIRCLE; + public override string Graphic => "elevation"; + public override string PreviewText => GameFacade.Strings.GetString("f130", "2"); + public override UICityPainterIntensityConfig IntensityConfig => DefaultIntensity; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + SetModes([]); + SetToggles([ + new ("auto", 20, () => MapPainter.AutoTerrain, (value) => { MapPainter.AutoTerrain = value; }), + new ("flat", 21, () => MapPainter.Flatten, (value) => { MapPainter.Flatten = value; }), + new ("rough", 22, () => MapPainter.RoughTerrain, (value) => { MapPainter.RoughTerrain = value; }) + ]); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterForestsOptions.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterForestsOptions.cs new file mode 100644 index 000000000..a98a49037 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterForestsOptions.cs @@ -0,0 +1,32 @@ +using FSO.Client.Rendering.City.Plugins; + +namespace FSO.Client.UI.Panels.CityPainter.Options +{ + internal class UICityPainterForestsOptions : AbstractCityPainterOptions + { + private UICityPainterIntensityConfig NonSprayIntensity = new UICityPainterIntensityConfig(1, 4, false); + public override PainterMode Mode => PainterMode.FOREST; + public override string Graphic => "forests"; + public override string PreviewText => GameFacade.Strings.GetString("f130", (40 + MapPainter.SelectedModifier).ToString()); + public override UICityPainterIntensityConfig IntensityConfig => MapPainter.SprayBrush ? DefaultIntensity : NonSprayIntensity; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + SetModes([ + new ("heavy", 40, 0), + new ("light", 41, 1), + new ("cacti", 42, 2), + new ("palm", 43, 3), + ]); + SetToggles([ + new ("spray", 12, () => MapPainter.SprayBrush, (value) => + { + MapPainter.SprayBrush = value; + + MapPainter.BrushIntensity = value ? 4f : 0.5f; + }), + ]); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterRoadsOptions.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterRoadsOptions.cs new file mode 100644 index 000000000..3e9ca0d1d --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterRoadsOptions.cs @@ -0,0 +1,37 @@ +using FSO.Client.Rendering.City.Plugins; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Panels.CityPainter.Options +{ + internal class UICityPainterRoadsOptions : AbstractCityPainterOptions + { + public override PainterMode Mode => PainterMode.ROAD; + public override string Graphic => "road"; + + public UILabel RoadLabel; + public override string PreviewText => GameFacade.Strings.GetString("f130", "4"); + public override UICityPainterIntensityConfig IntensityConfig => DisabledIntensity; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + + var style = TextStyle.DefaultLabel.Clone(); + style.Shadow = true; + + RoadLabel = new UILabel + { + Caption = GameFacade.Strings.GetString("f130", "17"), + Size = new Vector2(248, 51), + Alignment = TextAlignment.Center | TextAlignment.Middle, + CaptionStyle = style + }; + Add(RoadLabel); + + SetModes([]); + SetToggles([]); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterTerrainTypeOptions.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterTerrainTypeOptions.cs new file mode 100644 index 000000000..663abb2a7 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Options/UICityPainterTerrainTypeOptions.cs @@ -0,0 +1,27 @@ +using FSO.Client.Rendering.City.Plugins; + +namespace FSO.Client.UI.Panels.CityPainter.Options +{ + internal class UICityPainterTerrainTypeOptions : AbstractCityPainterOptions + { + public override PainterMode Mode => PainterMode.TERRAINTYPE; + public override string Graphic => "ttype"; + public override string PreviewText => GameFacade.Strings.GetString("f130", (30 + MapPainter.SelectedModifier).ToString()); + public override UICityPainterIntensityConfig IntensityConfig => MapPainter.SprayBrush ? DefaultIntensity : DisabledIntensity; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + SetModes([ + new ("grass", 30, 0), + new ("water", 31, 1), + new ("rock", 32, 2), + new ("snow", 33, 3), + new ("sand", 34, 4) + ]); + SetToggles([ + new ("spray", 12, () => MapPainter.SprayBrush, (value) => { MapPainter.SprayBrush = value; MapPainter.BrushIntensity = 0.5f; }), + ]); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterElevationPreview.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterElevationPreview.cs new file mode 100644 index 000000000..ad54753a8 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterElevationPreview.cs @@ -0,0 +1,121 @@ +using FSO.Client.Rendering.City.Plugins.PainterModes; +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; + +namespace FSO.Client.UI.Panels.CityPainter.Previews +{ + + internal class UICityPainterElevationPreview : AbstractCityPainterPreview + { + public MapPainterSpraypaint Spray; + + public override void Init(UICityPainter painter) + { + Spray = new MapPainterSpraypaint(true); + + base.Init(painter); + } + + public override void Draw(UISpriteBatch batch) + { + // Draw a grid representing the elevation change + + int tileCount = MapPainter.BrushSize * 2 + 2; + int vertCount = tileCount + 1; + PrepareTileMatrix(new Point(tileCount)); + + bool[] tileTouched = new bool[tileCount * tileCount]; + float[] vertices = new float[vertCount * vertCount]; + float[] intensityVertices = vertices; + int center = vertCount / 2; + + float baseSize = MapPainter.BrushSize + 0.5f; + var multiplier = MathF.Pow(baseSize, 0.8f) * ((MapPainter.Accelerate) ? 8 : 4); + + var erasing = !MapPainter.Flatten && MapPainter.Erasing; + + if (erasing) + { + multiplier *= -1; + } + + float intensity = 1; + if (MapPainter.Flatten) + { + int vi = 0; + for (int y = 0; y < vertCount; y++) + { + for (int x = 0; x < vertCount; x++) + { + vertices[vi++] = (y - vertCount / 2f) * -0.5f; + } + } + + multiplier = 50; + intensityVertices = new float[vertCount * vertCount]; + var centerElev = 0; + + IMapPainterMode.BrushFunc(MapPainter.BrushSize, (x, y, strength) => + { + if (strength > 0) + { + int vertInd = (y + center) * vertCount + x + center; + var elev = vertices[vertInd]; + + var change = (centerElev - elev) / 50f * multiplier; + if (change > 0) change = Math.Max(0.02f, change); + else change = Math.Min(-0.02f, change); + + vertices[vertInd] += change; + intensityVertices[vertInd] = Math.Max(Math.Abs(change), strength); + } + }); + } + else + { + intensity = MapPainter.BrushIntensity; + multiplier *= intensity; + IMapPainterMode.BrushFunc(MapPainter.BrushSize, (x, y, strength) => + { + if (strength > 0) + { + if (MapPainter.RoughTerrain) + { + strength = Spray.GetRoughEdge((256 + y) * 512 + 256 + x, strength, MapPainter.BrushSize); + } + + vertices[(y + center) * vertCount + x + center] = strength * multiplier; + } + }); + } + + Vector3 offset = new Vector3(-baseSize, -baseSize, 0); + Color baseColor = erasing ? Color.Red : Color.White; + + for (int y = 0; y < tileCount; y++) + { + for (int x = 0; x < tileCount; x++) + { + Vector3 v1 = new Vector3(x, y, vertices[y * vertCount + x]) + offset; + Vector3 v2 = new Vector3(x + 1, y, vertices[y * vertCount + x + 1]) + offset; + Vector3 v3 = new Vector3(x, y + 1, vertices[(y + 1) * vertCount + x]) + offset; + Vector3 v4 = new Vector3(x + 1, y + 1, vertices[(y + 1) * vertCount + x + 1]) + offset; + + float e1 = intensityVertices[y * vertCount + x]; + float e2 = intensityVertices[y * vertCount + x + 1]; + float e3 = intensityVertices[(y + 1) * vertCount + x]; + float e4 = intensityVertices[(y + 1) * vertCount + x + 1]; + + float mag = (e1 + e2 + e3 + e4) / 4; + + Color color = baseColor * Math.Min(1f, Math.Abs(mag / multiplier)); + + DrawLine(batch, v1, v2, 2, color); + DrawLine(batch, v3, v4, 2, color); + DrawLine(batch, v1, v3, 2, color); + DrawLine(batch, v2, v4, 2, color); + } + } + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterForestsPreview.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterForestsPreview.cs new file mode 100644 index 000000000..96166b77f --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterForestsPreview.cs @@ -0,0 +1,95 @@ +using FSO.Client.Rendering.City.Plugins.PainterModes; +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.CityPainter.Previews +{ + internal class UICityPainterForestsPreview : AbstractCityPainterPreview + { + private Texture2D Forests; + public MapPainterSpraypaint Spray; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + + Forests = LoadTSOTex("farzoom/forest00a.tga"); + Spray = new MapPainterSpraypaint(true); + } + + public override void Draw(UISpriteBatch batch) + { + // Draw forests based on the brush and the type + // If it's erasing then draw a red grid under them + + var fw = Forests.Width / 4; + var fh = Forests.Height / 4; + float intensityS = MapPainter.BrushIntensity; + float intensityF = MapPainter.BrushIntensity - 1; + int intensity = Math.Clamp((int)MathF.Round(intensityF), 0, 3); + int type = MapPainter.SelectedModifier; + + var size = MapPainter.BrushSize; + + Color tint = Color.White; + + PrepareTileMatrix(new Point(size * 2 + 1)); + + if (MapPainter.Erasing) + { + tint *= 0.5f; + + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + var multiplier = (MapPainter.Accelerate) ? 2 : 1; + if (strength > 0) + { + var v1 = new Vector3(x, y, 0); + var v2 = new Vector3(x + 1, y, 0); + var v3 = new Vector3(x + 1, y + 1, 0); + var v4 = new Vector3(x, y + 1, 0); + + Color color = Color.Red * Math.Min(1f, strength + 0.5f); + + DrawLine(batch, v1, v2, 2, color); + DrawLine(batch, v2, v3, 2, color); + DrawLine(batch, v3, v4, 2, color); + DrawLine(batch, v4, v1, 2, color); + } + }); + } + + var spray = MapPainter.SprayBrush; + + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + var multiplier = (MapPainter.Accelerate) ? 2 : 1; + + if (spray) + { + var brushIntensity = Spray.GetSpraypaint((256 + y) * 512 + 256 + x, strength) * intensityS * multiplier; + intensity = Math.Clamp((int)MathF.Round(brushIntensity * 8), 0, 4) - 1; + + if (intensity >= 0) + { + var src = new Rectangle(intensity * fw, type * fh, fw, fh); + var dst = GetTilePosition(x, y, fw, fh); + + DrawLocalTexture(batch, Forests, src, dst.Item1, dst.Item2, tint); + } + } + else + { + if (strength > 0) + { + var src = new Rectangle(intensity * fw, type * fh, fw, fh); + var dst = GetTilePosition(x, y, fw, fh); + + DrawLocalTexture(batch, Forests, src, dst.Item1, dst.Item2, tint); + } + } + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterRoadsPreview.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterRoadsPreview.cs new file mode 100644 index 000000000..b4aabca9c --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterRoadsPreview.cs @@ -0,0 +1,63 @@ +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.CityPainter.Previews +{ + internal class UICityPainterRoadsPreview : AbstractCityPainterPreview + { + private Texture2D[] RoadTilePreview; + + public override void Init(UICityPainter painter) + { + base.Init(painter); + + RoadTilePreview = LoadFSOTex([ + "roadcorner02.png", + "roadcorner04.png", + "road01.png", + "road04.png", + "roadcorner01.png", + "roadcorner08.png", + ]); + } + + public override void Draw(UISpriteBatch batch) + { + // Just draw a road at the middle. + + var erasing = MapPainter.Erasing; + var tint = erasing ? Color.White * 0.5f : Color.White; + + BeginTile(batch, new Point(3, 3)); + + for (int i = 0; i < RoadTilePreview.Length; i++) + { + int x = i % 2; + int y = i / 2; + batch.Draw(RoadTilePreview[i], new Rectangle((x * 2) - 1, y * 2 - 2, 2, 2), tint); + } + + EndTile(batch); + + if (MapPainter.Erasing) + { + PrepareTileMatrix(new Point(3, 3)); + + var roadSize = 0.22f; + + var v1 = new Vector3(1 - roadSize, 0 - roadSize, 0); + var v2 = new Vector3(1 + roadSize, 0 - roadSize, 0); + var v3 = new Vector3(1 + roadSize, 2 + roadSize, 0); + var v4 = new Vector3(1 - roadSize, 2 + roadSize, 0); + + Color color = Color.Red; + + DrawLine(batch, v1, v2, 2, color); + DrawLine(batch, v2, v3, 2, color); + DrawLine(batch, v3, v4, 2, color); + DrawLine(batch, v4, v1, 2, color); + } + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterTerrainTypePreview.cs b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterTerrainTypePreview.cs new file mode 100644 index 000000000..38d1f978b --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/Previews/UICityPainterTerrainTypePreview.cs @@ -0,0 +1,64 @@ +using FSO.Client.Rendering.City.Plugins.PainterModes; +using FSO.Client.UI.Framework; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.CityPainter.Previews +{ + + internal class UICityPainterTerrainTypePreview : AbstractCityPainterPreview + { + public Texture2D[] TerrainTextures; + public MapPainterSpraypaint Spray; + public override void Init(UICityPainter painter) + { + base.Init(painter); + + TerrainTextures = LoadTSOTex([ + "terrain/newformat/gr.tga", + "terrain/newformat/wt.tga", + "terrain/newformat/rk.tga", + "terrain/newformat/sn.tga", + "terrain/newformat/sd.tga", + ]); + + Spray = new MapPainterSpraypaint(true); + } + + private int PosMod(int x, int m) + { + return (x % m + m) % m; + } + + public override void Draw(UISpriteBatch batch) + { + // Draw the terrain brush result + + var size = MapPainter.BrushSize; + + var tex = TerrainTextures[MapPainter.SelectedModifier]; + var texSegment = new Point(tex.Width / 4, tex.Height / 4); + var spray = MapPainter.SprayBrush; + var intensity = MapPainter.BrushIntensity * 0.8f + 0.2f; // Small bias to assist the display. + + BeginTile(batch, new Point(size * 2 + 1)); + IMapPainterMode.BrushFunc(size, (x, y, strength) => + { + var multiplier = (MapPainter.Accelerate) ? 2 : 1; + + if (spray) + { + var brushIntensity = Spray.GetSpraypaint((256 + y) * 512 + 256 + x, strength) * intensity * multiplier; + strength = brushIntensity - 0.3f; + } + + if (strength > 0) + { + batch.Draw(tex, new Rectangle(x, y, 1, 1), new Rectangle(PosMod(x, 4) * texSegment.X, PosMod(y, 4) * texSegment.Y, texSegment.X, texSegment.Y), Color.White); + } + }); + + EndTile(batch); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainter.cs b/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainter.cs new file mode 100644 index 000000000..b7683904d --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainter.cs @@ -0,0 +1,591 @@ +using FSO.Client.Controllers; +using FSO.Client.Rendering.City; +using FSO.Client.Rendering.City.Plugins; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Model; +using FSO.Client.UI.Panels.CityPainter.Options; +using FSO.Client.UI.Panels.CityPainter.Previews; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Domain.Realestate; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.HIT; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.CityPainter +{ + internal readonly struct UICityPainterToolMode(string graphic, int captionId, int modeValue) + { + public readonly string Graphic = graphic; + public readonly int CaptionID = captionId; + public readonly int ModeValue = modeValue; + } + + internal readonly struct UICityPainterToolToggle(string graphic, int captionId, Func get, Action set) + { + public readonly string Graphic = graphic; + public readonly int CaptionID = captionId; + public readonly Func Get = get; + public readonly Action Set = set; + } + + internal readonly struct UICityPainterIntensityConfig + { + public readonly bool Disable; + public readonly float Min; + public readonly float Max; + public readonly bool AllowDecimal; + + public UICityPainterIntensityConfig() + { + Disable = true; + } + + public UICityPainterIntensityConfig(float min, float max, bool allowDecimal) + { + Disable = false; + Min = min; + Max = max; + AllowDecimal = allowDecimal; + } + } + + internal class UICityPainter : UIContainer + { + private const float ThumbDisplayDuration = 3.5f; + private const float ThumbDisplayFade = 1; + private const float ThumbFlashDuration = 0.2f; + private readonly struct ModeUI(UIImage tabBackground, UIButton tabButton, AbstractCityPainterOptions options, AbstractCityPainterPreview preview) + { + public readonly UIImage TabBackground = tabBackground; + public readonly UIButton TabButton = tabButton; + public readonly AbstractCityPainterOptions Options = options; + public readonly AbstractCityPainterPreview Preview = preview; + } + + public UIImage BackgroundImage { get; set; } + public UIButton DialogNameButton { get; set; } + public UIButton CloseButton { get; set; } + public UIButton LockButton { get; set; } + public UIButton CameraButton { get; set; } + + public UIButton UndoButton { get; set; } + public UIButton RedoButton { get; set; } + + public readonly MapPainterPlugin MapPainter; + private readonly ModeUI[] Modes; + + private readonly UIButton PreviewBg; + private int ActiveIndex = -1; + + private readonly UILabel BrushSizeLabel; + private readonly UISlider BrushSizeSlider; + + private readonly UILabel BrushIntensityLabel; + private readonly UISlider BrushIntensitySlider; + + private readonly Texture2D LockedGraphic; + private readonly Texture2D UnlockedGraphic; + private readonly UILabel PreviewLabel; + + private readonly Terrain Terrain; + private readonly TerrainController TController; + private readonly CityUndoStack UndoStack; + + private readonly Vector2[] TabBackgroundPositions = [ + new Vector2(203, -5), + new Vector2(246, -5), + new Vector2(291, -5), + new Vector2(336, -5) + ]; + + private RenderTarget2D CityThumbnailTarget; + private Texture2D CityThumbnailTexture; + private float CityThumbnailTimer; + + public UICityPainter(Terrain terrain) + { + Terrain = terrain; + MapPainter = new MapPainterPlugin(terrain); + + TController = Terrain.FindController(); + UndoStack = TController.Realestate.UndoStack; + + Modes = [ + GenerateMode(0), + GenerateMode(1), + GenerateMode(2), + GenerateMode(3), + ]; + + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + + Add(BackgroundImage = new UIImage(ui.Get("cityedit_bg.png").Get(gd))); + + Add(DialogNameButton = new UIButton(GetTexture(0x00000AFE00000001)) + { + Caption = GameFacade.CurrentCityName, + Size = new Vector2(193, 18), + Position = new Vector2(11, 8) + }); + + UIUtils.MakeDraggable(BackgroundImage, this, true); + + LockedGraphic = ui.Get("cityedit_locked.png").Get(gd); + UnlockedGraphic = ui.Get("cityedit_unlocked.png").Get(gd); + + Add(CloseButton = new UIButton(ui.Get("neighp_closebtn.png").Get(gd)) + { + Position = new Vector2(446, 26), + Tooltip = GameFacade.Strings.GetString("f130", "19") + }); + + Add(LockButton = new UIButton(LockedGraphic) + { + Position = new Vector2(9, 46), + Tooltip = GameFacade.Strings.GetString("f130", "18") + }); + + Add(CameraButton = new UIButton(ui.Get("cityedit_camera.png").Get(gd)) + { + Position = new Vector2(12, 98), + Tooltip = GameFacade.Strings.GetString("f130", "8") + }); + + Add(UndoButton = new UIButton(ui.Get("cityedit_undo.png").Get(gd)) + { + Position = new Vector2(44, 122), + Tooltip = GameFacade.Strings.GetString("f130", "6") + }); + + Add(RedoButton = new UIButton(ui.Get("cityedit_redo.png").Get(gd)) + { + Position = new Vector2(176, 122), + Tooltip = GameFacade.Strings.GetString("f130", "7") + }); + + Add(PreviewBg = new UIButton(GetTexture(0x0000079300000001)) + { + Position = new Vector2(55, 42), + Tooltip = GameFacade.Strings.GetString("f130", "13") + }); + + var font = TextStyle.DefaultLabel.Clone(); + font.Color = Color.White; + font.Size = 9; + font.Shadow = true; + + Add(PreviewLabel = new UILabel() + { + Position = new Vector2(67, 135), + Size = new Vector2(109, 17), + Alignment = TextAlignment.Center | TextAlignment.Top, + CaptionStyle = font + }); + + (BrushSizeLabel, BrushSizeSlider) = CreateSlider(new Vector2(223, 87), 102, 10); + (BrushIntensityLabel, BrushIntensitySlider) = CreateSlider(new Vector2(340, 87), 102, 11); + + BrushSizeSlider.Value = 0; + BrushSizeSlider.MinValue = 0; + BrushSizeSlider.MaxValue = 25; + BrushSizeSlider.AllowDecimals = false; + + BrushSizeSlider.OnChange += (slider) => + { + MapPainter.BrushSize = (int)BrushSizeSlider.Value; + }; + + BrushIntensitySlider.Value = 0; + BrushIntensitySlider.MinValue = 0; + BrushIntensitySlider.MaxValue = 10; + BrushIntensitySlider.AllowDecimals = true; + BrushIntensitySlider.OnChange += (slider) => + { + MapPainter.BrushIntensity = BrushIntensitySlider.Value; + }; + + foreach (var mode in Modes) + { + mode.TabBackground.Visible = false; + Add(mode.TabBackground); + } + + int i = 0; + foreach (var mode in Modes) + { + mode.TabButton.Position = new Vector2(226 + 45 * (i++), 8); + + Add(mode.TabButton); + } + + UpdateLockedGraphic(); + LockButton.OnButtonClick += ToggleLock; + CameraButton.OnButtonClick += TakeScreenshot; + PreviewBg.OnButtonClick += InvertBrush; + + UndoButton.OnButtonClick += Undo; + RedoButton.OnButtonClick += Redo; + + DialogNameButton.OnButtonClick += ChangeName; + + CloseButton.OnButtonClick += Close; + + SetMode(PainterMode.ROAD); + + UndoStack.UndoChanged += UndoChanged; + + UndoChanged(); + } + + private void ChangeName(UIElement button) + { + var dialog = new UILotPurchaseDialog() + .AsRenameDialog( + GameFacade.CurrentCityName, + GameFacade.Strings.GetString("f130", "51"), + GameFacade.Strings.GetString("f130", "52")); + + dialog.OnNameChosen += (name) => + { + // TODO: set on server + TController.UpdateCityName(name); + UIScreen.RemoveDialog(dialog); + }; + + UIScreen.GlobalShowDialog(new DialogReference + { + Dialog = dialog, + Controller = this, + Modal = true, + }); + } + + private void Redo(UIElement button) + { + if (!UndoStack.CanRedo()) return; + + PlayRepeatableSound(UISounds.BuildDragToolUp); + + var toRedo = UndoStack.Redo(); + + if (toRedo != null) + { + TController.CommitMapChange(toRedo); + } + } + + private void Undo(UIElement button) + { + if (!UndoStack.CanUndo()) return; + + PlayRepeatableSound(UISounds.BuildDragToolUp); + + int? uid = UndoStack.Undo(); + + if (uid != null) + { + TController.SendCityCommand(CityUpdateCommandMode.Undo, uid.Value); + } + } + + private void UndoChanged() + { + UndoButton.Disabled = !UndoStack.CanUndo(); + RedoButton.Disabled = !UndoStack.CanRedo(); + } + + private void InvertBrush(UIElement button) + { + MapPainter.ErasingToggle = !MapPainter.ErasingToggle; + } + + private void UpdateLockedGraphic() + { + LockButton.Texture = MapPainter.LockProperties ? LockedGraphic : UnlockedGraphic; + LockButton.Tooltip = GameFacade.Strings.GetString("f130", MapPainter.LockProperties ? "18" : "9"); + } + + private void ToggleLock(UIElement button) + { + MapPainter.LockProperties = !MapPainter.LockProperties; + UpdateLockedGraphic(); + } + + private void EnsureThumbnailTarget() + { + CityThumbnailTarget ??= new RenderTarget2D(GameFacade.GraphicsDevice, 720, 540, false, SurfaceFormat.Color, DepthFormat.Depth24); + } + + private void PlayRepeatableSound(string sfx) + { + var sound = HIT.HITVM.Get().PlaySoundEvent(sfx); + (sound as HITThread).WriteVar(0x31, 1); + } + + private void TakeScreenshot(UIElement button) + { + PlayRepeatableSound(UISounds.CameraPhoto); + + var gd = GameFacade.GraphicsDevice; + EnsureThumbnailTarget(); + + Terrain.DrawThumbnail(gd, CityThumbnailTarget); + + CityThumbnailTexture?.Dispose(); + CityThumbnailTexture = TextureUtils.Decimate(CityThumbnailTarget, gd, 4, false); + CityThumbnailTimer = 0; + + byte[] data; + using (var mem = new MemoryStream()) + { + CityThumbnailTexture.SaveAsPng(mem, CityThumbnailTexture.Width, CityThumbnailTexture.Height); + + data = mem.ToArray(); + } + + TController.UpdateThumbnail(data); + } + + private (UILabel, UISlider) CreateSlider(Vector2 position, float width, int stringIndex) + { + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + + var font = TextStyle.DefaultLabel.Clone(); + font.Color = Color.White; + font.Size = 9; + font.Shadow = true; + + var label = new UILabel() + { + Caption = GameFacade.Strings.GetString("f130", stringIndex.ToString()), + CaptionStyle = font, + Alignment = TextAlignment.Top | TextAlignment.Center, + Position = position, + Size = new Vector2(width, 1) + }; + + var slider = new UISlider() + { + Orientation = 0, + Texture = ui.Get("cityedit_slider.png").Get(gd), + Position = position + new Vector2(0, 16), + Size = new Vector2(width, 17), + }; + + Add(label); + Add(slider); + + return (label, slider); + } + + private void Close(UIElement button) + { + SetActive(false); + } + + public void SetActive(bool active) + { + if (active) + { + FSOFacade.Hints.TriggerHint("ui:city_editor"); + + Visible = true; + Terrain.Plugin = MapPainter; + TController.HideTooltip(); + } + else + { + Visible = false; + Terrain.Plugin = null; + } + } + + private void SetMode(PainterMode mode) + { + if (ActiveIndex != -1) + { + ref var activeUi = ref Modes[ActiveIndex]; + + Remove(activeUi.Options); + Remove(activeUi.Preview); + activeUi.TabBackground.Visible = false; + } + + int index = Array.FindIndex(Modes, (ui) => ui.Options.Mode == mode); + + if (index == -1) + { + return; + } + + for (int i = 0; i < Modes.Length; i++) + { + Modes[i].TabButton.Selected = i == index; + } + + ref var ui = ref Modes[index]; + + // Put the options and preview in the UI. + + ui.TabBackground.Visible = true; + + var options = ui.Options; + options.Position = new Vector2(209, 40); + options.Size = new Vector2(248, 90); + Add(options); + + options.Selected(); + + var preview = ui.Preview; + preview.Position = PreviewBg.Position; + preview.Size = PreviewBg.Size; + Add(preview); + + ActiveIndex = index; + + MapPainter.SwitchMode(mode); + } + + private ModeUI GenerateMode(int index) where TOptions : AbstractCityPainterOptions, new() where TPreview : AbstractCityPainterPreview, new() + { + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + + var background = new UIImage(ui.Get($"cityedit_tab{index+1}.png").Get(gd)); + var options = new TOptions(); + var preview = new TPreview(); + var button = new UIButton(ui.Get($"cityedit_{options.Graphic}.png").Get(gd)); + + var strings = GameFacade.Strings; + + background.Position = TabBackgroundPositions[index]; + + button.Tooltip = strings.GetString("f130", (index + 2).ToString()); + button.OnButtonClick += (btn) => SetMode(options.Mode); + + options.Init(this); + preview.Init(this); + return new ModeUI( + background, + button, + options, + preview + ); + } + + private void SetSliderEnabled(UISlider slider, UILabel label, bool enabled) + { + float opacity = enabled ? 1f : 0.5f; + + if (slider.Opacity != opacity) + { + slider.Opacity = opacity; + label.Opacity = opacity; + } + } + + public override void Update(UpdateState state) + { + base.Update(state); + + if (!Visible) + { + return; + } + + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Z)) + { + if (state.CtrlDown) + { + if (state.ShiftDown) + { + Redo(RedoButton); + } + else + { + Undo(UndoButton); + } + } + } + + if (GameFacade.CurrentCityName != DialogNameButton.Caption) + { + DialogNameButton.Caption = GameFacade.CurrentCityName; + } + + BrushSizeSlider.Value = MapPainter.BrushSize; + BrushIntensitySlider.Value = MapPainter.BrushIntensity; + + if (ActiveIndex != -1) + { + ref var activeUi = ref Modes[ActiveIndex]; + + var label = activeUi.Options.PreviewText; + if (PreviewLabel.Caption != label) + { + PreviewLabel.Caption = label; + } + + var intensity = activeUi.Options.IntensityConfig; + + SetSliderEnabled(BrushSizeSlider, BrushSizeLabel, activeUi.Options.Mode != PainterMode.ROAD); + SetSliderEnabled(BrushIntensitySlider, BrushIntensityLabel, !intensity.Disable); + + if (BrushIntensitySlider.MinValue != intensity.Min) BrushIntensitySlider.MinValue = intensity.Min; + if (BrushIntensitySlider.MaxValue != intensity.Max) BrushIntensitySlider.MaxValue = intensity.Max; + if (BrushIntensitySlider.AllowDecimals != intensity.AllowDecimal) BrushIntensitySlider.AllowDecimals = intensity.AllowDecimal; + } + + CityThumbnailTimer += 1f / FSOEnvironment.RefreshRate; + } + + public override void Draw(UISpriteBatch batch) + { + base.Draw(batch); + + if (CityThumbnailTexture != null) + { + var white = TextureGenerator.GetPxWhite(batch.GraphicsDevice); + + var whiteCol = Color.White; + var borderCol = Color.LightSlateGray; + var shadowCol = Color.Black * 0.3f; + var size = new Vector2(CityThumbnailTexture.Width, CityThumbnailTexture.Height); + var basePos = new Vector2(31, 168); + var shadowOffset = new Vector2(7, 7); + var borderOffset = new Vector2(4, 4); + var whiteOffset = new Vector2(3, 3); + + float alpha = CityThumbnailTimer > ThumbDisplayDuration ? Math.Max(0, 1 - (CityThumbnailTimer - ThumbDisplayDuration) / ThumbDisplayFade) : 1; + + if (alpha != 1) + { + whiteCol *= alpha; + borderCol *= alpha; + shadowCol *= alpha; + } + + if (alpha != 0) + { + DrawLocalTexture(batch, white, null, basePos + shadowOffset - borderOffset, size + borderOffset * 2, shadowCol); + DrawLocalTexture(batch, white, null, basePos - borderOffset, size + borderOffset * 2, borderCol); + DrawLocalTexture(batch, white, null, basePos - whiteOffset, size + whiteOffset * 2, whiteCol); + DrawLocalTexture(batch, CityThumbnailTexture, null, basePos, Vector2.One, Color.White * alpha); + + if (CityThumbnailTimer < ThumbFlashDuration) + { + float flashAlpha = Math.Max(0, 1 - CityThumbnailTimer / ThumbFlashDuration); + DrawLocalTexture(batch, white, null, basePos, size, whiteCol * flashAlpha); + } + } + } + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainterAvatar.cs b/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainterAvatar.cs new file mode 100644 index 000000000..192016e0a --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/CityPainter/UICityPainterAvatar.cs @@ -0,0 +1,116 @@ +using FSO.Client.Rendering.City; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Common; +using FSO.Common.DataService.Model; +using FSO.Common.Rendering.Framework.Model; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Panels.CityPainter +{ + internal class UICityPainterAvatarLayer : UIContainer + { + private readonly Terrain City; + private readonly Dictionary AvatarById = []; + + public UICityPainterAvatarLayer(Terrain city) + { + City = city; + } + + public void RegisterModification(CityModification mod) + { + if (!AvatarById.TryGetValue(mod.AvatarId, out var avatar)) + { + avatar = new UICityPainterAvatar(City, mod.AvatarId, City.Content.PainterCursor, City.Content.PainterCursorActive); + AvatarById[mod.AvatarId] = avatar; + Add(avatar); + } + + avatar.RegisterModification(mod); + } + } + + internal class UICityPainterAvatar : UIContainer + { + private readonly Terrain City; + private readonly UIImage Background; + private readonly UIPersonButton Person; + + private readonly Texture2D BaseTexture; + private readonly Texture2D ActiveTexture; + + private CityModification LastModification; + + public UICityPainterAvatar(Terrain city, uint avatarId, Texture2D baseTexture, Texture2D activeTexture) + { + BaseTexture = baseTexture; + ActiveTexture = activeTexture; + + City = city; + Background = new UIImage(baseTexture) + { + Size = new Vector2(baseTexture.Width / 2, baseTexture.Height / 2), + Position = new Vector2(baseTexture.Width / -4, baseTexture.Height / -2) + }; + + float personScale = 0.65f; + Person = new UIPersonButton() + { + AvatarId = avatarId, + FrameSize = UIPersonButtonSize.LARGE, + ScaleX = personScale, + ScaleY = personScale + }; + + var personButtonSize = Person.Size; + + Person.Position = new Vector2(Background.Position.X + Background.Size.X / 2, Background.Position.Y + Background.Size.X / 2) - personScale * personButtonSize / 2; + + Add(Background); + Add(Person); + Person.SetButtonVisible(false); + } + + public override void Update(UpdateState state) + { + if (LastModification.Timer < CityModification.EdgeDuration) + { + var tex = (Person.ButtonFrame == 1 || Person.ButtonFrame == 2) ? ActiveTexture : BaseTexture; + + if (tex != Background.Texture) + { + Background.Texture = tex; + } + + var bmp = LastModification.Bitmap; + var pos = new Vector2(bmp.X + bmp.Width / 2f, bmp.Y + bmp.Height / 2f); + + var proj = City.transformSpr4(new Vector3(pos.X, City.InterpElevationAt(pos) + 2f, pos.Y)); + + Position = new Vector2(proj.X, proj.Y) / FSOEnvironment.DPIScaleFactor; + Visible = (proj.Z > 0); + + if (Visible) + { + float alpha = LastModification.GetArrowAlpha(); + + Background.Opacity = alpha; + Person.Opacity = alpha; + } + } + else if (Visible) + { + Visible = false; + } + + base.Update(state); + } + + public void RegisterModification(CityModification mod) + { + LastModification = mod; + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIBandEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UIBandEOD.cs index 9266019e3..0befde533 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIBandEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIBandEOD.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Timers; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; using FSO.Client.UI.Model; using FSO.Client.UI.Panels.EODs.Utils; using FSO.Content.Model; using FSO.SimAntics.NetPlay.EODs.Handlers; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Timers; namespace FSO.Client.UI.Panels.EODs { @@ -18,8 +16,8 @@ public class UIBandEOD : UIEOD private static bool NoteSent; private UIScript Script; private UIEODLobby Lobby; - private Timer SequenceNoteTimer; - private Timer SyncTimer; + private System.Timers.Timer SequenceNoteTimer; + private System.Timers.Timer SyncTimer; private byte[] CurrentSequence; private int CurrentNote; @@ -60,7 +58,7 @@ public class UIBandEOD : UIEOD private int Creative2Skill; private int CurrentDisplayedCreative2Level; private List UpperUIElements; - private Timer LevelTimer; + private System.Timers.Timer LevelTimer; private int LevelTimerTicks; // buttons @@ -140,11 +138,11 @@ public UIBandEOD(UIEODController controller) : base(controller) Remove(CONTINUE); Remove(CASHOUT); - SequenceNoteTimer = new Timer(VMEODBandPlugin.MILLISECONDS_PER_NOTE_IN_SEQUENCE); + SequenceNoteTimer = new System.Timers.Timer(VMEODBandPlugin.MILLISECONDS_PER_NOTE_IN_SEQUENCE); SequenceNoteTimer.Elapsed += NextNoteHandler; - SyncTimer = new Timer(VMEODBandPlugin.MILLISECONDS_PER_NOTE_IN_SEQUENCE); + SyncTimer = new System.Timers.Timer(VMEODBandPlugin.MILLISECONDS_PER_NOTE_IN_SEQUENCE); SyncTimer.Elapsed += SyncTimerElapsedHandler; - LevelTimer = new Timer(250); + LevelTimer = new System.Timers.Timer(250); LevelTimer.Elapsed += SkillLevelHandler; // get the buttons and put into array in order to recover their references when the client connects @@ -185,7 +183,8 @@ public UIBandEOD(UIEODController controller) : base(controller) .WithPlayerUI(new UIEODLobbyPlayer(1, WaitPlayer2, Player2Wait)) .WithPlayerUI(new UIEODLobbyPlayer(2, WaitPlayer3, Player3Wait)) .WithPlayerUI(new UIEODLobbyPlayer(3, WaitPlayer4, Player4Wait)) - .WithCaptionProvider((player, avatar) => { + .WithCaptionProvider((player, avatar) => + { switch (player.Slot) { case (int)VMEODBandInstrumentTypes.Trumpet: diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIBlackjackEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UIBlackjackEOD.cs index 288ab63b5..d39920700 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIBlackjackEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIBlackjackEOD.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Timers; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; using FSO.Client.UI.Panels.EODs.Utils; using FSO.SimAntics.NetPlay.EODs.Handlers; using FSO.SimAntics.NetPlay.EODs.Handlers.Data; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Timers; namespace FSO.Client.UI.Panels.EODs { @@ -32,8 +29,8 @@ public class UIBlackjackEOD : UIEOD private bool IsBettingAllowed; private List CardsToDeal; private int DealingIndex; - private Timer DealTimer; - private Timer InvalidateTimer; + private System.Timers.Timer DealTimer; + private System.Timers.Timer InvalidateTimer; private Random Random = new Random(); private UIAlert InsuranceAlert; private short DealersID; @@ -277,10 +274,10 @@ public UIBlackjackEOD(UIEODController controller) : base(controller) PlaintextHandlers["blackjack_resume_manage"] = ResumeManageHandler; // other - DealTimer = new Timer(1400); + DealTimer = new System.Timers.Timer(1400); DealTimer.Elapsed += new ElapsedEventHandler(DealTimerHandler); DealersName = "MOMI"; - InvalidateTimer = new Timer(1000); + InvalidateTimer = new System.Timers.Timer(1000); /* * NOTE: If you haven't noticed how bad the EOD invalidateion problem is, just disable this timer and see how impossible it is to keep up * with the flow of the game due to message (tips) not showing on time or at all. @@ -301,10 +298,10 @@ public override void OnClose() private void PlayerShowUIHandler(string evt, byte[] playerSlotMinBetMaxBet) { if (playerSlotMinBetMaxBet == null) return; - + MainPlayerCardContainers = new List(); MainPlayerCardTotals = new List(); - + string[] data = VMEODGameCompDrawACardData.DeserializeStrings(playerSlotMinBetMaxBet); byte playerSlot = 5; int minBet = -1; @@ -394,7 +391,7 @@ private void OwnerShowUIHandler(string evt, string balanceMinMaxBet) labelTotalBet.Visible = false; EODTallBack.Visible = false; EODTallBackEnd.Visible = false; - + playerPos1.Visible = false; Player1Head.Visible = false; Player1CardContainer.Visible = false; @@ -412,13 +409,13 @@ private void OwnerShowUIHandler(string evt, string balanceMinMaxBet) Player3CardContainer.Visible = false; Player3TotalBack.Visible = false; Player3CardTotal.Visible = false; - + playerPos4.Visible = false; Player4Head.Visible = false; Player4CardContainer.Visible = false; Player4TotalBack.Visible = false; Player4CardTotal.Visible = false; - + DealerPos.Visible = false; DealerHead.Visible = false; DealerCardContainer.Visible = false; @@ -435,7 +432,7 @@ private void OwnerShowUIHandler(string evt, string balanceMinMaxBet) Player4BetAmount.Visible = false; DealerBetBack.Visible = false; DealerBetAmount.Visible = false; - + int tempBalance; int tempMinBet; int tempMaxBet; @@ -1038,10 +1035,10 @@ private void PlayerChoiceBroadcastHandler(string evt, byte[] player) // play the ka-ching sound HIT.HITVM.Get().PlaySoundEvent("ui_object_place"); } - // other option is 'a' for "blackjack_late_comer" + // other option is 'a' for "blackjack_late_comer" - // set the correct active hand - SetActiveOtherPlayerHand(player[0]); + // set the correct active hand + SetActiveOtherPlayerHand(player[0]); if (player[0] == MyPlayerNumber) SetNewTip(GameFacade.Strings["UIText", "263", "3"].Replace(".", appendix)); // "Your turn." @@ -1547,7 +1544,7 @@ private void PlayerUpperUIInit() { X = DealerBetBack.X, Y = DealerBetBack.Y + 4, - Size = DealerBetBack.Size.ToVector2(), + Size = DealerBetBack.Size, CurrentText = "Dealer", Alignment = TextAlignment.Center, TextStyle = captionStyle, @@ -1675,7 +1672,7 @@ private void SyncAllHands(List handSizesAndCards, bool useQueue) Int32.TryParse(handSizesAndCards[2], out player3NumCardsInHand) && Int32.TryParse(handSizesAndCards[3], out player4NumCardsInHand) && Int32.TryParse(handSizesAndCards[4], out dealerNumCardsInHand)) - { + { handSizesAndCards.RemoveAt(4); handSizesAndCards.RemoveAt(3); @@ -2014,19 +2011,23 @@ private void UpdateOtherPlayerHand(int player, bool setActive, params string[] c { CardHand playerHand = null; UILabel playertotal = null; - if (player == 0) { + if (player == 0) + { playerHand = Player1CardContainer; playertotal = Player1CardTotal; } - else if (player == 1) { + else if (player == 1) + { playerHand = Player2CardContainer; playertotal = Player2CardTotal; } - else if (player == 2) { + else if (player == 2) + { playerHand = Player3CardContainer; playertotal = Player3CardTotal; } - else if (player == 3) { + else if (player == 3) + { playerHand = Player4CardContainer; playertotal = Player4CardTotal; } @@ -2104,7 +2105,7 @@ private void UpdatePlayerBetAmount(int player, string amountString) return; if (player == 0) { - Player1BetCaption = amountString; + Player1BetCaption = amountString; Player1BetAmount.Caption = Player1BetCaption; Player1BetAmount.X = Player1BetBack.X + offsetX; } @@ -2189,7 +2190,7 @@ private void DisableBettingButtons() btnChip5.Disabled = true; } } -#endregion + #endregion internal class CardHand : UIContainer { private float CurrentOpacity = 1f; @@ -2403,7 +2404,7 @@ private void ShiftCardsLeft() { var cardList = GetChildren(); foreach (var card in cardList) - card.X -= 4* _CurrentScale; + card.X -= 4 * _CurrentScale; } private void HideFirstCard() { @@ -2433,9 +2434,9 @@ private void CalculateTotalValue() softAce = true; } } - else + else if (VMEODBlackjackPlugin.PlayingCardBlackjackValues.TryGetValue(split[0], out value)) - _TotalValueOfCards += value; + _TotalValueOfCards += value; } if (_TotalValueOfCards > 21) { diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIEODController.cs b/TSOClient/tso.client/UI/Panels/EODs/UIEODController.cs index ae755cc5e..cbed8bc43 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIEODController.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIEODController.cs @@ -1,4 +1,5 @@ using FSO.Client.UI.Framework; +using FSO.Client.UI.Screens; using FSO.SimAntics.NetPlay.EODs.Handlers; using FSO.SimAntics.NetPlay.Model.Commands; using System; @@ -103,8 +104,23 @@ public void OnEODMessage(VMNetEODMessageCmd cmd) } } + private void LazilyOpenLiveMode() + { + var ucp = (UIScreen.Current as IGameScreen)?.ucp; + if (ucp != null && ucp.CurrentPanel == -1) + { + ucp.SetPanel(1); + } + } + public void ShowEODMode(EODLiveModeOpt mode) { + if (DisplayMode == null && mode != null) + { + Lot.StealFocus = true; + LazilyOpenLiveMode(); + } + DisplayMode = mode; //gets picked up by live mode } diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIGameshowBuzzerEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UIGameshowBuzzerEOD.cs index ff1b77fb2..a1e3a2260 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIGameshowBuzzerEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIGameshowBuzzerEOD.cs @@ -1,15 +1,14 @@ -using System; -using System.Timers; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using Microsoft.Xna.Framework.Graphics; +using System.Timers; namespace FSO.Client.UI.Panels.EODs { public abstract class UIGameshowBuzzerEOD : UIEOD { //shared assets - private Timer InvalidateTimer; + private System.Timers.Timer InvalidateTimer; protected Texture2D PlayerScoreBackTexture = GetTexture(0x95500000001); // eod_buzzer_playerscoreback protected Texture2D PlayersVMPersonButtonBackTex = GetTexture(0x000002B300000001); // EOD_PizzaHeadPlaceholder1.bmp protected Texture2D Lightsframe1Tex; @@ -22,7 +21,7 @@ public abstract class UIGameshowBuzzerEOD : UIEOD public UIGameshowBuzzerEOD(UIEODController controller) : base(controller) { - InvalidateTimer = new Timer(1000); + InvalidateTimer = new System.Timers.Timer(1000); InvalidateTimer.Elapsed += new ElapsedEventHandler((obj, args) => { Parent.Invalidate(); }); InvalidateTimer.Start(); } @@ -112,10 +111,10 @@ protected void PlayerWinHandler(string evt, string playerName) internal class ContestantLightsFrame : UIContainer { private bool TexturesValid; - private UIImage Lights1 = new UIImage(); + private UIImage Lights1 = new UIImage(); private UIImage Lights2 = new UIImage(); private UIImage LightsBack = new UIImage(); - private UIImage LightsBlue = new UIImage(); + private UIImage LightsBlue = new UIImage(); private UIImage LightsRed = new UIImage(); private System.Timers.Timer FlashTimer; diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIHoldEmCasinoEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UIHoldEmCasinoEOD.cs index 358b78d1f..214fc9d55 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIHoldEmCasinoEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIHoldEmCasinoEOD.cs @@ -1,14 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Timers; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Panels.EODs.Utils; using FSO.SimAntics.NetPlay.EODs.Handlers; using FSO.SimAntics.NetPlay.EODs.Handlers.Data; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Timers; namespace FSO.Client.UI.Panels.EODs { @@ -23,16 +20,16 @@ public class UIHoldEmCasinoEOD : UIEOD private string DealersName; private int DealingIndex; private UIVMPersonButton DealerPersonButton; - private Timer DealTimer; + private System.Timers.Timer DealTimer; private bool DecisionIsAllowed; - private Timer InvalidateTimer; + private System.Timers.Timer InvalidateTimer; private UIEODLobby Lobby; private int MaxAnteBet; private int MaxSideBet; private int MinAnteBet; private int MyPlayerNumber; private UIManageEODObjectPanel OwnerPanel; - + // lower buttons private UIButton Ante1ChipButton; private UIButton Ante5ChipButton; @@ -164,7 +161,7 @@ public class UIHoldEmCasinoEOD : UIEOD { (byte)VMEODHoldEmCasinoAlerts.Side_Bet_Too_High, GameFacade.Strings.GetString("f111", "53") }, // sidebet { (byte)VMEODHoldEmCasinoAlerts.Object_Broken, GameFacade.Strings.GetString("f111", "90") } }; - + public UIHoldEmCasinoEOD(UIEODController controller) : base(controller) { InitLowerUI(); @@ -226,10 +223,10 @@ public UIHoldEmCasinoEOD(UIEODController controller) : base(controller) PlaintextHandlers["holdemcasino_resume_manage"] = ResumeManageHandler; // other - DealTimer = new Timer(1400); + DealTimer = new System.Timers.Timer(1400); DealTimer.Elapsed += new ElapsedEventHandler(DealTimerHandler); DealersName = "MOMI"; - InvalidateTimer = new Timer(1000); + InvalidateTimer = new System.Timers.Timer(1000); InvalidateTimer.Elapsed += new ElapsedEventHandler((obj, args) => { Parent.Invalidate(); }); InvalidateTimer.Start(); } @@ -372,7 +369,7 @@ private void ShowOwnerUIHandler(string evt, string balanceMinAnteMaxAnteSide) Player4SideBetLabel.Visible = false; DealerBetBack.Visible = false; DealerBetAmount.Visible = false; - + // parse the data and add owner panel int tempBalance; int tempMinAnte; @@ -593,7 +590,7 @@ private void BetCallbackHandler(string evt, byte[] bets) { // update AnteBet and the Bets for the (upper) fields pertaining to MyPlayerNumber var betStrings = VMEODGameCompDrawACardData.DeserializeStrings(bets); - + SetMyAnteBet(betStrings[0]); SetMySideBet(betStrings[1]); } @@ -665,7 +662,8 @@ private void SyncAllActiveHandsHandler(string evt, byte[] playersPlaying) { if (playersPlaying != null & playersPlaying.Length == 4) { - for (int index = 0; index < 4; index++) { + for (int index = 0; index < 4; index++) + { if (playersPlaying[index] == 1) { UpdateUpperPlayerHand(index + 1, "Back", "Back"); @@ -1018,7 +1016,7 @@ private void DealInitialCards(List allCardNames) { CardsToDeal.Add(new string[] { "6", cardName }); } - + // set the index for dealing and start the timer DealingIndex = -1; @@ -1105,7 +1103,8 @@ private void ToggleDecision(bool isAllowed) CallButton.Disabled = !isAllowed; FoldButton.Disabled = !isAllowed; } - private void UpdateUserInput(bool allowed) { + private void UpdateUserInput(bool allowed) + { Ante1ChipButton.Disabled = !allowed; Ante5ChipButton.Disabled = !allowed; Ante10ChipButton.Disabled = !allowed; @@ -1216,7 +1215,7 @@ private void InitLowerUI() Disabled = true }; Add(Ante100ChipButton); - + // buttons for side bets Side1ChipButton = new UIButton(Chip1ButtonTexture) @@ -1259,7 +1258,7 @@ private void InitLowerUI() }; Side100ChipButton.Y = Ante100ChipButton.Y + 47; Add(Side100ChipButton); - + // diving line var horizontalDivider = new UIHighlightSprite(107, 1, 0.25f); @@ -1301,7 +1300,7 @@ private void InitLowerUI() { X = AnteBetBack.X, Y = AnteBetBack.Y + 3, - Size = AnteBetBack.Size.ToVector2(), + Size = AnteBetBack.Size, Alignment = TextAlignment.Center, CurrentText = "Ante", Mode = UITextEditMode.ReadOnly, @@ -1314,7 +1313,7 @@ private void InitLowerUI() { X = SideBetBack.X, Y = SideBetBack.Y + 3, - Size = SideBetBack.Size.ToVector2(), + Size = SideBetBack.Size, Alignment = TextAlignment.Center, CurrentText = "Side", Mode = UITextEditMode.ReadOnly, @@ -1419,10 +1418,16 @@ private void InitLowerUI() SideBet.OnChange += MySideBetHandler; SubmitBetsButton.OnButtonClick += (btn) => { SubmitBetsHandler(); }; CallAndFoldHelpButton.OnButtonClick += (btn) => { ShowUIAlert(Holdem, AlertStrings[(byte)VMEODHoldEmCasinoAlerts.Call_Fold_Help], null); }; - HelpAnteBetButton.OnButtonClick += (btn) => { ShowUIAlert(Holdem, - AlertStrings[(byte)VMEODHoldEmCasinoAlerts.Ante_Bet_Help].Replace("%n", "" + MinAnteBet).Replace("%x", "" + MaxAnteBet), null); }; - HelpSideBetButton.OnButtonClick += (btn) => { ShowUIAlert(Holdem, - AlertStrings[(byte)VMEODHoldEmCasinoAlerts.Side_Bet_Help].Replace("Min: $%n ", "").Replace("%x", "" + MaxSideBet), null); }; + HelpAnteBetButton.OnButtonClick += (btn) => + { + ShowUIAlert(Holdem, + AlertStrings[(byte)VMEODHoldEmCasinoAlerts.Ante_Bet_Help].Replace("%n", "" + MinAnteBet).Replace("%x", "" + MaxAnteBet), null); + }; + HelpSideBetButton.OnButtonClick += (btn) => + { + ShowUIAlert(Holdem, + AlertStrings[(byte)VMEODHoldEmCasinoAlerts.Side_Bet_Help].Replace("Min: $%n ", "").Replace("%x", "" + MaxSideBet), null); + }; ClearAnteBetButton.OnButtonClick += (btn) => { ClearAnteBet(); }; ClearSideBetButton.OnButtonClick += (btn) => { ClearSideBet(); }; CallButton.OnButtonClick += (btn) => { CallOrFoldHandler(1); }; @@ -1460,7 +1465,7 @@ private void InitUpperUI() // community and player cards CommunityHand = new FiveCardHand(1.25f) { - Position = (new Vector2 (503, 321) - new Vector2(CommunityCardsWidth * 1.25f, 0)) / 2 + Position = (new Vector2(503, 321) - new Vector2(CommunityCardsWidth * 1.25f, 0)) / 2 }; CommunityHand.X += 64; Add(CommunityHand); @@ -1688,7 +1693,7 @@ private void InitUpperUI() { X = DealerBetBack.X - 10, Y = DealerBetBack.Y + 1, - Size = DealerBetBack.Size.ToVector2(), + Size = DealerBetBack.Size, CurrentText = "Dealer", Alignment = TextAlignment.Center, //TextStyle = captionStyle, @@ -1967,7 +1972,7 @@ internal class FiveCardHand : CardHand private UIImage Card3 = new UIImage(); private UIImage Card4 = new UIImage(); private UIImage Card5 = new UIImage(); - + public FiveCardHand(float targetScale) { // add the background @@ -1976,7 +1981,7 @@ public FiveCardHand(float targetScale) _CurrentScale = Background.ScaleX = Background.ScaleY = targetScale; Background.Reset(); Add(Background); - + // add the cards Card1.ScaleX = Card1.ScaleY = _CurrentScale; Card1.Position = _CurrentScale * CardStartOffset; diff --git a/TSOClient/tso.client/UI/Panels/EODs/UIRouletteEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UIRouletteEOD.cs index a4f0d3801..e95e4d604 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UIRouletteEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UIRouletteEOD.cs @@ -1,16 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; using FSO.Client.UI.Panels.EODs.Utils; using FSO.Common.Rendering.Framework.IO; using FSO.Common.Rendering.Framework.Model; using FSO.SimAntics.NetPlay.EODs.Handlers; -using System.Timers; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Timers; namespace FSO.Client.UI.Panels.EODs { @@ -73,7 +70,7 @@ class UIRouletteEOD : UIEOD private UIScript Script; private UIManageEODObjectPanel OwnerPanel; private UIMouseEventRef RouletteGraphMouseHandler; - private Timer WheelSpinTimer = new Timer(333); + private System.Timers.Timer WheelSpinTimer = new System.Timers.Timer(333); private int WheelSpinElapsedCounter = 0; // Text fields public UILabel labelNumber { get; set; } @@ -96,7 +93,7 @@ class UIRouletteEOD : UIEOD { (byte)VMEODRouletteInputErrorTypes.ObjectNSF, GameFacade.Strings.GetString("f111", "28") }, { (byte)VMEODRouletteInputErrorTypes.ObjectBroken, GameFacade.Strings.GetString("f111", "90") } }; - + public UIRouletteEOD(UIEODController controller) : base(controller) { InitUI(); @@ -471,7 +468,7 @@ private void UnderMinBetHandler(string evt, string lowBet) { TextSize = 12, Title = GameFacade.Strings.GetString("f111", "15"), // "Betting Error" - Message = GameFacade.Strings.GetString("f111", "21").Replace("%d","" + MinBet), // "Your bet must be at least $%d." + Message = GameFacade.Strings.GetString("f111", "21").Replace("%d", "" + MinBet), // "Your bet must be at least $%d." Alignment = TextAlignment.Center, TextEntry = false, Buttons = UIAlertButton.Ok((btn) => @@ -1031,7 +1028,7 @@ private void RecoverButtonRefs() btnShowAllBets = btnChipsArray[Array.LastIndexOf(btnChipsArray, btnShowAllBets)]; if (btnShowAllBets == null) btnShowAllBets = Script.Create("btnShowAllBets"); - btnChip1.Tooltip = GameFacade.Strings["UIText", "258", "8"].Replace("%d","1"); + btnChip1.Tooltip = GameFacade.Strings["UIText", "258", "8"].Replace("%d", "1"); btnChip2.Tooltip = GameFacade.Strings["UIText", "258", "8"].Replace("%d", "5"); btnChip3.Tooltip = GameFacade.Strings["UIText", "258", "8"].Replace("%d", "10"); btnChip4.Tooltip = GameFacade.Strings["UIText", "258", "8"].Replace("%d", "25"); @@ -1132,7 +1129,7 @@ private void CreateImages() // is it a red number found in the array of red numbers? isRed = (Array.IndexOf(RedNumbersArray, number) != -1); - + // sprite sprite = new UIHighlightSprite(20, 19) { @@ -1745,7 +1742,7 @@ private Vector2 NumbersListToActualVector(int[] numbersList) if (numbersList[0] + 1 != numbersList[1]) // if the numbers are not sequential, it must be a column bet { // x is constant, y varies by column which is determined by the first number in the list: 1, 2, or 3 - result = new Vector2(13 * NUMBER_SPACE_WIDTH + 1, (numbersList[0] % 3 == 0) ? 2 : ((3 - (numbersList[0] % 3)) * NUMBER_SPACE_HEIGHT)+ 2); + result = new Vector2(13 * NUMBER_SPACE_WIDTH + 1, (numbersList[0] % 3 == 0) ? 2 : ((3 - (numbersList[0] % 3)) * NUMBER_SPACE_HEIGHT) + 2); } /* * "dozen bet" - 1 to 12, 13 to 24, or 25 to 36 @@ -1940,7 +1937,8 @@ private void PlaceBet(int chipValue, VMEODRouletteBetTypes type, Vector2 actualC Add(image); } } - if (betIsValid && !skipEvent) { + if (betIsValid && !skipEvent) + { // send bet to server to be validated string typeString = ""; if (VMEODRoulettePlugin.RouletteBetTypes.TryGetValue(type, out typeString)) @@ -2038,7 +2036,7 @@ private void ThrowSomeShade(params int[] doNotShadeList) private void UpdateTotalBets() { int totalBets = 0; - foreach(var stack in MyChipsInPlay) + foreach (var stack in MyChipsInPlay) { totalBets += stack.TotalStackValue; } @@ -2156,7 +2154,8 @@ public ShadowBox(int number, bool isRed, UIHighlightSprite highlightsprite) _UIHighlightSprite.InvalidateOpacity(); } - public int Number { + public int Number + { get { return _Number; } } @@ -2172,7 +2171,7 @@ public UIHighlightSprite UIHighlightSprite } /* * This container holds all PlayChips and behaves like a "stack" data type. It keeps track of its location, total bet value and type, and no. of chips - */ + */ public class ChipStack { private Vector2 _Position; @@ -2258,7 +2257,7 @@ public UISlotsImage[] Dispose() return null; } // This will be useful for quickly and easily hiding chip stacks belonging to neighbor players on the table - public void Hide() + public void Hide() { foreach (var chip in _Chips) { @@ -2290,7 +2289,7 @@ public PlayChip(int value, Texture2D texture, Vector2 origin, int stackPosition) { Position = origin + new Vector2(-1 * stackPosition * 0, -1 * stackPosition * 4) // left 1 and up 3 for each chip added to stack }; - _ChipImage.SetBounds(0,0,texture.Width, texture.Height); + _ChipImage.SetBounds(0, 0, texture.Width, texture.Height); if (stackPosition > 0) { _TextureNeedsDisposal = true; @@ -2352,7 +2351,7 @@ internal class RouletteWheelStateNode { private RouletteWheelStateNode _Next; // each X value points to a simulated spinning wheel graphic private RouletteWheelStateNode _Ultimate; // one of the three X value options to show a non-spinning simulated graphic, for displaying the result - private int _X; + private int _X; public RouletteWheelStateNode(int x, RouletteWheelStateNode next) { @@ -2377,12 +2376,12 @@ public RouletteWheelStateNode Ultimate // Spinning disallows betting and animates the wheel, Idle is the betting phase, Dragging allows a chip to follow the user's mouse pointer during betting public enum UIRouletteEODStates : byte { - Spinning = 0, - Idle = 1, - Dragging = 2, - Results = 3, - Initializing = 4, - Managing = 5, - GameOver = 6 + Spinning = 0, + Idle = 1, + Dragging = 2, + Results = 3, + Initializing = 4, + Managing = 5, + GameOver = 6 } } diff --git a/TSOClient/tso.client/UI/Panels/EODs/UISecureTradeEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UISecureTradeEOD.cs index 313321b2d..cd79915dd 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UISecureTradeEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UISecureTradeEOD.cs @@ -450,7 +450,7 @@ public override void Update(UpdateState state) //are we currently moving an inventory item? if (DragItem != null) { - DragItem.Position = GlobalPoint(state.MouseState.Position.ToVector2() - new Vector2(22, 22)); + DragItem.Position = GlobalPoint(state.MouseState.Position.ToVector2()) - new Vector2(22, 22); if (!mouseDown) { //try place the item down @@ -473,7 +473,7 @@ public override void Update(UpdateState state) var index = Array.FindIndex(MyOffer.ObjectOffer, x => x != null && x.PID == DragUID); if (index == -1) { - var targ = Math.Min(4, (state.MouseState.Position.X - myOfferRect.X) / 45); + var targ = Math.Min(4, (state.MouseState.Position.X - myOfferRect.X) / (int)(45 * _Scale.X)); if (DragUID == 1 || DragUID == 2) { MyOffer.ObjectOffer[targ] = diff --git a/TSOClient/tso.client/UI/Panels/EODs/UISlotsEOD.cs b/TSOClient/tso.client/UI/Panels/EODs/UISlotsEOD.cs index 9d81f046a..4b8625374 100644 --- a/TSOClient/tso.client/UI/Panels/EODs/UISlotsEOD.cs +++ b/TSOClient/tso.client/UI/Panels/EODs/UISlotsEOD.cs @@ -1,11 +1,10 @@ -using FSO.Content.Model; -using FSO.Client.UI.Controls; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; using FSO.Client.UI.Panels.EODs.Utils; +using FSO.Content.Model; using FSO.SimAntics.NetPlay.EODs.Handlers; using Microsoft.Xna.Framework.Graphics; -using System; using System.Timers; namespace FSO.Client.UI.Panels.EODs @@ -116,9 +115,9 @@ public class UISlotsEOD : UIEOD public const int WHEEL_FRAME_CONSTANT = 5; // timers for animations - private Timer OfflineMessageTimer; - private Timer LightsTimer; - private Timer WheelsSpinTimer; + private System.Timers.Timer OfflineMessageTimer; + private System.Timers.Timer LightsTimer; + private System.Timers.Timer WheelsSpinTimer; public UISlotsEOD(UIEODController controller) : base(controller) { @@ -427,15 +426,15 @@ private void PlayerInitHandler(string evt, byte[] args) MachineTypeInit(0); // create a timer to animate the lights, milliseconds - LightsTimer = new Timer(666 + (2 / 3)); + LightsTimer = new System.Timers.Timer(666 + (2 / 3)); LightsTimer.Elapsed += new ElapsedEventHandler(LightsHandler); // create a timer to change offline messages - OfflineMessageTimer = new Timer(3000); + OfflineMessageTimer = new System.Timers.Timer(3000); OfflineMessageTimer.Elapsed += new ElapsedEventHandler(OfflineMessageHandler); // create a timer to handle the spinning of the wheels - WheelsSpinTimer = new Timer(25); + WheelsSpinTimer = new System.Timers.Timer(25); WheelsSpinTimer.Elapsed += new ElapsedEventHandler(AnimateWheelsHandler); } @@ -490,7 +489,7 @@ private void OwnerInitHandler(string evt, string paybackBalanceTypeIsOn) else if (payBack > 110) payBack = 110; MachineOdds = payBack; - + MachineBalance = machineBalance; // on/off button @@ -572,7 +571,7 @@ private void InputFailHandler(string evt, string message) { if (OwnerPanel != null) { - OwnerPanel.InputFailHandler(evt.Remove(0,6), message); // truncate "slots_" + OwnerPanel.InputFailHandler(evt.Remove(0, 6), message); // truncate "slots_" } } private void NewGameHandler(string evt, string message) @@ -721,7 +720,7 @@ private void OfflineMessageHandler(object source, ElapsedEventArgs args) if (EODController.EODMessage.Equals(GameFacade.Strings["UIText", "259", "22"])) // "Closed for Maintenance" SetTip(GameFacade.Strings["UIText", "259", "23"]); // "Please play another machine" else - SetTip(GameFacade.Strings["UIText", "259", "22"]); // "Closed for Maintenance" + SetTip(GameFacade.Strings["UIText", "259", "22"]); // "Closed for Maintenance" } } private void DrawWheelStops(bool wheelOneAlreadyDone, bool wheelTwoAlreadyDone, bool wheelThreeAlreadyDone) diff --git a/TSOClient/tso.client/UI/Panels/LotControls/UICheatHandler.cs b/TSOClient/tso.client/UI/Panels/LotControls/UICheatHandler.cs index 61e1c39c5..1c6136a55 100644 --- a/TSOClient/tso.client/UI/Panels/LotControls/UICheatHandler.cs +++ b/TSOClient/tso.client/UI/Panels/LotControls/UICheatHandler.cs @@ -200,8 +200,8 @@ public void SubmitCommand(string msg) public string ObjectSummary(VMEntity obj) { - return obj.ToString() + " | " + obj.ObjectID + " | " + "container: " + obj.Container - + "owner: " + ((obj.TSOState as SimAntics.Model.TSOPlatform.VMTSOObjectState)?.OwnerID ?? 0); + var owner = (obj.TSOState as SimAntics.Model.TSOPlatform.VMTSOObjectState)?.OwnerID ?? 0; + return $"{obj.ToString()} | {obj.ObjectID} | db: {obj.PersistID:x8} | container: {obj.Container} owner: {owner}"; } } diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIAbstractStickyContainer.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIAbstractStickyContainer.cs index db7ce1cb0..6c2e9ece4 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIAbstractStickyContainer.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIAbstractStickyContainer.cs @@ -108,7 +108,7 @@ public override void Draw(UISpriteBatch batch) effect.Parameters["stickyPersp"].SetValue((Size.Y/240f) * -0.2f); DrawLocalTexture(batch, Target, - new Rectangle((int)(-100*ScaleX), 0, (int)(Target.Width + 200 * ScaleX), Target.Height), + new Rectangle((int)(-100*Scale.X), 0, (int)(Target.Width + 200 * Scale.X), Target.Height), - (BackOffset.ToVector2() + new Vector2(100, 0)), new Vector2(1 / (Scale.X), 1 / (Scale.Y))); batch.SetEffect(); } diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIBulletinPost.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIBulletinPost.cs index d131bd25f..2ed653f02 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIBulletinPost.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIBulletinPost.cs @@ -340,6 +340,8 @@ private void GoBack(UIElement button) public void SetPost(BulletinItem item) { + var screen = FindController(); + ActiveItem = item; if (item == null) { @@ -362,7 +364,7 @@ public void SetPost(BulletinItem item) PropertyButtonBG.Visible = true; PersonButton.Visible = true; - PersonButton.AvatarId = FindController()?.MyID() ?? 0; + PersonButton.AvatarId = screen?.MyID() ?? 0; PersonButtonName.Visible = true; PersonButtonBG.Visible = true; @@ -408,8 +410,8 @@ public void SetPost(BulletinItem item) PersonButton.AvatarId = item.SenderID; var canPromote = IsMayor && item.Type == BulletinType.Community; - var myPost = FindController()?.IsMe(item.SenderID) ?? false; - var admin = GameFacade.EnableMod; + var myPost = screen?.IsMe(item.SenderID) ?? false; + var admin = screen?.ModerationLevel > 0; RightButton.Visible = true; if (canPromote) @@ -421,7 +423,7 @@ public void SetPost(BulletinItem item) } MiddleButton.Caption = GameFacade.Strings.GetString("f120", "35"); - MiddleButton.Visible = !myPost && GameFacade.EnableMod; + MiddleButton.Visible = !myPost && screen?.ModerationLevel > 0; } } diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIFullRatingItem.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIFullRatingItem.cs index b00c9e196..22414e6a0 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIFullRatingItem.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIFullRatingItem.cs @@ -109,17 +109,17 @@ public UIFullRatingItem(uint ratingID) btnCaption.Size = 8; btnCaption.Shadow = true; - if (GameFacade.EnableMod) + DeleteButton = new UIButton(btnTex) { - DeleteButton = new UIButton(btnTex); - DeleteButton.Caption = "Delete"; - DeleteButton.CaptionStyle = btnCaption; - DeleteButton.OnButtonClick += DeletePost; - DeleteButton.Width = 64; - DeleteButton.X = 135; - DeleteButton.Y = 4; - Add(DeleteButton); - } + Caption = "Delete", + CaptionStyle = btnCaption, + Width = 64, + X = 135, + Y = 4, + Visible = false + }; + DeleteButton.OnButtonClick += DeletePost; + Add(DeleteButton); Size = new Vector2(475, 70); PxWhite = TextureGenerator.GetPxWhite(GameFacade.GraphicsDevice); @@ -201,6 +201,8 @@ public override void Update(UpdateState state) { var cont = ControllerUtils.BindController(this); cont.SetRating(RatingID); + + DeleteButton.Visible = cont.ModerationLevel > 0; } } diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIManageDonatorDialog.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIManageDonatorDialog.cs index 1d8c8ef95..7c0f4453f 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIManageDonatorDialog.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIManageDonatorDialog.cs @@ -61,6 +61,7 @@ public UIManageDonatorDialog(UILotControl lotControl) : base(UIDialogStyle.Stand Add(Dropdown); RoommateListSlider.AttachButtons(RoommateListScrollUpButton, RoommateScrollDownButton, 1); + RoommateListBox.UseChildElements = true; RoommateListBox.AttachSlider(RoommateListSlider); RoommateListBox.Columns[1].Alignment = Framework.TextAlignment.Left | Framework.TextAlignment.Middle; @@ -82,7 +83,7 @@ public UIManageDonatorDialog(UILotControl lotControl) : base(UIDialogStyle.Stand private void AddDonator(uint donator, string name) { - LotControl.vm.TSOState.Names.Precache(LotControl.vm, donator); + LotControl.vm.TSOState.Names.Precache(LotControl.vm, VMGlobalEntityType.Avatar, donator); if (Community) { @@ -210,7 +211,7 @@ public void UpdateDonatorList() return new UIListBoxItem( x, personBtn, - LotControl.vm.TSOState.Names.GetNameForID(LotControl.vm, x), + LotControl.vm.TSOState.Names.GetNameForID(LotControl.vm, VMGlobalEntityType.Avatar, x), check, deleteBtn ); diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighBanner.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighBanner.cs index e4b62d2b4..748e956b0 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighBanner.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighBanner.cs @@ -152,7 +152,7 @@ private void Draw3Slice(UISpriteBatch batch, Texture2D tex, Rectangle rect, Colo public void DrawGlobalTexture(SpriteBatch batch, Texture2D texture, Nullable from, Vector2 to, Vector2 scale, Color blend) { - var pos = FlooredLocalPoint(Vector2.Zero)/_Scale; + var pos = AlignedLocalPoint(Vector2.Zero, _Scale)/_Scale; DrawLocalTexture(batch, texture, from, to/_Scale-pos, scale/_Scale, blend); } @@ -163,7 +163,7 @@ public void DrawGlobalString(SpriteBatch batch, string text, Vector2 to, TextSty _ScaleY = 1 / Parent.Scale.Y; CalculateMatrix(); - var pos = FlooredLocalPoint(Vector2.Zero); + var pos = AlignedLocalPoint(Vector2.Zero, _Scale); DrawLocalString(batch, text, to - pos, style, bounds, align); //_Scale = scale; _ScaleX = scale.X; diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighPage.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighPage.cs index ef3f50d89..d22ba2cb1 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighPage.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINeighPage.cs @@ -419,14 +419,15 @@ private void RefreshMayor() private void RateSwitch(UIElement button) { - if (MayorIsMe || GameFacade.EnableMod) + var screen = FindController(); + if (MayorIsMe || (screen?.ModerationLevel ?? 0) > 0) { CurrentMayorTab = UINeighMayorTabMode.Actions; Redraw(); } else { - FindController()?.NeighborhoodProtocol?.BeginRating + screen?.NeighborhoodProtocol?.BeginRating (CurrentNeigh.Value?.Id ?? 0, CurrentNeigh.Value?.Neighborhood_MayorID ?? 0, (success) => @@ -485,7 +486,8 @@ private void ShowRandomRatings() private void RenameAdmin(UIElement button) { - if (GameFacade.EnableMod) + var controller = FindController(); + if (controller.ModerationLevel > 1) { var lotName = new UILotPurchaseDialog(); lotName.OnNameChosen += (name) => @@ -493,7 +495,7 @@ private void RenameAdmin(UIElement button) if (CurrentNeigh != null && CurrentNeigh.Value != null) { CurrentNeigh.Value.Neighborhood_Name = name; - FindController().SaveName(CurrentNeigh.Value); + controller.SaveName(CurrentNeigh.Value); } UIScreen.RemoveDialog(lotName); @@ -635,7 +637,8 @@ public override void Update(UpdateState state) } if (Visible && CurrentNeigh?.Value != null) { - if (GameFacade.EnableMod) DescriptionText.Mode = UITextEditMode.Editor; + var controller = FindController(); + if (controller.ModerationLevel > 1) DescriptionText.Mode = UITextEditMode.Editor; string mayorString; if (CurrentNeigh.Value.Neighborhood_MayorID != 0) mayorString = GameFacade.Strings.GetString("f115", "22", new string[] { MayorPersonButton.MainButton.Tooltip }); @@ -659,11 +662,13 @@ public override void Update(UpdateState state) public void TrySaveDescription() { - if (CurrentNeigh != null && CurrentNeigh.Value != null && GameFacade.EnableMod + var controller = FindController(); + + if (CurrentNeigh != null && CurrentNeigh.Value != null && controller.ModerationLevel > 1 && DescriptionText.CurrentText != CurrentNeigh.Value.Neighborhood_Description && DescriptionChanged) { CurrentNeigh.Value.Neighborhood_Description = DescriptionText.CurrentText; - FindController().SaveDescription(CurrentNeigh.Value); + controller.SaveDescription(CurrentNeigh.Value); DescriptionChanged = false; } } @@ -839,13 +844,15 @@ private void Redraw() MayorElectionLabel.Visible = isMayor; MayorNominationLabel.Visible = isMayor; - + + var screen = FindController(); var now = ClientEpoch.Now; bool hasMayor = false; bool iAmMayor = false; + uint moderationLevel = screen?.ModerationLevel ?? 0; if (CurrentNeigh.Value != null) { - iAmMayor = FindController().IsMe(CurrentNeigh.Value.Neighborhood_MayorID); + iAmMayor = screen?.IsMe(CurrentNeigh.Value.Neighborhood_MayorID) ?? false; MayorIsMe = iAmMayor; if (CurrentTab == UINeighPageTab.Description && !DescriptionChanged) { @@ -891,7 +898,7 @@ private void Redraw() } if (isMayor) { - var canUseExtraTools = iAmMayor || GameFacade.EnableMod; + var canUseExtraTools = iAmMayor || moderationLevel > 0; MayorRatingFlairLabel.Caption = GameFacade.Strings.GetString("f115", (37 + (int)CurrentMayorTab).ToString()); RateButton.Caption = GameFacade.Strings.GetString("f115", (canUseExtraTools) ? "89" : "33"); if (!canUseExtraTools) CurrentMayorTab = UINeighMayorTabMode.Rate; @@ -901,13 +908,13 @@ private void Redraw() bool isRating = isMayor && CurrentMayorTab == UINeighMayorTabMode.Rate && hasMayor; MayorTabRateImage.Visible = isRating; - RateButton.Visible = isRating || GameFacade.EnableMod && isMayor; + RateButton.Visible = isRating || moderationLevel > 0 && isMayor; MayorRatingBox1.Visible = isRating; MayorRatingBox2.Visible = isRating; bool isMayorAction = isMayor && CurrentMayorTab == UINeighMayorTabMode.Actions; - MayorActionMod.Visible = isMayorAction && GameFacade.EnableMod; + MayorActionMod.Visible = isMayorAction && moderationLevel > 0; MayorActionMoveTH.Visible = isMayorAction; MayorActionMoveTH.Disabled = !HasTownHall; MayorActionNewTH.Visible = isMayorAction; diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINominationSelectContainer.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINominationSelectContainer.cs index df4a08dc3..eede2feb2 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UINominationSelectContainer.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UINominationSelectContainer.cs @@ -47,6 +47,7 @@ public UINominationSelectContainer(NhoodCandidateList candidates, bool nonPerson listBg.Height += 50; RoommateListSlider.AttachButtons(RoommateListScrollUpButton, RoommateScrollDownButton, 1); + RoommateListBox.UseChildElements = true; RoommateListBox.AttachSlider(RoommateListSlider); RoommateListBox.Columns[1].Alignment = Framework.TextAlignment.Left | Framework.TextAlignment.Middle; diff --git a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIRatingList.cs b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIRatingList.cs index beae8f16b..35d165cdf 100644 --- a/TSOClient/tso.client/UI/Panels/Neighborhoods/UIRatingList.cs +++ b/TSOClient/tso.client/UI/Panels/Neighborhoods/UIRatingList.cs @@ -55,7 +55,8 @@ public UIRatingList(uint avatarID) RatingList = new UIListBox() { RowHeight = 69, - Size = new Vector2(475, 352) + Size = new Vector2(475, 352), + UseChildElements = true }; RatingList.Columns.Add(new UIListBoxColumn() { Width = 475 }); RatingList.ScrollbarGutter = 5; diff --git a/TSOClient/tso.client/UI/Panels/UIAbstractCatalogMode.cs b/TSOClient/tso.client/UI/Panels/UIAbstractCatalogMode.cs index 9c5f97b24..63e875807 100644 --- a/TSOClient/tso.client/UI/Panels/UIAbstractCatalogMode.cs +++ b/TSOClient/tso.client/UI/Panels/UIAbstractCatalogMode.cs @@ -21,6 +21,7 @@ public abstract class UIAbstractCatalogPanel : UICachedContainer public UIObjectHolder Holder; public UIQueryPanel QueryPanel { get { return LotController.QueryPanel; } } public UILotControl LotController; + public bool AnyChanges { get; protected set; } protected VMMultitileGroup BuyItem; protected UILabel ObjLimitLabel; @@ -52,7 +53,7 @@ public UIAbstractCatalogPanel(string mode, UILotControl lotController) Background.Y = 0; Background.BlockInput(); this.AddAt(0, Background); - Size = Background.Size.ToVector2(); + Size = Background.Size; Catalog = new UICatalog((mode == "buildpanel") ? (useSmall ? 10 : 20) : (useSmall ? 14 : 24)); Catalog.LotControl = lotController; @@ -178,6 +179,7 @@ private void HolderPickup(UIObjectSelection holding, UpdateState state) } private void HolderPutDown(UIObjectSelection holding, UpdateState state) { + AnyChanges = true; if (OldSelection != -1) { if (!holding.IsBought && holding.InventoryPID == 0 && (state.ShiftDown)) @@ -198,6 +200,7 @@ private void HolderPutDown(UIObjectSelection holding, UpdateState state) private void HolderDelete(UIObjectSelection holding, UpdateState state) { + AnyChanges = true; if (OldSelection != -1) { Catalog.SetActive(OldSelection, false); @@ -268,6 +271,7 @@ protected virtual void Catalog_OnSelectionChange(int selection) QueryPanel.Tab = 0; QueryPanel.Active = true; } + AnyChanges = true; LotController.CustomControl = (UICustomLotControl)Activator.CreateInstance(item.Special.Control, LotController.vm, LotController.World, LotController, item.Special.Parameters); } else diff --git a/TSOClient/tso.client/UI/Panels/UIBuildMode.cs b/TSOClient/tso.client/UI/Panels/UIBuildMode.cs index 3d2b5beeb..1f70e81b4 100644 --- a/TSOClient/tso.client/UI/Panels/UIBuildMode.cs +++ b/TSOClient/tso.client/UI/Panels/UIBuildMode.cs @@ -27,6 +27,8 @@ public class UIBuildMode : UIAbstractCatalogPanel public UIButton RoofButton { get; set; } public UIButton HandButton { get; set; } + public UIButton DebugButton { get; set; } = new UIButton(); + public Texture2D subtoolsBackground { get; set; } public Texture2D dividerImage { get; set; } @@ -34,7 +36,7 @@ public class UIBuildMode : UIAbstractCatalogPanel public UIImage SubToolBg; public UISlider SubtoolsSlider { get; set; } - public UIButton PreviousPageButton { get; set; } + public UIButton PreviousPageButton { get; set; } public UIButton NextPageButton { get; set; } private UISlider RoofSlider; @@ -45,6 +47,9 @@ public class UIBuildMode : UIAbstractCatalogPanel public UIBuildMode(UILotControl lotController) : base("buildpanel", lotController) { + var gd = GameFacade.GraphicsDevice; + var ui = Content.Content.Get().CustomUI; + Divider = new UIImage(dividerImage); Divider.Position = new Vector2(337, 14); this.AddAt(1, Divider); @@ -71,7 +76,15 @@ public UIBuildMode(UILotControl lotController) : base("buildpanel", lotControlle RoofShallowBtn.X = 46; RoofShallowBtn.Y = 92; Add(RoofShallowBtn); - + + DebugButton.Texture = ui.Get("archive_cat_debug.png").Get(gd); + DebugButton.X = 43; + DebugButton.Y = 50; + DebugButton.Tooltip = GameFacade.Strings.GetString("f107", "4"); + Add(DebugButton); + + TerrainButton.Tooltip = GameFacade.Strings.GetString("f107", "1"); + RoofSlider = new UISlider(); RoofSlider.Orientation = 1; RoofSlider.Texture = GetTexture(0x4AB00000001); @@ -117,6 +130,7 @@ public override void InitCategoryMap() { WindowButton, 1 }, { RoofButton, 6 }, { HandButton, 28 }, + { DebugButton, 29 }, }; } @@ -182,7 +196,13 @@ public override void ChangeCategory(UIElement elem) public override void Update(UpdateState state) { - CategoryMap[TerrainButton] = (state.ShiftDown && (LotController?.ActiveEntity?.TSOState as VMTSOAvatarState)?.Permissions >= VMTSOAvatarPermissions.Admin) ? 29 : 10; + bool allowDebug = (LotController?.ActiveEntity?.TSOState as VMTSOAvatarState)?.Flags.HasFlag(VMTSOAvatarFlags.Debug) ?? false; + + if (DebugButton.Visible != allowDebug) + { + DebugButton.Visible = allowDebug; + } + var objCount = LotController.vm.Context.ObjectQueries.NumUserObjects; if (LastObjCount != objCount || LastDonator != LotController.ObjectHolder.DonateMode) { diff --git a/TSOClient/tso.client/UI/Panels/UIChatBalloon.cs b/TSOClient/tso.client/UI/Panels/UIChatBalloon.cs index cacc946e6..4ab663302 100644 --- a/TSOClient/tso.client/UI/Panels/UIChatBalloon.cs +++ b/TSOClient/tso.client/UI/Panels/UIChatBalloon.cs @@ -85,7 +85,7 @@ public void SetNameMessage(VMAvatar avatar) Name = avatar.Name; Message = avatar.Message; Gender = avatar.GetPersonData(SimAntics.Model.VMPersonDataVariable.Gender) > 0; - TTSContext?.Speak(Message.Replace('_', ' '), Gender, ((VMTSOAvatarState)avatar.TSOState).ChatTTSPitch); + TTSContext?.Speak(Message.Replace('_', ' '), Gender, ((VMTSOAvatarState)avatar.TSOState).ChatTTSPitch, avatar.PersistID); if (avatar.PersistID == 0) BgColor = new Color(100, 100, 100); // NPC chat color else if (((VMTSOAvatarState)avatar.TSOState).Permissions == VMTSOAvatarPermissions.Admin) @@ -344,6 +344,6 @@ public abstract class ITTSContext { public static Func Provider; public abstract void Dispose(); - public abstract void Speak(string text, bool gender, int pitch); + public abstract void Speak(string text, bool gender, int pitch, uint persistID); } } diff --git a/TSOClient/tso.client/UI/Panels/UIChatDialog.cs b/TSOClient/tso.client/UI/Panels/UIChatDialog.cs index 71ac46918..906b76282 100644 --- a/TSOClient/tso.client/UI/Panels/UIChatDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UIChatDialog.cs @@ -11,6 +11,8 @@ using FSO.SimAntics.Model.TSOPlatform; using FSO.Client.UI.Panels.Chat; using FSO.Common.Utils; +using FSO.Client.UI.Screens; +using FSO.Client.Controllers; namespace FSO.Client.UI.Panels { @@ -185,6 +187,7 @@ private void DragMouseEvents(UIMouseEventType evt, UpdateState state) private void CloseButton_OnButtonClick(UIElement button) { //hide self. + UIChatPanel.HistoryVisiblePreference = false; Visible = false; } @@ -240,7 +243,7 @@ public void ReceiveEvent(VMChatEvent evt) { var tts = GetOrCreateTTS(); var gender = avatar.GetPersonData(SimAntics.Model.VMPersonDataVariable.Gender) > 0; - tts?.Speak(evt.Text[1].Replace('_', ' '), gender, ((VMTSOAvatarState)avatar.TSOState).ChatTTSPitch); + tts?.Speak(evt.Text[1].Replace('_', ' '), gender, ((VMTSOAvatarState)avatar.TSOState).ChatTTSPitch, avatar.PersistID); } } } @@ -371,6 +374,18 @@ public string CleanUserMessage(string msg, VMChatEvent evt) return sanitary; } + private string GetAvatarExtra(uint senderUID) + { + if (senderUID == 0) + { + return ""; + } + + var username = FindController()?.TryGetUsername(senderUID); + + return username != null ? $" ({SanitizeBB(username)})" : ""; + } + public string RenderEvent(VMChatEvent evt) { var colorBefore = "[color=lightgray]"; @@ -378,7 +393,7 @@ public string RenderEvent(VMChatEvent evt) var colorAfter = "[/s][/color]"; var timestamp = evt.Timestamp; var showTimestamp = GlobalSettings.Default.ChatShowTimestamp; - var avatar = avatarColor + evt.Text[0] + colorAfter; //avatar names cannot normally contain bbcode + var avatar = avatarColor + evt.Text[0] + GetAvatarExtra(evt.SenderUID) + colorAfter; //avatar names cannot normally contain bbcode switch (evt.Type) { case VMChatEventType.Message: diff --git a/TSOClient/tso.client/UI/Panels/UIChatPanel.cs b/TSOClient/tso.client/UI/Panels/UIChatPanel.cs index 0be9001f4..51f465555 100644 --- a/TSOClient/tso.client/UI/Panels/UIChatPanel.cs +++ b/TSOClient/tso.client/UI/Panels/UIChatPanel.cs @@ -34,6 +34,8 @@ public Color SelectionFillColor } } + public static bool HistoryVisiblePreference = true; + public List Labels; public List InvalidAreas; private UILotControl Owner; @@ -95,7 +97,7 @@ public UIChatPanel(VM vm, UILotControl owner) HistoryDialog = new UIChatDialog(owner); HistoryDialog.Position = new Vector2(GlobalSettings.Default.ChatLocationX, GlobalSettings.Default.ChatLocationY); - HistoryDialog.Visible = true; + HistoryDialog.Visible = HistoryVisiblePreference; HistoryDialog.Opacity = 0.8f; HistoryDialog.OnSendMessage += SendMessage; this.Add(HistoryDialog); @@ -317,6 +319,7 @@ public override void Update(UpdateState state) if (state.NewKeys.Contains(Keys.H) && state.CtrlDown) { HistoryDialog.Visible = !HistoryDialog.Visible; + HistoryVisiblePreference = HistoryDialog.Visible; if (HistoryDialog.Visible) state.InputManager.SetFocus(HistoryDialog.ChatEntryTextEdit); else state.InputManager.SetFocus(null); } @@ -368,7 +371,7 @@ public void ReceiveEvent(VMChatEvent evt) public void SetLotName(string name) { - HistoryDialog.LotName = name; + HistoryDialog.LotName = GameFacade.Strings.TransformLotName(name); HistoryDialog.RenderTitle(); } } diff --git a/TSOClient/tso.client/UI/Panels/UIContextMenu.cs b/TSOClient/tso.client/UI/Panels/UIContextMenu.cs new file mode 100644 index 000000000..c0c4dc17b --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/UIContextMenu.cs @@ -0,0 +1,215 @@ +using FSO.Client.UI.Framework; +using FSO.Client.UI.Model; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace FSO.Client.UI.Panels +{ + public class UIContextMenu : UIContainer, IFocusableUI + { + public bool IsFocused { get; set; } + public int TabIndex { get; set; } = -1; + public UIElement Watching; + public string LastSearch; + private int Height; + private int Width = 200; + + public UIContextMenu(UIElement anchor, IEnumerable items, UIContainer parent = null) + { + Watching = anchor; + + int length = items.Count(); + Width = length == 0 ? 200 : items.Max(item => item.PreferredWidth); + + int i = 0; + + foreach (var item in items) + { + item.Width = Width; + item.Y = (i++) * 22; + + if (i == length) + { + item.Last = true; + } + + Add(item); + } + + Height = length * 22; + + if (parent is UICachedContainer cached) + { + cached.DynamicOverlay.Add(this); + } + else + { + (parent ?? anchor.Parent).Add(this); + } + + GameFacade.Screens.inputManager.SetFocus(this); + } + + private ButtonState _lastPressed; + + public override void Update(UpdateState state) + { + int xPos = Parent.LocalPoint(Watching.Position).X + Width > UIScreen.Current.ScreenWidth ? + ((int)Watching.Size.X - Width) : + 0; + + Position = Watching.Position + new Vector2(xPos, Watching.Size.Y); + base.Update(state); + + // if the mouse was pressed outside the context menu, instantly close it. + + ButtonState pressed = state.MouseState.LeftButton; + + if (pressed == ButtonState.Pressed && _lastPressed == ButtonState.Released) + { + var point = GlobalPoint(state.MouseState.Position.ToVector2()); + + if (point.X < 0 || point.Y < 0 || point.X > Width || point.Y > Height) + { + Close(); + return; + } + } + + _lastPressed = pressed; + + if (Visible) + { + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Down)) + MoveSelection(1); + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Up)) + MoveSelection(-1); + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Enter)) + Select(); + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Escape)) + Close(); + } + } + + public bool Select() + { + var bestOption = (UIContextMenuItem)Children.FirstOrDefault(x => ((UIContextMenuItem)x).Selected); + if (bestOption != null) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.Click); + bestOption.OnSelect?.Invoke(); + Close(); + return true; + } + + return false; + } + + public void MoveSelection(int off) + { + var i = Children.FindIndex(x => ((UIContextMenuItem)x).Selected); + var ni = i + off; + if (ni >= Children.Count || ni < 0) return; + if (i != -1) + { + ((UIContextMenuItem)Children[i]).Selected = false; + } + ((UIContextMenuItem)Children[ni]).Selected = true; + } + + public void ClearSelection() + { + foreach (UIContextMenuItem child in Children) + child.Selected = false; + } + + public void Close() + { + if (Parent != null) + { + if (Parent is UICachedContainer cached) + { + cached.DynamicOverlay.Remove(this); + } + else + { + Parent.Remove(this); + } + + Parent = null; + } + } + + public void OnFocusChanged(FocusEvent newFocus) + { + // If we lose focus, close the context menu. + + if (newFocus == FocusEvent.FocusOut && Parent != null) + { + Close(); + } + } + } + + public class UIContextMenuItem : UIElement + { + public Texture2D PxWhite; + public bool Last; + public bool Selected; + public string Caption; + public TextStyle Style; + public UIMouseEventRef ClickHandler; + public int PreferredWidth; + + public int Width; + public Action OnSelect; + + public UIContextMenuItem(string caption, Action onSelect) + { + PxWhite = TextureGenerator.GetPxWhite(GameFacade.GraphicsDevice); + Style = TextStyle.DefaultLabel.Clone(); + Style.Size = 8; + Caption = caption; + OnSelect = onSelect; + + ClickHandler = + ListenForMouse(new Rectangle(0, 0, 200, 22), new UIMouseEvent(MouseEvent)); + + PreferredWidth = (int)Style.MeasureString(caption).X + 26; + Width = PreferredWidth; + } + + public void MouseEvent(UIMouseEventType type, UpdateState state) + { + var owner = Parent as UIContextMenu; + + switch (type) + { + case UIMouseEventType.MouseOver: + owner.ClearSelection(); + Selected = true; + break; + case UIMouseEventType.MouseDown: + HIT.HITVM.Get().PlaySoundEvent(UISounds.Click); + OnSelect?.Invoke(); + owner.Close(); + break; + } + } + + public override void Draw(UISpriteBatch batch) + { + DrawLocalTexture(batch, PxWhite, null, Vector2.Zero, new Vector2(Width, 22), new Color(57, 85, 117)); + if (Selected) DrawLocalTexture(batch, PxWhite, null, Vector2.Zero, new Vector2(Width, 22), Color.White * 0.25f); + DrawLocalString(batch, Caption, new Vector2(3, 3), Style); + if (!Last) DrawLocalTexture(batch, PxWhite, null, new Vector2(0, 21), new Vector2(Width, 1), Color.White * 0.5f); + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/UICreditsPanel.cs b/TSOClient/tso.client/UI/Panels/UICreditsPanel.cs new file mode 100644 index 000000000..9ddb842c8 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/UICreditsPanel.cs @@ -0,0 +1,523 @@ +using FSO.Client.UI.Framework; +using FSO.Client.UI.Framework.Parser; +using FSO.Common; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.Content; +using FSO.Files.Formats.IFF.Chunks; +using FSO.Files.RC; +using Microsoft.Xna.Framework; +using System.Globalization; + +namespace FSO.Client.UI.Panels +{ + public class UICreditsPanel : UIElement + { + private struct CreditsNewLine + { + public int LineHeight; + public int FontSize; + public Color FontColor; + + public CreditsNewLine(string input) + { + // After NewLine| + var split = input[8..].Split('|'); + + LineHeight = int.Parse(split[0]); + FontSize = int.Parse(split[1]); + + var colorSplit = split[2].Split(','); + + FontColor = new Color(byte.Parse(colorSplit[0]), byte.Parse(colorSplit[1]), byte.Parse(colorSplit[2]), (byte)255); + } + } + + private enum CreditsAlignment + { + Left, + Center, + Right, + } + + private struct CreditsLineEntry + { + public CreditsAlignment Alignment; + public int NumericAlignment; + public string Text; + public Color? UnderlineColor; + + public CreditsLineEntry(string input) + { + // After LineEntry| + var split = input[10..].Split('|'); + + if (int.TryParse(split[0], out NumericAlignment)) + { + Alignment = CreditsAlignment.Left; + } + else + { + Alignment = split[0] switch + { + "Left" => CreditsAlignment.Left, + "Right" => CreditsAlignment.Right, + _ => CreditsAlignment.Center + }; + } + + Text = split[1]; + + if (split.Length > 2) + { + var colorSplit = split[2].Split(','); + UnderlineColor = new Color(byte.Parse(colorSplit[0]), byte.Parse(colorSplit[1]), byte.Parse(colorSplit[2]), (byte)255); + } + } + } + + private struct CreditsBlock + { + public CreditsNewLine LineInfo; + public List Entries; + public int Y; + } + + [UIAttribute("size")] + public override Vector2 Size { get; set; } + + private readonly TextStyle BaseStyle; + private List Blocks; + private float ScrollSpeed = 21; //pixels per second + private float ActiveScroll; + private FSO3DCredits[] RemeshCredits; + private Dictionary> ObjectsByFilename; + + public UICreditsPanel() + { + RemeshCredits = Content.Content.Get().RCMeshes.Packages.GetCredits(); + BaseStyle = TextStyle.DefaultLabel.Clone(); + } + + public void Init(bool fso) + { + ScrollSpeed = 21; + ActiveScroll = 0; + + Blocks = BuildBlocks(fso ? FreeSOCredits() : MaxisCredits()); + } + + private Dictionary> EnsureObjectsByFilename() + { + if (ObjectsByFilename == null) + { + var objProvider = Content.Content.Get().WorldObjects; + + var byFilename = new Dictionary>(); + + foreach (var item in objProvider.Entries) + { + var filename = Path.GetFileNameWithoutExtension(item.Value.FileName); + + if (!byFilename.TryGetValue(filename, out var list)) + { + list = []; + byFilename.Add(filename, list); + } + + list.Add(item.Value); + } + + ObjectsByFilename = byFilename; + } + + return ObjectsByFilename; + } + + private IEnumerable RemeshPackageCredits() + { + TextStyle measure = BaseStyle.Clone(); + measure.Size = 10; + var maxCreditWidth = 210; + + var smallNames = new List(2); + var largeNames = new List(2); + + foreach (var package in RemeshCredits) + { + yield return "NewLine|25|13|247,232,145"; + yield return $"LineEntry|Center|{package.Metadata.Name.ToUpper()}|247,232,145"; + yield return "NewLine|5|7|180,210,226"; + + foreach (var author in package.Authors) + { + yield return "NewLine|25|12|210,240,250"; + yield return $"LineEntry|Center|{author.Metadata.Name}|210,240,250"; + yield return "NewLine|5|7|180,210,226"; + + var groups = author.Groups; + + foreach (var group in author.Groups) + { + var name = group.Metadata.Name; + var width = measure.MeasureString(name).X; + + if (width > maxCreditWidth) + { + largeNames.Add(name); + } + else + { + smallNames.Add(name); + } + + if (smallNames.Count == 2) + { + yield return "NewLine|25|10|180,210,226"; + yield return $"LineEntry|Left|{smallNames[0]}"; + yield return $"LineEntry|Right|{smallNames[1]}"; + + smallNames.Clear(); + + foreach (var largeName in largeNames) + { + yield return "NewLine|25|10|180,210,226"; + yield return $"LineEntry|Center|{largeName}"; + } + + largeNames.Clear(); + } + } + + largeNames.AddRange(smallNames); + smallNames.Clear(); + + foreach (var largeName in largeNames) + { + yield return "NewLine|25|10|180,210,226"; + yield return $"LineEntry|Center|{largeName}"; + } + + largeNames.Clear(); + + yield return "NewLine|10|10|180,210,226"; + } + } + + yield break; + } + + private IEnumerable ObjectFileCredits(string arguments) + { + var split = arguments.Split('|'); + + if (split.Length < 2) + { + yield break; + } + + var filename = split[1]; + var iffs = EnsureObjectsByFilename(); + + TextStyle measure = BaseStyle.Clone(); + measure.Size = 10; + var maxCreditWidth = ((int)Size.X) - 20; + + bool printedFilename = false; + + float filenameWidth = measure.MeasureString(filename).X; + + // List the objects belonging to this iff file. + if (filename.EndsWith(".iff") && iffs.TryGetValue(filename[..^4], out var iffObjs)) + { + HashSet guidWhitelist = null; + if (split.Length >= 3) + { + var whitelistStr = split[2]; + if (whitelistStr != "") + { + guidWhitelist = []; + if (whitelistStr != "x") + { + var whitelistSplit = whitelistStr.Split(","); + + foreach (var item in whitelistSplit) + { + if (uint.TryParse(item, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out uint guid)) + { + guidWhitelist.Add(guid); + } + } + } + } + } + + HashSet seenNames = []; + + foreach (var obj in iffObjs) + { + if ((obj.SubIndex != -1 && obj.Group != 0) || (guidWhitelist != null && !guidWhitelist.Contains((uint)obj.ID))) + { + continue; + } + + // Try to get the object's CTSS. + + var res = obj.Get(); + + var ctss = res.Resource.Get(res.OBJ.CatalogStringsID); + string name = ctss?.GetString(0); + + if (string.IsNullOrEmpty(name) || seenNames.Contains(name)) + { + continue; + } + + seenNames.Add(name); + + if (!printedFilename) + { + bool tooBig = measure.MeasureString(name).X + filenameWidth > maxCreditWidth; + yield return $"NewLine|{(tooBig ? "18" : "0")}|10|210,240,250"; + yield return $"LineEntry|Left|{filename}"; + printedFilename = true; + } + + yield return "NewLine|18|10|180,210,226"; + yield return $"LineEntry|Right|{name}"; + } + } + + if (split.Length >= 4) + { + // Extra items for this iff + var extraStr = split[3]; + var extraSplit = extraStr.Split(","); + + foreach (var extra in extraSplit) + { + if (!printedFilename) + { + bool tooBig = measure.MeasureString(extra).X + filenameWidth > maxCreditWidth; + yield return $"NewLine|{(tooBig ? "18" : "0")}|10|210,240,250"; + yield return $"LineEntry|Left|{filename}"; + printedFilename = true; + } + + yield return "NewLine|18|10|180,210,226"; + yield return $"LineEntry|Right|{extra}"; + } + } + + yield return "NewLine|8|10|180,210,226"; + } + + private IEnumerable CSTCredits(string cst) + { + int index = 1; + var strings = GameFacade.Strings; + + bool hasValue = true; + do + { + string message = strings.GetString(cst, index.ToString()); + + index++; + + if (!string.IsNullOrEmpty(message)) + { + if (message == "RemeshPackage") + { + foreach (var line in RemeshPackageCredits()) + { + yield return line; + } + } + else if (message.StartsWith("ObjectFile|")) + { + foreach (var line in ObjectFileCredits(message)) + { + yield return line; + } + } + else + { + yield return message; + } + } + else + { + hasValue = false; + } + } + while (hasValue); + + yield break; + } + + private IEnumerable MaxisCredits() + { + return CSTCredits("242"); + } + + private IEnumerable FreeSOCredits() + { + return CSTCredits("f200"); + } + + private List BuildBlocks(IEnumerable nextLine) + { + var blocks = new List(); + List entries = []; + CreditsNewLine? activeLine = null; + int yTotal = 0; + + foreach (var line in nextLine) + { + if (line.StartsWith("NewLine|")) + { + if (activeLine != null) + { + var toAdd = activeLine.Value; + blocks.Add(new CreditsBlock() + { + LineInfo = toAdd, + Entries = entries, + Y = yTotal + }); + + entries = []; + yTotal += toAdd.LineHeight; + } + + activeLine = new CreditsNewLine(line); + } + else if (line.StartsWith("LineEntry|")) + { + var entry = new CreditsLineEntry(line); + + entries.Add(entry); + } + } + + return blocks; + } + + public override void Update(UpdateState state) + { + if (state.MouseState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed) + { + ScrollSpeed = Math.Min(400, ScrollSpeed + (100f / FSOEnvironment.RefreshRate)); + } + else + { + ScrollSpeed = Math.Max(21, ScrollSpeed - (300f / FSOEnvironment.RefreshRate)); + } + + float scrollPerUpdate = ScrollSpeed / FSOEnvironment.RefreshRate; + + ActiveScroll += scrollPerUpdate; + var lastBlock = Blocks.LastOrDefault(); + + // Prepare drawing for any items that are onscreen + float areaHeight = Size.Y; + float scrollHeight = lastBlock.Y + lastBlock.LineInfo.LineHeight + areaHeight; + + if (ActiveScroll > scrollHeight) + { + ActiveScroll -= scrollHeight; + } + + /* + int i = 0; + foreach (var block in Blocks) + { + float top = block.Y + areaHeight - ActiveScroll; + float bottom = top + block.LineInfo.LineHeight; + + if (top > areaHeight) + { + break; + } + + if (bottom > 0) + { + // Ensure this credits item can be drawn + } + + i++; + } + */ + + base.Update(state); + } + + public override void Draw(UISpriteBatch SBatch) + { + float areaWidth = Size.X; + float areaHeight = Size.Y; + var whitePx = TextureGenerator.GetPxWhite(SBatch.GraphicsDevice); + var style = BaseStyle; + + float edgeMargin = 10; + + int i = 0; + foreach (var block in Blocks) + { + i++; + float top = block.Y + areaHeight - ActiveScroll; + float bottom = top + block.LineInfo.LineHeight; + + if (top > areaHeight) + { + break; + } + + if (bottom > 0) + { + // Draw this item + var opacityHeight = block.LineInfo.LineHeight; + + if (opacityHeight == 0) + { + var nextIndex = Blocks.FindIndex(i, (x) => x.LineInfo.LineHeight != 0); + + if (nextIndex != -1) + { + opacityHeight = Blocks[nextIndex].LineInfo.LineHeight; + bottom = top + opacityHeight; + } + } + + float edgeDist = Math.Min(Math.Max(top - opacityHeight, 0), Math.Max(areaHeight - bottom, 0)); + float opacity = Math.Clamp(edgeDist / edgeMargin, 0, 1); + + style.Color = block.LineInfo.FontColor * opacity; + style.Size = block.LineInfo.FontSize; + + foreach (var entry in block.Entries) + { + float x = 0; + var entrySize = style.MeasureString(entry.Text); + + if (entry.Alignment != CreditsAlignment.Left) + { + x = entry.Alignment switch + { + CreditsAlignment.Right => areaWidth - entrySize.X, + _ => (areaWidth - entrySize.X) / 2, + }; + } + + DrawLocalString(SBatch, entry.Text, new Vector2(x, top), style); + + if (entry.UnderlineColor != null) + { + DrawLocalTexture(SBatch, whitePx, null, new Vector2(x, top + entrySize.Y), new Vector2(entrySize.X, 1), style.Color); + } + } + } + } + } + } +} diff --git a/TSOClient/tso.client/UI/Panels/UIDebugMenu.cs b/TSOClient/tso.client/UI/Panels/UIDebugMenu.cs index aa2c195f5..cf9242545 100644 --- a/TSOClient/tso.client/UI/Panels/UIDebugMenu.cs +++ b/TSOClient/tso.client/UI/Panels/UIDebugMenu.cs @@ -17,7 +17,7 @@ public class UIDebugMenu : UIDialog public UIDebugMenu() : base(UIDialogStyle.Tall, true) { - SetSize(500, 340); + SetSize(500, 320); Caption = "Debug Tools"; Position = new Microsoft.Xna.Framework.Vector2( @@ -47,43 +47,44 @@ public UIDebugMenu() : base(UIDialogStyle.Tall, true) }; Add(ContentBrowserBtn); - var connectLocalBtn = new UIButton(); - connectLocalBtn.Caption = (GlobalSettings.Default.UseCustomServer) ? "Use default server (TSO)" : "Use custom defined server"; - connectLocalBtn.Position = new Microsoft.Xna.Framework.Vector2(160, 90); - connectLocalBtn.Width = 300; - connectLocalBtn.OnButtonClick += x => - { - GlobalSettings.Default.UseCustomServer = !GlobalSettings.Default.UseCustomServer; - connectLocalBtn.Caption = (GlobalSettings.Default.UseCustomServer) ? "Use default server (TSO)" : "Use custom defined server"; - GlobalSettings.Default.Save(); - }; - Add(connectLocalBtn); - var cityPainterBtn = new UIButton(); - cityPainterBtn.Caption = "City Painter"; - cityPainterBtn.Position = new Microsoft.Xna.Framework.Vector2(160, 130); - cityPainterBtn.Width = 150; + cityPainterBtn.Caption = "Trigger hollow.fsoh regeneration"; + cityPainterBtn.Position = new Microsoft.Xna.Framework.Vector2(160, 90); + cityPainterBtn.Width = 300; cityPainterBtn.OnButtonClick += x => { var core = (GameFacade.Screens.CurrentUIScreen as CoreGameScreen); if (core == null) return; - if (core.CityRenderer.Plugin == null) + + var controller = core.FindController(); + + if (controller == null) return; + + if (controller.ModerationLevel < 3) { - core.CityRenderer.Plugin = new Rendering.City.Plugins.MapPainterPlugin(core.CityRenderer); - cityPainterBtn.Caption = "Disable Painter"; + UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Message = "You must be super admin to run this command." + }, true); + return; } - else + + UIAlert.YesNo("Lot cleanup", "Do you want to fully re-save lots that have been moved (yes), or just update all lots (no)?", true, (answer) => { - core.CityRenderer.Plugin = null; - cityPainterBtn.Caption = "City Painter"; - } + controller.RegenerateHollowLots(answer); + + UIScreen.GlobalShowAlert(new UIAlertOptions() + { + Message = "Regenerating hollow lots - this might take some time. Check the logs for the current progress." + }, true); + }); }; Add(cityPainterBtn); var ngbhBtn = new UIButton(); ngbhBtn.Caption = "Ngbh Editor"; - ngbhBtn.Position = new Microsoft.Xna.Framework.Vector2(160+150, 130); - ngbhBtn.Width = 150; + ngbhBtn.Position = new Microsoft.Xna.Framework.Vector2(160, 130); + ngbhBtn.Width = 300; ngbhBtn.OnButtonClick += x => { var core = (GameFacade.Screens.CurrentUIScreen as CoreGameScreen); @@ -96,7 +97,7 @@ public UIDebugMenu() : base(UIDialogStyle.Tall, true) else { core.CityRenderer.Plugin = null; - ngbhBtn.Caption = "Ngbh Editor"; + ngbhBtn.Caption = "Neighborhood Editor (local)"; } }; Add(ngbhBtn); @@ -199,37 +200,17 @@ public UIDebugMenu() : base(UIDialogStyle.Tall, true) }, true); }; Add(saveUpgradesBtn); - - serverNameBox = new UITextBox(); - serverNameBox.X = 50; - serverNameBox.Y = 340 - 54; - serverNameBox.SetSize(500 - 100, 25); - serverNameBox.CurrentText = GlobalSettings.Default.GameEntryUrl; - - Add(serverNameBox); } - private UITextBox serverNameBox; public override void Update(UpdateState state) { base.Update(state); if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.M)) { - //temporary until data service can inform people they're mod - //now i know what you're thinking - but these requests are permission checked server side anyways + // Enables client-side permissions overrides. (similar to move-objects) + // Now I know what you're thinking - but these requests are permission checked server side anyways. GameFacade.EnableMod = true; } - - if (serverNameBox.CurrentText != GlobalSettings.Default.GameEntryUrl) - { - GlobalSettings.Default.GameEntryUrl = serverNameBox.CurrentText; - GlobalSettings.Default.CitySelectorUrl = serverNameBox.CurrentText; - var auth = FSOFacade.Kernel.Get(); - auth.SetBaseUrl(serverNameBox.CurrentText); - var city = FSOFacade.Kernel.Get(); - city.SetBaseUrl(serverNameBox.CurrentText); - GlobalSettings.Default.Save(); - } } } } diff --git a/TSOClient/tso.client/UI/Panels/UIEnvPanel.cs b/TSOClient/tso.client/UI/Panels/UIEnvPanel.cs index 923918261..f42dde0a8 100644 --- a/TSOClient/tso.client/UI/Panels/UIEnvPanel.cs +++ b/TSOClient/tso.client/UI/Panels/UIEnvPanel.cs @@ -32,6 +32,15 @@ public UIEnvPanel(UILotControl lotController) Divider.Texture = DividerImage; Add(Divider); + // FreeSO does implement this, but as part of each individual light rather than a global control. + LightColorsButton.Visible = false; + + // This was never implemented in TSO. It also doesn't fit in FreeSO with the more integrated city surroundings. + // It could be useful for a "sandbox" type lot, or of the offbeat lot type were allowed to break established rules. + TimeOfDayButton.Visible = false; + + SoundsButton.Position = LightColorsButton.Position; + BtnToMode = new Dictionary() { { LightColorsButton, 0 }, @@ -298,7 +307,7 @@ public UISoundsPanel(UILotControl lotController) item.CaptionStyle = item.CaptionStyle.Clone(); item.CaptionStyle.Shadow = true; } - var noPermission = (!lotController.vm.TSOState.BuildRoommates.Contains(lotController.vm.MyUID) && lotController.vm.TSOState.OwnerID != lotController.vm.MyUID); + var noPermission = (!lotController.vm.TSOState.BuildRoommates.Contains(lotController.vm.MyUID) && lotController.vm.TSOState.OwnerID != lotController.vm.MyUID && lotController.vm.TSOState.OwnerID != 0); var j = 0; foreach (var item in CheckButtons) { @@ -404,7 +413,7 @@ public void SetPage(int page) var name = col[i]; var snd = amb.GetAmbienceFromName(name); - if (snd != null) ActiveBtns[j].Selected = amb.ActiveSounds.ContainsKey(amb.GetAmbienceFromGUID(snd.Value.GUID)); + if (snd != null) ActiveBtns[j].Selected = ((ulong)amb.UserBits & (1ul << (int)amb.GetAmbienceFromGUID(snd.Value.GUID))) != 0; } j++; } diff --git a/TSOClient/tso.client/UI/Panels/UIExitDialog.cs b/TSOClient/tso.client/UI/Panels/UIExitDialog.cs index a4024d592..52ce1f919 100644 --- a/TSOClient/tso.client/UI/Panels/UIExitDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UIExitDialog.cs @@ -25,7 +25,10 @@ public UIExitDialog() private void ExitButton_OnButtonClick(UIElement button) { - GameFacade.Kill(); + if (FSOFacade.Controller.CloseAttempt()) + { + GameFacade.Kill(); + } } private void CancelButton_OnButtonClick(UIElement button) diff --git a/TSOClient/tso.client/UI/Panels/UIGameTitle.cs b/TSOClient/tso.client/UI/Panels/UIGameTitle.cs index 1e4c4a736..22c8e5cbe 100644 --- a/TSOClient/tso.client/UI/Panels/UIGameTitle.cs +++ b/TSOClient/tso.client/UI/Panels/UIGameTitle.cs @@ -2,6 +2,8 @@ using FSO.Client.UI.Framework; using FSO.Client.UI.Controls; using Microsoft.Xna.Framework; +using FSO.Client.UI.Archive; +using FSO.Client.Controllers; namespace FSO.Client.UI.Panels { @@ -12,9 +14,12 @@ public class UIGameTitle : UICachedContainer public UIImage Background; public UILabel Label; public UIButton CancelButton; + public UIButton InfoButton; private string Title; private Tuple OverrideMode; + private bool ShowInfo; + private bool InfoOpen; public UIGameTitle() { @@ -44,9 +49,37 @@ public UIGameTitle() CancelButton.Y = 2; Add(CancelButton); + InfoButton = new UIButton(btnTex) + { + Caption = "i", + CaptionStyle = btnCaption, + Tooltip = GameFacade.Strings.GetString("f128", "19"), + Width = 20, + Y = 2, + Visible = false + }; + InfoButton.OnButtonClick += ShowServerInfo; + Add(InfoButton); + SetTitle("Not Blazing Falls"); } + private void ShowServerInfo(UIElement button) + { + if (!InfoOpen) + { + InfoOpen = true; + var info = new UIArchiveHostInformation(FindController()); + info.CloseButton.OnButtonClick += (elem) => + { + InfoOpen = false; + UIScreen.RemoveDialog(info); + }; + + UIScreen.ShowDialog(info, false); + } + } + private void CancelOverride(UIElement button) { OverrideMode?.Item2?.Invoke(); @@ -78,6 +111,8 @@ public void SetOverrideMode(string title, Action callback) CancelButton.Y = 2; CancelButton.Width = 64; + InfoButton.Visible = false; + OverrideMode = new Tuple(title, callback); } @@ -89,7 +124,9 @@ public void ClearOverrideMode() public void SetTitle(string title) { + ShowInfo = FindController()?.Mode == Regulators.CityConnectionMode.ARCHIVE; Title = title; + if (OverrideMode == null) SetNormalTitle(Title); } @@ -99,9 +136,11 @@ private void SetNormalTitle(string title) var style = Label.CaptionStyle; - var width = style.MeasureString(title).X; + var twidth = style.MeasureString(title).X; var ScreenWidth = GlobalSettings.Default.GraphicsWidth/2; + var width = ShowInfo ? twidth + 28 : twidth; + X = ScreenWidth - (width / 2 + 40); Background.X = 0; Background.SetSize(width + 80, 24); @@ -110,6 +149,12 @@ private void SetNormalTitle(string title) Label.X = 40; Label.Size = new Vector2(width, 20); + InfoButton.Visible = ShowInfo; + if (ShowInfo) + { + InfoButton.X = twidth + 48; + } + CancelButton.Visible = false; } } diff --git a/TSOClient/tso.client/UI/Panels/UIGizmo.cs b/TSOClient/tso.client/UI/Panels/UIGizmo.cs index 8604d203a..28f80e7db 100644 --- a/TSOClient/tso.client/UI/Panels/UIGizmo.cs +++ b/TSOClient/tso.client/UI/Panels/UIGizmo.cs @@ -496,21 +496,6 @@ public ImmutableList FilterList { } } - private bool ShownWelcome; - public uint SimAge - { - set - { - if (value < 14 && !ShownWelcome) - { - ShownWelcome = true; - GameThread.NextUpdate(e => { - FiltersProperty.FilterClicked(FiltersProperty.GetChildren().FirstOrDefault(x => (x.ID?.IndexOf("Welcome") ?? -1) > -1)); - }); - } - } - } - private List Btns = new List(); public void RegisterFilters() { @@ -638,7 +623,6 @@ public UIGizmo() .WithBinding(PIP, "SimBox.Avatar.BodyOutfitId", "Avatar_Appearance.AvatarAppearance_BodyOutfitID") .WithBinding(PIP, "SimBox.Avatar.HeadOutfitId", "Avatar_Appearance.AvatarAppearance_HeadOutfitID") .WithBinding(PIP, "SimBox.Avatar.Appearance", "Avatar_Appearance.AvatarAppearance_SkinTone", (x) => (Vitaboy.AppearanceType)((byte)x)) - .WithBinding(this, "SimAge", "Avatar_Age") .WithBinding(this, "FilterList", "Avatar_Top100ListFilter.Top100ListFilter_ResultsVec"); Tab = UIGizmoTab.Property; diff --git a/TSOClient/tso.client/UI/Panels/UIGraphicsOptionsDialog.cs b/TSOClient/tso.client/UI/Panels/UIGraphicsOptionsDialog.cs index dfe6a4b40..0e376e873 100644 --- a/TSOClient/tso.client/UI/Panels/UIGraphicsOptionsDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UIGraphicsOptionsDialog.cs @@ -418,7 +418,7 @@ private void SettingsChanged() vm.Context.World.ChangedWorldConfig(GameFacade.GraphicsDevice); if (oldSurrounding != settings.SurroundingLotMode) { - SimAntics.Utils.VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj); + SimAntics.Utils.VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj, true); } } } diff --git a/TSOClient/tso.client/UI/Panels/UIHouseMode.cs b/TSOClient/tso.client/UI/Panels/UIHouseMode.cs index daeef510d..4fd8c85a8 100644 --- a/TSOClient/tso.client/UI/Panels/UIHouseMode.cs +++ b/TSOClient/tso.client/UI/Panels/UIHouseMode.cs @@ -1,23 +1,20 @@ -using FSO.Client.UI.Controls; +using FSO.Client.Controllers; +using FSO.Client.Controllers.Panels; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; +using FSO.Client.UI.Model; +using FSO.Client.UI.Screens; +using FSO.Client.Utils; +using FSO.Common.DataService.Model; +using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; +using FSO.HIT; using FSO.SimAntics.Model; +using FSO.SimAntics.Model.TSOPlatform; +using FSO.SimAntics.NetPlay.Model.Commands; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using FSO.Common.Rendering.Framework.Model; -using FSO.SimAntics.NetPlay.Model.Commands; -using FSO.HIT; -using FSO.Client.UI.Model; -using FSO.SimAntics.Model.TSOPlatform; -using FSO.Client.Utils; -using FSO.Client.Controllers.Panels; -using FSO.Client.Controllers; -using FSO.Common.DataService.Model; -using FSO.Client.UI.Screens; namespace FSO.Client.UI.Panels { @@ -57,7 +54,7 @@ public UIHouseMode(UILotControl lotController) Background.BlockInput(); this.AddAt(0, Background); - Size = Background.Size.ToVector2()+new Vector2(0, 9); + Size = Background.Size + new Vector2(0, 9); Divider = script.Create("Divider"); Divider.Texture = DividerImage; @@ -93,7 +90,7 @@ private void SetMode(Framework.UIElement button) var controller = GameFacade.Screens.CurrentUIScreen.FindController(); if (controller != null) { - controller.ShowLotPage(controller.GetCurrentLotID()); + controller.ShowLotPage(LotControl.vm.TSOState?.LotID ?? 0); return; } } @@ -187,7 +184,7 @@ public UIStatsPanel(UILotControl lotController) this.RenderScript("statisticspanel.uis"); } } - + /// /// Set roommate build permissions. Check buttons disabled as anything but owner. /// @@ -214,7 +211,7 @@ public UIRoommatesPanel(UILotControl lotController) TitleLabel.Y -= 8; var buildico = new UIImage(BuildIconImage); - buildico.Position = new Vector2(30-18, 30+34+8); //to the left of all the checkboxes + buildico.Position = new Vector2(30 - 18, 30 + 34 + 8); //to the left of all the checkboxes buildico.Tooltip = GameFacade.Strings.GetString("178", "2"); UIUtils.GiveTooltip(buildico); Add(buildico); @@ -255,7 +252,7 @@ public class UIRoommateCheckList : UIContainer { private List RoommateButtons = new List(); private List CheckButtons = new List(); - public event Callback OnCheckChange; + public event Callback OnCheckChange; public bool Disabled = false; public UIRoommateCheckList() : base() { @@ -293,8 +290,8 @@ public void UpdateList(HashSet roommates, HashSet buildRoommates) if (RoommateButtons[i].AvatarId != id) RoommateButtons[i].AvatarId = id; var builder = buildRoommates.Contains(id); - CheckButtons[i].ForceState = (Disabled) ? (builder ? 5 : 4) : (builder?3:-1); - CheckButtons[i].Tooltip = GameFacade.Strings.GetString("178", builder?"3":"4"); + CheckButtons[i].ForceState = (Disabled) ? (builder ? 5 : 4) : (builder ? 3 : -1); + CheckButtons[i].Tooltip = GameFacade.Strings.GetString("178", builder ? "3" : "4"); } } } @@ -380,7 +377,7 @@ public UIAdmitBanPanel(UILotControl lotController) cg.PersonPage.FindController()?.Show(id); } }; - + if (lotController.vm.TSOState.OwnerID != lotController.vm.MyUID) { AdmitAllButton.Disabled = true; @@ -392,10 +389,10 @@ public UIAdmitBanPanel(UILotControl lotController) public void ChangePage(int delta) { - if (delta != 0) AdmitList.SetPage(Math.Max(0, Math.Min(AdmitList.Page + delta, AdmitList.TotalPages-1))); + if (delta != 0) AdmitList.SetPage(Math.Max(0, Math.Min(AdmitList.Page + delta, AdmitList.TotalPages - 1))); PreviousPageButton.Disabled = (AdmitList.Page == 0); - NextPageButton.Disabled = (AdmitList.Page == AdmitList.TotalPages-1); + NextPageButton.Disabled = (AdmitList.Page == AdmitList.TotalPages - 1); } public void SetResults(List avas) @@ -503,12 +500,12 @@ public void SetPage(int page) if (page >= TotalPages || page < 0) return; List1.SelectedIndex = -1; List2.SelectedIndex = -1; - var sublist1 = Data.GetRange(page*8, Math.Min(4,Data.Count-page*8)); + var sublist1 = Data.GetRange(page * 8, Math.Min(4, Data.Count - page * 8)); List1.Items = sublist1.ConvertAll(x => new UIListBoxItem(x, new ValuePointer(x, "Avatar_Name"))); if (page * 8 + 4 < Data.Count) { - var sublist2 = Data.GetRange(page * 8 + 4, Math.Min(4, Data.Count - (page * 8+4))); + var sublist2 = Data.GetRange(page * 8 + 4, Math.Min(4, Data.Count - (page * 8 + 4))); List2.Items = sublist2.ConvertAll(x => new UIListBoxItem(x, new ValuePointer(x, "Avatar_Name"))); } else List2.Items = new List(); @@ -660,7 +657,7 @@ public void UpdateCost() OldLotSize = lotInfo.Size; - UpdateSizeTarget = Math.Min(Math.Max(lotSize, UpdateSizeTarget), VMBuildableAreaInfo.BuildableSizes.Length-1); + UpdateSizeTarget = Math.Min(Math.Max(lotSize, UpdateSizeTarget), VMBuildableAreaInfo.BuildableSizes.Length - 1); UpdateFloorsTarget = Math.Min(Math.Max(lotFloors, UpdateFloorsTarget), 3); var totalTarget = UpdateFloorsTarget + UpdateSizeTarget; var totalOld = lotSize + lotFloors; @@ -673,7 +670,7 @@ public void UpdateCost() if (baseCost + roomieCost > (LotControl.ActiveEntity?.TSOState.Budget.Value ?? 0)) AcceptButton.Disabled = true; //can't afford //TODO: read from uiscript - TotalCostLabel.CaptionStyle.Color = (AcceptButton.Disabled)?new Color(255, 125, 125):TextStyle.DefaultLabel.Color; + TotalCostLabel.CaptionStyle.Color = (AcceptButton.Disabled) ? new Color(255, 125, 125) : TextStyle.DefaultLabel.Color; var targetTiles = VMBuildableAreaInfo.BuildableSizes[UpdateSizeTarget]; @@ -688,7 +685,7 @@ public void UpdateCost() new string[] { (UpdateSizeTarget+1) + "+" + UpdateFloorsTarget } }; - for (int i=0; i oldFloors || newSize > oldSize) { - DrawLineStack(Color.Black, nTop+shadO, nRight+shadO, 5, newFloors, Batch, newT); + DrawLineStack(Color.Black, nTop + shadO, nRight + shadO, 5, newFloors, Batch, newT); DrawLineStack(newCol, nTop, nRight, 5, newFloors, Batch, newT); } @@ -788,7 +785,7 @@ public void Dispose() public override void Draw(UISpriteBatch batch) { - DrawLocalTexture(batch, BuildableAreaBackground.Texture, new Rectangle(138, 0, 46, 51), + DrawLocalTexture(batch, BuildableAreaBackground.Texture, new Rectangle(138, 0, 46, 51), BuildableAreaBackground.Position + new Vector2(138, 39), new Vector2(1)); base.Draw(batch); } @@ -797,7 +794,7 @@ public override void Draw(UISpriteBatch batch) private void DrawPath(Color tint, SpriteBatch batch, int lineWidth, bool complete, params Vector2[] path) { - for (int i=0; i { - (Parent.Controller as InboxController)?.Search(query, false); + FindController()?.Search(query, false); }; Dropdown.OnSelect += (id, name) => { diff --git a/TSOClient/tso.client/UI/Panels/UIInteractionQueue.cs b/TSOClient/tso.client/UI/Panels/UIInteractionQueue.cs index 07f494563..b2903ece1 100644 --- a/TSOClient/tso.client/UI/Panels/UIInteractionQueue.cs +++ b/TSOClient/tso.client/UI/Panels/UIInteractionQueue.cs @@ -10,6 +10,7 @@ using FSO.SimAntics.NetPlay.Model.Commands; using FSO.Client.UI.Controls; using FSO.Common; +using FSO.UI.Utils; namespace FSO.Client.UI.Panels { @@ -244,6 +245,17 @@ public void Update() public void UpdateInteractionIcon() { UI.Icon = IconOwner?.GetIcon(GameFacade.GraphicsDevice, 0); + + if (UI.Icon == null) + { + uint guid = IconOwner.GroupDefinition.GUID; + UI.Icon = Content.Content.Get().WorldObjects.GetOrAddGeneratedIcon(guid, () => CatThumbGenerator.GenerateThumb(guid)); + } + else + { + // This lets the UI know it can delete the texture when it's removed. (if it's not managed by another cache) + UI.Icon.Tag ??= UI; + } } public void UpdateInteractionResult() diff --git a/TSOClient/tso.client/UI/Panels/UIJoinLotProgress.cs b/TSOClient/tso.client/UI/Panels/UIJoinLotProgress.cs index 109aaba8f..c41501023 100644 --- a/TSOClient/tso.client/UI/Panels/UIJoinLotProgress.cs +++ b/TSOClient/tso.client/UI/Panels/UIJoinLotProgress.cs @@ -62,7 +62,11 @@ public UIJoinLotProgress() : base(UIDialogStyle.Standard, false) public override void Update(UpdateState state) { base.Update(state); - if (Visible) GameFacade.Cursor.SetCursor(CursorType.Hourglass); + if (Visible) + { + CursorManager.INSTANCE.SetCursorPriority(1); + GameFacade.Cursor.SetCursor(CursorType.Hourglass, 1); + } } public float Progress diff --git a/TSOClient/tso.client/UI/Panels/UILiveMode.cs b/TSOClient/tso.client/UI/Panels/UILiveMode.cs index 3a5da5e86..79cb8b1ba 100644 --- a/TSOClient/tso.client/UI/Panels/UILiveMode.cs +++ b/TSOClient/tso.client/UI/Panels/UILiveMode.cs @@ -16,6 +16,7 @@ using FSO.Client.Utils; using FSO.LotView.Utils.Camera; using FSO.LotView; +using FSO.SimAntics.Utils; namespace FSO.Client.UI.Panels { diff --git a/TSOClient/tso.client/UI/Panels/UILoginDialog.cs b/TSOClient/tso.client/UI/Panels/UILoginDialog.cs index 70004d653..5bc0c82aa 100644 --- a/TSOClient/tso.client/UI/Panels/UILoginDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UILoginDialog.cs @@ -25,7 +25,6 @@ public UILoginDialog(Action login) m_TxtAccName.SetSize(310, 27); m_TxtAccName.CurrentText = GlobalSettings.Default.LastUser; m_TxtAccName.OnChange += M_TxtAccName_OnChange; - m_TxtAccName.OnTabPress += new KeyPressDelegate(m_TxtAccName_OnTabPress); m_TxtAccName.OnEnterPress += new KeyPressDelegate(loginBtn_OnButtonClick); this.Add(m_TxtAccName); @@ -37,9 +36,7 @@ public UILoginDialog(Action login) m_TxtPass.SetSize(310, 27); m_TxtPass.Password = true; m_TxtPass.OnChange += M_TxtAccName_OnChange; - //m_TxtPass.OnTabPress += new KeyPressDelegate(m_TxtPass_OnTabPress); m_TxtPass.OnEnterPress += new KeyPressDelegate(loginBtn_OnButtonClick); - m_TxtPass.OnShiftTabPress += new KeyPressDelegate(m_TxtPass_OnShiftTabPress); this.Add(m_TxtPass); /** Login button **/ @@ -117,21 +114,6 @@ public void ClearPassword() m_TxtPass.CurrentText = ""; } - /*void m_TxtPass_OnTabPress(UIElement element) - { - GameFacade.Screens.inputManager.SetFocus(m_TxtAccName); - }*/ - - void m_TxtAccName_OnTabPress(UIElement element) - { - GameFacade.Screens.inputManager.SetFocus(m_TxtPass); - } - - void m_TxtPass_OnShiftTabPress(UIElement element) - { - GameFacade.Screens.inputManager.SetFocus(m_TxtAccName); - } - public string Username { get @@ -160,7 +142,10 @@ void loginBtn_OnButtonClick(UIElement button) void exitBtn_OnButtonClick(UIElement button) { - GameFacade.Kill(); + if (FSOFacade.Controller.CloseAttempt()) + { + GameFacade.Kill(); + } /*var exitDialog = new UIExitDialog(); Parent.Add(exitDialog);*/ } diff --git a/TSOClient/tso.client/UI/Panels/UILoginProgress.cs b/TSOClient/tso.client/UI/Panels/UILoginProgress.cs index ee1b8d64b..cdef8bbcb 100644 --- a/TSOClient/tso.client/UI/Panels/UILoginProgress.cs +++ b/TSOClient/tso.client/UI/Panels/UILoginProgress.cs @@ -35,14 +35,14 @@ public UILoginProgress() : base(UIDialogStyle.Standard, false) this.Add(new UILabel { - Caption = GameFacade.Strings.GetString("210", "2"), + Caption = GameFacade.Strings.GetString("f100", "10"), X = 20, Y = 44 }); this.Add(new UILabel { - Caption = GameFacade.Strings.GetString("210", "3"), + Caption = GameFacade.Strings.GetString("f100", "11"), X = 20, Y = 97 }); diff --git a/TSOClient/tso.client/UI/Panels/UILotControl.cs b/TSOClient/tso.client/UI/Panels/UILotControl.cs index 2e9d8bcd9..054259f6f 100644 --- a/TSOClient/tso.client/UI/Panels/UILotControl.cs +++ b/TSOClient/tso.client/UI/Panels/UILotControl.cs @@ -1,45 +1,46 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FSO.Client.UI.Framework; +using FSO.Client.Debug; +using FSO.Client.Network; using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; using FSO.Client.UI.Model; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using FSO.Common.Rendering.Framework.Model; -using FSO.Common.Rendering.Framework.IO; +using FSO.Client.UI.Panels.EODs; +using FSO.Client.UI.Panels.LotControls; +using FSO.Client.UI.Panels.Neighborhoods; +using FSO.Client.UI.Panels.Profile; +using FSO.Client.UI.Screens; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Domain.Realestate; +using FSO.Common.Enum; +using FSO.Common.Model; using FSO.Common.Rendering.Framework; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Files.RC; using FSO.HIT; - using FSO.LotView; -using FSO.SimAntics; using FSO.LotView.Components; -using FSO.Client.UI.Panels.LotControls; -using Microsoft.Xna.Framework.Input; +using FSO.LotView.Facade; using FSO.LotView.Model; -using FSO.SimAntics.Primitives; -using FSO.SimAntics.NetPlay.Model.Commands; -using FSO.Client.Debug; -using FSO.SimAntics.NetPlay.Model; +using FSO.LotView.Utils.Camera; +using FSO.SimAntics; +using FSO.SimAntics.Engine; +using FSO.SimAntics.Engine.TSOTransaction; +using FSO.SimAntics.Model; using FSO.SimAntics.Model.TSOPlatform; -using FSO.Client.UI.Panels.EODs; +using FSO.SimAntics.NetPlay.Model; +using FSO.SimAntics.NetPlay.Model.Commands; +using FSO.SimAntics.Primitives; using FSO.SimAntics.Utils; -using FSO.Common; -using System.IO; -using FSO.SimAntics.Engine.TSOTransaction; -using FSO.LotView.Facade; -using FSO.Common.Enum; -using FSO.Client.UI.Screens; -using Ninject; -using FSO.Client.Network; -using FSO.Client.UI.Panels.Neighborhoods; using FSO.UI.Controls; -using FSO.Client.UI.Panels.Profile; -using FSO.SimAntics.Model; -using FSO.SimAntics.Engine; -using FSO.Client.Utils; -using FSO.Common.Model; -using FSO.LotView.Utils.Camera; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using Ninject; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace FSO.Client.UI.Panels { @@ -48,8 +49,10 @@ namespace FSO.Client.UI.Panels /// public class UILotControl : UIContainer, IDisposable, ITouchable, IFocusableUI { + public bool IsFocused { get; set; } + public int TabIndex { get; set; } = -1; private UIMouseEventRef MouseEvt; - public bool MouseIsOn; + public bool MouseIsOn { get; private set; } private UIPieMenu PieMenu; public UIChatPanel ChatPanel; @@ -78,6 +81,7 @@ public uint SelectedSimID { } } public short ObjectHover; + public string ObjectTooltip; public bool InteractionsAvailable; public UIInteractionQueue Queue; @@ -93,6 +97,12 @@ public uint SelectedSimID { public UIEODController EODs; public int WallsMode = 1; + private bool IsSpectator => (ActiveEntity as VMAvatar ?? vm.GetAvatarByPersist(vm.MyUID)) + is VMAvatar ava && ((VMTSOAvatarState)ava.TSOState)?.IsSpectator == true; + + private bool IsBlockedForSpectator(VMEntity obj) + => IsSpectator && obj is VMGameObject && obj != GotoObject && obj != TransitionObject + && obj.Object.OBJ.GUID != PAYPHONE_GUID && obj.Object.OBJ.GUID != NHOOD_PAYPHONE_GUID; private int OldMX; private int OldMY; @@ -121,6 +131,9 @@ public uint SelectedSimID { public I3DRotate Rotate { get { return World.State.Cameras.Camera3D; } } //(I3DRotate)World.State; } } public bool TVisible { get { return Visible; } } public bool UserModZoom { get; set; } + public bool StealFocus { get; set; } + + public bool EnableTransitions => vm?.TSOState?.Flags.HasFlag(VMTSOLotStateFlags.AllowFreeRoam) ?? false; public void Scroll(Vector2 vec) { @@ -131,13 +144,18 @@ public void Scroll(Vector2 vec) // and that the code actually blocks further dialogs from appearing while waiting for a response. // If we are to implement controlling multiple sims, this must be changed. private UIAlert BlockingDialog; - private UIAlert DialogTakeFocus; private UINeighborhoodSelectionPanel TS1NeighSelector; private ulong LastDialogID; private static uint GOTO_GUID = 0x000007C4; public VMEntity GotoObject; + private static uint TRANSITION_GUID = 0x746ED02B; + public VMEntity TransitionObject; + + private static uint PAYPHONE_GUID = 0x313D2F9A; + private static uint NHOOD_PAYPHONE_GUID = 0x303CD603; + private Rectangle MouseCutRect = new Rectangle(-4, -4, 4, 4); private List CutRooms = new List(); private HashSet LastCutRooms = new HashSet(); //final rooms, including those outside. used to detect dirty. @@ -148,6 +166,8 @@ public void Scroll(Vector2 vec) private bool LastRectCutNotable = false; //set if the last rect cut made a noticable change to the cuts array. If true refresh regardless of new cut effect. private bool HasLanded = false; + private HashSet DirectCancelUIDs = []; + /// /// Creates a new UILotControl instance. /// @@ -176,6 +196,7 @@ public UILotControl(FSO.SimAntics.VM vm, LotView.World World) RMBCursor = GetTexture(0x24B00000001); //exploreanchor.bmp vm.OnChatEvent += Vm_OnChatEvent; + vm.OnGenericVMEvent += Vm_OnGenericVMEvent; vm.OnDialog += vm_OnDialog; vm.OnBreakpoint += Vm_OnBreakpoint; @@ -186,6 +207,14 @@ public UILotControl(FSO.SimAntics.VM vm, LotView.World World) this.Add(DonatorDialog); } + private void Vm_OnGenericVMEvent(VMEventType type, object data) + { + if (type == VMEventType.Resync) + { + UpdateChatTitle(); + } + } + public void SetDonatorDialogVisible(bool visible) { if (vm.TSOState?.CommunityLot == true) @@ -256,7 +285,9 @@ private void Vm_OnBreakpoint(VMEntity entity) public string GetLotTitle() { - return vm.LotName + " - " + vm.Entities.Count(x => x is VMAvatar && x.PersistID != 0); + var title = GameFacade.Strings.TransformLotName(vm.LotName) + " - " + vm.Entities.Count(x => x is VMAvatar && x.PersistID != 0); + if (IsSpectator) title += " (Spectator)"; + return title; } void vm_OnDialog(FSO.SimAntics.Model.VMDialogInfo info) @@ -339,7 +370,7 @@ void vm_OnDialog(FSO.SimAntics.Model.VMDialogInfo info) return; } - var alert = UIScreen.GlobalShowAlert(options, false); + var alert = UIScreen.GlobalShowAlert(options, false, true); if (info.Block) { @@ -347,8 +378,6 @@ void vm_OnDialog(FSO.SimAntics.Model.VMDialogInfo info) LastDialogID = info.DialogID; } - DialogTakeFocus = alert; - var entity = info.Icon; if (entity is VMGameObject) { @@ -431,15 +460,15 @@ private void OnMouse(UIMouseEventType type, UpdateState state) } } - private short GetFloorBlockableHover(Point pt) + private short GetFloorBlockableHover(Point pt, out Vector3? tilePos) { - var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y)); + tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y), canFail: true); var newHover = World.GetObjectIDAtScreenPos(pt.X, pt.Y, GameFacade.GraphicsDevice); var hobj = vm.GetObjectById(newHover); - if (hobj == null || hobj.Position.Level < tilePos.Z) newHover = 0; + if (!tilePos.HasValue || hobj == null || hobj.Position.Level < tilePos.Value.Z) newHover = 0; return newHover; } @@ -455,10 +484,13 @@ public void Click(Point pt, UpdateState state) { VMEntity obj; //get new pie menu, make new pie menu panel for it - var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y)); + var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y), canFail: true); - LotTilePos targetPos = LotTilePos.FromBigTile((short)tilePos.X, (short)tilePos.Y, (sbyte)tilePos.Z); - if (vm.Context.SolidToAvatars(targetPos).Solid) targetPos = LotTilePos.OUT_OF_WORLD; + LotTilePos targetPos = tilePos.HasValue ? LotTilePos.FromBigTile((short)tilePos.Value.X, (short)tilePos.Value.Y, (sbyte)tilePos.Value.Z) : LotTilePos.OUT_OF_WORLD; + if (vm.Context.SolidToAvatars(targetPos).Solid) + { + targetPos = LotTilePos.OUT_OF_WORLD; + } GotoObject.SetPosition(targetPos, Direction.NORTH, vm.Context); @@ -466,16 +498,22 @@ public void Click(Point pt, UpdateState state) pt.Y, GameFacade.GraphicsDevice); - var hobj = vm.GetObjectById(newHover); - if (hobj == null || hobj.Position.Level < tilePos.Z) newHover = 0; + if (newHover == 0 && ObjectHover < 0) + { + // Special hover - take the existing value. + newHover = ObjectHover; + } + + var hobj = GetHoverById(newHover); + if (!tilePos.HasValue || hobj == null || hobj.Position.Level < tilePos.Value.Z) newHover = 0; ObjectHover = newHover; - bool objSelected = ObjectHover > 0; + bool objSelected = ObjectHover != 0; if (objSelected || (GotoObject.Position != LotTilePos.OUT_OF_WORLD && ObjectHover <= 0)) { if (objSelected) { - obj = vm.GetObjectById(ObjectHover); + obj = GetHoverById(ObjectHover); } else { @@ -492,7 +530,11 @@ public void Click(Point pt, UpdateState state) Queue.QueueOwner = ActiveEntity; Queue.DebugMode = true; }*/ - if (obj is VMGameObject && ((VMGameObject)obj).Disabled > 0) + if (objSelected && IsBlockedForSpectator(obj)) + { + ShowErrorTooltip(state, 0, true); + } + else if (obj is VMGameObject && ((VMGameObject)obj).Disabled > 0) { var flags = ((VMGameObject)obj).Disabled; @@ -587,6 +629,63 @@ public Point GetScaledPoint(Point TapPoint) return ((TapPoint - screenMiddle).ToVector2() / World.BackbufferScale).ToPoint() + screenMiddle; } + private bool TryPrepareLotTransition(Vector3 point) + { + if (PieMenu != null || TransitionObject == null || (point.X == 0 && point.Y == 0)) + { + // TODO: Ideally the tile location from hover should be nullable, but that needs changed in a lot of places.1 + return false; + } + + Point tilePoint = new Point((int)Math.Floor(point.X), (int)Math.Floor(point.Y)); + Point transitionScale = new Point(World.Architecture.Blueprint.Width - 2, World.Architecture.Blueprint.Height - 2); + Point border = new Point(1, 1); + + Point targetLotOffset = Vector2.Floor((tilePoint - border).ToVector2() / transitionScale.ToVector2()).ToPoint(); + + if (targetLotOffset == default) + { + // On this lot... + return false; + } + + if (Math.Abs(targetLotOffset.X) > 1 || Math.Abs(targetLotOffset.Y) > 1) + { + // Out of range... + return false; + } + + Point myLocation = MapCoordinates.Unpack(vm.TSOState.LotID).ToPoint(); + Point targetCityOffset = LotTransitionInfo.RelativeChangeLotToCity(targetLotOffset); + var newLocation = new MapCoordinate(myLocation + targetCityOffset); + + // TODO: pull map data from screen? + + if (!MapCoordinates.InBounds(newLocation.X, newLocation.Y)) + { + return false; + } + + var packed = MapCoordinates.Pack(newLocation.X, newLocation.Y); + + TransitionObject.SetAttribute(1, (short)newLocation.Y); // lot id (low) + TransitionObject.SetAttribute(2, (short)newLocation.X); // lot id (high) + TransitionObject.SetAttribute(3, (short)(((tilePoint.X - targetLotOffset.X * transitionScale.X) << 4) + 8)); // dest x + TransitionObject.SetAttribute(4, (short)(((tilePoint.Y - targetLotOffset.Y * transitionScale.Y) << 4) + 8)); // dest y + + return true; + } + + private VMEntity GetHoverById(short id) + { + if (id == -1) + { + return TransitionObject; + } + + return vm.GetObjectById(id); + } + public void LiveModeUpdate(UpdateState state, bool scrolled) { if (MouseIsOn && !RMBScroll && ActiveEntity != null) @@ -597,43 +696,45 @@ public void LiveModeUpdate(UpdateState state, bool scrolled) OldMX = state.MouseState.X; OldMY = state.MouseState.Y; var scaled = GetScaledPoint(state.MouseState.Position); - var newHover = GetFloorBlockableHover(scaled); + var newHover = GetFloorBlockableHover(scaled, out Vector3? tilePos); - if (ObjectHover != newHover) + if (newHover == 0 && tilePos.HasValue && TryPrepareLotTransition(tilePos.Value)) + { + newHover = -1; + } + + if (ObjectHover != newHover || ObjectTooltip != null) { ObjectHover = newHover; - if (ObjectHover > 0) + if (ObjectHover != 0) { - var obj = vm.GetObjectById(ObjectHover); + var obj = GetHoverById(ObjectHover); + if (obj != null) { var menu = obj.GetPieMenu(vm, ActiveEntity, false, true); InteractionsAvailable = (menu.Count > 0); + ObjectTooltip = menu.Find(x => x.IsTooltip)?.Name; + } + else + { + ObjectTooltip = null; } } } if (!TipIsError) ShowTooltip = false; - if (ObjectHover > 0) + if (ObjectHover != 0) { - var obj = vm.GetObjectById(ObjectHover); + var obj = GetHoverById(ObjectHover); if (!TipIsError && obj != null) { - if (obj is VMAvatar) + if (obj is VMAvatar && ObjectTooltip == null) { - if (((VMAvatar)obj).GetPersonData(VMPersonDataVariable.PersonType) != 255) - { - state.UIState.TooltipProperties.Show = true; - state.UIState.TooltipProperties.Color = Color.Black; - state.UIState.TooltipProperties.Opacity = 1; - state.UIState.TooltipProperties.Position = new Vector2(state.MouseState.X, - state.MouseState.Y); - state.UIState.Tooltip = GetAvatarString(obj as VMAvatar); - state.UIState.TooltipProperties.UpdateDead = false; - ShowTooltip = true; - } + ObjectTooltip = GetAvatarString(obj as VMAvatar); } - else if (((VMGameObject)obj).Disabled > 0) + + if (((obj as VMGameObject)?.Disabled ?? 0) > 0) { var flags = ((VMGameObject)obj).Disabled; if ((flags & VMGameObjectDisableFlags.ForSale) > 0) @@ -649,7 +750,17 @@ public void LiveModeUpdate(UpdateState state, bool scrolled) TipIsError = false; } } - + else if (ObjectTooltip != null && PieMenu == null) + { + state.UIState.TooltipProperties.Show = true; + state.UIState.TooltipProperties.Color = Color.Black; + state.UIState.TooltipProperties.Opacity = 1; + state.UIState.TooltipProperties.Position = new Vector2(state.MouseState.X, + state.MouseState.Y); + state.UIState.Tooltip = ObjectTooltip; + state.UIState.TooltipProperties.UpdateDead = false; + ShowTooltip = true; + } } } if (!ShowTooltip) @@ -678,11 +789,19 @@ public void LiveModeUpdate(UpdateState state, bool scrolled) { if (InteractionsAvailable) { - var obj = vm.GetObjectById(ObjectHover); - if (obj is VMAvatar) + var obj = GetHoverById(ObjectHover); + if (IsBlockedForSpectator(obj)) + { + cursor = CursorType.LiveObjectUnavail; + } + else if (obj is VMAvatar) { cursor = (((VMAvatar)obj).GetPersonData(VMPersonDataVariable.PersonType) < 254) ? CursorType.LivePerson : CursorType.LiveObjectAvail; } + else if (obj != null && obj == TransitionObject) + { + cursor = CursorType.LiveNothing; + } else { var tsoState = obj?.PlatformState as VMTSOObjectState; @@ -754,6 +873,7 @@ public void Landed() { //hints for landing var hints = FSOFacade.Hints; + hints.TriggerHint($"lot:{GameFacade.CurrentCityName}:{vm.TSOState?.LotID ?? 0}"); hints.TriggerHint("land"); if (vm.MyUID == vm.TSOState.OwnerID) @@ -797,9 +917,9 @@ public void Landed() vm.TSOState.Names = new VMDataServiceNameCache(FSOFacade.Kernel.Get()); foreach (var roomie in vm.TSOState.Roommates) { - vm.TSOState.Names.Precache(vm, roomie); + vm.TSOState.Names.Precache(vm, VMGlobalEntityType.Avatar, roomie); } - vm.TSOState.Names.Precache(vm, vm.TSOState.OwnerID); + vm.TSOState.Names.Precache(vm, VMGlobalEntityType.Avatar, vm.TSOState.OwnerID); var objOwners = new HashSet(); foreach (var ent in vm.Context.ObjectQueries.MultitileByPersist) @@ -808,7 +928,7 @@ public void Landed() if (owner != null) objOwners.Add(owner.Value); } foreach (var owner in objOwners) - vm.TSOState.Names.Precache(vm, owner); + vm.TSOState.Names.Precache(vm, VMGlobalEntityType.Avatar, owner); HasLanded = true; } @@ -865,6 +985,17 @@ public void SetTargetZoom(WorldZoom zoom) LastZoom = World.State.Zoom; } + public void ResetTargetZoom() + { + FoundMe = true; + + if (World.State.Cameras.ActiveType == LotView.Utils.Camera.CameraControllerType._3D) + { + var s3d = World.State.Cameras.Camera3D; + TargetZoom = (s3d.Zoom3D - 9.75f) / -5.7f + 0.25f; + } + } + private WorldZoom LastZoom; public override void Update(UpdateState state) { @@ -941,12 +1072,24 @@ public override void Update(UpdateState state) vm.Context.World.State.CenterTile = new Vector2(ActiveEntity.VisualPosition.X, ActiveEntity.VisualPosition.Y); vm.Context.World.State.ScrollAnchor = null; FoundMe = true; + + // Force walls up with roof for spectators + if (IsSpectator) + { + WallsMode = 3; + World.State.DrawRoofs = true; + } } Queue.QueueOwner = ActiveEntity; } if (GotoObject == null) GotoObject = vm.Context.CreateObjectInstance(GOTO_GUID, LotTilePos.OUT_OF_WORLD, Direction.NORTH, true).Objects[0]; + if (TransitionObject == null && EnableTransitions) + { + TransitionObject = vm.Context.CreateObjectInstance(TRANSITION_GUID, LotTilePos.OUT_OF_WORLD, Direction.NORTH, true).Objects[0]; + } + if (ActiveEntity != null && BlockingDialog != null) { //are we still waiting on a blocking dialog? if not, cancel. @@ -958,11 +1101,11 @@ public override void Update(UpdateState state) } } - if (DialogTakeFocus != null) + if (StealFocus) { - // Right now just steal it from the game. In future it should be given to the OK button. + // Right now just steal it from the game. In future dialog focus could be given to the OK button. state.InputManager.SetFocus(this); - DialogTakeFocus = null; + StealFocus = false; } if (Visible) @@ -1076,6 +1219,11 @@ public override void Update(UpdateState state) if (lastStack is VMDirectControlFrame frame) { + if (DirectCancelUIDs.Count > 0) + { + DirectCancelUIDs.Clear(); + } + frame.SendUserControls(new VMDirectControlInput() { ID = FirstPersonID++, @@ -1090,6 +1238,77 @@ public override void Update(UpdateState state) { if (FirstPersonSinceUpdate > 1/30f) { + if (intensity > 33 && ActiveEntity.Thread.ActiveQueueBlock != -1) + { + var top = ActiveEntity.Thread.Queue[ActiveEntity.Thread.ActiveQueueBlock]; + if (!DirectCancelUIDs.Contains(top.UID)) + { + var ava = (ActiveEntity as VMAvatar); + var priority = ava?.GetPersonData(VMPersonDataVariable.Priority) ?? 50; + + if (top.Mode == VMQueueMode.Normal && priority < 50 && !top.NotifyIdle) + { + HIT.HITVM.Get().PlaySoundEvent(UISounds.QueueDelete); + vm.SendCommand(new VMNetInteractionCancelCmd + { + ActionUID = top.UID + }); + DirectCancelUIDs.Add(top.UID); + } + + if (top.Mode == VMQueueMode.Idle && + ava?.GetPersonData(VMPersonDataVariable.Posture) == 1 && + ActiveEntity.Thread.Queue.Count(x => x.Mode != VMQueueMode.Idle) == 0) + { + var basePos = ActiveEntity.Position; + var baseDir = ActiveEntity.GetValue(VMStackObjectVariable.Direction) / 2; + for (int i = 0; i < 4; i++) + { + var testPos = basePos; + // Sims tend to get out of chairs in the direction they entered them from, so try route there first. + var testDir = (byte)(ava?.GetPersonData(VMPersonDataVariable.RouteEntryFlags) ?? 0); + var notches = (baseDir + i) * 2; + + testDir = (byte)((testDir << (notches)) | (testDir >> (8 - (notches)))); + switch ((Direction)testDir) + { + case Direction.SOUTH: + testPos.y += 16; + break; + case Direction.WEST: + testPos.x -= 16; + break; + case Direction.EAST: + testPos.x += 16; + break; + case Direction.NORTH: + testPos.y -= 16; + break; + } + + var overlapping = vm.Context.ObjectQueries.GetObjectsAt(testPos); + + if (overlapping == null || !vm.Context.SolidToAvatars(testPos).Solid) + { + DirectCancelUIDs.Add(top.UID); + // Try and stand up + vm.SendCommand(new VMNetGotoCmd + { + Interaction = 4, // Run here + Param0 = 0, + ActorUID = ActiveEntity.PersistID, + x = testPos.x, + y = testPos.y, + level = testPos.Level, + }); + + break; + } + } + } + } + } + vm.SendCommand(new VMNetDirectControlCommand() { Partial = true, @@ -1298,7 +1517,10 @@ private void SaveFacade(bool toObject) vm.Context.Architecture.SignalAllDirty(); vm.Context.Architecture.Tick(); } + var oldFloor = World.State.Level; + World.State.SilentLevel = World.Stories; SetOutsideTime(GameFacade.GraphicsDevice, vm, World, (1-i)*0.5f, false); + World.State.SilentLevel = oldFloor; var facade = new LotFacadeGenerator(); if (toObject) @@ -1418,9 +1640,5 @@ public void ClearCenter() { } - public void OnFocusChanged(FocusEvent newFocus) - { - - } } } diff --git a/TSOClient/tso.client/UI/Panels/UILotControlTouchHelper.cs b/TSOClient/tso.client/UI/Panels/UILotControlTouchHelper.cs index b6b9b4c36..2bcfaa4f0 100644 --- a/TSOClient/tso.client/UI/Panels/UILotControlTouchHelper.cs +++ b/TSOClient/tso.client/UI/Panels/UILotControlTouchHelper.cs @@ -22,6 +22,7 @@ public interface ITouchable I3DRotate Rotate { get; } bool TVisible { get; } bool UserModZoom { get; set; } + bool MouseIsOn { get; } void ClearCenter(); } @@ -78,6 +79,8 @@ private Point GetScaledPoint(Point TapPoint) public float MaxZoom = 2f; public bool _3D; + private bool MouseIsOn => Master.MouseIsOn; + public override void Update(UpdateState state) { var _3d = _3D; @@ -99,12 +102,16 @@ public override void Update(UpdateState state) else if (state.WindowFocused && state.MouseState.ScrollWheelValue != LastMouseWheel) { var diff = state.MouseState.ScrollWheelValue - LastMouseWheel; - Master.TargetZoom = Master.TargetZoom + diff / 1600f; LastMouseWheel = state.MouseState.ScrollWheelValue; - Master.TargetZoom = Math.Max(MinZoom, Math.Min(Master.TargetZoom, MaxZoom)); - Master.UserModZoom = true; - ZoomFreezeTime = (10 * FSOEnvironment.RefreshRate) / 60; - Master.ClearCenter(); + + if (MouseIsOn) + { + Master.TargetZoom = Master.TargetZoom + diff / 1600f; + Master.TargetZoom = Math.Max(MinZoom, Math.Min(Master.TargetZoom, MaxZoom)); + Master.UserModZoom = true; + ZoomFreezeTime = (10 * FSOEnvironment.RefreshRate) / 60; + Master.ClearCenter(); + } } ScrollWheelInvalid = invalidNow; } diff --git a/TSOClient/tso.client/UI/Panels/UILotPage.cs b/TSOClient/tso.client/UI/Panels/UILotPage.cs index f1af2f572..f944c56a8 100644 --- a/TSOClient/tso.client/UI/Panels/UILotPage.cs +++ b/TSOClient/tso.client/UI/Panels/UILotPage.cs @@ -3,16 +3,15 @@ using FSO.Client.Rendering.City; using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels.Profile; using FSO.Client.UI.Screens; using FSO.Client.Utils; using FSO.Common.DataService.Model; using FSO.Common.Enum; +using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using FSO.Common.Rendering.Framework.Model; namespace FSO.Client.UI.Panels { @@ -67,6 +66,14 @@ public class UILotPage : UIContainer public Texture2D RoommateThumbButtonImage { get; set; } public Texture2D VisitorThumbButtonImage { get; set; } + public string LotName + { + set + { + HouseNameButton.Caption = GameFacade.Strings.TransformLotName(value); + } + } + private UILotThumbButton LotThumbnail { get; set; } private UIRoommateList RoommateList { get; set; } private UIClickableLabel SkillGameplayLabel { get; set; } @@ -159,7 +166,7 @@ public UILotPage() HouseCategory_CommunityButtonImage = ui.Get("lotp_community_small.png").Get(GameFacade.GraphicsDevice); CurrentLot = new Binding() - .WithBinding(HouseNameButton, "Caption", "Lot_Name") + .WithBinding(this, "LotName", "Lot_Name") .WithBinding(NeighborhoodNameButton, "Caption", "Lot_NeighborhoodName") .WithBinding(HouseValueLabel, "Caption", "Lot_Price", x => MoneyFormatter.Format((uint)x)) .WithBinding(OccupantsNumberLabel, "Caption", "Lot_NumOccupants", x => x.ToString()) @@ -206,7 +213,7 @@ public UILotPage() //NeighborhoodNameButton.Visible = false; - Size = BackgroundExpandedImage.Size.ToVector2(); + Size = BackgroundExpandedImage.Size; SendToFront(ExpandButton, ContractButton); } @@ -380,12 +387,14 @@ public void AsyncAPIThumb(uint lotID) private void RefreshUI() { + var controller = FindController(); var isOpen = _Open == true; var isClosed = _Open == false; var isMyProperty = false; var isRoommate = false; var isOnline = false; var isCommunity = false; + var isEmpty = false; NeighborhoodNameButton.Size = new Vector2(173, 18); @@ -394,6 +403,7 @@ private void RefreshUI() isOnline = CurrentLot.Value.Lot_IsOnline || (CurrentLot.Value.Lot_LotAdmitInfo?.LotAdmitInfo_AdmitMode >= 4); isMyProperty = FindController().IsMe(CurrentLot.Value.Lot_LeaderID); isCommunity = CurrentLot.Value.Lot_Category == 11; + isEmpty = CurrentLot.Value.Lot_Category == 0 && CurrentLot.Value.Lot_LeaderID == 0; var roomies = new List(); if (CurrentLot.Value.Lot_RoommateVec != null) roomies.AddRange(CurrentLot.Value.Lot_RoommateVec); @@ -413,12 +423,19 @@ private void RefreshUI() if (OriginalDescription != CurrentLot.Value.Lot_Description) { OriginalDescription = CurrentLot.Value.Lot_Description; - HouseDescriptionTextEdit.CurrentText = OriginalDescription; + HouseDescriptionTextEdit.CurrentText = TransformLotDescription(CurrentLot.Value.Lot_Name, OriginalDescription); + } + + if (isEmpty) + { + LotThumbnail.SetThumbnail(null, CurrentLot.Value.Id); } } bool inBounds = CurrentLot.Value == null || CurrentLot.Value.Lot_Location_Packed < 0x10200 || CurrentLot.Value.Lot_Location_Packed >= 0x20000; - var canJoin = isMyProperty || isRoommate || (inBounds && (isOnline || isCommunity)) || GameFacade.EnableMod; + var canJoin = isMyProperty || isRoommate || (inBounds && (isOnline || isCommunity)) || (controller?.CanOpenAnyLot ?? false); + + // TODO (indicate that the rules are being broken if canJoin is true because of a nonzero mod level HouseNameButton.Disabled = !isMyProperty; @@ -456,12 +473,30 @@ private void RefreshUI() LotThumbnail.Disabled = true; } } + + private string TransformLotDescription(string name, string desc) + { + if (name.StartsWith('{') && name.EndsWith('}') && desc.StartsWith('{') && desc.EndsWith('}')) + { + var split = desc.Substring(1, name.Length - 2).Split(':'); + + if (split.Length == 3 && split[0] == "job" && int.TryParse(split[1], out int type) && int.TryParse(split[2], out int level)) + { + string lotName = GameFacade.Strings.GetString("UIText", "f132", (type * 100 + level).ToString()) ?? "Unknown"; + JobInformation jobInfo = JobInformation.FromLotInfo(level, type); + return GameFacade.Strings.GetString("f132", "1000", [jobInfo.Type, jobInfo.Title, level.ToString(), jobInfo.Hours, jobInfo.CarpoolHours]); + } + } + + return desc; + } + public override void Draw(UISpriteBatch batch) { if (!Visible) return; if (CurrentLot.Value != null) UITerrainHighlight.DrawArrow(batch, ((CoreGameScreen)GameFacade.Screens.CurrentUIScreen).CityRenderer, - (Position + (_Open? Size : BackgroundContractedImage.Size.ToVector2()) / 2)* Common.FSOEnvironment.DPIScaleFactor, (int)CurrentLot.Value.Id, new Color(200, 225, 255)); + (Position + (_Open? Size : BackgroundContractedImage.Size) / 2)* Common.FSOEnvironment.DPIScaleFactor, (int)CurrentLot.Value.Id, new Color(200, 225, 255)); base.Draw(batch); } } diff --git a/TSOClient/tso.client/UI/Panels/UILotPurchaseDialog.cs b/TSOClient/tso.client/UI/Panels/UILotPurchaseDialog.cs index 5a29f9a5d..a6d5b026d 100644 --- a/TSOClient/tso.client/UI/Panels/UILotPurchaseDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UILotPurchaseDialog.cs @@ -18,6 +18,7 @@ public class UILotPurchaseDialog : UIDialog public UITextEdit NameTextEdit { get; set; } public UIValidationMessages NameTextEditValidation { get; set; } public UILabel MessageText { get; set; } + public UILabel TitleText { get; set; } public string TextTitle { get; set; } public string InvalidNameErrorTitle { get; set; } @@ -69,6 +70,17 @@ public UILotPurchaseDialog() : base(UIDialogStyle.Standard| UIDialogStyle.OK | U OKButton.OnButtonClick += AcceptButton_OnButtonClick; CloseButton.OnButtonClick += CloseButton_OnButtonClick; } + + public UILotPurchaseDialog AsRenameDialog(string existingName, string title, string caption) + { + NameTextEdit.CurrentText = existingName; + NameTextEdit_OnChange(NameTextEdit); + + TitleText.Caption = title; + MessageText.Caption = caption; + + return this; + } private void CloseButton_OnButtonClick(Framework.UIElement button) { diff --git a/TSOClient/tso.client/UI/Panels/UIMessageWindow.cs b/TSOClient/tso.client/UI/Panels/UIMessageWindow.cs index bfb534490..818735c89 100644 --- a/TSOClient/tso.client/UI/Panels/UIMessageWindow.cs +++ b/TSOClient/tso.client/UI/Panels/UIMessageWindow.cs @@ -146,7 +146,7 @@ public UIMessageWindow() MyUser = new Binding(); User.ValueChanged += (x) => PersonButton.User.Value = x; - Size = Background.Size.ToVector2(); + Size = Background.Size; this.Opacity = GlobalSettings.Default.ChatWindowsOpacity; diff --git a/TSOClient/tso.client/UI/Panels/UINeighborhoodSelectionPanel.cs b/TSOClient/tso.client/UI/Panels/UINeighborhoodSelectionPanel.cs index f8ccdbe14..154de080c 100644 --- a/TSOClient/tso.client/UI/Panels/UINeighborhoodSelectionPanel.cs +++ b/TSOClient/tso.client/UI/Panels/UINeighborhoodSelectionPanel.cs @@ -11,6 +11,7 @@ using System.Linq; using FSO.Common.Rendering.Framework.Model; using FSO.HIT; +using FSO.Common; namespace FSO.Client.UI.Panels { @@ -159,7 +160,7 @@ public UINeighborhoodAnimationLayer(NeighborhoodImageAnim anim, bool pulsate, in Frames = anim.Frames.Select(x=> ((ITextureRef)provider.Get(x)).Get(GameFacade.GraphicsDevice)).ToArray(); SubFrame = frameTime; FrameTime = frameTime; - FrameTime *= GlobalSettings.Default.TargetRefreshRate; + FrameTime *= FSOEnvironment.RefreshRate; FrameTime /= 60; TotalFrames = pulsate ? (Frames.Length * 2 - 2) : Frames.Length; } diff --git a/TSOClient/tso.client/UI/Panels/UINetStatusTray.cs b/TSOClient/tso.client/UI/Panels/UINetStatusTray.cs index 1c04a9118..49e20cc9a 100644 --- a/TSOClient/tso.client/UI/Panels/UINetStatusTray.cs +++ b/TSOClient/tso.client/UI/Panels/UINetStatusTray.cs @@ -40,6 +40,10 @@ public override void Update(UpdateState state) { messages.Add(GameFacade.Strings.GetString("f100", "6", new string[] { status.RemeshesInProgress.ToString() })); } + if (status.RemeshUpdateProgress.HasValue) + { + messages.Add(GameFacade.Strings.GetString("f100", "12", new string[] { ((status.RemeshUpdateProgress ?? 0) * 100).ToString("0.00") })); + } DCLabel.Caption = string.Join(", ", messages); DCLabel.CaptionStyle.Color = status.Severe ? new Color(255, 122, 77) : Color.White; var screen = UIScreen.Current; diff --git a/TSOClient/tso.client/UI/Panels/UIObjectHolder.cs b/TSOClient/tso.client/UI/Panels/UIObjectHolder.cs index e5f9abf6c..5e8b01a89 100644 --- a/TSOClient/tso.client/UI/Panels/UIObjectHolder.cs +++ b/TSOClient/tso.client/UI/Panels/UIObjectHolder.cs @@ -446,13 +446,13 @@ private Point GetScaledPoint(Point TapPoint) private short GetFloorBlockableHover(Point pt) { - var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y)); + var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(pt.X, pt.Y), canFail: true); var newHover = World.GetObjectIDAtScreenPos(pt.X, pt.Y, GameFacade.GraphicsDevice); var hobj = vm.GetObjectById(newHover); - if (hobj == null || hobj.Position.Level < tilePos.Z) newHover = 0; + if (!tilePos.HasValue || hobj == null || hobj.Position.Level < tilePos.Value.Z) newHover = 0; return newHover; } @@ -550,7 +550,7 @@ public void Update(UpdateState state, bool scrolled) { //can place on any level below var tilePos = World.EstTileAtPosWithScroll3D(new Vector2(scaled.X, scaled.Y) + Holding.MousePosOffset * FSOEnvironment.DPIScaleFactor); - MoveSelected(new Vector2(tilePos.X, tilePos.Y), (sbyte)tilePos.Z); // + Holding.TilePosOffset + MoveSelected(new Vector2(tilePos.Value.X, tilePos.Value.Y), (sbyte)tilePos.Value.Z); // + Holding.TilePosOffset } } } diff --git a/TSOClient/tso.client/UI/Panels/UIOptions.cs b/TSOClient/tso.client/UI/Panels/UIOptions.cs index e9b44e63c..b09a210bc 100644 --- a/TSOClient/tso.client/UI/Panels/UIOptions.cs +++ b/TSOClient/tso.client/UI/Panels/UIOptions.cs @@ -43,7 +43,7 @@ public UIOptions() Background = new UIImage(GetTexture((FSOEnvironment.UIZoomFactor>1f || GlobalSettings.Default.GraphicsWidth < 1024) ? (ulong)0x000000D800000002 : (ulong)0x0000018300000002)); this.AddAt(0, Background); Background.BlockInput(); - Size = Background.Size.ToVector2(); + Size = Background.Size; Divider = new UIImage(DividerImage); Divider.X = 227; @@ -588,7 +588,7 @@ private void SettingsChanged() vm.Context.World.ChangedWorldConfig(GameFacade.GraphicsDevice); if (oldSurrounding != settings.SurroundingLotMode) { - SimAntics.Utils.VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj); + SimAntics.Utils.VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj, true); } } } diff --git a/TSOClient/tso.client/UI/Panels/UIPersonPage.cs b/TSOClient/tso.client/UI/Panels/UIPersonPage.cs index b5a718d7d..8e9af0dec 100644 --- a/TSOClient/tso.client/UI/Panels/UIPersonPage.cs +++ b/TSOClient/tso.client/UI/Panels/UIPersonPage.cs @@ -487,7 +487,7 @@ public UIPersonPage() }, "Lot_LotAdmitInfo.LotAdmitInfo_AdmitList", "Lot_LotAdmitInfo.LotAdmitInfo_BanList"); Redraw(); - Size = BackgroundExpandedImage.Size.ToVector2(); + Size = BackgroundExpandedImage.Size; } private void ModButton_OnButtonClick(UIElement button) @@ -930,11 +930,12 @@ private void Redraw() var isMe = false; var hasProperty = false; var privacyOn = false; + var screen = FindController(); if (CurrentAvatar != null && CurrentAvatar.Value != null) { isOnline = CurrentAvatar.Value.Avatar_IsOnline; - isMe = FindController().IsMe(CurrentAvatar.Value.Avatar_Id); + isMe = screen.IsMe(CurrentAvatar.Value.Avatar_Id); hasProperty = CurrentAvatar.Value.Avatar_LotGridXY != 0; if (CurrentAvatar.Value.Avatar_ModerationLevel != 0) @@ -1038,7 +1039,7 @@ private void Redraw() var isIncoming = _RelationshipsTab == UIRelationshipsTab.Incoming; var isOptions = _Tab == UIPersonPageTab.Options; - ModButton.Visible = GameFacade.EnableMod && isOptions; + ModButton.Visible = (screen?.ModerationLevel ?? 0) > 0 && isOptions; FindHouseButton.Disabled = !hasProperty; diff --git a/TSOClient/tso.client/UI/Panels/UIPieMenu.cs b/TSOClient/tso.client/UI/Panels/UIPieMenu.cs index ec878b2c8..2eaa078b3 100644 --- a/TSOClient/tso.client/UI/Panels/UIPieMenu.cs +++ b/TSOClient/tso.client/UI/Panels/UIPieMenu.cs @@ -13,6 +13,7 @@ using FSO.Common.Utils; using FSO.SimAntics.NetPlay.Model.Commands; using FSO.Common; +using FSO.LotView.Model; namespace FSO.Client.UI.Panels { @@ -78,7 +79,14 @@ public UIPieMenu(List pie, VMEntity obj, VMEntity caller, for (int i = 0; i < pie.Count; i++) { - string[] depth = (pie[i].Name == null)?new string[] { "???" } :pie[i].Name.Split('/'); + var pieItem = pie[i]; + + if (pieItem.IsTooltip) + { + continue; + } + + string[] depth = (pieItem.Name == null)?new string[] { "???" } : pieItem.Name.Split('/'); var category = m_PieTree; //set category to root for (int j = 0; j < depth.Length-1; j++) //iterate through categories @@ -116,9 +124,9 @@ public UIPieMenu(List pie, VMEntity obj, VMEntity caller, Category = false, Name = name, ColorMod = colorMod, - ID = pie[i].ID, - Param0 = pie[i].Param0, - Global = pie[i].Global + ID = pieItem.ID, + Param0 = pieItem.Param0, + Global = pieItem.Global }; category.Children.Add(item); category.ChildrenByName[item.Name] = item; @@ -344,6 +352,26 @@ private void PieButtonClick(UIElement button) level = m_Obj.Position.Level }); } + else if (m_Obj != null && m_Obj == m_Parent.TransitionObject) + { + var targ = new LotTilePos( + m_Obj.GetAttribute(3), + m_Obj.GetAttribute(4), + 1); + + var targLot = (int)m_Obj.GetAttribute(1) | (m_Obj.GetAttribute(2) << 16); + + m_Parent.vm.SendCommand(new VMNetGotoLotCmd + { + Interaction = action.ID, + Param0 = action.Param0, + ActorUID = m_Caller.PersistID, + x = targ.x, + y = targ.y, + level = 1, // TODO? + LotLocation = (uint)targLot + }); + } else { if (Debug.IDEHook.IDE != null && ShiftDown) { diff --git a/TSOClient/tso.client/UI/Panels/UIQueryPanel.cs b/TSOClient/tso.client/UI/Panels/UIQueryPanel.cs index fe4936d52..8b4452a13 100644 --- a/TSOClient/tso.client/UI/Panels/UIQueryPanel.cs +++ b/TSOClient/tso.client/UI/Panels/UIQueryPanel.cs @@ -234,12 +234,12 @@ public int Mode { if (value < 2) { - Size = QuerybackPanel.Size.ToVector2() + new Vector2(22, 42); + Size = QuerybackPanel.Size + new Vector2(22, 42); BackOffset = new Point(22, 0); } else { - Size = QuerybackTrade.Size.ToVector2() + new Vector2(22, 42); + Size = QuerybackTrade.Size + new Vector2(22, 42); BackOffset = new Point(40, 0); } this.Y = (value>0)?-114:0; @@ -287,7 +287,7 @@ public UIQueryPanel(UILotControl parent, LotView.World world) ListenForMouse(new Rectangle(0, 0, QuerybackPanel.Texture.Width, QuerybackPanel.Texture.Height), (t, s) => { }); - Size = QuerybackPanel.Size.ToVector2() + new Vector2(22, 42); + Size = QuerybackPanel.Size + new Vector2(22, 42); BackOffset = new Point(40, 0); QuerybackCatalog = new UIImage(BackgroundImageCatalog); @@ -487,7 +487,7 @@ private string DescProcess(VM vm, VMEntity ent, string desc, STR source) CodeOwner = ent.Object, StackObject = ent, Routine = null, - Args = new short[4], + Args = default, Thread = ent.Thread, }; @@ -625,7 +625,7 @@ public void SetInfo(VM vm, VMEntity entity, bool bought) if (entity is VMGameObject && ((VMTSOObjectState)entity.TSOState).OwnerID > 0) { var ownerID = ((VMTSOObjectState)entity.TSOState).OwnerID; - owner = (vm.TSOState.Names.GetNameForID(vm, ownerID)); + owner = (vm.TSOState.Names.GetNameForID(vm, VMGlobalEntityType.Avatar, ownerID)); if (((VMTSOObjectState)entity.TSOState).ObjectFlags.HasFlag(VMTSOObjectFlags.FSODonated)) { ownerTable = "f114"; @@ -743,7 +743,7 @@ public override void InternalDraw(UISpriteBatch batch) { base.InternalDraw(batch); - float scale = 0.7f; + float scale = 0.7f / FSOEnvironment.DPIScaleFactor; Texture2D thumb = null; if (Thumb3D != null) { diff --git a/TSOClient/tso.client/UI/Panels/UIRelationshipDialog.cs b/TSOClient/tso.client/UI/Panels/UIRelationshipDialog.cs index 687dc5db8..27562b0f2 100644 --- a/TSOClient/tso.client/UI/Panels/UIRelationshipDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UIRelationshipDialog.cs @@ -1,15 +1,12 @@ -using FSO.Client.UI.Controls; -using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; -using FSO.Common.Rendering.Framework.Model; +using FSO.Client.Controllers; +using FSO.Client.Controllers.Panels; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; using FSO.Common.DataService.Model; -using System.Collections.Immutable; -using FSO.Client.Controllers; +using FSO.Common.Rendering.Framework.Model; using FSO.Common.Utils; -using FSO.Client.Controllers.Panels; +using Microsoft.Xna.Framework; +using System.Collections.Immutable; namespace FSO.Client.UI.Panels { @@ -65,7 +62,8 @@ public UIRelationshipDialog() ResultsBox.SetSize(510, 230); ResultsBox.RowHeight = 40; ResultsBox.NumVisibleRows = 6; - ResultsBox.SelectionFillColor = Color.TransparentBlack; + ResultsBox.SelectionFillColor = Color.Transparent; + ResultsBox.UseChildElements = true; Add(ResultsBox); var seat = new UIImage(GetTexture(0x19700000002)); @@ -220,7 +218,7 @@ private void SetOutgoing(bool mode) IncomingButton.Visible = mode; OutgoingButton.Visible = !mode; - IncomingLabel.Caption = GameFacade.Strings.GetString("f106", ((mode)?"9":"8")); + IncomingLabel.Caption = GameFacade.Strings.GetString("f106", ((mode) ? "9" : "8")); OutgoingMode = mode; RedrawRels(); } @@ -250,7 +248,7 @@ private int OrderEnemy(Relationship rel) private int OrderAlmostFriendly(Relationship rel) { - return Math.Abs(60-rel.Relationship_LTR); + return Math.Abs(60 - rel.Relationship_LTR); } private int OrderAlmostEnemy(Relationship rel) @@ -372,7 +370,7 @@ private void DrawRel(UISpriteBatch batch, int x, int y, int value) Color bgcol = new Color((byte)(57 * p + 214 * (1 - p)), (byte)(97 * p), (byte)(90 * p)); var Filler = TextureGenerator.GetPxWhite(batch.GraphicsDevice); - batch.Draw(Filler, LocalRect(x+1, y+1, 80, 6), new Color(23,38,55)); + batch.Draw(Filler, LocalRect(x + 1, y + 1, 80, 6), new Color(23, 38, 55)); batch.Draw(Filler, LocalRect(x, y, 80, 6), bgcol); batch.Draw(Filler, LocalRect(x, y, (int)(80 * p), 6), barcol); batch.Draw(Filler, LocalRect(x + (int)(80 * p), y, 1, 6), Color.Black); @@ -391,7 +389,7 @@ public override void Draw(UISpriteBatch batch) DrawRel(batch, 40, 18, Rel.Relationship_STR); DrawRel(batch, 40, 26, Rel.Relationship_LTR); - if (Indicator != null) DrawLocalTexture(batch, Indicator, new Rectangle(Indicator.Width / 4, 0, Indicator.Width/4, Indicator.Height), new Vector2(142, 17)); + if (Indicator != null) DrawLocalTexture(batch, Indicator, new Rectangle(Indicator.Width / 4, 0, Indicator.Width / 4, Indicator.Height), new Vector2(142, 17)); if (Icon.Tooltip != null) { diff --git a/TSOClient/tso.client/UI/Panels/UISandboxSelector.cs b/TSOClient/tso.client/UI/Panels/UISandboxSelector.cs index 12b1d22af..c10d1ad71 100644 --- a/TSOClient/tso.client/UI/Panels/UISandboxSelector.cs +++ b/TSOClient/tso.client/UI/Panels/UISandboxSelector.cs @@ -106,9 +106,9 @@ public UISandboxSelector() : base(UIDialogStyle.Close, true) casButton.Caption = "CAS"; casButton.OnButtonClick += (btn) => { - if (UIScreen.Current is SandboxGameScreen) + if (UIScreen.Current is SandboxGameScreen screen) { - ((SandboxGameScreen)UIScreen.Current).CleanupLastWorld(); + screen.CleanupLastWorld(); } FSOFacade.Controller.ShowPersonCreation(null); }; @@ -118,6 +118,8 @@ public UISandboxSelector() : base(UIDialogStyle.Close, true) Add(casButton); SetSize(300, 500); + + GameFacade.Screens.inputManager.SetFocus(BookmarkListBox); } public void LotSwitch(string location, bool external) diff --git a/TSOClient/tso.client/UI/Panels/UISetupBackground.cs b/TSOClient/tso.client/UI/Panels/UISetupBackground.cs index 0e5eac760..7738190ce 100644 --- a/TSOClient/tso.client/UI/Panels/UISetupBackground.cs +++ b/TSOClient/tso.client/UI/Panels/UISetupBackground.cs @@ -1,6 +1,7 @@ using FSO.Client.GameContent; using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; +using FSO.Common; using FSO.Common.Utils; using FSO.Files; using Microsoft.Xna.Framework; @@ -36,7 +37,7 @@ public UISetupBackground() // Validate that the listed splash screens exist. for (int i = 0; i < splashes.Length; i++) { - string path = Path.Combine("Content/SplashScreens/", splashes[i]); + string path = Path.Combine(FSOEnvironment.ContentDir, "SplashScreens", splashes[i]); if (File.Exists(path)) { diff --git a/TSOClient/tso.client/UI/Panels/UITerrainHighlight.cs b/TSOClient/tso.client/UI/Panels/UITerrainHighlight.cs index 11132086d..933783a26 100644 --- a/TSOClient/tso.client/UI/Panels/UITerrainHighlight.cs +++ b/TSOClient/tso.client/UI/Panels/UITerrainHighlight.cs @@ -27,11 +27,8 @@ internal static void DrawLine(Texture2D Fill, Vector2 Start, Vector2 End, Sprite if (x > 511 || y > 511) return null; - var f1 = terrain.Get2DFromTile(x, y); - var f2 = terrain.Get2DFromTile(x+1, y+1); - if (f1.X == float.MaxValue || f2.X == float.MaxValue) return Vector2.Zero; - var to = (terrain.Get2DFromTile(x, y) + terrain.Get2DFromTile(x+1, y+1)) / 2; - return to; + var to = terrain.Get2DFromTile(x + 0.5f, y + 0.5f); + return to.X == float.MaxValue ? null : (Vector2?)to; } public static void DrawArrow(UISpriteBatch batch, Terrain terrain, Vector2 from, int location, Color tint) diff --git a/TSOClient/tso.client/UI/Panels/UIUCP.cs b/TSOClient/tso.client/UI/Panels/UIUCP.cs index 653be32b1..13e241b7d 100644 --- a/TSOClient/tso.client/UI/Panels/UIUCP.cs +++ b/TSOClient/tso.client/UI/Panels/UIUCP.cs @@ -19,6 +19,7 @@ using FSO.Client.UI.Model; using FSO.LotView.Utils.Camera; using FSO.LotView.Model; +using FSO.SimAntics.NetPlay.Model.Commands; namespace FSO.Client.UI.Panels { @@ -87,6 +88,7 @@ public class UIUCP : UICachedContainer /// public UIButton BookmarkButton { get; set; } public UIButton FriendshipWebButton { get; set; } + public UIButton BudgetButton { get; set; } /// /// Labels @@ -109,6 +111,10 @@ public class UIUCP : UICachedContainer private int InboxFlashTime; private bool InboxFlashing; + + private int UserListFlashTime; + private bool UserListFlashing; + public bool SpecialMusic; public UIUCP(UIScreen owner) @@ -190,6 +196,23 @@ public UIUCP(UIScreen owner) SetFocus(UCPFocusMode.Game); } + public void InitArchive() + { + var ui = Content.Content.Get().CustomUI; + var gd = GameFacade.GraphicsDevice; + + BudgetButton.Tooltip = "User List"; + BudgetButton.Texture = ui.Get("archive_clientsbtn.png").Get(gd); + + BudgetButton.OnButtonClick += UserListButtonClick; + } + + private void UserListButtonClick(UIElement button) + { + var screen = (GameFacade.Screens.CurrentUIScreen as CoreGameScreen); + screen?.OpenUserList(); + } + private void HelpButton_OnButtonClick(UIElement button) { UIScreen.ShowDialog(new UIHintWindow(), true); @@ -412,28 +435,12 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta TimeText.Caption = hour.ToString() + ":" + ZeroPad(min.ToString(), 2) + " " + suffix; MoneyText.Caption = "$" + Game.VisualBudget.ToString("##,#0"); - if (InboxFlashing) - { - if ((InboxFlashTime++) > FSOEnvironment.RefreshRate/2) - { - if (PhoneButton.ForceState == 2) - { - PhoneButton.ForceState = -1; - Invalidate(); - } - } else - { - if (PhoneButton.ForceState != 2) - { - PhoneButton.ForceState = 2; - Invalidate(); - } - } - InboxFlashTime %= FSOEnvironment.RefreshRate; - } + AdvanceFlashing(InboxFlashing, ref InboxFlashTime, PhoneButton); + AdvanceFlashing(UserListFlashing, ref UserListFlashTime, BudgetButton); var keys = state.NewKeys; - var nofocus = state.InputManager.GetFocus() == null; + var focus = state.InputManager.GetFocus(); + var nofocus = focus == null || focus is UIButton; base.Update(state); if (Game.InLot && state.WindowFocused) { @@ -450,7 +457,7 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta if (activeCamera.UseRotateHold) { //if the zoom or rotation buttons are down, gradually change their values. - var cam = Game.vm.Context.World.State.Cameras.Camera3D; + var cam = cameras.Camera3D; if (RotateClockwiseButton.IsDown || state.KeyboardState.IsKeyDown(Keys.OemPeriod)) cam.RotationX += 2f / FSOEnvironment.RefreshRate; if (RotateCounterClockwiseButton.IsDown || state.KeyboardState.IsKeyDown(Keys.OemComma)) cam.RotationX -= 2f / FSOEnvironment.RefreshRate; } @@ -478,6 +485,30 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta if (keys.Contains(Keys.F5)) SetPanel(5); // Options Mode Panel } + private void AdvanceFlashing(bool flash, ref int flashTime, UIButton button) + { + if (flash) + { + if ((flashTime++) > FSOEnvironment.RefreshRate / 2) + { + if (button.ForceState == 2) + { + button.ForceState = -1; + Invalidate(); + } + } + else + { + if (button.ForceState != 2) + { + button.ForceState = 2; + Invalidate(); + } + } + flashTime %= FSOEnvironment.RefreshRate; + } + } + public void FlashInbox(bool flash) { InboxFlashing = flash; @@ -488,6 +519,16 @@ public void FlashInbox(bool flash) } } + public void FlashUserList(bool flash) + { + UserListFlashing = flash; + UserListFlashTime = 0; + if (!flash) + { + BudgetButton.ForceState = -1; + } + } + private string ZeroPad(string input, int digits) { while (input.Length < digits) @@ -566,11 +607,22 @@ public void SetPanel(int newPanel) { if (CurrentPanel != -1) { + var permissions = (Game.vm?.GetAvatarByPersist(Game.vm.MyUID)?.TSOState as VMTSOAvatarState)?.Permissions ?? VMTSOAvatarPermissions.Visitor; switch (CurrentPanel) { case 3: case 2: - if (Game.InLot && Game.vm.TSOState.Roommates.Contains(Game.vm.MyUID)) FindController()?.UploadLotThumbnail(); + var changes = (Panel as UIAbstractCatalogPanel)?.AnyChanges ?? false; + if (permissions >= VMTSOAvatarPermissions.Roommate && changes) + { + var isBuild = CurrentPanel == 3 && permissions >= VMTSOAvatarPermissions.BuildBuyRoommate; + Game.vm?.SendCommand(new VMNetLeaveBuildBuyCmd() + { + Build = isBuild + }); + + FindController()?.UploadLotThumbnail(isBuild); + } break; } DynamicOverlay.Remove(Panel); diff --git a/TSOClient/tso.client/UI/Panels/UIWebDownloaderDialog.cs b/TSOClient/tso.client/UI/Panels/UIWebDownloaderDialog.cs index eaf81a25c..87c2e9424 100644 --- a/TSOClient/tso.client/UI/Panels/UIWebDownloaderDialog.cs +++ b/TSOClient/tso.client/UI/Panels/UIWebDownloaderDialog.cs @@ -1,7 +1,6 @@ using FSO.Common.Utils; -using System; -using System.IO; using System.Net; +using System.Security.Cryptography; namespace FSO.Client.UI.Panels { @@ -12,12 +11,15 @@ public class UIWebDownloaderDialog : UILoginProgress private int CurrentItem; private DownloadItem ItemMeta; - public event Callback OnComplete; + public delegate void OnCompleteEvent(bool success, string failedFile = null); + + public event OnCompleteEvent OnComplete; public UIWebDownloaderDialog(string title, DownloadItem[] items) : base() { if (title != null) Caption = title; else Caption = GameFacade.Strings.GetString("f101", "9"); + ProgressCaption = ""; Items = items; DownloadClient = new WebClient(); @@ -41,13 +43,55 @@ private void DownloadClient_DownloadProgressChanged(object sender, DownloadProgr }); } + private void DeleteFiles() + { + foreach (var item in Items) + { + if (File.Exists(item.DestPath)) + { + File.Delete(item.DestPath); + } + } + } + + private void Failure(string failedFile = null) + { + DeleteFiles(); + + GameThread.NextUpdate(x => OnComplete?.Invoke(false, failedFile)); + } + private void DownloadClient_DownloadFileCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e) { if (e.Error != null || e.Cancelled) { - GameThread.NextUpdate(x => OnComplete?.Invoke(false)); + Failure(ItemMeta.Name); return; } + + if (ItemMeta.Size != 0) + { + var size = new FileInfo(ItemMeta.DestPath).Length; + + if (size != ItemMeta.Size) + { + Failure(ItemMeta.Name); + return; + } + } + + if (ItemMeta.Hash != null) + { + using FileStream file = File.OpenRead(ItemMeta.DestPath); + var hash = SHA256.HashData(file); + + if (Convert.ToBase64String(hash) != ItemMeta.Hash) + { + Failure(ItemMeta.Name); + return; + } + } + AdvanceDownloader(); } @@ -70,5 +114,8 @@ public class DownloadItem public string Url; public string DestPath; public string Name; + + public int Size; + public string Hash; } } diff --git a/TSOClient/tso.client/UI/Panels/UIZipExtractDialog.cs b/TSOClient/tso.client/UI/Panels/UIZipExtractDialog.cs new file mode 100644 index 000000000..6feac0320 --- /dev/null +++ b/TSOClient/tso.client/UI/Panels/UIZipExtractDialog.cs @@ -0,0 +1,63 @@ +using FSO.Client.Utils; +using FSO.Common.Utils; +using System.IO; + +namespace FSO.Client.UI.Panels +{ + public class UIZipExtractDialog : UILoginProgress + { + private AbstractExtractor _zipExtractor; + private string _zipPath; + private string _destPath; + + public event Callback OnComplete; + + public UIZipExtractDialog(string title, string zipPath, string destPath) : base() + { + _zipPath = zipPath; + _destPath = destPath; + + if (title != null) Caption = title; + else Caption = GameFacade.Strings.GetString("f128", "7"); + } + + public void Start() where T : AbstractExtractor, new() + { + _zipExtractor = new T(); + _zipExtractor.Start(_zipPath, _destPath, OnUpdate); + } + + private void OnUpdate(ZipExtractionStatus status, int extractedCount, int totalCount) + { + GameThread.NextUpdate(x => + { + string name = _zipExtractor.Filename; + + if (status == ZipExtractionStatus.Completed) + { + OnComplete?.Invoke(true, null); + } + else if (status == ZipExtractionStatus.Preparing) + { + ProgressCaption = GameFacade.Strings.GetString("f128", "13", new string[] { + name, + totalCount.ToString(), + }); + } + else if (status == ZipExtractionStatus.Extracting) + { + Progress = (100f * extractedCount) / totalCount; + ProgressCaption = GameFacade.Strings.GetString("f128", "12", new string[] { + name, + extractedCount.ToString(), + totalCount.ToString(), + }); + } + else + { + OnComplete?.Invoke(false, _zipExtractor.Error); + } + }); + } + } +} diff --git a/TSOClient/tso.client/UI/Profile/UIJobInfo.cs b/TSOClient/tso.client/UI/Profile/UIJobInfo.cs index 94b57f14c..0488867d8 100644 --- a/TSOClient/tso.client/UI/Profile/UIJobInfo.cs +++ b/TSOClient/tso.client/UI/Profile/UIJobInfo.cs @@ -67,6 +67,17 @@ public JobInformation(int jobGrade, int jobType, int jobExperience) PromotionPercentage = (int)(promotionPercentage * 100); MaxLevel = (jobGrade == 10); } + + public static JobInformation FromLotInfo(int jobGrade, int jobLotType) + { + int type = jobLotType switch + { + 2 => 4, + _ => jobLotType + 1 + }; + + return new JobInformation(jobGrade, type, 0); + } } public class UIJobInfo : UIAlert diff --git a/TSOClient/tso.client/UI/Screens/ArchivePersonSelection.cs b/TSOClient/tso.client/UI/Screens/ArchivePersonSelection.cs new file mode 100644 index 000000000..f17c15fb5 --- /dev/null +++ b/TSOClient/tso.client/UI/Screens/ArchivePersonSelection.cs @@ -0,0 +1,911 @@ +using FSO.Client.Controllers; +using FSO.Client.Properties; +using FSO.Client.Regulators; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Framework.Parser; +using FSO.Client.UI.Model; +using FSO.Client.UI.Panels; +using FSO.Client.Utils; +using FSO.Common; +using FSO.Common.Utils; +using FSO.Files; +using FSO.HIT; +using FSO.Server.Clients; +using FSO.Server.Protocol.CitySelector; +using FSO.Server.Protocol.Electron.Packets; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Text; + +namespace FSO.Client.UI.Screens +{ + public class ArchivePersonSelection : GameScreen, IArchiveCharacterSelector + { + /// + /// Values from the UIScript + /// + public Texture2D BackgroundImage { get; set; } + public Texture2D BackgroundImageDialog { get; set; } + + public Texture2D SimCreateButtonImage { get; set; } + public Texture2D SimSelectButtonImage { get; set; } + public Texture2D HouseButtonTemplateImage { get; set; } + public Texture2D CityButtonTemplateImage { get; set; } + public Texture2D CityHouseButtonAlpha { get; set; } + + public UIButton CreditsButton { get; set; } + private ArchivePersonSlot PersonSlot { get; set; } + private UIButton m_ExitButton; + + public ApiClient Api; + + public LoginRegulator LoginRegulator; + public UIButton CASButton { get; set; } + public UIButton AcceptButton { get; set; } + + public UITextBox SearchBox; + public UIListBox AvatarListBox; + public UIImage ListBackground; + public UILabel StatusLabel; + + private UIListBoxTextStyle ListBoxColors; + private ArchiveAvatarsResponse Data; + + public UILabel SearchLabel; + public UILabel ListNameLabel; + public UILabel ListLotLabel; + + public Texture2D SimIconShared; + public Texture2D SimIconOwned; + public Texture2D SimIconRecent; + + public UILabel TitleLabel { get; set; } + + public ArchivePersonSelection() : base() + { + var offset = (new Vector2(1024, 768) - new Vector2(800, 600)) / 2; + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; + + BackgroundImageDialog = custom.Get("archive_sasbg.png").Get(gd); + + SimIconShared = custom.Get("archive_simshared.png").Get(gd); + SimIconOwned = custom.Get("archive_simowned.png").Get(gd); + SimIconRecent = custom.Get("archive_simrecent.png").Get(gd); + + //Arrange UI + Api = new ApiClient(ApiClient.CDNUrl ?? GlobalSettings.Default.GameEntryUrl); + + UIScript ui = null; + ui = this.RenderScript("archivepersonselection1024.uis"); + + Position = new Vector2(ScaleX * (GlobalSettings.Default.GraphicsWidth - 1024) / 2, ScaleY * (GlobalSettings.Default.GraphicsHeight - 768) / 2); + + m_ExitButton = (UIButton)ui["ExitButton"]; + + var numSlots = 1; + + TitleLabel.Alignment = TextAlignment.Center | TextAlignment.Middle; + TitleLabel.Size = new Vector2(620, 20); // For some reason, this changes to 525x20, so I need to change it back? + + for (var i = 0; i < numSlots; i++) + { + var index = (i + 1).ToString(); + + /** Tab Background **/ + var tabBackground = ui.Create("TabBackgroundImage" + index); + this.Add(tabBackground); + + tabBackground.With9Slice(0, 0, 75, 50); + tabBackground.Height += 18; + + var enterTabImage = ui.Create("EnterTabImage" + index); + this.Add(enterTabImage); + + var descTabImage = ui.Create("DescriptionTabImage" + index); + this.Add(descTabImage); + + var descTabBgImage = ui.Create("DescriptionTabBackgroundImage" + index); + var enterIcons = ui.Create("EnterTabBackgroundImage" + index); + + var personSlot = new ArchivePersonSlot(this) + { + AvatarButton = (UIButton)ui["AvatarButton" + index], + CityButton = (UIButton)ui["CityButton" + index], + HouseButton = (UIButton)ui["HouseButton" + index], + EnterTabButton = (UIButton)ui["EnterTabButton" + index], + DescTabButton = (UIButton)ui["DescriptionTabButton" + index], + NewAvatarButton = (UIButton)ui["NewAvatarButton" + index], + DeleteAvatarButton = (UIButton)ui["DeleteAvatarButton" + index], + PersonNameText = (UILabel)ui["PersonNameText" + index], + PersonDescriptionScrollUpButton = (UIButton)ui["PersonDescriptionScrollUpButton" + index], + PersonDescriptionScrollDownButton = (UIButton)ui["PersonDescriptionScrollDownButton" + index], + PersonDescriptionSlider = (UISlider)ui["PersonDescriptionSlider" + index], + CityNameText = (UILabel)ui["CityNameText" + index], + HouseNameText = (UILabel)ui["HouseNameText" + index], + PersonDescriptionText = (UITextEdit)ui["PersonDescriptionText" + index], + DescriptionTabBackgroundImage = descTabBgImage, + EnterTabBackgroundImage = enterIcons, + + TabBackground = tabBackground, + TabEnterBackground = enterTabImage, + TabDescBackground = descTabImage + }; + + this.AddBefore(descTabBgImage, personSlot.PersonDescriptionText); + this.AddBefore(enterIcons, personSlot.CityButton); + + personSlot.Init(); + personSlot.SetSlotAvailable(true); + PersonSlot = personSlot; + } + + /** Backgrounds **/ + var bg = new UIImage(BackgroundImage).With9Slice(128, 128, 84, 84); + this.AddAt(0, bg); + bg.SetSize(GlobalSettings.Default.GraphicsWidth, GlobalSettings.Default.GraphicsHeight); + bg.Position = new Vector2((GlobalSettings.Default.GraphicsWidth - 1024) / -2, (GlobalSettings.Default.GraphicsHeight - 768) / -2); + Background = bg; + + if (BackgroundImageDialog != null) + { + this.AddAt(1, new UIImage(BackgroundImageDialog) + { + X = 112, + Y = 84 + }); + } + + /** Archive controls **/ + + var titleFont = TextStyle.DefaultLabel.Clone(); + titleFont.Size = 10; + titleFont.Shadow = true; + + Add(SearchLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "332"), + Position = new Vector2(394, 92) + offset, + CaptionStyle = titleFont + }); + + Add(ListNameLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "333"), + Position = new Vector2(397, 230) + offset, + CaptionStyle = titleFont + }); + + Add(ListNameLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "334"), + Position = new Vector2(397 + 163, 230) + offset, + CaptionStyle = titleFont + }); + + var entryFont = TextStyle.DefaultLabel.Clone(); + entryFont.Size = 12; + + var listTex = custom.Get("archive_translist.png").Get(gd); + + Add(SearchBox = new UITextBox() + { + Position = new Vector2(385, 112) + offset, + TextStyle = entryFont + }); + + SearchBox.SetBackgroundTexture(listTex, 13, 13, 13, 13); + SearchBox.SetSize(304, 39); + SearchBox.TextMargin = new Rectangle(14, 8, 14, 8); + + var searchFont = TextStyle.DefaultLabel.Clone(); + searchFont.Size = 10; + + ListBoxColors = new UIListBoxTextStyle(searchFont) + { + NormalColor = new Color(247, 232, 145), + SelectedColor = new Color(0, 0, 0), + HighlightedColor = new Color(255, 255, 255), + DisabledColor = new Color(150, 150, 150) + }; + + ListBackground = new UIImage(listTex).With9Slice(13, 13, 13, 13); + ListBackground.Position = new Vector2(365, 250) + offset; + ListBackground.SetSize(358, 315); + Add(ListBackground); + + Add(AvatarListBox = new UIListBox() + { + Size = ListBackground.Size - new Vector2(20, 20), + Position = ListBackground.Position + new Vector2(10, 10), + Mask = true, + VisibleRows = 15, + Columns = new UIListBoxColumnCollection() + { + new UIListBoxColumn() { Width = 22, Alignment = TextAlignment.Left | TextAlignment.Middle }, + new UIListBoxColumn() { Width = 163, Alignment = TextAlignment.Left | TextAlignment.Middle }, + new UIListBoxColumn() { Width = 163, Alignment = TextAlignment.Left | TextAlignment.Middle } + }, + FontStyle = searchFont, + SelectionFillColor = new Color(250, 200, 140), + ScrollbarImage = GetTexture(0x31000000001), + ScrollbarGutter = 17, + RowHeight = 20 + }); + + AvatarListBox.InitDefaultSlider(); + + var statusStyle = TextStyle.DefaultLabel.Clone(); + statusStyle.Shadow = true; + + Add(StatusLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f128", "330"), + Position = AvatarListBox.Position, + Size = AvatarListBox.Size, + Wrapped = true, + Alignment = TextAlignment.Center | TextAlignment.Middle, + CaptionStyle = statusStyle, + }); + + AvatarListBox.OnChange += ChangedSelectedAvatar; + + /** + * Button plumbing + */ + CreditsButton.OnButtonClick += new ButtonClickDelegate(CreditsButton_OnButtonClick); + CreditsButton.Tooltip = GameFacade.Strings.GetString("f128", "125"); + m_ExitButton.OnButtonClick += new ButtonClickDelegate(m_ExitButton_OnButtonClick); + CASButton.OnButtonClick += OpenCAS; + CASButton.Disabled = true; + AcceptButton.OnButtonClick += AcceptSelection; + AcceptButton.Disabled = true; + SearchBox.OnChange += (elem) => + { + RefreshList(); + }; + + ControllerUtils.BindController(this); + + FindController().Refresh(); + + GameFacade.Screens.inputManager.SetFocus(SearchBox); + + /** + * Music + */ + + HITVM.Get().PlaySoundEvent(UIMusic.SAS); + + GameThread.NextUpdate(x => + { + // TODO: archive SAS hint + //FSOFacade.Hints.TriggerHint("screen:sas"); + }); + } + + private void AcceptSelection(UIElement button) + { + SelectAvatar(button, false); + } + + private void ChangedSelectedAvatar(UIElement element) + { + PersonSlot.AvatarButton.Disabled = AvatarListBox.SelectedItem == null; + AcceptButton.Disabled = PersonSlot.AvatarButton.Disabled; + + if (PersonSlot.AvatarButton.Disabled) + { + PersonSlot.SetSlotAvailable(true); + } + else + { + var ava = (ArchiveAvatar)AvatarListBox.SelectedItem.Data; + PersonSlot.DisplayAvatar(ava); + } + } + + public void SelectAvatar(Framework.UIElement button, bool gotoHouse) + { + if (AvatarListBox.SelectedItem == null) + { + return; + } + + var ava = (ArchiveAvatar)AvatarListBox.SelectedItem.Data; + FindController().SelectAvatar(ava.AvatarId, gotoHouse ? ava.LotId : 0u); + } + + public void OpenCAS(Framework.UIElement button) + { + FSOFacade.Controller.GotoCAS(true); + } + + public void SetData(ArchiveAvatarsResponse data) + { + Data = data; + + CASButton.Disabled = !data.CasEnabled; + PersonSlot.SetCasEnabled(data.CasEnabled); + + if (!data.IsVerified) + { + StatusLabel.Visible = true; + StatusLabel.Caption = GameFacade.Strings.GetString("f128", "331"); + } + else + { + StatusLabel.Visible = false; + } + + RefreshList(); + } + + public void RefreshList() + { + var query = (SearchBox.CurrentText ?? "").ToLower(); + + if (Data == null) + { + // Empty the list + AvatarListBox.Items.Clear(); + } + else + { + var recentIds = Data.RecentAvatars; + + var myItems = Data.UserAvatars + .Where(x => x.Name.ToLower().Contains(query) || x.LotName.ToLower().Contains(query)) + .Select((ArchiveAvatar x) => + { + return new UIListBoxItem(x, new object[] { SimIconOwned, x.Name, x.LotName }) + { + CustomStyle = ListBoxColors, + }; + }); + + var recentSorted = new ArchiveAvatar[Data.RecentAvatars.Length]; + + foreach (var shared in Data.SharedAvatars) + { + int index = Array.IndexOf(recentIds, shared.AvatarId); + if (index != -1) + { + recentSorted[index] = shared; + } + } + + var recentItems = recentSorted + .Where(x => x.AvatarId != 0 && (x.Name.ToLower().Contains(query) || x.LotName.ToLower().Contains(query))) + .Select((ArchiveAvatar x) => + { + return new UIListBoxItem(x, new object[] { SimIconRecent, x.Name, x.LotName }) + { + CustomStyle = ListBoxColors, + }; + }); + + var sharedItems = Data.SharedAvatars + .Where(x => !recentIds.Contains(x.AvatarId) && (x.Name.ToLower().Contains(query) || x.LotName.ToLower().Contains(query))) + .Select((ArchiveAvatar x) => + { + return new UIListBoxItem(x, new object[] { SimIconShared, x.Name, x.LotName }) + { + CustomStyle = ListBoxColors, + }; + }); + + AvatarListBox.Items.Clear(); + + AvatarListBox.Items.AddRange(myItems); + AvatarListBox.Items.AddRange(recentItems); + AvatarListBox.Items.AddRange(sharedItems); + } + + AvatarListBox.Items = AvatarListBox.Items; + Invalidate(); + } + + private UIImage Background; + + public override void GameResized() + { + base.GameResized(); + Position = new Vector2(ScaleX * (GlobalSettings.Default.GraphicsWidth - 1024) / 2, ScaleY * (GlobalSettings.Default.GraphicsHeight - 768) / 2); + Background.SetSize(GlobalSettings.Default.GraphicsWidth, GlobalSettings.Default.GraphicsHeight); + Background.Position = new Vector2((GlobalSettings.Default.GraphicsWidth - 1024) / -2, (GlobalSettings.Default.GraphicsHeight - 768) / -2); + InvalidateMatrix(); + Parent?.InvalidateMatrix(); + } + + public void AsyncAPILotThumbnail(uint shardId, uint lotId, Action callback) + { + Api.GetThumbnailAsync(shardId, lotId, (data) => + { + if (data != null) + { + GameThread.NextUpdate(x => + { + if (UIScreen.Current != this) return; + using (var mem = new MemoryStream(data)) + { + try + { + callback(ImageLoader.FromStream(GameFacade.GraphicsDevice, mem)); + } catch + { + + } + } + }); + } + }); + } + + public Texture2D GetLotThumbnail(string shardName, uint lotId) + { + // TODO: accesses the resource action regulator + + var thumb = TextureUtils.TextureFromFile(GameFacade.GraphicsDevice, GameFacade.GameFilePath("userdata/houses/defaulthouse.bmp")); + TextureUtils.ManualTextureMask(ref thumb, new uint[] { 0xFF000000 }); + return thumb; + } + + /// + /// Device was reset, SceneManager called Content.Unload(), so reload everything. + /// + /// The device. + public override void DeviceReset(GraphicsDevice Device) + { + PersonSlot.DeviceReset(Device); + CalculateMatrix(); + } + + private void m_ExitButton_OnButtonClick(UIElement button) + { + UIScreen.ShowDialog(new UIExitDialog(), true); + } + + private void CreditsButton_OnButtonClick(UIElement button) + { + /** Show the credits screen **/ + FSOFacade.Controller.ShowCredits(); + } + + public void ShowCitySelector(List shards, Callback onOk) + { + var cityPicker = new UICitySelector(shards); + cityPicker.OkButton.OnButtonClick += (UIElement btn) => + { + onOk(cityPicker.SelectedShard); + }; + ShowDialog(cityPicker, true); + } + + public void ShowSelectionError(ArchiveAvatarSelectCode code) + { + UIAlert alert = null; + alert = GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("f128", "100"), + Message = GameFacade.Strings.GetString("f128", (100 + (int)code).ToString()) , + Buttons = UIAlertButton.Ok(x => { + RemoveDialog(alert); + }), + }, true); + } + } + + public class ArchivePersonSlot + { + public UIButton CityButton { get; set; } + public UIButton AvatarButton { get; set; } + public UIButton HouseButton { get; set; } + public UIButton EnterTabButton { get; set; } + public UIButton DescTabButton { get; set; } + public UIButton NewAvatarButton { get; set; } + public UIButton DeleteAvatarButton { get; set; } + + public UIImage TabBackground { get; set; } + public UIImage TabEnterBackground { get; set; } + public UIImage TabDescBackground { get; set; } + public UIImage EnterTabBackgroundImage { get; set; } + + public UILabel PersonNameText { get; set; } + public UILabel CityNameText { get; set; } + public UILabel HouseNameText { get; set; } + + public UIButton PersonDescriptionScrollUpButton { get; set; } + public UIButton PersonDescriptionScrollDownButton { get; set; } + public UISlider PersonDescriptionSlider { get; set; } + public UITextEdit PersonDescriptionText { get; set; } + public UIImage DescriptionTabBackgroundImage { get; set; } + + private ArchivePersonSelection Screen { get; set; } + public ArchiveAvatar? Avatar; + private UIImage CityThumb { get; set; } + private UIImage HouseThumb { get; set; } + + private UISim Sim; + + private PersonSlotTab _tab = PersonSlotTab.EnterTab; + private uint CityThumbShard = uint.MaxValue; + + public ArchivePersonSlot(ArchivePersonSelection screen) + { + this.Screen = screen; + } + + /// + /// Setup UI events + /// + public void Init() + { + int offset = 9; + CityButton.Y += offset; + HouseButton.Y += offset; + NewAvatarButton.Y += offset; + DeleteAvatarButton.Y += offset; + CityNameText.Y += offset; + HouseNameText.Y += offset; + + PersonDescriptionText.Y += offset; + PersonDescriptionSlider.Y += offset; + PersonDescriptionScrollUpButton.Y += offset; + PersonDescriptionScrollDownButton.Y += offset; + + EnterTabBackgroundImage.Y += offset; + DescriptionTabBackgroundImage.Y += offset; + + /** Textures **/ + AvatarButton.Texture = Screen.SimCreateButtonImage; + CityButton.Texture = Screen.CityButtonTemplateImage; + HouseButton.Texture = Screen.HouseButtonTemplateImage; + + /** Send tab stuff to the bottom **/ + Screen.SendToBack(TabBackground, TabEnterBackground, TabDescBackground); + + /** Events **/ + EnterTabButton.OnButtonClick += new ButtonClickDelegate(EnterTabButton_OnButtonClick); + DescTabButton.OnButtonClick += new ButtonClickDelegate(DescTabButton_OnButtonClick); + + NewAvatarButton.OnButtonClick += new ButtonClickDelegate(this.Screen.OpenCAS); + DeleteAvatarButton.OnButtonClick += new ButtonClickDelegate(DeleteAvatarButton_OnButtonClick); + + PersonDescriptionSlider.AttachButtons(PersonDescriptionScrollUpButton, PersonDescriptionScrollDownButton, 1); + PersonDescriptionText.AttachSlider(PersonDescriptionSlider); + + CityThumb = new UIImage + { + X = CityButton.X + 6, + Y = CityButton.Y + 6 + }; + CityThumb.SetSize(78, 58); + Screen.Add(CityThumb); + + + HouseThumb = new UIImage + { + X = HouseButton.X + 6, + Y = HouseButton.Y + 6 + }; + HouseThumb.SetSize(78, 58); + Screen.Add(HouseThumb); + + Sim = new UISim(); + Sim.Visible = false; + Sim.Position = AvatarButton.Position + new Vector2(1, 10); + Sim.Size = new Vector2(140, 200); + + Screen.Add(Sim); + SetTab(PersonSlotTab.EnterTab); + + AvatarButton.OnButtonClick += new ButtonClickDelegate(OnSelect); + CityButton.OnButtonClick += new ButtonClickDelegate(OnSelect); + HouseButton.OnButtonClick += new ButtonClickDelegate(OnSelect); + + SetCasEnabled(false); + } + + void OnSelect(UIElement button) + { + this.Screen.SelectAvatar(button, button == HouseButton); + } + + private Texture2D DefaultHouseTex() + { + var thumb = TextureUtils.TextureFromFile(GameFacade.GraphicsDevice, GameFacade.GameFilePath("userdata/houses/defaulthouse.bmp")); + TextureUtils.ManualTextureMask(ref thumb, new uint[] { 0xFF000000 }); + + return thumb; + } + + /// + /// User clicked the "Retire avatar" button. + /// + private void DeleteAvatarButton_OnButtonClick(UIElement button) + { + // TODO: deletion of avatars + return; + + /** + if (Avatar == null) + { + return; + } + + UIAlertOptions AlertOptions = new UIAlertOptions(); + UIAlert alert = null; + + AlertOptions.Title = GameFacade.Strings.GetString("169", "9"); + AlertOptions.Message = GameFacade.Strings.GetString("169", "10"); + AlertOptions.Buttons = new UIAlertButton[] { + new UIAlertButton(UIAlertButtonType.OK, (btn) => { + FSOFacade.Controller.RetireAvatar(Avatar.ShardName, Avatar.ID); + }), + new UIAlertButton(UIAlertButtonType.Cancel) + }; + + alert = UIScreen.GlobalShowAlert(AlertOptions, true); + **/ + } + + public void DisplayAvatar(ArchiveAvatar avatar) + { + this.Avatar = avatar; + + SetSlotAvailable(false); + + PersonNameText.Caption = avatar.Name; + //PersonDescriptionText.CurrentText = avatar.Description; + AvatarButton.Texture = Screen.SimSelectButtonImage; + + + var shard = Screen.FindController().Shard; + + CityNameText.Caption = shard.ShardName; + + HouseNameText.Caption = avatar.LotName; + HouseThumb.Texture?.Dispose(); + HouseThumb.Texture = null; + + SetTab(_tab); + + Sim.Avatar.Appearance = (Vitaboy.AppearanceType)avatar.Type; + Sim.Avatar.BodyOutfitId = avatar.Body; + Sim.Avatar.HeadOutfitId = avatar.Head; + + Sim.Visible = true; + + PersonDescriptionText.CurrentText = GameFacade.Strings.GetString("f128", "330"); + + AsyncFetchAvatarData(1); // TODO: shard ID + AsyncFetchCityThumbData(1); // TODO: shard ID + } + + private Texture2D MaskCityThumb(Texture2D thumb) + { + Texture2D cityThumbTex = + TextureUtils.Resize( + GameFacade.GraphicsDevice, + thumb, + 78, + 58); + TextureUtils.CopyAlpha(ref cityThumbTex, Screen.CityHouseButtonAlpha); + return cityThumbTex; + } + + private int RequestNum = 0; + + private void PrepareDefaultCityThumb() + { + var map = "0100"; // TODO: from archive + + var cityThumb = (int.Parse(map) >= 100) ? + Path.Combine(FSOEnvironment.ContentDir, "Cities/city_" + map + "/thumbnail.png") + : GameFacade.GameFilePath("cities/city_" + map + "/thumbnail.bmp"); + + try + { + Texture2D cityThumbTex = TextureUtils.TextureFromFile(GameFacade.GraphicsDevice, cityThumb); + CityThumb.Texture = MaskCityThumb(cityThumbTex); + cityThumbTex.Dispose(); + } + catch + { + // No city texture? + } + } + + private void AsyncFetchCityThumbData(uint shardID) + { + if (CityThumbShard == shardID) + { + return; + } + + CityThumbShard = shardID; + + PrepareDefaultCityThumb(); + + var res = Screen.FindController().CityResource; + + res.GetCityThumbnailAsync(shardID, (data) => + { + if (data != null) + { + try + { + Texture2D tex; + + using (var mem = new MemoryStream(data)) + { + tex = ImageLoader.FromStream(GameFacade.GraphicsDevice, mem); + } + + CityThumb.Texture = MaskCityThumb(tex); + tex.Dispose(); + } + catch + { + // Leave the existing texture. + } + } + }); + } + + private void AsyncFetchAvatarData(uint shardID) + { + var res = Screen.FindController().CityResource; + var myNum = ++RequestNum; + + if (Avatar.Value.LotId != 0) + { + res.GetThumbnailAsync(shardID, Avatar.Value.LotId, (data) => + { + if (RequestNum != myNum) + { + return; + } + + if (data == null) + { + HouseThumb.Texture = DefaultHouseTex(); + } + else + { + try + { + Texture2D tex; + + using (var mem = new MemoryStream(data)) + { + tex = ImageLoader.FromStream(GameFacade.GraphicsDevice, mem); + } + + HouseThumb.Texture = tex; + } + catch + { + HouseThumb.Texture = DefaultHouseTex(); + } + } + + HouseThumb.Y += HouseThumb.Size.Y / 2; + HouseThumb.SetSize(HouseThumb.Size.X, (int)(HouseThumb.Size.X * ((double)HouseThumb.Texture.Height / HouseThumb.Texture.Width))); + HouseThumb.Y -= HouseThumb.Size.Y / 2; + }); + } + + res.GetAvatarDescriptionAsync(shardID, Avatar.Value.AvatarId, (data) => + { + if (RequestNum != myNum) + { + return; + } + + PersonDescriptionText.CurrentText = data == null ? "" : Encoding.UTF8.GetString(data); + }); + } + + public void SetSlotAvailable(bool isAvailable) + { + if (isAvailable) + { + this.Avatar = null; + } + + EnterTabButton.Disabled = isAvailable; + if (isAvailable) EnterTabButton.Selected = false; + DescTabButton.Disabled = isAvailable; + + NewAvatarButton.Visible = isAvailable; + DeleteAvatarButton.Visible = !isAvailable; + + if (isAvailable) + { + TabEnterBackground.Visible = false; + TabDescBackground.Visible = false; + TabBackground.Visible = false; + CityButton.Visible = false; + HouseButton.Visible = false; + PersonDescriptionScrollUpButton.Visible = false; + PersonDescriptionScrollDownButton.Visible = false; + HouseNameText.Visible = false; + CityNameText.Visible = false; + DescriptionTabBackgroundImage.Visible = false; + EnterTabBackgroundImage.Visible = false; + PersonDescriptionSlider.Visible = false; + PersonDescriptionText.Visible = false; + + Sim.Visible = false; + HouseThumb.Visible = false; + CityThumb.Visible = false; + PersonNameText.Visible = false; + + AvatarButton.Texture = Screen.SimCreateButtonImage; + } + else + { + Sim.Visible = true; + HouseThumb.Visible = true; + CityThumb.Visible = true; + PersonNameText.Visible = true; + TabBackground.Visible = true; + } + } + + public void SetTab(PersonSlotTab tab) + { + _tab = tab; + var isEnter = tab == PersonSlotTab.EnterTab; + TabEnterBackground.Visible = isEnter; + TabDescBackground.Visible = !isEnter; + + EnterTabButton.Selected = isEnter; + DescTabButton.Selected = !isEnter; + + CityNameText.Visible = isEnter; + CityButton.Visible = isEnter; + EnterTabBackgroundImage.Visible = isEnter; + CityThumb.Visible = isEnter; + HouseThumb.Visible = isEnter; + + PersonDescriptionScrollUpButton.Visible = !isEnter; + PersonDescriptionScrollDownButton.Visible = !isEnter; + + PersonDescriptionSlider.Visible = !isEnter; + DeleteAvatarButton.Visible = !isEnter; + PersonDescriptionText.Visible = !isEnter; + DescriptionTabBackgroundImage.Visible = !isEnter; + + var hasLot = Avatar != null && Avatar.Value.LotId != 0; + + HouseNameText.Visible = isEnter && hasLot; + HouseButton.Visible = isEnter && hasLot; + } + + private void DescTabButton_OnButtonClick(UIElement button) + { + SetTab(PersonSlotTab.DescriptionTab); + } + + private void EnterTabButton_OnButtonClick(UIElement button) + { + SetTab(PersonSlotTab.EnterTab); + } + + public void SetCasEnabled(bool enabled) + { + NewAvatarButton.Disabled = !enabled; + } + + public void DeviceReset(GraphicsDevice device){ + if (this.Avatar.HasValue) + { + DisplayAvatar(this.Avatar.Value); + } + } + } +} diff --git a/TSOClient/tso.client/UI/Screens/CoreGameScreen.cs b/TSOClient/tso.client/UI/Screens/CoreGameScreen.cs index 4fda1bfd3..295054131 100644 --- a/TSOClient/tso.client/UI/Screens/CoreGameScreen.cs +++ b/TSOClient/tso.client/UI/Screens/CoreGameScreen.cs @@ -1,41 +1,47 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using FSO.Client.Controllers; +using FSO.Client.Controllers.Panels; +using FSO.Client.Debug; +using FSO.Client.Rendering; +using FSO.Client.Rendering.City; +using FSO.Client.UI.Archive; +using FSO.Client.UI.Controls; using FSO.Client.UI.Framework; -using FSO.Client.UI.Panels; using FSO.Client.UI.Model; -using FSO.Client.Rendering.City; -using Microsoft.Xna.Framework; +using FSO.Client.UI.Panels; +using FSO.Client.UI.Panels.CityPainter; +using FSO.Client.UI.Panels.Neighborhoods; +using FSO.Client.UI.Panels.WorldUI; using FSO.Client.Utils; -using FSO.Common.Rendering.Framework.Model; -using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Domain.Realestate; +using FSO.Common.Domain.RealestateDomain; +using FSO.Common.Model; using FSO.Common.Rendering.Framework; +using FSO.Common.Rendering.Framework.IO; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; +using FSO.HIT; using FSO.LotView; +using FSO.LotView.Components; using FSO.LotView.Model; +using FSO.LotView.Utils.Camera; +using FSO.Server.Clients; using FSO.SimAntics; -using FSO.HIT; +using FSO.SimAntics.NetPlay; using FSO.SimAntics.NetPlay.Drivers; using FSO.SimAntics.NetPlay.Model.Commands; -using FSO.SimAntics.NetPlay; -using FSO.Client.UI.Controls; -using FSO.Client.Controllers; -using FSO.Client.Controllers.Panels; -using FSO.Client.Debug; -using FSO.Client.UI.Panels.WorldUI; -using FSO.Common.Utils; +using FSO.SimAntics.Utils; using FSO.UI.Model; -using FSO.Client.UI.Panels.Neighborhoods; -using FSO.Server.Clients; -using FSO.LotView.Utils.Camera; +using Microsoft.Xna.Framework; namespace FSO.Client.UI.Screens { public class CoreGameScreen : FSO.Client.UI.Framework.GameScreen, IGameScreen { - public UIUCP ucp; + public UIUCP ucp { get; set; } public UIGizmo gizmo; public UIInbox Inbox; public UIGameTitle Title; + public UIArchiveUserList UserList; public UISortedContainer CityFloatingContainer; public UIContainer WindowContainer; @@ -45,6 +51,7 @@ public class CoreGameScreen : FSO.Client.UI.Framework.GameScreen, IGameScreen public UIBookmarks Bookmarks; public UIRelationshipDialog Relationships; public UIMapWaypoint YouAreHere, YourHouseHere; + internal UICityPainterAvatarLayer CityUpdateLayer; private Queue StateChanges; @@ -62,7 +69,17 @@ public class CoreGameScreen : FSO.Client.UI.Framework.GameScreen, IGameScreen public VMClientDriver Driver; public uint VisualBudget { get; set; } - private UIMouseEventRef MouseHitAreaEventRef = null; + // Simantics VMs can be kept around for a load transition. + private VM TransitionVM; + private World TransitionWorld; + private CameraControllers TransitionCameras; + + public VM VisualVM => TransitionVM ?? vm; + public World VisualWorld => TransitionWorld ?? World; + public VisualSurroundPuppets SurroundPuppets { get; private set; } + + public UIButton CityEditButton; + private UICityPainter CityPainter; public bool InLot { @@ -220,25 +237,8 @@ public CoreGameScreen() : base() */ HITVM.Get().PlaySoundEvent(UIMusic.Map); - /*VMDebug = new UIButton() - { - Caption = "Simantics", - Y = 45, - Width = 100, - X = GlobalSettings.Default.GraphicsWidth - 110 - }; - VMDebug.OnButtonClick += new ButtonClickDelegate(VMDebug_OnButtonClick); - this.Add(VMDebug);*/ - - /*SaveHouseButton = new UIButton() - { - Caption = "Save House", - Y = 10, - Width = 100, - X = GlobalSettings.Default.GraphicsWidth - 110 - }; - SaveHouseButton.OnButtonClick += new ButtonClickDelegate(SaveHouseButton_OnButtonClick); - this.Add(SaveHouseButton);*/ + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; CityFloatingContainer = new UISortedContainer(); Add(CityFloatingContainer); @@ -300,8 +300,41 @@ public CoreGameScreen() : base() ControllerUtils.BindController(Inbox); WindowContainer.Add(Inbox); + UserList = new UIArchiveUserList(); + UserList.Visible = false; + ControllerUtils.BindController(UserList); + var userListController = UserList.FindController(); + if (userListController != null) + { + userListController.FlashCallback = FlashUserList; + } + WindowContainer.Add(UserList); + var status = new UINetStatusTray(); Add(status); + + SurroundPuppets = new(this); + + CityEditButton = new UIButton(custom.Get("cityedit_toggle.png").Get(gd)) + { + Position = new Vector2(10, 10), + Tooltip = GameFacade.Strings.GetString("f130", "1") + }; + CityEditButton.OnButtonClick += ToggleCityEdit; + + Add(CityEditButton); + } + + private void ToggleCityEdit(UIElement button) + { + if (CityPainter == null) + { + CityPainter = new UICityPainter(CityRenderer); + WindowContainer.Add(CityPainter); + } + + CityPainter.Position = new Vector2(20, 20); + CityPainter.SetActive(true); } public override void GameResized() @@ -317,13 +350,14 @@ public override void GameResized() gizmo.Y = ScreenHeight - 230; MessageTray.X = ScreenWidth - 70; World?.GameResized(); + TransitionWorld?.GameResized(); var oldPanel = ucp.CurrentPanel; ucp.SetPanel(-1); ucp.SetPanel(oldPanel); CityTooltipHitArea.SetSize(ScreenWidth, ScreenHeight); } - public void Initialize(string cityName, int cityMap, TerrainController terrainController) + public void Initialize(string cityName, TerrainController terrainController) { CalculateMatrix(); CityFloatingContainer.ScaleX = 1f / Scale.X; @@ -331,7 +365,7 @@ public void Initialize(string cityName, int cityMap, TerrainController terrainCo Title.SetTitle(cityName); GameFacade.CurrentCityName = cityName; - InitializeMap(cityMap); + InitializeMap(terrainController.Realestate); InitializeMouse(); ZoomLevel = 5; //screen always starts at far zoom, city visible. CityRenderer.m_ZoomProgress = 0; @@ -344,15 +378,33 @@ public void Initialize(string cityName, int cityMap, TerrainController terrainCo GameThread.NextUpdate(x => { - FSOFacade.Hints.TriggerHint("screen:city"); + var controller = FindController(); + if (controller.Mode != Regulators.CityConnectionMode.ARCHIVE) + { + // This hint doesn't make sense in archive mode, since players should be encouraged to join any lot. + // An archive specific city view hint should be triggered when returning to map, or when there's no welcome lot to join. + FSOFacade.Hints.TriggerHint("screen:city"); + } + else if (!controller.ArchiveConfig.HasFlag(Common.ArchiveConfigFlags.Offline) && FSOFacade.Controller.HasServer()) + { + GameThreadInterval interval = null; + interval = GameThread.SetInterval(() => + { + if (!FSOFacade.Hints.IsShowingHint()) + { + FSOFacade.Hints.TriggerHint("screen:archive_host"); + interval.Clear(); + } + }, 1000); + } }); } - private void InitializeMap(int cityMap) + private void InitializeMap(IShardRealestateDomain realestate) { CityRenderer = new Terrain(GameFacade.GraphicsDevice); //The Terrain class implements the ThreeDAbstract interface so that it can be treated as a scene but manage its own drawing and updates. CityRenderer.m_GraphicsDevice = GameFacade.GraphicsDevice; - CityRenderer.Initialize(cityMap); + CityRenderer.Initialize(realestate); CityRenderer.LoadContent(GameFacade.GraphicsDevice); CityRenderer.RegenData = true; CityRenderer.SetTimeOfDay(0.5); @@ -367,9 +419,11 @@ private void InitializeMap(int cityMap) YouAreHere = new UIMapWaypoint(UIMapWaypoint.UIMapWaypointStyle.YouAreHere); YourHouseHere = new UIMapWaypoint(UIMapWaypoint.UIMapWaypointStyle.YourHouseHere); + CityUpdateLayer = new UICityPainterAvatarLayer(CityRenderer); AddAt(2, YouAreHere); AddAt(2, YourHouseHere); + AddAt(2, CityUpdateLayer); } private void InitializeMouse(){ @@ -380,7 +434,7 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta //GameFacade.Game.IsFixedTimeStep = (vm == null || vm.Ready); Visible = ((World?.Visible == false || World?.State.Cameras.HideUI != true) && !CityRenderer.Camera.HideUI); - bool directControl = (World?.State.Cameras.ActiveCamera as CameraControllerFP)?.CaptureMouse == true; + bool directControl = (VisualWorld?.State.Cameras.ActiveCamera as CameraControllerFP)?.CaptureMouse == true; GameFacade.Game.IsMouseVisible = Visible && !directControl; base.Update(state); @@ -392,26 +446,16 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta { if (ZoomLevel > 3 && (CityRenderer.m_Zoomed == TerrainZoomMode.Near) != (ZoomLevel == 4)) ZoomLevel = (CityRenderer.m_Zoomed == TerrainZoomMode.Near) ? 4 : 5; - if (World != null) { + if (VisualWorld != null) { if (CityRenderer.m_Zoomed == TerrainZoomMode.Lot) { - if (World.FrameCounter < 3) - { - //wait until the draw stage has stabalized a bit. tends to be like this - // 1. heavy singular draw - // 2. update * 30 - // 3. normal draws - CityRenderer.m_LotZoomProgress = 0; - World.Visible = true; - World.Opacity = 0; - } - else if (World.FrameCounter == 5 && GlobalSettings.Default.CompatState < GlobalSettings.TARGET_COMPAT_STATE) + if (VisualWorld.FrameCounter == 5 && GlobalSettings.Default.CompatState < GlobalSettings.TARGET_COMPAT_STATE) { GlobalSettings.Default.CompatState = GlobalSettings.TARGET_COMPAT_STATE; GlobalSettings.Default.Save(); } - else - CityRenderer.InheritPosition(World, FindController(), false); + + CityRenderer.InheritPosition(VisualWorld, FindController(), false); } if (CityRenderer.m_LotZoomProgress > 0f && CityRenderer.m_LotZoomProgress < 1f) { @@ -427,10 +471,10 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta if (CityRenderer.m_LotZoomProgress < 0.0001f) { CityRenderer.m_LotZoomProgress = 0f; - World.Visible = false; + VisualWorld.Visible = false; } } - World.Opacity = Math.Max(0, (CityRenderer.m_LotZoomProgress - 0.5f) * 2); + VisualWorld.Opacity = Math.Max(0, (CityRenderer.m_LotZoomProgress - 0.5f) * 2); float scale = 1; if (CityRenderer.Camera is CityCamera2D) @@ -441,15 +485,19 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta / cam.m_LotZoomSize; } - World.State.PreciseZoom = scale; + VisualWorld.State.PreciseZoom = scale; } else { - World.Opacity = (CityRenderer.m_Zoomed == TerrainZoomMode.Lot)?1f:0f; + VisualWorld.Opacity = (CityRenderer.m_Zoomed == TerrainZoomMode.Lot)?1f:0f; } } + else if (CityRenderer.m_LotZoomProgress > 0) + { + CityRenderer.m_LotZoomProgress = 0; + } if (InLot) //if we're in a lot, use the VM's more accurate time! - CityRenderer.SetTimeOfDay((vm.Context.Clock.Hours / 24.0) + (vm.Context.Clock.Minutes / 1440.0) + (vm.Context.Clock.Seconds / 86400.0)); + CityRenderer.SetTimeOfDay((vm.Context.Clock.Hours / 24.0) + (vm.Context.Clock.Minutes / 1440.0) + (vm.Context.Clock.Seconds / 86400.0)); else { var time = DateTime.UtcNow; @@ -463,7 +511,7 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta while (StateChanges.Count > 0) { var e = StateChanges.Dequeue(); - ClientStateChangeProcess(e.State, e.Progress); + ClientStateChangeProcess(e.State, e.Progress, state); } } @@ -478,14 +526,39 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta } } - var joinAttempt = DiscordRpcEngine.Secret; - if (joinAttempt != null) + var secret = DiscordRpcEngine.Secret; + if (secret != null) { - var split = joinAttempt.Split('#'); - uint lotID; - if (uint.TryParse(split[0], out lotID)) + var joinAttempt = secret.Value; + bool joinLot = joinAttempt.LotID != 0; + // TODO: if the join attempt archive mode doesn't match archive mode enable, let the player know + if (joinAttempt.ArchiveMode) { - FindController()?.JoinLot(lotID); + if (joinAttempt.ServerID != DiscordRpcEngine.ArchiveID) + { + if (joinAttempt.ServerHostname == "") + { + UIAlert.Alert("", GameFacade.Strings.GetString("f128", "115"), true); + } + else + { + UIAlert.YesNo("", GameFacade.Strings.GetString("f128", "116"), true, (bool result) => + { + if (result) + { + FSOFacade.Controller.Disconnect(true); + GameThread.SetTimeout(() => { DiscordRpcEngine.Secret = joinAttempt; }, 100); + } + }); + } + joinLot = false; + } + } + + if (joinLot) + { + var lotId = joinAttempt.LotID; + FindController()?.JoinLot(lotId); } DiscordRpcEngine.Secret = null; @@ -495,21 +568,26 @@ public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState sta { GraphicsModeControl.ChangeMode((GraphicsModeControl.Mode == GlobalGraphicsMode.Full3D) ? GlobalGraphicsMode.Hybrid2D : GlobalGraphicsMode.Full3D); } + + CityEditButton.Visible = FindController()?.AllowCityEditor == true && ZoomLevel >= 4 && (CityPainter == null || !CityPainter.Visible); } public override void PreDraw(UISpriteBatch batch) { base.PreDraw(batch); + SurroundPuppets?.PreDraw(); + if (vm != null) { if (vm.FSOVAsyncLoading) { } else if (!WorldLoaded && vm.Context.Blueprint != null) { var result = World.Preload(GameFacade.GraphicsDevice); - if (result) + if (result && vm.GetAvatarByPersist(vm.MyUID) != null) { WorldLoaded = true; ClientStateChange(6, 1); + AssetStreaming.EndStreaming(); } else { @@ -538,16 +616,56 @@ public override void Draw(UISpriteBatch batch) } } - public void CleanupLastWorld() + public void CleanupTransition() { + if (TransitionVM != null) + { + TransitionVM.SuppressBHAVChanges(); + TransitionVM = null; + + if (World != null) + { + World.Visible = TransitionWorld.Visible; + } + + GameFacade.Scenes.Remove(TransitionWorld); + TransitionWorld.Dispose(); + TransitionWorld = null; + + CityRenderer.DisposeOnLot(); + } + } + + public void CleanupLastWorld(bool cleanupTransition = true) + { + if (cleanupTransition) + { + CleanupTransition(); + } + if (vm == null) return; + // Might be mid-load. + AssetStreaming.EndStreaming(); + //clear our cache too, if the setting lets us do that DiscordRpcEngine.SendFSOPresence(gizmo.CurrentAvatar.Value.Avatar_Name, null, 0, 0, 0, 0, null, gizmo.CurrentAvatar.Value.Avatar_PrivacyMode > 0); - TimedReferenceController.Clear(); - TimedReferenceController.Clear(); - if (ZoomLevel < 4) ZoomLevel = 5; + bool localTransition = FindController()?.LocalTransition ?? false; + + if (!localTransition) + { + TimedReferenceController.Clear(); + TimedReferenceController.Clear(); + + if (ZoomLevel < 4) ZoomLevel = 5; + } + + if (localTransition) + { + vm.Context.Ambience.BeginTransition(); + } + vm.Context.Ambience.Kill(); foreach (var ent in vm.Entities) { //stop object sounds var threads = ent.SoundThreads; @@ -559,18 +677,31 @@ public void CleanupLastWorld() } vm.CloseNet(VMCloseNetReason.LeaveLot); Driver.OnClientCommand -= VMSendCommand; - GameFacade.Scenes.Remove(World); - World.Dispose(); LotControl.Dispose(); this.Remove(LotControl); ucp.SetPanel(-1); ucp.SetInLot(false); - vm.SuppressBHAVChanges(); + + if (localTransition) + { + TransitionVM = vm; + TransitionWorld = World; + + TransitionWorld.State.SimSpeed = 0; + } + else + { + vm.SuppressBHAVChanges(); + + GameFacade.Scenes.Remove(World); + World.Dispose(); + CityRenderer.DisposeOnLot(); + } + vm = null; World = null; Driver = null; LotControl = null; - CityRenderer.DisposeOnLot(); } public void InitiateLotSwitch() @@ -590,6 +721,7 @@ public void ShowReconnectDialog(uint id) Buttons = new UIAlertButton[] { new UIAlertButton(UIAlertButtonType.Yes, (btn) => { + controller.ReconnectTransition = null; controller.ReconnectLotID = id; vm?.SendCommand(new VMNetSimLeaveCmd()); RemoveDialog(SwitchLotDialog); SwitchLotDialog = null; }), @@ -630,7 +762,7 @@ public void ClientStateChange(int state, float progress) lock (StateChanges) StateChanges.Enqueue(new SimConnectStateChange(state, progress)); } - public void ClientStateChangeProcess(int state, float progress) + public void ClientStateChangeProcess(int state, float progress, UpdateState updateState) { if (vm == null) return; switch (state) @@ -664,35 +796,184 @@ public void ClientStateChangeProcess(int state, float progress) case 6: //done world load GameFacade.Cursor.SetCursor(CursorType.Normal); UIScreen.RemoveDialog(JoinLotProgress); + CursorManager.INSTANCE.SetCursorPriority(0); ZoomLevel = 1; + + InheritTransition(updateState); + CleanupTransition(); + ucp.SetInLot(true); break; } } - public void InitializeLot() + private void InheritTransition(UpdateState state) { - CleanupLastWorld(); + if (TransitionCameras == null) + { + return; + } - /* - if (FSOEnvironment.Enable3D) + if (vm != null && World != null) { - var rc = new LotView.RC.WorldRC(GameFacade.GraphicsDevice); - rc.SetSurroundingWorld(CityRenderer); - World = rc; + var myAvatar = vm.GetAvatarByPersist(vm.MyUID); + + if (myAvatar == null) + { + // Not here yet. We'll try to keep first person on a future tick. + return; + } + + var info = FindController().ReconnectTransition; + + if (info == null) + { + // Should be impossible... + return; + } + + var lastWorld = TransitionWorld; + CameraControllers newCameras = World.State.Cameras; + World.State.DisableSmoothRotation = true; + + var position = MapCoordinates.Unpack(vm.TSOState.LotID); + var previousElevation = CityRenderer.GetElevationAt(position.X + info.RelativeChangeY, position.Y - info.RelativeChangeX); + var currentElevation = CityRenderer.GetElevationAt(position.X, position.Y); + + var baseAltDiff = (currentElevation - previousElevation) * 100; + var heightDiff = baseAltDiff * World.Architecture.Blueprint.TerrainFactor * 3; + + TransitionCameras.Camera3D.CamHeight -= heightDiff; + + if (lastWorld != null) + { + var surroundsToKeep = info.GetSurroundingLotMask() ^ 0b111111111; + var oldSubworlds = lastWorld.Architecture.Blueprint.SubWorlds; + var newSubworlds = World.Architecture.Blueprint.SubWorlds; + var size = World.Architecture.Blueprint.Width; + int baseHeight = VMLotTerrainRestoreTools.GetBaseLevel(vm, 1, 1); + bool anySubworldsMigrated = false; + + for (int i = 0; i < 9; i++) + { + if (i == 4) continue; + + uint bit = 1u << i; + + if ((surroundsToKeep & bit) != 0) + { + var oldIndex = info.GetOldSubworldForIndex(i); + var oldSurround = oldSubworlds.Find(x => x.Index == oldIndex); + + if (oldSurround != null) + { + oldSubworlds.Remove(oldSurround); + + oldSurround.Index = i; + int x = (i % 3); + int y = (i / 3); + oldSurround.GlobalPosition = new Vector2((1 - y) * (size - 2), (x - 1) * (size - 2)); + int newHeight = VMLotTerrainRestoreTools.GetBaseLevel(vm, x, y); + var bp = oldSurround.Architecture.Blueprint; + var oldAlt = bp.BaseAlt; + bp.BaseAlt = baseHeight - newHeight; + + if (oldAlt != bp.BaseAlt) + { + foreach (var obj in bp.Objects) + { + // Need to update the object altitudes + obj.Position = obj.UnmoddedPosition; + } + + bp.AdjustBaseAlt(bp.BaseAlt - oldAlt); + } + + newSubworlds.Add(oldSurround); + anySubworldsMigrated = true; + } + } + } + + if (anySubworldsMigrated) + { + World.InitSubWorlds(); + } + + var lastState = TransitionWorld.State; + if (World.State.Level != lastState.Level) World.State.Level = lastState.Level; + if (World.State.Rotation != lastState.Rotation) World.State.Rotation = lastState.Rotation; + if (World.State.Zoom != lastState.Zoom) World.State.Zoom = lastState.Zoom; + World.State.PreciseZoom = lastState.PreciseZoom; + World.State.CenterTile = lastState.CenterTile - new Vector2(info.RelativeChangeX * (TransitionWorld.Architecture.Blueprint.Width - 2), info.RelativeChangeY * (TransitionWorld.Architecture.Blueprint.Height - 2)); + if (lastWorld.State.ScrollAnchor != null) + { + var myOldAvatar = TransitionVM?.GetAvatarByPersist(TransitionVM.MyUID); + + if (myOldAvatar != null && lastWorld.State.ScrollAnchor == myOldAvatar.WorldUI) + { + World.State.ScrollAnchor = myAvatar.WorldUI as AvatarComponent; + } + } + } + + // TODO: shift camera height by surrounding lot height? + TransitionCamera(newCameras.Camera3D, TransitionCameras.Camera3D); + //TransitionCamera(newCameras.Camera2D, TransitionCameras.Camera2D); + //TransitionCamera(newCameras.CameraFirstPerson, TransitionCameras.CameraFirstPerson); + newCameras.CameraDirect.Inherit(TransitionCameras.CameraDirect); + + if (myAvatar.GetPersonData(SimAntics.Model.VMPersonDataVariable.UnusedAndDoNotUse2) == 32767) + { + World.ToggleFirstPerson(CameraControllerType.Direct); + CityRenderer.m_LotZoomProgress = 1; + + if (TransitionCameras != null) + { + var camera = World.State.Cameras.CameraDirect; + var lastCamera = TransitionCameras.CameraDirect; + camera.RotationX = lastCamera.RotationX; + camera.RotationY = lastCamera.RotationY; + + camera.FirstPersonAvatar = (LotView.Components.AvatarComponent)myAvatar.WorldUI; + World.State.Cameras.Update(state, World); + World.State.Cameras.PreDraw(World); + } + } + + World.State.DisableSmoothRotation = false; + LotControl.ResetTargetZoom(); } - else */ - World = new World(GameFacade.GraphicsDevice); - World.Surroundings = CityRenderer; + + TransitionCameras = null; + } + + private void TransitionCamera(ICameraController camera, ICameraController previousCamera) + { + camera.BeforeActive(previousCamera, World); + camera.OnActive(previousCamera, World); + camera.InvalidateCamera(World.State); + } + + public void InitializeLot() + { + CleanupLastWorld(false); + + World = new World(GameFacade.GraphicsDevice) + { + Surroundings = CityRenderer + }; WorldLoaded = false; World.Opacity = 0; + World.Visible = false; GameFacade.Scenes.Add(World); Driver = new VMClientDriver(ClientStateChange); Driver.OnClientCommand += VMSendCommand; Driver.OnShutdown += VMShutdown; vm = new VM(new VMContext(World), Driver, new UIHeadlineRendererProvider()); + AssetStreaming.BeginStreaming(AssetStreamingMode.Lot); vm.FSOVDoAsyncLoad = true; vm.ListenBHAVChanges(); vm.Init(); @@ -711,7 +992,10 @@ public void InitializeLot() LotControl.Visible = false; } - ZoomLevel = Math.Max(ZoomLevel, 4); + if (TransitionWorld == null) + { + ZoomLevel = Math.Max(ZoomLevel, 4); + } if (IDEHook.IDE != null) IDEHook.IDE.StartIDE(vm); @@ -735,12 +1019,20 @@ private void Vm_OnGenericVMEvent(VMEventType type, object data) var rnd = new Random(); dialog.Position = new Vector2(rnd.Next(Math.Max(0, ScreenWidth - 380)), rnd.Next(Math.Max(0, ScreenHeight - 180))); break; + case VMEventType.Resync: + if (ZoomLevel < 4) + { + SetTitle(); + } + break; } } - private void VMLotSwitch(uint lotId) + private void VMLotSwitch(uint lotId, LotTransitionInfo transition) { - FindController()?.SwitchLot(lotId); + TransitionCameras = World?.State?.Cameras; + + FindController()?.SwitchLot(lotId, transition); } private string lastLotTitle = ""; @@ -794,21 +1086,6 @@ private void HandleLoadErrors() vm.LoadErrors.Clear(); } - private void VMDebug_OnButtonClick(UIElement button) - { - /* - if (vm == null) return; - - var debugTools = new Simantics(vm); - - var window = GameFacade.Game.Window; - debugTools.Show(); - debugTools.Location = new System.Drawing.Point(window.ClientBounds.X + window.ClientBounds.Width, window.ClientBounds.Y); - debugTools.UpdateAQLocation(); - */ - - } - public void CloseInbox() { Inbox.Visible = false; @@ -823,11 +1100,30 @@ public void OpenInbox() ucp.FlashInbox(false); } + public void CloseUserList() + { + UserList.Visible = false; + } + + public void OpenUserList() + { + UserList.Visible = true; + UserList.X = (GlobalSettings.Default.GraphicsWidth - UserList.Width) / 2; + UserList.Y = (GlobalSettings.Default.GraphicsHeight - UserList.Height) / 2; + WindowContainer.SendToFront(UserList); + ucp.FlashUserList(false); + } + public void FlashInbox(bool flash) { ucp.FlashInbox(flash); } + public void FlashUserList(bool flash) + { + ucp.FlashUserList(flash); + } + private void MouseHandler(UIMouseEventType type, UpdateState state) { if (CityRenderer != null) CityRenderer.UIMouseEvent(type, state); //all the city renderer needs are events telling it if the mouse is over it or not. diff --git a/TSOClient/tso.client/UI/Screens/Credits.cs b/TSOClient/tso.client/UI/Screens/Credits.cs index 980a153ec..82a3880f1 100644 --- a/TSOClient/tso.client/UI/Screens/Credits.cs +++ b/TSOClient/tso.client/UI/Screens/Credits.cs @@ -1,33 +1,102 @@ using FSO.Client.UI.Framework; using Microsoft.Xna.Framework.Graphics; using FSO.Client.UI.Controls; +using FSO.Client.UI.Panels; +using Microsoft.Xna.Framework; namespace FSO.Client.UI.Screens { - public class Credits : GameScreen + public class Credits : UIContainer { public Texture2D BackgroundImage { get; set; } + public Texture2D LogoImage { get; set; } + public UIButton MaxisButton { get; set; } + public UILabel TitleLabel { get; set; } + public UILabel EALabel { get; set; } public UIButton BackButton { get; set; } public UIButton OkButton { get; set; } + public UIButton ExitButton { get; set; } + public UICreditsPanel CreditsArea; + + // FreeSO Credits additions + public UIImage TSOLogo { get; set; } + public UIImage FSOLogo { get; set; } + public UIButton TSOButton { get; set; } + public UIButton FSOButton { get; set; } + + private string TSOTitle; + private string FSOTitle; public Credits() { + var gd = GameFacade.GraphicsDevice; + var custom = Content.Content.Get().CustomUI; var ui = this.RenderScript("credits.uis"); - this.X = (float)((double)(ScreenWidth - 800)) / 2; - this.Y = (float)((double)(ScreenHeight - 600)) / 2; - this.AddAt(0, new UIImage(BackgroundImage)); - this.Add(ui.Create("TSOLogoImage")); + this.Add(TSOLogo = ui.Create("TSOLogoImage")); + + this.Add(CenterAt(FSOLogo = new UIImage(custom.Get("credits_fsologo.png").Get(gd)), new Vector2(140, 194))); + + this.Add(CenterAt(TSOButton = new UIButton(custom.Get("credits_tsobutton.png").Get(gd)), new Vector2(140, 486))); + this.Add(CenterAt(FSOButton = new UIButton(custom.Get("credits_fsobutton.png").Get(gd)), new Vector2(140, 320))); + + Add(CreditsArea = ui.Create("CreditsArea")); + + TSOTitle = ui.GetString("TitleLabelText"); + FSOTitle = GameFacade.Strings.GetString("f128", "122"); + SetCreditsType(true); + + TSOButton.OnButtonClick += (btn) => SetCreditsType(false); + TSOButton.Tooltip = GameFacade.Strings.GetString("f128", "123"); + FSOButton.OnButtonClick += (btn) => SetCreditsType(true); + FSOButton.Tooltip = GameFacade.Strings.GetString("f128", "124"); BackButton.OnButtonClick += new ButtonClickDelegate(BackButton_OnButtonClick); OkButton.OnButtonClick += new ButtonClickDelegate(BackButton_OnButtonClick); + ExitButton.OnButtonClick += ExitButton_OnButtonClick; + + GameResized(); + } + + private void SetCreditsType(bool fso) + { + TSOLogo.Visible = !fso; + MaxisButton.Visible = !fso; + EALabel.Visible = !fso; + FSOButton.Visible = !fso; + + FSOLogo.Visible = fso; + TSOButton.Visible = fso; + + TitleLabel.Caption = fso ? FSOTitle : TSOTitle; + + CreditsArea.Init(fso); + } + + private UIElement CenterAt(UIElement elem, Vector2 point) + { + elem.Position = point - elem.Size / 2; + + return elem; + } + + private void ExitButton_OnButtonClick(UIElement button) + { + UIScreen.ShowDialog(new UIExitDialog(), true); + } + + public override void GameResized() + { + base.GameResized(); + Position = new Vector2((GlobalSettings.Default.GraphicsWidth - 800) / 2, (GlobalSettings.Default.GraphicsHeight - 600) / 2); + InvalidateMatrix(); } void BackButton_OnButtonClick(UIElement button) { - GameFacade.Screens.RemoveScreen(this); + UIScreen.RemoveDialog(this); } } } diff --git a/TSOClient/tso.client/UI/Screens/EALogo.cs b/TSOClient/tso.client/UI/Screens/EALogo.cs index bc085fec4..aca332cad 100644 --- a/TSOClient/tso.client/UI/Screens/EALogo.cs +++ b/TSOClient/tso.client/UI/Screens/EALogo.cs @@ -1,7 +1,7 @@ -using System.Timers; -using FSO.Client.UI.Framework; +using FSO.Client.GameContent; using FSO.Client.UI.Controls; -using FSO.Client.GameContent; +using FSO.Client.UI.Framework; +using System.Timers; namespace FSO.Client.UI.Screens { @@ -9,7 +9,7 @@ public class EALogo : GameScreen { private UIImage m_EALogo; private UIContainer BackgroundCtnr; - private Timer m_CheckProgressTimer; + private System.Timers.Timer m_CheckProgressTimer; public EALogo() : base() @@ -27,7 +27,7 @@ public EALogo() this.Add(BackgroundCtnr); - m_CheckProgressTimer = new Timer(); + m_CheckProgressTimer = new System.Timers.Timer(); m_CheckProgressTimer.Interval = 5000; m_CheckProgressTimer.Elapsed += new ElapsedEventHandler(m_CheckProgressTimer_Elapsed); m_CheckProgressTimer.Start(); diff --git a/TSOClient/tso.client/UI/Screens/IGameScreen.cs b/TSOClient/tso.client/UI/Screens/IGameScreen.cs index d1dd14e8e..757ce6ef2 100644 --- a/TSOClient/tso.client/UI/Screens/IGameScreen.cs +++ b/TSOClient/tso.client/UI/Screens/IGameScreen.cs @@ -15,5 +15,6 @@ public interface IGameScreen UILotControl LotControl { get; set; } VM vm { get; set; } + UIUCP ucp { get; set; } } } diff --git a/TSOClient/tso.client/UI/Screens/LoadingScreen.cs b/TSOClient/tso.client/UI/Screens/LoadingScreen.cs index b1df231dc..f7bac08fb 100644 --- a/TSOClient/tso.client/UI/Screens/LoadingScreen.cs +++ b/TSOClient/tso.client/UI/Screens/LoadingScreen.cs @@ -83,10 +83,11 @@ void CheckProgressTimer_Elapsed() private string[] PreloadLabels; private int CurrentPreloadLabel = 0; private bool InTween = false; + private bool Done = false; private void CheckPreloadLabel() { - if (Controller == null) { return; } + if (Controller == null || Done) { return; } /** Have we preloaded the correct percent? **/ var percentDone = ((LoadingScreenController)Controller).Loader.Progress; @@ -112,6 +113,7 @@ private void CheckPreloadLabel() } if (percentDone >= 1) { + Done = true; CheckProgressTimer.Clear(); FSOFacade.Controller.ShowLogin(); return; diff --git a/TSOClient/tso.client/UI/Screens/LoginScreen.cs b/TSOClient/tso.client/UI/Screens/LoginScreen.cs index 374c78bd8..81ab95cf2 100644 --- a/TSOClient/tso.client/UI/Screens/LoginScreen.cs +++ b/TSOClient/tso.client/UI/Screens/LoginScreen.cs @@ -19,6 +19,8 @@ using FSO.Common.Utils; using FSO.Common.Rendering.Framework.Model; +using FSO.Client.UI.Archive; + namespace FSO.Client.UI.Screens { public class LoginScreen : GameScreen, IDisposable @@ -33,17 +35,6 @@ public class LoginScreen : GameScreen, IDisposable public LoginScreen(LoginRegulator regulator) { - try - { - if (File.Exists("update2.exe")) - { - File.Delete("update.exe"); - File.Move("update2.exe", "update.exe"); - } - } catch (Exception) { - //maybe signal to user that the updater update failed - } - this.Regulator = regulator; regulator.Logout(); @@ -62,10 +53,12 @@ public LoginScreen(LoginRegulator regulator) Background = new UISetupBackground(); /** Client version **/ - var lbl = new UILabel(); - lbl.Caption = "Version " + GlobalSettings.Default.ClientVersion; - lbl.X = 20; - lbl.Y = 558; + var lbl = new UILabel + { + Caption = "Version " + GlobalSettings.Default.ClientVersion, + X = 20, + Y = 558 + }; Background.BackgroundCtnr.Add(lbl); this.Add(Background); @@ -84,6 +77,8 @@ public LoginScreen(LoginRegulator regulator) LoginDialog.Y = (ScreenHeight - LoginDialog.Height) / 2; this.Add(LoginDialog); + //Add(new UIArchiveJoinDialog()); + bool usernamePopulated = false; var loginIniFile = GameFacade.GameFilePath("login.ini"); @@ -154,6 +149,9 @@ public LoginScreen(LoginRegulator regulator) //UIScreen.GlobalShowDialog(new Panels.Neighborhoods.UIBulletinDialog(), false); //Content.Content.Get().UIGraphics.ExportAll(GameFacade.GraphicsDevice); }); + + var status = new UINetStatusTray(); + Add(status); } public override void Update(UpdateState state) diff --git a/TSOClient/tso.client/UI/Screens/LotScreen.cs b/TSOClient/tso.client/UI/Screens/LotScreen.cs index 02f88335f..a701fa6ac 100644 --- a/TSOClient/tso.client/UI/Screens/LotScreen.cs +++ b/TSOClient/tso.client/UI/Screens/LotScreen.cs @@ -1,110 +1,110 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.UI.Framework; -using TSOClient.Code.Rendering.Lot.Model; -using TSOClient.Code.Rendering.Lot; -using TSOClient.ThreeD; -using SimsLib.FAR1; -using SimsLib.FAR3; -using TSOClient.Code.Data; -using TSOClient.Code.UI.Panels; -using Microsoft.Xna.Framework; - -namespace TSOClient.Code.UI.Screens -{ - public class LotScreen : GameScreen - { - //private HouseRenderer Renderer; - private HouseScene Scene; - private UIUCP ucp; - - public LotScreen() - { - ArchitectureCatalog.Init(); - - var lotInfo = HouseData.Parse(GameFacade.GameFilePath("housedata/blueprints/restaurant00_00.xml")); - //var lotInfo = HouseData.Parse("C:\\restaurant00_00_small.xml"); - //for (int i = 1; i < 64; i++) - //{ - // lotInfo.World.Floors.Add(new HouseDataFloor { - // X = 1, - // Y = i, - // Level = 0, - // Value = 9 - // }); - //} - - //lotInfo.World.Floors.Add(new HouseDataFloor { - // X = 0, Y = 0, - // Level = 0, Value = 20 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 63, - // Y = 63, - // Level = 0, - // Value = 40 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 0, - // Y = 63, - // Level = 0, - // Value = 20 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 63, - // Y = 0, - // Level = 0, - // Value = 20 - //}); - - Scene = new HouseScene(); - Scene.LoadHouse(lotInfo); - GameFacade.Scenes.Add(Scene); - - - //Renderer = new HouseRenderer(); - //Renderer.SetModel(lotInfo); - ////Renderer.Position = new Microsoft.Xna.Framework.Vector3(-32.0f, -40.0f, 0.0f); - - //var scene = new ThreeDScene(); - //var focusPoint = Vector3.Zero; - - //var yValue = (float)Math.Cos(MathHelper.ToRadians(30.0f)) * 96.0f; - //var cameraOffset = new Vector3(-96.0f, yValue, 96.0f); - //var rotatedOffset = Vector3.Transform(cameraOffset, Microsoft.Xna.Framework.Matrix.CreateRotationY(MathHelper.PiOver2 * 0.5f)); - - ////rotatedOffset = Vector3.Transform(rotatedOffset, Microsoft.Xna.Framework.Matrix.CreateScale(3f)); - ////Renderer.Position = new Vector3(-96.0f, 0.0f, -96.0f); - - //scene.Camera.Position = cameraOffset;// new Microsoft.Xna.Framework.Vector3(0, 0, 80); - //scene.Add(Renderer); - //Renderer.Scale = new Vector3(0.005f); - - //GameFacade.Scenes.AddScene(scene); - - - ucp = new UIUCP(); - ucp.Y = ScreenHeight - 210; - //ucp.OnZoomChanged += new UCPZoomChangeEvent(ucp_OnZoomChanged); - ucp.OnRotateChanged += new UCPRotateChangeEvent(ucp_OnRotateChanged); - this.Add(ucp); - } - - void ucp_OnRotateChanged(UCPRotateDirection direction) - { - var newDirection = (HouseRotation)( - (((int)Scene.Rotation) + (direction == UCPRotateDirection.Clockwise ? -1 : 1)) % 4 - ); - - Scene.Rotation = newDirection; - } - } -} +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using TSOClient.Code.UI.Framework; +//using TSOClient.Code.Rendering.Lot.Model; +//using TSOClient.Code.Rendering.Lot; +//using TSOClient.ThreeD; +//using SimsLib.FAR1; +//using SimsLib.FAR3; +//using TSOClient.Code.Data; +//using TSOClient.Code.UI.Panels; +//using Microsoft.Xna.Framework; + +//namespace TSOClient.Code.UI.Screens +//{ +// public class LotScreen : GameScreen +// { +// //private HouseRenderer Renderer; +// private HouseScene Scene; +// private UIUCP ucp; + +// public LotScreen() +// { +// ArchitectureCatalog.Init(); + +// var lotInfo = HouseData.Parse(GameFacade.GameFilePath("housedata/blueprints/restaurant00_00.xml")); +// //var lotInfo = HouseData.Parse("C:\\restaurant00_00_small.xml"); +// //for (int i = 1; i < 64; i++) +// //{ +// // lotInfo.World.Floors.Add(new HouseDataFloor { +// // X = 1, +// // Y = i, +// // Level = 0, +// // Value = 9 +// // }); +// //} + +// //lotInfo.World.Floors.Add(new HouseDataFloor { +// // X = 0, Y = 0, +// // Level = 0, Value = 20 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 63, +// // Y = 63, +// // Level = 0, +// // Value = 40 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 0, +// // Y = 63, +// // Level = 0, +// // Value = 20 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 63, +// // Y = 0, +// // Level = 0, +// // Value = 20 +// //}); + +// Scene = new HouseScene(); +// Scene.LoadHouse(lotInfo); +// GameFacade.Scenes.Add(Scene); + + +// //Renderer = new HouseRenderer(); +// //Renderer.SetModel(lotInfo); +// ////Renderer.Position = new Microsoft.Xna.Framework.Vector3(-32.0f, -40.0f, 0.0f); + +// //var scene = new ThreeDScene(); +// //var focusPoint = Vector3.Zero; + +// //var yValue = (float)Math.Cos(MathHelper.ToRadians(30.0f)) * 96.0f; +// //var cameraOffset = new Vector3(-96.0f, yValue, 96.0f); +// //var rotatedOffset = Vector3.Transform(cameraOffset, Microsoft.Xna.Framework.Matrix.CreateRotationY(MathHelper.PiOver2 * 0.5f)); + +// ////rotatedOffset = Vector3.Transform(rotatedOffset, Microsoft.Xna.Framework.Matrix.CreateScale(3f)); +// ////Renderer.Position = new Vector3(-96.0f, 0.0f, -96.0f); + +// //scene.Camera.Position = cameraOffset;// new Microsoft.Xna.Framework.Vector3(0, 0, 80); +// //scene.Add(Renderer); +// //Renderer.Scale = new Vector3(0.005f); + +// //GameFacade.Scenes.AddScene(scene); + + +// ucp = new UIUCP(); +// ucp.Y = ScreenHeight - 210; +// //ucp.OnZoomChanged += new UCPZoomChangeEvent(ucp_OnZoomChanged); +// ucp.OnRotateChanged += new UCPRotateChangeEvent(ucp_OnRotateChanged); +// this.Add(ucp); +// } + +// void ucp_OnRotateChanged(UCPRotateDirection direction) +// { +// var newDirection = (HouseRotation)( +// (((int)Scene.Rotation) + (direction == UCPRotateDirection.Clockwise ? -1 : 1)) % 4 +// ); + +// Scene.Rotation = newDirection; +// } +// } +//} diff --git a/TSOClient/tso.client/UI/Screens/LotScreenNew.cs b/TSOClient/tso.client/UI/Screens/LotScreenNew.cs index ae72b37ac..dff24d2d8 100644 --- a/TSOClient/tso.client/UI/Screens/LotScreenNew.cs +++ b/TSOClient/tso.client/UI/Screens/LotScreenNew.cs @@ -1,163 +1,163 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using TSOClient.Code.UI.Framework; -using TSOClient.ThreeD; -using SimsLib.FAR1; -using SimsLib.FAR3; -using TSOClient.Code.Data; -using TSOClient.Code.UI.Panels; -using Microsoft.Xna.Framework; -using tso.world; -using tso.world.model; -using tso.simantics; -using tso.content; -using TSOClient.LUI; -using tso.debug; -using tso.files.formats.iff.chunks; -using tso.simantics.utils; - -namespace TSOClient.Code.UI.Screens -{ - public class LotScreenNew : GameScreen - { - private UIUCP ucp; - private World World; - private UIButton VMDebug; - private VM vm; - - public LotScreenNew() - { - var lotInfo = XmlHouseData.Parse(GameFacade.GameFilePath("housedata/blueprints/restaurant07_00.xml")); - - World = new World(); - GameFacade.Scenes.Add(World); - - vm = new VM(new VMContext(World)); - vm.Init(); - - var activator = new VMWorldActivator(vm, World); - var blueprint = activator.LoadFromXML(lotInfo); - - World.InitBlueprint(blueprint); - vm.Context.Blueprint = blueprint; - - var sim = activator.CreateAvatar(); - //sim.Position = new Vector3(31.5f, 55.5f, 0.0f); - sim.Position = new Vector3(26.5f, 41.5f, 0.0f); - - VMDebug = new UIButton() - { - Caption = "Simantics", - Y = 45, - Width = 100, - X = GlobalSettings.Default.GraphicsWidth - 110 - }; - VMDebug.OnButtonClick += new ButtonClickDelegate(VMDebug_OnButtonClick); - this.Add(VMDebug); - - //var lotInfo = HouseData.Parse("C:\\restaurant00_00_small.xml"); - //for (int i = 1; i < 64; i++) - //{ - // lotInfo.World.Floors.Add(new HouseDataFloor { - // X = 1, - // Y = i, - // Level = 0, - // Value = 9 - // }); - //} - - //lotInfo.World.Floors.Add(new HouseDataFloor { - // X = 0, Y = 0, - // Level = 0, Value = 20 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 63, - // Y = 63, - // Level = 0, - // Value = 40 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 0, - // Y = 63, - // Level = 0, - // Value = 20 - //}); - - //lotInfo.World.Floors.Add(new HouseDataFloor - //{ - // X = 63, - // Y = 0, - // Level = 0, - // Value = 20 - //}); - - - - //Renderer = new HouseRenderer(); - //Renderer.SetModel(lotInfo); - ////Renderer.Position = new Microsoft.Xna.Framework.Vector3(-32.0f, -40.0f, 0.0f); - - //var scene = new ThreeDScene(); - //var focusPoint = Vector3.Zero; - - //var yValue = (float)Math.Cos(MathHelper.ToRadians(30.0f)) * 96.0f; - //var cameraOffset = new Vector3(-96.0f, yValue, 96.0f); - //var rotatedOffset = Vector3.Transform(cameraOffset, Microsoft.Xna.Framework.Matrix.CreateRotationY(MathHelper.PiOver2 * 0.5f)); - - ////rotatedOffset = Vector3.Transform(rotatedOffset, Microsoft.Xna.Framework.Matrix.CreateScale(3f)); - ////Renderer.Position = new Vector3(-96.0f, 0.0f, -96.0f); - - //scene.Camera.Position = cameraOffset;// new Microsoft.Xna.Framework.Vector3(0, 0, 80); - //scene.Add(Renderer); - //Renderer.Scale = new Vector3(0.005f); - - //GameFacade.Scenes.AddScene(scene); - - - ucp = new UIUCP(); - ucp.Y = ScreenHeight - 210; - ucp.OnZoomChanged += new UCPZoomChangeEvent(ucp_OnZoomChanged); - ucp.OnRotateChanged += new UCPRotateChangeEvent(ucp_OnRotateChanged); - this.Add(ucp); - } - - public override void Update(tso.common.rendering.framework.model.UpdateState state) - { - base.Update(state); - vm.Update(state.Time); - } - - void VMDebug_OnButtonClick(UIElement button) - { - System.Windows.Forms.Form gameWindowForm = - (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(GameFacade.Game.Window.Handle); - gameWindowForm.Location = new System.Drawing.Point(0, 0); - - var debugTools = new Simantics(vm); - debugTools.Show(); - debugTools.Location = new System.Drawing.Point(gameWindowForm.Location.X + gameWindowForm.Width, 0); - - } - - void ucp_OnRotateChanged(UCPRotateDirection direction) - { - /*var newDirection = (HouseRotation)( - (((int)Scene.Rotation) + (direction == UCPRotateDirection.Clockwise ? -1 : 1)) % 4 - );*/ - - //Scene.Rotation = newDirection; - } - - void ucp_OnZoomChanged(WorldZoom zoom) - { - World.State.Zoom = zoom; - //Scene.Zoom = zoom; - } - } -} +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using TSOClient.Code.UI.Framework; +//using TSOClient.ThreeD; +//using SimsLib.FAR1; +//using SimsLib.FAR3; +//using TSOClient.Code.Data; +//using TSOClient.Code.UI.Panels; +//using Microsoft.Xna.Framework; +//using tso.world; +//using tso.world.model; +//using tso.simantics; +//using tso.content; +//using TSOClient.LUI; +//using tso.debug; +//using tso.files.formats.iff.chunks; +//using tso.simantics.utils; + +//namespace TSOClient.Code.UI.Screens +//{ +// public class LotScreenNew : GameScreen +// { +// private UIUCP ucp; +// private World World; +// private UIButton VMDebug; +// private VM vm; + +// public LotScreenNew() +// { +// var lotInfo = XmlHouseData.Parse(GameFacade.GameFilePath("housedata/blueprints/restaurant07_00.xml")); + +// World = new World(); +// GameFacade.Scenes.Add(World); + +// vm = new VM(new VMContext(World)); +// vm.Init(); + +// var activator = new VMWorldActivator(vm, World); +// var blueprint = activator.LoadFromXML(lotInfo); + +// World.InitBlueprint(blueprint); +// vm.Context.Blueprint = blueprint; + +// var sim = activator.CreateAvatar(); +// //sim.Position = new Vector3(31.5f, 55.5f, 0.0f); +// sim.Position = new Vector3(26.5f, 41.5f, 0.0f); + +// VMDebug = new UIButton() +// { +// Caption = "Simantics", +// Y = 45, +// Width = 100, +// X = GlobalSettings.Default.GraphicsWidth - 110 +// }; +// VMDebug.OnButtonClick += new ButtonClickDelegate(VMDebug_OnButtonClick); +// this.Add(VMDebug); + +// //var lotInfo = HouseData.Parse("C:\\restaurant00_00_small.xml"); +// //for (int i = 1; i < 64; i++) +// //{ +// // lotInfo.World.Floors.Add(new HouseDataFloor { +// // X = 1, +// // Y = i, +// // Level = 0, +// // Value = 9 +// // }); +// //} + +// //lotInfo.World.Floors.Add(new HouseDataFloor { +// // X = 0, Y = 0, +// // Level = 0, Value = 20 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 63, +// // Y = 63, +// // Level = 0, +// // Value = 40 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 0, +// // Y = 63, +// // Level = 0, +// // Value = 20 +// //}); + +// //lotInfo.World.Floors.Add(new HouseDataFloor +// //{ +// // X = 63, +// // Y = 0, +// // Level = 0, +// // Value = 20 +// //}); + + + +// //Renderer = new HouseRenderer(); +// //Renderer.SetModel(lotInfo); +// ////Renderer.Position = new Microsoft.Xna.Framework.Vector3(-32.0f, -40.0f, 0.0f); + +// //var scene = new ThreeDScene(); +// //var focusPoint = Vector3.Zero; + +// //var yValue = (float)Math.Cos(MathHelper.ToRadians(30.0f)) * 96.0f; +// //var cameraOffset = new Vector3(-96.0f, yValue, 96.0f); +// //var rotatedOffset = Vector3.Transform(cameraOffset, Microsoft.Xna.Framework.Matrix.CreateRotationY(MathHelper.PiOver2 * 0.5f)); + +// ////rotatedOffset = Vector3.Transform(rotatedOffset, Microsoft.Xna.Framework.Matrix.CreateScale(3f)); +// ////Renderer.Position = new Vector3(-96.0f, 0.0f, -96.0f); + +// //scene.Camera.Position = cameraOffset;// new Microsoft.Xna.Framework.Vector3(0, 0, 80); +// //scene.Add(Renderer); +// //Renderer.Scale = new Vector3(0.005f); + +// //GameFacade.Scenes.AddScene(scene); + + +// ucp = new UIUCP(); +// ucp.Y = ScreenHeight - 210; +// ucp.OnZoomChanged += new UCPZoomChangeEvent(ucp_OnZoomChanged); +// ucp.OnRotateChanged += new UCPRotateChangeEvent(ucp_OnRotateChanged); +// this.Add(ucp); +// } + +// public override void Update(tso.common.rendering.framework.model.UpdateState state) +// { +// base.Update(state); +// vm.Update(state.Time); +// } + +// void VMDebug_OnButtonClick(UIElement button) +// { +// System.Windows.Forms.Form gameWindowForm = +// (System.Windows.Forms.Form)System.Windows.Forms.Form.FromHandle(GameFacade.Game.Window.Handle); +// gameWindowForm.Location = new System.Drawing.Point(0, 0); + +// var debugTools = new Simantics(vm); +// debugTools.Show(); +// debugTools.Location = new System.Drawing.Point(gameWindowForm.Location.X + gameWindowForm.Width, 0); + +// } + +// void ucp_OnRotateChanged(UCPRotateDirection direction) +// { +// /*var newDirection = (HouseRotation)( +// (((int)Scene.Rotation) + (direction == UCPRotateDirection.Clockwise ? -1 : 1)) % 4 +// );*/ + +// //Scene.Rotation = newDirection; +// } + +// void ucp_OnZoomChanged(WorldZoom zoom) +// { +// World.State.Zoom = zoom; +// //Scene.Zoom = zoom; +// } +// } +//} diff --git a/TSOClient/tso.client/UI/Screens/MaxisLogo.cs b/TSOClient/tso.client/UI/Screens/MaxisLogo.cs index 28fa575b7..745b15f63 100644 --- a/TSOClient/tso.client/UI/Screens/MaxisLogo.cs +++ b/TSOClient/tso.client/UI/Screens/MaxisLogo.cs @@ -1,7 +1,7 @@ -using System.Timers; -using FSO.Client.UI.Framework; +using FSO.Client.GameContent; using FSO.Client.UI.Controls; -using FSO.Client.GameContent; +using FSO.Client.UI.Framework; +using System.Timers; namespace FSO.Client.UI.Screens { @@ -9,7 +9,7 @@ public class MaxisLogo : GameScreen { private UIImage m_MaxisLogo; private UIContainer BackgroundCtnr; - private Timer m_CheckProgressTimer; + private System.Timers.Timer m_CheckProgressTimer; public MaxisLogo() : base() { @@ -25,7 +25,7 @@ public MaxisLogo() : base() this.Add(BackgroundCtnr); - m_CheckProgressTimer = new Timer(); + m_CheckProgressTimer = new System.Timers.Timer(); m_CheckProgressTimer.Interval = 5000; m_CheckProgressTimer.Elapsed += new ElapsedEventHandler(m_CheckProgressTimer_Elapsed); m_CheckProgressTimer.Start(); diff --git a/TSOClient/tso.client/UI/Screens/PersonSelection.cs b/TSOClient/tso.client/UI/Screens/PersonSelection.cs index ec6438713..a9104549a 100644 --- a/TSOClient/tso.client/UI/Screens/PersonSelection.cs +++ b/TSOClient/tso.client/UI/Screens/PersonSelection.cs @@ -1,24 +1,25 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.IO; -using FSO.Client.UI.Framework; -using Microsoft.Xna.Framework.Graphics; +using FSO.Client.Controllers; +using FSO.Client.Regulators; using FSO.Client.UI.Controls; -using FSO.Client.UI.Panels; +using FSO.Client.UI.Framework; using FSO.Client.UI.Framework.Parser; -using Microsoft.Xna.Framework; -using FSO.Files; -using FSO.Common.Utils; -using FSO.Server.Protocol.CitySelector; -using FSO.Vitaboy; -using FSO.Client.Regulators; -using FSO.Client.Controllers; -using FSO.HIT; using FSO.Client.UI.Model; +using FSO.Client.UI.Panels; +using FSO.Client.UI.Panels.Neighborhoods; using FSO.Common; +using FSO.Common.Utils; using FSO.Common.Utils.Cache; +using FSO.Files; +using FSO.HIT; using FSO.Server.Clients; +using FSO.Server.Protocol.CitySelector; +using FSO.Vitaboy; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; namespace FSO.Client.UI.Screens { @@ -143,6 +144,7 @@ public PersonSelection(LoginRegulator loginRegulator, ICache cache) : base() * Button plumbing */ CreditsButton.OnButtonClick += new ButtonClickDelegate(CreditsButton_OnButtonClick); + CreditsButton.Tooltip = GameFacade.Strings.GetString("f128", "125"); m_ExitButton.OnButtonClick += new ButtonClickDelegate(m_ExitButton_OnButtonClick); /** diff --git a/TSOClient/tso.client/UI/Screens/PersonSelectionEdit.cs b/TSOClient/tso.client/UI/Screens/PersonSelectionEdit.cs index 5d513cad5..cd13fa1ef 100644 --- a/TSOClient/tso.client/UI/Screens/PersonSelectionEdit.cs +++ b/TSOClient/tso.client/UI/Screens/PersonSelectionEdit.cs @@ -106,6 +106,7 @@ public PersonSelectionEdit() : base() NameTextEdit.OnChange += new ChangeDelegate(NameTextEdit_OnChange); NameTextEdit.CurrentText = GlobalSettings.Default.LastUser; + GameFacade.Screens.inputManager.SetFocus(NameTextEdit); AcceptButton.Disabled = NameTextEdit.CurrentText.Length == 0; AcceptButton.OnButtonClick += new ButtonClickDelegate(AcceptButton_OnButtonClick); @@ -280,7 +281,10 @@ public ulong BodyOutfitId private void m_ExitButton_OnButtonClick(UIElement button) { - GameFacade.Kill(); + if (FSOFacade.Controller.CloseAttempt()) + { + GameFacade.Kill(); + } } private void CancelButton_OnButtonClick(UIElement button) diff --git a/TSOClient/tso.client/UI/Screens/SandboxGameScreen.cs b/TSOClient/tso.client/UI/Screens/SandboxGameScreen.cs index 8c63cc667..c0d28c35b 100644 --- a/TSOClient/tso.client/UI/Screens/SandboxGameScreen.cs +++ b/TSOClient/tso.client/UI/Screens/SandboxGameScreen.cs @@ -21,6 +21,7 @@ using FSO.SimAntics.NetPlay.Model; using FSO.SimAntics.NetPlay.Model.Commands; using FSO.SimAntics.Utils; +using FSO.LotView; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using System; @@ -32,7 +33,7 @@ namespace FSO.Client.UI.Screens { public class SandboxGameScreen : FSO.Client.UI.Framework.GameScreen, IGameScreen { - public UIUCP ucp; + public UIUCP ucp { get; set; } public UIGameTitle Title; public UIContainer WindowContainer; @@ -186,6 +187,9 @@ public SandboxGameScreen() : base() }; Add(TS1NeighPanel); } + + var status = new UINetStatusTray(); + Add(status); } public override void GameResized() @@ -446,6 +450,7 @@ public void InitializeLot(string lotName, bool external) SkinTone = (byte)settings.DebugSkin, Gender = (short)(settings.DebugGender ? 0 : 1), Permissions = SimAntics.Model.TSOPlatform.VMTSOAvatarPermissions.Admin, + AvatarFlags = SimAntics.Model.TSOPlatform.VMTSOAvatarFlags.Debug, //CustomGUID = 0x396CD3D1, Budget = 1000000, }; @@ -503,11 +508,6 @@ public void InitializeLot(string lotName, bool external) LotControl = new UILotControl(vm, World); this.AddAt(0, LotControl); - var time = DateTime.UtcNow; - var tsoTime = TSOTime.FromUTC(time); - - vm.Context.Clock.Hours = tsoTime.Item1; - vm.Context.Clock.Minutes = tsoTime.Item2; if (m_ZoomLevel > 3) { World.Visible = false; @@ -529,7 +529,8 @@ public void InitializeLot(string lotName, bool external) ActiveFamily.SelectWholeFamily(); vm.TS1State.ActivateFamily(vm, ActiveFamily); } - BlueprintReset(lotName); + + bool fsov = BlueprintReset(lotName); var experimentalTuning = new Common.Model.DynamicTuning(new List { new Common.Model.DynTuningEntry() { tuning_type = "overfill", tuning_table = 255, tuning_index = 15, value = 200 }, @@ -549,7 +550,7 @@ public void InitializeLot(string lotName, bool external) vm.TSOState.Size |= (10) | (3 << 8); vm.Context.UpdateTSOBuildableArea(); - if (vm.GetGlobalValue(11) > -1) + if (!fsov || vm.GetGlobalValue(11) > -1) { for (int y = 0; y < 3; y++) { @@ -577,14 +578,38 @@ public void InitializeLot(string lotName, bool external) } vm.MyUID = myState.PersistID; ZoomLevel = 1; + + var time = DateTime.UtcNow; + var tsoTime = TSOTime.FromUTC(time); + + vm.Context.Clock.Hours = tsoTime.Item1; + vm.Context.Clock.Minutes = tsoTime.Item2; + + if (vm.Context.Architecture != null) + { + if (LotView.WorldConfig.Current.SurroundingLots > 0) + { + SimAntics.Utils.VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj); + } + + AssetStreaming.BeginStreaming(AssetStreamingMode.Lot); + while (!World.Preload(GameFacade.GraphicsDevice)) + { + // Don't show anything until preloading completes. + AssetStreaming.DigestStreamUpdate(); + } + AssetStreaming.EndStreaming(); + } } - public void BlueprintReset(string path) + public bool BlueprintReset(string path) { string filename = Path.GetFileName(path); try { - using (var file = new BinaryReader(File.OpenRead(Path.Combine(FSOEnvironment.UserDir, "LocalHouse/") + filename.Substring(0, filename.Length - 4) + ".fsov"))) + var fsovPath = filename.EndsWith(".fsov") ? path : Path.Combine(FSOEnvironment.UserDir, "LocalHouse/") + filename.Substring(0, filename.Length - 4) + ".fsov"; + + using (var file = new BinaryReader(File.OpenRead(fsovPath))) { var marshal = new SimAntics.Marshals.VMMarshal(); marshal.Deserialize(file); @@ -602,6 +627,8 @@ public void BlueprintReset(string path) ent.ExecuteEntryPoint(2, vm.Context, true); } } + + return true; } catch (Exception) { @@ -643,6 +670,8 @@ public void BlueprintReset(string path) }); } vm.Tick(); + + return false; } @@ -651,7 +680,7 @@ private void Vm_OnGenericVMEvent(VMEventType type, object data) //hmm... } - private void VMLotSwitch(uint lotId) + private void VMLotSwitch(uint lotId, LotTransitionInfo transition) { if ((short)lotId == -1) { diff --git a/TSOClient/tso.client/UI/Screens/TSOInstallScreen.cs b/TSOClient/tso.client/UI/Screens/TSOInstallScreen.cs new file mode 100644 index 000000000..232dc879e --- /dev/null +++ b/TSOClient/tso.client/UI/Screens/TSOInstallScreen.cs @@ -0,0 +1,425 @@ +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Client.Utils; +using FSO.Common.Utils; +using FSO.UI.Controls; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.UI.Screens +{ + internal class UITSOInstallSettingsDialog : UIDialog + { + private static readonly string TSO_DOWNLOAD_URL = "https://freeso.org/redirect/TheSimsOnline"; //"https://archive.org/download/TheSimsOnline_201802/TSO.zip"; + + private UIVBoxContainer RootBox; + private UILabel DescriptionLabel; + private UITextBox PathBox; + private UITextBox DownloadUrlBox; + private UIButton QuitButton; + private UIButton DownloadButton; + public Texture2D FreeSOLogoImage; + public UIImage FreeSOLogo; + + public delegate void BeginDownloadDelegate(string path, string url); + public event BeginDownloadDelegate OnBeginDownload; + + private bool _hasExisting; + + public UITSOInstallSettingsDialog() : base(UIDialogStyle.Standard, true) + { + Caption = ""; + RootBox = new UIVBoxContainer() + { + HorizontalAlignment = UIContainerHorizontalAlignment.Center, + }; + + RootBox.Add(new UISpacer(25)); + + RootBox.Add(DescriptionLabel = new UILabel() + { + Caption = GameFacade.Strings.GetString("f131", "2"), + Size = new Vector2(400, 190), + Wrapped = true + }); + + RootBox.Add(new UILabel() + { + Caption = GameFacade.Strings.GetString("f131", "3"), + Size = new Vector2(400, 16), + Alignment = TextAlignment.Left, + Wrapped = true + }); + RootBox.Add(PathBox = new UITextBox() { Size = new Vector2(400, 25) }); + + RootBox.Add(new UISpacer(0)); + + RootBox.Add(new UILabel() + { + Caption = GameFacade.Strings.GetString("f131", "4"), + Size = new Vector2(400, 16), + Alignment = TextAlignment.Left, + Wrapped = true + }); + RootBox.Add(DownloadUrlBox = new UITextBox() { Size = new Vector2(400, 25) }); + + RootBox.Add(new UISpacer(5)); + + var buttonsBox = new UIHBoxContainer() { Spacing = 30 }; + + buttonsBox.Add(QuitButton = new UIButton() { Caption = GameFacade.Strings.GetString("f131", "5") }); + buttonsBox.Add(DownloadButton = new UIButton() { Caption = GameFacade.Strings.GetString("f131", "6") }); + + RootBox.Add(buttonsBox); + + Add(RootBox); + + // Path should be without TSO client. + var fullPath = Path.GetFullPath(Path.Combine(GlobalSettings.Default.StartupPath, "..")); + var currentDir = Directory.GetCurrentDirectory(); + + PathBox.CurrentText = fullPath.StartsWith(currentDir) ? Path.GetRelativePath(currentDir, fullPath) : fullPath; + DownloadUrlBox.CurrentText = TSO_DOWNLOAD_URL; + + PathBox.OnChange += PathBox_OnChange; + + QuitButton.OnButtonClick += QuitButton_OnButtonClick; + DownloadButton.OnButtonClick += DownloadButton_OnButtonClick; + + RootBox.AutoSize(); + RootBox.Position = new Vector2(25, 40); + SetSize((int)RootBox.Size.X + 50, (int)RootBox.Size.Y + 60); + + var ui = Content.Content.Get().CustomUI; + + FreeSOLogoImage = ui.Get("archive_logo_1x.png").Get(GameFacade.GraphicsDevice); + + FreeSOLogo = new UIImage(FreeSOLogoImage) + { + Position = new Vector2((Width - FreeSOLogoImage.Width) / 2, -31) + }; + + DynamicOverlay.Add(FreeSOLogo); + } + + private void PathBox_OnChange(UIElement element) + { + bool newExisting = false; + + try + { + newExisting = File.Exists(Path.Combine(PathBox.CurrentText, "TSOClient/tuning.dat")); + } + catch + { + // Just ignore if the path is invalid. + } + + if (newExisting != _hasExisting) + { + DownloadUrlBox.Opacity = newExisting ? 0.5f : 1f; + DownloadUrlBox.Mode = newExisting ? UITextEditMode.ReadOnly : UITextEditMode.Editor; + + DownloadButton.Caption = newExisting ? + GameFacade.Strings.GetString("f131", "26") : + GameFacade.Strings.GetString("f131", "6"); + + RootBox.AutoSize(); + + _hasExisting = newExisting; + } + } + + private void DownloadButton_OnButtonClick(UIElement button) + { + OnBeginDownload?.Invoke(PathBox.CurrentText, DownloadUrlBox.CurrentText); + } + + private void QuitButton_OnButtonClick(UIElement button) + { + GameFacade.Kill(); + } + } + + internal class TSOInstallScreen : UIScreen + { + private const long WARNING_SPACE = 1024L * 1024L * 1024L * 3L; + private UISetupBackground Background; + private UIDialog ActiveDialog; + + private string DestPath; + private string InstallerPath; + private string InstallerFolderPath; + + public TSOInstallScreen() : base() + { + Background = new UISetupBackground(); + Add(Background); + + GameThread.NextUpdate((state) => + { + Settings(); + }); + } + + private void Settings() + { + var dialog = new UITSOInstallSettingsDialog(); + dialog.OnBeginDownload += BeginDownload; + + ShowDialog(dialog, true); + ActiveDialog = dialog; + } + + private void CheckDiskSpace(string path, string url) + { + try + { + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + // Check that we can write here. + File.Create(Path.Combine(path, "dummy.txt")).Close(); + File.Delete(Path.Combine(path, "dummy.txt")); + } + catch + { + ShowErrorDialog(GameFacade.Strings.GetString("f131", "16")); // Permissions error? + return; + } + + var info = new DriveInfo(Path.GetFullPath(path)); + + if (info.AvailableFreeSpace < WARNING_SPACE) + { + UIAlert alert = null; + + alert = new UIAlert(new() + { + Title = "", + Message = GameFacade.Strings.GetString("f131", "18"), + Buttons = [ + new UIAlertButton(UIAlertButtonType.Yes, (btn) => + { + RemoveDialog(alert); + + BeginDownloadInternal(path, url); + }, GameFacade.Strings.GetString("f131", "22")), + + new UIAlertButton(UIAlertButtonType.No, (btn) => + { + RemoveDialog(alert); + + Settings(); + }, GameFacade.Strings.GetString("f131", "27")) + ] + }); + + ActiveDialog = alert; + ShowDialog(alert, true); + } + else + { + BeginDownloadInternal(path, url); + } + } + + private void BeginDownloadInternal(string path, string url) + { + string installerPath = Path.Combine(path, "installer.zip"); + + DestPath = path; + InstallerPath = installerPath; + InstallerFolderPath = Path.Combine(DestPath, "installer"); + + var downloader = new UIWebDownloaderDialog(GameFacade.Strings.GetString("f131", "7"), [ + new DownloadItem() { + DestPath = installerPath, + Url = url, + Name = "TSO" + } + ]); + + downloader.OnComplete += DownloadComplete; + + ActiveDialog = downloader; + ShowDialog(downloader, true); + } + + private void BeginDownload(string path, string url) + { + DestPath = path; + InstallerFolderPath = null; + RemoveDialog(ActiveDialog); + + bool alreadyInstalled; + + try + { + alreadyInstalled = File.Exists(Path.Combine(path, "TSOClient/tuning.dat")); + } + catch + { + ShowErrorDialog(GameFacade.Strings.GetString("f131", "16")); // Permissions error? + return; + } + + if (alreadyInstalled) + { + UIAlert alert = null; + alert = GlobalShowAlert(new UIAlertOptions() + { + Title = GameFacade.Strings.GetString("f131", "25"), + Message = GameFacade.Strings.GetString("f131", "19"), + Buttons = [ + new UIAlertButton(UIAlertButtonType.Yes, (btn) => { RemoveDialog(alert); UncabComplete(true, null); }, GameFacade.Strings.GetString("f131", "22")), + new UIAlertButton(UIAlertButtonType.No, (btn) => { RemoveDialog(alert); CheckDiskSpace(path, url); }, GameFacade.Strings.GetString("f131", "23")), + new UIAlertButton(UIAlertButtonType.Cancel, (btn) => { RemoveDialog(alert); Settings(); }, GameFacade.Strings.GetString("f131", "24")), + ] + }, true); + } + else + { + CheckDiskSpace(path, url); + } + } + + private void ShowErrorDialog(string message) + { + // Show alert, return to config. + + UIAlert alert = null; + + alert = new UIAlert(new() + { + Title = "", + Message = message, + Buttons = [ + new UIAlertButton(UIAlertButtonType.OK, (btn) => + { + RemoveDialog(alert); + + Settings(); + }, GameFacade.Strings.GetString("f131", "15")) + ] + }); + + ActiveDialog = alert; + ShowDialog(alert, true); + } + + private void DownloadComplete(bool success, string failedFile = null) + { + RemoveDialog(ActiveDialog); + if (success) + { + // Move onto unzipping the installer. + + if (!Directory.Exists(InstallerFolderPath)) + { + try + { + Directory.CreateDirectory(InstallerFolderPath); + } + catch + { + ShowErrorDialog(GameFacade.Strings.GetString("f131", "16")); // Permissions error? + return; + } + } + + var unzip = new UIZipExtractDialog(GameFacade.Strings.GetString("f131", "8"), InstallerPath, InstallerFolderPath); + + unzip.OnComplete += InstallerUnzipped; + + unzip.Start(); + ActiveDialog = unzip; + ShowDialog(unzip, true); + } + else + { + ShowErrorDialog(GameFacade.Strings.GetString("f131", "11")); + } + } + + private void InstallerUnzipped(bool success, Exception error) + { + RemoveDialog(ActiveDialog); + + if (success) + { + // Delete the installer zip, start extracting from the cab files. + try + { + File.Delete(InstallerPath); + } + catch + { + // Not really fatal, but it is a huge waste of space. + } + + var uncab = new UIZipExtractDialog(GameFacade.Strings.GetString("f131", "9"), Path.Combine(InstallerFolderPath, "Data1.cab"), DestPath); + + uncab.OnComplete += UncabComplete; + + uncab.Start(); + ActiveDialog = uncab; + ShowDialog(uncab, true); + } + else + { + // TODO: special message for out of disk space? + ShowErrorDialog(GameFacade.Strings.GetString("f131", "12", [error.Message])); + } + } + + private void UncabComplete(bool success, Exception error) + { + RemoveDialog(ActiveDialog); + + if (success) + { + if (InstallerFolderPath != null) + { + try + { + Directory.Delete(InstallerFolderPath, true); + } + catch + { + // Not really fatal, but it is a huge waste of space. + } + } + + GlobalSettings.Default.StartupPath = Path.Combine(DestPath, "TSOClient"); + GlobalSettings.Default.Save(); + // on windows, save to the registry? + + UIAlert alert = null; + + alert = new UIAlert(new() + { + Title = "", + Message = GameFacade.Strings.GetString("f131", "14"), + Buttons = [ + new UIAlertButton(UIAlertButtonType.OK, (btn) => + { + FSOFacade.RestartGame(); + }, GameFacade.Strings.GetString("f131", "15")) + ] + }); + + ActiveDialog = alert; + ShowDialog(alert, true); + } + else + { + // TODO: special message for out of disk space? + ShowErrorDialog(GameFacade.Strings.GetString("f131", "13", [error.Message])); + } + } + } +} diff --git a/TSOClient/tso.client/UI/Screens/TSOVersionPatchScreen.cs b/TSOClient/tso.client/UI/Screens/TSOVersionPatchScreen.cs index f1477503a..fcf212637 100644 --- a/TSOClient/tso.client/UI/Screens/TSOVersionPatchScreen.cs +++ b/TSOClient/tso.client/UI/Screens/TSOVersionPatchScreen.cs @@ -87,7 +87,7 @@ public void BeginUpdate() Message = GameFacade.Strings.GetString("f101", "20", new string[] { message }), Buttons = UIAlertButton.Ok(y => { - RestartGame(); + FSOFacade.RestartGame(); }) }, true); } @@ -108,41 +108,11 @@ public void BeginUpdate() Message = GameFacade.Strings.GetString("f101", "13"), Buttons = UIAlertButton.Ok(y => { - RestartGame(); + FSOFacade.RestartGame(); }) }, true); }); }); } - - public void RestartGame() - { - try - { - if (FSOEnvironment.Linux) - { - System.Diagnostics.Process.Start("mono", "FreeSO.exe " + FSOEnvironment.Args); - } - else - { - var args = new ProcessStartInfo(".\\FreeSO.exe", FSOEnvironment.Args); - try - { - - System.Diagnostics.Process.Start(args); - } - catch (Exception) - { - args.FileName = "FreeSO.exe"; - System.Diagnostics.Process.Start(args); - } - } - } catch - { - - } - - GameFacade.Kill(); - } } } diff --git a/TSOClient/tso.client/UI/Screens/TransitionScreen.cs b/TSOClient/tso.client/UI/Screens/TransitionScreen.cs index da446a708..4cffa2fad 100644 --- a/TSOClient/tso.client/UI/Screens/TransitionScreen.cs +++ b/TSOClient/tso.client/UI/Screens/TransitionScreen.cs @@ -1,13 +1,16 @@ -using FSO.Client.UI.Framework; +using FSO.Client.UI.Archive; using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; using FSO.Client.UI.Panels; namespace FSO.Client.UI.Screens { public class TransitionScreen : GameScreen { - private UISetupBackground m_Background; + protected UISetupBackground m_Background; + protected UILabel VersionLabel; private UILoginProgress m_LoginProgress; + private UIButton SandboxModeButton; /// /// Creates a new CityTransitionScreen. @@ -22,11 +25,13 @@ public TransitionScreen() GameFacade.Cursor.SetCursor(Common.Rendering.Framework.CursorType.Hourglass); m_Background = new UISetupBackground(); - var lbl = new UILabel(); - lbl.Caption = "Version " + GlobalSettings.Default.ClientVersion; - lbl.X = 20; - lbl.Y = 558; - m_Background.BackgroundCtnr.Add(lbl); + VersionLabel = new UILabel + { + Caption = "Version " + GlobalSettings.Default.ClientVersion, + X = 20, + Y = 558 + }; + m_Background.BackgroundCtnr.Add(VersionLabel); this.Add(m_Background); m_LoginProgress = new UILoginProgress(); @@ -34,6 +39,9 @@ public TransitionScreen() m_LoginProgress.Y = (ScreenHeight - (m_LoginProgress.Height + 20)); m_LoginProgress.Opacity = 0.9f; this.Add(m_LoginProgress); + + var status = new UINetStatusTray(); + Add(status); } public override void GameResized() @@ -54,11 +62,61 @@ public bool ShowProgress m_LoginProgress.Visible = value; } } - - public void SetProgress(float progress, int stringIndex) + + public void SetProgress(float progress, int stringIndex, string source = "251") + { + m_LoginProgress.ProgressCaption = GameFacade.Strings.GetString(source, (stringIndex).ToString()); + m_LoginProgress.Progress = progress; + } + + public void SetProgressArchive(float progress, string message) { - m_LoginProgress.ProgressCaption = GameFacade.Strings.GetString("251", (stringIndex).ToString()); + // TODO: localization + m_LoginProgress.ProgressCaption = message; m_LoginProgress.Progress = progress; } + + public void ShowSandboxMode() + { + SandboxModeButton = new UIButton() + { + Caption = "Sandbox Mode", + Y = 10, + Width = 125, + X = 10 + }; + this.Add(SandboxModeButton); + SandboxModeButton.OnButtonClick += new ButtonClickDelegate(gameplayButton_OnButtonClick); + } + + void gameplayButton_OnButtonClick(UIElement button) + { + UIScreen.GlobalShowDialog(new UISandboxSelector(), true); + return; + } + + public void SetSandboxVisibility(bool visible) + { + if (SandboxModeButton != null) + { + SandboxModeButton.Visible = visible; + } + } + } + + public class TransitionScreenWithUpdate : TransitionScreen + { + private UIAutoUpdater AutoUpdater; + + public TransitionScreenWithUpdate() : base() + { + /** Auto updater **/ + AutoUpdater = new UIAutoUpdater() + { + X = VersionLabel.X, + Y = VersionLabel.Y - 5 + }; + m_Background.BackgroundCtnr.Add(AutoUpdater); + } } } diff --git a/TSOClient/tso.client/Utils/ArchiveSaves.cs b/TSOClient/tso.client/Utils/ArchiveSaves.cs new file mode 100644 index 000000000..1d8db8e5c --- /dev/null +++ b/TSOClient/tso.client/Utils/ArchiveSaves.cs @@ -0,0 +1,47 @@ +using FSO.Client.Model.Archive; +using FSO.Common; + +namespace FSO.Client.Utils +{ + internal static class ArchiveSaves + { + public static List ListManifests(bool template = false) + { + string[] dirs = Directory.GetDirectories(Path.Combine(FSOEnvironment.ContentDir, "ArchiveCities")); + + var manifests = new List(); + + foreach (string dir in dirs) + { + if (File.Exists(Path.Combine(dir, "archive.ini"))) + { + try + { + var manifest = new ArchiveManifest(Path.Combine(dir, "archive.ini")); + + if (string.IsNullOrEmpty(manifest.LocalDir)) + { + // Try correct it to the default directory `data/` if it exists. + + if (File.Exists(Path.Combine(dir, "data/fsoarchive.db"))) + { + manifest.LocalDir = "data/"; + } + } + + if (manifest.Template == template && (manifest.LocalDir != "" || manifest.ZipLocation != "")) + { + manifests.Add(manifest); + } + } + catch (Exception) + { + // Just ignore it. + } + } + } + + return manifests; + } + } +} diff --git a/TSOClient/tso.client/Utils/ArchiveServerFactory.cs b/TSOClient/tso.client/Utils/ArchiveServerFactory.cs new file mode 100644 index 000000000..bc071e62b --- /dev/null +++ b/TSOClient/tso.client/Utils/ArchiveServerFactory.cs @@ -0,0 +1,366 @@ +using FSO.Client.Controllers; +using FSO.Client.Model.Archive; +using FSO.Client.UI.Controls; +using FSO.Client.UI.Framework; +using FSO.Client.UI.Panels; +using FSO.Common; +using FSO.Common.Utils; +using System.IO.Compression; + +namespace FSO.Client.Utils +{ + internal class ArchiveServerFactory + { + private readonly ArchiveConfiguration Config; + private readonly ConnectArchiveController Controller; + private Action OnResult; + + public ArchiveServerFactory(ArchiveConfiguration config, ConnectArchiveController controller) + { + Config = config; + Controller = controller; + } + + public static ArchiveConfiguration GetQuickStartConfig() + { + var clientConfig = ClientArchiveConfiguration.Default; + var config = clientConfig.ToHostConfig(); + + config.Flags |= ArchiveConfigFlags.QuickStartDesirable; + config.Flags &= ~ArchiveConfigFlags.QuickStartUndesirable; + + config.CityPort = 33101; + config.LotPort = 34101; + + return config; + } + + public ArchiveConfiguration GetConfig() + { + return Config; + } + + private bool ValidateData(ArchiveManifest manifest, out string dir) + { + // Database should exist, Data directory should exist. + // Doesn't validate that they make any sense right now... + + // This is allowed to use absolute paths right now. If users can download these from someone else, that should be changed. + var dataFolder = Path.Combine(Path.GetDirectoryName(manifest.ActivePath), manifest.LocalDir); + + dir = null; + + if (dataFolder == null || dataFolder == "") + { + // Try the data/ subfolder. + + dataFolder = Path.Combine(Path.GetDirectoryName(manifest.ActivePath), "data"); + } + + if (dataFolder == null || !Directory.Exists(dataFolder)) + { + return false; + } + + dir = dataFolder; + + var dbFile = Path.Combine(dataFolder, "fsoarchive.db"); + + return File.Exists(dbFile); + } + + private bool ZipDataPresent(ArchiveManifest manifest, out string path) + { + var folder = Path.GetDirectoryName(manifest.ActivePath); + + path = Path.Combine(folder, "archive.zip"); + + try + { + using (var file = ZipFile.OpenRead(path)) + { + + } + } + catch + { + return false; + } + + // TODO: validate hash? + + return File.Exists(path); + } + + private static string BytesToMiB(long bytes) + { + return $"{bytes / (1024f * 1024f):0.00} MiB"; + } + + private void ExtractArchive(ArchiveManifest manifest, string path, Action onResult) + { + string extractPath = Path.Combine(Path.GetDirectoryName(manifest.ActivePath), "data/"); + var extractor = new UIZipExtractDialog(null, path, extractPath); + + extractor.OnComplete += (result, error) => + { + if (result) + { + UIScreen.RemoveDialog(extractor); + + manifest.LocalDir = "data/"; + manifest.Save(); + + GameThread.SetTimeout(() => + { + try + { + File.Delete(path); + } + catch + { + // Not fatal, just wastes a lot of disk space. + } + }, 100); + + Config.ArchiveDataDirectory = extractPath; + onResult(true); + } + else + { + UIScreen.RemoveDialog(extractor); + onResult(false); + } + }; + + extractor.Start(); + UIScreen.GlobalShowDialog(extractor, true); + } + + private void RequestDownload(ArchiveManifest manifest, Action onResult) + { + var basePath = Path.GetDirectoryName(manifest.ActivePath); + var downloadPath = Path.Combine(basePath, "archive.zip"); + + Uri uri; + try + { + uri = new Uri(manifest.ZipLocation); + } + catch + { + // TODO: dialog? + onResult(false); + return; + } + + _ = long.TryParse(manifest.ZipSize, out long zipSize); + _ = long.TryParse(manifest.Size, out long size); + + UIAlert alert = null; + + var startDownload = () => + { + var downloader = new UIWebDownloaderDialog(GameFacade.Strings.GetString("f128", "5"), + [ + new DownloadItem { + Url = manifest.ZipLocation, + DestPath = downloadPath, + Name = manifest.Name + } + ]); + + downloader.OnComplete += (bool success, string failedFile = null) => + { + UIScreen.RemoveDialog(downloader); + + if (success && ZipDataPresent(manifest, out _)) + { + ExtractArchive(manifest, downloadPath, onResult); + } + else + { + UIScreen.GlobalShowAlert(new UIAlertOptions + { + Title = GameFacade.Strings.GetString("f128", "10"), + Message = GameFacade.Strings.GetString("f128", "11"), + Buttons = UIAlertButton.Ok() + }, true); + + onResult(false); + } + }; + GameThread.NextUpdate(y => UIScreen.GlobalShowDialog(downloader, true)); + }; + + long warningSpace = zipSize + size; + + alert = UIScreen.GlobalShowAlert(new UIAlertOptions + { + Title = GameFacade.Strings.GetString("f128", "1"), + Message = GameFacade.Strings.GetString("f128", "2", [manifest.Name, uri.Host, BytesToMiB(zipSize), BytesToMiB(size)]), + Width = 500, + Buttons = UIAlertButton.YesNo(x => + { + UIScreen.RemoveDialog(alert); + var info = new DriveInfo(Path.GetFullPath(basePath)); + if (info.AvailableFreeSpace < warningSpace) + { + UIAlert alert = null; + + alert = UIScreen.GlobalShowAlert(new() + { + Title = GameFacade.Strings.GetString("f128", "154"), + Message = GameFacade.Strings.GetString("f128", "153", [BytesToMiB(warningSpace), BytesToMiB(info.AvailableFreeSpace)]), + Buttons = UIAlertButton.YesNo(x => + { + UIScreen.RemoveDialog(alert); + + startDownload(); + }, + x => + { + UIScreen.RemoveDialog(alert); + + onResult(false); + }) + }, true); + } + else + { + startDownload(); + } + }, + x => + { + GameThread.NextUpdate(state => + { + UIScreen.RemoveDialog(alert); + onResult(false); + }); + }) + }, true); + } + + public void Start(Action onResult) + { + var manifests = ArchiveSaves.ListManifests(); + + var clientConfig = ClientArchiveConfiguration.Default; + var name = clientConfig.SelectedArchiveName; + var selected = manifests.FirstOrDefault((item) => item.Name == name); + + if (selected == null) + { + // Nothing to start? + onResult(false); + } + else + { + Start(selected, onResult); + } + } + + public void Start(ArchiveManifest manifest, Action onResult) + { + Prepare(manifest, (success) => + { + if (!success) + { + onResult(false); + return; + } + + StartWithConfig(onResult); + }); + } + + public void Prepare(ArchiveManifest manifest, Action onResult) + { + if (ValidateData(manifest, out string dir)) + { + Config.ArchiveDataDirectory = dir; + Config.LoadEvents(); + onResult(true); + } + else + { + if (ZipDataPresent(manifest, out string zipPath)) + { + ExtractArchive(manifest, zipPath, onResult); + } + else + { + // Don't have anything - need to ask the user to download. + + RequestDownload(manifest, onResult); + } + } + } + + private async Task TryUPnP() + { + var cityNat = new NatPuncher("FreeSO Archive City Server"); + + var cityResult = await cityNat.NatPunch(33101, 1, 10); + + if (cityResult == 0 || cityResult == ushort.MaxValue) + { + return false; + } + + var lotNat = new NatPuncher("FreeSO Archive Lot Server", cityNat); + + var lotResult = await lotNat.NatPunch(34101, 1, 10); + + if (lotResult == 0 || lotResult == ushort.MaxValue) + { + cityNat.Dispose(); + return false; + } + + Config.CityPort = cityResult; + Config.LotPort = lotResult; + Config.Disposables = new IDisposable[] { cityNat, lotNat }; + + return true; + } + + private void StartWithConfig(Action onResult) + { + if (Config.Flags.HasFlag(ArchiveConfigFlags.UPnP) && !Config.Flags.HasFlag(ArchiveConfigFlags.Offline)) + { + var alert = UIScreen.GlobalShowAlert(new UIAlertOptions + { + Title = GameFacade.Strings.GetString("f128", "14"), + Message = GameFacade.Strings.GetString("f128", "15"), + Buttons = new UIAlertButton[0] + }, true); + + Task.Run(TryUPnP).ContinueWith(x => + { + bool result = x.Result; + + GameThread.NextUpdate(state => + { + UIScreen.RemoveDialog(alert); + if (result) + { + Controller.CreateServer(Config); + } + else + { + // UPnP failed. Get the user to disable it. + onResult(false); + UIAlert.Alert(GameFacade.Strings.GetString("f128", "16"), GameFacade.Strings.GetString("f128", "17"), true); + } + }); + }); + } + else + { + Controller.CreateServer(Config); + } + } + } +} diff --git a/TSOClient/tso.client/Utils/CabExtractor.cs b/TSOClient/tso.client/Utils/CabExtractor.cs new file mode 100644 index 000000000..2b91da7b0 --- /dev/null +++ b/TSOClient/tso.client/Utils/CabExtractor.cs @@ -0,0 +1,236 @@ +using FSO.Common.Utils; +using FSO.Files.Formats; +using System.Collections.Concurrent; +using static FSO.Client.Utils.MultithreadedZipExtractor; + +namespace FSO.Client.Utils +{ + internal class CabExtractor : AbstractExtractor + { + private struct QueuedFile + { + public string Path; + public byte[] Data; + } + + private struct ActiveFile + { + public bool IsActive; + public CabFileEntry InitialEntry; + public byte[] Data; + public int WriteOffset; + public int RemainingSize => (int)InitialEntry.Size - WriteOffset; + + public ActiveFile(CabFileEntry entry) + { + IsActive = true; + InitialEntry = entry; + Data = new byte[entry.Size]; + } + + public void AddChunk(Span chunk) + { + chunk.CopyTo(Data.AsSpan(WriteOffset)); + + WriteOffset += chunk.Length; + } + } + + private Thread _fileWriterThread; + private readonly BlockingCollection _fileQueue = new(50); + private readonly HashSet CreatedFolders = []; + private int _extractedCount; + private int _entryCount; + + public override void Start(string path, string extractPath, ZipExtractionProgressDelegate onUpdate) + { + base.Start(path, extractPath, onUpdate); + + _onUpdate?.Invoke(ZipExtractionStatus.Preparing, 0, 0); + + _fileWriterThread = new Thread(ConsumeIO); + _fileWriterThread.Start(); + + Task.Run(async () => + { + try + { + await ExtractCab(path); + + StopFileWriter(); + } + catch (Exception e) + { + ReportError(e); + } + }); + } + + private async Task ExtractCab(string path) + { + var firstCab = new CabFile(path); + string cabRoot = Path.GetDirectoryName(path); + + var cab = firstCab; + + var files = new HashSet(); + + // Try and calculate the total number of files by scanning all the cab files. + + while (cab != null) + { + foreach (var file in cab.Files) + { + files.Add(file.Filename); + } + + cab = cab.NextCabName == null ? null : new CabFile(PathUtils.SafeCombine(cabRoot, cab.NextCabName), false); + } + + cab = firstCab; + + _entryCount = files.Count; // The whole archive counts as a file that needs to be completed. + + _onUpdate?.Invoke(ZipExtractionStatus.Extracting, 0, _entryCount); + + CabBlockDecompressor activeFolder = null; + do + { + var folderData = new CabBlockDecompressor[cab.Folders.Length]; + + int folderI = 0; + foreach (var folder in cab.Folders) + { + var folderDecomp = folderI == 0 && activeFolder != null ? activeFolder : new(); + + var hasNext = folderDecomp.AddBlocks(folder.Blocks); + + folderData[folderI++] = folderDecomp; + + if (hasNext) + { + activeFolder = folderDecomp; + } + else + { + activeFolder = null; + } + } + + foreach (var file in cab.Files) + { + bool hasPrev = file.FolderID == 0xFFFD || file.FolderID == 0xFFFF; + bool hasNext = file.FolderID == 0xFFFE || file.FolderID == 0xFFFF; + + ushort folderId = file.FolderID switch + { + 0xFFFD => 0, + 0xFFFF => 0, + 0xFFFE => (ushort)(cab.FolderCount - 1), + _ => file.FolderID + }; + + var dataSource = folderData[folderId]; + + if (!hasNext) + { + // Flush this file's data to the filesystem. + + _fileQueue.Add(new QueuedFile() + { + Path = file.Filename, + Data = dataSource.GetData((int)file.Offset, (int)file.Size) + }); + } + } + + if (cab.NextCabName != null) + { + cab = new CabFile(PathUtils.SafeCombine(cabRoot, cab.NextCabName)); + + Filename = Path.GetFileName(cab.NextCabName); + } + else + { + cab = null; + } + } + while (cab != null && !_failed); + } + + protected override void HandleError() + { + base.HandleError(); + + _fileQueue?.Add(new QueuedFile()); + } + + private void ConsumeIO() + { + try + { + while (!_failed) + { + var item = _fileQueue.Take(); + + if (item.Data == null) + { + return; + } + + string realPath = GetDirectory(item.Path); + File.WriteAllBytes(realPath, item.Data); + + SignalUpdate(); + } + } + catch (Exception e) + { + ReportError(e); + } + } + + private string GetDirectory(string path) + { + string dir = Path.GetDirectoryName(path); + string targetDir = PathUtils.SafeCombine(_extractPath, dir); + + bool isCreated = false; + lock (CreatedFolders) + { + isCreated = CreatedFolders.Contains(targetDir); + } + + if (!isCreated) + { + Directory.CreateDirectory(targetDir); + + lock (CreatedFolders) + { + CreatedFolders.Add(targetDir); + } + } + + return PathUtils.SafeCombine(_extractPath, path); + } + + private void StopFileWriter() + { + _fileQueue.Add(default); + + _fileWriterThread.Join(); + } + + private void SignalUpdate() + { + int extracted = Interlocked.Increment(ref _extractedCount); + + _onUpdate?.Invoke(extracted == _entryCount ? ZipExtractionStatus.Completed : ZipExtractionStatus.Extracting, extracted, _entryCount); + } + + public override void Dispose() + { + + } + } +} diff --git a/TSOClient/tso.client/Utils/FSOFHelper.cs b/TSOClient/tso.client/Utils/FSOFHelper.cs new file mode 100644 index 000000000..af71b2f57 --- /dev/null +++ b/TSOClient/tso.client/Utils/FSOFHelper.cs @@ -0,0 +1,77 @@ +using FSO.Files.RC; +using FSO.LotView; +using FSO.LotView.Facade; +using FSO.SimAntics; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Client.Utils +{ + public class FSOFHelper + { + private GraphicsDevice gd; + private VM vm; + private World world; + + private List<(VMEntity, short)> allLights; + + public FSOFHelper(GraphicsDevice gd, VM vm, World world) + { + this.gd = gd; + this.vm = vm; + this.world = world; + + allLights = [.. vm.Entities.Where(x => x.Object.Resource.SemiGlobal?.Iff?.Filename == "lightglobals.iff") + .Select(x => (x, x.GetValue(SimAntics.Model.VMStackObjectVariable.LightingContribution)))]; + } + + public void SetAllLights(float outsideTime, short contribution) + { + foreach (var light in allLights) + { + light.Item1.SetValue(FSO.SimAntics.Model.VMStackObjectVariable.LightingContribution, contribution); + } + vm.Context.Architecture.SignalRedraw(); + vm.Context.Architecture.Tick(); + SetOutsideTime(outsideTime); + } + + public void RestoreLights() + { + foreach (var light in allLights) + { + light.Item1.SetValue(FSO.SimAntics.Model.VMStackObjectVariable.LightingContribution, light.Item2); + } + vm.Context.Architecture.SignalRedraw(); + vm.Context.Architecture.Tick(); + vm.Context.Architecture.SetTimeOfDay(); + world.Force2DPredraw(gd); + } + + public void SetOutsideTime(float time) + { + vm.Context.Architecture.SetTimeOfDay(time); + world.Force2DPredraw(gd); + vm.Context.Architecture.SetTimeOfDay(); + } + + public FSOF GenerateIngameFSOF() + { + /* + SetOutsideTime(0.5f); + */ + world.State.PrepareLighting(); + var facade = new LotFacadeGenerator(); + facade.FLOOR_TILES = 64; + facade.GROUND_SUBDIV = 5; + facade.FLOOR_RES_PER_TILE = 2; + + SetAllLights(0.5f, 0); + + var result = facade.GetFSOF(gd, world, vm.Context.Blueprint, () => { SetAllLights(0.0f, 100); }, true); + + RestoreLights(); + + return result; + } + } +} diff --git a/TSOClient/tso.client/Utils/GameLocator/ILocator.cs b/TSOClient/tso.client/Utils/GameLocator/ILocator.cs index d505fda23..a0de924d7 100644 --- a/TSOClient/tso.client/Utils/GameLocator/ILocator.cs +++ b/TSOClient/tso.client/Utils/GameLocator/ILocator.cs @@ -2,6 +2,11 @@ { public interface ILocator { + static bool ValidPath(string path) + { + return File.Exists(Path.Combine(path, "tuning.dat")); + } + string FindTheSimsOnline(); } } diff --git a/TSOClient/tso.client/Utils/GameLocator/LinuxLocator.cs b/TSOClient/tso.client/Utils/GameLocator/LinuxLocator.cs index ac51ecdd8..22ffb1b02 100644 --- a/TSOClient/tso.client/Utils/GameLocator/LinuxLocator.cs +++ b/TSOClient/tso.client/Utils/GameLocator/LinuxLocator.cs @@ -1,4 +1,5 @@ -using System.IO; +using System; +using System.IO; namespace FSO.Client.Utils.GameLocator { @@ -7,9 +8,16 @@ public class LinuxLocator : ILocator public string FindTheSimsOnline() { string localDir = @"../The Sims Online/TSOClient/"; - if (File.Exists(Path.Combine(localDir, "tuning.dat"))) return localDir; + if (ILocator.ValidPath(localDir)) return localDir; - return "game/TSOClient/"; + string localDir2 = "game/TSOClient/"; + if (ILocator.ValidPath(localDir2)) return localDir2; + + string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + string homeDir = Path.Combine(home, "Documents", "The Sims Online", "TSOClient") + "/"; + if (ILocator.ValidPath(homeDir)) return homeDir; + + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "The Sims Online", "TSOClient"); } } } diff --git a/TSOClient/tso.client/Utils/GameLocator/MacOSLocator.cs b/TSOClient/tso.client/Utils/GameLocator/MacOSLocator.cs index 14106f65a..9793db1da 100644 --- a/TSOClient/tso.client/Utils/GameLocator/MacOSLocator.cs +++ b/TSOClient/tso.client/Utils/GameLocator/MacOSLocator.cs @@ -8,9 +8,12 @@ public class MacOSLocator : ILocator public string FindTheSimsOnline() { string localDir = @"../The Sims Online/TSOClient/"; - if (File.Exists(Path.Combine(localDir, "tuning.dat"))) return localDir; - - return string.Format("{0}/Documents/The Sims Online/TSOClient/", Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)); + if (ILocator.ValidPath(localDir)) return localDir; + + string docsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "The Sims Online", "TSOClient"); + if (ILocator.ValidPath(docsPath)) return docsPath; + + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "The Sims Online", "TSOClient"); } } } diff --git a/TSOClient/tso.client/Utils/GameLocator/WindowsLocator.cs b/TSOClient/tso.client/Utils/GameLocator/WindowsLocator.cs index d888e517c..73f2b53d9 100644 --- a/TSOClient/tso.client/Utils/GameLocator/WindowsLocator.cs +++ b/TSOClient/tso.client/Utils/GameLocator/WindowsLocator.cs @@ -14,7 +14,7 @@ public string FindTheSimsOnline() // Search relative directory similar to how macOS and Linux works; allows portability string localDir = @"../The Sims Online/TSOClient/"; - if (File.Exists(Path.Combine(localDir, "tuning.dat"))) return localDir; + if (ILocator.ValidPath(localDir)) return localDir; using (var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) { @@ -29,13 +29,22 @@ public string FindTheSimsOnline() RegistryKey tsoKey = maxisKey.OpenSubKey("The Sims Online"); string installDir = (string)tsoKey.GetValue("InstallDir"); installDir += @"\TSOClient\"; - return installDir.Replace('\\', '/'); + installDir = installDir.Replace('\\', '/'); + + if (ILocator.ValidPath(installDir)) + { + return installDir; + } } } } - // Fall back to the default install location if the other two checks fail - return @"C:\Program Files\Maxis\The Sims Online\TSOClient\".Replace('\\', '/'); + string defaultPath = "C:/Program Files/Maxis/The Sims Online/TSOClient/"; + + if (ILocator.ValidPath(defaultPath)) return defaultPath; + + // If nothing was found, try appdata (the user will be asked to install here if it's not already there) + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "The Sims Online", "TSOClient"); } private static bool is64BitProcess = (IntPtr.Size == 8); diff --git a/TSOClient/tso.client/Utils/MonogameLinker.cs b/TSOClient/tso.client/Utils/MonogameLinker.cs index 8ca19daf3..f6848bf70 100644 --- a/TSOClient/tso.client/Utils/MonogameLinker.cs +++ b/TSOClient/tso.client/Utils/MonogameLinker.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -namespace FSO.Client.Utils +namespace FSO.Client.Utils { public class MonogameLinker { @@ -24,7 +21,8 @@ public static bool Link(bool preferDX11) preferDX11 = false; } - try { + try + { string contentDir = "Content/OGL/"; string monogameDir = "Monogame/WindowsGL/"; if (!linux) @@ -47,12 +45,13 @@ public static bool Link(bool preferDX11) return false; } - if (File.Exists("Monogame.Framework.dll")) File.Delete("Monogame.Framework.dll"); + //if (File.Exists("Monogame.Framework.dll")) File.Delete("Monogame.Framework.dll"); AssemblyDir = monogameDir; - } catch (Exception e) + } + catch (Exception e) { - FSOProgram.ShowDialog("Unable to link Monogame. Continuing... ("+e.ToString()+")"); + FSOProgram.ShowDialog("Unable to link Monogame. Continuing... (" + e.ToString() + ")"); } return preferDX11; diff --git a/TSOClient/tso.client/Utils/MultithreadedZipExtractor.cs b/TSOClient/tso.client/Utils/MultithreadedZipExtractor.cs new file mode 100644 index 000000000..65ab2c12f --- /dev/null +++ b/TSOClient/tso.client/Utils/MultithreadedZipExtractor.cs @@ -0,0 +1,255 @@ +using FSO.Common.Utils; +using System.Collections.Concurrent; +using System.IO.Compression; +using static FSO.Client.Utils.MultithreadedZipExtractor; + +namespace FSO.Client.Utils +{ + public enum ZipExtractionStatus + { + Preparing, + Extracting, + Completed, + Error + } + + public abstract class AbstractExtractor : IDisposable + { + protected string _extractPath; + protected ZipExtractionProgressDelegate _onUpdate; + + protected bool _failed; + public Exception Error { get; private set; } + public string Filename { get; protected set; } + + public virtual void Start(string path, string extractPath, ZipExtractionProgressDelegate onUpdate) + { + Filename = Path.GetFileName(path); + _onUpdate = onUpdate; + _extractPath = extractPath; + } + + public abstract void Dispose(); + + protected virtual void HandleError() + { + + } + + public void ReportError(Exception e) + { + if (Interlocked.Exchange(ref _failed, true) == false) + { + Error = e; + + HandleError(); + + _onUpdate?.Invoke(ZipExtractionStatus.Error, 0, 0); + } + } + } + + public class MultithreadedZipExtractor : AbstractExtractor + { + private struct QueuedFile + { + public string Path; + public byte[] Data; + } + + public delegate void ZipExtractionProgressDelegate(ZipExtractionStatus status, int extracted, int total); + private const int IOThreadCount = 4; + + private int _entryCount; + private Thread _extractThread; + + private HashSet _createdFolders = new HashSet(); + + private int _extractedCount; + private bool _cancelled; + private BlockingCollection _fileQueue; + + public override void Start(string path, string extractPath, ZipExtractionProgressDelegate onUpdate) + { + base.Start(path, extractPath, onUpdate); + + _extractThread = new Thread(() => + { + try + { + ExtractThread(path); + } + catch (Exception e) + { + ReportError(e); + } + }); + _extractThread.Start(); + } + + public void ExtractThread(string path) + { + using (var file = ZipFile.OpenRead(path)) + { + _entryCount = 0; + var entries = new List(); + + _onUpdate?.Invoke(ZipExtractionStatus.Preparing, 0, 0); + + foreach (var entry in file.Entries) + { + if (_cancelled || _failed) break; + + if (entry.Name.Length == 0) continue; + + entries.Add(entry); + _entryCount++; + + _onUpdate?.Invoke(ZipExtractionStatus.Preparing, 0, _entryCount); + } + + if (_entryCount == 0) + { + _onUpdate?.Invoke(ZipExtractionStatus.Completed, 0, 0); + return; + } + + var queue = new BlockingCollection(50); + _fileQueue = queue; + + Thread[] consumers = new Thread[IOThreadCount]; + + for (int i = 0; i < consumers.Length; i++) + { + consumers[i] = new Thread(() => ConsumeIO(queue)); + consumers[i].Start(); + } + + foreach (var entry in entries) + { + if (_cancelled || _failed) break; + + bool tooBig = entry.Length > 10_000_000; + + if (tooBig) + { + string realPath = GetDirectory(entry.FullName); + entry.ExtractToFile(realPath, true); + + SignalUpdate(); + } + else + { + byte[] data; + + using (var stream = entry.Open()) + { + using (var mem = new MemoryStream()) + { + stream.CopyTo(mem); + data = mem.ToArray(); + } + } + + var filepath = entry.FullName; + + queue.Add(new QueuedFile() + { + Path = entry.FullName, + Data = data + }); + } + } + + for (int i = 0; i < consumers.Length; i++) + { + queue.Add(new QueuedFile()); // Empty items signal for the consumers to shutdown. + } + + for (int i = 0; i < consumers.Length; i++) + { + consumers[i].Join(); + } + + queue.Dispose(); + } + } + + protected override void HandleError() + { + base.HandleError(); + + if (_fileQueue != null) + { + + for (int i = 0; i < IOThreadCount; i++) + { + // Wake the consumers so that they try to exit. + + _fileQueue.TryAdd(new QueuedFile()); + } + } + } + + private void ConsumeIO(BlockingCollection queue) + { + try + { + while (!_failed && !_cancelled) + { + var item = queue.Take(); + + if (item.Data == null) + { + return; + } + + string realPath = GetDirectory(item.Path); + File.WriteAllBytes(realPath, item.Data); + + SignalUpdate(); + } + } + catch (Exception e) + { + ReportError(e); + } + } + + private void SignalUpdate() + { + int extracted = Interlocked.Increment(ref _extractedCount); + + _onUpdate?.Invoke(extracted == _entryCount ? ZipExtractionStatus.Completed : ZipExtractionStatus.Extracting, extracted, _entryCount); + } + + private string GetDirectory(string path) + { + string dir = Path.GetDirectoryName(path); + string targetDir = PathUtils.SafeCombine(_extractPath, dir); + + bool isCreated = false; + lock (_createdFolders) + { + isCreated = _createdFolders.Contains(targetDir); + } + + if (!isCreated) + { + Directory.CreateDirectory(targetDir); + + lock (_createdFolders) + { + _createdFolders.Add(targetDir); + } + } + + return PathUtils.SafeCombine(_extractPath, path); + } + + public override void Dispose() + { + _cancelled = true; + } + } +} diff --git a/TSOClient/tso.client/Utils/NatPuncher.cs b/TSOClient/tso.client/Utils/NatPuncher.cs new file mode 100644 index 000000000..1612aba12 --- /dev/null +++ b/TSOClient/tso.client/Utils/NatPuncher.cs @@ -0,0 +1,146 @@ +using Open.Nat; +using System; +using System.Net; +using System.Net.NetworkInformation; +using System.Threading; +using System.Threading.Tasks; + +namespace FSO.Client.Utils +{ + public class NatPuncher : IDisposable + { + private const int PortLeaseRenew = 60 * 4; // 4 minutes + private const int PortLeaseLength = 300; // 5 minutes + + private string _entryName; + private bool _disposed; + private Mapping _portMapping; + private NatDevice _device; + private CancellationTokenSource _disposedCancellation; + + public NatPuncher(string entryName) + { + _entryName = entryName; + } + + public NatPuncher(string entryName, NatPuncher other) + { + _entryName = entryName; + _device = other._device; + } + + private static bool CanOpenTCP(ushort port) + { + IPGlobalProperties props = IPGlobalProperties.GetIPGlobalProperties(); + IPEndPoint[] listeners = props.GetActiveTcpListeners(); + + foreach (var listener in listeners) + { + if (listener.Port == port) + { + return false; + } + } + + return true; + } + + public async Task NatPunch(ushort basePort, ushort increment, int attempts) + { + NatDiscoverer discoverer = new NatDiscoverer(); + CancellationTokenSource cts = new CancellationTokenSource(1000); + + try + { + _device = await discoverer.DiscoverDeviceAsync(PortMapper.Upnp, cts); + } + catch (NatDeviceNotFoundException) + { + return 0; // No UPnP available + } + + for (int i = 0; i < attempts; i++) + { + ushort port = (ushort)(basePort + increment * i); + + if (CanOpenTCP(port)) + { + try + { + _portMapping = new Mapping(Protocol.Tcp, port, port, PortLeaseLength, _entryName); + + await _device.CreatePortMapAsync(_portMapping); + + BeginPolling(); + + return port; + } + catch (MappingException) + { + // Failed to get this port, check the next one. + continue; + } + catch (Exception) + { + // Unknown error - is UPnP broken? + return 0; + } + } + } + + return ushort.MaxValue; + } + + private void BeginPolling() + { + _disposedCancellation = new CancellationTokenSource(); + + _ = Task.Delay(PortLeaseRenew * 1000, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease)); + } + + private async Task RefreshLease() + { + if (_disposed || _device == null) + { + return; + } + + _portMapping = new Mapping(Protocol.Tcp, _portMapping.PrivatePort, _portMapping.PublicPort, PortLeaseLength, _portMapping.Description); + + try + { + Console.WriteLine($"Forwarded {_portMapping.PublicPort}"); + await _device.CreatePortMapAsync(_portMapping); + } + catch (Exception) + { + // Just ignore it + } + + _ = Task.Delay(PortLeaseRenew * 1000, _disposedCancellation.Token).ContinueWith((task) => Task.Run(RefreshLease)); + } + + public void Dispose() + { + if (!_disposed && _portMapping != null && _disposedCancellation != null) + { + _disposed = true; + _disposedCancellation.Cancel(); + + var task = Task.Run(async () => + { + try + { + await _device.DeletePortMapAsync(_portMapping); + } + catch (Exception) + { + + } + }); + + task.Wait(500); + } + } + } +} diff --git a/TSOClient/tso.client/app.config b/TSOClient/tso.client/app.config deleted file mode 100644 index 540b96961..000000000 --- a/TSOClient/tso.client/app.config +++ /dev/null @@ -1,129 +0,0 @@ - - - - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - - - English - - - 2106 - - - 0 - - - False - - - False - - - True - - - 2048 - - - True - - - True - - - True - - - True - - - 10 - - - 10 - - - 10 - - - 10 - - - - - - - - - 173.248.136.133 - - - True - - - 1024 - - - 768 - - - - - - True - - - 0 - - - 0 - - - True - - - 0 - - - 1 - - - - diff --git a/TSOClient/tso.client/app.manifest b/TSOClient/tso.client/app.manifest deleted file mode 100644 index 08f910bcd..000000000 --- a/TSOClient/tso.client/app.manifest +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - diff --git a/TSOClient/tso.client/packages.config b/TSOClient/tso.client/packages.config deleted file mode 100644 index e99a6755d..000000000 --- a/TSOClient/tso.client/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.client/sdl2.dll b/TSOClient/tso.client/sdl2.dll deleted file mode 100644 index 2b7e3193c..000000000 Binary files a/TSOClient/tso.client/sdl2.dll and /dev/null differ diff --git a/TSOClient/tso.client/soft_oal.dll b/TSOClient/tso.client/soft_oal.dll deleted file mode 100644 index 8c9e33c2c..000000000 Binary files a/TSOClient/tso.client/soft_oal.dll and /dev/null differ diff --git a/TSOClient/tso.common/ArchiveConfiguration.cs b/TSOClient/tso.common/ArchiveConfiguration.cs new file mode 100644 index 000000000..f8cbb2069 --- /dev/null +++ b/TSOClient/tso.common/ArchiveConfiguration.cs @@ -0,0 +1,343 @@ +using Newtonsoft.Json; +using System.Security.Cryptography; + +namespace FSO.Common +{ + [Flags] + public enum ArchiveConfigFlags + { + None = 0, + Offline = 1 << 0, + UPnP = 1 << 1, + HideNames = 1 << 2, + Verification = 1 << 3, + AllOpenable = 1 << 4, + DebugFeatures = 1 << 5, + AllowLotCreation = 1 << 6, + AllowSimCreation = 1 << 7, + LockArchivedSims = 1 << 8, + ReducedTickRate = 1 << 9, + CityEditor = 1 << 10, + CityEditorMods = 1 << 11, + CityEditorAllUsers = 1 << 12, + DebugFeaturesMods = 1 << 13, + DebugFeaturesAllUsers = 1 << 14, + + DedicatedServer = 1 << 16, + + Default = UPnP | AllOpenable | AllowLotCreation | AllowSimCreation, + + QuickStartDesirable = Offline | AllowLotCreation | AllowSimCreation | AllOpenable, + QuickStartUndesirable = UPnP | ReducedTickRate | Verification, + } + + public class ArchiveConfiguration + { + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("flags")] + public ArchiveConfigFlags Flags { get; set; } + [JsonProperty("archiveDataDirectory")] + public string ArchiveDataDirectory { get; set; } // Effectively equal to the nfs + [JsonProperty("cityPort")] + public ushort CityPort { get; set; } + [JsonProperty("lotPort")] + public ushort LotPort { get; set; } + [JsonProperty("serverKey")] + public string ServerKey { get; set; } + [JsonProperty("serverPublicKey")] + public string ServerPublicKey { get; set; } + [JsonProperty("gameScale")] + public float GameScale { get; set; } = 1; + [JsonProperty("allowUserApi")] + public bool AllowUserApi { get; set; } + + [JsonProperty("initialFunds")] + public int InitialFunds { get; set; } = 20000; + + // Runtime + public IDisposable[] Disposables; + public EventConfig? Events; + + public void LoadEvents() + { + // Try and load associated event config + var eventPath = Path.Combine(ArchiveDataDirectory, "events.json"); + + try + { + var eventJson = File.ReadAllText(eventPath); + + Events = EventConfig.FromJson(eventJson); + } + catch { } + } + + public void SaveEvents() + { + if (Events == null) + { + return; + } + + // Try and save associated event config + var eventPath = Path.Combine(ArchiveDataDirectory, "events.json"); + + try + { + File.WriteAllText(eventPath, Events.Value.ToJson()); + } + catch { } + } + } + + public enum ClientArchiveHistoryType + { + /// + /// MMO type server - user registration/login + mmo gameplay. + /// + FreeSO = 0, + + /// + /// Archive server - anonymous authentication + archive gameplay. + /// + Archive = 1, + + /// + /// Archive server, but triggered by a discord join. + /// Can only store one of these in history for quick rejoins. Address uses basic obfuscation. + /// + DiscordArchive = 2 + } + + public class ClientArchiveHistoryItem(ClientArchiveHistoryType serverType, string name, string address, ArchiveConfigFlags flags) + { + /// + /// The type of server. + /// + [JsonProperty("serverType")] + public ClientArchiveHistoryType ServerType { get; set; } = serverType; + + /// + /// Friendly name of the server. Updated when the server responds to the status query. + /// + [JsonProperty("name")] + public string Name { get; set; } = name; + + /// + /// Address of the server. If this is an archive server, will be hostname:port, otherwise it will be an http api base url. + /// + [JsonProperty("address")] + public string Address { get; set; } = address; + + /// + /// If UPnP is set, the server list will try all possible UPnP ports if the last address:port failed to respond. + /// + [JsonProperty("flags")] + public ArchiveConfigFlags Flags { get; set; } = flags; + } + + public class ClientArchiveConfiguration : JsonConfig + { + [JsonProperty("_comment")] + public string HeadingComment { get; set; } = "Archive client + self-hosting configuration. Don't send this to other users, as it contains authentication keys!"; + + private static ClientArchiveConfiguration defaultInstance; + + public static ClientArchiveConfiguration Default + { + get + { + if (defaultInstance == null) + { + defaultInstance = Load(Path.Combine(FSOEnvironment.UserDir, "archiveConfig.json")); + + defaultInstance.VerifyKeys(); + } + return defaultInstance; + } + } + + private static string GenerateGUID() + { + return Guid.NewGuid().ToString(); + } + + // Client configuration + + [JsonProperty("playerName")] + public string PlayerName { get; set; } = ""; + [JsonProperty("lastJoinedHost")] + public string LastJoinedHost { get; set; } = "127.0.0.1"; + [JsonProperty("selectedArchiveName")] + public string SelectedArchiveName { get; set; } = "FreeSO Archive"; + + // Keys + [JsonProperty("serverPrivateKey")] + public string ServerPrivateKey { get; set; } = ""; + [JsonProperty("serverPublicKey")] + public string ServerPublicKey { get; set; } = ""; + [JsonProperty("clientPrivateKey")] + public string ClientPrivateKey { get; set; } = ""; + [JsonProperty("clientPublicKey")] + public string ClientPublicKey { get; set; } = ""; + + // Server configuration + [JsonProperty("serverName")] + public string ServerName { get; set; } = ""; + [JsonProperty("flags")] + public int Flags { get; set; } = (int)ArchiveConfigFlags.Default; + [JsonProperty("cityPort")] + public ushort CityPort { get; set; } = 33101; + [JsonProperty("lotPort")] + public ushort LotPort { get; set; } = 34101; + [JsonProperty("gameScale")] + public float GameScale { get; set; } = 1; + + [JsonProperty("joinHistory")] + public List JoinHistory = []; + + public EventConfig? Events; + + public ArchiveConfiguration ToHostConfig() + { + return new ArchiveConfiguration() + { + Name = ServerName, + Flags = (ArchiveConfigFlags)Flags, + ArchiveDataDirectory = "", + CityPort = CityPort, + LotPort = LotPort, + GameScale = GameScale, + + ServerKey = ServerPrivateKey, + ServerPublicKey = ServerPublicKey, + }; + } + + public void ApplyHostConfig(ArchiveConfiguration config) + { + Flags = (int)config.Flags; + CityPort = config.CityPort; + LotPort = config.LotPort; + GameScale = config.GameScale; + } + + private void GenerateServerRsaKeys() + { + var rsa = RSA.Create(); + + ServerPublicKey = rsa.ExportRSAPublicKeyPem().Replace('\n', '^'); + ServerPrivateKey = rsa.ExportRSAPrivateKeyPem().Replace('\n', '^'); + } + + private bool VerifyServerRsaKeys() + { + if (ServerPrivateKey == "" || ServerPublicKey == "") + { + return false; + } + + var rsa = RSA.Create(); + + try + { + rsa.ImportFromPem(ServerPublicKey.Replace('^', '\n')); + + var publicRsaParams = rsa.ExportParameters(false); + + rsa.ImportFromPem(ServerPrivateKey.Replace('^', '\n')); + + // If the parameters were updated, it was valid. + + // This will fail if a private key wasn't imported. + var privateRsaParams = rsa.ExportParameters(true); + } + catch (Exception) + { + return false; + } + + return true; + } + + public void VerifyKeys() + { + bool changed = false; + if (!VerifyServerRsaKeys()) + { + GenerateServerRsaKeys(); + changed = true; + } + + if (ClientPrivateKey == "") + { + ClientPrivateKey = GenerateGUID(); + changed = true; + } + + if (ClientPublicKey == "") + { + ClientPublicKey = GenerateGUID(); + changed = true; + } + + if (changed) + { + Save(); + } + } + + public static bool ValidDisplayName(string name) + { + // Maybe there's a better location for this. + return name != null && name.Length > 0 && name.Length <= 24; + } + + public string GetDefaultServerName() + { + return $"{PlayerName}'s Server"; + } + + public string GetServerNameOrDefault() + { + return ServerName.Length > 0 ? ServerName : GetDefaultServerName(); + } + + public void RegisterJoin(ClientArchiveHistoryItem item) + { + var existing = JoinHistory.FindIndex(x => x.Address == item.Address && x.ServerType == item.ServerType); + + if (existing != -1) + { + // If the item already exists, we're moving it to the top (so remove the old entry) + JoinHistory.RemoveAt(existing); + } + + // Add it to the top. + + if (item.ServerType == ClientArchiveHistoryType.DiscordArchive) + { + // Only remember one discord server at a time. + JoinHistory.RemoveAll(x => x.ServerType == ClientArchiveHistoryType.DiscordArchive); + } + + JoinHistory.Insert(0, item); + + Save(); + } + + public void RemoveJoin(ClientArchiveHistoryItem item) + { + var existing = JoinHistory.FindIndex(x => x.Address == item.Address && x.ServerType == item.ServerType); + + if (existing != -1) + { + // If the item already exists, we're removing it. + JoinHistory.RemoveAt(existing); + } + + Save(); + } + } +} diff --git a/TSOClient/tso.common/Audio/MP3Player.cs b/TSOClient/tso.common/Audio/MP3Player.cs index ab2370003..949de4a8a 100644 --- a/TSOClient/tso.common/Audio/MP3Player.cs +++ b/TSOClient/tso.common/Audio/MP3Player.cs @@ -1,275 +1,313 @@ -using Mp3Sharp; -using System; -using System.Collections.Generic; -using Microsoft.Xna.Framework.Audio; +using Microsoft.Xna.Framework.Audio; +using MP3Sharp; +using System.Buffers; +using System.Collections.Concurrent; +using System.IO; using System.Threading; using System.Threading.Tasks; namespace FSO.Common.Audio { - public class MP3Player : ISFXInstanceLike + /// + /// An MP3 audio player that streams and decodes MP3 files into + /// MonoGame's DynamicSoundEffectInstance for playback. + /// + public class MP3Player : ISFXInstanceLike, IDisposable { public static bool NewMode = true; - private Mp3Stream Stream; - public DynamicSoundEffectInstance Inst; - private int LastChunkSize = 1; //don't die immediately.. - private Thread DecoderThread; + private MP3Stream? _stream; + private DynamicSoundEffectInstance? _inst; - private List NextBuffers = new List(); - private List NextSizes = new List(); - private int Requests; - private AutoResetEvent DecodeNext; - private AutoResetEvent BufferDone; - private bool EndOfStream; - private bool Active = true; - private Thread MainThread; //keep track of this, terminate when it closes. + /// + /// Queue holding decoded audio buffers ready for playback. + /// + private readonly ConcurrentQueue<(byte[] Buffer, int Size)> _nextBuffers = new(); - private SoundState _State = SoundState.Stopped; - private bool Disposed = false; + /// + /// Semaphore controlling the number of available buffers. + /// + private readonly SemaphoreSlim _bufferCount = new(0); - private float _Volume = 1f; - private float _Pan; + private CancellationTokenSource? _cts; + private Task? _decoderTask; - private object ControlLock = new object(); - private string Path; public int SendExtra = 2; - private static byte[] Blank = new byte[65536]; + private bool _endOfStream; + private bool _disposed; + private SoundState _state = SoundState.Stopped; + private float _volume = 1f; + private float _pan; + + /// + /// Lock object for synchronizing access to control properties and state changes. + /// + private readonly object _controlLock = new(); + + private readonly string _path; + + /// + /// Blank buffer used when no audio data is available. + /// + private static readonly byte[] _blank = new byte[65536]; + + // Tunables + private readonly int _bufferSize; + private readonly int _initialBuffers; + private readonly int _maxBuffers; + + private readonly ArrayPool _pool = ArrayPool.Shared; + private readonly bool _preload; + + + /// + /// Initializes a new MP3Player instance with default buffering options. + /// + /// Path to the MP3 file. + /// public MP3Player(string path) - { - Path = path; - // //let's get started... + : this(path, preload: false, bufferSize: 262144, initialBuffers: 6, maxBuffers: 12) { } - DecodeNext = new AutoResetEvent(true); - BufferDone = new AutoResetEvent(false); - MainThread = Thread.CurrentThread; - Task.Run((Action)Start); + /// + /// Initializes a new MP3Player instance with custom buffering and preload options. + /// + /// Path to the MP3 file. + /// If true, loads the entire MP3 into memory. + /// Size of each buffer chunk in bytes. + /// Number of buffers to prefill before playback. + /// Maximum number of buffers in the queue. + public MP3Player(string path, bool preload = false, int bufferSize = 262144, int initialBuffers = 6, int maxBuffers = 12) + { + _path = path; + _preload = preload; + _bufferSize = Math.Max(16384, bufferSize); + _initialBuffers = Math.Max(1, initialBuffers); + _maxBuffers = Math.Max(_initialBuffers, maxBuffers); + + Task.Run(Start); } - public void Start() + /// + /// Starts decoding the MP3 file and prepares the DynamicSoundEffectInstance. + /// Runs on a background task and manages buffers asynchronously. + /// + private void Start() { - Stream = new Mp3Stream(Path); - Stream.DecodeFrames(1); - var freq = Stream.Frequency; - lock (ControlLock) + try { - if (Disposed) return; - Inst = new DynamicSoundEffectInstance(freq, AudioChannels.Stereo); - Inst.IsLooped = false; - Inst.BufferNeeded += SubmitBufferAsync; - if (_State == SoundState.Playing) Inst.Play(); - else if (_State == SoundState.Paused) + _cts = new CancellationTokenSource(); + var token = _cts.Token; + + _stream = new MP3Stream(new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.Read)); + _stream.DecodeFrames(1); + var freq = _stream.Frequency; + + lock (_controlLock) { - Inst.Play(); - Inst.Pause(); + if (_disposed) return; + + _inst = new DynamicSoundEffectInstance(freq, AudioChannels.Stereo) + { + IsLooped = false, + Volume = _volume, + Pan = _pan + }; + _inst.BufferNeeded += SubmitBufferAsync; + + switch (_state) + { + case SoundState.Playing: + _inst.Play(); + break; + case SoundState.Paused: + _inst.Play(); + _inst.Pause(); + break; + } } - Inst.Volume = _Volume; - Inst.Pan = _Pan; - Requests = 2; - } - //SubmitBuffer(null, null); - //SubmitBuffer(null, null); + if (_preload) + { + PreloadStream(); + return; + } - DecoderThread = new Thread(() => - { - try + PrefillBuffers(token); + + _decoderTask = Task.Run(async () => { - while (Active && MainThread.IsAlive) + try { - DecodeNext.WaitOne(128); - bool go; - lock (this) go = Requests > 0; - while (go) + while (!token.IsCancellationRequested && !_endOfStream) { - var buf = new byte[262144];// 524288]; - var read = Stream.Read(buf, 0, buf.Length); - lock (this) + if (_nextBuffers.Count < _maxBuffers) { - Requests--; - NextBuffers.Add(buf); - NextSizes.Add(read); - if (read == 0) + var rent = _pool.Rent(_bufferSize); + int read = _stream.Read(rent, 0, rent.Length); + + if (read <= 0) { - EndOfStream = true; - BufferDone.Set(); - return; + _pool.Return(rent); + _endOfStream = true; + break; } - BufferDone.Set(); + + _nextBuffers.Enqueue((rent, read)); + _bufferCount.Release(); + continue; // fill aggressively } - lock (this) go = Requests > 0; + + await Task.Delay(12, token).ConfigureAwait(false); } } - } - catch (Exception e) { } - }); - DecoderThread.Start(); - DecodeNext.Set(); + catch (OperationCanceledException) { } + }, token); + } + catch + { + _endOfStream = true; + } } - public void Play() + private void PreloadStream() { - lock (ControlLock) + using var ms = new MemoryStream(); + var tmp = _pool.Rent(_bufferSize); + try { - _State = SoundState.Playing; - Inst?.Play(); + int read; + while ((read = _stream!.Read(tmp, 0, tmp.Length)) > 0) + { + ms.Write(tmp, 0, read); + } + } + finally + { + _pool.Return(tmp); } - } - public void Stop() - { - lock (ControlLock) + if (ms.Length > 0) { - _State = SoundState.Stopped; - Inst?.Stop(); + _inst?.SubmitBuffer(ms.ToArray(), 0, (int)ms.Length); + _endOfStream = true; } } - public void Pause() + private void PrefillBuffers(CancellationToken token) { - lock (ControlLock) + for (int i = 0; i < _initialBuffers && !token.IsCancellationRequested; i++) { - _State = SoundState.Paused; - Inst?.Pause(); + var buf = _pool.Rent(_bufferSize); + int bytesRead = _stream!.Read(buf, 0, _bufferSize); + + if (bytesRead <= 0) + { + _pool.Return(buf); + _endOfStream = true; + break; + } + + _nextBuffers.Enqueue((buf, bytesRead)); + _bufferCount.Release(); } } - public void Resume() + public void Play() { SetState(SoundState.Playing, s => s.Play()); } + public void Stop() { SetState(SoundState.Stopped, s => s.Stop()); } + public void Pause() { SetState(SoundState.Paused, s => s.Pause()); } + public void Resume() { SetState(SoundState.Playing, s => s.Resume()); } + + private void SetState(SoundState state, Action action) { - lock (ControlLock) + lock (_controlLock) { - _State = SoundState.Playing; - Inst?.Resume(); + _state = state; + if (_inst != null) action(_inst); } } + /// + /// Disposes the MP3Player, releasing all buffers, stopping playback, and canceling decoding. + /// public void Dispose() { - lock (ControlLock) + lock (_controlLock) { - Disposed = true; - Inst?.Dispose(); - Stream?.Dispose(); + if (_disposed) return; - Active = false; - DecodeNext.Set(); //end the mp3 thread immediately + _disposed = true; + _cts?.Cancel(); + _inst?.Dispose(); + _stream?.Dispose(); + } - EndOfStream = true; + while (_nextBuffers.TryDequeue(out var tuple)) + { + _pool.Return(tuple.Buffer); } - } - public bool IsEnded() - { - return EndOfStream && Inst.PendingBufferCount == 0; + while (_bufferCount.CurrentCount > 0) _bufferCount.Wait(0); + try { _decoderTask?.Wait(50); } catch { } + + GC.SuppressFinalize(this); } + public bool IsEnded() => _endOfStream && _inst?.PendingBufferCount == 0; + public float Volume { - get - { - lock (ControlLock) - { - if (Inst != null) return Inst.Volume; - else return _Volume; - } - } - set - { - lock (ControlLock) - { - _Volume = value; - if (Inst != null) Inst.Volume = value; - } - } + get { lock (_controlLock) return _inst?.Volume ?? _volume; } + set { SetControlProperty(ref _volume, value, (inst, v) => inst.Volume = v); } } public float Pan { - get - { - lock (ControlLock) - { - if (Inst != null) return Inst.Pan; - else return _Pan; - } - } - set - { - lock (ControlLock) - { - _Pan = value; - if (Inst != null) Inst.Pan = value; - } - } + get { lock (_controlLock) return _inst?.Pan ?? _pan; } + set { SetControlProperty(ref _pan, value, (inst, v) => inst.Pan = v); } } - public SoundState State + private void SetControlProperty(ref float backingField, float value, Action setter) { - get + lock (_controlLock) { - lock (ControlLock) - { - if (Inst != null) return Inst.State; - else return _State; - } + backingField = value; + if (_inst != null) setter(_inst, value); } } + public SoundState State { get { lock (_controlLock) return _inst?.State ?? _state; } } + public bool IsLooped { get; set; } - private void SubmitBuffer(object sender, EventArgs e) + private void SubmitBufferAsync(object? sender, EventArgs e) { - byte[] buffer = new byte[524288]; - lock (this) + if (_endOfStream && _bufferCount.CurrentCount == 0) return; + + if (!_bufferCount.Wait(50)) { - var read = Stream.Read(buffer, 0, buffer.Length); - LastChunkSize = read; - if (read == 0) - { - return; - } - Inst.SubmitBuffer(buffer, 0, read); + _inst?.SubmitBuffer(_blank, 0, _blank.Length); + return; } - } - private void SubmitBufferAsync(object sender, EventArgs e) - { - while (true) + if (_nextBuffers.TryDequeue(out var tuple)) { - if (EndOfStream) return; - var gotData = false; - lock (this) + try { - if (NextBuffers.Count > 0) - { - if (NextSizes[0] > 0) Inst.SubmitBuffer(NextBuffers[0], 0, NextSizes[0]); - gotData = true; - NextBuffers.RemoveAt(0); - NextSizes.RemoveAt(0); - Requests++; - DecodeNext.Set(); - if (SendExtra > 0) - { - SendExtra--; - continue; - } - return; - } - - if (EndOfStream) return; + if (tuple.Size > 0) + _inst?.SubmitBuffer(tuple.Buffer, 0, tuple.Size); } - if (!gotData) + finally { - Inst.SubmitBuffer(Blank, 0, Blank.Length); - Requests++; - DecodeNext.Set(); - return; - //if (NewMode) BufferDone.WaitOne(128); + _pool.Return(tuple.Buffer); } } + else + { + _inst?.SubmitBuffer(_blank, 0, _blank.Length); + } } } diff --git a/TSOClient/tso.common/Enum/LotCategory.cs b/TSOClient/tso.common/Enum/LotCategory.cs index 998aa12b6..1089cb158 100644 --- a/TSOClient/tso.common/Enum/LotCategory.cs +++ b/TSOClient/tso.common/Enum/LotCategory.cs @@ -15,6 +15,10 @@ public enum LotCategory residence = 10, community = 11, //cannot be set by users + archive_design = 251, + archive_playerschoice = 252, + archive_momichoice = 253, + archive_welcome = 254, recent = 255 //for filter searches } } diff --git a/TSOClient/tso.common/EventConfig.cs b/TSOClient/tso.common/EventConfig.cs new file mode 100644 index 000000000..08046176e --- /dev/null +++ b/TSOClient/tso.common/EventConfig.cs @@ -0,0 +1,109 @@ +namespace FSO.Common +{ + public struct EventCatalogEntry + { + public string label; + public int value; + public string startDate; + public string endDate; + } + + public struct EventModifierGift + { + public string title; + public string description; + public uint[] guids; + } + + public struct EventModifierOption + { + public string name; + public string label; + public string category; + public string unique; + public Dictionary tuning; + public bool enableTimed; + + // optional + public EventModifierGift? gift; + public string startDate; + public string endDate; + public bool enableManual; + } + + public struct EventModifier + { + public string name; + public string label; + public string type; + public string startDate; + public string endDate; + public EventModifierOption[] options; + } + + public struct EventConfig + { + public bool timed; + public EventCatalogEntry[] catalog; + public EventModifier[] modifiers; + public float? skillSpeed; + public float? payoutScale; + public float? singleplayerPenalty; + public int? speedyJobProgression; + + public static EventConfig FromJson(string json) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(json); + } + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static (DateTime, DateTime) GetNextRange(string start, string end) + { + var startDate = GetNextDayMonth(start); + var endDate = GetNextDayMonth(end); + + var now = DateTime.UtcNow; + + if (startDate > endDate) + { + // This implies the event carries through the end of the year into next year. + if (now > endDate) + { + // Start date is this year, end date is next + endDate = endDate.AddYears(1); + } + else + { + // Start date was last year (event is currently active) + startDate = startDate.AddYears(-1); + } + } + else if (now > endDate) + { + // If we're after the end date, move it to next year. + startDate = startDate.AddYears(1); + endDate = endDate.AddYears(1); + } + + return (startDate, endDate); + } + + private static DateTime GetNextDayMonth(string dayMonth) + { + var split = dayMonth.Split('-'); + + if (split.Length != 2 || !int.TryParse(split[0], out int day) || !int.TryParse(split[1], out int month)) + { + throw new InvalidDataException("Event date not correctly formatted, should be day-month."); + } + + var now = DateTime.UtcNow; + + return new DateTime(now.Year, month, day); + } + } +} diff --git a/TSOClient/tso.common/FSO.Common.csproj b/TSOClient/tso.common/FSO.Common.csproj index 02883c0c0..af4b55a0e 100644 --- a/TSOClient/tso.common/FSO.Common.csproj +++ b/TSOClient/tso.common/FSO.Common.csproj @@ -1,309 +1,42 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - Library - Properties - FSO.Common - FSO.Common - v4.5 - 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + net9.0 + enable + disable + True + true + true + true + full - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - false - true + + + True - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - true + + + True + - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - - - true - bin\x86\Debug\ - DEBUG;TRACE - true - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - ..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll - - - ..\packages\NLog.4.5.7\lib\net45\NLog.dll - - - - ..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {834cab58-648d-47cc-ac6f-d01c08c809a4} - Mp3Sharp - + + + - - Always + + PreserveNewest + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - + + + + + - - - - \ No newline at end of file + + diff --git a/TSOClient/tso.common/FSOEnvironment.cs b/TSOClient/tso.common/FSOEnvironment.cs index abe4e6fbd..eb9b68642 100644 --- a/TSOClient/tso.common/FSOEnvironment.cs +++ b/TSOClient/tso.common/FSOEnvironment.cs @@ -9,10 +9,25 @@ public static class FSOEnvironment public static string ContentDir = "Content/"; public static string UserDir = "Content/"; public static string GFXContentDir = "Content/OGL"; - public static bool DirectX = false; + public static bool MissingTSO = false; + private static bool _DirectX = false; + public static bool DirectX + { + get { return _DirectX; } + set + { + _DirectX = value; + // MacOS opengl drivers seem to have broken texel centers. + PxOffset2D = (_DirectX || !OperatingSystem.IsMacOS()) ? 0 : 0.5f; + } + } public static bool Linux = false; public static bool UseMRT = true; /// + /// Some platforms require a UV offset to realign pixel centers to avoid visual issues in 2D mode. + /// + public static float PxOffset2D = 0f; + /// /// True if system does not support gl_FragDepth (eg. iOS). Uses alternate pipeline that abuses stencil buffer. /// public static bool SoftwareDepth = false; @@ -27,7 +42,8 @@ public static class FSOEnvironment /// True if 3D features are enabled (like smooth rotation + zoom). Loads some content with mipmaps and other things. /// Used to mean "3d camera" as well, though that has been moved to configuration and world state. ///
- public static bool Enable3D; + public static bool Default3D = false; + public static bool Enable3D = true; public static bool EnableNPOTMip = true; public static bool TexCompress = true; public static bool TexCompressSupport = true; diff --git a/TSOClient/tso.common/FSOVersionInfo.cs b/TSOClient/tso.common/FSOVersionInfo.cs new file mode 100644 index 000000000..c6e83f92c --- /dev/null +++ b/TSOClient/tso.common/FSOVersionInfo.cs @@ -0,0 +1,105 @@ +using Newtonsoft.Json; + +namespace FSO.Common +{ + /// + /// Version info for a FreeSO client or server. + /// Should be saved as `version.json` next to the binary. + /// When acting as a server, sent to connecting clients so they can ensure the same version. + /// + public class FSOVersionInfo : IEquatable + { + /// + /// Public key used for official FreeSO client updates. + /// This changes the update warnings a little when transitioning from FreeSO update to another source. + /// + public static string FreeSOPublicKey = "-----BEGIN RSA PUBLIC KEY-----^MIIBCgKCAQEAukMS/klrVM7N7hjcfrWbgK7UjIU352RWkcAYRv5Uh7pt6Gd4U6Ng^9J8OoNCuU1aZfFEauCQvkX4i53KGWEKpBoBA6e3zFIGhyZQeq\u002BytxegDx/iMgRCi^U\u002BaKH1\u002BdxbmL/FU10eX2JNErhcJvQ/tcWttc\u002BJdWQlKM\u002BPBBR5PmgUrdcPBvhled^CMg9W\u002BRSGoAgqozeaspYPFJG3FoZDCqp16WZ7oFWAGsSKq2ovy2wPAMFqMrcGlas^pcWWXbVv\u002BgUefhWPjWZFAZObX77FCVdraHJlKnu5o2UX9XjNXkM6SDTeuDCHjap/^N/E2uWL5soHCtyUB9cqUt3penJ4mov\u002BeQQIDAQAB^-----END RSA PUBLIC KEY-----"; + + private static FSOVersionInfo _current; + public static FSOVersionInfo Current + { + get + { + if (_current == null) + { + _current = GetCurrent(); + } + + return _current; + } + } + + private static FSOVersionInfo GetCurrent() + { + try + { + if (File.Exists("version.json")) + { + using StreamReader reader = new StreamReader(File.Open("version.json", FileMode.Open, FileAccess.Read, FileShare.Read)); + + var result = JsonConvert.DeserializeObject(reader.ReadToEnd()); + + if (result != null) + { + return result; + } + } + } + catch (Exception) + { + + } + + return new FSOVersionInfo() + { + id = "dev", + channel = "FreeSO Development Build", + channelUrl = "", + publicKey = "" + }; + } + + public string id { get; set; } = "unknown"; + public string channel { get; set; } = "invalid"; + public string channelUrl { get; set; } = ""; + public string publicKey { get; set; } = ""; + + public static FSOVersionInfo FromJson(string json) + { + var result = JsonConvert.DeserializeObject(json); + + return result ?? new FSOVersionInfo(); + } + + public string ToJson() + { + return JsonConvert.SerializeObject(this); + } + + public FSOVersionInfo Clone() + { + return new FSOVersionInfo() + { + id = id, + channel = channel, + channelUrl = channelUrl, + publicKey = publicKey, + }; + } + + public bool Equals(FSOVersionInfo other) + { + return id == other.id && channel == other.channel && channelUrl == other.channelUrl && publicKey == other.publicKey; + } + + public override bool Equals(object obj) + { + return obj is FSOVersionInfo info && Equals(info); + } + + public override int GetHashCode() + { + return HashCode.Combine(id, channel, channelUrl, publicKey); + } + } +} diff --git a/TSOClient/tso.common/IniConfig.cs b/TSOClient/tso.common/IniConfig.cs index a7602882e..96d1d4d56 100644 --- a/TSOClient/tso.common/IniConfig.cs +++ b/TSOClient/tso.common/IniConfig.cs @@ -7,7 +7,8 @@ namespace FSO.Common { public abstract class IniConfig { - private string ActivePath; + public abstract string HeadingComment { get; } + public string ActivePath { get; private set; } public abstract Dictionary DefaultValues { @@ -21,6 +22,11 @@ private void SetValue(string key, string value) { try { + if (prop.SetMethod == null) + { + return; + } + if (prop.PropertyType != typeof(string)) prop.SetValue(this, Convert.ChangeType(value, prop.PropertyType, CultureInfo.InvariantCulture)); else prop.SetValue(this, value); @@ -63,13 +69,13 @@ public void Load() } } - public void Save() + public virtual void Save() { try { using (var stream = new StreamWriter(File.Open(ActivePath, FileMode.Create, FileAccess.Write))) { - stream.WriteLine("# FreeSO Settings File. Properties are self explanatory."); + stream.WriteLine($"# {HeadingComment}"); var props = this.GetType().GetProperties(); foreach (var prop in props) { diff --git a/TSOClient/tso.common/JsonConfig.cs b/TSOClient/tso.common/JsonConfig.cs new file mode 100644 index 000000000..c7e935b57 --- /dev/null +++ b/TSOClient/tso.common/JsonConfig.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; + +namespace FSO.Common +{ + public class JsonConfig + { + public string ActivePath { get; private set; } + + public JsonConfig() + { + } + + public virtual void Init() + { + + } + + public static T Load(string path) where T : JsonConfig, new() + { + if (!File.Exists(path)) + { + var item = new T + { + ActivePath = path + }; + item.Init(); + item.Save(); + + return item; + } + else + { + var str = File.ReadAllText(path); + + var item = JsonConvert.DeserializeObject(str); + item.ActivePath = path; + + return item; + } + } + + public virtual void Save() + { + try + { + using (var stream = new StreamWriter(File.Open(ActivePath, FileMode.Create, FileAccess.Write))) + { + stream.Write(JsonConvert.SerializeObject(this)); + } + } + catch (Exception) { } + } + } +} diff --git a/TSOClient/tso.common/MeshSimplify/MSTriangle.cs b/TSOClient/tso.common/MeshSimplify/MSTriangle.cs index 354a3e548..b778a57d0 100644 --- a/TSOClient/tso.common/MeshSimplify/MSTriangle.cs +++ b/TSOClient/tso.common/MeshSimplify/MSTriangle.cs @@ -1,11 +1,61 @@ using Microsoft.Xna.Framework; +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace FSO.Common.MeshSimplify { - public class MSTriangle + [StructLayout(LayoutKind.Sequential)] + public struct MSTriangleIndices { - public int[] v = new int[3]; - public double[] err = new double[4]; + public int i0; + public int i1; + public int i2; + + public MSTriangleIndices(int i0, int i1, int i2) + { + this.i0 = i0; this.i1 = i1; this.i2 = i2; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct MSTriangleError + { + public double e0; + public double e1; + public double e2; + public double e3; + } + + public static class MSTriangleExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref int GetRef(this ref MSTriangleIndices foo, int i) + { + if (i < 0 || i > 2) + { + throw new IndexOutOfRangeException(); + } + + return ref Unsafe.Add(ref foo.i0, i); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ref double GetRef(this ref MSTriangleError foo, int i) + { + if (i < 0 || i > 3) + { + throw new IndexOutOfRangeException(); + } + + return ref Unsafe.Add(ref foo.e0, i); + } + } + + public struct MSTriangle + { + public MSTriangleIndices v; + public MSTriangleError err; public bool deleted, dirty; public Vector3 n; } diff --git a/TSOClient/tso.common/MeshSimplify/MSVertex.cs b/TSOClient/tso.common/MeshSimplify/MSVertex.cs index 1c80658d8..4e1b1f2d9 100644 --- a/TSOClient/tso.common/MeshSimplify/MSVertex.cs +++ b/TSOClient/tso.common/MeshSimplify/MSVertex.cs @@ -2,7 +2,7 @@ namespace FSO.Common.MeshSimplify { - public class MSVertex + public struct MSVertex { public Vector3 p; public Vector2 t; //texcoord diff --git a/TSOClient/tso.common/MeshSimplify/Simplify.cs b/TSOClient/tso.common/MeshSimplify/Simplify.cs index 051774b9a..535867960 100644 --- a/TSOClient/tso.common/MeshSimplify/Simplify.cs +++ b/TSOClient/tso.common/MeshSimplify/Simplify.cs @@ -14,10 +14,22 @@ namespace FSO.Common.MeshSimplify { public class Simplify { - public List triangles = new List(); - public List vertices = new List(); + public MSTriangle[] triangles; + private int triangleCount; + + public MSVertex[] vertices; + private int vertexCount; + public List refs = new List(); + public Simplify(MSTriangle[] triangles, MSVertex[] vertices) + { + this.triangles = triangles; + this.triangleCount = triangles.Length; + this.vertices = vertices; + this.vertexCount = vertices.Length; + } + public void simplify_mesh(int target_count, double agressiveness = 7, int iterations = 100) { //for (int i=0; i(); var deleted1 = new List(); - int triangle_count = triangles.Count; + int triangle_count = triangleCount; for (int iteration=0; iteration threshold) continue; + ref var t = ref triangles[i]; //readonly + if (t.err.e3 > threshold) continue; if (t.deleted) continue; if (t.dirty) continue; for (int j = 0; j < 3; j++) { - if (t.err[j] < threshold) + if (t.err.GetRef(j) < threshold) { - int i0 = t.v[j]; var v0 = vertices[i0]; - int i1 = t.v[(j + 1) % 3]; var v1 = vertices[i1]; + int i0 = t.v.GetRef(j); ref var v0 = ref vertices[i0]; + int i1 = t.v.GetRef((j + 1) % 3); ref var v1 = ref vertices[i1]; // Border check if (v0.border != v1.border) continue; @@ -81,8 +93,8 @@ public void simplify_mesh(int target_count, double agressiveness = 7, int iterat for (int n = 0; n < v1.tcount; n++) deleted1.Add(0); // dont remove if flipped - if (flipped(p, i0, i1, v0, v1, deleted0)) continue; - if (flipped(p, i1, i0, v1, v0, deleted1)) continue; + if (flipped(in p, i0, i1, in v0, in v1, deleted0)) continue; + if (flipped(in p, i1, i0, in v1, in v0, deleted1)) continue; // not flipped, so remove edge @@ -96,8 +108,8 @@ public void simplify_mesh(int target_count, double agressiveness = 7, int iterat v0.q = v1.q + v0.q; int tstart = refs.Count; - update_triangles(i0, v0, deleted0, ref deleted_triangles); - update_triangles(i0, v1, deleted1, ref deleted_triangles); + update_triangles(i0, in v0, deleted0, ref deleted_triangles); + update_triangles(i0, in v1, deleted1, ref deleted_triangles); int tcount = refs.Count - tstart; @@ -128,17 +140,17 @@ public void simplify_mesh(int target_count, double agressiveness = 7, int iterat // Check if a triangle flips when this edge is removed - bool flipped(Vector3 p, int i0, int i1, MSVertex v0, MSVertex v1, List deleted) + bool flipped(in Vector3 p, int i0, int i1, in MSVertex v0, in MSVertex v1, List deleted) { int bordercount = 0; for (int k=0; k dele // Update triangle connections and edge error after a edge is collapsed - void update_triangles(int i0, MSVertex v, List deleted, ref int deleted_triangles) + void update_triangles(int i0, in MSVertex v, List deleted, ref int deleted_triangles) { Vector3 p = Vector3.Zero; for (int k = 0; k < v.tcount; k++) { var r = refs[v.tstart + k]; - var t = triangles[r.tid]; + ref var t = ref triangles[r.tid]; if (t.deleted) continue; if (k < deleted.Count && deleted[k] > 0) { @@ -174,12 +186,12 @@ void update_triangles(int i0, MSVertex v, List deleted, ref int deleted_tri deleted_triangles++; continue; } - t.v[r.tvertex] = i0; + t.v.GetRef(r.tvertex) = i0; t.dirty = true; - t.err[0] = calculate_error(t.v[0], t.v[1], ref p); - t.err[1] = calculate_error(t.v[1], t.v[2], ref p); - t.err[2] = calculate_error(t.v[2], t.v[0], ref p); - t.err[3] = Math.Min(t.err[0], Math.Min(t.err[1], t.err[2])); + t.err.e0 = calculate_error(t.v.i0, t.v.i1, ref p); + t.err.e1 = calculate_error(t.v.i1, t.v.i2, ref p); + t.err.e2 = calculate_error(t.v.i2, t.v.i0, ref p); + t.err.e3 = Math.Min(t.err.e0, Math.Min(t.err.e1, t.err.e2)); refs.Add(r); } } @@ -191,14 +203,14 @@ void update_mesh(int iteration) if (iteration > 0) // compact triangles { int dst = 0; - for (int i = 0; i vcount = new List(); List vids = new List(); - for (int i = 0; i < vertices.Count; i++) + for (int i = 0; i < vertexCount; i++) vertices[i].border = false; - for (int i = 0; i < vertices.Count; i++) + for (int i = 0; i < vertexCount; i++) { - var v = vertices[i]; + ref var v = ref vertices[i]; vcount.Clear(); vids.Clear(); for (int j = 0; j < v.tcount; j++) { int k = refs[v.tstart + j].tid; - var t = triangles[k]; + ref var t = ref triangles[k]; //readonly for (k = 0; k < 3; k++) { - int ofs = 0, id = t.v[k]; + int ofs = 0, id = t.v.GetRef(k); while (ofs < vcount.Count) { if (vids[ofs] == id) break; @@ -322,22 +337,24 @@ void update_mesh(int iteration) void compact_mesh() { int dst = 0; - for (int i = 0; i < vertices.Count; i++) + for (int i = 0; i < vertexCount; i++) { vertices[i].tcount = 0; } - for (int i = 0; i < triangles.Count; i++) + for (int i = 0; i < triangleCount; i++) { if (!triangles[i].deleted) { var t = triangles[i]; triangles[dst++] = t; - for (int j = 0; j < 3; j++) vertices[t.v[j]].tcount = 1; + for (int j = 0; j < 3; j++) vertices[t.v.GetRef(j)].tcount = 1; } } - triangles.RemoveRange(dst, triangles.Count - dst); + triangleCount = dst; + Array.Resize(ref triangles, triangleCount); + dst = 0; - for (int i = 0; i < vertices.Count; i++) + for (int i = 0; i < vertexCount; i++) { if (vertices[i].tcount > 0) { @@ -347,12 +364,13 @@ void compact_mesh() dst++; } } - for (int i = 0; i < triangles.Count; i++) + for (int i = 0; i < triangleCount; i++) { - var t = triangles[i]; - for (int j = 0; j < 3; j++) t.v[j] = vertices[t.v[j]].tstart; + ref var t = ref triangles[i]; + for (int j = 0; j < 3; j++) t.v.GetRef(j) = vertices[t.v.GetRef(j)].tstart; } - vertices.RemoveRange(dst, vertices.Count - dst); + vertexCount = dst; + Array.Resize(ref vertices, vertexCount); } // Error between vertex and Quadric diff --git a/TSOClient/tso.common/MeshSimplify/SymmetricMatrix.cs b/TSOClient/tso.common/MeshSimplify/SymmetricMatrix.cs index 23b0be39d..ee5eefb9d 100644 --- a/TSOClient/tso.common/MeshSimplify/SymmetricMatrix.cs +++ b/TSOClient/tso.common/MeshSimplify/SymmetricMatrix.cs @@ -1,9 +1,22 @@ -namespace FSO.Common.MeshSimplify +using System; +using System.Runtime.CompilerServices; + +namespace FSO.Common.MeshSimplify { - public class SymmetricMatrix + public struct SymmetricMatrix { - public SymmetricMatrix(double c) { - for (int i=0; i<10; i++) m[i] = c; + public double m11; + public double m12; + public double m13; + public double m14; + public double m22; + public double m23; + public double m24; + public double m33; + public double m34; + public double m44; + + public SymmetricMatrix(double c) : this(c, c, c, c, c, c, c, c, c, c) { } @@ -12,45 +25,63 @@ public SymmetricMatrix(double m11, double m12, double m13, double m14, double m33, double m34, double m44) { - m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14; - m[4] = m22; m[5] = m23; m[6] = m24; - m[7] = m33; m[8] = m34; - m[9] = m44; + this.m11 = m11; this.m12 = m12; this.m13 = m13; this.m14 = m14; + this.m22 = m22; this.m23 = m23; this.m24 = m24; + this.m33 = m33; this.m34 = m34; + this.m44 = m44; } // Make plane public SymmetricMatrix(double a, double b, double c, double d) { - m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d; - m[4] = b * b; m[5] = b * c; m[6] = b * d; - m[7] = c * c; m[8] = c * d; - m[9] = d * d; + this.m11 = a * a; this.m12 = a * b; this.m13 = a * c; this.m14 = a * d; + this.m22 = b * b; this.m23 = b * c; this.m24 = b * d; + this.m33 = c * c; this.m34 = c * d; + this.m44 = d * d; } public double this[int c] { - get { return m[c]; } - set { m[c] = value; } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (c < 0 || c > 9) + { + throw new IndexOutOfRangeException(); + } - public double[] m = new double[10]; + return Unsafe.Add(ref m11, c); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if (c < 0 || c > 9) + { + throw new IndexOutOfRangeException(); + } + + Unsafe.Add(ref m11, c) = value; + } + } //determinant + [MethodImpl(MethodImplOptions.AggressiveInlining)] public double det(int a11, int a12, int a13, int a21, int a22, int a23, int a31, int a32, int a33) { - double det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + m[a12] * m[a23] * m[a31] - - m[a13] * m[a22] * m[a31] - m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33]; + double det = this[a11] * this[a22] * this[a33] + this[a13] * this[a21] * this[a32] + this[a12] * this[a23] * this[a31] + - this[a13] * this[a22] * this[a31] - this[a11] * this[a23] * this[a32] - this[a12] * this[a21] * this[a33]; return det; } public static SymmetricMatrix operator +(SymmetricMatrix m, SymmetricMatrix n) { - return new SymmetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3] + n[3], - m[4] + n[4], m[5] + n[5], m[6] + n[6], - m[7] + n[7], m[8] + n[8], - m[9] + n[9]); + return new SymmetricMatrix(m.m11 + n.m11, m.m12 + n.m12, m.m13 + n.m13, m.m14 + n.m14, + m.m22 + n.m22, m.m23 + n.m23, m.m24 + n.m24, + m.m33 + n.m33, m.m34 + n.m34, + m.m44 + n.m44); } } } diff --git a/TSOClient/tso.common/Model/DynamicTuning.cs b/TSOClient/tso.common/Model/DynamicTuning.cs index b835fb33c..280f6e104 100644 --- a/TSOClient/tso.common/Model/DynamicTuning.cs +++ b/TSOClient/tso.common/Model/DynamicTuning.cs @@ -45,6 +45,17 @@ public DynamicTuning(IEnumerable entries) } } + public DynamicTuning(IEnumerable entries, HashSet filter) + { + foreach (var entry in entries) + { + if (filter.Contains(entry.tuning_type)) + { + AddTuning(entry); + } + } + } + public void AddTuning(DynTuningEntry entry) { Dictionary> tables; diff --git a/TSOClient/tso.common/Model/LotTransitionInfo.cs b/TSOClient/tso.common/Model/LotTransitionInfo.cs new file mode 100644 index 000000000..81fc3f794 --- /dev/null +++ b/TSOClient/tso.common/Model/LotTransitionInfo.cs @@ -0,0 +1,93 @@ +using Microsoft.Xna.Framework; + +namespace FSO.Common.Model +{ + public enum LotTransitionType + { + None, + DirectControl, + Routing + } + + public class LotTransitionInfo + { + public uint BeforeLocation; + public int RelativeChangeX; + public int RelativeChangeY; + + public int AvatarLotTilePosX; + public int AvatarLotTilePosY; + public float AvatarDirection; + + public LotTransitionType Type; + public uint RoutingTargetLocation; + public int RoutingLotTilePosX; + public int RoutingLotTilePosY; + + /// + /// By default, relative change x/y are in lot space, for calculation of the new lot tile pos on the target lot. + /// This function converts them to offsets usable for city map coordinates. + /// + /// Relative change in lot space + /// Relative change in city space + public static Point RelativeChangeLotToCity(Point relativeChange) + { + return new Point(-relativeChange.Y, relativeChange.X); + } + + /// + /// This function converts relative city tile x/y into lot space. + /// + /// Relative change in city space + /// Relative change in lot space + public static Point RelativeChangeCityToLot(Point relativeChange) + { + return new Point(relativeChange.Y, -relativeChange.X); + } + + /// + /// Gets a mask for the surrounding lots that need to be reloaded for this transition. + /// In order (-1, -1), (0, -1), (1, -1), (-1, 0)... + /// Bits that aren't set should be able to copy surrounding lots from the previous lot, rather than re-initialzing them. + /// + /// + public uint GetSurroundingLotMask() + { + uint updateMask = 0b111111111; + var cityOffset = RelativeChangeLotToCity(new Point(RelativeChangeX, RelativeChangeY)); + + int i = 0; + for (int y = -1; y < 2; y++) + { + for (int x = -1; x < 2; x++) + { + uint bit = 1u << i; + + // If this lot was present as an old surround lot, we can inherit it. + var oldX = x + cityOffset.X; + var oldY = y + cityOffset.Y; + + if (Math.Abs(oldX) < 2 && Math.Abs(oldY) < 2 && !(oldX == 0 && oldY == 0)) + { + // Within bounds, not the source lot. + updateMask &= ~bit; + } + + i++; + } + } + + return updateMask; + } + + public int GetOldSubworldForIndex(int index) + { + var cityOffset = RelativeChangeLotToCity(new Point(RelativeChangeX, RelativeChangeY)); + + index += cityOffset.X; + index += cityOffset.Y * 3; + + return index; + } + } +} diff --git a/TSOClient/tso.common/Model/SurroundPuppet.cs b/TSOClient/tso.common/Model/SurroundPuppet.cs new file mode 100644 index 000000000..9e6c19cb8 --- /dev/null +++ b/TSOClient/tso.common/Model/SurroundPuppet.cs @@ -0,0 +1,177 @@ +using Microsoft.Xna.Framework; + +namespace FSO.Common.Model +{ + [Flags] + public enum SurroundPuppetDelta : int + { + None = 0, + BodyInfo = 1 << 0, // persist id, skin tone, outfits, skeleton name + Position = 1 << 1, + Appearances = 1 << 2, + AnimationNames = 1 << 3, + AnimationState = 1 << 4, + + Animation = AnimationNames | AnimationState, + All = BodyInfo | Position | Appearances | Animation, + + Required = BodyInfo | Position | Animation, + + // This isn't a delta flag - it's just a special flag that means that this puppet should disappear + // if there's a puppet somewhere else without the flag, or a user on the source lot. + // This prevents some duplicate overlapping puppets during lot transitions. + Leaving = 1 << 31, + } + + [Flags] + public enum SurroundPuppetAnimationFlags + { + None = 0, + EndReached = 1 << 0, + PlayingBackwards = 1 << 1, + Loop = 1 << 2, + } + + public struct SurroundPuppetAnimation(string name, float currentFrame, float speed, float weight, SurroundPuppetAnimationFlags flags) + { + public readonly string Name = name; + public readonly float CurrentFrame = currentFrame; + public readonly float Speed = speed; + public readonly float Weight = weight; + public readonly SurroundPuppetAnimationFlags Flags = flags; + + public readonly bool EndReached => Flags.HasFlag(SurroundPuppetAnimationFlags.EndReached); + public readonly bool PlayingBackwards => Flags.HasFlag(SurroundPuppetAnimationFlags.PlayingBackwards); + public readonly bool Loop => Flags.HasFlag(SurroundPuppetAnimationFlags.Loop); + + public SurroundPuppetAnimation(string name, float currentFrame, float speed, float weight, bool endReached, bool playingBackwards, bool loop) + : this(name, currentFrame, speed, weight, (endReached ? SurroundPuppetAnimationFlags.EndReached : 0) | + (playingBackwards ? SurroundPuppetAnimationFlags.PlayingBackwards : 0) | + (loop ? SurroundPuppetAnimationFlags.Loop : 0)) + { + } + } + + public struct SurroundPuppet + { + public SurroundPuppetDelta Delta; + public uint PersistID; + public uint SkinTone; + public ulong HeadOutfit; + public ulong BodyOutfit; + public string SkeletonName; + public Vector4 VisualPositionStart; + public Vector4 Velocity; + public SurroundPuppetAnimation[] Animations; + public string[] Appearances; + + public void CalculateDelta(in SurroundPuppet previous) + { + SurroundPuppetDelta delta = Delta & (SurroundPuppetDelta.Leaving); + + if (PersistID != previous.PersistID || SkinTone != previous.SkinTone || HeadOutfit != previous.HeadOutfit || BodyOutfit != previous.BodyOutfit || SkeletonName != previous.SkeletonName) + { + delta |= SurroundPuppetDelta.BodyInfo; + } + + if (VisualPositionStart != previous.VisualPositionStart || Velocity != previous.Velocity) + { + delta |= SurroundPuppetDelta.Position; + } + + if (!Appearances.SequenceEqual(previous.Appearances)) + { + delta |= SurroundPuppetDelta.Appearances; + } + + if (Animations.Length != previous.Animations.Length) + { + delta |= SurroundPuppetDelta.Animation; + } + else + { + for (int i = 0; i < Animations.Length; i++) + { + ref readonly var anim = ref Animations[i]; + ref readonly var oldAnim = ref previous.Animations[i]; + + if (anim.Name != oldAnim.Name) + { + delta |= SurroundPuppetDelta.AnimationNames; + } + + if (anim.CurrentFrame != oldAnim.CurrentFrame || anim.Weight != oldAnim.Weight || anim.Speed != oldAnim.Speed || anim.Flags != oldAnim.Flags) + { + delta |= SurroundPuppetDelta.AnimationState; + } + } + } + + Delta = delta; + } + + public void ApplyDelta(SurroundPuppet puppet) + { + var delta = puppet.Delta; + if (delta.HasFlag(SurroundPuppetDelta.BodyInfo)) + { + PersistID = puppet.PersistID; + SkinTone = puppet.SkinTone; + HeadOutfit = puppet.HeadOutfit; + BodyOutfit = puppet.BodyOutfit; + SkeletonName = puppet.SkeletonName; + } + + if (delta.HasFlag(SurroundPuppetDelta.Position)) + { + VisualPositionStart = puppet.VisualPositionStart; + Velocity = puppet.Velocity; + } + + if ((delta & SurroundPuppetDelta.Animation) != 0) + { + if ((delta & SurroundPuppetDelta.Animation) == SurroundPuppetDelta.Animation || puppet.Animations.Length != (Animations?.Length ?? 0)) + { + Animations = puppet.Animations; + } + else + { + for (int i = 0; i < Animations.Length; i++) + { + ref var anim = ref Animations[i]; + ref var deltaAnim = ref puppet.Animations[i]; + + string animName = anim.Name; + + if (delta.HasFlag(SurroundPuppetDelta.AnimationNames)) + { + animName = deltaAnim.Name; + } + + float animCurrentFrame = anim.CurrentFrame; + float animSpeed = anim.Speed; + float animWeight = anim.Weight; + SurroundPuppetAnimationFlags animFlags = anim.Flags; + + if (delta.HasFlag(SurroundPuppetDelta.AnimationState)) + { + animCurrentFrame = deltaAnim.CurrentFrame; + animSpeed = deltaAnim.Speed; + animWeight = deltaAnim.Weight; + animFlags = deltaAnim.Flags; + } + + anim = new SurroundPuppetAnimation(animName, animCurrentFrame, animSpeed, animWeight, animFlags); + } + } + } + + if (delta.HasFlag(SurroundPuppetDelta.Appearances)) + { + Appearances = puppet.Appearances; + } + + Delta = delta; + } + } +} diff --git a/TSOClient/tso.common/Properties/AssemblyInfo.cs b/TSOClient/tso.common/Properties/AssemblyInfo.cs deleted file mode 100644 index ba49bd896..000000000 --- a/TSOClient/tso.common/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TSO.Common")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TSO.Common")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("f1dd298b-4150-4948-8cb2-dbba73e35dac")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.common/Rendering/Emoji/EmojiCache.cs b/TSOClient/tso.common/Rendering/Emoji/EmojiCache.cs index 8e4a301a0..e415e9d75 100644 --- a/TSOClient/tso.common/Rendering/Emoji/EmojiCache.cs +++ b/TSOClient/tso.common/Rendering/Emoji/EmojiCache.cs @@ -1,9 +1,6 @@ using FSO.Common.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; using System.Net; namespace FSO.Common.Rendering.Emoji @@ -13,7 +10,7 @@ public class EmojiCache public string Source = "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/"; public int DefaultRes = 24; public int Width = 32; - + public int NextIndex = 0; public List Emojis = new List(); public Dictionary EmojiToIndex = new Dictionary(); @@ -28,10 +25,10 @@ public EmojiCache(GraphicsDevice gd) { GD = gd; EmojiBatch = new SpriteBatch(gd); - + EmojiTex = new RenderTarget2D(gd, Width * DefaultRes, Width * DefaultRes, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); - ServicePointManager.Expect100Continue = true; - ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11; + //ServicePointManager.Expect100Continue = true; + //ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11; } public void ExpandIfNeeded() @@ -39,13 +36,15 @@ public void ExpandIfNeeded() //todo } - public Rectangle GetEmoji(string emojiID) { + public Rectangle GetEmoji(string emojiID) + { int index; if (EmojiToIndex.TryGetValue(emojiID, out index)) { return RectForIndex(index); - } else + } + else { index = NextIndex++; ExpandIfNeeded(); @@ -56,7 +55,8 @@ public Rectangle GetEmoji(string emojiID) { if (e.Cancelled || e.Error != null || e.Result == null) { lock (ErrorSpaces) ErrorSpaces.Add(index); - } else + } + else { GameThread.NextUpdate(x => { @@ -71,7 +71,7 @@ public Rectangle GetEmoji(string emojiID) { GD.SetRenderTarget(EmojiTex); if (needClear) { - GD.Clear(Color.TransparentBlack); + GD.Clear(ColorExtensions.TransparentBlack); needClear = false; } EmojiBatch.Begin(blendState: BlendState.NonPremultiplied, sortMode: SpriteSortMode.Immediate); @@ -88,7 +88,7 @@ public Rectangle GetEmoji(string emojiID) { } lock (IncompleteSpaces) IncompleteSpaces.Remove(index); }; - client.DownloadDataAsync(new Uri((emojiID[0] == '!')?(emojiID.Substring(1)):(Source + emojiID + ".png"))); + client.DownloadDataAsync(new Uri((emojiID[0] == '!') ? (emojiID.Substring(1)) : (Source + emojiID + ".png"))); Emojis.Add(emojiID); EmojiToIndex[emojiID] = index; return RectForIndex(index); diff --git a/TSOClient/tso.common/Rendering/Framework/3DTargetScene.cs b/TSOClient/tso.common/Rendering/Framework/3DTargetScene.cs index 90cda627d..9f7fc8766 100644 --- a/TSOClient/tso.common/Rendering/Framework/3DTargetScene.cs +++ b/TSOClient/tso.common/Rendering/Framework/3DTargetScene.cs @@ -15,6 +15,12 @@ public _3DTargetScene(GraphicsDevice device, Point size, int multisample) : base { Device = device; Multisample = multisample; + + if (OperatingSystem.IsMacOS()) + { + Multisample = 0; + } + SetSize(size); } diff --git a/TSOClient/tso.common/Rendering/Framework/CursorManager.cs b/TSOClient/tso.common/Rendering/Framework/CursorManager.cs index fc3b3ef96..30c728c3c 100644 --- a/TSOClient/tso.common/Rendering/Framework/CursorManager.cs +++ b/TSOClient/tso.common/Rendering/Framework/CursorManager.cs @@ -52,6 +52,7 @@ public class CursorManager private Dictionary m_CursorMap; private GraphicsDevice GD; public CursorType CurrentCursor { get; internal set;} = CursorType.Normal; + public int CurrentPriority { get; private set; } = 0; public CursorManager(GraphicsDevice gd) { @@ -60,9 +61,14 @@ public CursorManager(GraphicsDevice gd) this.GD = gd; } - public void SetCursor(CursorType type) + public void SetCursorPriority(int priority) { - if (m_CursorMap.ContainsKey(type)) + CurrentPriority = priority; + } + + public void SetCursor(CursorType type, int priority = 0) + { + if (CurrentCursor != type && priority >= CurrentPriority && m_CursorMap.ContainsKey(type)) { CurrentCursor = type; Mouse.SetCursor(m_CursorMap[type].MouseCursor); diff --git a/TSOClient/tso.common/Rendering/Framework/GameScreen.cs b/TSOClient/tso.common/Rendering/Framework/GameScreen.cs index a3c1b81e0..580cc1f16 100644 --- a/TSOClient/tso.common/Rendering/Framework/GameScreen.cs +++ b/TSOClient/tso.common/Rendering/Framework/GameScreen.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; -using System.Linq; +using FSO.Common.Rendering.Framework.Model; +using FSO.Common.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using FSO.Common.Rendering.Framework.Model; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input.Touch; +using System.Collections.Generic; +using System.Linq; namespace FSO.Common.Rendering.Framework { @@ -130,6 +131,8 @@ public void Update(GameTime time, bool hasFocus) State.SharedData.Clear(); State.Update(); + GameThread.DigestUpdate(State); + foreach (var layer in Layers){ layer.Update(State); } diff --git a/TSOClient/tso.common/Rendering/Framework/IO/IFocusableUI.cs b/TSOClient/tso.common/Rendering/Framework/IO/IFocusableUI.cs index a69829e51..f7e3b6dd0 100644 --- a/TSOClient/tso.common/Rendering/Framework/IO/IFocusableUI.cs +++ b/TSOClient/tso.common/Rendering/Framework/IO/IFocusableUI.cs @@ -2,7 +2,9 @@ { public interface IFocusableUI { - void OnFocusChanged(FocusEvent newFocus); + bool IsFocused { get; set; } + int TabIndex { get; } + void OnFocusChanged(FocusEvent newFocus) { } } public enum FocusEvent diff --git a/TSOClient/tso.common/Rendering/Framework/IO/InputManager.cs b/TSOClient/tso.common/Rendering/Framework/IO/InputManager.cs index 6a4868e9d..ae011ae81 100644 --- a/TSOClient/tso.common/Rendering/Framework/IO/InputManager.cs +++ b/TSOClient/tso.common/Rendering/Framework/IO/InputManager.cs @@ -23,12 +23,14 @@ public void SetFocus(IFocusableUI ui) if (LastFocus != null) { + LastFocus.IsFocused = false; LastFocus.OnFocusChanged(FocusEvent.FocusOut); } LastFocus = ui; if (ui != null) { + LastFocus.IsFocused = true; LastFocus.OnFocusChanged(FocusEvent.FocusIn); } } @@ -534,6 +536,11 @@ public void HandleMouseEvents(UpdateState state) mouse.LastMouseDown = mouse.LastMouseOver; mouse.LastMouseDown.Callback(UIMouseEventType.MouseDown, state); } + else if (LastFocus != null) + { + // If nothing has been clicked, clear focus. + SetFocus(null); + } } else { diff --git a/TSOClient/tso.common/Rendering/Framework/Model/UIState.cs b/TSOClient/tso.common/Rendering/Framework/Model/UIState.cs index caa28b845..d922887da 100644 --- a/TSOClient/tso.common/Rendering/Framework/Model/UIState.cs +++ b/TSOClient/tso.common/Rendering/Framework/Model/UIState.cs @@ -8,6 +8,21 @@ public class UIState public int Height; public UITooltipProperties TooltipProperties = new UITooltipProperties(); public string Tooltip; + + public void SetTooltip(UpdateState state, string message, Color color) + { + TooltipProperties.Show = true; + TooltipProperties.Color = color; + state.UIState.TooltipProperties.Opacity = 1; + state.UIState.TooltipProperties.Position = new Vector2(state.MouseState.X, state.MouseState.Y); + state.UIState.Tooltip = message; + state.UIState.TooltipProperties.UpdateDead = false; + } + + public void SetTooltip(UpdateState state, string message) + { + SetTooltip(state, message, Color.Black); + } } public class UITooltipProperties diff --git a/TSOClient/tso.common/Rendering/Framework/Model/UpdateState.cs b/TSOClient/tso.common/Rendering/Framework/Model/UpdateState.cs index 1198d7998..4c4c61212 100644 --- a/TSOClient/tso.common/Rendering/Framework/Model/UpdateState.cs +++ b/TSOClient/tso.common/Rendering/Framework/Model/UpdateState.cs @@ -43,6 +43,9 @@ public bool AltDown { get { return KeyboardState.IsKeyDown(Keys.LeftAlt) || KeyboardState.IsKeyDown(Keys.RightAlt); } } + public bool ActivationKeyPressed => NewKeys.Contains(Keys.Enter) || NewKeys.Contains(Keys.Space); + public bool FocusNextPressed => NewKeys.Contains(Keys.Tab) && !ShiftDown; + public bool FocusPrevPressed => NewKeys.Contains(Keys.Tab) && ShiftDown; public UIState UIState = new UIState(); public InputManager InputManager; @@ -59,6 +62,8 @@ public bool AltDown private List KeyInRepeatMode = new List(); public List NewKeys = new List(); + public int MouseWheelDelta; + private int _prevWheelPos; public int Depth; public bool WindowFocused; @@ -75,6 +80,8 @@ public bool ProcessMouseEvents public void Update() { NewKeys.Clear(); + MouseWheelDelta = (MouseState.ScrollWheelValue - _prevWheelPos) / 120; + _prevWheelPos = MouseState.ScrollWheelValue; Depth = 0; /** diff --git a/TSOClient/tso.common/Security/ISecurityContext.cs b/TSOClient/tso.common/Security/ISecurityContext.cs index 851deb177..4d3f488ca 100644 --- a/TSOClient/tso.common/Security/ISecurityContext.cs +++ b/TSOClient/tso.common/Security/ISecurityContext.cs @@ -4,6 +4,7 @@ namespace FSO.Common.Security { public interface ISecurityContext { + bool HasModerationLevel(int threshold); void DemandAvatar(uint id, AvatarPermissions permission); void DemandAvatars(IEnumerable id, AvatarPermissions permission); void DemandInternalSystem(); diff --git a/TSOClient/tso.common/Security/NullSecurityContext.cs b/TSOClient/tso.common/Security/NullSecurityContext.cs index 6b9d748e2..51c0434e0 100644 --- a/TSOClient/tso.common/Security/NullSecurityContext.cs +++ b/TSOClient/tso.common/Security/NullSecurityContext.cs @@ -6,6 +6,10 @@ public class NullSecurityContext : ISecurityContext { public static NullSecurityContext INSTANCE = new NullSecurityContext(); + public bool HasModerationLevel(int threshold) + { + return true; + } public void DemandAvatar(uint id, AvatarPermissions permission) { diff --git a/TSOClient/tso.common/Utils/AssetStreaming.cs b/TSOClient/tso.common/Utils/AssetStreaming.cs new file mode 100644 index 000000000..95de0fd58 --- /dev/null +++ b/TSOClient/tso.common/Utils/AssetStreaming.cs @@ -0,0 +1,166 @@ +using FSO.Common.Rendering.Framework.Model; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; + +namespace FSO.Common.Utils +{ + public enum AssetStreamingMode + { + None = 0, + Lot = 1, + } + + public static class AssetStreaming + { + private static object _StreamCallbacksLock = new object(); + private static Queue _StreamUpdateCallbacks = new Queue(); + private static Queue _StreamUpdateCallbacksSwap = new Queue(); + + private static AssetStreamingMode _LoadingType = AssetStreamingMode.None; + private static int _LoadingCount; + + private static int _LoadingRequests; + private static int _LoadingComplete; + + public static AssetStreamingMode LoadingType => _LoadingType; + + public static void DigestStreamUpdate() + { + Queue _callbacks; + + lock (_StreamCallbacksLock) + { + // Swap the active callbacks queue with the second one, so we can + // process entries without fear of more being added. + + _callbacks = _StreamUpdateCallbacks; + _StreamUpdateCallbacks = _StreamUpdateCallbacksSwap; + _StreamUpdateCallbacksSwap = _callbacks; + } + + // These callbacks have a frametime budget. If it's exceeded, the callbacks are pushed onto the next frame. + float frameAllowance = 0.002f; + float budgetSeconds = Math.Max(1f / FSOEnvironment.RefreshRate - frameAllowance, 0.005f); + long budgetTicks = (long)(Stopwatch.Frequency * budgetSeconds); + + long startTime = Stopwatch.GetTimestamp(); + + while (_callbacks.Count > 0) + { + _callbacks.Dequeue()(); + + long now = Stopwatch.GetTimestamp(); + + if ((now - startTime) > budgetTicks) + { + break; + } + } + + if (_callbacks.Count > 0) + { + lock (_StreamCallbacksLock) + { + // Push remaining callbacks onto the next frame. + + while (_callbacks.Count > 0) + { + _StreamUpdateCallbacks.Enqueue(_callbacks.Dequeue()); + } + } + } + } + + public static void InStreamUpdate(Callback callback) + { + lock (_StreamCallbacksLock) + { + _StreamUpdateCallbacks.Enqueue(callback); + } + } + + /// + /// Load a texture with support for multithreading during loading screens. + /// The texture should already be created with the correct width and height. + /// The data provider is called from a background thread when a loading state is active, + /// otherwise it's done in the current thread. + /// + /// Texture data type + /// Texture to put data into + /// The type of loading required for this texture load to multithread + /// Texture data generator + public static void LoadTexture(Texture2D tex, AssetStreamingMode type, Func[]> dataProvider) where T : struct + { + if (_LoadingType >= type) + { + // Async load. Try source the data on a task, then set it on the game thread. + AddLoadingResource(); + + Task.Run(dataProvider).ContinueWith((taskResult) => + { + var data = taskResult.Result; + InStreamUpdate(() => + { + TextureUtils.UploadTexData(tex, data); + + RemoveLoadingResource(); + }); + }); + } + else + { + var data = dataProvider(); + + TextureUtils.UploadTexData(tex, data); + } + } + + public static void BeginStreaming(AssetStreamingMode type) + { + _LoadingType = type; + } + + /// + /// Ends the multithreaded loading period. + /// Returns true when there are no pending loads. + /// + /// + public static bool EndStreaming() + { + _LoadingType = AssetStreamingMode.None; + + return Volatile.Read(ref _LoadingCount) == 0; + } + + public static void AddLoadingResource() + { + Interlocked.Increment(ref _LoadingRequests); + Interlocked.Increment(ref _LoadingCount); + } + + public static void RemoveLoadingResource() + { + Interlocked.Increment(ref _LoadingComplete); + Interlocked.Decrement(ref _LoadingCount); + } + + public static int GetCheckpoint() + { + // Note: doesn't entirely work as intended right now. + // Mesh reading tasks can dispatch other texture reading tasks that aren't counted when the checkpoint was taken. + + return Volatile.Read(ref _LoadingRequests); + } + + public static bool IsCheckpointMet(int checkpoint) + { + int diff = Volatile.Read(ref _LoadingComplete) - checkpoint; + + return diff >= 0; + } + } +} diff --git a/TSOClient/tso.common/Utils/ColorExtensions.cs b/TSOClient/tso.common/Utils/ColorExtensions.cs new file mode 100644 index 000000000..57e7c2103 --- /dev/null +++ b/TSOClient/tso.common/Utils/ColorExtensions.cs @@ -0,0 +1,9 @@ +using Microsoft.Xna.Framework; + +namespace FSO.Common.Utils +{ + public static class ColorExtensions + { + public static readonly Color TransparentBlack = new Color(0, 0, 0, 0); + } +} diff --git a/TSOClient/tso.common/Utils/CurLoader.cs b/TSOClient/tso.common/Utils/CurLoader.cs index ccc1ea013..ba01003c2 100644 --- a/TSOClient/tso.common/Utils/CurLoader.cs +++ b/TSOClient/tso.common/Utils/CurLoader.cs @@ -77,9 +77,21 @@ public static Tuple LoadCursor(GraphicsDevice gd, Stream strea outIO.Write(new char[] { 'B', 'M' }); outIO.Write(size + 14); //size, plus header outIO.Write(0); - outIO.Write(14); var data = new byte[size]; stream.Read(data, 0, size); + + var biSize = BitConverter.ToInt32(data, 0); + var biBitCount = BitConverter.ToInt16(data, 14); + var biClrUsed = BitConverter.ToInt32(data, 32); + + int paletteSize = 0; + if (biBitCount <= 8) + { + int colorCount = biClrUsed != 0 ? biClrUsed : (1 << biBitCount); + paletteSize = colorCount * 4; + } + + outIO.Write(14 + biSize + paletteSize); outIO.Write(data); tempbmp.Seek(0, SeekOrigin.Begin); diff --git a/TSOClient/tso.common/Utils/GameThread.cs b/TSOClient/tso.common/Utils/GameThread.cs index 24a3f632b..08dfbf15d 100644 --- a/TSOClient/tso.common/Utils/GameThread.cs +++ b/TSOClient/tso.common/Utils/GameThread.cs @@ -1,4 +1,5 @@ using FSO.Common.Rendering.Framework.Model; +using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.Threading; @@ -109,9 +110,11 @@ public class GameThread private static List _UpdateHooks = new List(); private static UpdateHook[] _UpdateHooksCopy = new UpdateHook[0]; private static List _UpdateHooksRemove = new List(); + public static AutoResetEvent OnWork = new AutoResetEvent(false); + + private static object _CallbacksLock = new object(); private static Queue> _UpdateCallbacks = new Queue>(); private static Queue> _UpdateCallbacksSwap = new Queue>(); - public static AutoResetEvent OnWork = new AutoResetEvent(false); public static void SetKilled() { @@ -151,7 +154,7 @@ public static UpdateHook EveryUpdate(Callback callback) public static void NextUpdate(Callback callback) { - lock (_UpdateCallbacks) + lock (_CallbacksLock) { _UpdateCallbacks.Enqueue(callback); } @@ -180,7 +183,7 @@ public static bool IsInGameThread() public static Task NextUpdate(Func callback) { TaskCompletionSource task = new TaskCompletionSource(); - lock (_UpdateCallbacks) + lock (_CallbacksLock) { _UpdateCallbacks.Enqueue(x => { @@ -212,7 +215,7 @@ public static void DigestUpdate(UpdateState state) { Queue> _callbacks; - lock (_UpdateCallbacks) + lock (_CallbacksLock) { // Swap the active callbacks queue with the second one, so we can // process entries without fear of more being added. @@ -227,6 +230,8 @@ public static void DigestUpdate(UpdateState state) _callbacks.Dequeue()(state); } + AssetStreaming.DigestStreamUpdate(); + int hookCount; UpdateHook[] _hooks; List toRemove = _UpdateHooksRemove; diff --git a/TSOClient/tso.common/Utils/PPXDepthEngine.cs b/TSOClient/tso.common/Utils/PPXDepthEngine.cs index a505459b2..654d9cbba 100644 --- a/TSOClient/tso.common/Utils/PPXDepthEngine.cs +++ b/TSOClient/tso.common/Utils/PPXDepthEngine.cs @@ -1,6 +1,5 @@ -using System; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; namespace FSO.Common.Utils { @@ -26,7 +25,7 @@ public static void InitScreenTargets() if (Backbuffer != null) Backbuffer.Dispose(); var scale = 1;//FSOEnvironment.DPIScaleFactor; if (!FSOEnvironment.Enable3D) - BackbufferDepth = CreateRenderTarget(GD, 1, MSAA, SurfaceFormat.Color, SSAA*GD.Viewport.Width/scale, SSAA * GD.Viewport.Height / scale, DepthFormat.None); + BackbufferDepth = CreateRenderTarget(GD, 1, MSAA, SurfaceFormat.Color, SSAA * GD.Viewport.Width / scale, SSAA * GD.Viewport.Height / scale, DepthFormat.None); Backbuffer = CreateRenderTarget(GD, 1, MSAA, SurfaceFormat.Color, SSAA * GD.Viewport.Width / scale, SSAA * GD.Viewport.Height / scale, DepthFormat.Depth24Stencil8); } @@ -36,7 +35,7 @@ public static void InitScreenTargets() public static void SetPPXTarget(RenderTarget2D color, RenderTarget2D depth, bool clear) { - SetPPXTarget(color, depth, clear, Color.TransparentBlack); + SetPPXTarget(color, depth, clear, ColorExtensions.TransparentBlack); } public static void SetPPXTarget(RenderTarget2D color, RenderTarget2D depth, bool clear, Color clearColor) @@ -48,7 +47,7 @@ public static void SetPPXTarget(RenderTarget2D color, RenderTarget2D depth, bool //if (color != null && depth != null) depth.InheritDepthStencil(color); var gd = GD; - gd.SetRenderTarget(color); //can be null + gd.SetRenderTarget(color); //can have null subresource when switching to 2d with supersampling enabled, which is odd since the texture is not disposed if (clear) { StencilValue = 1; @@ -206,7 +205,7 @@ public static RenderTarget2D CreateRenderTarget(GraphicsDevice device, int numbe // Create our render target return new RenderTarget2D(device, - width, height, (numberLevels>1), surface, + width, height, (numberLevels > 1), surface, DepthFormat.Depth24Stencil8, multisample, RenderTargetUsage.PreserveContents); } } diff --git a/TSOClient/tso.common/Utils/PathCaseTools.cs b/TSOClient/tso.common/Utils/PathCaseTools.cs index b758c28ca..08034674e 100644 --- a/TSOClient/tso.common/Utils/PathCaseTools.cs +++ b/TSOClient/tso.common/Utils/PathCaseTools.cs @@ -1,14 +1,68 @@ -using System.IO; +using System; +using System.IO; using System.Linq; namespace FSO.Common.Utils { public static class PathCaseTools { + /// + /// Resolves a file path case-insensitively on Linux/macOS. + /// On Windows, simply checks if the file exists. + /// public static string Insensitive(string file) { - var dir = Directory.GetFiles(Path.GetDirectoryName(file)); - return dir.FirstOrDefault(x => x.ToLowerInvariant().Replace('\\', '/') == file.ToLowerInvariant().Replace('\\', '/')); + if (string.IsNullOrEmpty(file)) + return null; + + // On Windows, file system is case-insensitive, just check existence + if (OperatingSystem.IsWindows()) + return File.Exists(file) ? file : null; + + file = file.Replace('\\', '/'); + + string[] parts; + string resolved; + + if (file.StartsWith("/")) + { + parts = file.Substring(1).Split('/'); + resolved = "/"; + } + else + { + parts = file.Split('/'); + resolved = ""; + } + + foreach (var part in parts) + { + if (string.IsNullOrEmpty(part)) + continue; + + var searchPath = string.IsNullOrEmpty(resolved) ? "." : resolved; + + if (!Directory.Exists(searchPath)) + return null; + + try + { + var entries = Directory.GetFileSystemEntries(searchPath); + var match = entries.FirstOrDefault(e => + Path.GetFileName(e).Equals(part, StringComparison.OrdinalIgnoreCase)); + + if (match == null) + return null; + + resolved = match; + } + catch + { + return null; + } + } + + return File.Exists(resolved) ? resolved : null; } } } diff --git a/TSOClient/tso.common/Utils/PathUtils.cs b/TSOClient/tso.common/Utils/PathUtils.cs new file mode 100644 index 000000000..ccc8a6434 --- /dev/null +++ b/TSOClient/tso.common/Utils/PathUtils.cs @@ -0,0 +1,22 @@ +namespace FSO.Common.Utils +{ + public static class PathUtils + { + private static bool PathIsChild(string parent, string child) + { + return Path.GetFullPath(child).StartsWith(Path.GetFullPath(parent)); + } + + public static string SafeCombine(string basePath, string relative) + { + var result = Path.Join(basePath, relative); + + if (!PathIsChild(basePath, result)) + { + throw new UnauthorizedAccessException($"Path '{relative}' is not a child directory."); + } + + return result; + } + } +} diff --git a/TSOClient/tso.common/Utils/TextureUtils.cs b/TSOClient/tso.common/Utils/TextureUtils.cs index 468213d4a..b6cfa0917 100644 --- a/TSOClient/tso.common/Utils/TextureUtils.cs +++ b/TSOClient/tso.common/Utils/TextureUtils.cs @@ -1,12 +1,22 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework; -using System.IO; namespace FSO.Common.Utils { + public readonly struct TextureData where T : struct + { + public readonly int Level; + public readonly T[] Data; + public readonly int ElemMultiplier; + + public TextureData(int level, T[] data, int elemMultiplier = 1) + { + Level = level; + Data = data; + ElemMultiplier = elemMultiplier; + } + } + public class TextureUtils { public static Texture2D TextureFromFile(GraphicsDevice gd, string filePath) @@ -17,24 +27,13 @@ public static Texture2D TextureFromFile(GraphicsDevice gd, string filePath) } } - public static Texture2D MipTextureFromFile(GraphicsDevice gd, string filePath) - { - var tex = TextureFromFile(gd, filePath); - var data = new Color[tex.Width * tex.Height]; - tex.GetData(data); - var newTex = new Texture2D(gd, tex.Width, tex.Height, true, SurfaceFormat.Color); - UploadWithAvgMips(newTex, gd, data); - tex.Dispose(); - return newTex; - } - private static Dictionary _TextureColors = new Dictionary(); public static Texture2D TextureFromColor(GraphicsDevice gd, Color color) { - if (_TextureColors.ContainsKey(color.PackedValue)) + if (_TextureColors.TryGetValue(color.PackedValue, out Texture2D result) && !result.IsDisposed) { - return _TextureColors[color.PackedValue]; + return result; } var tex = new Texture2D(gd, 1, 1); @@ -55,22 +54,8 @@ public static Texture2D TextureFromColor(GraphicsDevice gd, Color color, int wid return tex; } - /** - * Because the buffers can be fairly big, its much quicker to just keep some - * in memory and reuse them for resampling textures - * - * rhy: yeah, maybe, if the code actually did that. i'm also not sure about keeping ~32MB - * of texture buffers in memory at all times when the game is largely single threaded. - */ - private static List ResampleBuffers = new List(); - private static ulong MaxResampleBufferSize = 1024 * 768; - static TextureUtils() { - /*for (var i = 0; i < 10; i++) - { - ResampleBuffers.Add(new uint[MaxResampleBufferSize]); - }*/ } private static uint[] GetBuffer(int size) //todo: maybe implement something like described, old implementation was broken @@ -127,7 +112,7 @@ public static Texture2D Clip(GraphicsDevice gd, Texture2D texture, Rectangle sou var texBuf = GetBuffer(texture.Width * texture.Height); texture.GetData(texBuf); var destOff = 0; - for (int y=source.Y; y= newHeight || targx >= newWidth) continue; int avg = 0; int total = 0; - for (int yo = y; yo < y+factor && yo < Texture.Height; yo++) + for (int yo = y; yo < y + factor && yo < Texture.Height; yo++) { - for (int xo = x; xo < x+factor && xo < Texture.Width; xo++) + for (int xo = x; xo < x + factor && xo < Texture.Width; xo++) { - avg += (int)buffer[(yo * Texture.Width + xo)*4 + c]; + avg += (int)buffer[(yo * Texture.Width + xo) * 4 + c]; total++; } } avg /= total; - target[(targy * newWidth + targx)*4 + c] = (byte)avg; + target[(targy * newWidth + targx) * 4 + c] = (byte)avg; } } } @@ -337,32 +320,87 @@ public static Texture2D Decimate(Texture2D Texture, GraphicsDevice gd, int facto return outTex; } + private static int MipDimension(int size) + { + int count = 0; + + while (size > 0) + { + count++; + size >>= 1; + } + + return count; + } + + public static int CalculateMipCount(int width, int height) + { + return Math.Max(MipDimension(width), MipDimension(height)); + } + + public static int AlignUp(int value, int divisor) + { + int remainder = value % divisor; + return remainder > 0 ? (value + divisor - remainder) : value; + } + + public static int CalculateMipCountDXT(int width, int height) + { + return Math.Max(MipDimension(AlignUp(width, 4)), MipDimension(AlignUp(height, 4))); + } + public static void UploadWithMips(Texture2D Texture, GraphicsDevice gd, Color[] data) + { + UploadTexData(Texture, GenerateMips(Texture, data)); + } + + public static void UploadWithAvgMips(Texture2D Texture, GraphicsDevice gd, Color[] data) + { + UploadTexData(Texture, GenerateAvgMips(Texture, data)); + } + + public static TextureData[] GenerateMips(Texture2D Texture, Color[] data) + { + return GenerateMips(Texture.Width, Texture.Height, Texture.LevelCount, data); + } + + public static TextureData[] GenerateMips(int w, int h, int mips, Color[] data) { int level = 0; - int w = Texture.Width; - int h = Texture.Height; + + var result = new TextureData[mips]; while (data != null) { - Texture.SetData(level++, null, data, 0, data.Length); + result[level] = new TextureData(level, data); + level++; + data = Decimate(data, w, h); w /= 2; h /= 2; } + + return result; } - public static void UploadWithAvgMips(Texture2D Texture, GraphicsDevice gd, Color[] data) + public static TextureData[] GenerateAvgMips(Texture2D Texture, Color[] data) { int level = 0; int w = Texture.Width; int h = Texture.Height; + int mips = Texture.LevelCount; + + var result = new TextureData[mips]; while (data != null) { - Texture.SetData(level++, null, data, 0, data.Length); + result[level] = new TextureData(level, data); + level++; + data = AvgDecimate(data, w, h); w /= 2; h /= 2; } + + return result; } private static bool IsPowerOfTwo(int x) @@ -376,15 +414,28 @@ public static bool OverrideCompression(int w, int h) } public static void UploadDXT5WithMips(Texture2D Texture, int w, int h, GraphicsDevice gd, Color[] data) + { + UploadTexData(Texture, GenerateDXT5WithMips(Texture, w, h, data)); + } + + public static void UploadDXT1WithMips(Texture2D Texture, int w, int h, GraphicsDevice gd, Color[] data) + { + UploadTexData(Texture, GenerateDXT1WithMips(Texture, w, h, data)); + } + + public static TextureData[] GenerateDXT5WithMips(int mips, int w, int h, Color[] data) { int level = 0; int dw = ((w + 3) / 4) * 4; int dh = ((h + 3) / 4) * 4; + + var result = new TextureData[mips]; Tuple dxt = null; while (data != null) { - dxt = DXT5Compress(data, Math.Max(1,w), Math.Max(1,h), Math.Max(1, (dw+3)/4), Math.Max(1, (dh+3)/4)); - Texture.SetData(level++, null, dxt.Item1, 0, dxt.Item1.Length); + dxt = DXT5Compress(data, Math.Max(1, w), Math.Max(1, h), Math.Max(1, (dw + 3) / 4), Math.Max(1, (dh + 3) / 4)); + result[level] = new TextureData(level, dxt.Item1); + level++; data = Decimate(data, w, h); w /= 2; h /= 2; @@ -394,22 +445,39 @@ public static void UploadDXT5WithMips(Texture2D Texture, int w, int h, GraphicsD while (dw > 0 || dh > 0) { - Texture.SetData(level++, null, dxt.Item1, 0, dxt.Item1.Length); + result[level] = new TextureData(level, dxt.Item1); + level++; dw /= 2; dh /= 2; } + + return result; } - public static void UploadDXT1WithMips(Texture2D Texture, int w, int h, GraphicsDevice gd, Color[] data) + public static TextureData[] GenerateDXT5WithMips(Texture2D Texture, int w, int h, Color[] data) + { + return GenerateDXT5WithMips(Texture.LevelCount, w, h, data); + } + + public static TextureData[] GenerateDXT5WithMips(int w, int h, Color[] data) + { + return GenerateDXT5WithMips(CalculateMipCountDXT(w, h), w, h, data); + } + + public static TextureData[] GenerateDXT1WithMips(int mips, int w, int h, Color[] data) { int level = 0; int dw = ((w + 3) / 4) * 4; int dh = ((h + 3) / 4) * 4; + + var result = new TextureData[mips]; Tuple dxt = null; while (data != null) { dxt = DXT1Compress(data, Math.Max(1, w), Math.Max(1, h), Math.Max(1, (dw + 3) / 4), Math.Max(1, (dh + 3) / 4)); - Texture.SetData(level++, null, dxt.Item1, 0, dxt.Item1.Length*2); + result[level] = new TextureData(level, dxt.Item1, 2); + level++; + data = Decimate(data, w, h); w /= 2; h /= 2; @@ -419,10 +487,33 @@ public static void UploadDXT1WithMips(Texture2D Texture, int w, int h, GraphicsD while (dw > 0 || dh > 0) { - Texture.SetData(level++, null, dxt.Item1, 0, dxt.Item1.Length*2); + result[level] = new TextureData(level, dxt.Item1, 2); + level++; dw /= 2; dh /= 2; } + + return result; + } + + public static TextureData[] GenerateDXT1WithMips(Texture2D Texture, int w, int h, Color[] data) + { + return GenerateDXT1WithMips(Texture.LevelCount, w, h, data); + } + + public static TextureData[] GenerateDXT1WithMips(int w, int h, Color[] data) + { + return GenerateDXT1WithMips(CalculateMipCountDXT(w, h), w, h, data); + } + + public static void UploadTexData(Texture2D texture, TextureData[] data) where T : struct + { + for (int i = 0; i < data.Length; i++) + { + ref var item = ref data[i]; + + texture.SetData(item.Level, null, item.Data, 0, item.Data.Length * item.ElemMultiplier); + } } @@ -460,8 +551,8 @@ public static Color[] DXT5Decompress(byte[] data, int width, int height) var minCI = (uint)data[blockI++]; minCI |= (uint)data[blockI++] << 8; - - var maxCol = new Color((int)((maxCI >> 11) & 31), (int)((maxCI >> 6) & 31), (int)(maxCI & 31)) * (255f/31f); + + var maxCol = new Color((int)((maxCI >> 11) & 31), (int)((maxCI >> 6) & 31), (int)(maxCI & 31)) * (255f / 31f); var minCol = new Color((int)((minCI >> 11) & 31), (int)((minCI >> 6) & 31), (int)(minCI & 31)) * (255f / 31f); uint col = data[blockI++]; @@ -470,20 +561,20 @@ public static Color[] DXT5Decompress(byte[] data, int width, int height) col |= (uint)data[blockI++] << 24; var i = 0; - for (int y=0; y<4; y++) + for (int y = 0; y < 4; y++) { - for (int x=0; x<4; x++) + for (int x = 0; x < 4; x++) { - var abit = (alpha >> (i*3)) & 0x7; + var abit = (alpha >> (i * 3)) & 0x7; var cbit = (col >> (i * 2)) & 0x3; i++; Color col2; switch (cbit) { case 1: - col2 = minCol;break; + col2 = minCol; break; case 2: - col2 = Color.Lerp(minCol, maxCol, 2/3f); break; + col2 = Color.Lerp(minCol, maxCol, 2 / 3f); break; case 3: col2 = Color.Lerp(minCol, maxCol, 1 / 3f); break; default: @@ -494,9 +585,9 @@ public static Color[] DXT5Decompress(byte[] data, int width, int height) else { var a = (8 - abit) / 7f; - col2.A = (byte)(maxA*a + minA * (1-a)); + col2.A = (byte)(maxA * a + minA * (1 - a)); } - + result[targ2I++] = col2; } targ2I += width - 4; @@ -515,7 +606,8 @@ public static Tuple DXT5Compress(Color[] data, int width, int hei var blockI = 0; for (int by = 0; by < blockH; by++) { - for (int bx = 0; bx < blockW; bx++) { + for (int bx = 0; bx < blockW; bx++) + { var block = new Color[16]; var ti = 0; @@ -523,9 +615,9 @@ public static Tuple DXT5Compress(Color[] data, int width, int hei { var realy = ((by << 2) + y); if (realy >= height) break; - var i = realy * width + (bx<<2); + var i = realy * width + (bx << 2); + - for (int x = 0; x < 4; x++) { if ((x + (bx << 2)) >= width) @@ -564,7 +656,7 @@ public static Tuple DXT5Compress(Color[] data, int width, int hei result[blockI++] = (byte)(colorBin1 & 0xFF); result[blockI++] = (byte)((colorBin1 >> 8) & 0xFF); - + var indices = GetColorIndices(block, color0, color1); result[blockI++] = (byte)indices; result[blockI++] = (byte)(indices >> 8); @@ -631,7 +723,7 @@ public static Tuple DXT1Compress(Color[] data, int width, int hei // Transparent indices = GetA1ColorIndices(block, color0, color1); } - + result[blockI++] = (byte)indices; result[blockI++] = (byte)(indices >> 8); result[blockI++] = (byte)(indices >> 16); @@ -832,24 +924,24 @@ public static Color[] Decimate(Color[] old, int w, int h) if (nw == 0 && nh == 0) return null; if (nw == 0) { nw = 1; liney = true; } if (nh == 0) { nh = 1; linex = true; } - var size = nw*nh; + var size = nw * nh; Color[] buffer = new Color[size]; int tind = 0; int fyind = 0; - for (int y = 0; y < nh; y ++) + for (int y = 0; y < nh; y++) { var yb = y * 2 == h || linex; int find = fyind; - for (int x = 0; x < nw; x ++) + for (int x = 0; x < nw; x++) { var xb = x * 2 == h || liney; var c1 = old[find]; - var c2 = (xb)?Color.Transparent:old[find + 1]; - var c3 = (yb)?Color.Transparent:old[find + w]; - var c4 = (xb || yb)?Color.Transparent:old[find + 1 + w]; + var c2 = (xb) ? Color.Transparent : old[find + 1]; + var c3 = (yb) ? Color.Transparent : old[find + w]; + var c4 = (xb || yb) ? Color.Transparent : old[find + 1 + w]; - int r=0, g=0, b=0, t=0; + int r = 0, g = 0, b = 0, t = 0; if (c1.A > 0) { r += c1.R; g += c1.G; b += c1.B; t++; @@ -906,7 +998,7 @@ public static Color[] AvgDecimate(Color[] old, int w, int h) var c3 = (yb) ? Color.Transparent : old[find + w]; var c4 = (xb || yb) ? Color.Transparent : old[find + 1 + w]; - int r = 0, g = 0, b = 0, a=0, t = 0; + int r = 0, g = 0, b = 0, a = 0, t = 0; if (c1.A > 0) { r += c1.R; g += c1.G; b += c1.B; a += c1.A; t++; @@ -1004,12 +1096,12 @@ public static Texture2D Resize(GraphicsDevice gd, Texture2D texture, int newWidt gd, newWidth, newHeight, false, SurfaceFormat.Color, DepthFormat.None); - + Rectangle destinationRectangle = new Rectangle(0, 0, newWidth, newHeight); lock (gd) { gd.SetRenderTarget(renderTarget); - gd.Clear(Color.TransparentBlack); + gd.Clear(ColorExtensions.TransparentBlack); SpriteBatch batch = new SpriteBatch(gd); batch.Begin(); batch.Draw(texture, destinationRectangle, Color.White); diff --git a/TSOClient/tso.common/Utils/TimedReferenceCache.cs b/TSOClient/tso.common/Utils/TimedReferenceCache.cs index 002da034d..ebd3cb097 100644 --- a/TSOClient/tso.common/Utils/TimedReferenceCache.cs +++ b/TSOClient/tso.common/Utils/TimedReferenceCache.cs @@ -6,6 +6,8 @@ namespace FSO.Common.Utils { public static class TimedReferenceController { + private const int DerefThreshold = 100; + private static int CurRingNum = 0; private static List> ReferenceRing; private static Dictionary ObjectToRing; @@ -13,6 +15,7 @@ public static class TimedReferenceController private static int TicksToNextCheck; private static CacheType Type; private static object InternalLock = new object { }; + private static int DerefsSinceLastCollect = 0; public static CacheType CurrentType { get { return Type; } } static TimedReferenceController() @@ -72,13 +75,18 @@ public static void Tick() lock (InternalLock) { var toDereference = ReferenceRing[CurRingNum]; + DerefsSinceLastCollect += toDereference.Count; foreach (var obj in toDereference) ObjectToRing.Remove(obj); toDereference.Clear(); CurRingNum = (CurRingNum + 1) % ReferenceRing.Count; } TicksToNextCheck = CheckFreq; //GC.Collect(); - if (CurRingNum == 0) GC.Collect(); + if (CurRingNum == 0 && DerefsSinceLastCollect >= DerefThreshold) + { + GC.Collect(); + DerefsSinceLastCollect = 0; + } } } diff --git a/TSOClient/tso.common/WorldGeometry/Utils/TriangleSet.cs b/TSOClient/tso.common/WorldGeometry/Utils/TriangleSet.cs index 1caaf1901..3e302005e 100644 --- a/TSOClient/tso.common/WorldGeometry/Utils/TriangleSet.cs +++ b/TSOClient/tso.common/WorldGeometry/Utils/TriangleSet.cs @@ -19,7 +19,7 @@ namespace FSO.SimAntics.Model.Routing ///
public class BaseTriangleSet { - public VMObstacleSetNode[] Nodes; + public TriangleSetNode[] Nodes; protected List FreeList = new List(); protected int PoolInd = 0; public int Root = -1; @@ -35,7 +35,7 @@ public BaseTriangleSet(BaseTriangleSet last) if (last.Root != -1) { Count = last.Count; - Nodes = (VMObstacleSetNode[])last.Nodes.Clone(); + Nodes = (TriangleSetNode[])last.Nodes.Clone(); Root = last.Root; FreeList = last.FreeList.ToList(); PoolInd = last.PoolInd; @@ -57,7 +57,7 @@ private void InitNodes(int capacity) { if (Nodes == null) { - Nodes = new VMObstacleSetNode[capacity]; + Nodes = new TriangleSetNode[capacity]; } else { @@ -82,7 +82,7 @@ private int GetNode() private int GetNode(IntersectRectDimension dir, BaseMeshTriangle rect) { var ind = GetNode(); - Nodes[ind] = new VMObstacleSetNode() + Nodes[ind] = new TriangleSetNode() { Dimension = dir, Rect = rect, @@ -118,7 +118,7 @@ public void Add(BaseMeshTriangle rect) } } - private void AddAsChild(ref VMObstacleSetNode node, BaseMeshTriangle rect) + private void AddAsChild(ref TriangleSetNode node, BaseMeshTriangle rect) { bool rightSide = false; switch (node.Dimension) @@ -150,7 +150,7 @@ private void AddAsChild(ref VMObstacleSetNode node, BaseMeshTriangle rect) } } - public void RecursiveReAdd(VMObstacleSetNode node) + public void RecursiveReAdd(TriangleSetNode node) { Count--; Reclaim(node.Index); @@ -168,7 +168,7 @@ public bool SearchForIntersect(BaseMeshTriangle rect) } } - public bool SearchForIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect) + public bool SearchForIntersect(ref TriangleSetNode node, BaseMeshTriangle rect) { if (node.Intersects(rect)) return true; //search in child nodes. @@ -201,7 +201,7 @@ public List AllIntersect(BaseMeshTriangle rect) } } - public void AllIntersect(ref VMObstacleSetNode node, BaseMeshTriangle rect, List result) + public void AllIntersect(ref TriangleSetNode node, BaseMeshTriangle rect, List result) { if (node.Intersects(rect)) result.Add(node.Rect); //search in child nodes. @@ -237,7 +237,7 @@ public List OnEdge(BaseMeshTriangle rect) } } - public void OnEdge(ref VMObstacleSetNode node, BaseMeshTriangle rect, List result) + public void OnEdge(ref TriangleSetNode node, BaseMeshTriangle rect, List result) { if (node.OnEdge(rect)) result.Add(node.Rect); //search in child nodes. @@ -333,7 +333,7 @@ public bool Delete(ref VMObstacleSetNode node, VMEntityObstacle rect, ref VMObst */ } - public struct VMObstacleSetNode + public struct TriangleSetNode { public int LeftChild; public int RightChild; diff --git a/TSOClient/tso.common/app.config b/TSOClient/tso.common/app.config deleted file mode 100644 index 057fb30e5..000000000 --- a/TSOClient/tso.common/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/TSOClient/tso.common/packages.config b/TSOClient/tso.common/packages.config deleted file mode 100644 index e0fd6350a..000000000 --- a/TSOClient/tso.common/packages.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.common/version.json b/TSOClient/tso.common/version.json new file mode 100644 index 000000000..05668ab50 --- /dev/null +++ b/TSOClient/tso.common/version.json @@ -0,0 +1,6 @@ +{ + "id": "dev", + "channel": "FreeSO Development Build", + "channelUrl": "", + "publicKey": "" +} \ No newline at end of file diff --git a/TSOClient/tso.common/version.txt b/TSOClient/tso.common/version.txt deleted file mode 100644 index 262f16f2b..000000000 --- a/TSOClient/tso.common/version.txt +++ /dev/null @@ -1 +0,0 @@ -dev-0 diff --git a/TSOClient/tso.content/.config/dotnet-tools.json b/TSOClient/tso.content/.config/dotnet-tools.json new file mode 100644 index 000000000..6032a6619 --- /dev/null +++ b/TSOClient/tso.content/.config/dotnet-tools.json @@ -0,0 +1,36 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-mgcb": { + "version": "3.8.5", + "commands": [ + "mgcb" + ] + }, + "dotnet-mgcb-editor": { + "version": "3.8.4", + "commands": [ + "mgcb-editor" + ] + }, + "dotnet-mgcb-editor-linux": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-linux" + ] + }, + "dotnet-mgcb-editor-windows": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-windows" + ] + }, + "dotnet-mgcb-editor-mac": { + "version": "3.8.4", + "commands": [ + "mgcb-editor-mac" + ] + } + } +} diff --git a/TSOClient/tso.content/Audio.cs b/TSOClient/tso.content/Audio.cs index 8fefd1d22..3c073e3ca 100644 --- a/TSOClient/tso.content/Audio.cs +++ b/TSOClient/tso.content/Audio.cs @@ -1,15 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using FSO.Content.Model; using System.Text.RegularExpressions; -using System.IO; using FSO.Files.Formats.DBPF; using FSO.Files.XA; using FSO.Files.UTK; using FSO.Files.HIT; using Microsoft.Xna.Framework.Audio; using FSO.Content.Interfaces; +using FSO.Content.Framework; namespace FSO.Content { @@ -18,6 +16,8 @@ namespace FSO.Content /// public class Audio : IAudioProvider { + private const bool TRACE_MISSING = false; + private Content ContentManager; public bool Initialized; @@ -26,6 +26,8 @@ public class Audio : IAudioProvider private Dictionary StationsById; private List Modes; + private static Regex UserArchiveRegex = new Regex("^Audio/.*\\.dat"); + /** Audio DBPFs **/ public DBPFFile TSOAudio; //TSOAudio.dat public DBPFFile tsov2; //tsov2.dat @@ -33,6 +35,7 @@ public class Audio : IAudioProvider public DBPFFile EP5Samps; //EP5Samps.dat public DBPFFile EP2; //EP2.dat public DBPFFile Hitlists; //HitListsTemp.dat + public DBPFFile[] UserArchives; public Dictionary NightclubSounds = new Dictionary(); public Dictionary TracksById; @@ -122,6 +125,7 @@ public Audio(Content contentManager) public void Init() { if (Initialized) return; + this.Stations = new List(); this.StationsById = new Dictionary(); this.Modes = new List(); @@ -173,6 +177,16 @@ public void Init() RegisterEvents(tsov3); RegisterEvents(turkey); + // Add any user defined audio files + var userFiles = ContentManager.ContentFiles.Where(x => UserArchiveRegex.IsMatch(x.Replace('\\', '/'))).ToArray(); + + UserArchives = new DBPFFile[userFiles.Length]; + int i = 0; + foreach (var file in userFiles) + { + UserArchives[i++] = new DBPFFile(Path.Combine("Content/", file)); + } + //register the .xa files over in the nightclub folders. var files = Directory.GetFiles(content.GetPath("sounddata/nightclubsounds/")); foreach (var file in files) @@ -245,7 +259,7 @@ private byte[] GetAudioFrom(uint InstanceID, DBPFFile dbpf, out byte filetype) return dat; //either wav or mp3. } } - else + else if (TRACE_MISSING) Debug.WriteLine("Couldn't find sound!"); return null; } @@ -320,7 +334,7 @@ public Track GetTrack(uint value, uint fallback, HITResourceGroup group) { return TracksByBackupId[fallback]; } - else + else if (TRACE_MISSING) { Debug.WriteLine("Couldn't find track: " + value + ", with alternative " + fallback); } @@ -348,6 +362,14 @@ public SoundEffect GetSFX(Patch patch) if (data == null) data = GetAudioFrom(InstanceID, EP5Samps, out filetype); if (data == null) data = GetAudioFrom(InstanceID, EP2, out filetype); if (data == null) + { + foreach (var archive in UserArchives) + { + data = GetAudioFrom(InstanceID, archive, out filetype); + if (data != null) break; + } + } + if (data == null) { string source; if (NightclubSounds.TryGetValue(InstanceID, out source)) @@ -396,6 +418,11 @@ public Patch GetPatch(uint id, HITResourceGroup group) return new Patch(id); } + public FSC GetFSC(string path) + { + return new FSC(path); + } + /// /// Compiles the radio stations in the game to a list of AudioReference instances. /// diff --git a/TSOClient/tso.content/CityMapsProvider.cs b/TSOClient/tso.content/CityMapsProvider.cs index 6adbdeda6..48e575fae 100644 --- a/TSOClient/tso.content/CityMapsProvider.cs +++ b/TSOClient/tso.content/CityMapsProvider.cs @@ -24,14 +24,14 @@ public void Init() DirCache = new Dictionary(); Cache = new ConcurrentDictionary(); - var dir = Content.GetPath("cities"); + var dir = Path.Combine(FSOEnvironment.ContentDir, "Cities/"); foreach (var map in Directory.GetDirectories(dir)) { var id = int.Parse(Path.GetFileName(map).Replace("city_", "")); DirCache.Add(id, map); } - dir = Path.Combine(FSOEnvironment.ContentDir, "Cities/"); + dir = Content.GetPath("cities"); foreach (var map in Directory.GetDirectories(dir)) { var id = int.Parse(Path.GetFileName(map).Replace("city_", "")); @@ -39,6 +39,11 @@ public void Init() } } + public IEnumerable ListIDs() + { + return DirCache.Keys; + } + public CityMap Get(string id) { return Get(ulong.Parse(id)); @@ -56,6 +61,13 @@ public CityMap Get(ulong id) } } + public string GetDir(int id) + { + DirCache.TryGetValue(id, out string value); + + return value; + } + public CityMap Get(uint type, uint fileID) { throw new NotImplementedException(); diff --git a/TSOClient/tso.content/Codecs/FSCCodec.cs b/TSOClient/tso.content/Codecs/FSCCodec.cs new file mode 100644 index 000000000..3acd58ad6 --- /dev/null +++ b/TSOClient/tso.content/Codecs/FSCCodec.cs @@ -0,0 +1,18 @@ +using FSO.Content.Framework; +using FSO.Files.HIT; + +namespace FSO.Content.Codecs +{ + internal class FSCCodec : IContentCodec + { + public override object GenDecode(System.IO.Stream stream) + { + using (MemoryStream ms = new MemoryStream()) + { + stream.CopyTo(ms); + var data = ms.ToArray(); + return new FSC(data); + } + } + } +} diff --git a/TSOClient/tso.content/Codecs/SFXCodec.cs b/TSOClient/tso.content/Codecs/SFXCodec.cs index 46cef9885..0f912ba02 100644 --- a/TSOClient/tso.content/Codecs/SFXCodec.cs +++ b/TSOClient/tso.content/Codecs/SFXCodec.cs @@ -1,5 +1,4 @@ using FSO.Content.Framework; -using System.IO; using FSO.Files.XA; using FSO.Files.UTK; diff --git a/TSOClient/tso.content/Codecs/SmartCodec.cs b/TSOClient/tso.content/Codecs/SmartCodec.cs index 88897687b..f949bce10 100644 --- a/TSOClient/tso.content/Codecs/SmartCodec.cs +++ b/TSOClient/tso.content/Codecs/SmartCodec.cs @@ -22,7 +22,8 @@ public static class SmartCodec {".wav", new SFXCodec() }, {".mp3", new SFXCodec() }, {".xa", new SFXCodec() }, - {".utk", new SFXCodec() } + {".utk", new SFXCodec() }, + {".fsc", new FSCCodec() } }; public static object Decode(Stream stream, string extension) diff --git a/TSOClient/tso.content/Content.cs b/TSOClient/tso.content/Content.cs index 0e1a47c34..f8e8cd141 100644 --- a/TSOClient/tso.content/Content.cs +++ b/TSOClient/tso.content/Content.cs @@ -69,6 +69,7 @@ public static bool TS1Hybrid } public static FSOEngineMode Target; public static string TS1HybridBasePath = "D:/Games/The Sims/"; + public static bool TS1SteamInstall = false; /** * Content Manager @@ -207,7 +208,10 @@ private void InitBasic() if (!TS1) { var allFiles = new List(); - _ScanFiles(BasePath, allFiles, BasePath); + if (!FSOEnvironment.MissingTSO) + { + _ScanFiles(BasePath, allFiles, BasePath); + } AllFiles = allFiles.ToArray(); UIGraphics?.Init(); DataDefinition = new TSODataDefinition(); @@ -316,6 +320,14 @@ private void Init() /// The list of files to scan for. private void _ScanFiles(string dir, List fileList, string baseDir) { + var dirName = Path.GetFileName(dir); + + // Hack... Don't scan archive data. + if (dirName == "ArchiveCities") + { + return; + } + var fullPath = dir; var files = Directory.GetFiles(fullPath); foreach (var file in files) diff --git a/TSOClient/tso.content/Content/DX/Effects/2DWorldBatch.xnb b/TSOClient/tso.content/Content/DX/Effects/2DWorldBatch.xnb index 90102d548..a55f9a86f 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/2DWorldBatch.xnb and b/TSOClient/tso.content/Content/DX/Effects/2DWorldBatch.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/GrassShader.xnb b/TSOClient/tso.content/Content/DX/Effects/GrassShader.xnb index 910a8910b..5d40191ae 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/GrassShader.xnb and b/TSOClient/tso.content/Content/DX/Effects/GrassShader.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/GrassShaderiOS.xnb b/TSOClient/tso.content/Content/DX/Effects/GrassShaderiOS.xnb index ab705bafa..b3d6987c2 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/GrassShaderiOS.xnb and b/TSOClient/tso.content/Content/DX/Effects/GrassShaderiOS.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/LightMap2D.xnb b/TSOClient/tso.content/Content/DX/Effects/LightMap2D.xnb index 1cdad16ad..b120ce0c9 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/LightMap2D.xnb and b/TSOClient/tso.content/Content/DX/Effects/LightMap2D.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/MSDFFont.xnb b/TSOClient/tso.content/Content/DX/Effects/MSDFFont.xnb index 8b750e0f3..babc04988 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/MSDFFont.xnb and b/TSOClient/tso.content/Content/DX/Effects/MSDFFont.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/MapGeneration.xnb b/TSOClient/tso.content/Content/DX/Effects/MapGeneration.xnb new file mode 100644 index 000000000..f9400ace5 Binary files /dev/null and b/TSOClient/tso.content/Content/DX/Effects/MapGeneration.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/ParticleShader.xnb b/TSOClient/tso.content/Content/DX/Effects/ParticleShader.xnb index 243470096..98df2bc28 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/ParticleShader.xnb and b/TSOClient/tso.content/Content/DX/Effects/ParticleShader.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/PixShader.xnb b/TSOClient/tso.content/Content/DX/Effects/PixShader.xnb index 2f2fd7888..cc96e481c 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/PixShader.xnb and b/TSOClient/tso.content/Content/DX/Effects/PixShader.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/RCObject.xnb b/TSOClient/tso.content/Content/DX/Effects/RCObject.xnb index 2e35d95d0..202ec9719 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/RCObject.xnb and b/TSOClient/tso.content/Content/DX/Effects/RCObject.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/SSAA.xnb b/TSOClient/tso.content/Content/DX/Effects/SSAA.xnb index bf2c56647..7ee771dda 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/SSAA.xnb and b/TSOClient/tso.content/Content/DX/Effects/SSAA.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/SpriteEffects.xnb b/TSOClient/tso.content/Content/DX/Effects/SpriteEffects.xnb index 9149c440e..eb95b9ad2 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/SpriteEffects.xnb and b/TSOClient/tso.content/Content/DX/Effects/SpriteEffects.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/VerShader.xnb b/TSOClient/tso.content/Content/DX/Effects/VerShader.xnb index 7fc693721..8bed62f38 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/VerShader.xnb and b/TSOClient/tso.content/Content/DX/Effects/VerShader.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/Vitaboy.xnb b/TSOClient/tso.content/Content/DX/Effects/Vitaboy.xnb index 837e5ba46..9781a7c20 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/Vitaboy.xnb and b/TSOClient/tso.content/Content/DX/Effects/Vitaboy.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/colorpoly2D.xnb b/TSOClient/tso.content/Content/DX/Effects/colorpoly2D.xnb index e5d2f2112..b06bd5bfb 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/colorpoly2D.xnb and b/TSOClient/tso.content/Content/DX/Effects/colorpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Effects/gradpoly2D.xnb b/TSOClient/tso.content/Content/DX/Effects/gradpoly2D.xnb index 82980eacb..9c96291c8 100644 Binary files a/TSOClient/tso.content/Content/DX/Effects/gradpoly2D.xnb and b/TSOClient/tso.content/Content/DX/Effects/gradpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Fonts/Fallbacks/thai.xnb b/TSOClient/tso.content/Content/DX/Fonts/Fallbacks/thai.xnb deleted file mode 100644 index 83489395a..000000000 Binary files a/TSOClient/tso.content/Content/DX/Fonts/Fallbacks/thai.xnb and /dev/null differ diff --git a/TSOClient/tso.content/Content/DX/Fonts/mobile.xnb b/TSOClient/tso.content/Content/DX/Fonts/mobile.xnb index 64098c923..a1948ac8a 100644 Binary files a/TSOClient/tso.content/Content/DX/Fonts/mobile.xnb and b/TSOClient/tso.content/Content/DX/Fonts/mobile.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Fonts/simdialogue.xnb b/TSOClient/tso.content/Content/DX/Fonts/simdialogue.xnb index ad8a8f3df..8f0984b59 100644 Binary files a/TSOClient/tso.content/Content/DX/Fonts/simdialogue.xnb and b/TSOClient/tso.content/Content/DX/Fonts/simdialogue.xnb differ diff --git a/TSOClient/tso.content/Content/DX/Fonts/trebuchet.xnb b/TSOClient/tso.content/Content/DX/Fonts/trebuchet.xnb index 83b2213c6..d430eb743 100644 Binary files a/TSOClient/tso.content/Content/DX/Fonts/trebuchet.xnb and b/TSOClient/tso.content/Content/DX/Fonts/trebuchet.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/2DWorldBatch.xnb b/TSOClient/tso.content/Content/Effects/2DWorldBatch.xnb new file mode 100644 index 000000000..0b913dc02 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/2DWorldBatch.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/2DWorldBatchiOS.xnb b/TSOClient/tso.content/Content/Effects/2DWorldBatchiOS.xnb new file mode 100644 index 000000000..f7a3847c1 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/2DWorldBatchiOS.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/GrassShader.xnb b/TSOClient/tso.content/Content/Effects/GrassShader.xnb new file mode 100644 index 000000000..3dd1bba04 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/GrassShader.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/GrassShaderiOS.xnb b/TSOClient/tso.content/Content/Effects/GrassShaderiOS.xnb new file mode 100644 index 000000000..81b9d4d34 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/GrassShaderiOS.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/LightMap2D.xnb b/TSOClient/tso.content/Content/Effects/LightMap2D.xnb new file mode 100644 index 000000000..ba16a5d5c Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/LightMap2D.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/MSDFFont.xnb b/TSOClient/tso.content/Content/Effects/MSDFFont.xnb new file mode 100644 index 000000000..89801cc75 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/MSDFFont.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/ParticleShader.xnb b/TSOClient/tso.content/Content/Effects/ParticleShader.xnb new file mode 100644 index 000000000..914d0d762 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/ParticleShader.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/PixShader.xnb b/TSOClient/tso.content/Content/Effects/PixShader.xnb new file mode 100644 index 000000000..88e540b4c Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/PixShader.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/RCObject.xnb b/TSOClient/tso.content/Content/Effects/RCObject.xnb new file mode 100644 index 000000000..52268c146 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/RCObject.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/RCObjectiOS.xnb b/TSOClient/tso.content/Content/Effects/RCObjectiOS.xnb new file mode 100644 index 000000000..9d16d9ae0 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/RCObjectiOS.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/SSAA.xnb b/TSOClient/tso.content/Content/Effects/SSAA.xnb new file mode 100644 index 000000000..9d8747281 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/SSAA.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/SpriteEffectsiOS.xnb b/TSOClient/tso.content/Content/Effects/SpriteEffectsiOS.xnb new file mode 100644 index 000000000..f55525543 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/SpriteEffectsiOS.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/VerShader.xnb b/TSOClient/tso.content/Content/Effects/VerShader.xnb new file mode 100644 index 000000000..adf8ffa56 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/VerShader.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/Vitaboy.xnb b/TSOClient/tso.content/Content/Effects/Vitaboy.xnb new file mode 100644 index 000000000..9f928e323 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/Vitaboy.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/VitaboyiOS.xnb b/TSOClient/tso.content/Content/Effects/VitaboyiOS.xnb new file mode 100644 index 000000000..e1505d61e Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/VitaboyiOS.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/colorpoly2D.xnb b/TSOClient/tso.content/Content/Effects/colorpoly2D.xnb new file mode 100644 index 000000000..8234c000a Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/colorpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/Effects/gradpoly2D.xnb b/TSOClient/tso.content/Content/Effects/gradpoly2D.xnb new file mode 100644 index 000000000..70a075324 Binary files /dev/null and b/TSOClient/tso.content/Content/Effects/gradpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/Fonts/mobile.xnb b/TSOClient/tso.content/Content/Fonts/mobile.xnb index f3b772013..85d0f8893 100644 Binary files a/TSOClient/tso.content/Content/Fonts/mobile.xnb and b/TSOClient/tso.content/Content/Fonts/mobile.xnb differ diff --git a/TSOClient/tso.content/Content/Fonts/simdialogue.xnb b/TSOClient/tso.content/Content/Fonts/simdialogue.xnb index 22c191c22..2b7a0814c 100644 Binary files a/TSOClient/tso.content/Content/Fonts/simdialogue.xnb and b/TSOClient/tso.content/Content/Fonts/simdialogue.xnb differ diff --git a/TSOClient/tso.content/Content/Fonts/trebuchet.xnb b/TSOClient/tso.content/Content/Fonts/trebuchet.xnb index e7a86b20e..4d5e1d369 100644 Binary files a/TSOClient/tso.content/Content/Fonts/trebuchet.xnb and b/TSOClient/tso.content/Content/Fonts/trebuchet.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatch.xnb b/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatch.xnb index c744735e9..9ece360d1 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatch.xnb and b/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatch.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatchiOS.xnb b/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatchiOS.xnb index f2f8cf476..00f590bd9 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatchiOS.xnb and b/TSOClient/tso.content/Content/OGL/Effects/2DWorldBatchiOS.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/GrassShader.xnb b/TSOClient/tso.content/Content/OGL/Effects/GrassShader.xnb index f5db51be3..adc96dfca 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/GrassShader.xnb and b/TSOClient/tso.content/Content/OGL/Effects/GrassShader.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/GrassShaderiOS.xnb b/TSOClient/tso.content/Content/OGL/Effects/GrassShaderiOS.xnb index 30abfe350..550ae29cd 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/GrassShaderiOS.xnb and b/TSOClient/tso.content/Content/OGL/Effects/GrassShaderiOS.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/LightMap2D.xnb b/TSOClient/tso.content/Content/OGL/Effects/LightMap2D.xnb index c4375d709..a982cfc4f 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/LightMap2D.xnb and b/TSOClient/tso.content/Content/OGL/Effects/LightMap2D.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/MSDFFont.xnb b/TSOClient/tso.content/Content/OGL/Effects/MSDFFont.xnb index d06eae6c4..baff22dde 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/MSDFFont.xnb and b/TSOClient/tso.content/Content/OGL/Effects/MSDFFont.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/MapGeneration.xnb b/TSOClient/tso.content/Content/OGL/Effects/MapGeneration.xnb new file mode 100644 index 000000000..8fa100bcd Binary files /dev/null and b/TSOClient/tso.content/Content/OGL/Effects/MapGeneration.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/ParticleShader.xnb b/TSOClient/tso.content/Content/OGL/Effects/ParticleShader.xnb index 7718a177d..7cd413b61 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/ParticleShader.xnb and b/TSOClient/tso.content/Content/OGL/Effects/ParticleShader.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/PixShader.xnb b/TSOClient/tso.content/Content/OGL/Effects/PixShader.xnb index 3e685c218..a75b5d45b 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/PixShader.xnb and b/TSOClient/tso.content/Content/OGL/Effects/PixShader.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/RCObject.xnb b/TSOClient/tso.content/Content/OGL/Effects/RCObject.xnb index d65d427f0..35aaffdee 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/RCObject.xnb and b/TSOClient/tso.content/Content/OGL/Effects/RCObject.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/RCObjectiOS.xnb b/TSOClient/tso.content/Content/OGL/Effects/RCObjectiOS.xnb index ca361ee36..d32c1ef7b 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/RCObjectiOS.xnb and b/TSOClient/tso.content/Content/OGL/Effects/RCObjectiOS.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/SSAA.xnb b/TSOClient/tso.content/Content/OGL/Effects/SSAA.xnb index 477386d65..aa6e826f1 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/SSAA.xnb and b/TSOClient/tso.content/Content/OGL/Effects/SSAA.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/SpriteEffects.xnb b/TSOClient/tso.content/Content/OGL/Effects/SpriteEffects.xnb index cbbb1b21e..6458cec7a 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/SpriteEffects.xnb and b/TSOClient/tso.content/Content/OGL/Effects/SpriteEffects.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/VerShader.xnb b/TSOClient/tso.content/Content/OGL/Effects/VerShader.xnb index a9fb3a56a..fb0bb73d8 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/VerShader.xnb and b/TSOClient/tso.content/Content/OGL/Effects/VerShader.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/Vitaboy.xnb b/TSOClient/tso.content/Content/OGL/Effects/Vitaboy.xnb index e525966a1..baae015d3 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/Vitaboy.xnb and b/TSOClient/tso.content/Content/OGL/Effects/Vitaboy.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/VitaboyiOS.xnb b/TSOClient/tso.content/Content/OGL/Effects/VitaboyiOS.xnb index c09572854..1e807f262 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/VitaboyiOS.xnb and b/TSOClient/tso.content/Content/OGL/Effects/VitaboyiOS.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/colorpoly2D.xnb b/TSOClient/tso.content/Content/OGL/Effects/colorpoly2D.xnb index b8e991cbf..efb7d9e42 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/colorpoly2D.xnb and b/TSOClient/tso.content/Content/OGL/Effects/colorpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Effects/gradpoly2D.xnb b/TSOClient/tso.content/Content/OGL/Effects/gradpoly2D.xnb index 7f6a45e4a..5916d027c 100644 Binary files a/TSOClient/tso.content/Content/OGL/Effects/gradpoly2D.xnb and b/TSOClient/tso.content/Content/OGL/Effects/gradpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Fonts/mobile.xnb b/TSOClient/tso.content/Content/OGL/Fonts/mobile.xnb index f5f5423ed..09c77abfa 100644 Binary files a/TSOClient/tso.content/Content/OGL/Fonts/mobile.xnb and b/TSOClient/tso.content/Content/OGL/Fonts/mobile.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Fonts/simdialogue.xnb b/TSOClient/tso.content/Content/OGL/Fonts/simdialogue.xnb index 77e4a53a9..7262acdbf 100644 Binary files a/TSOClient/tso.content/Content/OGL/Fonts/simdialogue.xnb and b/TSOClient/tso.content/Content/OGL/Fonts/simdialogue.xnb differ diff --git a/TSOClient/tso.content/Content/OGL/Fonts/trebuchet.xnb b/TSOClient/tso.content/Content/OGL/Fonts/trebuchet.xnb index 56aca9704..0772c83ac 100644 Binary files a/TSOClient/tso.content/Content/OGL/Fonts/trebuchet.xnb and b/TSOClient/tso.content/Content/OGL/Fonts/trebuchet.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatch.xnb b/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatch.xnb index 768b801bc..c3665a710 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatch.xnb and b/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatch.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatchiOS.xnb b/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatchiOS.xnb index e479c31fe..8957b003e 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatchiOS.xnb and b/TSOClient/tso.content/Content/iOS/Effects/2DWorldBatchiOS.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/GrassShader.xnb b/TSOClient/tso.content/Content/iOS/Effects/GrassShader.xnb index 843958230..1def97feb 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/GrassShader.xnb and b/TSOClient/tso.content/Content/iOS/Effects/GrassShader.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/GrassShaderiOS.xnb b/TSOClient/tso.content/Content/iOS/Effects/GrassShaderiOS.xnb index 197df4053..595820055 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/GrassShaderiOS.xnb and b/TSOClient/tso.content/Content/iOS/Effects/GrassShaderiOS.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/LightMap2D.xnb b/TSOClient/tso.content/Content/iOS/Effects/LightMap2D.xnb index a9fbd1914..c00e53431 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/LightMap2D.xnb and b/TSOClient/tso.content/Content/iOS/Effects/LightMap2D.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/MSDFFont.xnb b/TSOClient/tso.content/Content/iOS/Effects/MSDFFont.xnb index 19d3f9d0b..c574de5ab 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/MSDFFont.xnb and b/TSOClient/tso.content/Content/iOS/Effects/MSDFFont.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/MapGeneration.xnb b/TSOClient/tso.content/Content/iOS/Effects/MapGeneration.xnb new file mode 100644 index 000000000..1f140c5ff Binary files /dev/null and b/TSOClient/tso.content/Content/iOS/Effects/MapGeneration.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/ParticleShader.xnb b/TSOClient/tso.content/Content/iOS/Effects/ParticleShader.xnb index 4c80daf19..fb561c2ff 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/ParticleShader.xnb and b/TSOClient/tso.content/Content/iOS/Effects/ParticleShader.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/PixShader.xnb b/TSOClient/tso.content/Content/iOS/Effects/PixShader.xnb index 2553029e3..b84070f38 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/PixShader.xnb and b/TSOClient/tso.content/Content/iOS/Effects/PixShader.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/RCObject.xnb b/TSOClient/tso.content/Content/iOS/Effects/RCObject.xnb index b82ea5692..e1d340bf3 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/RCObject.xnb and b/TSOClient/tso.content/Content/iOS/Effects/RCObject.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/RCObjectiOS.xnb b/TSOClient/tso.content/Content/iOS/Effects/RCObjectiOS.xnb index 1a4e05005..c4aff88d8 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/RCObjectiOS.xnb and b/TSOClient/tso.content/Content/iOS/Effects/RCObjectiOS.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/SSAA.xnb b/TSOClient/tso.content/Content/iOS/Effects/SSAA.xnb index c5cac6873..09be50fcf 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/SSAA.xnb and b/TSOClient/tso.content/Content/iOS/Effects/SSAA.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/SpriteEffectsiOS.xnb b/TSOClient/tso.content/Content/iOS/Effects/SpriteEffectsiOS.xnb index a883381ec..4a164bc7a 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/SpriteEffectsiOS.xnb and b/TSOClient/tso.content/Content/iOS/Effects/SpriteEffectsiOS.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/VerShader.xnb b/TSOClient/tso.content/Content/iOS/Effects/VerShader.xnb index 3bddc6abf..a53da3b17 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/VerShader.xnb and b/TSOClient/tso.content/Content/iOS/Effects/VerShader.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/Vitaboy.xnb b/TSOClient/tso.content/Content/iOS/Effects/Vitaboy.xnb index c10f2b3bd..476627b4a 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/Vitaboy.xnb and b/TSOClient/tso.content/Content/iOS/Effects/Vitaboy.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/VitaboyiOS.xnb b/TSOClient/tso.content/Content/iOS/Effects/VitaboyiOS.xnb index 837e879ad..b9e776830 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/VitaboyiOS.xnb and b/TSOClient/tso.content/Content/iOS/Effects/VitaboyiOS.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/colorpoly2D.xnb b/TSOClient/tso.content/Content/iOS/Effects/colorpoly2D.xnb index f4e0b2416..ffa106a51 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/colorpoly2D.xnb and b/TSOClient/tso.content/Content/iOS/Effects/colorpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Effects/gradpoly2D.xnb b/TSOClient/tso.content/Content/iOS/Effects/gradpoly2D.xnb index 8536a7433..2fe52a03b 100644 Binary files a/TSOClient/tso.content/Content/iOS/Effects/gradpoly2D.xnb and b/TSOClient/tso.content/Content/iOS/Effects/gradpoly2D.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Fonts/mobile.xnb b/TSOClient/tso.content/Content/iOS/Fonts/mobile.xnb index 4f0e80097..f9b7a91a5 100644 Binary files a/TSOClient/tso.content/Content/iOS/Fonts/mobile.xnb and b/TSOClient/tso.content/Content/iOS/Fonts/mobile.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Fonts/simdialogue.xnb b/TSOClient/tso.content/Content/iOS/Fonts/simdialogue.xnb index b89241360..22cdb1785 100644 Binary files a/TSOClient/tso.content/Content/iOS/Fonts/simdialogue.xnb and b/TSOClient/tso.content/Content/iOS/Fonts/simdialogue.xnb differ diff --git a/TSOClient/tso.content/Content/iOS/Fonts/trebuchet.xnb b/TSOClient/tso.content/Content/iOS/Fonts/trebuchet.xnb index 7d3b51e04..31510d648 100644 Binary files a/TSOClient/tso.content/Content/iOS/Fonts/trebuchet.xnb and b/TSOClient/tso.content/Content/iOS/Fonts/trebuchet.xnb differ diff --git a/TSOClient/tso.content/ContentSrc/Effects/2DWorldBatch.fx b/TSOClient/tso.content/ContentSrc/Effects/2DWorldBatch.fx index 3b48eb04f..265be7bf5 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/2DWorldBatch.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/2DWorldBatch.fx @@ -340,7 +340,7 @@ void psZDepthSprite(ZVertexOut v, out float4 color:COLOR0, out float4 depthB:COL else if (v.roomVec.x != 0.0) { //advanced lighting mode float4 projection = mul(float4(v.screenPos.x, v.screenPos.y, d.x*d.y, d.y), iWVP); - pixel = gammaMul(pixel, lightProcessLevel(projection, v.objectID.y)); + pixel = gammaMul(pixel, lightProcessLevel(projection, int(v.objectID.y))); pixel.rgb += projection.yzw * 0.00000000001; //monogame keeps trying to optimise out entire matrix columns im like well played guys who needs those right } color = pixel; @@ -401,7 +401,7 @@ void psZDepthSpriteDirLight(ZVertexOut v, out float4 color:COLOR0, out float4 de //advanced lighting mode float4 projection = mul(float4(v.screenPos.x, v.screenPos.y, d.x*d.y, d.y), iWVP); float3 normal = normalize(cross(ddx(projection.xyz), -ddy(projection.xyz))); - pixel *= lightProcessDirectionLevel(projection, normal, v.objectID.y); + pixel *= lightProcessDirectionLevel(projection, normal, int(v.objectID.y)); pixel.rgb += projection.yzw * 0.00000000001; //monogame keeps trying to optimise out entire matrix columns im like well played guys who needs those right } color = pixel; diff --git a/TSOClient/tso.content/ContentSrc/Effects/GrassShader.fx b/TSOClient/tso.content/ContentSrc/Effects/GrassShader.fx index 2cca27a3e..342116385 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/GrassShader.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/GrassShader.fx @@ -11,9 +11,13 @@ float4 LightBrown; float4 DarkBrown; float4 DiffuseColor; float2 ScreenOffset; +float LayerHeight; float GrassProb; float GrassFadeMul; +float2 GreenLengthDensity; +float2 BrownLengthDensity; + float2 TexOffset; float4 TexMatrix; @@ -236,6 +240,14 @@ float4 SimpleLight(float2 uv) { return tex2D(RoomLightSampler, RoomIDToUV(GetRoomID(uv / 3))); } +float4 FadeRectangle; +float FadeWidth; +float RectangleFade(float2 xz, float extend) { + float dx = max(abs(xz.x - FadeRectangle.x) - (FadeRectangle.z + extend), 0.0); + float dy = max(abs(xz.y - FadeRectangle.y) - (FadeRectangle.w + extend), 0.0); + return min(sqrt(dx * dx + dy * dy) / (FadeWidth - extend), 1.0); +} + GrassPSVTX GrassVS(GrassVTX input) { GrassPSVTX output = (GrassPSVTX)0; @@ -279,10 +291,30 @@ float4 LightDot(float3 normal) { return CM(dot(LightVec, normalize(normal)) * 0.5f + 0.5f); } -float4 LightSpecular(float3 normal, float4 modelpos) { +float4 LightSpecular(float4 baseColor, float3 normal, float4 modelpos) { + float4 specColor = DiffuseColor * lerp(baseColor, float4(1.0, 1.0, 1.0, 1.0), 0.25); + float3 pos = normalize(CamPos - modelpos.xyz); float cosan = abs(dot(pos, normal)); - return DiffuseColor*(1-pow(cosan, GrassShininess)); + return specColor * (1 - pow(cosan, GrassShininess)); +} + +float2 GetLengthDensity(GrassPSVTX input) +{ + return lerp(GreenLengthDensity, BrownLengthDensity, input.GrassInfo.x); +} + +void GrassDiscard(GrassPSVTX input, float2 rand) +{ + float2 lengthDensity = GetLengthDensity(input); + + // The closer to the top of the blade that we are, the less probable the blade is. + float bladeI = LayerHeight / lengthDensity.x; + + float adjustedProb = GrassProb * (1.0 - bladeI * 0.5) * lengthDensity.y; + + if (bladeI > 1.0 || rand.y > adjustedProb) + discard; } #if SIMPLE @@ -293,9 +325,8 @@ void BladesPS(GrassPSVTX input, out float4 color:COLOR0) void BladesPS(GrassPSVTX input, out float4 color:COLOR0, out float4 depthB : COLOR1) { #endif - - float2 rand = iterhash22(input.ScreenPos.xy+ScreenOffset); //nearest neighbour effect - if (rand.y > GrassProb*((2.0-input.GrassInfo.x)/2)) discard; + float2 rand = iterhash22(input.ScreenPos.xy + ScreenOffset); //nearest neighbour effect + GrassDiscard(input, rand); //grass blade here float d = input.GrassInfo.w; @@ -324,8 +355,8 @@ void BladesPSSimple(GrassPSVTX input, out float4 color:COLOR0) void BladesPSSimple(GrassPSVTX input, out float4 color:COLOR0, out float4 depthB : COLOR1) { #endif - float2 rand = iterhash22(input.ScreenPos.xy + ScreenOffset); //nearest neighbour effect - if (rand.y > GrassProb*((2.0 - input.GrassInfo.x) / 2)) discard; + float2 rand = iterhash22(input.ScreenPos.xy + ScreenOffset); //nearest neighbour effect + GrassDiscard(input, rand); //grass blade here float d = input.GrassInfo.w; @@ -349,15 +380,18 @@ void BladesPS3D(GrassPSVTX input, out float4 color:COLOR0) { float a = 2 - sqrt(input.ScreenPos.z / (25 * GrassFadeMul)); if (a <= 0) discard; - float2 rand = iterhash22(input.GrassInfo.yz*100); //nearest neighbour effect - if (rand.y > GrassProb*((2.0 - input.GrassInfo.x) / 2)) discard; + float2 rand = iterhash22(input.GrassInfo.yz * 100); //nearest neighbour effect + GrassDiscard(input, rand); //grass blade here float bladeCol = rand.x*0.6; float4 green = lerp(LightGreen, DarkGreen, bladeCol); float4 brown = lerp(LightBrown, DarkBrown, bladeCol); - color = gammaMad(lerp(green, brown, input.GrassInfo.x), lightProcessFloor(input.ModelPos) * LightDot(input.Normal), LightSpecular(input.Normal, input.ModelPos)); + float4 baseColor = lerp(green, brown, input.GrassInfo.x); + color = gammaMad(baseColor, lightProcessFloor(input.ModelPos) * LightDot(input.Normal), LightSpecular(baseColor, input.Normal, input.ModelPos)); color.a = a; + float fade = (1 - RectangleFade(input.ModelPos.xz, 0.0)); // Since it works with stacked layers, the fade needs to be a bit stronger. + color.a *= fade * fade; color.a *= Alpha; } @@ -430,8 +464,7 @@ float2 GrassParallaxMapping(float2 texCoords, float3 viewDir, float probability) float2 P = viewDir.xy * ParallaxHeight; float2 deltaTexCoords = P / layers; - //correction since grass repeats at a diagonal - deltaTexCoords = float2(deltaTexCoords.x*0.7071 - deltaTexCoords.y*0.7071, deltaTexCoords.y*0.7071 + deltaTexCoords.x*0.7071); + deltaTexCoords = float2(deltaTexCoords.x*ParallaxUVTexMat.x + deltaTexCoords.y*ParallaxUVTexMat.y, deltaTexCoords.y*ParallaxUVTexMat.z + deltaTexCoords.x*ParallaxUVTexMat.w); float2 currentTexCoords = texCoords; @@ -473,8 +506,10 @@ void BladesParallaxPS3D(GrassParallaxPSVTX input, out float4 color:COLOR0) float bladeCol = rand.x*0.6; float4 green = lerp(LightGreen, DarkGreen, bladeCol); float4 brown = lerp(LightBrown, DarkBrown, bladeCol); - color = gammaMad(lerp(green, brown, input.GrassInfo.x), lightProcessFloor(input.ModelPos) * LightDot(input.Normal), LightSpecular(input.Normal, input.ModelPos)); + float4 baseColor = lerp(green, brown, input.GrassInfo.x); + color = gammaMad(baseColor, lightProcessFloor(input.ModelPos) * LightDot(input.Normal), LightSpecular(baseColor, input.Normal, input.ModelPos)); color.a = a; + color.a *= (1 - RectangleFade(input.ModelPos.xz, 0.0)); color.a *= Alpha; } @@ -703,14 +738,6 @@ void BasePSMul(GrassPSVTX input, out float4 color:COLOR0) color = float4(1, 1, 1, 1)*max(0, min(1, (diff - MulBase) * MulRange)) * edgeDist; } -float4 FadeRectangle; -float FadeWidth; -float RectangleFade(float2 xz, float extend) { - float dx = max(abs(xz.x - FadeRectangle.x) - (FadeRectangle.z + extend), 0.0); - float dy = max(abs(xz.y - FadeRectangle.y) - (FadeRectangle.w + extend), 0.0); - return min(sqrt(dx * dx + dy * dy) / (FadeWidth-extend), 1.0); -} - void BasePS3D(GrassPSVTX input, out float4 color:COLOR0) { float d = input.GrassInfo.w; @@ -730,25 +757,26 @@ void BasePS3D(GrassPSVTX input, out float4 color:COLOR0) #endif if (color.a == 0) discard; - color = gammaMad(color, lightProcessRoof(input.ModelPos) * LightDot(input.Normal), LightSpecular(input.Normal, input.ModelPos)); + color = gammaMad(color, lightProcessRoof(input.ModelPos) * LightDot(input.Normal), LightSpecular(color, input.Normal, input.ModelPos)); color.a *= (1 - RectangleFade(input.ModelPos.xz, FadeWidth / 2)); } else { // Ceiling colour. color = float4(0.76, 0.78, 0.80, 1.00); - color = gammaMad(color, lightProcessRoofCeiling(input.ModelPos) * LightDot(input.Normal), LightSpecular(input.Normal, input.ModelPos)); + color = gammaMad(color, lightProcessRoofCeiling(input.ModelPos) * LightDot(input.Normal), LightSpecular(color, input.Normal, input.ModelPos)); color.a *= (1 - RectangleFade(input.ModelPos.xz, FadeWidth / 2)); } } else { - color = gammaMad(color, lightProcessRoof(input.ModelPos) * LightDot(input.Normal), LightSpecular(input.Normal, input.ModelPos)); + color = gammaMad(color, lightProcessRoof(input.ModelPos) * LightDot(input.Normal), LightSpecular(color, input.Normal, input.ModelPos)); float a = 1 - (2 - sqrt(input.ScreenPos.z / (25 * GrassFadeMul))); if (a > 0) { a = min(1, a); //blade mipmaps float2 rand = tex2D(TerrainNoiseMipSampler, input.GrassInfo.yz * 100 / 1024.0).xy; float multex = rand.x; - multex *= ((2.0 - input.GrassInfo.x) / 2); + float2 lengthDensity = GetLengthDensity(input); + multex *= (0.5 + lengthDensity.y * 0.5) * (0.75 + lengthDensity.x * 0.25); // Base intensity (0-1): density (0.5-1.0) * length (0.75-1.0) multex = (multex - 0.5) * 2.5 + 0.5; multex *= a; @@ -780,7 +808,7 @@ technique DrawBase #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BasePSSimple(); -#endif; +#endif } @@ -793,7 +821,7 @@ technique DrawBase #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BasePS(); -#endif; +#endif } @@ -806,7 +834,7 @@ technique DrawBase #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BasePS3D(); -#endif; +#endif } @@ -819,7 +847,7 @@ technique DrawBase #else VertexShader = compile vs_3_0 GrassParallaxVS(); PixelShader = compile ps_3_0 RoofParallaxPS3D(); -#endif; +#endif } #endif @@ -832,7 +860,7 @@ technique DrawBase #else VertexShader = compile vs_3_0 GrassParallaxVS(); PixelShader = compile ps_3_0 FloorParallaxPS3D(); -#endif; +#endif } #endif } @@ -848,7 +876,7 @@ technique DrawGrid #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 GridPS(); -#endif; +#endif } @@ -861,7 +889,7 @@ technique DrawGrid #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 GridPS3D(); -#endif; +#endif } @@ -874,7 +902,7 @@ technique DrawGrid #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 GridPSTex3D(); -#endif; +#endif } } @@ -889,7 +917,7 @@ technique DrawBlades #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BladesPSSimple(); -#endif; +#endif } pass MainBlades @@ -900,7 +928,7 @@ technique DrawBlades #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BladesPS(); -#endif; +#endif } pass MainBlades3D @@ -911,7 +939,7 @@ technique DrawBlades #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BladesPS3D(); -#endif; +#endif } #if !SIMPLE @@ -923,7 +951,7 @@ technique DrawBlades #else VertexShader = compile vs_3_0 GrassParallaxVS(); PixelShader = compile ps_3_0 BladesParallaxPS3D(); -#endif; +#endif } #endif @@ -940,7 +968,7 @@ technique DrawLMap #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BasePSLMap(); -#endif; +#endif } } @@ -956,7 +984,7 @@ technique DrawMask #else VertexShader = compile vs_3_0 GrassVS(); PixelShader = compile ps_3_0 BasePSMul(); -#endif; +#endif } } \ No newline at end of file diff --git a/TSOClient/tso.content/ContentSrc/Effects/LightingCommon.fx b/TSOClient/tso.content/ContentSrc/Effects/LightingCommon.fx index 257987723..41f85fb7d 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/LightingCommon.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/LightingCommon.fx @@ -59,18 +59,19 @@ float4 lightColorI(float4 intensities, float i) { return lerp(OutsideDark, float4(intensities.rgb * LightingAdjust, 1), (fshad - MinAvg.x) * MinAvg.y); } -float4 lightProcessLevel(float4 inPosition, float level) { +float4 lightProcessLevel(float4 inPosition, int level) { inPosition.xyz *= WorldToLightFactor; inPosition.xz += LightOffset; - inPosition.xz += 1 / MapLayout * floor(float2(level % MapLayout.x, level / MapLayout.x)); + int stride = int(MapLayout.x); + inPosition.xz += 1 / MapLayout * float2(int2(level % stride, level / stride)); float4 lTex = tex2D(advLightSampler, inPosition.xz); return lightColor(lTex); } float4 lightProcess(float4 inPosition) { - return lightProcessLevel(inPosition, Level); + return lightProcessLevel(inPosition, int(Level)); } float4 lightProcessFloor(float4 inPosition) { @@ -122,13 +123,32 @@ float4 lightInterp(float4 inPosition, float lightBleed) { return lightColorIAvg(lTex, clamp((inPosition.y % 1) * 3, 0, 1), avg); } -float4 lightProcessDirectionLevel(float4 inPosition, float3 normal, float level) { - float2 orig = inPosition.x; +float4 lightInterpClamp(float4 inPosition, float lightBleed) { inPosition.xyz *= WorldToLightFactor; + inPosition.xz = clamp(inPosition.xz, float2(0.0, 0.0), rcp(MapLayout)); inPosition.xz += LightOffset; + float level = min(Level, floor(inPosition.y) + 0.0001); + float belowLevel = level - 1; + float2 iPA = inPosition.xz + 1 / MapLayout * floor(float2(belowLevel % MapLayout.x, belowLevel / MapLayout.x)); inPosition.xz += 1 / MapLayout * floor(float2(level % MapLayout.x, level / MapLayout.x)); + float4 lTex = tex2D(advLightSampler, inPosition.xz); + + float avg = (lTex.r + lTex.g + lTex.b) / 3; + lTex.rgb = lerp(lTex.rgb, tex2D(advLightSampler, iPA).rgb, max(0, 1 - (inPosition.y % 1) * 2) * lightBleed); + + return lightColorIAvg(lTex, clamp((inPosition.y % 1) * 3, 0, 1), avg); +} + +float4 lightProcessDirectionLevel(float4 inPosition, float3 normal, int level) { + float2 orig = inPosition.x; + inPosition.xyz *= WorldToLightFactor; + inPosition.xz += LightOffset; + + int stride = int(MapLayout.x); + inPosition.xz += 1 / MapLayout * float2(int2(level % stride, level / stride)); + float4 lTex = tex2D(advLightSampler, inPosition.xz); float4 color = lightColor(lTex); float4 direction = tex2D(advDirectionSampler, inPosition.xz); @@ -145,7 +165,7 @@ float4 lightProcessDirectionLevel(float4 inPosition, float3 normal, float level) } float4 lightProcessDirection(float4 inPosition, float3 normal) { - return lightProcessDirectionLevel(inPosition, normal, Level); + return lightProcessDirectionLevel(inPosition, normal, int(Level)); } //coeffs from http://chilliant.blogspot.com/2012/08/srgb-approximations-for-hlsl.html. diff --git a/TSOClient/tso.content/ContentSrc/Effects/MapGeneration.fx b/TSOClient/tso.content/ContentSrc/Effects/MapGeneration.fx new file mode 100644 index 000000000..58f7632ac --- /dev/null +++ b/TSOClient/tso.content/ContentSrc/Effects/MapGeneration.fx @@ -0,0 +1,433 @@ +#if OPENGL + #define SV_POSITION POSITION + #define VS_SHADERMODEL vs_3_0 + #define PS_SHADERMODEL ps_3_0 + #define VS_SHADERMODEL3 vs_3_0 + #define PS_SHADERMODEL3 ps_3_0 + #define VS_SHADERMODEL4 vs_4_0 + #define PS_SHADERMODEL4 ps_4_0 +#else + #define VS_SHADERMODEL vs_4_0_level_9_1 + #define PS_SHADERMODEL ps_4_0_level_9_1 + + #define VS_SHADERMODEL3 vs_4_0_level_9_1 + #define PS_SHADERMODEL3 ps_4_0_level_9_3 + #define VS_SHADERMODEL4 vs_5_0 + #define PS_SHADERMODEL4 ps_5_0 +#endif + +texture BaseTexture; +sampler TextureSampler : register(s0) = sampler_state { + texture = ; + AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; + MIPFILTER = POINT; MINFILTER = POINT; MAGFILTER = POINT; +}; + +// SpriteBatch expects that default vertex transform parameter will have name 'MatrixTransform' +float4x4 MatrixTransform; + +struct VertexIn { + float4 position : SV_Position0; + float2 texCoord : TEXCOORD0; +}; + +struct VertexOut { + float4 position : SV_Position; + float2 texCoord : TEXCOORD0; +}; + +VertexOut VSMain(VertexIn v) +{ + VertexOut result; + result.position = v.position; + result.texCoord = v.texCoord; + result.texCoord.y = 1 - v.texCoord.y; + return result; +} + +float2 ImageSize; +int StepSize; + +int intMod(int value, int mod) { + return value - ((value / mod) * mod); +} + +float alpha8(float4 value) { +#if OPENGL + return value.r; +#else + return value.a; +#endif +} + +float4 encodeUV(float2 uv) { + int2 coord = int2(uv * ImageSize); + + return float4( + float(intMod(coord.x, 256)) / 255.0, + floor(coord.x / 256) / 255.0, + float(intMod(coord.y, 256)) / 255.0, + floor(coord.y / 256) / 255.0 + ); +} + +bool equal(float4 left, float4 right) { + return left.x == right.x && left.y == right.y && left.z == right.z && left.w == right.w; +} + +float2 decodeUV(float4 color) { + if (equal(color.rgba, float4(1.0, 1.0, 1.0, 1.0))) { + return float2(-1.0, -1.0); + } + + int x = int(color.x * 255.0 + 0.5) + (int(color.y * 255.0 + 0.5) * 256); + int y = int(color.z * 255.0 + 0.5) + (int(color.w * 255.0 + 0.5) * 256); + + float2 invSize = 1.0 / ImageSize; + + return float2(float(x) + 0.5, float(y) + 0.5) * invSize; +} + +float4 jumpFloodInit(VertexOut v) : COLOR0 +{ + float2 size = ImageSize; + float2 invSize = 1.0 / size; + + float centralA = tex2D(TextureSampler, v.texCoord).a; + + if (centralA != 1.0) { + // Alpha is 255 for city terrain type, so spread anything that isn't that. + // Spread this pixel in the jump flood + return encodeUV(v.texCoord); + } else { + return float4(1.0, 1.0, 1.0, 1.0); + } +} + +float4 jumpFloodStep(VertexOut v) : COLOR0 +{ + float2 size = ImageSize; + float2 invSize = 1.0 / size; + + float bestDist = 99999999; + float4 bestUVe = float4(1.0, 1.0, 1.0, 1.0); + + for (int i = 0; i < 9; i++) { + int realI = i; + int x = intMod(realI, 3); + int y = realI / 3; + + float2 newUV = v.texCoord + float2(float((x - 1) * StepSize), float((y - 1) * StepSize)) * invSize; + float4 edgeUVe = tex2D(TextureSampler, newUV); + float2 edgeUV = decodeUV(edgeUVe); + + if (edgeUV.x != -1.0) { + float2 delta = (v.texCoord - edgeUV) * size; + float sqDist = dot(delta, delta); + + if (sqDist < bestDist) { + bestDist = sqDist; + bestUVe = edgeUVe; + } + } + } + + return bestUVe; +} + +technique JumpFloodInit { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 jumpFloodInit(); + } +} + +technique JumpFloodStep { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 jumpFloodStep(); + } +} + +/* +technique JumpFloodFinal { + pass All + { + PixelShader = compile PS_SHADERMODEL3 jumpFloodFinal(); + } +} +*/ + +int EdgeValue; + +bool hasEdgeValue(float2 texCoord) { + float valueF = alpha8(tex2D(TextureSampler, texCoord)); + int value = int(round(valueF * 255.0)); + + return value == EdgeValue || value == 255; +} + +float4 cityEdgeDetect(VertexOut v) : COLOR0 +{ + // There's an edge if the target value is found at this pixel, but not found on all 4 adjacent ones. + float2 texCoord = v.texCoord; + if (hasEdgeValue(texCoord)) { + float2 size = ImageSize; + float2 invSize = 1.0 / size; + if (!hasEdgeValue(texCoord + float2(invSize.x, 0.0)) || !hasEdgeValue(texCoord - float2(invSize.x, 0.0)) || !hasEdgeValue(texCoord + float2(0.0, invSize.y)) || !hasEdgeValue(texCoord - float2(0.0, invSize.y))) { + float value = alpha8(tex2D(TextureSampler, texCoord)); + return float4(value, value, value, value); + } + } + + return float4(0.0, 0.0, 0.0, 1.0); // 255 is "null" +} + +technique CityEdgeDetect { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 cityEdgeDetect(); + } +} + +float SdfExpand; +float SdfFade; +float GradientScale; +float GradientBase; + + +texture TerrainType; + +sampler TerrainSampler : register(s1) = sampler_state { + texture = ; + AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; + MIPFILTER = POINT; MINFILTER = POINT; MAGFILTER = POINT; +}; + +texture DistToColor; + +sampler DistToColorSampler : register(s2) = sampler_state { + texture = ; + AddressU = CLAMP; AddressV = CLAMP; AddressW = CLAMP; + MIPFILTER = POINT; MINFILTER = POINT; MAGFILTER = POINT; +}; + +float getTerrain(float2 texCoord) { + // The coords snap to the nearest full tile on the map. + float mapSize = 512.0; + + float leftCornerY = 306.0 / mapSize; + float leftCornerY2 = 307.0 / mapSize; + float rightCornerY = 205.0 / mapSize; + float offset = 1.0 / mapSize; + + float y = texCoord.y; + float xStart = y < leftCornerY ? (leftCornerY - y) + offset : (y - leftCornerY); + float xEnd = y < rightCornerY ? leftCornerY2 + y - offset : (1 - (y - rightCornerY)); + + texCoord.x = clamp(texCoord.x, xStart, xEnd); + + return alpha8(tex2D(TerrainSampler, texCoord)); +} + +float getSignedDistance(float2 texCoord) { + float2 size = ImageSize; + float value = getTerrain(texCoord); + float4 closestUVe = tex2D(TextureSampler, texCoord); + float2 closestUV = decodeUV(closestUVe); + + float2 delta = (texCoord - closestUV) * size; + float dist = sqrt(dot(delta, delta)); + + // Within the value the distance is negative, outside it's positive. + return (int(round(value * 255)) == EdgeValue) ? -dist : dist; +} + +float4 jumpDistFill(VertexOut v) : COLOR0 +{ + float dist = getSignedDistance(v.texCoord); + + // Gradient texture is applied within the volume (negative dist -> x) + float4 grad = tex2D(DistToColorSampler, float2(-dist / GradientScale + GradientBase, 0.5)); + + float a = 1 - smoothstep(SdfExpand, SdfExpand + SdfFade, dist); + + return grad * a; +} + +technique JumpDistFill { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 jumpDistFill(); + } +} + +float TerrainScale; +float3 SunDir; + +float lightingTerm(float3 normal, float3 lightDir) { + return (dot(normal, lightDir) + 1) / 2; +} + +float3 posAt(float2 texCoord) { + return float3(texCoord.x, texCoord.y, alpha8(tex2D(TextureSampler, texCoord)) * TerrainScale); +} + +float SpecularPower; +float SpecularIntensity; + +bool isOOB(float2 texCoord) { + float value = alpha8(tex2D(TerrainSampler, texCoord)); + + return value == 1; +} + +float3 calcNormal(float2 texCoord) { + float2 invSize = 1.0 / ImageSize; + + if (isOOB(texCoord)) { + return float3(0, 0, 1); + } + + // Calculate normal + float3 posTL = posAt(texCoord); + float3 posTR = posAt(texCoord + float2(invSize.x, 0.0)); + float3 posBL = posAt(texCoord + float2(0.0, -invSize.y)); + float3 posBR = posAt(texCoord + float2(invSize.x, -invSize.y)); + + float3 normal1 = normalize(cross(posTR - posTL, posBL - posTL)); + float3 normal2 = normalize(cross(posBR - posBL, posBR - posTR)); + + float3 normal = -normalize((normal1 + normal2) / 2); + + return normal; +} + +float3 treatNormal(float4 col) { + return normalize(col.xyz * 2 - float3(1, 1, 1)); +} + +float4 terrainLighting(VertexOut v) : COLOR0 +{ + float2 texCoord = v.texCoord; + float3 normal = treatNormal(tex2D(DistToColorSampler, texCoord)); + float4 vertexColor = tex2D(TextureSampler, texCoord); + + if (isOOB(texCoord)) { + return vertexColor; + } + + // Diffuse term + float refDiffuse = lightingTerm(float3(0, 0, 1), SunDir); + float diffuse = lightingTerm(normal, SunDir); + + return float4(vertexColor.rgb * (diffuse / refDiffuse), 1); +} + +technique TerrainLighting { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 terrainLighting(); + } +} + +float4 terrainSpecular(VertexOut v) : COLOR0 +{ + float2 texCoord = v.texCoord; + float3 normal = treatNormal(tex2D(TextureSampler, texCoord)); + + // Specular term (reflects white) + float3 reflected = normalize(2 * dot(SunDir, normal) * normal - SunDir); + float3 camDir = float3(0, 0, 1); + float specularFactor = pow(max(0, dot(reflected, camDir)), SpecularPower) * SpecularIntensity; + float4 specularColor = specularFactor * float4(1.0, 1.0, 1.0, 1.0); + + return specularColor; +} + +technique TerrainSpecular { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 terrainSpecular(); + } +} + +float4 terrainNormal(VertexOut v) : COLOR0 +{ + float3 normal = calcNormal(v.texCoord); + + return float4((normal + float3(1, 1, 1)) / 2, 1.0); +} + +technique TerrainNormal { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 terrainNormal(); + } +} + +float4 Color; + +float4 forestOverlay(VertexOut v) : COLOR0 +{ + float2 texCoord = v.texCoord; + + float4 color = float4(Color.rgb, 1.0); + float a = tex2D(TextureSampler, texCoord).a * 0.75 * Color.a; // Forest density is RGBA + + float value = getTerrain(texCoord); + int type = int(round(value * 255)); + + if (!(type == 0 || type == 2)) { + // Needs to be grass or rock. + a = 0; + } + + a = min(a, 0.80); + float4 result = color * a; + + return result; +} + +technique ForestOverlay { + pass All + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 forestOverlay(); + } +} + +float2 GaussianStep; +int GaussianSize; // Up to 21 +float GaussianWeights[21]; + +float4 gauss(VertexOut v) : COLOR0 +{ + // Look up the texture color. + float2 texCoord = v.texCoord; + + float4 fragC = tex2D(TextureSampler, texCoord) * GaussianWeights[0]; + + for (int i = 1; i < GaussianSize; i++) { + fragC += tex2D(TextureSampler, texCoord+GaussianStep*i) * GaussianWeights[i]; + fragC += tex2D(TextureSampler, texCoord+GaussianStep*(-i)) * GaussianWeights[i]; + } + + return fragC; +} + +technique Gaussian +{ + pass OneDir + { + VertexShader = compile VS_SHADERMODEL3 VSMain(); + PixelShader = compile PS_SHADERMODEL3 gauss(); + } +} \ No newline at end of file diff --git a/TSOClient/tso.content/ContentSrc/Effects/ParticleShader.fx b/TSOClient/tso.content/ContentSrc/Effects/ParticleShader.fx index 6ec7da9f9..044117056 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/ParticleShader.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/ParticleShader.fx @@ -79,6 +79,10 @@ float3 RotateXY(float3 posIn, float angle) { return posIn; } +float2 NegMod(float2 value, float2 mod) { + return ((value % mod) - mod) % mod; +} + //Parameters: //miny, yrange, fall speed, fall speed variation //wind x, wind z, wind variation, rotation variation @@ -103,7 +107,7 @@ ParticleOutput SnowVS(in ParticleInput input) float2 xz = input.Position.xz + (realTime) * windSpeed; float2 xzbase = (Parameters3.xz + boxCtr.xz); //test - xz = ((xz - xzbase) % Parameters3.yw) + xzbase; + xz = NegMod((xz - xzbase), Parameters3.yw) + xzbase; float flakeSize = (sin(input.Position.y * 1000)*0.15 + 1) * Parameters4.x; float4 realCtr = float4(xz.x, newY + boxCtr.y, xz.y, 1); @@ -140,7 +144,7 @@ ParticleOutput RainVS(in ParticleInput input) float2 xz = input.Position.xz + (realTime) * windSpeed; float2 xzbase = (Parameters3.xz + boxCtr.xz); //test - xz = ((xz - xzbase) % Parameters3.yw) + xzbase; + xz = NegMod((xz - xzbase), Parameters3.yw) + xzbase; float4 realCtr = float4(xz.x, newY + boxCtr.y, xz.y, 1); float2 windDelta = (windSpeed * (TimeRate / repeatTime)) / -2; @@ -226,8 +230,9 @@ float dpth(float4 v) { float4 MainPS(ParticleOutput input) : COLOR { float level = (input.ModelPos.y) / (2.95*3); - if (level >= ClipLevel || round(dpth(tex2D(IndoorsSampler, input.ModelPos.xz / BpSize))*Stories) > level) discard; - return gammaMul(tex2D(TexSampler, input.TexCoord)*input.Color * Color, lightInterp(input.ModelPos, 1)) - SubColor; + float indoorsLevel = round(dpth(tex2D(IndoorsSampler, input.ModelPos.xz / BpSize))*Stories); + if (level >= ClipLevel || indoorsLevel > max(0.0, level)) discard; + return gammaMul(tex2D(TexSampler, input.TexCoord)*input.Color * Color, lightInterpClamp(input.ModelPos, 1)) - SubColor; } float4 SimplePS(ParticleOutput input) : COLOR @@ -238,8 +243,9 @@ float4 SimplePS(ParticleOutput input) : COLOR float4 RainPS(ParticleOutput input) : COLOR { float level = (input.ModelPos.y) / (2.95 * 3); - if (level >= ClipLevel || round(dpth(tex2D(IndoorsSampler, input.ModelPos.xz / BpSize))*Stories) > level) discard; - return gammaMul(((1-cos(input.TexCoord.y*3.1415*2)) * (1 - cos(input.TexCoord.x*3.1415 * 2))/4) *input.Color, lightInterp(input.ModelPos, 1)) - SubColor; + float indoorsLevel = round(dpth(tex2D(IndoorsSampler, input.ModelPos.xz / BpSize))*Stories); + if (level >= ClipLevel || indoorsLevel > max(0.0, level)) discard; + return gammaMul(((1-cos(input.TexCoord.y*3.1415*2)) * (1 - cos(input.TexCoord.x*3.1415 * 2))/4) *input.Color, lightInterpClamp(input.ModelPos, 1)) - SubColor; } float4 RainSimplePS(ParticleOutput input) : COLOR diff --git a/TSOClient/tso.content/ContentSrc/Effects/PixShader.fx b/TSOClient/tso.content/ContentSrc/Effects/PixShader.fx index 03abcf354..1d8f548a3 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/PixShader.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/PixShader.fx @@ -254,7 +254,7 @@ sampler2D ObjSampler = sampler_state float4 GetObjColor(ObjVertexOut Input) { float4 objCol = tex2D(ObjSampler, Input.texCoord); - objCol.xyz /= objCol.w; + // City graphics are non-premultiplied. return objCol; } @@ -266,7 +266,7 @@ float4 CityObjPS(ObjVertexOut Input) : COLOR0 float diffuse = 1;//dot(normalize(Input.normal.xyz), LightVec.xyz); if (diffuse < 0) diffuse *= 0.5; - return gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz*lerp(ShadowMult, 1, min(diffuse, shadowLerp(ShadSampler, ShadSize, Input.shadPos.xy, depth + 0.003*(2048.0 / ShadSize.x)))), 1)) * BCol.a; + return gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz*lerp(ShadowMult, 1, min(diffuse, shadowLerp(ShadSampler, ShadSize, Input.shadPos.xy, depth + 0.003*(2048.0 / ShadSize.x)))), 1)); } float4 CityObjPSNoShad(ObjVertexOut Input) : COLOR0 @@ -275,7 +275,7 @@ float4 CityObjPSNoShad(ObjVertexOut Input) : COLOR0 if (BCol.a < 0.01) discard; float diffuse = 1;//dot(normalize(Input.normal.xyz), LightVec.xyz); if (diffuse < 0) diffuse *= 0.5; - return float4(gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz*lerp(ShadowMult, 1, diffuse),1)).rgb*BCol.a, BCol.a); + return float4(gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz*lerp(ShadowMult, 1, diffuse),1)).rgb, BCol.a); } float4 CityObjPSFog(ObjVertexOut Input) : COLOR0 @@ -287,7 +287,7 @@ float4 CityObjPSFog(ObjVertexOut Input) : COLOR0 float fogDistance = min(1, length(Input.vPos) / FogMaxDist); BCol = float4(gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz*lerp(ShadowMult, 1, diffuse), 1)).rgb, BCol.a); - BCol.xyz = lerp(BCol.xyz, FogColor.xyz, fogDistance) * BCol.a; + BCol.xyz = lerp(BCol.xyz, FogColor.xyz, fogDistance); return BCol; } @@ -302,7 +302,7 @@ float4 CityObjPSFogShad(ObjVertexOut Input) : COLOR0 BCol = float4(gammaMul(float4(BCol.xyz, 1), float4(LightCol.xyz * lerp(ShadowMult, 1, min(diffuse, shadowLerp(ShadSampler, ShadSize, Input.shadPos.xy, depth + 0.003*(2048.0 / ShadSize.x)))), 1)).rgb, BCol.a); float fogDistance = min(1, length(Input.vPos) / FogMaxDist); - BCol.xyz = lerp(BCol.xyz, FogColor.xyz, fogDistance) * BCol.a; + BCol.xyz = lerp(BCol.xyz, FogColor.xyz, fogDistance); return BCol; } diff --git a/TSOClient/tso.content/ContentSrc/Effects/SpriteEffects.fx b/TSOClient/tso.content/ContentSrc/Effects/SpriteEffects.fx index 7799ab022..a4eba70f6 100644 --- a/TSOClient/tso.content/ContentSrc/Effects/SpriteEffects.fx +++ b/TSOClient/tso.content/ContentSrc/Effects/SpriteEffects.fx @@ -685,4 +685,4 @@ technique DequantizeDepth { PixelShader = compile PS_SHADERMODEL3 dequantizeDepth(); } -} \ No newline at end of file +} diff --git a/TSOClient/tso.content/ContentSrc/Fonts.mgcb b/TSOClient/tso.content/ContentSrc/Fonts.mgcb new file mode 100644 index 000000000..7c75a8446 --- /dev/null +++ b/TSOClient/tso.content/ContentSrc/Fonts.mgcb @@ -0,0 +1,40 @@ + +#----------------------------- Global Properties ----------------------------# + +/outputDir:../Content +/intermediateDir:obj +/platform:DesktopGL +/config: +/profile:Reach +/compress:False + +#-------------------------------- References --------------------------------# + +/reference:..\..\..\Other\libs\MSDFExtension\bin\Release\net8.0\MSDFExtension.dll + +#---------------------------------- Content ---------------------------------# + +#begin Fonts/mobile.ini +/importer:FieldFontImporter +/processor:FieldFontProcessor +/processorParam:ExternalPath=msdfgen.exe +/processorParam:Resolution=32 +/processorParam:Range=4 +/build:Fonts/mobile.ini + +#begin Fonts/simdialogue.ini +/importer:FieldFontImporter +/processor:FieldFontProcessor +/processorParam:ExternalPath=msdfgen.exe +/processorParam:Resolution=32 +/processorParam:Range=4 +/build:Fonts/simdialogue.ini + +#begin Fonts/trebuchet.ini +/importer:FieldFontImporter +/processor:FieldFontProcessor +/processorParam:ExternalPath=msdfgen.exe +/processorParam:Resolution=32 +/processorParam:Range=4 +/build:Fonts/trebuchet.ini + diff --git a/TSOClient/tso.content/ContentSrc/TSOClientContent.mgcb b/TSOClient/tso.content/ContentSrc/TSOClientContent.mgcb index a75bc7bfe..497c70adf 100644 --- a/TSOClient/tso.content/ContentSrc/TSOClientContent.mgcb +++ b/TSOClient/tso.content/ContentSrc/TSOClientContent.mgcb @@ -10,34 +10,9 @@ #-------------------------------- References --------------------------------# -/reference:..\..\..\other\libs\MSDFExtension\bin\Debug\MSDFExtension.dll #---------------------------------- Content ---------------------------------# -#begin Fonts/simdialogue.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/simdialogue.ini - -#begin Fonts/mobile.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/mobile.ini - -#begin Fonts/trebuchet.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/trebuchet.ini - #begin Effects/2DWorldBatch.fx /importer:EffectImporter /processor:EffectProcessor @@ -140,3 +115,8 @@ /processorParam:DebugMode=Auto /build:Effects/SpriteEffects.fx +#begin Effects/MapGeneration.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:Effects/MapGeneration.fx diff --git a/TSOClient/tso.content/ContentSrc/TSOClientContentDX.mgcb b/TSOClient/tso.content/ContentSrc/TSOClientContentDX.mgcb index f3e70268a..3c728da30 100644 --- a/TSOClient/tso.content/ContentSrc/TSOClientContentDX.mgcb +++ b/TSOClient/tso.content/ContentSrc/TSOClientContentDX.mgcb @@ -10,35 +10,8 @@ #-------------------------------- References --------------------------------# -/reference:..\..\..\other\libs\MSDFExtension\bin\Debug\MSDFData.dll -/reference:..\..\..\other\libs\MSDFExtension\bin\Debug\MSDFExtension.dll - #---------------------------------- Content ---------------------------------# -#begin Fonts/simdialogue.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/simdialogue.ini - -#begin Fonts/mobile.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/mobile.ini - -#begin Fonts/trebuchet.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/trebuchet.ini - #begin Effects/2DWorldBatch.fx /importer:EffectImporter /processor:EffectProcessor @@ -128,3 +101,9 @@ /processor:EffectProcessor /processorParam:DebugMode=Auto /build:Effects/GrassShader.fx + +#begin Effects/MapGeneration.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:Effects/MapGeneration.fx diff --git a/TSOClient/tso.content/ContentSrc/TSOClientContentiOS.mgcb b/TSOClient/tso.content/ContentSrc/TSOClientContentiOS.mgcb index 61761900b..a19497f44 100644 --- a/TSOClient/tso.content/ContentSrc/TSOClientContentiOS.mgcb +++ b/TSOClient/tso.content/ContentSrc/TSOClientContentiOS.mgcb @@ -10,34 +10,8 @@ #-------------------------------- References --------------------------------# -/reference:..\..\..\other\libs\MSDFExtension\bin\Debug\MSDFExtension.dll - #---------------------------------- Content ---------------------------------# -#begin Fonts/simdialogue.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/simdialogue.ini - -#begin Fonts/mobile.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/mobile.ini - -#begin Fonts/trebuchet.ini -/importer:FieldFontImporter -/processor:FieldFontProcessor -/processorParam:ExternalPath=msdfgen.exe -/processorParam:Resolution=32 -/processorParam:Range=4 -/build:Fonts/trebuchet.ini - #begin Effects/2DWorldBatch.fx /importer:EffectImporter /processor:EffectProcessor @@ -140,3 +114,8 @@ /processorParam:DebugMode=Auto /build:Effects/SpriteEffectsiOS.fx +#begin Effects/MapGeneration.fx +/importer:EffectImporter +/processor:EffectProcessor +/processorParam:DebugMode=Auto +/build:Effects/MapGeneration.fx diff --git a/TSOClient/tso.content/ContentSrc/test.png b/TSOClient/tso.content/ContentSrc/test.png deleted file mode 100644 index efab48acb..000000000 Binary files a/TSOClient/tso.content/ContentSrc/test.png and /dev/null differ diff --git a/TSOClient/tso.content/FSO.Content.csproj b/TSOClient/tso.content/FSO.Content.csproj index 6fa6ca231..43303e12b 100644 --- a/TSOClient/tso.content/FSO.Content.csproj +++ b/TSOClient/tso.content/FSO.Content.csproj @@ -1,247 +1,41 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {C0068DF7-F2E8-4399-846D-556BF9A35C00} + net9.0 + enable + disable Library - Properties FSO.Content FSO.Content - v4.5 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + True + true + true + true + full - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true + + + True - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + True - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - ..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - {d8232422-9d79-4200-a981-eb70ed82ccf3} - TargaImagePCL - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - + + + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + + + + - PreserveNewest @@ -278,19 +72,33 @@ PreserveNewest + + PreserveNewest + - - - - - - \ No newline at end of file + + + + diff --git a/TSOClient/tso.content/Interfaces/AbstractObjectProvider.cs b/TSOClient/tso.content/Interfaces/AbstractObjectProvider.cs index f50bdb1c8..58ede7e8a 100644 --- a/TSOClient/tso.content/Interfaces/AbstractObjectProvider.cs +++ b/TSOClient/tso.content/Interfaces/AbstractObjectProvider.cs @@ -2,6 +2,7 @@ using FSO.Common.Utils; using FSO.Files.Formats.IFF; using FSO.Files.Formats.IFF.Chunks; +using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; using System.IO; @@ -18,6 +19,8 @@ public abstract class AbstractObjectProvider : IContentProvider public Dictionary CatalogEnrich = new Dictionary(); public List ControllerObjects = new List(); + public Dictionary IconCache = []; + public AbstractObjectProvider(Content contentManager) { this.ContentManager = contentManager; @@ -204,5 +207,16 @@ public GameObject Get(string name) if (guid == 0) return null; return Get(guid); } + + public Texture2D GetOrAddGeneratedIcon(uint guid, Func generator) + { + if (!IconCache.TryGetValue(guid, out Texture2D result)) + { + result = generator(); + IconCache[guid] = result; + } + + return result; + } } } diff --git a/TSOClient/tso.content/Interfaces/IAudioProvider.cs b/TSOClient/tso.content/Interfaces/IAudioProvider.cs index 242a12467..46bc34049 100644 --- a/TSOClient/tso.content/Interfaces/IAudioProvider.cs +++ b/TSOClient/tso.content/Interfaces/IAudioProvider.cs @@ -42,6 +42,12 @@ public interface IAudioProvider /// A Patch instance. Patch GetPatch(uint id, HITResourceGroup group); + /// + /// Gets an FSC (ambient sound) sequence for the given path. + /// + /// The path to the FSC + /// An FSC instance. + FSC GetFSC(string path); /// /// A dictionary of sound events for the HIT VM to call upon. Should be generated when content initializes. diff --git a/TSOClient/tso.content/Interfaces/IObjectCatalog.cs b/TSOClient/tso.content/Interfaces/IObjectCatalog.cs index aed923687..20c4c2c80 100644 --- a/TSOClient/tso.content/Interfaces/IObjectCatalog.cs +++ b/TSOClient/tso.content/Interfaces/IObjectCatalog.cs @@ -18,6 +18,7 @@ public struct ObjectCatalogItem public string Name; public string CatalogName; public string Tags; + public uint Flags; // When non-zero, the entry will only retain its category when the catalog flag is set in tuning. (otherwise, category will become 29 if a matching entry doesn't already exist) public byte DisableLevel; //1 = only shopping, 2 = rare (unsellable?) public byte RoomSort; diff --git a/TSOClient/tso.content/Model/CityMap.cs b/TSOClient/tso.content/Model/CityMap.cs index b54e3736d..3ba13b8af 100644 --- a/TSOClient/tso.content/Model/CityMap.cs +++ b/TSOClient/tso.content/Model/CityMap.cs @@ -1,6 +1,5 @@ using Microsoft.Xna.Framework; -using System; -using System.IO; +using System.Runtime.InteropServices; namespace FSO.Content.Model { @@ -12,13 +11,16 @@ public class CityMap private static Color TERRAIN_ROCK = new Color(255, 0, 0); private static Color TERRAIN_SAND = new Color(255, 255, 0); + private static Color FOREST_HEAVY = new Color(0, 0x6A, 0x28); + private static Color FOREST_LIGHT = new Color(0, 0xEB, 0x42); + private static Color FOREST_CACTI = new Color(255, 0, 0); + private static Color FOREST_PALM = new Color(255, 0xFC, 0); + + public int Width => 512; + public int Height => 512; + private string _Directory; - public ITextureRef Elevation { get; internal set; } - public ITextureRef ForestDensity { get; internal set; } - public ITextureRef ForestType { get; internal set; } - public ITextureRef RoadMap { get; internal set; } - public ITextureRef TerrainTypeTex { get; internal set; } public ITextureRef VertexColour { get; internal set; } public ITextureRef Thumbnail { get; internal set; } @@ -26,6 +28,81 @@ public class CityMap private TextureValueMap _ElevationMap; private TextureValueMap _RoadMap; + private TextureValueMap _ForestDensity; + private TextureValueMap _ForestType; + + public byte[] ElevationData => _ElevationMap.GetRaw(); + public byte[] ForestDensityData => _ForestDensity.GetRaw(); + public byte[] RoadData => _RoadMap.GetRaw(); + public TerrainType[] TerrainType => _TerrainType.GetRaw(); + public ForestType[] ForestTypeData => _ForestType.GetRaw(); + + + public Color[] ElevationColorData => _ElevationMap.GetColor(); + public Color[] ForestDensityColorData => _ForestDensity.GetColor(); + public Color[] RoadColorData => _RoadMap.GetColor(); + public Color[] TerrainTypeColorData => _TerrainType.GetColor(); + public Color[] ForestTypeColorData => _ForestType.GetColor(); + + private CityMapAspects _Dirty = CityMapAspects.All; + + private static byte Red(Color color) + { + return color.R; + } + + private static Color ToGrayscale(byte value) + { + return new Color(value, value, value, (byte)255); + } + + public CityMap(CityMap other) + { + _Directory = other._Directory; + VertexColour = other.VertexColour; + + _TerrainType = new(other._TerrainType); + _ElevationMap = new(other._ElevationMap); + _RoadMap = new(other._RoadMap); + + _ForestDensity = new(other._ForestDensity); + _ForestType = new(other._ForestType); + } + + public CityMap(CityMapMarshal marshal) + { + _TerrainType = new TextureValueMap([.. MemoryMarshal.Cast(marshal.TerrainType)], TerrainTypeToColor); + _ElevationMap = new TextureValueMap(marshal.ElevationMap, ToGrayscale); + _RoadMap = new TextureValueMap(marshal.RoadMap, ToGrayscale); + + _ForestDensity = new TextureValueMap (marshal.ForestDensity, ToGrayscale); + _ForestType = new TextureValueMap([.. MemoryMarshal.Cast(marshal.TerrainType)], ForestTypeToColor); + } + + private static Color TerrainTypeToColor(TerrainType type) + { + return type switch + { + Model.TerrainType.GRASS => TERRAIN_GRASS, + Model.TerrainType.WATER => TERRAIN_WATER, + Model.TerrainType.SNOW => TERRAIN_SNOW, + Model.TerrainType.ROCK => TERRAIN_ROCK, + Model.TerrainType.SAND => TERRAIN_SAND, + _ => Color.Black + }; + } + private static Color ForestTypeToColor(ForestType type) + { + return type switch + { + Model.ForestType.HEAVY => FOREST_HEAVY, + Model.ForestType.LIGHT => FOREST_LIGHT, + Model.ForestType.CACTI => FOREST_CACTI, + Model.ForestType.PALM => FOREST_PALM, + _ => Color.Black + }; + } + public CityMap(string directory) { _Directory = directory; @@ -34,14 +111,18 @@ public CityMap(string directory) { ext = "png"; //fso maps use png } - Elevation = new FileTextureRef(Path.Combine(directory, "elevation." + ext)); - ForestDensity = new FileTextureRef(Path.Combine(directory, "forestdensity." + ext)); - ForestType = new FileTextureRef(Path.Combine(directory, "foresttype." + ext)); - RoadMap = new FileTextureRef(Path.Combine(directory, "roadmap." + ext)); - TerrainTypeTex = new FileTextureRef(Path.Combine(directory, "terraintype." + ext)); + VertexColour = new FileTextureRef(Path.Combine(directory, "vertexcolor." + ext)); Thumbnail = new FileTextureRef(Path.Combine(directory, "thumbnail." + ext)); + var Elevation = new FileTextureRef(Path.Combine(directory, "elevation." + ext)); + var ForestDensity = new FileTextureRef(Path.Combine(directory, "forestdensity." + ext)); + var ForestType = new FileTextureRef(Path.Combine(directory, "foresttype." + ext)); + var RoadMap = new FileTextureRef(Path.Combine(directory, "roadmap." + ext)); + var TerrainTypeTex = new FileTextureRef(Path.Combine(directory, "terraintype." + ext)); + + // Load from the files + _TerrainType = new TextureValueMap(TerrainTypeTex, x => { if (x == TERRAIN_GRASS) @@ -64,16 +145,68 @@ public CityMap(string directory) { return Model.TerrainType.SAND; } - return default(TerrainType); - }); - _ElevationMap = new TextureValueMap(Elevation, x => x.R); - _RoadMap = new TextureValueMap(RoadMap, x => x.R); + return Model.TerrainType.NULL; + }, TerrainTypeToColor); + + _ElevationMap = new TextureValueMap(Elevation, Red, ToGrayscale); + _RoadMap = new TextureValueMap(RoadMap, Red, ToGrayscale); + + _ForestType = new TextureValueMap(ForestType, x => + { + if (x == FOREST_HEAVY) + { + return Model.ForestType.HEAVY; + } + else if (x == FOREST_LIGHT) + { + return Model.ForestType.LIGHT; + } + else if (x == FOREST_CACTI) + { + return Model.ForestType.CACTI; + } + else if (x == FOREST_PALM) + { + return Model.ForestType.PALM; + } + + return Model.ForestType.NULL; + }, ForestTypeToColor); + + _ForestDensity = new TextureValueMap(ForestDensity, x => x.R, ToGrayscale); + } + + public CityMapAspects ConsumeDirty() + { + var toConsume = _Dirty; + _Dirty = CityMapAspects.None; + + return toConsume; + } + + public void SetDirty(CityMapAspects flags) + { + _Dirty |= flags; + } + + public void Set(CityMap other) + { + // TODO: limit aspects that are copied? + _TerrainType = new(other._TerrainType); + _ElevationMap = new(other._ElevationMap); + _RoadMap = new(other._RoadMap); + + _ForestDensity = new(other._ForestDensity); + _ForestType = new(other._ForestType); } public TerrainType GetTerrain(int x, int y) { - return _TerrainType.Get(x, y); + var type = _TerrainType.Get(x, y); + + // Compatibility for server terrain type checks (OOB always counts as grass) + return type == Model.TerrainType.NULL ? Model.TerrainType.GRASS : type; } public byte GetRoad(int x, int y) @@ -86,13 +219,38 @@ public byte GetElevation(int x, int y) return _ElevationMap.Get(x, y); } + public byte[] GetRawElevation() + { + return _ElevationMap.GetRaw(); + } + + public byte[] GetRawRoads() + { + return _RoadMap.GetRaw(); + } + + public TerrainType[] GetRawTerrain() + { + return _TerrainType.GetRaw(); + } + + public ForestType[] GetRawForestType() + { + return _ForestType.GetRaw(); + } + + public byte[] GetRawForestDensity() + { + return _ForestDensity.GetRaw(); + } + public TerrainBlend GetBlend(int x, int y) { TerrainType sample; TerrainType t; - var edges = new TerrainType[] { TerrainType.NULL, TerrainType.NULL, TerrainType.NULL, TerrainType.NULL, - TerrainType.NULL, TerrainType.NULL, TerrainType.NULL, TerrainType.NULL}; + var edges = new TerrainType[] { Model.TerrainType.NULL, Model.TerrainType.NULL, Model.TerrainType.NULL, Model.TerrainType.NULL, + Model.TerrainType.NULL, Model.TerrainType.NULL, Model.TerrainType.NULL, Model.TerrainType.NULL}; sample = GetTerrain(x, y); t = GetTerrain(x, y - 1); @@ -121,16 +279,16 @@ public TerrainBlend GetBlend(int x, int y) int binary = 0; for (int i = 0; i < 8; i++) - binary |= ((edges[i] > TerrainType.NULL) ? (1 << i) : 0); + binary |= ((edges[i] > Model.TerrainType.NULL) ? (1 << i) : 0); int waterbinary = 0; for (int i = 0; i < 8; i++) - waterbinary |= ((edges[i] == TerrainType.WATER) ? (1 << i) : 0); + waterbinary |= ((edges[i] == Model.TerrainType.WATER) ? (1 << i) : 0); - TerrainType maxEdge = TerrainType.WATER; + TerrainType maxEdge = Model.TerrainType.WATER; - for (int i = 0; i < 8; i++) - if (edges[i] < maxEdge && edges[i] != TerrainType.NULL) maxEdge = edges[i]; + for (int i = 0; i < 8; i += 2) + if (edges[i] < maxEdge && edges[i] != Model.TerrainType.NULL) maxEdge = edges[i]; TerrainBlend ReturnBlend = new TerrainBlend(); ReturnBlend.Base = sample; @@ -140,6 +298,24 @@ public TerrainBlend GetBlend(int x, int y) return ReturnBlend; } + + public bool IsInBounds(int x, int y) + { + return x >= 0 && y >= 0 && x < Width && y < Height; + } + + public CityMapMarshal Save() + { + return new CityMapMarshal() + { + TerrainType = [.. MemoryMarshal.Cast(_TerrainType.GetRaw())], + ElevationMap = [.. _ElevationMap.GetRaw()], + RoadMap = [.. _RoadMap.GetRaw()], + + ForestDensity = [.. _ForestDensity.GetRaw()], + ForestType = [..MemoryMarshal.Cast(_ForestType.GetRaw())], + }; + } } public struct TerrainBlend @@ -150,7 +326,7 @@ public struct TerrainBlend public byte WaterFlags; } - public enum TerrainType + public enum TerrainType : sbyte { WATER = 4, ROCK = 2, @@ -164,13 +340,47 @@ public enum TerrainType TS1Cloud = 7 } + public enum ForestType : sbyte + { + HEAVY = 0, + LIGHT = 1, + CACTI = 2, + PALM = 3, + + SNOW = 4, // special internal type + + NULL = -1 + } + + [Flags] + public enum CityMapAspects + { + None = 0, + Elevation = 1 << 0, + TerrainType = 1 << 1, + Forest = 1 << 2, + Road = 1 << 3, + + All = Elevation | TerrainType | Forest | Road + } + public class TextureValueMap { - private T[,] Values; + private const int Width = 512; + private const int Height = 512; + private readonly T[] Values; + private readonly Func ReverseConverter; - public TextureValueMap(ITextureRef texture, Func converter) + public TextureValueMap(T[] values, Func reverseConverter) { - Values = new T[512, 512]; + Values = values; + ReverseConverter = reverseConverter; + } + + public TextureValueMap(ITextureRef texture, Func converter, Func reverseConverter) + { + Values = new T[Width * Height]; + ReverseConverter = reverseConverter; var image = texture.GetImage(); var bytes = image.Data; @@ -180,6 +390,7 @@ public TextureValueMap(ITextureRef texture, Func converter) var index = 0; + int i = 0; for (var y = 0; y < 512; y++) { for (var x = 0; x < 512; x++) @@ -194,7 +405,7 @@ public TextureValueMap(ITextureRef texture, Func converter) //The game actually uses the pixel coordinates as the lot coordinates var color = new Color(r, g, b, a); var value = converter(color); - Values[y, x] = value; + Values[i++] = value; } } @@ -203,11 +414,27 @@ public TextureValueMap(ITextureRef texture, Func converter) public T Get(int x, int y) { - if (x < 0 || y < 0 || x >= 512 || y >= 512) + if (x < 0 || y < 0 || x >= Width || y >= Height) { return default(T); } - return Values[y, x]; + return Values[y * Width + x]; + } + + public T[] GetRaw() + { + return Values; + } + + public Color[] GetColor() + { + return Values.Select(x => ReverseConverter(x)).ToArray(); + } + + public TextureValueMap(TextureValueMap other) + { + Values = other.Values.ToArray(); + ReverseConverter = other.ReverseConverter; } } } diff --git a/TSOClient/tso.content/Model/CityMapMarshal.cs b/TSOClient/tso.content/Model/CityMapMarshal.cs new file mode 100644 index 000000000..7a22a78ae --- /dev/null +++ b/TSOClient/tso.content/Model/CityMapMarshal.cs @@ -0,0 +1,71 @@ +using FSO.Files.Utils; +using System.IO.Compression; + +namespace FSO.Content.Model +{ + public class CityMapMarshal + { + private const int MapWidth = 512; + private const int MapHeight = 512; + + public byte[] TerrainType; + public byte[] ElevationMap; + public byte[] RoadMap; + + public byte[] ForestDensity; + public byte[] ForestType; + + public CityMapMarshal() + { + + } + + public void Write(Stream str) + { + using (var compressed = new GZipStream(str, CompressionMode.Compress)) + { + using (var io = IoWriter.FromStream(str)) + { + io.WriteBytes(TerrainType); + io.WriteBytes(ElevationMap); + io.WriteBytes(RoadMap); + io.WriteBytes(ForestDensity); + io.WriteBytes(ForestType); + } + + compressed.Close(); + } + } + + public byte[] Write() + { + using (var mem = new MemoryStream()) + { + Write(mem); + + return mem.ToArray(); + } + } + + public void Read(Stream str) + { + using (var io = IoBuffer.FromStream(str)) + { + int pixelCount = MapWidth * MapHeight; + TerrainType = io.ReadBytes(pixelCount); + ElevationMap = io.ReadBytes(pixelCount); + RoadMap = io.ReadBytes(pixelCount); + ForestDensity = io.ReadBytes(pixelCount); + ForestType = io.ReadBytes(pixelCount); + } + } + + public void Read(byte[] data) + { + using (var mem = new MemoryStream(data)) + { + Read(mem); + } + } + } +} diff --git a/TSOClient/tso.content/Model/TextureRef.cs b/TSOClient/tso.content/Model/TextureRef.cs index dbd3d962a..efd7510da 100644 --- a/TSOClient/tso.content/Model/TextureRef.cs +++ b/TSOClient/tso.content/Model/TextureRef.cs @@ -18,16 +18,16 @@ public interface ITextureRef public class FileTextureRef : AbstractTextureRef { - private string _FilePath; + public readonly string FilePath; public FileTextureRef(string filepath) { - _FilePath = filepath; + FilePath = filepath; } protected override Stream GetStream() { - return new FileStream(_FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); + return new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read); } } @@ -51,6 +51,8 @@ protected override Texture2D Process(GraphicsDevice device, Stream stream) var texture = base.Process(device, stream); if (Mipmap) { + // TODO: get data, mip that and then upload instead of using a temporary texture + var data = new Color[texture.Width * texture.Height]; texture.GetData(data); texture.Dispose(); @@ -170,9 +172,16 @@ public Texture2D Get(GraphicsDevice device) } } + public static Func ImageFetchFallback; + public static GraphicsDevice FetchDevice; public static TexBitmap ImageFetchWithDevice(Stream stream, AbstractTextureRef texRef) { + if (ImageFetchFallback != null && !GameThread.IsInGameThread()) + { + return ImageFetchFallback(stream, texRef); + } + var tex = ImageLoader.FromStream(FetchDevice, stream); var data = new byte[tex.Width * tex.Height * 4]; tex.GetData(data); diff --git a/TSOClient/tso.content/Properties/AssemblyInfo.cs b/TSOClient/tso.content/Properties/AssemblyInfo.cs deleted file mode 100644 index fd878066f..000000000 --- a/TSOClient/tso.content/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.Content")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.Content")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3e94aa20-01fe-4a9b-975f-9687bd0f61ad")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.content/RCDBPFContent.cs b/TSOClient/tso.content/RCDBPFContent.cs new file mode 100644 index 000000000..729130483 --- /dev/null +++ b/TSOClient/tso.content/RCDBPFContent.cs @@ -0,0 +1,441 @@ +using FSO.Common; +using FSO.Common.Utils; +using FSO.Files.Formats.DBPF; +using FSO.Files.Formats.IFF.Chunks; +using FSO.Files.FSO; +using FSO.Files.RC; +using Microsoft.Xna.Framework.Graphics; +using System.Net; +using System.Security.Cryptography; + +namespace FSO.Content +{ + internal class RCDBPFFile : IDisposable + { + private readonly DBPFFile File; + private readonly Lock StreamLock = new(); + public readonly FSO3DDirectory Directory; + + private readonly Dictionary Meshes = []; + private readonly Dictionary Textures = []; + + private bool Disposed; + + public RCDBPFFile(string path) + { + File = new DBPFFile(path); + + var directoryData = File.GetItemByID(DBPFTypeID.FSO3DDirectory, 0); + + if (directoryData == null) + { + throw new InvalidDataException($"Remesh package {path} doesn't contain a directory chunk."); + } + + Directory = new FSO3DDirectory(); + using var dirStream = new MemoryStream(directoryData); + Directory.Read(dirStream); + } + + public FSO3DRef? GetRef(string fileName, ushort chunkId, bool mesh) + { + if (!Directory.Entries.TryGetValue(fileName, out var entry)) + { + return null; + } + + var lookup = mesh ? entry.Meshes : entry.Textures; + + if (!lookup.TryGetValue(chunkId, out var result)) + { + return null; + } + + return result; + } + + public DGRP3DMesh GetMesh(DGRP dgrp, GraphicsDevice gd, FSO3DRef reference) + { + lock (StreamLock) + { + if (!Meshes.TryGetValue(reference, out var result)) + { + if (Disposed) return null; + + var meshData = File.GetItemByID((DBPFTypeID)reference.TypeID, reference.FileID); + + // Deliberately doesn't close, as this is done asynchronously by DGRP3DMesh. + var meshStream = new MemoryStream(meshData); + + try + { + result = new DGRP3DMesh(dgrp, meshStream, gd); + } + catch (Exception e) + { + result = null; + } + + Meshes[reference] = result; + } + + return result; + } + } + + public IDGRP3DTextureHolder GetTexture(FSO3DRef reference) + { + lock (StreamLock) + { + if (!Textures.TryGetValue(reference, out var result)) + { + if (Disposed) return null; + + var texData = File.GetItemByID((DBPFTypeID)reference.TypeID, reference.FileID); + using var texStream = new MemoryStream(texData); + + try + { + switch ((DBPFTypeID)reference.TypeID) + { + case DBPFTypeID.MTX2: + { + var mtx2 = new MTX2(); + mtx2.Read(null, texStream); + result = mtx2; + break; + } + case DBPFTypeID.MTEX: + { + var mtex = new MTEX(); + mtex.Read(null, texStream); + result = mtex; + break; + } + default: + throw new NotSupportedException($"Unsupported remesh texture type {reference.TypeID:x8}"); + } + } + catch (Exception e) + { + result = null; + } + + Textures[reference] = result; + } + + return result; + } + } + + public FSO3DCredits GetCredits() + { + lock (StreamLock) + { + var creditsData = File.GetItemByID(DBPFTypeID.FSO3DCredits, 0); + + var credits = new FSO3DCredits(); + using var creditsStream = new MemoryStream(creditsData); + credits.Read(creditsStream); + + return credits; + } + } + + public void Dispose() + { + lock (StreamLock) + { + Disposed = true; + File.Dispose(); + } + } + } + + public class RCDBPFContent + { + public static float? DownloadPercentage; + + private readonly List Files = []; + private string RootDir; + + public RCDBPFContent(string rootDir) + { + RootDir = rootDir; + // Scan for dbpf + + var files = Directory.GetFiles(rootDir); + + foreach (var file in files) + { + if (file.EndsWith(".dat")) + { + var name = Path.GetFileNameWithoutExtension(file); + try + { + var collection = new RCDBPFFile(file); + + AddCollection(collection); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load remesh package {name}: {e}"); + } + } + } + } + + private void AddCollection(RCDBPFFile file) + { + Files.Add(file); + } + + public bool TryGetRemesh(DGRP dgrp, GraphicsDevice gd, string file, ushort chunkId, out DGRP3DMesh mesh) + { + foreach (var collection in Files) + { + var ref3d = collection.GetRef(file, chunkId, true); + + if (ref3d == null) + { + continue; + } + + mesh = collection.GetMesh(dgrp, gd, ref3d.Value); + return true; + } + + mesh = null; + return false; + } + + public bool TryGetRemeshTexture(string file, ushort chunkId, out IDGRP3DTextureHolder texture) + { + foreach (var collection in Files) + { + var ref3d = collection.GetRef(file, chunkId, false); + + if (ref3d == null) + { + continue; + } + + texture = collection.GetTexture(ref3d.Value); + return true; + } + + texture = null; + return false; + } + + public FSO3DCredits[] GetCredits() + { + return [.. Files.Select(x => x.GetCredits())]; + } + + private void TryDeleteFile(string path) + { + try + { + File.Delete(path); + } + catch + { + // Do nothing + } + } + + private FSO3DPackageTextureFormat GetPreferredFormat() + { + return FSOEnvironment.TexCompressSupport ? FSO3DPackageTextureFormat.Dxt : FSO3DPackageTextureFormat.Png; + } + + private FSORemeshFile SelectFileByFormat(FSORemeshChannel channel, FSO3DPackageTextureFormat format) + { + FSORemeshFile result = null; + + switch (format) + { + case FSO3DPackageTextureFormat.Png: + result = channel.png; + break; + case FSO3DPackageTextureFormat.Dxt: + result = channel.dxt; + break; + } + + return result ?? channel.png; + } + + public void TryUpdate(FSOUpdateResponse response) + { + if ((response.remeshes?.Length ?? 0) == 0) + { + return; + } + + // Try find a remesh package to update. + + foreach (var file in Files) + { + var credits = file.GetCredits(); + + var meta = credits.Metadata; + + // Try find a channel that matches this installed remesh package. + + var matching = response.remeshes.FirstOrDefault(x => x.channel == meta.ChannelName && x.publicKey == meta.PublicKey); + + if (matching.version > meta.Version) + { + DownloadPackage(matching, SelectFileByFormat(matching, meta.Format), file, () => TryUpdate(response)); + + // We can come back to update other packages after we download this one. + return; + } + } + + // Should we be downloading a channel automatically? + + if (Files.Count == 0 && response.autoRemeshChannel != null) + { + var target = response.remeshes.FirstOrDefault(x => x.channel == response.autoRemeshChannel); + + if (target != null && target.publicKey == FSOVersionInfo.Current.publicKey) + { + var file = SelectFileByFormat(target, GetPreferredFormat()); + + if (file != null) + { + DownloadPackage(target, file); + } + } + } + } + + private static RSA TryGetCrypto(string publicKey) + { + try + { + var rsa = RSA.Create(); + + rsa.ImportFromPem(publicKey.Replace('^', '\n')); + + return rsa; + } + catch (Exception) + { + return null; + } + } + + private void Unload(RCDBPFFile toReplace) + { + Files.Remove(toReplace); + toReplace.Dispose(); + } + + + private void DownloadPackage(FSORemeshChannel channel, FSORemeshFile file, RCDBPFFile toReplace = null, Action onComplete = null) + { + if (!Uri.TryCreate(file.url, UriKind.Absolute, out var uri)) + { + return; + } + + if (!string.IsNullOrEmpty(channel.publicKey)) + { + // Make sure the file signature is valid. + + var crypto = TryGetCrypto(channel.publicKey); + + if (crypto == null || !crypto.VerifyHash(Convert.FromBase64String(file.hash), Convert.FromBase64String(file.signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1)) + { + return; + } + } + + string name = Path.GetFileName(uri.LocalPath); + + var localPath = PathUtils.SafeCombine(RootDir, name); + var client = new WebClient(); + + DownloadPercentage = 0; + + client.DownloadProgressChanged += (obj, evt) => + { + DownloadPercentage = evt.ProgressPercentage / 100f; + }; + + client.DownloadFileCompleted += (obj, evt) => + { + DownloadPercentage = null; + + if (evt.Cancelled || evt.Error != null) + { + TryDeleteFile(localPath); + return; + } + + if (file.size != 0) + { + var size = new FileInfo(localPath).Length; + + if (size != file.size) + { + // Not valid. + return; + } + } + + if (file.hash != null) + { + using FileStream fileStr = File.OpenRead(localPath); + var hash = SHA256.HashData(fileStr); + + if (Convert.ToBase64String(hash) != file.hash) + { + // Not valid. + return; + } + } + + // Try and load the new package. + + GameThread.InUpdate(() => + { + try + { + var collection = new RCDBPFFile(localPath); + + AddCollection(collection); + + onComplete?.Invoke(); + } + catch (Exception e) + { + Console.WriteLine($"Failed to load downloaded remesh package {name}: {e}"); + + // It's probably corrupted. + TryDeleteFile(localPath); + } + }); + }; + + try + { + if (toReplace != null) + { + Unload(toReplace); + TryDeleteFile(localPath); + } + + client.DownloadFileAsync(uri, localPath); + } + catch + { + + } + } + } +} diff --git a/TSOClient/tso.content/RCMeshProvider.cs b/TSOClient/tso.content/RCMeshProvider.cs index 2ed3788da..404e71548 100644 --- a/TSOClient/tso.content/RCMeshProvider.cs +++ b/TSOClient/tso.content/RCMeshProvider.cs @@ -1,14 +1,8 @@ using FSO.Common; -using FSO.Common.Utils; -using FSO.Files; using FSO.Files.Formats.IFF.Chunks; using FSO.Files.RC; -using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; +using System.Collections.Concurrent; namespace FSO.Content { @@ -34,13 +28,16 @@ public RCMeshProvider(GraphicsDevice gd) } catch { } - CacheFiles = new HashSet(Directory.GetFiles(dir).Select(x => Path.GetFileName(x).ToLowerInvariant())); - ReplaceFiles = new HashSet(Directory.GetFiles(repldir).Select(x => Path.GetFileName(x).ToLowerInvariant())); + CacheFiles = [.. Directory.GetFiles(dir).Select(x => Path.GetFileName(x).ToLowerInvariant())]; + ReplaceFiles = [.. Directory.GetFiles(repldir).Select(x => Path.GetFileName(x).ToLowerInvariant())]; + Packages = new RCDBPFContent(repldir); } - public Dictionary Cache = new Dictionary(); - public HashSet IgnoreRCCache = new HashSet(); - public Dictionary ReplacementTex = new Dictionary(); - public Dictionary NameCache = new Dictionary(); + + public readonly Dictionary Cache = []; + public readonly HashSet IgnoreRCCache = []; + public readonly ConcurrentDictionary ReplacementTex = new(); + public readonly Dictionary NameCache = []; + public readonly RCDBPFContent Packages; public DGRP3DMesh Get(DGRP dgrp, OBJD obj) { @@ -49,16 +46,23 @@ public DGRP3DMesh Get(DGRP dgrp, OBJD obj) var dir = Path.Combine(FSOEnvironment.UserDir, "MeshCache/"); if (!Cache.TryGetValue(dgrp, out result)) { + // Does it exist in the loaded remesh packs? + string baseFile = obj.ChunkParent.Filename.Replace('.', '_').ToLowerInvariant(); + + if (Packages.TryGetRemesh(dgrp, GD, baseFile, dgrp.ChunkID, out result)) + { + Cache[dgrp] = result; + + return result; + } + //does it exist in replacements - var name = obj.ChunkParent.Filename.Replace('.', '_').ToLowerInvariant() + "_" + dgrp.ChunkID + ".fsom"; + var name = baseFile + "_" + dgrp.ChunkID + ".fsom"; if (ReplaceFiles.Contains(name)) { try { - using (var file = File.OpenRead(Path.Combine(repldir, name))) - { - result = new DGRP3DMesh(dgrp, file, GD); - } + result = new DGRP3DMesh(dgrp, Path.Combine(repldir, name), GD); } catch (Exception) { @@ -86,10 +90,7 @@ public DGRP3DMesh Get(DGRP dgrp, OBJD obj) //does it exist in rc cache try { - using (var file = File.OpenRead(Path.Combine(dir, name))) - { - result = new DGRP3DMesh(dgrp, file, GD); - } + result = new DGRP3DMesh(dgrp, Path.Combine(dir, name), GD); } catch (Exception) { @@ -121,10 +122,7 @@ public DGRP3DMesh Get(string name) //does it exist in replacements try { - using (var file = File.OpenRead(Path.Combine(repldir, name))) - { - result = new DGRP3DMesh(null, file, GD); - } + result = new DGRP3DMesh(null, Path.Combine(repldir, name), GD); } catch (Exception) { @@ -155,42 +153,49 @@ public void Replace(DGRP dgrp, DGRP3DMesh mesh) Cache[dgrp] = mesh; } - public Texture2D GetTex(string name) + public DGRP3DTextureSource? GetTex(string baseName, ushort pixelSPR) { - Texture2D result = null; + IDGRP3DTextureHolder result = null; + + string name = baseName; + if (pixelSPR != 65535) + { + name += "_TEX_" + pixelSPR + ".png"; + } + + // TODO: Could have load the same texture multiple times due to a race condition? if (!ReplacementTex.TryGetValue(name, out result)) { - string dir; - if (name.StartsWith("FSO_")) - { - dir = Path.Combine(FSOEnvironment.ContentDir, "3D/"); - name = name.Substring(4); - } - else dir = Path.Combine(FSOEnvironment.ContentDir, "MeshReplace/"); - //load from meshreplace folder - try + string lookupName = name; + if (!Packages.TryGetRemeshTexture(baseName, pixelSPR, out result)) { - using (var file = File.OpenRead(Path.Combine(dir, name))) + string dir; + if (name.StartsWith("FSO_")) { - result = ImageLoader.FromStream(GD, file); - if (FSOEnvironment.EnableNPOTMip) + dir = Path.Combine(FSOEnvironment.ContentDir, "3D/"); + name = name.Substring(4); + } + else dir = Path.Combine(FSOEnvironment.ContentDir, "MeshReplace/"); + //load from meshreplace folder + try + { + var path = Path.Combine(dir, name); + + if (File.Exists(path)) { - var data = new Color[result.Width * result.Height]; - result.GetData(data); - var n = new Texture2D(GD, result.Width, result.Height, true, SurfaceFormat.Color); - TextureUtils.UploadWithAvgMips(n, GD, data); - result.Dispose(); - result = n; + result = new MTEX(File.OpenRead(path)); } - }; - } - catch (Exception) - { - result = null; + } + catch (Exception) + { + result = null; + } } - ReplacementTex[name] = result; + + ReplacementTex[lookupName] = result; } - return result; + + return DGRP3DTextureSource.WithDecoded(result, GD); } } } diff --git a/TSOClient/tso.content/TS1/TS1Audio.cs b/TSOClient/tso.content/TS1/TS1Audio.cs index de172084d..de32ff6e3 100644 --- a/TSOClient/tso.content/TS1/TS1Audio.cs +++ b/TSOClient/tso.content/TS1/TS1Audio.cs @@ -25,9 +25,10 @@ public class TS1Audio : IAudioProvider private TS1SubProvider MP3Sounds; private TS1SubProvider XASounds; private TS1SubProvider UTKSounds; + private TS1SubProvider FSCs; /** Audio Cache **/ - public Dictionary SFXCache = new Dictionary(); + public Dictionary SFXCache = new Dictionary(); private Dictionary _Events = new Dictionary(); public Dictionary Events @@ -132,6 +133,7 @@ public TS1Audio(Content contentManager) MP3Sounds = new TS1SubProvider(ContentManager.TS1Global, ".mp3"); XASounds = new TS1SubProvider(ContentManager.TS1Global, ".xa"); UTKSounds = new TS1SubProvider(ContentManager.TS1Global, ".utk"); + FSCs = new TS1SubProvider(ContentManager.TS1Global, ".fsc"); } public void Init() @@ -144,6 +146,7 @@ public void Init() MP3Sounds.Init(); XASounds.Init(); UTKSounds.Init(); + FSCs.Init(); var FilePattern = new Regex(@".*\.hot"); @@ -218,7 +221,7 @@ public Track GetTrack(uint value, uint fallback, HITResourceGroup group) public SoundEffect GetSFX(Patch patch) { if (patch == null) return null; - if (SFXCache.ContainsKey(patch)) return SFXCache[patch]; + if (SFXCache.TryGetValue(patch.Filename, out var cached)) return cached; var aud = GetAudioFrom(patch.Filename); if (aud != null) @@ -226,7 +229,7 @@ public SoundEffect GetSFX(Patch patch) var stream = new MemoryStream(aud.Data); var sfx = SoundEffect.FromStream(stream); stream.Close(); - SFXCache.Add(patch, sfx); + SFXCache.Add(patch.Filename, sfx); switch (aud.Filetype) { case 2: @@ -275,5 +278,10 @@ public Patch GetPatch(uint id, HITResourceGroup group) group.hot.Patches.TryGetValue(id, out result); return result; } + + public FSC GetFSC(string path) + { + return FSCs.Get(Path.GetFileName(path).ToLowerInvariant()); + } } } diff --git a/TSOClient/tso.content/TS1/TS1NeighbourProvider.cs b/TSOClient/tso.content/TS1/TS1NeighbourProvider.cs index 2fb338888..df06b0f3b 100644 --- a/TSOClient/tso.content/TS1/TS1NeighbourProvider.cs +++ b/TSOClient/tso.content/TS1/TS1NeighbourProvider.cs @@ -1,4 +1,4 @@ -using FSO.Common; +using FSO.Common; using FSO.Files.Formats.IFF; using FSO.Files.Formats.IFF.Chunks; using System; @@ -53,25 +53,61 @@ public void InitSpecific(int id) var udName = "UserData" + ((id == 0) ? "" : (id + 1).ToString()); //simitone shouldn't modify existing ts1 data, since our house saves are incompatible. //therefore we should copy to the simitone user data. - + var userPath = Path.Combine(FSOEnvironment.UserDir, udName + "/"); - + if (!Directory.Exists(userPath)) { - var source = Path.Combine(ContentManager.TS1BasePath, udName + "/"); + + string source; + + // Check if user selected Steam install via Content.TS1SteamInstall flag + if (Content.TS1SteamInstall) + { + // Use Steam's "Saved Games" location (used by The Sims Legacy Collection) + source = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), + "Saved Games", "Electronic Arts", "The Sims 25", udName + "/" + ); + } + else + { + // Use install directory saves (non-Steam installs) + source = Path.Combine(ContentManager.TS1BasePath, udName + "/"); + } + + var destination = userPath; - //quick and dirty copy. + // Normalize paths for comparison (remove trailing slashes, use consistent separators) + var normalizedSource = source.TrimEnd('/', '\\'); + var normalizedDest = destination.TrimEnd('/', '\\'); - foreach (string dirPath in Directory.GetDirectories(source, "*", - SearchOption.AllDirectories)) - Directory.CreateDirectory(dirPath.Replace('\\', '/').Replace(source, destination)); + // Create directory structure + foreach (string dirPath in Directory.GetDirectories(source, "*", SearchOption.AllDirectories)) + { + var relativePath = dirPath.Substring(normalizedSource.Length).TrimStart('\\', '/'); + var destDir = Path.Combine(normalizedDest, relativePath); + Directory.CreateDirectory(destDir); + } - foreach (string newPath in Directory.GetFiles(source, "*.*", - SearchOption.AllDirectories)) - File.Copy(newPath, newPath.Replace('\\', '/').Replace(source, destination), true); + // Copy files with error handling + foreach (string srcPath in Directory.GetFiles(source, "*.*", SearchOption.AllDirectories)) + { + var relativePath = srcPath.Substring(normalizedSource.Length).TrimStart('\\', '/'); + var destPath = Path.Combine(normalizedDest, relativePath); + try + { + File.Copy(srcPath, destPath, true); + } + catch (IOException ex) + { + throw; // Re-throw to show error dialog + } + } + } - + UserPath = userPath; MainResource = new IffFile(Path.Combine(UserPath, "Neighborhood.iff")); diff --git a/TSOClient/tso.content/UIGraphicsProvider.cs b/TSOClient/tso.content/UIGraphicsProvider.cs index 8ff51de4d..6285df572 100644 --- a/TSOClient/tso.content/UIGraphicsProvider.cs +++ b/TSOClient/tso.content/UIGraphicsProvider.cs @@ -42,24 +42,6 @@ public UIGraphicsProvider(Content contentManager) Files[0x3D3AEF0856DDBAC] = "uigraphics/friendshipweb/f_web_outbtn.bmp"; //./uigraphics/eods/costumetrunk/eod_costumetrunkbodySkinBtn.bmp Pointers[0x0000028800000001] = 0x0000094600000001; - - - } - - public static string ReplacementImportDir = "D:/Stuff/waifu/UIScaled/"; - - public void ExportAll(GraphicsDevice gd) - { - var replacementExportDir = "D:/Stuff/waifu/UI/"; - Directory.CreateDirectory(replacementExportDir); - - foreach (var item in List()) - { - var texr = item.Get(); - var img = texr.GetImage(); - using (var stream = File.Open(replacementExportDir + ((Far3ProviderEntry)item).ID.ToString("x16") + ".png", FileMode.Create)) - ImageLoaderHelpers.SavePNGFunc(img.Data, img.Width, img.Height, stream); - } } protected override ITextureRef ResolveById(ulong id) @@ -78,15 +60,23 @@ protected override ITextureRef ResolveById(ulong id) } } var result = base.ResolveById(id); - /* - if (result.ReplacePath == null) + + if (result == null) { - if (File.Exists(ReplacementImportDir + id.ToString("x16") + "_[NS-L3][x2.000000].png")) + // Try the fallback directory. + string path = $"Content/uigraphics/fallback/0x{id:x16}.png"; + + if (File.Exists(path)) { - result.ReplacePath = ReplacementImportDir + id.ToString("x16") + "_[NS-L3][x2.000000].png"; + if (FilesCache.ContainsKey(id)) { return FilesCache[id]; } + using (var stream = File.OpenRead(path)) + { + FilesCache.Add(id, Codec.Decode(stream)); + return FilesCache[id]; + } } } - */ + return result; } } diff --git a/TSOClient/tso.content/WorldFloorProvider.cs b/TSOClient/tso.content/WorldFloorProvider.cs index 11f10f766..41854a271 100644 --- a/TSOClient/tso.content/WorldFloorProvider.cs +++ b/TSOClient/tso.content/WorldFloorProvider.cs @@ -1,17 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using FSO.Common.Content; +using FSO.Common.Content; +using FSO.Common.Utils; +using FSO.Content.Codecs; +using FSO.Content.Framework; using FSO.Content.Model; +using FSO.Files.FAR1; using FSO.Files.Formats.IFF; using FSO.Files.Formats.IFF.Chunks; -using FSO.Files.FAR1; -using System.IO; -using FSO.Content.Framework; -using System.Text.RegularExpressions; -using FSO.Content.Codecs; using Microsoft.Xna.Framework.Graphics; -using FSO.Common.Utils; +using System.Text.RegularExpressions; namespace FSO.Content { @@ -68,10 +64,11 @@ private void InitGlobals () { ID = floorID, FileName = "global", + Hardness = HardnessFromChunkName(far.ChunkLabel), Name = floorStrs.GetString((i - 1) * 3 + 1), Price = int.Parse(floorStrs.GetString((i - 1) * 3 + 0)), - Description = floorStrs.GetString((i - 1) * 3 + 2) + Description = floorStrs.GetString((i - 1) * 3 + 2), }); floorID++; @@ -139,6 +136,7 @@ public void InitTS1() { ID = floorID, FileName = Path.GetFileName(entry.ToString().Replace('\\', '/')).ToLowerInvariant(), + Hardness = HardnessFromChunkName(iff.GetLabel(1)), Name = catStrings.GetString(0), Price = int.Parse(catStrings.GetString(1)), @@ -150,6 +148,27 @@ public void InitTS1() NumFloors = floorID; } + private int HardnessFromChunkName(string name) + { + if (name == null || name.Length < 1) + { + return 2; + } + + // This can be 'C'. Not sure what that means. + switch (name[0]) + { + case 'H': + return 2; + case 'M': + return 1; + case 'S': + return 0; + } + + return 2; + } + /// /// Initiates loading of floors. /// @@ -198,6 +217,7 @@ public void Init() { ID = floorID, FileName = entry.Key, + Hardness = HardnessFromChunkName(iff.GetLabel(1)), Name = catStrings.GetString(0), Price = int.Parse(catStrings.GetString(1)), @@ -339,6 +359,7 @@ public class FloorReference : IContentReference public int Price; //remember these, just in place of a catalog public string Name; public string Description; + public int Hardness; private WorldFloorProvider Provider; diff --git a/TSOClient/tso.content/WorldGlobalProvider.cs b/TSOClient/tso.content/WorldGlobalProvider.cs index 1d58c34c6..6491ae62f 100644 --- a/TSOClient/tso.content/WorldGlobalProvider.cs +++ b/TSOClient/tso.content/WorldGlobalProvider.cs @@ -200,7 +200,12 @@ public GameGlobal Get(string filename) try { var rewrite = PIFFRegistry.GetOTFRewrite(filename + ".otf"); - otf = new OTFFile(rewrite ?? Path.Combine(ContentManager.BasePath, ("objectdata/globals/" + filename + ".otf"))); + var path = rewrite ?? Path.Combine(ContentManager.BasePath, ("objectdata/globals/" + filename + ".otf")); + + if (File.Exists(path)) + { + otf = new OTFFile(path); + } } catch (IOException) { diff --git a/TSOClient/tso.content/WorldObjectCatalog.cs b/TSOClient/tso.content/WorldObjectCatalog.cs index 2e141617c..25cc7ea2f 100644 --- a/TSOClient/tso.content/WorldObjectCatalog.cs +++ b/TSOClient/tso.content/WorldObjectCatalog.cs @@ -59,6 +59,9 @@ public void Init(Content content, Dictionary cat if (dCategory < 0) continue; catalogEnrich.TryGetValue(dguid, out var enrich); + string flagString = objectInfo.Attributes["f"]?.Value; + uint flags = flagString != null ? Convert.ToUInt32(flagString) : 0; + var ditem = new ObjectCatalogItem() { GUID = dguid, @@ -66,6 +69,7 @@ public void Init(Content content, Dictionary cat Price = Convert.ToUInt32(objectInfo.Attributes["p"].Value), Name = objectInfo.Attributes["n"].Value, Tags = objectInfo.Attributes["t"]?.Value, + Flags = flags, CatalogName = enrich?.CatalogName, DisableLevel = Convert.ToByte(objectInfo.Attributes["r"]?.Value ?? "0") }; diff --git a/TSOClient/tso.content/app.config b/TSOClient/tso.content/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.content/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.content/packages.config b/TSOClient/tso.content/packages.config deleted file mode 100644 index bacf76a24..000000000 --- a/TSOClient/tso.content/packages.config +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/TSOClient/tso.debug/ActionQueue.Designer.cs b/TSOClient/tso.debug/ActionQueue.Designer.cs index 3b288554b..9d4c2920f 100644 --- a/TSOClient/tso.debug/ActionQueue.Designer.cs +++ b/TSOClient/tso.debug/ActionQueue.Designer.cs @@ -59,8 +59,8 @@ private void InitializeComponent() // // ActionQueue // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(363, 123); this.Controls.Add(this.actionView); this.Controls.Add(this.objNameLabel); diff --git a/TSOClient/tso.debug/Content/ContentBrowser.Designer.cs b/TSOClient/tso.debug/Content/ContentBrowser.Designer.cs index 45850f6b2..055075b1d 100644 --- a/TSOClient/tso.debug/Content/ContentBrowser.Designer.cs +++ b/TSOClient/tso.debug/Content/ContentBrowser.Designer.cs @@ -160,8 +160,8 @@ private void InitializeComponent() // // ContentBrowser // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(1091, 388); this.Controls.Add(this.splitContainer1); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); diff --git a/TSOClient/tso.debug/Content/Preview/TexturePreview.Designer.cs b/TSOClient/tso.debug/Content/Preview/TexturePreview.Designer.cs index b7309609a..7d32fc235 100644 --- a/TSOClient/tso.debug/Content/Preview/TexturePreview.Designer.cs +++ b/TSOClient/tso.debug/Content/Preview/TexturePreview.Designer.cs @@ -43,8 +43,8 @@ private void InitializeComponent() // // TexturePreview // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.pictureBox1); this.Name = "TexturePreview"; this.Size = new System.Drawing.Size(444, 359); diff --git a/TSOClient/tso.debug/Content/Preview/VMRoutineInspector.Designer.cs b/TSOClient/tso.debug/Content/Preview/VMRoutineInspector.Designer.cs index c274ea745..783b848d8 100644 --- a/TSOClient/tso.debug/Content/Preview/VMRoutineInspector.Designer.cs +++ b/TSOClient/tso.debug/Content/Preview/VMRoutineInspector.Designer.cs @@ -42,8 +42,8 @@ private void InitializeComponent() // // VMRoutineInspector // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(548, 245); this.Controls.Add(this.display); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.SizableToolWindow; diff --git a/TSOClient/tso.debug/Controls/VMRoutineDisplay.Designer.cs b/TSOClient/tso.debug/Controls/VMRoutineDisplay.Designer.cs index e85d0a719..4fbaac19c 100644 --- a/TSOClient/tso.debug/Controls/VMRoutineDisplay.Designer.cs +++ b/TSOClient/tso.debug/Controls/VMRoutineDisplay.Designer.cs @@ -88,8 +88,8 @@ private void InitializeComponent() // // VMRoutineDisplay // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.Controls.Add(this.grid); this.Name = "VMRoutineDisplay"; this.Size = new System.Drawing.Size(569, 290); diff --git a/TSOClient/tso.debug/Controls/VMRoutineDisplay.cs b/TSOClient/tso.debug/Controls/VMRoutineDisplay.cs index fc1ee9b19..659a9f909 100644 --- a/TSOClient/tso.debug/Controls/VMRoutineDisplay.cs +++ b/TSOClient/tso.debug/Controls/VMRoutineDisplay.cs @@ -32,6 +32,8 @@ private void InvalidateRoutine() } private VMRoutine _Routine; + + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public VMRoutine Routine { get{ diff --git a/TSOClient/tso.debug/FSO.Debug.csproj b/TSOClient/tso.debug/FSO.Debug.csproj index db547e8ef..8cb7a6038 100644 --- a/TSOClient/tso.debug/FSO.Debug.csproj +++ b/TSOClient/tso.debug/FSO.Debug.csproj @@ -1,241 +1,28 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {43FBD6A2-4C4D-479C-A1A8-ED4CB591BDE4} + net9.0-windows + enable + disable WinExe Properties FSO.Debug FSO.Debug - v4.5 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + true + true + false + false + partial + true + true - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\Release\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - true - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - - - - - - - - - - Form - - - ContentBrowser.cs - - - - UserControl - - - TexturePreview.cs - - - Form - - - VMRoutineInspector.cs - - - UserControl - - - VMRoutineDisplay.cs - - - Form - - - Form1.cs - - - Form - - - ActionQueue.cs - - - - - ActionQueue.cs - - - ContentBrowser.cs - - - TexturePreview.cs - - - VMRoutineInspector.cs - Designer - - - VMRoutineDisplay.cs - Designer - - - Form1.cs - Designer - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - Simantics.cs - Designer - - - True - Resources.resx - True - - - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - Form - - - Simantics.cs - - - + - - {56f4bd87-2404-4263-80d5-6fa2161eb0a4} - TargaImage - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF} - FSO.SimAntics - - - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} - FSO.Vitaboy.Engine - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - + + + @@ -245,6 +32,7 @@ + PreserveNewest @@ -258,34 +46,15 @@ + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + + + + + + - - - \ No newline at end of file + + diff --git a/TSOClient/tso.debug/Form1.Designer.cs b/TSOClient/tso.debug/Form1.Designer.cs index 78c9b0b71..bb0a31348 100644 --- a/TSOClient/tso.debug/Form1.Designer.cs +++ b/TSOClient/tso.debug/Form1.Designer.cs @@ -53,8 +53,8 @@ private void InitializeComponent() // // Form1 // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(261, 185); this.Controls.Add(this.button2); this.Controls.Add(this.button1); diff --git a/TSOClient/tso.debug/Properties/AssemblyInfo.cs b/TSOClient/tso.debug/Properties/AssemblyInfo.cs deleted file mode 100644 index 2033ce1b4..000000000 --- a/TSOClient/tso.debug/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("tso.debug")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("tso.debug")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ce445f3c-5edd-44a7-99c8-36b3a0be2ef4")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.debug/Simantics.Designer.cs b/TSOClient/tso.debug/Simantics.Designer.cs index f592b34cb..d28997739 100644 --- a/TSOClient/tso.debug/Simantics.Designer.cs +++ b/TSOClient/tso.debug/Simantics.Designer.cs @@ -284,8 +284,8 @@ private void InitializeComponent() // // Simantics // - this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleDimensions = new SizeF(96F, 96F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.ClientSize = new System.Drawing.Size(363, 588); this.Controls.Add(this.pictureBox1); this.Controls.Add(this.textBox1); diff --git a/TSOClient/tso.debug/Vitaboy.Designer.cs b/TSOClient/tso.debug/Vitaboy.Designer.cs index 005006523..b995f1f73 100644 --- a/TSOClient/tso.debug/Vitaboy.Designer.cs +++ b/TSOClient/tso.debug/Vitaboy.Designer.cs @@ -1,4 +1,5 @@ -namespace FSO.Debug +/* +namespace FSO.Debug { partial class Vitaboy { @@ -37,7 +38,7 @@ private void InitializeComponent() this.outfitLoadBtn = new System.Windows.Forms.Button(); this.outfitList = new System.Windows.Forms.ListBox(); this.animationTab = new System.Windows.Forms.TabPage(); - this.canvas = new FSO.Common.Rendering.Framework.winforms.WinFormsGameWindow(); + //this.canvas = new FSO.Common.Rendering.Framework.winforms.WinFormsGameWindow(); this.animationLoadBtn = new System.Windows.Forms.Button(); this.animationsList = new System.Windows.Forms.ListBox(); this.tabControl1.SuspendLayout(); @@ -200,7 +201,7 @@ private void InitializeComponent() #endregion - private FSO.Common.Rendering.Framework.winforms.WinFormsGameWindow canvas; + //private FSO.Common.Rendering.Framework.winforms.WinFormsGameWindow canvas; private System.Windows.Forms.ToolStrip menu; private System.Windows.Forms.TabControl tabControl1; private System.Windows.Forms.TabPage avatarTab; @@ -213,4 +214,5 @@ private void InitializeComponent() private System.Windows.Forms.Button animationLoadBtn; private System.Windows.Forms.ListBox animationsList; } -} \ No newline at end of file +} +*/ \ No newline at end of file diff --git a/TSOClient/tso.debug/Vitaboy.cs b/TSOClient/tso.debug/Vitaboy.cs index 3a1b8b8c4..1a1cc11ab 100644 --- a/TSOClient/tso.debug/Vitaboy.cs +++ b/TSOClient/tso.debug/Vitaboy.cs @@ -1,4 +1,5 @@ -using System; +/* +using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; @@ -131,3 +132,4 @@ private void animationLoadBtn_Click(object sender, EventArgs e) } } } +*/ \ No newline at end of file diff --git a/TSOClient/tso.debug/app.config b/TSOClient/tso.debug/app.config deleted file mode 100644 index 47194a646..000000000 --- a/TSOClient/tso.debug/app.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/TSOClient/tso.debug/packages.config b/TSOClient/tso.debug/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/tso.debug/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/tso.files/FAR3/Decompresser.cs b/TSOClient/tso.files/FAR3/Decompresser.cs index 89f105618..af317e905 100644 --- a/TSOClient/tso.files/FAR3/Decompresser.cs +++ b/TSOClient/tso.files/FAR3/Decompresser.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.IO; +using System.Runtime.CompilerServices; namespace FSO.Files.FAR3 { @@ -33,45 +34,44 @@ public long CompressedSize /// Copies data from source to destination array.
/// The copy is byte by byte from srcPos to destPos and given length. ///
- /// The source array. - /// The source Position. - /// The destination array. - /// The destination Position. - /// The length. - private void ArrayCopy2(byte[] Src, int SrcPos, ref byte[] Dest, int DestPos, long Length) + /// The source array. + /// The source Position. + /// The destination array. + /// The destination Position. + /// The length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void ArrayCopy2(Span src, int srcPos, Span dest, int destPos, int length) { - if (Dest.Length < DestPos + Length) - { - byte[] DestExt = new byte[(int)(DestPos + Length)]; - Array.Copy(Dest, 0, DestExt, 0, Dest.Length); - Dest = DestExt; - } - - for (int i = 0; i < Length/* - 1*/; i++) - Dest[DestPos + i] = Src[SrcPos + i]; + src.Slice(srcPos, length).CopyTo(dest.Slice(destPos, length)); } /// /// Copies data from array at destPos-srcPos to array at destPos. /// /// The array. - /// The Position to copy from (reverse from end of array!) + /// The Position to copy from (reverse from end of array!) /// The Position to copy to. /// The length of data to copy. - private void OffsetCopy(ref byte[] array, int srcPos, int destPos, long length) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void OffsetCopy(Span array, int offset, int destPos, int length) { - srcPos = destPos - srcPos; + int srcPos = destPos - offset; - if (array.Length < destPos + length) + // This is a little complicated. + // If the length exceeds the offset, then we will start copying the data in a feedback loop. + // Normally memcpy could do this, but there's no c# equivalent. + + if (length > offset || length < 8) { - byte[] NewArray = new byte[(int)(destPos + length)]; - Array.Copy(array, 0, NewArray, 0, array.Length); - array = NewArray; + // This copy is also a bit faster if there's not much to copy. + for (int i = 0; i < length; i++) + { + array[destPos + i] = array[srcPos + i]; + } } - - for (int i = 0; i < length /*- 1*/; i++) + else { - array[destPos + i] = array[srcPos + i]; + array.Slice(srcPos, length).CopyTo(array.Slice(destPos, length)); } } @@ -111,6 +111,9 @@ public byte[] Compress(byte[] Data) int index = -1; bool end = false; + Span dataSpan = Data; + Span compressedSpan = cData; + // begin main compression loop while (index < Data.Length - 3) { @@ -200,7 +203,7 @@ public byte[] Compress(byte[] Data) cData[writeIndex++] = (byte)(0xE0 + copyCount); copyCount = 4 * copyCount + 4; - ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount); + ArrayCopy2(dataSpan, lastReadIndex, compressedSpan, writeIndex, copyCount); lastReadIndex += copyCount; writeIndex += copyCount; } @@ -231,7 +234,7 @@ public byte[] Compress(byte[] Data) } // do the offset copy - ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount); + ArrayCopy2(dataSpan, lastReadIndex, compressedSpan, writeIndex, copyCount); writeIndex += copyCount; lastReadIndex += copyCount; lastReadIndex += offsetCopyCount; @@ -251,14 +254,14 @@ public byte[] Compress(byte[] Data) cData[writeIndex++] = (byte)(0xE0 + copyCount); copyCount = 4 * copyCount + 4; - ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount); + ArrayCopy2(dataSpan, lastReadIndex, compressedSpan, writeIndex, copyCount); lastReadIndex += copyCount; writeIndex += copyCount; } copyCount = index - lastReadIndex; cData[writeIndex++] = (byte) (0xfc + copyCount); - ArrayCopy2(Data, lastReadIndex, ref cData, writeIndex, copyCount); + ArrayCopy2(dataSpan, lastReadIndex, compressedSpan, writeIndex, copyCount); writeIndex += copyCount; lastReadIndex += copyCount; @@ -295,116 +298,116 @@ public byte[] Compress(byte[] Data) /// An uncompressed array of bytes. public byte[] Decompress(byte[] Data) { - - MemoryStream MemData = new MemoryStream(Data); - BinaryReader Reader = new BinaryReader(MemData); - if (Data.Length > 6) { byte[] DecompressedData = new byte[(int)m_DecompressedSize]; + + Span dataSpan = Data; + Span decompressedSpan = DecompressedData; + int DataPos = 0; int Pos = 0; - long Control1 = 0; + byte Control1 = 0; - while (Control1 != 0xFC && Pos < Data.Length) + while (Control1 != 0xFC && Pos < dataSpan.Length) { - Control1 = Data[Pos]; + Control1 = dataSpan[Pos]; Pos++; - if (Pos == Data.Length) + if (Pos == dataSpan.Length) break; if (Control1 >= 0 && Control1 <= 127) { // 0x00 - 0x7F - long control2 = Data[Pos]; + byte control2 = dataSpan[Pos]; Pos++; - long numberOfPlainText = (Control1 & 0x03); - ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText); - DataPos += (int)numberOfPlainText; - Pos += (int)numberOfPlainText; + int numberOfPlainText = Control1 & 0x03; + ArrayCopy2(dataSpan, Pos, decompressedSpan, DataPos, numberOfPlainText); + DataPos += numberOfPlainText; + Pos += numberOfPlainText; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; - int offset = (int)(((Control1 & 0x60) << 3) + (control2) + 1); - long numberToCopyFromOffset = ((Control1 & 0x1C) >> 2) + 3; - OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset); - DataPos += (int)numberToCopyFromOffset; + int offset = (((Control1 & 0x60) << 3) + (control2) + 1); + int numberToCopyFromOffset = ((Control1 & 0x1C) >> 2) + 3; + OffsetCopy(decompressedSpan, offset, DataPos, numberToCopyFromOffset); + DataPos += numberToCopyFromOffset; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; } else if ((Control1 >= 128 && Control1 <= 191)) { // 0x80 - 0xBF - long control2 = Data[Pos]; + byte control2 = dataSpan[Pos]; Pos++; - long control3 = Data[Pos]; + byte control3 = dataSpan[Pos]; Pos++; - long numberOfPlainText = (control2 >> 6) & 0x03; - ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText); - DataPos += (int)numberOfPlainText; - Pos += (int)numberOfPlainText; + int numberOfPlainText = (control2 >> 6) & 0x03; + ArrayCopy2(dataSpan, Pos, decompressedSpan, DataPos, numberOfPlainText); + DataPos += numberOfPlainText; + Pos += numberOfPlainText; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; - int offset = (int)(((control2 & 0x3F) << 8) + (control3) + 1); - long numberToCopyFromOffset = (Control1 & 0x3F) + 4; - OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset); - DataPos += (int)numberToCopyFromOffset; + int offset = (((control2 & 0x3F) << 8) + (control3) + 1); + int numberToCopyFromOffset = (Control1 & 0x3F) + 4; + OffsetCopy(decompressedSpan, offset, DataPos, numberToCopyFromOffset); + DataPos += numberToCopyFromOffset; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; } else if (Control1 >= 192 && Control1 <= 223) { // 0xC0 - 0xDF - long numberOfPlainText = (Control1 & 0x03); - long control2 = Data[Pos]; + int numberOfPlainText = (Control1 & 0x03); + byte control2 = dataSpan[Pos]; Pos++; - long control3 = Data[Pos]; + byte control3 = dataSpan[Pos]; Pos++; - long control4 = Data[Pos]; + byte control4 = dataSpan[Pos]; Pos++; - ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText); - DataPos += (int)numberOfPlainText; - Pos += (int)numberOfPlainText; + ArrayCopy2(dataSpan, Pos, decompressedSpan, DataPos, numberOfPlainText); + DataPos += numberOfPlainText; + Pos += numberOfPlainText; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; - int offset = (int)(((Control1 & 0x10) << 12) + (control2 << 8) + (control3) + 1); - long numberToCopyFromOffset = ((Control1 & 0x0C) << 6) + (control4) + 5; - OffsetCopy(ref DecompressedData, offset, DataPos, numberToCopyFromOffset); - DataPos += (int)numberToCopyFromOffset; + int offset = (((Control1 & 0x10) << 12) + (control2 << 8) + (control3) + 1); + int numberToCopyFromOffset = ((Control1 & 0x0C) << 6) + (control4) + 5; + OffsetCopy(decompressedSpan, offset, DataPos, numberToCopyFromOffset); + DataPos += numberToCopyFromOffset; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; } else if (Control1 >= 224 && Control1 <= 251) { // 0xE0 - 0xFB - long numberOfPlainText = ((Control1 & 0x1F) << 2) + 4; - ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText); - DataPos += (int)numberOfPlainText; - Pos += (int)numberOfPlainText; + int numberOfPlainText = ((Control1 & 0x1F) << 2) + 4; + ArrayCopy2(dataSpan, Pos, decompressedSpan, DataPos, numberOfPlainText); + DataPos += numberOfPlainText; + Pos += numberOfPlainText; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; } else { - long numberOfPlainText = (Control1 & 0x03); - ArrayCopy2(Data, Pos, ref DecompressedData, DataPos, numberOfPlainText); + int numberOfPlainText = (Control1 & 0x03); + ArrayCopy2(dataSpan, Pos, decompressedSpan, DataPos, numberOfPlainText); - DataPos += (int)numberOfPlainText; - Pos += (int)numberOfPlainText; + DataPos += numberOfPlainText; + Pos += numberOfPlainText; - if (DataPos == (DecompressedData.Length)) + if (DataPos == (decompressedSpan.Length)) break; } } diff --git a/TSOClient/tso.files/FSO.Files.csproj b/TSOClient/tso.files/FSO.Files.csproj index 6c47203c6..91c3e3e3c 100644 --- a/TSOClient/tso.files/FSO.Files.csproj +++ b/TSOClient/tso.files/FSO.Files.csproj @@ -1,265 +1,43 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {18583453-A970-4AC5-83B1-2D6BFDF94C24} Library - Properties + net9.0 + enable + disable FSO.Files FSO.Files - v4.5 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - true - bin\x86\Debug\ - DEBUG;TRACE - true - full - x86 - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\Release\ - TRACE + true + true + true + full true - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - true - bin\Debug\ - DEBUG;TRACE - true - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset + + + True - - bin\Release\ - TRACE - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + True - - bin\ServerRelease\ - TRACE - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\bz2portable.1.0.1\lib\bz2portable.dll - - - ..\packages\deltaq.1.0.1\lib\deltaq.dll - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - ..\packages\xxHashSharp.1.0.0\lib\net45\xxHashSharp.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - + + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + + + + + + - - {d8232422-9d79-4200-a981-eb70ed82ccf3} - TargaImagePCL - - - {c42962a1-8796-4f47-9dcd-79ed5904d8ca} - FSO.Common - + + - - - \ No newline at end of file + diff --git a/TSOClient/tso.files/FSO/FSOUpdateMetadata.cs b/TSOClient/tso.files/FSO/FSOUpdateMetadata.cs new file mode 100644 index 000000000..091129026 --- /dev/null +++ b/TSOClient/tso.files/FSO/FSOUpdateMetadata.cs @@ -0,0 +1,140 @@ +namespace FSO.Files.FSO +{ + /// + /// Update file. + /// The SHA256 hash should be verified using the signature with the update channel's public key. + /// If the hash and size don't match, assume the update is invalid or tampered with. + /// The signature can be blank if the update channel doesn't have a key pair. + /// + public class FSOUpdateFile + { + public string zip { get; set; } + public string hash { get; set; } + public string signature { get; set; } + public int size { get; set; } + } + + /// + /// Update files by supported platform. + /// Unsupported platforms will have a null file. + /// + public class FSOUpdateCrossPlatformFile + { + public FSOUpdateFile windows { get; set; } + public FSOUpdateFile linux { get; set; } + public FSOUpdateFile mac { get; set; } + + public void SetPlatform(string target, FSOUpdateFile file) + { + switch (target) + { + case "windows": windows = file; break; + case "linux": linux = file; break; + case "mac": mac = file; break; + } + } + + public FSOUpdateFile CurrentPlatform() + { + if (OperatingSystem.IsMacOS()) + { + return mac; + } + else if (OperatingSystem.IsWindows()) + { + return windows; + } + else + { + return linux; + } + } + } + + /// + /// Metadata for an update within a channel. + /// + public class FSOUpdateMetadata + { + public string id { get; set; } + public string lastid { get; set; } // (nullable) + public uint date { get; set; } + public FSOUpdateCrossPlatformFile server { get; set; } + public FSOUpdateCrossPlatformFile full { get; set; } + public FSOUpdateCrossPlatformFile delta { get; set; } + public string changelog { get; set; } + + public FSOUpdateMetadata Clone() + { + return new FSOUpdateMetadata() { + id = id, + lastid = lastid, + date = date, + server = server, + full = full, + delta = delta, + changelog = changelog + }; + } + } + + /// + /// Metadata for an update that's by itself instead of part of a listing. + /// The manifests directly in github have this format. (it's removed when building the full version listing) + /// + public class FSOUpdateMetadataStandalone : FSOUpdateMetadata + { + public string channel { get; set; } + public string publicKey { get; set; } + } + + /// + /// Update channel, containing a list of updates for the channel from newest first. + /// + public class FSOUpdateChannel + { + public string channel { get; set; } + public string publicKey { get; set; } + public FSOUpdateMetadata[] updates { get; set; } = []; + } + + /// + /// Remesh file. + /// + public class FSORemeshFile + { + public string url { get; set; } + public string hash { get; set; } + public string signature { get; set; } + public int size { get; set; } + } + + /// + /// Remesh channel, containing the latest remesh package for the channel. + /// The client will automatically download remesh updates from the active channel if the public key matches the client. + /// + public class FSORemeshChannel + { + public string channel { get; set; } + public string publicKey { get; set; } + public int version { get; set; } + + // Credits metadata + public string name { get; set; } + public string description { get; set; } + public string url { get; set; } + + public FSORemeshFile dxt { get; set; } + public FSORemeshFile png { get; set; } + } + + /// + /// Update API response containing multiple update channels. + /// + public class FSOUpdateResponse + { + public FSOUpdateChannel[] channels { get; set; } = []; + public FSORemeshChannel[] remeshes { get; set; } = []; + public string autoRemeshChannel { get; set; } = null; + } +} diff --git a/TSOClient/tso.files/Formats/CabFile.cs b/TSOClient/tso.files/Formats/CabFile.cs new file mode 100644 index 000000000..9c90ab460 --- /dev/null +++ b/TSOClient/tso.files/Formats/CabFile.cs @@ -0,0 +1,285 @@ +using FSO.Files.Utils; +using ICSharpCode.SharpZipLib.Zip.Compression; +using System.IO.Compression; + +namespace FSO.Files.Formats +{ + public enum CabFlags : ushort + { + HasPrevious = 1, + HasNext = 2, + HasReserve = 4, + } + + public class CabFileEntry + { + public uint Size; + public uint Offset; + public ushort FolderID; + public ushort Unknown1; + public uint Unknown2; + public string Filename; + } + + public class CabFolderEntry + { + public uint BlockOffset; + public ushort BlockCount; + public ushort CompressionType; + public string FolderReserve; + + public CabBlock[] Blocks; + } + + public class CabBlock + { + public ushort CompressedSize; + public ushort UncompressedSize; + public byte[] CompressedData; + + public byte[] Decompress() + { + // First two bytes are 0x43 0x4B for MSZip + + using var inputStream = new MemoryStream([..CompressedData]); + using var outputStream = new MemoryStream(UncompressedSize); + + inputStream.Position = 2; + + using var decompressor = new DeflateStream(inputStream, CompressionMode.Decompress); + decompressor.CopyTo(outputStream); + + return outputStream.ToArray(); + } + } + + public class CabBlockDecompressor + { + private readonly MemoryStream InputStream; + private readonly MemoryStream OutputStream; + private readonly Inflater Inflater; + + private int HeaderBytesRead; + + public CabBlockDecompressor() + { + InputStream = new MemoryStream(); + OutputStream = new MemoryStream(); + + Inflater = new Inflater(true); + } + + public bool AddBlock(CabBlock block) + { + var basePos = InputStream.Position; + var size = block.UncompressedSize; + bool hasMore = size == 0; + + // If continuing the mszip, we don't need to skip the input data. + InputStream.Write(block.CompressedData.AsSpan(Math.Min(2 - HeaderBytesRead, block.CompressedSize))); + + HeaderBytesRead = Math.Min(2, HeaderBytesRead + block.CompressedSize); + + if (!hasMore) + { + Inflater.SetInput(InputStream.ToArray()); + + var result = new byte[size]; + Inflater.Inflate(result); + Inflater.Reset(); + + InputStream.SetLength(0); + InputStream.Position = 0; + + OutputStream.Write(result); + + HeaderBytesRead = 0; + } + + return hasMore; + } + + public bool AddBlocks(CabBlock[] blocks) + { + bool hasMore = false; + foreach (var block in blocks) + { + hasMore = AddBlock(block); + } + + return hasMore; + } + + public byte[] GetData(int offset, int size) + { + OutputStream.Position = offset; + + var result = new byte[size]; + OutputStream.Read(result, 0, size); + + OutputStream.Seek(0, SeekOrigin.End); + + return result; + } + + public byte[] ToArray() + { + return OutputStream.ToArray(); + } + } + + public class CabFile + { + public uint Size; + public uint OffsetFiles; + public byte MajorVersion; + public byte MinorVersion; + public ushort FolderCount; + public ushort FileCount; + public CabFlags Flags; + public ushort SetID; + public ushort ICabinet; + + // Reserve options + private ushort CabinetResBytes; + private byte FolderResBytes; + private byte DataResBytes; + + public string CabReserve; + + // Prev options + public string PrevCabName; + public string PrevCabDisk; + + // Next options + public string NextCabName; + public string NextCabDisk; + + public CabFileEntry[] Files; + public CabFolderEntry[] Folders; + + public CabFile(string filepath, bool withBlocks = true) + { + using (var stream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + this.Read(stream, withBlocks); + } + } + + public void Read(Stream stream, bool withBlocks) + { + using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + var magic = io.ReadUInt32(); + var reserved1 = io.ReadInt32(); + Size = io.ReadUInt32(); + var reserved2 = io.ReadInt32(); + OffsetFiles = io.ReadUInt32(); + var reserved3 = io.ReadInt32(); + MajorVersion = io.ReadByte(); + MinorVersion = io.ReadByte(); + + FolderCount = io.ReadUInt16(); + FileCount = io.ReadUInt16(); + Flags = (CabFlags)io.ReadUInt16(); + SetID = io.ReadUInt16(); + ICabinet = io.ReadUInt16(); + + if (Flags.HasFlag(CabFlags.HasReserve)) + { + CabinetResBytes = io.ReadUInt16(); + FolderResBytes = io.ReadByte(); + DataResBytes = io.ReadByte(); + + CabReserve = io.ReadCString(CabinetResBytes, true); + } + + if (Flags.HasFlag(CabFlags.HasPrevious)) + { + PrevCabName = io.ReadNullTerminatedString(); + PrevCabDisk = io.ReadNullTerminatedString(); + } + + if (Flags.HasFlag(CabFlags.HasNext)) + { + NextCabName = io.ReadNullTerminatedString(); + NextCabDisk = io.ReadNullTerminatedString(); + } + + var folders = new CabFolderEntry[FolderCount]; + + for (int i = 0; i < folders.Length; i++) + { + folders[i] = ReadFolder(io); + } + + var files = new CabFileEntry[FileCount]; + + for (int i = 0; i < files.Length; i++) + { + files[i] = ReadFile(io); + } + + if (withBlocks) + { + // Jump around and read the blocks for all the folders. + // We could read these only when needed, but it's honestly easier this way and the user needs a lot of RAM to run the game anyways. + + for (int i = 0; i < folders.Length; i++) + { + var folder = folders[i]; + var blocks = new CabBlock[folder.BlockCount]; + + io.Seek(SeekOrigin.Begin, folder.BlockOffset); + + for (int j = 0; j < blocks.Length; j++) + { + blocks[j] = ReadBlock(io); + } + + folder.Blocks = blocks; + } + } + + Folders = folders; + Files = files; + } + } + + private CabBlock ReadBlock(IoBuffer io) + { + io.ReadInt32(); // Checksum + var compressedSize = io.ReadUInt16(); + return new CabBlock() + { + CompressedSize = compressedSize, + UncompressedSize = io.ReadUInt16(), + CompressedData = io.ReadBytes(compressedSize) + }; + } + + private CabFolderEntry ReadFolder(IoBuffer io) + { + return new CabFolderEntry() + { + BlockOffset = io.ReadUInt32(), + BlockCount = io.ReadUInt16(), + CompressionType = io.ReadUInt16(), + FolderReserve = Flags.HasFlag(CabFlags.HasReserve) ? io.ReadCString(FolderResBytes, true) : null + }; + } + + private CabFileEntry ReadFile(IoBuffer io) + { + return new CabFileEntry() + { + Size = io.ReadUInt32(), + Offset = io.ReadUInt32(), + FolderID = io.ReadUInt16(), + Unknown1 = io.ReadUInt16(), + Unknown2 = io.ReadUInt32(), + Filename = io.ReadNullTerminatedString().Replace('\\', '/') + }; + } + } +} diff --git a/TSOClient/tso.files/Formats/DBPF/DBPFEntry.cs b/TSOClient/tso.files/Formats/DBPF/DBPFEntry.cs index 90d0d8d2d..10d0b8465 100644 --- a/TSOClient/tso.files/Formats/DBPF/DBPFEntry.cs +++ b/TSOClient/tso.files/Formats/DBPF/DBPFEntry.cs @@ -19,7 +19,9 @@ public enum DBPFGroupID : uint HitLabTestSamples = 0x1d8a8b4f, HitLabTest = 0xbd6e5937, EP2 = 0xdde8f5c6, - EP5Samps = 0x8a6fcc30 + EP5Samps = 0x8a6fcc30, + + RemeshPackage = 0xF500_0001, } /// @@ -33,7 +35,14 @@ public enum DBPFTypeID : uint MP3 = 0x3cec2b47, TRK = 0x5D73A611, HIT = 0x7b1acfcd, - SoundFX = 0x2026960b, + SoundFX = 0x2026960b, + + // FreeSO specific + FSOM = 0xF500_0001, + MTEX = 0xF500_0002, + MTX2 = 0xF500_0003, + FSO3DDirectory = 0xF500_0004, + FSO3DCredits = 0xF500_0005, } /// @@ -56,5 +65,8 @@ public class DBPFEntry //A 4-byte unsigned integer specifying the size of the entry's data public uint FileSize; + + //Literal data for a DBPF entry inserted at runtime + public byte[] Data; } } diff --git a/TSOClient/tso.files/Formats/DBPF/DBPFFile.cs b/TSOClient/tso.files/Formats/DBPF/DBPFFile.cs index a018e9d4e..15eadf8ed 100644 --- a/TSOClient/tso.files/Formats/DBPF/DBPFFile.cs +++ b/TSOClient/tso.files/Formats/DBPF/DBPFFile.cs @@ -19,10 +19,11 @@ public class DBPFFile : IDisposable private uint NumEntries; private IoBuffer m_Reader; - private List m_EntriesList = new List(); - private Dictionary m_EntryByID = new Dictionary(); - private Dictionary> m_EntriesByType = new Dictionary>(); + private List m_EntriesList = []; + private Dictionary m_EntryByID = []; + private Dictionary> m_EntriesByType = []; + private Stream Stream; private IoBuffer Io; /// @@ -30,16 +31,17 @@ public class DBPFFile : IDisposable /// public DBPFFile() { + DateCreated = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } /// /// Creates a DBPF instance from a path. /// /// The path to an DBPF archive. - public DBPFFile(string file) + public DBPFFile(string file) : this() { - var stream = File.OpenRead(file); - Read(stream); + Stream = File.OpenRead(file); + Read(Stream); } /// @@ -48,11 +50,9 @@ public DBPFFile(string file) /// The stream to read from. public void Read(Stream stream) { - m_EntryByID = new Dictionary(); - m_EntriesList = new List(); - var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN); m_Reader = io; + this.Stream = stream; this.Io = io; var magic = io.ReadCString(4); @@ -93,6 +93,11 @@ public void Read(Stream stream) var trashIndexOffset = io.ReadUInt32(); var trashIndexSize = io.ReadUInt32(); var indexMinor = io.ReadUInt32(); + + if (trashEntryCount != 0) + { + + } } else if (version == 2.0) { @@ -126,6 +131,76 @@ public void Read(Stream stream) } } + public void Write(Stream stream) + { + var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN); + io.WriteCString("DBPF", 4); + + io.WriteUInt32(1); // major version + io.WriteUInt32(0); // minor version + + io.Skip(12); + + io.WriteInt32(DateCreated); + this.DateModified = (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + io.WriteInt32(DateModified); + + io.WriteUInt32(7); // index major version + + io.WriteUInt32((uint)m_EntriesList.Count); + + var indexOffsetMark = stream.Position; + io.WriteUInt32(0); // placeholder index offset and size, calculated after the file data is inserted + io.WriteUInt32(0); + + io.WriteUInt32(0); // trashEntryCount + io.WriteUInt32(0); // trashIndexOffset + io.WriteUInt32(0); // trashIndexSize + io.WriteUInt32(0); // indexMinor + + io.Skip(32); + + var newEntries = new DBPFEntry[m_EntriesList.Count]; + int i = 0; + + // Insert entry data here. + foreach (var entry in m_EntriesList) + { + var data = GetEntry(entry); + + newEntries[i++] = new DBPFEntry() + { + FileOffset = (uint)stream.Position, + FileSize = (uint)data.Length, + GroupID = entry.GroupID, + InstanceID = entry.InstanceID, + TypeID = entry.TypeID + }; + + io.WriteBytes(data); + int skip = (4 - (data.Length % 4)) % 4; + + io.Skip(skip); + } + + // After all the entry data, insert the index, then go back and rewrite the index offset and size to be correct. + + var indexStart = stream.Position; + foreach (var entry in newEntries) + { + io.WriteUInt32((uint)entry.TypeID); + io.WriteUInt32((uint)entry.GroupID); + io.WriteUInt32(entry.InstanceID); + io.WriteUInt32(entry.FileOffset); + io.WriteUInt32(entry.FileSize); + } + var indexEnd = stream.Position; + + stream.Seek(indexOffsetMark, SeekOrigin.Begin); + io.WriteUInt32((uint)indexStart); + io.WriteUInt32((uint)(indexEnd - indexStart)); + } + /// /// Gets a DBPFEntry's data from this DBPF instance. /// @@ -133,6 +208,11 @@ public void Read(Stream stream) /// Data for entry. public byte[] GetEntry(DBPFEntry entry) { + if (entry.Data != null) + { + return entry.Data; + } + m_Reader.Seek(SeekOrigin.Begin, entry.FileOffset); return m_Reader.ReadBytes((int)entry.FileSize); @@ -151,6 +231,17 @@ public byte[] GetItemByID(ulong ID) return null; } + /// + /// Gets an entry from its ID (TypeID + FileID). + /// + /// The type of the entry. + /// The file ID of the entry. + /// The entry's data. + public byte[] GetItemByID(DBPFTypeID type, uint fileId) + { + return GetItemByID(((ulong)fileId << 32) | (ulong)type); + } + /// /// Gets all entries of a specific type. /// @@ -161,14 +252,49 @@ public List> GetItemsByType(DBPFTypeID Type) var result = new List>(); - var entries = m_EntriesByType[Type]; - for (int i = 0; i < entries.Count; i++) + if (m_EntriesByType.TryGetValue(Type, out var entries)) { - result.Add(new KeyValuePair(entries[i].InstanceID, GetEntry(entries[i]))); + for (int i = 0; i < entries.Count; i++) + { + result.Add(new KeyValuePair(entries[i].InstanceID, GetEntry(entries[i]))); + } } + return result; } + public void AddOrReplace(ulong id, DBPFGroupID groupId, byte[] data) + { + if (!m_EntryByID.TryGetValue(id, out DBPFEntry entry)) + { + entry = new DBPFEntry() + { + InstanceID = (uint)(id >> 32), + TypeID = (DBPFTypeID)(uint)id, + }; + + m_EntryByID[id] = entry; + m_EntriesList.Add(entry); + + if (!m_EntriesByType.TryGetValue(entry.TypeID, out var entries)) + { + entries = []; + m_EntriesByType[entry.TypeID] = entries; + } + + NumEntries++; + entries.Add(entry); + } + + entry.GroupID = groupId; + entry.Data = data; + } + + public void AddOrReplace(uint id, DBPFTypeID type, DBPFGroupID groupId, byte[] data) + { + AddOrReplace(((ulong)id << 32) | (ulong)(type), groupId, data); + } + #region IDisposable Members /// @@ -176,7 +302,8 @@ public List> GetItemsByType(DBPFTypeID Type) /// public void Dispose() { - Io.Dispose(); + Io?.Dispose(); + Stream?.Dispose(); } #endregion diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/FSOM.cs b/TSOClient/tso.files/Formats/IFF/Chunks/FSOM.cs index da0b3dfd2..d3180c2e0 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/FSOM.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/FSOM.cs @@ -39,10 +39,10 @@ public override bool Write(IffFile iff, Stream stream) public DGRP3DMesh Get(DGRP dgrp, GraphicsDevice device) { - if (Cached == null) { - using (var stream = new MemoryStream(data)) { - Cached = new DGRP3DMesh(dgrp, stream, device); - } + if (Cached == null) + { + var stream = new MemoryStream(data); + Cached = new DGRP3DMesh(dgrp, stream, device); } data = null; return Cached; diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/MTEX.cs b/TSOClient/tso.files/Formats/IFF/Chunks/MTEX.cs index 513f0958f..7d95d13b0 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/MTEX.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/MTEX.cs @@ -1,19 +1,41 @@ using FSO.Common; using FSO.Common.Utils; +using FSO.Files.RC; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using System; using System.IO; +using System.Threading; namespace FSO.Files.Formats.IFF.Chunks { /// /// Texture for a 3D Mesh. Can be jpg, png or bmp. /// - public class MTEX : IffChunk + public class MTEX : IffChunk, IDGRP3DTextureHolder { private byte[] data; + private Stream stream; + + private bool HasDecoded = false; + + private Func Producer; + + private TextureData[] Decoded; + private Point Size; + private Texture2D Cached; + public MTEX() + { + + } + + public MTEX(Stream stream) + { + this.stream = stream; + } + /// /// Reads a BMP chunk from a stream. /// @@ -31,29 +53,111 @@ public override bool Write(IffFile iff, Stream stream) return true; } + private int DecodingState; + + public void Decode(GraphicsDevice gd) + { + var exch = Interlocked.CompareExchange(ref DecodingState, 1, 0); + if (exch > 0) + { + // Can't decode more than once. + SpinWait wait = default; + while (exch == 1) + { + wait.SpinOnce(); + exch = Volatile.Read(ref DecodingState); + } + + return; + } + + if (data != null) + { + stream = new MemoryStream(data); + } + + var image = ImageLoader.DataFromStream(gd, stream); + + if (image == null) + { + throw new InvalidDataException("Invalid MTEX image!"); + } + + if (image.Value.Producer != null) + { + Producer = image.Value.Producer; + } + else if (image.Value.Data != null) + { + var data = image.Value.Data.Value; + Size = new Point(data.Width, data.Height); + + if (FSOEnvironment.EnableNPOTMip) + { + Decoded = TextureUtils.GenerateMips(data.Width, data.Height, TextureUtils.CalculateMipCount(data.Width, data.Height), data.Data); + } + else + { + Decoded = new TextureData[] { new TextureData(0, data.Data) }; + } + } + + if (!IffFile.RETAIN_CHUNK_DATA) + { + data = null; + } + + stream.Dispose(); + stream = null; + + Interlocked.Exchange(ref DecodingState, 2); + HasDecoded = true; + } + public Texture2D GetTexture(GraphicsDevice device) { if (Cached == null) { - Cached = ImageLoader.FromStream(device, new MemoryStream(data)); - if (FSOEnvironment.EnableNPOTMip) + if (!HasDecoded) + { + Decode(device); + } + + if (Producer != null) + { + Cached = Producer(); + Producer = null; + if (FSOEnvironment.EnableNPOTMip) + { + var data = new Color[Cached.Width * Cached.Height]; + Cached.GetData(data); + var n = new Texture2D(device, Cached.Width, Cached.Height, true, SurfaceFormat.Color); + Cached.Dispose(); + Cached = n; + + AssetStreaming.LoadTexture(n, AssetStreamingMode.Lot, () => + { + return TextureUtils.GenerateMips(n, data); + }); + } + } + else if (Decoded != null) { - var data = new Color[Cached.Width * Cached.Height]; - Cached.GetData(data); - var n = new Texture2D(device, Cached.Width, Cached.Height, true, SurfaceFormat.Color); - TextureUtils.UploadWithMips(n, device, data); - Cached.Dispose(); - Cached = n; + Cached = new Texture2D(device, Size.X, Size.Y, FSOEnvironment.EnableNPOTMip, SurfaceFormat.Color); + TextureUtils.UploadTexData(Cached, Decoded); + Decoded = null; } } - if (!IffFile.RETAIN_CHUNK_DATA) data = null; + return Cached; } public void SetData(byte[] data) { this.data = data; + HasDecoded = false; Cached = null; + Decoded = null; } } } diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/MTX2.cs b/TSOClient/tso.files/Formats/IFF/Chunks/MTX2.cs new file mode 100644 index 000000000..bd3bbd372 --- /dev/null +++ b/TSOClient/tso.files/Formats/IFF/Chunks/MTX2.cs @@ -0,0 +1,259 @@ +using FSO.Common; +using FSO.Common.Rendering; +using FSO.Common.Serialization; +using FSO.Common.Utils; +using FSO.Files.RC; +using FSO.Files.Utils; +using ICSharpCode.SharpZipLib.GZip; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.IO.Compression; +using System.Runtime.InteropServices; + +namespace FSO.Files.Formats.IFF.Chunks +{ + public enum MTX2Format : byte + { + RGBA = 0, + DXT1 = 1, + DXT5 = 2 + } + + public enum MTX2CompressionType : byte + { + None = 0, + GZip = 1, + } + + public class MTX2 : IffChunk, IDGRP3DTextureHolder + { + public const int CURRENT_VERSION = 1; + + private byte[] Data; + + public int Version = CURRENT_VERSION; + public int Width; + public int Height; + public MTX2Format Format; + public MTX2CompressionType Compression; + public int[] LevelOffsets; + + private byte[] Decoded; + private Texture2D Cached; + + private bool HasDecoded = false; + + public MTX2() + { + + } + + public override void Read(IffFile iff, Stream stream) + { + using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + var mtx2 = io.ReadCString(4); + if (mtx2 != "MTX2") throw new Exception("Invalid MTX2!"); + Version = io.ReadInt32(); + Width = io.ReadInt32(); + Height = io.ReadInt32(); + Format = (MTX2Format)io.ReadByte(); + Compression = (MTX2CompressionType)io.ReadByte(); + var levelCount = io.ReadInt32(); + LevelOffsets = new int[levelCount]; + for (int i = 0; i < levelCount; i++) + { + LevelOffsets[i] = io.ReadInt32(); + } + int dataLength = io.ReadInt32(); + Data = io.ReadBytes(dataLength); + } + } + + public override bool Write(IffFile iff, Stream stream) + { + using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + io.WriteCString("MTX2", 4); + io.WriteInt32(Version); + io.WriteInt32(Width); + io.WriteInt32(Height); + io.WriteByte((byte)Format); + io.WriteByte((byte)Compression); + io.WriteInt32(LevelOffsets.Length); + for (int i = 0; i < LevelOffsets.Length; i++) + { + io.WriteInt32(LevelOffsets[i]); + } + + if (Data == null) + { + // Encode data + Data = Compress(Decoded); + } + + io.WriteInt32(Data.Length); + io.WriteBytes(Data); + } + return true; + } + + private byte[] Compress(byte[] data) + { + switch (Compression) + { + case MTX2CompressionType.None: + return data; + case MTX2CompressionType.GZip: + using (var compressed = new MemoryStream()) + using (var cStream = new GZipStream(compressed, CompressionMode.Compress)) + using (var srcStream = new MemoryStream(data)) + { + srcStream.CopyTo(cStream); + + cStream.Close(); + + return compressed.ToArray(); + } + default: + throw new NotSupportedException($"Unknown MTX2 compression type {Compression}"); + } + } + + private byte[] Decompress(byte[] data) + { + switch (Compression) + { + case MTX2CompressionType.None: + return data; + case MTX2CompressionType.GZip: + using (var compressed = new MemoryStream(data)) + using (var cStream = new GZipStream(compressed, CompressionMode.Decompress)) + using (var dstStream = new MemoryStream()) + { + cStream.CopyTo(dstStream); + + return dstStream.ToArray(); + } + default: + throw new NotSupportedException($"Unknown MTX2 compression type {Compression}"); + } + } + + private int DecodingState; + + public void Decode(GraphicsDevice gd) + { + var exch = Interlocked.CompareExchange(ref DecodingState, 1, 0); + if (exch > 0) + { + // Can't decode more than once. + SpinWait wait = default; + while (exch == 1) + { + wait.SpinOnce(); + exch = Volatile.Read(ref DecodingState); + } + + return; + } + + Decoded = Decompress(Data); + + if (!IffFile.RETAIN_CHUNK_DATA) + { + Data = null; + } + + Interlocked.Exchange(ref DecodingState, 2); + HasDecoded = true; + } + + private SurfaceFormat GetSurfaceFormat() + { + return Format switch + { + MTX2Format.RGBA => SurfaceFormat.Color, + MTX2Format.DXT1 => SurfaceFormat.Dxt1, + MTX2Format.DXT5 => SurfaceFormat.Dxt5, + _ => throw new NotSupportedException($"Unknown MTX2 format {Format}") + }; + } + + private TextureData[] GetTextureData(int multiplier = 1) where T : unmanaged + { + return [.. LevelOffsets.Select((x, index) => + { + var decoded = Decoded.AsSpan(); + return new TextureData( + index, + MemoryMarshal.Cast( + decoded[x..(index == LevelOffsets.Length - 1 ? decoded.Length : LevelOffsets[index + 1])] + ).ToArray(), + multiplier); + })]; + } + + public Texture2D GetTexture(GraphicsDevice gd) + { + if (Cached == null) + { + if (!HasDecoded) + { + Decode(gd); + } + + if (Decoded != null) + { + bool compressed = Format != MTX2Format.RGBA; + + int alignedWidth = compressed ? TextureUtils.AlignUp(Width, 4) : Width; + int alignedHeight = compressed ? TextureUtils.AlignUp(Height, 4) : Height; + + Cached = new Texture2D(gd, alignedWidth, alignedHeight, LevelOffsets.Length > 1, GetSurfaceFormat()); + + if (Format == MTX2Format.RGBA) + { + TextureUtils.UploadTexData(Cached, GetTextureData()); + } + else + { + TextureUtils.UploadTexData(Cached, GetTextureData(Format == MTX2Format.DXT1 ? 1 : 1)); + } + + if (alignedWidth != Width || alignedHeight != Height) + { + Cached.Tag = new TextureInfo(Cached, Width, Height); + } + + Decoded = null; + } + } + + return Cached; + } + + public void SetData(MTX2Format format, TextureData[] data) + { + Format = format; + + var size = data.Sum(level => level.Data.Length); + + var result = new byte[size]; + LevelOffsets = new int[data.Length]; + int offset = 0; + + for (int i = 0; i < data.Length; i++) + { + var item = data[i].Data; + + LevelOffsets[i] = offset; + item.AsSpan().CopyTo(result.AsSpan(offset, item.Length)); + + offset += item.Length; + } + + Decoded = result; + } + } +} diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/OBJD.cs b/TSOClient/tso.files/Formats/IFF/Chunks/OBJD.cs index fcc2f8f13..789ed6e0c 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/OBJD.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/OBJD.cs @@ -517,29 +517,43 @@ public override void Read(IffFile iff, Stream stream) this.RatingBladder = io.ReadInt16(); this.RatingEnergy = io.ReadInt16(); this.RatingFun = io.ReadInt16(); - this.RatingRoom = io.ReadInt16(); + this.RatingRoom = io.ReadInt16(); // field 86 this.RatingSkillFlags = io.ReadUInt16(); if (numFields > 90) { - this.NumTypeAttributes = io.ReadUInt16(); - this.MiscFlags = io.ReadUInt16(); - this.TypeAttrGUID = io.ReadUInt32(); - try - { - this.FunctionSubsort = io.ReadUInt16(); - this.DTSubsort = io.ReadUInt16(); - this.KeepBuying = io.ReadUInt16(); - this.VacationSubsort = io.ReadUInt16(); - this.ResetLotAction = io.ReadUInt16(); - this.CommunitySubsort = io.ReadUInt16(); - this.DreamFlags = io.ReadUInt16(); - this.RenderFlags = io.ReadUInt16(); - this.VitaboyFlags = io.ReadUInt16(); - this.STSubsort = io.ReadUInt16(); - this.MTSubsort = io.ReadUInt16(); - } catch (Exception) + this.NumTypeAttributes = io.ReadUInt16(); // field 88 + this.MiscFlags = io.ReadUInt16(); // field 89 + this.TypeAttrGUID = io.ReadUInt32(); // field 90, 91 + + if (numFields > 92) { - //past this point if these fields are here is really a mystery + try + { + this.FunctionSubsort = io.ReadUInt16(); + if (numFields > 93) + { + this.DTSubsort = io.ReadUInt16(); + if (numFields > 94) + { + this.KeepBuying = io.ReadUInt16(); + if (numFields > 95) + { + this.VacationSubsort = io.ReadUInt16(); // fails with numFields 95 + this.ResetLotAction = io.ReadUInt16(); + this.CommunitySubsort = io.ReadUInt16(); + this.DreamFlags = io.ReadUInt16(); + this.RenderFlags = io.ReadUInt16(); + this.VitaboyFlags = io.ReadUInt16(); + this.STSubsort = io.ReadUInt16(); + this.MTSubsort = io.ReadUInt16(); + } + } + } + } + catch (Exception) + { + //past this point if these fields are here is really a mystery + } } } if (this.TypeAttrGUID == 0) this.TypeAttrGUID = GUID; diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/SPR.cs b/TSOClient/tso.files/Formats/IFF/Chunks/SPR.cs index b7a5d7af6..7e4309775 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/SPR.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/SPR.cs @@ -1,12 +1,13 @@ -using System; -using System.Collections.Generic; -using System.IO; +using FSO.Common; +using FSO.Common.Rendering; +using FSO.Common.Utils; using FSO.Files.Utils; -using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; -using FSO.Common.Utils; -using FSO.Common; -using FSO.Common.Rendering; +using Microsoft.Xna.Framework.Graphics; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; namespace FSO.Files.Formats.IFF.Chunks { @@ -94,6 +95,8 @@ public class SPRFrame : ITextureProvider private Texture2D ZCache; private byte[] ToDecode; + private PALT Palette; + /// /// Constructs a new SPRFrame instance. /// @@ -101,8 +104,30 @@ public class SPRFrame : ITextureProvider public SPRFrame(SPR parent) { this.Parent = parent; + + UpdatePalette(); + } + + private void UpdatePalette() + { + Palette = Parent.ChunkParent.Get(Parent.PaletteID); + if (Palette == null) + { + Palette = DEFAULT_PALT; + } } + private PALT EnsurePalette() + { + if (Palette == null) + { + UpdatePalette(); + } + + return Palette; + } + + /// /// Reads a SPRFrame from a stream. /// @@ -117,36 +142,63 @@ public void Read(uint version, IoBuffer io, uint guessedSize) var size = io.ReadUInt32(); this.Version = spriteFersion; + ReadHead(io); + if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io); - else ToDecode = io.ReadBytes(size); + else ToDecode = io.ReadBytes(size - 8); } else { + ReadHead(io); + this.Version = version; if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io); - else ToDecode = io.ReadBytes(guessedSize); + else ToDecode = io.ReadBytes(guessedSize - 8); } } - public void ReadDeferred(uint version, IoBuffer io) + private void ReadHead(IoBuffer io) { + // Useful to read these early for async loading var reserved = io.ReadUInt32(); - var height = io.ReadUInt16(); - var width = io.ReadUInt16(); - this.Init(width, height); + Height = io.ReadUInt16(); + Width = io.ReadUInt16(); + } + + public void ReadDeferred(uint version, IoBuffer io) + { + this.Init(); this.Decode(io); } + private int _decoding = 0; + public void DecodeIfRequired() { if (ToDecode != null) { - using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), Parent.ByteOrd)) + if (Interlocked.CompareExchange(ref _decoding, 1, 0) > 0) { - ReadDeferred(Version, buf); + // If another thread is already decoding the sprite, spin until it's done. + SpinWait w = default; + while (Volatile.Read(ref _decoding) > 0) + { + w.SpinOnce(); + } + + DecodeIfRequired(); } + else + { + using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), Parent.ByteOrd)) + { + ReadDeferred(Version, buf); + } - ToDecode = null; + ToDecode = null; + + Interlocked.Exchange(ref _decoding, 0); + } } } @@ -156,12 +208,7 @@ public void DecodeIfRequired() /// IOBuffer used to read a SPRFrame. private void Decode(IoBuffer io) { - var palette = Parent.ChunkParent.Get(Parent.PaletteID); - if (palette == null) - { - palette = DEFAULT_PALT; - } - + var palette = EnsurePalette(); var y = 0; var endmarker = false; @@ -238,10 +285,8 @@ private void Decode(IoBuffer io) public int Width { get; internal set; } public int Height { get; internal set; } - protected void Init(int width, int height) + protected void Init() { - this.Width = width; - this.Height = height; Data = new Color[Width * Height]; } @@ -257,7 +302,6 @@ public void SetPixel(int x, int y, Color color) public Texture2D GetTexture(GraphicsDevice device) { - DecodeIfRequired(); if (PixelCache == null) { var mip = !Parent.WallStyle && FSOEnvironment.Enable3D && FSOEnvironment.EnableNPOTMip; @@ -271,18 +315,50 @@ public Texture2D GetTexture(GraphicsDevice device) if (tc) { PixelCache = new Texture2D(device, ((w+3)/4)*4, ((h+3)/4)*4, mip, SurfaceFormat.Dxt5); - if (mip) - TextureUtils.UploadDXT5WithMips(PixelCache, w, h, device, Data); - else - PixelCache.SetData(TextureUtils.DXT5Compress(Data, w, h).Item1); + + AssetStreaming.LoadTexture(PixelCache, AssetStreamingMode.Lot, () => + { + DecodeIfRequired(); + + TextureData[] data; + if (mip) + data = TextureUtils.GenerateDXT5WithMips(PixelCache, w, h, Data); + else + { + data = new TextureData[] + { + new TextureData(0, TextureUtils.DXT5Compress(Data, w, h).Item1, 1) + }; + } + + if (!IffFile.RETAIN_CHUNK_DATA) Data = null; + + return data; + }); } else { PixelCache = new Texture2D(device, w, h, mip, SurfaceFormat.Color); - if (mip) - TextureUtils.UploadWithMips(PixelCache, device, Data); - else - PixelCache.SetData(this.Data); + + AssetStreaming.LoadTexture(PixelCache, AssetStreamingMode.Lot, () => + { + DecodeIfRequired(); + + TextureData[] data; + if (mip) + data = TextureUtils.GenerateMips(PixelCache, Data); + else + { + data = new TextureData[] + { + new TextureData(0, Data) + }; + } + + if (!IffFile.RETAIN_CHUNK_DATA) Data = null; + + return data; + }); } } else @@ -292,7 +368,6 @@ public Texture2D GetTexture(GraphicsDevice device) } PixelCache.Tag = new TextureInfo(PixelCache, Width, Height); - if (!IffFile.RETAIN_CHUNK_DATA) Data = null; } return PixelCache; } diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/SPR2.cs b/TSOClient/tso.files/Formats/IFF/Chunks/SPR2.cs index af52a5c6c..471616891 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/SPR2.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/SPR2.cs @@ -7,6 +7,7 @@ using FSO.Common.Utils; using FSO.Common.Rendering; using FSO.Common; +using System.Threading; namespace FSO.Files.Formats.IFF.Chunks { @@ -204,6 +205,7 @@ public class SPR2Frame : ITextureProvider, IWorldTextureProvider private SPR2 Parent; private uint Version; private byte[] ToDecode; + private PALT Palette; public bool Decoded { get @@ -212,7 +214,7 @@ public bool Decoded } } public bool ContainsNothing = false; - public bool ContainsNoZ = false; + public bool ContainsNoZ => (Flags & 0x02) != 0x02; public SPR2Frame(SPR2 parent) { @@ -231,17 +233,20 @@ public void Read(uint version, IoBuffer io, uint guessedSize) { var spriteVersion = io.ReadUInt32(); var spriteSize = io.ReadUInt32(); + ReadHead(version, io); if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1001, io); - else ToDecode = io.ReadBytes(spriteSize); + else ToDecode = io.ReadBytes(spriteSize - 10); } else { + ReadHead(version, io); if (IffFile.RETAIN_CHUNK_DATA) ReadDeferred(1000, io); - else ToDecode = io.ReadBytes(guessedSize); + else ToDecode = io.ReadBytes(guessedSize - 10); } } - public void ReadDeferred(uint version, IoBuffer io) + private void ReadHead(uint version, IoBuffer io) { + // Useful to read these early for async loading this.Width = io.ReadUInt16(); this.Height = io.ReadUInt16(); this.Flags = io.ReadUInt32(); @@ -252,6 +257,27 @@ public void ReadDeferred(uint version, IoBuffer io) this.PaletteID = (ushort)Parent.DefaultPaletteID; } + UpdatePalette(); + } + + private void UpdatePalette() + { + Palette = Parent.ChunkParent.Get(this.PaletteID); + if (Palette == null) Palette = new PALT() { Colors = new Color[256] }; + } + + private PALT EnsurePalette() + { + if (Palette == null) + { + UpdatePalette(); + } + + return Palette; + } + + public void ReadDeferred(uint version, IoBuffer io) + { TransparentColorIndex = io.ReadUInt16(); var y = io.ReadInt16(); @@ -261,16 +287,34 @@ public void ReadDeferred(uint version, IoBuffer io) this.Decode(io); } + private int _decoding = 0; + public void DecodeIfRequired(bool z) { - if (ToDecode != null && (((this.Flags & 0x02) == 0x02 && z && ZBufferData == null) || (!z && PixelData == null))) + if (ToDecode != null && ((!ContainsNoZ && ZBufferData == null) || (!z && PixelData == null))) { - using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), ByteOrder.LITTLE_ENDIAN)) + if (Interlocked.CompareExchange(ref _decoding, 1, 0) > 0) { - ReadDeferred(Version, buf); + // If another thread is already decoding the sprite, spin until it's done. + SpinWait w = default; + while (Volatile.Read(ref _decoding) > 0) + { + w.SpinOnce(); + } + + DecodeIfRequired(z); } + else + { + using (IoBuffer buf = IoBuffer.FromStream(new MemoryStream(ToDecode), ByteOrder.LITTLE_ENDIAN)) + { + ReadDeferred(Version, buf); + } - if (TimedReferenceController.CurrentType == CacheType.PERMANENT) ToDecode = null; + if (TimedReferenceController.CurrentType == CacheType.PERMANENT) ToDecode = null; + + Interlocked.Exchange(ref _decoding, 0); + } } } @@ -314,21 +358,25 @@ private void Decode(IoBuffer io) var numPixels = this.Width * this.Height; var ow = Width; var fc = Parent.FloorCopy; - if (fc > 0) + if (fc > 0 && Width % 2 != 0) { numPixels += Height; Width++; } + + Color[] pixelData = null; + byte[] palData = null; + byte[] zData = null; + if (hasPixels){ - this.PixelData = new Color[numPixels]; - this.PalData = new byte[numPixels]; + pixelData = new Color[numPixels]; + palData = new byte[numPixels]; } if (hasZBuffer){ - this.ZBufferData = new byte[numPixels]; + zData = new byte[numPixels]; } - var palette = Parent.ChunkParent.Get(this.PaletteID); - if (palette == null) palette = new PALT() { Colors = new Color[256] }; + var palette = EnsurePalette(); palette.References++; var transparentPixel = palette.Colors[TransparentColorIndex]; transparentPixel.A = 0; @@ -376,9 +424,9 @@ private void Decode(IoBuffer io) //this mode draws the transparent colour as solid for some reason. //fixes backdrop theater var offset = (y * Width) + x; - this.PixelData[offset] = pxColor; - this.PalData[offset] = pxValue; - this.ZBufferData[offset] = zValue; + pixelData[offset] = pxColor; + palData[offset] = pxValue; + zData[offset] = zValue; x++; } if (pxWithAlpha) @@ -394,11 +442,11 @@ private void Decode(IoBuffer io) for (var col = 0; col < pxCount; col++) { var offset = (y * Width) + x; - this.PixelData[offset] = transparentPixel; - this.PalData[offset] = (byte)TransparentColorIndex; - this.PixelData[offset].A = 0; + pixelData[offset] = transparentPixel; + palData[offset] = (byte)TransparentColorIndex; + pixelData[offset].A = 0; if (hasZBuffer){ - this.ZBufferData[offset] = 255; + zData[offset] = 255; } x++; } @@ -418,11 +466,11 @@ private void Decode(IoBuffer io) pxColor.A = 0; z = 255; }*/ - this.PixelData[offset] = pxColor; - this.PalData[offset] = pxIndex; + pixelData[offset] = pxColor; + palData[offset] = pxIndex; if (hasZBuffer) { - this.ZBufferData[offset] = z; + zData[offset] = z; } x++; } @@ -441,7 +489,7 @@ private void Decode(IoBuffer io) var offset = (y * Width) + x; if (hasZBuffer) { - this.ZBufferData[offset] = 255; + zData[offset] = 255; } x++; } @@ -456,16 +504,16 @@ private void Decode(IoBuffer io) var offset = ((y+row) * Width) + col; if (hasPixels) { - this.PixelData[offset] = transparentPixel; - this.PalData[offset] = (byte)TransparentColorIndex; + pixelData[offset] = transparentPixel; + palData[offset] = (byte)TransparentColorIndex; } if (hasAlpha) { - this.PixelData[offset].A = 0; + pixelData[offset].A = 0; } if (hasZBuffer) { - ZBufferData[offset] = 255; + zData[offset] = 255; } } } @@ -477,6 +525,11 @@ private void Decode(IoBuffer io) } y++; } + + this.PixelData = pixelData; + this.PalData = palData; + this.ZBufferData = zData; + if (!IffFile.RETAIN_CHUNK_DATA) PalData = null; if (Parent.ZAsAlpha) CopyZToAlpha(); if (Parent.FloorCopy == 1) FloorCopy(); @@ -624,40 +677,73 @@ private Texture2D GetTexture(GraphicsDevice device, bool onlyThis) Texture2D result = null; if (!PixelCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed) { - DecodeIfRequired(false); if (this.Width == 0 || this.Height == 0) { ContainsNothing = true; return null; } + + var effectiveWidth = Parent.FloorCopy > 0 ? ((Width + 1) & ~1) : Width; + var tc = FSOEnvironment.TexCompress; - var mip = FSOEnvironment.Enable3D && (FSOEnvironment.EnableNPOTMip || (Width == 128 && Height == 64)); - if (mip && TextureUtils.OverrideCompression(Width, Height)) tc = false; + var mip = FSOEnvironment.Enable3D && (FSOEnvironment.EnableNPOTMip || (effectiveWidth == 128 && Height == 64)); + if (mip && TextureUtils.OverrideCompression(effectiveWidth, Height)) tc = false; if (tc) { - result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height + 3) / 4) * 4, mip, SurfaceFormat.Dxt5); - if (mip) TextureUtils.UploadDXT5WithMips(result, Width, Height, device, this.PixelData); - else - { - var dxt = TextureUtils.DXT5Compress(this.PixelData, this.Width, this.Height); - result.SetData(dxt.Item1); - } + result = new CachableTexture2D(device, ((effectiveWidth + 3)/4)*4, ((Height + 3) / 4) * 4, mip, SurfaceFormat.Dxt5); + + AssetStreaming.LoadTexture(result, AssetStreamingMode.Lot, + () => { + DecodeIfRequired(false); + TextureData[] data; + if (mip) data = TextureUtils.GenerateDXT5WithMips(result, effectiveWidth, Height, this.PixelData); + else + { + data = new TextureData[] + { + new TextureData(0, TextureUtils.DXT5Compress(this.PixelData, effectiveWidth, this.Height).Item1, 1) + }; + } + + if (!IffFile.RETAIN_CHUNK_DATA) + { + PixelData = null; + } + + return data; + }); } else { - result = new CachableTexture2D(device, this.Width, this.Height, mip, SurfaceFormat.Color); - if (mip) TextureUtils.UploadWithMips(result, device, this.PixelData); - else result.SetData(this.PixelData); + result = new CachableTexture2D(device, effectiveWidth, this.Height, mip, SurfaceFormat.Color); + AssetStreaming.LoadTexture(result, AssetStreamingMode.Lot, + () => + { + DecodeIfRequired(false); + + TextureData[] data; + if (mip) data = TextureUtils.GenerateMips(result, this.PixelData); + else + { + data = new TextureData[] + { + new TextureData(0, this.PixelData) + }; + } + + if (!IffFile.RETAIN_CHUNK_DATA) + { + PixelData = null; + } + + return data; + }); } - result.Tag = new TextureInfo(result, Width, Height); + + result.Tag = new TextureInfo(result, effectiveWidth, Height); PixelCache = new WeakReference(result); if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefP = result; - if (!IffFile.RETAIN_CHUNK_DATA) - { - PixelData = null; - //if (onlyThis && !FSOEnvironment.Enable3D) ZBufferData = null; - } } if (TimedReferenceController.CurrentType != CacheType.PERMANENT) TimedReferenceController.KeepAlive(result, KeepAliveType.ACCESS); return result; @@ -688,36 +774,55 @@ private Texture2D GetZTexture(GraphicsDevice device, bool onlyThis) if (ContainsNothing || ContainsNoZ) return null; if (!ZCache.TryGetTarget(out result) || ((CachableTexture2D)result).BeingDisposed || result.IsDisposed) { - DecodeIfRequired(true); if (this.Width == 0 || this.Height == 0) { ContainsNothing = true; return null; } - if (ZBufferData == null) + if (ContainsNoZ) { - ContainsNoZ = true; return null; } + + var effectiveWidth = Parent.FloorCopy > 0 ? ((Width + 1) & ~1) : Width; + if (FSOEnvironment.TexCompress) { - result = new CachableTexture2D(device, ((Width+3)/4)*4, ((Height+3)/4)*4, false, SurfaceFormat.Alpha8); - var tempZ = new byte[result.Width * result.Height]; - var dind = 0; - var sind = 0; - for (int i=0; i(tempZ); + result = new CachableTexture2D(device, ((effectiveWidth+3)/4)*4, ((Height+3)/4)*4, false, SurfaceFormat.Alpha8); + AssetStreaming.LoadTexture(result, AssetStreamingMode.Lot, + () => + { + DecodeIfRequired(true); + + var tempZ = new byte[result.Width * result.Height]; + var dind = 0; + var sind = 0; + for (int i = 0; i < Height; i++) + { + Array.Copy(ZBufferData, sind, tempZ, dind, effectiveWidth); + sind += effectiveWidth; + dind += result.Width; + } + + if (!IffFile.RETAIN_CHUNK_DATA) + { + if (!FSOEnvironment.Enable3D) ZBufferData = null; + } + + return new TextureData[] { new TextureData(0, tempZ) }; + }); } else { - result = new CachableTexture2D(device, this.Width, this.Height, false, SurfaceFormat.Alpha8); - result.SetData(this.ZBufferData); + result = new CachableTexture2D(device, effectiveWidth, this.Height, false, SurfaceFormat.Alpha8); + + AssetStreaming.LoadTexture(result, AssetStreamingMode.Lot, + () => + { + DecodeIfRequired(true); + + return new TextureData[] { new TextureData(0, this.ZBufferData) }; + }); } ZCache = new WeakReference(result); if (TimedReferenceController.CurrentType == CacheType.PERMANENT) PermaRefZ = result; @@ -740,11 +845,6 @@ public WorldTexture GetWorldTexture(GraphicsDevice device) Pixel = this.GetTexture(device, false) }; result.ZBuffer = this.GetZTexture(device, false); - if (!IffFile.RETAIN_CHUNK_DATA) - { - PixelData = null; - if (!FSOEnvironment.Enable3D) ZBufferData = null; - } return result; } @@ -781,6 +881,8 @@ public void SetPalt(PALT p) if (old != null) old.References--; } PaletteID = p.ChunkID; + Palette = p; + p.References++; } } diff --git a/TSOClient/tso.files/Formats/IFF/Chunks/STR.cs b/TSOClient/tso.files/Formats/IFF/Chunks/STR.cs index e4f1ab0ff..5ef99d6ea 100644 --- a/TSOClient/tso.files/Formats/IFF/Chunks/STR.cs +++ b/TSOClient/tso.files/Formats/IFF/Chunks/STR.cs @@ -300,12 +300,21 @@ public override void Read(IffFile iff, Stream stream) else if (formatCode == -4) { var numLanguageSets = io.ReadByte(); - this.LanguageSets = new STRLanguageSet[numLanguageSets]; + if (LanguageSets.Length != numLanguageSets) + { + this.LanguageSets = new STRLanguageSet[numLanguageSets]; + } for(var i=0; i < numLanguageSets; i++) { var item = new STRLanguageSet(); var numStringPairs = io.ReadUInt16(); + + if (numStringPairs == 0) + { + continue; + } + item.Strings = new STRItem[numStringPairs]; for (var x = 0; x < numStringPairs; x++) { diff --git a/TSOClient/tso.files/Formats/IFF/IffFile.cs b/TSOClient/tso.files/Formats/IFF/IffFile.cs index be5f53e88..32770b6cc 100644 --- a/TSOClient/tso.files/Formats/IFF/IffFile.cs +++ b/TSOClient/tso.files/Formats/IFF/IffFile.cs @@ -5,6 +5,7 @@ using FSO.Files.Formats.IFF.Chunks; using FSO.Files.Utils; using FSO.Common.Utils; +using System.IO.Hashing; namespace FSO.Files.Formats.IFF { @@ -178,13 +179,14 @@ public void InitHash() { IEnumerable executableTypes = ByChunkType[typeof(BHAV)]; if (ByChunkType.ContainsKey(typeof(BCON))) executableTypes = executableTypes.Concat(ByChunkType[typeof(BCON)]); - var hash = new xxHashSharp.xxHash(); - hash.Init(); + + var hash = new XxHash32(); + hash.Reset(); foreach (IffChunk chunk in executableTypes) { - hash.Update(chunk.ChunkData ?? chunk.OriginalData, chunk.ChunkData.Length); + hash.Append(chunk.ChunkData ?? chunk.OriginalData); } - ExecutableHash = hash.Digest(); + ExecutableHash = hash.GetCurrentHashAsUInt32(); } } @@ -321,6 +323,21 @@ public T Get(ushort id){ return default(T); } + public string GetLabel(ushort id) + { + Type typeofT = typeof(T); + if (ByChunkId.ContainsKey(typeofT)) + { + var lookup = ByChunkId[typeofT]; + if (lookup.TryGetValue(id, out var chunk)) + { + return (chunk as IffChunk)?.ChunkLabel; + } + } + + return null; + } + public List ListAll() { var result = new List(); diff --git a/TSOClient/tso.files/Formats/tsodata/TSOp.cs b/TSOClient/tso.files/Formats/tsodata/TSOp.cs index 287004d91..fa3b8a02a 100644 --- a/TSOClient/tso.files/Formats/tsodata/TSOp.cs +++ b/TSOClient/tso.files/Formats/tsodata/TSOp.cs @@ -1,9 +1,6 @@ -using FSO.Files.Utils; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using deltaq; +using DeltaQ.BsDiff; +using FSO.Common.Utils; +using FSO.Files.Utils; namespace TSOVersionPatcher { @@ -95,7 +92,7 @@ private string GetRelativePath(string relativeTo, string path) var rel = Uri.UnescapeDataString(uri.MakeRelativeUri(new Uri(path)).ToString()).Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar); if (rel.Contains(Path.DirectorySeparatorChar.ToString()) == false) { - rel = $".{ Path.DirectorySeparatorChar }{ rel }"; + rel = $".{Path.DirectorySeparatorChar}{rel}"; } return rel; } @@ -117,10 +114,10 @@ public void Apply(string source, string dest, Action progress) foreach (var file in sourceFiles) { - var destP = Path.Combine(dest, file); + var destP = PathUtils.SafeCombine(dest, file); Directory.CreateDirectory(Path.GetDirectoryName(destP)); - File.Copy(Path.Combine(source, file), destP); + File.Copy(PathUtils.SafeCombine(source, file), destP); } } @@ -130,21 +127,21 @@ public void Apply(string source, string dest, Action progress) foreach (var patch in Patches) { progress($"Patching {patch.FileTarget}...", fileNum / (float)total); - var path = Path.Combine(source, patch.FileTarget); - var dpath = Path.Combine(dest, patch.FileTarget); + var path = PathUtils.SafeCombine(source, patch.FileTarget); + var dpath = PathUtils.SafeCombine(dest, patch.FileTarget); var data = File.ReadAllBytes(path); Directory.CreateDirectory(Path.GetDirectoryName(dpath)); Str.Seek(patch.Offset, SeekOrigin.Begin); var patchd = reader.ReadBytes(patch.Length); - BsPatch.Apply(data, patchd, File.Open(dpath, FileMode.Create, FileAccess.Write, FileShare.None)); + Patch.Apply(data, patchd, File.Open(dpath, FileMode.Create, FileAccess.Write, FileShare.None)); fileNum++; } foreach (var add in Additions) { progress($"Adding {add.FileTarget}...", fileNum / (float)total); - var dpath = Path.Combine(dest, add.FileTarget); + var dpath = PathUtils.SafeCombine(dest, add.FileTarget); Directory.CreateDirectory(Path.GetDirectoryName(dpath)); Str.Seek(add.Offset, SeekOrigin.Begin); @@ -158,7 +155,7 @@ public void Apply(string source, string dest, Action progress) try { progress($"Deleting {del}...", fileNum / (float)total); - File.Delete(Path.Combine(dest, del)); + File.Delete(PathUtils.SafeCombine(dest, del)); fileNum++; } catch diff --git a/TSOClient/tso.files/HIT/FSC.cs b/TSOClient/tso.files/HIT/FSC.cs index cef3b8779..182e0e25d 100644 --- a/TSOClient/tso.files/HIT/FSC.cs +++ b/TSOClient/tso.files/HIT/FSC.cs @@ -8,19 +8,26 @@ public class FSC { /// /// FSC is a tabulated plaintext format that describes a sequence of notes to be played. In this game it is used to sequence the ambient sounds. - /// The conditions in which the sequence is randomized are not entirely apparent, and have been mostly guessed. /// /// public List Notes; + /// + /// Each row contains a sequence of notes that play in parallel with other rows. (essentially, a track) + /// A column contains all the notes that need to evaluate to move to the next column. (essentially, one compound note) + /// Typically this is used to play ambience at a set interval, using the note probability to essentially + /// select one of many possible sounds to play at a time (or none) + /// + public FSCNote[][] NoteColumns; + public string VersionCode; public ushort MasterVolume; public ushort Priority; public ushort Min; public ushort Max; - public ushort Rows; //these seem to be outright lies, but let's leave them in + public ushort Rows; public ushort Columns; public ushort Tempo; public ushort BPB; //beats per bar @@ -30,8 +37,6 @@ public class FSC public ushort QuanY; public ushort DiffX; public ushort DiffY; - - public List RandomJumpPoints; /// /// Creates a new hsm file. @@ -55,8 +60,6 @@ private void ReadFile(Stream stream) { var io = new StreamReader(stream); - Notes = new List(); - RandomJumpPoints = new List(); VersionCode = io.ReadLine(); var line = io.ReadLine(); @@ -80,24 +83,31 @@ private void ReadFile(Stream stream) DiffX = Convert.ToUInt16(Head[13]); DiffY = Convert.ToUInt16(Head[14]); - line = io.ReadLine(); + NoteColumns = new FSCNote[Columns][]; - while (line.StartsWith("#") || line.StartsWith("cells")) - line = io.ReadLine(); + for (int i = 0; i < NoteColumns.Length; i++) + { + NoteColumns[i] = new FSCNote[Rows]; + } + + int column = 0; + int row = 0; + + line = io.ReadLine(); while (!io.EndOfStream) //read notes { - string line2 = io.ReadLine(); - string[] Values = line2.Split('\t'); - if (!line.StartsWith("#") && Values.Length == 20) + line = io.ReadLine(); + string[] Values = line.Split('\t'); + if (!line.StartsWith("#") && Values.Length >= 20) { var note = new FSCNote() { Volume = Convert.ToUInt16(Values[1]), - Rand = Values[2] != "0", + RandomVolume = Values[2] != "0", LRPan = Convert.ToUInt16(Values[3]), FBPan = Convert.ToUInt16(Values[4]), - Rand2 = Values[5] != "0", + RandomPan = Values[5] != "0", Fin = Convert.ToUInt16(Values[6]), FOut = Convert.ToUInt16(Values[7]), @@ -109,45 +119,107 @@ private void ReadFile(Stream stream) Quant = Convert.ToUInt16(Values[12]), Prob = Convert.ToUInt16(Values[13]), pitchL = Convert.ToInt16(Values[14]), - pitchR = Convert.ToInt16(Values[15]), + pitchH = Convert.ToInt16(Values[15]), Fast = Values[16] != "0", GroupID = Convert.ToUInt16(Values[17]), Stereo = Values[18] != "0", - Filename = Values[19] + Filename = Values[19], + ExclusionCells = Values.Length > 20 ? ParseExclusionCells([.. Values.Skip(20).Where(x => x != "")]) : null }; - if (note.Rand) RandomJumpPoints.Add(Notes.Count); - Notes.Add(note); + + NoteColumns[column][row] = note; + + // Notes fill columns one at a time. Move onto the next column each note. + column++; + if (column >= Columns) + { + // When the row is full, move to the next row. + column = 0; + row++; + if (row >= NoteColumns.Length) + { + break; + } + } } } io.Close(); } + + private static FSCExclusionCell[] ParseExclusionCells(string[] split) + { + if (split.Length == 0) + { + return null; + } + + var result = new FSCExclusionCell[split.Length]; + + for (int i = 0; i < split.Length; i++) + { + string elem = split[i]; + + if (elem.StartsWith("\"(") && elem.EndsWith(")\"")) + { + string[] parts = elem.Substring(2, elem.Length - 4).Split(','); + if (parts.Length != 2 || !int.TryParse(parts[0], out int x) || !int.TryParse(parts[1], out int y)) + { + return null; + } + + result[i] = new FSCExclusionCell() + { + X = x, + Y = y + }; + } + else + { + return null; + } + } + + return result; + } + } + + public struct FSCExclusionCell + { + public int X; + public int Y; } public struct FSCNote { public ushort Volume; //0-1024 - public bool Rand; + public bool RandomVolume; public ushort LRPan; //0-1024 public ushort FBPan; //0-1024, front back - public bool Rand2; + public bool RandomPan; public ushort Fin; public ushort FOut; - public ushort dly; - public bool Rand3; //what + public ushort dly; // Delay? + public bool Rand3; // Randomize delay? public ushort Loop; public bool Loop2; //might be count then decider here public ushort Quant; //but then what is this? - public ushort Prob; //probably random probability, not sure of range (0-16?) - public short pitchL; //pitch offsets - public short pitchR; + public ushort Prob; // random probability of note playing in % + public short pitchL; //pitch range: low + public short pitchH; // pitch range: high public bool Fast; public ushort GroupID; public bool Stereo; public string Filename; + + /// + /// Assumed behaviour: if this note plays, then the notes in the following cells cannot play. + /// Encoded in the file after the normal values, as "(x,y)" separated by tabs. + /// + public FSCExclusionCell[] ExclusionCells; } } diff --git a/TSOClient/tso.files/HIT/HSM.cs b/TSOClient/tso.files/HIT/HSM.cs index 0ca83a255..0ba6ebc18 100644 --- a/TSOClient/tso.files/HIT/HSM.cs +++ b/TSOClient/tso.files/HIT/HSM.cs @@ -1,11 +1,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; namespace FSO.Files.HIT { public class HSM { + private static Regex CamelCaseRegex = new Regex("([a-z])([A-Z])"); + /// /// HSM is a plaintext format that names various HIT constants including subroutine locations. /// @@ -41,10 +44,31 @@ private void ReadFile(Stream stream) string[] Values = line.Split(' '); var name = Values[0].ToLowerInvariant(); - if (!Constants.ContainsKey(name)) Constants.Add(name, Convert.ToInt32(Values[1])); //the repeats are just labels for locations (usually called gotit) + var normalName = NormalizeCase(Values[0]).ToLowerInvariant(); + var value = Convert.ToInt32(Values[1]); + Constants[name] = value; //the repeats are just labels for locations (usually called gotit) + if (name != normalName) + { + Constants[normalName] = value; + } } io.Close(); } + + private string NormalizeCase(string value) + { + var matches = CamelCaseRegex.Matches(value); + + int addedChars = 0; + + foreach (Match match in matches) + { + value = value.Substring(0, match.Index + addedChars + 1) + '_' + value.Substring(match.Index + addedChars + 1, value.Length - (match.Index + addedChars + 1)); + addedChars++; + } + + return value; + } } } diff --git a/TSOClient/tso.files/HIT/Hot.cs b/TSOClient/tso.files/HIT/Hot.cs index 6a8372852..a7435ac87 100644 --- a/TSOClient/tso.files/HIT/Hot.cs +++ b/TSOClient/tso.files/HIT/Hot.cs @@ -199,7 +199,7 @@ public void LoadFrom(byte[] FileData) private int HSMConst(string input) { int result = 0; - AsmNames?.Constants?.TryGetValue(input, out result); + AsmNames?.Constants?.TryGetValue(input.ToLowerInvariant(), out result); return result; } diff --git a/TSOClient/tso.files/HIT/Patch.cs b/TSOClient/tso.files/HIT/Patch.cs index d9f416989..e6d49c300 100644 --- a/TSOClient/tso.files/HIT/Patch.cs +++ b/TSOClient/tso.files/HIT/Patch.cs @@ -10,6 +10,8 @@ public class Patch public uint FileID; //patches are stubbed out in TSO. public bool TSO; + public Patch() { } + public Patch(uint id) { FileID = id; diff --git a/TSOClient/tso.files/HIT/Track.cs b/TSOClient/tso.files/HIT/Track.cs index e9bb4c18f..91a12ee38 100644 --- a/TSOClient/tso.files/HIT/Track.cs +++ b/TSOClient/tso.files/HIT/Track.cs @@ -53,6 +53,7 @@ public Track(byte[] Filedata) TrackName = Values[2]; SoundID = ParseHexString(Values[3]); TrackID = ParseHexString(Values[4]); + if (Values[5] != "\r\n" && Values[5] != "ETKD" && Values[5] != "") //some tracks terminate here... { ArgType = (HITArgs)ParseHexString(Values[5]); diff --git a/TSOClient/tso.files/ImageLoader.cs b/TSOClient/tso.files/ImageLoader.cs index 41eaead8d..3dddf3363 100644 --- a/TSOClient/tso.files/ImageLoader.cs +++ b/TSOClient/tso.files/ImageLoader.cs @@ -1,38 +1,299 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.Xna.Framework.Graphics; -using System.IO; +using FSO.Common.Utils; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using System.Runtime.InteropServices; +using System.Text; namespace FSO.Files { + public struct ImageData + { + private Color[] ColorData; + public Color[] Data => ColorData ?? GetColorData(); + public readonly byte[] ByteData; + public readonly int Width; + public readonly int Height; + + public ImageData(Color[] data, int width, int height) + { + ColorData = data; + ByteData = null; + Width = width; + Height = height; + } + + public ImageData(byte[] data, int width, int height) + { + ColorData = null; + ByteData = data; + Width = width; + Height = height; + } + + public Texture2D GetTexture(GraphicsDevice gd) + { + if (ColorData == null && ByteData == null) + { + return null; + } + + var tex = new Texture2D(gd, Width, Height); + if (ColorData != null) + { + tex.SetData(ColorData); + } + else + { + tex.SetData(ByteData); + } + + return tex; + } + + private unsafe Color[] GetColorData() + { + var data = ByteData; + Color[] colorData = new Color[data.Length / 4]; + + fixed (void* ptr = colorData) + { + Marshal.Copy(data, 0, (IntPtr)ptr, data.Length); + } + + ColorData = colorData; + + return colorData; + } + } + + public readonly struct ImageDataOrTextureProducer + { + public readonly ImageData? Data; + public readonly Func Producer; + + public ImageDataOrTextureProducer(ImageData data) + { + Data = data; + Producer = null; + } + + public ImageDataOrTextureProducer(Func producer) + { + Producer = producer; + Data = null; + } + + public Texture2D GetTexture(GraphicsDevice gd) + { + if (Producer != null) + { + return Producer(); + } + else if (Data != null) + { + return Data.Value.GetTexture(gd); + } + + return null; + } + + public Func GetProducer(GraphicsDevice gd) + { + if (Producer != null) + { + return Producer; + } + else if (Data != null) + { + var data = Data.Value; + return () => + { + return data.GetTexture(gd); + }; + } + else + { + return () => null; + } + } + } + public class ImageLoader { public static bool UseSoftLoad = true; public static int PremultiplyPNG = 0; - public static HashSet MASK_COLORS = new HashSet{ - new Microsoft.Xna.Framework.Color(0xFF, 0x00, 0xFF, 0xFF).PackedValue, - new Microsoft.Xna.Framework.Color(0xFE, 0x02, 0xFE, 0xFF).PackedValue, - new Microsoft.Xna.Framework.Color(0xFF, 0x01, 0xFF, 0xFF).PackedValue - }; - public static Func BaseFunction = WinFromStream; + public static Func> BaseNonUIFunction = WinNonUIFromStream; + public static Func BaseDataFunction = WinDataFromStream; + public static Texture2D FromStreamAvgMips(GraphicsDevice gd, Stream str) + { + var file = DataFromStream(gd, str); + + if (file == null) + { + return null; + } + + if (file.Value.Producer != null) + { + var nonMip = file.Value.Producer(); + var data = new Color[nonMip.Width * nonMip.Height]; + nonMip.GetData(data); + nonMip.Dispose(); + + var result = new Texture2D(gd, nonMip.Width, nonMip.Height, true, SurfaceFormat.Color); + TextureUtils.UploadWithAvgMips(result, gd, data); + + return result; + } + else if (file.Value.Data != null) + { + var data = file.Value.Data.Value; + + var result = new Texture2D(gd, data.Width, data.Height, true, SurfaceFormat.Color); + TextureUtils.UploadWithAvgMips(result, gd, data.Data); + + return result; + } + + return null; + } + + public static Texture2D MipTextureFromFile(GraphicsDevice gd, string filePath) + { + using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + return FromStreamAvgMips(gd, stream); + } + } public static Texture2D FromStream(GraphicsDevice gd, Stream str) { return BaseFunction(gd, str); } + /// + /// Gets data or a Texture2D factory for the given stream. + /// This runs the decoder work on the calling thread, and may return the raw image data, + /// or returns a function that creates the texture that should be called on the main thread. + /// + /// + /// + /// + public static ImageDataOrTextureProducer? DataFromStream(GraphicsDevice gd, Stream str) + { + return BaseDataFunction(gd, str); + } + + /// + /// Get a Texture2D factory for the given stream. + /// This runs the decoder work on the calling thread, and returns a function + /// that creates the texture that should be called on the main thread. + /// + /// + /// + /// + public static Func NonUIFromStream(GraphicsDevice gd, Stream str) + { + return BaseNonUIFunction(gd, str); + } + private static Texture2D WinFromStream(GraphicsDevice gd, Stream str) { return WinFromStreamP(gd, str, 0); } - public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premult) + private static Func WinNonUIFromStream(GraphicsDevice gd, Stream str) + { + return WinNonUIFromStreamP(gd, str, 0); + } + + private static ImageDataOrTextureProducer? WinDataFromStream(GraphicsDevice gd, Stream str) + { + return WinDataFromStreamP(gd, str, 0); + } + + public static bool Premultiply(Color[] buffer, int premult) + { + if (premult == 1) + { + for (int i = 0; i < buffer.Length; i++) + { + var a = buffer[i].A; + if (a != 255) + { + buffer[i] = new Color((byte)((buffer[i].R * a) / 255), (byte)((buffer[i].G * a) / 255), (byte)((buffer[i].B * a) / 255), a); + } + } + + return true; + } + else if (premult == -1) //divide out a premultiply... currently needed for dx since it premultiplies pngs without reason + { + for (int i = 0; i < buffer.Length; i++) + { + var rawA = buffer[i].A; + + if (rawA != 255) + { + var a = rawA / 255f; + buffer[i] = new Color((byte)(buffer[i].R / a), (byte)(buffer[i].G / a), (byte)(buffer[i].B / a), buffer[i].A); + } + } + + return true; + } + + return false; + } + + public static bool Premultiply(byte[] buffer, int premult) + { + if (premult == 1) + { + for (int i = 0; i < buffer.Length; i += 4) + { + var a = buffer[i + 3]; + if (a != 255) + { + buffer[i] = (byte)((buffer[i] * a) / 255); + buffer[i + 1] = (byte)((buffer[i + 1] * a) / 255); + buffer[i + 2] = (byte)((buffer[i + 2] * a) / 255); + } + } + + return true; + } + else if (premult == -1) //divide out a premultiply... currently needed for dx since it premultiplies pngs without reason + { + for (int i = 0; i < buffer.Length; i += 4) + { + var rawA = buffer[i]; + + if (rawA != 255) + { + var a = rawA / 255f; + + buffer[i] = (byte)(buffer[i] / a); + buffer[i + 1] = (byte)(buffer[i + 1] / a); + buffer[i + 2] = (byte)(buffer[i + 2] / a); + } + } + + return true; + } + + return false; + } + + public static Func WinNonUIFromStreamP(GraphicsDevice gd, Stream str, int premult) + { + return WinDataFromStreamP(gd, str, premult)?.GetProducer(gd); + } + + public static ImageDataOrTextureProducer? WinDataFromStreamP(GraphicsDevice gd, Stream str, int premult) { //if (!UseSoftLoad) //{ @@ -46,20 +307,25 @@ public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premul try { //it's a bitmap. - Texture2D tex; if (ImageLoaderHelpers.BitmapFunction != null) { var bmp = ImageLoaderHelpers.BitmapFunction(str); if (bmp == null) return null; - tex = new Texture2D(gd, bmp.Item2, bmp.Item3); - tex.SetData(bmp.Item1); + + ManualTextureMaskData(bmp.Item1); + + return new ImageDataOrTextureProducer(new ImageData(bmp.Item1, bmp.Item2, bmp.Item3)); } else { - tex = Texture2D.FromStream(gd, str); + return new ImageDataOrTextureProducer(() => + { + Texture2D tex = Texture2D.FromStream(gd, str); + + ManualTextureMaskSingleThreaded(ref tex); + return tex; + }); } - ManualTextureMaskSingleThreaded(ref tex, MASK_COLORS.ToArray()); - return tex; } catch (Exception) { @@ -78,9 +344,8 @@ public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premul try { var tga = new TargaImagePCL.TargaImage(str); - var tex = new Texture2D(gd, tga.Image.Width, tga.Image.Height); - tex.SetData(tga.Image.ToBGRA(true)); - return tex; + + return new ImageDataOrTextureProducer(new ImageData(tga.Image.ToBGRA(true), tga.Image.Width, tga.Image.Height)); } catch (Exception) { @@ -92,76 +357,56 @@ public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premul //anything else try { - Texture2D tex; - Color[] buffer = null; + premult += PremultiplyPNG; + if (ImageLoaderHelpers.BitmapFunction != null) { var bmp = ImageLoaderHelpers.BitmapFunction(str); if (bmp == null) return null; - tex = new Texture2D(gd, bmp.Item2, bmp.Item3); - tex.SetData(bmp.Item1); + + Premultiply(bmp.Item1, premult); + + return new ImageDataOrTextureProducer(new ImageData(bmp.Item1, bmp.Item2, bmp.Item3)); //buffer = bmp.Item1; } else { - tex = Texture2D.FromStream(gd, str); - } - - premult += PremultiplyPNG; - if (premult == 1) - { - if (buffer == null) + return new ImageDataOrTextureProducer(() => { - buffer = new Color[tex.Width * tex.Height]; - tex.GetData(buffer); - } + Texture2D tex = Texture2D.FromStream(gd, str); - for (int i = 0; i < buffer.Length; i++) - { - var a = buffer[i].A; - buffer[i] = new Color((byte)((buffer[i].R * a) / 255), (byte)((buffer[i].G * a) / 255), (byte)((buffer[i].B * a) / 255), a); - } - tex.SetData(buffer); - } - else if (premult == -1) //divide out a premultiply... currently needed for dx since it premultiplies pngs without reason - { - if (buffer == null) - { - buffer = new Color[tex.Width * tex.Height]; - tex.GetData(buffer); - } + if (premult != 0) + { + var buffer = new Color[tex.Width * tex.Height]; + tex.GetData(buffer); + Premultiply(buffer, premult); + tex.SetData(buffer); + } - for (int i = 0; i < buffer.Length; i++) - { - var a = buffer[i].A / 255f; - buffer[i] = new Color((byte)(buffer[i].R / a), (byte)(buffer[i].G / a), (byte)(buffer[i].B / a), buffer[i].A); - } - tex.SetData(buffer); + return tex; + }); } - return tex; } catch (Exception e) { Console.WriteLine("error: " + e.ToString()); - return new Texture2D(gd, 1, 1); + return new ImageDataOrTextureProducer(new ImageData(new Color[1], 1, 1)); } } } } - public static void ManualTextureMaskSingleThreaded(ref Texture2D Texture, uint[] ColorsFrom) + public static Texture2D WinFromStreamP(GraphicsDevice gd, Stream str, int premult) { - var ColorTo = Microsoft.Xna.Framework.Color.Transparent.PackedValue; - - var size = Texture.Width * Texture.Height * 4; - byte[] buffer = new byte[size]; - - Texture.GetData(buffer); + return WinNonUIFromStreamP(gd, str, premult)(); + } + public static bool ManualTextureMaskData(byte[] buffer) + { var didChange = false; - for (int i = 0; i < size; i += 4) + for (int i = 0; i < buffer.Length; i += 4) { if (buffer[i] >= 248 && buffer[i + 2] >= 248 && buffer[i + 1] <= 4) { @@ -170,12 +415,21 @@ public static void ManualTextureMaskSingleThreaded(ref Texture2D Texture, uint[] } } - if (didChange) + return didChange; + } + + public static void ManualTextureMaskSingleThreaded(ref Texture2D Texture) + { + var size = Texture.Width * Texture.Height * 4; + byte[] buffer = new byte[size]; + + Texture.GetData(buffer); + + if (ManualTextureMaskData(buffer)) { Texture.SetData(buffer); } - else return; } } -} +} \ No newline at end of file diff --git a/TSOClient/tso.files/Properties/AssemblyInfo.cs b/TSOClient/tso.files/Properties/AssemblyInfo.cs deleted file mode 100644 index 84beb191a..000000000 --- a/TSOClient/tso.files/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SimsLib")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SimsLib")] -[assembly: AssemblyCopyright("Copyright © 2010")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("97db5061-8f10-4ca0-8470-f42601031d4e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.files/RC/DGRP3DGeometry.cs b/TSOClient/tso.files/RC/DGRP3DGeometry.cs index dc479e8e7..9a63fee2d 100644 --- a/TSOClient/tso.files/RC/DGRP3DGeometry.cs +++ b/TSOClient/tso.files/RC/DGRP3DGeometry.cs @@ -1,4 +1,5 @@ -using FSO.Files.Formats.IFF; +using FSO.Common.Utils; +using FSO.Files.Formats.IFF; using FSO.Files.Formats.IFF.Chunks; using FSO.Files.Utils; using Microsoft.Xna.Framework; @@ -9,18 +10,61 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Threading; namespace FSO.Files.RC { + public struct DGRP3DTextureSource + { + public Texture2D Literal; + public IDGRP3DTextureHolder Holder; + public DGRPSprite Sprite; + + public DGRP3DTextureSource(Texture2D literal) + { + Literal = literal; + Holder = null; + Sprite = null; + } + + public DGRP3DTextureSource(IDGRP3DTextureHolder holder) + { + Literal = null; + Holder = holder; + Sprite = null; + } + + public DGRP3DTextureSource(DGRPSprite sprite) + { + Literal = null; + Holder = null; + Sprite = sprite; + } + + public static DGRP3DTextureSource? WithDecoded(IDGRP3DTextureHolder holder, GraphicsDevice gd) + { + if (holder == null) + { + return null; + } + + holder.Decode(gd); + + return new DGRP3DTextureSource(holder); + } + } + public class DGRP3DGeometry { + public bool Rendered = false; public Texture2D Pixel; + private DGRP3DTextureSource PixelSource; public ushort PixelSPR; public ushort PixelDir; public ushort CustomTexture; - public static Func ReplTextureProvider; + public static Func ReplTextureProvider; public List SVerts; //simplified vertices public List SIndices; //simplified indices @@ -29,6 +73,8 @@ public class DGRP3DGeometry public IndexBuffer Indices; public int PrimCount; + public int RefCount = 1; + public void SComplete(GraphicsDevice gd) { Rendered = true; @@ -46,12 +92,17 @@ public void SComplete(GraphicsDevice gd) if (!IffFile.RETAIN_CHUNK_DATA) { - SVerts = null; - SIndices = null; + DecrementDataRef(); } } public DGRP3DGeometry() { } + /// + /// Initializes DGRP3DGeometry from an FSOM. CompleteFSOMLoad should be called from the GPU thread to make it renderable. + /// + /// + /// + /// public DGRP3DGeometry(IoBuffer io, DGRP source, GraphicsDevice gd, int Version) { PixelSPR = io.ReadUInt16(); @@ -62,22 +113,18 @@ public DGRP3DGeometry(IoBuffer io, DGRP source, GraphicsDevice gd, int Version) if (source == null) { //temporary system for models without DGRP - Pixel = ReplTextureProvider("FSO_TEX_" + PixelSPR + ".png"); + PixelSource = ReplTextureProvider("FSO", PixelSPR) ?? default; } else { - var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff"); - name += "_TEX_" + PixelSPR + ".png"; - Pixel = ReplTextureProvider(name); - if (Pixel == null) - { - Pixel = source.ChunkParent.Get(PixelSPR)?.GetTexture(gd); - } + var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff").ToLowerInvariant(); + var pxSource = ReplTextureProvider(name, PixelSPR) ?? DGRP3DTextureSource.WithDecoded(source.ChunkParent.Get(PixelSPR), gd); + PixelSource = pxSource ?? default; } } else { - Pixel = source.GetImage(1, 3, PixelDir).Sprites[PixelSPR].GetTexture(gd); + PixelSource = new DGRP3DTextureSource(source.GetImage(1, 3, PixelDir).Sprites[PixelSPR]); } var vertCount = io.ReadInt32(); @@ -120,6 +167,25 @@ public DGRP3DGeometry(IoBuffer io, DGRP source, GraphicsDevice gd, int Version) if (Version < 2) GenerateNormals(false); + } + + private void GetPixelFromSource(GraphicsDevice gd) + { + if (PixelSource.Holder != null) + { + Pixel = PixelSource.Holder.GetTexture(gd); + } + else if (PixelSource.Sprite != null) + { + Pixel = PixelSource.Sprite.GetTexture(gd); + } + + PixelSource = default; + } + + public void CompleteFSOMLoad(GraphicsDevice gd) + { + GetPixelFromSource(gd); SComplete(gd); } @@ -138,15 +204,18 @@ public DGRP3DGeometry(string[] splitName, OBJ obj, List indices, DGRP sou CustomTexture = 1; PixelDir = 65535; - var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff"); - name += "_TEX_" + PixelSPR + ".png"; - Pixel = ReplTextureProvider(name); - if (Pixel == null) + var name = source.ChunkParent.Filename.Replace('.', '_').Replace("spf", "iff").ToLowerInvariant(); + var pxSource = ReplTextureProvider(name, PixelSPR); + if (pxSource == null) { - Pixel = source.ChunkParent.Get(PixelSPR)?.GetTexture(gd); + pxSource = DGRP3DTextureSource.WithDecoded(source.ChunkParent.Get(PixelSPR), gd); } + + PixelSource = pxSource ?? default; } + GetPixelFromSource(gd); + SVerts = new List(); SIndices = new List(); var dict = new Dictionary, int>(); @@ -193,8 +262,11 @@ public void GenerateNormals(bool invert) DGRP3DVert.GenerateNormals(invert, SVerts, SIndices); } - public void Save(IoWriter io) + public void Save(IoWriter io, List verts = null, List indices = null) { + if (verts == null) verts = SVerts; + if (indices == null) indices = SIndices; + io.WriteUInt16(PixelSPR); io.WriteUInt16(PixelDir); io.WriteInt32(SVerts.Count); @@ -311,5 +383,19 @@ private static byte[] ToByteArray(T[] input) Buffer.BlockCopy(input, 0, result, 0, result.Length); return result; } + + public void IncrementDataRef() + { + Interlocked.Increment(ref RefCount); + } + + public void DecrementDataRef() + { + if (Interlocked.Decrement(ref RefCount) == 0) + { + SVerts = null; + SIndices = null; + } + } } } diff --git a/TSOClient/tso.files/RC/DGRP3DMesh.cs b/TSOClient/tso.files/RC/DGRP3DMesh.cs index 0077f5bc4..cef659db9 100644 --- a/TSOClient/tso.files/RC/DGRP3DMesh.cs +++ b/TSOClient/tso.files/RC/DGRP3DMesh.cs @@ -12,6 +12,7 @@ using System.IO.Compression; using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace FSO.Files.RC { @@ -117,6 +118,16 @@ public static void RCWorkerLoop() } } + public static void SaveAsync(DGRP3DMesh mesh) + { + mesh.IncrementDataRef(); + QueueWork(() => + { + mesh.Save(); + mesh.DecrementDataRef(); + }); + } + //END STATIC public int Version = CURRENT_VERSION; @@ -134,13 +145,139 @@ public static void RCWorkerLoop() private float MaxAllowedSq = 0.065f * 0.065f; public List BoundPts = new List(); + private List> UnloadedGeoms; + /// + /// Create a DGRP3DMesh from FSOM data. + /// + /// The source stream will be disposed by this method, either immediately or when asset streaming completes. + /// The DGRP this mesh represents + /// Source stream containing FSOM mesh data + /// Graphics device public DGRP3DMesh(DGRP dgrp, Stream source, GraphicsDevice gd) + { + Geoms = new List>(); + if (AssetStreaming.LoadingType > AssetStreamingMode.None) + { + ReconstructVersion = CURRENT_RECONSTRUCT; + Name = "Loading"; + + AssetStreaming.AddLoadingResource(); + + Task.Run(() => + { + try + { + LoadData(dgrp, source, gd); + } + catch + { + AssetStreaming.InStreamUpdate(() => + { + CleanupFailedLoad(dgrp, gd, null); + AssetStreaming.RemoveLoadingResource(); + }); + } + finally + { + source.Dispose(); + } + + AssetStreaming.InStreamUpdate(() => + { + CompleteFSOMLoad(gd); + + AssetStreaming.RemoveLoadingResource(); + }); + }); + } + else + { + try + { + LoadData(dgrp, source, gd); + } + catch (Exception e) + { + + } + finally + { + source.Dispose(); + } + + CompleteFSOMLoad(gd); + } + } + + /// + /// Create a DGRP3DMesh from FSOM data. + /// + /// The DGRP this mesh represents + /// Path to a file containing FSOM mesh data + /// Graphics device + public DGRP3DMesh(DGRP dgrp, string filePath, GraphicsDevice gd) + { + Geoms = new List>(); + if (AssetStreaming.LoadingType > AssetStreamingMode.None) + { + ReconstructVersion = CURRENT_RECONSTRUCT; + Name = "Loading"; + + AssetStreaming.AddLoadingResource(); + + Task.Run(() => + { + // Open the stream in the task, as the file ctor can be a bit expensive. + try + { + using (var source = File.OpenRead(filePath)) + { + LoadData(dgrp, source, gd); + } + } + catch + { + AssetStreaming.InStreamUpdate(() => + { + CleanupFailedLoad(dgrp, gd, filePath); + AssetStreaming.RemoveLoadingResource(); + }); + } + + AssetStreaming.InStreamUpdate(() => + { + CompleteFSOMLoad(gd); + + AssetStreaming.RemoveLoadingResource(); + }); + }); + } + else + { + using (var source = File.OpenRead(filePath)) + { + LoadData(dgrp, source, gd); + } + + CompleteFSOMLoad(gd); + } + } + + private void CleanupFailedLoad(DGRP dgrp, GraphicsDevice gd, string filePath) + { + // TODO: force reconstruction to run + UnloadedGeoms?.Clear(); + CompleteFSOMLoad(gd); + } + + private void LoadData(DGRP dgrp, Stream source, GraphicsDevice gd) { using (var cstream = new GZipStream(source, CompressionMode.Decompress)) { using (var io = IoBuffer.FromStream(cstream, ByteOrder.LITTLE_ENDIAN)) { + var fsom = io.ReadCString(4); Version = io.ReadInt32(); ReconstructVersion = io.ReadInt32(); @@ -149,18 +286,18 @@ public DGRP3DMesh(DGRP dgrp, Stream source, GraphicsDevice gd) Name = io.ReadPascalString(); var geomCount = io.ReadInt32(); - Geoms = new List>(); + UnloadedGeoms = new List>(); for (int i = 0; i < geomCount; i++) { - var d = new Dictionary(); + var d = new List(); var subCount = io.ReadInt32(); for (int j = 0; j < subCount; j++) { var geom = new DGRP3DGeometry(io, dgrp, gd, Version); - if (geom.Pixel == null && geom.PrimCount > 0) throw new Exception("Invalid Mesh! (old format)"); - d.Add(geom.Pixel, geom); + //if (geom.Pixel == null && geom.PrimCount > 0) throw new Exception("Invalid Mesh! (old format)"); //TODO? + d.Add(geom); } - Geoms.Add(d); + UnloadedGeoms.Add(d); } if (Version > 2) @@ -181,6 +318,27 @@ public DGRP3DMesh(DGRP dgrp, Stream source, GraphicsDevice gd) } } + private void CompleteFSOMLoad(GraphicsDevice gd) + { + foreach (var group in UnloadedGeoms) + { + var d = new Dictionary(); + foreach (var geom in group) + { + geom.CompleteFSOMLoad(gd); + + if (geom.Pixel != null) + { + d.Add(geom.Pixel, geom); + } + } + + Geoms.Add(d); + } + + DepthMask?.CompleteFSOMLoad(gd); + } + public string SaveDirectory; public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) @@ -254,6 +412,8 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) var depthB = sprite.GetDepth(); + if (depthB == null) continue; + var useDequantize = false; float[] depth = null; int iterations = 125; @@ -272,15 +432,17 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) } else if (depthB != null) { - depth = depthB.Select(x => x / 255f).ToArray(); iterations = 125; aggressiveness = 3.5f; } - if (depth == null) continue; - QueueWork(() => { + if (depth == null && depthB != null) + { + depth = depthB.Select(x => x / 255f).ToArray(); + } + var boundPts = new List(); //begin async part var w = ((TextureInfo)tex.Tag).Size.X; @@ -335,13 +497,14 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) for (int x = 0; x < w - 1; x++) { //try make a triangle or two - var quad = new int?[] { - QuickTryGet(dict, x+y*w), - QuickTryGet(dict, x+1+y*w), - QuickTryGet(dict, x+1+(y+1)*w), - QuickTryGet(dict, x+(y+1)*w) - }; - var total = quad.Sum(v => (v == null) ? 0 : 1); + int total = 0; + Span quad = [ + QuickTryGet(dict, x+y*w, ref total), + QuickTryGet(dict, x+1+y*w, ref total), + QuickTryGet(dict, x+1+(y+1)*w, ref total), + QuickTryGet(dict, x+(y+1)*w, ref total) + ]; + if (total == 4) { var d1 = Vector3.DistanceSquared(verts[quad[0].Value].Position, verts[quad[2].Value].Position); @@ -483,16 +646,20 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) if (useSimplification) { - var simple = new Simplify(); - simple.vertices = verts.Select(x => new MSVertex() { p = x.Position, t = x.TextureCoordinate }).ToList(); + var vertices = verts.Select(x => new MSVertex() { p = x.Position, t = x.TextureCoordinate }).ToArray(); + var triangles = new MSTriangle[indices.Count / 3]; + int ind = 0; for (int t = 0; t < indices.Count; t += 3) { - simple.triangles.Add(new MSTriangle() + triangles[ind++] = (new MSTriangle() { - v = new int[] { indices[t], indices[t + 1], indices[t + 2] } + v = new MSTriangleIndices(indices[t], indices[t + 1], indices[t + 2]) }); } - simple.simplify_mesh(simple.triangles.Count / triDivisor, agressiveness: aggressiveness, iterations: iterations); + + var simple = new Simplify(triangles, vertices); + + simple.simplify_mesh(triangles.Length / triDivisor, agressiveness: aggressiveness, iterations: iterations); verts = simple.vertices.Select(x => { @@ -507,12 +674,15 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) indices.Clear(); foreach (var t in simple.triangles) { - indices.Add(t.v[0]); - indices.Add(t.v[1]); - indices.Add(t.v[2]); + indices.Add(t.v.i0); + indices.Add(t.v.i1); + indices.Add(t.v.i2); } - GameThread.NextUpdate(x => + var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList(); + DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices); + + AssetStreaming.InStreamUpdate(() => { if (geom.SVerts == null) { @@ -522,8 +692,6 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) var bID = geom.SVerts.Count; foreach (var id in indices) geom.SIndices.Add(id + bID); - var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList(); - DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices); geom.SVerts.AddRange(verts2); lock (this) @@ -534,7 +702,10 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) } else { - GameThread.NextUpdate(x => + var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList(); + DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices); + + AssetStreaming.InStreamUpdate(() => { if (geom.SVerts == null) { @@ -544,8 +715,6 @@ public DGRP3DMesh(DGRP dgrp, OBJD obj, GraphicsDevice gd, string saveDirectory) var baseID = geom.SVerts.Count; foreach (var id in indices) geom.SIndices.Add(id + baseID); - var verts2 = verts.Select(v => new DGRP3DVert(v.Position, Vector3.Zero, v.TextureCoordinate)).ToList(); - DGRP3DVert.GenerateNormals(!sprite.Flip, verts2, indices); geom.SVerts.AddRange(verts2); lock (this) { @@ -612,7 +781,7 @@ private void Complete(GraphicsDevice gd) { Bounds = (BoundPts.Count == 0) ? new BoundingBox() : BoundingBox.CreateFromPoints(BoundPts); BoundPts = null; - Save(); + SaveAsync(this); foreach (var g in Geoms) foreach (var e in g) { @@ -627,8 +796,14 @@ public void Save() Directory.CreateDirectory(SaveDirectory); using (var stream = File.Open(dir, FileMode.Create)) { - using (var cstream = new GZipStream(stream, CompressionMode.Compress)) - Save(cstream); + using (var memstream = new MemoryStream()) + { + Save(memstream); + + memstream.Position = 0; + using (var cstream = new GZipStream(stream, CompressionMode.Compress)) + memstream.CopyTo(cstream); + } } } @@ -718,11 +893,37 @@ public void SaveMTL(Stream stream, string path) } } + public void IncrementDataRef() + { + foreach (var geom in Geoms) + { + foreach (var texGeom in geom.Values) + { + texGeom.IncrementDataRef(); + } + } + } - private int? QuickTryGet(Dictionary dict, int pt) + public void DecrementDataRef() + { + foreach (var geom in Geoms) + { + foreach (var texGeom in geom.Values) + { + texGeom.DecrementDataRef(); + } + } + } + + + private int? QuickTryGet(Dictionary dict, int pt, ref int count) { int result; - if (dict.TryGetValue(pt, out result)) return result; + if (dict.TryGetValue(pt, out result)) + { + count++; + return result; + } return null; } } diff --git a/TSOClient/tso.files/RC/DGRP3DVert.cs b/TSOClient/tso.files/RC/DGRP3DVert.cs index a7ac58597..753ee6d6e 100644 --- a/TSOClient/tso.files/RC/DGRP3DVert.cs +++ b/TSOClient/tso.files/RC/DGRP3DVert.cs @@ -3,7 +3,7 @@ namespace Microsoft.Xna.Framework.Graphics { - [StructLayout(LayoutKind.Sequential, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 32)] public struct DGRP3DVert : IVertexType { public Vector3 Position; @@ -65,9 +65,9 @@ static DGRP3DVert() VertexDeclaration = declaration; } - public static void GenerateNormals(bool invert, List verts, IList indices) + public static void GenerateNormals(bool invert, Span verts, ReadOnlySpan indices) { - for (int i = 0; i < indices.Count; i += 3) + for (int i = 0; i < indices.Length; i += 3) { var v1 = verts[indices[i + 1]].Position - verts[indices[i]].Position; var v2 = verts[indices[i + 2]].Position - verts[indices[i + 1]].Position; @@ -75,20 +75,25 @@ public static void GenerateNormals(bool invert, List verts, IList verts, List indices) + { + GenerateNormals(invert, CollectionsMarshal.AsSpan(verts), CollectionsMarshal.AsSpan(indices)); + } + public static List StripToTri(List ind) { var result = new List(); diff --git a/TSOClient/tso.files/RC/FSO3DCredits.cs b/TSOClient/tso.files/RC/FSO3DCredits.cs new file mode 100644 index 000000000..a6cf8a3ec --- /dev/null +++ b/TSOClient/tso.files/RC/FSO3DCredits.cs @@ -0,0 +1,202 @@ +using FSO.Files.Utils; + +namespace FSO.Files.RC +{ + public enum FSO3DPackageTextureFormat : int + { + Credits, // No resources included. + Png, + Dxt, + } + + public class FSO3DPackageMetadata + { + public string Name; + public string ID; + public string Description; + public string Url; + + // Version 2 (auto updater) + public string ChannelName = ""; + public string PublicKey = ""; + public int Version; + public FSO3DPackageTextureFormat Format; + + public void Read(IoBuffer io, int version) + { + Name = io.ReadVariableLengthPascalString(); + ID = io.ReadVariableLengthPascalString(); + Description = io.ReadVariableLengthPascalString(); + Url = io.ReadVariableLengthPascalString(); + + if (version > 1) + { + ChannelName = io.ReadVariableLengthPascalString(); + PublicKey = io.ReadVariableLengthPascalString(); + Version = io.ReadInt32(); + Format = (FSO3DPackageTextureFormat)io.ReadInt32(); + } + } + + public void Write(IoWriter io) + { + io.WriteVariableLengthPascalString(Name); + io.WriteVariableLengthPascalString(ID); + io.WriteVariableLengthPascalString(Description); + io.WriteVariableLengthPascalString(Url); + + io.WriteVariableLengthPascalString(ChannelName); + io.WriteVariableLengthPascalString(PublicKey); + io.WriteInt32(Version); + io.WriteInt32((int)Format); + } + } + + public class FSO3DGroupMetadata + { + public string Name; + public string Description; + + public void Read(IoBuffer io) + { + Name = io.ReadVariableLengthPascalString(); + Description = io.ReadVariableLengthPascalString(); + } + + public void Write(IoWriter io) + { + io.WriteVariableLengthPascalString(Name); + io.WriteVariableLengthPascalString(Description); + } + } + + public class FSO3DAuthorMetadata + { + public string Name; + public string Description; + + public void Read(IoBuffer io) + { + Name = io.ReadVariableLengthPascalString(); + Description = io.ReadVariableLengthPascalString(); + } + + public void Write(IoWriter io) + { + io.WriteVariableLengthPascalString(Name); + io.WriteVariableLengthPascalString(Description); + } + } + + public class FSO3DCreditsGroup + { + public FSO3DGroupMetadata Metadata; + public List Files; + + public void Read(IoBuffer io) + { + Metadata = new FSO3DGroupMetadata(); + Metadata.Read(io); + + var fileCount = io.ReadInt32(); + Files = new List(fileCount); + for (int i = 0; i < fileCount; i++) + { + Files.Add(new FSO3DRef(io.ReadUInt16(), io.ReadUInt32(), io.ReadUInt32())); + } + } + + public void Write(IoWriter io) + { + Metadata.Write(io); + + io.WriteInt32(Files.Count); + foreach (var file in Files) + { + io.WriteUInt16(file.ID); + io.WriteUInt32(file.FileID); + io.WriteUInt32(file.TypeID); + } + } + } + + public class FSO3DCreditsAuthor + { + public FSO3DAuthorMetadata Metadata; + public List Groups; + + public void Read(IoBuffer io) + { + Metadata = new FSO3DAuthorMetadata(); + Metadata.Read(io); + + var groupCount = io.ReadInt32(); + Groups = []; + for (int i = 0; i < groupCount; i++) + { + var group = new FSO3DCreditsGroup(); + group.Read(io); + Groups.Add(group); + } + } + + public void Write(IoWriter io) + { + Metadata.Write(io); + + io.WriteInt32(Groups.Count); + foreach (var group in Groups) + { + group.Write(io); + } + } + } + + public class FSO3DCredits + { + private const int CURRENT_VERSION = 2; + + public int Version = CURRENT_VERSION; + public FSO3DPackageMetadata Metadata; + public List Authors; + + public void Read(Stream stream) + { + using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + var fdir = io.ReadCString(4); + if (fdir != "fCRE") throw new Exception("Invalid FSO3DCredits!"); + Version = io.ReadInt32(); + + Metadata = new FSO3DPackageMetadata(); + Metadata.Read(io, Version); + + int authorCount = io.ReadInt32(); + Authors = []; + for (int i = 0; i < authorCount; i++) + { + var author = new FSO3DCreditsAuthor(); + author.Read(io); + Authors.Add(author); + } + } + } + + public void Write(Stream stream) + { + using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + io.WriteCString("fCRE", 4); + io.WriteInt32(CURRENT_VERSION); + + Metadata.Write(io); + + io.WriteInt32(Authors.Count); + foreach (var author in Authors) + { + author.Write(io); + } + } + } + } +} diff --git a/TSOClient/tso.files/RC/FSO3DDirectory.cs b/TSOClient/tso.files/RC/FSO3DDirectory.cs new file mode 100644 index 000000000..e0602c672 --- /dev/null +++ b/TSOClient/tso.files/RC/FSO3DDirectory.cs @@ -0,0 +1,130 @@ +using FSO.Files.Utils; +using System.Diagnostics.CodeAnalysis; + +namespace FSO.Files.RC +{ + public struct FSO3DRef(ushort id, uint fileID, uint typeID) : IEquatable + { + public ushort ID = id; + public uint FileID = fileID; + public uint TypeID = typeID; + + public bool Equals(FSO3DRef other) + { + return (FileID == other.FileID && TypeID == other.TypeID && ID == other.ID); + } + + public override bool Equals([NotNullWhen(true)] object obj) + { + return obj is FSO3DRef oRef && Equals(oRef); + } + + public override readonly int GetHashCode() + { + return HashCode.Combine(ID, FileID, TypeID); + } + + public static bool operator ==(FSO3DRef left, FSO3DRef right) + { + return left.Equals(right); + } + + public static bool operator !=(FSO3DRef left, FSO3DRef right) + { + return !(left == right); + } + } + + public class FSO3DDirectoryEntry + { + public int ID; + public string Filename; + public Dictionary Meshes; + public Dictionary Textures; + + public void Read(IoBuffer io) + { + ID = io.ReadInt32(); + Filename = io.ReadVariableLengthPascalString(); + + var meshCount = io.ReadInt32(); + Meshes = new Dictionary(meshCount); + for (int i = 0; i < meshCount; i++) + { + var mesh = new FSO3DRef(io.ReadUInt16(), io.ReadUInt32(), io.ReadUInt32()); + Meshes[mesh.ID] = mesh; + } + + var textureCount = io.ReadInt32(); + Textures = new Dictionary(textureCount); + for (int i = 0; i < textureCount; i++) + { + var tex = new FSO3DRef(io.ReadUInt16(), io.ReadUInt32(), io.ReadUInt32()); + Textures[tex.ID] = tex; + } + } + + public void Write(IoWriter io) + { + io.WriteInt32(ID); + io.WriteVariableLengthPascalString(Filename); + + io.WriteInt32(Meshes.Count); + foreach (var mesh in Meshes.Values) + { + io.WriteUInt16(mesh.ID); + io.WriteUInt32(mesh.FileID); + io.WriteUInt32(mesh.TypeID); + } + + io.WriteInt32(Textures.Count); + foreach (var tex in Textures.Values) + { + io.WriteUInt16(tex.ID); + io.WriteUInt32(tex.FileID); + io.WriteUInt32(tex.TypeID); + } + } + } + + public class FSO3DDirectory + { + private const int CURRENT_VERSION = 1; + + public int Version = CURRENT_VERSION; + public Dictionary Entries; + + public void Read(Stream stream) + { + using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + var fdir = io.ReadCString(4); + if (fdir != "fDIR") throw new Exception("Invalid FSO3DDirectory!"); + Version = io.ReadInt32(); + + int entryCount = io.ReadInt32(); + Entries = new Dictionary(entryCount); + for (int i = 0; i < entryCount; i++) + { + var entry = new FSO3DDirectoryEntry(); + entry.Read(io); + Entries[entry.Filename] = entry; + } + } + } + + public void Write(Stream stream) + { + using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + io.WriteCString("fDIR", 4); + io.WriteInt32(Version); + io.WriteInt32(Entries.Count); + foreach (var entry in Entries.Values) + { + entry.Write(io); + } + } + } + } +} diff --git a/TSOClient/tso.files/RC/FSOF.cs b/TSOClient/tso.files/RC/FSOF.cs index f91580acf..4799cb18b 100644 --- a/TSOClient/tso.files/RC/FSOF.cs +++ b/TSOClient/tso.files/RC/FSOF.cs @@ -13,6 +13,18 @@ namespace FSO.Files.RC { public class FSOF { + // Constants for FSOF server validation + private const int EXPECTED_FLOOR_WIDTH = 384; + private const int EXPECTED_FLOOR_HEIGHT = 256; + private const int EXPECTED_WALL_WIDTH = 512; + private const int MAX_WALL_HEIGHT = 4096; + + private const int MAX_FLOOR_TRIS = 10000; + private const int MAX_WALL_TRIS = 100000; + + private const float MAX_XZ_POSITION = 77; + private const float MAX_Y_POSITION = 300; + public int TexCompressionType; //RGBA8, DXT5 public int FloorWidth; @@ -100,6 +112,98 @@ public void Save(Stream stream) } } + public void ValidateFSO(Stream stream) + { + using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) + { + var fsof = io.ReadCString(4); + if (fsof != "FSOf") throw new Exception("Invalid FSOf!"); + Version = io.ReadInt32(); + Compressed = io.ReadByte() > 0; + + if (Version < 0 || Version > CURRENT_VERSION) + { + throw new Exception("Unknown FSOF Version"); + } + + GZipStream compressed = null; + var cio = io; + if (Compressed) + { + compressed = new GZipStream(stream, CompressionMode.Decompress); + cio = IoBuffer.FromStream(compressed, ByteOrder.LITTLE_ENDIAN); + } + + TexCompressionType = cio.ReadInt32(); + if (!(TexCompressionType == 1)) + { + throw new Exception("FSO compression must be 1 (DXT)"); + } + FloorWidth = cio.ReadInt32(); + FloorHeight = cio.ReadInt32(); + + if (!(FloorWidth == EXPECTED_FLOOR_WIDTH && FloorHeight == EXPECTED_FLOOR_HEIGHT)) + { + throw new Exception("Unexpected dimensions for floor texture"); + } + + WallWidth = cio.ReadInt32(); + WallHeight = cio.ReadInt32(); + + if (!(WallWidth == EXPECTED_WALL_WIDTH && WallHeight <= MAX_WALL_HEIGHT)) + { + throw new Exception("Unexpected dimensions for wall texture"); + } + + var hasNight = cio.ReadByte() > 0; + + if (!hasNight) + { + throw new Exception("Must have night texture"); + } + + var floorTSize = cio.ReadInt32(); + cio.ReadBytes(floorTSize); + var wallTSize = cio.ReadInt32(); + cio.ReadBytes(wallTSize); + + if (!(floorTSize == DXTSize(FloorWidth, FloorHeight) && wallTSize == DXTSize(WallWidth, WallHeight))) + { + throw new Exception("Day wall/floor have incorrect size for DXT"); + } + + if (hasNight) + { + floorTSize = cio.ReadInt32(); + cio.ReadBytes(floorTSize); + wallTSize = cio.ReadInt32(); + cio.ReadBytes(wallTSize); + + if (!(floorTSize == DXTSize(FloorWidth, FloorHeight) && wallTSize == DXTSize(WallWidth, WallHeight))) + { + throw new Exception("Night wall/floor have incorrect size for DXT"); + } + + NightLightColor = new Color(cio.ReadUInt32()); + } + + var floor = ValidateVerts(cio, MAX_FLOOR_TRIS * 3, MAX_FLOOR_TRIS * 3); + FloorVertices = floor.Item1; + FloorIndices = floor.Item2; + var wall = ValidateVerts(cio, MAX_WALL_TRIS * 3, MAX_WALL_TRIS * 3); + WallVertices = wall.Item1; + WallIndices = wall.Item2; + } + } + + private static int DXTSize(int width, int height) + { + width = ((width + 3) >> 2) << 2; + height = ((height + 3) >> 2) << 2; + + return width * height; + } + public void Read(Stream stream) { using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN)) @@ -216,17 +320,85 @@ public void Dispose() WallIGPU?.Dispose(); } + private static Tuple ValidateVerts(IoBuffer io, int maxVerts, int maxIndices) + { + var vertCount = io.ReadInt32(); + if (vertCount > maxVerts) + { + throw new Exception("Too many vertices"); + } + + var readVerts = ReadArray(io, vertCount); + + var indCount = io.ReadInt32(); + if (indCount > maxIndices) + { + throw new Exception("Too many indices"); + } + + var indices = ReadArray(io, indCount); + + bool valid = true; + foreach (int ind in indices) + { + if (ind < 0 || ind >= vertCount) + { + valid = false; + break; + } + } + + if (!valid) + { + throw new Exception("Indices go out of bounds"); + } + + // Basic dimensions check + + float maxNormalMagnitude = 1.01f * 1.01f; + + foreach (var vert in readVerts) + { + if (float.IsNaN(vert.Position.X) || float.IsNaN(vert.Position.Y) || float.IsNaN(vert.Position.Z) || float.IsNaN(vert.Normal.X) || float.IsNaN(vert.Normal.Y) || float.IsNaN(vert.Normal.Z) || float.IsNaN(vert.TextureCoordinate.X) || float.IsNaN(vert.TextureCoordinate.Y)) + { + valid = false; + break; + } + + if (vert.Position.X < 0 || vert.Position.Z < 0 || vert.Position.X > MAX_XZ_POSITION || vert.Position.Z > MAX_XZ_POSITION || vert.Position.Y < -MAX_Y_POSITION || vert.Position.Y > MAX_Y_POSITION) + { + valid = false; + break; + } + + if (vert.Normal.LengthSquared() > maxNormalMagnitude) + { + valid = false; + break; + } + + if (vert.TextureCoordinate.X < 0.0 || vert.TextureCoordinate.X > 1.0 || vert.TextureCoordinate.Y < 0.0 || vert.TextureCoordinate.Y > 1.0) + { + valid = false; + break; + } + } + + if (!valid) + { + throw new Exception("Vertex data out of range"); + } + + return new Tuple(readVerts, indices); + } + private Tuple ReadVerts(IoBuffer io) { var vertCount = io.ReadInt32(); - var bytes = io.ReadBytes(vertCount * Marshal.SizeOf(typeof(DGRP3DVert))); - var readVerts = new DGRP3DVert[vertCount]; - var pinnedHandle = GCHandle.Alloc(readVerts, GCHandleType.Pinned); - Marshal.Copy(bytes, 0, pinnedHandle.AddrOfPinnedObject(), bytes.Length); - pinnedHandle.Free(); + var readVerts = ReadArray(io, vertCount); var indCount = io.ReadInt32(); - var indices = ToTArray(io.ReadBytes(indCount * 4)); + var indices = ReadArray(io, indCount); return new Tuple(readVerts, indices); } @@ -234,34 +406,27 @@ private Tuple ReadVerts(IoBuffer io) private void WriteVerts(DGRP3DVert[] verts, int[] indices, IoWriter io) { io.WriteInt32(verts.Length); - foreach (var vert in verts) - { - io.WriteFloat(vert.Position.X); - io.WriteFloat(vert.Position.Y); - io.WriteFloat(vert.Position.Z); - io.WriteFloat(vert.TextureCoordinate.X); - io.WriteFloat(vert.TextureCoordinate.Y); - io.WriteFloat(vert.Normal.X); - io.WriteFloat(vert.Normal.Y); - io.WriteFloat(vert.Normal.Z); - } + WriteArray(io, verts); io.WriteInt32(indices.Length); - io.WriteBytes(ToByteArray(indices.ToArray())); + WriteArray(io, indices); } - private static T[] ToTArray(byte[] input) + public static T[] ReadArray(IoBuffer reader, int size) where T : unmanaged { - var result = new T[input.Length / Marshal.SizeOf(typeof(T))]; - Buffer.BlockCopy(input, 0, result, 0, input.Length); + var result = new T[size]; + var bytes = MemoryMarshal.Cast(result); + + reader.ReadBytes(bytes); + return result; } - private static byte[] ToByteArray(T[] input) + public static void WriteArray(IoWriter writer, T[] data) where T : unmanaged { - var result = new byte[input.Length * Marshal.SizeOf(typeof(T))]; - Buffer.BlockCopy(input, 0, result, 0, result.Length); - return result; + var bytes = MemoryMarshal.Cast(data); + + writer.WriteBytes(bytes); } } } diff --git a/TSOClient/tso.files/RC/IDGRP3DTextureHolder.cs b/TSOClient/tso.files/RC/IDGRP3DTextureHolder.cs new file mode 100644 index 000000000..ed43e1ce7 --- /dev/null +++ b/TSOClient/tso.files/RC/IDGRP3DTextureHolder.cs @@ -0,0 +1,10 @@ +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.Files.RC +{ + public interface IDGRP3DTextureHolder + { + void Decode(GraphicsDevice gd); + Texture2D GetTexture(GraphicsDevice gd); + } +} diff --git a/TSOClient/tso.files/Tuning.cs b/TSOClient/tso.files/Tuning.cs index 4c7a85ec2..3bcad326c 100644 --- a/TSOClient/tso.files/Tuning.cs +++ b/TSOClient/tso.files/Tuning.cs @@ -4,6 +4,7 @@ using System.Text; using FSO.Files.FAR3; using System.Globalization; +using FSO.Common; namespace FSO.Files { @@ -57,8 +58,14 @@ public Tuning(byte[] Data) } public Tuning(string Path) - { - try + { + if (FSOEnvironment.MissingTSO) + { + // Don't try to load the tuning. + return; + } + + try { m_Reader = new BinaryReader(File.OpenRead(Path)); } diff --git a/TSOClient/tso.files/UTK/UTKFile2.cs b/TSOClient/tso.files/UTK/UTKFile2.cs index f23671d02..e02e56bd0 100644 --- a/TSOClient/tso.files/UTK/UTKFile2.cs +++ b/TSOClient/tso.files/UTK/UTKFile2.cs @@ -1,7 +1,4 @@ -using System; -using System.IO; - -namespace FSO.Files.UTK +namespace FSO.Files.UTK { /// /// Represents a *.UTK file. @@ -95,10 +92,17 @@ public class UTKFile2 private int m_UnreadBitsValue, m_UnreadBitsCount; private bool m_HalvedExcitation; private byte m_VoicedThreshold; - private float[] m_InnovationPower = new float[64]; - private float[] m_RC = new float[12]; - private float[] m_History = new float[12]; - private float[] m_DecompressedFrame = new float[756]; + private readonly float[] m_InnovationPower = new float[64]; + private readonly float[] m_RC = new float[12]; + private readonly float[] m_History = new float[12]; + private readonly float[] m_DecompressedFrame = new float[756]; + + // Work buffers to prevent allocation for each frame + private readonly float[] m_Excitation = new float[118]; //includes 5 0-valued samples to both the left and the right. + private readonly float[] m_RCDelta = new float[12]; + private readonly float[] m_LPC = new float[12]; + private readonly float[] m_RCTemp = new float[12]; + private readonly float[] m_LPCTemp = new float[12]; /// /// Returns a decompressed wav stream. @@ -196,36 +200,6 @@ private byte ReadBits(byte Bits) return Value; } - /// - /// Finds the lesser of two ints. - /// - /// The first value. - /// The second value. - /// The lesser of the two ints. - private int Lesser(int A, int B) - { - return (A < B ? A : B); - } - - /// - /// Clamps a value between an upper and lower bound. - /// - /// The type of the value to clamp. - /// The value to clamp. - /// Upper bound. - /// Lower bound. - /// - public static T Clamp(T value, T max, T min) - where T : System.IComparable - { - T result = value; - if (value.CompareTo(max) < 0) - result = max; - if (value.CompareTo(min) > 0) - result = min; - return result; - } - /// /// Decodes the UTK data in this file. /// @@ -235,13 +209,13 @@ public void UTKDecode() while (Frames > 0) { - int BlockSize = Lesser((int)Frames, 432); + int BlockSize = Math.Min((int)Frames, 432); DecodeFrame(); for (int i = 0; i < BlockSize; i++) { int Value = (int)Math.Round(m_DecompressedFrame[324 + i]); - Value = Clamp(Value, -32768, 32767); + Value = Math.Clamp(Value, -32768, 32767); m_Writer.Write((ushort)Value); } @@ -255,8 +229,8 @@ public void UTKDecode() private void DecodeFrame() { - float[] Excitation = new float[118]; //includes 5 0-valued samples to both the left and the right. - float[] RCDelta = new float[12]; + float[] Excitation = m_Excitation; //includes 5 0-valued samples to both the left and the right. + float[] RCDelta = m_RCDelta; bool Voiced = false; for (int i = 0; i < 12; i++) @@ -281,7 +255,7 @@ private void DecodeFrame() if (m_HalvedExcitation == false) { - GenerateExcitation(5, ref Excitation, Voiced, 1); + GenerateExcitation(5, Excitation, Voiced, 1); } else { @@ -290,7 +264,7 @@ private void DecodeFrame() bool FillWithZero = (ReadBits(1) != 0); int Offset = 5 + (1 - Alignment); - GenerateExcitation(5 + Alignment, ref Excitation, Voiced, 2); + GenerateExcitation(5 + Alignment, Excitation, Voiced, 2); if (FillWithZero) { @@ -326,7 +300,7 @@ private void DecodeFrame() } } - private void GenerateExcitation(int Offset, ref float[] Excitation, bool Voiced, int Interval) + private void GenerateExcitation(int Offset, float[] Excitation, bool Voiced, int Interval) { if (Voiced) { @@ -353,7 +327,7 @@ private void GenerateExcitation(int Offset, ref float[] Excitation, bool Voiced, { //Fill between 7 and 70 samples with 0s int x = ReadBits(6) + 7; - x = Lesser(x, (Offset + 108 - i) / Interval); + x = Math.Min(x, (Offset + 108 - i) / Interval); while (x > 0) { @@ -391,9 +365,9 @@ private void GenerateExcitation(int Offset, ref float[] Excitation, bool Voiced, private void Synthesize(int Sample, int Samples) { - float[] LPC = new float[12]; + float[] LPC = m_LPC; int offset = -1; - RCtoLPC(ref m_RC, ref LPC); + RCtoLPC(m_RC, LPC); while (Samples > 0) { @@ -408,10 +382,10 @@ private void Synthesize(int Sample, int Samples) } } - private void RCtoLPC(ref float[] RC, ref float[] LPC) + private void RCtoLPC(float[] RC, float[] LPC) { int i, j; - float[] RCTemp = new float[12], LPCTemp = new float[12]; + float[] RCTemp = m_RCTemp, LPCTemp = m_LPCTemp; RCTemp[0] = 1.0f; Array.Copy(RC, 0, RCTemp, 1, 11); diff --git a/TSOClient/tso.files/Utils/DiffGenerator.cs b/TSOClient/tso.files/Utils/DiffGenerator.cs index 28cd99fcf..3ee276675 100644 --- a/TSOClient/tso.files/Utils/DiffGenerator.cs +++ b/TSOClient/tso.files/Utils/DiffGenerator.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; -using xxHashSharp; +using System.IO.Hashing; namespace FSO.Files.Utils { @@ -39,14 +39,14 @@ public static List GetDiffs(string sourcePath, string destPath) foreach (var removed in removedFiles) { var bytes = GetFileBytes(Path.Combine(sourcePath, removed)); - var hash = xxHash.CalculateHash(bytes).ToString("x8"); + var hash = XxHash32.HashToUInt32(bytes).ToString("x8"); diffs.Add(new FileDiff(FileDiffType.Remove, removed, hash, null)); } foreach (var added in addFiles) { var bytes = GetFileBytes(Path.Combine(destPath, added)); - var hash = xxHash.CalculateHash(bytes).ToString("x8"); + var hash = XxHash32.HashToUInt32(bytes).ToString("x8"); diffs.Add(new FileDiff(FileDiffType.Add, added, null, hash)); } @@ -54,8 +54,8 @@ public static List GetDiffs(string sourcePath, string destPath) { var bytesBefore = GetFileBytes(Path.Combine(sourcePath, same)); var bytesAfter = GetFileBytes(Path.Combine(destPath, same)); - var hashBefore = xxHash.CalculateHash(bytesBefore).ToString("x8"); - var hashAfter = xxHash.CalculateHash(bytesAfter).ToString("x8"); + var hashBefore = XxHash32.HashToUInt32(bytesBefore).ToString("x8"); + var hashAfter = XxHash32.HashToUInt32(bytesAfter).ToString("x8"); diffs.Add(new FileDiff( (hashBefore == hashAfter) ? FileDiffType.Unchanged : FileDiffType.Modify, same, hashBefore, hashAfter)); diff --git a/TSOClient/tso.files/Utils/IoBuffer.cs b/TSOClient/tso.files/Utils/IoBuffer.cs index 09dc5084b..740ca504d 100644 --- a/TSOClient/tso.files/Utils/IoBuffer.cs +++ b/TSOClient/tso.files/Utils/IoBuffer.cs @@ -211,6 +211,15 @@ public byte[] ReadBytes(uint num) return Reader.ReadBytes((int)num); } + /// + /// Reads a span of bytes from the current stream. + /// + /// Byte span to read into + public void ReadBytes(Span output) + { + Reader.BaseStream.ReadExactly(output); + } + /// /// Reads a number of bytes from the current stream. /// @@ -237,7 +246,11 @@ public string ReadLongPascalString() /// A string. public string ReadNullTerminatedString() { + var first = (char)Reader.ReadByte(); + if (first == '\0') return ""; + var sb = new StringBuilder(); + sb.Append(first); while (true){ char ch = (char)Reader.ReadByte(); if (ch == '\0'){ @@ -250,7 +263,11 @@ public string ReadNullTerminatedString() public string ReadNullTerminatedUTF8() { + var first = Reader.ReadByte(); + if (first == 0) return ""; + var sb = new List(); + sb.Add(first); while (true) { var b = Reader.ReadByte(); diff --git a/TSOClient/tso.files/Utils/IoWriter.cs b/TSOClient/tso.files/Utils/IoWriter.cs index f0700d59f..44e974809 100644 --- a/TSOClient/tso.files/Utils/IoWriter.cs +++ b/TSOClient/tso.files/Utils/IoWriter.cs @@ -170,6 +170,15 @@ public void WriteBytes(byte[] bytes) Writer.Write(bytes); } + /// + /// Writes a number of bytes to the current stream. + /// + /// Bytes to write out. + public void WriteBytes(ReadOnlySpan bytes) + { + Writer.Write(bytes); + } + /// /// Writes a pascal string to the current stream, which is prefixed by a 16bit short. /// diff --git a/TSOClient/tso.files/XA/XAFile.cs b/TSOClient/tso.files/XA/XAFile.cs index 295b6e811..8fd649dd3 100644 --- a/TSOClient/tso.files/XA/XAFile.cs +++ b/TSOClient/tso.files/XA/XAFile.cs @@ -1,6 +1,4 @@ -using System.IO; - -namespace FSO.Files.XA +namespace FSO.Files.XA { public enum SoundType { @@ -137,16 +135,20 @@ public void DecompressFile() if (m_Channels == 1) //Mono { + var buffer = new byte[0xF]; while (m_Reader.BaseStream.Position < m_Reader.BaseStream.Length) { - DecompressMono(m_Reader.ReadBytes(0xF)); + m_Reader.Read(buffer); + DecompressMono(buffer); } } else if (m_Channels == 2) //Stereo { + var buffer = new byte[0x1E]; while (m_Reader.BaseStream.Position < m_Reader.BaseStream.Length) { - DecompressStereo(m_Reader.ReadBytes(0x1E)); + m_Reader.Read(buffer); + DecompressStereo(buffer); } } } diff --git a/TSOClient/tso.files/app.config b/TSOClient/tso.files/app.config deleted file mode 100644 index 4f3764928..000000000 --- a/TSOClient/tso.files/app.config +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/TSOClient/tso.files/packages.config b/TSOClient/tso.files/packages.config deleted file mode 100644 index 3a1465a6e..000000000 --- a/TSOClient/tso.files/packages.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.simantics/Engine/Routing/VMRectRouter.cs b/TSOClient/tso.simantics/Engine/Routing/VMRectRouter.cs index 3084835a0..dcb02f0da 100644 --- a/TSOClient/tso.simantics/Engine/Routing/VMRectRouter.cs +++ b/TSOClient/tso.simantics/Engine/Routing/VMRectRouter.cs @@ -17,7 +17,7 @@ public VMRectRouter(VMObstacleSet map) public LinkedList Route(Point from, Point to, int startCardinal) { - var openSet = new List(); + var openSet = new PriorityQueue(); var startRect = new VMWalkableRect(from.X, from.Y, from.X, from.Y); ConstructFirstFree(startRect); @@ -32,12 +32,12 @@ public LinkedList Route(Point from, Point to, int startCardinal) startRect.ParentSourceHiP = new Point(from.X * 0x8000, from.Y * 0x8000); startRect.OriginalG = 0; - openSet.Add(startRect); + openSet.Enqueue(startRect, startRect.FScore); while (openSet.Count > 0) { - var current = openSet[0]; - openSet.RemoveAt(0); + var current = openSet.Dequeue(); + if (current.State == 2) continue; // skip stale entries (lazy deletion) if (current.Contains(to)) { @@ -83,15 +83,7 @@ public LinkedList Route(Point from, Point to, int startCardinal) r.GScore = newGScore; r.FScore = newGScore + PointDist(closest, to); - if (newcomer) - { - OpenSetSortedInsert(openSet, r); - } - else - { - openSet.Remove(r); - OpenSetSortedInsert(openSet, r); - } + openSet.Enqueue(r, r.FScore); } } } @@ -231,19 +223,6 @@ private int PointDist(Point pt1, Point pt2) return (int)Math.Sqrt(diff.X * diff.X + diff.Y * diff.Y); } - private void OpenSetSortedInsert(List set, VMWalkableRect item) - { - for (var i = 0; i < set.Count; i++) - { - if (set[i].FScore > item.FScore) - { - set.Insert(i, item); - return; - } - } - set.Add(item); - } - private Point RectIntersect(VMObstacle r1, VMObstacle r2, Point destPoint) { diff --git a/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTS1GlobalLinkStub.cs b/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTS1GlobalLinkStub.cs index 4e1c432eb..685471ecf 100644 --- a/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTS1GlobalLinkStub.cs +++ b/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTS1GlobalLinkStub.cs @@ -167,8 +167,8 @@ public void Tick(VM vm) public void LeaveLot(VM vm, VMAvatar avatar) { - //TODO: in the global server, this will save the avatar (and possibly lot) states and send back to server. - if (avatar.PersistID == vm.MyUID) + // Disconnect immediately on the client, at least if they aren't fast forwarding to the current lot state. + if (avatar.PersistID == vm.MyUID && !vm.Driver.RunningCatchup) { //stub has some functionality here. if we have left lot, disconnect. vm.CloseNet(VMCloseNetReason.LeaveLot); diff --git a/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTSOGlobalLinkStub.cs b/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTSOGlobalLinkStub.cs index 37feee4a4..bd13347de 100644 --- a/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTSOGlobalLinkStub.cs +++ b/TSOClient/tso.simantics/Engine/TSOGlobalLink/VMTSOGlobalLinkStub.cs @@ -138,8 +138,8 @@ public void Tick(VM vm) public void LeaveLot(VM vm, VMAvatar avatar) { - //TODO: in the global server, this will save the avatar (and possibly lot) states and send back to server. - if (avatar.PersistID == vm.MyUID) + // Disconnect immediately on the client, at least if they aren't fast forwarding to the current lot state. + if (avatar.PersistID == vm.MyUID && !vm.Driver.RunningCatchup) { //stub has some functionality here. if we have left lot, disconnect. vm.CloseNet(VMCloseNetReason.LeaveLot); diff --git a/TSOClient/tso.simantics/Engine/VMAmbientSound.cs b/TSOClient/tso.simantics/Engine/VMAmbientSound.cs index 0cc0e73ee..d0617517a 100644 --- a/TSOClient/tso.simantics/Engine/VMAmbientSound.cs +++ b/TSOClient/tso.simantics/Engine/VMAmbientSound.cs @@ -1,12 +1,95 @@ -using System.Collections.Generic; +using FSO.Common; +using FSO.Content; +using FSO.Content.Model; using FSO.HIT; +using FSO.LotView; +using FSO.LotView.Model; +using FSO.SimAntics.Model.TSOPlatform; +using System.Diagnostics; namespace FSO.SimAntics.Engine { + [Flags] + public enum VMAmbientSoundType: ulong + { + AnimalsSongBirds = 1 << 0, + MechanicalExplosions = 1 << 1, + AnimalsFarm = 1 << 2, + MechanicalGunshot = 1 << 3, + MechanicalPlanes = 1 << 4, + WeatherLightingThunder = 1 << 5, + + LoopBrook = 1 << 6, + LoopCrowd = 1 << 7, + LoopHeartbeat = 1 << 8, + LoopIndoor = 1 << 9, + LoopInsects = 1 << 10, + LoopOcean = 1 << 11, + LoopOutdoor = 1 << 12, + LoopRain = 1 << 13, + LoopTechno = 1 << 14, + LoopStorm = 1 << 15, + LoopTraffic = 1 << 16, + LoopWind = 1 << 17, + + WeatherBreeze = 1 << 18, + MechanicalConstruction = 1 << 19, + AnimalsDog = 1 << 20, + MechanicalDriveBy = 1 << 21, + WeatherHowlingWind = 1 << 22, + MechanicalIndustrial = 1 << 23, + AnimalsInsects = 1 << 24, + AnimalsJungle = 1 << 25, + PeopleOffice = 1 << 26, + PeopleRestaurant = 1 << 27, + MechanicalSciBleeps = 1 << 28, + MechanicalSirens = 1 << 29, + AnimalsWolf = 1 << 30, + AnimalsSeaBirds = 1ul << 31, + WeatherRainDrops = 1ul << 32, + PeopleMagic = 1ul << 33, + MechanicalSmallMachines = 1ul << 34, + PeopleScreams = 1ul << 35, + AnimalsNightBirds = 1ul << 36, + PeopleGym = 1ul << 37, + PeopleGhost = 1ul << 38, + + TS1Ambience = 1 << 0, + TS1NightLoop = 1 << 1 + } + public class VMAmbientSound { + private const VMAmbientSoundType AutoWeatherTypes = VMAmbientSoundType.LoopRain | VMAmbientSoundType.WeatherLightingThunder; + private const VMAmbientSoundType AutoLocationTypes = + VMAmbientSoundType.LoopOcean | + VMAmbientSoundType.LoopBrook | + VMAmbientSoundType.LoopInsects | + VMAmbientSoundType.LoopOutdoor | + VMAmbientSoundType.WeatherHowlingWind | + VMAmbientSoundType.AnimalsSongBirds | + VMAmbientSoundType.AnimalsSeaBirds | + VMAmbientSoundType.AnimalsNightBirds; + private const VMAmbientSoundType AllLoops = + VMAmbientSoundType.LoopBrook | + VMAmbientSoundType.LoopRain | + VMAmbientSoundType.LoopCrowd | + VMAmbientSoundType.LoopHeartbeat | + VMAmbientSoundType.LoopIndoor | + VMAmbientSoundType.LoopOutdoor | + VMAmbientSoundType.LoopInsects | + VMAmbientSoundType.LoopStorm | + VMAmbientSoundType.LoopOcean | + VMAmbientSoundType.LoopTechno | + VMAmbientSoundType.LoopTraffic; + + private const float AmbienceTransitionTime = 6; + private const float AmbienceDuckTime = 0.33f; + private const float IndoorsVolumeDuck = 0.4f; + private static VMAmbientSound ToTransition; + public static bool ForceDisable; - public static Dictionary AmbienceByGUID = new Dictionary() //may want to load this from ambience.ini in future... + private static Dictionary TSOAmbienceByGUID = new Dictionary() //may want to load this from ambience.ini in future... { {0x3dd887a6, new Ambience("sounddata/ambience/daybirds/daybirds.fsc", false)}, {0x3dd887aa, new Ambience("sounddata/ambience/explosions/explosions.fsc", false)}, @@ -52,7 +135,7 @@ public class VMAmbientSound {0x1e0bc2b5, new Ambience("sounddata/ambience/loops/wind_lp.xa", true)} }; - public static List SoundByBitField = new List() { + private static List TSOSoundByBitField = new List() { new VMCategorisedAmb(0x3dd887a6, 0, "AnimalsSongBirds"), new VMCategorisedAmb(0x3dd887aa, 1, "MechanicalExplosions"), new VMCategorisedAmb(0x7dd887ad, 0, "AnimalsFarm"), @@ -96,9 +179,38 @@ public class VMAmbientSound new VMCategorisedAmb(0xa9b9653e, 3, "PeopleGhost") }; + private static Dictionary TS1AmbienceByGUID = new Dictionary() + { + {0x00000001, new Ambience("sounddata/outdoors/sim_amb.fsc", false)}, + + //Loops + + {0x00000002, new Ambience("sounddata/outdoors/nite_loop.xa", true)}, + }; + + private static List TS1SoundByBitField = new List() { + new VMCategorisedAmb(0x00000001, 0, "TS1Ambience", 2f), + new VMCategorisedAmb(0x00000002, 1, "TS1NightLoop", 0.6f), + }; + + public Dictionary AmbienceByGUID => TS1 ? TS1AmbienceByGUID : TSOAmbienceByGUID; + public List SoundByBitField => TS1 ? TS1SoundByBitField : TSOSoundByBitField; + + public Dictionary ActiveSounds; - public ulong ActiveBits; - public VMCategorisedAmb? ActiveLoop; + public VMAmbientSoundType UserBits; + public VMAmbientSoundType ActiveBits; + public float Volume = 0; + public long LastTimestamp = Stopwatch.GetTimestamp(); + + private VMAmbientSoundType AutoBaseBits; + private TerrainType BaseTerrain = TerrainType.GRASS; + private bool Paused; + private int UserCount; + + private float VolumeDuck = 1f; + private float TargetVolumeDuck = 1f; + private bool TS1; /// /// Handles ambient sound in lots. @@ -106,7 +218,191 @@ public class VMAmbientSound /// public VMAmbientSound() { + UserCount = 1; ActiveSounds = new Dictionary(); + TS1 = Content.Content.Get().TS1; + } + + public static VMAmbientSound TryTransition() + { + if (VM.UseWorld && ToTransition != null) + { + var trans = ToTransition; + + ToTransition = null; + + return trans; + } + + return new VMAmbientSound(); + } + + public void InitAutoBase(VM vm) + { + AutoBaseBits = TS1 ? VMAmbientSoundType.TS1Ambience : 0; + + if (vm.PlatformState is VMTSOLotState lot) + { + BaseTerrain = lot.Terrain.BlendN[1, 1].Base; + + var height = (lot.Terrain.Height[1, 1] + lot.Terrain.Height[1, 2] + lot.Terrain.Height[2, 1] + lot.Terrain.Height[2, 2]) / 4; + + bool hasWaterSurround = false; + foreach (var blend in lot.Terrain.BlendN) + { + if (blend.Base == TerrainType.WATER) + { + hasWaterSurround = true; + } + } + + if (hasWaterSurround) + { + if ((BaseTerrain == TerrainType.SAND && height < 20) || (BaseTerrain == TerrainType.WATER && height < 10)) + { + AutoBaseBits |= VMAmbientSoundType.LoopOcean; + } + else + { + AutoBaseBits |= VMAmbientSoundType.LoopBrook; + } + } + else + { + AutoBaseBits |= VMAmbientSoundType.LoopOutdoor; + } + + if (height > 128) + { + AutoBaseBits |= VMAmbientSoundType.WeatherHowlingWind; + } + } + } + + public VMAmbientSoundType EvaluateAutoAmbience(VM vm) + { + var clock = vm.Context.Clock; + + if (TS1) + { + float dayPct = (clock.Hours + clock.Minutes / 60f) / 24f; + bool isNightTS1 = clock.Hours > 18 || clock.Hours < 6; + + if (ActiveSounds.TryGetValue(0, out var basePlayer)) + { + basePlayer.SetLoopingNote(dayPct); + } + + return AutoBaseBits | (isNightTS1 ? VMAmbientSoundType.TS1NightLoop : 0); + } + + var tempBits = AutoBaseBits; + + bool isNight = clock.Hours > 20 || clock.Hours < 6; + + if (isNight) + { + if (BaseTerrain == TerrainType.GRASS) + { + tempBits &= ~AllLoops; + tempBits |= VMAmbientSoundType.LoopInsects; + } + else if (BaseTerrain == TerrainType.SNOW) + { + tempBits |= VMAmbientSoundType.AnimalsNightBirds; + } + } + else + { + if ((AutoBaseBits & VMAmbientSoundType.LoopOcean) != 0) + { + tempBits |= VMAmbientSoundType.AnimalsSeaBirds; + } + else + { + tempBits |= VMAmbientSoundType.AnimalsSongBirds; + } + } + + var weather = vm.Context.Blueprint?.Weather; + if (weather != null && weather.ParticleType == LotView.Components.ParticleType.RAIN && weather.WeatherIntensity > 0) + { + // Rain only fully replaces the basic outdoor or insects loops + tempBits &= ~(VMAmbientSoundType.LoopOutdoor | VMAmbientSoundType.LoopInsects); + + tempBits |= VMAmbientSoundType.LoopRain; + + if (weather.WeatherIntensity > 0.5) + { + tempBits |= VMAmbientSoundType.WeatherLightingThunder; + } + } + + return tempBits; + } + + public void Tick(VM vm) + { + if (Paused) + { + return; + } + + var tempBits = EvaluateAutoAmbience(vm) | UserBits; + + if (ActiveBits != tempBits) + { + SwitchAmbience(tempBits, false); + } + + long now = Stopwatch.GetTimestamp(); + long deltaLong = now - LastTimestamp; + float delta = deltaLong / (float)Stopwatch.Frequency; + + if (VolumeDuck != TargetVolumeDuck) + { + var diff = TargetVolumeDuck - VolumeDuck; + var change = delta / AmbienceDuckTime; + + if (Math.Abs(diff) < change) + { + VolumeDuck = TargetVolumeDuck; + } + else + { + VolumeDuck += diff > 0 ? change : -change; + } + } + + LastTimestamp = now; + List toKill = null; + + foreach (var soundPair in ActiveSounds) + { + var sound = soundPair.Value; + if (sound.HasTransition()) + { + if (sound.TickVolume(delta) && sound.Volume == 0) + { + if (toKill == null) + { + toKill = []; + } + + toKill.Add(soundPair.Key); + } + } + } + + if (toKill != null) + { + foreach (byte id in toKill) + { + var cat = SoundByBitField[id]; + ActiveSounds[id].Kill(); + ActiveSounds.Remove(id); + } + } } public bool AmbienceActive(byte id) @@ -132,42 +428,154 @@ public byte GetAmbienceFromGUID(uint GUID) return null; } - public void SetAmbience(byte id, bool active) + public void SetVolumeWithCameraInfo(WorldStateCameraInfo cameraInfo) + { + Volume = Math.Clamp((float)Math.Sqrt(15 / cameraInfo.GroundDistance), 0, 1); + + TargetVolumeDuck = cameraInfo.IsIndoors ? IndoorsVolumeDuck : 1; + + foreach (var sound in ActiveSounds.Values) + { + sound.SetPositionalVolume(Volume * VolumeDuck); + } + } + + public void Pause() + { + Paused = true; + foreach (var sound in ActiveSounds.Values) + { + sound.Pause(); + } + } + + public void Resume() + { + Paused = false; + foreach (var sound in ActiveSounds.Values) + { + sound.Resume(); + } + } + + public void SwitchAmbience(VMAmbientSoundType newAmbience, bool instant) + { + for (int i = 0; i < SoundByBitField.Count; i++) + { + bool oldBit = ((ulong)ActiveBits & (1ul << i)) != 0; + bool newBit = ((ulong)newAmbience & (1ul << i)) != 0; + + if (oldBit != newBit) + { + SetAmbience((byte)i, newBit, instant); + } + } + } + + public void SetUserBits(ulong type) + { + UserBits = (VMAmbientSoundType)type; + } + + public void SetUserAmbience(byte id, bool active) + { + if (id > SoundByBitField.Count) return; + if (active) + { + var cat = SoundByBitField[id]; + var newActiveBits = ActiveBits; + if (cat.Category == 4) + { + // cancel other loops + newActiveBits &= ~AllLoops; // TODO keep auto bits? + UserBits &= ~AllLoops; + + if (newActiveBits != ActiveBits) + { + SwitchAmbience(newActiveBits, true); + } + } + + UserBits |= (VMAmbientSoundType)((ulong)1 << id); + } + else + { + UserBits &= (VMAmbientSoundType)~(((ulong)1 << id)); + } + + SetAmbience(id, active, true); + } + + public void SetAmbience(byte id, bool active, bool instant = true) { if (ForceDisable || HITVM.DISABLE_SOUND) return; if (id > SoundByBitField.Count) return; + instant |= !VM.UseWorld; if (active) { - ActiveBits |= ((ulong)1 << id); - if (!ActiveSounds.ContainsKey(id)) + ActiveBits |= (VMAmbientSoundType)((ulong)1 << id); + if (!ActiveSounds.TryGetValue(id, out AmbiencePlayer player)) { var cat = SoundByBitField[id]; var amb = AmbienceByGUID[cat.GUID]; - if (cat.Category == 4 && ActiveLoop != null) SetAmbience(GetAmbienceFromGUID(ActiveLoop.Value.GUID), false); //cancel previous loop - if (VM.UseWorld) ActiveSounds.Add(id, new AmbiencePlayer(amb)); - if (cat.Category == 4) ActiveLoop = cat; + if (VM.UseWorld) + { + player = new AmbiencePlayer(amb, instant ? Volume : 0); + ActiveSounds.Add(id, player); + if (!instant) + { + player.SetVolume(cat.Volume, AmbienceTransitionTime); + } + } + } + else if (VM.UseWorld) + { + var cat = SoundByBitField[id]; + if (instant) + { + player.SetVolume(cat.Volume); + } + else + { + player.SetVolume(cat.Volume, AmbienceTransitionTime); + } } } else { - ActiveBits &= ~(((ulong)1 << id)); - if (ActiveSounds.ContainsKey(id)) + ActiveBits &= (VMAmbientSoundType)~(((ulong)1 << id)); + if (ActiveSounds.TryGetValue(id, out AmbiencePlayer player)) { - var cat = SoundByBitField[id]; - ActiveSounds[id].Kill(); - ActiveSounds.Remove(id); - if (cat.Category == 4) ActiveLoop = null; + if (instant) + { + var cat = SoundByBitField[id]; + ActiveSounds[id].Kill(); + ActiveSounds.Remove(id); + } + else + { + player.SetVolume(0, AmbienceTransitionTime); + } } } } + public void BeginTransition() + { + ToTransition = this; + UserCount++; + } + public void Kill() { - foreach (var sound in ActiveSounds) + if (--UserCount == 0) { - sound.Value.Kill(); + foreach (var sound in ActiveSounds) + { + sound.Value.Kill(); + } + ActiveSounds.Clear(); } - ActiveSounds.Clear(); } } @@ -177,12 +585,18 @@ public struct VMCategorisedAmb public uint GUID; public byte Category; public string Name; + public float Volume; - public VMCategorisedAmb(uint guid, byte cat, string name) + public VMCategorisedAmb(uint guid, byte cat, string name, float volume) { GUID = guid; Category = cat; Name = name; + Volume = volume; + } + + public VMCategorisedAmb(uint guid, byte cat, string name) : this(guid, cat, name, cat == 4 ? 0.8f : 0.33f) + { } } } diff --git a/TSOClient/tso.simantics/Engine/VMDialogHandler.cs b/TSOClient/tso.simantics/Engine/VMDialogHandler.cs index 51881d2b4..922db6238 100644 --- a/TSOClient/tso.simantics/Engine/VMDialogHandler.cs +++ b/TSOClient/tso.simantics/Engine/VMDialogHandler.cs @@ -15,7 +15,7 @@ public static class VMDialogHandler private static string[] valid = { "Object", "Me", "TempXL:", "Temp:", "$", "Attribute:", "DynamicStringLocal:", "Local:", "TimeLocal:", "NameLocal:", "FixedLocal:", "DynamicObjectName", "MoneyXL:", "JobOffer:", "Job:", "JobDesc:", "Param:", "Neighbor", "\r\n", "ListObject", - "CatalogLocal:", "DateLocal:", "ObjectLocal:", "\\n" + "CatalogLocal:", "DateLocal:", "ObjectLocal:", "LotLocal:", "\\n" }; public static void ShowDialog(VMStackFrame context, VMDialogOperand operand, STR source) @@ -91,7 +91,7 @@ public static string ParseDialogString(VMStackFrame context, string input, STR s { try { - if (cmdString == "DynamicStringLocal:" || cmdString == "TimeLocal:" || cmdString == "JobOffer:" || cmdString == "Job:" || cmdString == "JobDesc:" || cmdString == "DateLocal:") + if (cmdString == "DynamicStringLocal:" || cmdString == "TimeLocal:" || cmdString == "JobOffer:" || cmdString == "Job:" || cmdString == "JobDesc:" || cmdString == "DateLocal:" || cmdString == "LotLocal:") { values[1] = -1; values[2] = -1; @@ -146,6 +146,7 @@ public static string ParseDialogString(VMStackFrame context, string input, STR s //StackObjectOwnerID call sets the id to -1 if no owner found. (null is usually 0) output.Append(context.VM.TSOState.Names.GetNameForID( context.VM, + VMGlobalEntityType.Avatar, (context.Callee.TSOState as VMTSOObjectState)?.OwnerID ?? 0 )); } else @@ -267,6 +268,18 @@ public static string ParseDialogString(VMStackFrame context, string input, STR s var date = new DateTime(context.Locals[values[2]], context.Locals[values[1]], context.Locals[values[0]]); output.Append(date.ToLongDateString()); break; + case "LotLocal:": + uint idLow = (uint)context.Locals[values[0]]; + uint idHigh = (uint)context.Locals[values[1]] << 16; + + uint persistId = idLow | idHigh; + + output.Append(context.VM.TSOState.Names.GetNameForID( + context.VM, + VMGlobalEntityType.Lot, + persistId + )); + break; case "\\n": output.Append("\n"); break; diff --git a/TSOClient/tso.simantics/Engine/VMDirectControlFrame.cs b/TSOClient/tso.simantics/Engine/VMDirectControlFrame.cs index ba9b34e3e..0440e25b1 100644 --- a/TSOClient/tso.simantics/Engine/VMDirectControlFrame.cs +++ b/TSOClient/tso.simantics/Engine/VMDirectControlFrame.cs @@ -1,5 +1,6 @@ using FSO.LotView.Model; using FSO.SimAntics.Model.Routing; +using FSO.SimAntics.Model.TSOPlatform; using FSO.SimAntics.Model; using Microsoft.Xna.Framework; using System.Collections.Generic; @@ -57,6 +58,7 @@ public class VMDirectControlFrame : VMStackFrame private VMDirectControlState State; private VMDirectControlInput UserInput; + private bool HasUserInput; private int HasDelayedInputs; private VMDirectControlInput DelayedInput; @@ -71,11 +73,13 @@ public class VMDirectControlFrame : VMStackFrame public VMDirectControlFrame() { - + SpecialFrame = true; } public void Init() { + State.Input.LookDirectionInt = (short)((Caller as VMAvatar)?.RadianDirection / Math.PI * 32767); + State.Input.Direction = State.Input.LookDirectionInt; (Caller as VMAvatar)?.SetPersonData(VMPersonDataVariable.Priority, 1); } @@ -143,6 +147,7 @@ public void SendControls(VMDirectControlInput input) public void SendUserControls(VMDirectControlInput input) { UserInput = input; + HasUserInput = true; } public void TakeDelayedInput() @@ -206,8 +211,8 @@ public VMObstacleSet GetObstacles() WithinRange(obj, startPos.x, startPos.y) && (obj is VMGameObject || considerAvatars) && ((flags & VMEntityFlags.DisallowPersonIntersection) > 0 || (flags & VMEntityFlags.AllowPersonIntersection) == 0) - && (!(Caller.ExecuteEntryPoint(5, VM.Context, true, obj, new short[] { obj.ObjectID, 1, 0, 0 }) - || obj.ExecuteEntryPoint(5, VM.Context, true, Caller, new short[] { Caller.ObjectID, 1, 0, 0 })))) + && (!(Caller.ExecuteEntryPoint(5, VM.Context, true, obj, new([obj.ObjectID, 1, 0, 0])) + || obj.ExecuteEntryPoint(5, VM.Context, true, Caller, new([Caller.ObjectID, 1, 0, 0]))))) obstacles.Add(new VMEntityObstacle(ft.x1 - 3, ft.y1 - 3, ft.x2 + 3, ft.y2 + 3, obj)); } @@ -367,7 +372,7 @@ private bool TryMove(VMObstacleSet obstacles, ref VMDirectControlState state, in } } - if (portal != null && framePredict == 0) + if (portal != null && framePredict == 0 && !(((VMTSOAvatarState)Caller.TSOState)?.IsSpectator ?? false)) { State = new VMDirectControlState(); @@ -621,7 +626,7 @@ public VMPrimitiveExitCode Tick() bool notified = (Thread.ActiveAction.NotifyIdle || Thread.Queue.Any(interaction => interaction.Mode != VMQueueMode.Idle)); - if (VM.MyUID == Caller.PersistID) + if (VM.MyUID == Caller.PersistID && HasUserInput) { VM.SendCommand(new VMNetDirectControlCommand() { Input = UserInput }); @@ -654,7 +659,7 @@ private bool PushEntryPoint(int entryPoint, VMEntity ent) CodeOwner = Behavior.owner, StackObject = ent, Routine = Behavior.routine, - Args = new short[4] + Args = default }) == VMPrimitiveExitCode.RETURN_TRUE); } else Execute = true; @@ -680,7 +685,7 @@ private bool PushEntryPoint(int entryPoint, VMEntity ent) StackObject = ent, ActionTree = ActionTree }; - childFrame.Args = new short[routine.Arguments]; + childFrame.Args = new(routine.Arguments); Thread.Push(childFrame); return true; } @@ -695,6 +700,24 @@ private bool PushEntryPoint(int entryPoint, VMEntity ent) } } + public Point EdgeCheck(int marginTiles) + { + var position = Caller.Position; + + int marginSubtiles = marginTiles << 4; + int w = VM.Context.Architecture.Width << 4; + int h = VM.Context.Architecture.Height << 4; + + var result = new Point(); + + if (position.x < marginSubtiles) result.X--; + if (position.y < marginSubtiles) result.Y--; + if (position.x > w - marginSubtiles) result.X++; + if (position.y > h - marginSubtiles) result.Y++; + + return result; + } + #region VM Marshalling Functions public override VMStackFrameMarshal Save() { @@ -728,6 +751,7 @@ public override void Load(VMStackFrameMarshal input, VMContext context) public VMDirectControlFrame(VMStackFrameMarshal input, VMContext context, VMThread thread) { + SpecialFrame = true; Thread = thread; Load(input, context); } diff --git a/TSOClient/tso.simantics/Engine/VMQueuedAction.cs b/TSOClient/tso.simantics/Engine/VMQueuedAction.cs index f3cedd1af..185f93041 100644 --- a/TSOClient/tso.simantics/Engine/VMQueuedAction.cs +++ b/TSOClient/tso.simantics/Engine/VMQueuedAction.cs @@ -1,6 +1,7 @@ using FSO.Content; using FSO.SimAntics.Marshals.Threads; using FSO.Files.Formats.IFF.Chunks; +using FSO.SimAntics.Model; namespace FSO.SimAntics.Engine { @@ -55,8 +56,8 @@ public VMStackFrame ToStackFrame(VMEntity caller) StackObject = StackObject, ActionTree = true }; - if (Args == null) frame.Args = new short[4]; //always 4? i got crashes when i used the value provided by the routine, when for that same routine edith displayed 4 in the properties... - else frame.Args = Args; //WARNING - if you use this, the args array MUST have the same number of elements the routine is expecting! + if (Args == null) frame.Args = default; + else frame.Args = new VMArguments(Args); //WARNING - if you use this, the args array MUST have the same number of elements the routine is expecting! return frame; } diff --git a/TSOClient/tso.simantics/Engine/VMRouteFinder.cs b/TSOClient/tso.simantics/Engine/VMRouteFinder.cs index f07602179..9a9709bab 100644 --- a/TSOClient/tso.simantics/Engine/VMRouteFinder.cs +++ b/TSOClient/tso.simantics/Engine/VMRouteFinder.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using FSO.Files.Formats.IFF.Chunks; using Microsoft.Xna.Framework; -using TSO.Files.formats.iff.chunks; namespace TSO.Simantics.engine { @@ -27,18 +23,20 @@ public static List FindAvaliableLocations(Vector2 center, * Then pick the one nearest to the optimal value */ var result = new List(); - + var proximity = minProximity; var proximityIncrement = 16; var proximityNudge = proximityIncrement * 0.25f; var currentDepth = 1.0f; - while (proximity <= maxProximity){ + while (proximity <= maxProximity) + { var angle = 0.0f; /** Every time we move out by 1 tile in proximity, there will be more tiles to look at **/ var angleIncrement = 360.0f / (currentDepth * 8); - while (angle < 360.0f){ + while (angle < 360.0f) + { var radians = angle * (Math.PI / 180.0f); var radius = proximity + proximityNudge; @@ -50,12 +48,13 @@ public static List FindAvaliableLocations(Vector2 center, var direction = GetDirection(center, new Vector2(tileX, tileY)); - if ((flags&direction) == direction) + if ((flags & direction) == direction) { //TODO: Check if slot is occupied or out of bounds /** This is acceptible to the slot :) **/ - result.Add(new VMFindLocationResult { + result.Add(new VMFindLocationResult + { Direction = direction, Position = new Vector2(tileX, tileY), Proximity = proximity @@ -80,21 +79,31 @@ public static List FindAvaliableLocations(Vector2 center, /// /// /// - public static SLOTFlags GetDirection(Vector2 center, Vector2 target){ + public static SLOTFlags GetDirection(Vector2 center, Vector2 target) + { target.X -= center.X; target.Y -= center.Y; - if (target.Y < 0){ - if (target.X > 0){ + if (target.Y < 0) + { + if (target.X > 0) + { return SLOTFlags.NORTH_EAST; - }else if (target.X < 0){ + } + else if (target.X < 0) + { return SLOTFlags.NORTH_WEST; } return SLOTFlags.NORTH; - }else if (target.Y > 0){ - if (target.X > 0){ + } + else if (target.Y > 0) + { + if (target.X > 0) + { return SLOTFlags.SOUTH_EAST; - }else if (target.Y < 0){ + } + else if (target.Y < 0) + { return SLOTFlags.SOUTH_WEST; } return SLOTFlags.SOUTH; @@ -116,7 +125,8 @@ public class VMProximitySorter : IComparer { private int DesiredProximity; - public VMProximitySorter(int desiredProximity){ + public VMProximitySorter(int desiredProximity) + { this.DesiredProximity = desiredProximity; } @@ -128,12 +138,15 @@ public int Compare(VMFindLocationResult x, VMFindLocationResult y) var distanceX = Math.Abs(x.Proximity - DesiredProximity); var distanceY = Math.Abs(y.Proximity - DesiredProximity); - if (distanceX < distanceY){ + if (distanceX < distanceY) + { return -1; - }else if (distanceX > distanceY) + } + else if (distanceX > distanceY) { return -1; - }else + } + else { return ((int)x.Direction).CompareTo((int)y.Direction); } diff --git a/TSOClient/tso.simantics/Engine/VMRoutingFrame.cs b/TSOClient/tso.simantics/Engine/VMRoutingFrame.cs index 802705d83..409bf8da6 100644 --- a/TSOClient/tso.simantics/Engine/VMRoutingFrame.cs +++ b/TSOClient/tso.simantics/Engine/VMRoutingFrame.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using FSO.SimAntics.Model; +using FSO.SimAntics.Model.TSOPlatform; using Microsoft.Xna.Framework; using FSO.LotView.Model; using FSO.Files.Formats.IFF.Chunks; @@ -110,7 +111,10 @@ private bool InPool private VMFindLocationResult CurRoute; private short LastWalkStyle = -1; - public VMRoutingFrame() { } + public VMRoutingFrame() + { + SpecialFrame = true; + } private void Init() { @@ -231,6 +235,13 @@ private bool DoRoomRoute(VMFindLocationResult route) if (DestRoom == MyRoom || IgnoreRooms) return true; //we don't have to do any room finding for this else { + // Spectators cannot traverse between rooms (doors) + if (Caller is VMAvatar ava) + { + if ((ava.TSOState as VMTSOAvatarState)?.IsSpectator == true) + return false; + } + //find shortest room traversal to destination. Simple A* pathfind. //Portals are considered nodes to allow multiple portals between rooms to be considered. @@ -366,8 +377,8 @@ private bool AttemptWalk() if (obj != Caller && ft != null && (obj is VMGameObject || (considerAvatars && AvatarsToConsider.Contains(obj))) && ((flags & VMEntityFlags.DisallowPersonIntersection) > 0 || (flags & VMEntityFlags.AllowPersonIntersection) == 0) - && (!(Caller.ExecuteEntryPoint(5, VM.Context, true, obj, new short[] { obj.ObjectID, 1, 0, 0 }) - || obj.ExecuteEntryPoint(5, VM.Context, true, Caller, new short[] { Caller.ObjectID, 1, 0, 0 })))) + && (!(Caller.ExecuteEntryPoint(5, VM.Context, true, obj, new([obj.ObjectID, 1, 0, 0])) + || obj.ExecuteEntryPoint(5, VM.Context, true, Caller, new([Caller.ObjectID, 1, 0, 0]))))) obstacles.Add(new VMObstacle(ft.x1-3, ft.y1-3, ft.x2+3, ft.y2+3)); } @@ -470,7 +481,7 @@ private bool PushEntryPoint(int entryPoint, VMEntity ent) { CodeOwner = Behavior.owner, StackObject = ent, Routine = Behavior.routine, - Args = new short[4] + Args = default }) == VMPrimitiveExitCode.RETURN_TRUE); } else Execute = true; @@ -496,7 +507,7 @@ private bool PushEntryPoint(int entryPoint, VMEntity ent) { StackObject = ent, ActionTree = ActionTree }; - childFrame.Args = new short[routine.Arguments]; + childFrame.Args = new(routine.Arguments); Thread.Push(childFrame); return true; } @@ -719,8 +730,10 @@ public VMPrimitiveExitCode Tick() var anims = InPool ? avatar.SwimAnimations : avatar.WalkAnimations; var animation = FSO.Content.Content.Get().AvatarAnimations.Get(anims[3] + ".anim"); - var state = new VMAnimationState(animation, false); - state.Loop = true; + var state = new VMAnimationState(animation, false) + { + Loop = true + }; avatar.Animations.Add(state); PreExit(); @@ -1346,6 +1359,7 @@ public override void Load(VMStackFrameMarshal input, VMContext context) public VMRoutingFrame(VMStackFrameMarshal input, VMContext context, VMThread thread) { + SpecialFrame = true; Thread = thread; Load(input, context); } diff --git a/TSOClient/tso.simantics/Engine/VMScheduler.cs b/TSOClient/tso.simantics/Engine/VMScheduler.cs index a74a3e5a8..e62d05f36 100644 --- a/TSOClient/tso.simantics/Engine/VMScheduler.cs +++ b/TSOClient/tso.simantics/Engine/VMScheduler.cs @@ -1,13 +1,13 @@ -using System.Collections.Generic; +using FSO.SimAntics.Model; namespace FSO.SimAntics.Engine { public class VMScheduler { - private VM vm; - private Dictionary> TickSchedule = new Dictionary>(); - private List TickThisFrame; - public HashSet PendingDeletion = new HashSet(); + private readonly VM vm; + private readonly Dictionary> TickSchedule = []; + private VMObjectList TickThisFrame; + public readonly HashSet PendingDeletion = []; public uint CurrentTickID; public short CurrentObjectID; public bool RunningNow; @@ -27,20 +27,19 @@ public void ScheduleTickIn(VMEntity ent, uint delay) public void ScheduleTick(VMEntity ent, uint tick) { if (ent.Dead) return; - List targEnts; - if (!TickSchedule.TryGetValue(tick, out targEnts)) + if (!TickSchedule.TryGetValue(tick, out var targEnts)) { - targEnts = new List(); + targEnts = []; TickSchedule[tick] = targEnts; } ent.Thread.ScheduleIdleEnd = tick; - VM.AddToObjList(targEnts, ent); + targEnts.AddToObjList(ent); } public void ScheduleCurrentTick(VMEntity ent) { ent.Thread.ScheduleIdleEnd = CurrentTickID; - VM.AddToObjList(TickThisFrame, ent); + TickThisFrame.AddToObjList(ent); } public void DescheduleTick(VMEntity ent) @@ -48,7 +47,7 @@ public void DescheduleTick(VMEntity ent) //on delete or interrupt. if (ent.Thread != null && ent.Thread.ScheduleIdleEnd > CurrentTickID) { - TickSchedule[ent.Thread.ScheduleIdleEnd].Remove(ent); + TickSchedule[ent.Thread.ScheduleIdleEnd].DeleteFromObjList(ent); } } @@ -66,8 +65,7 @@ public void BeginTick(uint tickID) if (CurrentTickID == 0) { //if we were on tick 0 it's likely we just resynced. Migrate ticks to the the correct tick id. - List firstTick; - if (TickSchedule.TryGetValue(1, out firstTick)) + if (TickSchedule.TryGetValue(1, out var firstTick)) { TickSchedule[tickID] = firstTick; //new objects are queued on next tick. if (tickID != 1) TickSchedule.Remove(1); diff --git a/TSOClient/tso.simantics/Engine/VMStackFrame.cs b/TSOClient/tso.simantics/Engine/VMStackFrame.cs index 29bb42011..ec1fdcc2e 100644 --- a/TSOClient/tso.simantics/Engine/VMStackFrame.cs +++ b/TSOClient/tso.simantics/Engine/VMStackFrame.cs @@ -1,6 +1,8 @@ using System; +using System.Runtime.CompilerServices; using FSO.Content; using FSO.SimAntics.Marshals.Threads; +using FSO.SimAntics.Model; namespace FSO.SimAntics.Engine { @@ -18,6 +20,8 @@ public class VMStackFrame { public VMStackFrame() { } + public bool SpecialFrame; + /** Thread executing this routine **/ public VMThread Thread; @@ -42,6 +46,22 @@ public VMEntity StackObject _StackObjectID = value?.ObjectID ?? 0; } } + + public VMEntity StackObjectSafe + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + var obj = _StackObject; + if ((obj == null || obj.Dead) && _StackObjectID != 0) + { + // This is undefined behaviour, but make sure it's at least consistent. + obj = VM.GetObjectById(_StackObjectID); + } + return obj; + } + } + public short StackObjectID { get { return _StackObjectID; } @@ -84,7 +104,7 @@ public GameIffResource ScopeResource { /** * Arguments */ - public short[] Args; + public VMArguments Args; public GameObjectResource CallerPrivate { @@ -146,7 +166,7 @@ public virtual VMStackFrameMarshal Save() StackObject = StackObjectID, CodeOwnerGUID = CodeOwner.OBJ.GUID, Locals = (short[])Locals?.Clone(), - Args = (short[])Args?.Clone(), + Args = Args.Clone(), SpecialResult = SpecialResult, ActionTree = ActionTree, }; @@ -174,7 +194,7 @@ public virtual void Load(VMStackFrameMarshal input, VMContext context) { Locals = input.Locals; } - Args = input.Args; + Args = new VMArguments(input.Args); SpecialResult = input.SpecialResult; ActionTree = input.ActionTree; } diff --git a/TSOClient/tso.simantics/Engine/VMSuitProvider.cs b/TSOClient/tso.simantics/Engine/VMSuitProvider.cs index cc35a6713..26fd19f3d 100644 --- a/TSOClient/tso.simantics/Engine/VMSuitProvider.cs +++ b/TSOClient/tso.simantics/Engine/VMSuitProvider.cs @@ -1,7 +1,7 @@ -using FSO.Files.Formats.IFF.Chunks; +using FSO.Content; +using FSO.Files.Formats.IFF.Chunks; using FSO.SimAntics.Engine.Scopes; using FSO.SimAntics.Model; -using System; namespace FSO.SimAntics.Engine { @@ -178,23 +178,35 @@ private static bool IsValid(string suitName) return !(suitName == null || suitName == "" || suitName == "ADDED"); } + public static object GetSuit(VMAvatar avatar, VMSuitScope scope, ushort id) + { + // This isn't fantastic - when this is called from SetPersonData we don't have info on the active thread. + // The object scope can be lost if coming from another thread right now, but other scopes will work. + var context = avatar.Thread.Stack.LastOrDefault(); + return GetSuit(avatar, context?.CodeOwner, avatar.Thread.Context.Globals, scope, id); + } + public static object GetSuit(VMStackFrame context, VMSuitScope scope, ushort id) { - STR suitTable = null; + return GetSuit((VMAvatar)context.Caller, context.CodeOwner, context.Global, scope, id); + } - var avatar = (VMAvatar)context.Caller; + public static object GetSuit(VMAvatar avatar, GameObject codeOwner, GameGlobal global, VMSuitScope scope, ushort id) + { + STR suitTable = null; + var vm = avatar.Thread.Context.VM; switch (scope) { case VMSuitScope.Object: - suitTable = context.CodeOwner.Resource.Get(304); + suitTable = codeOwner.Resource.Get(304); break; case VMSuitScope.Global: - suitTable = context.Global.Resource.Get(304); + suitTable = global.Resource.Get(304); break; case VMSuitScope.Person: //get outfit from person - if (context.VM.TS1) return GetPersonSuitTS1((VMAvatar)context.Caller, id); + if (vm.TS1) return GetPersonSuitTS1(avatar, id); var type = (VMPersonSuits)id; bool male = (avatar.GetPersonData(VMPersonDataVariable.Gender) == 0); @@ -209,7 +221,7 @@ public static object GetSuit(VMStackFrame context, VMSuitScope scope, ushort id) case VMPersonSuits.DefaultSwimwear: return avatar.DefaultSuits.Swimwear.ID; case VMPersonSuits.JobOutfit: - if (context.VM.TS1) return null; + if (vm.TS1) return null; var job = avatar.GetPersonData(VMPersonDataVariable.OnlineJobID); if (job < 1 || job > 5) return null; var level = Math.Max(0, Math.Min(2, ((int)avatar.GetPersonData(VMPersonDataVariable.OnlineJobGrade) + 1) / 4)); diff --git a/TSOClient/tso.simantics/Engine/VMThread.cs b/TSOClient/tso.simantics/Engine/VMThread.cs index 6e819895a..8e60ed729 100644 --- a/TSOClient/tso.simantics/Engine/VMThread.cs +++ b/TSOClient/tso.simantics/Engine/VMThread.cs @@ -55,8 +55,8 @@ public VMQueuedAction ActiveAction public bool QueueDirty; public sbyte ActiveQueueBlock = -1; //cannot reorder items in the queue with index <= this. - public short[] TempRegisters = new short[20]; - public int[] TempXL = new int[2]; + public VMTempRegisters TempRegisters; + public VMTempXLRegisters TempXL; public VMPrimitiveExitCode LastStackExitCode = VMPrimitiveExitCode.GOTO_FALSE; public VMAsyncState BlockingState; @@ -92,12 +92,12 @@ public static VMPrimitiveExitCode EvaluateCheck(VMContext context, VMEntity enti public static VMPrimitiveExitCode EvaluateCheck(VMContext context, VMEntity entity, VMStackFrame initFrame, VMQueuedAction action, List actionStrings) { var temp = new VMThread(context, entity, 5); - var forceClone = !context.VM.Scheduler.RunningNow; + var forceClone = entity.Thread != null && !context.VM.Scheduler.RunningNow; //temps should only persist on check trees running within the vm tick to avoid desyncs. if (entity.Thread != null) { - temp.TempRegisters = forceClone?(short[])entity.Thread.TempRegisters.Clone() : entity.Thread.TempRegisters; - temp.TempXL = forceClone ? (int[])entity.Thread.TempXL.Clone() : entity.Thread.TempXL; + temp.TempRegisters = entity.Thread.TempRegisters; + temp.TempXL = entity.Thread.TempXL; } temp.IsCheck = true; temp.ActionStrings = actionStrings; //generate and place action strings in here @@ -112,6 +112,14 @@ public static VMPrimitiveExitCode EvaluateCheck(VMContext context, VMEntity enti temp.Tick(); temp.ThreadBreak = VMThreadBreakMode.Active; //cannot breakpoint in check trees } + + // Need to copy temps back + if (forceClone) + { + entity.Thread.TempRegisters = temp.TempRegisters; + entity.Thread.TempXL = temp.TempXL; + } + if (actionStrings != null && actionStrings.Count == 0) { //add an action string containing any modified ads @@ -442,12 +450,14 @@ private void NextInstruction() var currentFrame = Stack.LastOrDefault(); if (currentFrame == null) return; - if (currentFrame is VMRoutingFrame) HandleResult(currentFrame, null, ((VMRoutingFrame)currentFrame).Tick()); - else if (currentFrame is VMDirectControlFrame) HandleResult(currentFrame, null, ((VMDirectControlFrame)currentFrame).Tick()); + if (currentFrame.SpecialFrame) + { + if (currentFrame is VMRoutingFrame routing) HandleResult(currentFrame, null, routing.Tick()); + else if (currentFrame is VMDirectControlFrame direct) HandleResult(currentFrame, null, direct.Tick()); + } else { - VMInstruction instruction; - VMPrimitiveExitCode result = currentFrame.Routine.Execute(currentFrame, out instruction); + VMPrimitiveExitCode result = currentFrame.Routine.Execute(currentFrame, out VMInstruction instruction); HandleResult(currentFrame, instruction, result); } } @@ -505,7 +515,7 @@ public void ExecuteSubRoutine(VMStackFrame frame, VMRoutine routine, GameObject _StackObjectID = frame.StackObjectID, //pass this without doing a lookup ActionTree = frame.ActionTree }; - childFrame.Args = new short[(routine.Arguments > 4) ? routine.Arguments : 4]; + childFrame.Args = new VMArguments((routine.Arguments > 4) ? routine.Arguments : 4); for (var i = 0; i < childFrame.Args.Length; i++) { short argValue = (i > 3) ? (short)-1 : args.Arguments[i]; @@ -642,7 +652,7 @@ private void HandleResult(VMStackFrame frame, VMInstruction instruction, VMPrimi private void MoveToInstruction(VMStackFrame frame, byte instruction, bool continueExecution) { - if (frame is VMRoutingFrame) + if (frame.SpecialFrame && frame is VMRoutingFrame) { //TODO: Handle returning false into the pathfinder (indicates failure) return; @@ -704,9 +714,11 @@ public void Breakpoint(VMStackFrame frame, string description) public void Pop(VMPrimitiveExitCode result) { - var discardResult = Stack[Stack.Count - 1].SpecialResult; - var contextSwitch = (Stack.Count > 1) && Stack.LastOrDefault().ActionTree != Stack[Stack.Count - 2].ActionTree; - Stack.RemoveAt(Stack.Count - 1); + var stackCount = Stack.Count; + var stackTop = Stack[stackCount - 1]; + var discardResult = stackTop.SpecialResult; + + Stack.RemoveAt(stackCount - 1); LastStackExitCode = result; if (discardResult == VMSpecialResult.Interaction) //interaction switching back to main (it cannot be the other way...) @@ -726,7 +738,7 @@ public void Pop(VMPrimitiveExitCode result) result = VMPrimitiveExitCode.GOTO_TRUE; else if (result == VMPrimitiveExitCode.RETURN_FALSE) result = VMPrimitiveExitCode.GOTO_FALSE; - var currentFrame = Stack.Last(); + var currentFrame = Stack[^1]; HandleResult(currentFrame, currentFrame.GetCurrentInstruction(), result); } else // :( @@ -742,7 +754,7 @@ public bool Push(VMStackFrame frame) /** Initialize the locals **/ var numLocals = Math.Max(frame.Routine.Locals, frame.Routine.Arguments); - frame.Locals = new short[numLocals]; + frame.Locals = numLocals == 0 ? [] : new short[numLocals]; frame.Thread = this; frame.InstructionPointer = 0; @@ -926,7 +938,7 @@ public List CheckTS1Action(VMQueuedAction action, bool aut } if (action.CheckRoutine != null) { - var args = new short[4]; + VMArguments args = default; if (auto) args[0] = 1; if (EvaluateCheck(Context, Entity, new VMStackFrame() { @@ -1006,10 +1018,9 @@ public List CheckAction(VMQueuedAction action, bool auto = //if flags are empty apart from "Non-Empty", force everything but visitor. (a kind of default state) if (tsoCompare == TSOFlags.NonEmpty) tsoCompare |= TSOFlags.AllowFriends | TSOFlags.AllowRoommates | TSOFlags.AllowObjectOwner; - //DEBUG: enable debug interction for all CSRs. if ((action.Flags & TTABFlags.Debug) > 0) { - if ((tsoState & TSOFlags.AllowCSRs) > 0) + if (avatar.AvatarState.Flags.HasFlag(VMTSOAvatarFlags.Debug)) return result; //do not bother running check else return null; //disable debug for everyone else. @@ -1032,7 +1043,7 @@ public List CheckAction(VMQueuedAction action, bool auto = if ((!action.Flags.HasFlag(TTABFlags.FSOSkipPermissions) || ((action.Flags & TTABFlags.TSORunCheckAlways) > 0)) && action.CheckRoutine != null) { - var args = new short[4]; + VMArguments args = default; if (auto) args[0] = 1; if (EvaluateCheck(Context, Entity, new VMStackFrame() { @@ -1096,8 +1107,8 @@ public virtual VMThreadMarshal Save() Stack = stack, Queue = queue, ActiveQueueBlock = ActiveQueueBlock, - TempRegisters = (short[])TempRegisters.Clone(), - TempXL = (int[])TempXL.Clone(), + TempRegisters = TempRegisters.AsSpan().ToArray(), + TempXL = TempXL.AsSpan().ToArray(), LastStackExitCode = LastStackExitCode, BlockingState = BlockingState, @@ -1133,8 +1144,8 @@ public virtual void Load(VMThreadMarshal input, VMContext context) QueueDirty = true; foreach (var item in input.Queue) Queue.Add(new VMQueuedAction(item, context)); ActiveQueueBlock = input.ActiveQueueBlock; - TempRegisters = input.TempRegisters; - TempXL = input.TempXL; + TempRegisters = new(input.TempRegisters); + TempXL = new(input.TempXL); LastStackExitCode = input.LastStackExitCode; BlockingState = input.BlockingState; diff --git a/TSOClient/tso.simantics/Entities/VMAvatar.cs b/TSOClient/tso.simantics/Entities/VMAvatar.cs index 580eee8b8..190f36de7 100644 --- a/TSOClient/tso.simantics/Entities/VMAvatar.cs +++ b/TSOClient/tso.simantics/Entities/VMAvatar.cs @@ -22,6 +22,7 @@ using FSO.SimAntics.Primitives; using FSO.SimAntics.Model.Platform; using FSO.SimAntics.Model.TS1Platform; +using FSO.Common.Model; namespace FSO.SimAntics { @@ -70,7 +71,6 @@ public string Message public int KillTimeout = -1; private static readonly int FORCE_DELETE_TIMEOUT = 60 * 30; private readonly ushort LEAVE_LOT_TREE = 8373; - private readonly ushort LEAVE_LOT_ACTION = 173; /* APPEARANCE DATA @@ -128,10 +128,11 @@ public AppearanceType SkinTone get { return _SkinTone; } } + private Vector3 _ServerVisualPosition; public override Vector3 VisualPosition { - get { return (UseWorld) ? ((AvatarComponent)WorldUI).StoredPosition : new Vector3(); } - set { if (UseWorld) WorldUI.Position = value; } + get { return (UseWorld) ? ((AvatarComponent)WorldUI).StoredPosition : _ServerVisualPosition; } + set { if (UseWorld) WorldUI.Position = value; else _ServerVisualPosition = value; } } public override float RadianDirection { @@ -238,7 +239,11 @@ public VMAvatar(GameObject obj) var avatarc = (AvatarComponent)WorldUI; avatarc.Avatar = Avatar; var type = BodyStrings?.GetString(0) ?? "adult"; - if (type != "adult" && type != "child") avatarc.IsPet = true; + if (type != "adult" && type != "child") + { + avatarc.IsPet = true; + avatarc.UseNormal = true; + } } @@ -302,7 +307,7 @@ public void SetAvatarBodyStrings(STR data, VMContext context) var skinTone = data.GetString(14); var skinToneSpl = skinTone.Split(';').Where(x => !string.IsNullOrEmpty(x)).ToArray(); - var randSkinTone = skinToneSpl[context.NextRandom((ulong)skinToneSpl.Length)]; + var randSkinTone = skinToneSpl.Length > 1 ? skinToneSpl[context.NextRandom((ulong)skinToneSpl.Length)] : skinToneSpl[0]; if (randSkinTone.Equals("lgt", StringComparison.InvariantCultureIgnoreCase)) SkinTone = AppearanceType.Light; else if (randSkinTone.Equals("med", StringComparison.InvariantCultureIgnoreCase)) SkinTone = AppearanceType.Medium; @@ -440,7 +445,7 @@ public override void Reset(VMContext context) if (context.VM.EODHost != null) context.VM.EODHost.ForceDisconnect(this); } - private void HandleTimePropsEvent(TimePropertyListItem tp) + private void HandleTimePropsEvent(TimePropertyListItem tp, bool primaryAnimation) { VMAvatar avatar = this; var evt = tp.Properties["xevt"]; @@ -449,7 +454,6 @@ private void HandleTimePropsEvent(TimePropertyListItem tp) short eventValue = 0; short.TryParse(evt, out eventValue); avatar.CurrentAnimationState.EventQueue.Add(eventValue); - if (eventValue < 100) avatar.CurrentAnimationState.EventsRun++; } var rhevt = tp.Properties["righthand"]; if (rhevt != null) @@ -468,23 +472,15 @@ private void HandleTimePropsEvent(TimePropertyListItem tp) var owner = this; if (UseWorld && soundevt != null && owner.SoundThreads.FirstOrDefault(x => x.Name == soundevt) == null) { - var thread = FSO.HIT.HITVM.Get().PlaySoundEvent(soundevt); - if (thread != null) - { - - if (thread is HITThread) SubmitHITVars((HITThread)thread); - - if (!thread.AlreadyOwns(owner.ObjectID)) thread.AddOwner(owner.ObjectID); + PlayTimepropsSound(soundevt); + } - var entry = new VMSoundEntry() - { - Sound = thread, - Pan = true, - Zoom = true, - }; - owner.SoundThreads.Add(entry); - owner.Thread?.Context?.VM?.SoundEntities?.Add(this); - owner.TickSounds(); + if (UseWorld) + { + var footstep = tp.Properties["footstep"]; + if (footstep != null && primaryAnimation && GetPersonData(VMPersonDataVariable.IsGhost) == 0 && Thread?.Context?.VM != null) + { + PlayTimepropsSound(VMFootsteps.GetFootstepEvent(Thread.Context.VM, this)); } } @@ -505,6 +501,29 @@ private void HandleTimePropsEvent(TimePropertyListItem tp) } } + private void PlayTimepropsSound(string soundevt) + { + var owner = this; + var thread = FSO.HIT.HITVM.Get().PlaySoundEvent(soundevt); + if (thread != null) + { + + if (thread is HITThread) SubmitHITVars((HITThread)thread); + + if (!thread.AlreadyOwns(owner.ObjectID)) thread.AddOwner(owner.ObjectID); + + var entry = new VMSoundEntry() + { + Sound = thread, + Pan = true, + Zoom = true, + }; + owner.SoundThreads.Add(entry); + owner.Thread?.Context?.VM?.SoundEntities?.Add(this); + owner.TickSounds(); + } + } + public override void Tick() { Velocity = new Vector3(0, 0, 0); @@ -541,6 +560,7 @@ public override void Tick() //animation update for avatars VMAvatar avatar = this; float totalWeight = 0f; + float maxWeight = Animations.Count > 0 ? Animations.Max(x => x.Weight) : 0; foreach (var state in Animations) { totalWeight += state.Weight; @@ -564,7 +584,7 @@ public override void Tick() timeProps.RemoveAt(0); i--; - HandleTimePropsEvent(tp); + HandleTimePropsEvent(tp, state.Weight == maxWeight); } } else @@ -578,7 +598,7 @@ public override void Tick() } timeProps.RemoveAt(timeProps.Count - 1); - HandleTimePropsEvent(tp); + HandleTimePropsEvent(tp, state.Weight == maxWeight); } } } @@ -588,6 +608,7 @@ public override void Tick() { if (state.Loop) { + state.ResetTimeProps(); if (state.PlayingBackwards) state.CurrentFrame += state.Anim.NumFrames; else state.CurrentFrame -= state.Anim.NumFrames; } @@ -628,11 +649,11 @@ public override void Tick() } } - public void UserLeaveLot() + public void UserLeaveLot(bool instant = false) { //interaction cancel should handle this //if (Thread.Context.VM.EODHost != null) Thread.Context.VM.EODHost.ForceDisconnect(this); //try this a lot. - if (Thread.Queue.Exists(x => x.ActionRoutine.ID == LEAVE_LOT_TREE && Thread.Queue.IndexOf(x) <= Thread.ActiveQueueBlock+1)) return; //we're already leaving + if (Thread.Queue.Exists(x => x.Callee == this && x.ActionRoutine.ID == LEAVE_LOT_TREE && Thread.Queue.IndexOf(x) <= Thread.ActiveQueueBlock+1)) return; //we're already leaving var actions = new List(Thread.Queue); foreach (var action in actions) { @@ -641,13 +662,26 @@ public void UserLeaveLot() var tree = GetRoutineWithOwner(LEAVE_LOT_TREE, Thread.Context); - var qaction = GetAction(LEAVE_LOT_ACTION, this, Thread.Context, false); + VMQueuedAction qaction = null; + + // Try find the "leave lot" action on the current tree table, and enqueue it. + if (TreeTable != null) + { + var index = TreeTable.Interactions.FirstOrDefault(x => x.ActionFunction == LEAVE_LOT_TREE)?.TTAIndex; + + if (index != null) + { + qaction = GetAction((int)index.Value, this, Thread.Context, false); + } + } + if (qaction != null) { qaction.Flags |= TTABFlags.FSOSkipPermissions; Thread.EnqueueAction(qaction); } - else + + if (instant || qaction == null) { KillTimeout = FORCE_DELETE_TIMEOUT; } @@ -930,7 +964,7 @@ public virtual bool SetPersonData(VMPersonDataVariable variable, short value) if (Thread.Context.VM.TS1) BodyOutfit = VMSuitProvider.GetPersonSuitTS1(this, (ushort)value); else { - var suit = VMSuitProvider.GetSuit(Thread.Stack.LastOrDefault(), Engine.Scopes.VMSuitScope.Person, (ushort)value); + var suit = VMSuitProvider.GetSuit(this, Engine.Scopes.VMSuitScope.Person, (ushort)value); if (suit is VMOutfitReference) BodyOutfit = suit as VMOutfitReference; if (suit is ulong) BodyOutfit = new VMOutfitReference((ulong)suit); } @@ -1177,6 +1211,23 @@ public override Texture2D GetIcon(GraphicsDevice gd, int store) return (store > 0 && ico != null)?TextureUtils.Decimate(ico, gd, (1<<(2-store)) * decimateMul, false):ico; } + public SurroundPuppet GetSurroundPuppet() + { + return new SurroundPuppet() + { + Delta = KillTimeout != -1 ? SurroundPuppetDelta.Leaving : 0, + PersistID = PersistID == 0 ? (uint)ObjectID | 0xFFFF0000u : PersistID, + SkinTone = (uint)SkinTone, + BodyOutfit = BodyOutfit?.ID ?? 0, + HeadOutfit = HeadOutfit?.ID ?? 0, + SkeletonName = Avatar?.Skeleton?.Name ?? "adult", + VisualPositionStart = new Vector4(VisualPositionStart ?? VisualPosition, (float)RadianDirection), + Velocity = new Vector4(VisualPositionStart == null ? new Vector3() : Velocity, (float)TurnVelocity), + Appearances = BoundAppearances.Count == 0 ? [] : [.. BoundAppearances], + Animations = [.. Animations.Select(x => new SurroundPuppetAnimation(x.Anim.Name, x.CurrentFrame, x.Speed, x.Weight, x.EndReached, x.PlayingBackwards, x.Loop))] + }; + } + #region VM Marshalling Functions public VMAvatarMarshal Save() { diff --git a/TSOClient/tso.simantics/Entities/VMEntity.cs b/TSOClient/tso.simantics/Entities/VMEntity.cs index a2f9395b1..d5ba9ed1c 100644 --- a/TSOClient/tso.simantics/Entities/VMEntity.cs +++ b/TSOClient/tso.simantics/Entities/VMEntity.cs @@ -32,7 +32,7 @@ public class VMEntityRTTI public abstract class VMEntity { public static Func MissingIconProvider; - public static bool UseWorld = true; + public static bool UseWorld => VM.UseWorld; public VMEntityRTTI RTTI; public bool GhostImage; @@ -120,6 +120,7 @@ public virtual void SetAttribute(int index, short value) public VMEntityTuning TuningReplacement; public bool Portal => EntryPoints[15].ActionFunction != 0; public bool Window => ((VMEntityFlags2)GetValue(VMStackObjectVariable.FlagField2)).HasFlag(VMEntityFlags2.ArchitectualWindow); + public OBJD GroupDefinition => MasterDefinition ?? Object.OBJ; public virtual bool MovesOften { get @@ -359,12 +360,24 @@ public void TickSounds() if (rcs != null) { var vp = VisualPosition * 3f; - var delta = rcs.Camera.Target - new Vector3(vp.X, vp.Z, vp.Y); - delta.Z /= 3f; - //volume = 4f / delta.Length(); - volume = 1.5f - delta.Length() / 40f; - volume *= (10 / ((rcs.Zoom3D * rcs.Zoom3D) + 10)); - volume *= worldState.PreciseZoom; + Vector3 delta; + if (rcs is CameraControllerFP) + { + delta = rcs.Camera.Position - new Vector3(vp.X, vp.Z, vp.Y); + delta.Z /= 3f; + //volume = 4f / delta.Length(); + volume = 1.5f - delta.Length() / 40f; + volume *= worldState.PreciseZoom; + } + else + { + delta = rcs.Camera.Target - new Vector3(vp.X, vp.Z, vp.Y); + delta.Z /= 3f; + //volume = 4f / delta.Length(); + volume = 1.5f - delta.Length() / 40f; + volume *= (10 / ((rcs.Zoom3D * rcs.Zoom3D) + 10)); + volume *= worldState.PreciseZoom; + } //Calculate 3D sound if (SoundThreads[i].Pan) @@ -492,15 +505,14 @@ public virtual void Init(VMContext context) } ExecuteEntryPoint(0, context, true); //Init - ExecuteEntryPoint(8, context, true, null, new short[] { 0, 0, 0, 0 }); //dynamic multitile - say we don't have any adjacent objects to start + ExecuteEntryPoint(8, context, true, null, default); //dynamic multitile - say we don't have any adjacent objects to start if (!GhostImage) { - short[] Args = null; + VMArguments Args = default; VMEntity StackOBJ = null; if (MainParam != 0) { - Args = new short[4]; Args[0] = MainParam; MainParam = 0; } @@ -556,15 +568,15 @@ public void FetchTreeByName(VMContext context) public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately) { - return ExecuteEntryPoint(entry, context, runImmediately, null, null); + return ExecuteEntryPoint(entry, context, runImmediately, null, default); } public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately, VMEntity stackOBJ) { - return ExecuteEntryPoint(entry, context, runImmediately, stackOBJ, null); + return ExecuteEntryPoint(entry, context, runImmediately, stackOBJ, default); } - public bool ExecuteGenericEntryPoint(OBJfFunctionEntry entry, VMContext context, bool runImmediately, VMEntity stackOBJ, short[] args) + public bool ExecuteGenericEntryPoint(OBJfFunctionEntry entry, VMContext context, bool runImmediately, VMEntity stackOBJ, in VMArguments args) { if (entry.ActionFunction > 255) { @@ -620,9 +632,8 @@ public bool ExecuteGenericEntryPoint(OBJfFunctionEntry entry, VMContext context, } } - public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately, VMEntity stackOBJ, short[] args) + public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately, VMEntity stackOBJ, in VMArguments args) { - if (args == null) args = new short[4]; if (entry == 11) { //user placement, hack to do auto floor removal/placement for stairs @@ -639,7 +650,7 @@ public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately, if (entry < EntryPoints.Length) { - return ExecuteGenericEntryPoint(EntryPoints[entry], context, runImmediately, stackOBJ, args); + return ExecuteGenericEntryPoint(EntryPoints[entry], context, runImmediately, stackOBJ, in args); } else { @@ -647,7 +658,7 @@ public bool ExecuteEntryPoint(int entry, VMContext context, bool runImmediately, } } - public bool ExecuteNamedEntryPoint(string name, VMContext context, bool runImmediately, VMEntity stackOBJ, short[] args) + public bool ExecuteNamedEntryPoint(string name, VMContext context, bool runImmediately, VMEntity stackOBJ, in VMArguments args) { VMTreeByNameTableEntry tree; if (TreeByName.TryGetValue(name, out tree)) @@ -1580,7 +1591,7 @@ public void UpdateDynamicMultitile(VMContext context) flags |= 1 << dirDiff; } } - ExecuteEntryPoint(8, context, true, null, new short[] { (short)flags, 0, 0, 0 }); + ExecuteEntryPoint(8, context, true, null, new([(short)flags, 0, 0, 0])); } public void UpdateDynamicMultitileFlags(VMContext context) @@ -1599,7 +1610,7 @@ public void UpdateDynamicMultitileFlags(VMContext context) flags |= 1 << dirDiff; } } - ExecuteEntryPoint(8, context, true, null, new short[] { (short)flags, 0, 0, 0 }); + ExecuteEntryPoint(8, context, true, null, new([(short)flags, 0, 0, 0])); } public abstract Texture2D GetIcon(GraphicsDevice gd, int store); @@ -1848,6 +1859,7 @@ public class VMPieMenuInteraction public bool Global; public Dictionary MotiveAdChanges; + public bool IsTooltip; public override string ToString() { diff --git a/TSOClient/tso.simantics/Entities/VMEntityTuning.cs b/TSOClient/tso.simantics/Entities/VMEntityTuning.cs index 6d8c41f13..7159a8d8c 100644 --- a/TSOClient/tso.simantics/Entities/VMEntityTuning.cs +++ b/TSOClient/tso.simantics/Entities/VMEntityTuning.cs @@ -30,8 +30,8 @@ public VMEntityTuning(VMEntity owner, VM vm) { AppendTable(tuning.GetTables(owner.SemiGlobal.MainIff.Filename), 8192); } - // todo: global tuning replacement (?) - // AppendTable(?, 256); + + AppendTable(tuning.GetTables("global.iff"), 256); } } diff --git a/TSOClient/tso.simantics/Entities/VMFootsteps.cs b/TSOClient/tso.simantics/Entities/VMFootsteps.cs new file mode 100644 index 000000000..8a31c806d --- /dev/null +++ b/TSOClient/tso.simantics/Entities/VMFootsteps.cs @@ -0,0 +1,107 @@ +using FSO.SimAntics.Engine.Scopes; + +namespace FSO.SimAntics.Entities +{ + public static class VMFootsteps + { + private struct VMFootstepType + { + public readonly string Shoes; + public readonly string NoShoes; + + public VMFootstepType(string shoes, string noShoes = null) + { + Shoes = shoes; + NoShoes = noShoes; + } + + public string GetEvent(bool shoes) + { + return shoes || NoShoes == null ? Shoes : NoShoes; + } + } + + private static VMFootstepType[] FootstepByHardness = [ + new VMFootstepType("footstep_soft", "footstep_soft_noshoe"), + new VMFootstepType("footstep_medium", "footstep_medium_noshoe"), + new VMFootstepType("footstep_hard", "footstep_hard_noshoe"), + ]; + + private static VMFootstepType TerrainFootstep = new VMFootstepType("footstep_terrain"); + private static VMFootstepType SnowFootstep = new VMFootstepType("footstep_snow"); + private static VMFootstepType SwimFootstep = new VMFootstepType("footstep_swim_stroke"); + + // These are notably the same GUID in TSO and TS1. + private static Dictionary SoundByGUID = new() + { + { 0x63416BA1, new VMFootstepType("footstep_ash") }, + { 0x3E7470F6, new VMFootstepType("footstep_puddle") }, + { 0x7F907075, new VMFootstepType("footstep_trash") }, + { 0x4415A98E, new VMFootstepType("footstep_roach") }, + }; + + public static string GetFootstepEvent(VM vm, VMAvatar ava) + { + var suit = (VMPersonSuits)ava.GetPersonData(Model.VMPersonDataVariable.CurrentOutfit); + bool shoes = !(suit == VMPersonSuits.Naked || + suit == VMPersonSuits.DefaultSleepwear || + suit == VMPersonSuits.DefaultSwimwear || + suit == VMPersonSuits.DynamicSleepwear || + suit == VMPersonSuits.DynamicSwimwear); + + VMFootstepType footstep; + var pos = ava.Position; + var arch = vm.Context.Architecture; + + ushort floorTileId = 0; + + if (pos.TileX >= 0 && pos.TileY >= 0 && pos.TileX < arch.Width && pos.TileY < arch.Height) + { + // Check for any objects sharing the tile that have special sounds + var objs = vm.Context.ObjectQueries.GetObjectsAt(ava.Position); + if (objs != null) + { + var lookup = SoundByGUID; + foreach (var obj in objs) + { + if (lookup.TryGetValue(obj.Object.OBJ.GUID, out footstep)) + { + return footstep.GetEvent(shoes); + } + } + } + + floorTileId = arch.GetPreciseFloor(pos); + } + + + // Check the floor tile + + if (floorTileId == 0) + { + // Walking on terrain + if (vm.Context.Architecture.Terrain.LightType == Content.Model.TerrainType.SNOW && vm.TS1) + { + footstep = SnowFootstep; + } + else + { + footstep = TerrainFootstep; + } + } + else + { + int hardness = 2; + + if (Content.Content.Get().WorldFloors.Entries.TryGetValue(floorTileId, out var floor)) + { + hardness = floor.Hardness; + } + + footstep = FootstepByHardness[hardness]; + } + + return footstep.GetEvent(shoes); + } + } +} diff --git a/TSOClient/tso.simantics/Entities/VMGameObject.cs b/TSOClient/tso.simantics/Entities/VMGameObject.cs index 8878181e3..245888702 100644 --- a/TSOClient/tso.simantics/Entities/VMGameObject.cs +++ b/TSOClient/tso.simantics/Entities/VMGameObject.cs @@ -457,7 +457,10 @@ public void EnableParticle(ushort id) part.Mode = ParticleType.GENERIC_BOX; GameThread.InUpdate(() => { - part.Tex = Content.Content.Get().RCMeshes.GetTex("FSO_smoke.png"); + var meshes = Content.Content.Get().RCMeshes; + // somehow this leaks in the facade worker.. + // they tend to be allocated a lot back to back, but they also allocate far apart + part.Tex = meshes.GetTex("FSO_smoke.png", 65535).Value.Holder.GetTexture(meshes.GD); WorldUI.blueprint.ObjectParticles.Add(part); }); ((ObjectComponent)WorldUI).Particles.Add(part); diff --git a/TSOClient/tso.simantics/Entities/VMMultitileGroup.cs b/TSOClient/tso.simantics/Entities/VMMultitileGroup.cs index 542f5619d..f49be7eb7 100644 --- a/TSOClient/tso.simantics/Entities/VMMultitileGroup.cs +++ b/TSOClient/tso.simantics/Entities/VMMultitileGroup.cs @@ -163,9 +163,9 @@ public VMPlacementResult ChangePosition(LotTilePos pos, Direction direction, VMC var count = Objects.Count; VMEntity[] OldContainers = new VMEntity[count]; - short[] OldSlotNum = new short[count]; - bool[] RoomChange = new bool[count]; - LotTilePos[] Targets = new LotTilePos[count]; + Span OldSlotNum = stackalloc short[count]; + Span RoomChange = stackalloc bool[count]; + Span Targets = stackalloc LotTilePos[count]; for (int i = 0; i < count; i++) { OldContainers[i] = Objects[i].Container; diff --git a/TSOClient/tso.simantics/FSO.SimAntics.csproj b/TSOClient/tso.simantics/FSO.SimAntics.csproj index cefb57b3f..323227ad1 100644 --- a/TSOClient/tso.simantics/FSO.SimAntics.csproj +++ b/TSOClient/tso.simantics/FSO.SimAntics.csproj @@ -1,495 +1,37 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {5EDDEFD2-C850-49C1-812D-DDEFF09125EF} + net9.0 + enable + disable Library - Properties FSO.SimAntics FSO.SimAntics - v4.5 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + True + true + true + true + full - - true - bin\x86\Debug\ - TRACE;DEBUG - full - x86 - prompt - MinimumRecommendedRules.ruleset - true + + + True - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + True - - true - bin\Debug\ - TRACE;DEBUG;IDE_COMPAT - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE;IDE_COMPAT - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll - - - ..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll - - - ..\packages\FreeSO.HandEvaluator.1.2.0\lib\net20\HandEvaluator.dll - - - - ..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - ..\packages\MonoGame.Framework.WindowsGL.3.4.0.459\lib\net40\OpenTK.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {B1A6E4C2-E080-4C34-A604-D11B5296A9B8} - FSO.LotView - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {072781D8-51EC-4143-9CAE-DAF50177D3AD} - FSO.HIT - - - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} - FSO.Vitaboy.Engine - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - - + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + + - - + + + - - - - \ No newline at end of file + + diff --git a/TSOClient/tso.simantics/Marshals/Hollow/VMHollowGameObjectMarshal.cs b/TSOClient/tso.simantics/Marshals/Hollow/VMHollowGameObjectMarshal.cs index 1d8f64032..e858096b3 100644 --- a/TSOClient/tso.simantics/Marshals/Hollow/VMHollowGameObjectMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/Hollow/VMHollowGameObjectMarshal.cs @@ -43,11 +43,7 @@ public void Deserialize(BinaryReader reader) DynamicSpriteFlags2 = reader.ReadUInt64(); var contC = reader.ReadInt32(); - Contained = new short[contC]; - for (int i=0; i(reader, contC); Container = reader.ReadInt16(); ContainerSlot = reader.ReadInt16(); @@ -69,7 +65,7 @@ public void SerializeInto(BinaryWriter writer) writer.Write(DynamicSpriteFlags2); writer.Write(Contained.Length); - writer.Write(VMSerializableUtils.ToByteArray(Contained)); + VMSerializableUtils.WriteArray(writer, Contained); writer.Write(Container); writer.Write(ContainerSlot); diff --git a/TSOClient/tso.simantics/Marshals/Hollow/VMHollowMarshal.cs b/TSOClient/tso.simantics/Marshals/Hollow/VMHollowMarshal.cs index 6699a00aa..e5ebbbf72 100644 --- a/TSOClient/tso.simantics/Marshals/Hollow/VMHollowMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/Hollow/VMHollowMarshal.cs @@ -20,14 +20,17 @@ public void Deserialize(BinaryReader reader) Version = reader.ReadInt32(); Compressed = reader.ReadBoolean(); - MemoryStream cStream; - GZipStream zipStream; if (Compressed) { var length = reader.ReadInt32(); - cStream = new MemoryStream(reader.ReadBytes(length)); - zipStream = new GZipStream(cStream, CompressionMode.Decompress); - reader = new BinaryReader(zipStream); + var cStream = new MemoryStream(reader.ReadBytes(length)); + var zipStream = new GZipStream(cStream, CompressionMode.Decompress); + var decompStream = new MemoryStream(); + zipStream.CopyTo(decompStream); + decompStream.Seek(0, SeekOrigin.Begin); + reader = new BinaryReader(decompStream); + cStream.Close(); + zipStream.Close(); } Context = new VMContextMarshal(Version); @@ -49,6 +52,11 @@ public void Deserialize(BinaryReader reader) MultitileGroups[i] = new VMMultitileGroupMarshal(Version); MultitileGroups[i].Deserialize(reader); } + + if (Compressed) + { + reader.BaseStream.Close(); + } } public void SerializeInto(BinaryWriter writer) @@ -57,14 +65,13 @@ public void SerializeInto(BinaryWriter writer) writer.Write(VMMarshal.LATEST_VERSION); writer.Write(Compressed); + var uWriter = writer; MemoryStream cStream = null; - GZipStream zipStream = null; if (Compressed) { cStream = new MemoryStream(); - zipStream = new GZipStream(cStream, CompressionMode.Compress); - writer = new BinaryWriter(zipStream); + writer = new BinaryWriter(cStream); } var timer = new System.Diagnostics.Stopwatch(); @@ -84,10 +91,21 @@ public void SerializeInto(BinaryWriter writer) if (Compressed) { writer.Close(); - zipStream.Close(); + //zipStream.Close(); var data = cStream.ToArray(); - uWriter.Write(data.Length); - uWriter.Write(data); + + var zipMStream = new MemoryStream(); + var zipStream = new GZipStream(zipMStream, CompressionMode.Compress); + zipStream.Write(data, 0, data.Length); + zipStream.Close(); + + var cData = zipMStream.ToArray(); + + uWriter.Write(cData.Length); + uWriter.Write(cData); + + cStream.Close(); + zipMStream.Close(); } } } diff --git a/TSOClient/tso.simantics/Marshals/Threads/VMStackFrameMarshal.cs b/TSOClient/tso.simantics/Marshals/Threads/VMStackFrameMarshal.cs index 26f8e4404..ea7372ec9 100644 --- a/TSOClient/tso.simantics/Marshals/Threads/VMStackFrameMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/Threads/VMStackFrameMarshal.cs @@ -34,15 +34,13 @@ public virtual void Deserialize(BinaryReader reader) var localN = reader.ReadInt32(); if (localN > -1) { - Locals = new short[localN]; - for (int i = 0; i < localN; i++) Locals[i] = reader.ReadInt16(); + Locals = VMSerializableUtils.ReadArray(reader, localN); } var argsN = reader.ReadInt32(); if (argsN > -1) { - Args = new short[argsN]; - for (int i = 0; i < argsN; i++) Args[i] = reader.ReadInt16(); + Args = VMSerializableUtils.ReadArray(reader, argsN); } if (Version > 3) SpecialResult = (VMSpecialResult)reader.ReadByte(); @@ -58,9 +56,9 @@ public virtual void SerializeInto(BinaryWriter writer) writer.Write(StackObject); writer.Write(CodeOwnerGUID); writer.Write((Locals == null)?-1:Locals.Length); - if (Locals != null) writer.Write(VMSerializableUtils.ToByteArray(Locals)); + if (Locals != null) VMSerializableUtils.WriteArray(writer, Locals); writer.Write((Args == null) ? -1 : Args.Length); - if (Args != null) writer.Write(VMSerializableUtils.ToByteArray(Args)); + if (Args != null) VMSerializableUtils.WriteArray(writer, Args); writer.Write((byte)SpecialResult); writer.Write(ActionTree); } diff --git a/TSOClient/tso.simantics/Marshals/Threads/VMThreadMarshal.cs b/TSOClient/tso.simantics/Marshals/Threads/VMThreadMarshal.cs index f8d1d6a51..da8f4f0d3 100644 --- a/TSOClient/tso.simantics/Marshals/Threads/VMThreadMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/Threads/VMThreadMarshal.cs @@ -54,7 +54,7 @@ public void SerializeInto(BinaryWriter writer) foreach (var item in Queue) item.SerializeInto(writer); writer.Write(ActiveQueueBlock); - writer.Write(VMSerializableUtils.ToByteArray(TempRegisters)); + VMSerializableUtils.WriteArray(writer, TempRegisters); foreach (var item in TempXL) writer.Write(item); writer.Write((byte)LastStackExitCode); @@ -100,8 +100,7 @@ public void Deserialize(BinaryReader reader) } if (Version > 4) ActiveQueueBlock = reader.ReadSByte(); - TempRegisters = new short[20]; - for (int i = 0; i < 20; i++) TempRegisters[i] = reader.ReadInt16(); + TempRegisters = VMSerializableUtils.ReadArray(reader, 20); TempXL = new int[2]; for (int i = 0; i < 2; i++) TempXL[i] = reader.ReadInt32(); LastStackExitCode = (VMPrimitiveExitCode)reader.ReadByte(); diff --git a/TSOClient/tso.simantics/Marshals/VMArchitectureMarshal.cs b/TSOClient/tso.simantics/Marshals/VMArchitectureMarshal.cs index 028e55ff4..21934e2c3 100644 --- a/TSOClient/tso.simantics/Marshals/VMArchitectureMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/VMArchitectureMarshal.cs @@ -1,8 +1,8 @@ using FSO.SimAntics.NetPlay.Model; -using System.Linq; -using System.IO; using FSO.LotView.Model; using FSO.SimAntics.Model; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace FSO.SimAntics.Marshals { @@ -47,15 +47,16 @@ public void Deserialize(BinaryReader reader) Walls = new WallTile[Stories][]; for (int l=0;l(reader, size); + var level = new WallTile[size]; + for (int i = 0; i < size; i++) WallTileSerializer.Deserialize(in savedWalls[i], ref level[i]); + Walls[l] = level; } Floors = new FloorTile[Stories][]; for (int l = 0; l < Stories; l++) { - Floors[l] = new FloorTile[size]; - for (int i = 0; i < size; i++) Floors[l][i] = new FloorTile { Pattern = reader.ReadUInt16() }; + Floors[l] = VMSerializableUtils.ReadArray(reader, size); } WallsDirty = reader.ReadBoolean(); @@ -102,18 +103,19 @@ public void SerializeInto(BinaryWriter writer) foreach (var level in Walls) { - foreach (var wall in level) + var savedWalls = new WallTileSerialized[level.Length]; + + for (int i = 0; i < level.Length; i++) { - WallTileSerializer.SerializeInto(wall, writer); + WallTileSerializer.SerializeInto(in level[i], ref savedWalls[i]); } + + VMSerializableUtils.WriteArray(writer, savedWalls); } foreach (var level in Floors) { - foreach (var floor in level) - { - writer.Write(floor.Pattern); - } + VMSerializableUtils.WriteArray(writer, level); } writer.Write(WallsDirty); @@ -136,36 +138,30 @@ public void Preserialize() using (var mem = new MemoryStream()) { using (var io = new BinaryWriter(mem)) + { SerializeInto(io); - Preserialized = mem.ToArray(); + Preserialized = mem.ToArray(); + } } } } public static class WallTileSerializer { - public static WallTile Deserialize(BinaryReader reader) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Deserialize(in WallTileSerialized tile, ref WallTile output) { - var result = new WallTile(); - result.Segments = (WallSegments)reader.ReadByte(); - result.TopLeftPattern = reader.ReadUInt16(); - result.TopRightPattern = reader.ReadUInt16(); - result.BottomLeftPattern = reader.ReadUInt16(); - result.BottomRightPattern = reader.ReadUInt16(); - result.TopLeftStyle = reader.ReadUInt16(); - result.TopRightStyle = reader.ReadUInt16(); - return result; + Span resultTruncated = MemoryMarshal.Cast(MemoryMarshal.CreateSpan(ref output, 1)); + + resultTruncated[0] = tile; } - public static void SerializeInto(WallTile wall, BinaryWriter writer) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void SerializeInto(in WallTile tile, ref WallTileSerialized output) { - writer.Write((byte)wall.Segments); - writer.Write(wall.TopLeftPattern); - writer.Write(wall.TopRightPattern); - writer.Write(wall.BottomLeftPattern); - writer.Write(wall.BottomRightPattern); - writer.Write(wall.TopLeftStyle); - writer.Write(wall.TopRightStyle); + ReadOnlySpan sourceTruncated = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(in tile, 1)); + + output = sourceTruncated[0]; } } } diff --git a/TSOClient/tso.simantics/Marshals/VMAvatarMarshal.cs b/TSOClient/tso.simantics/Marshals/VMAvatarMarshal.cs index 0dc67576d..c3f272fc5 100644 --- a/TSOClient/tso.simantics/Marshals/VMAvatarMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/VMAvatarMarshal.cs @@ -66,7 +66,7 @@ public override void Deserialize(BinaryReader reader) var mdats = reader.ReadInt32(); MotiveData = new short[mdats]; - for (int i = 0; i < mdats; i++) MotiveData[i] = reader.ReadInt16(); + MotiveData = VMSerializableUtils.ReadArray(reader, mdats); HandObjectOld = reader.ReadInt16(); RadianDirection = reader.ReadSingle(); @@ -110,10 +110,10 @@ public override void SerializeInto(BinaryWriter writer) foreach (var item in MotiveChanges) { item.SerializeInto(writer); } MotiveDecay.SerializeInto(writer); writer.Write(PersonData.Length); - writer.Write(VMSerializableUtils.ToByteArray(PersonData)); + VMSerializableUtils.WriteArray(writer, PersonData); //foreach (var item in PersonData) { writer.Write(item); } writer.Write(MotiveData.Length); - writer.Write(VMSerializableUtils.ToByteArray(MotiveData)); + VMSerializableUtils.WriteArray(writer, MotiveData); //foreach (var item in MotiveData) { writer.Write(item); } writer.Write(HandObjectOld); writer.Write(RadianDirection); diff --git a/TSOClient/tso.simantics/Marshals/VMEntityMarshal.cs b/TSOClient/tso.simantics/Marshals/VMEntityMarshal.cs index a2e7150d4..9d9c33b96 100644 --- a/TSOClient/tso.simantics/Marshals/VMEntityMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/VMEntityMarshal.cs @@ -60,12 +60,10 @@ public virtual void Deserialize(BinaryReader reader) PlatformState.Deserialize(reader); var datas = reader.ReadInt32(); - ObjectData = new short[datas]; - for (int i = 0; i < datas; i++) ObjectData[i] = reader.ReadInt16(); + ObjectData = VMSerializableUtils.ReadArray(reader, datas); var listLen = reader.ReadInt32(); - MyList = new short[listLen]; - for (int i = 0; i < listLen; i++) MyList[i] = reader.ReadInt16(); + MyList = VMSerializableUtils.ReadArray(reader, listLen); if (reader.ReadBoolean()) { @@ -80,14 +78,12 @@ public virtual void Deserialize(BinaryReader reader) MainStackOBJ = reader.ReadInt16(); var contN = reader.ReadInt32(); - Contained = new short[contN]; - for (int i = 0; i < contN; i++) Contained[i] = reader.ReadInt16(); + Contained = VMSerializableUtils.ReadArray(reader, contN); Container = reader.ReadInt16(); ContainerSlot = reader.ReadInt16(); var attrN = reader.ReadInt32(); - Attributes = new short[attrN]; - for (int i = 0; i < attrN; i++) Attributes[i] = reader.ReadInt16(); + Attributes = VMSerializableUtils.ReadArray(reader, attrN); var relN = reader.ReadInt32(); MeToObject = new VMEntityRelationshipMarshal[relN]; @@ -130,10 +126,10 @@ public virtual void SerializeInto(BinaryWriter writer) writer.Write(PersistID); PlatformState.SerializeInto(writer); writer.Write(ObjectData.Length); - writer.Write(VMSerializableUtils.ToByteArray(ObjectData)); + VMSerializableUtils.WriteArray(writer, ObjectData); //foreach (var item in ObjectData) writer.Write(item); writer.Write(MyList.Length); - writer.Write(VMSerializableUtils.ToByteArray(MyList)); + VMSerializableUtils.WriteArray(writer, MyList); //foreach (var item in MyList) writer.Write(item); writer.Write(Headline != null); @@ -146,13 +142,13 @@ public virtual void SerializeInto(BinaryWriter writer) writer.Write(MainStackOBJ); writer.Write(Contained.Length); //object ids - writer.Write(VMSerializableUtils.ToByteArray(Contained)); + VMSerializableUtils.WriteArray(writer, Contained); //foreach (var item in Contained) writer.Write(item); writer.Write(Container); writer.Write(ContainerSlot); writer.Write(Attributes.Length); - writer.Write(VMSerializableUtils.ToByteArray(Attributes)); + VMSerializableUtils.WriteArray(writer, Attributes); //foreach (var item in Attributes) writer.Write(item); writer.Write(MeToObject.Length); foreach (var item in MeToObject) item.SerializeInto(writer); @@ -188,7 +184,7 @@ public virtual void SerializeInto(BinaryWriter writer) { writer.Write(Target); writer.Write(Values.Length); - writer.Write(VMSerializableUtils.ToByteArray(Values)); + VMSerializableUtils.WriteArray(writer, Values); } } @@ -212,7 +208,7 @@ public virtual void SerializeInto(BinaryWriter writer) { writer.Write(Target); writer.Write(Values.Length); - writer.Write(VMSerializableUtils.ToByteArray(Values)); + VMSerializableUtils.WriteArray(writer, Values); } } } diff --git a/TSOClient/tso.simantics/Marshals/VMMarshal.cs b/TSOClient/tso.simantics/Marshals/VMMarshal.cs index 4ff7d630c..f1f6974f3 100644 --- a/TSOClient/tso.simantics/Marshals/VMMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/VMMarshal.cs @@ -24,7 +24,8 @@ public class VMMarshal : VMSerializable // 36 - FSO Inventory Token (inventory ops async state has temp list) // 37 - Inventory Token Total // 38 - Direct Control Frame - public static readonly int LATEST_VERSION = 38; + // 39 - VMTSOLotState Flags + public static readonly int LATEST_VERSION = 39; public int Version = LATEST_VERSION; public bool Compressed = true; diff --git a/TSOClient/tso.simantics/Marshals/VMMultitileGroupMarshal.cs b/TSOClient/tso.simantics/Marshals/VMMultitileGroupMarshal.cs index 065fd4c3c..884a82b25 100644 --- a/TSOClient/tso.simantics/Marshals/VMMultitileGroupMarshal.cs +++ b/TSOClient/tso.simantics/Marshals/VMMultitileGroupMarshal.cs @@ -25,8 +25,7 @@ public void Deserialize(BinaryReader reader) if (Version > 12) SalePrice = reader.ReadInt32(); var objs = reader.ReadInt32(); - Objects = new short[objs]; - for (int i=0; i(reader, objs); Offsets = new LotTilePos[objs]; for (int i = 0; i < objs; i++) @@ -43,7 +42,7 @@ public void SerializeInto(BinaryWriter writer) writer.Write(Price); writer.Write(SalePrice); writer.Write(Objects.Length); - writer.Write(VMSerializableUtils.ToByteArray(Objects)); + VMSerializableUtils.WriteArray(writer, Objects); foreach (var item in Offsets) item.SerializeInto(writer); } } diff --git a/TSOClient/tso.simantics/Model/Platform/VMDefaultValidator.cs b/TSOClient/tso.simantics/Model/Platform/VMDefaultValidator.cs index 0feed733c..d234049da 100644 --- a/TSOClient/tso.simantics/Model/Platform/VMDefaultValidator.cs +++ b/TSOClient/tso.simantics/Model/Platform/VMDefaultValidator.cs @@ -94,7 +94,7 @@ public override PurchaseMode GetPurchaseMode(PurchaseMode desired, VMAvatar ava, var whitelist = (ava.AvatarState.Permissions == VMTSOAvatarPermissions.Roommate) ? RoomieWhiteList : BuilderWhiteList; if (item == null || !whitelist.Contains(item.Value.Category)) { - if (ava.AvatarState.Permissions != VMTSOAvatarPermissions.Admin) return PurchaseMode.Disallowed; + if (!ava.AvatarState.Flags.HasFlag(VMTSOAvatarFlags.Debug)) return PurchaseMode.Disallowed; } } diff --git a/TSOClient/tso.simantics/Model/Platform/VMIAvatarState.cs b/TSOClient/tso.simantics/Model/Platform/VMIAvatarState.cs index c0ca4aa5e..772837248 100644 --- a/TSOClient/tso.simantics/Model/Platform/VMIAvatarState.cs +++ b/TSOClient/tso.simantics/Model/Platform/VMIAvatarState.cs @@ -5,5 +5,6 @@ namespace FSO.SimAntics.Model.Platform public interface VMIAvatarState { VMTSOAvatarPermissions Permissions { get; set; } + VMTSOAvatarFlags Flags { get; set; } } } diff --git a/TSOClient/tso.simantics/Model/TS1Platform/VMTS1AvatarState.cs b/TSOClient/tso.simantics/Model/TS1Platform/VMTS1AvatarState.cs index 48d2d8e11..d0f6500a2 100644 --- a/TSOClient/tso.simantics/Model/TS1Platform/VMTS1AvatarState.cs +++ b/TSOClient/tso.simantics/Model/TS1Platform/VMTS1AvatarState.cs @@ -13,6 +13,10 @@ public VMTSOAvatarPermissions Permissions { get; set; } + public VMTSOAvatarFlags Flags + { + get; set; + } public override void Deserialize(BinaryReader reader) { diff --git a/TSOClient/tso.simantics/Model/TSOPlatform/IVMAvatarNameCache.cs b/TSOClient/tso.simantics/Model/TSOPlatform/IVMAvatarNameCache.cs deleted file mode 100644 index 65f7a11f3..000000000 --- a/TSOClient/tso.simantics/Model/TSOPlatform/IVMAvatarNameCache.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; - -namespace FSO.SimAntics.Model.TSOPlatform -{ - /// - /// An interface that allows components outwith SimAntics to provide names for avatars not present within the lot. - /// - public interface IVMAvatarNameCache - { - string GetNameForID(VM vm, uint persistID); - - /// - /// Called to cache an avatar in. This is asynchronous - so it should be called before the user has any chance to do any action that requires the name. - /// Ideal call times: When we join the lot (cache all roommates), when a roommate changes (cache the new roommate). - /// - /// The Persist ID for the avatar whose name we want to cache. - bool Precache(VM vm, uint persistID); - } - - public class VMBasicAvatarNameCache : IVMAvatarNameCache - { - protected Dictionary AvatarNames = new Dictionary(); - - public virtual string GetNameForID(VM vm, uint persistID) - { - if (persistID == 0) return ""; - string name; - if (AvatarNames.TryGetValue(persistID, out name)) - return name; - if (Precache(vm, persistID)) - { - if (AvatarNames.TryGetValue(persistID, out name)) - return name; - } - return "(offline user)"; - } - - public virtual bool Precache(VM vm, uint persistID) - { - //very simple implementation. if the sim is in the lot, cache their name - var ava = vm.GetAvatarByPersist(persistID); - if (ava != null) - { - AvatarNames[persistID] = ava.Name; - return true; - } - return false; - } - } -} diff --git a/TSOClient/tso.simantics/Model/TSOPlatform/IVMGlobalNameCache.cs b/TSOClient/tso.simantics/Model/TSOPlatform/IVMGlobalNameCache.cs new file mode 100644 index 000000000..bff88004e --- /dev/null +++ b/TSOClient/tso.simantics/Model/TSOPlatform/IVMGlobalNameCache.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; + +namespace FSO.SimAntics.Model.TSOPlatform +{ + /// + /// An interface that allows components outwith SimAntics to provide names for entities not present within the lot. + /// + public interface IVMGlobalNameCache + { + string GetNameForID(VM vm, VMGlobalEntityType type, uint persistID); + + /// + /// Called to cache an entity in. This is asynchronous - so it should be called before the user has any chance to do any action that requires the name. + /// Ideal call times: When we join the lot (cache all roommates), when a roommate changes (cache the new roommate). + /// + /// The Persist ID for the avatar whose name we want to cache. + bool Precache(VM vm, VMGlobalEntityType type, uint persistID); + } + + public enum VMGlobalEntityType + { + Avatar, + Lot + } + + public class VMBasicGlobalNameCache : IVMGlobalNameCache + { + protected Dictionary> Caches = []; + + protected Dictionary GetTypeCache(VMGlobalEntityType type) + { + if (!Caches.TryGetValue(type, out var cache)) + { + cache = new Dictionary(); + Caches[type] = cache; + } + + return cache; + } + + public virtual string GetNameForID(VM vm, VMGlobalEntityType type, uint persistID) + { + if (persistID == 0) return ""; + + var cache = GetTypeCache(type); + + string name; + if (cache.TryGetValue(persistID, out name)) + return name; + if (Precache(vm, type, persistID)) + { + if (cache.TryGetValue(persistID, out name)) + return name; + } + + switch (type) + { + case VMGlobalEntityType.Avatar: + return "(offline user)"; + case VMGlobalEntityType.Lot: + return $"({(short)(persistID >> 16)}, {(short)persistID})"; + default: + return "Retrieving..."; + } + } + + public virtual bool Precache(VM vm, VMGlobalEntityType type, uint persistID) + { + //very simple implementation. if the sim is in the lot, cache their name + var ava = vm.GetAvatarByPersist(persistID); + if (ava != null) + { + var cache = GetTypeCache(type); + cache[persistID] = ava.Name; + return true; + } + return false; + } + } +} diff --git a/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOAvatarState.cs b/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOAvatarState.cs index 7e9b792dd..a515d457f 100644 --- a/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOAvatarState.cs +++ b/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOAvatarState.cs @@ -11,7 +11,8 @@ public class VMTSOAvatarState : VMTSOEntityState, VMIAvatarState public VMTSOAvatarPermissions Permissions { get; set; } = VMTSOAvatarPermissions.Visitor; public HashSet IgnoredAvatars = new HashSet(); public Dictionary JobInfo = new Dictionary(); - public VMTSOAvatarFlags Flags; + public VMTSOAvatarFlags Flags { get; set; } + public bool IsSpectator => Flags.HasFlag(VMTSOAvatarFlags.Spectator); public Color ChatColor = Color.White; public sbyte ChatTTSPitch; //-100 to 100. public byte ChatChannel = 255; //the chat channel this avatar is viewing. 255 = all. @@ -105,8 +106,29 @@ public enum VMTSOAvatarPermissions : byte [Flags] public enum VMTSOAvatarFlags : uint { + /// + /// Isn't a roomie or owner of another lot. + /// CanBeRoommate = 1, //TODO: update on becoming roomie of another lot, while on this lot. - NewPlayer = 2, //under a week old - Mayor = 4, //is mayor of this neighborhood + + /// + /// Under a week old. + /// + NewPlayer = 2, + + /// + /// Is mayor of this neighborhood. + /// + Mayor = 4, + + /// + /// Read-only visitor in spectator mode. + /// + Spectator = 8, + + /// + /// Can use debug interactions. Tends to be set on moderators/administrators, but archive mode can enforce extra rules. + /// + Debug = 16, } } diff --git a/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOLotState.cs b/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOLotState.cs index b66b0e55d..89280f01f 100644 --- a/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOLotState.cs +++ b/TSOClient/tso.simantics/Model/TSOPlatform/VMTSOLotState.cs @@ -7,10 +7,20 @@ namespace FSO.SimAntics.Model.TSOPlatform { + [Flags] + public enum VMTSOLotStateFlags : uint + { + Archived = 1, + AllowFreeRoam = 2, + DebugAdmin = 4, + DebugMod = 8, + DebugAll = 16 + } + public class VMTSOLotState : VMAbstractLotState { //ephemeral state - public IVMAvatarNameCache Names = new VMBasicAvatarNameCache(); + public IVMGlobalNameCache Names = new VMBasicGlobalNameCache(); //permanent state public string Name = "Lot"; @@ -30,6 +40,7 @@ public class VMTSOLotState : VMAbstractLotState public byte SkillMode; public List ChatChannels = new List(); public uint NhoodID; + public VMTSOLotStateFlags Flags; public bool CommunityLot { @@ -96,6 +107,11 @@ public override void Deserialize(BinaryReader reader) { NhoodID = reader.ReadUInt32(); } + + if (Version > 38) + { + Flags = (VMTSOLotStateFlags)reader.ReadUInt32(); + } } public override void SerializeInto(BinaryWriter writer) @@ -122,6 +138,7 @@ public override void SerializeInto(BinaryWriter writer) channel.SerializeInto(writer); } writer.Write(NhoodID); + writer.Write((uint)Flags); } public override bool CanPlaceNewUserObject(VM vm) diff --git a/TSOClient/tso.simantics/Model/VMAnimationState.cs b/TSOClient/tso.simantics/Model/VMAnimationState.cs index 03d42f725..2f7cbf920 100644 --- a/TSOClient/tso.simantics/Model/VMAnimationState.cs +++ b/TSOClient/tso.simantics/Model/VMAnimationState.cs @@ -12,9 +12,10 @@ public class VMAnimationState { public byte EventsRun; //total # of xevts fired during anim. up to short if anything uses more than 255. public bool EndReached; public bool PlayingBackwards; - public float Speed = 1.0f; + public float Speed = 30 / 25f; public float Weight = 1.0f; //For animation blending. All active animations should add up to 1 but won't break if it doesn't. public bool Loop = false; + private List TimePropertyListBuilder = new List(); public List TimePropertyLists = new List(); public VMAnimationState(Animation animation, bool backwards) @@ -27,11 +28,13 @@ public VMAnimationState(Animation animation, bool backwards) CurrentFrame = Anim.NumFrames; } - GetTimeProps(); + ResetTimeProps(); } - private void GetTimeProps() + public void ResetTimeProps() { + TimePropertyListBuilder.Clear(); + var animation = Anim; foreach (var motion in animation.Motions) { @@ -41,14 +44,15 @@ private void GetTimeProps() { foreach (var item in tp.Items) { - TimePropertyLists.Add(item); + TimePropertyListBuilder.Add(item); } } } /** Sort time property lists by time **/ //stable sort - TimePropertyLists.OrderBy(x => x.ID); //.Sort(new TimePropertyListItemSorter()); + TimePropertyLists.Clear(); + TimePropertyLists.AddRange(TimePropertyListBuilder.OrderBy(x => x.ID)); } #region VM Marshalling Functions @@ -79,7 +83,7 @@ public virtual void Load(VMAnimationStateMarshal input) Speed = input.Speed; Weight = input.Weight; Loop = input.Loop; - GetTimeProps(); + ResetTimeProps(); var currentFrame = CurrentFrame; var currentTime = (currentFrame * 1000) / 30; diff --git a/TSOClient/tso.simantics/Model/VMArchitectureTerrain.cs b/TSOClient/tso.simantics/Model/VMArchitectureTerrain.cs index 977e1929c..946f72d72 100644 --- a/TSOClient/tso.simantics/Model/VMArchitectureTerrain.cs +++ b/TSOClient/tso.simantics/Model/VMArchitectureTerrain.cs @@ -1,5 +1,6 @@ using FSO.Content.Model; using FSO.SimAntics.NetPlay.Model; +using FSO.SimAntics.Utils; using System; using System.IO; @@ -9,7 +10,6 @@ public class VMArchitectureTerrain : VMSerializable { public int Width; public int Height; - public bool LowQualityGrassState; public short[] Heights; public short[] Centers; @@ -155,13 +155,13 @@ public static float[] GeneratePerlinNoise(int size, int seed) return result; } - public void GenerateGrassStates() //generates a set of grass states for a lot. + public void GenerateGrassStates(RestoreLotType type) //generates a set of grass states for a lot. { //right now only works for square lots, but that's all tso has! var random = new Random(); int width = Width; - if (LowQualityGrassState) + if (type == RestoreLotType.Blank) { GrassState = new byte[Width * Height]; return; @@ -240,9 +240,8 @@ public void SerializeInto(BinaryWriter writer) { writer.Write((byte)LightType); writer.Write((byte)DarkType); - var ba = VMSerializableUtils.ToByteArray(Heights); - writer.Write(ba.Length); - writer.Write(ba); + writer.Write(Heights.Length * 2); + VMSerializableUtils.WriteArray(writer, Heights); writer.Write(GrassState.Length); writer.Write(GrassState); } @@ -251,12 +250,14 @@ public void Deserialize(BinaryReader reader) { LightType = (TerrainType)reader.ReadByte(); DarkType = (TerrainType)reader.ReadByte(); - var dat = reader.ReadBytes(reader.ReadInt32()); + int byteCount = reader.ReadInt32(); if (Version > 18) { - Heights = VMSerializableUtils.ToTArray(dat); - } else + Heights = VMSerializableUtils.ReadArray(reader, byteCount / 2); + } + else { + var dat = reader.ReadBytes(byteCount); Heights = Array.ConvertAll(dat, x => (short)x); } diff --git a/TSOClient/tso.simantics/Model/VMArguments.cs b/TSOClient/tso.simantics/Model/VMArguments.cs new file mode 100644 index 000000000..cddd75de8 --- /dev/null +++ b/TSOClient/tso.simantics/Model/VMArguments.cs @@ -0,0 +1,133 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace FSO.SimAntics.Model +{ + /// + /// Arguments for a SimAntics stack frame. + /// These typically have 4 elements, but can use more if required. + /// + public struct VMArguments + { + // If this is zero, default to 4. + private readonly int LengthPlusOne; + private short Arg1; +#pragma warning disable IDE0044 // Add readonly modifier + private short Arg2; + private short Arg3; + private short Arg4; +#pragma warning restore IDE0044 // Add readonly modifier + + public readonly int Length => LengthPlusOne == 0 ? 4 : (LengthPlusOne - 1); + + private readonly short[] ExtraArgs; + private Span BaseArgs => MemoryMarshal.CreateSpan(ref Arg1, 4); + + private static void ThrowIndexOutOfRangeException() + { + throw new IndexOutOfRangeException(); + } + + public short this[int index] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + if (index >= Length) + { + ThrowIndexOutOfRangeException(); + } + + if (index < 4) + { + return BaseArgs[index]; + } + + return ExtraArgs[index - 4]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + set + { + if (index >= Length) + { + ThrowIndexOutOfRangeException(); + } + + if (index < 4) + { + BaseArgs[index] = value; + } + else + { + ExtraArgs[index - 4] = value; + } + } + } + + public VMArguments(ReadOnlySpan args) + { + LengthPlusOne = args.Length + 1; + + args[..Math.Min(4, args.Length)].CopyTo(BaseArgs); + + if (args.Length > 4) + { + // Extras go on a heap array + ExtraArgs = new short[args.Length - 4]; + + args[4..].CopyTo(ExtraArgs); + } + } + + public VMArguments(int size) + { + LengthPlusOne = size + 1; + + if (size > 4) + { + ExtraArgs = new short[size - 4]; + } + } + + public short[] Clone() + { + if (ExtraArgs != null) + { + short[] array = [.. BaseArgs, .. ExtraArgs]; + return array; + } + + return [ ..BaseArgs[..Length] ]; + } + + public Span ToSpan() + { + if (ExtraArgs != null) + { + short[] array = [.. BaseArgs, .. ExtraArgs]; + return array; + } + + return BaseArgs[..Length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ref short GetRef(int index) + { + if (index >= Length) + { + ThrowIndexOutOfRangeException(); + } + + if (index < 4) + { + return ref BaseArgs[index]; + } + else + { + return ref ExtraArgs[index - 4]; + } + } + } +} diff --git a/TSOClient/tso.simantics/Model/VMBuildableAreaInfo.cs b/TSOClient/tso.simantics/Model/VMBuildableAreaInfo.cs index b8d76a795..14f83b213 100644 --- a/TSOClient/tso.simantics/Model/VMBuildableAreaInfo.cs +++ b/TSOClient/tso.simantics/Model/VMBuildableAreaInfo.cs @@ -129,6 +129,8 @@ public static void UpdateOverbudgetObjects(VM vm) var limit = GetObjectLimit(vm); vm.TSOState.ObjectLimit = limit; + var disableOverbudget = !vm.TSOState.Flags.HasFlag(TSOPlatform.VMTSOLotStateFlags.Archived); + //community lots cannot go overbudget, as the object limit does not change! if (!vm.TSOState.CommunityLot) { @@ -151,7 +153,7 @@ public static void UpdateOverbudgetObjects(VM vm) { if (o is VMGameObject) { - if (i >= limit) + if (i >= limit && disableOverbudget) { ((VMGameObject)o).Disabled |= VMGameObjectDisableFlags.ObjectLimitExceeded; ((VMGameObject)o).Disabled &= ~VMGameObjectDisableFlags.ObjectLimitThreadDisable; diff --git a/TSOClient/tso.simantics/Model/VMGenericTSOCallMode.cs b/TSOClient/tso.simantics/Model/VMGenericTSOCallMode.cs index 8a5d7b510..c225dbd5f 100644 --- a/TSOClient/tso.simantics/Model/VMGenericTSOCallMode.cs +++ b/TSOClient/tso.simantics/Model/VMGenericTSOCallMode.cs @@ -84,6 +84,7 @@ public enum VMGenericTSOCallMode : byte FSOIsStackObjectTradable = 138, FSOSetStackObjectTransient = 139, FSOIsStackObjectPendingRoommateDeletion = 140, - FSOIsStackObjectAllowedByLotCategory = 141 + FSOIsStackObjectAllowedByLotCategory = 141, + FSOShowCheckTreeTooltipTemp0Temp1 = 142 } } diff --git a/TSOClient/tso.simantics/Model/VMObjectList.cs b/TSOClient/tso.simantics/Model/VMObjectList.cs new file mode 100644 index 000000000..0625fc328 --- /dev/null +++ b/TSOClient/tso.simantics/Model/VMObjectList.cs @@ -0,0 +1,178 @@ +using System.Collections; +using System.Runtime.InteropServices; + +namespace FSO.SimAntics.Model +{ + public struct VMObjectListEntry where T : VMEntity + { + public readonly short ObjectID; + public readonly T Object; + + public VMObjectListEntry(T obj) + { + ObjectID = obj.ObjectID; + Object = obj; + } + } + + public class VMObjectList : IList where T : VMEntity + { + private readonly List> List = []; + + public int Count => List.Count; + + public bool IsReadOnly => false; + + public T this[int index] { get => List[index].Object; set => List[index] = new VMObjectListEntry(value); } + + public void AddToObjList(T entity) + { + var raw = CollectionsMarshal.AsSpan(List); + if (raw.Length == 0) { Add(entity); return; } + + int id = entity.ObjectID; + int max = raw.Length; + int min = 0; + while (max > min) + { + int mid = (max + min) / 2; + int nid = raw[mid].ObjectID; + if (id < nid) max = mid; + else if (id == nid) return; //do not add dupes + else min = mid + 1; + } + Insert(min, entity); + } + + public int FindInObjList(T entity) + { + var raw = CollectionsMarshal.AsSpan(List); + if (raw.Length == 0) { return -1; } + + int id = entity.ObjectID; + int max = raw.Length; + int min = 0; + while (max > min) + { + int mid = (max + min) / 2; + int nid = raw[mid].ObjectID; + if (id < nid) max = mid; + else if (id == nid) + { + return mid; + } + else min = mid + 1; + } + + return -1; + } + + public bool DeleteFromObjList(T entity) + { + var raw = CollectionsMarshal.AsSpan(List); + if (raw.Length == 0) { return false; } + + int id = entity.ObjectID; + int max = raw.Length; + int min = 0; + while (max > min) + { + int mid = (max + min) / 2; + int nid = raw[mid].ObjectID; + if (id < nid) max = mid; + else if (id == nid) + { + RemoveAt(mid); //found it + return true; + } + else min = mid + 1; + } + return false; + } + + public int FindNextIndexInObjList(short targId) + { + var raw = CollectionsMarshal.AsSpan(List); + int count = raw.Length; + if (count == 0) return 0; + + int max = count; + int min = 0; + while (max > min) + { + int mid = (max + min) / 2; + int nid = raw[mid].ObjectID; + if (targId < nid) max = mid; //target object is below us + else if (targId == nid) + { + //found it. find NEXT! + return mid + 1; + } + else min = mid + 1; //target object is above us + } + if (min >= count) return count; + return raw[min].ObjectID > targId ? min : min + 1; + } + + public int IndexOf(T item) + { + return FindInObjList(item); + } + + public void Insert(int index, T item) + { + List.Insert(index, new(item)); + } + + public void RemoveAt(int index) + { + List.RemoveAt(index); + } + + public void Add(T item) + { + List.Add(new(item)); + } + + public void Clear() + { + List.Clear(); + } + + public bool Contains(T item) + { + return FindInObjList(item) != -1; + } + + public void CopyTo(T[] array, int arrayIndex) + { + ArgumentNullException.ThrowIfNull(array); + ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex); + + if (arrayIndex + Count > array.Length) + { + throw new ArgumentException(null, nameof(array)); + } + + foreach (var entry in List) + { + array[arrayIndex++] = entry.Object; + } + } + + public bool Remove(T item) + { + return DeleteFromObjList(item); + } + + public IEnumerator GetEnumerator() + { + return List.Select(x => x.Object).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/TSOClient/tso.simantics/Model/VMObjectQueries.cs b/TSOClient/tso.simantics/Model/VMObjectQueries.cs index 35f2dc973..0202c0c9c 100644 --- a/TSOClient/tso.simantics/Model/VMObjectQueries.cs +++ b/TSOClient/tso.simantics/Model/VMObjectQueries.cs @@ -1,24 +1,21 @@ using FSO.LotView.Model; using FSO.SimAntics.Entities; using FSO.SimAntics.Model.TSOPlatform; -using System; -using System.Collections.Generic; -using System.Linq; namespace FSO.SimAntics.Model { public class VMObjectQueries { private VMContext Context; - private Dictionary> TileToObjects = new Dictionary>(); + private Dictionary> TileToObjects = []; - private Dictionary> ObjectsByGUID = new Dictionary>(); - private Dictionary> ObjectsByCategory = new Dictionary>(); - private Dictionary> ObjectsBySemiGlobal = new Dictionary>(); - public List Avatars = new List(); - public Dictionary AvatarsByPersist = new Dictionary(); - public Dictionary MultitileByPersist = new Dictionary(); - public List WithAutonomy = new List(); + private Dictionary> ObjectsByGUID = []; + private Dictionary> ObjectsByCategory = []; + private Dictionary> ObjectsBySemiGlobal = []; + public VMObjectList Avatars = []; + public Dictionary AvatarsByPersist = []; + public Dictionary MultitileByPersist = []; + public VMObjectList WithAutonomy = []; public int NumUserObjects { @@ -56,24 +53,22 @@ public void RegisterObjectPos(VMEntity ent) { var off = GetOffest(ent.Position); - List tile; - if (!TileToObjects.TryGetValue(off, out tile)) + if (!TileToObjects.TryGetValue(off, out var tile)) { - tile = new List(); + tile = []; TileToObjects.Add(off, tile); } - VM.AddToObjList(tile, ent); //if it's already on this tile, this will do nothing + tile.AddToObjList(ent); //if it's already on this tile, this will do nothing } public void UnregisterObjectPos(VMEntity ent) { var off = GetOffest(ent.Position); - List tile; - if (TileToObjects.TryGetValue(off, out tile)) + if (TileToObjects.TryGetValue(off, out var tile)) { - tile.Remove(ent); + tile.DeleteFromObjList(ent); if (tile.Count == 0) TileToObjects.Remove(off); } } @@ -126,54 +121,51 @@ public void RemoveMultitilePersist(VM vm, uint persistID) public void RegisterCategory(VMEntity obj, short category) { - List tile; - if (!ObjectsByCategory.TryGetValue(category, out tile)) + if (!ObjectsByCategory.TryGetValue(category, out var tile)) { - tile = new List(); + tile = []; ObjectsByCategory.Add(category, tile); } //debug check: use if things are going weird //if (!tile.Contains(obj)) - VM.AddToObjList(tile, obj); + tile.AddToObjList(obj); } public void RemoveCategory(VMEntity obj, short category) { - List tile; - - if (ObjectsByCategory.TryGetValue(category, out tile)) + if (ObjectsByCategory.TryGetValue(category, out var tile)) { - VM.DeleteFromObjList(tile, obj); + tile.DeleteFromObjList(obj); if (tile.Count == 0) ObjectsByCategory.Remove(category); } } public void RegisterSemiGlobal(VMEntity obj, string semiGlobal) { - List tile; + VMObjectList tile; if (semiGlobal != null) { if (!ObjectsBySemiGlobal.TryGetValue(semiGlobal.ToLowerInvariant(), out tile)) { - tile = new List(); + tile = []; ObjectsBySemiGlobal.Add(semiGlobal.ToLowerInvariant(), tile); } //debug check: use if things are going weird //if (!tile.Contains(obj)) - VM.AddToObjList(tile, obj); + tile.AddToObjList(obj); } } public void RemoveSemiGlobal(VMEntity obj, string semiGlobal) { - List tile; + VMObjectList tile; if (semiGlobal != null) { if (ObjectsBySemiGlobal.TryGetValue(semiGlobal, out tile)) { - VM.DeleteFromObjList(tile, obj); + tile.DeleteFromObjList(obj); if (tile.Count == 0) ObjectsBySemiGlobal.Remove(semiGlobal); } } @@ -183,14 +175,13 @@ public void NewObject(VMEntity obj) { var guid = obj.Object.OBJ.GUID; - List list; - if (!ObjectsByGUID.TryGetValue(guid, out list)) + if (!ObjectsByGUID.TryGetValue(guid, out var list)) { - list = new List(); + list = []; ObjectsByGUID.Add(guid, list); } - VM.AddToObjList(list, obj); + list.AddToObjList(obj); RegisterCategory(obj, obj.GetValue(VMStackObjectVariable.Category)); if (obj.SemiGlobal != null) @@ -203,13 +194,13 @@ public void NewObject(VMEntity obj) if (obj is VMAvatar) { - VM.AddToObjList(Avatars, obj); + Avatars.AddToObjList(obj); if (obj.PersistID != 0) AvatarsByPersist[obj.PersistID] = (VMAvatar)obj; } if (obj.TreeTable != null && obj.TreeTable.AutoInteractions.Length > 0) { - VM.AddToObjList(WithAutonomy, obj); + WithAutonomy.AddToObjList(obj); } } @@ -217,10 +208,9 @@ public void RemoveObject(VMEntity obj) { var guid = obj.Object.OBJ.GUID; - List list; - if (ObjectsByGUID.TryGetValue(guid, out list)) + if (ObjectsByGUID.TryGetValue(guid, out var list)) { - VM.DeleteFromObjList(list, obj); + list.DeleteFromObjList(obj); if (list.Count == 0) ObjectsByGUID.Remove(guid); } @@ -236,7 +226,7 @@ public void RemoveObject(VMEntity obj) if (obj is VMAvatar) { - Avatars.Remove(obj); + Avatars.DeleteFromObjList(obj); AvatarsByPersist.Remove(obj.PersistID); } else if (obj.PersistID > 0 && obj.MultitileGroup.Objects.Count == 1) @@ -251,38 +241,34 @@ public void RemoveObject(VMEntity obj) if (obj.TreeTable != null && obj.TreeTable.AutoInteractions.Length > 0) { - WithAutonomy.Remove(obj); + WithAutonomy.DeleteFromObjList(obj); } } - public List GetObjectsAt(LotTilePos pos) + public VMObjectList GetObjectsAt(LotTilePos pos) { var off = GetOffest(pos); - List tile; - TileToObjects.TryGetValue(off, out tile); + TileToObjects.TryGetValue(off, out var tile); return tile; } - public List GetObjectsByGUID(uint guid) + public VMObjectList GetObjectsByGUID(uint guid) { - List tile; - ObjectsByGUID.TryGetValue(guid, out tile); + ObjectsByGUID.TryGetValue(guid, out var tile); return tile; } - public List GetObjectsByCategory(short category) + public VMObjectList GetObjectsByCategory(short category) { - List tile; - ObjectsByCategory.TryGetValue(category, out tile); + ObjectsByCategory.TryGetValue(category, out var tile); return tile; } - public List GetObjectsBySemiGlobal(string semiGlobal) + public VMObjectList GetObjectsBySemiGlobal(string semiGlobal) { - List tile; - ObjectsBySemiGlobal.TryGetValue(semiGlobal.ToLowerInvariant(), out tile); + ObjectsBySemiGlobal.TryGetValue(semiGlobal.ToLowerInvariant(), out var tile); return tile; } } diff --git a/TSOClient/tso.simantics/Model/VMRoomInfo.cs b/TSOClient/tso.simantics/Model/VMRoomInfo.cs index fbefd7436..8192ceeb1 100644 --- a/TSOClient/tso.simantics/Model/VMRoomInfo.cs +++ b/TSOClient/tso.simantics/Model/VMRoomInfo.cs @@ -1,8 +1,6 @@ using FSO.SimAntics.Model.Routing; using FSO.SimAntics.NetPlay.Model; using Microsoft.Xna.Framework; -using System.Collections.Generic; -using System.IO; using FSO.LotView.Model; namespace FSO.SimAntics.Model @@ -11,7 +9,7 @@ public struct VMRoomInfo { public List Portals; public List WindowPortals; - public List Entities; + public VMObjectList Entities; public List DynamicObstacles; public VMObstacleSet StaticObstacles; diff --git a/TSOClient/tso.simantics/Model/VMRoomMap.cs b/TSOClient/tso.simantics/Model/VMRoomMap.cs index 817c58037..027367111 100644 --- a/TSOClient/tso.simantics/Model/VMRoomMap.cs +++ b/TSOClient/tso.simantics/Model/VMRoomMap.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Text; using Microsoft.Xna.Framework; using FSO.LotView.Model; using FSO.SimAntics.Model.Routing; @@ -17,14 +14,14 @@ public class VMRoomMap public int Width; public int Height; - private ushort ExpectedTile; - /// /// Generates the room map for the specified walls array. /// public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int height, List rooms, sbyte floor, VMContext context) //for first floor gen, curRoom should be 1. For floors above, it should be the last genmap result { - Map = new uint[width*height]; //although 0 is the base of the array, room 1 is known to simantics as room 0. + uint[] map = new uint[width*height]; //although 0 is the base of the array, room 1 is known to simantics as room 0. + Map = map; + //values of 0 indicate the room has not been chosen in that location yet. bool noFloorBad = (rooms.Count > 1); @@ -37,11 +34,16 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei bool remaining = true; bool outside = true; int i = 0; + + var spread = new Stack(width * height); + + ushort expectedTile = 0; + while (remaining) { - var spread = new Stack(); + spread.Clear(); remaining = false; - while (i < Map.Length) + while (i < map.Length) { remaining = true; @@ -49,50 +51,50 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei var wall = Walls[i]; var segs = wall.Segments; var room = (uint)rooms.Count; - if (Map[i] == 0 && (segs & (WallSegments.AnyDiag)) == 0) + if (map[i] == 0 && (segs & (WallSegments.AnyDiag)) == 0) { //normal tile - no diagonal - ExpectedTile = Floors[i].Pattern; - Map[i] = room | (room << 16); + expectedTile = Floors[i].Pattern; + map[i] = room | (room << 16); spread.Push(new SpreadItem(new Point(i % width, i / width), WallSegments.AnyAdj)); break; } - else if ((Map[i] & 0xFFFF) == 0) + else if ((map[i] & 0xFFFF) == 0) { //start spreading from this side of the diagonal WallSegments validSpread; if ((segs & WallSegments.HorizontalDiag) > 0) { validSpread = WallSegments.TopLeft | WallSegments.TopRight; - ExpectedTile = wall.TopLeftStyle; - Map[i] |= 0x80000000; + expectedTile = wall.TopLeftStyle; + map[i] |= 0x80000000; } else { validSpread = WallSegments.TopRight | WallSegments.BottomRight; - ExpectedTile = wall.TopLeftPattern; + expectedTile = wall.TopLeftPattern; } - Map[i] |= room; + map[i] |= room; spread.Push(new SpreadItem(new Point(i % width, i / width), validSpread)); break; } - else if ((Map[i] & 0x7FFF0000) == 0) + else if ((map[i] & 0x7FFF0000) == 0) { //start spreading the other side WallSegments validSpread; if ((segs & WallSegments.HorizontalDiag) > 0) { validSpread = WallSegments.BottomLeft | WallSegments.BottomRight; - ExpectedTile = wall.TopLeftPattern; - Map[i] |= 0x80000000; + expectedTile = wall.TopLeftPattern; + map[i] |= 0x80000000; } else { validSpread = WallSegments.TopLeft | WallSegments.BottomLeft; - ExpectedTile = wall.TopLeftStyle; + expectedTile = wall.TopLeftStyle; } - Map[i] |= (room << 16); + map[i] |= (room << 16); spread.Push(new SpreadItem(new Point(i % width, i / width), validSpread)); i++; break; @@ -103,9 +105,10 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei if (remaining) { - int rminX = spread.Peek().Pt.X; + var start = spread.Peek(); + int rminX = start.Pt.X; int rmaxX = rminX; - int rminY = spread.Peek().Pt.Y; + int rminY = start.Pt.Y; int rmaxY = rminY; var wallObs = new List(); var wallLines = (VM.UseWorld)?new VMWallLineBuilder():null; @@ -183,19 +186,19 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei bool segAllow = ((PXWalls.Segments & WallSegments.TopLeft) == 0); if ((segAllow || PXWalls.TopLeftStyle != 1) && ((itemT.Dir & WallSegments.BottomRight) > 0)) - SpreadOnto(Walls, Floors, plusX, item.Y, 0, Map, width, height, spread, (ushort)rooms.Count, ExpectedTile, noFloorBad, adjRooms, !segAllow); + SpreadOnto(Walls, Floors, plusX, item.Y, 0, map, width, height, spread, (ushort)rooms.Count, expectedTile, noFloorBad, adjRooms, !segAllow); segAllow = ((mainWalls.Segments & WallSegments.TopLeft) == 0); if ((segAllow || mainWalls.TopLeftStyle != 1) && ((itemT.Dir & WallSegments.TopLeft) > 0)) - SpreadOnto(Walls, Floors, minX, item.Y, 2, Map, width, height, spread, (ushort)rooms.Count, ExpectedTile, noFloorBad, adjRooms, !segAllow); + SpreadOnto(Walls, Floors, minX, item.Y, 2, map, width, height, spread, (ushort)rooms.Count, expectedTile, noFloorBad, adjRooms, !segAllow); segAllow = ((PYWalls.Segments & WallSegments.TopRight) == 0); if ((segAllow || PYWalls.TopRightStyle != 1) && ((itemT.Dir & WallSegments.BottomLeft) > 0)) - SpreadOnto(Walls, Floors, item.X, plusY, 1, Map, width, height, spread, (ushort)rooms.Count, ExpectedTile, noFloorBad, adjRooms, !segAllow); + SpreadOnto(Walls, Floors, item.X, plusY, 1, map, width, height, spread, (ushort)rooms.Count, expectedTile, noFloorBad, adjRooms, !segAllow); segAllow = ((mainWalls.Segments & WallSegments.TopRight) == 0); if ((segAllow || mainWalls.TopRightStyle != 1) && ((itemT.Dir & WallSegments.TopRight) > 0)) - SpreadOnto(Walls, Floors, item.X, minY, 3, Map, width, height, spread, (ushort)rooms.Count, ExpectedTile, noFloorBad, adjRooms, !segAllow); + SpreadOnto(Walls, Floors, item.X, minY, 3, map, width, height, spread, (ushort)rooms.Count, expectedTile, noFloorBad, adjRooms, !segAllow); } var bounds = new Rectangle(rminX, rminY, (rmaxX - rminX) + 1, (rmaxY - rminY) + 1); @@ -216,6 +219,8 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei minRoom = room.LightBaseRoom; } room.AdjRooms.Add(myRoom); + + // This might not be working recursively in certain situations. if (outside) MakeOutside(rooms, room); else if (room.IsOutside) outside = true; } @@ -235,7 +240,7 @@ public void GenerateMap(WallTile[] Walls, FloorTile[] Floors, int width, int hei rooms.Add(new VMRoom { IsOutside = outside, - IsPool = ExpectedTile > 65533, + IsPool = expectedTile > 65533, Bounds = bounds, WallObs = wallObs, RoomObs = roomObs, @@ -396,13 +401,14 @@ public List GenerateRoomObs(ushort room, sbyte level, Rectangle boun var x2 = Math.Min(Width, bounds.Right + 1); var y1 = Math.Max(0, bounds.Y - 1); var y2 = Math.Min(Height, bounds.Bottom + 1); + var map = Map; for (int y = y1; y < y2; y++) { VMObstacle next = null; for (int x = x1; x < x2; x++) { - uint tRoom = Map[x + y * Width]; + uint tRoom = map[x + y * Width]; if ((ushort)tRoom != room && ((tRoom>>16)&0x7FFF) != room) { //is there a door on this tile? diff --git a/TSOClient/tso.simantics/Model/VMTempRegisters.cs b/TSOClient/tso.simantics/Model/VMTempRegisters.cs new file mode 100644 index 000000000..57ea67f2d --- /dev/null +++ b/TSOClient/tso.simantics/Model/VMTempRegisters.cs @@ -0,0 +1,39 @@ +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace FSO.SimAntics.Model +{ + [InlineArray(20)] + public struct VMTempRegisters + { + private short _element0; + public static int Length => 20; + + public VMTempRegisters(Span data) + { + data.CopyTo(AsSpan()); + } + + public Span AsSpan() + { + return MemoryMarshal.CreateSpan(ref _element0, 20); + } + } + + [InlineArray(2)] + public struct VMTempXLRegisters + { + private int _element0; + public static int Length => 2; + + public VMTempXLRegisters(Span data) + { + data.CopyTo(AsSpan()); + } + + public Span AsSpan() + { + return MemoryMarshal.CreateSpan(ref _element0, 2); + } + } +} diff --git a/TSOClient/tso.simantics/NetPlay/Drivers/VMClientDriver.cs b/TSOClient/tso.simantics/NetPlay/Drivers/VMClientDriver.cs index e695686cb..2f21ec4cb 100644 --- a/TSOClient/tso.simantics/NetPlay/Drivers/VMClientDriver.cs +++ b/TSOClient/tso.simantics/NetPlay/Drivers/VMClientDriver.cs @@ -10,14 +10,16 @@ namespace FSO.SimAntics.NetPlay.Drivers public class VMClientDriver : VMNetDriver { + private const int BASE_TICKS_PER_PACKET = 1; + private Queue TickBuffer; private Queue OutgoingCommands; private Queue ServerMessages; - private const int TICKS_PER_PACKET = 4; + private int TicksPerPacket = BASE_TICKS_PER_PACKET; private const int BUFFER_STABLE_TICKS = 3 * 30; //if buffer does not drop below 2 large for this number of ticks, tighten buffer size - private int BufferSize = TICKS_PER_PACKET * 2; + private int BufferSize = BASE_TICKS_PER_PACKET * 2; private int TicksSinceCloseCall = 0; private bool ReplenishBuffer = false; // when true, ticks run at half speed until BufferSize. private bool ExecutedAnything; @@ -119,7 +121,7 @@ public override bool Tick(VM vm) else { TicksSinceLastCommand = Math.Min(TicksSinceLastCommand, 0); - if (TickBuffer.Count <= TICKS_PER_PACKET) TicksSinceCloseCall = 0; + if (TickBuffer.Count <= TicksPerPacket) TicksSinceCloseCall = 0; } if (ReplenishBuffer) @@ -131,7 +133,7 @@ public override bool Tick(VM vm) { TicksSinceCloseCall = 0; BufferSize--; - if (BufferSize < TICKS_PER_PACKET) BufferSize = TICKS_PER_PACKET; + if (BufferSize < TicksPerPacket) BufferSize = TicksPerPacket; } // === END BUFFER SIZE MANAGEMENT === @@ -140,6 +142,8 @@ public override bool Tick(VM vm) { ExecutedAnything = true; var tick = TickBuffer.Dequeue(); + //Console.WriteLine($"CLIENT running tick [{tick.TickID}] with {string.Join(',', tick.Commands.Select(x => x.Type.ToString()))}"); + RunningCatchup = tick.RunningCatchup; InternalTick(vm, tick); if (vm.FSOVAsyncLoading) { @@ -147,11 +151,6 @@ public override bool Tick(VM vm) //right now we assume the sync tick is by itself, and sets "runTick" to false anyways //so it does not need to be requeued - /* requeue code - var temp = new List(TickBuffer); - temp.Insert(0, tick); - TickBuffer = new Queue(temp); - */ return false; } if (timer.ElapsedMilliseconds > 66) @@ -226,6 +225,7 @@ private void HandleServerMessage(VMNetMessage message) } else { + var runningCatchup = message.Type == VMNetMessageType.CatchupTick; var tick = new VMNetTickList(); try { @@ -242,9 +242,18 @@ private void HandleServerMessage(VMNetMessage message) return; } + if (tick.Ticks.Count > TicksPerPacket) + { + TicksPerPacket = tick.Ticks.Count; + + TicksSinceCloseCall = 0; + BufferSize = Math.Max(BufferSize, TicksPerPacket * 2); + } + for (int i = 0; i < tick.Ticks.Count; i++) { tick.Ticks[i].ImmediateMode = tick.ImmediateMode; + tick.Ticks[i].RunningCatchup = runningCatchup; TickBuffer.Enqueue(tick.Ticks[i]); } } diff --git a/TSOClient/tso.simantics/NetPlay/Drivers/VMServerDriver.cs b/TSOClient/tso.simantics/NetPlay/Drivers/VMServerDriver.cs index e4172684d..0f6cf371f 100644 --- a/TSOClient/tso.simantics/NetPlay/Drivers/VMServerDriver.cs +++ b/TSOClient/tso.simantics/NetPlay/Drivers/VMServerDriver.cs @@ -1,13 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using FSO.SimAntics.Model.TSOPlatform; using FSO.SimAntics.NetPlay.Model; -using System.IO; using FSO.SimAntics.NetPlay.Model.Commands; using FSO.SimAntics.NetPlay.SandboxMode; -using System.Threading; using FSO.SimAntics.Engine.TSOTransaction; -using System.Threading.Tasks; namespace FSO.SimAntics.NetPlay.Drivers { @@ -16,12 +11,13 @@ public class VMServerDriver : VMNetDriver private List QueuedCmds; private List DeferredCmds; - private const int TICKS_PER_PACKET = 4; private const int INACTIVITY_TICKS_WARN = 15 * 60 * 30; private const int INACTIVITY_TICKS_KICK = 20 * 60 * 30; private uint ProblemTick; private List TickBuffer; + public int TicksPerPacket = 4; + // Networking Abstractions private uint LastDesyncTick; private List LastDesyncPcts = new List(); @@ -29,6 +25,9 @@ public class VMServerDriver : VMNetDriver private Dictionary Clients; + //runtime variable that contains what clients a tick should be broadcast to. + private HashSet BroadcastClients = []; + private HashSet ClientsToDC; private HashSet ClientsToSync; //a subset of ClientsToSync which we should NOT send intermediate ticks to. (since they don't have the lot yet) @@ -36,15 +35,17 @@ public class VMServerDriver : VMNetDriver //resyncing is a second class action - we will only provide state to resynced clients when there is a minimal amount of history. //this is to make sure they do not spend too long waiting for their game to catch up, and to avoid replaying sound effects. private HashSet ResyncClients; + // When sync is being sent to new clients, send every tick to reduce join latency. + private bool FastTick; //Sync and sync history - private const int MAX_HISTORY = (30 * 30) / TICKS_PER_PACKET; + private int MaxHistory => (30 * 30) / TicksPerPacket; private bool SyncSerializing; //this is set when we begin serializing the state on another thread. private byte[] LastSync; private List TicksSinceSync; public event VMServerBroadcastHandler OnTickBroadcast; - public delegate void VMServerBroadcastHandler(VMNetMessage msg, HashSet ignore); + public delegate void VMServerBroadcastHandler(VMNetMessage msg, HashSet clients); public event VMServerDirectHandler OnDirectMessage; public delegate void VMServerDirectHandler(VMNetClient target, VMNetMessage msg); @@ -57,6 +58,9 @@ public class VMServerDriver : VMNetDriver public BanList SandboxBans; public bool SelfResync; + public bool Transitioning; + + private volatile int _syncGeneration; private uint TickID = 1; @@ -76,6 +80,47 @@ public VMServerDriver(IVMTSOGlobalLink globalLink) SandboxBans = new BanList(); } + public void RecordAvatarStateForTransition(VM vm) + { + lock (Clients) + { + foreach (var client in Clients.Values) + { + var ava = vm.GetAvatarByPersist(client.PersistID); + if (ava != null) + { + client.AvatarState.Save(ava); + } + } + } + } + + /// + /// Recreates all client avatars after a VM state reload and triggers a full resync. + /// + public void RejoinClients(VM vm, VMTSOAvatarFlags withFlags = 0) + { + foreach (var avatar in vm.Context.ObjectQueries.Avatars.ToList()) + avatar.Delete(true, vm.Context); + + lock (Clients) + { + foreach (var client in Clients.Values) + { + client.HadAvatar = false; + client.AvatarState.AvatarFlags &= ~VMTSOAvatarFlags.Spectator; + client.AvatarState.AvatarFlags |= withFlags; + new VMNetSimJoinCmd + { + ActorUID = client.PersistID, + AvatarState = client.AvatarState, + }.Execute(vm); + } + } + TickBuffer.Clear(); + SyncAllClients(asNew: true); + } + public void ConnectClient(VMNetClient client) { lock (Clients) @@ -85,6 +130,7 @@ public void ConnectClient(VMNetClient client) { ActorUID = client.PersistID, AvatarState = client.AvatarState, + TransitionInfo = client.TransitionInfo }); } lock (ClientsToSync) @@ -93,6 +139,7 @@ public void ConnectClient(VMNetClient client) { ClientsToSync.Add(client); NewClients.Add(client); //note that the lock for clientstosync is valid for newclients too. + FastTick = true; } } } @@ -113,6 +160,7 @@ public void RefreshClient(uint id) { ClientsToSync.Add(client); NewClients.Add(client); //note that the lock for clientstosync is valid for newclients too. + FastTick = true; } } } @@ -137,6 +185,70 @@ public void DisconnectClient(VMNetClient Client) } } + public void PrepareSync(VM vm) + { + SyncSerializing = true; + TicksSinceSync = new List(); //start saving a history. + + // This was advanced after we created the broadcats tick. The sync is an _alternative_ for the broadcast, + // and we should follow up by sending ticks that sequentially happen after it. + var tick = TickID - 1; + + //Console.WriteLine($"[{tick}] Serializing tick with {string.Join(';', TickBuffer.Select(x => string.Join(',', x.Commands.Select(x => x.Type.ToString()))))}"); + var state = vm.Save(); //must be saved on lot thread. we can serialize elsewhere tho. + var statecmd = new VMStateSyncCmd { State = state }; + if (vm.Trace != null) + statecmd.Traces = vm.Trace.History; + var cmd = new VMNetCommand(statecmd); + + //currently just hack this on the tick system. might switch later + var ticks = new VMNetTickList + { + Ticks = new List { + new VMNetTick { + Commands = new List { cmd }, + RandomSeed = 0, //will be restored by client from cmd + TickID = tick + } + } + }; + + var gen = _syncGeneration; + Task.Run(() => + { + byte[] data; + using (var stream = new MemoryStream()) + { + using (var writer = new BinaryWriter(stream)) + { + ticks.SerializeInto(writer); + } + data = stream.ToArray(); + } + if (gen == _syncGeneration) + LastSync = data; + SyncSerializing = false; + }); + } + + public void SyncAllClients(bool asNew = false) + { + lock (ClientsToSync) + { + lock (Clients) + { + foreach (var client in Clients.Values) + { + ClientsToSync.Add(client); + if (asNew) NewClients.Add(client); + } + } + _syncGeneration++; + LastSync = null; + FastTick = true; + } + } + private void SendState(VM vm) { if (ResyncClients.Count != 0 && LastSync == null && !SyncSerializing) @@ -156,48 +268,14 @@ private void SendState(VM vm) if (LastSync == null && !SyncSerializing) { - SyncSerializing = true; - TicksSinceSync = new List(); //start saving a history. - - var state = vm.Save(); //must be saved on lot thread. we can serialize elsewhere tho. - var statecmd = new VMStateSyncCmd { State = state }; - if (vm.Trace != null) - statecmd.Traces = vm.Trace.History; - var cmd = new VMNetCommand(statecmd); - - //currently just hack this on the tick system. might switch later - var ticks = new VMNetTickList - { - Ticks = new List { - new VMNetTick { - Commands = new List { cmd }, - RandomSeed = 0, //will be restored by client from cmd - TickID = TickID - } - } - }; - - Task.Run(() => - { - byte[] data; - using (var stream = new MemoryStream()) - { - using (var writer = new BinaryWriter(stream)) - { - ticks.SerializeInto(writer); - } - data = stream.ToArray(); - } - LastSync = data; - SyncSerializing = false; - }); + PrepareSync(vm); } else if (LastSync != null) { foreach (var client in ClientsToSync) { - Send(client, new VMNetMessage(VMNetMessageType.BroadcastTick, LastSync)); + Send(client, new VMNetMessage(VMNetMessageType.CatchupTick, LastSync)); foreach (var tick in TicksSinceSync) //catch this client up with what happened since the last state was created. - Send(client, new VMNetMessage(VMNetMessageType.BroadcastTick, tick)); + Send(client, new VMNetMessage(VMNetMessageType.CatchupTick, tick)); } ClientsToSync.Clear(); NewClients.Clear(); //note that the lock for clientstosync is valid for newclients too. @@ -323,12 +401,23 @@ public override bool Tick(VM vm) TickBuffer.Add(tick); - if (TickBuffer.Count >= TICKS_PER_PACKET) + if (!Transitioning && (FastTick || TickBuffer.Count >= TicksPerPacket)) { + BroadcastClients.Clear(); + lock (Clients) + BroadcastClients.UnionWith(Clients.Values); + lock (ClientsToSync) { + BroadcastClients.ExceptWith(NewClients); + SendTickBuffer(); SendState(vm); + + if (ClientsToSync.Count == 0) + { + FastTick = false; + } } } @@ -351,7 +440,7 @@ private void SendTickBuffer() if (TicksSinceSync != null) { - if (TicksSinceSync.Count > MAX_HISTORY && !SyncSerializing) + if (TicksSinceSync.Count > MaxHistory && !SyncSerializing) { //when we have many seconds of ticks for the player to get through, //it might take them a while to catch up, even after assets load @@ -365,7 +454,8 @@ private void SendTickBuffer() } } - Broadcast(new VMNetMessage(VMNetMessageType.BroadcastTick, data), NewClients); + //Console.WriteLine($"Sending tick with {string.Join(';', TickBuffer.Select(x => "[" + (x.TickID) + "] " + string.Join(',', x.Commands.Select(x => x.Type.ToString()))))}"); + Broadcast(new VMNetMessage(VMNetMessageType.BroadcastTick, data), BroadcastClients); TickBuffer.Clear(); } @@ -409,9 +499,9 @@ private void Send(VMNetClient client, VMNetMessage message) if (OnDirectMessage != null) OnDirectMessage(client, message); } - private void Broadcast(VMNetMessage message, HashSet ignore) + private void Broadcast(VMNetMessage message, HashSet clients) { - if (OnTickBroadcast != null) OnTickBroadcast(message, ignore); + if (OnTickBroadcast != null) OnTickBroadcast(message, clients); } private void DropClient(VMNetClient client) @@ -421,6 +511,7 @@ private void DropClient(VMNetClient client) private void HandleClients(VM vm) { + if (Transitioning) return; lock (Clients) { ClientsToDC.Clear(); diff --git a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODBandPlugin.cs b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODBandPlugin.cs index af9a1cebc..e65f4277c 100644 --- a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODBandPlugin.cs +++ b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODBandPlugin.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Timers; -using FSO.SimAntics.Model; +using FSO.SimAntics.Model; using FSO.SimAntics.NetPlay.EODs.Model; using FSO.SimAntics.NetPlay.EODs.Utils; +using System.Timers; namespace FSO.SimAntics.NetPlay.EODs.Handlers { @@ -16,7 +14,7 @@ public class VMEODBandPlugin : VMEODHandler private EODLobby Lobby; private Random IsBuzzNoteRandom = new Random(); private Random NonBuzzNoteRandom = new Random(); - private Timer SequenceTimer; + private System.Timers.Timer SequenceTimer; private List Song; private short CurrentSongLength; private int CurrentNote; @@ -24,7 +22,7 @@ public class VMEODBandPlugin : VMEODHandler private int TimerFrames; private decimal CombinedSkillAmount; private int[] PayoutScheme; - + public const int PRESHOW_TIMER_DEFAULT = 10; public const int DECISION_TIMER_DEFAULT = 10; public const int NOTE_TIMER_DEFAULT = 10; @@ -42,7 +40,7 @@ public VMEODBandPlugin(VMEODServer server) : base(server) .OnFailedToJoinDisconnect(); State = VMEODBandStates.Lobby; - SequenceTimer = new Timer(MILLISECONDS_PER_NOTE_IN_SEQUENCE); + SequenceTimer = new System.Timers.Timer(MILLISECONDS_PER_NOTE_IN_SEQUENCE); SequenceTimer.Elapsed += SequenceTimerElapsedHandler; InitPayoutScheme(); @@ -269,7 +267,7 @@ private void NoteSelectedHandler(string evt, byte[] playerChoice, VMEODClient cl var slot = Lobby.GetSlotData(client); if (slot == null) return; - + byte note = 9; lock (NoteDecision) @@ -388,7 +386,7 @@ private void SetTimer(int newValue) private void SendTime() { - Lobby.Broadcast("Band_Timer", "" + UITimer); + Lobby.Broadcast("Band_Timer", "" + UITimer); } private void InitGame(int Timer) { diff --git a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODPermissionDoorPlugin.cs b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODPermissionDoorPlugin.cs index 23e9a7667..feec46c28 100644 --- a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODPermissionDoorPlugin.cs +++ b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODPermissionDoorPlugin.cs @@ -151,10 +151,10 @@ public override void OnConnection(VMEODClient client) { var param = client.Invoker.Thread.TempRegisters; Mode = (VMEODPermissionDoorMode)param[0]; - if (param.Length > 1) MaxFee = param[1]; - if (param.Length > 2) PermissionState = param[2]; - if (param.Length > 3) DoorFee = param[3]; - if (param.Length > 4) Flags = param[4]; + if (SimAntics.Model.VMTempRegisters.Length > 1) MaxFee = param[1]; + if (SimAntics.Model.VMTempRegisters.Length > 2) PermissionState = param[2]; + if (SimAntics.Model.VMTempRegisters.Length > 3) DoorFee = param[3]; + if (SimAntics.Model.VMTempRegisters.Length > 4) Flags = param[4]; } } diff --git a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODWarGamePlugin.cs b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODWarGamePlugin.cs index d45b15778..24de8f2a3 100644 --- a/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODWarGamePlugin.cs +++ b/TSOClient/tso.simantics/NetPlay/EODs/Handlers/VMEODWarGamePlugin.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using FSO.SimAntics.NetPlay.EODs.Model; +using FSO.SimAntics.NetPlay.EODs.Model; using System.Timers; namespace FSO.SimAntics.NetPlay.EODs.Handlers @@ -14,8 +12,8 @@ public class VMEODWarGamePlugin : VMEODHandler private List Players; private VMEODWarGamePiece ChosenBluePiece; private VMEODWarGamePiece ChosenRedPiece; - private Timer GameMessageTimer; - private Timer RoundMessageTimer; + private System.Timers.Timer GameMessageTimer; + private System.Timers.Timer RoundMessageTimer; public VMEODWarGamePlugin(VMEODServer server) : base(server) { @@ -24,9 +22,9 @@ public VMEODWarGamePlugin(VMEODServer server) : base(server) PlaintextHandlers["WarGame_Close_UI"] = OnCloseUIHandler; SimanticsHandlers[(short)VMEODWarGameEvents.NextRound] = NextRoundHandler; SimanticsHandlers[(short)VMEODWarGameEvents.NextGame] = NextGameHandler; - GameMessageTimer = new Timer(5000); + GameMessageTimer = new System.Timers.Timer(5000); GameMessageTimer.Elapsed += GameTieMessageHandler; - RoundMessageTimer = new Timer(5000); + RoundMessageTimer = new System.Timers.Timer(5000); RoundMessageTimer.Elapsed += RoundTieMessageHandler; } @@ -37,7 +35,7 @@ public override void OnConnection(VMEODClient client) { // get the params, temp 0 is player type var local = client.Invoker.Thread.TempRegisters; - if ((local != null) && (local[0] == (short)VMEODWarGamePlayers.Blue)) + if (local[0] == (short)VMEODWarGamePlayers.Blue) { BluePlayerClient = client; BluePlayerClient.Send("WarGame_Init", BluePlayerClient.Avatar.ObjectID + "%blue"); @@ -268,7 +266,8 @@ class VMEODWarGamePiece private VMEODWarGamePieceTypes m_PieceType; public List Defeats; - public VMEODWarGamePiece(VMEODWarGamePieceTypes type) { + public VMEODWarGamePiece(VMEODWarGamePieceTypes type) + { m_PieceType = type; } diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMChangePermissionsCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMChangePermissionsCmd.cs index 37cf3499f..7c3ad0fd0 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMChangePermissionsCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMChangePermissionsCmd.cs @@ -1,7 +1,5 @@ using FSO.SimAntics.Model; using FSO.SimAntics.Model.TSOPlatform; -using System.Collections.Generic; -using System.IO; namespace FSO.SimAntics.NetPlay.Model.Commands { @@ -10,6 +8,7 @@ public class VMChangePermissionsCmd : VMNetCommandBodyAbstract public uint TargetUID; public VMTSOAvatarPermissions Level; public VMChangePermissionsMode Mode; + public bool? Debug; public uint ReplaceUID; //for object inherit modes. Set implicitly for owner replacement. public bool Verified; public override bool Execute(VM vm) @@ -85,7 +84,7 @@ private bool ChangeUserLevel(VM vm, uint pid, VMTSOAvatarPermissions level) { roomieChange = !roomieChange; vm.TSOState.Roommates.Add(pid); - vm.TSOState.Names.Precache(vm, pid); + vm.TSOState.Names.Precache(vm, VMGlobalEntityType.Avatar, pid); if (level > VMTSOAvatarPermissions.Roommate) vm.TSOState.BuildRoommates.Add(pid); if (level == VMTSOAvatarPermissions.Owner) vm.TSOState.OwnerID = pid; } @@ -113,6 +112,22 @@ private bool ChangeUserLevel(VM vm, uint pid, VMTSOAvatarPermissions level) if (level >= VMTSOAvatarPermissions.BuildBuyRoommate) vm.TSOState.BuildRoommates.Add(obj.PersistID); if (level == VMTSOAvatarPermissions.Owner) vm.TSOState.OwnerID = pid; else if (vm.TSOState.OwnerID == pid) vm.TSOState.OwnerID = 0; + + if (Debug != null) + { + var flags = obj.AvatarState.Flags; + + if (Debug.Value) + { + flags |= VMTSOAvatarFlags.Debug; + } + else + { + flags &= ~VMTSOAvatarFlags.Debug; + } + + obj.AvatarState.Flags = flags; + } } return roomieChange && playerOwned; } @@ -167,6 +182,12 @@ public override void SerializeInto(BinaryWriter writer) writer.Write(ReplaceUID); writer.Write((byte)Level); writer.Write((byte)Mode); + writer.Write(Debug.HasValue); + + if (Debug.HasValue) + { + writer.Write(Debug.Value); + } } public override void Deserialize(BinaryReader reader) @@ -176,6 +197,9 @@ public override void Deserialize(BinaryReader reader) ReplaceUID = reader.ReadUInt32(); Level = (VMTSOAvatarPermissions)reader.ReadByte(); Mode = (VMChangePermissionsMode)reader.ReadByte(); + + var hasDebug = reader.ReadBoolean(); + Debug = hasDebug ? reader.ReadBoolean() : null; } #endregion diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAdjHollowSyncCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAdjHollowSyncCmd.cs index cee9a1e78..a6199e65a 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAdjHollowSyncCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAdjHollowSyncCmd.cs @@ -1,16 +1,65 @@ -using System.IO; +using FSO.SimAntics.Utils; namespace FSO.SimAntics.NetPlay.Model.Commands { + public enum VMHollowAdjType : byte + { + None = 0, + Reuse = 1, + Terrain = 2, + Hollow = 3, + } + + public struct VMHollowAdjEntry + { + public VMHollowAdjType Type; + public byte[] Data; + + public VMHollowAdjEntry(VMHollowAdjType type, byte[] data) + { + Type = type; + Data = data; + } + + public VMHollowAdjEntry(VMHollowAdjType type) + { + Type = type; + Data = null; + } + } + public class VMNetAdjHollowSyncCmd : VMNetCommandBodyAbstract { - public byte[][] HollowAdj; + public VMHollowAdjEntry[] HollowAdj; public override bool AcceptFromClient { get { return false; } } public override bool Execute(VM vm) { - vm.HollowAdj = HollowAdj; + if (vm.HollowAdj == null) + { + vm.HollowAdj = HollowAdj; + } + else + { + bool changed = false; + for (int i = 0; i < HollowAdj.Length; i++) + { + var toCopy = HollowAdj[i]; + ref var target = ref vm.HollowAdj[i]; + + if (toCopy.Type == VMHollowAdjType.Hollow) + { + target = toCopy; + changed = true; + } + } + + if (VM.UseWorld && vm.Ready && !vm.FSOVAsyncLoading && !vm.Driver.RunningCatchup && changed && vm.Context.Blueprint.SubWorlds.Count != 0) + { + VMLotTerrainRestoreTools.RestoreSurroundings(vm, HollowAdj); + } + } return true; } @@ -27,12 +76,11 @@ public override void SerializeInto(BinaryWriter writer) writer.Write(HollowAdj.Length); foreach (var item in HollowAdj) { - if (item == null) writer.Write(false); - else + writer.Write((byte)item.Type); + if (item.Type >= VMHollowAdjType.Hollow) { - writer.Write(true); - writer.Write(item.Length); - writer.Write(item); + writer.Write(item.Data.Length); + writer.Write(item.Data); } } } @@ -40,10 +88,13 @@ public override void SerializeInto(BinaryWriter writer) public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); - HollowAdj = new byte[reader.ReadInt32()][]; + HollowAdj = new VMHollowAdjEntry[reader.ReadInt32()]; for (int i=0; i= VMHollowAdjType.Hollow ? reader.ReadBytes(reader.ReadInt32()) : null; + + HollowAdj[i] = new VMHollowAdjEntry(type, data); } } diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetArchitectureCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetArchitectureCmd.cs index ae2373448..78f72236c 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetArchitectureCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetArchitectureCmd.cs @@ -23,6 +23,7 @@ public override bool Execute(VM vm) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; //since architecture commands must be run in order, we need to run all architecture commands synchronously. //it must be queued on the global link. diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAsyncSaleCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAsyncSaleCmd.cs index 518c2fb46..d8d6c8d01 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAsyncSaleCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetAsyncSaleCmd.cs @@ -50,6 +50,7 @@ public override bool Execute(VM vm, VMAvatar caller) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; if (ObjectPID == 0) return false; var targObj = vm.GetObjectByPersist(ObjectPID); diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBatchGraphicCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBatchGraphicCmd.cs index 8d4627c4d..0567161ed 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBatchGraphicCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBatchGraphicCmd.cs @@ -44,7 +44,7 @@ public override void SerializeInto(BinaryWriter writer) { base.SerializeInto(writer); writer.Write(Objects.Length); - writer.Write(VMSerializableUtils.ToByteArray(Objects)); + VMSerializableUtils.WriteArray(writer, Objects); writer.Write(Graphics); } @@ -53,7 +53,7 @@ public override void Deserialize(BinaryReader reader) { base.Deserialize(reader); var length = reader.ReadInt32(); - Objects = VMSerializableUtils.ToTArray(reader.ReadBytes(length * 2)); + Objects = VMSerializableUtils.ReadArray(reader, length); Graphics = reader.ReadBytes(length); } diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBeginFreeRoamCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBeginFreeRoamCmd.cs new file mode 100644 index 000000000..3da85cd4b --- /dev/null +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBeginFreeRoamCmd.cs @@ -0,0 +1,59 @@ +using FSO.Common.Model; + +namespace FSO.SimAntics.NetPlay.Model.Commands +{ + public class VMNetBeginFreeRoamCmd : VMNetCommandBodyAbstract + { + public uint AvatarPID; + public uint TargetLot; + public LotTransitionInfo Transition; + + public override bool Execute(VM vm) + { + // Begin the leave process on the avatar. + // Change the direct control frame so that it can't exit until the followup message arrives. + + if (!vm.Context.ObjectQueries.AvatarsByPersist.TryGetValue(AvatarPID, out VMAvatar avatar)) + { + return false; + } + + avatar.Thread.Interrupt = true; + + // Starts leaving lot, but the player will disconnect a lot earlier. + avatar.UserLeaveLot(false); + + // If this command is meant for this client, and we're not fast forwarding through state, then begin the lot switch. + if (vm.MyUID == AvatarPID && !vm.Driver.RunningCatchup) + { + // Prepare to transition to the new lot. We'll do this as soon as we disconnect. + vm.SignalLotSwitch(TargetLot, Transition); + } + + return true; + } + + public override bool Verify(VM vm, VMAvatar caller) + { + return !FromNet; //can only be sent out by server + } + + #region VMSerializable Members + public override void SerializeInto(BinaryWriter writer) + { + base.SerializeInto(writer); + writer.Write(AvatarPID); + writer.Write(TargetLot); + VMNetSimJoinCmd.PutTransition(writer, Transition); + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + AvatarPID = reader.ReadUInt32(); + TargetLot = reader.ReadUInt32(); + Transition = VMNetSimJoinCmd.GetTransition(reader); + } + #endregion + } +} diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBuyObjectCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBuyObjectCmd.cs index b0aee4ce3..a1acfdcdb 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBuyObjectCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetBuyObjectCmd.cs @@ -130,6 +130,7 @@ private bool TryPlace(VM vm, VMAvatar caller) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; //set internally when transaction succeeds. trust that the verification happened. value = 0; //do not trust value from net if (!vm.TS1) diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeEnvironmentCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeEnvironmentCmd.cs index 07d1edf4f..5258d7877 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeEnvironmentCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeEnvironmentCmd.cs @@ -14,14 +14,15 @@ public override bool Execute(VM vm) { var amb = vm.Context.Ambience; foreach (var guid in GUIDsToClear) - amb.SetAmbience(amb.GetAmbienceFromGUID(guid), false); + amb.SetUserAmbience(amb.GetAmbienceFromGUID(guid), false); foreach (var guid in GUIDsToAdd) - amb.SetAmbience(amb.GetAmbienceFromGUID(guid), true); + amb.SetUserAmbience(amb.GetAmbienceFromGUID(guid), true); return true; } public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (caller == null || //caller must be on lot, be a build roommate. caller.AvatarState.Permissions < VMTSOAvatarPermissions.BuildBuyRoommate) return false; diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeLotSizeCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeLotSizeCmd.cs index f854f1bb0..4afefd59c 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeLotSizeCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChangeLotSizeCmd.cs @@ -23,6 +23,7 @@ public override bool Execute(VM vm) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; //set internally when transaction succeeds. trust that the verification happened. if (caller == null || //caller must be on lot, have owner permissions diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs index 90bdfcc8c..42bdeee77 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetChatCmd.cs @@ -32,164 +32,187 @@ public override bool Execute(VM vm, VMAvatar avatar) var args = Message.Substring(Math.Min(Message.Length, spaceIndex + 1), Math.Max(0, Message.Length - (spaceIndex + 1))); var server = (VMServerDriver)vm.Driver; VMEntity sim; - switch (cmd.ToLowerInvariant()) + + try { - case "ban": - server.BanUser(vm, args); - break; - case "banip": - server.BanIP(args); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Added " + args + " to the IP ban list.")); - break; - case "unban": - server.SandboxBans.Remove(args.ToLowerInvariant().Trim(' ')); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Removed " + args + " from the IP ban list.")); - break; - case "banlist": - string result = ""; - foreach (var ban in server.SandboxBans.List()) result += ban + "\r\n"; - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "==== BANNED IPS: ==== \r\n"+result)); - break; - case "builder": - sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); - if (sim != null) - { - vm.ForwardCommand(new VMChangePermissionsCmd() + switch (cmd.ToLowerInvariant()) + { + case "ban": + server.BanUser(vm, args); + break; + case "banip": + server.BanIP(args); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Added " + args + " to the IP ban list.")); + break; + case "unban": + server.SandboxBans.Remove(args.ToLowerInvariant().Trim(' ')); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Removed " + args + " from the IP ban list.")); + break; + case "banlist": + string result = ""; + foreach (var ban in server.SandboxBans.List()) result += ban + "\r\n"; + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "==== BANNED IPS: ==== \r\n" + result)); + break; + case "builder": + sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); + if (sim != null) { - TargetUID = sim.PersistID, - Level = VMTSOAvatarPermissions.BuildBuyRoommate, - Verified = true - }); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a build-roommate.")); - } - break; - case "admin": - sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); - if (sim != null) - { - vm.ForwardCommand(new VMChangePermissionsCmd() + vm.ForwardCommand(new VMChangePermissionsCmd() + { + TargetUID = sim.PersistID, + Level = VMTSOAvatarPermissions.BuildBuyRoommate, + Verified = true + }); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a build-roommate.")); + } + break; + case "admin": + sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); + if (sim != null) { - TargetUID = sim.PersistID, - Level = VMTSOAvatarPermissions.Admin, - Verified = true - }); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " an admin.")); - } - break; - case "roomie": - sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); - if (sim != null) - { - vm.ForwardCommand(new VMChangePermissionsCmd() + vm.ForwardCommand(new VMChangePermissionsCmd() + { + TargetUID = sim.PersistID, + Level = VMTSOAvatarPermissions.Admin, + Verified = true + }); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " an admin.")); + } + break; + case "roomie": + sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); + if (sim != null) + { + vm.ForwardCommand(new VMChangePermissionsCmd() + { + TargetUID = sim.PersistID, + Level = VMTSOAvatarPermissions.Roommate, + Verified = true + }); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a roommate.")); + } + break; + case "visitor": + sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); + if (sim != null) + { + vm.ForwardCommand(new VMChangePermissionsCmd() + { + TargetUID = sim.PersistID, + Level = VMTSOAvatarPermissions.Visitor, + Verified = true + }); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a visitor.")); + } + break; + case "close": + if (FromNet) return false; + vm.CloseNet(VMCloseNetReason.ServerShutdown); + break; + case "qtrday": + var count = int.Parse(args); + for (int i = 0; i < count; i++) + { + vm.ProcessQTRDay(); + } + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Ran " + count + " quarter days.")); + break; + case "setjob": + var jobsplit = args.Split(' '); + if (jobsplit.Length < 2) return true; + var jobid = short.Parse(jobsplit[0]); + var jobgrade = short.Parse(jobsplit[1]); + avatar.SetPersonData(SimAntics.Model.VMPersonDataVariable.OnlineJobID, jobid); + avatar.SetPersonData(SimAntics.Model.VMPersonDataVariable.OnlineJobGrade, jobgrade); + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Set " + avatar.ToString() + " job grade/type to " + jobgrade + "/" + jobid + ".")); + break; + case "trace": + //enables desync tracing + vm.UseSchedule = false; + vm.Trace = new Engine.Debug.VMSyncTrace(); + break; + case "reload": + //enables desync tracing + var servD = vm.Driver as VMServerDriver; + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Debug, "Manually requested self resync.")); + if (servD != null) servD.SelfResync = true; + break; + case "time": + var timesplit = args.Split(' '); + if (timesplit.Length < 2) return true; + vm.Context.Clock.Hours = int.Parse(timesplit[0]); + vm.Context.Clock.Minutes = int.Parse(timesplit[1]); + vm.Context.Clock.MinuteFractions = 0; + break; + case "tickrate": + /* TS1 has 30 ticks per minute (1 minute per 1 irl second) + * TSO has 150 ticks per minute (1 minute per 5 irl second)*/ + if (int.TryParse(args, out var tick) && tick >= 1) vm.Context.Clock.TicksPerMinute = tick; + else tick = vm.Context.Clock.TicksPerMinute = 30 * 5; + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, $"Set tickrate to {tick} ticks per minute.")); + break; + case "speed": + /* By default, 0 = paused, normal = 1, medium = 3, fast = 10 */ + if (int.TryParse(args, out var speed) && speed is >= 0 and <= 999) vm.SpeedMultiplier = speed; + else vm.SpeedMultiplier = speed = 1; + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, $"Set speed multiplier to {speed}.")); + break; + case "tuning": + var tuningsplit = args.Split(' '); + if (tuningsplit.Length < 4) return true; + vm.Tuning.AddTuning(new Common.Model.DynTuningEntry() { - TargetUID = sim.PersistID, - Level = VMTSOAvatarPermissions.Roommate, - Verified = true + tuning_type = tuningsplit[0], + tuning_table = int.Parse(tuningsplit[1]), + tuning_index = int.Parse(tuningsplit[2]), + value = float.Parse(tuningsplit[3]), }); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a roommate.")); - } - break; - case "visitor": - sim = vm.Entities.Where(x => x is VMAvatar && x.ToString().ToLowerInvariant().Trim(' ') == args.ToLowerInvariant().Trim(' ')).FirstOrDefault(); - if (sim != null) - { - vm.ForwardCommand(new VMChangePermissionsCmd() + vm.ForwardCommand(new VMNetTuningCmd() { - TargetUID = sim.PersistID, - Level = VMTSOAvatarPermissions.Visitor, - Verified = true + Tuning = vm.Tuning }); - vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Made " + sim.Name + " a visitor.")); - } - break; - case "close": - if (FromNet) return false; - vm.CloseNet(VMCloseNetReason.ServerShutdown); - break; - case "qtrday": - var count = int.Parse(args); - for (int i=0; i= VMThread.MAX_USER_ACTIONS) return false; - VMEntity callee = vm.Context.CreateObjectInstance(GOTO_GUID, new LotTilePos(x, y, level), Direction.NORTH).Objects[0]; + + return QueueGoto(vm, caller, new LotTilePos(x, y, level), Interaction, Param0); + } + + public static bool QueueGoto(VM vm, VMAvatar caller, LotTilePos pos, int interactionNumber = 4, int param0 = 0) + { + VMEntity callee = vm.Context.CreateObjectInstance(GOTO_GUID, pos, Direction.NORTH).Objects[0]; if (callee?.Position == LotTilePos.OUT_OF_WORLD) callee.Delete(true, vm.Context); if (callee == null) return false; - callee.PushUserInteraction(Interaction, caller, vm.Context, false, new short[] { Param0, 0, 0, 0 }); + callee.PushUserInteraction(interactionNumber, caller, vm.Context, false, new short[] { 0, 0, 0, 0 }); return true; } diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetGotoLotCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetGotoLotCmd.cs new file mode 100644 index 000000000..864c6718a --- /dev/null +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetGotoLotCmd.cs @@ -0,0 +1,151 @@ +using FSO.Common.Model; +using FSO.LotView.Model; +using FSO.SimAntics.Engine; +using FSO.SimAntics.Model.TSOPlatform; +using Microsoft.Xna.Framework; + +namespace FSO.SimAntics.NetPlay.Model.Commands +{ + public class VMNetGotoLotCmd : VMNetCommandBodyAbstract + { + public ushort Interaction; + public short Param0; + + public short x; + public short y; + public sbyte level; + + public uint LotLocation; + + private static uint TRANSITION_GUID = 0x746ED02B; + + private Point GetLotRelativeDirection(VM vm) + { + var myLocation = vm.TSOState.LotID; + var myX = (short)(myLocation >> 16); + var myY = (short)(myLocation); + + var targX = (short)(LotLocation >> 16); + var targY = (short)(LotLocation); + + var cityRelative = new Point(targX - myX, targY - myY); + + return LotTransitionInfo.RelativeChangeCityToLot(cityRelative); + } + + public override bool Execute(VM vm, VMAvatar caller) + { + if (caller == null) return false; + if (caller.Thread.Queue.Count >= VMThread.MAX_USER_ACTIONS) return false; + + // Try find an edge to place the transition destination + var relativeDir = GetLotRelativeDirection(vm); + + LotTilePos target; + + if (relativeDir.X != 0 && relativeDir.Y != 0) + { + // This is a corner. There's only one place to go. + + short x = (short)(relativeDir.X == -1 ? 0 : vm.Context.Architecture.Width - 1); + short y = (short)(relativeDir.Y == -1 ? 0 : vm.Context.Architecture.Height - 1); + + target = LotTilePos.FromBigTile(x, y, 1); + } + else + { + // Determine target point along the edge. + bool isYEdge = relativeDir.Y != 0; + bool isNegativeEdge = relativeDir.X == -1 || relativeDir.Y == -1; + + int myPerpAxis = isYEdge ? caller.Position.y : caller.Position.x; + int perpLotSize = isYEdge ? vm.Context.Architecture.Height : vm.Context.Architecture.Width; + int myDistToEdge = isNegativeEdge ? myPerpAxis : ((perpLotSize << 4) - myPerpAxis); + + int targPerpAxis = isYEdge ? y : x; + int targDistToEdge = isNegativeEdge ? ((perpLotSize << 4) - targPerpAxis) : targPerpAxis; + + int perpAxisTarget = isNegativeEdge ? 8 : (perpLotSize << 4) - 8; + + int myAlongAxis = isYEdge ? caller.Position.x : caller.Position.y; + int targAlongAxis = isYEdge ? x : y; + + float perpDistProportion = myDistToEdge / (float)(myDistToEdge + targDistToEdge); + int alongAxisTarget = (int)(myAlongAxis * (1 - perpDistProportion) + targAlongAxis * perpDistProportion); + + target = new LotTilePos((short)(isYEdge ? alongAxisTarget : perpAxisTarget), (short)(isYEdge ? perpAxisTarget : alongAxisTarget), 1); + } + + VMEntity callee = vm.Context.CreateObjectInstance(TRANSITION_GUID, target, Direction.NORTH).Objects[0]; + + if (callee?.Position == LotTilePos.OUT_OF_WORLD) + { + callee.Delete(true, vm.Context); + return false; + } + if (callee == null) return false; + + // Copy requested destination to the object + + callee.SetAttribute(1, (short)LotLocation); // lot id (low) + callee.SetAttribute(2, (short)(LotLocation >> 16)); // lot id (high) + callee.SetAttribute(3, x); // dest x + callee.SetAttribute(4, y); // dest y + + callee.PushUserInteraction(Interaction, caller, vm.Context, false, new short[] { Param0, 0, 0, 0 }); + + return true; + } + + public override bool Verify(VM vm, VMAvatar caller) + { + if (!vm.TSOState.Flags.HasFlag(VMTSOLotStateFlags.AllowFreeRoam)) + { + return false; + } + + // Ensure the target lot is in range. + var relativeDir = GetLotRelativeDirection(vm); + + if (relativeDir.X == 0 && relativeDir.Y == 0) + { + // That's this lot... + return false; + } + + if (Math.Abs(relativeDir.X) > 1 || Math.Abs(relativeDir.Y) > 1) + { + // That's too far away... + return false; + } + + return true; + } + + #region VMSerializable Members + + public override void SerializeInto(BinaryWriter writer) + { + base.SerializeInto(writer); + writer.Write(Interaction); + writer.Write(Param0); + writer.Write(x); + writer.Write(y); + writer.Write(level); + writer.Write(LotLocation); + } + + public override void Deserialize(BinaryReader reader) + { + base.Deserialize(reader); + Interaction = reader.ReadUInt16(); + Param0 = reader.ReadInt16(); + x = reader.ReadInt16(); + y = reader.ReadInt16(); + level = reader.ReadSByte(); + LotLocation = reader.ReadUInt32(); + } + + #endregion + } +} diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetInteractionCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetInteractionCmd.cs index 217955add..3cf78ce02 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetInteractionCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetInteractionCmd.cs @@ -29,9 +29,14 @@ public override bool Execute(VM vm, VMAvatar caller) return true; } + private static readonly uint PAYPHONE_GUID = 0x313D2F9A; + private static readonly uint NHOOD_PAYPHONE_GUID = 0x303CD603; + public override bool Verify(VM vm, VMAvatar caller) { if (caller == null && FromNet) return false; + if (IsSpectator(caller) && vm.GetObjectById(CalleeID) is VMGameObject obj + && obj.Object.OBJ.GUID != PAYPHONE_GUID && obj.Object.OBJ.GUID != NHOOD_PAYPHONE_GUID) return false; if (!FromNet) return true; VMEntity callee = vm.GetObjectById(CalleeID); diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetLeaveBuildBuyCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetLeaveBuildBuyCmd.cs new file mode 100644 index 000000000..684d6d213 --- /dev/null +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetLeaveBuildBuyCmd.cs @@ -0,0 +1,37 @@ +namespace FSO.SimAntics.NetPlay.Model.Commands +{ + public class VMNetLeaveBuildBuyCmd : VMNetCommandBodyAbstract + { + public bool Build; + + public override bool Execute(VM vm, VMAvatar caller) + { + vm.SignalGenericVMEvt(VMEventType.TSOUserLeaveBuildBuy, this); + + return true; + } + + public override bool Verify(VM vm, VMAvatar caller) + { + return true; + } + + #region VMSerializable Members + + public override void SerializeInto(BinaryWriter writer) + { + writer.Write(Build); + + base.SerializeInto(writer); + } + + public override void Deserialize(BinaryReader reader) + { + Build = reader.ReadBoolean(); + + base.Deserialize(reader); + } + + #endregion + } +} diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetMoveObjectCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetMoveObjectCmd.cs index 02a874024..1f0c602f2 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetMoveObjectCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetMoveObjectCmd.cs @@ -13,6 +13,12 @@ public class VMNetMoveObjectCmd : VMNetCommandBodyAbstract public sbyte level; public Direction dir; + public override bool Verify(VM vm, VMAvatar caller) + { + if (IsSpectator(caller)) return false; + return true; + } + public override bool Execute(VM vm, VMAvatar caller) { VMEntity obj = vm.GetObjectById(ObjectID); diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetPlaceInventoryCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetPlaceInventoryCmd.cs index 9a4f96b74..56a71c231 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetPlaceInventoryCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetPlaceInventoryCmd.cs @@ -191,6 +191,7 @@ private bool TryPlace(VM vm, VMAvatar caller) /// public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; //set internally when transaction succeeds. trust that the verification happened. //typically null caller, non-roommate cause failure. some lot specific things may apply. Mode = vm.PlatformState.Validator.GetPurchaseMode(Mode, caller, 0, true); diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSendToInventoryCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSendToInventoryCmd.cs index 1879a4fe0..1a61154b4 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSendToInventoryCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSendToInventoryCmd.cs @@ -52,6 +52,7 @@ public override bool Execute(VM vm, VMAvatar caller) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; if (ObjectPID == 0) return false; var targObj = vm.GetObjectByPersist(ObjectPID); diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSetRoofCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSetRoofCmd.cs index b2ebec1ce..7ded4b3dc 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSetRoofCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSetRoofCmd.cs @@ -9,6 +9,12 @@ public class VMNetSetRoofCmd : VMNetCommandBodyAbstract public float Pitch; public uint Style; + public override bool Verify(VM vm, VMAvatar caller) + { + if (IsSpectator(caller)) return false; + return true; + } + public override bool Execute(VM vm, VMAvatar caller) { if (!vm.TS1 && (caller == null || caller.AvatarState.Permissions < VMTSOAvatarPermissions.Owner)) return false; diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSimJoinCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSimJoinCmd.cs index 73752df3d..12c62841a 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSimJoinCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetSimJoinCmd.cs @@ -1,10 +1,9 @@ -using System; -using System.IO; -using System.Linq; +using FSO.Common.Model; +using FSO.Common.Utils; using FSO.LotView.Model; -using FSO.SimAntics.Primitives; using FSO.SimAntics.Model; using FSO.SimAntics.Model.TSOPlatform; +using FSO.SimAntics.Primitives; namespace FSO.SimAntics.NetPlay.Model.Commands { @@ -15,6 +14,7 @@ public class VMNetSimJoinCmd : VMNetCommandBodyAbstract public override bool AcceptFromClient { get { return false; } } public VMNetAvatarPersistState AvatarState; + public LotTransitionInfo TransitionInfo; public static ushort CurVer = 0xFFEE; @@ -47,11 +47,41 @@ public override bool Execute(VM vm) var guid = (AvatarState.CustomGUID == 0) ? VMAvatar.TEMPLATE_PERSON : AvatarState.CustomGUID; var sim = vm.Context.CreateObjectInstance(guid, LotTilePos.OUT_OF_WORLD, Direction.NORTH).Objects[0]; + + ValidateTransitionInfo(vm); + + bool toMailbox = true; + + if (TransitionInfo != null) + { + // The edge tiles on a lot are blank and overlap with the surrounding lot... + // ...so when wrapping the position, they are subtracted. + int wOffset = (vm.Context.Architecture.Width - 2) << 4; + int hOffset = (vm.Context.Architecture.Height - 2) << 4; + + int x = TransitionInfo.AvatarLotTilePosX - TransitionInfo.RelativeChangeX * wOffset; + int y = TransitionInfo.AvatarLotTilePosY - TransitionInfo.RelativeChangeY * hOffset; + + if (sim.SetPosition(new LotTilePos((short)x, (short)y, 1), Direction.NORTH, vm.Context).Status == VMPlacementError.Success) + { + sim.RadianDirection = TransitionInfo.AvatarDirection; + toMailbox = false; + } + } + + // Try to avoid playing the lot enter sound for the active player when transitioning between lots. + if (VM.UseWorld && ((TransitionInfo == null && vm.Ready) || vm.MyUID != AvatarState.PersistID)) + { + FSO.HIT.HITVM.Get().PlaySoundEvent("lot_enter"); + } + var mailbox = vm.Entities.FirstOrDefault(x => (x.Object.OBJ.GUID == 0xEF121974 || x.Object.OBJ.GUID == 0x1D95C9B0)); + if (toMailbox) + { + if (mailbox != null) VMFindLocationFor.FindLocationFor(sim, mailbox, vm.Context, VMPlaceRequestFlags.Default); + else sim.SetPosition(LotTilePos.FromBigTile(3, 3, 1), Direction.NORTH, vm.Context); + } - if (VM.UseWorld) FSO.HIT.HITVM.Get().PlaySoundEvent("lot_enter"); - if (mailbox != null) VMFindLocationFor.FindLocationFor(sim, mailbox, vm.Context, VMPlaceRequestFlags.Default); - else sim.SetPosition(LotTilePos.FromBigTile(3, 3, 1), Direction.NORTH, vm.Context); sim.PersistID = ActorUID; if (vm.Tuning?.GetTuning("aprilfools", 0, 2019) == 1f) @@ -122,20 +152,86 @@ public override bool Execute(VM vm) vm.Context.ObjectQueries.RegisterAvatarPersist(avatar, avatar.PersistID); if (ActorUID == uint.MaxValue - 1) { + // Old code for the invisible "server" sim from sandbox server days. avatar.SetValue(VMStackObjectVariable.Hidden, 1); avatar.SetPosition(LotTilePos.OUT_OF_WORLD, Direction.NORTH, vm.Context); avatar.SetFlag(VMEntityFlags.HasZeroExtent, true); avatar.SetPersonData(VMPersonDataVariable.IsGhost, 1); //oooooOOooooOo } + // If this avatar is not a spectator but existing avatars are, + // the lot is transitioning from spectator mode (a roommate/admin joined). + if (!((VMTSOAvatarState)avatar.TSOState).IsSpectator) + { + bool hadSpectators = false; + foreach (VMAvatar ava in vm.Context.ObjectQueries.Avatars) + { + if (ava == avatar) continue; + var ts = (VMTSOAvatarState)ava.TSOState; + if (ts.IsSpectator) + { + ts.Flags &= ~VMTSOAvatarFlags.Spectator; + hadSpectators = true; + } + } + if (hadSpectators) + { + vm.SignalChatEvent(new VMChatEvent(null, VMChatEventType.Generic, "Lot transitioned from spectator mode.")); + } + } + vm.SignalChatEvent(new VMChatEvent(avatar, VMChatEventType.Join, avatar.Name)); - var oow = vm.Context.ObjectQueries.GetObjectsAt(LotTilePos.OUT_OF_WORLD); - if (oow != null) + if (toMailbox) + { + var oow = vm.Context.ObjectQueries.GetObjectsAt(LotTilePos.OUT_OF_WORLD); + if (oow != null) + { + foreach (var obj in oow) + { + obj.ExecuteNamedEntryPoint("CT - FSO Player Joined", vm.Context, true, obj, new([avatar.ObjectID, 0, 0, 0 ])); + } + } + } + else { - foreach (var obj in oow) + if (TransitionInfo.Type == LotTransitionType.DirectControl) { - obj.ExecuteNamedEntryPoint("CT - FSO Player Joined", vm.Context, true, obj, new short[] { avatar.ObjectID, 0, 0, 0 }); + avatar.SetPersonData(VMPersonDataVariable.UnusedAndDoNotUse2, 32767); // Enable direct control + avatar.Thread.EnsureDirectControlAction(); + } + else if (TransitionInfo.Type == LotTransitionType.Routing) + { + int w = vm.Context.Architecture.Width << 4; + int h = vm.Context.Architecture.Height << 4; + int targX = TransitionInfo.RoutingLotTilePosX; + int targY = TransitionInfo.RoutingLotTilePosY; + bool targInBounds = targX >= 0 && targX < w && targY >= 0 && targY < h; + + if (!targInBounds || !VMNetGotoCmd.QueueGoto(vm, avatar, new LotTilePos((short)TransitionInfo.RoutingLotTilePosX, (short)TransitionInfo.RoutingLotTilePosY, 1))) + { + // Try to go behind the mailbox. + if (mailbox != null) + { + LotTilePos tpos = new LotTilePos(mailbox.Position); + switch (mailbox.Direction) + { + case Direction.SOUTH: + tpos.y += 16; + break; + case Direction.WEST: + tpos.x -= 16; + break; + case Direction.EAST: + tpos.x += 16; + break; + case Direction.NORTH: + tpos.y -= 16; + break; + } + VMNetGotoCmd.QueueGoto(vm, avatar, tpos); + } + } } } @@ -147,12 +243,95 @@ public override bool Verify(VM vm, VMAvatar caller) return !FromNet; //can only be sent out by server } + private void ValidateTransitionInfo(VM vm) + { + if (TransitionInfo != null) + { + if (TransitionInfo.RelativeChangeX < -1 || TransitionInfo.RelativeChangeX > 1 || + TransitionInfo.RelativeChangeY < -1 || TransitionInfo.RelativeChangeY > 1 || + (TransitionInfo.RelativeChangeX == 0 && TransitionInfo.RelativeChangeY == 0)) + { + TransitionInfo = null; + return; + } + + // Needs to be on the correct side for the relative change info + + int w = vm.Context.Architecture.Width << 4; + int h = vm.Context.Architecture.Height << 4; + + var x = TransitionInfo.AvatarLotTilePosX; + var y = TransitionInfo.AvatarLotTilePosY; + var xEdge = w * ((TransitionInfo.RelativeChangeX + 1) / 2); + var yEdge = h * ((TransitionInfo.RelativeChangeY + 1) / 2); + + int acceptableMargin = 16; + + bool xValid = TransitionInfo.RelativeChangeX == 0 || (x >= 0 && x < w && Math.Abs(x - xEdge) < acceptableMargin); + bool yValid = TransitionInfo.RelativeChangeY == 0 || (y >= 0 && y < h && Math.Abs(y - yEdge) < acceptableMargin); + + if (!xValid && !yValid) + { + TransitionInfo = null; + return; + } + + TransitionInfo.AvatarDirection = (float)DirectionUtils.Normalize(TransitionInfo.AvatarDirection); + + if (float.IsNaN(TransitionInfo.AvatarDirection) || float.IsInfinity(TransitionInfo.AvatarDirection)) + { + TransitionInfo.AvatarDirection = 0; + } + } + } + + public static void PutTransition(BinaryWriter output, LotTransitionInfo info) + { + output.Write(info.BeforeLocation); + output.Write(info.RelativeChangeX); + output.Write(info.RelativeChangeY); + + output.Write(info.AvatarLotTilePosX); + output.Write(info.AvatarLotTilePosY); + output.Write(info.AvatarDirection); + + output.Write((int)info.Type); + output.Write(info.RoutingTargetLocation); + output.Write(info.RoutingLotTilePosX); + output.Write(info.RoutingLotTilePosY); + } + + public static LotTransitionInfo GetTransition(BinaryReader input) + { + return new LotTransitionInfo() + { + BeforeLocation = input.ReadUInt32(), + RelativeChangeX = input.ReadInt32(), + RelativeChangeY = input.ReadInt32(), + + AvatarLotTilePosX = input.ReadInt32(), + AvatarLotTilePosY = input.ReadInt32(), + AvatarDirection = input.ReadSingle(), + + Type = (LotTransitionType)input.ReadInt32(), + RoutingTargetLocation = input.ReadUInt32(), + RoutingLotTilePosX = input.ReadInt32(), + RoutingLotTilePosY = input.ReadInt32(), + }; + } + #region VMSerializable Members public override void SerializeInto(BinaryWriter writer) { base.SerializeInto(writer); writer.Write(Version); AvatarState.SerializeInto(writer); + + writer.Write(TransitionInfo != null); + if (TransitionInfo != null) + { + PutTransition(writer, TransitionInfo); + } } public override void Deserialize(BinaryReader reader) @@ -161,6 +340,12 @@ public override void Deserialize(BinaryReader reader) Version = reader.ReadUInt16(); AvatarState = new VMNetAvatarPersistState(); AvatarState.Deserialize(reader); + + var hasTransition = reader.ReadBoolean(); + if (hasTransition) + { + TransitionInfo = GetTransition(reader); + } } #endregion } diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetUpgradeCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetUpgradeCmd.cs index 1c0ef3c29..444524f74 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetUpgradeCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMNetUpgradeCmd.cs @@ -52,6 +52,7 @@ public override bool Execute(VM vm, VMAvatar caller) public override bool Verify(VM vm, VMAvatar caller) { + if (IsSpectator(caller)) return false; if (Verified) return true; var obj = vm.GetObjectByPersist(ObjectPID); var currentLevel = (obj.PlatformState as VMTSOObjectState)?.UpgradeLevel ?? 0; diff --git a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMStateSyncCmd.cs b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMStateSyncCmd.cs index aef595ff2..bfadedda4 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/Commands/VMStateSyncCmd.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/Commands/VMStateSyncCmd.cs @@ -31,6 +31,8 @@ public override bool Execute(VM vm) vm.FSOVAsyncLoading = true; Task.Run(() => { + // If an async load is happening, we assume that it's in the game client instead of the server and force the thread's UseWorld to true. + VM.UseWorld = true; vm.FSOVClientJoin = (vm.Context.Architecture == null); vm.LoadAsync(State); if (VM.UseWorld && vm.Context.Blueprint.SubWorlds.Count == 0) VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj); @@ -44,6 +46,8 @@ public override bool Execute(VM vm) { vm.Load(State); if (VM.UseWorld && vm.Context.Blueprint.SubWorlds.Count == 0) VMLotTerrainRestoreTools.RestoreSurroundings(vm, vm.HollowAdj); + + vm.SignalGenericVMEvt(VMEventType.Resync, null); } return true; } diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetAvatarPersistState.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetAvatarPersistState.cs index f7def4f37..c81fb8978 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetAvatarPersistState.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetAvatarPersistState.cs @@ -132,8 +132,8 @@ public void SerializeInto(BinaryWriter writer) writer.Write(IsWorker); writer.Write(CustomGUID); - writer.Write(VMSerializableUtils.ToByteArray(MotiveData)); - writer.Write(VMSerializableUtils.ToByteArray(PersonData)); + VMSerializableUtils.WriteArray(writer, MotiveData); + VMSerializableUtils.WriteArray(writer, PersonData); writer.Write(Relationships.Length); foreach (var rel in Relationships) diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetClient.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetClient.cs index a7e07dd25..6ad145967 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetClient.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetClient.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using FSO.Common.Model; +using System.Collections.Generic; namespace FSO.SimAntics.NetPlay.Model { @@ -10,6 +11,7 @@ public class VMNetClient public uint PersistID; public string RemoteIP; public VMNetAvatarPersistState AvatarState; //initial... obviously this can change while the lot is running. + public LotTransitionInfo TransitionInfo; public bool HadAvatar; public int InactivityTicks; public object NetHandle; diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetCommand.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetCommand.cs index b7973aeb6..4b2dff3d4 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetCommand.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetCommand.cs @@ -58,6 +58,10 @@ public class VMNetCommand : VMSerializable { VMCommandType.SM64Position, typeof(VMNetSM64PositionCmd) }, { VMCommandType.SM64Event, typeof(VMNetSM64EventCmd) }, { VMCommandType.SM64AnimData, typeof(VMNetSM64AnimDataCmd) }, + + { VMCommandType.BeginFreeRoam, typeof(VMNetBeginFreeRoamCmd) }, + { VMCommandType.GotoLot, typeof(VMNetGotoLotCmd) }, + { VMCommandType.LeaveBuildBuy, typeof(VMNetLeaveBuildBuyCmd) }, }; public static Dictionary ReverseMap = CmdMap.ToDictionary(x => x.Value, x => x.Key); @@ -170,6 +174,11 @@ public enum VMCommandType : byte DirectControlToggle = 45, SM64Position = 46, SM64Event = 47, - SM64AnimData = 48 + SM64AnimData = 48, + + // Archive + BeginFreeRoam = 49, + GotoLot = 50, + LeaveBuildBuy = 51, } } diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetCommandBodyAbstract.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetCommandBodyAbstract.cs index 3e78c6cca..6139ff014 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetCommandBodyAbstract.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetCommandBodyAbstract.cs @@ -1,9 +1,16 @@ -using System.IO; +using FSO.SimAntics.Model.TSOPlatform; +using System.IO; namespace FSO.SimAntics.NetPlay.Model { public abstract class VMNetCommandBodyAbstract : VMSerializable { + protected static bool IsSpectator(VMAvatar caller) + { + if (caller == null) return false; + return ((VMTSOAvatarState)caller.TSOState)?.IsSpectator ?? false; + } + public uint ActorUID; public bool FromNet = false; diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetMessageType.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetMessageType.cs index ac944c49a..107b3aa0f 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetMessageType.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetMessageType.cs @@ -6,7 +6,8 @@ public enum VMNetMessageType : byte BroadcastTick = 0, Direct = 1, AvatarData = 2, - + CatchupTick = 3, + //client -> server Command = 128 } diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMNetTick.cs b/TSOClient/tso.simantics/NetPlay/Model/VMNetTick.cs index e92914e07..0a669bf5b 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMNetTick.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMNetTick.cs @@ -8,6 +8,7 @@ public class VMNetTick : VMSerializable public uint TickID; public ulong RandomSeed; public bool ImmediateMode; //not serialized + public bool RunningCatchup; //not serialized public List Commands; diff --git a/TSOClient/tso.simantics/NetPlay/Model/VMSerializable.cs b/TSOClient/tso.simantics/NetPlay/Model/VMSerializable.cs index 77cde4b3d..93b237d9d 100644 --- a/TSOClient/tso.simantics/NetPlay/Model/VMSerializable.cs +++ b/TSOClient/tso.simantics/NetPlay/Model/VMSerializable.cs @@ -12,16 +12,26 @@ public interface VMSerializable public static class VMSerializableUtils { - public static byte[] ToByteArray(T[] input) + public static T[] ReadArray(BinaryReader reader, int size) where T : unmanaged { - var result = new byte[input.Length * Marshal.SizeOf(typeof(T))]; - Buffer.BlockCopy(input, 0, result, 0, result.Length); + var result = new T[size]; + var bytes = MemoryMarshal.Cast(result); + + reader.BaseStream.ReadExactly(bytes); + return result; } + public static void WriteArray(BinaryWriter writer, T[] data) where T : unmanaged + { + var bytes = MemoryMarshal.Cast(data); + + writer.Write(bytes); + } + public static T[] ToTArray(byte[] input) { - var result = new T[input.Length / Marshal.SizeOf(typeof(T))]; + var result = new T[input.Length / Marshal.SizeOf()]; Buffer.BlockCopy(input, 0, result, 0, input.Length); return result; } diff --git a/TSOClient/tso.simantics/NetPlay/VMNetDriver.cs b/TSOClient/tso.simantics/NetPlay/VMNetDriver.cs index d4e3ab83e..04a5b37ae 100644 --- a/TSOClient/tso.simantics/NetPlay/VMNetDriver.cs +++ b/TSOClient/tso.simantics/NetPlay/VMNetDriver.cs @@ -18,6 +18,8 @@ public abstract class VMNetDriver private BinaryWriter RecordStream; public VMNetCommand Executing; + public bool RunningCatchup { get; protected set; } + /// /// Indicates a VM inspired total connection shutdown. /// @@ -41,6 +43,8 @@ public bool InResync public bool AsyncBreak; //if + private bool HasShutdown; + protected void InternalTick(VM vm, VMNetTick tick) { CurrentTick = tick.TickID; @@ -49,15 +53,12 @@ protected void InternalTick(VM vm, VMNetTick tick) { if (DesyncCooldown == 0) { - System.Console.WriteLine("DESYNC - Requested state from host"); + System.Console.WriteLine($"[{CurrentTick}] DESYNC - Requested state from host"); if (DesyncTick == 0) DesyncTick = CurrentTick - 1; vm.SendCommand(new VMRequestResyncCmd()); DesyncCooldown = 30 * 30; } - else - { - System.Console.WriteLine("WARN - DESYNC - Expected " + tick.RandomSeed + ", was at " + vm.Context.RandomSeed); - } + System.Console.WriteLine($"[{CurrentTick}] WARN - DESYNC - Expected " + tick.RandomSeed + ", was at " + vm.Context.RandomSeed); } if (RecordStream != null) RecordTick(tick); @@ -68,7 +69,8 @@ protected void InternalTick(VM vm, VMNetTick tick) { if (cmd.Command is VMStateSyncCmd && ((VMStateSyncCmd)cmd.Command).Run) { - if (LastTick + 1 != tick.TickID) System.Console.WriteLine("Jump to tick " + tick.TickID); + if (LastTick + 1 != tick.TickID) System.Console.WriteLine("Jump to tick " + tick.TickID + " from " + LastTick); + LastTick = tick.TickID; if (!(this is VMFSORDriver)) doTick = false; //something weird here. this can break loading from saves casually - but must not be active for resyncs. //disable just for fsor playback } @@ -85,10 +87,10 @@ protected void InternalTick(VM vm, VMNetTick tick) } } - if (tick.TickID < LastTick) System.Console.WriteLine("Tick wrong! Got " + tick.TickID + ", Missed " + ((int)tick.TickID - (LastTick + 1))); + if (tick.TickID < LastTick + 1) System.Console.WriteLine("Tick wrong (duplicate/early)! Got " + tick.TickID + ", Missed " + ((int)tick.TickID - (LastTick + 1))); else if (doTick && vm.Context.Ready) { - if (tick.TickID > LastTick + 1) System.Console.WriteLine("Tick wrong! Got " + tick.TickID + ", Missed " + ((int)tick.TickID - (LastTick + 1))); + if (tick.TickID > LastTick + 1) System.Console.WriteLine("Tick wrong (skipped)! Got " + tick.TickID + ", Missed " + ((int)tick.TickID - (LastTick + 1))); vm.Trace?.NewTick(tick.TickID); vm.InternalTick(tick.TickID); if (DesyncCooldown > 0) DesyncCooldown--; @@ -118,7 +120,11 @@ public void EndRecord() public virtual void Shutdown() { - if (OnShutdown != null) OnShutdown(CloseReason); + if (!HasShutdown) + { + HasShutdown = true; + if (OnShutdown != null) OnShutdown(CloseReason); + } } public delegate void VMNetMessageHandler(VMNetMessageType type, byte[] data); diff --git a/TSOClient/tso.simantics/Primitives/VMAnimateSim.cs b/TSOClient/tso.simantics/Primitives/VMAnimateSim.cs index 8849e7042..2620ab2a7 100644 --- a/TSOClient/tso.simantics/Primitives/VMAnimateSim.cs +++ b/TSOClient/tso.simantics/Primitives/VMAnimateSim.cs @@ -18,8 +18,6 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe Animation animation; var id = (operand.IDFromParam) ? (ushort)(context.Args[operand.AnimationID]) : operand.AnimationID; - var newMode = true; // (context.VM.Tuning?.GetTuning("feature", 0, 0) ?? 0) != 0; //might need to disable this suddenly - too many things to test - if (id == 0) { //reset if (operand.Mode == 3) @@ -42,12 +40,11 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe if (animation == null) return VMPrimitiveExitCode.GOTO_TRUE; - var state = new VMAnimationState(animation, operand.PlayBackwards); - - if (context.VM.TS1 || newMode) - state.Speed = 30 / 25f; + var state = new VMAnimationState(animation, operand.PlayBackwards) + { + Loop = true + }; - state.Loop = true; avatar.Animations.Add(state); avatar.Avatar.LeftHandGesture = SimHandGesture.Idle; avatar.Avatar.RightHandGesture = SimHandGesture.Idle; @@ -108,11 +105,12 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe { case 1: avatar.Animations.Clear(); - var state = new VMAnimationState(animation, operand.PlayBackwards); - if (context.VM.TS1 || newMode) - state.Speed = 30 / 25f; + var state = new VMAnimationState(animation, operand.PlayBackwards) + { + Loop = true + }; + if (avatar.GetValue(VMStackObjectVariable.WalkStyle) == 1 && operand.Hurryable) state.Speed *= 2; - state.Loop = true; avatar.Animations.Add(state); avatar.Avatar.LeftHandGesture = SimHandGesture.Idle; @@ -128,8 +126,12 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe /** Start it **/ avatar.Animations.Clear(); var astate = new VMAnimationState(animation, operand.PlayBackwards); - if (context.VM.TS1 || newMode) - astate.Speed = 30 / 25f; + + if (operand.PlayBackwards) + { + astate.EventsRun = (byte)(operand.ExpectedEventCount - 1); + } + if (avatar.GetValue(VMStackObjectVariable.WalkStyle) == 1 && operand.Hurryable) astate.Speed *= 2; avatar.Animations.Add(astate); @@ -141,28 +143,54 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe { var cAnim = avatar.CurrentAnimationState; - //SPECIAL CASE: if we are ending the animation, and the number of events run < expected events - //forcefully run those events, with id as their event number. (required for bath drain) - if (cAnim.EndReached) - { - while (cAnim.EventsRun < operand.ExpectedEventCount) - { - cAnim.EventQueue.Add(cAnim.EventsRun++); - } - } + int eventDirection = operand.PlayBackwards ? -1 : 1; - if (cAnim.EventQueue.Count > 0) //favor events over end. do not want to miss any. + while (cAnim.EventQueue.Count > 0) //favor events over end. do not want to miss any. { var code = cAnim.EventQueue[0]; cAnim.EventQueue.RemoveAt(0); + + // Events under 100 are expected to fire in sequence, and are ignored past the count defined in the primitive. + if (code < 100) + { + if (code == avatar.CurrentAnimationState.EventsRun && code < operand.ExpectedEventCount && code >= 0) + { + avatar.CurrentAnimationState.EventsRun += (byte)eventDirection; + } + else + { + // Unexpected event (out of order or past the expected count) + continue; + } + } + if (operand.StoreFrameInLocal) VMMemory.SetVariable(context, VMVariableScope.Local, operand.LocalEventNumber, code); else VMMemory.SetVariable(context, VMVariableScope.Parameters, 0, code); + return VMPrimitiveExitCode.GOTO_FALSE; } - else if (cAnim.EndReached) + + if (cAnim.EndReached) { + //SPECIAL CASE: if we are ending the animation, and the number of events run < expected events + //forcefully run those events, with id as their event number. (required for bath drain) + + if (cAnim.EventsRun < operand.ExpectedEventCount) // This also works backwards, as it overflows to 255 after it hits 0 + { + short code = cAnim.EventsRun; + + cAnim.EventsRun += (byte)eventDirection; + + if (operand.StoreFrameInLocal) + VMMemory.SetVariable(context, VMVariableScope.Local, operand.LocalEventNumber, code); + else + VMMemory.SetVariable(context, VMVariableScope.Parameters, 0, code); + + return VMPrimitiveExitCode.GOTO_FALSE; + } + avatar.Animations.Clear(); return VMPrimitiveExitCode.GOTO_TRUE; } @@ -173,6 +201,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe } case 2: avatar.CarryAnimationState = new VMAnimationState(animation, false); + return VMPrimitiveExitCode.GOTO_TRUE; } return VMPrimitiveExitCode.GOTO_TRUE; diff --git a/TSOClient/tso.simantics/Primitives/VMBurn.cs b/TSOClient/tso.simantics/Primitives/VMBurn.cs index d1eba2efe..e7e24588c 100644 --- a/TSOClient/tso.simantics/Primitives/VMBurn.cs +++ b/TSOClient/tso.simantics/Primitives/VMBurn.cs @@ -77,7 +77,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe var item = spread.Dequeue(); if (item == LotTilePos.OUT_OF_WORLD) continue; - var objat = query.GetObjectsAt(item) ?? new List(); + var objat = query.GetObjectsAt(item) ?? []; if (first && !(objat?.Any(x => x.Object.OBJ.GUID == FIRE_GUID) ?? false)) { diff --git a/TSOClient/tso.simantics/Primitives/VMFindBestAction.cs b/TSOClient/tso.simantics/Primitives/VMFindBestAction.cs index b3682e883..de416f508 100644 --- a/TSOClient/tso.simantics/Primitives/VMFindBestAction.cs +++ b/TSOClient/tso.simantics/Primitives/VMFindBestAction.cs @@ -98,12 +98,22 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe return VMPrimitiveExitCode.GOTO_TRUE; } + var caller = (VMAvatar)context.Caller; + + // Check if free will is disabled for player family Sims + // Visitors (PersonType == 1) and pets should still have autonomy + var visitor = (caller.GetPersonData(VMPersonDataVariable.PersonType) == 1); + if (!VM.FreeWillEnabled && !visitor && !caller.IsPet) + { + // Free will is disabled and this is a player family Sim (not visitor, not pet) + // Return false to indicate no autonomous action was chosen + return VMPrimitiveExitCode.GOTO_FALSE; + } + var ents = new List(context.VM.Context.ObjectQueries.WithAutonomy); var processed = new HashSet(); - var caller = (VMAvatar)context.Caller; var pos1 = caller.Position; - var visitor = (caller.GetPersonData(VMPersonDataVariable.PersonType) == 1); var child = (caller.IsChild && context.VM.TS1); var attenTable = visitor ? TTAB.VisitorAttenuationValues : TTAB.AttenuationValues; var global = Content.Content.Get().WorldObjectGlobals; diff --git a/TSOClient/tso.simantics/Primitives/VMFindBestObjectForFunction.cs b/TSOClient/tso.simantics/Primitives/VMFindBestObjectForFunction.cs index 13882a88a..2616785a0 100644 --- a/TSOClient/tso.simantics/Primitives/VMFindBestObjectForFunction.cs +++ b/TSOClient/tso.simantics/Primitives/VMFindBestObjectForFunction.cs @@ -109,7 +109,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe CodeOwner = Behavior.owner, StackObject = ent, Routine = Behavior.routine, - Args = new short[4] + Args = default }); Execute = (test == VMPrimitiveExitCode.RETURN_TRUE); diff --git a/TSOClient/tso.simantics/Primitives/VMFindLocationFor.cs b/TSOClient/tso.simantics/Primitives/VMFindLocationFor.cs index 402a519f9..bc3ec72b2 100644 --- a/TSOClient/tso.simantics/Primitives/VMFindLocationFor.cs +++ b/TSOClient/tso.simantics/Primitives/VMFindLocationFor.cs @@ -41,9 +41,16 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe obj.SetPosition(LotTilePos.OUT_OF_WORLD, Direction.NORTH, context.VM.Context, flags); return VMPrimitiveExitCode.GOTO_TRUE; case 2: - //"smoke cloud" - halfway between callee and caller (is "caller" actually reference object?) - var smokePos = context.Callee.Position; - smokePos += context.Caller.Position; + // "smoke cloud" - halfway between ref and the "attack target" + // it's not entirely clear how TSO/TS1 do this, the only place the attack target is stored are: + // TSO: in attribute 9 of the callee (person B), but this isn't true for the crafting table + // TS1: other person with relationship to the callee (is involved (0): 1), this is never true in TSO + // both: action icon (correct in all cases, but TS1 doesn't flinch if you change this) + + // For now, use the action icon owner. + var smokeRef = context.Thread.ActiveAction.IconOwner; + var smokePos = refObj.Position; + smokePos += smokeRef.Position; smokePos /= 2; smokePos -= new LotTilePos(8, 8, 0); //smoke is 2x2... offset to center it. return (obj.SetPosition(smokePos, Direction.NORTH, context.VM.Context).Status == VMPlacementError.Success)? diff --git a/TSOClient/tso.simantics/Primitives/VMGenericTSOCall.cs b/TSOClient/tso.simantics/Primitives/VMGenericTSOCall.cs index d81f586aa..b82e5c47e 100644 --- a/TSOClient/tso.simantics/Primitives/VMGenericTSOCall.cs +++ b/TSOClient/tso.simantics/Primitives/VMGenericTSOCall.cs @@ -419,7 +419,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe } case VMGenericTSOCallMode.FSOGoToLotIDTemp01: { - if (context.Caller.PersistID == context.VM.MyUID) + if (context.Caller.PersistID == context.VM.MyUID && !context.VM.Driver.RunningCatchup) { uint idLow = (uint)context.Thread.TempRegisters[0]; uint idHigh = (uint)context.Thread.TempRegisters[1] << 16; @@ -469,6 +469,31 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe var gobj = context.StackObject as VMGameObject; return (gobj != null && !gobj.Disabled.HasFlag(VMGameObjectDisableFlags.LotCategoryWrong)) ? VMPrimitiveExitCode.GOTO_TRUE : VMPrimitiveExitCode.GOTO_FALSE; } + case VMGenericTSOCallMode.FSOShowCheckTreeTooltipTemp0Temp1: + { + // Show the string in STR#[temp 0][temp 1] as a tooltip for this object on hover. + // Only works while we're executing in a check tree. + + if (context.Thread.IsCheck && context.Thread.ActionStrings != null) + { + STR table = context.ScopeResource.Get((ushort)context.Thread.TempRegisters[0]); + + if (table == null) return VMPrimitiveExitCode.GOTO_FALSE; + + var newName = VMDialogHandler.ParseDialogString(context, table.GetString(context.Thread.TempRegisters[1] - 1), table); + + context.Thread.ActionStrings.Add(new VMPieMenuInteraction() + { + Name = newName, + Param0 = context.StackObjectID, + IsTooltip = true + }); + + return VMPrimitiveExitCode.GOTO_TRUE; + } + + return VMPrimitiveExitCode.GOTO_FALSE; + } default: return VMPrimitiveExitCode.GOTO_TRUE; } diff --git a/TSOClient/tso.simantics/Primitives/VMInventoryOperations.cs b/TSOClient/tso.simantics/Primitives/VMInventoryOperations.cs index 333048050..8ae8f98bd 100644 --- a/TSOClient/tso.simantics/Primitives/VMInventoryOperations.cs +++ b/TSOClient/tso.simantics/Primitives/VMInventoryOperations.cs @@ -59,7 +59,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe if (state.WriteResult) VMMemory.SetBigVariable(context, state.WriteScope, state.WriteData, state.Temp0Value); if (state.TempWrite.Count > 0) { - var length = Math.Min(context.Thread.TempRegisters.Length, state.TempWrite.Count); + var length = Math.Min(Model.VMTempRegisters.Length, state.TempWrite.Count); for (int i=0; i(); - var temps = context.Thread.TempRegisters; - var attrCount = Math.Min(temps[0], temps.Length - 1); + ref var temps = ref context.Thread.TempRegisters; + var attrCount = Math.Min(temps[0], Model.VMTempRegisters.Length - 1); for (int i = 0; i < attrCount; i++) { data.Add(temps[i + 1]); diff --git a/TSOClient/tso.simantics/Primitives/VMOnlineJobsCall.cs b/TSOClient/tso.simantics/Primitives/VMOnlineJobsCall.cs index 7cf557cc1..8b2bd96bf 100644 --- a/TSOClient/tso.simantics/Primitives/VMOnlineJobsCall.cs +++ b/TSOClient/tso.simantics/Primitives/VMOnlineJobsCall.cs @@ -24,10 +24,15 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe switch (operand.Call) { case VMOnlineJobsCallMode.GotoJobLot: - if (context.Caller.PersistID == context.VM.MyUID) context.VM.SignalLotSwitch(0x200); + if (context.Caller.PersistID == context.VM.MyUID && !context.VM.Driver.RunningCatchup) context.VM.SignalLotSwitch(0x200); break; case VMOnlineJobsCallMode.SetControllerID: - context.VM.SetGlobalValue(21, (context.StackObject == null) ? (short)0 : context.StackObject.ObjectID); + var controllerId = context.StackObjectID; + context.VM.SetGlobalValue(21, controllerId); + if (controllerId == 0) + { + context.VM.TSOState.JobUI = null; + } break; case VMOnlineJobsCallMode.GetRandomJob: var jobs = new List() { 1, 2, 4, 5 }; diff --git a/TSOClient/tso.simantics/Primitives/VMRunFunctionalTree.cs b/TSOClient/tso.simantics/Primitives/VMRunFunctionalTree.cs index eaefeb199..f0d24c9dd 100644 --- a/TSOClient/tso.simantics/Primitives/VMRunFunctionalTree.cs +++ b/TSOClient/tso.simantics/Primitives/VMRunFunctionalTree.cs @@ -1,4 +1,5 @@ using FSO.Files.Utils; +using FSO.SimAntics.Model; using System.IO; namespace FSO.SimAntics.Engine.Primitives @@ -30,7 +31,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe CodeOwner = Behavior.owner, StackObject = ent, Routine = Behavior.routine, - Args = new short[4] + Args = default }) == VMPrimitiveExitCode.RETURN_TRUE); } else Execute = true; @@ -56,7 +57,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe ActionTree = context.ActionTree }; if (operand.Flags > 0 && context.ActionTree) context.Thread.ActiveAction.IconOwner = context.StackObject; - childFrame.Args = new short[routine.Arguments]; + childFrame.Args = new VMArguments(routine.Arguments); context.Thread.Push(childFrame); return VMPrimitiveExitCode.CONTINUE; } diff --git a/TSOClient/tso.simantics/Primitives/VMRunTreeByName.cs b/TSOClient/tso.simantics/Primitives/VMRunTreeByName.cs index 2448bd19b..9df7b426d 100644 --- a/TSOClient/tso.simantics/Primitives/VMRunTreeByName.cs +++ b/TSOClient/tso.simantics/Primitives/VMRunTreeByName.cs @@ -39,14 +39,14 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe } else if (operand.Destination == 0) { - var result = context.Thread.RunInMyStack((VMRoutine)tree.bhav, context.StackObject.Object, context.Thread.TempRegisters, context.StackObject) + var result = context.Thread.RunInMyStack((VMRoutine)tree.bhav, context.StackObject.Object, context.Thread.TempRegisters.AsSpan()[..4].ToArray(), context.StackObject) ? VMPrimitiveExitCode.GOTO_TRUE : VMPrimitiveExitCode.GOTO_FALSE; return (context.VM.Aborting) ? VMPrimitiveExitCode.ERROR : result; //run in my stack } else { - var result = context.StackObject.Thread.RunInMyStack((VMRoutine)tree.bhav, context.StackObject.Object, context.Thread.TempRegisters, context.StackObject) + var result = context.StackObject.Thread.RunInMyStack((VMRoutine)tree.bhav, context.StackObject.Object, context.Thread.TempRegisters.AsSpan()[..4].ToArray(), context.StackObject) ? VMPrimitiveExitCode.GOTO_TRUE : VMPrimitiveExitCode.GOTO_FALSE; return (context.VM.Aborting) ? VMPrimitiveExitCode.ERROR : result; //run in stack obj's stack diff --git a/TSOClient/tso.simantics/Primitives/VMSetToNext.cs b/TSOClient/tso.simantics/Primitives/VMSetToNext.cs index a9fd8fe5e..9ba4b90bd 100644 --- a/TSOClient/tso.simantics/Primitives/VMSetToNext.cs +++ b/TSOClient/tso.simantics/Primitives/VMSetToNext.cs @@ -1,11 +1,10 @@ -using System.Linq; +using FSO.Files.Utils; +using FSO.LotView.Model; using FSO.SimAntics.Engine; -using FSO.Files.Utils; using FSO.SimAntics.Engine.Scopes; using FSO.SimAntics.Engine.Utils; using Microsoft.Xna.Framework; -using System.IO; -using FSO.LotView.Model; +using System.Runtime.CompilerServices; namespace FSO.SimAntics.Primitives { @@ -20,13 +19,44 @@ public class VMSetToNext : VMPrimitiveHandler new Point(0, 1), new Point(-1, 0), }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetVariable(VMStackFrame context, VMSetToNextOperand operand, short result) + { + VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, result); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetVariable(VMStackFrame context, VMSetToNextOperand operand, VMEntity result) + { + if (operand.TargetOwner == VMVariableScope.StackObjectID) + { + context.StackObject = result; + } + else + { + SetVariable(context, operand, result.ObjectID); + } + } + public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOperand args) { var operand = (VMSetToNextOperand)args; - var targetValue = VMMemory.GetVariable(context, operand.TargetOwner, operand.TargetData); var entities = context.VM.Entities; - VMEntity Pointer = context.VM.GetObjectById(targetValue); + VMEntity Pointer; + short targetValue; + if (operand.TargetOwner == VMVariableScope.StackObjectID) + { + targetValue = context.StackObjectID; + Pointer = context.StackObjectSafe; + } + else + { + targetValue = VMMemory.GetVariable(context, operand.TargetOwner, operand.TargetData); + Pointer = context.VM.GetObjectById(targetValue); + } //re-evaluation of what this actually does: //tries to find the next object id (from the previous) that meets a specific condition. @@ -37,7 +67,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe if (operand.SearchType == VMSetToNextSearchType.PartOfAMultipartTile) { var result = MultitilePart(context, Pointer, targetValue); if (result == 0) return VMPrimitiveExitCode.GOTO_FALSE; - VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, result); + SetVariable(context, operand, result); return VMPrimitiveExitCode.GOTO_TRUE; } else if (operand.SearchType == VMSetToNextSearchType.ObjectAdjacentToObjectInLocal) @@ -46,7 +76,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe if (result == null) return VMPrimitiveExitCode.GOTO_FALSE; else { - VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, result.ObjectID); + SetVariable(context, operand, result); return VMPrimitiveExitCode.GOTO_TRUE; } } @@ -54,21 +84,21 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe { var next = Content.Content.Get().Jobs.SetToNext(targetValue); if (next < 0) return VMPrimitiveExitCode.GOTO_FALSE; - VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, next); + SetVariable(context, operand, next); return VMPrimitiveExitCode.GOTO_TRUE; } else if (operand.SearchType == VMSetToNextSearchType.NeighborId) { var next = Content.Content.Get().Neighborhood.SetToNext(targetValue); if (next < 0) return VMPrimitiveExitCode.GOTO_FALSE; - VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, next); + SetVariable(context, operand, next); return VMPrimitiveExitCode.GOTO_TRUE; } else if (operand.SearchType == VMSetToNextSearchType.NeighborOfType) { var next = Content.Content.Get().Neighborhood.SetToNext(targetValue, operand.GUID); if (next < 0) return VMPrimitiveExitCode.GOTO_FALSE; - VMMemory.SetVariable(context, operand.TargetOwner, operand.TargetData, next); + SetVariable(context, operand, next); return VMPrimitiveExitCode.GOTO_TRUE; } else { @@ -106,7 +136,7 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe bool loop = (operand.SearchType == VMSetToNextSearchType.ObjectOnSameTile) || (operand.SearchType == VMSetToNextSearchType.FamilyMember); - var ind = VM.FindNextIndexInObjList(entities, targetValue); + var ind = entities.FindNextIndexInObjList(targetValue); for (int i=ind; i ExpenseTuningMultiplier = new Dictionary() - { - /*{ VMTransferFundsExpenseType.IncomeJob, 20f }, - { VMTransferFundsExpenseType.IncomeClubJob, 20f }, - { VMTransferFundsExpenseType.IncomeRestaurantJob, 20f }, - { VMTransferFundsExpenseType.IncomeRobotJob, 20f }, - - //{ VMTransferFundsExpenseType.IncomeMisc, 20f }, - { VMTransferFundsExpenseType.IncomeCanning, 20f }, - { VMTransferFundsExpenseType.IncomeChalkboard, 20f }, - { VMTransferFundsExpenseType.IncomeChemistry, 20f }, - - { VMTransferFundsExpenseType.IncomeEasel, 20f }, - { VMTransferFundsExpenseType.IncomeEaselPlayers, 20f }, - { VMTransferFundsExpenseType.IncomeFoodCounterPlayers, 20f }, - { VMTransferFundsExpenseType.IncomeGGWorkbench, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsBlackjack, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsMaze, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsPaperC, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsPizza, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsPoker, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsRoulette, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsSkill, 20f }, - { VMTransferFundsExpenseType.IncomeObjectsSlots, 20f }, - { VMTransferFundsExpenseType.IncomePinata, 20f }, - { VMTransferFundsExpenseType.IncomePinataPlayers, 20f }, - - { VMTransferFundsExpenseType.IncomeTelemarket, 20f }, - { VMTransferFundsExpenseType.IncomeTypewriter, 20f },*/ - }; - //income objects skill maps to these public static Dictionary SkillTypes = new Dictionary() { @@ -55,6 +24,40 @@ public class VMTransferFunds : VMPrimitiveHandler { 0xF77D1200, VMTransferFundsExpenseType.IncomeTypewriter } }; + // Job income can be scaled by tuning + private static HashSet AllJobIncome = + [ + // single skill + VMTransferFundsExpenseType.IncomeTypewriter, + VMTransferFundsExpenseType.IncomeEasel, + VMTransferFundsExpenseType.IncomeEaselPlayers, + VMTransferFundsExpenseType.IncomeChalkboard, + VMTransferFundsExpenseType.IncomeCanning, + VMTransferFundsExpenseType.IncomeChemistry, + VMTransferFundsExpenseType.IncomeGGWorkbench, + VMTransferFundsExpenseType.IncomePinata, + VMTransferFundsExpenseType.IncomePinataPlayers, + VMTransferFundsExpenseType.IncomeTelemarket, + + // generic single skill + VMTransferFundsExpenseType.IncomePlayersSkill, + VMTransferFundsExpenseType.IncomeObjectsSkill, + + // group skill + VMTransferFundsExpenseType.IncomePlayersPizza, + VMTransferFundsExpenseType.IncomeObjectsPizza, + VMTransferFundsExpenseType.IncomePlayersPaperC, + VMTransferFundsExpenseType.IncomeObjectsPaperC, + VMTransferFundsExpenseType.IncomePlayersMaze, + VMTransferFundsExpenseType.IncomeObjectsMaze, + + // onlinejobs + VMTransferFundsExpenseType.IncomeJob, + VMTransferFundsExpenseType.IncomeRobotJob, + VMTransferFundsExpenseType.IncomeRestaurantJob, + VMTransferFundsExpenseType.IncomeClubJob, + ]; + public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOperand args) { var operand = (VMTransferFundsOperand)args; @@ -81,10 +84,25 @@ public override VMPrimitiveExitCode Execute(VMStackFrame context, VMPrimitiveOpe } var amount = VMMemory.GetBigVariable(context, operand.GetAmountOwner(), (short)operand.AmountData); - float scale = 1f; - if (ExpenseTuningMultiplier.TryGetValue(operand.ExpenseType, out scale)) + + if (AllJobIncome.Contains(operand.ExpenseType)) { - amount = (int)(amount * scale); + var scale = context.VM.Tuning.GetTuning("income_mul", 0, 0); + + if (scale != null) + { + amount = (int)(amount * scale); + } + + if (operand.ExpenseType == VMTransferFundsExpenseType.IncomeObjectsSkill) + { + scale = context.VM.Tuning.GetTuning("income_mul", 0, 1); + + if (scale != null) + { + amount = (int)(amount * scale); + } + } } uint source = uint.MaxValue; diff --git a/TSOClient/tso.simantics/Properties/AssemblyInfo.cs b/TSOClient/tso.simantics/Properties/AssemblyInfo.cs deleted file mode 100644 index 4da043702..000000000 --- a/TSOClient/tso.simantics/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.SimAntics")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("FSO.SimAntics")] -[assembly: AssemblyCopyright("Copyright © 2015")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("d89f44fe-8d79-446e-8c56-e6348cdf4d25")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.client/Utils/FirstPersonHelper.cs b/TSOClient/tso.simantics/Utils/FirstPersonHelper.cs similarity index 58% rename from TSOClient/tso.client/Utils/FirstPersonHelper.cs rename to TSOClient/tso.simantics/Utils/FirstPersonHelper.cs index 5411e4c71..8688aca3b 100644 --- a/TSOClient/tso.client/Utils/FirstPersonHelper.cs +++ b/TSOClient/tso.simantics/Utils/FirstPersonHelper.cs @@ -1,13 +1,13 @@ using FSO.Common; -using FSO.SimAntics; -namespace FSO.Client.Utils +namespace FSO.SimAntics.Utils { - internal static class FirstPersonHelper + public static class FirstPersonHelper { public static float GetTuning(VM vm) { - return vm?.Tuning?.GetTuning("aprilfools", 0, 2023) ?? 0; + return 1; + //return vm?.Tuning?.GetTuning("aprilfools", 0, 2023) ?? 0; } public static bool IsEnabled(VM vm) diff --git a/TSOClient/tso.simantics/Utils/VMLotTerrainRestoreTools.cs b/TSOClient/tso.simantics/Utils/VMLotTerrainRestoreTools.cs index 83e28a6db..1308afd06 100644 --- a/TSOClient/tso.simantics/Utils/VMLotTerrainRestoreTools.cs +++ b/TSOClient/tso.simantics/Utils/VMLotTerrainRestoreTools.cs @@ -1,4 +1,6 @@ -using FSO.Content.Model; +using FSO.Common.Model; +using FSO.Common.Utils; +using FSO.Content.Model; using FSO.LotView; using FSO.LotView.Components; using FSO.LotView.Model; @@ -7,6 +9,7 @@ using FSO.SimAntics.Model; using FSO.SimAntics.Model.TSOPlatform; using FSO.SimAntics.NetPlay.Drivers; +using FSO.SimAntics.NetPlay.Model.Commands; using Microsoft.Xna.Framework; using System; using System.IO; @@ -433,12 +436,12 @@ public static void StampTerrainmap(VMArchitecture arch, byte[] tilemap, short x, } } - public static void RestoreTerrain(VM vm, RestoreLotType type = RestoreLotType.Normal) + public static void RestoreTerrain(VM vm, RestoreLotType type = RestoreLotType.Normal, bool canFlatten = true) { //take center of lotstate RestoreTerrain(vm, vm.TSOState.Terrain.BlendN[1, 1], vm.TSOState.Terrain.Roads[1, 1], type); - RestoreHeight(vm, vm.TSOState.Terrain, 1, 1); + RestoreHeight(vm, vm.TSOState.Terrain, 1, 1, type != RestoreLotType.Blank && canFlatten); } public static int GetBaseLevel(VM vm, VMTSOSurroundingTerrain terrain, int x, int y) @@ -458,7 +461,44 @@ public static int GetBaseLevel(VM vm, VMTSOSurroundingTerrain terrain, int x, in return (int)(((sr[1, 1] + sr[1, 2] + sr[2, 2] + sr[2, 1]) / 4) * 100); } - public static int RestoreHeight(VM vm, VMTSOSurroundingTerrain terrain, int x, int y) + public static int GetBaseLevel(VM vm, int x, int y) + { + return GetBaseLevel(vm, vm.TSOState.Terrain, x, y); + } + + public static (byte[], short[]) SnapshotTerrain(VM vm) + { + var terrain = vm.Context.Architecture.Terrain; + return ([.. terrain.GrassState], [.. terrain.Heights]); + } + + public static void RestoreBuildableTerrain(VM vm, (byte[], short[]) data) + { + var (grass, heights) = data; + var target = vm.Context.Architecture.Terrain; + + var lotSInfo = vm.TSOState.Size; + if (vm.TSOState.OwnerID == 0) + { + lotSInfo = 10; + } + + var ret = vm.Context.GetTSOBuildableArea(lotSInfo); + + ret.Inflate(-1, -1); + + for (int oy = ret.Top; oy <= ret.Bottom; oy++) + { + for (int ox = ret.Left; ox <= ret.Right; ox++) + { + int index = (oy) * target.Width + (ox); + target.Heights[index] = heights[index]; + target.GrassState[index] = grass[index]; + } + } + } + + public static int RestoreHeight(VM vm, VMTSOSurroundingTerrain terrain, int x, int y, bool flatten = true) { var sr = new float[4, 4]; @@ -478,22 +518,22 @@ public static int RestoreHeight(VM vm, VMTSOSurroundingTerrain terrain, int x, i var xn = VMArchitectureTerrain.TerrainXNoise; var yn = VMArchitectureTerrain.TerrainYNoise; + bool isWater = terrain.BlendN[x, y].Base == TerrainType.WATER; + for (int oy = 1; oy < target.Height; oy++) { - - for (int ox = 1; ox < target.Width; ox++) { int index = (target.Height - oy) * target.Width + (target.Height - ox); float fracy = (oy - 1f) / (target.Height - 2f); - fracy -= (yn[index]-0.5f)/5f; + if (!isWater) fracy -= (yn[index]-0.5f)/5f; float y1 = Cubic(sr[0, 0], sr[0, 1], sr[0, 2], sr[0, 3], fracy); float y2 = Cubic(sr[1, 0], sr[1, 1], sr[1, 2], sr[1, 3], fracy); float y3 = Cubic(sr[2, 0], sr[2, 1], sr[2, 2], sr[2, 3], fracy); float y4 = Cubic(sr[3, 0], sr[3, 1], sr[3, 2], sr[3, 3], fracy); float fracx = (ox - 1f) / (target.Width - 2f); - fracx -= (xn[index] - 0.5f) / 5f; + if (!isWater) fracx -= (xn[index] - 0.5f) / 5f; var h = Cubic(y1, y2, y3, y4, fracx); target.Heights[index] = (short)(((h * 100f) - baseLevel)); @@ -509,7 +549,7 @@ public static int RestoreHeight(VM vm, VMTSOSurroundingTerrain terrain, int x, i var mailbox = vm.Entities.FirstOrDefault(m => (m.Object.OBJ.GUID == 0xEF121974 || m.Object.OBJ.GUID == 0x1D95C9B0)); - if (mailbox != null && mailbox.Position != LotTilePos.OUT_OF_WORLD) + if (mailbox != null && mailbox.Position != LotTilePos.OUT_OF_WORLD && flatten) { var mailheight = target.Heights[mailbox.Position.TileY * target.Width + mailbox.Position.TileX]; @@ -584,7 +624,7 @@ public static void RestoreTerrain(VM vm, TerrainBlend blend, byte roads, Restore var baseB = blend.Base; arch.Terrain.LightType = (baseB == TerrainType.WATER) ? TerrainType.SAND : blend.Base; arch.Terrain.DarkType = (blend.Blend == TerrainType.WATER) ? blend.Base : blend.Blend; - arch.Terrain.GenerateGrassStates(); + arch.Terrain.GenerateGrassStates(type); //clear all previous roads/sea VMArchitectureTools.FloorPatternRect(arch, new Rectangle(0, 0, arch.Width, 5), 0, 0, 1); @@ -594,7 +634,15 @@ public static void RestoreTerrain(VM vm, TerrainBlend blend, byte roads, Restore if (baseB == TerrainType.WATER) { - //... + //Move everything out of world. + foreach (var obj in vm.Entities) + { + if (obj.Position != LotTilePos.OUT_OF_WORLD) + { + obj.SetPosition(LotTilePos.OUT_OF_WORLD, obj.Direction, vm.Context); + } + } + VMArchitectureTools.FloorPatternRect(arch, new Rectangle(1, 1, arch.Width - 3, arch.Height - 3), 0, 65534, 1); } @@ -661,7 +709,7 @@ public static void RestoreTerrain(VM vm, TerrainBlend blend, byte roads, Restore new float[] { (15f / 180f) * (float)Math.PI, (-15f / 180f) * (float)Math.PI }); RestoreRoad(vm, roads); - if (vm.GetGlobalValue(11) == -1) + if (vm.GetGlobalValue(11) == -1 || vm.TSOState.ObjectLimit == 0) { //set road dir. should only really do this FIRST EVER time, then road dir changes after are manual and rotate the contents of the lot. vm.TSOState.Size &= 0xFFFF; @@ -795,6 +843,14 @@ public static void PositionLandmarkObjects(VM vm, RestoreLotType type) // if we can't place the object, put it oow. ent.MultitileGroup.BaseObject.SetPosition(LotTilePos.OUT_OF_WORLD, (Direction)(1 << ((lotDir * 2 + pos.DirOff) % 8)), vm.Context); } + + if (type == RestoreLotType.Blank) + { + // Hide all landmark objects and make them intangible. + + ent.SetValue(VMStackObjectVariable.Hidden, 1); + ent.SetFlag(VMEntityFlags.HasZeroExtent, true); + } } } @@ -811,7 +867,7 @@ public static void PositionLandmarkObjects(VM vm, RestoreLotType type) if (ped != null) ped.SetPosition(LotTilePos.FromBigTile((short)ctr.X, (short)ctr.Y, 1), (Direction)(1 << ((lotDir * 2 + 0) % 8)), vm.Context); var rPos = ctr + (-13 * xperp) + (2 * yperp); - if (ped != null) + if (ped != null && type != RestoreLotType.Blank) { StampTerrainmap(arch, CarDirtRoad, (short)rPos.X, (short)rPos.Y, xperp, yperp); } @@ -819,7 +875,7 @@ public static void PositionLandmarkObjects(VM vm, RestoreLotType type) private static VMEntity EntityByGUID(VM vm, uint GUID) { - return vm.Entities.FindAll(x => (x.MasterDefinition?.GUID ?? 0) == GUID || x.Object.GUID == GUID).FirstOrDefault(); + return vm.Entities.FirstOrDefault(x => (x.MasterDefinition?.GUID ?? 0) == GUID || x.Object.GUID == GUID); } private static byte RotateByte(byte flags, int amount) @@ -886,8 +942,10 @@ public static void ApplyTerrainBlend(VMArchitecture arch, int flags, Rectangle a val = 0; } } - val += arch.Terrain.GrassState[oy * arch.Width + ox]; - arch.Terrain.GrassState[oy * arch.Width + ox] = (byte)(Math.Max(0, Math.Min(255,val))); + + ref var target = ref arch.Terrain.GrassState[oy * arch.Width + ox]; + + target = (byte)(target + (((255 - target) * val) / 255)); } } } @@ -964,7 +1022,7 @@ public static void PopulateBlankTerrain(VM vm) var arch = vm.Context.Architecture; var objs = BlankTerrainObjects[(int)arch.Terrain.LightType]; - var random = new Random(); + var random = new Random((int)vm.TSOState.LotID); var toPlace = 15 + random.Next(20); for (int i=0; i> 16; + uint baseY = baseLocation & 0xFFFF; + if (lotsMode == 0) return; for (int y=0; y<3; y++) { for (int x=0; x<3; x++) { + int i = y * 3 + x; if (x == 1 & y == 1) continue; //that's us... + + var adj = hollowAdj == null ? new VMHollowAdjEntry(VMHollowAdjType.Terrain) : hollowAdj[i]; + + if (adj.Type < VMHollowAdjType.Terrain) + { + // Reuse or ignore + continue; + } + + // If there's an existing subworld with this lot id, replace it. + vm.Context.Blueprint.SubWorlds.RemoveAll((x) => + { + if (x.Index == i) + { + x.Dispose(); + return true; + } + + return false; + }); + + Point cityRelative = new Point(x - 1, y - 1); + uint newLocation = (uint)(((baseX + cityRelative.X) << 16) | ((baseY + cityRelative.Y) & 0xFFFF)); + var gd = vm.Context.World.State.Device; - var subworld = vm.Context.World.MakeSubWorld(gd); + var subworld = vm.Context.World.MakeSubWorld(gd, i); subworld.Initialize(gd); var tempVM = new VM(new VMContext(subworld), new VMServerDriver(new VMTSOGlobalLinkStub()), new VMNullHeadlineProvider()); tempVM.Init(); - var state = (hollowAdj == null)? null : hollowAdj[y * 3 + x]; + var state = adj.Data; if (lotsMode == 1) state = null; float height; @@ -1026,7 +1117,6 @@ public static void RestoreSurroundings(VM vm, byte[][] hollowAdj) } tempVM.HollowLoad(hollow); - RestoreTerrain(tempVM, terrain.BlendN[x, y], terrain.Roads[x, y], RestoreLotType.Normal); if (hollow.Version < 19) height = RestoreHeight(tempVM, terrain, x, y); @@ -1044,7 +1134,7 @@ public static void RestoreSurroundings(VM vm, byte[][] hollowAdj) } catch (Exception) { - hollowAdj[y * 3 + x] = null; + hollowAdj[i] = new VMHollowAdjEntry(VMHollowAdjType.Terrain); subworld.Dispose(); x--; continue; //try this surrounding lot again, but as an empty one. @@ -1055,7 +1145,13 @@ public static void RestoreSurroundings(VM vm, byte[][] hollowAdj) { var blueprint = new Blueprint(size, size); tempVM.Context.Blueprint = blueprint; - subworld.InitBlueprint(blueprint); + // This inits some GPU resources, so make sure it's done on the right thread. + subworld.InitBlueprintNoGPU(blueprint); + GameThread.InUpdate(() => + { + subworld.InitBlueprintGPU(blueprint); + }); + tempVM.TSOState.LotID = newLocation; tempVM.Context.Architecture = new VMArchitecture(size, size, blueprint, tempVM.Context); tempVM.Context.Architecture.EmptyRoomMap(); @@ -1065,11 +1161,13 @@ public static void RestoreSurroundings(VM vm, byte[][] hollowAdj) terrainC.Initialize(subworld.State.Device, subworld.State); blueprint.Terrain = terrainC; - tempVM.Context.Architecture.Terrain.LowQualityGrassState = true; RestoreTerrain(tempVM, terrain.BlendN[x, y], terrain.Roads[x, y], RestoreLotType.Blank); height = RestoreHeight(tempVM, terrain, x, y); tempVM.Context.Blueprint.BaseAlt = (int)((baseHeight - height)); + tempVM.TSOState.Size = 10 | (3 << 8); // Maximum size for admin placement on unowned lot (matches what it looks like in free roam) + tempVM.Context.UpdateTSOBuildableArea(); + EnsureCoreObjects(tempVM, RestoreLotType.Blank); PopulateBlankTerrain(tempVM); tempVM.Context.Architecture.ClearDirty(); tempVM.Context.Architecture.RegenRoomMap(); @@ -1082,10 +1180,18 @@ public static void RestoreSurroundings(VM vm, byte[][] hollowAdj) subworld.GlobalPosition = new Vector2((1 - y) * (size - 2), (x - 1) * (size - 2)); - vm.Context.Blueprint.SubWorlds.Add(subworld); + // This could conflict with some main thread actions. + GameThread.InUpdate(() => + { + vm.Context.Blueprint.SubWorlds.Add(subworld); + }); } } - vm.Context.World.InitSubWorlds(); + + GameThread.InUpdate(() => + { + vm.Context.World.InitSubWorlds(); + }); } } diff --git a/TSOClient/tso.simantics/Utils/VMTS1Activator.cs b/TSOClient/tso.simantics/Utils/VMTS1Activator.cs index c54dcf58c..2afd09831 100644 --- a/TSOClient/tso.simantics/Utils/VMTS1Activator.cs +++ b/TSOClient/tso.simantics/Utils/VMTS1Activator.cs @@ -64,7 +64,7 @@ public Blueprint LoadFromIff(IffFile iff) var type = simi.GlobalData[35]; var size = Size; //ts1 lots are 64x64... but we convert them into dynamic size. if (VM.UseWorld) this.Blueprint = new Blueprint(size, size); - VM.Entities = new List(); + VM.Entities = []; VM.Scheduler = new Engine.VMScheduler(VM); VM.TS1State.SimulationInfo = simi; VM.Context = new VMContext(VM.Context.World); diff --git a/TSOClient/tso.simantics/Utils/VMTS1ActivatorNew.cs b/TSOClient/tso.simantics/Utils/VMTS1ActivatorNew.cs index 8d3332cd8..5a246d0e6 100644 --- a/TSOClient/tso.simantics/Utils/VMTS1ActivatorNew.cs +++ b/TSOClient/tso.simantics/Utils/VMTS1ActivatorNew.cs @@ -668,6 +668,23 @@ public Blueprint LoadFromIff(IffFile iff) VM.Load(fsov); VM.UpdateFreeObjectID(); + // Spawn controller objects that are missing from the saved lot. + // In vanilla TS1, these are spawned automatically and saved into OBJM. + // If the lot was never opened in vanilla, they won't be in the save, + // so we need to spawn them here. + var controllerObjects = content.WorldObjects.ControllerObjects.Select(x => (uint)x.ID).ToList(); + + foreach (var controller in controllerObjects) + { + // Check if controller already exists in the loaded lot + var exists = VM.Entities.Any(e => e.Object.OBJ.GUID == controller); + if (!exists) + { + // Spawn missing controller at OUT_OF_WORLD + VM.Context.CreateObjectInstance(controller, LotTilePos.OUT_OF_WORLD, Direction.NORTH); + } + } + // Attempt to recover queue names. foreach (var ava in VM.Context.ObjectQueries.Avatars) { diff --git a/TSOClient/tso.simantics/Utils/VMWorldActivator.cs b/TSOClient/tso.simantics/Utils/VMWorldActivator.cs index 5194db902..18a77ccb6 100644 --- a/TSOClient/tso.simantics/Utils/VMWorldActivator.cs +++ b/TSOClient/tso.simantics/Utils/VMWorldActivator.cs @@ -44,7 +44,7 @@ public Blueprint LoadFromXML(XmlHouseData model){ if (size == 0) size = model.Size; model.Size = size; if (VM.UseWorld) this.Blueprint = new Blueprint(size, size); - VM.Entities = new List(); + VM.Entities = []; VM.Scheduler = new Engine.VMScheduler(VM); VM.Context = new VMContext(VM.Context.World); VM.Context.VM = VM; @@ -100,7 +100,7 @@ public Blueprint LoadFromXML(XmlHouseData model){ { foreach (var obj in model.Sounds) { - VM.Context.Ambience.SetAmbience(VM.Context.Ambience.GetAmbienceFromGUID(obj.ID), (obj.On == 1)); + VM.Context.Ambience.SetUserAmbience(VM.Context.Ambience.GetAmbienceFromGUID(obj.ID), (obj.On == 1)); } diff --git a/TSOClient/tso.simantics/VM.cs b/TSOClient/tso.simantics/VM.cs index 806891f9e..121baea97 100644 --- a/TSOClient/tso.simantics/VM.cs +++ b/TSOClient/tso.simantics/VM.cs @@ -37,16 +37,17 @@ namespace FSO.SimAntics public class VM { public bool UseSchedule = true; - private static bool _UseWorld = true; public static bool SignalBreaks = false; + + [ThreadStatic] + private static bool _UseWorld = true; + public static bool UseWorld { get { return _UseWorld; } set { _UseWorld = value; - VMContext.UseWorld = value; - VMEntity.UseWorld = value; } } @@ -65,12 +66,18 @@ public bool BlueprintRestore //we can assume one application won't be running TS1 and TSO at the same time. public bool Aborting = false; + /// + /// Global toggle for free will (autonomy). When disabled, player family Sims will not + /// autonomously choose actions. Visitors and pets still have free will. + /// + public static bool FreeWillEnabled = true; + private const long TickInterval = 33 * TimeSpan.TicksPerMillisecond; - public byte[][] HollowAdj; + public VMHollowAdjEntry[] HollowAdj; public VMContext Context { get; internal set; } - public List Entities = new List(); + public VMObjectList Entities = []; public HashSet SoundEntities = new HashSet(); public short[] GlobalState; public VMAbstractLotState PlatformState; @@ -125,7 +132,7 @@ public string LotName public delegate void VMRefreshHandler(); public delegate void VMBreakpointHandler(VMEntity entity); public delegate void VMEODMessageHandler(VMNetEODMessageCmd msg); - public delegate void VMLotSwitchHandler(uint lotId); + public delegate void VMLotSwitchHandler(uint lotId, LotTransitionInfo transition); public delegate void VMGenericEvtHandler(VMEventType type, object data); public IVMTSOGlobalLink GlobalLink @@ -273,6 +280,11 @@ public void Update() forward.Normalize(); listener.Forward = forward; Context.World.State.SimSpeed = Math.Max(0, SpeedMultiplier); + if (Context.World.Visible && Context.World.FrameCounter != 0) + { + Context.Ambience.SetVolumeWithCameraInfo(Context.World.State.CameraInfo()); + } + Context.Ambience.Tick(this); } if (LastFrameSpeed != SpeedMultiplier) @@ -283,8 +295,16 @@ public void Update() allSounds.AddRange(ent.SoundThreads.Select(x => x.Sound)); } - if (SpeedMultiplier < 1 && SpeedMultiplier > -2 && LastFrameSpeed >= 1) allSounds.ForEach((x) => x.Pause()); - else if (SpeedMultiplier >= 1 && LastFrameSpeed < 1) allSounds.ForEach((x) => x.Resume()); + if (SpeedMultiplier < 1 && SpeedMultiplier > -2 && LastFrameSpeed >= 1) + { + Context.Ambience.Pause(); + allSounds.ForEach((x) => x.Pause()); + } + else if (SpeedMultiplier >= 1 && LastFrameSpeed < 1) + { + Context.Ambience.Resume(); + allSounds.ForEach((x) => x.Resume()); + } LastFrameSpeed = SpeedMultiplier; } @@ -340,7 +360,7 @@ public string GetUserIP(uint uid) public void CloseNet(VMCloseNetReason reason) { - if (reason == VMCloseNetReason.LeaveLot && !Ready) return; + if (reason == VMCloseNetReason.LeaveLot && Driver.RunningCatchup) return; Driver.CloseReason = reason; Driver.Shutdown(); } @@ -479,82 +499,21 @@ public void AddEntity(VMEntity entity) { entity.ObjectID = ObjectId; ObjectsById.Add(entity.ObjectID, entity); - AddToObjList(this.Entities, entity); + this.Entities.AddToObjList(entity); if (!entity.GhostImage) Context.ObjectQueries.NewObject(entity); ObjectId = NextObjID(); } - public static void AddToObjList(List list, VMEntity entity) - { - if (list.Count == 0) { list.Add(entity); return; } - int id = entity.ObjectID; - int max = list.Count; - int min = 0; - while (max>min) - { - int mid = (max+min) / 2; - int nid = list[mid].ObjectID; - if (id < nid) max = mid; - else if (id == nid) return; //do not add dupes - else min = mid+1; - } - list.Insert(min, entity); - // list.Insert((list[min].ObjectID>id)?min:((list[max].ObjectID > id)?max:max+1), entity); - } - - public static void DeleteFromObjList(List list, VMEntity entity) - { - if (list.Count == 0) { return; } - int id = entity.ObjectID; - int max = list.Count; - int min = 0; - while (max > min) - { - int mid = (max + min) / 2; - int nid = list[mid].ObjectID; - if (id < nid) max = mid; - else if (id == nid) - { - list.RemoveAt(mid); //found it - return; - } - else min = mid + 1; - } - //list.RemoveAt(min); - } - - public static int FindNextIndexInObjList(List list, short targId) - { - if (list.Count == 0) return 0; - int count = list.Count; - int max = count; - int min = 0; - while (max > min) - { - int mid = (max + min) / 2; - int nid = list[mid].ObjectID; - if (targId < nid) max = mid; //target object is below us - else if (targId == nid) - { - //found it. find NEXT! - return mid+1; - } - else min = mid + 1; //target object is above us - } - if (min >= count) return count; - return list[min].ObjectID > targId ? min : min+1; - } - /// /// Removes an entity from this Virtual Machine. /// /// The entity to remove. public void RemoveEntity(VMEntity entity) { - if (Entities.Contains(entity)) + if (Entities.FindInObjList(entity) != -1) { Context.ObjectQueries.RemoveObject(entity); - DeleteFromObjList(Entities, entity); + Entities.DeleteFromObjList(entity); ObjectsById.Remove(entity.ObjectID); Scheduler.DescheduleTick(entity); if (entity.ObjectID < ObjectId) ObjectId = entity.ObjectID; //this id is now the smallest free object id. @@ -656,9 +615,9 @@ public void SignalEODMessage(VMNetEODMessageCmd msg) OnEODMessage?.Invoke(msg); } - public void SignalLotSwitch(uint lotId) + public void SignalLotSwitch(uint lotId, LotTransitionInfo transition = null) { - OnRequestLotSwitch?.Invoke(lotId); + OnRequestLotSwitch?.Invoke(lotId, transition); } public void SignalGenericVMEvt(VMEventType type, object data) @@ -672,7 +631,7 @@ public VMSandboxRestoreState Sandbox() ObjectsById = ObjectsById, ObjectQueries = Context.ObjectQueries, RandomSeed = Context.RandomSeed }; Context.ObjectQueries = new VMObjectQueries(Context); - Entities = new List(); + Entities = []; ObjectsById = new Dictionary(); ObjectId = 1; @@ -823,7 +782,7 @@ public void LoadAsync(VMMarshal input) } SoundEntities = new HashSet(); - Entities = new List(); + Entities = []; Scheduler.Reset(); ObjectsById = new Dictionary(); FSOVObjTotal = input.Entities.Length; @@ -932,6 +891,18 @@ public void LoadAsync(VMMarshal input) //just a few final changes to refresh everything, and avoid signalling objects var clock = Context.Clock; + if (VM.UseWorld) + { + if (lastBp != null) + { + Context.Blueprint.Weather.Inherit(lastBp.Weather); + } + else + { + Context.Blueprint.Weather.UpdateLighting(); + } + } + Context.Architecture.SetTimeOfDay(); Context.Architecture.SignalAllDirty(); @@ -958,7 +929,8 @@ public void LoadAsync(VMMarshal input) } }); } - + + Context.Ambience.InitAutoBase(this); Context.UpdateTSOBuildableArea(); Tuning = input.Tuning; UpdateTuning(); @@ -997,7 +969,7 @@ public void HollowLoad(VMHollowMarshal input) Context.Architecture.RegenRoomMap(); Context.RegeneratePortalInfo(); - Entities = new List(); + Entities = []; ObjectsById = new Dictionary(); var includedEnts = new List(); foreach (var ent in input.Entities) @@ -1068,7 +1040,7 @@ public void SuppressBHAVChanges() public class VMSandboxRestoreState { - public List Entities; + public VMObjectList Entities; public Dictionary ObjectsById; public short ObjectId = 1; public VMObjectQueries ObjectQueries; @@ -1081,6 +1053,8 @@ public enum VMEventType TSOTimeout, TS1LotChange, TS1BuildBuyChange, - TSOUpgraded + TSOUpgraded, + TSOUserLeaveBuildBuy, + Resync } } diff --git a/TSOClient/tso.simantics/VMArchitecture.cs b/TSOClient/tso.simantics/VMArchitecture.cs index bfd1e117d..a2de9e4f8 100644 --- a/TSOClient/tso.simantics/VMArchitecture.cs +++ b/TSOClient/tso.simantics/VMArchitecture.cs @@ -1,4 +1,4 @@ -using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework; using System; using System.Collections.Generic; using System.Linq; @@ -202,7 +202,7 @@ public void UpdateBuildableArea(Rectangle area, int floors) //notify the lotview this has changed too, so it can be drawn. BuildableArea = area; BuildableFloors = floors; - if (VM.UseWorld) + if (VM.UseWorld && WorldUI.Terrain != null) { WorldUI.BuildableArea = BuildableArea; WorldUI.Terrain.TerrainDirty = true; @@ -495,7 +495,7 @@ public int RunCommands(List commands, bool transient) var com = commands[i]; var avaEnt = Context.VM.Entities.FirstOrDefault(x => x.PersistID == com.CallerUID); if ((avaEnt == null || avaEnt is VMGameObject) && !transient && !Context.VM.TS1) return 0; //we need an avatar to run a command from net - var avatar = (transient)? null : (VMAvatar)avaEnt; + var avatar = (transient || Context.VM.TS1) ? null : (VMAvatar)avaEnt; lastAvatar = avatar; var styleInd = -1; var walls = Content.Content.Get().WorldWalls; diff --git a/TSOClient/tso.simantics/VMContext.cs b/TSOClient/tso.simantics/VMContext.cs index 758d6d29f..985e3cb4f 100644 --- a/TSOClient/tso.simantics/VMContext.cs +++ b/TSOClient/tso.simantics/VMContext.cs @@ -20,7 +20,7 @@ namespace FSO.SimAntics { public class VMContext { - public static bool UseWorld = true; + public static bool UseWorld => VM.UseWorld; public Blueprint Blueprint; public VMClock Clock { get; internal set; } public VMCheatState Cheats { get; internal set; } @@ -67,7 +67,7 @@ public VMContext(LotView.World world, VMContext oldContext){ if (oldContext == null) { - this.Ambience = new VMAmbientSound(); + this.Ambience = VMAmbientSound.TryTransition(); } else { this.Ambience = oldContext.Ambience; @@ -585,7 +585,7 @@ public void RegeneratePortalInfo() RoomInfo = new VMRoomInfo[Architecture.RoomData.Count()]; for (int i = 0; i < RoomInfo.Length; i++) { - RoomInfo[i].Entities = new List(); + RoomInfo[i].Entities = []; RoomInfo[i].Portals = new List(); RoomInfo[i].WindowPortals = new List(); RoomInfo[i].Room = Architecture.RoomData[i]; @@ -598,7 +598,7 @@ public void RegeneratePortalInfo() { var room = GetObjectRoom(obj); var roomInfo = RoomInfo[room]; - VM.AddToObjList(roomInfo.Entities, obj); + roomInfo.Entities.AddToObjList(obj); //register collision footprint (if present) var footprint = obj.Footprint; @@ -621,10 +621,11 @@ public void RegeneratePortalInfo() } var visited = new HashSet(); - for (ushort i=0; i(); + int remaining = DeferredLightingRefresh.Count; + foreach (var room in DeferredLightingRefresh) { - RefreshLighting(DeferredLightingRefresh.ElementAt(i), i == DeferredLightingRefresh.Count - 1, new HashSet()); + remaining--; + RefreshLighting(room, remaining == 0, visited); } DeferredLightingRefresh.Clear(); } @@ -705,11 +709,11 @@ public void RefreshLighting(ushort room, bool commit, HashSet visited) foreach (var rm in info.Room.SupportRooms) { - info = RoomInfo[rm]; - light.Bounds = Rectangle.Union(light.Bounds, info.Room.Bounds); + var info2 = RoomInfo[rm]; + light.Bounds = Rectangle.Union(light.Bounds, info2.Room.Bounds); RoomInfo[room].Light = light; //adjacent rooms share a light object. - area += info.Room.Area; - foreach (var ent in info.Entities) + area += info2.Room.Area; + foreach (var ent in info2.Entities) { // This roughly attempts to avoid allocations by using and clearing a list... objs.Clear(); @@ -742,7 +746,7 @@ public void RefreshLighting(ushort room, bool commit, HashSet visited) { light.Lights.Add(new LotView.LMap.LightData( new Vector2(subent.Position.x, subent.Position.y), - true, 160, room, info.Room.Floor, subent.LightColor, + true, 160, room, info2.Room.Floor, subent.LightColor, subent.WorldUI as ObjectComponent)); outside += (ushort)subent.GetValue(VMStackObjectVariable.LightingContribution); } @@ -759,7 +763,7 @@ public void RefreshLighting(ushort room, bool commit, HashSet visited) inside += (ushort)subent.GetValue(VMStackObjectVariable.LightingContribution); } avg /= objs.Count; - light.Lights.Add(new LotView.LMap.LightData(avg, false, 160, room, info.Room.Floor, ent.LightColor, ent.WorldUI as ObjectComponent)); + light.Lights.Add(new LotView.LMap.LightData(avg, false, 160, room, info2.Room.Floor, ent.LightColor, ent.WorldUI as ObjectComponent)); } else { @@ -770,7 +774,7 @@ public void RefreshLighting(ushort room, bool commit, HashSet visited) { light.Lights.Add(new LotView.LMap.LightData( new Vector2(subent.Position.x, subent.Position.y), - false, 160, room, info.Room.Floor, subent.LightColor, + false, 160, room, info2.Room.Floor, subent.LightColor, subent.WorldUI as ObjectComponent)); inside += cont; } @@ -791,11 +795,11 @@ public void RefreshLighting(ushort room, bool commit, HashSet visited) if (roomImpact != 0) roomScore += roomImpact; } - foreach (var portal in info.WindowPortals) + foreach (var portal in info2.WindowPortals) { if (RoomInfo[RoomInfo[portal.TargetRoom].Room.LightBaseRoom].Room.IsOutside) continue; var ent = VM.GetObjectById(portal.ObjectID); - var wlight = new LotView.LMap.LightData(new Vector2(ent.Position.x, ent.Position.y), false, 100, room, info.Room.Floor, ent.LightColor); + var wlight = new LotView.LMap.LightData(new Vector2(ent.Position.x, ent.Position.y), false, 100, room, info2.Room.Floor, ent.LightColor); wlight.WindowRoom = portal.TargetRoom; var bRoom = RoomInfo[portal.TargetRoom].Room.LightBaseRoom; affected.Add(bRoom); @@ -943,7 +947,7 @@ public void RegisterObjectPos(VMEntity obj, bool roomChange) if (roomChange) { var roomInfo = RoomInfo[room]; - VM.AddToObjList(roomInfo.Entities, obj); //if it's already in this room, this will do nothing + roomInfo.Entities.AddToObjList(obj); //if it's already in this room, this will do nothing //register collision footprint (if present) var footprint = obj.Footprint; @@ -994,7 +998,7 @@ public void UnregisterObjectPos(VMEntity obj, bool roomChange) if (roomChange) { var room = GetObjectRoom(obj); - VM.DeleteFromObjList(RoomInfo[room].Entities, obj); + RoomInfo[room].Entities.DeleteFromObjList(obj); //unregister collision footprint (if present) obj.Footprint?.Unregister(); @@ -1212,8 +1216,8 @@ public VMPlacementResult GetAvatarPlace(VMEntity target, LotTilePos pos, Directi if (obj.MultitileGroup == target.MultitileGroup) continue; var ghost = (short)((target.GhostImage || obj.GhostImage) ? 1 : 0); - if ((!(target.ExecuteEntryPoint(5, this, true, obj, new short[] { obj.ObjectID, ghost, 0, 0 }) - || obj.ExecuteEntryPoint(5, this, true, target, new short[] { target.ObjectID, ghost, 0, 0 }))) + if ((!(target.ExecuteEntryPoint(5, this, true, obj, new([obj.ObjectID, ghost, 0, 0])) + || obj.ExecuteEntryPoint(5, this, true, target, new([target.ObjectID, ghost, 0, 0])))) ) { var flags = (VMEntityFlags)obj.GetValue(VMStackObjectVariable.Flags); @@ -1265,8 +1269,8 @@ public VMPlacementResult GetObjPlace(VMEntity target, LotTilePos pos, Direction || (target.IgnoreIntersection != null && target.IgnoreIntersection.Objects.Contains(obj))) continue; var ghost = (short)((target.GhostImage || obj.GhostImage) ? 1 : 0); - if ((!(target.ExecuteEntryPoint(5, this, true, obj, new short[] { obj.ObjectID, ghost, 0, 0 }) - || obj.ExecuteEntryPoint(5, this, true, target, new short[] { target.ObjectID, ghost, 0, 0 }))) + if ((!(target.ExecuteEntryPoint(5, this, true, obj, new([obj.ObjectID, ghost, 0, 0])) + || obj.ExecuteEntryPoint(5, this, true, target, new([target.ObjectID, ghost, 0, 0])))) ) { statusObj = obj; @@ -1565,7 +1569,7 @@ public virtual VMContextMarshal Save() { Architecture = Architecture.Save(), Clock = Clock.Save(), - Ambience = new VMAmbientSoundMarshal { ActiveBits = Ambience.ActiveBits }, + Ambience = new VMAmbientSoundMarshal { ActiveBits = (ulong)Ambience.UserBits }, RandomSeed = RandomSeed }; } @@ -1576,7 +1580,7 @@ public virtual void Load(VMContextMarshal input) Architecture = new VMArchitecture(input.Architecture, this, Blueprint); Clock = new VMClock(input.Clock); - for (int i=0; i 0); + Ambience.SetUserBits(input.Ambience.ActiveBits); if (VM.UseWorld) { diff --git a/TSOClient/tso.simantics/app.config b/TSOClient/tso.simantics/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.simantics/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.simantics/packages.config b/TSOClient/tso.simantics/packages.config deleted file mode 100644 index b88c01dae..000000000 --- a/TSOClient/tso.simantics/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.sound/AmbiencePlayer.cs b/TSOClient/tso.sound/AmbiencePlayer.cs index 42d81b931..caa71297d 100644 --- a/TSOClient/tso.sound/AmbiencePlayer.cs +++ b/TSOClient/tso.sound/AmbiencePlayer.cs @@ -1,7 +1,7 @@ -using FSO.Files.XA; -using Microsoft.Xna.Framework.Audio; -using System.IO; +using FSO.Content; +using FSO.Files.XA; using FSO.HIT.Model; +using Microsoft.Xna.Framework.Audio; namespace FSO.HIT { @@ -12,18 +12,24 @@ public class AmbiencePlayer private SoundEffect sfx; private SoundEffectInstance inst; - public AmbiencePlayer(Ambience amb) + private float PositionalVolume; + public float Volume { get; private set; } + private float TargetVolume; + private float VolumeChangeSpeed; + private bool DisposeLoop; + + public AmbiencePlayer(Ambience amb, float volume = 1f) { + Volume = volume; + PositionalVolume = volume; + if (amb.Loop) { - byte[] data = new XAFile(FSO.Content.Content.Get().GetPath(amb.Path)).DecompressedData; - var stream = new MemoryStream(data); - sfx = SoundEffect.FromStream(stream); - stream.Close(); + sfx = GetLoopSfx(amb.Path); inst = sfx.CreateInstance(); inst.IsLooped = true; - inst.Volume = HITVM.Get().GetMasterVolume(HITVolumeGroup.AMBIENCE); + inst.Volume = volume * HITVM.Get().GetMasterVolume(HITVolumeGroup.AMBIENCE); inst.Play(); HITVM.Get().AmbLoops.Add(inst); @@ -31,12 +37,137 @@ public AmbiencePlayer(Ambience amb) } else { + var content = FSO.Content.Content.Get(); + fsc = HITVM.Get().PlayFSC(FSO.Content.Content.Get().GetPath(amb.Path)); - fsc.SetVolume(0.33f); //may need tweaking + fsc.SetVolume(volume); //may need tweaking fscMode = true; } } + private SoundEffect GetLoopSfx(string path) + { + var content = FSO.Content.Content.Get(); + + if (content.TS1) + { + DisposeLoop = false; + return content.Audio.GetSFX(new Files.HIT.Patch() { Filename = Path.GetFileName(path) }); + } + else + { + DisposeLoop = true; + var data = new XAFile(FSO.Content.Content.Get().GetPath(path)).DecompressedData; + var stream = new MemoryStream(data); + var sfx = SoundEffect.FromStream(stream); + stream.Close(); + + return sfx; + } + } + + private void UpdateVolume() + { + if (fscMode) + { + fsc.SetVolume(Volume * PositionalVolume); + } + else + { + inst.Volume = Volume * PositionalVolume * HITVM.Get().GetMasterVolume(HITVolumeGroup.AMBIENCE); + } + } + + public void SetPositionalVolume(float volume) + { + PositionalVolume = volume; + UpdateVolume(); + } + + public void SetVolume(float volume) + { + Volume = volume; + + UpdateVolume(); + + TargetVolume = volume; + VolumeChangeSpeed = 0; + } + + public void SetVolume(float volume, float transitionDuration) + { + if (transitionDuration == 0) + { + SetVolume(volume); + return; + } + + TargetVolume = volume; + VolumeChangeSpeed = (volume - Volume) / transitionDuration; + } + + public bool TickVolume(float delta) + { + if (Volume == TargetVolume) + { + return true; + } + + var below = Volume < TargetVolume; + + Volume += VolumeChangeSpeed * delta; + + var newBelow = Volume < TargetVolume; + + if (below != newBelow) + { + Volume = TargetVolume; + VolumeChangeSpeed = 0; + + return true; + } + + return false; + } + + + public bool HasTransition() + { + return VolumeChangeSpeed != 0; + } + + public void Pause() + { + if (fscMode) + { + fsc.Pause(); + } + else + { + inst.Pause(); + } + } + + public void Resume() + { + if (fscMode) + { + fsc.Resume(); + } + else + { + inst.Resume(); + } + } + + public void SetLoopingNote(float note) + { + if (fscMode) + { + fsc.SetLoopingNote(note); + } + } + public void Kill() { if (fscMode) HITVM.Get().StopFSC(fsc); @@ -45,7 +176,11 @@ public void Kill() inst.Stop(); inst.Dispose(); HITVM.Get().AmbLoops.Remove(inst); - sfx.Dispose(); + + if (DisposeLoop) + { + sfx.Dispose(); + } } } } diff --git a/TSOClient/tso.sound/FSCPlayer.cs b/TSOClient/tso.sound/FSCPlayer.cs index 235447866..ca0006792 100644 --- a/TSOClient/tso.sound/FSCPlayer.cs +++ b/TSOClient/tso.sound/FSCPlayer.cs @@ -1,14 +1,40 @@ -using System; -using System.Collections.Generic; -using FSO.Files.HIT; +using FSO.Files.HIT; using FSO.Files.XA; using Microsoft.Xna.Framework.Audio; -using System.IO; namespace FSO.HIT { - public class FSCPlayer + public class FSCPlayer : IDisposable { + private struct FSCNoteInstance : IDisposable + { + public SoundState State => Instance.State; + public readonly SoundEffectInstance Instance; + public readonly float Volume; + + public FSCNoteInstance(SoundEffectInstance instance, float volume) + { + Instance = instance; + Volume = volume; + } + + public void Pause() + { + Instance.Pause(); + } + + public void Resume() + { + Instance.Resume(); + } + + public void Dispose() + { + Instance.Stop(); + Instance.Dispose(); + } + } + /// /// A Class to play FSC sequences. Bundled in with the HIT engine because it wouldn't really go anywhere else. :I /// @@ -21,7 +47,10 @@ public class FSCPlayer private string BaseDir; private float BeatLength; private float Volume = 1; - private List SoundEffects; + private List SoundEffects; + private bool Paused; + private int? LoopingNote; + private bool DisposeCache; private Dictionary SoundCache; @@ -30,7 +59,7 @@ public FSCPlayer(FSC fsc, string basedir) this.fsc = fsc; this.BaseDir = basedir; SoundCache = new Dictionary(); - SoundEffects = new List(); + SoundEffects = new List(); BeatLength = 60.0f / fsc.Tempo; RestartFSC(); @@ -44,14 +73,49 @@ public void SetManualTempo(int tempo) public void SetVolume(float volume) { Volume = volume; + RecalculateVolume(); } public void RecalculateVolume() { + foreach (var inst in SoundEffects) + { + inst.Instance.Volume = GetFinalVolume(inst.Volume); + } + } + + public void Pause() + { + Paused = true; + foreach (var inst in SoundEffects) + { + inst.Pause(); + } } - public void Tick(float time) { + public void Resume() + { + Paused = false; + + foreach (var inst in SoundEffects) + { + inst.Resume(); + } + } + + public void SetLoopingNote(float note) + { + LoopingNote = Math.Clamp((int)Math.Floor(note * fsc.NoteColumns.Length), 0, fsc.NoteColumns.Length); + } + + public void Tick(float time) + { + if (Paused) + { + return; + } + for (int i = 0; i < SoundEffects.Count; i++) //dispose and remove sound effect instances that are finished { if (SoundEffects[i].State != SoundState.Playing) @@ -71,66 +135,125 @@ public void Tick(float time) { private SoundEffect LoadSound(string filename) { - if (SoundCache.ContainsKey(filename)) return SoundCache[filename]; + if (SoundCache.TryGetValue(filename, out var cached)) return cached; try { - byte[] data = new XAFile(BaseDir + filename).DecompressedData; - var stream = new MemoryStream(data); - var sfx = SoundEffect.FromStream(stream); - stream.Close(); + var content = Content.Content.Get(); + SoundEffect sfx; + if (content.TS1) + { + sfx = content.Audio.GetSFX(new Patch() { Filename = Path.GetFileName(filename) }); + } + else + { + DisposeCache = true; + byte[] data = new XAFile(BaseDir + filename).DecompressedData; + var stream = new MemoryStream(data); + sfx = SoundEffect.FromStream(stream); + stream.Close(); + } + SoundCache.Add(filename, sfx); return sfx; - } catch (Exception) + } + catch (Exception) { + SoundCache[filename] = null; return null; } } private void RestartFSC() { - if (fsc.RandomJumpPoints.Count == 0) CurrentPosition = 0; - else - { - CurrentPosition = fsc.RandomJumpPoints[new Random().Next(fsc.RandomJumpPoints.Count)]; - } + CurrentPosition = 0; + LoopCount = -1; + } + + private float GetFinalVolume(float volume) + { + return Math.Min(1, volume * Volume * HITVM.Get().GetMasterVolume(Model.HITVolumeGroup.AMBIENCE)); } private void NextNote() { if (LoopCount == -1) { - var note = fsc.Notes[CurrentPosition++]; - if (note.Rand || CurrentPosition >= fsc.Notes.Count) + if (LoopingNote != null) { - RestartFSC(); //current random segment ended. jump to another. - note = fsc.Notes[CurrentPosition]; + CurrentPosition = LoopingNote.Value; } - if (note.Filename != "NONE") + + var noteColumn = fsc.NoteColumns[CurrentPosition++]; + + if (CurrentPosition >= fsc.NoteColumns.Length) { - bool play; - if (note.Prob > 0) play = (new Random().Next(16) < note.Prob); - else play = true; + // Loops back to the start. + CurrentPosition = 0; + } + + LoopCount = (short)(Math.Max(-1, noteColumn.Max(x => x.Loop) - 2)); + + // Evaluate all of the notes, and see which we can play. - if (play) + int y = 0; + foreach (var note in noteColumn) + { + if (note.Filename != null && note.Filename != "NONE") { - float volume = (note.Volume / 1024.0f) * (fsc.MasterVolume / 1024.0f) * Volume * HITVM.Get().GetMasterVolume(Model.HITVolumeGroup.AMBIENCE); - var sound = LoadSound(note.Filename); + bool play; + if (note.Prob > 0) play = (Random.Shared.Next(100) < note.Prob); + else play = true; + + bool exceedsMax = SoundEffects.Count >= fsc.Max; - if (sound != null) + if (play && !exceedsMax) { - var instance = sound.CreateInstance(); - instance.Volume = volume; - instance.Pan = (note.LRPan / 512.0f) - 1; - instance.Play(); + float noteVolume = (note.Volume / 1024.0f); + if (note.RandomVolume) + { + // Maybe this should allow for volumes closer to 0, but that didn't feel right. + noteVolume *= Random.Shared.NextSingle() * 0.66f + 0.33f; + } + float volume = noteVolume * (fsc.MasterVolume / 1024.0f); + var sound = LoadSound(note.Filename); + + if (sound != null) + { + var instance = sound.CreateInstance(); + instance.Volume = GetFinalVolume(volume); + + float notePan = note.RandomPan ? (Random.Shared.Next(1000) / 500f - 1) : (note.LRPan / 512.0f - 1); + float pitchRange = note.pitchH - note.pitchL; + float pitch = (pitchRange != 0) ? Random.Shared.NextSingle() * pitchRange + note.pitchL : 0f; + instance.Pitch = pitch / 12f; + instance.Pan = notePan; + instance.Play(); - SoundEffects.Add(instance); + SoundEffects.Add(new FSCNoteInstance(instance, volume)); + } } } - LoopCount = (short)(note.Loop - 1); - } + y++; + } } else LoopCount--; } + + public void Dispose() + { + foreach (var sound in SoundEffects) + { + sound.Dispose(); + } + + if (DisposeCache) + { + foreach (var sound in SoundCache.Values) + { + sound?.Dispose(); + } + } + } } } diff --git a/TSOClient/tso.sound/FSO.HIT.csproj b/TSOClient/tso.sound/FSO.HIT.csproj index 14bd2ee59..72ccabcaa 100644 --- a/TSOClient/tso.sound/FSO.HIT.csproj +++ b/TSOClient/tso.sound/FSO.HIT.csproj @@ -1,169 +1,31 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {072781D8-51EC-4143-9CAE-DAF50177D3AD} Library - Properties + net9.0 + enable + disable FSO.HIT FSO.HIT - v4.5 + True 512 - - - - - 3.5 - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - AllRules.ruleset - true + + + True - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - AllRules.ruleset + + + True - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - AllRules.ruleset - - - bin\Release\ - TRACE - true - pdbonly - AnyCPU - prompt - AllRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - AllRules.ruleset - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - AllRules.ruleset - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - - - - - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - - - - - - - - - - - - - + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + - - + + - - - \ No newline at end of file + + diff --git a/TSOClient/tso.sound/HITThread.cs b/TSOClient/tso.sound/HITThread.cs index 7f64f70cf..acaea6e6a 100644 --- a/TSOClient/tso.sound/HITThread.cs +++ b/TSOClient/tso.sound/HITThread.cs @@ -408,6 +408,11 @@ public void Unduck() private void LocalVarSet(int location, int value) { + if (LocalVar == null) + { + LocalVar = new int[54]; + } + switch (location) { case 0x12: //patch, switch active track diff --git a/TSOClient/tso.sound/HITVM.cs b/TSOClient/tso.sound/HITVM.cs index 7131a7f5f..105da0bcc 100644 --- a/TSOClient/tso.sound/HITVM.cs +++ b/TSOClient/tso.sound/HITVM.cs @@ -135,21 +135,25 @@ public void Tick() } } + var timeDiff = 1f / FSOEnvironment.RefreshRate; for (int i = 0; i < FSCPlayers.Count; i++) { - FSCPlayers[i].Tick(1/60f); + FSCPlayers[i].Tick(timeDiff); } } public void StopFSC(FSCPlayer input) { + input.Dispose(); FSCPlayers.Remove(input); } public FSCPlayer PlayFSC(string path) { - var dir = Path.GetDirectoryName(path)+"/"; - FSC fsc = new FSC(path); + var content = Content.Content.Get(); + + FSC fsc = content.Audio.GetFSC(path); + string dir = Path.GetDirectoryName(path) + "/"; var player = new FSCPlayer(fsc, dir); FSCPlayers.Add(player); diff --git a/TSOClient/tso.sound/app.config b/TSOClient/tso.sound/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.sound/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.sound/packages.config b/TSOClient/tso.sound/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/tso.sound/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/tso.vitaboy.engine/Avatar.cs b/TSOClient/tso.vitaboy.engine/Avatar.cs index 92757cfeb..81a09c6b4 100644 --- a/TSOClient/tso.vitaboy.engine/Avatar.cs +++ b/TSOClient/tso.vitaboy.engine/Avatar.cs @@ -432,6 +432,7 @@ public void DrawHeadObject(GraphicsDevice device, Effect effect) effect.CurrentTechnique = oldTech; } + // TODO: memory leak private static VertexBuffer ShadBuf; private static IndexBuffer ShadIBuf; diff --git a/TSOClient/tso.vitaboy.engine/FSO.Vitaboy.Engine.csproj b/TSOClient/tso.vitaboy.engine/FSO.Vitaboy.Engine.csproj index c78ba407d..c1b182a72 100644 --- a/TSOClient/tso.vitaboy.engine/FSO.Vitaboy.Engine.csproj +++ b/TSOClient/tso.vitaboy.engine/FSO.Vitaboy.Engine.csproj @@ -1,167 +1,32 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} + net9.0 + enable + disable Library - Properties FSO.Vitaboy FSO.Vitaboy.Engine - v4.5 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - + True - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - true + + + True - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + True - - true - bin\Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - - - - - - - - - - - - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {18583453-a970-4ac5-83b1-2d6bfdf94c24} - FSO.Files - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - - + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + - - + + + - - - \ No newline at end of file + + diff --git a/TSOClient/tso.vitaboy.engine/Properties/AssemblyInfo.cs b/TSOClient/tso.vitaboy.engine/Properties/AssemblyInfo.cs deleted file mode 100644 index 5228f60ce..000000000 --- a/TSOClient/tso.vitaboy.engine/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TSO.Vitaboy.engine")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TSO.Vitaboy.engine")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("73d26a51-a1bf-4b26-8036-a3dfdcaba8f1")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.vitaboy.engine/app.config b/TSOClient/tso.vitaboy.engine/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.vitaboy.engine/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.vitaboy.engine/packages.config b/TSOClient/tso.vitaboy.engine/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/tso.vitaboy.engine/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/tso.vitaboy.model/FSO.Vitaboy.csproj b/TSOClient/tso.vitaboy.model/FSO.Vitaboy.csproj index f00e3d9cc..480730920 100644 --- a/TSOClient/tso.vitaboy.model/FSO.Vitaboy.csproj +++ b/TSOClient/tso.vitaboy.model/FSO.Vitaboy.csproj @@ -1,179 +1,31 @@ - - + + - Debug - AnyCPU - 9.0.30729 - 2.0 - {9D9558A9-755E-43F9-8BB6-B26F365F5042} + net9.0 + enable + disable Library - Properties FSO.Vitaboy FSO.Vitaboy - v4.5 + True 512 - - - - - 3.5 - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - true - full - x86 - prompt - MinimumRecommendedRules.ruleset - true + + + True - - bin\x86\Release\ - TRACE - true - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset + + + True - - true - bin\Debug\ - DEBUG;TRACE - true - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE - true - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE - true - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - + - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - + + - - + + - - - \ No newline at end of file + + diff --git a/TSOClient/tso.vitaboy.model/Properties/AssemblyInfo.cs b/TSOClient/tso.vitaboy.model/Properties/AssemblyInfo.cs deleted file mode 100644 index 3efe65185..000000000 --- a/TSOClient/tso.vitaboy.model/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TSO.Vitaboy")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TSO.Vitaboy")] -[assembly: AssemblyCopyright("Copyright © 2013")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("3202a25e-ea35-496f-8c10-24c85805ff6e")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.vitaboy.model/app.config b/TSOClient/tso.vitaboy.model/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.vitaboy.model/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.vitaboy.model/packages.config b/TSOClient/tso.vitaboy.model/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/tso.vitaboy.model/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/TSOClient/tso.world/Components/3DFloorGeometry.cs b/TSOClient/tso.world/Components/3DFloorGeometry.cs index fd7ef1b5c..fbb1e4e95 100644 --- a/TSOClient/tso.world/Components/3DFloorGeometry.cs +++ b/TSOClient/tso.world/Components/3DFloorGeometry.cs @@ -1,12 +1,11 @@ using FSO.Common; using FSO.Common.Utils; using FSO.LotView.Components.Geometry; +using FSO.LotView.Components.Model; using FSO.LotView.Effects; using FSO.LotView.Model; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; namespace FSO.LotView.Components { @@ -683,7 +682,7 @@ public void Dispose() public class FloorTileGroup : IDisposable { - public Dictionary> GeomForOffset = new Dictionary>(); + public Dictionary GeomForOffset = new Dictionary(); public IndexBuffer GPUData; public virtual void PrepareGPU(GraphicsDevice gd) @@ -697,13 +696,14 @@ public virtual void PrepareGPU(GraphicsDevice gd) private int[] BuildIndexData() { var result = new int[GeomForOffset.Count * 6]; + var resultSpan = result.AsSpan(); int i = 0; foreach (var geom in GeomForOffset.Values) { - foreach (var elem in geom) - { - result[i++] = elem; - } + var geomCopy = geom; + int newI = i + geom.Length; + geomCopy.GetSpan().CopyTo(resultSpan[i..newI]); + i = newI; } return result; } @@ -712,7 +712,7 @@ public void AddIndex(ushort offset) { // var o2 = offset * 4; - var result = new List { o2, o2 + 1, o2 + 2, o2 + 2, o2 + 3, o2 }; + var result = new FloorTileIndices(o2, o2 + 1, o2 + 2, o2 + 2, o2 + 3, o2); GeomForOffset[offset] = result; } @@ -720,15 +720,15 @@ public void AddDiagIndex(ushort offset, bool side, bool vertical) { // var o2 = offset * 4; - List result; + FloorTileIndices result; if (vertical) { - if (side) result = new List { o2, o2 + 1, o2 + 2 }; - else result = new List { o2 + 2, o2 + 3, o2 }; + if (side) result = new FloorTileIndices(o2, o2 + 1, o2 + 2); + else result = new FloorTileIndices(o2 + 2, o2 + 3, o2); } else { - if (side) result = new List { o2+1, o2 + 2, o2 + 3 }; - else result = new List { o2, o2 + 1, o2 + 3 }; + if (side) result = new FloorTileIndices(o2 +1, o2 + 2, o2 + 3); + else result = new FloorTileIndices(o2, o2 + 1, o2 + 3); } GeomForOffset[(ushort)(offset + (side?32768:0))] = result; } diff --git a/TSOClient/tso.world/Components/AbstractSkyDome.cs b/TSOClient/tso.world/Components/AbstractSkyDome.cs index 7d86a4d1d..48d112871 100644 --- a/TSOClient/tso.world/Components/AbstractSkyDome.cs +++ b/TSOClient/tso.world/Components/AbstractSkyDome.cs @@ -6,9 +6,6 @@ using FSO.LotView.Model; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.IO; namespace FSO.LotView.Components { @@ -213,7 +210,7 @@ public void Draw(GraphicsDevice gd, Color outsideColor, Matrix view, Matrix proj if (LastSkyPos != time) BuildSkyDome(gd, time); - var color = ocolor - new Vector4(0.35f) * 1.5f + new Vector4(0.35f); + var color = (ocolor - new Vector4(0.35f)) * 1.5f + new Vector4(0.35f); color.W = 1; var wint = Math.Min(1f, weather.WeatherIntensity); @@ -229,6 +226,8 @@ public void Draw(GraphicsDevice gd, Color outsideColor, Matrix view, Matrix proj //var view = view;state.Camera.View; view.M41 = 0; view.M42 = 0; view.M43 = 0; + var scaleVec = Vector3.TransformNormal(new Vector3(1, 0, 0), view); + view = Matrix.CreateScale(1 / scaleVec.Length()) * view; effect.View = view; effect.Projection = projection;// (state.Camera as WorldCamera3D)?.BaseProjection() ?? state.Camera.Projection; effect.World = Matrix.CreateScale(5f * scale); @@ -256,7 +255,7 @@ public void Draw(GraphicsDevice gd, Color outsideColor, Matrix view, Matrix proj var dist = 0.5f + pos.Y * 2; dist *= dist; dist += 0.5f; - if (night) dist = 35; + if (night) dist = 65; var sunMat = Matrix.CreateTranslation(0, 0, dist) * Matrix.CreateBillboard(pos, new Vector3(0, 0.4f, 0), Vector3.Up, null); var geom = WorldContent.GetTextureVerts(gd); @@ -264,7 +263,25 @@ public void Draw(GraphicsDevice gd, Color outsideColor, Matrix view, Matrix proj effect.VertexColorEnabled = false; effect.TextureEnabled = true; effect.Texture = (night) ? TextureGenerator.GetMoon(gd) : TextureGenerator.GetSun(gd); - effect.DiffuseColor = FinaleUtils.BiasSunIntensity(new Vector3(color.X, color.Y, color.Z) * ((night) ? 2f : 0.6f), time); + gd.SamplerStates[0] = SamplerState.LinearClamp; + + if (night) + { + var tint = new Vector3(color.X, color.Y, color.Z) * 0.6f; + var lightIntensity = new Vector3(color.X, color.Y, color.Z).Length() + 0.4f; + effect.DiffuseColor = FinaleUtils.BiasSunIntensity(new Vector3(lightIntensity) + tint, time); + } + else + { + float colorBias = Math.Abs(color.Z - color.X); + + // when the colour is uniformly white, penalize the brightness a bit + + color *= 0.6f + Math.Min(1f, colorBias / 0.8f) * 0.4f; + + effect.DiffuseColor = FinaleUtils.BiasSunIntensity(new Vector3(color.X, color.Y, color.Z) * 0.6f, time); + } + gd.BlendState = (night) ? BlendState.NonPremultiplied : BlendState.Additive; foreach (var pass in effect.CurrentTechnique.Passes) diff --git a/TSOClient/tso.world/Components/AvatarComponent.cs b/TSOClient/tso.world/Components/AvatarComponent.cs index cb6646d91..d942ae60a 100644 --- a/TSOClient/tso.world/Components/AvatarComponent.cs +++ b/TSOClient/tso.world/Components/AvatarComponent.cs @@ -106,7 +106,7 @@ public override Vector3 Position if (blueprint != null) { AltitudeNormal = blueprint.InterpNormal(_Position); - AltitudeOff = new Vector3(0, 0, blueprint.InterpAltitude(_Position)); + AltitudeOff = new Vector3(0, 0, blueprint.InterpAltitudeWithSubworlds(_Position)); } OnPositionChanged(); _WorldDirty = true; @@ -172,7 +172,7 @@ private List CloseLightPositions(Vector3 Position) public override Vector3 GetHeadlinePos() { var headpos = Avatar.Skeleton.GetBone("HEAD").AbsolutePosition / 3.0f; - return Vector3.Transform(new Vector3(headpos.X, headpos.Z, headpos.Y), Matrix.CreateRotationZ((float)(RadianDirection + Math.PI))); + return Vector3.Transform(new Vector3(headpos.X, headpos.Z, headpos.Y), GetRotationMatrix()); } public override Vector3 GetLookTarget() @@ -212,13 +212,25 @@ public void DrawAvatarMesh(GraphicsDevice device, WorldState state, Matrix world } } + private Matrix GetRotationMatrix() + { + var result = Matrix.CreateRotationZ((float)(RadianDirection + Math.PI)); + + if (UseNormal) + { + return result * Matrix.Invert(NormalToMatrix()); + } + + return result; + } + public override void Draw(GraphicsDevice device, WorldState world) { var pos = Position; Avatar.Position = WorldSpace.GetWorldFromTile(pos); if (Avatar.Skeleton == null) return; var headpos = Avatar.Skeleton.GetBone("HEAD").AbsolutePosition / 3.0f; - var tHead1 = Vector3.Transform(new Vector3(headpos.X, headpos.Z, headpos.Y), Matrix.CreateRotationZ((float)(RadianDirection + Math.PI))); + var tHead1 = Vector3.Transform(new Vector3(headpos.X, headpos.Z, headpos.Y), GetRotationMatrix()); var transhead = tHead1 + pos - new Vector3(0.5f, 0.5f, 0f); if (!Visible) return; diff --git a/TSOClient/tso.world/Components/EntityComponent.cs b/TSOClient/tso.world/Components/EntityComponent.cs index 6e808e0a5..35f9dc997 100644 --- a/TSOClient/tso.world/Components/EntityComponent.cs +++ b/TSOClient/tso.world/Components/EntityComponent.cs @@ -116,8 +116,9 @@ public override Vector3 Position } set { + _UnmoddedPosition = value; _Position = value; - if (blueprint != null) _Position.Z += blueprint.InterpAltitude(new Vector3(0.5f, 0.5f, 0) + _Position - MTOffset / 16) + MTOffset.Z / 16f; + if (blueprint != null) _Position.Z += blueprint.InterpAltitudeWithSubworlds(new Vector3(0.5f, 0.5f, 0) + _Position - MTOffset / 16) + MTOffset.Z / 16f; OnPositionChanged(); _WorldDirty = true; @@ -173,14 +174,16 @@ public void PrepareSlotInterpolation() } } + private Vector3 _UnmoddedPosition; public Vector3 UnmoddedPosition { get { - return _Position; + return _UnmoddedPosition; } set { + _UnmoddedPosition = value; _Position = value; OnPositionChanged(); _WorldDirty = true; @@ -251,7 +254,7 @@ private Matrix NormalToMatrix(Vector3 v1, Vector3 v2, Vector3 v3) new Vector4(0, 0, 0, 1)); } - private Matrix NormalToMatrix() + protected Matrix NormalToMatrix() { return NormalToMatrix(VisualNormal, Vector3.Backward, Vector3.Right); } diff --git a/TSOClient/tso.world/Components/Geometry/Modelled3DFloorTile.cs b/TSOClient/tso.world/Components/Geometry/Modelled3DFloorTile.cs index 4c509cfe5..748787ec3 100644 --- a/TSOClient/tso.world/Components/Geometry/Modelled3DFloorTile.cs +++ b/TSOClient/tso.world/Components/Geometry/Modelled3DFloorTile.cs @@ -1,4 +1,5 @@ using FSO.Common.Utils; +using FSO.Files; using FSO.Files.RC; using FSO.LotView.Utils; using Microsoft.Xna.Framework; @@ -57,7 +58,7 @@ public Modelled3DFloorTile(OBJ model, string textureName) public Texture2D GetTexture(GraphicsDevice gd) { if (Texture != null) return Texture; - Texture = TextureUtils.MipTextureFromFile(gd, $"Content/3D/floor/{TextureName}"); + Texture = ImageLoader.MipTextureFromFile(gd, $"Content/3D/floor/{TextureName}"); return Texture; } } diff --git a/TSOClient/tso.world/Components/Model/FloorTileIndices.cs b/TSOClient/tso.world/Components/Model/FloorTileIndices.cs new file mode 100644 index 000000000..b82d25988 --- /dev/null +++ b/TSOClient/tso.world/Components/Model/FloorTileIndices.cs @@ -0,0 +1,46 @@ +namespace FSO.LotView.Components.Model +{ + public readonly struct FloorTileIndices + { + [System.Runtime.CompilerServices.InlineArray(6)] + public struct Buffer6 + { + private T _element0; + } + + public readonly int Length; + public readonly Buffer6 Data; + + public FloorTileIndices(int i1, int i2, int i3) + { + Length = 3; + Data[0] = i1; + Data[1] = i2; + Data[2] = i3; + } + + public FloorTileIndices(int i1, int i2, int i3, int i4, int i5, int i6) + { + Length = 6; + Data[0] = i1; + Data[1] = i2; + Data[2] = i3; + Data[3] = i4; + Data[4] = i5; + Data[5] = i6; + } + } + + public static class FloorTileIndicesExtensions + { + public static ReadOnlySpan GetSpan(this ref FloorTileIndices indices) + { + if (indices.Length < 6) + { + return indices.Data[..indices.Length]; + } + + return indices.Data; + } + } +} diff --git a/TSOClient/tso.world/Components/ObjectComponent.cs b/TSOClient/tso.world/Components/ObjectComponent.cs index a718a6142..fe306fda2 100644 --- a/TSOClient/tso.world/Components/ObjectComponent.cs +++ b/TSOClient/tso.world/Components/ObjectComponent.cs @@ -125,8 +125,13 @@ public BoundingBox GetBounds() if (bounds == null) { - _BoundsDirty = false; - return new BoundingBox(); //don't cache + if (!dgrp.CanHaveBounds) + { + // No need to keep checking this. + _BoundsDirty = false; + } + + return new BoundingBox(); //don't cache, but also check again next time. } else { @@ -174,6 +179,23 @@ public override short ObjectID { } } + private Vector3 RotateCenterRelative(Vector3 centerRelative) + { + switch (_Direction) + { + case Direction.NORTH: + return centerRelative; + case Direction.EAST: + return new Vector3(-centerRelative.Y, centerRelative.X, centerRelative.Z); + case Direction.SOUTH: + return new Vector3(-centerRelative.X, -centerRelative.Y, centerRelative.Z); + case Direction.WEST: + return new Vector3(centerRelative.Y, -centerRelative.X, centerRelative.Z); + default: + return centerRelative; + } + } + public override Vector3 GetSLOTPosition(int slot, bool avatar) { var item = (ContainerSlots != null && ContainerSlots.Count > slot) ? ContainerSlots[slot] : null; @@ -181,7 +203,7 @@ public override Vector3 GetSLOTPosition(int slot, bool avatar) { var off = item.Offset; var centerRelative = new Vector3(off.X * (1 / 16.0f), off.Y * (1 / 16.0f), ((item.Height != 5 && item.Height != 0) ? SLOT.HeightOffsets[item.Height - 1] : off.Z) * (1 / 5.0f)); - centerRelative = Vector3.Transform(centerRelative, Matrix.CreateRotationZ(RadianDirection)); + centerRelative = RotateCenterRelative(centerRelative); if (avatar) centerRelative.Z = 0; return this.Position + centerRelative; } else return this.Position; diff --git a/TSOClient/tso.world/Components/ParticleComponent.cs b/TSOClient/tso.world/Components/ParticleComponent.cs index 87990c1d9..ea2e78b3d 100644 --- a/TSOClient/tso.world/Components/ParticleComponent.cs +++ b/TSOClient/tso.world/Components/ParticleComponent.cs @@ -247,7 +247,7 @@ public override void Draw(GraphicsDevice device, WorldState world) rot.Up = new Vector3(0, 1, 0); var invxz = (cam3d)?Matrix.Invert(rot): Matrix.Identity; effect.Parameters["InvXZRotation"].SetValue(invxz * Matrix.CreateScale(0.5f)); - effect.Parameters["SubColor"].SetValue(Bp.OutsideColor.ToVector4() * 0.6f * opacity); + effect.Parameters["SubColor"].SetValue(Bp.OutsideColor.ToVector4() * 0.6f * opacity * GetFadeAlpha()); } else { effect.Parameters["SubColor"].SetValue(Vector4.Zero); @@ -259,6 +259,14 @@ public override void Draw(GraphicsDevice device, WorldState world) InternalDraw(device, effect, scale2d, true, cam3d); } + private float GetFadeAlpha() + { + var fade = FadeProgress ?? 0f; + if (fade < 0) fade = fade * fade; //give a bias to weather fading in + + return 1 - Math.Abs(fade); + } + private Vector3 LastPosition; public void GenericDraw(GraphicsDevice device, Common.Rendering.Framework.Camera.ICamera camera, Color lightColor, bool useDepth) @@ -294,7 +302,7 @@ public void GenericDraw(GraphicsDevice device, Common.Rendering.Framework.Camera rot.Up = new Vector3(0, 1, 0); var invxz = Matrix.Invert(rot); effect.Parameters["InvXZRotation"].SetValue(invxz * Matrix.CreateScale(0.5f)); - effect.Parameters["SubColor"].SetValue(lightColor.ToVector4()*0.5f);// * new Vector4(0.25f, 0.25f, 0.5f, 0.25f)); + effect.Parameters["SubColor"].SetValue(lightColor.ToVector4() * 0.5f * GetFadeAlpha());// * new Vector4(0.25f, 0.25f, 0.5f, 0.25f)); } else { @@ -313,11 +321,10 @@ private void InternalDraw(GraphicsDevice device, Effect effect, int scale, bool { effect.Parameters["BaseTex"].SetValue(Tex); effect.Parameters["IndoorsTex"].SetValue(Indoors); + effect.Parameters["Color"].SetValue(Tint.ToVector4() * GetFadeAlpha()); - var fade = FadeProgress ?? 0f; - if (fade < 0) fade = fade * fade; //give a bias to weather fading in - effect.Parameters["Color"].SetValue(Tint.ToVector4() * (1 - Math.Abs(fade))); - effect.Parameters["TimeRate"].SetValue(Math.Max(1,TimeRate)*0.001f/ FSOEnvironment.RefreshRate); + int exposureRate = 60; //FSOEnvironment.RefreshRate + effect.Parameters["TimeRate"].SetValue(Math.Max(1,TimeRate)*0.001f/ exposureRate); //Parameters: //miny, yrange, fall speed, fall speed variation diff --git a/TSOClient/tso.world/Components/RoofComponent.cs b/TSOClient/tso.world/Components/RoofComponent.cs index a5ed2aa91..f43613878 100644 --- a/TSOClient/tso.world/Components/RoofComponent.cs +++ b/TSOClient/tso.world/Components/RoofComponent.cs @@ -1,14 +1,16 @@ -using FSO.LotView.Model; +using FSO.Common.Utils; +using FSO.Files; +using FSO.LotView.Effects; +using FSO.LotView.LMap; +using FSO.LotView.Model; +using FSO.LotView.Utils; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; using System; using System.Collections.Generic; -using System.Linq; -using Microsoft.Xna.Framework.Graphics; -using FSO.LotView.Utils; -using FSO.Common.Utils; -using FSO.LotView.LMap; using System.IO; -using FSO.LotView.Effects; +using System.Linq; +using System.Runtime.CompilerServices; namespace FSO.LotView.Components { @@ -27,10 +29,12 @@ public class RoofComponent : WorldComponent, IDisposable public uint RoofStyle; public float RoofPitch; - public bool StyleDirty = false; + public bool StyleDirty = true; public bool ShapeDirty = true; public float TexRescale = 1f; + private float HeightAdjust; + public void SetStylePitch(uint style, float pitch) { RoofStyle = style; @@ -46,18 +50,15 @@ public RoofComponent(Blueprint bp) this.Effect = WorldContent.GrassEffect; } - private Texture2D GenMips(GraphicsDevice device, Texture2D texture) + public void AdjustHeight(float diff) { - var data = new Color[texture.Width * texture.Height]; - texture.GetData(data); - texture.Dispose(); - texture = new Texture2D(device, texture.Width, texture.Height, true, SurfaceFormat.Color); - TextureUtils.UploadWithAvgMips(texture, device, data); - return texture; + HeightAdjust += diff; } private void PrepTextures(GraphicsDevice device) { + // TODO: content system for roof textures so they can be reused + ParallaxTexture?.Dispose(); NormalMap?.Dispose(); EdgeTexture?.Dispose(); @@ -72,9 +73,18 @@ private void PrepTextures(GraphicsDevice device) try { TexRescale = 0.2f; - using (var strm = File.OpenRead($"Content/Textures/roof/{searchName}.png")) + var path = $"Content/Textures/roof/{searchName}.png"; + if (File.Exists(path)) { - Texture = Texture2D.FromStream(device, strm); + using (var strm = File.OpenRead(path)) + { + Texture = Texture2D.FromStream(device, strm); + } + } + else + { + TexRescale = 1f; + Texture = roofs.Get(name).Get(device); } } catch @@ -92,19 +102,13 @@ private void PrepTextures(GraphicsDevice device) ParallaxTexture = Texture2D.FromStream(device, strm); } - using (var strm = File.OpenRead($"Content/Textures/roof/{searchName}_n.png")) - { - NormalMap = GenMips(device, Texture2D.FromStream(device, strm)); - } + NormalMap = ImageLoader.MipTextureFromFile(device, $"Content/Textures/roof/{searchName}_n.png"); } catch (Exception) { } - using (var strm = File.OpenRead($"Content/Textures/roof/default_edge.png")) - { - EdgeTexture = GenMips(device, Files.ImageLoader.FromStream(device, strm)); - } + EdgeTexture = ImageLoader.MipTextureFromFile(device, $"Content/Textures/roof/default_edge.png"); var color = new Vector4(); var data = new Color[Texture.Width * Texture.Height]; @@ -117,13 +121,41 @@ private void PrepTextures(GraphicsDevice device) RoofAvgColor = new Color(color); } + private void EnsureTextures(GraphicsDevice device) + { + if (StyleDirty || Texture == null) + { + if (RoofRects.Sum(x => x?.Count ?? 0) == 0) + { + // Don't need the texture right now. + return; + } + + PrepTextures(device); + + StyleDirty = false; + } + } + public void RegenRoof(GraphicsDevice device) { - if (Texture == null) PrepTextures(device); + var (indoorsMap, hasAnyIndoors) = BuildIndoorsMap(); for (int i = 1; i <= blueprint.Stories; i++) { - RegenRoof((sbyte)(i + 1), device); + if (hasAnyIndoors[i - 1]) + { + RegenRoof((sbyte)(i + 1), device, indoorsMap); + } + else if (RoofRects[i - 1]?.Count > 0) + { + // Whole story has no indoor tiles left, clear the old roof so it stops rendering. + + RoofRects[i - 1] = null; + var dg = Drawgroups[i - 1]; + dg?.Dispose(); + Drawgroups[i - 1] = null; + } } blueprint.SM64?.UpdateRoof(); @@ -131,7 +163,8 @@ public void RegenRoof(GraphicsDevice device) public void RemeshRoof(GraphicsDevice device) { - PrepTextures(device); + HeightAdjust = 0; + EnsureTextures(device); for (int i = 1; i <= blueprint.Stories; i++) { @@ -139,7 +172,7 @@ public void RemeshRoof(GraphicsDevice device) } } - public void RegenRoof(sbyte level, GraphicsDevice device) + public void RegenRoof(sbyte level, GraphicsDevice device, bool[][] indoorsMap) { //algorithm overview: // 1. divide each tile into 4. @@ -166,15 +199,17 @@ public void RegenRoof(sbyte level, GraphicsDevice device) { evaluated[off] = true; var tilePos = new LotTilePos((short)(x * 8), (short)(y * 8), level); - if (IsRoofable(tilePos)) + if (IsRoofable(tilePos, indoorsMap)) { //bingo. try expand a rectangle here. - RoofSpread(tilePos, evaluated, width, height, level, result); + RoofSpread(tilePos, evaluated, width, height, level, result, indoorsMap); } } } } RoofRects[level - 2] = result; + + EnsureTextures(device); MeshRects(level, device); } @@ -234,10 +269,10 @@ public RoofData MeshRectData(int level) var heightMod = 0;// height / 400f; var pitch = RoofPitch; - var tl = ToWorldPos(rect.x1, rect.y1, 0, level, pitch) + new Vector3(0, heightMod, 0); - var tr = ToWorldPos(rect.x2, rect.y1, 0, level, pitch) + new Vector3(0, heightMod, 0); - var bl = ToWorldPos(rect.x1, rect.y2, 0, level, pitch) + new Vector3(0, heightMod, 0); - var br = ToWorldPos(rect.x2, rect.y2, 0, level, pitch) + new Vector3(0, heightMod, 0); + var tl = ToWorldPos(rect.x1, rect.y1, 0, level, pitch, 8, 8) + new Vector3(0, heightMod, 0); + var tr = ToWorldPos(rect.x2, rect.y1, 0, level, pitch, -8, 8) + new Vector3(0, heightMod, 0); + var bl = ToWorldPos(rect.x1, rect.y2, 0, level, pitch, 8, -8) + new Vector3(0, heightMod, 0); + var br = ToWorldPos(rect.x2, rect.y2, 0, level, pitch, -8, -8) + new Vector3(0, heightMod, 0); var m_tl = ToWorldPos(rect.x1 + height, rect.y1 + height, height, level, pitch) + new Vector3(0, heightMod, 0); var m_tr = ToWorldPos(rect.x2 - height, rect.y1 + height, height, level, pitch) + new Vector3(0, heightMod, 0); @@ -420,11 +455,7 @@ public void MeshRects(int level, GraphicsDevice device) if (Drawgroups[level - 2] != null && Drawgroups[level - 2].NumPrimitives > 0) { - Drawgroups[level - 2].VertexBuffer.Dispose(); - Drawgroups[level - 2].IndexBuffer.Dispose(); - - Drawgroups[level - 2].AdvVertexBuffer?.Dispose(); - Drawgroups[level - 2].AdvIndexBuffer?.Dispose(); + Drawgroups[level - 2].Dispose(); } var result = new RoofDrawGroup() { Data = data }; @@ -452,9 +483,9 @@ public void MeshRects(int level, GraphicsDevice device) Drawgroups[level - 2] = result; } - private Vector3 ToWorldPos(int x, int y, int z, int level, float pitch) + private Vector3 ToWorldPos(int x, int y, int z, int level, float pitch, int xBias = 0, int yBias = 0) { - return new Vector3((x / 16f) * 3f, (z * pitch / 16f) * 3f + ((level - 1) * 2.95f * 3f) + blueprint.GetAltitude(x / 16, y / 16) * 3, (y / 16f) * 3f); + return new Vector3((x / 16f) * 3f, (z * pitch / 16f) * 3f + ((level - 1) * 2.95f * 3f) + (blueprint.GetAltPoint((x + xBias) / 16, (y + yBias) / 16) - blueprint.BaseAlt) * blueprint.TerrainFactor * 3, (y / 16f) * 3f); } private static Point[] advanceByDir = new Point[] @@ -498,7 +529,7 @@ private bool RangeCheck(RoofRect me, RoofRect into, int dir) 2, 3, 1, -1 }; - private void RoofSpread(LotTilePos start, bool[] evaluated, int width, int height, sbyte level, List result) + private void RoofSpread(LotTilePos start, bool[] evaluated, int width, int height, sbyte level, List result, bool[][] indoorsMap) { var rect = new RoofRect(start.x, start.y, start.x + 8, start.y + 8); var toCtr = new Point(4, 4); @@ -517,7 +548,7 @@ private void RoofSpread(LotTilePos start, bool[] evaluated, int width, int heigh for (int i = 0; i < count; i++) { var tile = new LotTilePos((short)testPt.X, (short)testPt.Y, level); - if (!IsRoofable(tile)) + if (!IsRoofable(tile, indoorsMap)) { canExpand = false; break; @@ -619,21 +650,17 @@ public bool Intersects(RoofRect other) } } - public bool TileIndoors(int x, int y, int level) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool TileIndoors(int x, int y, bool[] map) { - var room = blueprint.RoomMap[level - 1][x + y * blueprint.Width]; - var room1 = room & 0xFFFF; - var room2 = (room >> 16) & 0x7FFF; - if (room1 < blueprint.Rooms.Count && !blueprint.Rooms[(int)room1].IsOutside) return true; - if (room2 > 0 && room2 < blueprint.Rooms.Count && !blueprint.Rooms[(int)room2].IsOutside) return true; - return false; + return map[x + y * blueprint.Width]; } - public bool IndoorsOrFloor(int x, int y, int level) + public bool IndoorsOrFloor(int x, int y, int level, bool[] map) { if (level <= blueprint.Stories) { - if (TileIndoors(x, y, level)) return true; + if (TileIndoors(x, y, map)) return true; if (blueprint.GetFloor((short)x, (short)y, (sbyte)level).Pattern != 0) return true; var wall = blueprint.GetWall((short)x, (short)y, (sbyte)level); if ((wall.Segments & WallSegments.AnyDiag) > 0) return true; @@ -641,7 +668,41 @@ public bool IndoorsOrFloor(int x, int y, int level) return false; } - public bool IsRoofable(LotTilePos pos) + private (bool[][], bool[]) BuildIndoorsMap() + { + bool[] roomIndoors = blueprint.Rooms.Select(x => !x.IsOutside).ToArray(); + bool[][] indoors = new bool[blueprint.Stories][]; + bool[] hasAnyIndoors = new bool[blueprint.Stories]; + + for (int i = 0; i < blueprint.Stories; i++) + { + bool anyIndoors = false; + var roomMap = blueprint.RoomMap[i]; + var floorIndoors = new bool[blueprint.Width * blueprint.Height]; + indoors[i] = floorIndoors; + + for (int j = 0; j < floorIndoors.Length; j++) + { + uint room = roomMap[j]; + + bool tileIndoors = false; + + var room1 = room & 0xFFFF; + var room2 = (room >> 16) & 0x7FFF; + if (room1 < roomIndoors.Length && roomIndoors[room1]) tileIndoors = true; + if (room2 > 0 && room2 < roomIndoors.Length && roomIndoors[room2]) tileIndoors = true; + + floorIndoors[j] = tileIndoors; + anyIndoors |= tileIndoors; + } + + hasAnyIndoors[i] = anyIndoors; + } + + return (indoors, hasAnyIndoors); + } + + public bool IsRoofable(LotTilePos pos, bool[][] indoorsMap) { if (pos.Level == 1) return false; var tileX = pos.TileX; @@ -651,29 +712,33 @@ public bool IsRoofable(LotTilePos pos) var fDiag = false; //must be over indoors var halftile = false; - if (!TileIndoors(tileX, tileY, level - 1)) + + var indoorsLower = indoorsMap[level - 2]; + var indoorsUpper = level <= blueprint.Stories ? indoorsMap[level - 1] : null; + + if (!TileIndoors(tileX, tileY, indoorsLower)) { //are a half tile away from indoors? bool found = false; if (pos.x % 16 == 8) { - if (TileIndoors(tileX + 1, tileY, level - 1)) found = true; + if (TileIndoors(tileX + 1, tileY, indoorsLower)) found = true; } else { - if (TileIndoors(tileX - 1, tileY, level - 1)) found = true; + if (TileIndoors(tileX - 1, tileY, indoorsLower)) found = true; } if (pos.y % 16 == 8) { - if (TileIndoors(tileX, tileY + 1, level - 1)) found = true; + if (TileIndoors(tileX, tileY + 1, indoorsLower)) found = true; } else { - if (TileIndoors(tileX, tileY - 1, level - 1)) found = true; + if (TileIndoors(tileX, tileY - 1, indoorsLower)) found = true; } - if (TileIndoors(tileX + ((pos.x % 16 == 8) ? 1 : -1), tileY + ((pos.y % 16 == 8) ? 1 : -1), level - 1)) + if (TileIndoors(tileX + ((pos.x % 16 == 8) ? 1 : -1), tileY + ((pos.y % 16 == 8) ? 1 : -1), indoorsLower)) { if (!found) fDiag = true; found = true; @@ -682,29 +747,29 @@ public bool IsRoofable(LotTilePos pos) halftile = true; } //on our level, the tile must not be indoors or floored - if (IndoorsOrFloor(tileX, tileY, level)) return false; + if (IndoorsOrFloor(tileX, tileY, level, indoorsUpper)) return false; if (halftile) { if (pos.x % 16 == 8) { - if (IndoorsOrFloor(tileX + 1, tileY, level)) return false; + if (IndoorsOrFloor(tileX + 1, tileY, level, indoorsUpper)) return false; } else { - if (IndoorsOrFloor(tileX - 1, tileY, level)) return false; + if (IndoorsOrFloor(tileX - 1, tileY, level, indoorsUpper)) return false; } if (pos.y % 16 == 8) { - if (IndoorsOrFloor(tileX, tileY + 1, level)) return false; + if (IndoorsOrFloor(tileX, tileY + 1, level, indoorsUpper)) return false; } else { - if (IndoorsOrFloor(tileX, tileY - 1, level)) return false; + if (IndoorsOrFloor(tileX, tileY - 1, level, indoorsUpper)) return false; } - if (fDiag && IndoorsOrFloor(tileX + ((pos.x % 16 == 8) ? 1 : -1), tileY + ((pos.y % 16 == 8) ? 1 : -1), level)) return false; + if (fDiag && IndoorsOrFloor(tileX + ((pos.x % 16 == 8) ? 1 : -1), tileY + ((pos.y % 16 == 8) ? 1 : -1), level, indoorsUpper)) return false; } return true; @@ -712,22 +777,21 @@ public bool IsRoofable(LotTilePos pos) public void Dispose() { + ParallaxTexture?.Dispose(); + NormalMap?.Dispose(); + EdgeTexture?.Dispose(); + foreach (var buf in Drawgroups) { if (buf != null && buf.NumPrimitives > 0) { - buf.IndexBuffer.Dispose(); - buf.VertexBuffer.Dispose(); - - buf.AdvIndexBuffer?.Dispose(); - buf.AdvVertexBuffer?.Dispose(); + buf.Dispose(); } } } public override void Draw(GraphicsDevice device, WorldState world) { - var enableParallax = WorldConfig.Current.Complex && ParallaxTexture != null; device.RasterizerState = RasterizerState.CullNone; if (ShapeDirty) { @@ -741,6 +805,15 @@ public override void Draw(GraphicsDevice device, WorldState world) StyleDirty = false; } + if (Drawgroups.Length == 0) + { + return; + } + + var enableParallax = WorldConfig.Current.Complex && ParallaxTexture != null; + + Matrix worldMat = HeightAdjust == 0 ? Matrix.Identity : Matrix.CreateTranslation(0, HeightAdjust, 0); + device.RasterizerState = RasterizerState.CullClockwise; device.BlendState = BlendState.AlphaBlend; int maxLevel = world.ScrollAnchor?.MyMario != null ? world.Level - 2 : world.Level - 1; @@ -756,7 +829,7 @@ public override void Draw(GraphicsDevice device, WorldState world) { Effect.View = world.View; Effect.Projection = world.Projection; - Effect.World = Matrix.Identity; + Effect.World = worldMat; Effect.DiffuseColor = new Vector4(world.OutsideColor.R / 255f, world.OutsideColor.G / 255f, world.OutsideColor.B / 255f, 1.0f); Effect.UseTexture = true; Effect.BaseTex = Texture; @@ -918,7 +991,7 @@ public class RoofData public int AdvNumPrimitives; } - public class RoofDrawGroup + public class RoofDrawGroup : IDisposable { public RoofData Data; @@ -929,5 +1002,13 @@ public class RoofDrawGroup public IndexBuffer AdvIndexBuffer; public VertexBuffer AdvVertexBuffer; public int AdvNumPrimitives; + + public void Dispose() + { + VertexBuffer?.Dispose(); + IndexBuffer?.Dispose(); + AdvVertexBuffer?.Dispose(); + AdvIndexBuffer?.Dispose(); + } } } diff --git a/TSOClient/tso.world/Components/SM64Component.cs b/TSOClient/tso.world/Components/SM64Component.cs index ccdef21ee..4fa6d63ec 100644 --- a/TSOClient/tso.world/Components/SM64Component.cs +++ b/TSOClient/tso.world/Components/SM64Component.cs @@ -350,6 +350,11 @@ public Tuple GetBaseMarioPos() return new Tuple(pos, angle); } + public ushort GetPreciseFloor(Vector3 tile) + { + return Bp.GetPreciseFloor(tile); + } + public bool TileIndoors(int x, int y, int level) { if (x < 0 || y < 0 || level < 0 || x >= Bp.Width || y >= Bp.Height || level >= Bp.Stories) @@ -424,6 +429,18 @@ public static void SetAnimData(byte[] data) } } + private Vector2 BoostStick(Vector2 stick, float factor) + { + stick *= factor; + + if (stick.LengthSquared() > 1) + { + stick.Normalize(); + } + + return stick; + } + private ControllerState GenerateControllerState() { // Generate controller state from the first plugged in XNA controller. @@ -443,11 +460,15 @@ private ControllerState GenerateControllerState() if (gamepad.Buttons.RightShoulder == ButtonState.Pressed) controllerState.ButtonDown |= Button.Z_TRIG; if (gamepad.Buttons.LeftShoulder == ButtonState.Pressed) controllerState.ButtonDown |= Button.Z_TRIG; - controllerState.StickX = gamepad.ThumbSticks.Left.X * 64; - controllerState.StickY = gamepad.ThumbSticks.Left.Y * 64; + var leftStick = BoostStick(gamepad.ThumbSticks.Left, 1.20f); - controllerState.RawStickX = (short)(gamepad.ThumbSticks.Left.X * 32767f); - controllerState.RawStickY = (short)(gamepad.ThumbSticks.Left.Y * 32767f); + Console.WriteLine($"{leftStick.Length()}"); + + controllerState.StickX = leftStick.X * 64; + controllerState.StickY = leftStick.Y * 64; + + controllerState.RawStickX = (short)(leftStick.X * 32767f); + controllerState.RawStickY = (short)(leftStick.Y * 32767f); controllerState.ButtonPressed = controllerState.ButtonDown & (~LastState.ButtonDown); @@ -1062,14 +1083,17 @@ public void UpdateFloors() terrain = special.Item2; } - foreach (var tiletuple in tilegroup.GeomForOffset) + foreach (var tiletuple in tilegroup.GeomForOffset.Values) { + var tiletupleCopy = tiletuple; + var indices = tiletupleCopy.GetSpan(); + if (floorType == 0) { // Just use the base terrain triangles. // Select a tile by picking the first vertex, dividing by 4. - int tileIndex = (tiletuple.Value[0] / 4); + int tileIndex = (indices[0] / 4); // Then select both triangles. @@ -1082,7 +1106,7 @@ public void UpdateFloors() // They're all going to be on the same tile... // Select it by picking the first vertex, dividing by 4. - int tileIndex = (tiletuple.Value[0] / 4); + int tileIndex = (indices[0] / 4); int baseIndex = tileIndex * 4; var tri1 = TerrainBase[tileIndex << 1]; @@ -1093,8 +1117,7 @@ public void UpdateFloors() tile[2] = ToVector3(tri1.Vertex1); tile[3] = ToVector3(tri2.Vertex2); - var indices = tiletuple.Value; - for (int j = 0; j < indices.Count; j += 3) + for (int j = 0; j < indices.Length; j += 3) { var v1 = tile[indices[j + 2] - baseIndex] + heightOffset; var v2 = tile[indices[j + 1] - baseIndex] + heightOffset; @@ -1650,6 +1673,14 @@ public SM64Scene(SM64Component component) { 1, "sting_potion_funny" } }; + private static readonly string[] FootstepSounds = + [ + "footstep_soft", + "footstep_medium", + "footstep_hard", + "footstep_terrain", + ]; + public void SetSource(VisualMario visual) { Source = visual; @@ -1659,7 +1690,29 @@ public override void play_sound(uint soundBits, Vec3f pos) { // TODO: play from mario location... - if (SoundBitsToHitEvt.TryGetValue(soundBits, out string evt)) + const uint TerrainMask = 0xFFF0FFFF; + + string evt = null; + + if ((soundBits & TerrainMask) == Mario.Enum.Sound.SOUND_ACTION_TERRAIN_STEP) + { + int hardness = 2; + var visualPos = Component.MyMario.Position ?? default; + ushort floorTileId = Component.GetPreciseFloor(new Vector3(visualPos.X, visualPos.Z, visualPos.Y) / 3f); + + if (floorTileId == 0) + { + hardness = 3; + } + else if (Content.Content.Get().WorldFloors.Entries.TryGetValue(floorTileId, out var floor)) + { + hardness = floor.Hardness; + } + + evt = FootstepSounds[hardness]; + } + + if (evt != null || SoundBitsToHitEvt.TryGetValue(soundBits, out evt)) { var hitvm = FSO.HIT.HITVM.Get(); diff --git a/TSOClient/tso.world/Components/SubWorldComponent.cs b/TSOClient/tso.world/Components/SubWorldComponent.cs index 3edeaeb75..3669369ac 100644 --- a/TSOClient/tso.world/Components/SubWorldComponent.cs +++ b/TSOClient/tso.world/Components/SubWorldComponent.cs @@ -6,8 +6,6 @@ using FSO.LotView.Utils; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; namespace FSO.LotView.Components { @@ -17,11 +15,13 @@ public class SubWorldComponent : World /// Creates a new World instance. /// /// A GraphicsDevice instance. - public SubWorldComponent(GraphicsDevice Device) + public SubWorldComponent(GraphicsDevice Device, int index) : base(Device) { + Index = index; } + public int Index; public Vector2 GlobalPosition; public bool UseFade = true; @@ -55,14 +55,12 @@ public void Initialize(GraphicsDevice device) HasInit = HasInitGPU & HasInitBlueprint; } - public override void InitBlueprint(Blueprint blueprint) + public void InitBlueprintNoGPU(Blueprint blueprint) { this.Blueprint = blueprint; HasInitBlueprint = true; HasInit = HasInitGPU & HasInitBlueprint; - Light?.Init(Blueprint); - State.Rooms.Init(blueprint); blueprint.Changes.SetFlag(BlueprintGlobalChanges.ROOM_CHANGED); blueprint.Changes.SetFlag(BlueprintGlobalChanges.OUTDOORS_LIGHTING_CHANGED); Architecture = new WorldArchitecture(blueprint); @@ -73,6 +71,18 @@ public override void InitBlueprint(Blueprint blueprint) blueprint.Changes.Subworld = true; } + public void InitBlueprintGPU(Blueprint blueprint) + { + Light?.Init(Blueprint); + State.Rooms.Init(blueprint); + } + + public override void InitBlueprint(Blueprint blueprint) + { + InitBlueprintNoGPU(blueprint); + InitBlueprintGPU(blueprint); + } + public override void InitDefaultGraphicsMode() { } @@ -101,6 +111,14 @@ public virtual void PreDraw(GraphicsDevice gd, WorldState state) state.SilentLevel = State.Level; state.SilentBuildMode = 0; State._2D = state._2D; + + if ((Blueprint.Changes.Dirty & (BlueprintGlobalChanges.LIGHTING_ANY | BlueprintGlobalChanges.ROOM_CHANGED)) != 0) + { + State.OutsideColor = state.OutsideColor; + Blueprint.OutsideColor = state.OutsideColor; + State.Light?.BuildOutdoorsLight(Blueprint.OutsideTime); + } + Blueprint.Changes.PreDraw(gd, State); state.SilentBuildMode = oldBuild; @@ -112,11 +130,6 @@ public virtual void PreDraw(GraphicsDevice gd, WorldState state) * We use the same BlueprintChanges for simplicity, though after load it won't really change. (and static/dynamic distinction is ignored) */ - if (Blueprint.Changes.UpdateColor) - { - State.OutsideColor = state.OutsideColor; - Blueprint.OutsideColor = state.OutsideColor; - } State.LightingAdjust = state.OutsideColor.ToVector3() / State.OutsideColor.ToVector3(); /* @@ -290,7 +303,7 @@ public void UpdateBounds() float maxAlt = 0; foreach (var height in Blueprint.Altitude) { - var alt = height * Blueprint.TerrainFactor - Blueprint.BaseAlt; + var alt = (height - Blueprint.BaseAlt) * Blueprint.TerrainFactor * 3; if (alt < minAlt) { minAlt = alt; diff --git a/TSOClient/tso.world/Components/TerrainComponent.cs b/TSOClient/tso.world/Components/TerrainComponent.cs index 2067aa270..d891ca632 100644 --- a/TSOClient/tso.world/Components/TerrainComponent.cs +++ b/TSOClient/tso.world/Components/TerrainComponent.cs @@ -11,6 +11,9 @@ using FSO.LotView.LMap; using FSO.LotView.Effects; using FSO.Common.Model; +using FSO.LotView.Utils.Camera; +using System.Runtime.CompilerServices; +using FSO.Files; namespace FSO.LotView.Components { @@ -18,6 +21,9 @@ public class TerrainComponent : WorldComponent, IDisposable { private Rectangle Size; + private static Matrix RotToNormalXY = Matrix.CreateRotationZ((float)(Math.PI / 2)); + private static Matrix RotToNormalZY = Matrix.CreateRotationX(-(float)(Math.PI / 2)); + private int GeomLength; private byte[] GrassState; //0 = green, 255 = brown. to start with, should be randomly distriuted in range 0-128. private short[] GroundHeight; @@ -26,7 +32,6 @@ public class TerrainComponent : WorldComponent, IDisposable private int GridPrimitives; private int TGridPrimitives; private IndexBuffer IndexBuffer; - private IndexBuffer BladeIndexBuffer; private IndexBuffer GridIndexBuffer; private IndexBuffer TGridIndexBuffer; public VertexBuffer VertexBuffer; @@ -38,8 +43,11 @@ public class TerrainComponent : WorldComponent, IDisposable private Color LightGreen = new Color(80, 116, 59); private Color LightBrown = new Color(157, 117, 65); + private Vector2 GreenLengthDensity = new Vector2(1, 1); private Color DarkGreen = new Color(8, 52, 8); private Color DarkBrown = new Color(81, 60, 18); + private Vector2 BrownLengthDensity = new Vector2(0.05f, 0.5f); + private int GrassHeight; private float GrassDensityScale = 1f; public bool DepthMode; @@ -118,72 +126,81 @@ public void ForceSnow(float type) public void UpdateLotType() { int index = (int)LightType; - LightGreen = LotTypeGrassInfo.LightGreen[index]; - DarkGreen = LotTypeGrassInfo.DarkGreen[index]; + var dindex = (int)DarkType; + ref var light = ref LotTypeGrassInfo.Info[index]; + + LightGreen = light.LightGreen; + DarkGreen = light.DarkGreen; + GreenLengthDensity = light.GreenLengthDensity; if (LightType != DarkType) { - var dindex = (int)DarkType; - LightBrown = LotTypeGrassInfo.LightGreen[dindex]; - DarkBrown = LotTypeGrassInfo.DarkGreen[dindex]; + // Uses the "green" type of the secondary grass colour as the brown colour. + ref var dark = ref LotTypeGrassInfo.Info[dindex]; + LightBrown = dark.LightGreen; + DarkBrown = dark.DarkGreen; + BrownLengthDensity = dark.GreenLengthDensity; } else { - LightBrown = LotTypeGrassInfo.LightBrown[index]; - DarkBrown = LotTypeGrassInfo.DarkBrown[index]; + LightBrown = light.LightBrown; + DarkBrown = light.DarkBrown; + BrownLengthDensity = light.BrownLengthDensity; } - GrassHeight = LotTypeGrassInfo.Heights[index]; + GrassHeight = light.MaxHeight; if (!FSOEnvironment.UseMRT) GrassHeight /= 2; if (GrassHeight == 0) GrassHeight = 1; - GrassDensityScale = LotTypeGrassInfo.GrassDensity[index]; + GrassDensityScale = light.BaseDensity; } - private Vector3 GetNormalAt(int x, int y) { - var sum = new Vector3(); - var rotToNormalXY = Matrix.CreateRotationZ((float)(Math.PI / 2)); - var rotToNormalZY = Matrix.CreateRotationX(-(float)(Math.PI / 2)); var limit = (Size.Width - 1); + float myElevation = GetElevationPoint(x, y); + + // vec.x = 1, vec.y = difference + float xElevDifference = 0; + int xElevCount = 0; + if (x < limit) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(x + 1, y) - GetElevationPoint(x, y); - vec = Vector3.Transform(vec, rotToNormalXY); - sum += vec; + xElevDifference += GetElevationPoint(x + 1, y) - myElevation; + xElevCount++; } if (x > 1) { - var vec = new Vector3(); - vec.X = 1; - vec.Y = GetElevationPoint(x, y) - GetElevationPoint(x - 1, y); - vec = Vector3.Transform(vec, rotToNormalXY); - sum += vec; + xElevDifference += myElevation - GetElevationPoint(x - 1, y); + xElevCount++; } + xElevDifference /= xElevCount; + + // vec.z = 1, vec.z = difference; + float yElevDifference = 0; + int yElevCount = 0; + if (y < limit) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(x, y + 1) - GetElevationPoint(x, y); - vec = Vector3.Transform(vec, rotToNormalZY); - sum += vec; + yElevDifference += GetElevationPoint(x, y + 1) - myElevation; + yElevCount++; } if (y > 1) { - var vec = new Vector3(); - vec.Z = 1; - vec.Y = GetElevationPoint(x, y) - GetElevationPoint(x, y - 1); - vec = Vector3.Transform(vec, rotToNormalZY); - sum += vec; + yElevDifference += myElevation - GetElevationPoint(x, y - 1); + yElevCount++; } - if (sum != Vector3.Zero) sum.Normalize(); - return sum; + + yElevDifference /= yElevCount; + + Vector3 cross = Vector3.Cross(new Vector3(0, yElevDifference, 3f), new Vector3(3f, xElevDifference, 0)); + + if (cross != Vector3.Zero) cross.Normalize(); + return cross; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public float GetElevationPoint(int x, int y) { if (x >= Size.Width || y >= Size.Height) return 0; @@ -201,7 +218,6 @@ public void RegenTerrain(GraphicsDevice device, Blueprint blueprint) if (VertexBuffer != null) { IndexBuffer.Dispose(); - BladeIndexBuffer.Dispose(); VertexBuffer.Dispose(); GridIndexBuffer?.Dispose(); TGridIndexBuffer?.Dispose(); @@ -217,12 +233,10 @@ public void RegenTerrain(GraphicsDevice device, Blueprint blueprint) TerrainParallaxVertex[] Geom = new TerrainParallaxVertex[numQuads * 4]; int[] Indexes = new int[numQuads * 6]; - int[] BladeIndexes = new int[numQuads * 6]; NumPrimitives = (numQuads * 2); int geomOffset = 0; int indexOffset = 0; - int bindexOffset = 0; var offsetX = WorldSpace.GetWorldFromTile(Size.X); var offsetY = WorldSpace.GetWorldFromTile(Size.Y); @@ -251,18 +265,6 @@ public void RegenTerrain(GraphicsDevice device, Blueprint blueprint) short tx = (short)x, ty = (short)y; - if (blueprint.GetFloor(tx, ty, 1).Pattern == 0 && - (blueprint.GetWall(tx, ty, 1).Segments & (WallSegments.HorizontalDiag | WallSegments.VerticalDiag)) == 0) - { - BladeIndexes[bindexOffset++] = geomOffset; - BladeIndexes[bindexOffset++] = (geomOffset + 1); - BladeIndexes[bindexOffset++] = (geomOffset + 2); - - BladeIndexes[bindexOffset++] = (geomOffset + 2); - BladeIndexes[bindexOffset++] = (geomOffset + 3); - BladeIndexes[bindexOffset++] = geomOffset; - } - Color tlCol = Color.Lerp(LightGreen, LightBrown, GetGrassState(x, y)); Color trCol = Color.Lerp(LightGreen, LightBrown, GetGrassState(x + 1, y)); Color blCol = Color.Lerp(LightGreen, LightBrown, GetGrassState(x, y + 1)); @@ -284,10 +286,6 @@ public void RegenTerrain(GraphicsDevice device, Blueprint blueprint) IndexBuffer = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, sizeof(int) * Indexes.Length, BufferUsage.None); IndexBuffer.SetData(Indexes); - BladePrimitives = (bindexOffset / 3); - - BladeIndexBuffer = new IndexBuffer(device, IndexElementSize.ThirtyTwoBits, sizeof(int) * Indexes.Length, BufferUsage.None); - BladeIndexBuffer.SetData(BladeIndexes); GeomLength = Geom.Length; var primLength = (GridAsTexture) ? 3 : 2; @@ -308,23 +306,10 @@ public void RegenTerrain(GraphicsDevice device, Blueprint blueprint) if (GridTex == null) { - using (var strm = File.Open($"Content/Textures/lot/tile_dashed.png", FileMode.Open, FileAccess.Read, FileShare.Read)) - { - GridTex = GenMips(device, Texture2D.FromStream(device, strm)); - } + GridTex = ImageLoader.MipTextureFromFile(device, $"Content/Textures/lot/tile_dashed.png"); } } - private Texture2D GenMips(GraphicsDevice device, Texture2D texture) - { - var data = new Color[texture.Width * texture.Height]; - texture.GetData(data); - texture.Dispose(); - texture = new Texture2D(device, texture.Width, texture.Height, true, SurfaceFormat.Color); - TextureUtils.UploadWithAvgMips(texture, device, data); - return texture; - } - public TerrainParallaxVertex[] GetVertices(GraphicsDevice gd) { if (VertexBuffer == null) RegenTerrain(gd, Bp); @@ -452,7 +437,8 @@ public override void Draw(GraphicsDevice device, WorldState world){ } if (VertexBuffer == null) return; if (world.Light != null) LightVec = world.Light.LightVec; - var transitionIntensity = (world.Camera as WorldCamera3D)?.FromIntensity ?? 0f; + var weights = (world.Camera as CameraControllers)?.TransitionWeights; + var transitionIntensity = weights?.Count == 1 ? weights[0].Percent : 0f; Alpha = 1 - (float)Math.Pow(transitionIntensity, 150f); device.DepthStencilState = DepthStencilState.Default; @@ -466,6 +452,9 @@ public override void Draw(GraphicsDevice device, WorldState world){ Effect.LightBrown = LightBrown.ToVector4(); var light = new Vector3(0.3f, 1, -0.3f); + Effect.GreenLengthDensity = GreenLengthDensity; + Effect.BrownLengthDensity = BrownLengthDensity; + Effect.LightVec = LightVec; Effect.UseTexture = false; Effect.ScreenSize = new Vector2(device.Viewport.Width, device.Viewport.Height) / world.PreciseZoom; @@ -489,9 +478,11 @@ public override void Draw(GraphicsDevice device, WorldState world){ Effect.ScreenMatrix = smat; var anchor = cam2d.RotationAnchor; var ctr = new Vector2(); + + var altOff = Bp.BaseAlt * Bp.TerrainFactor * 3; if (anchor != null) { - ctr = world.WorldSpace.GetScreenFromTile(new Vector2(anchor.Value.X, anchor.Value.Y)); + ctr = world.WorldSpace.GetScreenFromTile(new Vector3(anchor.Value.X, anchor.Value.Y, -altOff / 3)); ctr -= world.WorldSpace.GetScreenFromTile(new Vector2(cam2d.CenterTile.X, cam2d.CenterTile.Y)); } ctr += world.WorldSpace.WorldPx / 2; @@ -505,7 +496,6 @@ public override void Draw(GraphicsDevice device, WorldState world){ var translation = ((world.Zoom == WorldZoom.Far) ? -7 : ((world.Zoom == WorldZoom.Medium) ? -5 : -3)) * (20 / 522f); if (world.PreciseZoom < 1) translation /= world.PreciseZoom; else translation *= world.PreciseZoom; - var altOff = Bp.BaseAlt * Bp.TerrainFactor * 3; var worldmat = Matrix.Identity * Matrix.CreateTranslation(0, translation - altOff, 0); Effect.World = worldmat; if (_3d) Effect.CamPos = world.Camera.Position + (world.Cameras.ModelTranslation ?? new Vector3()); @@ -516,7 +506,7 @@ public override void Draw(GraphicsDevice device, WorldState world){ var pos = Vector3.Transform(new Vector3(0, 0, 20000), Matrix.Invert(flat)); Effect.CamPos = pos; } - Effect.DiffuseColor = world.OutsideColor.ToVector4() * Color.Lerp(LightGreen, Color.White, 0.25f).ToVector4(); + Effect.DiffuseColor = world.OutsideColor.ToVector4(); device.SetVertexBuffer(VertexBuffer); device.Indices = IndexBuffer; @@ -582,15 +572,19 @@ public override void Draw(GraphicsDevice device, WorldState world){ if (parallax) { grassScale *= grassNum; grassNum = 1; + Effect.ParallaxUVTexMat = new Vector4(0, -1, 0, 1); } for (int i = 1; i <= grassNum; i++) { Effect.World = Matrix.Identity * Matrix.CreateTranslation(0, i * (20 / 522f) * grassScale - altOff, 0); + Effect.LayerHeight = (i - 1) / (float)grassNum; + if (!parallax) - Effect.GrassProb = grassDensity * ((grassNum - (i / (2f * grassNum))) / (float)grassNum); + Effect.GrassProb = grassDensity; //Effect.GrassProb = grassDensity * ((grassNum - (i / (2f * grassNum))) / (float)grassNum); else Effect.GrassProb = grassDensity * ((4 - (2 / (2f * 4))) / (float)4); + Effect.ParallaxHeight = grassScale * (20 / 522f) * (100/512f) / 4; offset += new Vector2(smat.Z, smat.W); @@ -877,7 +871,6 @@ public void Dispose() if (VertexBuffer != null) { IndexBuffer.Dispose(); - BladeIndexBuffer.Dispose(); VertexBuffer.Dispose(); GridIndexBuffer?.Dispose(); TGridIndexBuffer?.Dispose(); diff --git a/TSOClient/tso.world/Effects/GrassEffect.cs b/TSOClient/tso.world/Effects/GrassEffect.cs index 961c97c20..7fb672fb6 100644 --- a/TSOClient/tso.world/Effects/GrassEffect.cs +++ b/TSOClient/tso.world/Effects/GrassEffect.cs @@ -22,9 +22,13 @@ protected override Type TechniqueType private EffectParameter pDarkBrown; private EffectParameter pDiffuseColor; private EffectParameter pScreenOffset; + private EffectParameter pLayerHeight; private EffectParameter pGrassProb; private EffectParameter pGrassFadeMul; + private EffectParameter pGreenLengthDensity; + private EffectParameter pBrownLengthDensity; + private EffectParameter pTexOffset; private EffectParameter pTexMatrix; @@ -136,6 +140,13 @@ public Vector2 ScreenOffset pScreenOffset.SetValue(value); } } + public float LayerHeight + { + set + { + pLayerHeight.SetValue(value); + } + } public float GrassProb { set @@ -151,6 +162,21 @@ public float GrassFadeMul } } + public Vector2 GreenLengthDensity + { + set + { + pGreenLengthDensity.SetValue(value); + } + } + public Vector2 BrownLengthDensity + { + set + { + pBrownLengthDensity.SetValue(value); + } + } + public Vector2 TexOffset { set @@ -419,9 +445,13 @@ protected override void PrepareParams() pDarkBrown = Parameters["DarkBrown"]; pDiffuseColor = Parameters["DiffuseColor"]; pScreenOffset = Parameters["ScreenOffset"]; + pLayerHeight = Parameters["LayerHeight"]; pGrassProb = Parameters["GrassProb"]; pGrassFadeMul = Parameters["GrassFadeMul"]; + pGreenLengthDensity = Parameters["GreenLengthDensity"]; + pBrownLengthDensity = Parameters["BrownLengthDensity"]; + pTexOffset = Parameters["TexOffset"]; pTexMatrix = Parameters["TexMatrix"]; diff --git a/TSOClient/tso.world/Effects/LightMappedEffect.cs b/TSOClient/tso.world/Effects/LightMappedEffect.cs index 9a391fd92..f9f583acc 100644 --- a/TSOClient/tso.world/Effects/LightMappedEffect.cs +++ b/TSOClient/tso.world/Effects/LightMappedEffect.cs @@ -51,11 +51,17 @@ public Vector2 MapLayout pMapLayout.SetValue(value); } } + + private float _CurrentLevel; public float Level { set { - pLevel.SetValue(value); + if (value != _CurrentLevel) + { + _CurrentLevel = value; + pLevel.SetValue(value); + } } } diff --git a/TSOClient/tso.world/Effects/MapGeneration.cs b/TSOClient/tso.world/Effects/MapGeneration.cs new file mode 100644 index 000000000..ea273f4b0 --- /dev/null +++ b/TSOClient/tso.world/Effects/MapGeneration.cs @@ -0,0 +1,277 @@ +using System; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace FSO.LotView.Effects +{ + public class MapGeneration : WorldEffect + { + protected override Type TechniqueType => typeof(MapGenerationTechniques); + + private EffectParameter pMatrixTransform; + private EffectParameter pImageSize; + private EffectParameter pStepSize; + private EffectParameter pEdgeValue; + + private EffectParameter pSdfExpand; + private EffectParameter pSdfFade; + private EffectParameter pGradientScale; + private EffectParameter pGradientBase; + + private EffectParameter pTerrainScale; + private EffectParameter pSunDir; + private EffectParameter pColor; + + private EffectParameter pSpecularPower; + private EffectParameter pSpecularIntensity; + + private EffectParameter pBaseTexture; + private EffectParameter pTerrainType; + private EffectParameter pDistToColor; + + private EffectParameter pGaussianStep; + private EffectParameter pGaussianSize; + private EffectParameter pGaussianWeights; + + public Matrix MatrixTransform + { + set + { + pMatrixTransform.SetValue(value); + } + } + public Vector2 ImageSize + { + set + { + pImageSize.SetValue(value); + } + } + public int StepSize + { + set + { + pStepSize.SetValue(value); + } + } + + public int EdgeValue + { + set + { + pEdgeValue.SetValue(value); + } + } + + public float SdfExpand + { + set + { + pSdfExpand.SetValue(value); + } + } + public float SdfFade + { + set + { + pSdfFade.SetValue(value); + } + } + + public float GradientScale + { + set + { + pGradientScale.SetValue(value); + } + } + public float GradientBase + { + set + { + pGradientBase.SetValue(value); + } + } + + public float TerrainScale + { + set + { + pTerrainScale.SetValue(value); + } + } + public Vector3 SunDir + { + set + { + pSunDir.SetValue(value); + } + } + public Color Color + { + set + { + pColor.SetValue(value.ToVector4()); + } + } + + public Vector4 ColorVec + { + set + { + pColor.SetValue(value); + } + } + + public float SpecularPower + { + set + { + pSpecularPower.SetValue(value); + } + } + public float SpecularIntensity + { + set + { + pSpecularIntensity.SetValue(value); + } + } + + public Texture2D BaseTexture + { + set + { + pBaseTexture.SetValue(value); + } + } + public Texture2D TerrainType + { + set + { + pTerrainType.SetValue(value); + } + } + public Texture2D DistToColor + { + set + { + pDistToColor.SetValue(value); + } + } + + public Vector2 GaussianStep + { + set + { + pGaussianStep.SetValue(value); + } + } + + public int GaussianSize + { + set + { + pGaussianSize.SetValue(value); + } + } + + public float[] GaussianWeights + { + set + { + pGaussianWeights.SetValue(value); + } + } + + public MapGeneration(GraphicsDevice graphicsDevice, byte[] effectCode) : base(graphicsDevice, effectCode) + { + } + + public MapGeneration(GraphicsDevice graphicsDevice, byte[] effectCode, int index, int count) : base(graphicsDevice, effectCode, index, count) + { + } + + public MapGeneration(Effect cloneSource) : base(cloneSource) + { + } + + private float[] GaussianWorkingArray = new float[21]; + + public void PrepareGaussianKernel(float blurSize) + { + var array = GaussianWorkingArray; + int arraySize = Math.Min(array.Length, (int)Math.Round((blurSize + 0.5f) / 2)); + float sigma = (blurSize - 1) / 6; + float sigma2 = sigma * sigma; + + float sum = 0; + for (int i = 0; i < arraySize; i++) + { + array[i] = MathF.Exp(-(i * i) / (2 * sigma2)); + sum += array[i]; + if (i > 0) + { + sum += array[i]; + } + } + + for (int i = 0; i < arraySize; i++) + { + array[i] /= sum; + } + + GaussianWeights = array; + GaussianSize = arraySize; + } + + protected override void PrepareParams() + { + base.PrepareParams(); + + pMatrixTransform = Parameters["MatrixTransform"]; + pImageSize = Parameters["ImageSize"]; + pStepSize = Parameters["StepSize"]; + pEdgeValue = Parameters["EdgeValue"]; + + pSdfExpand = Parameters["SdfExpand"]; + pSdfFade = Parameters["SdfFade"]; + pGradientScale = Parameters["GradientScale"]; + pGradientBase = Parameters["GradientBase"]; + + pTerrainScale = Parameters["TerrainScale"]; + pSunDir = Parameters["SunDir"]; + pColor = Parameters["Color"]; + + pSpecularPower = Parameters["SpecularPower"]; + pSpecularIntensity = Parameters["SpecularIntensity"]; + + pTerrainType = Parameters["TerrainType"]; + pDistToColor = Parameters["DistToColor"]; + pBaseTexture = Parameters["BaseTexture"]; + + pGaussianSize = Parameters["GaussianSize"]; + pGaussianStep = Parameters["GaussianStep"]; + pGaussianWeights = Parameters["GaussianWeights"]; + } + + public void SetTechnique(MapGenerationTechniques technique) + { + SetTechnique((int)technique); + } + } + + public enum MapGenerationTechniques + { + JumpFloodInit, + JumpFloodStep, + JumpFloodFinal, + CityEdgeDetect, + JumpDistFill, + TerrainLighting, + TerrainSpecular, + TerrainNormal, + ForestOverlay, + Gaussian, + } +} diff --git a/TSOClient/tso.world/Effects/RCObjectEffect.cs b/TSOClient/tso.world/Effects/RCObjectEffect.cs index 2e672b706..673369c5c 100644 --- a/TSOClient/tso.world/Effects/RCObjectEffect.cs +++ b/TSOClient/tso.world/Effects/RCObjectEffect.cs @@ -45,11 +45,18 @@ public float ObjectID pObjectID.SetValue(value); } } + + private Vector2 _CurrentUVScale = new Vector2(); public Vector2 UVScale { set { - pUVScale.SetValue(value); + // Avoid redundant updates for this parameter. + if (value != _CurrentUVScale) + { + _CurrentUVScale = value; + pUVScale.SetValue(value); + } } } public Vector4 AmbientLight diff --git a/TSOClient/FSO.IDE/Common/ExternalWorld.cs b/TSOClient/tso.world/ExternalWorld.cs similarity index 97% rename from TSOClient/FSO.IDE/Common/ExternalWorld.cs rename to TSOClient/tso.world/ExternalWorld.cs index a0134eaa2..5c14385df 100644 --- a/TSOClient/FSO.IDE/Common/ExternalWorld.cs +++ b/TSOClient/tso.world/ExternalWorld.cs @@ -1,11 +1,10 @@ using FSO.Common; using FSO.Common.Rendering.Framework; using FSO.Common.Utils; -using FSO.LotView; using FSO.LotView.Utils; using Microsoft.Xna.Framework.Graphics; -namespace FSO.IDE.Common +namespace FSO.LotView { public class ExternalWorld : World { diff --git a/TSOClient/tso.world/FSO.LotView.csproj b/TSOClient/tso.world/FSO.LotView.csproj index 742bfb7be..44b03e2ed 100644 --- a/TSOClient/tso.world/FSO.LotView.csproj +++ b/TSOClient/tso.world/FSO.LotView.csproj @@ -1,245 +1,41 @@ - - + + - Debug - x86 - 8.0.30703 - 2.0 - {B1A6E4C2-E080-4C34-A604-D11B5296A9B8} + net9.0 + enable + disable Library - Properties FSO.LotView FSO.LotView 512 - v4.5 - + True - - x86 - true - full - false - bin\WindowsGL\Debug\ - DEBUG;TRACE;WINDOWS - prompt - 4 - false - true + + + True - - x86 - pdbonly - true - bin\WindowsGL\Release\ - TRACE;WINDOWS - prompt - 4 - false - - - - - - - - - - true - bin\Debug\ - DEBUG;TRACE;WINDOWS - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - bin\Release\ - TRACE;WINDOWS - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - true - - - bin\x86\ServerRelease\ - TRACE;WINDOWS - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - bin\ServerRelease\ - TRACE;WINDOWS - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset + + + True + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - $(MSBuildExtensionsPath)\..\MonoGame\v3.0\Assemblies\WindowsGL\Lidgren.Network.dll - - - False - .\Mario.dll - - - ..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll - False - - - - - - - - Always - - - Always - - - - - {C42962A1-8796-4F47-9DCD-79ED5904D8CA} - FSO.Common - - - {C0068DF7-F2E8-4399-846D-556BF9A35C00} - FSO.Content - - - {18583453-A970-4AC5-83B1-2D6BFDF94C24} - FSO.Files - - - {072781d8-51ec-4143-9cae-daf50177d3ad} - FSO.HIT - - - {FD7957F7-A1E0-4D00-8F6C-3FA555EAA163} - FSO.Vitaboy.Engine - - - {9D9558A9-755E-43F9-8BB6-B26F365F5042} - FSO.Vitaboy - + + + + + + + - - + + Mario.dll + - - - \ No newline at end of file + + diff --git a/TSOClient/tso.world/Facade/LotFacadeGenerator.cs b/TSOClient/tso.world/Facade/LotFacadeGenerator.cs index 6b26ead78..154bcb9b9 100644 --- a/TSOClient/tso.world/Facade/LotFacadeGenerator.cs +++ b/TSOClient/tso.world/Facade/LotFacadeGenerator.cs @@ -5,11 +5,7 @@ using FSO.LotView.Model; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; using System.Globalization; -using System.IO; -using System.Linq; using System.Runtime.InteropServices; namespace FSO.LotView.Facade @@ -28,6 +24,7 @@ public class LotFacadeGenerator public static int WALL_HEIGHT = 22; public static int MAX_WALL_WIDTH = 64; //above this tile width the pixel width for the wall will not increase. public static int GAP = 1; + public static int SUPERSAMPLE_COUNT = 2; public int FLOOR_RES_PER_TILE = 2; public int FLOOR_TILES = 64;//98; @@ -40,7 +37,7 @@ public class LotFacadeGenerator private List WallBins = new List(); public RasterizerState Scissor = new RasterizerState() { ScissorTestEnable = true, CullMode = CullMode.None }; - public RenderTarget2D WallTarget; + public Texture2D WallTarget; public VertexPositionTexture[] WallVerts; public int[] WallIndices; @@ -55,6 +52,11 @@ public class LotFacadeGenerator public bool RoofOnFloor; public sbyte FloorsUsed; + private Rectangle MulRect(Rectangle rect, int factor) + { + return new Rectangle(rect.X * factor, rect.Y * factor, rect.Width * factor, rect.Height * factor); + } + public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool justTexture) { //generate wall geometry and texture. @@ -77,16 +79,15 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus } //ok, allocate the texture for the wall. - var tex = new RenderTarget2D(gd, MAX_WALL_WIDTH * WALL_WIDTH, CeilToFour(Math.Max(1, WallBins.Count * (WALL_HEIGHT + GAP * 2) - GAP * 2)), false, SurfaceFormat.Color, DepthFormat.Depth24); + var tex = new RenderTarget2D(gd, MAX_WALL_WIDTH * WALL_WIDTH * SUPERSAMPLE_COUNT, CeilToFour(Math.Max(1, WallBins.Count * (WALL_HEIGHT + GAP * 2) - GAP * 2)) * SUPERSAMPLE_COUNT, false, SurfaceFormat.Color, DepthFormat.Depth24); gd.SetRenderTarget(tex); gd.DepthStencilState = DepthStencilState.Default; - gd.Clear(Color.TransparentBlack); + gd.Clear(ColorExtensions.TransparentBlack); //ace, let's draw each wall var state = world.State; var oldLevel = world.State.Level; state.SilentLevel = bp.Stories; - state.ZeroWallOffset = true; var cuts = bp.Cutaway; bp.Cutaway = new bool[cuts.Length]; bp.WCRC?.Generate(gd, world.State, false); @@ -111,15 +112,10 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus var xFlip = 1f; //which side is "outside"? //check one side. assume the other is outside if we fail - var testPos = (ctr + rNorm * 0.6f).ToPoint(); - if (testPos.X >= 0 && testPos.X < bp.Width && testPos.Y >= 0 && testPos.Y < bp.Height) + if (bp.IsIndoorsPrecise(ctr + rNorm * 0.1f, wall.Room.Floor)) { - var room = bp.RoomMap[wall.Room.Floor][testPos.X + testPos.Y * bp.Width]; - if (!bp.Rooms[bp.Rooms[(ushort)room].Base].IsOutside) - { - rNorm *= -1; - xFlip *= -1; - } + rNorm *= -1; + xFlip *= -1; } var height = (wall.Room.Floor + 0.5f) * 2.95f * 3 + bp.InterpAltitude(new Vector3(ctr, 0)) * 3f + 0.2f; @@ -131,11 +127,12 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus //rescale our camera matrix to render to the correct part of the render target. Apply scissor test for that area. var rect = new Rectangle(xPos + (wall.EffectiveLength - (wall.Length + GAP)), yPos, wall.Length, WALL_HEIGHT); + rect = MulRect(rect, SUPERSAMPLE_COUNT); gd.RasterizerState = Scissor; gd.ScissorRectangle = rect; var trans = Matrix.CreateScale((rect.Width / ((float)tex.Width)), (rect.Height / ((float)tex.Height)), 1) * - Matrix.CreateTranslation((-(rect.X * -2 - wall.Length) / (float)tex.Width) - 1f, (-(rect.Y * 2 + WALL_HEIGHT - 2) / (float)tex.Height) + 1f, 0); + Matrix.CreateTranslation((-(rect.X * -2 - wall.Length * SUPERSAMPLE_COUNT) / (float)tex.Width) - 1f, (-(rect.Y * 2 + (WALL_HEIGHT - 2) * SUPERSAMPLE_COUNT) / (float)tex.Height) + 1f, 0); var frustrum = new BoundingFrustum(lookat * ortho); ortho = ortho * trans; @@ -147,13 +144,29 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus effect.ViewProjection = vp; state.ViewProjection = vp; state.Frustum = frustrum; + state.WallOffsetView = lookat; + + state.PrepareLighting(); bp.WCRC?.Draw(gd, world.State); effect.SetTechnique(RCObjectTechniques.Draw); - var objs = bp.Objects.Where(x => x.Level >= wall.Room.Floor - 5 && frustrum.Intersects(x.GetBounds())) - .OrderBy(x => { x.UpdateDrawOrder(state); return x.DrawOrder; }); + var floor = wall.Room.Floor + 1; + var wallAdj = wall.Points[1] - wall.Points[0]; + var wallNormal = new Vector2(-wallAdj.Y, wallAdj.X); + wallNormal.Normalize(); + var wallDot = Vector2.Dot(wall.Points[0] / 16f, wallNormal); + float wallMaxDist = 16 * 5; + + var objs = bp.Objects.Where(x => + { + if (!(x.Level == floor || x.Level == floor - 1)) return false; + var pos = x.Position; + return (Math.Abs(Vector2.Dot(wallNormal, new Vector2(pos.X, pos.Y)) - wallDot) < wallMaxDist) && + frustrum.Intersects(x.GetBounds()); + }).OrderBy(x => { x.UpdateDrawOrder(state); return x.DrawOrder; }).ToList(); + foreach (var obj in objs) { obj.Draw(gd, world.State); @@ -167,12 +180,20 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus bp.Cutaway = cuts; bp.WCRC?.Generate(gd, world.State, false); - world.State.ZeroWallOffset = false; + world.State.WallOffsetView = null; world.State.SilentLevel = oldLevel; + gd.SetRenderTarget(null); + Texture2D result = tex; + + if (SUPERSAMPLE_COUNT > 1) + { + result = TextureUtils.Decimate(result, gd, SUPERSAMPLE_COUNT, true); + } + //generate wall geometry - var data = new Color[tex.Width * tex.Height]; - tex.GetData(data); + var data = new Color[result.Width * result.Height]; + result.GetData(data); var verts = new VertexPositionTexture[wallCount * 4]; var indices = new int[wallCount * 6]; @@ -184,25 +205,25 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus { var xInt = 0; var yInt = bini * (WALL_HEIGHT + GAP * 2); - var yPos = bini * (WALL_HEIGHT + GAP * 2) / (float)tex.Height; + var yPos = bini * (WALL_HEIGHT + GAP * 2) / (float)result.Height; var xPos = 0f; - var div = WALL_HEIGHT / (float)tex.Height; + var div = WALL_HEIGHT / (float)result.Height; foreach (var wall in bin.Walls) { var rect = new Rectangle(xInt + (wall.EffectiveLength - (wall.Length + GAP)), yInt, wall.Length, WALL_HEIGHT); - BleedRect(data, rect, tex.Width, tex.Height); + BleedRect(data, rect, result.Width, result.Height); if (!justTexture) { var ctr = (wall.Points[0] + wall.Points[1]) / (2 * 16); - var off = (wall.EffectiveLength - (wall.Length + GAP)) / (float)tex.Width; + var off = (wall.EffectiveLength - (wall.Length + GAP)) / (float)result.Width; var height1 = ((wall.Room.Floor) * 2.95f + bp.InterpAltitude(new Vector3(ctr, 0))); var height2 = height1 + 2.95f; var pt1 = wall.Points[0] / 16f; var pt2 = wall.Points[1] / 16f; verts[verti++] = new VertexPositionTexture(new Vector3(pt1.X, height2, pt1.Y), new Vector2(xPos + off, yPos)); - verts[verti++] = new VertexPositionTexture(new Vector3(pt2.X, height2, pt2.Y), new Vector2(xPos + off + wall.Length / (float)tex.Width, yPos)); - verts[verti++] = new VertexPositionTexture(new Vector3(pt2.X, height1, pt2.Y), new Vector2(xPos + off + wall.Length / (float)tex.Width, (yPos + div))); + verts[verti++] = new VertexPositionTexture(new Vector3(pt2.X, height2, pt2.Y), new Vector2(xPos + off + wall.Length / (float)result.Width, yPos)); + verts[verti++] = new VertexPositionTexture(new Vector3(pt2.X, height1, pt2.Y), new Vector2(xPos + off + wall.Length / (float)result.Width, (yPos + div))); verts[verti++] = new VertexPositionTexture(new Vector3(pt1.X, height1, pt1.Y), new Vector2(xPos + off, (yPos + div))); indices[indi++] = verti - 2; @@ -214,20 +235,17 @@ public void GenerateWalls(GraphicsDevice gd, World world, Blueprint bp, bool jus indices[indi++] = verti - 2; xInt += wall.EffectiveLength; - xPos += wall.EffectiveLength / (float)tex.Width; + xPos += wall.EffectiveLength / (float)result.Width; } } bini++; } - //using (var fs = new FileStream(@"C:\Users\Rhys\Desktop\walls.png", FileMode.Create, FileAccess.Write)) - // tex.SaveAsPng(fs, tex.Width, tex.Height); - - tex.SetData(data); + result.SetData(data); + WallTarget = result; if (!justTexture) { - WallTarget = tex; WallVerts = verts; WallIndices = indices; } @@ -245,10 +263,16 @@ public void GenerateRoof(GraphicsDevice gd, World world, Blueprint bp) for (int i = 1; i < bp.Stories + 1; i++) { - var basetc = new Vector2((1 / 3f) * ((Math.Min(i, 4) % 3)+1), (1 / 2f) * ((Math.Min(i, 4) / 3) + 1)); + var basetc = new Vector2((1 / 3f) * ((Math.Min(i, 4) % 3) + 1), (1 / 2f) * ((Math.Min(i, 4) / 3) + 1)); var data = bp.RoofComp.MeshRectData(i + 1); + + if (data == null) + { + continue; + } + if (RoofOnFloor) - verts.AddRange(data.Vertices.Select(x => new VertexPositionTexture(x.Position / 3f, basetc - new Vector2((x.Position.X-basepos.X) / (3f*FLOOR_TILES*3), (x.Position.Z- basepos.Y) / (3f * FLOOR_TILES * 2))))); + verts.AddRange(data.Vertices.Select(x => new VertexPositionTexture(x.Position / 3f, basetc - new Vector2((x.Position.X - basepos.X) / (3f * FLOOR_TILES * 3), (x.Position.Z - basepos.Y) / (3f * FLOOR_TILES * 2))))); else verts.AddRange(data.Vertices.Select(x => new VertexPositionTexture(x.Position / 3f, new Vector2(x.GrassInfo.Y, x.GrassInfo.Z)))); inds.AddRange(data.Indices.Select(x => x + baseIndex)); @@ -264,21 +288,22 @@ public void GenerateFloor(GraphicsDevice gd, World world, Blueprint bp, bool jus FloorsUsed = floorsNum; var state = world.State; var dim = FLOOR_RES_PER_TILE * FLOOR_TILES; - var tex = new RenderTarget2D(gd, dim * 3, dim * 2, false, SurfaceFormat.Color, DepthFormat.Depth24); + var tex = new RenderTarget2D(gd, dim * 3 * SUPERSAMPLE_COUNT, dim * 2 * SUPERSAMPLE_COUNT, false, SurfaceFormat.Color, DepthFormat.Depth24); gd.SetRenderTarget(tex); - gd.Clear(Color.TransparentBlack); + gd.Clear(ColorExtensions.TransparentBlack); var lookat = Matrix.CreateLookAt(new Vector3(bp.Width * 1.5f, 200, bp.Height * 1.5f), new Vector3(bp.Width * 1.5f, 0, bp.Height * 1.5f), new Vector3(0, 0, 1)); var baseO = Matrix.CreateOrthographic(FLOOR_TILES * 3f, FLOOR_TILES * 3f, 0, 400); var oldLevel = state.SilentLevel; - for (int i = 0; i < floorsNum + 1; i++) { + for (int i = 0; i < floorsNum + 1; i++) + { world.State.SilentLevel = (sbyte)(i + 1); var x = i % 3; var y = i / 3; var offMat = Matrix.CreateScale(1 / 3f, 1 / 2f, 1f) * Matrix.CreateTranslation(-1 + ((x + 0.5f) * 2 / 3f), 1 - ((y + 0.5f) * 2 / 2f), 0); gd.RasterizerState = Scissor; - gd.ScissorRectangle = new Rectangle(dim * x + 1, dim * y + 1, dim - 2, dim - 2); + gd.ScissorRectangle = MulRect(new Rectangle(dim * x + 1, dim * y + 1, dim - 2, dim - 2), SUPERSAMPLE_COUNT); if (i == bp.Stories) { @@ -316,7 +341,7 @@ public void GenerateFloor(GraphicsDevice gd, World world, Blueprint bp, bool jus state.Frustum = frustrum; effect.SetTechnique(RCObjectTechniques.Draw); - + var objs = bp.Objects.Where(o => o.Level == i + 1 && frustrum.Intersects(o.GetBounds())) .OrderBy(o => { o.UpdateDrawOrder(state); return o.DrawOrder; }); foreach (var obj in objs) @@ -408,7 +433,7 @@ public void GenerateFloor(GraphicsDevice gd, World world, Blueprint bp, bool jus } } - FloorTexture = tex; + FloorTexture = SUPERSAMPLE_COUNT > 1 ? TextureUtils.Decimate(tex, gd, SUPERSAMPLE_COUNT, true) : tex; gd.SetRenderTarget(null); } @@ -419,7 +444,7 @@ private byte[] TexToData(Texture2D tex, bool compressed) if (compressed) { //let's assume the width and height didn't change. the default settings should always result in textures divisible by 4. - return TextureUtils.DXT5Compress(data, tex.Width, tex.Height).Item1; + return TextureUtils.DXT5Compress(data, tex.Width, tex.Height).Item1; } return ToByteArray(data.Select(x => x.PackedValue).ToArray()); } @@ -433,15 +458,20 @@ private static byte[] ToByteArray(T[] input) public void SimplifyFloor() { - var simple = new Simplify(); - simple.vertices = FloorVerts.Select(x => new MSVertex() { p = x.Position, t = x.TextureCoordinate }).ToList(); + var vertices = FloorVerts.Select(x => new MSVertex() { p = x.Position, t = x.TextureCoordinate }).ToArray(); + var triangles = new MSTriangle[FloorIndices.Length / 3]; + + int i = 0; for (int t = 0; t < FloorIndices.Length; t += 3) { - simple.triangles.Add(new MSTriangle() + triangles[i++] = new MSTriangle() { - v = new int[] { FloorIndices[t], FloorIndices[t + 1], FloorIndices[t + 2] } - }); + v = new MSTriangleIndices(FloorIndices[t], FloorIndices[t + 1], FloorIndices[t + 2]) + }; } + + var simple = new Simplify(triangles, vertices); + simple.simplify_mesh(2, agressiveness: 3, iterations: 300); FloorVerts = simple.vertices.Select(x => @@ -452,9 +482,9 @@ public void SimplifyFloor() var indices = new List(); foreach (var t in simple.triangles) { - indices.Add(t.v[0]); - indices.Add(t.v[1]); - indices.Add(t.v[2]); + indices.Add(t.v.i0); + indices.Add(t.v.i1); + indices.Add(t.v.i2); } FloorIndices = indices.ToArray(); } @@ -479,7 +509,9 @@ public FSOF GetFSOF(GraphicsDevice gd, World world, Blueprint bp, Action onNight result.FloorTextureData = TexToData(FloorTexture, compressed); result.WallTextureData = TexToData(WallTarget, compressed); - + FloorTexture.Dispose(); + WallTarget.Dispose(); + var tVerts = new List(); var tInd = new List(); var indOff = 0; @@ -488,7 +520,7 @@ public FSOF GetFSOF(GraphicsDevice gd, World world, Blueprint bp, Action onNight var tcOffset = new Vector2((i % 3) / 3f, (i / 3) / 2f); //save each floor. offset the floor for each level var posOffset = i * 2.95f; - var fVerts = FloorVerts.Select(x => new DGRP3DVert(new Vector3(x.Position.X, x.Position.Y+posOffset, x.Position.Z), Vector3.Zero, x.TextureCoordinate + tcOffset)); + var fVerts = FloorVerts.Select(x => new DGRP3DVert(new Vector3(x.Position.X, x.Position.Y + posOffset, x.Position.Z), Vector3.Zero, x.TextureCoordinate + tcOffset)); tVerts.AddRange(fVerts); tInd.AddRange(FloorIndices.Select(x => x + indOff)); @@ -509,12 +541,13 @@ public FSOF GetFSOF(GraphicsDevice gd, World world, Blueprint bp, Action onNight tVerts.AddRange(RoofVerts.Select(x => new DGRP3DVert(x.Position, Vector3.Zero, x.TextureCoordinate))); tInd.AddRange(RoofIndices.Select(x => x + indOff)); - DGRP3DVert.GenerateNormals(false, tVerts, FloorIndices); + DGRP3DVert.GenerateNormals(false, tVerts, tInd); result.FloorVertices = tVerts.ToArray(); result.FloorIndices = tInd.ToArray(); var tempVerts = WallVerts.Select(x => new DGRP3DVert(x.Position, Vector3.Zero, x.TextureCoordinate)).ToList(); - DGRP3DVert.GenerateNormals(false, tempVerts, WallIndices); + var vertsSpan = CollectionsMarshal.AsSpan(tempVerts); + DGRP3DVert.GenerateNormals(false, vertsSpan, WallIndices); result.WallVertices = tempVerts.ToArray(); result.WallIndices = WallIndices; @@ -526,6 +559,11 @@ public FSOF GetFSOF(GraphicsDevice gd, World world, Blueprint bp, Action onNight result.NightWallTextureData = TexToData(WallTarget, compressed); result.NightLightColor = world.State.OutsideColor; + FloorTexture.Dispose(); + WallTarget.Dispose(); + + gd.Indices = null; + return result; } @@ -545,8 +583,9 @@ private void BleedRect(Color[] img, Rectangle rect, int width, int height) var h = rect.Height; var lo = 0; var w = rect.Width; - if (rect.X > 0) { - int i = rect.Y*width + rect.X; + if (rect.X > 0) + { + int i = rect.Y * width + rect.X; for (int y = 0; y < h; y++) { img[i - 1] = img[i]; @@ -652,17 +691,18 @@ public void AppendOBJ(StreamWriter io, string filename, int indCount, Vector3? o if (TexBase == null) { SaveOBJData(io, WallVerts, WallIndices, ref indCount, LotName + "_walls"); - SaveOBJData(io, RoofVerts, RoofIndices, ref indCount, LotName + ((RoofOnFloor) ? "_floor":"_roof")); - } else + SaveOBJData(io, RoofVerts, RoofIndices, ref indCount, LotName + ((RoofOnFloor) ? "_floor" : "_roof")); + } + else { - SaveOBJData(io, WallVerts, WallIndices, ref indCount, "TEX_"+TexBase); - SaveOBJData(io, RoofVerts, RoofIndices, ref indCount, "TEX_"+(TexBase+((RoofOnFloor)?1:2))); + SaveOBJData(io, WallVerts, WallIndices, ref indCount, "TEX_" + TexBase); + SaveOBJData(io, RoofVerts, RoofIndices, ref indCount, "TEX_" + (TexBase + ((RoofOnFloor) ? 1 : 2))); } } for (int i = 0; i < FloorsUsed; i++) { //save each floor. offset the floor for each level - var floorName = (TexBase == null)?(LotName + "_floor"): "TEX_" + (TexBase+1); + var floorName = (TexBase == null) ? (LotName + "_floor") : "TEX_" + (TexBase + 1); var posOffset = i * 2.95f; var tcOffset = new Vector2((i % 3) / 3f, (i / 3) / 2f); var o = off ?? Vector3.Zero; @@ -691,7 +731,7 @@ public void SaveMTL(Stream stream, string path) public void AppendMTL(StreamWriter io, string path) { var tex = TexBase != null; - SaveMTLData(io, path, tex?("TEX_" + (TexBase)):(LotName + "_walls"), WallTarget); + SaveMTLData(io, path, tex ? ("TEX_" + (TexBase)) : (LotName + "_walls"), WallTarget); if (!RoofOnFloor) SaveMTLData(io, path, tex ? ("TEX_" + (TexBase + 2)) : (LotName + "_roof"), RoofTexture); SaveMTLData(io, path, tex ? ("TEX_" + (TexBase + 1)) : (LotName + "_floor"), FloorTexture); } @@ -762,7 +802,7 @@ public bool TryAdd(LotFacadeWall wall) wall.EffectiveLength = effectiveLength; Walls.Add(wall); return true; - } + } } } @@ -779,11 +819,9 @@ public LotFacadeWall(Vector2[] points, Room room) { Room = room; Points = points; - PhysicalLength = (Points[0] - Points[1]).Length()/16f; + PhysicalLength = (Points[0] - Points[1]).Length() / 16f; Length = Math.Min(MAX_WALL_WIDTH * WALL_WIDTH, (int)Math.Round(PhysicalLength * WALL_WIDTH)); } } } - - } diff --git a/TSOClient/tso.world/LMap/LMapBatch.cs b/TSOClient/tso.world/LMap/LMapBatch.cs index 0223031d6..b43ab3216 100644 --- a/TSOClient/tso.world/LMap/LMapBatch.cs +++ b/TSOClient/tso.world/LMap/LMapBatch.cs @@ -7,9 +7,6 @@ using FSO.LotView.RC; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using System; -using System.Collections.Generic; -using System.Linq; namespace FSO.LotView.LMap { @@ -103,8 +100,6 @@ public LMapBatch(GraphicsDevice device, int res) this.GradEffect = WorldContent.Grad2DEffect; this.LightEffect = WorldContent.Light2DEffect; - - InitBasicData(); } public void SetMapLayout(int width, int height) @@ -153,19 +148,20 @@ public void Init(Blueprint blueprint) if (w > 64 && FSOEnvironment.SoftwareDepth) ultra = false; - resPerTile = ultra ? targetResPerTile : targetResPerTile/2; + resPerTile = ultra ? targetResPerTile : targetResPerTile / 2; var wl = resPerTile * (w - borderSize); var wh = resPerTile * (h - borderSize); Dispose(); + InitBasicData(); ShadowTarg = new RenderTarget2D(GD, wl, wh, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); - ObjShadowTarg = new RenderTarget2D(GD, (ultra)?(wl*2):wl, (ultra)?(wh*2):wh, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + ObjShadowTarg = new RenderTarget2D(GD, (ultra) ? (wl * 2) : wl, (ultra) ? (wh * 2) : wh, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); LightMap = new RenderTarget2D(GD, (wl * 3), (wh * 2), false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); //just ground floor for now. ShadowTargBlit = new SpriteBatch(GD); Projection = Matrix.CreateOrthographicOffCenter(new Rectangle(0, 0, (w - borderSize) * 16, (h - borderSize) * 16), -10, 10); - if (directional) LightMapDirection = new RenderTarget2D(GD, (w - borderSize)*3*4, (h - borderSize)*2*4, false, SurfaceFormat.HalfVector4, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + if (directional) LightMapDirection = new RenderTarget2D(GD, (w - borderSize) * 3 * 4, (h - borderSize) * 2 * 4, false, SurfaceFormat.HalfVector4, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); //initialize lighteffect with default params LightEffect.shadowMap = ShadowTarg; @@ -232,7 +228,7 @@ private void AddDirtyRoom(ushort id, bool important) public void InvalidateOutdoors() { //if the outside color is too different from the last, we need to invalidate all instead. - if (ColorDiff(Blueprint.OutsideColor, LastOutsideColor) > 20 || + if (ColorDiff(Blueprint.OutsideColor, LastOutsideColor) > 20 || (Blueprint.OutsideColor == Color.White && LastOutsideColor != Color.White)) { InvalidateAll(); @@ -347,7 +343,7 @@ public void RedrawAll(WorldState state, int floorLimit) if (LightMapDirection != null) { GD.SetRenderTarget(LightMapDirection); - GD.Clear(Color.TransparentBlack); + GD.Clear(ColorExtensions.TransparentBlack); } for (int i = 0; i < rooms.Count; i++) @@ -527,10 +523,10 @@ public void DrawRoom(Room room, RoomLighting lighting, bool clear) GD.ScissorRectangle = DrawRect; LightEffect.LightColor = Color.White.ToVector4() * outFactor.W; LightEffect.ShadowPowers = new Vector2(0.75f, 0.6f) * light.ShadowMultiplier; - LightEffect.LightHeight = 1f/(float)Blueprint.Width; + LightEffect.LightHeight = 1f / (float)Blueprint.Width; LightEffect.LightPosition = light.LightPos / (size * 16f); //in position space (0,1) - LightEffect.LightDirection = new Vector3(-SunVector.Z, SunVector.Y*-1, SunVector.X); + LightEffect.LightDirection = new Vector3(-SunVector.Z, SunVector.Y * -1, SunVector.X); LightEffect.LightSize = float.MaxValue; //in position space (0,1) LightEffect.IsOutdoors = true; @@ -594,7 +590,7 @@ public void DrawRoom(Room room, RoomLighting lighting, bool clear) LightEffect.LightSize = light.LightSize / (size * 16f); //in position space (0,1) var l = light.LightColor.ToVector4(); l.W = (l.X + l.Y + l.Z) / 3; - + if (light.OutdoorsColor) l = Vector4.Multiply(l, outFactor); else l *= 0.70f; LightEffect.LightColor = l * light.LightIntensity; @@ -607,10 +603,11 @@ public void DrawRoom(Room room, RoomLighting lighting, bool clear) if (WorldConfig.Current.UltraLighting) { - LightEffect.BlurMin = (light.OutdoorsColor)?(1 / (Blueprint.Width*9f)):0; + LightEffect.BlurMin = (light.OutdoorsColor) ? (1 / (Blueprint.Width * 9f)) : 0; LightEffect.BlurMax = (1 / (Blueprint.Width * 5f)); passes[5].Apply(); - } else + } + else passes[0].Apply(); GD.SetVertexBuffer(LightBuf); @@ -641,7 +638,7 @@ public void MultiplyOutdoors(Rectangle bigBounds) DrawRect.Offset(ScissorBase); for (int i = 0; i < (LightMapDirection != null ? 4 : 1); i++) { - GD.SetRenderTarget((i==0)?LightMap:LightMapDirection); + GD.SetRenderTarget((i == 0) ? LightMap : LightMapDirection); if (i == 1) DrawRect = ScaleDirectionScissor(DrawRect); GD.ScissorRectangle = DrawRect; @@ -651,7 +648,7 @@ public void MultiplyOutdoors(Rectangle bigBounds) EffectPassCollection passes = effect.Techniques[tech].Passes; var l = Blueprint.OutsideColor.ToVector4(); l.W = (l.X + l.Y + l.Z) / 3; - if (i >= 2) effect.LightColor = new Vector4(new Vector3(Math.Abs(SunVector.Z), Math.Abs(SunVector.Y), Math.Abs(SunVector.X)) * l.W * ((i==3)?-1:1), l.W); + if (i >= 2) effect.LightColor = new Vector4(new Vector3(Math.Abs(SunVector.Z), Math.Abs(SunVector.Y), Math.Abs(SunVector.X)) * l.W * ((i == 3) ? -1 : 1), l.W); else effect.LightColor = l; GD.BlendState = MulBlend; passes[2].Apply(); @@ -724,12 +721,12 @@ public Matrix GetLightMat(LightData pointLight) var height = pointLight.Height; //lights are assumed to be in the middle - var tan = ((Blueprint.Width- borderSize) /2f) / height; + var tan = ((Blueprint.Width - borderSize) / 2f) / height; var fov = (float)Math.Atan(tan); var lpos = new Vector2(pointLight.LightPos.X / 16f, pointLight.LightPos.Y / 16f); //return Matrix.CreateTranslation(-lpos.X, -lpos.Y, height) * Matrix.CreatePerspectiveFieldOfView(fov, 1, 0.01f, 3f) * Matrix.CreateTranslation(lpos.X, lpos.Y, 0); - var mat = Matrix.CreateTranslation(-(lpos.X), -(lpos.Y), -height) * ProjFromTan(tan, 1, 0.01f, height) * Matrix.CreateScale(1, -1f, 1) * Matrix.CreateTranslation(lpos.X / (Blueprint.Width - borderSize) *2 - 1f, -(lpos.Y / ((Blueprint.Height - borderSize) /2f) - 1f), 0); + var mat = Matrix.CreateTranslation(-(lpos.X), -(lpos.Y), -height) * ProjFromTan(tan, 1, 0.01f, height) * Matrix.CreateScale(1, -1f, 1) * Matrix.CreateTranslation(lpos.X / (Blueprint.Width - borderSize) * 2 - 1f, -(lpos.Y / ((Blueprint.Height - borderSize) / 2f) - 1f), 0); return mat; } @@ -739,7 +736,7 @@ public Matrix GetLightMat(LightData pointLight) ColorBlendFunction = BlendFunction.Max, ColorDestinationBlend = Blend.One, }; - + public BlendState MinBlend = new BlendState() { AlphaBlendFunction = BlendFunction.Min, @@ -792,8 +789,8 @@ public void CreateOutsideIfMissing() if (OutsideShadowTarg == null || OutsideShadowTarg.IsDisposed) { var div = ShadowTargQualityDivider; - OutsideShadowTarg = new RenderTarget2D(GD, (ShadowTarg.Width*2)/div, (ShadowTarg.Height*2) / div, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); - OutsideShadowTargPost = new RenderTarget2D(GD, (ShadowTarg.Width*2) / div, (ShadowTarg.Height*2) / div, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + OutsideShadowTarg = new RenderTarget2D(GD, (ShadowTarg.Width * 2) / div, (ShadowTarg.Height * 2) / div, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); + OutsideShadowTargPost = new RenderTarget2D(GD, (ShadowTarg.Width * 2) / div, (ShadowTarg.Height * 2) / div, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); LightEffect.SSAASize = new Vector2(1f / OutsideShadowTarg.Width, 1f / OutsideShadowTarg.Height); LastShadowTargQualityDivider = ShadowTargQualityDivider; } @@ -823,7 +820,7 @@ public void DrawWallShadows(List walls, LightData pointLight) if (OutShadowFloor == pointLight.Level) return; OutShadowFloor = pointLight.Level; GD.SetRenderTarget(OutsideShadowTarg); - var rect = new Rectangle(DrawRect.X * 2, DrawRect.Y * 2, DrawRect.Width*2, DrawRect.Height*2); + var rect = new Rectangle(DrawRect.X * 2, DrawRect.Y * 2, DrawRect.Width * 2, DrawRect.Height * 2); GD.ScissorRectangle = rect; GD.Clear(Color.Black); var effect = this.GradEffect; @@ -881,21 +878,22 @@ public void DrawWallShadows(List walls, LightData pointLight) seffect.hardenBias = new Vector2(harden, harden * 0.5f); seffect.noiseTexture = TextureGenerator.GetUniformNoise(GD); - for (int i=0; i<4; i++) + for (int i = 0; i < 4; i++) { seffect.SetTechnique((int)SpriteEffectTechniques.ShadowSeparableBlit1 + i); RenderTarget2D tex; - if (i%2 == 0) + if (i % 2 == 0) { GD.SetRenderTarget(OutsideShadowTargPost); tex = OutsideShadowTarg; - } else + } + else { GD.SetRenderTarget(OutsideShadowTarg); tex = OutsideShadowTargPost; } - ShadowTargBlit.Begin(blendState: (i == 1)? OpaqueBA : BlendState.Opaque, effect: seffect, samplerState: SamplerState.PointClamp); + ShadowTargBlit.Begin(blendState: (i == 1) ? OpaqueBA : BlendState.Opaque, effect: seffect, samplerState: SamplerState.PointClamp); ShadowTargBlit.Draw(tex, new Rectangle(0, 0, tex.Width, tex.Height), Color.White); ShadowTargBlit.End(); } @@ -954,7 +952,7 @@ public void Draw3DObjShadows(LightData pointLight, bool clear) //we doubled the shadow resolution, so this is different. var dr = DrawRect; - GD.ScissorRectangle = new Rectangle(dr.X*2, dr.Y*2, dr.Width*2, dr.Height*2); + GD.ScissorRectangle = new Rectangle(dr.X * 2, dr.Y * 2, dr.Width * 2, dr.Height * 2); GD.BlendState = MaxBlendGreen; if (clear) GD.Clear(Color.Black); @@ -965,7 +963,7 @@ public void Draw3DObjShadows(LightData pointLight, bool clear) var outside = pointLight.LightType == LightType.OUTDOORS; if (outside) - effect.ViewProjection = Matrix.CreateScale(1 / 3f, -1/9f, 1 / 3f) * Matrix.CreateRotationX((float)Math.PI/-2) * GetSunlightMat(pointLight) * Projection; + effect.ViewProjection = Matrix.CreateScale(1 / 3f, -1 / 9f, 1 / 3f) * Matrix.CreateRotationX((float)Math.PI / -2) * GetSunlightMat(pointLight) * Projection; else effect.ViewProjection = Matrix.CreateScale(1 / 3f, -1 / 3f, 1 / 3f) * Matrix.CreateRotationX((float)Math.PI / -2) * GetLightMat(pointLight); @@ -974,7 +972,8 @@ public void Draw3DObjShadows(LightData pointLight, bool clear) List objs; - if (outside) { + if (outside) + { objs = new List(); for (int i = 0; i < Blueprint.Rooms.Count; i++) { @@ -985,7 +984,8 @@ public void Draw3DObjShadows(LightData pointLight, bool clear) objs.AddRange(Blueprint.Light[i].Components); } } - } else + } + else { objs = Blueprint.Light[pointLight.Room].Components; } diff --git a/TSOClient/tso.world/LMap/ShadowGeometry.cs b/TSOClient/tso.world/LMap/ShadowGeometry.cs index 8bfb40ba0..d1472bf3b 100644 --- a/TSOClient/tso.world/LMap/ShadowGeometry.cs +++ b/TSOClient/tso.world/LMap/ShadowGeometry.cs @@ -1,8 +1,5 @@ using FSO.Common.Utils; using Microsoft.Xna.Framework; -using System; -using System.Collections.Generic; -using System.Linq; namespace FSO.LotView.LMap { @@ -40,7 +37,8 @@ public GradMesh GenerateObjShadows(List walls, LightData pointLight) foreach (var i in walls) { if (i.Contains(pointLight.LightPos)) topDown.Add(i); - else { + else + { projWalls.Add(ClosestPtsClockwise(i, pointLight.LightPos)); var ctr = i.Center; ctrWidths.Add(new Vector3(ctr.X, ctr.Y, (float)Math.Sqrt(i.Width * i.Width + i.Height * i.Height) / 2.5f)); @@ -104,13 +102,15 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData if (light.LightType == LightType.OUTDOORS) { leftNorm = midNorm = rightNorm = light.LightDir; - } else { + } + else + { leftNorm = pts.Pt0 - pointLight; leftNorm.Normalize(); midNorm = mid - pointLight; midNorm.Normalize(); rightNorm = pts.Pt2 - pointLight; rightNorm.Normalize(); } - leftFac = leftNorm* distM; rightFac = rightNorm* distM; midFac = midNorm * distM; + leftFac = leftNorm * distM; rightFac = rightNorm * distM; midFac = midNorm * distM; EllipseDesc ellipse; if (ctrWidths != null) @@ -119,7 +119,7 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData var ctW = ctrWidths[j++]; var mid2 = new Vector2(ctW.X, ctW.Y); float height; - if (light.LightType == LightType.OUTDOORS) height = 16*light.FalloffMultiplier; + if (light.LightType == LightType.OUTDOORS) height = 16 * light.FalloffMultiplier; else height = (mid2 - pointLight).Length() * 16 / ((16 * 3) - 16); var midNorm2 = mid2 - pointLight; midNorm2.Normalize(); var largeDim = (ctW.Z + height) * midNorm2; @@ -140,11 +140,11 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData var midN2 = light.LightDir; var dot = Vector2.Dot(perp, midN2); -//if (Math.Abs(dot) < 0.35) continue; + //if (Math.Abs(dot) < 0.35) continue; var length = distM * (dot); var spos = pts.Pt0;// - ((dot>0)?perp:(-perp))*2; perp *= length; - ellipse = new EllipseDesc { pos = spos, dimensions = new Vector4(length*length, 0, perp.X, perp.Y) }; + ellipse = new EllipseDesc { pos = spos, dimensions = new Vector4(length * length, 0, perp.X, perp.Y) }; } else ellipse = basicDesc; @@ -174,9 +174,9 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData //yes //continue; var conectr = pts.Pt2 + rightNorm; - vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, conectr, Color.TransparentBlack, hc, p/2, ellipse); - vertices[vert++] = GradVertex.ConeVert(rightpen1, pts.Pt2, conectr, Color.TransparentBlack, hc, p/2, ellipse); - vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, conectr, Color.TransparentBlack, hc, p/2, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, conectr, ColorExtensions.TransparentBlack, hc, p / 2, ellipse); + vertices[vert++] = GradVertex.ConeVert(rightpen1, pts.Pt2, conectr, ColorExtensions.TransparentBlack, hc, p / 2, ellipse); + vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, conectr, ColorExtensions.TransparentBlack, hc, p / 2, ellipse); for (int i = 0; i < 3; i++) indices[index++] = baseIdx + i; continue; } @@ -189,9 +189,9 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData //yes //continue; var conectr = pts.Pt0 + leftNorm; - vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, conectr, Color.TransparentBlack, hc, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, conectr, Color.TransparentBlack, hc, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(leftpen2, pts.Pt0, conectr, Color.TransparentBlack, hc, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, conectr, ColorExtensions.TransparentBlack, hc, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, conectr, ColorExtensions.TransparentBlack, hc, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(leftpen2, pts.Pt0, conectr, ColorExtensions.TransparentBlack, hc, p, ellipse); for (int i = 0; i < 3; i++) indices[index++] = baseIdx + i; continue; } @@ -225,13 +225,13 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData //penumbras - vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(leftpen2, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(leftpen2, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(rightpen1, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(rightpen1, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); for (int i = 0; i < 6; i++) indices[index++] = baseIdx + i; } @@ -240,7 +240,7 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData var inter = pts.Pt0 + a * t; var distant = mid + Vector2.Normalize(inter - pointLight) * distM; vertices[vert++] = GradVertex.SolidVert(pts.Pt0, Color.White, ellipse); - vertices[vert++] = GradVertex.SolidVert(pts.Pt0 + a*t, Color.White, ellipse); + vertices[vert++] = GradVertex.SolidVert(pts.Pt0 + a * t, Color.White, ellipse); vertices[vert++] = GradVertex.SolidVert(pts.Pt2, Color.White, ellipse); vertices[vert++] = GradVertex.SolidVert(pts.Pt1, Color.White, ellipse); @@ -251,15 +251,15 @@ public GradMesh GenerateShadows(IEnumerable volumes, LightData //penumbras: each penumbra becomes two tris as they intersect - vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(inter, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(distant, pts.Pt0, leftpen2, Color.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt0, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(leftpen1, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(inter, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(distant, pts.Pt0, leftpen2, ColorExtensions.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(inter, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); - vertices[vert++] = GradVertex.ConeVert(distant, pts.Pt2, rightpen1, Color.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(pts.Pt2, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(rightpen2, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(inter, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); + vertices[vert++] = GradVertex.ConeVert(distant, pts.Pt2, rightpen1, ColorExtensions.TransparentBlack, Color.White, p, ellipse); indices[index++] = baseIdx; indices[index++] = baseIdx + 1; indices[index++] = baseIdx + 2; indices[index++] = baseIdx + 1; indices[index++] = baseIdx + 3; indices[index++] = baseIdx + 2; @@ -298,25 +298,31 @@ private static ClockwisePoints ClosestPtsClockwise(Rectangle rect, Vector2 point diff = pt - point; ccwPts.Pt1 = pt; var dir = (float)DirectionUtils.Difference(Math.Atan2(diff.Y, diff.X), dir1); - if (dir > bestDir) { bestDir = dir; bestInd = 1; }; - if (dir < bestDirO) { bestDirO = dir; bestIndO = 1; }; + if (dir > bestDir) { bestDir = dir; bestInd = 1; } + ; + if (dir < bestDirO) { bestDirO = dir; bestIndO = 1; } + ; pt = new Vector2(rect.Right, rect.Bottom); diff = pt - point; ccwPts.Pt2 = pt; dir = (float)DirectionUtils.Difference(Math.Atan2(diff.Y, diff.X), dir1); - if (dir > bestDir) { bestDir = dir; bestInd = 2; }; - if (dir < bestDirO) { bestDirO = dir; bestIndO = 2; }; + if (dir > bestDir) { bestDir = dir; bestInd = 2; } + ; + if (dir < bestDirO) { bestDirO = dir; bestIndO = 2; } + ; pt = new Vector2(rect.Right, rect.Top); diff = pt - point; ccwPts.Pt3 = pt; dir = (float)DirectionUtils.Difference(Math.Atan2(diff.Y, diff.X), dir1); - if (dir > bestDir) { bestDir = dir; bestInd = 3; }; - if (dir < bestDirO) { bestDirO = dir; bestIndO = 3; }; + if (dir > bestDir) { bestDir = dir; bestInd = 3; } + ; + if (dir < bestDirO) { bestDirO = dir; bestIndO = 3; } + ; var result = new ClockwisePoints(); - for (int i=0; i<3; i++) + for (int i = 0; i < 3; i++) { result[i] = ccwPts[bestInd]; if (bestInd != bestIndO) diff --git a/TSOClient/tso.world/Model/Blueprint.cs b/TSOClient/tso.world/Model/Blueprint.cs index 342daab08..d2374583b 100644 --- a/TSOClient/tso.world/Model/Blueprint.cs +++ b/TSOClient/tso.world/Model/Blueprint.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; +using FSO.Common; using FSO.LotView.Components; -using FSO.LotView.Utils; -using Microsoft.Xna.Framework; +using FSO.LotView.Effects; using FSO.LotView.LMap; using FSO.LotView.RC; -using FSO.Common; -using FSO.LotView.Effects; +using FSO.LotView.Utils; +using Microsoft.Xna.Framework; +using System; +using System.Collections.Generic; namespace FSO.LotView.Model { @@ -16,6 +16,7 @@ namespace FSO.LotView.Model /// public class Blueprint { + public const float TerrainFactorConst = 3 / 160f; public List Damage = new List(); public int Width; @@ -113,13 +114,14 @@ private Color PowColor(Color col, float pow) public short[] Altitude; public short[] AltitudeCenters; - public float TerrainFactor = 3 / 160f; + public float TerrainFactor = TerrainFactorConst; public int BaseAlt; // AF2022, obviously getting removed in a day public SM64Component SM64; - public Blueprint(int width, int height){ + public Blueprint(int width, int height) + { this.Width = width; this.Height = height; @@ -133,7 +135,7 @@ public Blueprint(int width, int height){ this.WCRC = new WallComponentRC(); WCRC.blueprint = this; } - + RoomColors = new Color[65536]; this.WallsAt = new List[Stories]; this.Walls = new WallTile[Stories][]; @@ -141,7 +143,7 @@ public Blueprint(int width, int height){ this.Floors = new FloorTile[Stories][]; - for (int i=0; i(); this.Walls[i] = new WallTile[numTiles]; @@ -156,31 +158,79 @@ public Blueprint(int width, int height){ this.SM64 = new SM64Component(this); } + public void AdjustBaseAlt(int altDiff) + { + float heightDiff = altDiff * TerrainFactor * -3; + + WCRC?.AdjustHeight(heightDiff / 3); + RoofComp?.AdjustHeight(heightDiff); + } + + public void BoundAltPoint(ref int x, ref int y) + { + x = Math.Max(1, Math.Min(Width - 1, x)); + y = Math.Max(1, Math.Min(Height - 1, y)); + } + public float GetAltitude(int x, int y) { - if (x <= 0 || y <= 0) return 0f; - return (AltitudeCenters[((y % Height) * Width + (x % Width))] - BaseAlt) * TerrainFactor; + BoundAltPoint(ref x, ref y); + if (AltitudeCenters == null) return 0; + return (AltitudeCenters[y * Width + x] - BaseAlt) * TerrainFactor; } public float GetAltPoint(int x, int y) { //x += 1; y += 1; - if (x <= 0 || y <= 0) return 0f; - return (Altitude[((y % Height) * Width + (x % Width))]); + BoundAltPoint(ref x, ref y); + return Altitude[y * Width + x]; + } + + public float InterpAltitudeWithSubworlds(Vector3 Position) + { + if (Position.X > 0 && Position.X < Width && Position.Y > 0 && Position.Y < Height) + { + return InterpAltitude(Position); + } + + // Try find a subworld to get the altitude from + + foreach (var subworld in SubWorlds) + { + var newPos = Position + new Vector3(subworld.GlobalPosition, 0); + var subBp = subworld.Architecture.Blueprint; + + if (newPos.X > 0 && newPos.X < subBp.Width && newPos.Y > 0 && newPos.Y < subBp.Height) + { + return subBp.InterpAltitude(newPos); + } + } + + return InterpAltitude(Position); } public float InterpAltitude(Vector3 Position) { if (Altitude == null) return 0f; - var baseX = (int)Math.Max(1, Math.Min(Width-1, Position.X)); - var baseY = (int)Math.Max(1, Math.Min(Height-1, Position.Y)); + var baseX = (int)Math.Max(1, Math.Min(Width-1, Math.Floor(Position.X))); + var baseY = (int)Math.Max(1, Math.Min(Height-1, Math.Floor(Position.Y))); if (baseX < 0 || baseY < 0) return 0; var nextX = (int)Math.Max(1, Math.Min(Width - 1, Math.Ceiling(Position.X))); var nextY = (int)Math.Max(1, Math.Min(Height - 1, Math.Ceiling(Position.Y))); var xLerp = Position.X % 1f; var yLerp = Position.Y % 1f; + if (xLerp < 0) + { + xLerp = -xLerp; + } + + if (yLerp < 0) + { + yLerp = -yLerp; + } + var by = (baseY % Height) * Width; var bx = (baseX % Width); var ny = (nextY % Height) * Width; @@ -340,6 +390,38 @@ public FloorTile GetFloor(short tileX, short tileY, sbyte level) return Floors[level-1][offset]; } + public ushort GetPreciseFloor(Vector3 tile) + { + if (!TileInbounds(new Vector2(tile.X, tile.Y))) + { + return 0; + } + + short tileX = (short)tile.X; + short tileY = (short)tile.Y; + float floorRelativeHeight = tile.Z - InterpAltitude(tile); + + sbyte level = (sbyte)(Math.Max(0, Math.Min(Stories - 1, (int)(floorRelativeHeight / 2.95f))) + 1); + + var wall = GetWall(tileX, tileY, level); + if ((wall.Segments & WallSegments.VerticalDiag) > 0) + { + if ((tile.X % 1) - (tile.Y % 1) > 0) + return wall.TopLeftPattern; + else + return wall.TopLeftStyle; + } + else if ((wall.Segments & WallSegments.HorizontalDiag) > 0) + { + if ((tile.X % 1) + (tile.Y % 1) > 15) + return wall.TopLeftPattern; + else + return wall.TopLeftStyle; + } + + return GetFloor(tileX, tileY, level).Pattern; + } + public bool TileInbounds(Vector2 tile) { return (tile.X >= 0 && tile.Y >= 0 && tile.X < Width && tile.Y < Height); @@ -456,6 +538,58 @@ public byte[] GetIndoors() return Indoors; } + public bool IsIndoorsPrecise(Vector2 pos, int floor) + { + Point testPos = pos.ToPoint(); + + if (testPos.X >= 0 && testPos.X < Width && testPos.Y >= 0 && testPos.Y < Height) + { + int offset = testPos.X + testPos.Y * Width; + var room = RoomMap[floor][offset]; + + if ((ushort)room != (ushort)((room >> 16) & 0x7FFF)) + { + // Need to evaluate the diagonal orientation. It's stored in the wall segments in TS1. + var wall = Walls[floor][offset]; + float xFrac = pos.X - testPos.X; + float yFrac = pos.Y - testPos.Y; + + if (wall.Segments == WallSegments.HorizontalDiag) + { + if (xFrac + yFrac > 1) + { + room = ((room >> 16) & 0x7FFF); + } + } + else if (wall.Segments == WallSegments.VerticalDiag) + { + if (xFrac - yFrac < 0) + { + room = ((room >> 16) & 0x7FFF); + } + } + } + + return !Rooms[Rooms[(ushort)room].Base].IsOutside; + } + + return false; + } + + public bool IsIndoorsPrecise(Vector3 pos) + { + var terrainHeight = InterpAltitude(pos); + var effectiveHeight = pos.Z - terrainHeight; + + int floor = (int)Math.Floor(effectiveHeight / 2.95f); + if (floor < 0 || floor >= Stories) + { + return false; + } + + return IsIndoorsPrecise(new Vector2(pos.X, pos.Y), floor); + } + private byte[] GrassMask; public byte[] GetGrassMask() { diff --git a/TSOClient/tso.world/Model/BlueprintChanges.cs b/TSOClient/tso.world/Model/BlueprintChanges.cs index 91ba6550f..9633986be 100644 --- a/TSOClient/tso.world/Model/BlueprintChanges.cs +++ b/TSOClient/tso.world/Model/BlueprintChanges.cs @@ -31,7 +31,6 @@ public class BlueprintChanges public bool StaticObjectDirty; public bool DrawImmediate; - public bool UpdateColor; public bool Arch2D; @@ -47,7 +46,6 @@ public BlueprintChanges(Blueprint blueprint) public void PreDraw(GraphicsDevice gd, WorldState state) { DrawImmediate = state.ForceImmediate; - UpdateColor = false; if (state.CameraMode < CameraRenderMode._3D) { state.CameraMode = (state.Cameras.Safe2D) ? CameraRenderMode._2D : CameraRenderMode._2DRotate; @@ -97,7 +95,6 @@ public void PreDraw(GraphicsDevice gd, WorldState state) if ((Dirty & BlueprintGlobalChanges.LIGHTING_ANY) > 0) { - UpdateColor = true; Blueprint.GenerateRoomLights(); state.OutsideColor = Blueprint.RoomColors[1]; state.OutsidePx.SetData(new Color[] { new Color(Blueprint.OutsideColor, (Blueprint.OutsideColor.R + Blueprint.OutsideColor.G + Blueprint.OutsideColor.B) / (255 * 3f)) }); @@ -167,7 +164,6 @@ public void PreDraw(GraphicsDevice gd, WorldState state) } if (state.Light != null) { - UpdateColor = true; state.Light.InvalidateAll(); } Blueprint.Indoors = null; @@ -239,6 +235,22 @@ public void SetFlag(BlueprintGlobalChanges flag) { Dirty |= flag; } + + public void Preload(GraphicsDevice gd, WorldState state) + { + if ((Dirty & BlueprintGlobalChanges.FLOOR_CHANGED) > 0) + { + Blueprint.FloorGeom.FullReset(gd, state.BuildMode > 1); + Dirty &= ~BlueprintGlobalChanges.FLOOR_CHANGED; + } + + if ((Dirty & BlueprintGlobalChanges.WALL_CHANGED) > 0) + { + state.Platform.RecacheWalls(gd, state, false); + StaticSurfaceDirty = true; + Dirty &= ~BlueprintGlobalChanges.WALL_CHANGED; + } + } } [Flags] diff --git a/TSOClient/tso.world/Model/FloorTile.cs b/TSOClient/tso.world/Model/FloorTile.cs index be041fe00..5c8ae8805 100644 --- a/TSOClient/tso.world/Model/FloorTile.cs +++ b/TSOClient/tso.world/Model/FloorTile.cs @@ -1,5 +1,8 @@ -namespace FSO.LotView.Model +using System.Runtime.InteropServices; + +namespace FSO.LotView.Model { + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct FloorTile { public ushort Pattern; diff --git a/TSOClient/tso.world/Model/LotTypes.cs b/TSOClient/tso.world/Model/LotTypes.cs index 4d1d7204d..e1c74635c 100644 --- a/TSOClient/tso.world/Model/LotTypes.cs +++ b/TSOClient/tso.world/Model/LotTypes.cs @@ -2,77 +2,116 @@ namespace FSO.LotView.Model { - public static class LotTypeGrassInfo + public readonly struct LotTypeGrassInfo(Color lightGreen, Color lightBrown, Color darkGreen, Color darkBrown, Vector2 greenLengthDensity, Vector2 brownLengthDensity, int maxHeight, float baseDensity) { - public static Color[] LightGreen = { - new Color(80, 116, 59), - new Color(181, 171, 149), - new Color(126,96,70), - new Color(240,245,250), - new Color(0,0,255), + public readonly Color LightGreen = lightGreen; + public readonly Color LightBrown = lightBrown; + public readonly Color DarkGreen = darkGreen; + public readonly Color DarkBrown = darkBrown; + public readonly Vector2 GreenLengthDensity = greenLengthDensity; + public readonly Vector2 BrownLengthDensity = brownLengthDensity; + public readonly int MaxHeight = maxHeight; + public readonly float BaseDensity = baseDensity; - new Color(74,89,66), //TS1 Dark Grass - new Color(140,113,49), //TS1 Autumn Grass - new Color(240,245,250), //TS1 Cloud - }; - public static Color[] LightBrown = { - new Color(157, 117, 65), - new Color(196, 185, 162), - new Color(126,96,70), - new Color(240,245,250), - new Color(0,0,255), + public const float MinDetailLength = 0.05f; - new Color(90,69,41), //TS1 Dark Grass - new Color(115,73,33), //TS1 Autumn Grass - new Color(15,20,140), //TS1 Cloud - }; - public static Color[] DarkGreen = { - new Color(8, 52, 8), - new Color(115, 109, 95), - new Color(107,77,57), - new Color(180,180,190), - new Color(0,0,255), + public static LotTypeGrassInfo[] Info = + [ + // Grass + new( + lightGreen: new Color(80, 116, 59), + lightBrown: new Color(157, 117, 65), + darkGreen: new Color(8, 52, 8), + darkBrown: new Color(81, 60, 18), + greenLengthDensity: new Vector2(1, 1), + brownLengthDensity: new Vector2(MinDetailLength, 0.5f), + maxHeight: 6, + baseDensity: 1 + ), - new Color(21,30,13), //new Color(41,52,33), //TS1 Dark Grass - new Color(109,63,35), //new Color(123,85,41), //TS1 Autumn Grassi - new Color(180,180,190), //TS1 Cloud - }; - public static Color[] DarkBrown = { - new Color(81, 60, 18), - new Color(121, 114, 100), - new Color(107,77,57), - new Color(180,180,190), - new Color(0,0,255), + // Sand + new( + lightGreen: new Color(181, 171, 149), + lightBrown: new Color(196, 185, 162), + darkGreen: new Color(115, 109, 95), + darkBrown: new Color(121, 114, 100), + greenLengthDensity: new Vector2(MinDetailLength, 0.9f), + brownLengthDensity: new Vector2(MinDetailLength, 0.75f), + maxHeight: 1, + baseDensity: 1 + ), - new Color(64,69,14), //new Color(74,69,24), //TS1 Dark Grass - new Color(56,35,17), //new Color(82,52,24), //TS1 Autumn Grass - new Color(15,20,140), //TS1 Cloud - }; + // Rock + new( + lightGreen: new Color(126, 96, 70), + lightBrown: new Color(126, 96, 70), + darkGreen: new Color(107, 77, 57), + darkBrown: new Color(107, 77, 57), + greenLengthDensity: new Vector2(MinDetailLength, 1f), + brownLengthDensity: new Vector2(MinDetailLength, 1f), + maxHeight: 1, + baseDensity: 1 + ), - public static int[] Heights = - { - 6, - 1, - 1, - 1, - 0, + // Snow + new( + lightGreen: new Color(240, 245, 250), + lightBrown: new Color(240, 245, 250), + darkGreen: new Color(180, 180, 190), + darkBrown: new Color(180, 180, 190), + greenLengthDensity: new Vector2(MinDetailLength, 0.85f), + brownLengthDensity: new Vector2(MinDetailLength, 0.85f), + maxHeight: 1, + baseDensity: 1 + ), - 6, - 6, - 1 - }; + // Water (debug) + new( + lightGreen: new Color(0, 0, 255), + lightBrown: new Color(0, 0, 255), + darkGreen: new Color(0, 0, 255), + darkBrown: new Color(0, 0, 255), + greenLengthDensity: new Vector2(0, 1f), + brownLengthDensity: new Vector2(0, 1f), + maxHeight: 0, + baseDensity: 0 + ), - public static float[] GrassDensity = - { - 1f, - 1f, - 1f, - 1f, - 1f, + // TS1 Dark Grass + new( + lightGreen: new Color(74, 89, 66), + lightBrown: new Color(90, 69, 41), + darkGreen: new Color(21, 30, 13), + darkBrown: new Color(64, 69, 14), + greenLengthDensity: new Vector2(1, 1), + brownLengthDensity: new Vector2(MinDetailLength, 0.5f), + maxHeight: 6, + baseDensity: 0.8f + ), - 0.8f, - 0.8f, - 1f, - }; + // TS1 Autumn Grass + new( + lightGreen: new Color(140, 113, 49), + lightBrown: new Color(115, 73, 33), + darkGreen: new Color(109, 63, 35), + darkBrown: new Color(56, 35, 17), + greenLengthDensity: new Vector2(1, 1), + brownLengthDensity: new Vector2(MinDetailLength, 0.5f), + maxHeight: 6, + baseDensity: 0.8f + ), + + // Clouds + new( + lightGreen: new Color(240, 245, 250), + lightBrown: new Color(15, 20, 140), + darkGreen: new Color(180, 180, 190), + darkBrown: new Color(15, 20, 140), + greenLengthDensity: new Vector2(MinDetailLength, 1f), + brownLengthDensity: new Vector2(MinDetailLength, 1f), + maxHeight: 1, + baseDensity: 1 + ), + ]; } } diff --git a/TSOClient/tso.world/Model/WallTile.cs b/TSOClient/tso.world/Model/WallTile.cs index 28b67a17e..be542ff1b 100644 --- a/TSOClient/tso.world/Model/WallTile.cs +++ b/TSOClient/tso.world/Model/WallTile.cs @@ -1,5 +1,25 @@ -namespace FSO.LotView.Model +using System.Runtime.InteropServices; + +namespace FSO.LotView.Model { + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct WallTileSerialized + { + public WallSegments Segments; + + //the patterns of each side of the tile's wall. + public ushort TopLeftPattern; + public ushort TopRightPattern; + public ushort BottomLeftPattern; + public ushort BottomRightPattern; + + //the style of the wall at the top left and top right. bottom left and bottom right are to be obtained from the tiles in those directions. + //1 generally means "normal wall". Not sure how to deal with cutouts while keeping these as "normal wall". + public ushort TopLeftStyle; + public ushort TopRightStyle; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct WallTile { public WallSegments Segments; diff --git a/TSOClient/tso.world/Model/WeatherController.cs b/TSOClient/tso.world/Model/WeatherController.cs index bfb2e77dc..a1f7a1001 100644 --- a/TSOClient/tso.world/Model/WeatherController.cs +++ b/TSOClient/tso.world/Model/WeatherController.cs @@ -25,6 +25,7 @@ public class WeatherController public bool IsManual => (WeatherData & (1 << 8)) > 0; public WeatherType WeatherType => (WeatherType)((WeatherData >> 9) & 3); public bool IsThunder => (WeatherData & (1 << 11)) > 0; + public ParticleType ParticleType => Current?.Mode ?? ParticleType.GENERIC_BOX; public float[] ModeToIntensity = new float[] { @@ -72,6 +73,49 @@ private Vector4 SRGBToLinear(Vector4 col) public void Update() { + var enabled = WorldConfig.Current.Weather; + + var particle = UpdateLighting(); + + if (!particle.HasValue) + { + return; + } + + var ptype = particle.Value; + + if (WeatherIntensity > 0.01f && enabled) + { + bool isFaded = false; + //is the new weather different enough? does the old one need to be refreshed? + if (Current != null && (Current.Time > 100 || Math.Abs(Current.WeatherIntensity - WeatherIntensity) > 0.04f)) + { + Current.FadeProgress = 0; + isFaded = true; + Current = null; + } + if (Current == null) + { + Current = new ParticleComponent(Bp, Particles); + Current.Mode = ptype; + Current.FadeProgress = isFaded ? (float?)-1 : null; + Current.WeatherIntensity = WeatherIntensity; + Particles.Add(Current); + } + } + else + { + if (Current != null) + { + Current.FadeProgress = 0; + Current = null; + } + } + } + + public ParticleType? UpdateLighting() + { + var enabled = WorldConfig.Current.Weather; var now = DateTime.UtcNow; var i = Math.Min(((now.Minute * 60) + (now.Second)) / 150f, 1f); //DECEMBER TEMP: snow replace @@ -85,13 +129,12 @@ public void Update() var wint = Math.Min(1f, WeatherIntensity); FogColor = (color * new Color(0x80, 0xC0, 0xFF, 0xFF).ToVector4()) * (1 - wint * 0.75f) + LinearToSRGB(ocolor) * (wint * 0.75f); FogColor.W = (wint) * (15 * 75f) + (1 - wint) * (300f * 75f); - var enabled = WorldConfig.Current.Weather; ParticleType ptype; if (IsManual && !FinaleUtils.IsFinale()) { - if (WeatherData == LastWeatherData && enabled == LastEnabled) return; + if (WeatherData == LastWeatherData && enabled == LastEnabled) return null; LastWeatherData = WeatherData; LastEnabled = enabled; @@ -111,7 +154,7 @@ public void Update() } else { - if (LastI == i && LastHour == now.Hour && (Current?.Time ?? 0) < 100 && enabled == LastEnabled) return; + if (LastI == i && LastHour == now.Hour && (Current?.Time ?? 0) < 100 && enabled == LastEnabled) return null; var curInt = GetAutoWeatherIntensity(now); var lastInt = GetAutoWeatherIntensity(now - new TimeSpan(1, 0, 0)); @@ -125,6 +168,13 @@ public void Update() ptype = (ParticleType)(curInt / 3); } + UpdateTint(); + + return ptype; + } + + private void UpdateTint() + { OutsideWeatherTint = Color.Lerp(Color.White, new Color(159, 164, 181), Darken); if (Bp != null) @@ -132,40 +182,33 @@ public void Update() Bp.OutsideWeatherTint = new Color(159, 164, 181); Bp.OutsideWeatherTintP = Darken; } + } - if (WeatherIntensity > 0.01f && enabled) - { - bool isFaded = false; - //is the new weather different enough? does the old one need to be refreshed? - if (Current != null && (Current.Time > 100 || Math.Abs(Current.WeatherIntensity - WeatherIntensity) > 0.04f)) - { - Current.FadeProgress = 0; - isFaded = true; - Current = null; - } - if (Current == null) - { - Current = new ParticleComponent(Bp, Particles); - Current.Mode = ptype; - Current.FadeProgress = isFaded ? (float?)-1 : null; - Current.WeatherIntensity = WeatherIntensity; - Particles.Add(Current); - } - } - else + public void Inherit(WeatherController other) + { + Darken = other.Darken; + + UpdateTint(); + } + + public void SetWeather(short data) { + var oldData = WeatherData; + WeatherData = data; + + var isManual = IsManual; + var wasManual = (oldData & (1 << 8)) != 0; + + if (!isManual && wasManual) { + // Remove the manual weather instantly. if (Current != null) { - Current.FadeProgress = 0; + Current.FadeProgress = 1; Current = null; } } } - public void SetWeather(short data) { - WeatherData = data; - } - private int GetAutoWeatherIntensity(DateTime time) { if (FinaleUtils.IsFinale()) diff --git a/TSOClient/tso.world/Model/XmlHouse.cs b/TSOClient/tso.world/Model/XmlHouse.cs index 80aff2e61..ed934ffa4 100644 --- a/TSOClient/tso.world/Model/XmlHouse.cs +++ b/TSOClient/tso.world/Model/XmlHouse.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Xml; using System.Xml.Serialization; using System.IO; @@ -35,8 +36,13 @@ public static XmlHouseData Parse(string xmlFilePath) public static XmlHouseData Parse(Stream reader) { - XmlSerializer serialize = new XmlSerializer(typeof(XmlHouseData)); - return (XmlHouseData)serialize.Deserialize(reader); + XmlSerializer serializer = new XmlSerializer(typeof(XmlHouseData)); + var settings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Parse }; + + using (var xmlReader = XmlReader.Create(reader, settings)) + { + return (XmlHouseData)serializer.Deserialize(xmlReader); + } } public static void Save(string xmlFilePath, XmlHouseData data) @@ -190,7 +196,7 @@ public int _Segments } [Flags] - public enum WallSegments + public enum WallSegments : byte { TopLeft = 1, TopRight = 2, diff --git a/TSOClient/tso.world/Platform/WorldPlatform3D.cs b/TSOClient/tso.world/Platform/WorldPlatform3D.cs index 7eea1b3a0..acce659d0 100644 --- a/TSOClient/tso.world/Platform/WorldPlatform3D.cs +++ b/TSOClient/tso.world/Platform/WorldPlatform3D.cs @@ -295,7 +295,7 @@ public Texture2D GetObjectThumb(ObjectComponent[] objects, Vector3[] positions, obj.Direction = oldObjRot; obj.Room = oldRoom; obj.Container = oldContainer; - obj.UnmoddedPosition = oldObjPos; + obj.Position = oldObjPos; obj.OnRotationChanged(state); obj.OnZoomChanged(state); } diff --git a/TSOClient/tso.world/Properties/AssemblyInfo.cs b/TSOClient/tso.world/Properties/AssemblyInfo.cs deleted file mode 100644 index a6fd6c345..000000000 --- a/TSOClient/tso.world/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("FSO.LotView")] -[assembly: AssemblyProduct("FSO.LotView")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("8c1bef53-4a1a-4f0d-9ef5-f1667e379087")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TSOClient/tso.world/RC/WallComponentRC.cs b/TSOClient/tso.world/RC/WallComponentRC.cs index 42f1bf38a..7ad18df6c 100644 --- a/TSOClient/tso.world/RC/WallComponentRC.cs +++ b/TSOClient/tso.world/RC/WallComponentRC.cs @@ -25,6 +25,8 @@ public class WallComponentRC public Dictionary WallCache = new Dictionary(); public Dictionary WallStyleCache = new Dictionary(); + private float HeightAdjust = 0; + private Wall GetPattern(ushort id) { if (!WallCache.ContainsKey(id)) WallCache.Add(id, Content.Content.Get().WorldWalls.Get(id)); @@ -62,6 +64,8 @@ private bool TileIndoors(int x, int y, int level) public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool allowCut = true) { + HeightAdjust = 0; + var wallContent = Content.Content.Get().WorldWalls; var floorContent = Content.Content.Get().WorldFloors; if (!cutaway) Dispose(); @@ -103,75 +107,73 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool return allowCut && blueprint.Cutaway[index] ? 0.12f : 1; }; - for (short y = 0; y < blueprint.Height; y++) - { - for (short x = 0; x < blueprint.Height; x++) + Action addLineGeom = (int x, int y, Vector2 from, Vector2 to, ushort pattern, ushort style, int topMode, float starttc, float endtc, float aboveFloor) => { + var tex = world._2D.GetTexture(GetPattern(pattern)?.Near?.Frames[2]); + var mask = world._2D.GetTexture(GetStyle(style)?.WallsUpNear?.Frames[(topMode != 4) ? 0 : 2]); + + var g = Fetch(tex, mask, style, grp); + g.UseOffset = (topMode != 4); + + var p1 = new Vector3(from.X + x, from.Y + y, 0); + var a1 = blueprint.InterpAltitude(Vector3.Round(p1)) + (level - 1) * 2.95f; + p1.Z = a1; + var p2 = new Vector3(to.X + x, to.Y + y, 0); + var a2 = blueprint.InterpAltitude(Vector3.Round(p2)) + (level - 1) * 2.95f; + p2.Z = a2; + + //generate the geometry for this line + var l = level - 1; + var baseI = g.Verts.Count; + var h1 = (topMode == 4) ? 0.98f : GetWallHeight(p1); + var h2 = (topMode == 4) ? 0.98f : GetWallHeight(p2); + var col = (from.X == to.X) ? darker : white; + g.Verts.Add(new WallVertexRC(p1, col, new Vector3(starttc, l, aboveFloor))); + g.Verts.Add(new WallVertexRC(p2, col, new Vector3(endtc, l, aboveFloor))); + g.Verts.Add(new WallVertexRC(p1 + wallHeight * h1, col, new Vector3(starttc, h1 + l, aboveFloor))); + g.Verts.Add(new WallVertexRC(p2 + wallHeight * h2, col, new Vector3(endtc, h2 + l, aboveFloor))); + + g.Indices.Add(baseI); g.Indices.Add(baseI + 2); g.Indices.Add(baseI + 1); + g.Indices.Add(baseI + 2); g.Indices.Add(baseI + 3); g.Indices.Add(baseI + 1); + + if (topMode < 4) { + var g2 = Fetch(whitepx, whitepx, 0, grp); + baseI = g2.Verts.Count; + Vector3 toBack = Vector3.Zero; - Action addLineGeom = (Vector2 from, Vector2 to, ushort pattern, ushort style, int topMode, float starttc, float endtc, float aboveFloor) => { - var tex = world._2D.GetTexture(GetPattern(pattern)?.Near?.Frames[2]); - var mask = world._2D.GetTexture(GetStyle(style)?.WallsUpNear?.Frames[(topMode != 4)?0:2]); - - var g = Fetch(tex, mask, style, grp); - g.UseOffset = (topMode != 4); - - var p1 = new Vector3(from.X + x, from.Y + y, 0); - var a1 = blueprint.InterpAltitude(p1) + (level-1)*2.95f; - p1.Z = a1; - var p2 = new Vector3(to.X + x, to.Y + y, 0); - var a2 = blueprint.InterpAltitude(p2) + (level-1) * 2.95f; - p2.Z = a2; - - //generate the geometry for this line - var l = level - 1; - var baseI = g.Verts.Count; - var h1 = (topMode == 4) ? 0.98f : GetWallHeight(p1); - var h2 = (topMode == 4) ? 0.98f : GetWallHeight(p2); - var col = (from.X == to.X) ? darker : white; - g.Verts.Add(new WallVertexRC(p1, col, new Vector3(starttc, l, aboveFloor))); - g.Verts.Add(new WallVertexRC(p2, col, new Vector3(endtc, l, aboveFloor))); - g.Verts.Add(new WallVertexRC(p1+ wallHeight*h1, col, new Vector3(starttc, h1 + l, aboveFloor))); - g.Verts.Add(new WallVertexRC(p2+ wallHeight*h2, col, new Vector3(endtc, h2 + l, aboveFloor))); - - g.Indices.Add(baseI); g.Indices.Add(baseI + 2); g.Indices.Add(baseI + 1); - g.Indices.Add(baseI + 2); g.Indices.Add(baseI+3); g.Indices.Add(baseI + 1); - - if (topMode < 4) - { - var g2 = Fetch(whitepx, whitepx, 0, grp); - - baseI = g2.Verts.Count; - Vector3 toBack = Vector3.Zero; - - switch (topMode) - { - case 0: - toBack = new Vector3(-thickness*2, 0, 0); - break; - case 1: - toBack = new Vector3(0, -thickness * 2, 0); - break; - case 2: - toBack = new Vector3(thickness * -2, thickness * -2, 0); - break; - case 3: - toBack = new Vector3(thickness * 2, thickness * -2, 0); - break; - } + switch (topMode) + { + case 0: + toBack = new Vector3(-thickness * 2, 0, 0); + break; + case 1: + toBack = new Vector3(0, -thickness * 2, 0); + break; + case 2: + toBack = new Vector3(thickness * -2, thickness * -2, 0); + break; + case 3: + toBack = new Vector3(thickness * 2, thickness * -2, 0); + break; + } - var vec = new Vector2(0, 1 + l); - h1 -= 0.001f; h2 -= 0.001f; - g2.Verts.Add(new WallVertexRC(p1 + wallHeight*h1, wallTop, new Vector3(0, h1 + l, aboveFloor))); - g2.Verts.Add(new WallVertexRC(p2 + wallHeight*h2, wallTop, new Vector3(0, h2 + l, aboveFloor))); - g2.Verts.Add(new WallVertexRC(p1 + wallHeight*h1 + toBack, wallTop, new Vector3(0, h1 + l, aboveFloor))); - g2.Verts.Add(new WallVertexRC(p2 + wallHeight*h2 + toBack, wallTop, new Vector3(0, h2 + l, aboveFloor))); + var vec = new Vector2(0, 1 + l); + h1 -= 0.001f; h2 -= 0.001f; + g2.Verts.Add(new WallVertexRC(p1 + wallHeight * h1, wallTop, new Vector3(0, h1 + l, aboveFloor))); + g2.Verts.Add(new WallVertexRC(p2 + wallHeight * h2, wallTop, new Vector3(0, h2 + l, aboveFloor))); + g2.Verts.Add(new WallVertexRC(p1 + wallHeight * h1 + toBack, wallTop, new Vector3(0, h1 + l, aboveFloor))); + g2.Verts.Add(new WallVertexRC(p2 + wallHeight * h2 + toBack, wallTop, new Vector3(0, h2 + l, aboveFloor))); - g2.Indices.Add(baseI); g2.Indices.Add(baseI + 2); g2.Indices.Add(baseI + 1); - g2.Indices.Add(baseI + 2); g2.Indices.Add(baseI + 3); g2.Indices.Add(baseI + 1); - } - }; + g2.Indices.Add(baseI); g2.Indices.Add(baseI + 2); g2.Indices.Add(baseI + 1); + g2.Indices.Add(baseI + 2); g2.Indices.Add(baseI + 3); g2.Indices.Add(baseI + 1); + } + }; + for (short y = 0; y < blueprint.Height; y++) + { + for (short x = 0; x < blueprint.Height; x++) + { var comp = blueprint.GetWall(x, y, level); if (comp.Segments != 0) { @@ -187,22 +189,22 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool { extentBack = -thickness; //cap this end - addLineGeom(new Vector2(thickness, extentBack + 0.005f), new Vector2(-thickness, extentBack + 0.005f), comp.TopLeftPattern, 1, 5, 0, thickness * 2, bleedLight); + addLineGeom(x, y, new Vector2(thickness, extentBack + 0.005f), new Vector2(-thickness, extentBack + 0.005f), comp.TopLeftPattern, 1, 5, 0, thickness * 2, bleedLight); } if (y < blueprint.Height - 1 && !blueprint.GetWall(x, (short)(y + 1), level).TopLeftThick) { extentFront = thickness; //cap this end - addLineGeom(new Vector2(-thickness, 1 + extentFront - 0.005f), new Vector2(thickness, 1 + extentFront - 0.005f), comp.TopLeftPattern, 1, 5, 0, thickness * 2, bleedLight); + addLineGeom(x, y, new Vector2(-thickness, 1 + extentFront - 0.005f), new Vector2(thickness, 1 + extentFront - 0.005f), comp.TopLeftPattern, 1, 5, 0, thickness * 2, bleedLight); } if (x > 0) - addLineGeom(new Vector2(-thickness, extentBack), new Vector2(-thickness, 1 + extentFront), blueprint.GetWall((short)(x - 1), y, level).BottomRightPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 5, -(0 + extentBack), -(1 + extentFront), bleedLight2); - addLineGeom(new Vector2(thickness, 1+extentFront), new Vector2(thickness, extentBack), comp.TopLeftPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 0, 0-extentFront, 1-extentBack, bleedLight); + addLineGeom(x, y, new Vector2(-thickness, extentBack), new Vector2(-thickness, 1 + extentFront), blueprint.GetWall((short)(x - 1), y, level).BottomRightPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 5, -(0 + extentBack), -(1 + extentFront), bleedLight2); + addLineGeom(x, y, new Vector2(thickness, 1+extentFront), new Vector2(thickness, extentBack), comp.TopLeftPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 0, 0-extentFront, 1-extentBack, bleedLight); } else { //fence tl - addLineGeom(new Vector2(0, 1), new Vector2(0, 0), comp.TopLeftPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 4, 0, 1, bleedLight); + addLineGeom(x, y, new Vector2(0, 1), new Vector2(0, 0), comp.TopLeftPattern, (comp.ObjSetTLStyle == 0) ? comp.TopLeftStyle : comp.ObjSetTLStyle, 4, 0, 1, bleedLight); } } if ((comp.Segments & WallSegments.TopRight) > 0) @@ -216,22 +218,22 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool { extentBack = -thickness; //cap this end - addLineGeom(new Vector2(extentBack+0.005f, -thickness), new Vector2(extentBack + 0.005f, thickness), comp.TopRightPattern, 1, 5, 0, thickness*2, bleedLight); + addLineGeom(x, y, new Vector2(extentBack+0.005f, -thickness), new Vector2(extentBack + 0.005f, thickness), comp.TopRightPattern, 1, 5, 0, thickness*2, bleedLight); } if (x < blueprint.Width - 1 && !blueprint.GetWall((short)(x + 1), y, level).TopRightThick) { extentFront = thickness; //cap this end - addLineGeom(new Vector2(1 + extentFront - 0.005f, thickness), new Vector2(1 + extentFront - 0.005f, -thickness), comp.TopRightPattern, 1, 5, 0, thickness * 2, bleedLight); + addLineGeom(x, y, new Vector2(1 + extentFront - 0.005f, thickness), new Vector2(1 + extentFront - 0.005f, -thickness), comp.TopRightPattern, 1, 5, 0, thickness * 2, bleedLight); } if (y > 0) - addLineGeom(new Vector2(1 + extentFront, -thickness), new Vector2(extentBack, -thickness), blueprint.GetWall(x, (short)(y - 1), level).BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, extentFront, -(1 - extentBack), bleedLight2); - addLineGeom(new Vector2(extentBack, thickness), new Vector2(1+extentFront, thickness), comp.TopRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 1, extentBack, 1+extentFront, bleedLight); + addLineGeom(x, y, new Vector2(1 + extentFront, -thickness), new Vector2(extentBack, -thickness), blueprint.GetWall(x, (short)(y - 1), level).BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, extentFront, -(1 - extentBack), bleedLight2); + addLineGeom(x, y, new Vector2(extentBack, thickness), new Vector2(1+extentFront, thickness), comp.TopRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 1, extentBack, 1+extentFront, bleedLight); } else { //fence tr - addLineGeom(new Vector2(0, 0), new Vector2(1, 0), comp.TopRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bleedLight); + addLineGeom(x, y, new Vector2(0, 0), new Vector2(1, 0), comp.TopRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bleedLight); } } @@ -240,7 +242,7 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool //fence bl var comp2 = blueprint.GetWall(x, (short)(y + 1), level); if (!comp2.TopRightThick) - addLineGeom(new Vector2(1, 1), new Vector2(0, 1), comp.BottomLeftPattern, (comp2.ObjSetTRStyle == 0) ? comp2.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bleedLight); + addLineGeom(x, y, new Vector2(1, 1), new Vector2(0, 1), comp.BottomLeftPattern, (comp2.ObjSetTRStyle == 0) ? comp2.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bleedLight); } if ((comp.Segments & WallSegments.BottomRight) > 0 && x < blueprint.Width) { @@ -248,7 +250,7 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool var comp2 = blueprint.GetWall((short)(x + 1), y, level); if (!comp2.TopLeftThick) - addLineGeom(new Vector2(1, 0), new Vector2(1, 1), comp.BottomRightPattern, (comp2.ObjSetTLStyle == 0) ? comp2.TopLeftStyle : comp2.ObjSetTLStyle, 4, 0, 1, bleedLight); + addLineGeom(x, y, new Vector2(1, 0), new Vector2(1, 1), comp.BottomRightPattern, (comp2.ObjSetTLStyle == 0) ? comp2.TopLeftStyle : comp2.ObjSetTLStyle, 4, 0, 1, bleedLight); } if ((comp.Segments & WallSegments.HorizontalDiag) > 0) @@ -257,18 +259,18 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool var bl1 = (level == 1 || comp.TopLeftPattern != 0) ? 0 : 1; if (comp.TopRightStyle == 1 || comp.TopRightStyle == 255) { - addLineGeom(new Vector2(thickDiag, 1+thickDiag), new Vector2(1+thickDiag, thickDiag), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 2, 0, 1, bl1); - addLineGeom(new Vector2(1-thickDiag, -thickDiag), new Vector2(-thickDiag, 1 - thickDiag), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, 0, 1, bl2); + addLineGeom(x, y, new Vector2(thickDiag, 1+thickDiag), new Vector2(1+thickDiag, thickDiag), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 2, 0, 1, bl1); + addLineGeom(x, y, new Vector2(1-thickDiag, -thickDiag), new Vector2(-thickDiag, 1 - thickDiag), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, 0, 1, bl2); //caps - addLineGeom(new Vector2(-thickDiag, 1 - thickDiag), new Vector2(thickDiag, 1 + thickDiag), comp.BottomRightPattern, 1, 5, 0, thickDiag*2, Math.Min(bl1, bl2)); - addLineGeom(new Vector2(1 + thickDiag, thickDiag), new Vector2(1 - thickDiag, -thickDiag), comp.BottomLeftPattern, 1, 5, 0, thickDiag * 2, Math.Min(bl1, bl2)); + addLineGeom(x, y, new Vector2(-thickDiag, 1 - thickDiag), new Vector2(thickDiag, 1 + thickDiag), comp.BottomRightPattern, 1, 5, 0, thickDiag*2, Math.Min(bl1, bl2)); + addLineGeom(x, y, new Vector2(1 + thickDiag, thickDiag), new Vector2(1 - thickDiag, -thickDiag), comp.BottomLeftPattern, 1, 5, 0, thickDiag * 2, Math.Min(bl1, bl2)); } else { //fence horiz - addLineGeom(new Vector2(0, 1), new Vector2(1, 0), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl1); - addLineGeom(new Vector2(1, 0), new Vector2(0, 1), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl2); + addLineGeom(x, y, new Vector2(0, 1), new Vector2(1, 0), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl1); + addLineGeom(x, y, new Vector2(1, 0), new Vector2(0, 1), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl2); } } if ((comp.Segments & WallSegments.VerticalDiag) > 0) @@ -277,19 +279,19 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool var bl2 = (level == 1 || comp.TopLeftPattern != 0) ? 0 : 1; if (comp.TopRightStyle == 1 || comp.TopRightStyle == 255) { - addLineGeom(new Vector2(-thickDiag, thickDiag), new Vector2(1- thickDiag, 1+thickDiag), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 3, 0, 1, bl1); - addLineGeom(new Vector2(1+ thickDiag, 1- thickDiag), new Vector2(0 + thickDiag, -thickDiag), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, 0, 1, bl2); + addLineGeom(x, y, new Vector2(-thickDiag, thickDiag), new Vector2(1- thickDiag, 1+thickDiag), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 3, 0, 1, bl1); + addLineGeom(x, y, new Vector2(1+ thickDiag, 1- thickDiag), new Vector2(0 + thickDiag, -thickDiag), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 5, 0, 1, bl2); //caps - addLineGeom(new Vector2(0 + thickDiag, -thickDiag), new Vector2(-thickDiag, thickDiag), comp.BottomLeftPattern, 1, 5, 0, thickDiag*2, Math.Min(bl1, bl2)); - addLineGeom(new Vector2(1 - thickDiag, 1 + thickDiag), new Vector2(1 + thickDiag, 1 - thickDiag), comp.BottomRightPattern, 1, 5, 0, thickDiag * 2, Math.Min(bl1, bl2)); + addLineGeom(x, y, new Vector2(0 + thickDiag, -thickDiag), new Vector2(-thickDiag, thickDiag), comp.BottomLeftPattern, 1, 5, 0, thickDiag*2, Math.Min(bl1, bl2)); + addLineGeom(x, y, new Vector2(1 - thickDiag, 1 + thickDiag), new Vector2(1 + thickDiag, 1 - thickDiag), comp.BottomRightPattern, 1, 5, 0, thickDiag * 2, Math.Min(bl1, bl2)); } else { // fence vert - addLineGeom(new Vector2(0, 0), new Vector2(1, 1), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl1); - addLineGeom(new Vector2(1, 1), new Vector2(0, 0), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl2); + addLineGeom(x, y, new Vector2(0, 0), new Vector2(1, 1), comp.BottomLeftPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl1); + addLineGeom(x, y, new Vector2(1, 1), new Vector2(0, 0), comp.BottomRightPattern, (comp.ObjSetTRStyle == 0) ? comp.TopRightStyle : comp.ObjSetTRStyle, 4, 0, 1, bl2); } } @@ -305,6 +307,11 @@ public void Generate(GraphicsDevice device, WorldState world, bool cutaway, bool } } + public void AdjustHeight(float diff) + { + HeightAdjust += diff; + } + public void Draw(GraphicsDevice gd, WorldState state) { var effect = WorldContent.RCObject; @@ -318,6 +325,11 @@ public void Draw(GraphicsDevice gd, WorldState state) gd.BlendState = BlendState.Opaque; if (!gd.RasterizerState.ScissorTestEnable) gd.RasterizerState = RasterizerState.CullCounterClockwise; var baseWorld = Matrix.CreateRotationX((float)Math.PI / 2) * Matrix.CreateScale(3f, -3f, 3f); + if (HeightAdjust != 0) + { + baseWorld = Matrix.CreateTranslation(new Vector3(0, 0, HeightAdjust)) * baseWorld; + } + effect.World = baseWorld; effect.SideMask = 0f; effect.Level = (float)(state.Level - 0.999f); diff --git a/TSOClient/tso.world/Utils/Camera/CameraController3D.cs b/TSOClient/tso.world/Utils/Camera/CameraController3D.cs index 763aaa40d..22a80be52 100644 --- a/TSOClient/tso.world/Utils/Camera/CameraController3D.cs +++ b/TSOClient/tso.world/Utils/Camera/CameraController3D.cs @@ -113,6 +113,18 @@ public virtual ICameraController BeforeActive(ICameraController previous, World } } } + else if (previous is CameraController3D) + { + var _3d = (CameraController3D)previous; + _RotationX = _3d.RotationX; + _RotationY = _3d.RotationY; + _Zoom3D = _3d.Zoom3D; + InvalidateCamera(world.State); + var relative = ComputeCenterRelative(); + //SwitchCenter = world.State.CenterTile - new Vector2(relative.X / WorldSpace.WorldUnitsPerTile, relative.Z / WorldSpace.WorldUnitsPerTile); + + CamHeight = _3d.CamHeight; + } else if (previous is CameraController2D) { //just guess camera zoom and rotation? diff --git a/TSOClient/tso.world/Utils/Camera/CameraControllerDirect.cs b/TSOClient/tso.world/Utils/Camera/CameraControllerDirect.cs index 7e6d4cc2a..1a538d8fa 100644 --- a/TSOClient/tso.world/Utils/Camera/CameraControllerDirect.cs +++ b/TSOClient/tso.world/Utils/Camera/CameraControllerDirect.cs @@ -1,5 +1,4 @@ -using System; -using FSO.Common; +using FSO.Common; using FSO.Common.Rendering.Framework.Model; using FSO.LotView.Components; using Microsoft.Xna.Framework; @@ -11,18 +10,27 @@ namespace FSO.LotView.Utils.Camera public class CameraControllerDirect : CameraControllerFP { public AvatarComponent FirstPersonAvatar; + private float ThirdPersonDistance; + private float ThirdPersonTargetDistance; + private float ThirdPersonLimitDistance = 4; + + private int LastWheel = 0; public CameraControllerDirect(GraphicsDevice gd, WorldState state) : base(gd, state) { } + private Vector3 GetCameraDirection() + { + var mat = Matrix.CreateRotationZ((_RotationY - (float)Math.PI / 2) * 0.99f) * Matrix.CreateRotationY(_RotationX); + return Vector3.Transform(new Vector3(-10, 0, 0), mat); + } + public override void InvalidateCamera(WorldState state) { var baseHeight = 0; Camera.Position = new Vector3(state.CenterTile.X * WorldSpace.WorldUnitsPerTile, baseHeight + FPCamHeight, state.CenterTile.Y * WorldSpace.WorldUnitsPerTile); - - var mat = Matrix.CreateRotationZ((_RotationY - (float)Math.PI / 2) * 0.99f) * Matrix.CreateRotationY(_RotationX); - Camera.Target = Camera.Position + Vector3.Transform(new Vector3(-10, 0, 0), mat); + Camera.Target = Camera.Position + GetCameraDirection(); } public override void Update(UpdateState state, World world) @@ -44,6 +52,9 @@ public override void Update(UpdateState state, World world) var worldState = world.State; var terrainHeight = CorrectCameraHeight(world); var hz = FSOEnvironment.RefreshRate; + var power = 60f / hz; + var interpRate = (float)(1f - (float)Math.Pow(0.8f, power)); + if (state.WindowFocused) { var mx = (int)worldState.WorldSpace.WorldPxWidth / 2; @@ -53,14 +64,32 @@ public override void Update(UpdateState state, World world) var camera = Camera; if (LastFP && !(mpos.X == 0 && mpos.Y == 0)) { - RotationX -= ((mpos.X - mx) / 500f) * camera.FOV; - RotationY += ((mpos.Y - my) / 500f) * camera.FOV; + RotationX -= ((mpos.X - mx) / 166f) * camera.FOV * power; + RotationY += ((mpos.Y - my) / 166f) * camera.FOV * power; } Mouse.SetPosition(mx, my); + var wheel = state.MouseState.ScrollWheelValue; + + if (LastFP && wheel != LastWheel) + { + var diff = (wheel - LastWheel) / -300f; + ThirdPersonTargetDistance += diff; + ThirdPersonTargetDistance = Math.Clamp(ThirdPersonTargetDistance, 0, 4); + } + + ThirdPersonDistance += (ThirdPersonTargetDistance - ThirdPersonDistance) * interpRate; + + LastWheel = wheel; + if (FirstPersonAvatar != null) { - FirstPersonAvatar.Avatar.HideHead = true; + FirstPersonAvatar.Avatar.HideHead = ThirdPersonDistance < 0.3; + + var avatarIndoors = world.Architecture.Blueprint.IsIndoorsPrecise(new Vector2(FirstPersonAvatar.Position.X, FirstPersonAvatar.Position.Y), FirstPersonAvatar.Level - 1); + var targLimit = avatarIndoors ? 2 : 4; + + ThirdPersonLimitDistance += (targLimit - ThirdPersonLimitDistance) * interpRate; } LastFP = true; @@ -72,14 +101,128 @@ public override void Update(UpdateState state, World world) } } + private float GetWallLimitedDistance(World world, Vector3 basePos, Vector3 frontVec, float targetDistance) + { + const float wallMargin = 0.30f; + + var dir = -new Vector3(frontVec.X, frontVec.Z, frontVec.Y); + dir.Normalize(); + var ray = new Ray(new Vector3(basePos.X, basePos.Z, basePos.Y) * 3, dir); + + var hit = WallRaycaster.RaycastMultifloor(ray, world.Architecture.Blueprint, (targetDistance + wallMargin) * 3); + + if (hit != null) + { + return Math.Clamp(hit.Value.Item1 / 3 - wallMargin, 0f, targetDistance); + } + + return targetDistance; + } + + private float GetFloorLimitedDistance(World world, Vector3 basePos, Vector3 frontVec, float targetDistance) + { + const float floorMargin = 0.15f; + const float perAttempt = 0.25f; + const float floorHeight = 2.95f; + + float attemptDistance = 0; + var bp = world.Architecture.Blueprint; + float lastFloorDist = basePos.Z - bp.InterpAltitudeWithSubworlds(basePos); + + float floorBottom = Math.Max(0, (float)Math.Floor(lastFloorDist / floorHeight)) * floorHeight; + float min = floorBottom + floorMargin; + float max = floorBottom + floorHeight - floorMargin; + + while (attemptDistance < targetDistance) + { + attemptDistance += perAttempt; + + var attempt = basePos - attemptDistance * frontVec; + + float floorDist = attempt.Z - bp.InterpAltitudeWithSubworlds(attempt); + + if (floorDist < min) + { + // What percentage of the attempt distance passed the threshold, roughly? + float diff = (min - lastFloorDist) / (floorDist - lastFloorDist); + + return attemptDistance - perAttempt * (1 - diff); + } + + if (floorDist > max && bp.IsIndoorsPrecise(new Vector3(attempt.X, attempt.Y, basePos.Z))) + { + // What percentage of the attempt distance passed the threshold, roughly? + float diff = (max - lastFloorDist) / (floorDist - lastFloorDist); + + return attemptDistance - perAttempt * (1 - diff); + } + + lastFloorDist = floorDist; + } + + return targetDistance; + } + public override void PreDraw(World world) { if (FirstPersonAvatar != null) { if (Camera.FOV != 0.9f) Camera.FOV = 0.9f; var headPos = FirstPersonAvatar.GetHeadlinePos() * FirstPersonAvatar.Scale + FirstPersonAvatar.Position; + + headPos.Z += (0.25f * FirstPersonAvatar.Scale) / 3f; + + if (ThirdPersonDistance > 0) + { + var originalHeadPos = headPos; + var tpPos = FirstPersonAvatar.Position + new Vector3(0, 0, FirstPersonAvatar.IsPet ? 0.65f : 1.77f) * FirstPersonAvatar.Scale; + + var frontVec = GetCameraDirection(); + frontVec.Normalize(); + frontVec = new Vector3(frontVec.X, frontVec.Z, frontVec.Y); + + var downVec = new Vector3(0, 0, 1); + var sideVec = Vector3.Cross(frontVec, downVec); + + headPos = tpPos; + headPos -= sideVec * 0.27f + downVec * 0.17f; + + var dist = GetWallLimitedDistance(world, headPos, frontVec, Math.Min(ThirdPersonLimitDistance, ThirdPersonDistance)); + dist = GetFloorLimitedDistance(world, headPos, frontVec, dist); + + ThirdPersonLimitDistance = dist; + + headPos -= frontVec * dist; + + if (ThirdPersonDistance < 0.5) + { + headPos = Vector3.Lerp(originalHeadPos, headPos, ThirdPersonDistance * 2); + } + // + } + world.State.CenterTile = new Vector2(headPos.X, headPos.Y); - FPCamHeight = headPos.Z * 3 + 0.25f * FirstPersonAvatar.Scale; + FPCamHeight = headPos.Z * 3; + InvalidateCamera(world.State); + } + } + + public void Inherit(CameraControllerDirect direct) + { + ThirdPersonDistance = direct.ThirdPersonDistance; + ThirdPersonTargetDistance = direct.ThirdPersonTargetDistance; + ThirdPersonLimitDistance = direct.ThirdPersonLimitDistance; + + LastWheel = direct.LastWheel; + } + + public override void OnActive(ICameraController previous, World world) + { + base.OnActive(previous, world); + + if (previous is CameraControllerDirect direct) + { + Inherit(direct); } } } diff --git a/TSOClient/tso.world/Utils/Camera/CameraControllerFP.cs b/TSOClient/tso.world/Utils/Camera/CameraControllerFP.cs index c7303fdbf..e68b486b3 100644 --- a/TSOClient/tso.world/Utils/Camera/CameraControllerFP.cs +++ b/TSOClient/tso.world/Utils/Camera/CameraControllerFP.cs @@ -52,6 +52,7 @@ public override void Update(UpdateState state, World world) var worldState = world.State; var terrainHeight = CorrectCameraHeight(world); var hz = FSOEnvironment.RefreshRate; + var power = 60f / hz; if (state.WindowFocused) { var mx = (int)worldState.WorldSpace.WorldPxWidth / 2; @@ -61,8 +62,8 @@ public override void Update(UpdateState state, World world) var camera = Camera; if (LastFP && !(mpos.X == 0 && mpos.Y == 0)) { - RotationX -= ((mpos.X - mx) / 500f) * camera.FOV; - RotationY += ((mpos.Y - my) / 500f) * camera.FOV; + RotationX -= ((mpos.X - mx) / 166f) * camera.FOV * power; + RotationY += ((mpos.Y - my) / 166f) * camera.FOV * power; } Mouse.SetPosition(mx, my); diff --git a/TSOClient/tso.world/Utils/DGRPRenderer.cs b/TSOClient/tso.world/Utils/DGRPRenderer.cs index b3f97d345..c31522a9d 100644 --- a/TSOClient/tso.world/Utils/DGRPRenderer.cs +++ b/TSOClient/tso.world/Utils/DGRPRenderer.cs @@ -118,6 +118,8 @@ private float RadianDirection } } + public bool CanHaveBounds => DGRP != null && Mesh != null && Mesh.Geoms.Count > 0; + public void InvalidateRotation() { _TextureDirty = true; @@ -407,10 +409,10 @@ public void DrawLMap(GraphicsDevice device, sbyte level, float yOff) public virtual void Preload(WorldState world, ComponentRenderMode mode) { - if (mode.IsSet(ComponentRenderMode._2D)) + if (mode.IsSet(ComponentRenderMode._2D) && world.CameraMode != CameraRenderMode._3D) ValidateSprite(world); - if (mode.IsSet(ComponentRenderMode._3D)) + if (mode.IsSet(ComponentRenderMode._3D) || world.CameraMode != CameraRenderMode._2D || WorldConfig.Current.UltraLighting) { if (_Dirty.IsSet(ComponentRenderMode._3D)) { diff --git a/TSOClient/tso.world/Utils/TileRaycaster.cs b/TSOClient/tso.world/Utils/TileRaycaster.cs new file mode 100644 index 000000000..a48834947 --- /dev/null +++ b/TSOClient/tso.world/Utils/TileRaycaster.cs @@ -0,0 +1,243 @@ +using FSO.LotView.Model; +using Microsoft.Xna.Framework; +using System.Runtime.CompilerServices; + +namespace FSO.LotView.Utils +{ + internal interface ITileRaycastTarget + where TResult : struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static abstract (float, TResult)? TestRay(Ray ray, Point tile, Point nextTile, float? edge, sbyte level, Blueprint bp); + } + + internal class WallTileRaycastTarget : ITileRaycastTarget + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (float, ushort)? TestRay(Ray ray, Point tile, Point nextTile, float? edge, sbyte level, Blueprint bp) + { + var wall = bp.GetWall((short)tile.X, (short)tile.Y, level); + + if (wall.Segments != 0) + { + float wallBottom = bp.GetAltitude(tile.X, tile.Y) * 3; + float wallTop = wallBottom + 2.95f * 3f; + + float? wallIntersectPoint = default; + ushort wallId = 0; + + if ((wall.Segments & WallSegments.AnyDiag) != 0) + { + var mid = new Vector3((tile.X + 0.5f) * 3, 0, (tile.Y + 0.5f) * 3); + var corner = mid + (wall.Segments.HasFlag(WallSegments.VerticalDiag) ? new Vector3(-1.5f, 0, -1.5f) : new Vector3(-1.5f, 0, 1.5f)); + var plane = new Plane(mid, mid + Vector3.Up, corner); + + var dist = ray.Intersects(plane); + if (dist.HasValue) + { + wallIntersectPoint = dist.Value; + wallId = 1; // TODO + } + } + else if ((wall.Segments & WallSegments.AnyAdj) != 0 && edge.HasValue) + { + var bound = nextTile - tile; + + WallSegments edgeSegs = 0; + if (bound.Y > 0) edgeSegs |= WallSegments.BottomLeft; + if (bound.X < 0) edgeSegs |= WallSegments.TopLeft; + if (bound.Y < 0) edgeSegs |= WallSegments.TopRight; + if (bound.X > 0) edgeSegs |= WallSegments.BottomRight; + + if ((edgeSegs & wall.Segments) != 0) + { + wallIntersectPoint = edge; + wallId = 1; //TODO + } + } + + if (wallIntersectPoint != null) + { + var rayY = ray.Position.Y + ray.Direction.Y * wallIntersectPoint.Value; + + if (rayY >= wallBottom && rayY < wallTop) + { + return (wallIntersectPoint.Value, wallId); + } + } + } + + return null; + } + } + + internal class CombinedTileRaycastTarget : ITileRaycastTarget<(TFirstResult?, TSecondResult?)> + where TFirst : ITileRaycastTarget + where TSecond : ITileRaycastTarget + where TFirstResult : struct + where TSecondResult : struct + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static (float, (TFirstResult?, TSecondResult?))? TestRay(Ray ray, Point tile, Point nextTile, float? edge, sbyte level, Blueprint bp) + { + var first = TFirst.TestRay(ray, tile, nextTile, edge, level, bp); + var second = TSecond.TestRay(ray, tile, nextTile, edge, level, bp); + + if (first.HasValue && second.HasValue) + { + if (first.Value.Item1 <= second.Value.Item1) + { + return (first.Value.Item1, (first.Value.Item2, default)); + } + else + { + return (second.Value.Item1, (default, second.Value.Item2)); + } + } + + if (first.HasValue) + { + return (first.Value.Item1, (first.Value.Item2, default)); + } + + if (second.HasValue) + { + return (second.Value.Item1, (default, second.Value.Item2)); + } + + return null; + } + } + + internal class TileRaycaster + where T : ITileRaycastTarget + where TResult : struct + { + private static float? BoxRC2(Ray ray, float tileSize) + { + var px = (ray.Direction.X > 0); + var py = (ray.Direction.Z > 0); + //find current tile + int x = (!px) ? (int)Math.Ceiling(ray.Position.X / tileSize) : + (int)(ray.Position.X / tileSize); + int y = (!py) ? (int)Math.Ceiling(ray.Position.Z / tileSize) : + (int)(ray.Position.Z / tileSize); + + //find next tile boundary + float nx = ((px) ? (x + 1) : (x - 1)) * 3; + float ny = ((py) ? (y + 1) : (y - 1)) * 3; + + const float Epsilon = 1e-6f; + float? min = null; + if (Math.Abs(ray.Direction.X) > Epsilon) + { + min = (nx - ray.Position.X) / ray.Direction.X; + } + + if (Math.Abs(ray.Direction.Z) > Epsilon) + { + var min2 = (ny - ray.Position.Z) / ray.Direction.Z; + if (min == null || min.Value > min2) min = min2; + } + return min; + } + + public static (float, TResult)? Raycast(Ray ray, sbyte level, Blueprint bp, float maxDist) + { + Ray baseRay = ray; + var baseBox = new BoundingBox(new Vector3(0, -5000, 0), new Vector3(bp.Width * 3, 5000, bp.Height * 3)); + if (baseBox.Contains(ray.Position) != ContainmentType.Contains) + { + //move ray start inside box + var i = baseBox.Intersects(ray); + if (i != null) + { + ray.Position += ray.Direction * (i.Value + 0.01f); + } + } + + var mx = (int)ray.Position.X / 3; + var my = (int)ray.Position.Z / 3; + + var px = (ray.Direction.X > 0); + var py = (ray.Direction.Z > 0); + + var canProj = bp?.Altitude != null; + + float totalDist = 0; + + int iteration = 0; + while (mx >= 0 && mx < bp.Width && my >= 0 && my < bp.Width && canProj) + { + var tileDist = BoxRC2(ray, 3); // T to the next tile + + Ray nextRay = ray; + + if (tileDist == null) break; + + float addDist = (tileDist.Value + 0.00001f); + nextRay.Position += nextRay.Direction * addDist; + + int nextX = (!px) ? ((int)Math.Ceiling(nextRay.Position.X / 3) - 1) : + (int)(nextRay.Position.X / 3); + int nextY = (!py) ? ((int)Math.Ceiling(nextRay.Position.Z / 3) - 1) : + (int)(nextRay.Position.Z / 3); + + var result = T.TestRay(ray, new Point(mx, my), new Point(nextX, nextY), tileDist, level, bp); + + if (tileDist != null && result != null && result.Value.Item1 <= tileDist) + { + addDist = result.Value.Item1 + 0.00001f; + totalDist += addDist; + + if (totalDist > maxDist) + { + return null; + } + + // The result was hit first. + return (totalDist, result.Value.Item2); + } + + ray = nextRay; + totalDist += addDist; + + mx = nextX; + my = nextY; + + if (iteration++ > 1000 || totalDist > maxDist) break; + } + + return null; + } + + public static (float, TResult)? RaycastMultifloor(Ray ray, Blueprint bp, float maxDist, int maxFloor = -1) + { + if (maxFloor == -1) + { + maxFloor = bp.Stories; + } + + (float, TResult)? bestResult = null; + for (int i = 1; i <= maxFloor; i++) + { + var result = Raycast(ray, (sbyte)i, bp, maxDist); + + if (result != null && (bestResult == null || result.Value.Item1 < bestResult.Value.Item1)) + { + bestResult = result; + } + + // Next floor + ray.Position -= new Vector3(0, 2.95f * 3, 0); + } + + return bestResult; + } + } + + internal class WallRaycaster : TileRaycaster + { + + } +} diff --git a/TSOClient/tso.world/Utils/_2DStandaloneSprite.cs b/TSOClient/tso.world/Utils/_2DStandaloneSprite.cs index fe90e8531..55becc48b 100644 --- a/TSOClient/tso.world/Utils/_2DStandaloneSprite.cs +++ b/TSOClient/tso.world/Utils/_2DStandaloneSprite.cs @@ -1,4 +1,5 @@ -using Microsoft.Xna.Framework; +using FSO.Common; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using System; @@ -49,7 +50,8 @@ public void PrepareVertices(GraphicsDevice gd) private Vector2 GetUV(Texture2D Texture, float x, float y) { - return new Vector2(x / (float)Texture.Width, y / (float)Texture.Height); + float offset = FSOEnvironment.PxOffset2D; + return new Vector2((x + offset) / (float)Texture.Width, (y + offset) / (float)Texture.Height); } } } diff --git a/TSOClient/tso.world/Utils/_2DWorldBatch.cs b/TSOClient/tso.world/Utils/_2DWorldBatch.cs index e44ead69a..0e939c28c 100644 --- a/TSOClient/tso.world/Utils/_2DWorldBatch.cs +++ b/TSOClient/tso.world/Utils/_2DWorldBatch.cs @@ -722,7 +722,8 @@ private void RenderSpriteList(List<_2DSprite> sprites, WorldBatchEffect effect, private Vector2 GetUV(Texture2D Texture, float x, float y) { - return new Vector2(x / (float)Texture.Width, y / (float)Texture.Height); + float offset = FSOEnvironment.PxOffset2D; + return new Vector2((x + offset) / (float)Texture.Width, (y + offset) / (float)Texture.Height); } public void ResetMatrices(int width, int height) diff --git a/TSOClient/tso.world/World.cs b/TSOClient/tso.world/World.cs index 88f8f40f6..6e6a75e8b 100644 --- a/TSOClient/tso.world/World.cs +++ b/TSOClient/tso.world/World.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Runtime.CompilerServices; namespace FSO.LotView { @@ -397,7 +398,7 @@ public void SetGraphicsMode(GlobalGraphicsMode mode, bool instant) public Tuple Get3DTTHeights() { if (Blueprint == null) { return new Tuple(0, 0); } - var terrainHeight = (Blueprint.InterpAltitude(new Vector3(State.CenterTile, 0))) * 3; + var terrainHeight = (Blueprint.InterpAltitudeWithSubworlds(new Vector3(State.CenterTile, 0))) * 3; float targHeight; @@ -407,7 +408,7 @@ public Tuple Get3DTTHeights() } else { - targHeight = Math.Max((Blueprint.InterpAltitude(new Vector3(State.Camera.Position.X, State.Camera.Position.Z, 0) / 3) + (State.Level - 1) * 2.95f) * 3, terrainHeight); + targHeight = Math.Max((Blueprint.InterpAltitudeWithSubworlds(new Vector3(State.Camera.Position.X, State.Camera.Position.Z, 0) / 3) + (State.Level - 1) * 2.95f) * 3, terrainHeight); } return new Tuple(terrainHeight, targHeight); @@ -520,7 +521,14 @@ public void CenterTo(EntityComponent comp) } else { - State.CenterTile = new Vector2(pelvisCenter.X, pelvisCenter.Y); + if (!isFirstPerson) + { + State.CenterTile = new Vector2(pelvisCenter.X, pelvisCenter.Y); + } + else + { + State.Cameras.CameraDirect.PreDraw(this); + } State.Cameras.CameraDirect.FirstPersonAvatar = isFirstPerson ? comp as AvatarComponent : null; if (isFirstPerson && State.Cameras.ActiveType == CameraControllerType.Direct) @@ -611,7 +619,7 @@ public override void Update(UpdateState state) { if (FSOEnvironment.Enable3D && CanSwitchCameras) { - if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Tab)) + if (state.NewKeys.Contains(Microsoft.Xna.Framework.Input.Keys.Tab) && state.InputManager.GetFocus() == null) { ToggleFirstPerson(CameraControllerType.FirstPerson); } @@ -668,7 +676,7 @@ public override void PreDraw(GraphicsDevice device) Blueprint.Changes.PreDraw(device, State); Static?.PreDraw(device, State); - if (UseBackbuffer) + if (UseBackbuffer && Visible) { PPXDepthEngine.SetPPXTarget(null, null, true); InternalDraw(device); @@ -686,8 +694,9 @@ public override void Draw(GraphicsDevice device){ if (HasInit == false) { return; } FrameCounter++; - if (FrameCounter < LastCacheClear + 60*60) + if (FrameCounter > LastCacheClear + 60*60) { + LastCacheClear = FrameCounter; State._2D.ClearTextureCache(); } if (!UseBackbuffer) @@ -819,6 +828,12 @@ public Vector2 EstTileAtPosWithScroll(Vector2 pos, sbyte level = -1) if (level == -1) level = State.Level; var ray = State.CameraRayAtScreenPos(pos, level); + return EstTileAtPosWithScroll(ray, level).Value; + } + + public Vector2? EstTileAtPosWithScroll(Ray ray, sbyte level, bool canFail = false) + { + Ray baseRay = ray; var baseBox = new BoundingBox(new Vector3(0, -5000, 0), new Vector3(Blueprint.Width * 3, 5000, Blueprint.Height * 3)); if (baseBox.Contains(ray.Position) != ContainmentType.Contains) { @@ -892,8 +907,36 @@ public Vector2 EstTileAtPosWithScroll(Vector2 pos, sbyte level = -1) if (iteration++ > 1000) break; } + // Failed to cast a ray into the main world. If there are subworlds, try there. + if (Blueprint.SubWorlds.Count > 0) + { + foreach (var nextWorld in Blueprint.SubWorlds) + { + Ray newRay = baseRay; + newRay.Position -= new Vector3(nextWorld.GlobalPosition.X * -3, nextWorld.Blueprint.BaseAlt * nextWorld.Blueprint.TerrainFactor * -3, nextWorld.GlobalPosition.Y * -3); + var subPos = nextWorld.EstTileAtPosWithScroll(newRay, level, true); + + if (subPos == null) + { + continue; + } + + return subPos.Value - nextWorld.GlobalPosition; + } + } + + if (canFail) + { + return null; + } + //fall back to base positioning var bplane = new Plane(new Vector3(0, 0, 0), new Vector3(Blueprint.Width * 3, 0, 0), new Vector3(0, 0, Blueprint.Height * 3)); + if (ray.Position.Y < 0) + { + ray.Direction *= -1; + } + var cast = ray.Intersects(bplane); if (cast != null) { @@ -904,24 +947,44 @@ public Vector2 EstTileAtPosWithScroll(Vector2 pos, sbyte level = -1) return new Vector2(0, 0); } - public Vector3 EstTileAtPosWithScroll3D(Vector2 pos, sbyte startFloor = -1) + public Vector3? EstTileAtPosWithScroll3D(Vector2 pos, sbyte startFloor = -1, bool canFail = false) { + var initialRay = State.CameraRayAtScreenPos(pos, 1); + + bool pointingUp = initialRay.Direction.Y > 0; + if (startFloor == -1) startFloor = State.Level; - for (sbyte floor = startFloor; floor > 0; floor--) + sbyte endFloor = 0; + sbyte iterator = -1; + + if (pointingUp) { - var result = EstTileAtPosWithScroll(pos, floor); - if (floor == 1 || (Blueprint.TileInbounds(result) && Blueprint.GetFloor((short)result.X, (short)result.Y, floor).Pattern != 0)) + (startFloor, endFloor) = ((sbyte)(endFloor + 1), (sbyte)(startFloor + 1)); + iterator = 1; + } + + for (sbyte floor = startFloor; floor != endFloor; floor += iterator) + { + var ray = State.CameraRayAtScreenPos(pos, floor); + var result = EstTileAtPosWithScroll(ray, floor, true); + if (result.HasValue && (floor == 1 || (Blueprint.TileInbounds(result.Value) && Blueprint.GetFloor((short)result.Value.X, (short)result.Value.Y, floor).Pattern != 0))) { - return new Vector3(result, floor); + return new Vector3(result.Value, floor); } } + + if (canFail) return null; + return new Vector3(EstTileAtPosWithScroll(pos), State.Level); } public Vector3 EstTileAtPosWithScrollHeight(Vector2 pos, sbyte startFloor = -1) { - var result = EstTileAtPosWithScroll3D(pos, startFloor); - result.Z = Blueprint.InterpAltitude(result) + (result.Z-1) * 2.95f; + var result = EstTileAtPosWithScroll3D(pos, startFloor).Value; + + float altitude = Blueprint.InterpAltitudeWithSubworlds(result); + + result.Z = altitude + (result.Z-1) * 2.95f; return result; } @@ -1051,9 +1114,9 @@ public virtual ObjectComponent MakeObjectComponent(Content.GameObject obj) return new ObjectComponent(obj); } - public virtual SubWorldComponent MakeSubWorld(GraphicsDevice gd) + public virtual SubWorldComponent MakeSubWorld(GraphicsDevice gd, int index) { - return new SubWorldComponent(gd); + return new SubWorldComponent(gd, index); } public BoundingBox[] SkyBounds; @@ -1087,25 +1150,64 @@ public virtual void InitSubWorlds() public int PreloadProgress; public int PreloadObjProgress; + private Queue PreloadCheckpoints = new Queue(); + + private struct PreloadCheckpoint + { + public int Checkpoint; + public Action Action; + + public PreloadCheckpoint(Action action) + { + Checkpoint = AssetStreaming.GetCheckpoint(); + Action = action; + } + + public bool TryRun() + { + if (AssetStreaming.IsCheckpointMet(Checkpoint)) + { + Action(); + return true; + } + + return false; + } + } + + private void ProcessPreloadCheckpoints(Func shouldReturn) + { + while (PreloadCheckpoints.Count > 0 && !shouldReturn() && PreloadCheckpoints.Peek().TryRun()) + { + PreloadCheckpoints.Dequeue(); + } + } public bool Preload(GraphicsDevice gd) { var watch = new Stopwatch(); watch.Start(); - if (PreloadProgress == 0) { + bool shouldReturn() + { + if (watch.ElapsedMilliseconds > 16) + { + watch.Stop(); + return true; + } + + return false; + } + + if (PreloadProgress == 0) + { var done = 0; for (int i = PreloadObjProgress; i < Blueprint.Objects.Count; i++) { var obj = Blueprint.Objects[i]; obj.Preload(gd, State); PreloadObjProgress++; - if (watch.ElapsedMilliseconds > 16 && done >= 6) - { - watch.Stop(); - return false; - } - done++; + if (done++ >= 6 && shouldReturn()) return false; } for (int i=0; i + { + State.PrepareLighting(); + Blueprint.Changes.PreDraw(gd, State); + })); + PreloadProgress = 1; PreloadObjProgress = 0; } for (int i= PreloadProgress-1; i 16) - { - watch.Stop(); - return false; - } + if (shouldReturn()) return false; } + + world.State._2D = State._2D; + world.Blueprint.Changes.Preload(gd, world.State); + + PreloadCheckpoints.Enqueue(new PreloadCheckpoint(() => + { + world.PreDraw(gd, State); + })); + PreloadProgress++; PreloadObjProgress = 0; } - return true; + ProcessPreloadCheckpoints(shouldReturn); + + return PreloadCheckpoints.Count == 0; } public override void Dispose() diff --git a/TSOClient/tso.world/WorldContent.cs b/TSOClient/tso.world/WorldContent.cs index 822850d88..61098dcc9 100644 --- a/TSOClient/tso.world/WorldContent.cs +++ b/TSOClient/tso.world/WorldContent.cs @@ -34,6 +34,7 @@ public static void LoadEffects(bool reload) SpriteEffect = new Effects.SpriteEffect(ContentManager.Load("Effects/SpriteEffects" + EffectSuffix)); ParticleEffect = new LightMappedEffect(ContentManager.Load("Effects/ParticleShader")); AvatarEffect = new LightMappedEffect(ContentManager.Load("Effects/Vitaboy" + EffectSuffix)); + MapGenerationEffect = new MapGeneration(ContentManager.Load("Effects/MapGeneration" + EffectSuffix)); Files.RC.Utils.DepthTreatment.SpriteEffect = SpriteEffect; @@ -72,6 +73,8 @@ public static string EffectSuffix public static LightMappedEffect AvatarEffect; + public static MapGeneration MapGenerationEffect; + private static VertexBuffer _TextureVerts; public static VertexBuffer GetTextureVerts(GraphicsDevice gd) { diff --git a/TSOClient/tso.world/WorldEntities.cs b/TSOClient/tso.world/WorldEntities.cs index 25193de00..ca7da4032 100644 --- a/TSOClient/tso.world/WorldEntities.cs +++ b/TSOClient/tso.world/WorldEntities.cs @@ -111,8 +111,10 @@ public void Draw(GraphicsDevice gd, WorldState state) var changes = Blueprint.Changes; var _2d = state._2D; + BlendState blend = state.CameraMode == CameraRenderMode._2D ? BlendState.AlphaBlend : BlendState.NonPremultiplied; + var effect = WorldContent.RCObject; - gd.BlendState = BlendState.NonPremultiplied; + gd.BlendState = blend; effect.ViewProjection = state.ViewProjection; gd.RasterizerState = RasterizerState.CullNone; @@ -130,7 +132,7 @@ public void Draw(GraphicsDevice gd, WorldState state) if (changes.DrawImmediate) dyn = Blueprint.Objects; else dyn = changes.DynamicObjects; - gd.BlendState = BlendState.NonPremultiplied; + gd.BlendState = blend; dyn = dyn.Where(x => (x.Level <= state.Level) && x.DoDraw(state)); if (state.CameraMode == CameraRenderMode._3D) //only use for full 3d - the draw order for 2d rotation is a completely different coordinate space. { @@ -138,7 +140,7 @@ public void Draw(GraphicsDevice gd, WorldState state) } dyn = dyn.OrderBy(x => x.DrawOrder); - gd.BlendState = BlendState.NonPremultiplied; + gd.BlendState = blend; foreach (var obj in dyn) { obj.Draw(gd, state); @@ -175,10 +177,13 @@ private void DrawObjBuf(GraphicsDevice gd, WorldState state, Vector2 pxOffset) //foreach (var sub in Blueprint.SubWorlds) sub.DrawObjects(gd, state); - _2d.SetScroll(pxOffset); - _2d.OffsetPixel(new Vector2()); - _2d.OffsetTile(new Vector3()); - _2d.PrepareImmediate(Effects.WorldBatchTechniques.drawZSpriteDepthChannel); + if (state.CameraMode == CameraRenderMode._2D) + { + _2d.SetScroll(pxOffset); + _2d.OffsetPixel(new Vector2()); + _2d.OffsetTile(new Vector3()); + _2d.PrepareImmediate(Effects.WorldBatchTechniques.drawZSpriteDepthChannel); + } var size = new Vector2(state._2D.LastWidth, state._2D.LastHeight); var mainBd = state.WorldSpace.GetScreenFromTile(state.CenterTile); diff --git a/TSOClient/tso.world/WorldState.cs b/TSOClient/tso.world/WorldState.cs index 95a86c889..4626ea2e7 100644 --- a/TSOClient/tso.world/WorldState.cs +++ b/TSOClient/tso.world/WorldState.cs @@ -13,6 +13,18 @@ namespace FSO.LotView { + public struct WorldStateCameraInfo + { + public readonly float GroundDistance; + public readonly bool IsIndoors; + + public WorldStateCameraInfo(float groundDistance, bool isIndoors) + { + GroundDistance = groundDistance; + IsIndoors = isIndoors; + } + } + /// /// Holds state information retaining to world. /// @@ -370,10 +382,52 @@ public Ray CameraRayAtScreenPos(Vector2 pos, sbyte level = -1) return ray; } + public WorldStateCameraInfo CameraInfo() + { + if (CameraMode != CameraRenderMode._3D) + { + // Use the zoom level and precise zoom to estimate camera height + + int zoomDist = 3; + + switch (Zoom) + { + case WorldZoom.Near: + zoomDist = 0; + break; + case WorldZoom.Medium: + zoomDist = 1; + break; + case WorldZoom.Far: + zoomDist = 3; + break; + } + + // TODO: alter with smooth zoom + return new WorldStateCameraInfo(15 + zoomDist * 40 * (1 / PreciseZoom), false); + } + else + { + var pos = Camera.Position; + float dist = Math.Max(0, pos.Y - (Cameras.ActiveCamera as CameraController3D)?.CamHeight ?? 0); + if (CameraMode == CameraRenderMode._3D && Cameras.ExternalTransitionActive()) + { + float pct = (float)Math.Pow(Cameras.GetExternalTransition().Percent, 10); + dist = 500 * pct + dist * (1 - pct); + } + + var tilePos = pos / WorldSpace.WorldUnitsPerTile; + tilePos = new Vector3(tilePos.X, tilePos.Z, tilePos.Y); + return new WorldStateCameraInfo(dist, World.Architecture.Blueprint.IsIndoorsPrecise(tilePos)); + } + } + public Vector2 Project2DCenterTile(Vector3 pos) { var ray = CameraRayAtScreenPos(WorldSpace.WorldPx / 2); ray.Position = new Vector3(pos.X, pos.Z, pos.Y); + + if (pos.Z < 0) ray.Direction *= -1; var groundPlane = new Plane(new Vector3(0, 1, 0), 0); var t = ray.Intersects(groundPlane); if (t == null) return new Vector2(pos.X, pos.Y); @@ -384,16 +438,15 @@ public Vector2 Project2DCenterTile(Vector3 pos) } } - public bool ZeroWallOffset = false; + public Matrix? WallOffsetView; public Vector2 GetWallOffset() { - if (ZeroWallOffset) return Vector2.Zero; if (CameraMode == CameraRenderMode._2D) { var fd = Camera2D.FrontDirection(); return new Vector2(fd.X, fd.Z) / -6; } - var vd = View; + var vd = WallOffsetView ?? View; vd.M41 = 0; vd.M42 = 0; vd.M43 = 0; var transform = Vector3.Transform(new Vector3(1, 0, 0), vd); diff --git a/TSOClient/tso.world/app.config b/TSOClient/tso.world/app.config deleted file mode 100644 index 57f3478b8..000000000 --- a/TSOClient/tso.world/app.config +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TSOClient/tso.world/packages.config b/TSOClient/tso.world/packages.config deleted file mode 100644 index 78caa928a..000000000 --- a/TSOClient/tso.world/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 54e32187c..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,66 +0,0 @@ -# .NET Desktop (+ core i guess) -# Build and run tests for .NET Desktop or Windows classic desktop solutions. -# Add steps that publish symbols, save build artifacts, and more: -# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net - -trigger: -- master - -pool: - vmImage: 'windows-2019' - -variables: - solution: './TSOClient/FreeSO.sln' - buildPlatform: 'Any CPU' - buildConfiguration: 'Release' - -steps: -- checkout: self - submodules: recursive - persistCredentials: true - -- powershell: cd ./Other/libs/FSOMonoGame/; ./protobuild.exe --generate; cd ../../../ - name: Protobuild - continueOnError: true - -- task: NuGetToolInstaller@1 - -- powershell: cd ./TSOClient/FSO.SimAntics.JIT.Roslyn/; dotnet restore; cd ../../ - name: RestoreRoslyn - continueOnError: true - -- task: NuGetCommand@2 - inputs: - command: 'restore' - restoreSolution: '$(solution)' - -- task: VSBuild@1 - name: BuildClient - inputs: - solution: '$(solution)' - platform: '$(buildPlatform)' - msbuildArgs: '/restore /t:FSO_IDE /p:Configuration=Release;OutDir=$(Build.ArtifactStagingDirectory)/client' - -- task: VSBuild@1 - name: BuildServer - inputs: - solution: '$(solution)' - platform: '$(buildPlatform)' - msbuildArgs: '/t:FSO_Server_Core:Publish /p:Configuration=Release;OutDir=$(Build.ArtifactStagingDirectory)/server' - -- task: VSTest@2 - inputs: - platform: '$(buildPlatform)' - configuration: '$(buildConfiguration)' - -- task: PublishBuildArtifacts@1 - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/client' - ArtifactName: 'FreeSOClient' - publishLocation: 'Container' - -- task: PublishBuildArtifacts@1 - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)/server' - ArtifactName: 'FreeSOServer' - publishLocation: 'Container' diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 000000000..baf3eeecc --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,22 @@ +# Stage 1: Build +FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build +WORKDIR /src +COPY TSOClient/ TSOClient/ +COPY Other/libs/MSDFData/ Other/libs/MSDFData/ +COPY Other/libs/TargaImagePCL/ Other/libs/TargaImagePCL/ +RUN dotnet publish TSOClient/FSO.Server.Core/FSO.Server.Core.csproj \ + -c Release --self-contained false -o /app + +# Stage 2: Runtime +FROM mcr.microsoft.com/dotnet/aspnet:9.0 + +LABEL org.opencontainers.image.title="FreeSO Server" +LABEL org.opencontainers.image.source="https://github.com/riperiperi/FreeSO" +LABEL org.opencontainers.image.licenses="MPL-2.0" + +WORKDIR /app +COPY --from=build /app . +COPY docker/entrypoint.sh /app/entrypoint.sh +RUN chmod +x /app/entrypoint.sh +EXPOSE 9000 33100 34100 35100 +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000..d0e8ab529 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,51 @@ +# FreeSO Server Docker Setup + +Runs the FreeSO server and MariaDB database via Docker Compose. + +## Usage + +Run from the repository root `FreeSO/`: + +```bash +docker compose -f docker/docker-compose.yml up --build -d +``` + +Stop the server: + +```bash +docker compose -f docker/docker-compose.yml down +``` + +## Configuration + +Edit `docker/config.json` before starting: + +- **`secret`** - Leave as `GENERATE` for auto-generation by the container, or set your own hex string +- **`public_host`** - Change if hosting remotely (defaults to `localhost`) +- **`database.connectionString`** - Update if you changed MariaDB credentials in `docker-compose.yml` + +Update `docker-compose.yml` to point to your local TSO client installation: + +Windows: +```yaml +- C:/Path/To/Your/TSOClient:/game:ro +``` +MacOS: +```yaml +- ~/Documents/The Sims Online/TSOClient:/game:ro +``` + + +## What's Running + +- **FreeSO server** - Game server on ports 9000 (API), 33100-33101 (city), 34100-34101 (lots), 35100-35101 (tasks) +- **MariaDB 11** - Database with persistent storage in a Docker volume + +The database is automatically initialized on first run. + +Connect to the server in game via default ip: `http://localhost:9000` (or your public IP if hosting remotely). + +## Requirements + +- The Sims Online client files +- Ports 9000, 33100-33101, 34100-34101, 35100-35101 available diff --git a/docker/config.json b/docker/config.json new file mode 100644 index 000000000..645111b30 --- /dev/null +++ b/docker/config.json @@ -0,0 +1,108 @@ +{ + "gameLocation": "/game/", + "secret": "GENERATE", + "simNFS": "/nfs", + "allOpenable": true, + + "database": { + "connectionString": "server=mariadb;uid=fsoserver;pwd=password;database=fso;" + }, + + "services": { + "tasks": { + "enabled": true, + "call_sign": "callisto", + "binding": "0.0.0.0:35100", + "internal_host": "127.0.0.1:35101", + "public_host": "localhost:35101", + "schedule": [ + { + "cron": "0 3 * * *", + "task": "prune_database", + "timeout": 3600, + "parameter": {} + }, + { + "cron": "0 4 * * *", + "task": "bonus", + "timeout": 3600, + "shard_id": 1, + "parameter": {} + }, + { + "cron": "0 4 * * *", + "task": "job_balance", + "timeout": 3600, + "parameter": {} + }, + { + "cron": "0 0 * * *", + "task": "neighborhood_tick", + "timeout": 3600, + "run_if_missed": true, + "parameter": {} + }, + { + "cron": "0 0 * * *", + "task": "birthday_gift", + "timeout": 3600, + "run_if_missed": true, + "parameter": {} + } + ], + "tuning": { + "bonus": { + "property_bonus": { + "per_unit": 10, + "overrides": { + "1": 1500, + "2": 1250, + "3": 1000 + } + }, + "visitor_bonus": { + "per_unit": 8 + } + } + } + }, + "userApi": { + "enabled": true, + "bindings": [ + "http://+:9000/" + ], + "maintenance": false + }, + "cities": [ + { + "call_sign": "ganymede", + "id": 1, + "binding": "0.0.0.0:33100", + "internal_host": "127.0.0.1:33101", + "public_host": "localhost:33101", + "neighborhoods": { + "mayor_elegibility_limit": 4, + "mayor_elegibility_falloff": 4, + "min_nominations": 2, + "election_week_align": true, + "election_move_penalty": 14 + } + } + ], + "lots": [ + { + "call_sign": "europa", + "binding": "0.0.0.0:34100", + "internal_host": "127.0.0.1:34101", + "public_host": "localhost:34101", + "max_lots": 25, + "cities": [ + { + "id": 1, + "host": "127.0.0.1:33100" + } + ] + } + ] + } +} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..717d55591 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,43 @@ +name: freeso + +services: + mariadb: + image: mariadb:11 + environment: + MARIADB_ROOT_PASSWORD: rootpassword + MARIADB_DATABASE: fso + MARIADB_USER: fsoserver + MARIADB_PASSWORD: password + volumes: + - mariadb_data:/var/lib/mysql + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 5s + retries: 10 + + freeso-server: + build: + context: .. + dockerfile: docker/Dockerfile + labels: + org.opencontainers.image.source: "https://github.com/riperiperi/FreeSO" + org.opencontainers.image.licenses: "MPL-2.0" + depends_on: + mariadb: + condition: service_healthy + ports: + - "9000:9000" + - "33100:33100" + - "33101:33101" + - "34100:34100" + - "34101:34101" + - "35100:35100" + - "35101:35101" + volumes: + - ~/Documents/The Sims Online/TSOClient:/game:ro + - ./nfs:/nfs:rw + - ./config.json:/app/config.json:cached + +volumes: + mariadb_data: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 000000000..93fbbe73f --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -e + +# Auto-generate a secret if the user hasn't provided one +if grep -q '"secret": "GENERATE"' /app/config.json; then + SECRET=$(openssl rand -hex 32) + sed "s/\"secret\": \"GENERATE\"/\"secret\": \"$SECRET\"/" /app/config.json > /tmp/config.json + cp /tmp/config.json /app/config.json + rm /tmp/config.json + echo "Generated random server secret." +fi + +echo "Running db-init..." +yes y | dotnet FSO.Server.Core.dll db-init || true + +echo "Starting server..." +exec dotnet FSO.Server.Core.dll run