├── .github └── workflows │ ├── build.yml │ └── codeql.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmarks ├── DoomSharp.Benchmarks.Runner │ ├── DoomSharp.Benchmarks.Runner.csproj │ ├── Program.cs │ └── Workloads │ │ ├── ArrayClearVsNewInstance.cs │ │ ├── ArraysVsLists.cs │ │ ├── StructsVsClasses.cs │ │ └── UnsafeVsSafe.cs └── DoomSharp.Benchmarks.sln └── src ├── DoomSharp.AnsiConsole ├── ConsoleOutputRenderer.cs ├── Data │ └── WadStreamProvider.cs ├── DoomConsole.cs ├── DoomSharp.AnsiConsole.csproj ├── Program.cs ├── Properties │ └── launchSettings.json └── fmod.dll ├── DoomSharp.Core ├── Abstractions │ └── IWadStreamProvider.cs ├── Angle.cs ├── Constants.cs ├── Data │ ├── DoomRandom.cs │ ├── PurgeTag.cs │ ├── WadFile.cs │ ├── WadFileCollection.cs │ └── WadLump.cs ├── DoomErrorException.cs ├── DoomGame.cs ├── DoomMath.cs ├── DoomSharp.Core.csproj ├── Extensions │ └── DoomConvert.cs ├── Fixed.cs ├── GameAction.cs ├── GameLanguage.cs ├── GameLogic │ ├── AmmoType.cs │ ├── Button.cs │ ├── Ceiling.cs │ ├── CeilingType.cs │ ├── Cheat.cs │ ├── DirectionType.cs │ ├── DividerLine.cs │ ├── Door.cs │ ├── DoorType.cs │ ├── FireFlicker.cs │ ├── Floor.cs │ ├── FloorType.cs │ ├── GameController.cs │ ├── GlowingLight.cs │ ├── Intercept.cs │ ├── IntermissionState.cs │ ├── KeyCardType.cs │ ├── LightFlash.cs │ ├── MapLumps.cs │ ├── MapObject.cs │ ├── MapObjectFlag.cs │ ├── MapObjectInfo.cs │ ├── MapObjectType.cs │ ├── MapThing.cs │ ├── MapThingFlag.cs │ ├── Platform.cs │ ├── PlatformState.cs │ ├── PlatformType.cs │ ├── Player.cs │ ├── PlayerSprite.cs │ ├── PlayerSpriteType.cs │ ├── PlayerState.cs │ ├── PowerUpType.cs │ ├── Result.cs │ ├── SpriteNum.cs │ ├── StaircaseType.cs │ ├── State.cs │ ├── StateNum.cs │ ├── StrobeFlash.cs │ ├── SwitchControl.cs │ ├── Thinker.cs │ ├── Trigger.cs │ ├── WeaponInfo.cs │ ├── WeaponType.cs │ ├── WorldMapInfo.cs │ └── WorldMapPlayer.cs ├── GameMode.cs ├── GameState.cs ├── Graphics │ ├── AnimatingItem.cs │ ├── AnimationDefinition.cs │ ├── BoundingBox.cs │ ├── ClipWallSegment.cs │ ├── Column.cs │ ├── DrawSegment.cs │ ├── IOutputRenderer.cs │ ├── MapTexture.cs │ ├── Patch.cs │ ├── RenderEngine.cs │ ├── RenderTypeDefinitions.cs │ ├── Sky.cs │ ├── Sprite.cs │ ├── Texture.cs │ ├── Video.cs │ ├── VisPlane.cs │ ├── VisSprite.cs │ └── WipeMethod.cs ├── IConsole.cs ├── Input │ └── InputTypes.cs ├── Internals │ ├── FMOD.cs │ └── SoundDriver.cs ├── Networking │ ├── Command.cs │ ├── DoomCommunication.cs │ ├── DoomData.cs │ ├── NetworkController.cs │ └── TicCommand.cs ├── NullConsole.cs ├── NullGraphics.cs ├── SkillLevel.cs ├── Sound │ ├── ISoundDriver.cs │ ├── Mus2MidiConverter.cs │ ├── MusicInfo.cs │ ├── MusicType.cs │ ├── NullSoundDriver.cs │ ├── SfxInfo.cs │ ├── SoundChannel.cs │ ├── SoundController.cs │ └── SoundType.cs ├── UI │ ├── HudController.cs │ ├── IntermissionController.cs │ ├── Menu.cs │ ├── MenuController.cs │ ├── Messages.cs │ └── StatusBar.cs └── Zone.cs ├── DoomSharp.Droid.Bindings ├── Additions │ └── AboutAdditions.txt ├── DoomSharp.Droid.Bindings.csproj ├── Jars │ ├── arm64-v8a │ │ └── libfmod.so │ ├── armeabi-v7a │ │ └── libfmod.so │ ├── fmod.jar │ └── x86_64 │ │ └── libfmod.so └── Transforms │ ├── EnumFields.xml │ ├── EnumMethods.xml │ └── Metadata.xml ├── DoomSharp.Maui ├── App.xaml ├── App.xaml.cs ├── AppShell.xaml ├── AppShell.xaml.cs ├── Behaviors │ ├── KeyboardBehavior.Windows.cs │ └── KeyboardBehavior.cs ├── Controls │ └── GameControl.cs ├── Data │ └── WadStreamProvider.cs ├── DoomSharp.Maui.csproj ├── MainPage.xaml ├── MainPage.xaml.cs ├── MauiProgram.cs ├── Model │ ├── Int32Rect.cs │ ├── Key.cs │ └── KeyDownEventArgs.cs ├── Platforms │ ├── Android │ │ ├── AndroidManifest.xml │ │ ├── MainActivity.cs │ │ ├── MainApplication.cs │ │ └── Resources │ │ │ └── values │ │ │ └── colors.xml │ ├── MacCatalyst │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs │ ├── Tizen │ │ ├── Main.cs │ │ └── tizen-manifest.xml │ ├── Windows │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── Package.appxmanifest │ │ └── app.manifest │ └── iOS │ │ ├── AppDelegate.cs │ │ ├── Info.plist │ │ └── Program.cs ├── Properties │ └── launchSettings.json ├── Resources │ ├── AppIcon │ │ ├── appicon.svg │ │ └── appiconfg.svg │ ├── Fonts │ │ ├── OpenSans-Regular.ttf │ │ └── OpenSans-Semibold.ttf │ ├── Images │ │ ├── action.png │ │ ├── dotnet_bot.svg │ │ ├── down.png │ │ ├── left.png │ │ ├── right.png │ │ ├── start.png │ │ ├── up.png │ │ └── use.png │ ├── Raw │ │ ├── AboutAssets.txt │ │ └── DOOM1.WAD │ ├── Splash │ │ └── splash.svg │ └── Styles │ │ ├── Colors.xaml │ │ └── Styles.xaml ├── ViewModels │ ├── BitmapRenderedEventHandler.cs │ ├── ConsoleViewModel.cs │ ├── MainViewModel.cs │ └── ViewModelLocator.cs └── fmod.dll ├── DoomSharp.Windows ├── Annotations.cs ├── App.xaml ├── App.xaml.cs ├── AssemblyInfo.cs ├── ConsoleOutput.xaml ├── ConsoleOutput.xaml.cs ├── Data │ └── WadStreamProvider.cs ├── DoomSharp.Windows.csproj ├── MainWindow.xaml ├── MainWindow.xaml.cs ├── ViewModels │ ├── ConsoleViewModel.cs │ ├── MainViewModel.cs │ └── ViewModelLocator.cs └── fmod.dll ├── DoomSharp.sln ├── DoomSharp.sln.DotSettings └── DoomSharp.sln.startup.json /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: windows-latest 9 | steps: 10 | - uses: actions/setup-dotnet@v3 11 | with: 12 | dotnet-version: '8.x' 13 | 14 | - uses: actions/setup-java@v4 15 | with: 16 | java-version: '21.x' 17 | distribution: 'microsoft' 18 | 19 | - uses: actions/checkout@v3 20 | 21 | - run: | 22 | dotnet build src/DoomSharp.sln 23 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '35 17 * * 0' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: 'windows-latest' 31 | timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} 32 | permissions: 33 | # required for all workflows 34 | security-events: write 35 | 36 | # required to fetch internal or private CodeQL packs 37 | packages: read 38 | 39 | # only required for workflows in private repositories 40 | actions: read 41 | contents: read 42 | 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | include: 47 | - language: csharp 48 | build-mode: autobuild 49 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 50 | # Use `c-cpp` to analyze code written in C, C++ or both 51 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 52 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 53 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 54 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 55 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 56 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 57 | steps: 58 | - name: Checkout repository 59 | uses: actions/checkout@v4 60 | 61 | - uses: actions/setup-dotnet@v3 62 | with: 63 | dotnet-version: '8.x' 64 | 65 | - uses: actions/setup-java@v4 66 | with: 67 | java-version: '21.x' 68 | distribution: 'microsoft' 69 | 70 | # Initializes the CodeQL tools for scanning. 71 | - name: Initialize CodeQL 72 | uses: github/codeql-action/init@v3 73 | with: 74 | languages: ${{ matrix.language }} 75 | build-mode: ${{ matrix.build-mode }} 76 | # If you wish to specify custom queries, you can do so here or in a config file. 77 | # By default, queries listed here will override any specified in a config file. 78 | # Prefix the list here with "+" to use these queries and those in the config file. 79 | 80 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 81 | # queries: security-extended,security-and-quality 82 | 83 | # If the analyze step fails for one of the languages you are analyzing with 84 | # "We were unable to automatically build your code", modify the matrix above 85 | # to set the build mode to "manual" for that language. Then modify this step 86 | # to build your code. 87 | # ℹ️ Command-line programs to run using the OS shell. 88 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 89 | - if: matrix.build-mode == 'manual' 90 | shell: bash 91 | run: | 92 | echo 'If you are using a "manual" build mode for one or more of the' \ 93 | 'languages you are analyzing, replace this with the commands to build' \ 94 | 'your code, for example:' 95 | echo ' make bootstrap' 96 | echo ' make release' 97 | exit 1 98 | 99 | - name: Perform CodeQL Analysis 100 | uses: github/codeql-action/analyze@v3 101 | with: 102 | category: "/language:${{matrix.language}}" 103 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DooM# 2 | 3 | This is a vanilla port of the [DooM source code](https://github.com/id-Software/DOOM/tree/master/linuxdoom-1.10) to C#, currently targeting .NET 8. 4 | 5 | 6 | We currently support two platforms: 7 | * Windows, via WPF and MAUI 8 | * Android 9 | 10 | ## Getting started 11 | 12 | * Make sure you have the .NET 8 SDK installed: https://dotnet.microsoft.com/en-us/download . 13 | 14 | * Install [Visual Studio 2022](https://visualstudio.microsoft.com/downloads), there's a free community edition available, or use [VS Code](https://code.visualstudio.com/) if you want a lightweight editor. You can also check out [Rider](https://www.jetbrains.com/rider) from JetBrains if you're more familiar with IntelliJ. 15 | 16 | * Fork or clone this repo (duh). 17 | 18 | * Open the `src/DoomSharp.sln` file and set the DoomSharp.Windows project as your startup project. 19 | 20 | * Before running, you need some WAD files. If you have an original copy of DooM, you can use these files. If not, you can freely download the shareware files here: https://www.doomworld.com/idgames/idstuff/doom/doom19s 21 | 22 | * Once you have your WAD files in a directory - I prefer to use a directory called WAD in the repository's folder structure - , open the DoomSharp.Windows project properties and add a debug profile. In this debug profile, add an environment variable called `DOOMWADDIR` and set its value to the path that contains the WAD file(s). 23 | This should give you a file called `launchSettings.json` which looks like this: 24 | ```json 25 | { 26 | "profiles": { 27 | "DoomSharp.Windows": { 28 | "commandName": "Project", 29 | "environmentVariables": { 30 | "DOOMWADDIR": "Path\\To\\Wads" 31 | } 32 | } 33 | } 34 | } 35 | ``` 36 | 37 | ## Progress 38 | Loading the original WAD files should work, only the shareware and retail WADs have been tested. 39 | 40 | The demo games are not running as they should yet, the commands seem to be out of sync somehow. 41 | You can start your own game, and probably play the entire first episode! 42 | 43 | What does not yet work: 44 | * Android: music (on Windows, music should work if you have the standard MIDI sound bank installed) 45 | * Finishing an episode and going to the next one 46 | * Returning from the secret level 47 | * The automap 48 | * Cheats (hah!) 49 | * Messages when you pick up items 50 | * Network support (not the focus of this port, but who knows, one day) 51 | -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/DoomSharp.Benchmarks.Runner.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0;net7.0;net8.0 6 | enable 7 | enable 8 | true 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/Program.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Running; 2 | using DoomSharp.Benchmarks.Runner.Workloads; 3 | 4 | var test = new UnsafeVsSafe(); 5 | 6 | // BenchmarkRunner.Run(); 7 | // BenchmarkRunner.Run(); 8 | // BenchmarkRunner.Run(); 9 | BenchmarkRunner.Run(); -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/Workloads/ArrayClearVsNewInstance.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace DoomSharp.Benchmarks.Runner.Workloads; 5 | 6 | [SimpleJob(RuntimeMoniker.Net60)] 7 | [SimpleJob(RuntimeMoniker.NativeAot60)] 8 | [SimpleJob(RuntimeMoniker.Net70)] 9 | [SimpleJob(RuntimeMoniker.NativeAot70)] 10 | [SimpleJob(RuntimeMoniker.Net80)] 11 | [SimpleJob(RuntimeMoniker.NativeAot80)] 12 | [MemoryDiagnoser] 13 | public class ArrayClearVsNewInstance 14 | { 15 | private const int Size = 1024; 16 | 17 | private readonly Random _randomizer = new(42); 18 | 19 | private int[] _renewedArray = new int[Size]; 20 | private readonly int[] _preAllocatedArray = new int[Size]; 21 | 22 | [Benchmark] 23 | public void RenewingArrays() 24 | { 25 | // Create a new instance and fill with data 26 | _renewedArray = new int[Size]; 27 | for (var i = 0; i < Size; i++) 28 | { 29 | _renewedArray[i] = i; 30 | } 31 | } 32 | 33 | [Benchmark] 34 | public void ReusingArrays() 35 | { 36 | // Clear the array and fill with data 37 | Array.Clear(_preAllocatedArray); 38 | for (var i = 0; i < Size; i++) 39 | { 40 | _preAllocatedArray[i] = i; 41 | } 42 | } 43 | 44 | [Benchmark] 45 | public void RenewingArraysUsingRandom() 46 | { 47 | // Create a new instance and fill with data 48 | _renewedArray = new int[Size]; 49 | for (var i = 0; i < Size; i++) 50 | { 51 | _renewedArray[i] = _randomizer.Next(); 52 | } 53 | } 54 | 55 | [Benchmark] 56 | public void ReusingArraysUsingRandom() 57 | { 58 | // Clear the array and fill with data 59 | Array.Clear(_preAllocatedArray); 60 | for (var i = 0; i < Size; i++) 61 | { 62 | _preAllocatedArray[i] = _randomizer.Next(); 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/Workloads/ArraysVsLists.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace DoomSharp.Benchmarks.Runner.Workloads; 5 | 6 | [SimpleJob(RuntimeMoniker.Net60)] 7 | [SimpleJob(RuntimeMoniker.Net70)] 8 | [SimpleJob(RuntimeMoniker.Net80)] 9 | [MemoryDiagnoser] 10 | public class ArraysVsLists 11 | { 12 | private const int Size = 1024; 13 | 14 | private readonly Random _randomizer = new(42); 15 | 16 | [Benchmark] 17 | public void PreAllocatedArray() 18 | { 19 | // Create a new instance with the intended size, and fill with data 20 | var array = new int[Size]; 21 | for (var i = 0; i < Size; i++) 22 | { 23 | array[i] = _randomizer.Next(); 24 | } 25 | } 26 | 27 | [Benchmark] 28 | public void PreAllocatedList() 29 | { 30 | // Create a new instance with the intended size, and fill with data 31 | // ReSharper disable once CollectionNeverQueried.Local 32 | var list = new List(Size); 33 | for (var i = 0; i < Size; i++) 34 | { 35 | list.Add(_randomizer.Next()); 36 | } 37 | } 38 | 39 | [Benchmark] 40 | public void DynamicallyResizedList() 41 | { 42 | // Create a new instance with a minimum size, and fill with data while resizing 43 | // ReSharper disable once CollectionNeverQueried.Local 44 | var list = new List(); 45 | for (var i = 0; i < Size; i++) 46 | { 47 | list.Add(_randomizer.Next()); 48 | } 49 | 50 | list.TrimExcess(); // it's only fair to trim the list to the actual size since we're doing it for arrays as well 51 | } 52 | 53 | [Benchmark] 54 | public void DynamicallyResizedArrayDoublingSequence() 55 | { 56 | // Create a new instance with a small size, and fill with data while resizing in increasing sizes 57 | var array = new int[4]; 58 | for (var i = 0; i < Size; i++) 59 | { 60 | if (array.Length == i) 61 | { 62 | Array.Resize(ref array, i * 2); 63 | } 64 | 65 | array[i] = _randomizer.Next(); 66 | } 67 | 68 | // Resize down to the actual size 69 | Array.Resize(ref array, Size); 70 | } 71 | 72 | 73 | [Benchmark] 74 | public void DynamicallyResizedArrayChunked() 75 | { 76 | // Create a new instance with a small size, and fill with data while resizing in chunks 77 | var array = new int[10]; 78 | for (var i = 0; i < Size; i++) 79 | { 80 | if (array.Length == i) 81 | { 82 | Array.Resize(ref array, i + 10); 83 | } 84 | array[i] = _randomizer.Next(); 85 | } 86 | 87 | // Resize down to the actual size 88 | Array.Resize(ref array, Size); 89 | } 90 | 91 | [Benchmark] 92 | public void DynamicallyResizedArrayAggressive() 93 | { 94 | // Create a new instance with a minimum size, and fill with data while resizing 95 | var array = new int[1]; 96 | for (var i = 0; i < Size; i++) 97 | { 98 | if (array.Length == i) 99 | { 100 | Array.Resize(ref array, i + 1); 101 | } 102 | array[i] = _randomizer.Next(); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/Workloads/StructsVsClasses.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace DoomSharp.Benchmarks.Runner.Workloads; 5 | 6 | [SimpleJob(RuntimeMoniker.Net60)] 7 | [SimpleJob(RuntimeMoniker.NativeAot60)] 8 | [SimpleJob(RuntimeMoniker.Net70)] 9 | [SimpleJob(RuntimeMoniker.NativeAot70)] 10 | [SimpleJob(RuntimeMoniker.Net80)] 11 | [SimpleJob(RuntimeMoniker.NativeAot80)] 12 | [MemoryDiagnoser] 13 | public class StructsVsClasses 14 | { 15 | public class SimpleClass 16 | { 17 | public int Value1; 18 | public int Value2; 19 | } 20 | 21 | public struct SimpleStruct 22 | { 23 | public int Value1; 24 | public int Value2; 25 | } 26 | 27 | private const int Size = 1_000_000; 28 | 29 | private readonly SimpleClass[] _classes = new SimpleClass[Size]; 30 | private readonly SimpleStruct[] _structs = new SimpleStruct[Size]; 31 | 32 | public StructsVsClasses() 33 | { 34 | for (var i = 0; i < Size; i++) 35 | { 36 | _classes[i] = new SimpleClass { Value1 = Random.Shared.Next(), Value2 = Random.Shared.Next() }; 37 | _structs[i] = new SimpleStruct { Value1 = Random.Shared.Next(), Value2 = Random.Shared.Next() }; 38 | } 39 | } 40 | 41 | [Benchmark] 42 | public int StructArrayAccess() 43 | { 44 | var result = 0; 45 | for (var i = 0; i < Size; i++) 46 | { 47 | result += Helper1(_structs, i); 48 | } 49 | 50 | return result; 51 | } 52 | 53 | [Benchmark] 54 | public int ClassArrayAccess() 55 | { 56 | var result = 0; 57 | for (var i = 0; i < Size; i++) 58 | { 59 | result += Helper2(_classes, i); 60 | } 61 | 62 | return result; 63 | } 64 | 65 | [Benchmark] 66 | public int StructImmediateAccess() 67 | { 68 | var result = 0; 69 | for (var i = 0; i < Size; i++) 70 | { 71 | result += _structs[i].Value1; 72 | } 73 | 74 | return result; 75 | } 76 | 77 | [Benchmark] 78 | public int ClassImmediateAccess() 79 | { 80 | var result = 0; 81 | for (var i = 0; i < Size; i++) 82 | { 83 | result += _classes[i].Value1; 84 | } 85 | 86 | return result; 87 | } 88 | 89 | public int Helper1(SimpleStruct[] array, int index) 90 | { 91 | return array[index].Value1; 92 | } 93 | 94 | public int Helper2(SimpleClass[] array, int index) 95 | { 96 | return array[index].Value1; 97 | } 98 | } -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.Runner/Workloads/UnsafeVsSafe.cs: -------------------------------------------------------------------------------- 1 | using BenchmarkDotNet.Attributes; 2 | using BenchmarkDotNet.Jobs; 3 | 4 | namespace DoomSharp.Benchmarks.Runner.Workloads; 5 | 6 | [SimpleJob(RuntimeMoniker.Net60)] 7 | [SimpleJob(RuntimeMoniker.NativeAot60)] 8 | [SimpleJob(RuntimeMoniker.Net70)] 9 | [SimpleJob(RuntimeMoniker.NativeAot70)] 10 | [SimpleJob(RuntimeMoniker.Net80)] 11 | [SimpleJob(RuntimeMoniker.NativeAot80)] 12 | [MemoryDiagnoser] 13 | public class UnsafeVsSafe 14 | { 15 | // Code sample: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code#pointer-types 16 | 17 | [Benchmark] 18 | public void Unsafe() 19 | { 20 | // Normal pointer to an object. 21 | var a = new int[] { 10, 20, 30, 40, 50 }; 22 | // Must be in unsafe code to use interior pointers. 23 | unsafe 24 | { 25 | // Must pin object on heap so that it doesn't move while using interior pointers. 26 | fixed (int* p = &a[0]) 27 | { 28 | // p is pinned as well as object, so create another pointer to show incrementing it. 29 | int* p2 = p; 30 | Console.WriteLine(*p2); 31 | // Incrementing p2 bumps the pointer by four bytes due to its type ... 32 | p2 += 1; 33 | Console.WriteLine(*p2); 34 | p2 += 1; 35 | Console.WriteLine(*p2); 36 | Console.WriteLine("--------"); 37 | Console.WriteLine(*p); 38 | // Dereferencing p and incrementing changes the value of a[0] ... 39 | *p += 1; 40 | Console.WriteLine(*p); 41 | *p += 1; 42 | Console.WriteLine(*p); 43 | } 44 | } 45 | 46 | Console.WriteLine("--------"); 47 | Console.WriteLine(a[0]); 48 | 49 | /* 50 | Output: 51 | 10 52 | 20 53 | 30 54 | -------- 55 | 10 56 | 11 57 | 12 58 | -------- 59 | 12 60 | */ 61 | } 62 | 63 | [Benchmark] 64 | public void Safe() 65 | { 66 | // Normal pointer to an object. 67 | var a = new int[] { 10, 20, 30, 40, 50 }; 68 | 69 | var index = 0; 70 | var index2 = index; 71 | Console.WriteLine(a[index2]); 72 | // Incrementing p2 bumps the pointer by four bytes due to its type ... 73 | index2 += 1; 74 | Console.WriteLine(a[index2]); 75 | index2 += 1; 76 | Console.WriteLine(a[index2]); 77 | Console.WriteLine("--------"); 78 | Console.WriteLine(a[index]); 79 | // Dereferencing p and incrementing changes the value of a[0] ... 80 | a[index] += 1; 81 | Console.WriteLine(a[index]); 82 | a[index] += 1; 83 | Console.WriteLine(a[index]); 84 | 85 | Console.WriteLine("--------"); 86 | Console.WriteLine(a[0]); 87 | 88 | /* 89 | Output: 90 | 10 91 | 20 92 | 30 93 | -------- 94 | 10 95 | 11 96 | 12 97 | -------- 98 | 12 99 | */ 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /benchmarks/DoomSharp.Benchmarks.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomSharp.Benchmarks.Runner", "DoomSharp.Benchmarks.Runner\DoomSharp.Benchmarks.Runner.csproj", "{C832F225-6078-46FA-9344-B414461039FF}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(SolutionProperties) = preSolution 14 | HideSolutionNode = FALSE 15 | EndGlobalSection 16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 17 | {C832F225-6078-46FA-9344-B414461039FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {C832F225-6078-46FA-9344-B414461039FF}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {C832F225-6078-46FA-9344-B414461039FF}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {C832F225-6078-46FA-9344-B414461039FF}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/ConsoleOutputRenderer.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core; 2 | using DoomSharp.Core.Graphics; 3 | using Spectre.Console; 4 | 5 | namespace DoomSharp.AnsiConsole; 6 | 7 | public class ConsoleOutputRenderer : IGraphics 8 | { 9 | private Canvas? _canvas; 10 | private Color[]? _palette; 11 | 12 | public void Initialize() 13 | { 14 | _canvas = new Canvas(Constants.ScreenWidth, Constants.ScreenHeight) 15 | { 16 | PixelWidth = 1, 17 | Scale = true 18 | }; 19 | 20 | Spectre.Console.AnsiConsole.Write(_canvas); 21 | } 22 | 23 | public void UpdatePalette(byte[] palette) 24 | { 25 | var colors = new List(256); 26 | for (var i = 0; i < 256 * 3; i += 3) 27 | { 28 | colors.Add(new Color(palette[i], palette[i + 1], palette[i + 2])); 29 | } 30 | 31 | _palette = colors.ToArray(); 32 | } 33 | 34 | public void ScreenReady(byte[] output) 35 | { 36 | if (_canvas is null || _palette is null) 37 | { 38 | return; 39 | } 40 | 41 | var x = 0; 42 | var y = 0; 43 | foreach (var colorIndex in output) 44 | { 45 | _canvas.SetPixel(x++, y, _palette[colorIndex]); 46 | 47 | if (x >= Constants.ScreenWidth) 48 | { 49 | x = 0; 50 | y++; 51 | } 52 | } 53 | 54 | Spectre.Console.AnsiConsole.Clear(); 55 | Spectre.Console.AnsiConsole.Write(_canvas); 56 | } 57 | 58 | public void StartTic() 59 | { 60 | // TODO: hook up console input and dispatch events to DoomGame 61 | } 62 | } -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/Data/WadStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DoomSharp.Core; 3 | using DoomSharp.Core.Abstractions; 4 | using DoomSharp.Core.Data; 5 | 6 | namespace DoomSharp.AnsiConsole.Data 7 | { 8 | internal sealed class WadStreamProvider : IWadStreamProvider 9 | { 10 | public Task LoadFromFile(string file) 11 | { 12 | var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None); 13 | var br = new BinaryReader(fs, Encoding.ASCII, false); 14 | 15 | DoomGame.Console.WriteLine($" adding {file}"); 16 | 17 | if (string.Equals(Path.GetExtension(file), ".wad", StringComparison.OrdinalIgnoreCase)) 18 | { 19 | // WAD file 20 | return Task.FromResult(LoadWad(file, br)); 21 | } 22 | else 23 | { 24 | // Single lump file 25 | // TODO 26 | } 27 | 28 | return Task.FromResult(null); 29 | } 30 | 31 | private static WadFile? LoadWad(string file, BinaryReader reader) 32 | { 33 | var header = new WadFile.WadInfo 34 | { 35 | Identification = Encoding.ASCII.GetString(reader.ReadBytes(4)).TrimEnd('\0'), 36 | NumLumps = reader.ReadInt32(), 37 | InfoTableOfs = reader.ReadInt32() 38 | }; 39 | 40 | var wadFile = new WadFile(reader) 41 | { 42 | Header = header 43 | }; 44 | 45 | if (string.Equals(wadFile.Header.Identification, "IWAD", StringComparison.Ordinal) == false) 46 | { 47 | if (string.Equals(wadFile.Header.Identification, "PWAD", StringComparison.Ordinal) == false) 48 | { 49 | DoomGame.Error($"Wad file {file} doesn't have IWAD or PWAD id"); 50 | return null; 51 | } 52 | } 53 | 54 | var fileInfo = new List(wadFile.LumpCount); 55 | 56 | reader.BaseStream.Seek(wadFile.Header.InfoTableOfs, SeekOrigin.Begin); 57 | for (var i = 0; i < wadFile.LumpCount; i++) 58 | { 59 | var lump = WadFile.FileLump.ReadFromWadData(reader); 60 | fileInfo.Add(new WadLump(wadFile, lump)); 61 | } 62 | 63 | wadFile.Lumps = fileInfo; 64 | 65 | return wadFile; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/DoomConsole.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core; 2 | using Spectre.Console; 3 | 4 | namespace DoomSharp.AnsiConsole; 5 | 6 | public class DoomConsole : IConsole 7 | { 8 | //private readonly Panel _panel; 9 | //private readonly LiveDisplay _liveUpdate; 10 | 11 | public DoomConsole(/*Layout zone*/) 12 | { 13 | //Spectre.Console.AnsiConsole.Live().Start(); 14 | //_liveUpdate = new LiveDisplay(Spectre.Console.AnsiConsole.Console, zone); 15 | //// _panel = new Panel(_liveUpdate).Expand(); 16 | //zone.Update(_liveUpdate.); 17 | } 18 | 19 | public void Write(string message) 20 | { 21 | //_liveUpdate.Update(message); 22 | } 23 | 24 | public void SetTitle(string title) 25 | { 26 | //_panel.Header(title, Justify.Center); 27 | } 28 | 29 | public void Shutdown() 30 | { 31 | } 32 | } -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/DoomSharp.AnsiConsole.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0 6 | enable 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.AnsiConsole; 2 | using DoomSharp.AnsiConsole.Data; 3 | using DoomSharp.Core; 4 | using DoomSharp.Core.Data; 5 | using DoomSharp.Core.Internals; 6 | using Spectre.Console; 7 | using System; 8 | using System.Text; 9 | 10 | //const string GamePanel = "Game"; 11 | //const string ConsolePanel = "Console"; 12 | 13 | //var layout = new Layout(); 14 | //layout.SplitRows( 15 | // new Layout(GamePanel), 16 | // new Layout(ConsolePanel) 17 | //); 18 | 19 | //var gamePanel = layout[GamePanel]; 20 | //var consolePanel = layout[ConsolePanel]; 21 | 22 | //gamePanel.Ratio(3); 23 | //consolePanel.Ratio(1); 24 | //AnsiConsole.Write(layout); 25 | 26 | var soundDriver = new SoundDriver(); 27 | 28 | try 29 | { 30 | //DoomGame.SetConsole(new DoomConsole()); 31 | DoomGame.SetOutputRenderer(new ConsoleOutputRenderer()); 32 | DoomGame.SetSoundDriver(soundDriver); 33 | WadFileCollection.Init(new WadStreamProvider()); 34 | 35 | Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8; 36 | AnsiConsole.Profile.Width = Constants.ScreenWidth/2; 37 | AnsiConsole.Profile.Height = Constants.ScreenHeight/2; 38 | 39 | await Task.Run(DoomGame.Instance.RunAsync); 40 | } 41 | finally 42 | { 43 | soundDriver.Dispose(); 44 | } -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DoomSharp.Windows": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOOMWADDIR": "C:\\git\\doom-sharp\\wads\\Retail", 7 | "DOOMWADDIRShareware": "C:\\git\\doom-sharp\\wads\\Shareware", 8 | "DOOMWADDIRRetail": "C:\\git\\doom-sharp\\wads\\Retail", 9 | "DOOMWADDIRDoom2": "C:\\git\\doom-sharp\\wads\\Doom2" 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /src/DoomSharp.AnsiConsole/fmod.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.AnsiConsole/fmod.dll -------------------------------------------------------------------------------- /src/DoomSharp.Core/Abstractions/IWadStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Data; 2 | 3 | namespace DoomSharp.Core.Abstractions 4 | { 5 | public interface IWadStreamProvider 6 | { 7 | Task LoadFromFile(string file); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Angle.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public readonly struct Angle 4 | { 5 | private readonly uint _value; 6 | 7 | // Binary Angle Measument, BAM. 8 | public static readonly Angle Angle0 = new(0); 9 | public static readonly Angle Angle45 = new(0x20000000); 10 | public static readonly Angle Angle90 = new(0x40000000); 11 | public static readonly Angle Angle180 = new(0x80000000); 12 | public static readonly Angle Angle270 = new(0xc0000000); 13 | 14 | public static readonly Angle TraceAngle = new(0xc000000); 15 | 16 | public Angle(uint value) 17 | { 18 | _value = value; 19 | } 20 | 21 | public Angle(int value) 22 | { 23 | _value = (uint)value; 24 | } 25 | 26 | public uint Value => _value; 27 | 28 | public double ToDegrees() 29 | { 30 | return 360 * ((double)_value / 0x100000000); 31 | } 32 | 33 | public static Angle Abs(Angle angle) 34 | { 35 | var value = (int)angle._value; 36 | return value < 0 ? new Angle(-value) : angle; 37 | } 38 | 39 | public static Angle operator +(Angle angle) 40 | { 41 | return angle; 42 | } 43 | 44 | public static Angle operator -(Angle angle) 45 | { 46 | var inverseAngle = -(int)angle._value; 47 | return new Angle(inverseAngle); 48 | } 49 | 50 | public static Angle operator +(Angle a, Angle b) 51 | { 52 | return new Angle(a._value + b._value); 53 | } 54 | 55 | public static Angle operator -(Angle a, Angle b) 56 | { 57 | return new Angle(a._value - b._value); 58 | } 59 | 60 | public static Angle operator *(uint a, Angle b) 61 | { 62 | return new Angle(a * b._value); 63 | } 64 | 65 | public static Angle operator *(Angle a, uint b) 66 | { 67 | return new Angle(a._value * b); 68 | } 69 | 70 | public static Angle operator /(Angle a, uint b) 71 | { 72 | return new Angle(a._value / b); 73 | } 74 | 75 | public static bool operator ==(Angle a, Angle b) 76 | { 77 | return a._value == b._value; 78 | } 79 | 80 | public static bool operator !=(Angle a, Angle b) 81 | { 82 | return a._value != b._value; 83 | } 84 | 85 | public static bool operator <(Angle a, Angle b) 86 | { 87 | return a._value < b._value; 88 | } 89 | 90 | public static bool operator >(Angle a, Angle b) 91 | { 92 | return a._value > b._value; 93 | } 94 | 95 | public static bool operator <=(Angle a, Angle b) 96 | { 97 | return a._value <= b._value; 98 | } 99 | 100 | public static bool operator >=(Angle a, Angle b) 101 | { 102 | return a._value >= b._value; 103 | } 104 | 105 | public override bool Equals(object? obj) 106 | { 107 | if (obj is Angle b) 108 | { 109 | return _value == b._value; 110 | } 111 | 112 | return false; 113 | } 114 | 115 | public override int GetHashCode() 116 | { 117 | return _value.GetHashCode(); 118 | } 119 | 120 | public override string ToString() 121 | { 122 | return ToDegrees().ToString(); 123 | } 124 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public static class Constants 4 | { 5 | public const int BaseWidth = 320; 6 | 7 | public const int ScreenWidth = 320; 8 | public const int ScreenHeight = 200; 9 | 10 | public const int ScreenMul = 1; 11 | public const float InvertedAspectRatio = 0.625f; 12 | 13 | /// 14 | /// State updates, number of ticks / second 15 | /// 16 | public const int TicRate = 35; 17 | 18 | public const int DemoMarker = 0x80; 19 | 20 | // Networking and tick handling related 21 | public const int BackupTics = 12; 22 | 23 | public const int DoomComId = 0x12345678; 24 | public const int MaxPlayers = 4; 25 | public const int MaxNetNodes = 8; // 4 players max + drones 26 | public const int ResendCount = 10; 27 | public const int PlayerDrone = 0x80; 28 | 29 | public const int MaxEvents = 64; 30 | 31 | // Fixed point, 32 bit as 16.16 32 | public const int FracBits = 16; 33 | public const int FracUnit = (1 << FracBits); 34 | 35 | public static readonly Fixed FloatSpeed = Fixed.FromInt(4); 36 | public const int MaxHealth = 100; 37 | public static readonly Fixed ViewHeight = Fixed.FromInt(41); 38 | 39 | // LineDef attributes 40 | 41 | public static class Line 42 | { 43 | /// 44 | /// Solid, is an obstacle. 45 | /// 46 | public const int Blocking = 1; 47 | 48 | /// 49 | /// Block monsters only. 50 | /// 51 | public const int BlockMonsters = 2; 52 | 53 | /// 54 | /// Backside will not be present at all if not two sided. 55 | /// 56 | public const int TwoSided = 4; 57 | 58 | // If a texture is pegged, the texture will have 59 | // the end exposed to air held constant at the 60 | // top or bottom of the texture (stairs or pulled 61 | // down things) and will move with a height change 62 | // of one of the neighbor sectors. 63 | // Unpegged textures allways have the first row of 64 | // the texture at the top pixel of the line for both 65 | // top and bottom textures (use next to windows). 66 | 67 | /// 68 | /// upper texture unpegged 69 | /// 70 | public const int DontPegTop = 8; 71 | 72 | /// 73 | /// lower texture unpegged 74 | /// 75 | public const int DontPegBottom = 16; 76 | 77 | /// 78 | /// In AutoMap: don't map as two-sided: IT'S A SECRET! 79 | /// 80 | public const int Secret = 32; 81 | 82 | /// 83 | /// Sound rendering: don't let sound cross two of these 84 | /// 85 | public const int SoundBlock = 64; 86 | 87 | /// 88 | /// Don't draw on the automap at all 89 | /// 90 | public const int DontDraw = 128; 91 | 92 | /// 93 | /// Set if already seen, thus drawn in automap. 94 | /// 95 | public const int Mapped = 256; 96 | } 97 | 98 | // mapblocks are used to check movement 99 | // against lines and things 100 | public const int MapBlockunits = 128; 101 | public static readonly Fixed MapBlockSize = Fixed.FromInt(MapBlockunits); 102 | public const int MapBlockShift = (FracBits + 7); 103 | // public const int MapBlockMask = (MapBlockSize - 1); 104 | public const int MapBlockToFrac = (MapBlockShift - FracBits); 105 | 106 | // MAXRADIUS is for precalculated sector block boxes 107 | // the spider demon is larger, 108 | // but we do not have any moving sectors nearby 109 | public static readonly Fixed MaxRadius = Fixed.FromInt(32); 110 | 111 | public static readonly Fixed Gravity = Fixed.Unit; 112 | public static readonly Fixed MaxMove = Fixed.FromInt(30); 113 | 114 | public static readonly Fixed UseRange = Fixed.FromInt(64); 115 | public static readonly Fixed MeleeRange = Fixed.FromInt(64); 116 | public static readonly Fixed MissileRange = Fixed.FromInt(32 * 64); 117 | 118 | // follow a player exclusively for 3 seconds 119 | public const int BaseThreshold = 100; 120 | 121 | public static class NetCommands 122 | { 123 | public const uint Exit = 0x80000000; 124 | public const int Retransmit = 0x40000000; 125 | public const int Setup = 0x20000000; 126 | public const int Kill = 0x10000000; // kill game 127 | public const int CheckSum = 0x0fffffff; 128 | } 129 | 130 | public static readonly string[] SpriteNames = 131 | { 132 | "TROO","SHTG","PUNG","PISG","PISF","SHTF","SHT2","CHGG","CHGF","MISG", 133 | "MISF","SAWG","PLSG","PLSF","BFGG","BFGF","BLUD","PUFF","BAL1","BAL2", 134 | "PLSS","PLSE","MISL","BFS1","BFE1","BFE2","TFOG","IFOG","PLAY","POSS", 135 | "SPOS","VILE","FIRE","FATB","FBXP","SKEL","MANF","FATT","CPOS","SARG", 136 | "HEAD","BAL7","BOSS","BOS2","SKUL","SPID","BSPI","APLS","APBX","CYBR", 137 | "PAIN","SSWV","KEEN","BBRN","BOSF","ARM1","ARM2","BAR1","BEXP","FCAN", 138 | "BON1","BON2","BKEY","RKEY","YKEY","BSKU","RSKU","YSKU","STIM","MEDI", 139 | "SOUL","PINV","PSTR","PINS","MEGA","SUIT","PMAP","PVIS","CLIP","AMMO", 140 | "ROCK","BROK","CELL","CELP","SHEL","SBOX","BPAK","BFUG","MGUN","CSAW", 141 | "LAUN","PLAS","SHOT","SGN2","COLU","SMT2","GOR1","POL2","POL5","POL4", 142 | "POL3","POL1","POL6","GOR2","GOR3","GOR4","GOR5","SMIT","COL1","COL2", 143 | "COL3","COL4","CAND","CBRA","COL6","TRE1","TRE2","ELEC","CEYE","FSKU", 144 | "COL5","TBLU","TGRN","TRED","SMBT","SMGT","SMRT","HDB1","HDB2","HDB3", 145 | "HDB4","HDB5","HDB6","POB1","POB2","BRS1","TLMP","TLP2" 146 | }; 147 | 148 | public const int MaxButtons = MaxPlayers * 4; 149 | public const int ButtonTime = TicRate; // 1 second in ticks 150 | 151 | /// 152 | /// Max number of wall switches in a level 153 | /// 154 | public const int MaxSwitches = 50; 155 | 156 | public const int MaxAnimations = 32; 157 | public const int MaxLineAnimations = 64; 158 | 159 | public const int NodeLeafSubSector = 0x8000; 160 | 161 | public static readonly Fixed OnFloorZ = Fixed.MinValue; 162 | public static readonly Fixed OnCeilingZ = Fixed.MaxValue; 163 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Data/DoomRandom.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Data; 2 | 3 | public static class DoomRandom 4 | { 5 | private static readonly int[] RandomTable = 6 | { 7 | 0, 8, 109, 220, 222, 241, 149, 107, 75, 248, 254, 140, 16, 66 , 8 | 74, 21, 211, 47, 80, 242, 154, 27, 205, 128, 161, 89, 77, 36 , 9 | 95, 110, 85, 48, 212, 140, 211, 249, 22, 79, 200, 50, 28, 188 , 10 | 52, 140, 202, 120, 68, 145, 62, 70, 184, 190, 91, 197, 152, 224 , 11 | 149, 104, 25, 178, 252, 182, 202, 182, 141, 197, 4, 81, 181, 242 , 12 | 145, 42, 39, 227, 156, 198, 225, 193, 219, 93, 122, 175, 249, 0 , 13 | 175, 143, 70, 239, 46, 246, 163, 53, 163, 109, 168, 135, 2, 235 , 14 | 25, 92, 20, 145, 138, 77, 69, 166, 78, 176, 173, 212, 166, 113 , 15 | 94, 161, 41, 50, 239, 49, 111, 164, 70, 60, 2, 37, 171, 75 , 16 | 136, 156, 11, 56, 42, 146, 138, 229, 73, 146, 77, 61, 98, 196 , 17 | 135, 106, 63, 197, 195, 86, 96, 203, 113, 101, 170, 247, 181, 113 , 18 | 80, 250, 108, 7, 255, 237, 129, 226, 79, 107, 112, 166, 103, 241 , 19 | 24, 223, 239, 120, 198, 58, 60, 82, 128, 3, 184, 66, 143, 224 , 20 | 145, 224, 81, 206, 163, 45, 63, 90, 168, 114, 59, 33, 159, 95 , 21 | 28, 139, 123, 98, 125, 196, 15, 70, 194, 253, 54, 14, 109, 226 , 22 | 71, 17, 161, 93, 186, 87, 244, 138, 20, 52, 123, 251, 26, 36 , 23 | 17, 46, 52, 231, 232, 76, 31, 221, 84, 37, 216, 165, 212, 106 , 24 | 197, 242, 98, 43, 39, 175, 254, 145, 190, 84, 118, 222, 187, 136 , 25 | 120, 163, 236, 249 26 | }; 27 | 28 | private static int _randomIndex = 0; 29 | private static int _pRandomIndex = 0; 30 | 31 | public static int RandomIndex => _randomIndex; 32 | 33 | public static int P_Random() 34 | { 35 | _pRandomIndex = (_pRandomIndex + 1) & 0xFF; 36 | return RandomTable[_pRandomIndex]; 37 | } 38 | 39 | public static int M_Random() 40 | { 41 | _randomIndex = (_randomIndex + 1) & 0xFF; 42 | return RandomTable[_randomIndex]; 43 | } 44 | 45 | public static void ClearRandom() 46 | { 47 | _randomIndex = _pRandomIndex = 0; 48 | } 49 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Data/PurgeTag.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Data 2 | { 3 | public enum PurgeTag 4 | { 5 | /// 6 | /// Static entire execution time 7 | /// 8 | Static = 1, 9 | 10 | /// 11 | /// Static while playing 12 | /// 13 | Sound = 2, 14 | 15 | /// 16 | /// Static while playing 17 | /// 18 | Music = 3, 19 | 20 | /// 21 | /// Anything else Dave wants static 22 | /// 23 | Dave = 4, 24 | 25 | /// 26 | /// Static until level exited 27 | /// 28 | Level = 50, 29 | 30 | /// 31 | /// A special thinker in a level 32 | /// 33 | LevSpec = 51, 34 | 35 | // Tags >= 100 are purgable whenever needed 36 | PurgeLevel = 100, 37 | Cache = 101 38 | } 39 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Data/WadFile.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace DoomSharp.Core.Data; 4 | 5 | public class WadFile : IDisposable 6 | { 7 | public struct WadInfo 8 | { 9 | // Should be "IWAD" or "PWAD". 10 | public string Identification; 11 | public int NumLumps; 12 | public int InfoTableOfs; 13 | } 14 | 15 | public struct FileLump 16 | { 17 | public int FilePos; 18 | public int Size; 19 | public string Name; 20 | 21 | public static FileLump ReadFromWadData(BinaryReader reader) 22 | { 23 | return new FileLump 24 | { 25 | FilePos = reader.ReadInt32(), 26 | Size = reader.ReadInt32(), 27 | Name = Encoding.ASCII.GetString(reader.ReadBytes(8)).TrimEnd('\0') 28 | }; 29 | } 30 | } 31 | 32 | private readonly BinaryReader _reader; 33 | 34 | public WadFile(BinaryReader reader) 35 | { 36 | _reader = reader; 37 | } 38 | 39 | public WadInfo Header { get; init; } 40 | public ICollection Lumps { get; set; } = Array.Empty(); 41 | public int LumpCount => Header.NumLumps; 42 | 43 | public void ReadLumpData(WadLump destination) 44 | { 45 | _reader.BaseStream.Seek(destination.Lump.FilePos, SeekOrigin.Begin); 46 | destination.Data = _reader.ReadBytes(destination.Lump.Size); 47 | } 48 | 49 | public void Dispose() 50 | { 51 | _reader.Dispose(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Data/WadFileCollection.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Abstractions; 2 | 3 | namespace DoomSharp.Core.Data; 4 | 5 | public class WadFileCollection : List, IDisposable 6 | { 7 | private readonly List _wadFiles = new(); 8 | private static IWadStreamProvider _wadStreamProvider; 9 | 10 | public static void Init(IWadStreamProvider wadStreamProvider) 11 | { 12 | _wadStreamProvider = wadStreamProvider; 13 | } 14 | 15 | /// 16 | /// Pass a null terminated list of files to use. 17 | /// All files are optional, but at least one file 18 | /// must be found. 19 | /// Files with a .wad extension are idlink files 20 | /// with multiple lumps. 21 | /// Other files are single lumps with the base filename 22 | /// for the lump name. 23 | /// Lump names can appear multiple times. 24 | /// The name searcher looks backwards, so a later file 25 | /// does override all earlier ones. 26 | /// 27 | /// 28 | public static async Task InitializeMultipleFiles(IEnumerable files) 29 | { 30 | if (_wadStreamProvider == null) 31 | { 32 | throw new InvalidOperationException("WadStreamProvider is not initialized"); 33 | } 34 | 35 | var collection = new WadFileCollection(); 36 | 37 | foreach (var file in files) 38 | { 39 | var wadFile = await _wadStreamProvider.LoadFromFile(file).ConfigureAwait(false); 40 | if (wadFile != null) 41 | { 42 | collection._wadFiles.Add(wadFile); 43 | collection.AddRange(wadFile.Lumps); 44 | } 45 | } 46 | 47 | if (collection.LumpCount < 1) 48 | { 49 | throw new Exception("W_InitFiles: no files found"); 50 | } 51 | 52 | return collection; 53 | } 54 | 55 | public byte[]? GetLumpName(string name, PurgeTag tag) 56 | { 57 | return GetLumpNum(GetNumForName(name), tag); 58 | } 59 | 60 | public byte[]? GetLumpNum(int lump, PurgeTag tag) 61 | { 62 | if (lump < 0) 63 | { 64 | return null; 65 | } 66 | 67 | if (lump >= LumpCount) 68 | { 69 | DoomGame.Error($"W_CacheLumpNum: {lump} >= numlumps"); 70 | } 71 | 72 | if (this[lump].Data == null) 73 | { 74 | // read the lump in 75 | ReadLump(lump, this[lump]); 76 | ChangeTag(this[lump], tag); 77 | } 78 | else 79 | { 80 | ChangeTag(this[lump], tag); 81 | } 82 | 83 | return this[lump].Data!; 84 | } 85 | 86 | public int GetNumForName(string name) 87 | { 88 | var num = CheckNumForName(name); 89 | if (num == -1) 90 | { 91 | DoomGame.Console.WriteLine($"W_GetNumForName: {name} not found!"); 92 | } 93 | 94 | return num; 95 | } 96 | 97 | public int CheckNumForName(string name) 98 | { 99 | // scan backwards so patch lump files take precedence 100 | var i = LumpCount; 101 | while (i-- > 0) 102 | { 103 | if (string.Equals(this[i].Lump.Name, name, StringComparison.OrdinalIgnoreCase)) 104 | { 105 | return i; 106 | } 107 | } 108 | 109 | // TFB. Not found. 110 | return -1; 111 | } 112 | 113 | public int LumpLength(int lump) 114 | { 115 | if (lump >= LumpCount) 116 | { 117 | DoomGame.Error($"W_LumpLength: {lump} >= numlumps"); 118 | return -1; 119 | } 120 | 121 | return this[lump].Lump.Size; 122 | } 123 | 124 | public void ReadLump(int lump, WadLump destination) 125 | { 126 | if (lump < 0 || lump >= LumpCount) 127 | { 128 | DoomGame.Error($"W_ReadLump: {lump} >= numlumps (or < 0)"); 129 | } 130 | 131 | destination.File.ReadLumpData(destination); 132 | } 133 | 134 | private void ChangeTag(WadLump lump, PurgeTag tag) 135 | { 136 | lump.Tag = tag; 137 | } 138 | 139 | public int LumpCount => Count; 140 | 141 | public void Dispose() 142 | { 143 | foreach (var item in _wadFiles) 144 | { 145 | item.Dispose(); 146 | } 147 | } 148 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Data/WadLump.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Data; 2 | 3 | public record WadLump(WadFile File, WadFile.FileLump Lump) 4 | { 5 | public byte[]? Data { get; set; } 6 | public PurgeTag Tag { get; set; } = PurgeTag.Cache; // Default (purgeable) 7 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/DoomErrorException.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | internal class DoomErrorException : Exception 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/DoomMath.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core; 4 | 5 | public static class DoomMath 6 | { 7 | public const int FineAngleCount = 8192; 8 | public const int FineMask = FineAngleCount - 1; 9 | public const int AngleToFineShift = 19; 10 | 11 | private const float PI = 3.141592654f; 12 | 13 | public static void InitTables() 14 | { 15 | // viewangle tangent table 16 | for (var i = 0; i < (FineAngleCount / 2); i++) 17 | { 18 | var angle = (i - FineAngleCount / 4.0 + 0.5) * PI * 2 / FineAngleCount; 19 | var fv = Constants.FracUnit * Math.Tan(angle); 20 | FineTangent[i] = (int)fv; 21 | } 22 | 23 | // finesine table 24 | const int fineCosOffset = FineAngleCount / 4; 25 | for (var i = 0; i < (5 * FineAngleCount / 4); i++) 26 | { 27 | var angle = (i + 0.5) * PI * 2 / FineAngleCount; 28 | var t = Constants.FracUnit * Math.Sin(angle); 29 | FineSine[i] = (int)t; 30 | 31 | var c = i - fineCosOffset; 32 | if (c < 0) 33 | { 34 | c += 5 * FineAngleCount / 4; 35 | } 36 | FineCosine[c] = (int)t; 37 | } 38 | } 39 | 40 | public static void InitPointToAngle() 41 | { 42 | for (var i = 0; i <= RenderEngine.SlopeRange; i++) 43 | { 44 | var f = (float)Math.Atan((float)i / RenderEngine.SlopeRange) / (PI * 2); // PI was wrong here: 3.141592657f 45 | var t = (long)(0xffffffff * f); 46 | TanToAngle[i] = new Angle((uint)t); 47 | } 48 | } 49 | 50 | public static Fixed Tan(Angle angle) 51 | { 52 | var idx = angle.Value >> AngleToFineShift; 53 | if (idx > (FineAngleCount / 2)) 54 | { 55 | idx -= FineAngleCount / 2; 56 | } 57 | 58 | return new Fixed(FineTangent[idx]); 59 | } 60 | 61 | public static Fixed Tan(int fineAngle) 62 | { 63 | if (fineAngle > (FineAngleCount / 2)) 64 | { 65 | fineAngle -= FineAngleCount / 2; 66 | } 67 | return new Fixed(FineTangent[fineAngle]); 68 | } 69 | 70 | public static Fixed Sin(Angle angle) 71 | { 72 | return new Fixed(FineSine[angle.Value >> AngleToFineShift]); 73 | } 74 | 75 | public static Fixed Sin(int fineAngle) 76 | { 77 | return new Fixed(FineSine[fineAngle]); 78 | } 79 | 80 | public static Fixed Cos(Angle angle) 81 | { 82 | return new Fixed(FineCosine[(angle.Value >> AngleToFineShift)]); 83 | } 84 | 85 | public static Fixed Cos(int fineAngle) 86 | { 87 | return new Fixed(FineCosine[fineAngle]); 88 | } 89 | 90 | private static readonly int[] FineTangent = new int[FineAngleCount / 2]; 91 | private static readonly int[] FineSine = new int[5 * FineAngleCount / 4]; 92 | private static readonly int[] FineCosine = new int[5 * FineAngleCount / 4]; 93 | public static readonly Angle[] TanToAngle = new Angle[RenderEngine.SlopeRange + 1]; 94 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/DoomSharp.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Extensions/DoomConvert.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Extensions; 2 | 3 | public static class DoomConvert 4 | { 5 | public static short ToInt16(byte[] data) 6 | { 7 | if (data.Length != 4) 8 | { 9 | return 0; 10 | } 11 | 12 | return (short)((data[1] << 8) + data[0]); 13 | } 14 | 15 | public static int ToInt32(byte[] data) 16 | { 17 | if (data.Length != 4) 18 | { 19 | return 0; 20 | } 21 | 22 | return (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0]; 23 | } 24 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Fixed.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace DoomSharp.Core; 4 | 5 | public readonly struct Fixed : IComparable 6 | { 7 | private readonly int _value; 8 | 9 | public Fixed(int value) 10 | { 11 | _value = value; 12 | } 13 | 14 | public static readonly Fixed MinValue = new(int.MinValue); 15 | public static readonly Fixed MaxValue = new(int.MaxValue); 16 | public static readonly Fixed Zero = new(0); 17 | public static readonly Fixed Unit = new(Constants.FracUnit); 18 | 19 | public int Value => _value; 20 | 21 | public Fixed AddRadius(int radius) 22 | { 23 | return new Fixed(_value + radius); 24 | } 25 | 26 | public static Fixed FromInt(int value) 27 | { 28 | return new Fixed(value << Constants.FracBits); 29 | } 30 | 31 | public static Fixed Abs(Fixed value) 32 | { 33 | if (value._value < 0) 34 | { 35 | return -value; 36 | } 37 | 38 | return value; 39 | } 40 | 41 | public static Fixed operator +(Fixed a, Fixed b) 42 | { 43 | return new Fixed(a._value + b._value); 44 | } 45 | 46 | public static Fixed operator -(Fixed a, Fixed b) 47 | { 48 | return new Fixed(a._value - b._value); 49 | } 50 | 51 | public static Fixed operator -(Fixed a) 52 | { 53 | return new Fixed(-a._value); 54 | } 55 | 56 | public static Fixed operator *(Fixed a, Fixed b) 57 | { 58 | return new Fixed((int)(((long)a._value * b._value) >> Constants.FracBits)); 59 | } 60 | 61 | public static Fixed operator *(int a, Fixed b) 62 | { 63 | return new Fixed(a * b._value); 64 | } 65 | 66 | public static Fixed operator *(Fixed a, int b) 67 | { 68 | return new Fixed(a._value * b); 69 | } 70 | 71 | public static Fixed operator /(Fixed a, Fixed b) 72 | { 73 | if ((Math.Abs(a._value) >> 14) >= Math.Abs(b._value)) 74 | { 75 | return new Fixed((a._value ^ b._value) < 0 ? int.MaxValue : int.MinValue); 76 | } 77 | 78 | return Div2(a, b); 79 | } 80 | 81 | private static Fixed Div2(Fixed a, Fixed b) 82 | { 83 | var c = (double)a._value / b._value * Constants.FracUnit; 84 | 85 | if (c is >= 2147483648.0 or < -2147483648.0) 86 | { 87 | DoomGame.Error("FixedDiv: divide by zero"); 88 | return new Fixed(0); 89 | } 90 | 91 | return new Fixed((int)c); 92 | } 93 | 94 | public static Fixed operator /(Fixed a, int b) 95 | { 96 | return new Fixed(a._value / b); 97 | } 98 | 99 | public static Fixed operator <<(Fixed a, int b) 100 | { 101 | return new Fixed(a._value << b); 102 | } 103 | 104 | public static Fixed operator >>(Fixed a, int b) 105 | { 106 | return new Fixed(a._value >> b); 107 | } 108 | 109 | public static bool operator ==(Fixed a, Fixed b) 110 | { 111 | return a._value == b._value; 112 | } 113 | 114 | public static bool operator !=(Fixed a, Fixed b) 115 | { 116 | return a._value != b._value; 117 | } 118 | 119 | public static bool operator <(Fixed a, Fixed b) 120 | { 121 | return a._value < b._value; 122 | } 123 | 124 | public static bool operator >(Fixed a, Fixed b) 125 | { 126 | return a._value > b._value; 127 | } 128 | 129 | public static bool operator <=(Fixed a, Fixed b) 130 | { 131 | return a._value <= b._value; 132 | } 133 | 134 | public static bool operator >=(Fixed a, Fixed b) 135 | { 136 | return a._value >= b._value; 137 | } 138 | 139 | public bool Equals(Fixed other) 140 | { 141 | return _value == other._value; 142 | } 143 | 144 | public override bool Equals(object? obj) 145 | { 146 | return obj is Fixed other && Equals(other); 147 | } 148 | 149 | public override int GetHashCode() 150 | { 151 | return _value.GetHashCode(); 152 | } 153 | 154 | public override string ToString() 155 | { 156 | return ((double)_value / Constants.FracUnit).ToString(CultureInfo.InvariantCulture); 157 | } 158 | 159 | public int CompareTo(Fixed other) 160 | { 161 | return _value.CompareTo(other._value); 162 | } 163 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameAction.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public enum GameAction 4 | { 5 | Nothing = 0, 6 | LoadLevel, 7 | NewGame, 8 | LoadGame, 9 | SaveGame, 10 | PlayDemo, 11 | Completed, 12 | Victory, 13 | WorldDone, 14 | Screenshot 15 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLanguage.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core 2 | { 3 | public enum GameLanguage 4 | { 5 | English, 6 | French 7 | } 8 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/AmmoType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum AmmoType 4 | { 5 | /// 6 | /// Pistol / chaingun ammo. 7 | /// 8 | Clip = 0, 9 | /// 10 | /// Shotgun / double barreled shotgun. 11 | /// 12 | Shell, 13 | /// 14 | /// Plasma rifle, BFG 15 | /// 16 | Cell, 17 | /// 18 | /// Missile launcher 19 | /// 20 | Missile, 21 | NumAmmo, 22 | /// 23 | /// Unlimited for chainsaw / fist. 24 | /// 25 | NoAmmo 26 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Button.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Button 6 | { 7 | public ButtonWhere Where { get; set; } 8 | public int Texture { get; set; } 9 | public int Timer { get; set; } 10 | public Line? Line { get; set; } = null; 11 | public MapObject? SoundOrigin { get; set; } = null; 12 | } 13 | 14 | public enum ButtonWhere 15 | { 16 | Top, 17 | Middle, 18 | Bottom 19 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Ceiling.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Ceiling : Thinker 6 | { 7 | public static readonly Fixed DefaultSpeed = Fixed.Unit; 8 | public const int Wait = 150; 9 | public const int MaxCeilings = 30; 10 | 11 | public CeilingType Type { get; set; } 12 | 13 | public Sector? Sector { get; set; } 14 | 15 | public Fixed BottomHeight { get; set; } 16 | public Fixed TopHeight { get; set; } 17 | 18 | public Fixed Speed { get; set; } 19 | public bool Crush { get; set; } 20 | 21 | // 1 = up, 0 = waiting, -1 = down 22 | public int Direction { get; set; } 23 | 24 | // ID 25 | public int Tag { get; set; } 26 | public int OldDirection { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/CeilingType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum CeilingType 4 | { 5 | LowerToFloor, 6 | RaiseToHighest, 7 | LowerAndCrush, 8 | CrushAndRaise, 9 | FastCrushAndRaise, 10 | SilentCrushAndRaise 11 | } 12 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Cheat.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | [Flags] 4 | public enum Cheat 5 | { 6 | /// 7 | /// No clipping, walk through barriers 8 | /// 9 | NoClip = 1, 10 | /// 11 | /// No damage, no health loss 12 | /// 13 | GodMode = 2, 14 | /// 15 | /// Not really a cheat, just a debug aid 16 | /// 17 | NoMomentum = 4 18 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/DirectionType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum DirectionType 4 | { 5 | East, 6 | NorthEast, 7 | North, 8 | NorthWest, 9 | West, 10 | SouthWest, 11 | South, 12 | SouthEast, 13 | NoDirection, 14 | NumDirections 15 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/DividerLine.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public class DividerLine 4 | { 5 | public Fixed X { get; set; } 6 | public Fixed Y { get; set; } 7 | public Fixed Dx { get; set; } 8 | public Fixed Dy { get; set; } 9 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Door.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Door : Thinker 6 | { 7 | public static readonly Fixed DefaultSpeed = Fixed.FromInt(2); 8 | public const int Wait = 150; 9 | 10 | public Door(Sector sector) 11 | { 12 | Sector = sector; 13 | } 14 | 15 | public Sector Sector { get; } 16 | public DoorType Type { get; set; } 17 | public Fixed TopHeight { get; set; } 18 | public Fixed Speed { get; set; } 19 | 20 | /// 21 | /// 1 = up, 0 = waiting at top, -1 = down 22 | /// 23 | public int Direction { get; set; } 24 | 25 | /// 26 | /// Tics to wait at the top 27 | /// 28 | public int TopWait { get; set; } 29 | 30 | /// 31 | /// (keep in case a door going down is reset) 32 | /// when it reaches 0, start going down 33 | /// 34 | public int TopCountDown { get; set; } 35 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/DoorType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum DoorType 4 | { 5 | Normal, 6 | Close30ThenOpen, 7 | Close, 8 | Open, 9 | RaiseIn5Mins, 10 | BlazeRaise, 11 | BlazeOpen, 12 | BlazeClose 13 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/FireFlicker.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic 4 | { 5 | public class FireFlicker : Thinker 6 | { 7 | public FireFlicker(Sector sector) 8 | { 9 | Sector = sector; 10 | } 11 | 12 | public Sector Sector { get; } 13 | public int MaxLight { get; set; } 14 | public int MinLight { get; set; } 15 | public int Count { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Floor.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Floor : Thinker 6 | { 7 | public static readonly Fixed FloorSpeed = Fixed.Unit; 8 | 9 | public FloorType Type { get; set; } 10 | public bool Crush { get; set; } 11 | public Sector? Sector { get; set; } 12 | public int Direction { get; set; } 13 | public int NewSpecial { get; set; } 14 | public short Texture { get; set; } 15 | public Fixed FloorDestHeight { get; set; } 16 | public Fixed Speed { get; set; } 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/FloorType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum FloorType 4 | { 5 | /// 6 | /// lower floor to highest surrounding floor 7 | /// 8 | LowerFloor, 9 | 10 | /// 11 | /// lower floor to lowest surrounding floor 12 | /// 13 | LowerFloorToLowest, 14 | 15 | /// 16 | /// lower floor to highest surrounding floor VERY FAST 17 | /// 18 | TurboLower, 19 | 20 | /// 21 | /// raise floor to lowest surrounding ceiling 22 | /// 23 | RaiseFloor, 24 | 25 | /// 26 | /// raise floor to next highest surrounding floor 27 | /// 28 | RaiseFloorToNearest, 29 | 30 | /// 31 | /// raise floor to shortest height texture around it 32 | /// 33 | RaiseToTexture, 34 | 35 | /// 36 | /// lower floor to lowest surrounding floor and change floorpic 37 | /// 38 | LowerAndChange, 39 | 40 | RaiseFloor24, 41 | RaiseFloor24AndChange, 42 | RaiseFloorCrush, 43 | 44 | /// 45 | /// raise to next highest floor, turbo-speed 46 | /// 47 | RaiseFloorTurbo, 48 | DonutRaise, 49 | RaiseFloor512 50 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/GlowingLight.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic 4 | { 5 | public class GlowingLight : Thinker 6 | { 7 | public const int Speed = 8; 8 | 9 | public GlowingLight(Sector sector) 10 | { 11 | Sector = sector; 12 | } 13 | 14 | public Sector Sector { get; } 15 | public int MinLight { get; set; } 16 | public int MaxLight { get; set; } 17 | public int Direction { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Intercept.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Intercept 6 | { 7 | public Fixed Frac { get; set; } // along trace line 8 | public bool IsLine { get; set; } 9 | public MapObject? Thing { get; set; } 10 | public Line? Line { get; set; } 11 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/IntermissionState.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum IntermissionState 4 | { 5 | NoState = -1, 6 | StatCounting, 7 | ShowNextLocation 8 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/KeyCardType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum KeyCardType 4 | { 5 | BlueCard = 0, 6 | YellowCard, 7 | RedCard, 8 | BlueSkull, 9 | YellowSkull, 10 | RedSkull, 11 | NumberOfKeyCards 12 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/LightFlash.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic 4 | { 5 | public class LightFlash : Thinker 6 | { 7 | public LightFlash(Sector sector) 8 | { 9 | Sector = sector; 10 | } 11 | 12 | public Sector Sector { get; } 13 | 14 | public int Count { get; set; } 15 | public int MaxLight { get; set; } 16 | public int MinLight { get; set; } 17 | public int MaxTime { get; set; } 18 | public int MinTime { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/MapLumps.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public static class MapLumps 4 | { 5 | /// 6 | /// A separator, name, ExMx or MAPxx 7 | /// 8 | public const int Label = 0; 9 | 10 | /// 11 | /// Monsters, items, ... 12 | /// 13 | public const int Things = 1; 14 | 15 | /// 16 | /// LineDefs 17 | /// 18 | public const int LineDefs = 2; 19 | 20 | /// 21 | /// SideDefs 22 | /// 23 | public const int SideDefs = 3; 24 | 25 | /// 26 | /// Vertices, edited and BSP splits generated 27 | /// 28 | public const int Vertices = 4; 29 | 30 | /// 31 | /// LineSegs, from LineDefs split by BSP 32 | /// 33 | public const int Segs = 5; 34 | 35 | /// 36 | /// SubSectors, list of LineSegs 37 | /// 38 | public const int SubSectors = 6; 39 | 40 | /// 41 | /// BSP nodes 42 | /// 43 | public const int Nodes = 7; 44 | 45 | /// 46 | /// Sectors 47 | /// 48 | public const int Sectors = 8; 49 | 50 | /// 51 | /// LUT, sector-sector visibility 52 | /// 53 | public const int Reject = 9; 54 | 55 | /// 56 | /// LUT, motion clipping, walls/grid element 57 | /// 58 | public const int BlockMap = 10; 59 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/MapObjectFlag.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | [Flags] 4 | public enum MapObjectFlag 5 | { 6 | // Call P_SpecialThing when touched. 7 | MF_SPECIAL = 1, 8 | // Blocks. 9 | MF_SOLID = 2, 10 | // Can be hit. 11 | MF_SHOOTABLE = 4, 12 | // Don't use the sector links (invisible but touchable). 13 | MF_NOSECTOR = 8, 14 | // Don't use the blocklinks (inert but displayable) 15 | MF_NOBLOCKMAP = 16, 16 | 17 | // Not to be activated by sound, deaf monster. 18 | MF_AMBUSH = 32, 19 | // Will try to attack right back. 20 | MF_JUSTHIT = 64, 21 | // Will take at least one step before attacking. 22 | MF_JUSTATTACKED = 128, 23 | // On level spawning (initial position), 24 | // hang from ceiling instead of stand on floor. 25 | MF_SPAWNCEILING = 256, 26 | // Don't apply gravity (every tic), 27 | // that is, object will float, keeping current height 28 | // or changing it actively. 29 | MF_NOGRAVITY = 512, 30 | 31 | // Movement flags. 32 | // This allows jumps from high places. 33 | MF_DROPOFF = 0x400, 34 | // For players, will pick up items. 35 | MF_PICKUP = 0x800, 36 | // Player cheat. ??? 37 | MF_NOCLIP = 0x1000, 38 | // Player: keep info about sliding along walls. 39 | MF_SLIDE = 0x2000, 40 | // Allow moves to any height, no gravity. 41 | // For active floaters, e.g. cacodemons, pain elementals. 42 | MF_FLOAT = 0x4000, 43 | // Don't cross lines 44 | // ??? or look at heights on teleport. 45 | MF_TELEPORT = 0x8000, 46 | // Don't hit same species, explode on block. 47 | // Player missiles as well as fireballs of various kinds. 48 | MF_MISSILE = 0x10000, 49 | // Dropped by a demon, not level spawned. 50 | // E.g. ammo clips dropped by dying former humans. 51 | MF_DROPPED = 0x20000, 52 | // Use fuzzy draw (shadow demons or spectres), 53 | // temporary player invisibility powerup. 54 | MF_SHADOW = 0x40000, 55 | // Flag: don't bleed when shot (use puff), 56 | // barrels and shootable furniture shall not bleed. 57 | MF_NOBLOOD = 0x80000, 58 | // Don't stop moving halfway off a step, 59 | // that is, have dead bodies slide down all the way. 60 | MF_CORPSE = 0x100000, 61 | // Floating to a height for a move, ??? 62 | // don't auto float to target's height. 63 | MF_INFLOAT = 0x200000, 64 | 65 | // On kill, count this enemy object 66 | // towards intermission kill total. 67 | // Happy gathering. 68 | MF_COUNTKILL = 0x400000, 69 | 70 | // On picking up, count this item object 71 | // towards intermission item total. 72 | MF_COUNTITEM = 0x800000, 73 | 74 | // Special handling: skull in flight. 75 | // Neither a cacodemon nor a missile. 76 | MF_SKULLFLY = 0x1000000, 77 | 78 | // Don't spawn this object 79 | // in death match mode (e.g. key cards). 80 | MF_NOTDMATCH = 0x2000000, 81 | 82 | // Player sprites in multiplayer modes are modified 83 | // using an internal color lookup table for re-indexing. 84 | // If 0x4 0x8 or 0xc, 85 | // use a translation table for player colormaps 86 | MF_TRANSLATION = 0xc000000, 87 | // Hmm ???. 88 | MF_TRANSSHIFT = 26 89 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/MapObjectType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum MapObjectType 4 | { 5 | MT_PLAYER, 6 | MT_POSSESSED, 7 | MT_SHOTGUY, 8 | MT_VILE, 9 | MT_FIRE, 10 | MT_UNDEAD, 11 | MT_TRACER, 12 | MT_SMOKE, 13 | MT_FATSO, 14 | MT_FATSHOT, 15 | MT_CHAINGUY, 16 | MT_TROOP, 17 | MT_SERGEANT, 18 | MT_SHADOWS, 19 | MT_HEAD, 20 | MT_BRUISER, 21 | MT_BRUISERSHOT, 22 | MT_KNIGHT, 23 | MT_SKULL, 24 | MT_SPIDER, 25 | MT_BABY, 26 | MT_CYBORG, 27 | MT_PAIN, 28 | MT_WOLFSS, 29 | MT_KEEN, 30 | MT_BOSSBRAIN, 31 | MT_BOSSSPIT, 32 | MT_BOSSTARGET, 33 | MT_SPAWNSHOT, 34 | MT_SPAWNFIRE, 35 | MT_BARREL, 36 | MT_TROOPSHOT, 37 | MT_HEADSHOT, 38 | MT_ROCKET, 39 | MT_PLASMA, 40 | MT_BFG, 41 | MT_ARACHPLAZ, 42 | MT_PUFF, 43 | MT_BLOOD, 44 | MT_TFOG, 45 | MT_IFOG, 46 | MT_TELEPORTMAN, 47 | MT_EXTRABFG, 48 | MT_MISC0, 49 | MT_MISC1, 50 | MT_MISC2, 51 | MT_MISC3, 52 | MT_MISC4, 53 | MT_MISC5, 54 | MT_MISC6, 55 | MT_MISC7, 56 | MT_MISC8, 57 | MT_MISC9, 58 | MT_MISC10, 59 | MT_MISC11, 60 | MT_MISC12, 61 | MT_INV, 62 | MT_MISC13, 63 | MT_INS, 64 | MT_MISC14, 65 | MT_MISC15, 66 | MT_MISC16, 67 | MT_MEGA, 68 | MT_CLIP, 69 | MT_MISC17, 70 | MT_MISC18, 71 | MT_MISC19, 72 | MT_MISC20, 73 | MT_MISC21, 74 | MT_MISC22, 75 | MT_MISC23, 76 | MT_MISC24, 77 | MT_MISC25, 78 | MT_CHAINGUN, 79 | MT_MISC26, 80 | MT_MISC27, 81 | MT_MISC28, 82 | MT_SHOTGUN, 83 | MT_SUPERSHOTGUN, 84 | MT_MISC29, 85 | MT_MISC30, 86 | MT_MISC31, 87 | MT_MISC32, 88 | MT_MISC33, 89 | MT_MISC34, 90 | MT_MISC35, 91 | MT_MISC36, 92 | MT_MISC37, 93 | MT_MISC38, 94 | MT_MISC39, 95 | MT_MISC40, 96 | MT_MISC41, 97 | MT_MISC42, 98 | MT_MISC43, 99 | MT_MISC44, 100 | MT_MISC45, 101 | MT_MISC46, 102 | MT_MISC47, 103 | MT_MISC48, 104 | MT_MISC49, 105 | MT_MISC50, 106 | MT_MISC51, 107 | MT_MISC52, 108 | MT_MISC53, 109 | MT_MISC54, 110 | MT_MISC55, 111 | MT_MISC56, 112 | MT_MISC57, 113 | MT_MISC58, 114 | MT_MISC59, 115 | MT_MISC60, 116 | MT_MISC61, 117 | MT_MISC62, 118 | MT_MISC63, 119 | MT_MISC64, 120 | MT_MISC65, 121 | MT_MISC66, 122 | MT_MISC67, 123 | MT_MISC68, 124 | MT_MISC69, 125 | MT_MISC70, 126 | MT_MISC71, 127 | MT_MISC72, 128 | MT_MISC73, 129 | MT_MISC74, 130 | MT_MISC75, 131 | MT_MISC76, 132 | MT_MISC77, 133 | MT_MISC78, 134 | MT_MISC79, 135 | MT_MISC80, 136 | MT_MISC81, 137 | MT_MISC82, 138 | MT_MISC83, 139 | MT_MISC84, 140 | MT_MISC85, 141 | MT_MISC86, 142 | NUMMOBJTYPES 143 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/MapThing.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | /// 4 | /// Thing definition, position, orientation and type, 5 | /// plus skill/visibility flags and attributes. 6 | /// 7 | public struct MapThing 8 | { 9 | public const int SizeOfStruct = 2 + 2 + 2 + 2 + 2; 10 | 11 | public short X { get; set; } 12 | public short Y { get; set; } 13 | public short Angle { get; set; } 14 | public short Type { get; set; } 15 | public short Options { get; set; } 16 | 17 | public static MapThing FromWadData(BinaryReader reader) 18 | { 19 | return new MapThing 20 | { 21 | X = reader.ReadInt16(), 22 | Y = reader.ReadInt16(), 23 | Angle = reader.ReadInt16(), 24 | Type = reader.ReadInt16(), 25 | Options = reader.ReadInt16(), 26 | }; 27 | } 28 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/MapThingFlag.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum MapThingFlag 4 | { 5 | MTF_EASY = 1, 6 | MTF_NORMAL = 2, 7 | MTF_HARD = 4, 8 | 9 | // Deaf monsters/do not react to sound. 10 | MTF_AMBUSH = 8 11 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Platform.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class Platform : Thinker 6 | { 7 | public const int DefaultWait = 3; 8 | public static readonly Fixed DefaultSpeed = Fixed.Unit; 9 | public const int MaxPlats = 30; 10 | 11 | public Sector? Sector { get; set; } 12 | 13 | public Fixed Speed { get; set; } 14 | public Fixed Low { get; set; } 15 | public Fixed High { get; set; } 16 | 17 | public int Wait { get; set; } 18 | public int Count { get; set; } 19 | 20 | public PlatformState Status { get; set; } 21 | public PlatformState OldStatus { get; set; } 22 | 23 | public bool Crush { get; set; } 24 | 25 | public int Tag { get; set; } 26 | public PlatformType Type { get; set; } 27 | } 28 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PlatformState.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum PlatformState 4 | { 5 | Up, 6 | Down, 7 | Waiting, 8 | InStatis 9 | } 10 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PlatformType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum PlatformType 4 | { 5 | PerpetualRaise, 6 | DownWaitUpStay, 7 | RaiseAndChange, 8 | RaiseToNearestAndChange, 9 | BlazeDWUS 10 | } 11 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PlayerSprite.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public class PlayerSprite 4 | { 5 | public State? State { get; set; } 6 | public int Tics { get; set; } 7 | public Fixed SX { get; set; } 8 | public Fixed SY { get; set; } 9 | } 10 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PlayerSpriteType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum PlayerSpriteType 4 | { 5 | Weapon, 6 | Flash, 7 | NumPlayerSprites 8 | } 9 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PlayerState.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum PlayerState 4 | { 5 | NotSet, 6 | /// 7 | /// Playing or camping 8 | /// 9 | Alive, 10 | /// 11 | /// Dead on the ground, view follows killer 12 | /// 13 | Dead, 14 | /// 15 | /// Ready to restart/respawn 16 | /// 17 | Reborn 18 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/PowerUpType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum PowerUpType 4 | { 5 | Invulnerability = 0, 6 | Strength, 7 | Invisibility, 8 | IronFeet, 9 | AllMap, 10 | InfraRed, 11 | NumberOfPowerUps 12 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Result.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum Result 4 | { 5 | Ok, 6 | Crushed, 7 | PastDest 8 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/SpriteNum.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum SpriteNum 4 | { 5 | SPR_TROO, 6 | SPR_SHTG, 7 | SPR_PUNG, 8 | SPR_PISG, 9 | SPR_PISF, 10 | SPR_SHTF, 11 | SPR_SHT2, 12 | SPR_CHGG, 13 | SPR_CHGF, 14 | SPR_MISG, 15 | SPR_MISF, 16 | SPR_SAWG, 17 | SPR_PLSG, 18 | SPR_PLSF, 19 | SPR_BFGG, 20 | SPR_BFGF, 21 | SPR_BLUD, 22 | SPR_PUFF, 23 | SPR_BAL1, 24 | SPR_BAL2, 25 | SPR_PLSS, 26 | SPR_PLSE, 27 | SPR_MISL, 28 | SPR_BFS1, 29 | SPR_BFE1, 30 | SPR_BFE2, 31 | SPR_TFOG, 32 | SPR_IFOG, 33 | SPR_PLAY, 34 | SPR_POSS, 35 | SPR_SPOS, 36 | SPR_VILE, 37 | SPR_FIRE, 38 | SPR_FATB, 39 | SPR_FBXP, 40 | SPR_SKEL, 41 | SPR_MANF, 42 | SPR_FATT, 43 | SPR_CPOS, 44 | SPR_SARG, 45 | SPR_HEAD, 46 | SPR_BAL7, 47 | SPR_BOSS, 48 | SPR_BOS2, 49 | SPR_SKUL, 50 | SPR_SPID, 51 | SPR_BSPI, 52 | SPR_APLS, 53 | SPR_APBX, 54 | SPR_CYBR, 55 | SPR_PAIN, 56 | SPR_SSWV, 57 | SPR_KEEN, 58 | SPR_BBRN, 59 | SPR_BOSF, 60 | SPR_ARM1, 61 | SPR_ARM2, 62 | SPR_BAR1, 63 | SPR_BEXP, 64 | SPR_FCAN, 65 | SPR_BON1, 66 | SPR_BON2, 67 | SPR_BKEY, 68 | SPR_RKEY, 69 | SPR_YKEY, 70 | SPR_BSKU, 71 | SPR_RSKU, 72 | SPR_YSKU, 73 | SPR_STIM, 74 | SPR_MEDI, 75 | SPR_SOUL, 76 | SPR_PINV, 77 | SPR_PSTR, 78 | SPR_PINS, 79 | SPR_MEGA, 80 | SPR_SUIT, 81 | SPR_PMAP, 82 | SPR_PVIS, 83 | SPR_CLIP, 84 | SPR_AMMO, 85 | SPR_ROCK, 86 | SPR_BROK, 87 | SPR_CELL, 88 | SPR_CELP, 89 | SPR_SHEL, 90 | SPR_SBOX, 91 | SPR_BPAK, 92 | SPR_BFUG, 93 | SPR_MGUN, 94 | SPR_CSAW, 95 | SPR_LAUN, 96 | SPR_PLAS, 97 | SPR_SHOT, 98 | SPR_SGN2, 99 | SPR_COLU, 100 | SPR_SMT2, 101 | SPR_GOR1, 102 | SPR_POL2, 103 | SPR_POL5, 104 | SPR_POL4, 105 | SPR_POL3, 106 | SPR_POL1, 107 | SPR_POL6, 108 | SPR_GOR2, 109 | SPR_GOR3, 110 | SPR_GOR4, 111 | SPR_GOR5, 112 | SPR_SMIT, 113 | SPR_COL1, 114 | SPR_COL2, 115 | SPR_COL3, 116 | SPR_COL4, 117 | SPR_CAND, 118 | SPR_CBRA, 119 | SPR_COL6, 120 | SPR_TRE1, 121 | SPR_TRE2, 122 | SPR_ELEC, 123 | SPR_CEYE, 124 | SPR_FSKU, 125 | SPR_COL5, 126 | SPR_TBLU, 127 | SPR_TGRN, 128 | SPR_TRED, 129 | SPR_SMBT, 130 | SPR_SMGT, 131 | SPR_SMRT, 132 | SPR_HDB1, 133 | SPR_HDB2, 134 | SPR_HDB3, 135 | SPR_HDB4, 136 | SPR_HDB5, 137 | SPR_HDB6, 138 | SPR_POB1, 139 | SPR_POB2, 140 | SPR_BRS1, 141 | SPR_TLMP, 142 | SPR_TLP2, 143 | NUMSPRITES 144 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/StaircaseType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum StaircaseType 4 | { 5 | /// 6 | /// Slowly build by 8 7 | /// 8 | Build8, 9 | 10 | /// 11 | /// Quickly build by 16 12 | /// 13 | Turbo16 14 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/StrobeFlash.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core.GameLogic; 4 | 5 | public class StrobeFlash : Thinker 6 | { 7 | public const int StrobeBright = 5; 8 | public const int SlowDark = 35; 9 | public const int FastDark = 15; 10 | 11 | public Sector? Sector { get; set; } 12 | public int Count { get; set; } 13 | public int MinLight { get; set; } 14 | public int MaxLight { get; set; } 15 | public int DarkTime { get; set; } 16 | public int BrightTime { get; set; } 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/SwitchControl.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public record SwitchControl(string Name1, string Name2, int Episode) 4 | { 5 | public static readonly SwitchControl[] PredefinedSwitchList = 6 | { 7 | // Doom shareware episode 1 switches 8 | new("SW1BRCOM", "SW2BRCOM", 1), 9 | new("SW1BRN1", "SW2BRN1", 1), 10 | new("SW1BRN2", "SW2BRN2", 1), 11 | new("SW1BRNGN", "SW2BRNGN", 1), 12 | new("SW1BROWN", "SW2BROWN", 1), 13 | 14 | new("SW1COMM", "SW2COMM", 1), 15 | new("SW1COMP", "SW2COMP", 1), 16 | new("SW1DIRT", "SW2DIRT", 1), 17 | new("SW1EXIT", "SW2EXIT", 1), 18 | new("SW1GRAY", "SW2GRAY", 1), 19 | new("SW1GRAY1", "SW2GRAY1", 1), 20 | new("SW1METAL", "SW2METAL", 1), 21 | new("SW1PIPE", "SW2PIPE", 1), 22 | new("SW1SLAD", "SW2SLAD", 1), 23 | new("SW1STARG", "SW2STARG", 1), 24 | new("SW1STON1", "SW2STON1", 1), 25 | new("SW1STON2", "SW2STON2", 1), 26 | new("SW1STONE", "SW2STONE", 1), 27 | new("SW1STRTN", "SW2STRTN", 1), 28 | 29 | // Doom registered episodes 2&3 switches 30 | new("SW1BLUE", "SW2BLUE", 2), 31 | new("SW1CMT", "SW2CMT", 2), 32 | new("SW1GARG", "SW2GARG", 2), 33 | new("SW1GSTON", "SW2GSTON", 2), 34 | new("SW1HOT", "SW2HOT", 2), 35 | new("SW1LION", "SW2LION", 2), 36 | new("SW1SATYR", "SW2SATYR", 2), 37 | new("SW1SKIN", "SW2SKIN", 2), 38 | new("SW1VINE", "SW2VINE", 2), 39 | new("SW1WOOD", "SW2WOOD", 2), 40 | 41 | // Doom II switches 42 | new("SW1PANEL", "SW2PANEL", 3), 43 | new("SW1ROCK", "SW2ROCK", 3), 44 | new("SW1MET2", "SW2MET2", 3), 45 | new("SW1WDMET", "SW2WDMET", 3), 46 | new("SW1BRIK", "SW2BRIK", 3), 47 | new("SW1MOD1", "SW2MOD1", 3), 48 | new("SW1ZIM", "SW2ZIM", 3), 49 | new("SW1STON6", "SW2STON6", 3), 50 | new("SW1TEK", "SW2TEK", 3), 51 | new("SW1MARB", "SW2MARB", 3), 52 | new("SW1SKULL", "SW2SKULL", 3), 53 | 54 | new("", "", 0) 55 | }; 56 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/Thinker.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public abstract class Thinker 4 | { 5 | public Action? Action { get; set; } 6 | } 7 | 8 | public record ActionParams(MapObject? MapObject = null, Player? Player = null, PlayerSprite? PlayerSprite = null, Thinker? Thinker = null); -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/WeaponInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public record WeaponInfo(AmmoType Ammo, StateNum UpState, StateNum DownState, StateNum ReadyState, StateNum AttackState, StateNum FlashState) 4 | { 5 | public static readonly Fixed LowerSpeed = Fixed.FromInt(6); 6 | public static readonly Fixed RaiseSpeed = Fixed.FromInt(6); 7 | 8 | public static readonly Fixed WeaponBottom = Fixed.FromInt(128); 9 | public static readonly Fixed WeaponTop = Fixed.FromInt(32); 10 | 11 | public const int BFGCells = 40; 12 | 13 | private static readonly List _predefined = new(); 14 | 15 | static WeaponInfo() 16 | { 17 | // fist 18 | _predefined.Add(new WeaponInfo(AmmoType.NoAmmo, StateNum.S_PUNCHUP, StateNum.S_PUNCHDOWN, StateNum.S_PUNCH, StateNum.S_PUNCH1, StateNum.S_NULL)); 19 | 20 | // pistol 21 | _predefined.Add(new WeaponInfo(AmmoType.Clip, StateNum.S_PISTOLUP, StateNum.S_PISTOLDOWN, StateNum.S_PISTOL, StateNum.S_PISTOL1, StateNum.S_PISTOLFLASH)); 22 | 23 | // shotgun 24 | _predefined.Add(new WeaponInfo(AmmoType.Shell, StateNum.S_SGUNUP, StateNum.S_SGUNDOWN, StateNum.S_SGUN, StateNum.S_SGUN1, StateNum.S_SGUNFLASH1)); 25 | 26 | // chaingun 27 | _predefined.Add(new WeaponInfo(AmmoType.Clip, StateNum.S_CHAINUP, StateNum.S_CHAINDOWN, StateNum.S_CHAIN, StateNum.S_CHAIN1, StateNum.S_CHAINFLASH1)); 28 | 29 | // missile launcher 30 | _predefined.Add(new WeaponInfo(AmmoType.Missile, StateNum.S_MISSILEUP, StateNum.S_MISSILEDOWN, StateNum.S_MISSILE, StateNum.S_MISSILE1, StateNum.S_MISSILEFLASH1)); 31 | 32 | // plasma rifle 33 | _predefined.Add(new WeaponInfo(AmmoType.Cell, StateNum.S_PLASMAUP, StateNum.S_PLASMADOWN, StateNum.S_PLASMA, StateNum.S_PLASMA1, StateNum.S_PLASMAFLASH1)); 34 | 35 | // bfg 9000 36 | _predefined.Add(new WeaponInfo(AmmoType.Cell, StateNum.S_BFGUP, StateNum.S_BFGDOWN, StateNum.S_BFG, StateNum.S_BFG1, StateNum.S_BFGFLASH1)); 37 | 38 | // chainsaw 39 | _predefined.Add(new WeaponInfo(AmmoType.NoAmmo, StateNum.S_SAWUP, StateNum.S_SAWDOWN, StateNum.S_SAW, StateNum.S_SAW1, StateNum.S_NULL)); 40 | 41 | // super shotgun 42 | _predefined.Add(new WeaponInfo(AmmoType.Shell, StateNum.S_DSGUNUP, StateNum.S_DSGUNDOWN, StateNum.S_DSGUN, StateNum.S_DSGUN1, StateNum.S_DSGUNFLASH1)); 43 | } 44 | 45 | public static WeaponInfo GetByType(WeaponType type) 46 | { 47 | return _predefined[(int)type]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/WeaponType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public enum WeaponType 4 | { 5 | Fist = 0, 6 | Pistol, 7 | Shotgun, 8 | Chaingun, 9 | Missile, 10 | Plasma, 11 | Bfg, 12 | Chainsaw, 13 | SuperShotgun, 14 | NumberOfWeapons, 15 | NoChange 16 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/WorldMapInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public class WorldMapInfo 4 | { 5 | public WorldMapInfo() 6 | { 7 | for (var i = 0; i < Constants.MaxPlayers; i++) 8 | { 9 | Players[i] = new WorldMapPlayer(); 10 | } 11 | } 12 | 13 | /// 14 | /// Episode # (0 - 2) 15 | /// 16 | public int Episode { get; set; } 17 | 18 | /// 19 | /// If true, splash the secret level 20 | /// 21 | public bool DidSecret { get; set; } 22 | 23 | // previous and next levels, origin 0 24 | public int Last { get; set; } 25 | public int Next { get; set; } 26 | 27 | public int MaxKills { get; set; } 28 | public int MaxItems { get; set; } 29 | public int MaxSecret { get; set; } 30 | public int MaxFrags { get; set; } 31 | 32 | /// 33 | /// The par time 34 | /// 35 | public int ParTime { get; set; } 36 | 37 | /// 38 | /// The index of this player in game 39 | /// 40 | public int PlayerNum { get; set; } 41 | 42 | public WorldMapPlayer[] Players { get; } = new WorldMapPlayer[Constants.MaxPlayers]; 43 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameLogic/WorldMapPlayer.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.GameLogic; 2 | 3 | public class WorldMapPlayer 4 | { 5 | /// 6 | /// Whether the player is in game 7 | /// 8 | public bool In { get; set; } 9 | 10 | // player stats, kills, collected items etc. 11 | public int Kills { get; set; } 12 | public int Items { get; set; } 13 | public int Secret { get; set; } 14 | public int Time { get; set; } 15 | public int[] Frags { get; } = new int[4]; 16 | 17 | /// 18 | /// Current score on entry, modified on return 19 | /// 20 | public int Score { get; set; } 21 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameMode.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public enum GameMode 4 | { 5 | Indetermined = 0, 6 | Shareware, 7 | Retail, 8 | Registered, 9 | Commercial 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/GameState.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public enum GameState 4 | { 5 | Wipe = -1, 6 | Level = 0, 7 | Intermission = 1, 8 | Finale = 2, 9 | DemoScreen = 3 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/AnimatingItem.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public class AnimatingItem 4 | { 5 | public bool IsTexture { get; set; } 6 | public int PicNum { get; set; } 7 | public int BasePic { get; set; } 8 | public int NumPics { get; set; } 9 | public int Speed { get; set; } 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/AnimationDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public record AnimationDefinition(bool IsTexture, string EndName, string StartName, int Speed) 4 | { 5 | /// 6 | /// Floor/ceiling animation sequences, 7 | /// defined by first and last frame, 8 | /// i.e. the flat (64x64 tile) name to 9 | /// be used. 10 | /// The full animation sequence is given 11 | /// using all the flats between the start 12 | /// and end entry, in the order found in 13 | /// the WAD file. 14 | /// 15 | public static readonly AnimationDefinition[] Definitions = 16 | { 17 | new(false, "NUKAGE3", "NUKAGE1", 8), 18 | new(false, "FWATER4", "FWATER1", 8), 19 | new(false, "SWATER4", "SWATER1", 8), 20 | new(false, "LAVA4", "LAVA1", 8), 21 | new(false, "BLOOD3", "BLOOD1", 8), 22 | 23 | // DOOM II flat animations. 24 | new(false, "RROCK08", "RROCK05", 8), 25 | new(false, "SLIME04", "SLIME01", 8), 26 | new(false, "SLIME08", "SLIME05", 8), 27 | new(false, "SLIME12", "SLIME09", 8), 28 | 29 | new(true, "BLODGR4", "BLODGR1", 8), 30 | new(true, "SLADRIP3", "SLADRIP1", 8), 31 | 32 | new(true, "BLODRIP4", "BLODRIP1", 8), 33 | new(true, "FIREWALL", "FIREWALA", 8), 34 | new(true, "GSTFONT3", "GSTFONT1", 8), 35 | new(true, "FIRELAVA", "FIRELAV3", 8), 36 | new(true, "FIREMAG3", "FIREMAG1", 8), 37 | new(true, "FIREBLU2", "FIREBLU1", 8), 38 | new(true, "ROCKRED3", "ROCKRED1", 8), 39 | 40 | new(true, "BFALL4", "BFALL1", 8), 41 | new(true, "SFALL4", "SFALL1", 8), 42 | new(true, "WFALL4", "WFALL1", 8), 43 | new(true, "DBRAIN4", "DBRAIN1", 8), 44 | }; 45 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/BoundingBox.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public static class BoundingBox 4 | { 5 | public const int BoxTop = 0; 6 | public const int BoxBottom = 1; 7 | public const int BoxLeft = 2; 8 | public const int BoxRight = 3; 9 | 10 | public static void ClearBox(Fixed[] box) 11 | { 12 | box[BoxTop] = box[BoxRight] = Fixed.MinValue; 13 | box[BoxBottom] = box[BoxLeft] = Fixed.MaxValue; 14 | } 15 | 16 | public static void AddToBox(Fixed[] box, Fixed x, Fixed y) 17 | { 18 | if (x < box[BoxLeft]) 19 | { 20 | box[BoxLeft] = x; 21 | } 22 | else if (x > box[BoxRight]) 23 | { 24 | box[BoxRight] = x; 25 | } 26 | 27 | if (y < box[BoxBottom]) 28 | { 29 | box[BoxBottom] = y; 30 | } 31 | else if (y > box[BoxTop]) 32 | { 33 | box[BoxTop] = y; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/ClipWallSegment.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public struct ClipWallSegment 4 | { 5 | public int First { get; set; } 6 | public int Last { get; set; } 7 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/Column.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public record Column(byte TopDelta, byte Length, byte[] Pixels) 4 | { 5 | public Column? Next { get; set; } = null; 6 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/DrawSegment.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public class DrawSegment 4 | { 5 | private short[]? _spriteTopClip; 6 | private short[]? _spriteBottomClip; 7 | private short[]? _maskedTextureCol; 8 | 9 | public Segment? CurrentLine { get; set; } 10 | public int X1 { get; set; } 11 | public int X2 { get; set; } 12 | 13 | public Fixed Scale1 { get; set; } 14 | public Fixed Scale2 { get; set; } 15 | public Fixed ScaleStep { get; set; } 16 | 17 | // 0=none, 1=bottom, 2=top, 3=both 18 | public int Silhouette { get; set; } 19 | 20 | // do not clip sprites above this 21 | public Fixed BottomSilhouetteHeight { get; set; } 22 | 23 | // do not clip sprites below this 24 | public Fixed TopSilhouetteHeight { get; set; } 25 | 26 | // Pointers to lists for sprite clipping, 27 | // all three adjusted so [x1] is first value. 28 | public short[]? SpriteTopClip 29 | { 30 | get => _spriteTopClip; 31 | set 32 | { 33 | SpriteTopClipIdx = null; 34 | if (value is { Length: > 0 }) 35 | { 36 | SpriteTopClipIdx = 0; 37 | } 38 | 39 | _spriteTopClip = value; 40 | } 41 | } 42 | public int? SpriteTopClipIdx { get; set; } 43 | 44 | public short[]? SpriteBottomClip 45 | { 46 | get => _spriteBottomClip; 47 | set 48 | { 49 | SpriteBottomClipIdx = null; 50 | if (value is { Length: > 0 }) 51 | { 52 | SpriteBottomClipIdx = 0; 53 | } 54 | 55 | _spriteBottomClip = value; 56 | } 57 | } 58 | public int? SpriteBottomClipIdx { get; set; } 59 | 60 | public short[]? MaskedTextureCol 61 | { 62 | get => _maskedTextureCol; 63 | set 64 | { 65 | MaskedTextureColIdx = null; 66 | if (value is { Length: > 0 }) 67 | { 68 | MaskedTextureColIdx = 0; 69 | } 70 | 71 | _maskedTextureCol = value; 72 | } 73 | } 74 | public int? MaskedTextureColIdx { get; set; } 75 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/IOutputRenderer.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public interface IGraphics 4 | { 5 | void Initialize(); 6 | void UpdatePalette(byte[] palette); 7 | void ScreenReady(byte[] output); 8 | void StartTic(); 9 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/MapTexture.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace DoomSharp.Core.Graphics; 4 | 5 | /// 6 | /// A DOOM wall texture is a list of patches 7 | /// which are to be combined in a predefined order. 8 | /// 9 | public record MapTexture(string Name, bool Masked, short Width, short Height, short PatchCount, int ColumnDirectory = 0) 10 | { 11 | public const int BaseSize = 8 + 4 + 2 + 2 + 2 + 4; // Name = 8 bytes, Masked = 4 byte bool 12 | 13 | public MapPatch[] Patches { get; } = new MapPatch[PatchCount]; 14 | public int Size => BaseSize + PatchCount * MapPatch.Size; 15 | 16 | public void ReadMapPatches(byte[] data) 17 | { 18 | using var stream = new MemoryStream(data, false); 19 | using var reader = new BinaryReader(stream); 20 | 21 | // Read map patches 22 | for (var i = 0; i < PatchCount; i++) 23 | { 24 | Patches[i] = MapPatch.FromReader(reader); 25 | } 26 | } 27 | 28 | public static MapTexture FromBytes(byte[] data) 29 | { 30 | using var stream = new MemoryStream(data, false); 31 | using var reader = new BinaryReader(stream); 32 | 33 | var nameBytes = reader.ReadBytes(8); 34 | var name = Encoding.ASCII.GetString(nameBytes).TrimEnd('\0'); 35 | var masked = reader.ReadInt32() != 0; 36 | var width = reader.ReadInt16(); 37 | var height = reader.ReadInt16(); 38 | var columnDirectory = reader.ReadInt32(); 39 | var patchCount = reader.ReadInt16(); 40 | 41 | return new MapTexture(name, masked, width, height, patchCount, columnDirectory); 42 | } 43 | } 44 | 45 | /// 46 | /// Each texture is composed of one or more patches, 47 | /// with patches being lumps stored in the WAD. 48 | /// The lumps are referenced by number, and patched 49 | /// into the rectangular texture space using origin 50 | /// and possibly other attributes. 51 | /// 52 | public record MapPatch(short OriginX, short OriginY, short Patch, short StepDir = 0, short ColorMap = 0) 53 | { 54 | public const int Size = 2 + 2 + 2 + 2 + 2; 55 | 56 | public static MapPatch FromReader(BinaryReader reader) 57 | { 58 | var originX = reader.ReadInt16(); 59 | var originY = reader.ReadInt16(); 60 | var patch = reader.ReadInt16(); 61 | var stepDir = reader.ReadInt16(); 62 | var colorMap = reader.ReadInt16(); 63 | 64 | return new MapPatch(originX, originY, patch, stepDir, colorMap); 65 | } 66 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/Patch.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | /// 4 | /// Patches. 5 | /// A patch holds one or more columns. 6 | /// Patches are used for sprites and all masked pictures, 7 | /// and we compose textures from the TEXTURE1/2 lists 8 | /// of patches. 9 | /// 10 | public record struct Patch(ushort Width, ushort Height, short LeftOffset, short TopOffset, uint[] ColumnOffsets, Column?[] Columns) 11 | { 12 | public static Patch FromBytes(byte[] patchData) 13 | { 14 | using var stream = new MemoryStream(patchData, false); 15 | using var reader = new BinaryReader(stream); 16 | 17 | var width = reader.ReadUInt16(); 18 | var height = reader.ReadUInt16(); 19 | var left = reader.ReadInt16(); 20 | var top = reader.ReadInt16(); 21 | 22 | var offsets = new uint[width]; 23 | for (var i = 0; i < width; i++) 24 | { 25 | offsets[i] = reader.ReadUInt32(); 26 | } 27 | 28 | var columns = new Column?[width]; 29 | for (var i = 0; i < width; i++) 30 | { 31 | stream.Seek(offsets[i], SeekOrigin.Begin); 32 | 33 | Column? currentColumn = null; 34 | var rowStart = reader.ReadByte(); 35 | if (rowStart == 255) 36 | { 37 | columns[i] = null; 38 | continue; 39 | } 40 | 41 | while (rowStart != 255) 42 | { 43 | var pixelCount = reader.ReadByte(); 44 | _ = reader.ReadByte(); // dummy value 45 | var pixels = reader.ReadBytes(pixelCount); 46 | _ = reader.ReadByte(); // dummy value 47 | 48 | var column = new Column(rowStart, pixelCount, pixels); 49 | if (currentColumn is null) 50 | { 51 | columns[i] = column; 52 | } 53 | else 54 | { 55 | currentColumn.Next = column; 56 | } 57 | 58 | currentColumn = column; 59 | rowStart = reader.ReadByte(); 60 | } 61 | } 62 | 63 | return new Patch(width, height, left, top, offsets, columns); 64 | } 65 | 66 | public Column? GetColumnByOffset(uint offset, int skip = 0) 67 | { 68 | var i = 0; 69 | foreach (var columnOffset in ColumnOffsets) 70 | { 71 | if (columnOffset == offset) 72 | { 73 | return i + skip >= Columns.Length ? Columns[i + skip - Columns.Length] : Columns[i + skip]; 74 | } 75 | 76 | i++; 77 | } 78 | 79 | return null; 80 | } 81 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/Sky.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public class Sky 4 | { 5 | public const string FlatName = "F_SKY1"; 6 | public const int AngleToSkyShift = 22; 7 | 8 | public int FlatNum { get; set; } 9 | public int Texture { get; set; } 10 | public Fixed TextureMid { get; set; } 11 | 12 | public void InitSkyMap() 13 | { 14 | TextureMid = Fixed.FromInt(100); 15 | } 16 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/Sprite.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public class Sprite 4 | { 5 | public int NumFrames { get; set; } 6 | public SpriteFrame[] Frames { get; set; } = Array.Empty(); 7 | } 8 | 9 | public class SpriteFrame 10 | { 11 | public const int FullBright = 0x8000; 12 | public const int FrameMask = 0x7FFF; 13 | 14 | public bool? Rotate { get; set; } 15 | 16 | /// 17 | /// Lump to use for view angles 0-7 18 | /// 19 | public short[] Lumps { get; } = { -1, -1, -1, -1, -1, -1, -1, -1 }; 20 | 21 | /// 22 | /// Flip bit (true = flip) to use for view angles 0-7 23 | /// 24 | public bool[] Flip { get; } = new bool[8]; 25 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/Texture.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | /// 4 | /// A maptexturedef_t describes a rectangular texture, 5 | /// which is composed of one or more mappatch_t structures 6 | /// that arrange graphic patches. 7 | /// 8 | public record Texture(string Name, short Width, short Height, short PatchCount) 9 | { 10 | public TexturePatch[] Patches { get; } = new TexturePatch[PatchCount]; 11 | } 12 | 13 | /// 14 | /// A single patch from a texture definition, 15 | /// basically a rectangular area within 16 | /// the texture rectangle. 17 | /// 18 | public record TexturePatch(int OriginX, int OriginY, int Patch); -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/VisPlane.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public class VisPlane 4 | { 5 | public VisPlane() 6 | { 7 | Height = default; 8 | PicNum = 0; 9 | LightLevel = 0; 10 | MinX = 0; 11 | MaxX = 0; 12 | Pad1 = 0; 13 | Pad2 = 0; 14 | Pad3 = 0; 15 | Pad4 = 0; 16 | 17 | FillTop(0xff); 18 | } 19 | 20 | public Fixed Height { get; set; } 21 | public int PicNum { get; set; } 22 | public int LightLevel { get; set; } 23 | public int MinX { get; set; } 24 | public int MaxX { get; set; } 25 | 26 | // leave pads for [minx-1]/[maxx+1] 27 | 28 | private byte Pad1 { get; set; } 29 | // Here lies the rub for all 30 | // dynamic resize/change of resolution. 31 | private byte[] Top { get; } = new byte[Constants.ScreenWidth]; 32 | private byte Pad2 { get; set; } 33 | private byte Pad3 { get; set; } 34 | // See above. 35 | private byte[] Bottom { get; } = new byte[Constants.ScreenWidth]; 36 | private byte Pad4 { get; set; } 37 | 38 | public byte ReadTop(int i) 39 | { 40 | return i switch 41 | { 42 | < 0 => Pad1, 43 | >= Constants.ScreenWidth => Pad2, 44 | _ => Top[i] 45 | }; 46 | } 47 | 48 | public void WriteTop(int i, byte value) 49 | { 50 | switch (i) 51 | { 52 | case < 0: 53 | Pad1 = value; 54 | break; 55 | case >= Constants.ScreenWidth: 56 | Pad2 = value; 57 | break; 58 | default: 59 | Top[i] = value; 60 | break; 61 | } 62 | } 63 | 64 | public byte ReadBottom(int i) 65 | { 66 | return i switch 67 | { 68 | < 0 => Pad3, 69 | >= Constants.ScreenWidth => Pad4, 70 | _ => Bottom[i] 71 | }; 72 | } 73 | 74 | public void WriteBottom(int i, byte value) 75 | { 76 | switch (i) 77 | { 78 | case < 0: 79 | Pad3 = value; 80 | break; 81 | case >= Constants.ScreenWidth: 82 | Pad4 = value; 83 | break; 84 | default: 85 | Bottom[i] = value; 86 | break; 87 | } 88 | } 89 | 90 | private void FillTop(byte value) 91 | { 92 | Pad1 = value; 93 | Pad2 = value; 94 | Array.Fill(Top, value); 95 | } 96 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/VisSprite.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.GameLogic; 2 | 3 | namespace DoomSharp.Core.Graphics; 4 | 5 | /// 6 | /// A vissprite_t is a thing 7 | /// that will be drawn during a refresh. 8 | /// I.e. a sprite object that is partly visible. 9 | /// 10 | public class VisSprite 11 | { 12 | // Doubly linked list. 13 | public VisSprite? Prev { get; set; } 14 | public VisSprite? Next { get; set; } 15 | 16 | public int X1 { get; set; } 17 | public int X2 { get; set; } 18 | 19 | // for line side calculation 20 | public Fixed GX { get; set; } 21 | public Fixed GY { get; set; } 22 | 23 | // global bottom / top for silhouette clipping 24 | public Fixed GZ { get; set; } 25 | public Fixed GZTop { get; set; } 26 | 27 | // horizontal position of x1 28 | public Fixed StartFrac { get; set; } 29 | 30 | public Fixed Scale { get; set; } 31 | 32 | // negative if flipped 33 | public Fixed XiScale { get; set; } 34 | 35 | public Fixed TextureMid { get; set; } 36 | public int Patch { get; set; } 37 | 38 | // for color translation and shadow draw, 39 | // maxbright frames as well 40 | public byte[]? Colormap { get; set; } 41 | 42 | public MapObjectFlag MapObjectFlags { get; set; } 43 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Graphics/WipeMethod.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Graphics; 2 | 3 | public enum WipeMethod 4 | { 5 | /// 6 | /// Simple gradual pixel change for 8-bit only 7 | /// 8 | ColorTransform = 0, 9 | 10 | /// 11 | /// Weird screen melt 12 | /// 13 | Melt = 1 14 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/IConsole.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public interface IConsole 4 | { 5 | void Write(string message); 6 | 7 | public void WriteLine(string message) 8 | { 9 | Write(message); 10 | Write(Environment.NewLine); 11 | } 12 | 13 | void SetTitle(string title); 14 | void Shutdown(); 15 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Input/InputTypes.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Input; 2 | 3 | 4 | // Data1: keys / mouse/joystick buttons 5 | // Data2: mouse/joystick x move 6 | // Data3: mouse/joystick y move 7 | public record InputEvent(EventType Type, int Data1, int Data2, int Data3); 8 | 9 | public enum EventType 10 | { 11 | KeyDown, 12 | KeyUp, 13 | Mouse, 14 | Joystick 15 | } 16 | 17 | [Flags] 18 | public enum ButtonCode 19 | { 20 | /// 21 | /// Press "Fire". 22 | /// 23 | Attack = 1, 24 | /// 25 | /// Use button, to open doors, activate switches. 26 | /// 27 | Use = 2, 28 | 29 | /// 30 | /// Flag: game events, not really buttons. 31 | /// 32 | Special = 128, 33 | SpecialMask = 3, 34 | 35 | /// 36 | /// Flag, weapon change pending. 37 | /// If true, the next 3 bits hold weapon num. 38 | /// 39 | Change = 4, 40 | /// 41 | /// The 3 bit weapon mask and shift, convenience. 42 | /// 43 | WeaponMask = 8+16+32, 44 | WeaponShift = 3, 45 | 46 | /// 47 | /// Pause the game. 48 | /// 49 | Pause = 1, 50 | /// 51 | /// Save the game at each console. 52 | /// 53 | SaveGame = 2, 54 | 55 | /// 56 | /// Savegame slot numbers 57 | /// Occupy the second byte of buttons. 58 | /// 59 | SaveMask = 4+8+16, 60 | SaveShift = 2 61 | } 62 | 63 | public enum Keys 64 | { 65 | RightArrow = 0xae, 66 | LeftArrow = 0xac, 67 | UpArrow = 0xad, 68 | DownArrow = 0xaf, 69 | Escape = 27, 70 | Enter = 13, 71 | Tab = 9, 72 | F1 = 0x80 + 0x3b, 73 | F2 = 0x80 + 0x3c, 74 | F3 = 0x80 + 0x3d, 75 | F4 = 0x80 + 0x3e, 76 | F5 = 0x80 + 0x3f, 77 | F6 = 0x80 + 0x40, 78 | F7 = 0x80 + 0x41, 79 | F8 = 0x80 + 0x42, 80 | F9 = 0x80 + 0x43, 81 | F10 = 0x80 + 0x44, 82 | F11 = 0x80 + 0x57, 83 | F12 = 0x80 + 0x58, 84 | 85 | Backspace = 127, 86 | Pause = 0xff, 87 | 88 | Equals = 0x3d, 89 | Minus = 0x2d, 90 | 91 | RShift = 0x80 + 0x36, 92 | RCtrl = 0x80 + 0x1d, 93 | RAlt = 0x80 + 0x38, 94 | 95 | LAlt = RAlt 96 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Networking/Command.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Networking; 2 | 3 | public enum Command 4 | { 5 | NotSet = 0, 6 | Send = 1, 7 | Get = 2 8 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Networking/DoomCommunication.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Networking; 2 | 3 | public class DoomCommunication 4 | { 5 | // Supposed to be DOOMCOM_ID? 6 | public long Id { get; set; } 7 | 8 | // DOOM executes an int to execute commands. 9 | public short IntNum { get; set; } 10 | 11 | // Communication between DOOM and the driver. 12 | // Is CMD_SEND or CMD_GET. 13 | public Command Command { get; set; } 14 | 15 | // Is dest for send, set by get (-1 = no packet). 16 | public int RemoteNode { get; set; } 17 | 18 | // Number of bytes in doomdata to be sent 19 | public int DataLength { get; set; } 20 | 21 | // Info common to all nodes. 22 | // Console is allways node 0. 23 | public short NumNodes { get; set; } 24 | // Flag: 1 = no duplication, 2-5 = dup for slow nets. 25 | public short TicDup { get; set; } 26 | // Flag: 1 = send a backup tic in every packet. 27 | public short ExtraTics { get; set; } 28 | // Flag: 1 = deathmatch. 29 | public bool DeathMatch { get; set; } 30 | // Flag: -1 = new game, 0-5 = load savegame 31 | public short SaveGame { get; set; } 32 | public short Episode { get; set; } // 1-3 33 | public short Map { get; set; } // 1-9 34 | public short Skill { get; set; } // 1-5 35 | 36 | // Info specific to this node. 37 | public short ConsolePlayer { get; set; } 38 | public short NumPlayers { get; set; } 39 | 40 | // These are related to the 3-display mode, 41 | // in which two drones looking left and right 42 | // were used to render two additional views 43 | // on two additional computers. 44 | // Probably not operational anymore. 45 | // 1 = left, 0 = center, -1 = right 46 | public short AngleOffset { get; set; } 47 | // 1 = drone 48 | public short Drone { get; set; } 49 | 50 | // The packet data to be sent. 51 | public DoomData Data { get; set; } = new(); 52 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Networking/DoomData.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Networking; 2 | 3 | public class DoomData 4 | { 5 | public uint CheckSum { get; set; } 6 | public byte RetransmitFrom { get; set; } 7 | public byte StartTic { get; set; } 8 | public byte Player { get; set; } 9 | public byte NumTics { get; set; } 10 | 11 | public TicCommand[] Commands { get; } = 12 | { 13 | new(),new(),new(),new(), 14 | new(),new(),new(),new(), 15 | new(),new(),new(),new() 16 | }; 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Networking/NetworkController.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Networking; 2 | 3 | public class NetworkController 4 | { 5 | public DoomCommunication DoomCom { get; } = new(); 6 | 7 | public void Initialize() 8 | { 9 | DoomCom.TicDup = 1; 10 | DoomCom.ExtraTics = 0; 11 | 12 | // Single player support only 13 | DoomGame.Instance.Game.NetGame = false; 14 | DoomCom.Id = Constants.DoomComId; 15 | DoomCom.NumPlayers = DoomCom.NumNodes = 1; 16 | DoomCom.DeathMatch = false; 17 | DoomCom.ConsolePlayer = 0; 18 | 19 | // when multiplayer: setup netsend/netget functions 20 | } 21 | 22 | public void NetworkCommand() 23 | { 24 | if (DoomCom.Command == Command.Send) 25 | { 26 | NetSend(); 27 | return; 28 | } 29 | 30 | if (DoomCom.Command == Command.Get) 31 | { 32 | NetGet(); 33 | return; 34 | } 35 | 36 | DoomGame.Error($"Bad net cmd: {DoomCom.Command}"); 37 | } 38 | 39 | private void NetSend() 40 | { 41 | 42 | } 43 | 44 | private void NetGet() 45 | { 46 | 47 | } 48 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Networking/TicCommand.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Input; 2 | 3 | namespace DoomSharp.Core.Networking; 4 | 5 | /// 6 | /// The data sampled per tick (single player) 7 | /// and transmitted to other peers (multiplayer). 8 | /// Mainly movements/button commands per game tick, 9 | /// plus a checksum for internal state consistency. 10 | /// 11 | public class TicCommand 12 | { 13 | public sbyte ForwardMove { get; set; } 14 | public sbyte SideMove { get; set; } 15 | public short AngleTurn { get; set; } 16 | public short Consistency { get; set; } 17 | public char ChatChar { get; set; } 18 | public ButtonCode Buttons { get; set; } 19 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/NullConsole.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace DoomSharp.Core; 4 | 5 | public class NullConsole : IConsole 6 | { 7 | public void Write(string message) 8 | { 9 | } 10 | 11 | public void SetTitle(string title) 12 | { 13 | } 14 | 15 | public void Shutdown() 16 | { 17 | Process.GetCurrentProcess().Close(); 18 | } 19 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/NullGraphics.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.Graphics; 2 | 3 | namespace DoomSharp.Core; 4 | 5 | public class NullGraphics : IGraphics 6 | { 7 | public void Initialize() 8 | { 9 | 10 | } 11 | 12 | public void UpdatePalette(byte[] output) 13 | { 14 | 15 | } 16 | 17 | public void ScreenReady(byte[] output) 18 | { 19 | 20 | } 21 | 22 | public void StartTic() 23 | { 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/SkillLevel.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public enum SkillLevel 4 | { 5 | Baby, 6 | Easy, 7 | Medium, 8 | Hard, 9 | Nightmare 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/ISoundDriver.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | public interface ISoundDriver 4 | { 5 | void SetChannels(); 6 | 7 | int RegisterSong(byte[] data); 8 | void PlaySong(int handle, bool looping); 9 | void PauseSong(int handle); 10 | void ResumeSong(int handle); 11 | void StopSong(int handle); 12 | void UnregisterSong(int handle); 13 | 14 | void SetMusicVolume(int volume); 15 | 16 | bool SoundIsPlaying(int handle); 17 | int StartSound(SoundType soundType, byte[] data, int volume, int stereoSeparation, int pitch, int priority); 18 | void UpdateSoundParams(int handle, int volume, int stereoSeparation, int pitch); 19 | void StopSound(int handle); 20 | 21 | void SubmitSound(); 22 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/MusicInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | public record MusicInfo(string Name = "") 4 | { 5 | /// 6 | /// Up to 6-character name 7 | /// 8 | public string Name { get; } = Name; 9 | 10 | /// 11 | /// Lump number of music 12 | /// 13 | public int LumpNum { get; set; } 14 | 15 | /// 16 | /// Music data 17 | /// 18 | public byte[] Data { get; set; } = Array.Empty(); 19 | 20 | /// 21 | /// Music handle once registered 22 | /// 23 | public int Handle { get; set; } 24 | } 25 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/MusicType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | public enum MusicType 4 | { 5 | mus_None, 6 | mus_e1m1, 7 | mus_e1m2, 8 | mus_e1m3, 9 | mus_e1m4, 10 | mus_e1m5, 11 | mus_e1m6, 12 | mus_e1m7, 13 | mus_e1m8, 14 | mus_e1m9, 15 | mus_e2m1, 16 | mus_e2m2, 17 | mus_e2m3, 18 | mus_e2m4, 19 | mus_e2m5, 20 | mus_e2m6, 21 | mus_e2m7, 22 | mus_e2m8, 23 | mus_e2m9, 24 | mus_e3m1, 25 | mus_e3m2, 26 | mus_e3m3, 27 | mus_e3m4, 28 | mus_e3m5, 29 | mus_e3m6, 30 | mus_e3m7, 31 | mus_e3m8, 32 | mus_e3m9, 33 | mus_inter, 34 | mus_intro, 35 | mus_bunny, 36 | mus_victor, 37 | mus_introa, 38 | mus_runnin, 39 | mus_stalks, 40 | mus_countd, 41 | mus_betwee, 42 | mus_doom, 43 | mus_the_da, 44 | mus_shawn, 45 | mus_ddtblu, 46 | mus_in_cit, 47 | mus_dead, 48 | mus_stlks2, 49 | mus_theda2, 50 | mus_doom2, 51 | mus_ddtbl2, 52 | mus_runni2, 53 | mus_dead2, 54 | mus_stlks3, 55 | mus_romero, 56 | mus_shawn2, 57 | mus_messag, 58 | mus_count2, 59 | mus_ddtbl3, 60 | mus_ampie, 61 | mus_theda3, 62 | mus_adrian, 63 | mus_messg2, 64 | mus_romer2, 65 | mus_tense, 66 | mus_shawn3, 67 | mus_openin, 68 | mus_evil, 69 | mus_ultima, 70 | mus_read_m, 71 | mus_dm2ttl, 72 | mus_dm2int, 73 | NUMMUSIC 74 | } 75 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/NullSoundDriver.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | internal class NullSoundDriver : ISoundDriver 4 | { 5 | public void SetChannels() { } 6 | 7 | public int RegisterSong(byte[] data) => 1; 8 | public void PlaySong(int handle, bool looping) { } 9 | public void PauseSong(int handle) { } 10 | public void ResumeSong(int handle) { } 11 | public void StopSong(int handle) { } 12 | public void UnregisterSong(int handle) { } 13 | 14 | public void SetMusicVolume(int volume) { } 15 | 16 | public bool SoundIsPlaying(int handle) => false; 17 | public int StartSound(SoundType soundType, byte[] data, int volume, int stereoSeparation, int pitch, int priority) => 1; 18 | public void UpdateSoundParams(int handle, int volume, int stereoSeparation, int pitch) { } 19 | public void StopSound(int handle) { } 20 | 21 | public void SubmitSound() { } 22 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/SfxInfo.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | public record SfxInfo(string Name, bool Singularity, int Priority, SfxInfo? Link, int Pitch, int Volume) 4 | { 5 | /// 6 | /// Up to 6-character name 7 | /// 8 | public string Name { get; } = Name; 9 | 10 | /// 11 | /// Lump number of sfx 12 | /// 13 | public int LumpNum { get; set; } 14 | 15 | /// 16 | /// Sfx singularity (only one at a time) 17 | /// 18 | public bool Singularity { get; } = Singularity; 19 | 20 | /// 21 | /// Sfx priority 22 | /// 23 | public int Priority { get; } = Priority; 24 | 25 | /// 26 | /// Referenced sound if a link 27 | /// 28 | public SfxInfo? Link { get; } = Link; 29 | 30 | /// 31 | /// Pitch if a link 32 | /// 33 | public int Pitch { get; } = Pitch; 34 | 35 | /// 36 | /// Volume if a link 37 | /// 38 | public int Volume { get; } = Volume; 39 | 40 | /// 41 | /// Sound data 42 | /// 43 | public byte[] Data { get; set; } = Array.Empty(); 44 | 45 | /// 46 | /// This is checked every second to see if sound can be thrown out: 47 | /// if 0, then decrement. 48 | /// if -1, then throw out. 49 | /// if > 0, then it is in use. 50 | /// 51 | public int Usefulness { get; set; } 52 | } 53 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/SoundChannel.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core.GameLogic; 2 | 3 | namespace DoomSharp.Core.Sound; 4 | 5 | public class SoundChannel 6 | { 7 | /// 8 | /// Sound information (if null, channel is available) 9 | /// 10 | public SfxInfo? SfxInfo { get; set; } 11 | 12 | /// 13 | /// The origin of sound 14 | /// 15 | public MapObject? Origin { get; set; } 16 | 17 | /// 18 | /// Handle of the sound being played 19 | /// 20 | public int Handle { get; set; } 21 | } 22 | -------------------------------------------------------------------------------- /src/DoomSharp.Core/Sound/SoundType.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.Sound; 2 | 3 | public enum SoundType 4 | { 5 | sfx_None, 6 | sfx_pistol, 7 | sfx_shotgn, 8 | sfx_sgcock, 9 | sfx_dshtgn, 10 | sfx_dbopn, 11 | sfx_dbcls, 12 | sfx_dbload, 13 | sfx_plasma, 14 | sfx_bfg, 15 | sfx_sawup, 16 | sfx_sawidl, 17 | sfx_sawful, 18 | sfx_sawhit, 19 | sfx_rlaunc, 20 | sfx_rxplod, 21 | sfx_firsht, 22 | sfx_firxpl, 23 | sfx_pstart, 24 | sfx_pstop, 25 | sfx_doropn, 26 | sfx_dorcls, 27 | sfx_stnmov, 28 | sfx_swtchn, 29 | sfx_swtchx, 30 | sfx_plpain, 31 | sfx_dmpain, 32 | sfx_popain, 33 | sfx_vipain, 34 | sfx_mnpain, 35 | sfx_pepain, 36 | sfx_slop, 37 | sfx_itemup, 38 | sfx_wpnup, 39 | sfx_oof, 40 | sfx_telept, 41 | sfx_posit1, 42 | sfx_posit2, 43 | sfx_posit3, 44 | sfx_bgsit1, 45 | sfx_bgsit2, 46 | sfx_sgtsit, 47 | sfx_cacsit, 48 | sfx_brssit, 49 | sfx_cybsit, 50 | sfx_spisit, 51 | sfx_bspsit, 52 | sfx_kntsit, 53 | sfx_vilsit, 54 | sfx_mansit, 55 | sfx_pesit, 56 | sfx_sklatk, 57 | sfx_sgtatk, 58 | sfx_skepch, 59 | sfx_vilatk, 60 | sfx_claw, 61 | sfx_skeswg, 62 | sfx_pldeth, 63 | sfx_pdiehi, 64 | sfx_podth1, 65 | sfx_podth2, 66 | sfx_podth3, 67 | sfx_bgdth1, 68 | sfx_bgdth2, 69 | sfx_sgtdth, 70 | sfx_cacdth, 71 | sfx_skldth, 72 | sfx_brsdth, 73 | sfx_cybdth, 74 | sfx_spidth, 75 | sfx_bspdth, 76 | sfx_vildth, 77 | sfx_kntdth, 78 | sfx_pedth, 79 | sfx_skedth, 80 | sfx_posact, 81 | sfx_bgact, 82 | sfx_dmact, 83 | sfx_bspact, 84 | sfx_bspwlk, 85 | sfx_vilact, 86 | sfx_noway, 87 | sfx_barexp, 88 | sfx_punch, 89 | sfx_hoof, 90 | sfx_metal, 91 | sfx_chgun, 92 | sfx_tink, 93 | sfx_bdopn, 94 | sfx_bdcls, 95 | sfx_itmbk, 96 | sfx_flame, 97 | sfx_flamst, 98 | sfx_getpow, 99 | sfx_bospit, 100 | sfx_boscub, 101 | sfx_bossit, 102 | sfx_bospn, 103 | sfx_bosdth, 104 | sfx_manatk, 105 | sfx_mandth, 106 | sfx_sssit, 107 | sfx_ssdth, 108 | sfx_keenpn, 109 | sfx_keendt, 110 | sfx_skeact, 111 | sfx_skesit, 112 | sfx_skeatk, 113 | sfx_radio, 114 | NUMSFX 115 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/UI/Menu.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core.UI; 2 | 3 | public class Menu 4 | { 5 | public Menu(int numItems, Menu? previousMenu, IList items, Action drawRoutine, int x, int y, int lastOn) 6 | { 7 | NumItems = numItems; 8 | PreviousMenu = previousMenu; 9 | Items = items; 10 | DrawRoutine = drawRoutine; 11 | X = x; 12 | Y = y; 13 | LastOn = lastOn; 14 | } 15 | 16 | public int NumItems { get; set; } 17 | public Menu? PreviousMenu { get; set; } 18 | public IList Items { get; set; } 19 | public Action DrawRoutine { get; set; } 20 | 21 | public int X { get; set; } 22 | public int Y { get; set; } 23 | public int LastOn { get; set; } 24 | } 25 | 26 | 27 | public class MenuItem 28 | { 29 | public MenuItem(MenuItemStatus status, string name, Action? choiceRoutine = null, char? hotKey = null) 30 | { 31 | Status = status; 32 | Name = name; 33 | ChoiceRoutine = choiceRoutine; 34 | HotKey = hotKey; 35 | } 36 | 37 | public MenuItemStatus Status { get; set; } 38 | public string Name { get; set; } 39 | public Action? ChoiceRoutine { get; set; } 40 | public char? HotKey { get; set; } 41 | } 42 | 43 | 44 | public enum MenuItemStatus 45 | { 46 | Empty = -1, 47 | NoCursorHere = 0, 48 | Ok = 1, 49 | ArrowsOk = 2 50 | } -------------------------------------------------------------------------------- /src/DoomSharp.Core/Zone.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Core; 2 | 3 | public class Zone 4 | { 5 | public void Initialize() 6 | { 7 | 8 | } 9 | /* 10 | * 11 | // 12 | // Z_Init 13 | // 14 | void Z_Init (void) 15 | { 16 | memblock_t* block; 17 | int size; 18 | 19 | mainzone = (memzone_t *)I_ZoneBase (&size); 20 | mainzone->size = size; 21 | 22 | // set the entire zone to one free block 23 | mainzone->blocklist.next = 24 | mainzone->blocklist.prev = 25 | block = (memblock_t *)( (byte *)mainzone + sizeof(memzone_t) ); 26 | 27 | mainzone->blocklist.user = (void *)mainzone; 28 | mainzone->blocklist.tag = PU_STATIC; 29 | mainzone->rover = block; 30 | 31 | block->prev = block->next = &mainzone->blocklist; 32 | 33 | // NULL indicates a free block. 34 | block->user = NULL; 35 | 36 | block->size = mainzone->size - sizeof(memzone_t); 37 | } 38 | */ 39 | } -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Additions/AboutAdditions.txt: -------------------------------------------------------------------------------- 1 | Additions allow you to add arbitrary C# to the generated classes 2 | before they are compiled. This can be helpful for providing convenience 3 | methods or adding pure C# classes. 4 | 5 | == Adding Methods to Generated Classes == 6 | 7 | Let's say the library being bound has a Rectangle class with a constructor 8 | that takes an x and y position, and a width and length size. It will look like 9 | this: 10 | 11 | public partial class Rectangle 12 | { 13 | public Rectangle (int x, int y, int width, int height) 14 | { 15 | // JNI bindings 16 | } 17 | } 18 | 19 | Imagine we want to add a constructor to this class that takes a Point and 20 | Size structure instead of 4 ints. We can add a new file called Rectangle.cs 21 | with a partial class containing our new method: 22 | 23 | public partial class Rectangle 24 | { 25 | public Rectangle (Point location, Size size) : 26 | this (location.X, location.Y, size.Width, size.Height) 27 | { 28 | } 29 | } 30 | 31 | At compile time, the additions class will be added to the generated class 32 | and the final assembly will a Rectangle class with both constructors. 33 | 34 | 35 | == Adding C# Classes == 36 | 37 | Another thing that can be done is adding fully C# managed classes to the 38 | generated library. In the above example, let's assume that there isn't a 39 | Point class available in Java or our library. The one we create doesn't need 40 | to interact with Java, so we'll create it like a normal class in C#. 41 | 42 | By adding a Point.cs file with this class, it will end up in the binding library: 43 | 44 | public class Point 45 | { 46 | public int X { get; set; } 47 | public int Y { get; set; } 48 | } -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/DoomSharp.Droid.Bindings.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0-android 4 | 21 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Jars/arm64-v8a/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Droid.Bindings/Jars/arm64-v8a/libfmod.so -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Jars/armeabi-v7a/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Droid.Bindings/Jars/armeabi-v7a/libfmod.so -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Jars/fmod.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Droid.Bindings/Jars/fmod.jar -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Jars/x86_64/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Droid.Bindings/Jars/x86_64/libfmod.so -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Transforms/EnumFields.xml: -------------------------------------------------------------------------------- 1 |  2 | 14 | -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Transforms/EnumMethods.xml: -------------------------------------------------------------------------------- 1 |  2 | 13 | -------------------------------------------------------------------------------- /src/DoomSharp.Droid.Bindings/Transforms/Metadata.xml: -------------------------------------------------------------------------------- 1 |  2 | 9 | 10 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/App.xaml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Maui.ViewModels; 2 | 3 | namespace DoomSharp.Maui 4 | { 5 | public partial class App : Application 6 | { 7 | public static ViewModelLocator Locator { get; } = new(); 8 | 9 | public App() 10 | { 11 | InitializeComponent(); 12 | 13 | MainPage = new AppShell(); 14 | } 15 | 16 | protected override void OnStart() 17 | { 18 | Microsoft.Maui.Handlers.WindowHandler.Mapper.AppendToMapping("KeyboardSupport", (handler, view) => 19 | { 20 | #if WINDOWS 21 | handler.PlatformView.Content.KeyDown += (sender, args) => 22 | { 23 | 24 | }; 25 | #endif 26 | }); 27 | base.OnStart(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/AppShell.xaml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/AppShell.xaml.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core; 2 | using DoomSharp.Core.Data; 3 | using DoomSharp.Maui.Data; 4 | using DoomSharp.Maui.ViewModels; 5 | 6 | namespace DoomSharp.Maui 7 | { 8 | public partial class AppShell : Shell 9 | { 10 | public AppShell() 11 | { 12 | InitializeComponent(); 13 | WadFileCollection.Init(new WadStreamProvider()); 14 | DoomGame.SetConsole(ConsoleViewModel.Instance); 15 | DoomGame.SetOutputRenderer(MainViewModel.Instance); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Behaviors/KeyboardBehavior.Windows.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Maui.Model; 2 | using Microsoft.UI.Xaml; 3 | using Microsoft.UI.Xaml.Controls; 4 | using Microsoft.UI.Xaml.Media; 5 | using Window = Microsoft.UI.Xaml.Window; 6 | 7 | namespace DoomSharp.Maui.Behaviors; 8 | 9 | public partial class KeyboardBehavior : PlatformBehavior 10 | { 11 | protected override void OnAttachedTo(VisualElement bindable, UIElement platformView) 12 | { 13 | //We need to look for the outermost native Microsoft UI element, 14 | //since that is the only one with keydown and keyup events that don't get swallowed by MAUI 15 | 16 | //Find the first scrollviewer in the visual tree 17 | //We look for scrollviewer because that is a Microsoft UI control. 18 | //We could do grid as well but there are many grids in the visual tree and only a few scrollviewers 19 | DependencyObject window = GetParent(platformView); 20 | var parent = VisualTreeHelper.GetParent(window); 21 | 22 | //Keep looking for parents until we find the outermost control 23 | //This is always a Microsoft UI control, not MAUI 24 | while (parent != null) 25 | { 26 | window = parent; 27 | parent = VisualTreeHelper.GetParent(window); 28 | } 29 | 30 | //cast it to a UIElement so we can get to the key events 31 | var root = window as UIElement; 32 | 33 | if (root == null) 34 | { 35 | return; 36 | } 37 | 38 | //Attach keydown and keyup events 39 | root.KeyDown += (sender, args) => 40 | { 41 | KeyDown.Invoke(this, new KeyDownEventArgs(args.Key.ToString())); 42 | }; 43 | 44 | root.KeyUp += (sender, args) => 45 | { 46 | KeyUp.Invoke(this, new KeyDownEventArgs(args.Key.ToString())); 47 | }; 48 | } 49 | 50 | public static T GetParent(DependencyObject element) 51 | { 52 | if (element is T typedElement) 53 | { 54 | return typedElement; 55 | } 56 | 57 | var parent = VisualTreeHelper.GetParent(element); 58 | if (parent != null) 59 | { 60 | return GetParent(parent); 61 | } 62 | 63 | return default; 64 | } 65 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Behaviors/KeyboardBehavior.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Versioning; 2 | using DoomSharp.Maui.Model; 3 | 4 | namespace DoomSharp.Maui.Behaviors; 5 | 6 | /// 7 | /// that enables hardware keyboard presses 8 | /// 9 | [UnsupportedOSPlatform("Android"), UnsupportedOSPlatform("iOS")] 10 | public partial class KeyboardBehavior 11 | { 12 | public event EventHandler KeyDown; 13 | public event EventHandler KeyUp; 14 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Controls/GameControl.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Maui.Controls 2 | { 3 | internal class GameControl : Image 4 | { 5 | public string Key { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Data/WadStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using DoomSharp.Core; 3 | using DoomSharp.Core.Abstractions; 4 | using DoomSharp.Core.Data; 5 | 6 | namespace DoomSharp.Maui.Data 7 | { 8 | internal sealed class WadStreamProvider : IWadStreamProvider 9 | { 10 | public async Task LoadFromFile(string file) 11 | { 12 | Stream fileStream = await FileSystem.Current.OpenAppPackageFileAsync(file); 13 | BinaryReader reader; 14 | 15 | //some platforms, like Android, can't seek on a stream, so we need to wrap it in a MemoryStream 16 | if (fileStream.CanSeek) 17 | { 18 | reader = new BinaryReader(fileStream, Encoding.ASCII, false); 19 | } 20 | else 21 | { 22 | MemoryStream memoryStream = new(); 23 | await fileStream.CopyToAsync(memoryStream); 24 | memoryStream.Position = 0; 25 | reader = new BinaryReader(memoryStream, Encoding.ASCII, false); 26 | } 27 | 28 | DoomGame.Console.WriteLine($" adding {file}"); 29 | 30 | if (string.Equals(Path.GetExtension(file), ".WAD", StringComparison.OrdinalIgnoreCase)) 31 | { 32 | // WAD file 33 | return LoadWad(file, reader); 34 | } 35 | else 36 | { 37 | // Single lump file 38 | // TODO 39 | } 40 | 41 | return null; 42 | } 43 | 44 | private static WadFile? LoadWad(string file, BinaryReader reader) 45 | { 46 | var header = new WadFile.WadInfo 47 | { 48 | Identification = Encoding.ASCII.GetString(reader.ReadBytes(4)).TrimEnd('\0'), 49 | NumLumps = reader.ReadInt32(), 50 | InfoTableOfs = reader.ReadInt32() 51 | }; 52 | 53 | var wadFile = new WadFile(reader) 54 | { 55 | Header = header 56 | }; 57 | 58 | if (string.Equals(wadFile.Header.Identification, "IWAD", StringComparison.Ordinal) == false) 59 | { 60 | if (string.Equals(wadFile.Header.Identification, "PWAD", StringComparison.Ordinal) == false) 61 | { 62 | DoomGame.Error($"Wad file {file} doesn't have IWAD or PWAD id"); 63 | return null; 64 | } 65 | } 66 | 67 | var fileInfo = new List(wadFile.LumpCount); 68 | reader.BaseStream.Seek(wadFile.Header.InfoTableOfs, SeekOrigin.Begin); 69 | for (var i = 0; i < wadFile.LumpCount; i++) 70 | { 71 | var lump = WadFile.FileLump.ReadFromWadData(reader); 72 | fileInfo.Add(new WadLump(wadFile, lump)); 73 | } 74 | 75 | wadFile.Lumps = fileInfo; 76 | 77 | return wadFile; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/DoomSharp.Maui.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0-android;net8.0-ios;net8.0-maccatalyst 5 | $(TargetFrameworks);net8.0-windows10.0.19041.0 6 | 7 | 8 | Exe 9 | DoomSharp.Maui 10 | true 11 | true 12 | enable 13 | 14 | 15 | DoomSharp.Maui 16 | 17 | 18 | com.companyname.doomsharp.maui 19 | FAF3C376-7099-4574-B557-B8980BF9A3B2 20 | 21 | 22 | 1.0 23 | 1 24 | 25 | 14.2 26 | 14.0 27 | 21.0 28 | 10.0.17763.0 29 | 10.0.17763.0 30 | 6.5 31 | True 32 | enable 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | PreserveNewest 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/MainPage.xaml: -------------------------------------------------------------------------------- 1 |  2 | 9 | 10 | 11 | 12 | 17 | 18 | 26 | 30 | 31 | 36 | 37 | 41 | 42 | 47 | 48 | 49 | 53 | 57 | 58 | 62 | 63 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/MainPage.xaml.cs: -------------------------------------------------------------------------------- 1 | #if ANDROID 2 | using Android.Views; 3 | #endif 4 | using DoomSharp.Core; 5 | using DoomSharp.Core.Input; 6 | using DoomSharp.Maui.Behaviors; 7 | using DoomSharp.Maui.Controls; 8 | using DoomSharp.Maui.Model; 9 | using SkiaSharp; 10 | using SkiaSharp.Views.Maui; 11 | using DoomSharp.Maui.ViewModels; 12 | 13 | 14 | namespace DoomSharp.Maui; 15 | 16 | public partial class MainPage : ContentPage 17 | { 18 | private SKBitmap? _lastOutput; 19 | 20 | public MainPage() 21 | { 22 | InitializeComponent(); 23 | BindingContext = App.Locator.MainViewModel; 24 | } 25 | 26 | protected override void OnAppearing() 27 | { 28 | Microsoft.Maui.Handlers.ImageHandler.Mapper.AppendToMapping("TouchUpDownSupport", (handler, view) => 29 | { 30 | if (view is GameControl control) 31 | { 32 | #if WINDOWS 33 | var behavior = new KeyboardBehavior(); 34 | behavior.KeyUp += KeyboardBehavior_OnKeyUp; 35 | behavior.KeyDown += KeyboardBehavior_OnKeyDown; 36 | 37 | Behaviors.Add(behavior); 38 | 39 | handler.PlatformView.PointerPressed += (_, _) => MainViewModel.Instance.OnKeyAction(control.Key, EventType.KeyDown); 40 | handler.PlatformView.PointerReleased += (_, _) => MainViewModel.Instance.OnKeyAction(control.Key, EventType.KeyUp); 41 | #endif 42 | #if ANDROID 43 | handler.PlatformView.Touch += (sender, args) => 44 | { 45 | switch (args.Event?.ActionMasked) 46 | { 47 | case MotionEventActions.Down: 48 | MainViewModel.Instance.OnKeyAction(control.Key, EventType.KeyDown); 49 | break; 50 | case MotionEventActions.Up: 51 | MainViewModel.Instance.OnKeyAction(control.Key, EventType.KeyUp); 52 | break; 53 | } 54 | }; 55 | 56 | #endif 57 | #if IOS 58 | 59 | //handler.PlatformView.UserInteractionEnabled = true; 60 | //handler.PlatformView.AddGestureRecognizer(new UILongPressGestureRecognizer(HandleLongClick)); 61 | #endif 62 | } 63 | }); 64 | 65 | _ = Task.Run(async () => 66 | { 67 | (GameMode, string) doomVersion = await IdentifyVersion(); 68 | await DoomGame.Instance.RunAsync(doomVersion.Item1, doomVersion.Item2); 69 | }); 70 | 71 | App.Locator.MainViewModel.BitmapRendered += OnBitmapRendered; 72 | } 73 | 74 | private void OnBitmapRendered(object sender, BitmapRenderedEventArgs e) 75 | { 76 | _lastOutput = e.Bitmap; 77 | GameSurface.InvalidateSurface(); 78 | } 79 | 80 | private void SKCanvasView_OnPaintSurface(object sender, SKPaintSurfaceEventArgs e) 81 | { 82 | try 83 | { 84 | if (_lastOutput is null) 85 | { 86 | return; 87 | } 88 | 89 | var info = e.Info; 90 | var surface = e.Surface; 91 | var canvas = surface.Canvas; 92 | canvas.Clear(); 93 | 94 | var scale = Math.Min((float)info.Width / _lastOutput.Width, (float)info.Height / _lastOutput.Height); 95 | var x = (info.Width - scale * _lastOutput.Width) / 2; 96 | var y = (info.Height - scale * _lastOutput.Height) / 2; 97 | var dest = new SKRect(x, y, x + scale * _lastOutput.Width, y + scale * _lastOutput.Height); 98 | 99 | e.Surface.Canvas.DrawBitmap(_lastOutput, dest); 100 | } 101 | finally 102 | { 103 | _lastOutput?.Dispose(); 104 | _lastOutput = null; 105 | } 106 | } 107 | 108 | private async Task<(GameMode, string)> IdentifyVersion() 109 | { 110 | (GameMode, string) version = new(); 111 | 112 | // Shareware 113 | var wadFile = "DOOM1.WAD"; 114 | if (await FileSystem.Current.AppPackageFileExistsAsync(wadFile)) 115 | { 116 | version.Item1 = GameMode.Shareware; 117 | version.Item2 = wadFile; 118 | return version; 119 | } 120 | 121 | // Commercial 122 | wadFile = "DOOM2.WAD"; 123 | if (await FileSystem.Current.AppPackageFileExistsAsync(wadFile)) 124 | { 125 | version.Item1 = GameMode.Commercial; 126 | version.Item2 = wadFile; 127 | return version; 128 | } 129 | 130 | // Retail 131 | wadFile = "DOOMU.WAD"; 132 | if (await FileSystem.Current.AppPackageFileExistsAsync(wadFile)) 133 | { 134 | version.Item1 = GameMode.Retail; 135 | version.Item2 = wadFile; 136 | return version; 137 | } 138 | 139 | // Registered 140 | wadFile = "DOOM.WAD"; 141 | if (await FileSystem.Current.AppPackageFileExistsAsync(wadFile)) 142 | { 143 | version.Item1 = GameMode.Registered; 144 | version.Item2 = wadFile; 145 | return version; 146 | } 147 | 148 | return version; 149 | } 150 | 151 | private void Button_OnClicked(object? sender, EventArgs e) 152 | { 153 | throw new NotImplementedException(); 154 | } 155 | 156 | private void KeyboardBehavior_OnKeyDown(object? sender, KeyDownEventArgs e) 157 | { 158 | MainViewModel.Instance.OnKeyAction(e.Key, EventType.KeyDown); 159 | } 160 | 161 | private void KeyboardBehavior_OnKeyUp(object? sender, KeyDownEventArgs e) 162 | { 163 | MainViewModel.Instance.OnKeyAction(e.Key, EventType.KeyUp); 164 | } 165 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/MauiProgram.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp.Views.Maui.Controls.Hosting; 2 | 3 | namespace DoomSharp.Maui 4 | { 5 | public static class MauiProgram 6 | { 7 | public static MauiApp CreateMauiApp() 8 | { 9 | var builder = MauiApp.CreateBuilder(); 10 | builder 11 | .UseMauiApp() 12 | .UseSkiaSharp() 13 | .ConfigureFonts(fonts => 14 | { 15 | fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); 16 | fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); 17 | }); 18 | 19 | return builder.Build(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Model/Int32Rect.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Maui.Model 2 | { 3 | internal class Int32Rect 4 | { 5 | public Int32Rect(int x, int y, int width, int height) 6 | { 7 | X = x; 8 | Y = y; 9 | Width = width; 10 | Height = height; 11 | } 12 | 13 | public int X { get; set; } 14 | public int Y { get; set; } 15 | public int Width { get; set; } 16 | public int Height { get; set; } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Model/KeyDownEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Maui.Model; 2 | 3 | public sealed class KeyDownEventArgs : EventArgs 4 | { 5 | public string Key { get; } 6 | 7 | public KeyDownEventArgs(string key) 8 | { 9 | Key = key; 10 | } 11 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Android/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Android/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Content.PM; 3 | using Android.OS; 4 | using DoomSharp.Core; 5 | using DoomSharp.Core.Internals; 6 | 7 | namespace DoomSharp.Maui 8 | { 9 | [Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, ScreenOrientation = ScreenOrientation.Landscape, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] 10 | public class MainActivity : MauiAppCompatActivity 11 | { 12 | protected override void OnCreate(Bundle savedInstanceState) 13 | { 14 | Java.Lang.JavaSystem.LoadLibrary("fmod"); 15 | Org.Fmod.FMOD.Init(this); 16 | 17 | DoomGame.SetSoundDriver(new SoundDriver()); 18 | 19 | base.OnCreate(savedInstanceState); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Android/MainApplication.cs: -------------------------------------------------------------------------------- 1 | using Android.App; 2 | using Android.Runtime; 3 | 4 | namespace DoomSharp.Maui 5 | { 6 | [Application] 7 | public class MainApplication : MauiApplication 8 | { 9 | public MainApplication(IntPtr handle, JniHandleOwnership ownership) 10 | : base(handle, ownership) 11 | { 12 | 13 | } 14 | 15 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Android/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #512BD4 4 | #2B0B98 5 | #2B0B98 6 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/MacCatalyst/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace DoomSharp.Maui 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/MacCatalyst/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIDeviceFamily 6 | 7 | 1 8 | 2 9 | 10 | UIRequiredDeviceCapabilities 11 | 12 | arm64 13 | 14 | UISupportedInterfaceOrientations 15 | 16 | UIInterfaceOrientationPortrait 17 | UIInterfaceOrientationLandscapeLeft 18 | UIInterfaceOrientationLandscapeRight 19 | 20 | UISupportedInterfaceOrientations~ipad 21 | 22 | UIInterfaceOrientationPortrait 23 | UIInterfaceOrientationPortraitUpsideDown 24 | UIInterfaceOrientationLandscapeLeft 25 | UIInterfaceOrientationLandscapeRight 26 | 27 | XSAppIconAssets 28 | Assets.xcassets/appicon.appiconset 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/MacCatalyst/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace DoomSharp.Maui 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Tizen/Main.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Maui; 2 | using Microsoft.Maui.Hosting; 3 | using System; 4 | 5 | namespace DoomSharp.Maui 6 | { 7 | internal class Program : MauiApplication 8 | { 9 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 10 | 11 | static void Main(string[] args) 12 | { 13 | var app = new Program(); 14 | app.Run(args); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Tizen/tizen-manifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | maui-appicon-placeholder 7 | 8 | 9 | 10 | 11 | http://tizen.org/privilege/internet 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Windows/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using DoomSharp.Core; 2 | using DoomSharp.Core.Internals; 3 | using Microsoft.Maui.LifecycleEvents; 4 | using Microsoft.UI.Xaml; 5 | using Window = Microsoft.UI.Xaml.Window; 6 | 7 | // To learn more about WinUI, the WinUI project structure, 8 | // and more about our project templates, see: http://aka.ms/winui-project-info. 9 | 10 | namespace DoomSharp.Maui.WinUI 11 | { 12 | /// 13 | /// Provides application-specific behavior to supplement the default Application class. 14 | /// 15 | public partial class App : MauiWinUIApplication 16 | { 17 | public static App Current 18 | { 19 | get; 20 | set; 21 | } 22 | 23 | /// 24 | /// Initializes the singleton application object. This is the first line of authored code 25 | /// executed, and as such is the logical equivalent of main() or WinMain(). 26 | /// 27 | public App() 28 | { 29 | InitializeComponent(); 30 | Current = this; 31 | } 32 | 33 | protected override MauiApp CreateMauiApp() 34 | { 35 | var mauiApp = MauiProgram.CreateMauiApp(); 36 | DoomGame.SetSoundDriver(new SoundDriver()); 37 | 38 | return mauiApp; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Windows/Package.appxmanifest: -------------------------------------------------------------------------------- 1 |  2 | 7 | 8 | 9 | 10 | 11 | $placeholder$ 12 | User Name 13 | $placeholder$.png 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/Windows/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | true/PM 12 | PerMonitorV2, PerMonitor 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | 3 | namespace DoomSharp.Maui 4 | { 5 | [Register("AppDelegate")] 6 | public class AppDelegate : MauiUIApplicationDelegate 7 | { 8 | protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LSRequiresIPhoneOS 6 | 7 | UIDeviceFamily 8 | 9 | 1 10 | 2 11 | 12 | UIRequiredDeviceCapabilities 13 | 14 | arm64 15 | 16 | UISupportedInterfaceOrientations 17 | 18 | UIInterfaceOrientationPortrait 19 | UIInterfaceOrientationLandscapeLeft 20 | UIInterfaceOrientationLandscapeRight 21 | 22 | UISupportedInterfaceOrientations~ipad 23 | 24 | UIInterfaceOrientationPortrait 25 | UIInterfaceOrientationPortraitUpsideDown 26 | UIInterfaceOrientationLandscapeLeft 27 | UIInterfaceOrientationLandscapeRight 28 | 29 | XSAppIconAssets 30 | Assets.xcassets/appicon.appiconset 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Platforms/iOS/Program.cs: -------------------------------------------------------------------------------- 1 | using ObjCRuntime; 2 | using UIKit; 3 | 4 | namespace DoomSharp.Maui 5 | { 6 | public class Program 7 | { 8 | // This is the main entry point of the application. 9 | static void Main(string[] args) 10 | { 11 | // if you want to use a different Application Delegate class from "AppDelegate" 12 | // you can specify it here. 13 | UIApplication.Main(args, null, typeof(AppDelegate)); 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Windows Machine": { 4 | "commandName": "MsixPackage", 5 | "nativeDebugging": false, 6 | "environmentVariables": { 7 | "DOOMWADDIR": "c:\\git\\doom-sharp\\wads\\Shareware" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/AppIcon/appicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/AppIcon/appiconfg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Fonts/OpenSans-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Fonts/OpenSans-Semibold.ttf -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/action.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/down.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/left.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/right.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/start.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/up.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Images/use.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Images/use.png -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Raw/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories). Deployment of the asset to your application 3 | is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. 4 | 5 | 6 | 7 | These files will be deployed with you package and will be accessible using Essentials: 8 | 9 | async Task LoadMauiAsset() 10 | { 11 | using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); 12 | using var reader = new StreamReader(stream); 13 | 14 | var contents = reader.ReadToEnd(); 15 | } 16 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Raw/DOOM1.WAD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/Resources/Raw/DOOM1.WAD -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Splash/splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/Resources/Styles/Colors.xaml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 6 | 7 | #512BD4 8 | #DFD8F7 9 | #2B0B98 10 | White 11 | Black 12 | #E1E1E1 13 | #C8C8C8 14 | #ACACAC 15 | #919191 16 | #6E6E6E 17 | #404040 18 | #212121 19 | #141414 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | #F7B548 35 | #FFD590 36 | #FFE5B9 37 | #28C2D1 38 | #7BDDEF 39 | #C3F2F4 40 | #3E8EED 41 | #72ACF1 42 | #A7CBF6 43 | 44 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/ViewModels/BitmapRenderedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using SkiaSharp; 2 | 3 | namespace DoomSharp.Maui.ViewModels 4 | { 5 | public delegate void BitmapRenderedEventHandler(object? sender, BitmapRenderedEventArgs e); 6 | 7 | public class BitmapRenderedEventArgs : EventArgs 8 | { 9 | public BitmapRenderedEventArgs(SKBitmap bitmap) 10 | { 11 | Bitmap = bitmap; 12 | } 13 | 14 | public SKBitmap Bitmap { get; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/DoomSharp.Maui/ViewModels/ConsoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using DoomSharp.Core; 4 | 5 | namespace DoomSharp.Maui.ViewModels; 6 | 7 | public class ConsoleViewModel : IConsole, INotifyPropertyChanged 8 | { 9 | public static readonly ConsoleViewModel Instance = new(); 10 | 11 | private ConsoleViewModel() {} 12 | 13 | private string _consoleOutput = ""; 14 | private string _title = "DooM# - Console output"; 15 | 16 | public string ConsoleOutput 17 | { 18 | get => _consoleOutput; 19 | set 20 | { 21 | _consoleOutput = value; 22 | OnPropertyChanged(); 23 | } 24 | } 25 | 26 | public string Title 27 | { 28 | get => _title; 29 | set 30 | { 31 | _title = value; 32 | OnPropertyChanged(); 33 | } 34 | } 35 | 36 | public void Write(string message) 37 | { 38 | ConsoleOutput += message; 39 | } 40 | 41 | public void SetTitle(string title) 42 | { 43 | Title = $"{title} - Console output"; 44 | MainViewModel.Instance.Title = title; 45 | } 46 | 47 | public void Shutdown() 48 | { 49 | //TODO 50 | //Application.Current.Dispatcher.Invoke(() => 51 | //{ 52 | // Application.Current.Shutdown(); 53 | //}); 54 | } 55 | 56 | public event PropertyChangedEventHandler? PropertyChanged; 57 | 58 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 59 | { 60 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 61 | } 62 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | using DoomSharp.Core; 4 | using DoomSharp.Core.Graphics; 5 | using DoomSharp.Core.Input; 6 | using DoomSharp.Maui.Model; 7 | using SkiaSharp; 8 | using Constants = DoomSharp.Core.Constants; 9 | 10 | namespace DoomSharp.Maui.ViewModels; 11 | 12 | public class MainViewModel : INotifyPropertyChanged, IGraphics 13 | { 14 | public static readonly MainViewModel Instance = new(); 15 | 16 | private MainViewModel() 17 | { 18 | var stride = (_rectangle.Width * 8 /* bpp */ + 7) / 8; 19 | _screenBuffer = new byte[_rectangle.Height * stride]; 20 | } 21 | 22 | private string _title = "DooM#"; 23 | 24 | private readonly SKColor[] _palette = new SKColor[256]; 25 | 26 | private readonly Int32Rect _rectangle = new(0, 0, Constants.ScreenWidth, Constants.ScreenHeight); 27 | private readonly byte[] _screenBuffer; 28 | private readonly Queue _events = new(); 29 | 30 | public string Title 31 | { 32 | get => _title; 33 | set 34 | { 35 | _title = value; 36 | OnPropertyChanged(); 37 | } 38 | } 39 | 40 | public event PropertyChangedEventHandler? PropertyChanged; 41 | public event BitmapRenderedEventHandler BitmapRendered; 42 | 43 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 44 | { 45 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 46 | } 47 | 48 | public void Initialize() 49 | { 50 | } 51 | 52 | public void UpdatePalette(byte[] palette) 53 | { 54 | for (var i = 0; i < 256; i++) 55 | { 56 | #if ANDROID 57 | _palette[i] = new SKColor(palette[i * 3 + 2], palette[i * 3 + 1], palette[i * 3]); 58 | #else 59 | _palette[i] = new SKColor(palette[i * 3], palette[i * 3 + 1], palette[i * 3 + 2]); 60 | #endif 61 | } 62 | } 63 | 64 | public void ScreenReady(byte[] output) 65 | { 66 | Array.Copy(output, 0, _screenBuffer, 0, output.Length); 67 | 68 | var bitmap = new SKBitmap(Constants.ScreenWidth, Constants.ScreenHeight); 69 | var pixelsPtr = bitmap.GetPixels(); 70 | 71 | unsafe 72 | { 73 | uint* ptr = (uint*)pixelsPtr.ToPointer(); 74 | for (var x = 0; x < Constants.ScreenWidth; x++) 75 | { 76 | for (var y = 0; y < Constants.ScreenHeight; y++) 77 | { 78 | var pixelIdx = x * Constants.ScreenHeight + y; 79 | *ptr++ = (uint)_palette[_screenBuffer[pixelIdx]]; 80 | } 81 | } 82 | } 83 | 84 | App.Current.Dispatcher.Dispatch(() => 85 | { 86 | BitmapRendered?.Invoke(this, new BitmapRenderedEventArgs(bitmap)); 87 | }); 88 | } 89 | 90 | public void StartTic() 91 | { 92 | while (_events.TryDequeue(out var ev)) // has events 93 | { 94 | DoomGame.Instance.PostEvent(ev); 95 | } 96 | } 97 | 98 | public void AddEvent(InputEvent ev) 99 | { 100 | _events.Enqueue(ev); 101 | } 102 | 103 | public void OnKeyAction(string key, EventType actionType) 104 | { 105 | int translatedKey = TranslateKey(key); 106 | AddEvent(new InputEvent(actionType, translatedKey, 0, 0)); 107 | } 108 | 109 | private int TranslateKey(string keyIndex) 110 | { 111 | Key key = Enum.Parse(keyIndex); 112 | 113 | switch (key) 114 | { 115 | case Key.Left: 116 | return (int)Keys.LeftArrow; 117 | case Key.Right: 118 | return (int)Keys.RightArrow; 119 | case Key.Down: 120 | return (int)Keys.DownArrow; 121 | case Key.Up: 122 | return (int)Keys.UpArrow; 123 | case Key.Escape: 124 | return (int)Keys.Escape; 125 | case Key.Enter: 126 | return (int)Keys.Enter; 127 | case Key.Tab: 128 | return (int)Keys.Tab; 129 | 130 | case Key.F1: 131 | return (int)Keys.F1; 132 | case Key.F2: 133 | return (int)Keys.F2; 134 | case Key.F3: 135 | return (int)Keys.F3; 136 | case Key.F4: 137 | return (int)Keys.F4; 138 | case Key.F5: 139 | return (int)Keys.F5; 140 | case Key.F6: 141 | return (int)Keys.F6; 142 | case Key.F7: 143 | return (int)Keys.F7; 144 | case Key.F8: 145 | return (int)Keys.F8; 146 | case Key.F9: 147 | return (int)Keys.F9; 148 | case Key.F10: 149 | return (int)Keys.F10; 150 | case Key.F11: 151 | return (int)Keys.F11; 152 | case Key.F12: 153 | return (int)Keys.F12; 154 | 155 | case Key.Back: 156 | case Key.Delete: 157 | return (int)Keys.Backspace; 158 | 159 | case Key.Pause: 160 | return (int)Keys.Pause; 161 | 162 | case Key.OemPlus: 163 | return (int)Keys.Equals; 164 | 165 | case Key.OemMinus: 166 | return (int)Keys.Minus; 167 | 168 | case Key.LeftShift: 169 | case Key.RightShift: 170 | return (int)Keys.RShift; 171 | 172 | case Key.Control: 173 | case Key.LeftCtrl: 174 | case Key.RightCtrl: 175 | return (int)Keys.RCtrl; 176 | 177 | case Key.RightAlt: 178 | case Key.Menu: 179 | case Key.LeftAlt: 180 | return (int)Keys.RAlt; 181 | 182 | //case >= Key.D0 and <= Key.D9: 183 | // return '0' + ((int)keyIndex - (int)Key.D0); 184 | 185 | //case >= Key.NumPad0 and <= Key.NumPad9: 186 | // return '0' + (keyIndex - (int)Key.NumPad0); 187 | 188 | case >= Key.A and <= Key.Z: 189 | return 'A' + (int.Parse(keyIndex) - (int)Key.A); 190 | 191 | case Key.Space: 192 | return ' '; 193 | 194 | default: 195 | return 0; 196 | } 197 | } 198 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/ViewModels/ViewModelLocator.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Maui.ViewModels; 2 | 3 | public class ViewModelLocator 4 | { 5 | public ConsoleViewModel ConsoleViewModel => ConsoleViewModel.Instance; 6 | public MainViewModel MainViewModel => MainViewModel.Instance; 7 | } -------------------------------------------------------------------------------- /src/DoomSharp.Maui/fmod.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Maui/fmod.dll -------------------------------------------------------------------------------- /src/DoomSharp.Windows/App.xaml: -------------------------------------------------------------------------------- 1 |  7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using DoomSharp.Core; 3 | using DoomSharp.Core.Data; 4 | using DoomSharp.Core.Internals; 5 | using DoomSharp.Windows.Data; 6 | using DoomSharp.Windows.ViewModels; 7 | 8 | namespace DoomSharp.Windows 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | private readonly SoundDriver _soundDriver = new(); 16 | 17 | public App() 18 | { 19 | Exit += App_Exit; 20 | 21 | DoomGame.SetConsole(ConsoleViewModel.Instance); 22 | DoomGame.SetOutputRenderer(MainViewModel.Instance); 23 | DoomGame.SetSoundDriver(_soundDriver); 24 | WadFileCollection.Init(new WadStreamProvider()); 25 | } 26 | 27 | private void App_Exit(object sender, ExitEventArgs e) 28 | { 29 | _soundDriver.Dispose(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | [assembly: ThemeInfo( 4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 5 | //(used if a resource is not found in the page, 6 | // or application resource dictionaries) 7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 8 | //(used if a resource is not found in the page, 9 | // app, or any theme specific resource dictionaries) 10 | )] 11 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/ConsoleOutput.xaml: -------------------------------------------------------------------------------- 1 |  13 | 14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/ConsoleOutput.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | 4 | namespace DoomSharp.Windows 5 | { 6 | /// 7 | /// Interaction logic for ConsoleOutput.xaml 8 | /// 9 | public partial class ConsoleOutput : Window 10 | { 11 | public ConsoleOutput() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private void OnConsoleOutputChanged(object sender, TextChangedEventArgs e) 17 | { 18 | var box = e.Source as TextBox; 19 | box!.ScrollToEnd(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/Data/WadStreamProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using DoomSharp.Core; 7 | using DoomSharp.Core.Abstractions; 8 | using DoomSharp.Core.Data; 9 | 10 | namespace DoomSharp.Windows.Data 11 | { 12 | internal sealed class WadStreamProvider : IWadStreamProvider 13 | { 14 | public Task LoadFromFile(string file) 15 | { 16 | var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None); 17 | var br = new BinaryReader(fs, Encoding.ASCII, false); 18 | 19 | DoomGame.Console.WriteLine($" adding {file}"); 20 | 21 | if (string.Equals(Path.GetExtension(file), ".wad", StringComparison.OrdinalIgnoreCase)) 22 | { 23 | // WAD file 24 | return Task.FromResult(LoadWad(file, br)); 25 | } 26 | else 27 | { 28 | // Single lump file 29 | // TODO 30 | } 31 | 32 | return Task.FromResult(null); 33 | } 34 | 35 | private static WadFile? LoadWad(string file, BinaryReader reader) 36 | { 37 | var header = new WadFile.WadInfo 38 | { 39 | Identification = Encoding.ASCII.GetString(reader.ReadBytes(4)).TrimEnd('\0'), 40 | NumLumps = reader.ReadInt32(), 41 | InfoTableOfs = reader.ReadInt32() 42 | }; 43 | 44 | var wadFile = new WadFile(reader) 45 | { 46 | Header = header 47 | }; 48 | 49 | if (string.Equals(wadFile.Header.Identification, "IWAD", StringComparison.Ordinal) == false) 50 | { 51 | if (string.Equals(wadFile.Header.Identification, "PWAD", StringComparison.Ordinal) == false) 52 | { 53 | DoomGame.Error($"Wad file {file} doesn't have IWAD or PWAD id"); 54 | return null; 55 | } 56 | } 57 | 58 | var fileInfo = new List(wadFile.LumpCount); 59 | 60 | reader.BaseStream.Seek(wadFile.Header.InfoTableOfs, SeekOrigin.Begin); 61 | for (var i = 0; i < wadFile.LumpCount; i++) 62 | { 63 | var lump = WadFile.FileLump.ReadFromWadData(reader); 64 | fileInfo.Add(new WadLump(wadFile, lump)); 65 | } 66 | 67 | wadFile.Lumps = fileInfo; 68 | 69 | return wadFile; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/DoomSharp.Windows.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WinExe 5 | net8.0-windows 6 | enable 7 | true 8 | x64 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/DoomSharp.Windows/ViewModels/ConsoleViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Diagnostics; 3 | using System.Runtime.CompilerServices; 4 | using System.Threading.Tasks; 5 | using System.Windows; 6 | using DoomSharp.Core; 7 | using DoomSharp.Windows.Annotations; 8 | 9 | namespace DoomSharp.Windows.ViewModels; 10 | 11 | public class ConsoleViewModel : IConsole, INotifyPropertyChanged 12 | { 13 | public static readonly ConsoleViewModel Instance = new(); 14 | 15 | private ConsoleViewModel() {} 16 | 17 | private string _consoleOutput = ""; 18 | private string _title = "DooM# - Console output"; 19 | 20 | public string ConsoleOutput 21 | { 22 | get => _consoleOutput; 23 | set 24 | { 25 | _consoleOutput = value; 26 | OnPropertyChanged(); 27 | } 28 | } 29 | 30 | public string Title 31 | { 32 | get => _title; 33 | set 34 | { 35 | _title = value; 36 | OnPropertyChanged(); 37 | } 38 | } 39 | 40 | public void Write(string message) 41 | { 42 | #if DEBUG 43 | if (Debugger.IsAttached) 44 | { 45 | Debug.WriteLine(message.TrimEnd('\r', '\n')); 46 | } 47 | #endif 48 | ConsoleOutput += message; 49 | } 50 | 51 | public void SetTitle(string title) 52 | { 53 | Title = $"{title} - Console output"; 54 | MainViewModel.Instance.Title = title; 55 | } 56 | 57 | public void Shutdown() 58 | { 59 | try 60 | { 61 | Application.Current?.Dispatcher?.Invoke(() => 62 | { 63 | Application.Current?.Shutdown(); 64 | }); 65 | } 66 | catch (TaskCanceledException) { } 67 | } 68 | 69 | public event PropertyChangedEventHandler? PropertyChanged; 70 | 71 | [NotifyPropertyChangedInvocator] 72 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 73 | { 74 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 75 | } 76 | } -------------------------------------------------------------------------------- /src/DoomSharp.Windows/ViewModels/MainViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Runtime.CompilerServices; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Media; 8 | using System.Windows.Media.Imaging; 9 | using DoomSharp.Core; 10 | using DoomSharp.Core.Graphics; 11 | using DoomSharp.Core.Input; 12 | using DoomSharp.Windows.Annotations; 13 | using Constants = DoomSharp.Core.Constants; 14 | 15 | namespace DoomSharp.Windows.ViewModels; 16 | 17 | public class MainViewModel : INotifyPropertyChanged, IGraphics 18 | { 19 | public static readonly MainViewModel Instance = new(); 20 | 21 | private MainViewModel() 22 | { 23 | _stride = (_rectangle.Width * 8 /* bpp */ + 7) / 8; 24 | _screenBuffer = new byte[_rectangle.Height * _stride]; 25 | } 26 | 27 | private string _title = "DooM#"; 28 | 29 | private WriteableBitmap? _output; 30 | private WriteableBitmap? _newPaletteOutput; 31 | 32 | private readonly Int32Rect _rectangle = new(0, 0, Constants.ScreenWidth, Constants.ScreenHeight); 33 | private readonly int _stride; 34 | private readonly byte[] _screenBuffer; 35 | private readonly Queue _events = new(); 36 | 37 | public string Title 38 | { 39 | get => _title; 40 | set 41 | { 42 | _title = value; 43 | OnPropertyChanged(); 44 | } 45 | } 46 | 47 | public WriteableBitmap? Output 48 | { 49 | get => _output; 50 | set 51 | { 52 | if (value is null) 53 | { 54 | return; 55 | } 56 | 57 | _output = value; 58 | OnPropertyChanged(); 59 | } 60 | } 61 | 62 | public event PropertyChangedEventHandler? PropertyChanged; 63 | 64 | [NotifyPropertyChangedInvocator] 65 | protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) 66 | { 67 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 68 | } 69 | 70 | public void Initialize() 71 | { 72 | 73 | } 74 | 75 | public void UpdatePalette(byte[] palette) 76 | { 77 | var colors = new List(256); 78 | for (var i = 0; i < 256*3; i += 3) 79 | { 80 | colors.Add(Color.FromRgb(palette[i], palette[i + 1], palette[i + 2])); 81 | } 82 | 83 | Application.Current?.Dispatcher?.Invoke(() => 84 | { 85 | var bitmapPalette = new BitmapPalette(colors); 86 | _newPaletteOutput = new WriteableBitmap(Constants.ScreenWidth, Constants.ScreenHeight, 96, 96, PixelFormats.Indexed8, bitmapPalette); 87 | }); 88 | } 89 | 90 | public void ScreenReady(byte[] output) 91 | { 92 | Array.Copy(output, 0, _screenBuffer, 0, output.Length); 93 | 94 | var bitmap = _output; 95 | var switchOutput = false; 96 | if (_newPaletteOutput is not null) 97 | { 98 | bitmap = _newPaletteOutput; 99 | _newPaletteOutput = null; 100 | switchOutput = true; 101 | } 102 | 103 | if (bitmap is null) 104 | { 105 | return; 106 | } 107 | 108 | try 109 | { 110 | Application.Current?.Dispatcher?.Invoke(() => 111 | { 112 | bitmap.WritePixels(_rectangle, _screenBuffer, _stride, 0); 113 | if (switchOutput) 114 | { 115 | Output = bitmap; 116 | } 117 | }); 118 | } 119 | catch (TaskCanceledException) {} 120 | } 121 | 122 | public void StartTic() 123 | { 124 | while (_events.TryDequeue(out var ev)) // has events 125 | { 126 | DoomGame.Instance.PostEvent(ev); 127 | } 128 | } 129 | 130 | public void AddEvent(InputEvent ev) 131 | { 132 | _events.Enqueue(ev); 133 | } 134 | } -------------------------------------------------------------------------------- /src/DoomSharp.Windows/ViewModels/ViewModelLocator.cs: -------------------------------------------------------------------------------- 1 | namespace DoomSharp.Windows.ViewModels; 2 | 3 | public class ViewModelLocator 4 | { 5 | public ConsoleViewModel ConsoleViewModel => ConsoleViewModel.Instance; 6 | public MainViewModel MainViewModel => MainViewModel.Instance; 7 | } -------------------------------------------------------------------------------- /src/DoomSharp.Windows/fmod.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wcabus/doom-sharp/45e940eaa6350d1d6db2b2b161a1eae9be22ee3f/src/DoomSharp.Windows/fmod.dll -------------------------------------------------------------------------------- /src/DoomSharp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32630.192 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DoomSharp.Core", "DoomSharp.Core\DoomSharp.Core.csproj", "{24B4556C-2C14-4356-AF07-7E396546A008}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DoomSharp.Windows", "DoomSharp.Windows\DoomSharp.Windows.csproj", "{12AD853C-AA4E-4C39-BBC2-4ACCDEE147C7}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{59C39BE7-05AF-4BE7-8A3B-6077BD17E405}" 11 | ProjectSection(SolutionItems) = preProject 12 | ..\.github\workflows\build.yml = ..\.github\workflows\build.yml 13 | ..\.github\workflows\codeql.yml = ..\.github\workflows\codeql.yml 14 | ..\README.md = ..\README.md 15 | EndProjectSection 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DoomSharp.Maui", "DoomSharp.Maui\DoomSharp.Maui.csproj", "{23FF2559-7A76-4AE0-BBBB-88C23198CB00}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DoomSharp.Droid.Bindings", "DoomSharp.Droid.Bindings\DoomSharp.Droid.Bindings.csproj", "{5914DBCB-30BB-48F3-933C-523263290EA0}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomSharp.AnsiConsole", "DoomSharp.AnsiConsole\DoomSharp.AnsiConsole.csproj", "{905BF476-E5D5-417B-891A-E33A497C7021}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {24B4556C-2C14-4356-AF07-7E396546A008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {24B4556C-2C14-4356-AF07-7E396546A008}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {24B4556C-2C14-4356-AF07-7E396546A008}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {24B4556C-2C14-4356-AF07-7E396546A008}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {12AD853C-AA4E-4C39-BBC2-4ACCDEE147C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {12AD853C-AA4E-4C39-BBC2-4ACCDEE147C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {12AD853C-AA4E-4C39-BBC2-4ACCDEE147C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {12AD853C-AA4E-4C39-BBC2-4ACCDEE147C7}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {23FF2559-7A76-4AE0-BBBB-88C23198CB00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {23FF2559-7A76-4AE0-BBBB-88C23198CB00}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {23FF2559-7A76-4AE0-BBBB-88C23198CB00}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {23FF2559-7A76-4AE0-BBBB-88C23198CB00}.Release|Any CPU.Deploy.0 = Release|Any CPU 41 | {5914DBCB-30BB-48F3-933C-523263290EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {5914DBCB-30BB-48F3-933C-523263290EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {5914DBCB-30BB-48F3-933C-523263290EA0}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {905BF476-E5D5-417B-891A-E33A497C7021}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {905BF476-E5D5-417B-891A-E33A497C7021}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {905BF476-E5D5-417B-891A-E33A497C7021}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {905BF476-E5D5-417B-891A-E33A497C7021}.Release|Any CPU.Build.0 = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(SolutionProperties) = preSolution 50 | HideSolutionNode = FALSE 51 | EndGlobalSection 52 | GlobalSection(ExtensibilityGlobals) = postSolution 53 | SolutionGuid = {B84D3873-73DD-4361-B0DD-9339259760F8} 54 | EndGlobalSection 55 | EndGlobal 56 | -------------------------------------------------------------------------------- /src/DoomSharp.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True -------------------------------------------------------------------------------- /src/DoomSharp.sln.startup.json: -------------------------------------------------------------------------------- 1 | /* 2 | This is a configuration file for the SwitchStartupProject Visual Studio Extension 3 | See https://heptapod.host/thirteen/switchstartupproject/blob/branch/current/Configuration.md 4 | */ 5 | { 6 | /* Configuration File Version */ 7 | "Version": 3, 8 | 9 | /* Create an item in the dropdown list for each project in the solution? */ 10 | "ListAllProjects": true, 11 | 12 | /* 13 | Dictionary of named configurations with one or multiple startup projects 14 | and optional parameters like command line arguments and working directory. 15 | Example: 16 | 17 | "MultiProjectConfigurations": { 18 | "A + B (Ext)": { 19 | "Projects": { 20 | "MyProjectA": {}, 21 | "MyProjectB": { 22 | "CommandLineArguments": "1234", 23 | "WorkingDirectory": "%USERPROFILE%\\test", 24 | "StartExternalProgram": "c:\\myprogram.exe" 25 | } 26 | } 27 | }, 28 | "A + B": { 29 | "Projects": { 30 | "MyProjectA": {}, 31 | "MyProjectB": { 32 | "CommandLineArguments": "", 33 | "WorkingDirectory": "", 34 | "StartProject": true 35 | } 36 | } 37 | }, 38 | "D (Debug x86)": { 39 | "Projects": { 40 | "MyProjectD": {} 41 | }, 42 | "SolutionConfiguration": "Debug", 43 | "SolutionPlatform": "x86", 44 | }, 45 | "D (Release x64)": { 46 | "Projects": { 47 | "MyProjectD": {} 48 | }, 49 | "SolutionConfiguration": "Release", 50 | "SolutionPlatform": "x64", 51 | } 52 | } 53 | */ 54 | "MultiProjectConfigurations": {} 55 | } 56 | --------------------------------------------------------------------------------